/* $NetBSD: pbr.S,v 1.24 2023/12/08 16:29:04 tsutsui Exp $ */ /*- * Copyright (c) 2003,2004 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by David Laight. * * 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. */ /* * i386 partition boot code * * This code resides in sector zero of the netbsd partition, or sector * zero of an unpartitioned disk (eg a floppy). * Sector 1 is assumed to contain the netbsd disklabel. * Sectors 2 until the end of the track contain the next phase of bootstrap. * Which know how to read the interactive 'boot' program from filestore. * The job of this code is to read in the phase 1 bootstrap. * * Makefile supplies: * PRIMARY_LOAD_ADDRESS: Address we load code to (0x1000). * BOOTXX_SECTORS: Number of sectors we load (15). * X86_BOOT_MAGIC_1: A random magic number. * * Although this code is executing at 0x7c00, it is linked to address 0x1000. * All data references MUST be fixed up using R(). */ #include #include #define OURADDR 0x7c00 /* our address */ #define BOOTADDR PRIMARY_LOAD_ADDRESS #define R(a) (a - BOOTADDR + OURADDR) #define lba_info R(_lba_info) #define lba_sector R(_lba_sector) #define errtxt R(_errtxt) #define errcod R(_errcod) #define newline R(_newline) #define TABENTRYSIZE (MBR_BS_PARTNAMESIZE + 1) #define NAMETABSIZE (4 * TABENTRYSIZE) #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 #define GPT_MAGIC 0x54504721 /* '!GPT' magic on hybrid MBR boot */ #define GPT_ENTRY_OFF 20 /* GPT part entry in handover struct */ #define GPT_ENT_LBA_OFF 32 /* ent_lba_start in struct gpt_ent */ #ifdef TERSE_ERROR /* * Error codes. Done this way to save space. */ #define ERR_READ '2' /* Read error */ #define ERR_NO_BOOTXX 'B' /* No bootxx_xfs in 3rd sector */ #define ERR_PTN 'P' /* partition not defined */ #define ERR_NO_LBA 'L' /* sector above chs limit */ #define set_err(err) movb $err, %al #else #define set_err(err) mov $R(err), %ax #endif /* * This code is loaded to address 0:7c00 by either the system BIOS * (for a floppy) or the mbr boot code. Since the boot program will * be loaded to address 1000:0, we don't need to relocate ourselves * and can load the subsequent blocks (that load boot) to an address * of our choosing. 0:1000 is a not unreasonable choice. * * On entry the BIOS drive number is in %dl and %esi may contain the * sector we were loaded from (if we were loaded by NetBSD mbr code). * In any case we have to re-read sector zero of the disk and hunt * through the BIOS partition table for the NetBSD partition. * * Or, we may have been loaded by a GPT hybrid MBR, handoff state is * specified in T13 EDD-4 annex A. */ .text .code16 ENTRY(start) /* * The PC BIOS architecture defines a Boot Parameter Block (BPB) here. * The actual format varies between different MS-DOS versions, but * apparently some system BIOS insist on patching this area * (especially on LS120 drives - which I thought had an MBR...). * The initial jmp and nop are part of the standard and may be * tested for by the system BIOS. */ jmp start0 nop .ascii "NetBSD60" /* oemname (8 bytes) */ . = start + MBR_BPB_OFFSET /* move to start of BPB */ /* (ensures oemname doesn't overflow) */ . = start + MBR_AFTERBPB /* skip BPB */ start0: xor %cx, %cx /* don't trust values of ds, es or ss */ mov %cx, %ss mov %cx, %sp mov %cx, %es #ifndef BOOT_FROM_FAT cmpl $GPT_MAGIC, %eax /* did a GPT hybrid MBR start us? */ je boot_gpt #endif mov %cx, %ds xor %ax, %ax /* A 'reset disk system' request is traditional here... */ push %dx /* some BIOS zap %dl here :-( */ int $0x13 /* ah == 0 from code above */ pop %dx /* Read from start of disk */ incw %cx /* track zero sector 1 */ movb %ch, %dh /* dh = head = 0 */ call chs_read /* See if this is our code, if so we have already loaded the next stage */ xorl %ebp, %ebp /* pass sector 0 to next stage */ movl (%bx), %eax /* MBR code shouldn't even have ... */ cmpl R(start), %eax /* ... a jmp at the start. */ je pbr_read_ok1 /* Now scan the MBR partition table for a netbsd partition */ xorl %ebx, %ebx /* for base extended ptn chain */ scan_ptn_tbl: xorl %ecx, %ecx /* for next extended ptn */ movw $BOOTADDR + MBR_PART_OFFSET, %di 1: movb 4(%di), %al /* mbrp_type */ movl 8(%di), %ebp /* mbrp_start == LBA sector */ addl lba_sector, %ebp /* add base of extended partition */ #ifdef BOOT_FROM_FAT cmpb $MBR_PTYPE_FAT12, %al je 5f cmpb $MBR_PTYPE_FAT16S, %al je 5f cmpb $MBR_PTYPE_FAT16B, %al je 5f cmpb $MBR_PTYPE_FAT32, %al je 5f cmpb $MBR_PTYPE_FAT32L, %al je 5f cmpb $MBR_PTYPE_FAT16L, %al je 5f #else cmpb $MBR_PTYPE_NETBSD, %al #endif jne 10f 5: testl %esi, %esi /* looking for a specific sector? */ je boot cmpl %ebp, %esi /* ptn we wanted? */ je boot /* check for extended partition */ 10: cmpb $MBR_PTYPE_EXT, %al je 15f cmpb $MBR_PTYPE_EXT_LBA, %al je 15f cmpb $MBR_PTYPE_EXT_LNX, %al jne 20f 15: movl 8(%di), %ecx /* sector of next ext. ptn */ 20: add $0x10, %di cmp $BOOTADDR + MBR_MAGIC_OFFSET, %di jne 1b /* not in base partitions, check extended ones */ jecxz no_netbsd_ptn testl %ebx, %ebx jne 30f xchgl %ebx, %ecx /* save base of ext ptn chain */ 30: addl %ebx, %ecx /* address this ptn */ movl %ecx, lba_sector /* sector to read */ call read_lba jmp scan_ptn_tbl no_netbsd_ptn: /* Specific sector not found: try again looking for first NetBSD ptn */ testl %esi, %esi set_err(ERR_PTN) jz error xorl %esi, %esi movl %esi, lba_sector jmp start /* * Sector below CHS limit * Do a cylinder-head-sector read instead * I believe the BIOS should do reads that cross track boundaries. * (but the read should start at the beginning of a track...) */ read_chs: movb 1(%di), %dh /* head */ movw 2(%di), %cx /* ch=cyl, cl=sect */ call chs_read pbr_read_ok1: jmp pbr_read_ok /* * Active partition pointed to by di. * * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical * Block Address) read. Always doing the LBA one * would be nice - unfortunately not all systems support it. * Also some may contain a separate (eg SCSI) BIOS that doesn't * support it even when the main BIOS does. * * The safest thing seems to be to find out whether the sector we * want is inside the CHS sector count. If it is we use CHS, if * outside we use LBA. * * Actually we check that the CHS values reference the LBA sector, * if not we assume that the LBA sector is above the limit, or that * the geometry used (by fdisk) isn't correct. */ boot: movl %ebp, lba_sector /* to control block */ testl %ebx, %ebx /* was it an extended ptn? */ jnz boot_lba /* yes - boot with LBA reads */ /* get CHS values from BIOS */ push %dx /* save drive number */ movb $8, %ah int $0x13 /* chs info */ /* * Validate geometry, if the CHS sector number doesn't match the LBA one * we'll do an LBA read. * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector * and compare against LBA sector number. * Take a slight 'flier' and assume we can just check 16bits (very likely * to be true because the number of sectors per track is 63). */ movw 2(%di), %ax /* cylinder + sector */ push %ax /* save for sector */ shr $6, %al xchgb %al, %ah /* 10 bit cylinder number */ shr $8, %dx /* last head */ inc %dx /* number of heads */ mul %dx mov 1(%di), %dl /* head we want */ add %dx, %ax and $0x3f, %cx /* number of sectors */ mul %cx pop %dx /* recover sector we want */ and $0x3f, %dx add %dx, %ax dec %ax pop %dx /* recover drive number */ cmp %bp, %ax je read_chs check_lba: #ifdef NO_LBA_CHECK jmp boot_lba #else /* * Determine whether we have int13-extensions, by calling * int 13, function 41. Check for the magic number returned, * and the disk packet capability. * * This is actually relatively pointless: * 1) we only use LBA reads if CHS ones would fail * 2) the MBR code managed to read the same sectors * 3) the BIOS will (ok should) reject the LBA read as a bad BIOS call */ movw $0x55aa, %bx movb $0x41, %ah int $0x13 jc 1f /* no int13 extensions */ cmpw $0xaa55, %bx jnz 1f testb $1, %cl jnz boot_lba 1: set_err(ERR_NO_LBA) #endif /* NO_LBA_CHECK */ /* * Something went wrong, * Output error code, */ error: #ifdef TERSE_ERROR movb %al, errcod movw $errtxt, %si call message #else push %ax movw $errtxt, %si call message pop %si call message movw $newline, %si call message #endif 1: sti hlt jmp 1b boot_lba: call read_lba /* * Check magic number for valid stage 2 bootcode * then jump into it. */ pbr_read_ok: cmpl $X86_BOOT_MAGIC_1, bootxx_magic set_err(ERR_NO_BOOTXX) jnz error movl %ebp, %esi /* %esi ptn base, %dl disk id */ movl lba_sector + 4, %edi /* %edi ptn base high */ jmp $0, $bootxx /* our %cs may not be zero */ /* Read disk using int13-extension parameter block */ read_lba: pusha movw $lba_info, %si /* ds:si is ctl block */ movb $0x42, %ah do_read: int $0x13 popa set_err(ERR_READ) jc error ret /* Read using CHS */ chs_read: movw $BOOTADDR, %bx /* es:bx is buffer */ pusha movw $0x200 + BOOTXX_SECTORS, %ax /* command 2, xx sectors */ jmp do_read #ifndef BOOT_FROM_FAT boot_gpt: /* DS:SI has a pointer to the hybrid MBR handover structure */ movl (GPT_ENTRY_OFF+GPT_ENT_LBA_OFF+0)(%si), %ebp movl (GPT_ENTRY_OFF+GPT_ENT_LBA_OFF+4)(%si), %edi movw %cx, %ds movl %ebp, lba_sector + 0 movl %edi, lba_sector + 4 movl %ebp, %esi jmp boot_lba #endif _errtxt: .ascii "Error " /* runs into newline... */ _errcod: .byte 0 /* ... if errcod set */ _newline: .asciz "\r\n" #ifndef TERSE_ERROR ERR_READ: .asciz "read" ERR_NO_BOOTXX: .asciz "no magic" ERR_PTN: .asciz "no slice" #ifndef NO_LBA_CHECK ERR_NO_LBA: .asciz "need LBA" #endif #endif /* * I hate #including source files, but pbr_magic below has to be at * the correct absolute address. * Clearly this could be done with a linker script. */ #include #if 0 #include #endif /* Control block for int-13 LBA read. */ _lba_info: .word 0x10 /* control block length */ .word BOOTXX_SECTORS /* sector count */ .word BOOTADDR /* offset in segment */ .word 0 /* segment */ _lba_sector: .quad 0 /* sector # goes here... */ /* Drive Serial Number */ . = _C_LABEL(start) + MBR_DSN_OFFSET .long 0 /* mbr_bootsel_magic (not used here) */ . = _C_LABEL(start) + MBR_BS_MAGIC_OFFSET .word 0 /* * Provide empty MBR partition table. * If this is installed as an MBR, the user can use fdisk(8) to create * the correct partition table ... */ . = _C_LABEL(start) + MBR_PART_OFFSET _pbr_part0: .byte 0, 0, 0, 0, 0, 0, 0, 0 .long 0, 0 _pbr_part1: .byte 0, 0, 0, 0, 0, 0, 0, 0 .long 0, 0 _pbr_part2: .byte 0, 0, 0, 0, 0, 0, 0, 0 .long 0, 0 _pbr_part3: .byte 0, 0, 0, 0, 0, 0, 0, 0 .long 0, 0 /* * The magic comes last */ . = _C_LABEL(start) + MBR_MAGIC_OFFSET pbr_magic: .word MBR_MAGIC