1/* $NetBSD: mbr.S,v 1.3 2003/07/02 14:35:51 dsl Exp $ */ 2 3/* 4 * Copyright (c) 1999-2003 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Frank van der Linden, based on an earlier work by Wolfgang Solfrank. 9 * Major surgery performed by David Laight. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. All advertising materials mentioning features or use of this software 20 * must display the following acknowledgement: 21 * This product includes software developed by the NetBSD 22 * Foundation, Inc. and its contributors. 23 * 4. Neither the name of The NetBSD Foundation nor the names of its 24 * contributors may be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * 27 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 28 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 30 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 31 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 * POSSIBILITY OF SUCH DAMAGE. 38 */ 39 40/* 41 * i386 master boot code 42 */ 43 44/* Compile options: 45 * BOOTSEL - bootselector code 46 * BOOT_EXTENDED - scan extended partition list (LBA reads) 47 * TERSE_ERROR - terse error messages 48 * NO_CHS - all reads are LBA 49 * NO_LBA_CHECK - no check if bios supports LBA reads 50 */ 51 52#include <machine/asm.h> 53#include <sys/disklabel_mbr.h> 54 55#define BOOTADDR 0x7c00 56#define LOADADDR 0x0600 /* address were are linked to */ 57 58#define TABENTRYSIZE (PARTNAMESIZE + 1) 59#define NAMETABSIZE (NMBRPART * TABENTRYSIZE) 60 61/* 62 * Minimum and maximum drive number that is considered to be valid. 63 */ 64#define MINDRV 0x80 65#define MAXDRV 0x87 66 67#ifdef TERSE_ERROR 68/* 69 * Error codes. Done this way to save space. 70 */ 71#define ERR_INVPART '1' /* Invalid partition table */ 72#define ERR_READ '2' /* Read error */ 73#define ERR_NOOS '3' /* Magic no. check failed for part. */ 74#define ERR_KEY '?' /* unknown key press */ 75#define ERR_NO_LBA 'L' /* sector above chs limit */ 76 77#define set_err(err) movb $err, %al 78 79#else 80#define set_err(err) mov $err, %ax 81#endif 82 83 .text 84 .code16 85/* 86 * Move ourselves out of the way first. 87 * (to the address we are linked at - 0x600) 88 * and zero our bss 89 */ 90ENTRY(start) 91 xor %ax, %ax 92 mov %ax, %ss 93 movw $BOOTADDR, %sp 94 mov %ax, %es 95 mov %ax, %ds 96 mov %sp, %si 97 movw $start, %di 98 movw $(bss_start - start)/2, %cx 99 rep 100 movsw 101 mov $(bss_end - bss_start + 1)/2, %cx 102 rep 103 stosw 104 ljmp $0, $mbr /* leap into copy of code */ 105 106/* 107 * Sanity check the drive number passed by the BIOS. Some BIOSs may not 108 * do this and pass garbage. 109 */ 110mbr: 111 cmpb $MAXDRV, %dl /* relies on MINDRV being 0x80 */ 112 jle 1f 113 movb $MINDRV, %dl /* garbage in, boot disk 0 */ 1141: 115 push %dx /* save drive number */ 116 push %dx /* twice - for err_msg loop */ 117 118#ifdef BOOTSEL 119/* 120 * Walk through the selector (name) table printing used entries. 121 */ 122bootsel_menu: 123 movw $nametab, %bx 124#ifdef BOOT_EXTENDED 125 xorl %ecx, %ecx /* base of extended partition */ 126next_extended: 127 xorl %edx, %edx /* for next extended partition */ 128#endif 129 lea parttab - nametab(%bx), %bp 130next_ptn: 131 movb 4(%bp), %al /* partition type */ 132#ifdef BOOT_EXTENDED 133 movl 8(%bp), %edi /* partition sector number */ 134 cmpb $MBR_PTYPE_EXT, %al /* Extended partition */ 135 je 1f 136 cmpb $MBR_PTYPE_EXT_LBA, %al /* Extended LBA partition */ 137 je 1f 138 cmpb $MBR_PTYPE_EXT_LNX, %al /* Linux extended partition */ 139 jne 2f 1401: movl %edi, %edx /* save next extended ptn */ 141 jmp 3f 1422: 143#endif 144 test %al, %al /* undefined partition */ 145 je 3f 146 cmpb $0, (%bx) /* check for prompt */ 147 jz 3f 148 149 /* output menu item */ 150 movw $prefix, %si 151 incb (%si) 152 call message /* menu number */ 153 mov (%si), %si /* ':' << 8 | '1' + count */ 154 shl $2, %si /* const + count * 4 */ 155#define CONST (4 * ((':' << 8) + '1' - ((SCAN_1 - SCAN_F1) & 0xff))) 156#ifdef NO_CHS 157 addl lba_sector, %edi 158 movl %edi, ptn_list - CONST(%si) /* sector to read */ 159#else 160 mov %bp, ptn_list - CONST(%si) /* partition info */ 161#endif 162#undef CONST 163 mov %bx, %si 164 call message /* prompt */ 165 movw $crlf, %si 166 call message 1673: 168 add $0x10, %bp 169 add $TABENTRYSIZE, %bx 170 cmpb $(nametab - start - 0x100) + 4 * TABENTRYSIZE, %bl 171 jne next_ptn 172 173#ifdef BOOT_EXTENDED 174/* 175 * Now check extended partition chain 176 */ 177 testl %edx, %edx 178 je wait_key 179 testl %ecx, %ecx 180 jne 1f 181 xchg %ecx, %edx /* save base of ext ptn chain */ 1821: addl %ecx, %edx /* sector to read */ 183 movl %edx, lba_sector 184 movw $lba_info, %si 185 movb $0x42, %ah 186 pop %dx /* recover drive # */ 187 push %dx /* save drive */ 188 int $0x13 189 jc wait_key /* abort menu on read fail */ 190 cmpw $MBR_MAGIC, LOADADDR + MBR_MAGICOFF 191 movw $nametab - LOADADDR + BOOTADDR, %bx 192 je next_extended 193#endif 194 195/* 196 * Get the initial time value for the timeout comparison. It is returned 197 * by int 1a in cx:dx. We do sums modulo 2^16 so it doesn't matter if 198 * the counter wraps (which it does every hour) - so we can safely 199 * ignore 'cx'. 200 * 201 * Loop around checking for a keypress until we have one, or timeout is 202 * reached. 203 */ 204wait_key: 205 xorb %ah, %ah 206 int $0x1a 207 mov %dx, %di /* start time to di */ 2083: 209 movb $1, %ah /* looks to see if a */ 210 int $0x16 /* key has been pressed */ 211 jnz get_key 212 xorb %ah, %ah 213 int $0x1a /* current time to cx:dx */ 214 sub %di, %dx 215 movw timeout, %ax 216 cmp %ax, %dx /* always wait for 1 tick... */ 217 jbe 3b /* 0xffff means never timeout */ 218def_key: 219 movb defkey, %al /* timedout - pick default key */ 220 jmp check_key 221get_key: 222 xorb %ah, %ah 223 int $0x16 /* 'read key', code ah, ascii al */ 224 shr $8, %ax /* code in %al, %ah zero */ 225 226/* 227 * We have a keycode, see what it means. 228 * If we don't know we generate error '?' and go ask again 229 */ 230check_key: 231/* 232 * <enter> -> boot active partition. 233 */ 234 cmpb $SCAN_ENTER, %al 235 jne boot_disk 236#endif /* BOOTSEL */ 237 238/* 239 * Scan MBR for first active partition, and boot it. 240 */ 241 mov $NMBRPART, %cx 242 mov $parttab, %si 2431: 244 cmpb $0x80, (%si) 245#ifdef NO_CHS 246 jne 10f /* not active */ 247 movl 8(%si), %ebp /* sector number of ptn */ 248 jmp boot_lba 24910: 250#else 251 je boot_si 252#endif 253 add $0x10, %si 254 loop 1b 255 set_err(ERR_INVPART) 256 jmp err_msg 257 258#ifdef BOOTSEL 259/* 260 * F1-F10 -> boot disk 0-9. Check if the requested disk isn't above 261 * the number of disks actually in the system as stored in 0:0475 by 262 * the BIOS. 263 * If we trust loc 475, we needn't check the upper bound on the keystroke 264 * This is always sector 0, so always read using chs. 265 */ 266boot_disk: 267 subb $SCAN_F1, %al 268 cmpb 0x0475, %al 269 jae boot_ptn 270 addb $0x80, %al 271 pop %dx /* dump saved drive # */ 272 push %ax /* replace with new */ 273#ifdef NO_CHS 274 xorl %ebp, %ebp /* read sector number 0 */ 275 jmp boot_lba 276#else 277 movw $chs_zero, %si /* chs read sector zero info */ 278 jmp read_chs 279#endif 280 281/* 282 * Boot requested partition. 283 * Use keycode to index the table we generated when we scanned the mbr 284 * while generating the menu. 285 * 286 * We very carfully saved the values in the correct part of the table. 287 */ 288 289boot_ptn: 290 shl $2, %ax 291 movw %ax, %si 292#ifdef NO_CHS 293 movl ptn_list(%si), %ebp 294 testl %ebp, %ebp 295 jnz boot_lba 296#else 297 mov ptn_list(%si), %si 298 test %si, %si 299 jnz boot_si 300#endif 301 set_err(ERR_KEY) 302 /* jmp err_msg */ 303#endif /* BOOTSEL */ 304 305/* Something went wrong... 306 * Output error code, 307 * reset disk subsystem - needed after read failure, 308 * and wait for user key 309 */ 310err_msg: 311#ifdef TERSE_ERROR 312 movb %al, errcod 313 movw $errtxt, %si 314 call message 315#else 316 push %ax 317 movw $errtxt, %si 318 call message 319 pop %si 320 call message 321 movw $crlf, %si 322 call message 323#endif 324 pop %dx /* drive we errored on */ 325 xor %ax,%ax /* only need %ah = 0 */ 326 int $0x13 /* reset disk subsystem */ 327#ifdef BOOTSEL 328 pop %dx /* original drive number */ 329 push %dx 330 push %dx 331 jmp get_key 332#else 333 int $0x18 /* BIOS might ask for a key */ 334 /* press and retry boot seq. */ 3351: sti 336 hlt 337 jmp 1b 338#endif 339 340#ifndef NO_CHS 341/* 342 * Active partition pointed to by si. 343 * Read the first sector. 344 * 345 * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical 346 * Block Address) read. Always doing the LBA one 347 * would be nice - unfortunately not all systems support it. 348 * Also some may contain a separate (eg SCSI) bios that doesn't 349 * support it even when the main bios does. 350 * 351 * There is also the additional problem that the CHS values may be wrong 352 * (eg if fdisk was run on a different system that used different BIOS 353 * geometry). We convert the CHS value to a LBA sector number using 354 * the geometry from the BIOS, if the number matches we do a CHS read. 355 */ 356boot_si: 357 movl 8(%si), %ebp /* get sector # */ 358 359 testb $BFL_READ_LBA, flags 360 jnz boot_lba /* fdisk forced LBA read */ 361 362 pop %dx /* collect saved drive... */ 363 push %dx /* ...number to dl */ 364 movb $8, %ah 365 int $0x13 /* chs info */ 366 367/* 368 * Validate geometry, if the CHS sector number doesn't match the LBA one 369 * we'll do an LBA read. 370 * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector 371 * and compare against LBA sector number. 372 * Take a slight 'flier' and assume we can just check 16bits (very likely 373 * to be true because the number of sectors per track is 63). 374 */ 375 movw 2(%si), %ax /* cylinder + sector */ 376 push %ax /* save for sector */ 377 shr $6, %al 378 xchgb %al, %ah /* 10 bit cylinder number */ 379 shr $8, %dx /* last head */ 380 inc %dx /* number of heads */ 381 mul %dx 382 mov 1(%si), %dl /* head we want */ 383 add %dx, %ax 384 and $0x3f, %cx /* number of sectors */ 385 mul %cx 386 pop %dx /* recover sector we want */ 387 and $0x3f, %dx 388 add %dx, %ax 389 dec %ax 390 391 cmp %bp, %ax 392 je read_chs 393 394#ifndef NO_LBA_CHECK 395/* 396 * Determine whether we have int13-extensions, by calling int 13, function 41. 397 * Check for the magic number returned, and the disk packet capability. 398 */ 399 movw $0x55aa, %bx 400 movb $0x41, %ah 401 pop %dx 402 push %dx 403 int $0x13 404 jc 1f /* no int13 extensions */ 405 cmpw $0xaa55, %bx 406 jnz 1f 407 testb $1, %cl 408 jnz boot_lba 4091: set_err(ERR_NO_LBA) 410 jmp err_msg 411#endif /* NO_LBA_CHECK */ 412#endif /* NO_CHS */ 413 414/* 415 * Save sector number (passed in %ebp) into lba parameter block, 416 * read the sector and leap into it. 417 */ 418boot_lba: 419 movl %ebp, lba_sector /* save sector number */ 420 movw $lba_info, %si 421 movb $0x42, %ah 422 pop %dx /* recover drive # */ 423do_read: 424 push %dx /* save drive */ 425 int $0x13 426 427 set_err(ERR_READ) 428 jc err_msg 429 430/* 431 * Check signature for valid bootcode 432 */ 433 movb BOOTADDR, %al /* first byte non-zero */ 434 test %al, %al 435 jz 1f 436 movw BOOTADDR + MBR_MAGICOFF, %ax 4371: cmp $MBR_MAGIC, %ax 438 set_err(ERR_NOOS) 439 jnz err_msg 440 441/* We pass the sector number through to the next stage boot. 442 * It doesn't have to use it (indeed no other mbr code will generate) it, 443 * but it does let us have a NetBSD pbr that can identify where it was 444 * read from! This lets us use this code to select between two 445 * NetBSD system on the same physical driver. 446 * (If we've read the mbr of a different disk, it gets a random number 447 * - but it wasn't expecting anything...) 448*/ 449 movl %ebp, %esi 450 pop %dx /* recover drive # */ 451 jmp start - LOADADDR + BOOTADDR 452 453 454#ifndef NO_CHS 455/* 456 * Sector below CHS limit 457 * Do a cylinder-head-sector read instead. 458 */ 459read_chs: 460 pop %dx /* recover drive # */ 461 movb 1(%si), %dh /* head */ 462 movw 2(%si), %cx /* ch=cyl, cl=sect */ 463 movw $BOOTADDR, %bx /* es:bx is buffer */ 464 movw $0x201, %ax /* command 2, 1 sector */ 465 jmp do_read 466#endif 467 468/* 469 * Control block for int-13 LBA read. 470 * We need a xx, 00, 01, 00 somewhere to load chs for sector zero, 471 * by a complete fluke there is one here! 472 */ 473chs_zero: 474lba_info: 475 .word 0x10 /* control block length */ 476 .word 1 /* sector count */ 477 .word BOOTADDR /* offset in segment */ 478 .word 0 /* segment */ 479lba_sector: 480 .long 0x0000 /* sector # goes here... */ 481 .long 0x0000 482 483errtxt: .ascii "Error " /* runs into crlf if errcod set */ 484errcod: .byte 0 485crlf: .asciz "\r\n" 486 487#ifdef BOOTSEL 488prefix: .asciz "0: " 489#endif 490 491#ifndef TERSE_ERROR 492ERR_INVPART: .asciz "No active partition" 493ERR_READ: .asciz "Disk read error" 494ERR_NOOS: .asciz "No operating system" 495#ifndef NO_LBA_CHECK 496ERR_NO_LBA: .asciz "Invalid CHS read" 497#endif 498#ifdef BOOTSEL 499ERR_KEY: .asciz "bad key" 500#endif 501#endif 502 503/* 504 * I hate #including source files, but the stuff below has to be at 505 * the correct absolute address. 506 * Clearly this could be done with a linker script. 507 */ 508 509#include <message.S> 510#if 0 511#include <dump_eax.S> 512#endif 513 514/* 515 * Stuff from here on is overwritten by fdisk - the offset must not change... 516 * 517 * Get amount of space to makefile can report it. 518 * (Unfortunately I can't seem to get the value reported when it is -ve) 519 */ 520mbr_space = defkey - . 521 . = start + MBR_BOOTSELOFF 522/* 523 * Default action, as a keyvalue we'd normally read from the BIOS. 524 */ 525defkey: 526 .byte SCAN_ENTER /* ps/2 code */ 527#ifndef BOOTSEL_FLAGS 528#define BOOTSEL_FLAGS 0 529#endif 530flags: .byte BFL_NEWMBR | BOOTSEL_FLAGS 531/* 532 * Timeout value. ~65536 ticks per hour, which is ~18.2 times per second. 533 * 0xffff means never timeout. 534 */ 535timeout: 536 .word 182 /* default to 10 seconds */ 537/* 538 * Space for name/select table and partition table. 539 */ 540nametab: 541 .fill NMBRPART * (PARTNAMESIZE + 1), 0x01, 0x00 542 543 . = start + MBR_PARTOFF - 2 544 .word MBR_MAGIC 545 546 . = start + MBR_PARTOFF 547parttab: 548 .fill 0x40, 0x01, 0x00 549 550 . = start + MBR_MAGICOFF 551 .word MBR_MAGIC 552 553/* zeroed data space */ 554bss_off = 0 555bss_start = . 556#define BSS(name, size) name = bss_start + bss_off; bss_off = bss_off + size 557 BSS(ptn_list, 256 * 4) /* long[]: boot sector numbers */ 558 BSS(bss_end, 0) 559