1264977Sian/* $NetBSD: mbr.S,v 1.24 2023/08/01 21:26:27 andvar Exp $ */ 2264977Sian 3264977Sian/* 4264977Sian * Copyright (c) 1999-2004 The NetBSD Foundation, Inc. 5264977Sian * All rights reserved. 6264977Sian * 7264977Sian * This code is derived from software contributed to The NetBSD Foundation 8264977Sian * by Frank van der Linden, based on an earlier work by Wolfgang Solfrank. 9264977Sian * Major surgery performed by David Laight. 10264977Sian * 11264977Sian * Redistribution and use in source and binary forms, with or without 12264977Sian * modification, are permitted provided that the following conditions 13264977Sian * are met: 14264977Sian * 1. Redistributions of source code must retain the above copyright 15264977Sian * notice, this list of conditions and the following disclaimer. 16264977Sian * 2. Redistributions in binary form must reproduce the above copyright 17264977Sian * notice, this list of conditions and the following disclaimer in the 18264977Sian * documentation and/or other materials provided with the distribution. 19264977Sian * 20264977Sian * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21264977Sian * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22264977Sian * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23264977Sian * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24264977Sian * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25264977Sian * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26264977Sian * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27264977Sian * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28264977Sian * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29264977Sian * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30264977Sian * POSSIBILITY OF SUCH DAMAGE. 31264977Sian */ 32264977Sian 33264977Sian/* 34264977Sian * i386 master boot code 35264977Sian */ 36264977Sian 37264977Sian/* Compile options: 38264977Sian * BOOTSEL - bootselector code 39264977Sian * BOOT_EXTENDED - scan extended partition list (LBA reads) 40264977Sian * COM_PORT - do serial io to specified port number 41264977Sian * 0..3 => bios port, otherwise actual io_addr 42264977Sian * COM_BAUD - initialise serial port baud rate 43264977Sian * 44264977Sian * TERSE_ERROR - terse error messages 45264977Sian * NO_CHS - all reads are LBA 46264977Sian * NO_LBA_CHECK - no check if bios supports LBA reads 47264977Sian * NO_BANNER - do not output title line 'banner' 48264977Sian */ 49264977Sian 50273659Sian#ifdef BOOT_EXTENDED 51264977Sian#define NO_CHS 1 52264977Sian#define BOOTSEL 1 53264977Sian#endif 54283500Sian 55264977Sian#ifdef COM_PORT 56283501Sian#if COM_PORT < 4 57283501Sian/* The first 4 items in the 40:xx segment are the serial port base addresses */ 58283501Sian#define COM_PORT_VAL (0x400 + (COM_PORT * 2)) 59283501Sian#else 60264977Sian#define COM_PORT_VAL $COM_PORT 61#endif 62 63#if !defined(COM_FREQ) 64#define COM_FREQ 1843200 65#endif 66#else 67#undef COM_BAUD 68#endif 69 70#ifdef BOOTSEL 71#define TERSE_ERROR 1 72#endif 73 74#include <machine/asm.h> 75#include <sys/bootblock.h> 76 77#define BOOTADDR 0x7c00 /* where we get loaded to */ 78 79#define TABENTRYSIZE (MBR_BS_PARTNAMESIZE + 1) 80#define NAMETABSIZE (MBR_PART_COUNT * TABENTRYSIZE) 81 82#ifdef COM_PORT 83/* ASCII values for the keys */ 84#define KEY_ACTIVE '\r' 85#define KEY_DISK1 'a' 86#define KEY_PTN1 '1' 87#else 88/* Scan values for the various keys we use, as returned by the BIOS */ 89#define SCAN_ENTER 0x1c 90#define SCAN_F1 0x3b 91#define SCAN_1 0x2 92 93#define KEY_ACTIVE SCAN_ENTER 94#define KEY_DISK1 SCAN_F1 95#define KEY_PTN1 SCAN_1 96#endif 97 98/* 99 * Minimum and maximum drive number that is considered to be valid. 100 */ 101#define MINDRV 0x80 102#define MAXDRV 0x8f 103 104#ifdef TERSE_ERROR 105/* 106 * Error codes. Done this way to save space. 107 */ 108#define ERR_INVPART '1' /* Invalid partition table */ 109#define ERR_READ '2' /* Read error */ 110#define ERR_NOOS '3' /* Magic no. check failed for part. */ 111#define ERR_KEY '?' /* unknown key press */ 112#define ERR_NO_LBA 'L' /* sector above chs limit */ 113 114#define set_err(err) movb $err, %al 115 116#else 117#define set_err(err) mov $err, %ax 118#endif 119 120 .text 121 .code16 122/* 123 * Move ourselves out of the way first. 124 * (to the address we are linked at) 125 * and zero our bss 126 */ 127ENTRY(start) 128 xor %ax, %ax 129 mov %ax, %ss 130 movw $BOOTADDR, %sp 131 mov %ax, %es 132 mov %ax, %ds 133 movw $mbr, %di 134 mov $BOOTADDR + (mbr - start), %si 135 push %ax /* zero for %cs of lret */ 136 push %di 137 movw $(bss_start - mbr), %cx 138 rep 139 movsb /* relocate code */ 140 mov $(bss_end - bss_start + 511)/512, %ch 141 rep 142 stosw /* zero bss */ 143 lret /* Ensures %cs == 0 */ 144 145/* 146 * Sanity check the drive number passed by the BIOS. Some BIOSs may not 147 * do this and pass garbage. 148 */ 149mbr: 150 cmpb $MAXDRV, %dl /* relies on MINDRV being 0x80 */ 151 jle 1f 152 movb $MINDRV, %dl /* garbage in, boot disk 0 */ 1531: 154 push %dx /* save drive number */ 155 push %dx /* twice - for err_msg loop */ 156 157#if defined(COM_BAUD) 158 mov $com_args, %si 159 mov $num_com_args, %cl /* %ch is zero from above */ 160 mov COM_PORT_VAL, %dx 1611: lodsw 162 add %ah, %dl 163 outb %dx 164 loop 1b 165#endif 166 167#ifndef NO_BANNER 168 mov $banner, %si 169 call message_crlf 170#endif 171 172/* 173 * Walk through the selector (name) table printing used entries. 174 * 175 * Register use: 176 * %ax temp 177 * %bx nametab[] boot selector menu 178 * %ecx base of 'extended' partition 179 * %edx next extended partition 180 * %si message ptr (etc) 181 * %edi sector number of this partition 182 * %bp parttab[] mbr partition table 183 */ 184bootsel_menu: 185 movw $nametab, %bx 186#ifdef BOOT_EXTENDED 187 xorl %ecx, %ecx /* base of extended partition */ 188next_extended: 189 xorl %edx, %edx /* for next extended partition */ 190#endif 191 lea parttab - nametab(%bx), %bp 192next_ptn: 193 movb 4(%bp), %al /* partition type */ 194#ifdef NO_CHS 195 movl 8(%bp), %edi /* partition sector number */ 196#ifdef BOOT_EXTENDED 197 cmpb $MBR_PTYPE_EXT, %al /* Extended partition */ 198 je 1f 199 cmpb $MBR_PTYPE_EXT_LBA, %al /* Extended LBA partition */ 200 je 1f 201 cmpb $MBR_PTYPE_EXT_LNX, %al /* Linux extended partition */ 202 jne 2f 2031: movl %edi, %edx /* save next extended ptn */ 204 jmp 4f 2052: 206#endif 207 addl lba_sector, %edi /* add in extended ptn base */ 208#endif 209 test %al, %al /* undefined partition */ 210 je 4f 211 cmpb $0x80, (%bp) /* check for active partition */ 212 jne 3f /* jump if not... */ 213#define ACTIVE (4 * ((KEY_ACTIVE - KEY_DISK1) & 0xff)) 214#ifdef NO_CHS 215 movl %edi, ptn_list + ACTIVE /* save location of active ptn */ 216#else 217 mov %bp, ptn_list + ACTIVE 218#endif 219#undef ACTIVE 2203: 221#ifdef BOOTSEL 222 cmpb $0, (%bx) /* check for prompt */ 223 jz 4f 224 /* output menu item */ 225 movw $prefix, %si 226 incb (%si) 227 call message /* menu number */ 228 mov (%si), %si /* ':' << 8 | '1' + count */ 229 shl $2, %si /* const + count * 4 */ 230#define CONST (4 * ((':' << 8) + '1' - ((KEY_PTN1 - KEY_DISK1) & 0xff))) 231#ifdef NO_CHS 232 movl %edi, ptn_list - CONST(%si) /* sector to read */ 233#else 234 mov %bp, ptn_list - CONST(%si) /* partition info */ 235#endif 236#undef CONST 237 mov %bx, %si 238 call message_crlf /* prompt */ 239#endif 2404: 241 add $0x10, %bp 242 add $TABENTRYSIZE, %bx 243 cmpb $(nametab - start - 0x100) + 4 * TABENTRYSIZE, %bl 244 jne next_ptn 245 246#ifdef BOOT_EXTENDED 247/* 248 * Now check extended partition chain 249 */ 250 testl %edx, %edx 251 je wait_key 252 testl %ecx, %ecx 253 jne 1f 254 xchg %ecx, %edx /* save base of ext ptn chain */ 2551: addl %ecx, %edx /* sector to read */ 256 movl %edx, lba_sector 257 movw $lba_info, %si 258 movb $0x42, %ah 259 pop %dx /* recover drive # */ 260 push %dx /* save drive */ 261 int $0x13 262 movw $BOOTADDR + (nametab - start), %bx 263 jnc next_extended /* abort menu on read fail */ 264#endif 265 266/* 267 * The non-bootsel code traverses this code path, it needs the 268 * correct keycode to select the active partition. 269 */ 270 271#ifndef BOOTSEL 272 mov $(KEY_ACTIVE - KEY_DISK1) & 0xff, %ax 273#else 274/* 275 * Get the initial time value for the timeout comparison. It is returned 276 * by int 1a in cx:dx. We do sums modulo 2^16 so it doesn't matter if 277 * the counter wraps (which it does every hour) - so we can safely 278 * ignore 'cx'. 279 * 280 * Loop around checking for a keypress until we have one, or timeout is 281 * reached. 282 */ 283wait_key: 284 xorb %ah, %ah 285 int $0x1a 286 mov %dx, %di /* start time to di */ 2873: 288#ifdef COM_PORT_VAL 289 mov COM_PORT_VAL, %dx 290 push %dx 291 add $5, %dx 292 inb %dx 293 pop %dx 294 test $1, %al 295 jz 1f 296 inb %dx 297 jmp check_key 298#else 299 movb $1, %ah /* looks to see if a */ 300 int $0x16 /* key has been pressed */ 301 jz 1f 302get_key: 303 xorb %ah, %ah 304 int $0x16 /* 'read key', code ah, ascii al */ 305 shr $8, %ax /* code in %al, %ah zero */ 306 jmp check_key 307#endif 308 3091: xorb %ah, %ah 310 int $0x1a /* current time to cx:dx */ 311 sub %di, %dx 312 cmpw timeout, %dx /* always wait for 1 tick... */ 313 jbe 3b /* 0xffff means never timeout */ 314def_key: 315 mov defkey, %al /* timedout - we need %ah to still be zero! */ 316 317/* 318 * We have a keycode, see what it means. 319 * If we don't know we generate error '?' and go ask again 320 */ 321check_key: 322/* 323 * F1-F10 -> boot disk 0-9. Check if the requested disk isn't above 324 * the number of disks actually in the system as stored in 0:0475 by 325 * the BIOS. 326 * If we trust loc 475, we needn't check the upper bound on the keystroke 327 * This is always sector 0, so always read using chs. 328 */ 329 subb $KEY_DISK1, %al 330 cmpb 0x0475, %al 331 jae boot_ptn 332 addb $0x80, %al 333 pop %dx /* dump saved drive # */ 334 push %ax /* replace with new */ 335#ifdef NO_CHS 336 xorl %ebp, %ebp /* read sector number 0 */ 337 jmp boot_lba 338#else 339 movw $chs_zero, %si /* chs read sector zero info */ 340 jmp read_chs 341#endif 342#endif /* BOOTSEL */ 343 344/* 345 * Boot requested partition. 346 * Use keycode to index the table we generated when we scanned the mbr 347 * while generating the menu. 348 * 349 * We very carfully saved the values in the correct part of the table. 350 */ 351 352boot_ptn: 353 shl $2, %ax 354 movw %ax, %si 355#ifdef NO_CHS 356 movl ptn_list(%si), %ebp 357 testl %ebp, %ebp 358 jnz boot_lba 359#else 360 mov ptn_list(%si), %si 361 test %si, %si 362 jnz boot_si 363#endif 364#ifdef BOOTSEL 365 set_err(ERR_KEY) 366#else 367 set_err(ERR_INVPART) 368#endif 369 /* jmp err_msg */ 370 371/* Something went wrong... 372 * Output error code, 373 * reset disk subsystem - needed after read failure, 374 * and wait for user key 375 */ 376err_msg: 377#ifdef TERSE_ERROR 378 movb %al, errcod 379 movw $errtxt, %si 380 call message 381#else 382 push %ax 383 movw $errtxt, %si 384 call message 385 pop %si 386 call message_crlf 387#endif 388 pop %dx /* drive we errored on */ 389 xor %ax,%ax /* only need %ah = 0 */ 390 int $0x13 /* reset disk subsystem */ 391#ifdef BOOTSEL 392 pop %dx /* original drive number */ 393 push %dx 394 push %dx 395#ifdef COM_PORT_VAL 396 jmp wait_key /* Read with timeout (again) */ 397#else 398 jmp get_key /* Blocking read */ 399#endif 400#else 401 int $0x18 /* BIOS might ask for a key */ 402 /* press and retry boot seq. */ 4031: sti 404 hlt 405 jmp 1b 406#endif 407 408#ifndef NO_CHS 409/* 410 * Active partition pointed to by si. 411 * Read the first sector. 412 * 413 * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical 414 * Block Address) read. Always doing the LBA one 415 * would be nice - unfortunately not all systems support it. 416 * Also some may contain a separate (eg SCSI) bios that doesn't 417 * support it even when the main bios does. 418 * 419 * There is also the additional problem that the CHS values may be wrong 420 * (eg if fdisk was run on a different system that used different BIOS 421 * geometry). We convert the CHS value to a LBA sector number using 422 * the geometry from the BIOS, if the number matches we do a CHS read. 423 */ 424boot_si: 425 movl 8(%si), %ebp /* get sector # */ 426 427 testb $MBR_BS_READ_LBA, flags 428 jnz boot_lba /* fdisk forced LBA read */ 429 430 pop %dx /* collect saved drive... */ 431 push %dx /* ...number to dl */ 432 movb $8, %ah 433 int $0x13 /* chs info */ 434 435/* 436 * Validate geometry, if the CHS sector number doesn't match the LBA one 437 * we'll do an LBA read. 438 * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector 439 * and compare against LBA sector number. 440 * Take a slight 'flier' and assume we can just check 16bits (very likely 441 * to be true because the number of sectors per track is 63). 442 */ 443 movw 2(%si), %ax /* cylinder + sector */ 444 push %ax /* save for sector */ 445 shr $6, %al 446 xchgb %al, %ah /* 10 bit cylinder number */ 447 shr $8, %dx /* last head */ 448 inc %dx /* number of heads */ 449 mul %dx 450 mov 1(%si), %dl /* head we want */ 451 add %dx, %ax 452 and $0x3f, %cx /* number of sectors */ 453 mul %cx 454 pop %dx /* recover sector we want */ 455 and $0x3f, %dx 456 add %dx, %ax 457 dec %ax 458 459 cmp %bp, %ax 460 je read_chs 461 462#ifndef NO_LBA_CHECK 463/* 464 * Determine whether we have int13-extensions, by calling int 13, function 41. 465 * Check for the magic number returned, and the disk packet capability. 466 */ 467 movw $0x55aa, %bx 468 movb $0x41, %ah 469 pop %dx 470 push %dx 471 int $0x13 472 set_err(ERR_NO_LBA) 473 jc err_msg /* no int13 extensions */ 474 cmpw $0xaa55, %bx 475 jnz err_msg 476 testb $1, %cl 477 jz err_msg 478#endif /* NO_LBA_CHECK */ 479#endif /* NO_CHS */ 480 481/* 482 * Save sector number (passed in %ebp) into lba parameter block, 483 * read the sector and leap into it. 484 */ 485boot_lba: 486 movl %ebp, lba_sector /* save sector number */ 487 movw $lba_info, %si 488 movb $0x42, %ah 489 pop %dx /* recover drive # */ 490do_read: 491 push %dx /* save drive */ 492 int $0x13 493 494 set_err(ERR_READ) 495 jc err_msg 496 497/* 498 * Check signature for valid bootcode 499 */ 500 movb BOOTADDR, %al /* first byte non-zero */ 501 test %al, %al 502 jz 1f 503 movw BOOTADDR + MBR_MAGIC_OFFSET, %ax 5041: cmp $MBR_MAGIC, %ax 505 set_err(ERR_NOOS) 506 jnz err_msg 507 508/* We pass the sector number through to the next stage boot. 509 * It doesn't have to use it (indeed no other mbr code will generate) it, 510 * but it does let us have a NetBSD pbr that can identify where it was 511 * read from! This lets us use this code to select between two 512 * NetBSD system on the same physical driver. 513 * (If we've read the mbr of a different disk, it gets a random number 514 * - but it wasn't expecting anything...) 515*/ 516 movl %ebp, %esi 517 pop %dx /* recover drive # */ 518 jmp BOOTADDR 519 520 521#ifndef NO_CHS 522/* 523 * Sector below CHS limit 524 * Do a cylinder-head-sector read instead. 525 */ 526read_chs: 527 pop %dx /* recover drive # */ 528 movb 1(%si), %dh /* head */ 529 movw 2(%si), %cx /* ch=cyl, cl=sect */ 530 movw $BOOTADDR, %bx /* es:bx is buffer */ 531 movw $0x201, %ax /* command 2, 1 sector */ 532 jmp do_read 533#endif 534 535/* 536 * Control block for int-13 LBA read. 537 * We need a xx, 00, 01, 00 somewhere to load chs for sector zero, 538 * by a complete fluke there is one here! 539 */ 540chs_zero: 541lba_info: 542 .word 0x10 /* control block length */ 543 .word 1 /* sector count */ 544 .word BOOTADDR /* offset in segment */ 545 .word 0 /* segment */ 546lba_sector: 547 .long 0x0000 /* sector # goes here... */ 548 .long 0x0000 549 550errtxt: .ascii "Error " /* runs into crlf if errcod set */ 551errcod: .byte 0 552crlf: .asciz "\r\n" 553 554#ifndef NO_BANNER 555#ifdef BOOTSEL 556#ifdef COM_PORT_VAL 557banner: .asciz "a: disk" 558#else 559banner: .asciz "Fn: diskn" 560#endif 561#else 562banner: .asciz "NetBSD MBR boot" 563#endif 564#endif 565 566#ifdef BOOTSEL 567prefix: .asciz "0: " 568#endif 569 570#ifndef TERSE_ERROR 571ERR_INVPART: .asciz "No active partition" 572ERR_READ: .asciz "Disk read error" 573ERR_NOOS: .asciz "No operating system" 574#ifndef NO_LBA_CHECK 575ERR_NO_LBA: .asciz "Invalid CHS read" 576#endif 577#ifdef BOOTSEL 578ERR_KEY: .asciz "bad key" 579#endif 580#endif 581 582#if defined(COM_BAUD) 583#define COM_DIVISOR (((COM_FREQ / COM_BAUD) + 8) / 16) 584com_args: 585 .byte 0x80 /* divisor latch enable */ 586 .byte +3 /* io_port + 3 */ 587 .byte COM_DIVISOR & 0xff 588 .byte -3 /* io_port */ 589 .byte COM_DIVISOR >> 8 /* high baud */ 590 .byte +1 /* io_port + 1 */ 591 .byte 0x03 /* 8 bit no parity */ 592 .byte +2 /* io_port + 3 */ 593num_com_args = (. - com_args)/2 594#endif 595 596/* 597 * I hate #including source files, but the stuff below has to be at 598 * the correct absolute address. 599 * Clearly this could be done with a linker script. 600 */ 601 602message_crlf: 603 call message 604 movw $crlf, %si 605#include <message.S> 606#if 0 607#include <dump_eax.S> 608#endif 609 610/* 611 * Stuff from here on is overwritten by fdisk - the offset must not change... 612 * 613 * Get amount of space to makefile can report it. 614 * (Unfortunately I can't seem to get the value reported when it is -ve) 615 */ 616mbr_space = defkey - . 617 . = start + MBR_BS_OFFSET 618/* 619 * Default action, as a keyvalue we'd normally read from the BIOS. 620 */ 621defkey: 622 .byte KEY_ACTIVE /* ps/2 code */ 623#ifndef BOOTSEL_FLAGS 624#define BOOTSEL_FLAGS 0 625#endif 626flags: .byte MBR_BS_NEWMBR | BOOTSEL_FLAGS 627/* 628 * Timeout value. ~65536 ticks per hour, which is ~18.2 times per second. 629 * 0xffff means never timeout. 630 */ 631timeout: 632 .word 182 /* default to 10 seconds */ 633/* 634 * mbr_bootsel 635 */ 636nametab: 637 .fill MBR_PART_COUNT * (MBR_BS_PARTNAMESIZE + 1), 0x01, 0x00 638 639/* space for mbr_dsn */ 640 . = start + MBR_DSN_OFFSET 641 .long 0 642 643/* mbr_bootsel_magic */ 644 . = start + MBR_BS_MAGIC_OFFSET 645 .word MBR_BS_MAGIC 646 647/* 648 * MBR partition table 649 */ 650 . = start + MBR_PART_OFFSET 651parttab: 652 .fill 0x40, 0x01, 0x00 653 654 . = start + MBR_MAGIC_OFFSET 655 .word MBR_MAGIC 656 657/* zeroed data space */ 658bss_off = 0 659bss_start = . 660#define BSS(name, size) name = bss_start + bss_off; bss_off = bss_off + size 661 BSS(ptn_list, 256 * 4) /* long[]: boot sector numbers */ 662 BSS(dump_eax_buff, 16) 663 BSS(bss_end, 0) 664