1/* $OpenBSD: mbr.S,v 1.9 2022/09/02 07:46:03 krw 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 70#ifdef DEBUG 71#define CHAR_S 'S' /* started */ 72#define CHAR_R 'R' /* relocated */ 73#define CHAR_L 'L' /* looking for bootable partition */ 74#define CHAR_B 'B' /* loading boot */ 75#define CHAR_G 'G' /* jumping to boot */ 76 77#define DBGMSG(c) movb $c, %al; call Lchr 78#else /* !DEBUG */ 79#define DBGMSG(c) 80#endif /* !DEBUG */ 81 82/* Clobbers %al - maybe more */ 83#define putc(c) movb $c, %al; call Lchr 84 85/* Clobbers %esi - maybe more */ 86#define puts(s) movw $s, %si; call Lmessage 87 88 89 .text 90 .code16 91 92 .globl start 93start: 94 /* Adjust %cs to be right */ 95 ljmp $BOOTSEG, $1f 961: 97 /* Set up stack */ 98 movw %cs, %ax 99 100 /* 101 * We don't need to disable and re-enable interrupts around the 102 * the load of ss and sp. 103 * 104 * From 80386 Programmer's Reference Manual: 105 * "A MOV into SS inhibits all interrupts until after the execution 106 * of the next instruction (which is presumably a MOV into eSP)" 107 * 108 * According to Hamarsoft's 86BUGS list (which is distributed with 109 * Ralph Brown's Interrupt List), some early 8086/88 processors 110 * failed to disable interrupts following a load into a segment 111 * register, but this was fixed with later steppings. 112 * 113 * Accordingly, this code will fail on very early 8086/88s, but 114 * nick@ will just have to live with it. Others will note that 115 * we require at least a Pentium compatible processor anyway. 116 */ 117 /* cli */ 118 movw %ax, %ss 119 movw $BOOTSTACKOFF, %sp 120 /* sti */ /* XXX not necessary; see above */ 121 122 /* Set up data segment */ 123 movw %ax, %ds 124 DBGMSG(CHAR_S) 125 126 /* 127 * On the PC architecture, the boot record (originally on a floppy 128 * disk) is loaded at 0000:7C00 (hex) and execution starts at the 129 * beginning. 130 * 131 * When hard disk support was added, a scheme to partition disks into 132 * four separate partitions was used, to allow multiple operating 133 * systems to be installed on the one disk. The boot sectors of the 134 * operating systems on each partition would of course expect to be 135 * loaded at 0000:7C00. 136 * 137 * The first sector of the hard disk is the master boot record (MBR). 138 * It is this which defines the partitions and says which one is 139 * bootable. Of course, the BIOS loads the MBR at 0000:7C00, the 140 * same location where the MBR needs to load the partition boot 141 * record (PBR, called biosboot in OpenBSD). 142 * 143 * Therefore, the MBR needs to relocate itself before loading the PBR. 144 * 145 * Make it so. 146 */ 147 movw $BOOTRELOCSEG, %ax 148 movw %ax, %es 149 xorw %si, %si 150 xorw %di, %di 151 movw $0x200, %cx /* Bytes in MBR, relocate it all */ 152 cld 153 rep 154 movsb 155 156 /* Jump to relocated self */ 157 ljmp $BOOTRELOCSEG, $reloc 158reloc: 159 DBGMSG(CHAR_R) 160 161 /* Set up %es and %ds */ 162 pushw %ds 163 popw %es /* next boot is at the same place as we were loaded */ 164 pushw %cs 165 popw %ds /* and %ds is at the %cs */ 166 167#ifdef SERIAL 168 /* Initialize the serial port to 9600 baud, 8N1. 169 */ 170 pushw %dx 171 xorw %ax, %ax 172 movb $0xe3, %ax 173 movw $SERIAL, %dx 174 int $0x14 175 popw %dx 176#endif 177 178 /* BIOS passes us drive number in %dl 179 * 180 * XXX - This is not always true. We currently check if %dl 181 * points to a HD, and if not we complain, and set it to point 182 * to the first HDD. Note, this is not 100% correct, since 183 * there is a possibility that you boot from HD #2, and still 184 * get (%dl & 0x80) == 0x00, these type of systems will lose. 185 */ 186 testb $0x80, %dl 187 jnz drive_ok 188 189 /* MBR on floppy or old BIOS 190 * Note: MBR (this code) should never be on a floppy. It does 191 * not belong there, so %dl should never be 0x00. 192 * 193 * Here we simply complain (should we?), and then hardcode the 194 * boot drive to 0x80. 195 */ 196 puts(efdmbr) 197 198 /* If we are passed bogus data, set it to HD #1 199 */ 200 movb $0x80, %dl 201 202drive_ok: 203 /* Find the first active partition. 204 * Note: this should be the only active partition. We currently 205 * don't check for that. 206 */ 207 movw $pt, %si 208 209 movw $NDOSPART, %cx 210find_active: 211 DBGMSG(CHAR_L) 212 movb (%si), %al 213 214 cmpb $DOSACTIVE, %al 215 je found 216 217 addw $PARTSZ, %si 218 loop find_active 219 220 /* No bootable partition */ 221no_part: 222 movw $enoboot, %si 223 224err_stop: 225 call Lmessage 226 227stay_stopped: 228 sti /* Ensure Ctl-Alt-Del will work */ 229 hlt 230 /* Just to make sure */ 231 jmp stay_stopped 232 233found: 234 /* 235 * Found bootable partition 236 */ 237 238 DBGMSG(CHAR_B) 239 240 /* Store the drive number (from %dl) in decimal */ 241 movb %dl, %al 242 andb $0x0F, %al 243 addb $'0', %al 244 movb %al, drive_num 245 246 /* 247 * Store the partition number, in decimal. 248 * 249 * We started with cx = 4; if found we want part '0' 250 * cx = 3; part '1' 251 * cx = 2; part '2' 252 * cx = 1; part '3' 253 * 254 * We'll come into this with no other values for cl. 255 */ 256 movb $'0'+4, %al 257 subb %cl, %al 258 movb %al, part_num 259 260 /* 261 * Tell operator what partition we're trying to boot. 262 * 263 * Using drive X, partition Y 264 * - this used to be printed out after successfully loading the 265 * partition boot record; we now print it out before 266 */ 267 pushw %si 268 movw $info, %si 269 call Lmessage 270 popw %si 271 272 /* 273 * Partition table entry format: 274 * 275 * 0x00 BYTE boot indicator (0x80 = active, 0x00 = inactive) 276 * 0x01 BYTE start head 277 * 0x02 WORD start cylinder, sector 278 * 0x04 BYTE system type (0xA6 = OpenBSD) 279 * 0x05 BYTE end head 280 * 0x06 WORD end cylinder, sector 281 * 0x08 LONG start LBA sector 282 * 0x0C LONG number of sectors in partition 283 * 284 * In the case of a partition that extends beyond the 8GB boundary, 285 * the LBA values will be correct, the CHS values will have their 286 * maximums (typically (C,H,S) = (1023,255,63)). 287 * 288 * %ds:%si points to the active partition table entry. 289 */ 290 291 /* We will load the partition boot sector (biosboot) where we 292 * were originally loaded. We'll check to make sure something 293 * valid comes in. So that we don't find ourselves, zero out 294 * the signature at the end. 295 */ 296 movw $0, %es:signature(,1) 297 298 /* 299 * We will use the LBA sector number if we have LBA support, 300 * so find out. 301 */ 302 303 /* 304 * BIOS call "INT 0x13 Extensions Installation Check" 305 * Call with %ah = 0x41 306 * %bx = 0x55AA 307 * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc) 308 * Return: 309 * carry set: failure 310 * %ah = error code (0x01, invalid func) 311 * carry clear: success 312 * %bx = 0xAA55 (must verify) 313 * %ah = major version of extensions 314 * %al (internal use) 315 * %cx = capabilities bitmap 316 * 0x0001 - extnd disk access funcs 317 * 0x0002 - rem. drive ctrl funcs 318 * 0x0004 - EDD functions with EBP 319 * %dx (extension version?) 320 */ 321 322 movb %dl, (%si) /* Store drive here temporarily */ 323 /* (This call trashes %dl) */ 324 /* 325 * XXX This is actually the correct 326 * place to store this. The 0x80 327 * value used to indicate the 328 * active partition is by intention 329 * the same as the BIOS drive value 330 * for the first hard disk (0x80). 331 * At one point, 0x81 would go here 332 * for the second hard disk; the 333 * 0x80 value is often used as a 334 * bit flag for testing, rather 335 * than an exact byte value. 336 */ 337 movw $0x55AA, %bx 338 movb $0x41, %ah 339 int $0x13 340 341 movb (%si), %dl /* Get back drive number */ 342 343 jc do_chs /* Did the command work? Jump if not */ 344 cmpw $0xAA55, %bx /* Check that bl, bh exchanged */ 345 jne do_chs /* If not, don't have EDD extensions */ 346 testb $0x01, %cl /* And do we have "read" available? */ 347 jz do_chs /* Again, use CHS if not */ 348 349do_lba: 350 /* 351 * BIOS call "INT 0x13 Extensions Extended Read" 352 * Call with %ah = 0x42 353 * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc) 354 * %ds:%si = segment:offset of command packet 355 * Return: 356 * carry set: failure 357 * %ah = error code (0x01, invalid func) 358 * command packet's sector count field set 359 * to the number of sectors successfully 360 * transferred 361 * carry clear: success 362 * %ah = 0 (success) 363 * Command Packet: 364 * 0x0000 BYTE packet size (0x10 or 0x18) 365 * 0x0001 BYTE reserved (should be 0) 366 * 0x0002 WORD sectors to transfer (max 127) 367 * 0x0004 DWORD seg:offset of transfer buffer 368 * 0x0008 QWORD starting sector number 369 */ 370 movb $CHAR_LBA_READ, %al 371 call Lchr 372 373 /* Load LBA sector number from active partition table entry */ 374 movl 8(%si), %ecx 375 movl %ecx, lba_sector 376 377 pushw %si /* We'll need %si later */ 378 379 movb $0x42, %ah 380 movw $lba_command, %si 381 int $0x13 382 383 popw %si /* get back %si */ 384 385 jnc booting_os /* If it worked, run the pbr we got */ 386 387 /* 388 * LBA read failed, fall through to try CHS read 389 */ 390 391do_chs: 392 /* 393 * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into 394 * memory 395 * Call with %ah = 0x2 396 * %al = number of sectors 397 * %ch = cylinder & 0xFF 398 * %cl = sector (0-63) | rest of cylinder bits 399 * %dh = head 400 * %dl = drive (0x80 for hard disk) 401 * %es:%bx = segment:offset of buffer 402 * Return: 403 * carry set: failure 404 * %ah = err code 405 * %al = number of sectors transferred 406 * carry clear: success 407 * %al = 0x0 OR number of sectors transferred 408 * (depends on BIOS!) 409 * (according to Ralph Brown Int List) 410 */ 411 movb $CHAR_CHS_READ, %al 412 call Lchr 413 414 /* Load values from active partition table entry */ 415 movb 1(%si), %dh /* head */ 416 movw 2(%si), %cx /* sect, cyl */ 417 movw $0x201, %ax /* function and number of blocks */ 418 xorw %bx, %bx /* put it at %es:0 */ 419 int $0x13 420 jnc booting_os 421 422read_error: 423 movw $eread, %si 424 jmp err_stop 425 426booting_os: 427 puts(crlf) 428 DBGMSG(CHAR_G) 429 430 /* 431 * Make sure the pbr we loaded has a valid signature at the end. 432 * This also ensures that something did load where we were expecting 433 * it, as there's still a copy of our code there... 434 */ 435 cmpw $DOSMBR_SIGNATURE, %es:signature(,1) 436 jne missing_os 437 438 /* jump to the new code (%ds:%si is at the right point) */ 439 ljmp $0, $BOOTSEG << 4 440 /* not reached */ 441 442missing_os: 443 movw $enoos, %si 444 jmp err_stop 445 446/* 447 * Display string 448 */ 449Lmessage: 450 pushw %ax 451 cld 4521: 453 lodsb /* %al = *%si++ */ 454 testb %al, %al 455 jz 1f 456 call Lchr 457 jmp 1b 458 459/* 460 * Lchr: write the error message in %ds:%si to console 461 */ 462Lchr: 463 pushw %ax 464 465#ifdef SERIAL 466 pushw %dx 467 movb $0x01, %ah 468 movw $SERIAL, %dx 469 int $0x14 470 popw %dx 471#else 472 pushw %bx 473 movb $0x0e, %ah 474 movw $1, %bx 475 int $0x10 476 popw %bx 477#endif 4781: popw %ax 479 ret 480 481/* command packet for LBA read of boot sector */ 482lba_command: 483 .byte 0x10 /* size of command packet */ 484 .byte 0x00 /* reserved */ 485 .word 0x0001 /* sectors to transfer, just 1 */ 486 .word 0 /* target buffer, offset */ 487 .word BOOTSEG /* target buffer, segment */ 488lba_sector: 489 .long 0, 0 /* sector number */ 490 491/* Info messages */ 492info: .ascii "Using drive " 493drive_num: 494 .byte 'X' 495 .ascii ", partition " 496part_num: 497 .asciz "Y" 498 499/* Error messages */ 500efdmbr: .asciz "MBR on floppy or old BIOS\r\n" 501eread: .asciz "\r\nRead error\r\n" 502enoos: .asciz "No O/S\r\n" 503enoboot: .ascii "No active partition" /* runs into crlf... */ 504crlf: .asciz "\r\n" 505 506endofcode: 507 nop 508 509/* (MBR) NT disk signature offset */ 510 . = 0x1b8 511 .space 4, 0 512 513/* partition table */ 514/* flag, head, sec, cyl, type, ehead, esect, ecyl, start, len */ 515 . = DOSPARTOFF /* starting address of partition table */ 516pt: .fill 0x40,1,0 517/* the last 2 bytes in the sector 0 contain the signature */ 518 . = 0x1fe 519signature: 520 .short DOSMBR_SIGNATURE 521 . = 0x200 522