1/* $OpenBSD: mbr.S,v 1.3 2007/06/25 14:10:17 tom Exp $ */ 2 3/* 4 * Copyright (c) 1997 Michael Shalayeff and Tobias Weingartner 5 * Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com> 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 */ 30/* Copyright (c) 1996 VaX#n8 (vax@linkdead.paranoia.com) 31 * last edited 9 July 1996 32 * many thanks to Erich Boleyn (erich@uruk.org) for putting up with 33 * all my questions, and for his work on GRUB 34 * You may use this code or fragments thereof in a manner consistent 35 * with the other copyrights as long as you retain my pseudonym and 36 * this copyright notice in the file. 37 */ 38 39 .file "mbr.S" 40 41#include <machine/asm.h> 42#include <assym.h> 43 44/* 45 * Memory layout: 46 * 47 * 0x07C00 -> 0x07DFF BIOS loads us here (at 31k) 48 * 0x07E00 -> 0x17BFC our stack (to 95k) 49 * 50 * 0x07A00 -> 0x07BFF we relocate to here (at 30k5) 51 * 52 * 0x07C00 -> 0x07DFF we load PBR here (at 31k) 53 * 54 * The BIOS loads us at physical address 0x07C00. We use a long jmp to 55 * normalise our address to seg:offset 07C0:0000. We then relocate to 56 * 0x07A00, seg:offset 07A0:0000. 57 * 58 * We use a long jmp to normalise our address to seg:offset 07A0:0000 59 * We set the stack to start at 07C0:FFFC (grows down on i386) 60 * The partition boot record (PBR) loads /boot at seg:offset 4000:0000 61 */ 62#define BOOTSEG 0x7c0 /* segment where we are loaded */ 63#define BOOTRELOCSEG 0x7a0 /* segment where we relocate to */ 64#define BOOTSTACKOFF 0xfffc /* stack starts here, grows down */ 65#define PARTSZ 16 /* each partition table entry is 16 bytes */ 66 67#define CHAR_LBA_READ '.' 68#define CHAR_CHS_READ ';' 69#define CHAR_CHS_FORCE '!' 70#define CHAR_SHIFT_SEEN 0x07 /* Use BEL */ 71 72#define MBR_FLAGS_FORCE_CHS 0x0001 73 74#ifdef DEBUG 75#define CHAR_S 'S' /* started */ 76#define CHAR_R 'R' /* relocated */ 77#define CHAR_L 'L' /* looking for bootable partition */ 78#define CHAR_B 'B' /* loading boot */ 79#define CHAR_G 'G' /* jumping to boot */ 80 81#define DBGMSG(c) movb $c, %al; call Lchr 82#else /* !DEBUG */ 83#define DBGMSG(c) 84#endif /* !DEBUG */ 85 86/* Clobbers %al - maybe more */ 87#define putc(c) movb $c, %al; call Lchr 88 89/* Clobbers %esi - maybe more */ 90#define puts(s) movw $s, %si; call Lmessage 91 92 93 .text 94 .code16 95 96 .globl start 97start: 98 /* Adjust %cs to be right */ 99 ljmp $BOOTSEG, $1f 1001: 101 /* Set up stack */ 102 movw %cs, %ax 103 104 /* 105 * We don't need to disable and re-enable interrupts around the 106 * the load of ss and sp. 107 * 108 * From 80386 Programmer's Reference Manual: 109 * "A MOV into SS inhibits all interrupts until after the execution 110 * of the next instruction (which is presumably a MOV into eSP)" 111 * 112 * According to Hamarsoft's 86BUGS list (which is distributed with 113 * Ralph Brown's Interrupt List), some early 8086/88 processors 114 * failed to disable interrupts following a load into a segment 115 * register, but this was fixed with later steppings. 116 * 117 * Accordingly, this code will fail on very early 8086/88s, but 118 * nick@ will just have to live with it. Others will note that 119 * we require an 80386 (or compatible) or above processor, anyway. 120 */ 121 /* cli */ 122 movw %ax, %ss 123 movw $BOOTSTACKOFF, %sp 124 /* sti */ /* XXX not necessary; see above */ 125 126 /* Set up data segment */ 127 movw %ax, %ds 128 DBGMSG(CHAR_S) 129 130 /* 131 * On the PC architecture, the boot record (originally on a floppy 132 * disk) is loaded at 0000:7C00 (hex) and execution starts at the 133 * beginning. 134 * 135 * When hard disk support was added, a scheme to partition disks into 136 * four separate partitions was used, to allow multiple operating 137 * systems to be installed on the one disk. The boot sectors of the 138 * operating systems on each partition would of course expect to be 139 * loaded at 0000:7C00. 140 * 141 * The first sector of the hard disk is the master boot record (MBR). 142 * It is this which defines the partitions and says which one is 143 * bootable. Of course, the BIOS loads the MBR at 0000:7C00, the 144 * same location where the MBR needs to load the partition boot 145 * record (PBR, called biosboot in OpenBSD). 146 * 147 * Therefore, the MBR needs to relocate itself before loading the PBR. 148 * 149 * Make it so. 150 */ 151 movw $BOOTRELOCSEG, %ax 152 movw %ax, %es 153 xorw %si, %si 154 xorw %di, %di 155 movw $0x200, %cx /* Bytes in MBR, relocate it all */ 156 cld 157 rep 158 movsb 159 160 /* Jump to relocated self */ 161 ljmp $BOOTRELOCSEG, $reloc 162reloc: 163 DBGMSG(CHAR_R) 164 165 /* Set up %es and %ds */ 166 pushw %ds 167 popw %es /* next boot is at the same place as we were loaded */ 168 pushw %cs 169 popw %ds /* and %ds is at the %cs */ 170 171#ifdef SERIAL 172 /* Initialize the serial port to 9600 baud, 8N1. 173 */ 174 xorw %ax, %ax 175 movb $0xe3, %ax 176 movw $SERIAL, %dx 177 int $0x14 178#endif 179 180 /* 181 * If the SHIFT key is held down on entry, force CHS read 182 */ 183 184 /* 185 * BIOS call "INT 0x16 Get Keyboard Shift Flags 186 * Call with %ah = 0x02 187 * Return: 188 * %al = shift flags 189 * %ah - undefined by many BIOSes 190 */ 191 movb $0x02, %ah 192 int $0x16 193 testb $0x3, %al /* Either shift key down? */ 194 jz no_shift 195 196 putc(CHAR_SHIFT_SEEN) /* Signal that shift key was seen */ 197 198 orb $MBR_FLAGS_FORCE_CHS, flags 199 200no_shift: 201 /* BIOS passes us drive number in %dl 202 * 203 * XXX - This is not always true. We currently check if %dl 204 * points to a HD, and if not we complain, and set it to point 205 * to the first HDD. Note, this is not 100% correct, since 206 * there is a possibility that you boot from HD #2, and still 207 * get (%dl & 0x80) == 0x00, these type of systems will lose. 208 */ 209 testb $0x80, %dl 210 jnz drive_ok 211 212 /* MBR on floppy or old BIOS 213 * Note: MBR (this code) should never be on a floppy. It does 214 * not belong there, so %dl should never be 0x00. 215 * 216 * Here we simply complain (should we?), and then hardcode the 217 * boot drive to 0x80. 218 */ 219 puts(efdmbr) 220 221 /* If we are passed bogus data, set it to HD #1 222 */ 223 movb $0x80, %dl 224 225drive_ok: 226 /* Find the first active partition. 227 * Note: this should be the only active partition. We currently 228 * don't check for that. 229 */ 230 movw $pt, %si 231 232 movw $NDOSPART, %cx 233find_active: 234 DBGMSG(CHAR_L) 235 movb (%si), %al 236 237 cmpb $DOSACTIVE, %al 238 je found 239 240 addw $PARTSZ, %si 241 loop find_active 242 243 /* No bootable partition */ 244no_part: 245 movw $enoboot, %si 246 247err_stop: 248 call Lmessage 249 250stay_stopped: 251 sti /* Ensure Ctl-Alt-Del will work */ 252 hlt 253 /* Just to make sure */ 254 jmp stay_stopped 255 256found: 257 /* 258 * Found bootable partition 259 */ 260 261 DBGMSG(CHAR_B) 262 263 /* Store the drive number (from %dl) in decimal */ 264 movb %dl, %al 265 andb $0x0F, %al 266 addb $'0', %al 267 movb %al, drive_num 268 269 /* 270 * Store the partition number, in decimal. 271 * 272 * We started with cx = 4; if found we want part '0' 273 * cx = 3; part '1' 274 * cx = 2; part '2' 275 * cx = 1; part '3' 276 * 277 * We'll come into this with no other values for cl. 278 */ 279 movb $'0'+4, %al 280 subb %cl, %al 281 movb %al, part_num 282 283 /* 284 * Tell operator what partition we're trying to boot. 285 * 286 * Using drive X, partition Y 287 * - this used to be printed out after successfully loading the 288 * partition boot record; we now print it out before 289 */ 290 pushw %si 291 movw $info, %si 292 testb $MBR_FLAGS_FORCE_CHS, flags 293 jnz 1f 294 incw %si 2951: 296 call Lmessage 297 popw %si 298 299 /* 300 * Partition table entry format: 301 * 302 * 0x00 BYTE boot indicator (0x80 = active, 0x00 = inactive) 303 * 0x01 BYTE start head 304 * 0x02 WORD start cylinder, sector 305 * 0x04 BYTE system type (0xA6 = OpenBSD) 306 * 0x05 BYTE end head 307 * 0x06 WORD end cylinder, sector 308 * 0x08 LONG start LBA sector 309 * 0x0C LONG number of sectors in partition 310 * 311 * In the case of a partition that extends beyond the 8GB boundary, 312 * the LBA values will be correct, the CHS values will have their 313 * maximums (typically (C,H,S) = (1023,255,63)). 314 * 315 * %ds:%si points to the active partition table entry. 316 */ 317 318 /* We will load the partition boot sector (biosboot) where we 319 * were originally loaded. We'll check to make sure something 320 * valid comes in. So that we don't find ourselves, zero out 321 * the signature at the end. 322 */ 323 movw $0, %es:signature(,1) 324 325 /* 326 * Have we been instructed to ignore LBA? 327 */ 328 testb $MBR_FLAGS_FORCE_CHS, flags 329 jnz do_chs 330 331 /* 332 * We will use the LBA sector number if we have LBA support, 333 * so find out. 334 */ 335 336 /* 337 * BIOS call "INT 0x13 Extensions Installation Check" 338 * Call with %ah = 0x41 339 * %bx = 0x55AA 340 * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc) 341 * Return: 342 * carry set: failure 343 * %ah = error code (0x01, invalid func) 344 * carry clear: success 345 * %bx = 0xAA55 (must verify) 346 * %ah = major version of extensions 347 * %al (internal use) 348 * %cx = capabilities bitmap 349 * 0x0001 - extnd disk access funcs 350 * 0x0002 - rem. drive ctrl funcs 351 * 0x0004 - EDD functions with EBP 352 * %dx (extension version?) 353 */ 354 355 movb %dl, (%si) /* Store drive here temporarily */ 356 /* (This call trashes %dl) */ 357 /* 358 * XXX This is actually the correct 359 * place to store this. The 0x80 360 * value used to indicate the 361 * active partition is by intention 362 * the same as the BIOS drive value 363 * for the first hard disk (0x80). 364 * At one point, 0x81 would go here 365 * for the second hard disk; the 366 * 0x80 value is often used as a 367 * bit flag for testing, rather 368 * than an exact byte value. 369 */ 370 movw $0x55AA, %bx 371 movb $0x41, %ah 372 int $0x13 373 374 movb (%si), %dl /* Get back drive number */ 375 376 jc do_chs /* Did the command work? Jump if not */ 377 cmpw $0xAA55, %bx /* Check that bl, bh exchanged */ 378 jne do_chs /* If not, don't have EDD extensions */ 379 testb $0x01, %cl /* And do we have "read" available? */ 380 jz do_chs /* Again, use CHS if not */ 381 382do_lba: 383 /* 384 * BIOS call "INT 0x13 Extensions Extended Read" 385 * Call with %ah = 0x42 386 * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc) 387 * %ds:%si = segment:offset of command packet 388 * Return: 389 * carry set: failure 390 * %ah = error code (0x01, invalid func) 391 * command packet's sector count field set 392 * to the number of sectors successfully 393 * transferred 394 * carry clear: success 395 * %ah = 0 (success) 396 * Command Packet: 397 * 0x0000 BYTE packet size (0x10 or 0x18) 398 * 0x0001 BYTE reserved (should be 0) 399 * 0x0002 WORD sectors to transfer (max 127) 400 * 0x0004 DWORD seg:offset of transfer buffer 401 * 0x0008 QWORD starting sector number 402 */ 403 movb $CHAR_LBA_READ, %al 404 call Lchr 405 406 /* Load LBA sector number from active partition table entry */ 407 movl 8(%si), %ecx 408 movl %ecx, lba_sector 409 410 pushw %si /* We'll need %si later */ 411 412 movb $0x42, %ah 413 movw $lba_command, %si 414 int $0x13 415 416 popw %si /* (get back %si) flags unchanged */ 417 418 jnc booting_os /* If it worked, run the pbr we got */ 419 420 /* 421 * LBA read failed, fall through to try CHS read 422 */ 423 424do_chs: 425 /* 426 * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into 427 * memory 428 * Call with %ah = 0x2 429 * %al = number of sectors 430 * %ch = cylinder & 0xFF 431 * %cl = sector (0-63) | rest of cylinder bits 432 * %dh = head 433 * %dl = drive (0x80 for hard disk) 434 * %es:%bx = segment:offset of buffer 435 * Return: 436 * carry set: failure 437 * %ah = err code 438 * %al = number of sectors transferred 439 * carry clear: success 440 * %al = 0x0 OR number of sectors transferred 441 * (depends on BIOS!) 442 * (according to Ralph Brown Int List) 443 */ 444 movb $CHAR_CHS_READ, %al 445 call Lchr 446 447 /* Load values from active partition table entry */ 448 movb 1(%si), %dh /* head */ 449 movw 2(%si), %cx /* sect, cyl */ 450 movw $0x201, %ax /* function and number of blocks */ 451 xorw %bx, %bx /* put it at %es:0 */ 452 int $0x13 453 jnc booting_os 454 455read_error: 456 movw $eread, %si 457 jmp err_stop 458 459booting_os: 460 puts(crlf) 461 DBGMSG(CHAR_G) 462 463 /* 464 * Make sure the pbr we loaded has a valid signature at the end. 465 * This also ensures that something did load where we were expecting 466 * it, as there's still a copy of our code there... 467 */ 468 cmpw $DOSMBR_SIGNATURE, %es:signature(,1) 469 jne missing_os 470 471 /* jump to the new code (%ds:%si is at the right point) */ 472 ljmp $0, $BOOTSEG << 4 473 /* not reached */ 474 475missing_os: 476 movw $enoos, %si 477 jmp err_stop 478 479/* 480 * Display string 481 */ 482Lmessage: 483 pushw %ax 484 cld 4851: 486 lodsb /* %al = *%si++ */ 487 testb %al, %al 488 jz 1f 489 call Lchr 490 jmp 1b 491 492/* 493 * Lchr: write the error message in %ds:%si to console 494 */ 495Lchr: 496 pushw %ax 497 498#ifdef SERIAL 499 pushw %dx 500 movb $0x01, %ah 501 movw SERIAL, %dx 502 int $0x14 503 popw %dx 504#else 505 pushw %bx 506 movb $0x0e, %ah 507 movw $1, %bx 508 int $0x10 509 popw %bx 510#endif 5111: popw %ax 512 ret 513 514/* command packet for LBA read of boot sector */ 515lba_command: 516 .byte 0x10 /* size of command packet */ 517 .byte 0x00 /* reserved */ 518 .word 0x0001 /* sectors to transfer, just 1 */ 519 .word 0 /* target buffer, offset */ 520 .word BOOTSEG /* target buffer, segment */ 521lba_sector: 522 .long 0, 0 /* sector number */ 523 524/* Info messages */ 525info: .ascii "!Using drive " 526drive_num: 527 .byte 'X' 528 .ascii ", partition " 529part_num: 530 .asciz "Y" 531 532/* Error messages */ 533efdmbr: .asciz "MBR on floppy or old BIOS\r\n" 534eread: .asciz "\r\nRead error\r\n" 535enoos: .asciz "No O/S\r\n" 536enoboot: .ascii "No active partition" /* runs into crlf... */ 537crlf: .asciz "\r\n" 538 539endofcode: 540 nop 541 542/* We're going to store a flags word here */ 543 544 . = 0x1b4 545flags: 546 .word 0x0000 547 .ascii "Ox" /* Indicate that the two bytes */ 548 /* before us are the flags word */ 549 550/* (MBR) NT disk signature offset */ 551 . = 0x1b8 552 .space 4, 0 553 554/* partition table */ 555/* flag, head, sec, cyl, type, ehead, esect, ecyl, start, len */ 556 . = DOSPARTOFF /* starting address of partition table */ 557pt: 558 .byte 0x0,0,0,0,0,0,0,0 559 .long 0,0 560 .byte 0x0,0,0,0,0,0,0,0 561 .long 0,0 562 .byte 0x0,0,0,0,0,0,0,0 563 .long 0,0 564 .byte DOSACTIVE,0,1,0,DOSPTYP_OPENBSD,255,255,255 565 .long 0,0x7FFFFFFF 566/* the last 2 bytes in the sector 0 contain the signature */ 567 . = 0x1fe 568signature: 569 .short DOSMBR_SIGNATURE 570 . = 0x200 571