Annotation of sys/arch/i386/stand/biosboot/biosboot.S, Revision 1.1.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