Annotation of sys/arch/i386/stand/biosboot/biosboot.S, Revision 1.1
1.1 ! nbrk 1: /* $OpenBSD: biosboot.S,v 1.38 2007/05/31 23:34:46 weingart Exp $ */
! 2:
! 3: /*
! 4: * Copyright (c) 2003 Tobias Weingartner
! 5: * Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
! 6: * Copyright (c) 1997 Michael Shalayeff, Tobias Weingartner
! 7: * All rights reserved.
! 8: *
! 9: * Redistribution and use in source and binary forms, with or without
! 10: * modification, are permitted provided that the following conditions
! 11: * are met:
! 12: * 1. Redistributions of source code must retain the above copyright
! 13: * notice, this list of conditions and the following disclaimer.
! 14: * 2. Redistributions in binary form must reproduce the above copyright
! 15: * notice, this list of conditions and the following disclaimer in the
! 16: * documentation and/or other materials provided with the distribution.
! 17: *
! 18: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
! 19: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
! 20: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
! 21: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
! 22: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
! 23: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
! 24: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
! 25: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
! 26: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
! 27: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 28: * SUCH DAMAGE.
! 29: *
! 30: */
! 31: .file "biosboot.S"
! 32:
! 33: #include <machine/asm.h>
! 34: #include <assym.h>
! 35:
! 36: /* Error indicators */
! 37: #define PBR_READ_ERROR 'R'
! 38: #define PBR_CANT_BOOT 'X'
! 39: #define PBR_BAD_MAGIC 'M'
! 40: #define PBR_TOO_MANY_INDIRECTS 'I'
! 41:
! 42: #define CHAR_BLOCK_READ '.'
! 43: #define CHAR_CHS_READ ';'
! 44:
! 45: /*
! 46: * Memory layout:
! 47: *
! 48: * 0x00000 -> 0x079FF our stack (to 30k5)
! 49: * 0x07A00 -> 0x07BFF typical MBR loc (at 30k5)
! 50: * 0x07C00 -> 0x07DFF our code (at 31k)
! 51: * 0x07E00 -> ... /boot inode block (at 31k5)
! 52: * 0x07E00 -> ... (indirect block if nec)
! 53: * 0x40000 -> ... /boot (at 256k)
! 54: *
! 55: * The BIOS loads the MBR at physical address 0x07C00. It then relocates
! 56: * itself to (typically) 0x07A00.
! 57: *
! 58: * The MBR then loads us at physical address 0x07C00.
! 59: *
! 60: * We use a long jmp to normalise our address to seg:offset 07C0:0000.
! 61: * (In real mode on x86, segment registers contain a base address in
! 62: * paragraphs (16 bytes). 0000:00010 is the same as 0001:0000.)
! 63: *
! 64: * We set the stack to start at 0000:79FC (grows down on i386)
! 65: *
! 66: * We then read the inode for /boot into memory just above us at
! 67: * 07E0:0000, and run through the direct block table (and the first
! 68: * indirect block table, if necessary).
! 69: *
! 70: * We load /boot at seg:offset 4000:0000.
! 71: *
! 72: * Previous versions limited the size of /boot to 64k (loaded in a single
! 73: * segment). This version does not have this limitation.
! 74: */
! 75: #define INODESEG 0x07e0 /* where we put /boot's inode's block */
! 76: #define INDIRECTSEG 0x07e0 /* where we put indirect table, if nec */
! 77: #define BOOTSEG 0x07c0 /* biosboot loaded here */
! 78: #define BOOTSTACKOFF ((BOOTSEG << 4) - 4) /* stack starts here, grows down */
! 79: #define LFMAGIC 0x464c /* LFMAGIC (last two bytes of \7fELF) */
! 80: #define ELFMAGIC 0x464c457f /* ELFMAGIC ("\7fELF") */
! 81:
! 82: #define INODEOFF ((INODESEG-BOOTSEG) << 4)
! 83:
! 84: /*
! 85: * The data passed by installboot is:
! 86: *
! 87: * inodeblk uint32 the filesystem block that holds /boot's inode
! 88: * inodedbl uint32 the memory offset to the beginning of the
! 89: * direct block list (di_db[]). (This is the
! 90: * offset within the block + $INODEOFF, which is
! 91: * where we load the block to.)
! 92: * fs_bsize_p uint16 the filesystem block size _in paragraphs_
! 93: * (i.e. fs_bsize / 16)
! 94: * fs_bsize_s uint16 the number of 512-byte sectors in a filesystem
! 95: * block (i.e. fs_bsize / 512). Directly written
! 96: * into the LBA command block, at lba_count.
! 97: * XXX LIMITED TO 127 BY PHOENIX EDD SPEC.
! 98: * fsbtodb uint8 shift count to convert filesystem blocks to
! 99: * disk blocks (sectors). Note that this is NOT
! 100: * log2 fs_bsize, since fragmentation allows
! 101: * the trailing part of a file to use part of a
! 102: * filesystem block. In other words, filesystem
! 103: * block numbers can point into the middle of
! 104: * filesystem blocks.
! 105: * p_offset uint32 the starting disk block (sector) of the
! 106: * filesystem
! 107: * nblocks uint16 the number of filesystem blocks to read.
! 108: * While this can be calculated as
! 109: * howmany(di_size, fs_bsize) it takes us too
! 110: * many code bytes to do it.
! 111: *
! 112: * All of these are patched directly into the code where they are used
! 113: * (once only, each), to save space.
! 114: *
! 115: * One more symbol is exported, in anticipation of a "-c" flag in
! 116: * installboot to force CHS reads:
! 117: *
! 118: * force_chs uint8 set to the value 1 to force biosboot to use CHS
! 119: * reads (this will of course cause the boot sequence
! 120: * to fail if /boot is above 8 GB).
! 121: */
! 122:
! 123: .globl inodeblk, inodedbl, fs_bsize_p, fsbtodb, p_offset, nblocks
! 124: .globl fs_bsize_s, force_chs
! 125: .type inodeblk, @function
! 126: .type inodedbl, @function
! 127: .type fs_bsize_p, @function
! 128: .type fs_bsize_s, @function
! 129: .type fsbtodb, @function
! 130: .type p_offset, @function
! 131: .type nblocks, @function
! 132: .type force_chs, @function
! 133:
! 134:
! 135: /* Clobbers %ax, maybe more */
! 136: #define putc(c) movb $c, %al; call Lchr
! 137:
! 138: /* Clobbers %ax, %si, maybe more */
! 139: #define puts(s) movw $s, %si; call Lmessage
! 140:
! 141:
! 142: .text
! 143: .code16
! 144: .globl _start
! 145: _start:
! 146: jmp begin
! 147: nop
! 148:
! 149: /*
! 150: * BIOS Parameter Block. Read by many disk utilities.
! 151: *
! 152: * We would have liked biosboot to go from the superblock to
! 153: * the root directory to the inode for /boot, thence to read
! 154: * its blocks into memory.
! 155: *
! 156: * As code and data space is quite tight in the 512-byte
! 157: * partition boot sector, we instead get installboot to pass
! 158: * us some pre-processed fields.
! 159: *
! 160: * We would have liked to put these in the BIOS parameter block,
! 161: * as that seems to be the right place to put them (it's really
! 162: * the equivalent of the superblock for FAT filesystems), but
! 163: * caution prevents us.
! 164: *
! 165: * For now, these fields are either directly in the code (when they
! 166: * are used once only) or at the end of this sector.
! 167: */
! 168:
! 169: . = _start + 3
! 170:
! 171: .asciz "OpenBSD"
! 172:
! 173: /* BPB */
! 174: . = _start + 0x0b
! 175: bpb: .word DEV_BSIZE /* sector size */
! 176: .byte 2 /* sectors/cluster */
! 177: .word 0 /* reserved sectors */
! 178: .byte 0 /* # of FAT */
! 179: .word 0 /* root entries */
! 180: .word 0 /* small sectors */
! 181: .byte 0xf8 /* media type (hd) */
! 182: .word 0 /* sectors/fat */
! 183: .word 0 /* sectors per track */
! 184: .word 0 /* # of heads */
! 185:
! 186: /* EBPB */
! 187: . = _start + 0x1c
! 188: ebpb: .long 16 /* hidden sectors */
! 189: .long 0 /* large sectors */
! 190: .word 0 /* physical disk */
! 191: .byte 0x29 /* signature, needed by NT */
! 192: .space 4, 0 /* volume serial number */
! 193: .asciz "UNIX LABEL"
! 194: .asciz "UFS 4.4"
! 195:
! 196: /* boot code */
! 197: . = _start + 0x3e
! 198:
! 199: begin:
! 200: /* Fix up %cs just in case */
! 201: ljmp $BOOTSEG, $main
! 202:
! 203: /*
! 204: * Come here if we have to do a CHS boot, but we get an error from
! 205: * BIOS get drive parameters, or it returns nsectors == 0 (in which
! 206: * case we can't do the division we need to convert LBA sector
! 207: * number to CHS).
! 208: */
! 209: cant_boot:
! 210: movb $PBR_CANT_BOOT, %al
! 211: jmp err_print_crlf
! 212:
! 213: main:
! 214: /* Set up stack */
! 215: xorw %ax, %ax
! 216: movw %ax, %ss
! 217: movw $BOOTSTACKOFF, %sp
! 218:
! 219: /* Set up needed data segment reg */
! 220: pushw %cs
! 221: popw %ds /* Now %cs == %ds, != %ss (%ss == 0) */
! 222:
! 223: #ifdef SERIAL
! 224: /* Initialize the serial port to 9600 baud, 8N1 */
! 225: push %dx
! 226: movw $0x00e3, %ax
! 227: movw SERIAL, %dx
! 228: int $0x14
! 229: pop %dx
! 230: #endif
! 231:
! 232: #ifdef BDEBUG
! 233: putc('R')
! 234: #endif
! 235:
! 236: /*
! 237: * We're going to print our sign-on message.
! 238: *
! 239: * We're now LBA-aware, and will use LBA to load /boot if the
! 240: * BIOS says it's available. However, we have seen machines
! 241: * where CHS is required even when LBA is available. Therefore
! 242: * we provide a way to force CHS use:
! 243: *
! 244: * If the SHIFT key is held down on entry, force CHS reads.
! 245: */
! 246: movw $load_msg+1, %si /* "Loading" */
! 247: movb %dl, %dh
! 248:
! 249: /*
! 250: * BIOS call "INT 0x16 Get Keyboard Shift Flags
! 251: * Call with %ah = 0x02
! 252: * Return:
! 253: * %al = shift flags
! 254: * %ah - undefined by many BIOSes
! 255: */
! 256: movb $0x02, %ah
! 257: int $0x16
! 258:
! 259: /*
! 260: * We provide the ability to force CHS use without having to hold
! 261: * down the SHIFT key each boot. Just set the byte at force_chs
! 262: * to 1 (more accurately any value with either of the bottom two
! 263: * bits set, but the use of 1 is recommended).
! 264: */
! 265: force_chs = .+1
! 266: orb $0, %al
! 267:
! 268: testb $0x3, %al /* Either shift key down? */
! 269: jz no_force_chs
! 270:
! 271: decw %si /* "!Loading" indicates forced CHS */
! 272: xorb %dh, %dh /* Pretend a floppy, so no LBA use */
! 273:
! 274: no_force_chs:
! 275: /* Print pretty message */
! 276: call Lmessage
! 277:
! 278: /*
! 279: * We will use LBA reads if we have LBA support, so find out.
! 280: */
! 281:
! 282: /*
! 283: * But don't even try on floppies, OR if forcing to CHS.
! 284: *
! 285: * (We're really testing %dl, but use %dh so we can force the
! 286: * top bit to zero to force CHS boot.)
! 287: */
! 288: testb $0x80, %dh
! 289: jz no_lba
! 290:
! 291: /*
! 292: * BIOS call "INT 0x13 Extensions Installation Check"
! 293: * Call with %ah = 0x41
! 294: * %bx = 0x55AA
! 295: * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc)
! 296: * Return:
! 297: * carry set: failure
! 298: * %ah = error code (0x01, invalid func)
! 299: * carry clear: success
! 300: * %bx = 0xAA55 (must verify)
! 301: * %ah = major version of extensions
! 302: * %al (internal use)
! 303: * %cx = capabilities bitmap
! 304: * 0x0001 - extnd disk access funcs
! 305: * 0x0002 - rem. drive ctrl funcs
! 306: * 0x0004 - EDD functions with EBP
! 307: * %dx (extension version?)
! 308: */
! 309:
! 310: pushw %dx /* Save the drive number (%dl) */
! 311: movw $0x55AA, %bx
! 312: movb $0x41, %ah
! 313: int $0x13
! 314: popw %dx /* Retrieve drive number */
! 315:
! 316: jc no_lba /* Did the command work? Jump if not */
! 317: cmpw $0xAA55, %bx /* Check that bl, bh exchanged */
! 318: jne no_lba /* If not, don't have EDD extensions */
! 319: testb $0x01, %cl /* And do we have "read" available? */
! 320: jz no_lba /* Again, use CHS if not */
! 321:
! 322: /* We have LBA support, so that's the vector to use */
! 323:
! 324: movw $load_lba, load_fsblock
! 325: jmp get_going
! 326:
! 327: no_lba:
! 328: pushw %dx
! 329:
! 330: /*
! 331: * BIOS call "INT 0x13 Function 0x08" to get drive parameters
! 332: * Call with %ah = 0x08
! 333: * %dl = drive (0x80 for 1st hd, 0x81 for 2nd...)
! 334: * Return:
! 335: * carry set: failure
! 336: * %ah = err code
! 337: * carry clear: success
! 338: * %ah = 0x00
! 339: * %al = 0x00 (some BIOSes)
! 340: * %ch = 0x00 (some BIOSes)
! 341: * %ch = max-cylinder & 0xFF
! 342: * %cl = max sector | rest of max-cyl bits
! 343: * %dh = max head number
! 344: * %dl = number of drives
! 345: * (according to Ralph Brown Int List)
! 346: */
! 347: movb $0x08, %ah
! 348: int $0x13 /* We need to know heads & sectors */
! 349:
! 350: jc cant_boot /* If error, can't boot */
! 351:
! 352: movb %dh, maxheads /* Remember this */
! 353:
! 354: andb $0x3F, %cl
! 355: jz cant_boot
! 356: movb %cl, nsectors
! 357:
! 358: putc(CHAR_CHS_READ) /* Indicate (subtly) CHS reads */
! 359:
! 360: popw %dx /* Retrieve the drive number */
! 361:
! 362: get_going:
! 363: /*
! 364: * Older versions of biosboot used to set up the destination
! 365: * segment, and increase the target offset every time a number
! 366: * of blocks was read. That limits /boot to 64k.
! 367: *
! 368: * In order to support /boots > 64k, we always read to offset
! 369: * 0000 in the target segment, and just increase the target segment
! 370: * each time.
! 371: */
! 372:
! 373: /*
! 374: * We would do movl inodeblk, %eax here, but that instruction
! 375: * is 4 bytes long; add 4 bytes for data takes 8 bytes. Using
! 376: * a load immediate takes 6 bytes, and we just get installboot
! 377: * to patch here, rather than data anywhere else.
! 378: */
! 379: inodeblk = .+2
! 380: movl $0x90909090, %eax /* mov $inodeblk, %eax */
! 381:
! 382: movw $INODESEG, %bx /* Where to put /boot's inode */
! 383:
! 384: /*
! 385: * %eax - filesystem block to read
! 386: * %bx - target segment (target offset is 0000)
! 387: * %dl - BIOS drive number
! 388: */
! 389: call *load_fsblock /* This will crash'n'burn on errs */
! 390:
! 391: /*
! 392: * We now have /boot's inode in memory.
! 393: *
! 394: * /usr/include/ufs/ufs/dinode.h for the details:
! 395: *
! 396: * Offset 8 (decimal): 64-bit file size (only use low 32 bits)
! 397: * Offset 40 (decimal): list of NDADDR (12) direct disk blocks
! 398: * Offset 88 (decimal): list of NIADDR (3) indirect disk blocks
! 399: *
! 400: * NOTE: list of indirect blocks immediately follows list of
! 401: * direct blocks. We use this fact in the code.
! 402: *
! 403: * We only support loading from direct blocks plus the first
! 404: * indirect block. This is the same as the previous biosboot/
! 405: * installboot limit. Note that, with default 16,384-bytes
! 406: * filesystem blocks, the direct block list supports files up
! 407: * to 192 KB. /boot is currently around 60 KB.
! 408: *
! 409: * The on-disk format can't change (filesystems with this format
! 410: * already exist) so okay to hardcode offsets here.
! 411: *
! 412: * The nice thing about doing things with filesystem blocks
! 413: * rather than sectors is that filesystem blocks numbers have
! 414: * 32 bits, so fit into a single register (even if "e"d).
! 415: *
! 416: * Note that this code does need updating if booting from a new
! 417: * filesystem is required.
! 418: */
! 419: #define NDADDR 12
! 420: #define di_db 40 /* Not used; addr put in by instboot */
! 421: #define di_ib 88 /* Not used; run on from direct blks */
! 422:
! 423: /*
! 424: * Register usage:
! 425: *
! 426: * %eax - block number for load_fsblock
! 427: * %bx - target segment (target offset is 0000) for load_fsblock
! 428: * %dl - BIOS drive number for load_fsblock
! 429: * %esi - points to block table in inode/indirect block
! 430: * %cx - number of blocks to load within loop (i.e. from current
! 431: * block list, which is either the direct block list di_db[]
! 432: * or the indirect block list)
! 433: * %di - total number of blocks to load
! 434: */
! 435:
! 436: /*
! 437: * We would do movl inodedbl, %esi here, but that instruction
! 438: * is 4 bytes long; add 4 bytes for data takes 8 bytes. Using
! 439: * a load immediate takes 6 bytes, and we just get installboot
! 440: * to patch here, rather than in data anywhere else.
! 441: */
! 442: inodedbl = .+2
! 443: movl $0x90909090, %esi /* mov $inodedbl, %esi */
! 444: /* Now esi -> di_db[] */
! 445:
! 446: nblocks = .+1
! 447: movw $0x9090, %di /* mov nblocks, %di */
! 448: movw %di, %cx
! 449: cmpw $NDADDR, %cx
! 450: jc 1f
! 451: movw $NDADDR, %cx
! 452: 1: /* %cx = min(nblocks, $NADDR) */
! 453:
! 454: movw $(LOADADDR >> 4), %bx /* Target segment for /boot */
! 455:
! 456: load_blocks:
! 457: putc(CHAR_BLOCK_READ) /* Show progress indicator */
! 458:
! 459: cld
! 460:
! 461: /* Get the next filesystem block number into %eax */
! 462: lodsl /* %eax = *(%si++), make sure 0x66 0xad */
! 463:
! 464: pushal /* Save all 32-bit registers */
! 465:
! 466: /*
! 467: * Read a single filesystem block (will almost certainly be multiple
! 468: * disk sectors)
! 469: *
! 470: * %eax - filesystem block to read
! 471: * %bx - target segment (target offset is 0000)
! 472: * %dl - BIOS drive number
! 473: */
! 474: call *load_fsblock /* This will crash'n'burn on errs */
! 475:
! 476: popal /* Restore 32-bit registers */
! 477:
! 478: /*
! 479: * We want to put addw fs_bsize_p, %bx, which takes 4 bytes
! 480: * of code and two bytes of data.
! 481: *
! 482: * Instead, use an immediate load, and have installboot patch
! 483: * here directly.
! 484: */
! 485: /* Move on one filesystem block */
! 486: fs_bsize_p = .+2
! 487: addw $0x9090, %bx /* addw $fs_bsize_p, %bx */
! 488:
! 489: decw %di
! 490: loop load_blocks
! 491:
! 492: /* %cx == 0 ... important it stays this way (used later) */
! 493:
! 494: /*
! 495: * Finished reading a set of blocks.
! 496: *
! 497: * This was either the direct blocks, and there may or may not
! 498: * be indirect blocks to read, or it was the indirect blocks,
! 499: * and we may or may not have read in all of /boot. (Ideally
! 500: * will have read in all of /boot.)
! 501: */
! 502: orw %di, %di
! 503: jz done_load /* No more sectors to read */
! 504:
! 505: /* We have more blocks to load */
! 506:
! 507: /* We only support a single indirect block (the same as previous
! 508: * versions of installboot. This is required for the boot floppies.
! 509: *
! 510: * We use a bit of the code to store a flag that indicates
! 511: * whether we have read the first indirect block or not.
! 512: *
! 513: * If we've already read the indirect list, we can't load this /boot.
! 514: *
! 515: * indirect uint8 0 => running through load_blocks loop reading
! 516: * direct blocks. If != 0, we're reading the
! 517: * indirect blocks. Must use a field that is
! 518: * initialised to 0.
! 519: */
! 520: indirect = .+2
! 521: movw $PBR_TOO_MANY_INDIRECTS, %ax /* movb $PRB_TOO..., %al */
! 522: /* movb indirect, %ah */
! 523: orb %ah, %ah
! 524: jnz err_print_crlf
! 525:
! 526: incb indirect /* No need to worry about wrap */
! 527: /* around, as this will only be done */
! 528: /* once before we fail */
! 529:
! 530: /* Okay, let's read in the indirect block */
! 531:
! 532: lodsl /* Get blk num of 1st indirect blk */
! 533:
! 534: pushw %bx /* Remember where we got to */
! 535: movw $INODESEG, %bx
! 536: call *load_fsblock /* This will crash'n'burn on errs */
! 537: popw %bx /* Indirect blocks get added on to */
! 538: /* just after where we got to */
! 539: movl $INODEOFF, %esi
! 540: movw %di, %cx /* How many blocks left to read */
! 541:
! 542: jmp load_blocks
! 543:
! 544: done_load:
! 545: puts(crlf)
! 546:
! 547: /* %cx == 0 from loop above... keep it that way */
! 548:
! 549: /*
! 550: * Check the magic signature at the beginning of /boot.
! 551: * Since /boot is now ELF, this should be 0xFF E L F.
! 552: */
! 553: movw $(LOADADDR >> 4), %ax /* Target segment */
! 554: movw %ax, %es
! 555:
! 556: /*
! 557: * We cheat a little here, and only check the L and F.
! 558: *
! 559: * (Saves 3 bytes of code... the two signature bytes we
! 560: * don't check, and the operand size prefix that's not
! 561: * needed.)
! 562: */
! 563: cmpw $LFMAGIC, %es:2(,1)
! 564: je exec_boot
! 565:
! 566: movb $PBR_BAD_MAGIC, %al
! 567:
! 568: err_print:
! 569: movw $err_txt, %si
! 570: err_print2:
! 571: movb %al, err_id
! 572: err_stop:
! 573: call Lmessage
! 574: stay_stopped:
! 575: sti /* Ensure Ctl-Alt-Del will work */
! 576: hlt /* (don't require power cycle) */
! 577: jmp stay_stopped /* Just to make sure :-) */
! 578:
! 579: exec_boot:
! 580: /* At this point we could try to use the entry point in
! 581: * the image we just loaded. But if we do that, we also
! 582: * have to potentially support loading that image where it
! 583: * is supposed to go. Screw it, just assume that the image
! 584: * is sane.
! 585: */
! 586: #ifdef BDEBUG
! 587: putc('P')
! 588: #endif
! 589:
! 590: /* %cx == 0 from loop above... keep it that way */
! 591:
! 592: /*
! 593: * We want to do movzbl %dl, %eax ; pushl %eax to zero-extend the
! 594: * drive number to 32 bits and pass it to /boot. However, this
! 595: * takes 6 bytes.
! 596: *
! 597: * Doing it this way saves 2 bytes.
! 598: */
! 599: pushw %cx
! 600: movb %dl, %cl
! 601: pushw %cx
! 602:
! 603: pushl $BOOTMAGIC /* use some magic */
! 604:
! 605: /* jmp /boot */
! 606: ljmp $(LINKADDR >> 4), $0
! 607: /* not reached */
! 608:
! 609:
! 610: /*
! 611: * Load a single filesystem block into memory using CHS calls.
! 612: *
! 613: * Input: %eax - 32-bit filesystem block number
! 614: * %bx - target segment (target offset is 0000)
! 615: * %dl - BIOS drive number
! 616: *
! 617: * Output: block successfully read in (panics if not)
! 618: * all general purpose registers may have been trashed
! 619: */
! 620: load_chs:
! 621: /*
! 622: * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into
! 623: * memory.
! 624: * Call with %ah = 0x42
! 625: * %ah = 0x2
! 626: * %al = number of sectors
! 627: * %ch = cylinder & 0xFF
! 628: * %cl = sector (0-63) | rest of cylinder bits
! 629: * %dh = head
! 630: * %dl = drive (0x80 for 1st hd, 0x81 for 2nd...)
! 631: * %es:%bx = segment:offset of buffer
! 632: * Return:
! 633: * carry set: failure
! 634: * %ah = err code
! 635: * %al = number of sectors transferred
! 636: * carry clear: success
! 637: * %al = 0x0 OR number of sectors transferred
! 638: * (depends on BIOS!)
! 639: * (according to Ralph Brown Int List)
! 640: */
! 641:
! 642: /* Convert the filesystem block into a sector value */
! 643: call fsbtosector
! 644: movl lba_sector, %eax /* we can only use 24 bits, really */
! 645:
! 646: movw fs_bsize_s, %cx /* sectors per filesystem block */
! 647:
! 648: /*
! 649: * Some BIOSes require that reads don't cross track boundaries.
! 650: * Therefore we do all CHS reads single-sector.
! 651: */
! 652: calc_chs:
! 653: pushal
! 654: movw %bx, %es /* Set up target segment */
! 655:
! 656: pushw %dx /* Save drive number (in %dl) */
! 657: xorl %edx, %edx
! 658: movl %edx, %ecx
! 659:
! 660: nsectors = .+1
! 661: movb $0x90, %cl /* movb $nsectors, %cl */
! 662: /* Doing it this way saves 4-2 = 2 bytes code */
! 663: /* bytes (no data, since we would overload) */
! 664:
! 665: divl %ecx, %eax
! 666: /* Now have sector number in %dl */
! 667: pushw %dx /* Remember for later */
! 668:
! 669: xorl %edx, %edx
! 670:
! 671: maxheads = .+1
! 672: movb $0x90, %cl /* movb $maxheads, %cl; 0 <= maxheads <= 255 */
! 673: /* Doing it this way saves 4-2 = 2 code */
! 674: /* bytes (no data, since we would overload */
! 675:
! 676: incw %cx /* Number of heads is 1..256, no "/0" worries */
! 677:
! 678: divl %ecx, %eax
! 679: /* Have head number in %dl */
! 680: /* Cylinder number in %ax */
! 681: movb %al, %ch /* Bottom 8 bits of cyl number */
! 682: shlb $6, %ah /* Move up top 2 bits of cyl number */
! 683: movb %ah, %cl /* Top 2 bits of cyl number in here */
! 684:
! 685: popw %bx /* (pushed %dx, but need %dl for now */
! 686: incb %bl /* Sector numbers run from 1, not 0 */
! 687: orb %bl, %cl /* Or the sector number into top bits cyl */
! 688:
! 689: /* Remember, %dl has head number */
! 690: popw %ax
! 691: /* %al has BIOS drive number -> %dl */
! 692:
! 693: movb %dl, %dh /* Now %dh has head number (from 0) */
! 694: movb %al, %dl /* Now %dl has BIOS drive number */
! 695:
! 696: xorw %bx, %bx /* Set up target offset */
! 697:
! 698: movw $0x0201, %ax /* %al = 1 - read one sector at a time */
! 699: /* %ah = 2 - int 0x13 function for CHS read */
! 700:
! 701: call do_int_13 /* saves us 1 byte :-) */
! 702:
! 703: /* Get the next sector */
! 704:
! 705: popal
! 706: incl %eax
! 707: addw $32, %bx /* Number of segments/paras in a sector */
! 708: loop calc_chs
! 709:
! 710: ret
! 711:
! 712: /* read error */
! 713: read_error:
! 714: movb $PBR_READ_ERROR, %al
! 715: err_print_crlf:
! 716: movw $err_txt_crlf, %si
! 717: jmp err_print2
! 718:
! 719:
! 720: /*
! 721: * Load a single filesystem block into memory using LBA calls.
! 722: *
! 723: * Input: %eax - 32-bit filesystem block number
! 724: * %bx - target segment (target offset is 0000)
! 725: * %dl - BIOS drive number
! 726: *
! 727: * Output: block successfully read in (panics if not)
! 728: * all general purpose registers may have been trashed
! 729: */
! 730: load_lba:
! 731: /*
! 732: * BIOS call "INT 0x13 Extensions Extended Read"
! 733: * Call with %ah = 0x42
! 734: * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc)
! 735: * %ds:%si = segment:offset of command packet
! 736: * Return:
! 737: * carry set: failure
! 738: * %ah = error code (0x01, invalid func)
! 739: * command packet's sector count field set
! 740: * to the number of sectors successfully
! 741: * transferred
! 742: * carry clear: success
! 743: * %ah = 0 (success)
! 744: * Command Packet:
! 745: * 0x0000 BYTE packet size (0x10 or 0x18)
! 746: * 0x0001 BYTE reserved (should be 0)
! 747: * 0x0002 WORD sectors to transfer (max 127)
! 748: * 0x0004 DWORD seg:offset of transfer buffer
! 749: * 0x0008 QWORD starting sector number
! 750: */
! 751: call fsbtosector /* Set up lba_sector & lba_sector+4 */
! 752:
! 753: /* movb %dh, lba_count <- XXX done by installboot */
! 754: movw %bx, lba_seg
! 755: movw $lba_command, %si
! 756: movb $0x42, %ah
! 757: do_int_13:
! 758: int $0x13
! 759: jc read_error
! 760:
! 761: ret
! 762:
! 763:
! 764: /*
! 765: * Converts a given filesystem block number into a disk sector
! 766: * at lba_sector and lba_sector+4.
! 767: *
! 768: * Input: %eax - 32-bit filesystem block number
! 769: *
! 770: * Output: lba_sector and lba_sector+4 set up
! 771: * XXX
! 772: */
! 773: fsbtosector:
! 774: /*
! 775: * We want to do
! 776: *
! 777: * movb fsbtodb, %ch /# Shift counts we'll need #/
! 778: * movb $32, %cl
! 779: *
! 780: * which is 6 bytes of code + 1 byte of data.
! 781: *
! 782: * We'll actually code it with an immediate 16-bit load into %cx,
! 783: * which is just 3 bytes of data (saves 4 bytes).
! 784: */
! 785: fsbtodb = .+2
! 786: movw $0x9020, %cx /* %ch = fsbtodb, %cl = 0x20 */
! 787:
! 788: pushl %eax
! 789: subb %ch, %cl
! 790: shrl %cl, %eax
! 791: movl %eax, lba_sector+4
! 792: popl %eax
! 793:
! 794: movb %ch, %cl
! 795: shll %cl, %eax
! 796:
! 797: /*
! 798: * And add p_offset, which is the block offset to the start
! 799: * of the filesystem.
! 800: *
! 801: * We would do addl p_offset, %eax, which is 5 bytes of code
! 802: * and 4 bytes of data, but it's more efficient to have
! 803: * installboot patch directly in the code (this variable is
! 804: * only used here) for 6 bytes of code (but no data).
! 805: */
! 806: p_offset = .+2
! 807: addl $0x90909090, %eax /* addl $p_offset, %eax */
! 808:
! 809: movl %eax, lba_sector
! 810: jnc 1f
! 811:
! 812: incl lba_sector+4
! 813: 1:
! 814: ret
! 815:
! 816:
! 817: /*
! 818: * Display string
! 819: */
! 820: Lmessage:
! 821: cld
! 822: 1:
! 823: lodsb /* load a byte into %al */
! 824: orb %al, %al
! 825: jz 1f
! 826: call Lchr
! 827: jmp 1b
! 828:
! 829: /*
! 830: * Lchr: write the character in %al to console
! 831: */
! 832: Lchr:
! 833: #ifdef SERIAL
! 834: pushw %dx
! 835: movb $0x01, %ah
! 836: xorw %dx, %dx
! 837: movb SERIAL, %dl
! 838: int $0x14
! 839: popw %dx
! 840: #else
! 841: pushw %bx
! 842: movb $0x0e, %ah
! 843: xorw %bx, %bx
! 844: incw %bx /* movw $0x01, %bx */
! 845: int $0x10
! 846: popw %bx
! 847: #endif
! 848: 1:
! 849: ret
! 850:
! 851: /* .data */
! 852:
! 853: /* vector to the routine to read a particular filesystem block for us */
! 854: load_fsblock:
! 855: .word load_chs
! 856:
! 857:
! 858: /* This next block is used for the EDD command packet used to read /boot
! 859: * sectors.
! 860: *
! 861: * lba_count is set up for us by installboot. It is the number of sectors
! 862: * in a filesystem block. (Max value 127.)
! 863: *
! 864: * XXX The EDD limit of 127 sectors in one read means that we currently
! 865: * restrict filesystem blocks to 127 sectors, or < 64 KB. That is
! 866: * effectively a 32 KB block limit, as filesystem block sizes are
! 867: * powers of two. The default filesystem block size is 16 KB.
! 868: *
! 869: * I say we run with this limitation and see where it bites us...
! 870: */
! 871:
! 872: lba_command:
! 873: .byte 0x10 /* size of command packet */
! 874: .byte 0x00 /* reserved */
! 875: fs_bsize_s:
! 876: lba_count:
! 877: .word 0 /* sectors to transfer, max 127 */
! 878: .word 0 /* target buffer, offset */
! 879: lba_seg:
! 880: .word 0 /* target buffer, segment */
! 881: lba_sector:
! 882: .long 0, 0 /* sector number */
! 883:
! 884: load_msg:
! 885: .asciz "!Loading"
! 886: err_txt_crlf:
! 887: .ascii "\r\n"
! 888: err_txt:
! 889: .ascii "ERR "
! 890: err_id:
! 891: .ascii "?"
! 892: crlf: .asciz "\r\n"
! 893:
! 894: . = 0x200 - 2
! 895: /* a little signature */
! 896: .word DOSMBR_SIGNATURE
CVSweb