bab8f47388ea34069354f4b92c2b38b86466aa10
[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         /* Copy ROM to PMM block */
356         xorw    %ax, %ax
357         movw    %ax, %es
358         movl    image_source, %edi
359         xorl    %esi, %esi
360         movzbl  romheader_size, %ecx
361         shll    $9, %ecx
362         addr32 rep movsb        /* PMM presence implies flat real mode */
363         movl    %edi, decompress_to
364         /* Shrink ROM */
365         movb    $_prefix_memsz_sect, romheader_size
366 pmm_fail:
367         /* Restore upper register halves */
368         popal
369 no_pmm:
370
371         /* Update checksum */
372         xorw    %bx, %bx
373         xorw    %si, %si
374         movzbw  romheader_size, %cx
375         shlw    $9, %cx
376 1:      lodsb
377         addb    %al, %bl
378         loop    1b
379         subb    %bl, checksum
380
381         /* Copy self to option ROM space.  Required for PCI3.0, which
382          * loads us to a temporary location in low memory.  Will be a
383          * no-op for lower PCI versions.
384          */
385         movb    $( ' ' ), %al
386         xorw    %di, %di
387         call    print_character
388         movw    %gs, %ax
389         call    print_hex_word
390         movzbw  romheader_size, %cx
391         shlw    $9, %cx
392         movw    %ax, %es
393         xorw    %si, %si
394         xorw    %di, %di
395         cs rep  movsb
396
397         /* Prompt for POST-time shell */
398         movw    $init_message_prompt, %si
399         xorw    %di, %di
400         call    print_message
401         movw    $prodstr, %si
402         call    print_message
403         movw    $init_message_dots, %si
404         call    print_message
405         /* Wait for Ctrl-B */
406         movw    $0xff02, %bx
407         call    wait_for_key
408         /* Clear prompt */
409         pushf
410         xorw    %di, %di
411         call    print_kill_line
412         movw    $init_message_done, %si
413         call    print_message
414         popf
415         jnz     2f
416         /* Ctrl-B was pressed: invoke gPXE.  The keypress will be
417          * picked up by the initial shell prompt, and we will drop
418          * into a shell.
419          */
420         pushw   %cs
421         call    exec
422 2:
423         /* Restore registers */
424         popw    %gs
425         popw    %fs
426         popw    %es
427         popw    %ds
428         popaw
429
430         /* Indicate boot capability to PnP BIOS, if present */
431         movw    $0x20, %ax
432         lret
433         .size init, . - init
434
435 /*
436  * Note to hardware vendors:
437  *
438  * If you wish to brand this boot ROM, please do so by defining the
439  * strings PRODUCT_NAME and PRODUCT_SHORT_NAME in config/general.h.
440  *
441  * While nothing in the GPL prevents you from removing all references
442  * to gPXE or http://etherboot.org, we prefer you not to do so.
443  *
444  * If you have an OEM-mandated branding requirement that cannot be
445  * satisfied simply by defining PRODUCT_NAME and PRODUCT_SHORT_NAME,
446  * please contact us.
447  *
448  * [ Including an ASCII NUL in PRODUCT_NAME is considered to be
449  *   bypassing the spirit of this request! ]
450  */
451 init_message:
452         .ascii  "\n"
453         .ascii  PRODUCT_NAME
454         .ascii  "\n"
455         .asciz  "gPXE (http://etherboot.org) - "
456         .size   init_message, . - init_message
457 init_message_pci:
458         .asciz  " PCI"
459         .size   init_message_pci, . - init_message_pci
460 init_message_pnp:
461         .asciz  " PnP"
462         .size   init_message_pnp, . - init_message_pnp
463 init_message_bbs:
464         .asciz  " BBS"
465         .size   init_message_bbs, . - init_message_bbs
466 init_message_pmm:
467         .asciz  " PMM"
468         .size   init_message_pmm, . - init_message_pmm
469 init_message_int19:
470         .asciz  " INT19"
471         .size   init_message_int19, . - init_message_int19
472 init_message_prompt:
473         .asciz  "\nPress Ctrl-B to configure "
474         .size   init_message_prompt, . - init_message_prompt
475 init_message_dots:
476         .asciz  "..."
477         .size   init_message_dots, . - init_message_dots
478 init_message_done:
479         .asciz  "\n\n"
480         .size   init_message_done, . - init_message_done
481
482 /* ROM image location
483  *
484  * May be either within option ROM space, or within PMM-allocated block.
485  */
486         .globl  image_source
487 image_source:
488         .long   0
489         .size   image_source, . - image_source
490
491 /* Temporary decompression area
492  *
493  * May be either at HIGHMEM_LOADPOINT, or within PMM-allocated block.
494  */
495         .globl  decompress_to
496 decompress_to:
497         .long   HIGHMEM_LOADPOINT
498         .size   decompress_to, . - decompress_to
499
500 /* BBS version
501  *
502  * Filled in by BBS BIOS.  We ignore the value.
503  */
504 bbs_version:
505         .word   0
506         .size   bbs_version, . - bbs_version
507
508 /* Boot Execution Vector entry point
509  *
510  * Called by the PnP BIOS when it wants to boot us.
511  */
512 bev_entry:
513         pushw   %cs
514         call    exec
515         lret
516         .size   bev_entry, . - bev_entry
517
518 /* INT19 entry point
519  *
520  * Called via the hooked INT 19 if we detected a non-PnP BIOS.  We
521  * attempt to return via the original INT 19 vector (if we were able
522  * to store it).
523  */
524 int19_entry:
525         pushw   %cs
526         popw    %ds
527         /* Prompt user to press B to boot */
528         movw    $int19_message_prompt, %si
529         xorw    %di, %di
530         call    print_message
531         movw    $prodstr, %si
532         call    print_message
533         movw    $int19_message_dots, %si
534         call    print_message
535         movw    $0xdf4e, %bx
536         call    wait_for_key
537         pushf
538         xorw    %di, %di
539         call    print_kill_line
540         movw    $int19_message_done, %si
541         call    print_message
542         popf
543         jz      1f
544         /* Leave keypress in buffer and start gPXE.  The keypress will
545          * cause the usual initial Ctrl-B prompt to be skipped.
546          */
547         pushw   %cs
548         call    exec
549 1:      /* Try to call original INT 19 vector */
550         movl    %cs:orig_int19, %eax
551         testl   %eax, %eax
552         je      2f
553         ljmp    *%cs:orig_int19
554 2:      /* No chained vector: issue INT 18 as a last resort */
555         int     $0x18
556         .size   int19_entry, . - int19_entry
557 orig_int19:
558         .long   0
559         .size   orig_int19, . - orig_int19
560
561 int19_message_prompt:
562         .asciz  "Press N to skip booting from "
563         .size   int19_message_prompt, . - int19_message_prompt
564 int19_message_dots:
565         .asciz  "..."
566         .size   int19_message_dots, . - int19_message_dots
567 int19_message_done:
568         .asciz  "\n\n"
569         .size   int19_message_done, . - int19_message_done
570         
571 /* Execute as a boot device
572  *
573  */
574 exec:   /* Set %ds = %cs */
575         pushw   %cs
576         popw    %ds
577
578         /* Print message as soon as possible */
579         movw    $prodstr, %si
580         xorw    %di, %di
581         call    print_message
582         movw    $exec_message, %si
583         call    print_message
584
585         /* Store magic word on BIOS stack and remember BIOS %ss:sp */
586         pushl   $STACK_MAGIC
587         movw    %ss, %dx
588         movw    %sp, %bp
589
590         /* Obtain a reasonably-sized temporary stack */
591         xorw    %ax, %ax
592         movw    %ax, %ss
593         movw    $0x7c00, %sp
594
595         /* Install gPXE */
596         movl    image_source, %esi
597         movl    decompress_to, %edi
598         call    alloc_basemem
599         call    install_prealloc
600
601         /* Set up real-mode stack */
602         movw    %bx, %ss
603         movw    $_estack16, %sp
604
605         /* Jump to .text16 segment */
606         pushw   %ax
607         pushw   $1f
608         lret
609         .section ".text16", "awx", @progbits
610 1:      /* Call main() */
611         pushl   $main
612         pushw   %cs
613         call    prot_call
614         popl    %ecx /* discard */
615
616         /* Uninstall gPXE */
617         call    uninstall
618
619         /* Restore BIOS stack */
620         movw    %dx, %ss
621         movw    %bp, %sp
622
623         /* Check magic word on BIOS stack */
624         popl    %eax
625         cmpl    $STACK_MAGIC, %eax
626         jne     1f
627         /* BIOS stack OK: return to caller */
628         lret
629 1:      /* BIOS stack corrupt: use INT 18 */
630         int     $0x18
631         .previous
632
633 exec_message:
634         .asciz  " starting execution\n"
635         .size exec_message, . - exec_message
636
637 /* Wait for key press specified by %bl (masked by %bh)
638  *
639  * Used by init and INT19 code when prompting user.  If the specified
640  * key is pressed, it is left in the keyboard buffer.
641  *
642  * Returns with ZF set iff specified key is pressed.
643  */
644 wait_for_key:
645         /* Preserve registers */
646         pushw   %cx
647         pushw   %ax
648 1:      /* Empty the keyboard buffer before waiting for input */
649         movb    $0x01, %ah
650         int     $0x16
651         jz      2f
652         xorw    %ax, %ax
653         int     $0x16
654         jmp     1b
655 2:      /* Wait for a key press */
656         movw    $ROM_BANNER_TIMEOUT, %cx
657 3:      decw    %cx
658         js      99f             /* Exit with ZF clear */
659         /* Wait for timer tick to be updated */
660         call    wait_for_tick
661         /* Check to see if a key was pressed */
662         movb    $0x01, %ah
663         int     $0x16
664         jz      3b
665         /* Check to see if key was the specified key */
666         andb    %bh, %al
667         cmpb    %al, %bl
668         je      99f             /* Exit with ZF set */
669         /* Not the specified key: remove from buffer and stop waiting */
670         pushfw
671         xorw    %ax, %ax
672         int     $0x16
673         popfw                   /* Exit with ZF clear */
674 99:     /* Restore registers and return */
675         popw    %ax
676         popw    %cx
677         ret
678         .size wait_for_key, . - wait_for_key
679
680 /* Wait for timer tick
681  *
682  * Used by wait_for_key
683  */
684 wait_for_tick:
685         pushl   %eax
686         pushw   %fs
687         movw    $0x40, %ax
688         movw    %ax, %fs
689         movl    %fs:(0x6c), %eax
690 1:      pushf
691         sti
692         hlt
693         popf
694         cmpl    %fs:(0x6c), %eax
695         je      1b
696         popw    %fs
697         popl    %eax
698         ret
699         .size wait_for_tick, . - wait_for_tick