unstack - fix ipcvecs
[minix.git] / sys / arch / i386 / stand / mbr / mbr.S
blob43281ce3df10971c8c3a3de45ae93ef8812107fe
1 /*      $NetBSD: mbr.S,v 1.23 2010/12/08 21:56:42 jakllsch Exp $        */
3 /*
4  * Copyright (c) 1999-2004 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, based on an earlier work by Wolfgang Solfrank.
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  *
20  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
34  * i386 master boot code
35  */
37 /* Compile options:
38  * BOOTSEL      - bootselector code
39  * BOOT_EXTENDED - scan extended partition list (LBA reads)
40  * COM_PORT     - do serial io to specified port number
41  *                0..3 => bios port, otherwise actual io_addr
42  * COM_BAUD     - initialise serial port baud rate
43  *
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  * NO_BANNER    - do not output title line 'banner'
48  */
50 #ifdef BOOT_EXTENDED
51 #define NO_CHS  1
52 #define BOOTSEL 1
53 #endif
55 #ifdef COM_PORT
56 #if COM_PORT < 4
57 /* The first 4 items in the 40:xx segment are the serial port base addresses */
58 #define COM_PORT_VAL (0x400 + (COM_PORT * 2))
59 #else
60 #define COM_PORT_VAL $COM_PORT
61 #endif
63 #if !defined(COM_FREQ)
64 #define COM_FREQ 1843200
65 #endif
66 #else
67 #undef COM_BAUD
68 #endif
70 #ifdef BOOTSEL
71 #define TERSE_ERROR 1
72 #endif
74 #include <machine/asm.h>
75 #include <sys/bootblock.h>
77 #define BOOTADDR        0x7c00          /* where we get loaded to */
79 #define TABENTRYSIZE    (MBR_BS_PARTNAMESIZE + 1)
80 #define NAMETABSIZE     (MBR_PART_COUNT * TABENTRYSIZE)
82 #ifdef COM_PORT
83 /* ASCII values for the keys */
84 #define KEY_ACTIVE      '\r'
85 #define KEY_DISK1       'a'
86 #define KEY_PTN1        '1'
87 #else
88 /* Scan values for the various keys we use, as returned by the BIOS */
89 #define SCAN_ENTER      0x1c
90 #define SCAN_F1         0x3b
91 #define SCAN_1          0x2
93 #define KEY_ACTIVE      SCAN_ENTER
94 #define KEY_DISK1       SCAN_F1
95 #define KEY_PTN1        SCAN_1
96 #endif
99  * Minimum and maximum drive number that is considered to be valid.
100  */
101 #define MINDRV          0x80
102 #define MAXDRV          0x8f
104 #ifdef TERSE_ERROR
106  * Error codes. Done this way to save space.
107  */
108 #define ERR_INVPART     '1'             /* Invalid partition table */
109 #define ERR_READ        '2'             /* Read error */
110 #define ERR_NOOS        '3'             /* Magic no. check failed for part. */
111 #define ERR_KEY         '?'             /* unknown key press */
112 #define ERR_NO_LBA      'L'             /* sector above chs limit */
114 #define set_err(err)    movb    $err, %al
116 #else
117 #define set_err(err)    mov     $err, %ax
118 #endif
120         .text
121         .code16
123  * Move ourselves out of the way first.
124  * (to the address we are linked at)
125  * and zero our bss
126  */
127 ENTRY(start)
128         xor     %ax, %ax
129         mov     %ax, %ss
130         movw    $BOOTADDR, %sp
131         mov     %ax, %es
132         mov     %ax, %ds
133         movw    $mbr, %di
134         mov     $BOOTADDR + (mbr - start), %si
135         push    %ax                     /* zero for %cs of lret */
136         push    %di
137         movw    $(bss_start - mbr), %cx
138         rep
139         movsb                           /* relocate code */
140         mov     $(bss_end - bss_start + 511)/512, %ch
141         rep
142         stosw                           /* zero bss */
143         lret                            /* Ensures %cs == 0 */
146  * Sanity check the drive number passed by the BIOS. Some BIOSs may not
147  * do this and pass garbage.
148  */
149 mbr:
150         cmpb    $MAXDRV, %dl            /* relies on MINDRV being 0x80 */
151         jle     1f
152         movb    $MINDRV, %dl            /* garbage in, boot disk 0 */
154         push    %dx                     /* save drive number */
155         push    %dx                     /* twice - for err_msg loop */
157 #if defined(COM_BAUD)
158         mov     $com_args, %si
159         mov     $num_com_args, %cl      /* %ch is zero from above */
160         mov     COM_PORT_VAL, %dx
161 1:      lodsw
162         add     %ah, %dl
163         outb    %dx
164         loop    1b
165 #endif
167 #ifndef NO_BANNER
168         mov     $banner, %si
169         call    message_crlf
170 #endif
173  * Walk through the selector (name) table printing used entries.
175  * Register use:
176  * %ax                  temp
177  * %bx  nametab[]       boot seletor menu
178  * %ecx                 base of 'extended' partition
179  * %edx                 next extended partition
180  * %si                  message ptr (etc)
181  * %edi                 sector number of this partition
182  * %bp  parttab[]       mbr partition table
183  */
184 bootsel_menu:
185         movw    $nametab, %bx
186 #ifdef BOOT_EXTENDED
187         xorl    %ecx, %ecx              /* base of extended partition */
188 next_extended:
189         xorl    %edx, %edx              /* for next extended partition */
190 #endif
191         lea     parttab - nametab(%bx), %bp
192 next_ptn:
193         movb    4(%bp), %al             /* partition type */
194 #ifdef NO_CHS
195         movl    8(%bp), %edi            /* partition sector number */
196 #ifdef BOOT_EXTENDED
197         cmpb    $MBR_PTYPE_EXT, %al     /* Extended partition */
198         je      1f
199         cmpb    $MBR_PTYPE_EXT_LBA, %al /* Extended LBA partition */
200         je      1f
201         cmpb    $MBR_PTYPE_EXT_LNX, %al /* Linux extended partition */
202         jne     2f
203 1:      movl    %edi, %edx              /* save next extended ptn */
204         jmp     4f
206 #endif
207         addl    lba_sector, %edi        /* add in extended ptn base */
208 #endif
209         test    %al, %al                /* undefined partition */
210         je      4f
211         cmpb    $0x80, (%bp)            /* check for active partition */
212         jne     3f                      /* jump if not... */
213 #define ACTIVE  (4 * ((KEY_ACTIVE - KEY_DISK1) & 0xff))
214 #ifdef NO_CHS
215         movl    %edi, ptn_list + ACTIVE /* save location of active ptn */
216 #else
217         mov     %bp, ptn_list + ACTIVE
218 #endif
219 #undef ACTIVE
221 #ifdef BOOTSEL
222         cmpb    $0, (%bx)               /* check for prompt */
223         jz      4f
224         /* output menu item */
225         movw    $prefix, %si
226         incb    (%si)
227         call    message                 /* menu number */
228         mov     (%si), %si              /* ':' << 8 | '1' + count */
229         shl     $2, %si                 /* const + count * 4 */
230 #define CONST   (4 * ((':' << 8) + '1' - ((KEY_PTN1 - KEY_DISK1) & 0xff)))
231 #ifdef NO_CHS
232         movl    %edi, ptn_list - CONST(%si)     /* sector to read */
233 #else
234         mov     %bp, ptn_list - CONST(%si)      /* partition info */
235 #endif
236 #undef CONST
237         mov     %bx, %si
238         call    message_crlf                    /* prompt */
239 #endif
241         add     $0x10, %bp
242         add     $TABENTRYSIZE, %bx
243         cmpb    $(nametab - start - 0x100) + 4 * TABENTRYSIZE, %bl
244         jne     next_ptn
246 #ifdef BOOT_EXTENDED
248  * Now check extended partition chain
249  */
250         testl   %edx, %edx
251         je      wait_key
252         testl   %ecx, %ecx
253         jne     1f
254         xchg    %ecx, %edx              /* save base of ext ptn chain */
255 1:      addl    %ecx, %edx              /* sector to read */
256         movl    %edx, lba_sector
257         movw    $lba_info, %si
258         movb    $0x42, %ah
259         pop     %dx                     /* recover drive # */
260         push    %dx                     /* save drive */
261         int     $0x13
262         movw    $BOOTADDR + (nametab - start), %bx
263         jnc     next_extended           /* abort menu on read fail */
264 #endif
267  * The non-bootsel code traverses this code path, it needs the
268  * correct keycode to select the active partition.
269  */
271 #ifndef BOOTSEL
272         mov     $(KEY_ACTIVE - KEY_DISK1) & 0xff, %ax
273 #else
275  * Get the initial time value for the timeout comparison. It is returned
276  * by int 1a in cx:dx. We do sums modulo 2^16 so it doesn't matter if
277  * the counter wraps (which it does every hour) - so we can safely
278  * ignore 'cx'.
280  * Loop around checking for a keypress until we have one, or timeout is
281  * reached.
282  */
283 wait_key:
284         xorb    %ah, %ah
285         int     $0x1a
286         mov     %dx, %di                /* start time to di */
288 #ifdef COM_PORT_VAL
289         mov     COM_PORT_VAL, %dx
290         push    %dx
291         add     $5, %dx
292         inb     %dx
293         pop     %dx
294         test    $1, %al
295         jz      1f
296         inb     %dx
297         jmp     check_key
298 #else
299         movb    $1, %ah                 /* looks to see if a */
300         int     $0x16                   /* key has been pressed */
301         jz      1f
302 get_key:
303         xorb    %ah, %ah
304         int     $0x16                   /* 'read key', code ah, ascii al */
305         shr     $8, %ax                 /* code in %al, %ah zero */
306         jmp     check_key
307 #endif
309 1:      xorb    %ah, %ah
310         int     $0x1a                   /* current time to cx:dx */
311         sub     %di, %dx
312         cmpw    timeout, %dx            /* always wait for 1 tick... */
313         jbe     3b                      /* 0xffff means never timeout */
314 def_key:
315         mov     defkey, %al             /* timedout - we need %ah to still be zero! */
318  * We have a keycode, see what it means.
319  * If we don't know we generate error '?' and go ask again
320  */
321 check_key:
323  * F1-F10 -> boot disk 0-9. Check if the requested disk isn't above
324  * the number of disks actually in the system as stored in 0:0475 by
325  * the BIOS.
326  * If we trust loc 475, we needn't check the upper bound on the keystroke
327  * This is always sector 0, so always read using chs.
328  */
329         subb    $KEY_DISK1, %al
330         cmpb    0x0475, %al
331         jae     boot_ptn
332         addb    $0x80, %al
333         pop     %dx                     /* dump saved drive # */
334         push    %ax                     /* replace with new */
335 #ifdef NO_CHS
336         xorl    %ebp, %ebp              /* read sector number 0 */
337         jmp     boot_lba
338 #else
339         movw    $chs_zero, %si          /* chs read sector zero info */
340         jmp     read_chs
341 #endif
342 #endif  /* BOOTSEL */
345  * Boot requested partition.
346  * Use keycode to index the table we generated when we scanned the mbr
347  * while generating the menu.
349  * We very carfully saved the values in the correct part of the table.
350  */
352 boot_ptn:
353         shl     $2, %ax
354         movw    %ax, %si
355 #ifdef NO_CHS
356         movl    ptn_list(%si), %ebp
357         testl   %ebp, %ebp
358         jnz     boot_lba
359 #else
360         mov     ptn_list(%si), %si
361         test    %si, %si
362         jnz     boot_si
363 #endif
364 #ifdef BOOTSEL
365         set_err(ERR_KEY)
366 #else
367         set_err(ERR_INVPART)
368 #endif
369   /*    jmp     err_msg */
371 /* Something went wrong...
372  * Output error code,
373  * reset disk subsystem - needed after read failure,
374  * and wait for user key
375  */
376 err_msg:
377 #ifdef TERSE_ERROR
378         movb    %al, errcod
379         movw    $errtxt, %si
380         call    message
381 #else
382         push    %ax
383         movw    $errtxt, %si
384         call    message
385         pop     %si
386         call    message_crlf
387 #endif
388         pop     %dx                     /* drive we errored on */
389         xor     %ax,%ax                 /* only need %ah = 0 */
390         int     $0x13                   /* reset disk subsystem */
391 #ifdef BOOTSEL
392         pop     %dx                     /* original drive number */
393         push    %dx
394         push    %dx
395 #ifdef COM_PORT_VAL
396         jmp     wait_key                /* Read with timeout (again) */
397 #else
398         jmp     get_key                 /* Blocking read */
399 #endif
400 #else
401         int     $0x18                   /* BIOS might ask for a key */
402                                         /* press and retry boot seq. */
403 1:      sti
404         hlt
405         jmp     1b
406 #endif
408 #ifndef NO_CHS
410  * Active partition pointed to by si.
411  * Read the first sector.
413  * We can either do a CHS (Cylinder Head Sector) or an LBA (Logical
414  * Block Address) read.  Always doing the LBA one
415  * would be nice - unfortunately not all systems support it.
416  * Also some may contain a separate (eg SCSI) bios that doesn't
417  * support it even when the main bios does.
419  * There is also the additional problem that the CHS values may be wrong
420  * (eg if fdisk was run on a different system that used different BIOS
421  * geometry).  We convert the CHS value to a LBA sector number using
422  * the geometry from the BIOS, if the number matches we do a CHS read.
423  */
424 boot_si:
425         movl    8(%si), %ebp            /* get sector # */
427         testb   $MBR_BS_READ_LBA, flags
428         jnz     boot_lba                /* fdisk forced LBA read */
430         pop     %dx                     /* collect saved drive... */
431         push    %dx                     /* ...number to dl */
432         movb    $8, %ah
433         int     $0x13                   /* chs info */
436  * Validate geometry, if the CHS sector number doesn't match the LBA one
437  * we'll do an LBA read.
438  * calc: (cylinder * number_of_heads + head) * number_of_sectors + sector
439  * and compare against LBA sector number.
440  * Take a slight 'flier' and assume we can just check 16bits (very likely
441  * to be true because the number of sectors per track is 63).
442  */
443         movw    2(%si), %ax             /* cylinder + sector */
444         push    %ax                     /* save for sector */
445         shr     $6, %al
446         xchgb   %al, %ah                /* 10 bit cylinder number */
447         shr     $8, %dx                 /* last head */
448         inc     %dx                     /* number of heads */
449         mul     %dx
450         mov     1(%si), %dl             /* head we want */
451         add     %dx, %ax
452         and     $0x3f, %cx              /* number of sectors */
453         mul     %cx
454         pop     %dx                     /* recover sector we want */
455         and     $0x3f, %dx
456         add     %dx, %ax
457         dec     %ax
459         cmp     %bp, %ax
460         je      read_chs
462 #ifndef NO_LBA_CHECK
464  * Determine whether we have int13-extensions, by calling int 13, function 41.
465  * Check for the magic number returned, and the disk packet capability.
466  */
467         movw    $0x55aa, %bx
468         movb    $0x41, %ah
469         pop     %dx
470         push    %dx
471         int     $0x13
472         set_err(ERR_NO_LBA)
473         jc      err_msg                 /* no int13 extensions */
474         cmpw    $0xaa55, %bx
475         jnz     err_msg
476         testb   $1, %cl
477         jz      err_msg
478 #endif  /* NO_LBA_CHECK */
479 #endif  /* NO_CHS */
482  * Save sector number (passed in %ebp) into lba parameter block,
483  * read the sector and leap into it.
484  */
485 boot_lba:
486         movl    %ebp, lba_sector        /* save sector number */
487         movw    $lba_info, %si
488         movb    $0x42, %ah
489         pop     %dx                     /* recover drive # */
490 do_read:
491         push    %dx                     /* save drive */
492         int     $0x13
494         set_err(ERR_READ)
495         jc      err_msg
498  * Check signature for valid bootcode
499  */
500         movb    BOOTADDR, %al           /* first byte non-zero */
501         test    %al, %al
502         jz      1f
503         movw    BOOTADDR + MBR_MAGIC_OFFSET, %ax
504 1:      cmp     $MBR_MAGIC, %ax
505         set_err(ERR_NOOS)
506         jnz     err_msg
508 /* We pass the sector number through to the next stage boot.
509  * It doesn't have to use it (indeed no other mbr code will generate) it,
510  * but it does let us have a NetBSD pbr that can identify where it was
511  * read from!  This lets us use this code to select between two
512  * NetBSD system on the same physical driver.
513  * (If we've read the mbr of a different disk, it gets a random number
514  * - but it wasn't expecting anything...)
516         movl    %ebp, %esi
517         pop     %dx                     /* recover drive # */
518 #ifdef __minix
519         /* There some bug in MINIX tools, which causes to
520          * generate wrong code for "jmp BOOTADDR" (relocation issue).
521          */
522         jmp     start - LOADADDR + BOOTADDR
523 #else
524         jmp     BOOTADDR
525 #endif
528 #ifndef NO_CHS
530  * Sector below CHS limit
531  * Do a cylinder-head-sector read instead.
532  */
533 read_chs:
534         pop     %dx                     /* recover drive # */
535         movb    1(%si), %dh             /* head */
536         movw    2(%si), %cx             /* ch=cyl, cl=sect */
537         movw    $BOOTADDR, %bx          /* es:bx is buffer */
538         movw    $0x201, %ax             /* command 2, 1 sector */
539         jmp     do_read
540 #endif
543  * Control block for int-13 LBA read.
544  * We need a xx, 00, 01, 00 somewhere to load chs for sector zero,
545  * by a complete fluke there is one here!
546  */
547 chs_zero:
548 lba_info:
549         .word   0x10                    /* control block length */
550         .word   1                       /* sector count */
551         .word   BOOTADDR                /* offset in segment */
552         .word   0                       /* segment */
553 lba_sector:
554         .long   0x0000                  /* sector # goes here... */
555         .long   0x0000
557 errtxt: .ascii  "Error "                /* runs into crlf if errcod set */
558 errcod: .byte   0
559 crlf:   .asciz  "\r\n"
561 #ifndef NO_BANNER
562 #ifdef BOOTSEL
563 #ifdef COM_PORT_VAL
564 banner: .asciz  "a: disk"
565 #else
566 banner: .asciz  "Fn: diskn"
567 #endif
568 #else
569 banner: .asciz  "NetBSD MBR boot"
570 #endif
571 #endif
573 #ifdef BOOTSEL
574 prefix: .asciz  "0: "
575 #endif
577 #ifndef TERSE_ERROR
578 ERR_INVPART:    .asciz  "No active partition"
579 ERR_READ:       .asciz  "Disk read error"
580 ERR_NOOS:       .asciz  "No operating system"
581 #ifndef NO_LBA_CHECK
582 ERR_NO_LBA:     .asciz  "Invalid CHS read"
583 #endif
584 #ifdef BOOTSEL
585 ERR_KEY:        .asciz  "bad key"
586 #endif
587 #endif
589 #if defined(COM_BAUD)
590 #define COM_DIVISOR (((COM_FREQ / COM_BAUD) + 8) / 16)
591 com_args:
592         .byte   0x80                    /* divisor latch enable */
593         .byte   +3                      /* io_port + 3 */
594         .byte   COM_DIVISOR & 0xff
595         .byte   -3                      /* io_port */
596         .byte   COM_DIVISOR >> 8        /* high baud */
597         .byte   +1                      /* io_port + 1 */
598         .byte   0x03                    /* 8 bit no parity */
599         .byte   +2                      /* io_port + 3 */
600 num_com_args = (. - com_args)/2
601 #endif
604  * I hate #including source files, but the stuff below has to be at
605  * the correct absolute address.
606  * Clearly this could be done with a linker script.
607  */
609 message_crlf:
610         call    message
611         movw    $crlf, %si
612 #include <message.S>
613 #if 0
614 #include <dump_eax.S>
615 #endif
618  * Stuff from here on is overwritten by fdisk - the offset must not change...
620  * Get amount of space to makefile can report it.
621  * (Unfortunately I can't seem to get the value reported when it is -ve)
622  */
623 mbr_space       = defkey - .
624         . = start + MBR_BS_OFFSET
626  * Default action, as a keyvalue we'd normally read from the BIOS.
627  */
628 defkey:
629         .byte   KEY_ACTIVE              /* ps/2 code */
630 #ifndef BOOTSEL_FLAGS
631 #define BOOTSEL_FLAGS   0
632 #endif
633 flags:  .byte   MBR_BS_NEWMBR | BOOTSEL_FLAGS
635  * Timeout value. ~65536 ticks per hour, which is ~18.2 times per second.
636  * 0xffff means never timeout.
637  */
638 timeout:
639         .word   182                     /* default to 10 seconds */
641  * mbr_bootsel
642  */
643 nametab:
644         .fill   MBR_PART_COUNT * (MBR_BS_PARTNAMESIZE + 1), 0x01, 0x00
646 /* space for mbr_dsn */
647         . = start + MBR_DSN_OFFSET
648         .long   0
650 /* mbr_bootsel_magic */
651         . = start + MBR_BS_MAGIC_OFFSET
652         .word   MBR_BS_MAGIC
655  * MBR partition table
656  */
657         . = start + MBR_PART_OFFSET
658 parttab:
659         .fill   0x40, 0x01, 0x00
661         . = start + MBR_MAGIC_OFFSET
662         .word   MBR_MAGIC
664 /* zeroed data space */
665 bss_off = 0
666 bss_start = .
667 #define BSS(name, size) name = bss_start + bss_off; bss_off = bss_off + size
668         BSS(ptn_list, 256 * 4)          /* long[]: boot sector numbers */
669         BSS(dump_eax_buff, 16)
670         BSS(bss_end, 0)