1/* $NetBSD: pbr.S,v 1.19 2011/01/06 01:08:48 jakllsch Exp $ */ 2 3/*- 4 * Copyright (c) 2003,2004 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by David Laight. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32/* 33 * i386 partition boot code 34 * 35 * This code resides in sector zero of the netbsd partition, or sector 36 * zero of an unpartitioned disk (eg a floppy). 37 * Sector 1 is assumed to contain the netbsd disklabel. 38 * Sectors 2 until the end of the track contain the next phase of bootstrap. 39 * Which know how to read the interactive 'boot' program from filestore. 40 * The job of this code is to read in the phase 1 bootstrap. 41 * 42 * Makefile supplies: 43 * PRIMARY_LOAD_ADDRESS: Address we load code to (0x1000). 44 * BOOTXX_SECTORS: Number of sectors we load (15). 45 * X86_BOOT_MAGIC_1: A random magic number. 46 * 47 * Although this code is executing at 0x7c00, it is linked to address 0x1000. 48 * All data references MUST be fixed up using R(). 49 */ 50 51#include <machine/asm.h> 52#include <sys/bootblock.h> 53 54#define OURADDR 0x7c00 /* our address */ 55#define BOOTADDR PRIMARY_LOAD_ADDRESS 56 57#define R(a) (a - BOOTADDR + OURADDR) 58 59#define lba_info R(_lba_info) 60#define lba_sector R(_lba_sector) 61#define errtxt R(_errtxt) 62#define errcod R(_errcod) 63#define newline R(_newline) 64 65#define TABENTRYSIZE (MBR_BS_PARTNAMESIZE + 1) 66#define NAMETABSIZE (4 * TABENTRYSIZE) 67 68#ifdef BOOT_FROM_FAT 69#define MBR_AFTERBPB 90 /* BPB size in FAT32 partition BR */ 70#else 71#define MBR_AFTERBPB 62 /* BPB size in floppy master BR */ 72#endif 73 74#ifdef TERSE_ERROR 75/* 76 * Error codes. Done this way to save space. 77 */ 78#define ERR_READ '2' /* Read error */ 79#define ERR_NO_BOOTXX 'B' /* No bootxx_xfs in 3rd sector */ 80#define ERR_PTN 'P' /* partition not defined */ 81#define ERR_NO_LBA 'L' /* sector above chs limit */ 82 83#define set_err(err) movb $err, %al 84 85#else 86#define set_err(err) mov $R(err), %ax 87#endif 88 89/* 90 * This code is loaded to addresss 0:7c00 by either the system BIOS 91 * (for a floppy) or the mbr boot code. Since the boot program will 92 * be loaded to address 1000:0, we don't need to relocate ourselves 93 * and can load the subsequent blocks (that load boot) to an address 94 * of our choosing. 0:1000 is a not unreasonable choice. 95 * 96 * On entry the BIOS drive number is in %dl and %esi may contain the 97 * sector we were loaded from (if we were loaded by NetBSD mbr code). 98 * In any case we have to re-read sector zero of the disk and hunt 99 * through the BIOS partition table for the NetBSD partition. 100 * However, if there's a magic number in %eax, then %ecx:%ebx 101 * contains the sector we were loaded from. 102 */ 103 104 .text 105 .code16 106ENTRY(start) 107 /* 108 * The PC BIOS architecture defines a Boot Parameter Block (BPB) here. 109 * The actual format varies between different MS-DOS versions, but 110 * apparently some system BIOS insist on patching this area 111 * (especially on LS120 drives - which I thought had an MBR...). 112 * The initial jmp and nop are part of the standard and may be 113 * tested for by the system BIOS. 114 */ 115 jmp start0 116 nop 117 .ascii "NetBSD60" /* oemname (8 bytes) */ 118 119 . = start + MBR_BPB_OFFSET /* move to start of BPB */ 120 /* (ensures oemname doesn't overflow) */ 121 122 . = start + MBR_AFTERBPB /* skip BPB */ 123start0: 124#ifndef BOOT_FROM_FAT 125 movl %eax, %edi /* move %eax magic out of the way */ 126#endif 127 xor %ax, %ax /* don't trust values of ds, es or ss */ 128 mov %ax, %ds 129 mov %ax, %es 130 mov %ax, %ss 131 mov $0xfffc, %sp 132 133#ifndef BOOT_FROM_FAT 134 cmpl $X86_MBR_GPT_MAGIC, %edi /* did mbr_gpt leave us a LBA? */ 135 je boot_gpt 136#endif 137 138 /* A 'reset disk system' request is traditional here... */ 139 push %dx /* some BIOS zap %dl here :-( */ 140 int $0x13 /* ah == 0 from code above */ 141 pop %dx 142 143 /* Read from start of disk */ 144 movw $0x0001, %cx /* track zero sector 1 */ 145 movb %ch, %dh /* dh = head = 0 */ 146 call chs_read 147 148/* See if this is our code, if so we have already loaded the next stage */ 149 150 xorl %ebp, %ebp /* pass sector 0 to next stage */ 151 movl (%bx), %eax /* MBR code shouldn't even have ... */ 152 cmpl R(start), %eax /* ... a jmp at the start. */ 153 je pbr_read_ok1 154 155/* Now scan the MBR partition table for a netbsd partition */ 156 157 xorl %ebx, %ebx /* for base extended ptn chain */ 158scan_ptn_tbl: 159 xorl %ecx, %ecx /* for next extended ptn */ 160 movw $BOOTADDR + MBR_PART_OFFSET, %di 1611: movb 4(%di), %al /* mbrp_type */ 162 movl 8(%di), %ebp /* mbrp_start == LBA sector */ 163 addl lba_sector, %ebp /* add base of extended partition */ 164#ifdef BOOT_FROM_FAT 165 cmpb $MBR_PTYPE_FAT12, %al 166 je 5f 167 cmpb $MBR_PTYPE_FAT16S, %al 168 je 5f 169 cmpb $MBR_PTYPE_FAT16B, %al 170 je 5f 171 cmpb $MBR_PTYPE_FAT32, %al 172 je 5f 173 cmpb $MBR_PTYPE_FAT32L, %al 174 je 5f 175 cmpb $MBR_PTYPE_FAT16L, %al 176 je 5f 177#else 178 cmpb $MBR_PTYPE_NETBSD, %al 179#endif 180 jne 10f 1815: testl %esi, %esi /* looking for a specific sector? */ 182 je boot 183 cmpl %ebp, %esi /* ptn we wanted? */ 184 je boot 185 /* check for extended partition */ 18610: cmpb $MBR_PTYPE_EXT, %al 187 je 15f 188 cmpb $MBR_PTYPE_EXT_LBA, %al 189 je 15f 190 cmpb $MBR_PTYPE_EXT_LNX, %al 191 jne 20f 19215: movl 8(%di), %ecx /* sector of next ext. ptn */ 19320: add $0x10, %di 194 cmp $BOOTADDR + MBR_MAGIC_OFFSET, %di 195 jne 1b 196 197 /* not in base partitions, check extended ones */ 198 jecxz no_netbsd_ptn 199 testl %ebx, %ebx 200 jne 30f 201 xchgl %ebx, %ecx /* save base of ext ptn chain */ 20230: addl %ebx, %ecx /* address this ptn */ 203 movl %ecx, lba_sector /* sector to read */ 204 call read_lba 205 jmp scan_ptn_tbl 206 207no_netbsd_ptn: 208 /* Specific sector not found: try again looking for first NetBSD ptn */ 209 testl %esi, %esi 210 set_err(ERR_PTN) 211 jz error 212 xorl %esi, %esi 213 movl %esi, lba_sector 214 jmp start 215 216/* 217 * Sector below CHS limit 218 * Do a cylinder-head-sector read instead 219 * I believe the BIOS should do reads that cross track boundaries. 220 * (but the read should start at the beginning of a track...) 221 */ 222read_chs: 223 movb 1(%di), %dh /* head */ 224 movw 2(%di), %cx /* ch=cyl, cl=sect */ 225 call chs_read 226pbr_read_ok1: 227 jmp pbr_read_ok 228 229/* 230 * Active partition pointed to by di. 231 * 232 * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical 233 * Block Address) read. Always doing the LBA one 234 * would be nice - unfortunately not all systems support it. 235 * Also some may contain a separate (eg SCSI) BIOS that doesn't 236 * support it even when the main BIOS does. 237 * 238 * The safest thing seems to be to find out whether the sector we 239 * want is inside the CHS sector count. If it is we use CHS, if 240 * outside we use LBA. 241 * 242 * Actually we check that the CHS values reference the LBA sector, 243 * if not we assume that the LBA sector is above the limit, or that 244 * the geometry used (by fdisk) isn't correct. 245 */ 246boot: 247 movl %ebp, lba_sector /* to control block */ 248 testl %ebx, %ebx /* was it an extended ptn? */ 249 jnz boot_lba /* yes - boot with LBA reads */ 250 251/* get CHS values from BIOS */ 252 push %dx /* save drive number */ 253 movb $8, %ah 254 int $0x13 /* chs info */ 255 256/* 257 * Validate geometry, if the CHS sector number doesn't match the LBA one 258 * we'll do an LBA read. 259 * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector 260 * and compare against LBA sector number. 261 * Take a slight 'flier' and assume we can just check 16bits (very likely 262 * to be true because the number of sectors per track is 63). 263 */ 264 movw 2(%di), %ax /* cylinder + sector */ 265 push %ax /* save for sector */ 266 shr $6, %al 267 xchgb %al, %ah /* 10 bit cylinder number */ 268 shr $8, %dx /* last head */ 269 inc %dx /* number of heads */ 270 mul %dx 271 mov 1(%di), %dl /* head we want */ 272 add %dx, %ax 273 and $0x3f, %cx /* number of sectors */ 274 mul %cx 275 pop %dx /* recover sector we want */ 276 and $0x3f, %dx 277 add %dx, %ax 278 dec %ax 279 pop %dx /* recover drive nmber */ 280 281 cmp %bp, %ax 282 je read_chs 283 284check_lba: 285#ifdef NO_LBA_CHECK 286 jmp boot_lba 287#else 288/* 289 * Determine whether we have int13-extensions, by calling 290 * int 13, function 41. Check for the magic number returned, 291 * and the disk packet capability. 292 * 293 * This is actually relatively pointless: 294 * 1) we only use LBA reads if CHS ones would fail 295 * 2) the MBR code managed to read the same sectors 296 * 3) the BIOS will (ok should) reject the LBA read as a bad BIOS call 297 */ 298 movw $0x55aa, %bx 299 movb $0x41, %ah 300 int $0x13 301 jc 1f /* no int13 extensions */ 302 cmpw $0xaa55, %bx 303 jnz 1f 304 testb $1, %cl 305 jnz boot_lba 3061: set_err(ERR_NO_LBA) 307#endif /* NO_LBA_CHECK */ 308 309/* 310 * Something went wrong, 311 * Output error code, 312 */ 313 314error: 315#ifdef TERSE_ERROR 316 movb %al, errcod 317 movw $errtxt, %si 318 call message 319#else 320 push %ax 321 movw $errtxt, %si 322 call message 323 pop %si 324 call message 325 movw $newline, %si 326 call message 327#endif 3281: sti 329 hlt 330 jmp 1b 331 332boot_lba: 333 call read_lba 334 335/* 336 * Check magic number for valid stage 2 bootcode 337 * then jump into it. 338 */ 339pbr_read_ok: 340 cmpl $X86_BOOT_MAGIC_1, bootxx_magic 341 set_err(ERR_NO_BOOTXX) 342 jnz error 343 344 movl %ebp, %esi /* %esi ptn base, %dl disk id */ 345 movl lba_sector + 4, %edi /* %edi ptn base high */ 346 jmp $0, $bootxx /* our %cs may not be zero */ 347 348/* Read disk using int13-extension parameter block */ 349read_lba: 350 pusha 351 movw $lba_info, %si /* ds:si is ctl block */ 352 movb $0x42, %ah 353do_read: 354 int $0x13 355 popa 356 357 set_err(ERR_READ) 358 jc error 359 ret 360 361/* Read using CHS */ 362 363chs_read: 364 movw $BOOTADDR, %bx /* es:bx is buffer */ 365 pusha 366 movw $0x200 + BOOTXX_SECTORS, %ax /* command 2, xx sectors */ 367 jmp do_read 368 369#ifndef BOOT_FROM_FAT 370boot_gpt: 371 /* 64-bit LBA in %ecx:%ebx */ 372 movl %ebx, lba_sector 373 movl %ecx, lba_sector + 4 374 movl %ebx, %ebp 375 jmp boot_lba 376#endif 377 378_errtxt: .ascii "Error " /* runs into newline... */ 379_errcod: .byte 0 /* ... if errcod set */ 380_newline: 381 .asciz "\r\n" 382 383#ifndef TERSE_ERROR 384ERR_READ: .asciz "read" 385ERR_NO_BOOTXX: .asciz "no magic" 386ERR_PTN: .asciz "no slice" 387#ifndef NO_LBA_CHECK 388ERR_NO_LBA: .asciz "need LBA" 389#endif 390#endif 391 392/* 393 * I hate #including source files, but pbr_magic below has to be at 394 * the correct absolute address. 395 * Clearly this could be done with a linker script. 396 */ 397 398#include <message.S> 399#if 0 400#include <dump_eax.S> 401#endif 402 403/* Control block for int-13 LBA read. */ 404_lba_info: 405 .word 0x10 /* control block length */ 406 .word BOOTXX_SECTORS /* sector count */ 407 .word BOOTADDR /* offset in segment */ 408 .word 0 /* segment */ 409_lba_sector: 410 .quad 0 /* sector # goes here... */ 411 412/* Drive Serial Number */ 413 . = _C_LABEL(start) + MBR_DSN_OFFSET 414 .long 0 415 416/* mbr_bootsel_magic (not used here) */ 417 . = _C_LABEL(start) + MBR_BS_MAGIC_OFFSET 418 .word 0 419 420/* 421 * Provide empty MBR partition table. 422 * If this is installed as an MBR, the user can use fdisk(8) to create 423 * the correct partition table ... 424 */ 425 . = _C_LABEL(start) + MBR_PART_OFFSET 426_pbr_part0: 427 .byte 0, 0, 0, 0, 0, 0, 0, 0 428 .long 0, 0 429_pbr_part1: 430 .byte 0, 0, 0, 0, 0, 0, 0, 0 431 .long 0, 0 432_pbr_part2: 433 .byte 0, 0, 0, 0, 0, 0, 0, 0 434 .long 0, 0 435_pbr_part3: 436 .byte 0, 0, 0, 0, 0, 0, 0, 0 437 .long 0, 0 438 439/* 440 * The magic comes last 441 */ 442 . = _C_LABEL(start) + MBR_MAGIC_OFFSET 443pbr_magic: 444 .word MBR_MAGIC 445