952eccddeb0ce7ad468b947803a78e7a8ffe09f4
[people/cooldavid/gpxe.git] / src / arch / i386 / prefix / romprefix.S
1 /* At entry, the processor is in 16 bit real mode and the code is being
2  * executed from an address it was not linked to. Code must be pic and
3  * 32 bit sensitive until things are fixed up.
4  *
5  * Also be very careful as the stack is at the rear end of the interrupt
6  * table so using a noticeable amount of stack space is a no-no.
7  */
8
9 FILE_LICENCE ( GPL2_OR_LATER )
10
11 #include <config/general.h>
12
13 #define PNP_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) )
14 #define PMM_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'M' << 16 ) + ( 'M' << 24 ) )
15 #define PCI_SIGNATURE ( 'P' + ( 'C' << 8 ) + ( 'I' << 16 ) + ( ' ' << 24 ) )
16 #define STACK_MAGIC ( 'L' + ( 'R' << 8 ) + ( 'E' << 16 ) + ( 'T' << 24 ) )
17 #define PNP_GET_BBS_VERSION 0x60
18 #define PMM_ALLOCATE 0x0000
19 #define PMM_DEALLOCATE 0x0002
20
21 /* ROM banner timeout.  Based on the configurable BANNER_TIMEOUT in
22  * config.h, but converted to a number of (18Hz) timer ticks, and
23  * doubled to allow for BIOSes that switch video modes immediately
24  * beforehand, so rendering the message almost invisible to the user.
25  */
26 #define ROM_BANNER_TIMEOUT ( 2 * ( 18 * BANNER_TIMEOUT ) / 10 )
27
28         .text
29         .code16
30         .arch i386
31         .section ".prefix", "ax", @progbits
32         
33         .org    0x00
34 romheader:
35         .word   0xAA55                  /* BIOS extension signature */
36 romheader_size: .byte 0                 /* Size in 512-byte blocks */
37         jmp     init                    /* Initialisation vector */
38 checksum:
39         .byte   0
40         .org    0x16
41         .word   undiheader
42         .org    0x18
43         .word   pciheader
44         .org    0x1a
45         .word   pnpheader
46         .size romheader, . - romheader
47         
48         .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */
49         .ascii  "ADDB"
50         .long   romheader_size
51         .long   512
52         .long   0
53         .previous
54
55 pciheader:
56         .ascii  "PCIR"                  /* Signature */
57         .word   pci_vendor_id           /* Vendor identification */ 
58         .word   pci_device_id           /* Device identification */
59         .word   0x0000                  /* Device list pointer */
60         .word   pciheader_len           /* PCI data structure length */
61         .byte   0x03                    /* PCI data structure revision */
62         .byte   0x02, 0x00, 0x00        /* Class code */
63 pciheader_image_length:
64         .word   0                       /* Image length */
65         .word   0x0001                  /* Revision level */
66         .byte   0x00                    /* Code type */
67         .byte   0x80                    /* Last image indicator */
68 pciheader_runtime_length:
69         .word   0                       /* Maximum run-time image length */
70         .word   0x0000                  /* Configuration utility code header */
71         .word   0x0000                  /* DMTF CLP entry point */
72         .equ pciheader_len, . - pciheader
73         .size pciheader, . - pciheader
74         
75         .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */
76         .ascii  "ADDW"
77         .long   pciheader_image_length
78         .long   512
79         .long   0
80         .ascii  "ADDW"
81         .long   pciheader_runtime_length
82         .long   512
83         .long   0
84         .previous
85
86 pnpheader:
87         .ascii  "$PnP"                  /* Signature */
88         .byte   0x01                    /* Structure revision */
89         .byte   ( pnpheader_len / 16 )  /* Length (in 16 byte increments) */
90         .word   0x0000                  /* Offset of next header */
91         .byte   0x00                    /* Reserved */
92         .byte   0x00                    /* Checksum */
93         .long   0x00000000              /* Device identifier */
94         .word   mfgstr                  /* Manufacturer string */
95         .word   prodstr                 /* Product name */
96         .byte   0x02                    /* Device base type code */
97         .byte   0x00                    /* Device sub-type code */
98         .byte   0x00                    /* Device interface type code */
99         .byte   0xf4                    /* Device indicator */
100         .word   0x0000                  /* Boot connection vector */
101         .word   0x0000                  /* Disconnect vector */
102         .word   bev_entry               /* Boot execution vector */
103         .word   0x0000                  /* Reserved */
104         .word   0x0000                  /* Static resource information vector*/
105         .equ pnpheader_len, . - pnpheader
106         .size pnpheader, . - pnpheader
107
108 /* Manufacturer string */
109 mfgstr:
110         .asciz  "http://etherboot.org"
111         .size mfgstr, . - mfgstr
112
113 /* Product string
114  *
115  * Defaults to PRODUCT_SHORT_NAME.  If the ROM image is writable at
116  * initialisation time, it will be filled in to include the PCI
117  * bus:dev.fn number of the card as well.
118  */
119 prodstr:
120         .ascii  PRODUCT_SHORT_NAME
121 prodstr_separator:
122         .byte   0
123         .ascii  "(PCI "
124 prodstr_pci_id:
125         .asciz  "xx:xx.x)"              /* Filled in by init code */
126         .size prodstr, . - prodstr
127
128         .globl  undiheader      
129         .weak   undiloader
130 undiheader:
131         .ascii  "UNDI"                  /* Signature */
132         .byte   undiheader_len          /* Length of structure */
133         .byte   0                       /* Checksum */
134         .byte   0                       /* Structure revision */
135         .byte   0,1,2                   /* PXE version: 2.1.0 */
136         .word   undiloader              /* Offset to loader routine */
137         .word   _data16_memsz           /* Stack segment size */
138         .word   _data16_memsz           /* Data segment size */
139         .word   _text16_memsz           /* Code segment size */
140         .ascii  "PCIR"                  /* Bus type */
141         .equ undiheader_len, . - undiheader
142         .size undiheader, . - undiheader
143
144 /* Initialisation (called once during POST)
145  *
146  * Determine whether or not this is a PnP system via a signature
147  * check.  If it is PnP, return to the PnP BIOS indicating that we are
148  * a boot-capable device; the BIOS will call our boot execution vector
149  * if it wants to boot us.  If it is not PnP, hook INT 19.
150  */
151 init:
152         /* Preserve registers, clear direction flag, set %ds=%cs */
153         pushaw
154         pushw   %ds
155         pushw   %es
156         pushw   %fs
157         pushw   %gs
158         cld
159         pushw   %cs
160         popw    %ds
161
162         /* Shuffle some registers around.  We need %di available for
163          * the print_xxx functions, and in a register that's
164          * addressable from %es, so shuffle as follows:
165          *
166          *    %di (pointer to PnP structure) => %bx
167          *    %bx (runtime segment address, for PCI 3.0) => %gs
168          */
169         movw    %bx, %gs
170         movw    %di, %bx
171
172         /* Print message as early as possible */
173         movw    $init_message, %si
174         xorw    %di, %di
175         call    print_message
176         call    print_pci_busdevfn
177
178         /* Fill in product name string, if possible */
179         movw    $prodstr_pci_id, %di
180         call    print_pci_busdevfn
181         movb    $( ' ' ), prodstr_separator
182
183         /* Print segment address */
184         movb    $( ' ' ), %al
185         xorw    %di, %di
186         call    print_character
187         movw    %cs, %ax
188         call    print_hex_word
189
190         /* Check for PCI BIOS version */
191         pushl   %ebx
192         pushl   %edx
193         pushl   %edi
194         stc
195         movw    $0xb101, %ax
196         int     $0x1a
197         jc      no_pci3
198         cmpl    $PCI_SIGNATURE, %edx
199         jne     no_pci3
200         testb   %ah, %ah
201         jnz     no_pci3
202         movw    $init_message_pci, %si
203         xorw    %di, %di
204         call    print_message
205         movb    %bh, %al
206         call    print_hex_nibble
207         movb    $( '.' ), %al
208         call    print_character
209         movb    %bl, %al
210         call    print_hex_byte
211         cmpb    $3, %bh
212         jb      no_pci3
213         /* PCI >=3.0: leave %gs as-is if sane */
214         movw    %gs, %ax
215         cmpw    $0xa000, %ax    /* Insane if %gs < 0xa000 */
216         jb      pci3_insane
217         movw    %cs, %bx        /* Sane if %cs == %gs */
218         cmpw    %bx, %ax
219         je      1f
220         movzbw  romheader_size, %cx /* Sane if %cs+len <= %gs */
221         shlw    $5, %cx
222         addw    %cx, %bx
223         cmpw    %bx, %ax
224         jae     1f
225         movw    %cs, %bx        /* Sane if %gs+len <= %cs */
226         addw    %cx, %ax
227         cmpw    %bx, %ax
228         jbe     1f
229 pci3_insane: /* PCI 3.0 with insane %gs value: print error and ignore %gs */
230         movb    $( '!' ), %al
231         call    print_character
232         movw    %gs, %ax
233         call    print_hex_word
234 no_pci3:
235         /* PCI <3.0: set %gs (runtime segment) = %cs (init-time segment) */
236         pushw   %cs
237         popw    %gs
238 1:      popl    %edi
239         popl    %edx
240         popl    %ebx
241
242         /* Check for PnP BIOS.  Although %es:di should point to the
243          * PnP BIOS signature on entry, some BIOSes fail to do this.
244          */
245         movw    $( 0xf000 - 1 ), %bx
246 pnp_scan:
247         incw    %bx
248         jz      no_pnp
249         movw    %bx, %es
250         cmpl    $PNP_SIGNATURE, %es:0
251         jne     pnp_scan
252         xorw    %dx, %dx
253         xorw    %si, %si
254         movzbw  %es:5, %cx
255 1:      es lodsb
256         addb    %al, %dl
257         loop    1b
258         jnz     pnp_scan
259         /* Is PnP: print PnP message */
260         movw    $init_message_pnp, %si
261         xorw    %di, %di
262         call    print_message
263         /* Check for BBS */
264         pushw   %es:0x1b        /* Real-mode data segment */
265         pushw   %ds             /* &(bbs_version) */
266         pushw   $bbs_version
267         pushw   $PNP_GET_BBS_VERSION
268         lcall   *%es:0xd
269         addw    $8, %sp
270         testw   %ax, %ax
271         je      got_bbs
272 no_pnp: /* Not PnP-compliant - therefore cannot be BBS-compliant */
273 no_bbs: /* Not BBS-compliant - must hook INT 19 */
274         movw    $init_message_int19, %si
275         xorw    %di, %di
276         call    print_message
277         xorw    %ax, %ax
278         movw    %ax, %es
279         pushl   %es:( 0x19 * 4 )
280         popl    orig_int19
281         pushw   %gs /* %gs contains runtime %cs */
282         pushw   $int19_entry
283         popl    %es:( 0x19 * 4 )
284         jmp     bbs_done
285 got_bbs: /* BBS compliant - no need to hook INT 19 */
286         movw    $init_message_bbs, %si
287         xorw    %di, %di
288         call    print_message
289 bbs_done:
290
291         /* Check for PMM */
292         movw    $( 0xe000 - 1 ), %bx
293 pmm_scan:
294         incw    %bx
295         jz      no_pmm
296         movw    %bx, %es
297         cmpl    $PMM_SIGNATURE, %es:0
298         jne     pmm_scan
299         xorw    %dx, %dx
300         xorw    %si, %si
301         movzbw  %es:5, %cx
302 1:      es lodsb
303         addb    %al, %dl
304         loop    1b
305         jnz     pmm_scan
306         /* PMM found: print PMM message */
307         movw    $init_message_pmm, %si
308         xorw    %di, %di
309         call    print_message
310         /* We have PMM and so a 1kB stack: preserve upper register halves */
311         pushal
312         /* Calculate required allocation size in %esi */
313         movzbl  romheader_size, %eax
314         shll    $9, %eax
315         addl    $_textdata_memsz, %eax
316         orw     $0xffff, %ax    /* Ensure allocation size is at least 64kB */
317         bsrl    %eax, %ecx
318         subw    $15, %cx        /* Round up and convert to 64kB count */
319         movw    $1, %si
320         shlw    %cl, %si
321 pmm_loop:
322         /* Try to allocate block via PMM */
323         pushw   $0x0006         /* Aligned, extended memory */
324         pushl   $0xffffffff     /* No handle */
325         movzwl  %si, %eax
326         shll    $12, %eax
327         pushl   %eax            /* Allocation size in paragraphs */
328         pushw   $PMM_ALLOCATE
329         lcall   *%es:7
330         addw    $12, %sp
331         /* Abort if allocation fails */
332         testw   %dx, %dx        /* %ax==0 even on success, since align>=64kB */
333         jz      pmm_fail
334         /* If block has A20==1, free block and try again with twice
335          * the allocation size (and hence alignment).
336          */
337         testw   $0x0010, %dx
338         jz      got_pmm
339         pushw   %dx
340         pushw   $0
341         pushw   $PMM_DEALLOCATE
342         lcall   *%es:7
343         addw    $6, %sp
344         addw    %si, %si
345         jmp     pmm_loop
346 got_pmm: /* PMM allocation succeeded */
347         movw    %dx, ( image_source + 2 )
348         movw    %dx, %ax
349         xorw    %di, %di
350         call    print_hex_word
351         movb    $( '@' ), %al
352         call    print_character
353         movw    %si, %ax
354         call    print_hex_byte
355 pmm_copy:
356         /* Copy ROM to PMM block */
357         xorw    %ax, %ax
358         movw    %ax, %es
359         movl    image_source, %edi
360         xorl    %esi, %esi
361         movzbl  romheader_size, %ecx
362         shll    $9, %ecx
363         addr32 rep movsb        /* PMM presence implies flat real mode */
364         movl    %edi, decompress_to
365         /* Shrink ROM */
366         movb    $_prefix_memsz_sect, romheader_size
367 #ifdef SHRINK_WITHOUT_PMM
368         jmp     pmm_done
369 pmm_fail:
370         /* Print marker and copy ourselves to high memory */
371         movl    $HIGHMEM_LOADPOINT, image_source
372         xorw    %di, %di
373         movb    $( '!' ), %al
374         call    print_character
375         jmp     pmm_copy
376 pmm_done:
377 #else
378 pmm_fail:
379 #endif
380         /* Restore upper register halves */
381         popal
382 no_pmm:
383
384         /* Update checksum */
385         xorw    %bx, %bx
386         xorw    %si, %si
387         movzbw  romheader_size, %cx
388         shlw    $9, %cx
389 1:      lodsb
390         addb    %al, %bl
391         loop    1b
392         subb    %bl, checksum
393
394         /* Copy self to option ROM space.  Required for PCI3.0, which
395          * loads us to a temporary location in low memory.  Will be a
396          * no-op for lower PCI versions.
397          */
398         movb    $( ' ' ), %al
399         xorw    %di, %di
400         call    print_character
401         movw    %gs, %ax
402         call    print_hex_word
403         movzbw  romheader_size, %cx
404         shlw    $9, %cx
405         movw    %ax, %es
406         xorw    %si, %si
407         xorw    %di, %di
408         cs rep  movsb
409
410         /* Prompt for POST-time shell */
411         movw    $init_message_prompt, %si
412         xorw    %di, %di
413         call    print_message
414         movw    $prodstr, %si
415         call    print_message
416         movw    $init_message_dots, %si
417         call    print_message
418         /* Wait for Ctrl-B */
419         movw    $0xff02, %bx
420         call    wait_for_key
421         /* Clear prompt */
422         pushf
423         xorw    %di, %di
424         call    print_kill_line
425         movw    $init_message_done, %si
426         call    print_message
427         popf
428         jnz     2f
429         /* Ctrl-B was pressed: invoke gPXE.  The keypress will be
430          * picked up by the initial shell prompt, and we will drop
431          * into a shell.
432          */
433         pushw   %cs
434         call    exec
435 2:
436         /* Restore registers */
437         popw    %gs
438         popw    %fs
439         popw    %es
440         popw    %ds
441         popaw
442
443         /* Indicate boot capability to PnP BIOS, if present */
444         movw    $0x20, %ax
445         lret
446         .size init, . - init
447
448 /*
449  * Note to hardware vendors:
450  *
451  * If you wish to brand this boot ROM, please do so by defining the
452  * strings PRODUCT_NAME and PRODUCT_SHORT_NAME in config/general.h.
453  *
454  * While nothing in the GPL prevents you from removing all references
455  * to gPXE or http://etherboot.org, we prefer you not to do so.
456  *
457  * If you have an OEM-mandated branding requirement that cannot be
458  * satisfied simply by defining PRODUCT_NAME and PRODUCT_SHORT_NAME,
459  * please contact us.
460  *
461  * [ Including an ASCII NUL in PRODUCT_NAME is considered to be
462  *   bypassing the spirit of this request! ]
463  */
464 init_message:
465         .ascii  "\n"
466         .ascii  PRODUCT_NAME
467         .ascii  "\n"
468         .asciz  "gPXE (http://etherboot.org) - "
469         .size   init_message, . - init_message
470 init_message_pci:
471         .asciz  " PCI"
472         .size   init_message_pci, . - init_message_pci
473 init_message_pnp:
474         .asciz  " PnP"
475         .size   init_message_pnp, . - init_message_pnp
476 init_message_bbs:
477         .asciz  " BBS"
478         .size   init_message_bbs, . - init_message_bbs
479 init_message_pmm:
480         .asciz  " PMM"
481         .size   init_message_pmm, . - init_message_pmm
482 init_message_int19:
483         .asciz  " INT19"
484         .size   init_message_int19, . - init_message_int19
485 init_message_prompt:
486         .asciz  "\nPress Ctrl-B to configure "
487         .size   init_message_prompt, . - init_message_prompt
488 init_message_dots:
489         .asciz  "..."
490         .size   init_message_dots, . - init_message_dots
491 init_message_done:
492         .asciz  "\n\n"
493         .size   init_message_done, . - init_message_done
494
495 /* ROM image location
496  *
497  * May be either within option ROM space, or within PMM-allocated block.
498  */
499         .globl  image_source
500 image_source:
501         .long   0
502         .size   image_source, . - image_source
503
504 /* Temporary decompression area
505  *
506  * May be either at HIGHMEM_LOADPOINT, or within PMM-allocated block.
507  */
508         .globl  decompress_to
509 decompress_to:
510         .long   HIGHMEM_LOADPOINT
511         .size   decompress_to, . - decompress_to
512
513 /* BBS version
514  *
515  * Filled in by BBS BIOS.  We ignore the value.
516  */
517 bbs_version:
518         .word   0
519         .size   bbs_version, . - bbs_version
520
521 /* Boot Execution Vector entry point
522  *
523  * Called by the PnP BIOS when it wants to boot us.
524  */
525 bev_entry:
526         pushw   %cs
527         call    exec
528         lret
529         .size   bev_entry, . - bev_entry
530
531 /* INT19 entry point
532  *
533  * Called via the hooked INT 19 if we detected a non-PnP BIOS.  We
534  * attempt to return via the original INT 19 vector (if we were able
535  * to store it).
536  */
537 int19_entry:
538         pushw   %cs
539         popw    %ds
540         /* Prompt user to press B to boot */
541         movw    $int19_message_prompt, %si
542         xorw    %di, %di
543         call    print_message
544         movw    $prodstr, %si
545         call    print_message
546         movw    $int19_message_dots, %si
547         call    print_message
548         movw    $0xdf4e, %bx
549         call    wait_for_key
550         pushf
551         xorw    %di, %di
552         call    print_kill_line
553         movw    $int19_message_done, %si
554         call    print_message
555         popf
556         jz      1f
557         /* Leave keypress in buffer and start gPXE.  The keypress will
558          * cause the usual initial Ctrl-B prompt to be skipped.
559          */
560         pushw   %cs
561         call    exec
562 1:      /* Try to call original INT 19 vector */
563         movl    %cs:orig_int19, %eax
564         testl   %eax, %eax
565         je      2f
566         ljmp    *%cs:orig_int19
567 2:      /* No chained vector: issue INT 18 as a last resort */
568         int     $0x18
569         .size   int19_entry, . - int19_entry
570 orig_int19:
571         .long   0
572         .size   orig_int19, . - orig_int19
573
574 int19_message_prompt:
575         .asciz  "Press N to skip booting from "
576         .size   int19_message_prompt, . - int19_message_prompt
577 int19_message_dots:
578         .asciz  "..."
579         .size   int19_message_dots, . - int19_message_dots
580 int19_message_done:
581         .asciz  "\n\n"
582         .size   int19_message_done, . - int19_message_done
583         
584 /* Execute as a boot device
585  *
586  */
587 exec:   /* Set %ds = %cs */
588         pushw   %cs
589         popw    %ds
590
591         /* Print message as soon as possible */
592         movw    $prodstr, %si
593         xorw    %di, %di
594         call    print_message
595         movw    $exec_message, %si
596         call    print_message
597
598         /* Store magic word on BIOS stack and remember BIOS %ss:sp */
599         pushl   $STACK_MAGIC
600         movw    %ss, %dx
601         movw    %sp, %bp
602
603         /* Obtain a reasonably-sized temporary stack */
604         xorw    %ax, %ax
605         movw    %ax, %ss
606         movw    $0x7c00, %sp
607
608         /* Install gPXE */
609         movl    image_source, %esi
610         movl    decompress_to, %edi
611         call    alloc_basemem
612         call    install_prealloc
613
614         /* Set up real-mode stack */
615         movw    %bx, %ss
616         movw    $_estack16, %sp
617
618         /* Jump to .text16 segment */
619         pushw   %ax
620         pushw   $1f
621         lret
622         .section ".text16", "awx", @progbits
623 1:      /* Call main() */
624         pushl   $main
625         pushw   %cs
626         call    prot_call
627         popl    %ecx /* discard */
628
629         /* Uninstall gPXE */
630         call    uninstall
631
632         /* Restore BIOS stack */
633         movw    %dx, %ss
634         movw    %bp, %sp
635
636         /* Check magic word on BIOS stack */
637         popl    %eax
638         cmpl    $STACK_MAGIC, %eax
639         jne     1f
640         /* BIOS stack OK: return to caller */
641         lret
642 1:      /* BIOS stack corrupt: use INT 18 */
643         int     $0x18
644         .previous
645
646 exec_message:
647         .asciz  " starting execution\n"
648         .size exec_message, . - exec_message
649
650 /* Wait for key press specified by %bl (masked by %bh)
651  *
652  * Used by init and INT19 code when prompting user.  If the specified
653  * key is pressed, it is left in the keyboard buffer.
654  *
655  * Returns with ZF set iff specified key is pressed.
656  */
657 wait_for_key:
658         /* Preserve registers */
659         pushw   %cx
660         pushw   %ax
661 1:      /* Empty the keyboard buffer before waiting for input */
662         movb    $0x01, %ah
663         int     $0x16
664         jz      2f
665         xorw    %ax, %ax
666         int     $0x16
667         jmp     1b
668 2:      /* Wait for a key press */
669         movw    $ROM_BANNER_TIMEOUT, %cx
670 3:      decw    %cx
671         js      99f             /* Exit with ZF clear */
672         /* Wait for timer tick to be updated */
673         call    wait_for_tick
674         /* Check to see if a key was pressed */
675         movb    $0x01, %ah
676         int     $0x16
677         jz      3b
678         /* Check to see if key was the specified key */
679         andb    %bh, %al
680         cmpb    %al, %bl
681         je      99f             /* Exit with ZF set */
682         /* Not the specified key: remove from buffer and stop waiting */
683         pushfw
684         xorw    %ax, %ax
685         int     $0x16
686         popfw                   /* Exit with ZF clear */
687 99:     /* Restore registers and return */
688         popw    %ax
689         popw    %cx
690         ret
691         .size wait_for_key, . - wait_for_key
692
693 /* Wait for timer tick
694  *
695  * Used by wait_for_key
696  */
697 wait_for_tick:
698         pushl   %eax
699         pushw   %fs
700         movw    $0x40, %ax
701         movw    %ax, %fs
702         movl    %fs:(0x6c), %eax
703 1:      pushf
704         sti
705         hlt
706         popf
707         cmpl    %fs:(0x6c), %eax
708         je      1b
709         popw    %fs
710         popl    %eax
711         ret
712         .size wait_for_tick, . - wait_for_tick