mbr.S revision 1.2 1 /* $NetBSD: mbr.S,v 1.2 2003/04/29 10:24:24 dsl Exp $ */
2
3 /*
4 * Copyright (c) 1999-2003 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Frank van der Linden.
9 * Major surgery performed by David Laight.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by TooLs GmbH.
22 * 4. The name of TooLs GmbH may not be used to endorse or promote products
23 * derived from this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
26 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28 * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
31 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
33 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
34 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36
37 /*
38 * i386 master boot code
39 */
40
41 /* Compile options:
42 * BOOTSEL - bootselector code
43 * BOOT_EXTENDED - scan extended partition list (LBA reads)
44 * TERSE_ERROR - terse error messages
45 * NO_CHS - all reads are LBA
46 * NO_LBA_CHECK - no check if bios supports LBA reads
47 */
48
49 #include <machine/asm.h>
50 #include <sys/disklabel_mbr.h>
51
52 #define BOOTADDR 0x7c00
53 #define LOADADDR 0x0600 /* address were are linked to */
54
55 #define TABENTRYSIZE (PARTNAMESIZE + 1)
56 #define NAMETABSIZE (NMBRPART * TABENTRYSIZE)
57
58 /*
59 * Minimum and maximum drive number that is considered to be valid.
60 */
61 #define MINDRV 0x80
62 #define MAXDRV 0x87
63
64 #ifdef TERSE_ERROR
65 /*
66 * Error codes. Done this way to save space.
67 */
68 #define ERR_INVPART '1' /* Invalid partition table */
69 #define ERR_READ '2' /* Read error */
70 #define ERR_NOOS '3' /* Magic no. check failed for part. */
71 #define ERR_KEY '?' /* unknown key press */
72 #define ERR_NO_LBA 'L' /* sector above chs limit */
73
74 #define set_err(err) movb $err, %al
75
76 #else
77 #define set_err(err) mov $err, %ax
78 #endif
79
80 .text
81 .code16
82 /*
83 * Move ourselves out of the way first.
84 * (to the address we are linked at - 0x600)
85 * and zero our bss
86 */
87 ENTRY(start)
88 xor %ax, %ax
89 mov %ax, %ss
90 movw $BOOTADDR, %sp
91 mov %ax, %es
92 mov %ax, %ds
93 mov %sp, %si
94 movw $start, %di
95 movw $(bss_start - start)/2, %cx
96 rep
97 movsw
98 mov $(bss_end - bss_start + 1)/2, %cx
99 rep
100 stosw
101 ljmp $0, $mbr /* leap into copy of code */
102
103 /*
104 * Sanity check the drive number passed by the BIOS. Some BIOSs may not
105 * do this and pass garbage.
106 */
107 mbr:
108 cmpb $MAXDRV, %dl /* relies on MINDRV being 0x80 */
109 jle 1f
110 movb $MINDRV, %dl /* garbage in, boot disk 0 */
111 1:
112 push %dx /* save drive number */
113 push %dx /* twice - for err_msg loop */
114
115 #ifdef BOOTSEL
116 /*
117 * Walk through the selector (name) table printing used entries.
118 */
119 bootsel_menu:
120 movw $nametab, %bx
121 #ifdef BOOT_EXTENDED
122 xorl %ecx, %ecx /* base of extended partition */
123 next_extended:
124 xorl %edx, %edx /* for next extended partition */
125 #endif
126 lea parttab - nametab(%bx), %bp
127 next_ptn:
128 movb 4(%bp), %al /* partition type */
129 #ifdef BOOT_EXTENDED
130 movl 8(%bp), %edi /* partition sector number */
131 cmpb $MBR_PTYPE_EXT, %al /* Extended partition */
132 je 1f
133 cmpb $MBR_PTYPE_EXT_LBA, %al /* Extended LBA partition */
134 je 1f
135 cmpb $MBR_PTYPE_EXT_LNX, %al /* Linux extended partition */
136 jne 2f
137 1: movl %edi, %edx /* save next extended ptn */
138 jmp 3f
139 2:
140 #endif
141 test %al, %al /* undefined partition */
142 je 3f
143 cmpb $0, (%bx) /* check for prompt */
144 jz 3f
145
146 /* output menu item */
147 movw $prefix, %si
148 incb (%si)
149 call message /* menu number */
150 mov (%si), %si /* ':' << 8 | '1' + count */
151 shl $2, %si /* const + count * 4 */
152 #define CONST (4 * ((':' << 8) + '1' - ((SCAN_1 - SCAN_F1) & 0xff)))
153 #ifdef NO_CHS
154 addl lba_sector, %edi
155 movl %edi, ptn_list - CONST(%si) /* sector to read */
156 #else
157 mov %bp, ptn_list - CONST(%si) /* partition info */
158 #endif
159 #undef CONST
160 mov %bx, %si
161 call message /* prompt */
162 movw $crlf, %si
163 call message
164 3:
165 add $0x10, %bp
166 add $TABENTRYSIZE, %bx
167 cmpb $(nametab - start - 0x100) + 4 * TABENTRYSIZE, %bl
168 jne next_ptn
169
170 #ifdef BOOT_EXTENDED
171 /*
172 * Now check extended partition chain
173 */
174 testl %edx, %edx
175 je wait_key
176 testl %ecx, %ecx
177 jne 1f
178 xchg %ecx, %edx /* save base of ext ptn chain */
179 1: addl %ecx, %edx /* sector to read */
180 movl %edx, lba_sector
181 movw $lba_info, %si
182 movb $0x42, %ah
183 pop %dx /* recover drive # */
184 push %dx /* save drive */
185 int $0x13
186 jc wait_key /* abort menu on read fail */
187 cmpw $MBR_MAGIC, LOADADDR + MBR_MAGICOFF
188 movw $nametab - LOADADDR + BOOTADDR, %bx
189 je next_extended
190 #endif
191
192 /*
193 * Get the initial time value for the timeout comparison. It is returned
194 * by int 1a in cx:dx. We do sums modulo 2^16 so it doesn't matter if
195 * the counter wraps (which it does every hour) - so we can safely
196 * ignore 'cx'.
197 *
198 * Loop around checking for a keypress until we have one, or timeout is
199 * reached.
200 */
201 wait_key:
202 xorb %ah, %ah
203 int $0x1a
204 mov %dx, %di /* start time to di */
205 3:
206 movb $1, %ah /* looks to see if a */
207 int $0x16 /* key has been pressed */
208 jnz get_key
209 xorb %ah, %ah
210 int $0x1a /* current time to cx:dx */
211 sub %di, %dx
212 movw timeout, %ax
213 cmp %ax, %dx /* always wait for 1 tick... */
214 jbe 3b /* 0xffff means never timeout */
215 def_key:
216 movb defkey, %al /* timedout - pick default key */
217 jmp check_key
218 get_key:
219 xorb %ah, %ah
220 int $0x16 /* 'read key', code ah, ascii al */
221 shr $8, %ax /* code in %al, %ah zero */
222
223 /*
224 * We have a keycode, see what it means.
225 * If we don't know we generate error '?' and go ask again
226 */
227 check_key:
228 /*
229 * <enter> -> boot active partition.
230 */
231 cmpb $SCAN_ENTER, %al
232 jne boot_disk
233 #endif /* BOOTSEL */
234
235 /*
236 * Scan MBR for first active partition, and boot it.
237 */
238 mov $NMBRPART, %cx
239 mov $parttab, %si
240 1:
241 cmpb $0x80, (%si)
242 #ifdef NO_CHS
243 jne 10f /* not active */
244 movl 8(%si), %ebp /* sector number of ptn */
245 jmp boot_lba
246 10:
247 #else
248 je boot_si
249 #endif
250 add $0x10, %si
251 loop 1b
252 set_err(ERR_INVPART)
253 jmp err_msg
254
255 #ifdef BOOTSEL
256 /*
257 * F1-F10 -> boot disk 0-9. Check if the requested disk isn't above
258 * the number of disks actually in the system as stored in 0:0475 by
259 * the BIOS.
260 * If we trust loc 475, we needn't check the upper bound on the keystroke
261 * This is always sector 0, so always read using chs.
262 */
263 boot_disk:
264 subb $SCAN_F1, %al
265 cmpb 0x0475, %al
266 jae boot_ptn
267 addb $0x80, %al
268 pop %dx /* dump saved drive # */
269 push %ax /* replace with new */
270 #ifdef NO_CHS
271 xorl %ebp, %ebp /* read sector number 0 */
272 jmp boot_lba
273 #else
274 movw $chs_zero, %si /* chs read sector zero info */
275 jmp read_chs
276 #endif
277
278 /*
279 * Boot requested partition.
280 * Use keycode to index the table we generated when we scanned the mbr
281 * while generating the menu.
282 *
283 * We very carfully saved the values in the correct part of the table.
284 */
285
286 boot_ptn:
287 shl $2, %ax
288 movw %ax, %si
289 #ifdef NO_CHS
290 movl ptn_list(%si), %ebp
291 testl %ebp, %ebp
292 jnz boot_lba
293 #else
294 mov ptn_list(%si), %si
295 test %si, %si
296 jnz boot_si
297 #endif
298 set_err(ERR_KEY)
299 /* jmp err_msg */
300 #endif /* BOOTSEL */
301
302 /* Something went wrong...
303 * Output error code,
304 * reset disk subsystem - needed after read failure,
305 * and wait for user key
306 */
307 err_msg:
308 #ifdef TERSE_ERROR
309 movb %al, errcod
310 movw $errtxt, %si
311 call message
312 #else
313 push %ax
314 movw $errtxt, %si
315 call message
316 pop %si
317 call message
318 movw $crlf, %si
319 call message
320 #endif
321 pop %dx /* drive we errored on */
322 xor %ax,%ax /* only need %ah = 0 */
323 int $0x13 /* reset disk subsystem */
324 #ifdef BOOTSEL
325 pop %dx /* original drive number */
326 push %dx
327 push %dx
328 jmp get_key
329 #else
330 int $0x18 /* BIOS might ask for a key */
331 /* press and retry boot seq. */
332 1: sti
333 hlt
334 jmp 1b
335 #endif
336
337 #ifndef NO_CHS
338 /*
339 * Active partition pointed to by si.
340 * Read the first sector.
341 *
342 * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical
343 * Block Address) read. Always doing the LBA one
344 * would be nice - unfortunately not all systems support it.
345 * Also some may contain a separate (eg SCSI) bios that doesn't
346 * support it even when the main bios does.
347 *
348 * There is also the additional problem that the CHS values may be wrong
349 * (eg if fdisk was run on a different system that used different BIOS
350 * geometry). We convert the CHS value to a LBA sector number using
351 * the geometry from the BIOS, if the number matches we do a CHS read.
352 */
353 boot_si:
354 movl 8(%si), %ebp /* get sector # */
355
356 testb $BFL_READ_LBA, flags
357 jnz boot_lba /* fdisk forced LBA read */
358
359 pop %dx /* collect saved drive... */
360 push %dx /* ...number to dl */
361 movb $8, %ah
362 int $0x13 /* chs info */
363
364 /*
365 * Validate geometry, if the CHS sector number doesn't match the LBA one
366 * we'll do an LBA read.
367 * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector
368 * and compare against LBA sector number.
369 * Take a slight 'flier' and assume we can just check 16bits (very likely
370 * to be true because the number of sectors per track is 63).
371 */
372 movw 2(%si), %ax /* cylinder + sector */
373 push %ax /* save for sector */
374 shr $6, %al
375 xchgb %al, %ah /* 10 bit cylinder number */
376 shr $8, %dx /* last head */
377 inc %dx /* number of heads */
378 mul %dx
379 mov 1(%si), %dl /* head we want */
380 add %dx, %ax
381 and $0x3f, %cx /* number of sectors */
382 mul %cx
383 pop %dx /* recover sector we want */
384 and $0x3f, %dx
385 add %dx, %ax
386 dec %ax
387
388 cmp %bp, %ax
389 je read_chs
390
391 #ifndef NO_LBA_CHECK
392 /*
393 * Determine whether we have int13-extensions, by calling int 13, function 41.
394 * Check for the magic number returned, and the disk packet capability.
395 */
396 movw $0x55aa, %bx
397 movb $0x41, %ah
398 pop %dx
399 push %dx
400 int $0x13
401 jc 1f /* no int13 extensions */
402 cmpw $0xaa55, %bx
403 jnz 1f
404 testb $1, %cl
405 jnz boot_lba
406 1: set_err(ERR_NO_LBA)
407 jmp err_msg
408 #endif /* NO_LBA_CHECK */
409 #endif /* NO_CHS */
410
411 /*
412 * Save sector number (passed in %ebp) into lba parameter block,
413 * read the sector and leap into it.
414 */
415 boot_lba:
416 movl %ebp, lba_sector /* save sector number */
417 movw $lba_info, %si
418 movb $0x42, %ah
419 pop %dx /* recover drive # */
420 do_read:
421 push %dx /* save drive */
422 int $0x13
423
424 set_err(ERR_READ)
425 jc err_msg
426
427 /*
428 * Check signature for valid bootcode
429 */
430 movb BOOTADDR, %al /* first byte non-zero */
431 test %al, %al
432 jz 1f
433 movw BOOTADDR + MBR_MAGICOFF, %ax
434 1: cmp $MBR_MAGIC, %ax
435 set_err(ERR_NOOS)
436 jnz err_msg
437
438 /* We pass the sector number through to the next stage boot.
439 * It doesn't have to use it (indeed no other mbr code will generate) it,
440 * but it does let us have a NetBSD pbr that can identify where it was
441 * read from! This lets us use this code to select between two
442 * NetBSD system on the same physical driver.
443 * (If we've read the mbr of a different disk, it gets a random number
444 * - but it wasn't expecting anything...)
445 */
446 movl %ebp, %esi
447 pop %dx /* recover drive # */
448 jmp start - LOADADDR + BOOTADDR
449
450
451 #ifndef NO_CHS
452 /*
453 * Sector below CHS limit
454 * Do a cylinder-head-sector read instead.
455 */
456 read_chs:
457 pop %dx /* recover drive # */
458 movb 1(%si), %dh /* head */
459 movw 2(%si), %cx /* ch=cyl, cl=sect */
460 movw $BOOTADDR, %bx /* es:bx is buffer */
461 movw $0x201, %ax /* command 2, 1 sector */
462 jmp do_read
463 #endif
464
465 /*
466 * Control block for int-13 LBA read.
467 * We need a xx, 00, 01, 00 somewhere to load chs for sector zero,
468 * by a complete fluke there is one here!
469 */
470 chs_zero:
471 lba_info:
472 .word 0x10 /* control block length */
473 .word 1 /* sector count */
474 .word BOOTADDR /* offset in segment */
475 .word 0 /* segment */
476 lba_sector:
477 .long 0x0000 /* sector # goes here... */
478 .long 0x0000
479
480 errtxt: .ascii "Error " /* runs into crlf if errcod set */
481 errcod: .byte 0
482 crlf: .asciz "\r\n"
483
484 #ifdef BOOTSEL
485 prefix: .asciz "0: "
486 #endif
487
488 #ifndef TERSE_ERROR
489 ERR_INVPART: .asciz "No active partition"
490 ERR_READ: .asciz "Disk read error"
491 ERR_NOOS: .asciz "No operating system"
492 #ifndef NO_LBA_CHECK
493 ERR_NO_LBA: .asciz "Invalid CHS read"
494 #endif
495 #ifdef BOOTSEL
496 ERR_KEY: .asciz "bad key"
497 #endif
498 #endif
499
500 /*
501 * I hate #including source files, but the stuff below has to be at
502 * the correct absolute address.
503 * Clearly this could be done with a linker script.
504 */
505
506 #include <message.S>
507 #if 0
508 #include <dump_eax.S>
509 #endif
510
511 /*
512 * Stuff from here on is overwritten by fdisk - the offset must not change...
513 *
514 * Get amount of space to makefile can report it.
515 * (Unfortunately I can't seem to get the value reported when it is -ve)
516 */
517 mbr_space = defkey - .
518 . = start + MBR_BOOTSELOFF
519 /*
520 * Default action, as a keyvalue we'd normally read from the BIOS.
521 */
522 defkey:
523 .byte SCAN_ENTER /* ps/2 code */
524 #ifndef BOOTSEL_FLAGS
525 #define BOOTSEL_FLAGS 0
526 #endif
527 flags: .byte BFL_NEWMBR | BOOTSEL_FLAGS
528 /*
529 * Timeout value. ~65536 ticks per hour, which is ~18.2 times per second.
530 * 0xffff means never timeout.
531 */
532 timeout:
533 .word 182 /* default to 10 seconds */
534 /*
535 * Space for name/select table and partition table.
536 */
537 nametab:
538 .fill NMBRPART * (PARTNAMESIZE + 1), 0x01, 0x00
539
540 . = start + MBR_PARTOFF - 2
541 .word MBR_MAGIC
542
543 . = start + MBR_PARTOFF
544 parttab:
545 .fill 0x40, 0x01, 0x00
546
547 . = start + MBR_MAGICOFF
548 .word MBR_MAGIC
549
550 /* zeroed data space */
551 bss_off = 0
552 bss_start = .
553 #define BSS(name, size) name = bss_start + bss_off; bss_off = bss_off + size
554 BSS(ptn_list, 256 * 4) /* long[]: boot sector numbers */
555 BSS(bss_end, 0)
556