Annotation of sys/arch/i386/stand/mbr/mbr.S, Revision 1.1
1.1 ! nbrk 1: /* $OpenBSD: mbr.S,v 1.21 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
! 97: start:
! 98: /* Adjust %cs to be right */
! 99: ljmp $BOOTSEG, $1f
! 100: 1:
! 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
! 162: reloc:
! 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:
! 200: no_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:
! 225: drive_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
! 233: find_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 */
! 244: no_part:
! 245: movw $enoboot, %si
! 246:
! 247: err_stop:
! 248: call Lmessage
! 249:
! 250: stay_stopped:
! 251: sti /* Ensure Ctl-Alt-Del will work */
! 252: hlt /* (don't require power cycle) */
! 253: /* Just to make sure */
! 254: jmp stay_stopped
! 255:
! 256: found:
! 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
! 295: 1:
! 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:
! 382: do_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:
! 424: do_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:
! 455: read_error:
! 456: movw $eread, %si
! 457: jmp err_stop
! 458:
! 459: booting_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:
! 475: missing_os:
! 476: movw $enoos, %si
! 477: jmp err_stop
! 478:
! 479: /*
! 480: * Display string
! 481: */
! 482: Lmessage:
! 483: pushw %ax
! 484: cld
! 485: 1:
! 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: */
! 495: Lchr:
! 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
! 511: 1: popw %ax
! 512: ret
! 513:
! 514: /* command packet for LBA read of boot sector */
! 515: lba_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 */
! 521: lba_sector:
! 522: .long 0, 0 /* sector number */
! 523:
! 524: /* Info messages */
! 525: info: .ascii "!Using drive "
! 526: drive_num:
! 527: .byte 'X'
! 528: .ascii ", partition "
! 529: part_num:
! 530: .asciz "Y"
! 531:
! 532: /* Error messages */
! 533: efdmbr: .asciz "MBR on floppy or old BIOS\r\n"
! 534: eread: .asciz "\r\nRead error\r\n"
! 535: enoos: .asciz "No O/S\r\n"
! 536: enoboot: .ascii "No active partition" /* runs into crlf... */
! 537: crlf: .asciz "\r\n"
! 538:
! 539: endofcode:
! 540: nop
! 541:
! 542: /* We're going to store a flags word here */
! 543:
! 544: . = 0x1b4
! 545: flags:
! 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 */
! 557: pt:
! 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
! 568: signature:
! 569: .short DOSMBR_SIGNATURE
! 570: . = 0x200
CVSweb