root/sys/arch/i386/stand/cdboot/cdboot.S
/*      $NetBSD: cdboot.S,v 1.13 2021/12/05 02:47:01 msaitoh Exp $      */

/*-
 * Copyright (c) 2005 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Bang Jun-Young.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This is a primary boot loader that loads a secondary boot loader
 * directly from CD without performing floppy/hard disk emulation as
 * described by the El Torito specification.
 */

#include <machine/asm.h>
#include <sys/bootblock.h>

#define BOOT_ADDR       0x7c00
#define BLOCK_SIZE      2048            /* Default for ISO 9660 */
#define VD_LBA          16              /* LBA of Volume Descriptor (VD) */
#define PVD_ADDR        end             /* Where Primary VD is loaded */
#define ROOTDIR_ADDR    end+BLOCK_SIZE  /* Where Root Directory is loaded */
#define LOADER_ADDR     SECONDARY_LOAD_ADDRESS

#ifdef BOOT_FROM_FAT
#define MBR_AFTERBPB    90              /* BPB size in FAT32 partition BR */
#else
#define MBR_AFTERBPB    62              /* BPB size in floppy master BR */
#endif

/*
 * See src/sys/sys/bootblock.h for details.
 */
#define MBR_PART_COUNT  4
#define MBR_PART_OFFSET 446
#define MBR_PART_SIZE   16              /* sizeof(struct mbr_partition) */

/*
 * Disk error codes
 */
#define ERROR_TIMEOUT   0x80

/*
 * Volume Descriptor types.
 */
#define VD_PRIMARY              1
#define VD_SUPPLEMENTARY        2
#define VD_TERMINATOR           255

/* Only actually used entries are listed below */

/*
 * Format of Primary Volume Descriptor (8.4)
 */
#define PVD_ROOT_DR     156     /* Offset of Root Directory Record */

/*
 * Format of Directory Record (9.1)
 */
#define DR_LEN          0
#define DR_EXTENT       2
#define DR_DATA_LEN     10
#define DR_NAME_LEN     32
#define DR_NAME         33

        .text
        .code16
ENTRY(start)
        jmp     start1

        . = start + MBR_AFTERBPB        /* skip BPB */
        . = start + MBR_DSN_OFFSET
        .long   0

/* mbr_bootsel_magic (not used here) */
        . = start + MBR_BS_MAGIC_OFFSET
        .word   0

        . = start + MBR_PART_OFFSET
        . = start + MBR_MAGIC_OFFSET
pbr_magic:
        .word   MBR_MAGIC
        .fill   512                     /* reserve space for disklabel */
start1:
        jmp     1f
        .balign 4
        .long   X86_BOOT_MAGIC_1        /* checked by installboot & pbr code */
boot_params:                            /* space for patchable variables */
        .long   1f - boot_params        /* length of this data area */
#include <boot_params.S>
        . = start1 + 0x80               /* Space for patching unknown params */

1:      xorw    %ax, %ax
        movw    %ax, %ds
        movw    %ax, %es
        movw    %ax, %ss
        movw    $BOOT_ADDR, %sp
        movw    %sp, %si
        movw    $start, %di
        movw    $BLOCK_SIZE/2, %cx
        rep
        movsw
        ljmp    $0, $real_start

real_start:
        movb    %dl, boot_drive         /* Save boot drive number */

#ifndef DISABLE_KEYPRESS
        /*
         * We can skip boot wait when:
         *  - there's no hard disk present.
         *  - there's no active partition in the MBR of the 1st hard disk.
         */

        /*
         * Check presence of hard disks.
         */
        movw    $0x475, %si
        movb    (%si), %al
        testb   %al, %al
        jz      boot_cdrom

        /*
         * Find the active partition from the MBR.
         */
        movw    $0x0201, %ax            /* %al = number of sectors to read */
        movw    $BOOT_ADDR, %bx         /* %es:%bx = data buffer */
        movw    $0x0001, %cx            /* %ch = low 8 bits of cylinder no */
                                        /* %cl = high 2 bits of cyl no & */
                                        /*       sector number */
        movw    $0x0080, %dx            /* %dh = head number */
                                        /* %dl = disk number */
        int     $0x13                   /* Read MBR into memory */
        jc      boot_cdrom              /* CF set on error */

        movb    $1, mbr_loaded
        movb    $MBR_PART_COUNT, %cl
        movw    $BOOT_ADDR+MBR_PART_OFFSET, %si
1:
        movb    (%si), %al
        testb   $0x80, %al
        jnz     found_active
        addw    $MBR_PART_SIZE, %si
        decb    %cl
        testb   %cl, %cl
        jnz     1b                      /* If 0, no active partition found */
        jmp     boot_cdrom

found_active:
        movw    $str_press_key, %si
        call    message
next_second:
        movw    $str_dot, %si
        call    message
        decb    wait_count
        jz      boot_hard_disk
        xorb    %ah, %ah                /* Get system time */
        int     $0x1a
        movw    %dx, %di                /* %cx:%dx = number of clock ticks */
        addw    $19, %di                /* 19 ~= 18.2 Hz */
wait_key:
        movb    $1, %ah                 /* Check for keystroke */
        int     $0x16
        jz      not_avail               /* ZF clear if keystroke available */
        xorb    %ah, %ah                /* Read key to flush keyboard buf */
        int     $0x16
        jmp     boot_cdrom
not_avail:
        xorb    %ah, %ah                /* Get system time */
        int     $0x1a
        cmpw    %dx, %di                /* Compare with saved time */
        jnz     wait_key
        jmp     next_second

boot_hard_disk:
        movw    $str_crlf, %si
        call    message
        cmpb    $1, mbr_loaded
        jz      1f
        movw    $0x0201, %ax            /* %al = number of sectors to read */
        movw    $BOOT_ADDR, %bx         /* %es:%bx = data buffer */
        movw    $0x0001, %cx            /* %ch = low 8 bits of cylinder no */
                                        /* %cl = high 2 bits of cyl no & */
                                        /*       sector number */
        movw    $0x0080, %dx            /* %dh = head number */
                                        /* %dl = disk number */
        int     $0x13                   /* Read MBR into memory */
        jc      panic                   /* CF set on error */
1:
        movw    %cs, %ax                /* Restore initial state */
        movw    %ax, %ds
        movw    %ax, %es
        movw    $0x0080, %dx            /* %dl = boot drive number */
        jmp     $0, $BOOT_ADDR          /* Jump to MBR! */
        jmp     panic                   /* This should be never executed */
#endif /* !DISABLE_KEYPRESS */

boot_cdrom:
        movw    $str_banner, %si
        call    message

/* Read volume descriptor sectors until Primary descriptor found */
        movl    $VD_LBA, %eax
next_block:
        movb    $1, %dh                 /* Number of sectors to read */
        movl    $PVD_ADDR, %ebx
        call    read_sectors
        cmpb    $VD_PRIMARY, (%bx)      /* Is it Primary Volume Descriptor? */
        jz      pvd_found
        incl    %eax
        cmpb    $VD_TERMINATOR, (%bx)
        jnz     next_block
        movw    $str_no_pvd, %si
        call    message
        jmp     panic

/* Read all of root directory */
pvd_found:
        movw    $PVD_ADDR+PVD_ROOT_DR, %bx
        movl    DR_EXTENT(%bx), %eax    /* LBA of the root directory */
        movl    DR_DATA_LEN(%bx), %edx
        shrl    $11, %edx               /* Convert to number of sectors */
        movb    %dl, %dh                /*  ... and load it to %dh */
        movl    $ROOTDIR_ADDR, %ebx
        call    read_sectors

/* Scan directory entries searching for /boot */
next_entry:
        cmpb    $0, DR_LEN(%bx)
        jz      last_entry
        movw    %bx, %si
        addw    $DR_NAME, %si
        movb    DR_NAME_LEN(%bx), %cl
        movw    $str_loader, %di
1:
        movb    (%si), %al
        cmpb    %al, (%di)
        jnz     fail
        incw    %si
        incw    %di
        decb    %cl
        jnz     1b
        jmp     load_loader
fail:
        addw    DR_LEN(%bx), %bx
        jmp     next_entry
last_entry:
        movw    $str_no_loader, %si
        call    message
        jmp     panic

/* Found /boot, read contents to 0x1000:0 */
load_loader:
        movl    DR_EXTENT(%bx), %eax
        movl    DR_DATA_LEN(%bx), %edx
        addl    $(BLOCK_SIZE-1), %edx   /* Convert file length to */
        shrl    $11, %edx               /*  ... number of sectors */
        movb    %dl, %dh
        movl    $LOADER_ADDR, %ebx
        call    read_sectors

/* Finally call into code of /boot */
        movl    $boot_params, %esi      /* Provide boot_params */
        xorl    %edx, %edx
        movb    boot_drive, %dl
        xorl    %ebx, %ebx              /* Zero sector number */
        lcall   $LOADER_ADDR/16, $0
        /* fall through on load failure */
panic:
        hlt
        jmp     panic

/*
 * Read disk sector(s) into memory
 *
 * %eax = LBA of starting sector
 * %ebx = buffer to store sectors
 * %dh = number of sectors to read
 *
 * Long transfers are split onto multiple 64k reads
 */
#define MAX_SECTORS (0x10000/BLOCK_SIZE)
read_sectors:
        pushal
        movl    %eax, edd_lba
        shrl    $4, %ebx                /* Convert buffer addr to seg:0 */
        movw    %bx, edd_segment
1:      movb    %dh, edd_nsecs
        cmpb    $MAX_SECTORS, %dh
        jle     2f                      /* j if less than 64k */
        movb    $MAX_SECTORS, edd_nsecs /* Read 32 sectors - 64k bytes */
2:      movb    boot_drive, %dl
        movw    $edd_packet, %si
read_again:
        movb    $0x42, %ah
        push    %dx                     /* bios shouldn't kill %dh, but ... */
        int     $0x13
        pop     %dx                     /* ... better safe than sorry! */
        jc      read_fail
        addw    $0x1000, edd_segment    /* Advance segment addr by 64k bytes */
        addl    $MAX_SECTORS, edd_lba   /* And sector number to match */
        sub     edd_nsecs, %dh          /* Number of sectors remaining */
        jnz     1b                      
        popal
        ret

read_fail:
        cmpb    $ERROR_TIMEOUT, %ah
        jz      read_again
        movw    $str_read_error, %si
        call    message
        jmp     panic

#include <message.S>

edd_packet:
edd_len:        .word   16
edd_nsecs:      .word   0               /* Number of sectors to transfer */
edd_offset:     .word   0
edd_segment:    .word   0
edd_lba:        .quad   0

wait_count:     .byte   6
boot_drive:     .byte   0
mbr_loaded:     .byte   0

str_banner:     .ascii  "\r\nNetBSD/x86 cd9660 Primary Bootstrap"
str_crlf:       .asciz  "\r\n"
str_press_key:  .asciz  "\r\nPress any key to boot from CD"
str_dot:        .asciz  "."
str_read_error: .asciz  "Can't read CD"
str_no_pvd:     .asciz  "Can't find Primary Volume Descriptor"
str_no_loader:  .asciz  "Can't find /boot"
str_loader:     .asciz  "BOOT.;1"

/* Used to calculate free bytes */
free_space = end - .

        . = start + BLOCK_SIZE
end: