| /* At entry, the processor is in 16 bit real mode and the code is being |
| * executed from an address it was not linked to. Code must be pic and |
| * 32 bit sensitive until things are fixed up. |
| * |
| * Also be very careful as the stack is at the rear end of the interrupt |
| * table so using a noticeable amount of stack space is a no-no. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER ) |
| |
| #include <config/general.h> |
| |
| #define PNP_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'n' << 16 ) + ( 'P' << 24 ) ) |
| #define PMM_SIGNATURE ( '$' + ( 'P' << 8 ) + ( 'M' << 16 ) + ( 'M' << 24 ) ) |
| #define PCI_SIGNATURE ( 'P' + ( 'C' << 8 ) + ( 'I' << 16 ) + ( ' ' << 24 ) ) |
| #define STACK_MAGIC ( 'L' + ( 'R' << 8 ) + ( 'E' << 16 ) + ( 'T' << 24 ) ) |
| #define PNP_GET_BBS_VERSION 0x60 |
| #define PMM_ALLOCATE 0x0000 |
| #define PMM_DEALLOCATE 0x0002 |
| |
| /* ROM banner timeout. Based on the configurable BANNER_TIMEOUT in |
| * config.h, but converted to a number of (18Hz) timer ticks, and |
| * doubled to allow for BIOSes that switch video modes immediately |
| * beforehand, so rendering the message almost invisible to the user. |
| */ |
| #define ROM_BANNER_TIMEOUT ( 2 * ( 18 * BANNER_TIMEOUT ) / 10 ) |
| |
| /* We can load a ROM in two ways: have the BIOS load all of it (.rom prefix) |
| * or have the BIOS load a stub that loads the rest using PCI (.xrom prefix). |
| * The latter is not as widely supported, but allows the use of large ROMs |
| * on some systems with crowded option ROM space. |
| */ |
| |
| #ifdef LOAD_ROM_FROM_PCI |
| #define ROM_SIZE_VALUE _prefix_filesz_sect /* Amount to load in BIOS */ |
| #else |
| #define ROM_SIZE_VALUE 0 /* Load amount (before compr. fixup) */ |
| #endif |
| |
| |
| .text |
| .code16 |
| .arch i386 |
| .section ".prefix", "ax", @progbits |
| |
| .org 0x00 |
| romheader: |
| .word 0xAA55 /* BIOS extension signature */ |
| romheader_size: .byte ROM_SIZE_VALUE /* Size in 512-byte blocks */ |
| jmp init /* Initialisation vector */ |
| checksum: |
| .byte 0, 0 |
| real_size: |
| .word 0 |
| .org 0x16 |
| .word undiheader |
| .org 0x18 |
| .word pciheader |
| .org 0x1a |
| .word pnpheader |
| .size romheader, . - romheader |
| |
| .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */ |
| #ifndef LOAD_ROM_FROM_PCI |
| .ascii "ADDB" |
| .long romheader_size |
| .long 512 |
| .long 0 |
| #endif |
| .ascii "ADDB" |
| .long real_size |
| .long 512 |
| .long 0 |
| .previous |
| |
| pciheader: |
| .ascii "PCIR" /* Signature */ |
| .word pci_vendor_id /* Vendor identification */ |
| .word pci_device_id /* Device identification */ |
| .word 0x0000 /* Device list pointer */ |
| .word pciheader_len /* PCI data structure length */ |
| .byte 0x03 /* PCI data structure revision */ |
| .byte 0x02, 0x00, 0x00 /* Class code */ |
| pciheader_image_length: |
| .word ROM_SIZE_VALUE /* Image length */ |
| .word 0x0001 /* Revision level */ |
| .byte 0x00 /* Code type */ |
| .byte 0x80 /* Last image indicator */ |
| pciheader_runtime_length: |
| .word ROM_SIZE_VALUE /* Maximum run-time image length */ |
| .word 0x0000 /* Configuration utility code header */ |
| .word 0x0000 /* DMTF CLP entry point */ |
| .equ pciheader_len, . - pciheader |
| .size pciheader, . - pciheader |
| |
| #ifndef LOAD_ROM_FROM_PCI |
| .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */ |
| .ascii "ADDW" |
| .long pciheader_image_length |
| .long 512 |
| .long 0 |
| .ascii "ADDW" |
| .long pciheader_runtime_length |
| .long 512 |
| .long 0 |
| .previous |
| #endif |
| |
| pnpheader: |
| .ascii "$PnP" /* Signature */ |
| .byte 0x01 /* Structure revision */ |
| .byte ( pnpheader_len / 16 ) /* Length (in 16 byte increments) */ |
| .word 0x0000 /* Offset of next header */ |
| .byte 0x00 /* Reserved */ |
| .byte 0x00 /* Checksum */ |
| .long 0x00000000 /* Device identifier */ |
| .word mfgstr /* Manufacturer string */ |
| .word prodstr /* Product name */ |
| .byte 0x02 /* Device base type code */ |
| .byte 0x00 /* Device sub-type code */ |
| .byte 0x00 /* Device interface type code */ |
| .byte 0xf4 /* Device indicator */ |
| .word 0x0000 /* Boot connection vector */ |
| .word 0x0000 /* Disconnect vector */ |
| .word bev_entry /* Boot execution vector */ |
| .word 0x0000 /* Reserved */ |
| .word 0x0000 /* Static resource information vector*/ |
| .equ pnpheader_len, . - pnpheader |
| .size pnpheader, . - pnpheader |
| |
| /* Manufacturer string */ |
| mfgstr: |
| .asciz "http://etherboot.org" |
| .size mfgstr, . - mfgstr |
| |
| /* Product string |
| * |
| * Defaults to PRODUCT_SHORT_NAME. If the ROM image is writable at |
| * initialisation time, it will be filled in to include the PCI |
| * bus:dev.fn number of the card as well. |
| */ |
| prodstr: |
| .ascii PRODUCT_SHORT_NAME |
| prodstr_separator: |
| .byte 0 |
| .ascii "(PCI " |
| prodstr_pci_id: |
| .asciz "xx:xx.x)" /* Filled in by init code */ |
| .size prodstr, . - prodstr |
| |
| .globl undiheader |
| .weak undiloader |
| undiheader: |
| .ascii "UNDI" /* Signature */ |
| .byte undiheader_len /* Length of structure */ |
| .byte 0 /* Checksum */ |
| .byte 0 /* Structure revision */ |
| .byte 0,1,2 /* PXE version: 2.1.0 */ |
| .word undiloader /* Offset to loader routine */ |
| .word _data16_memsz /* Stack segment size */ |
| .word _data16_memsz /* Data segment size */ |
| .word _text16_memsz /* Code segment size */ |
| .ascii "PCIR" /* Bus type */ |
| .equ undiheader_len, . - undiheader |
| .size undiheader, . - undiheader |
| |
| /* Initialisation (called once during POST) |
| * |
| * Determine whether or not this is a PnP system via a signature |
| * check. If it is PnP, return to the PnP BIOS indicating that we are |
| * a boot-capable device; the BIOS will call our boot execution vector |
| * if it wants to boot us. If it is not PnP, hook INT 19. |
| */ |
| init: |
| /* Preserve registers, clear direction flag, set %ds=%cs */ |
| pushaw |
| pushw %ds |
| pushw %es |
| pushw %fs |
| pushw %gs |
| cld |
| pushw %cs |
| popw %ds |
| |
| /* Shuffle some registers around. We need %di available for |
| * the print_xxx functions, and in a register that's |
| * addressable from %es, so shuffle as follows: |
| * |
| * %di (pointer to PnP structure) => %bx |
| * %bx (runtime segment address, for PCI 3.0) => %gs |
| */ |
| movw %bx, %gs |
| movw %di, %bx |
| |
| /* Print message as early as possible */ |
| movw $init_message, %si |
| xorw %di, %di |
| call print_message |
| call print_pci_busdevfn |
| |
| #ifdef LOAD_ROM_FROM_PCI |
| /* Save PCI bus:dev.fn for later use */ |
| movw %ax, pci_busdevfn |
| #endif |
| |
| /* Fill in product name string, if possible */ |
| movw $prodstr_pci_id, %di |
| call print_pci_busdevfn |
| movb $( ' ' ), prodstr_separator |
| |
| /* Print segment address */ |
| movb $( ' ' ), %al |
| xorw %di, %di |
| call print_character |
| movw %cs, %ax |
| call print_hex_word |
| |
| /* Check for PCI BIOS version */ |
| pushl %ebx |
| pushl %edx |
| pushl %edi |
| stc |
| movw $0xb101, %ax |
| int $0x1a |
| jc no_pci3 |
| cmpl $PCI_SIGNATURE, %edx |
| jne no_pci3 |
| testb %ah, %ah |
| jnz no_pci3 |
| #ifdef LOAD_ROM_FROM_PCI |
| incb pcibios_present |
| #endif |
| movw $init_message_pci, %si |
| xorw %di, %di |
| call print_message |
| movb %bh, %al |
| call print_hex_nibble |
| movb $( '.' ), %al |
| call print_character |
| movb %bl, %al |
| call print_hex_byte |
| cmpb $3, %bh |
| jb no_pci3 |
| /* PCI >=3.0: leave %gs as-is if sane */ |
| movw %gs, %ax |
| cmpw $0xa000, %ax /* Insane if %gs < 0xa000 */ |
| jb pci3_insane |
| movw %cs, %bx /* Sane if %cs == %gs */ |
| cmpw %bx, %ax |
| je 1f |
| movzbw romheader_size, %cx /* Sane if %cs+len <= %gs */ |
| shlw $5, %cx |
| addw %cx, %bx |
| cmpw %bx, %ax |
| jae 1f |
| movw %cs, %bx /* Sane if %gs+len <= %cs */ |
| addw %cx, %ax |
| cmpw %bx, %ax |
| jbe 1f |
| pci3_insane: /* PCI 3.0 with insane %gs value: print error and ignore %gs */ |
| movb $( '!' ), %al |
| call print_character |
| movw %gs, %ax |
| call print_hex_word |
| no_pci3: |
| /* PCI <3.0: set %gs (runtime segment) = %cs (init-time segment) */ |
| pushw %cs |
| popw %gs |
| 1: popl %edi |
| popl %edx |
| popl %ebx |
| |
| /* Check for PnP BIOS. Although %es:di should point to the |
| * PnP BIOS signature on entry, some BIOSes fail to do this. |
| */ |
| movw $( 0xf000 - 1 ), %bx |
| pnp_scan: |
| incw %bx |
| jz no_pnp |
| movw %bx, %es |
| cmpl $PNP_SIGNATURE, %es:0 |
| jne pnp_scan |
| xorw %dx, %dx |
| xorw %si, %si |
| movzbw %es:5, %cx |
| 1: es lodsb |
| addb %al, %dl |
| loop 1b |
| jnz pnp_scan |
| /* Is PnP: print PnP message */ |
| movw $init_message_pnp, %si |
| xorw %di, %di |
| call print_message |
| /* Check for BBS */ |
| pushw %es:0x1b /* Real-mode data segment */ |
| pushw %ds /* &(bbs_version) */ |
| pushw $bbs_version |
| pushw $PNP_GET_BBS_VERSION |
| lcall *%es:0xd |
| addw $8, %sp |
| testw %ax, %ax |
| je got_bbs |
| no_pnp: /* Not PnP-compliant - therefore cannot be BBS-compliant */ |
| no_bbs: /* Not BBS-compliant - must hook INT 19 */ |
| movw $init_message_int19, %si |
| xorw %di, %di |
| call print_message |
| xorw %ax, %ax |
| movw %ax, %es |
| pushl %es:( 0x19 * 4 ) |
| popl orig_int19 |
| pushw %gs /* %gs contains runtime %cs */ |
| pushw $int19_entry |
| popl %es:( 0x19 * 4 ) |
| jmp bbs_done |
| got_bbs: /* BBS compliant - no need to hook INT 19 */ |
| movw $init_message_bbs, %si |
| xorw %di, %di |
| call print_message |
| bbs_done: |
| |
| /* Check for PMM */ |
| movw $( 0xe000 - 1 ), %bx |
| pmm_scan: |
| incw %bx |
| jz no_pmm |
| movw %bx, %es |
| cmpl $PMM_SIGNATURE, %es:0 |
| jne pmm_scan |
| xorw %dx, %dx |
| xorw %si, %si |
| movzbw %es:5, %cx |
| 1: es lodsb |
| addb %al, %dl |
| loop 1b |
| jnz pmm_scan |
| /* PMM found: print PMM message */ |
| movw $init_message_pmm, %si |
| xorw %di, %di |
| call print_message |
| /* We have PMM and so a 1kB stack: preserve upper register halves */ |
| pushal |
| /* Calculate required allocation size in %esi */ |
| movzwl real_size, %eax |
| shll $9, %eax |
| addl $_textdata_memsz, %eax |
| orw $0xffff, %ax /* Ensure allocation size is at least 64kB */ |
| bsrl %eax, %ecx |
| subw $15, %cx /* Round up and convert to 64kB count */ |
| movw $1, %si |
| shlw %cl, %si |
| pmm_loop: |
| /* Try to allocate block via PMM */ |
| pushw $0x0006 /* Aligned, extended memory */ |
| pushl $0xffffffff /* No handle */ |
| movzwl %si, %eax |
| shll $12, %eax |
| pushl %eax /* Allocation size in paragraphs */ |
| pushw $PMM_ALLOCATE |
| lcall *%es:7 |
| addw $12, %sp |
| /* Abort if allocation fails */ |
| testw %dx, %dx /* %ax==0 even on success, since align>=64kB */ |
| jz pmm_fail |
| /* If block has A20==1, free block and try again with twice |
| * the allocation size (and hence alignment). |
| */ |
| testw $0x0010, %dx |
| jz got_pmm |
| pushw %dx |
| pushw $0 |
| pushw $PMM_DEALLOCATE |
| lcall *%es:7 |
| addw $6, %sp |
| addw %si, %si |
| jmp pmm_loop |
| got_pmm: /* PMM allocation succeeded */ |
| movw %dx, ( image_source + 2 ) |
| movw %dx, %ax |
| xorw %di, %di |
| call print_hex_word |
| movb $( '@' ), %al |
| call print_character |
| movw %si, %ax |
| call print_hex_byte |
| pmm_copy: |
| /* Copy ROM to PMM block */ |
| xorw %ax, %ax |
| movw %ax, %es |
| movl image_source, %edi |
| xorl %esi, %esi |
| movzbl romheader_size, %ecx |
| shll $9, %ecx |
| addr32 rep movsb /* PMM presence implies flat real mode */ |
| movl %edi, decompress_to |
| /* Shrink ROM */ |
| movb $_prefix_memsz_sect, romheader_size |
| #if defined(SHRINK_WITHOUT_PMM) || defined(LOAD_ROM_FROM_PCI) |
| jmp pmm_done |
| pmm_fail: |
| /* Print marker and copy ourselves to high memory */ |
| movl $HIGHMEM_LOADPOINT, image_source |
| xorw %di, %di |
| movb $( '!' ), %al |
| call print_character |
| jmp pmm_copy |
| pmm_done: |
| #else |
| pmm_fail: |
| #endif |
| /* Restore upper register halves */ |
| popal |
| #if defined(LOAD_ROM_FROM_PCI) |
| call load_from_pci |
| jc load_err |
| jmp load_ok |
| no_pmm: |
| /* Cannot continue without PMM - print error message */ |
| xorw %di, %di |
| movw $init_message_no_pmm, %si |
| call print_message |
| load_err: |
| /* Wait for five seconds to let user see message */ |
| movw $90, %cx |
| 1: call wait_for_tick |
| loop 1b |
| /* Mark environment as invalid and return */ |
| movl $0, decompress_to |
| jmp out |
| |
| load_ok: |
| #else |
| no_pmm: |
| #endif |
| /* Update checksum */ |
| xorw %bx, %bx |
| xorw %si, %si |
| movzbw romheader_size, %cx |
| shlw $9, %cx |
| 1: lodsb |
| addb %al, %bl |
| loop 1b |
| subb %bl, checksum |
| |
| /* Copy self to option ROM space. Required for PCI3.0, which |
| * loads us to a temporary location in low memory. Will be a |
| * no-op for lower PCI versions. |
| */ |
| movb $( ' ' ), %al |
| xorw %di, %di |
| call print_character |
| movw %gs, %ax |
| call print_hex_word |
| movzbw romheader_size, %cx |
| shlw $9, %cx |
| movw %ax, %es |
| xorw %si, %si |
| xorw %di, %di |
| cs rep movsb |
| |
| /* Prompt for POST-time shell */ |
| movw $init_message_prompt, %si |
| xorw %di, %di |
| call print_message |
| movw $prodstr, %si |
| call print_message |
| movw $init_message_dots, %si |
| call print_message |
| /* Wait for Ctrl-B */ |
| movw $0xff02, %bx |
| call wait_for_key |
| /* Clear prompt */ |
| pushf |
| xorw %di, %di |
| call print_kill_line |
| movw $init_message_done, %si |
| call print_message |
| popf |
| jnz out |
| /* Ctrl-B was pressed: invoke gPXE. The keypress will be |
| * picked up by the initial shell prompt, and we will drop |
| * into a shell. |
| */ |
| pushw %cs |
| call exec |
| out: |
| /* Restore registers */ |
| popw %gs |
| popw %fs |
| popw %es |
| popw %ds |
| popaw |
| |
| /* Indicate boot capability to PnP BIOS, if present */ |
| movw $0x20, %ax |
| lret |
| .size init, . - init |
| |
| /* |
| * Note to hardware vendors: |
| * |
| * If you wish to brand this boot ROM, please do so by defining the |
| * strings PRODUCT_NAME and PRODUCT_SHORT_NAME in config/general.h. |
| * |
| * While nothing in the GPL prevents you from removing all references |
| * to gPXE or http://etherboot.org, we prefer you not to do so. |
| * |
| * If you have an OEM-mandated branding requirement that cannot be |
| * satisfied simply by defining PRODUCT_NAME and PRODUCT_SHORT_NAME, |
| * please contact us. |
| * |
| * [ Including an ASCII NUL in PRODUCT_NAME is considered to be |
| * bypassing the spirit of this request! ] |
| */ |
| init_message: |
| .ascii "\n" |
| .ascii PRODUCT_NAME |
| .ascii "\n" |
| .asciz "gPXE (http://etherboot.org) - " |
| .size init_message, . - init_message |
| init_message_pci: |
| .asciz " PCI" |
| .size init_message_pci, . - init_message_pci |
| init_message_pnp: |
| .asciz " PnP" |
| .size init_message_pnp, . - init_message_pnp |
| init_message_bbs: |
| .asciz " BBS" |
| .size init_message_bbs, . - init_message_bbs |
| init_message_pmm: |
| .asciz " PMM" |
| .size init_message_pmm, . - init_message_pmm |
| #ifdef LOAD_ROM_FROM_PCI |
| init_message_no_pmm: |
| .asciz "\nPMM required but not present!\n" |
| .size init_message_no_pmm, . - init_message_no_pmm |
| #endif |
| init_message_int19: |
| .asciz " INT19" |
| .size init_message_int19, . - init_message_int19 |
| init_message_prompt: |
| .asciz "\nPress Ctrl-B to configure " |
| .size init_message_prompt, . - init_message_prompt |
| init_message_dots: |
| .asciz "..." |
| .size init_message_dots, . - init_message_dots |
| init_message_done: |
| .asciz "\n\n" |
| .size init_message_done, . - init_message_done |
| |
| /* ROM image location |
| * |
| * May be either within option ROM space, or within PMM-allocated block. |
| */ |
| .globl image_source |
| image_source: |
| .long 0 |
| .size image_source, . - image_source |
| |
| /* Temporary decompression area |
| * |
| * May be either at HIGHMEM_LOADPOINT, or within PMM-allocated block. |
| * If a PCI ROM load fails, this will be set to zero. |
| */ |
| .globl decompress_to |
| decompress_to: |
| .long HIGHMEM_LOADPOINT |
| .size decompress_to, . - decompress_to |
| |
| #ifdef LOAD_ROM_FROM_PCI |
| |
| /* Set if the PCI BIOS is present, even <3.0 */ |
| pcibios_present: |
| .byte 0 |
| .byte 0 /* for alignment */ |
| .size pcibios_present, . - pcibios_present |
| |
| /* PCI bus:device.function word |
| * |
| * Filled in by init in the .xrom case, so the remainder of the ROM |
| * can be located. |
| */ |
| pci_busdevfn: |
| .word 0 |
| .size pci_busdevfn, . - pci_busdevfn |
| |
| #endif |
| |
| /* BBS version |
| * |
| * Filled in by BBS BIOS. We ignore the value. |
| */ |
| bbs_version: |
| .word 0 |
| .size bbs_version, . - bbs_version |
| |
| /* Boot Execution Vector entry point |
| * |
| * Called by the PnP BIOS when it wants to boot us. |
| */ |
| bev_entry: |
| pushw %cs |
| call exec |
| lret |
| .size bev_entry, . - bev_entry |
| |
| |
| #ifdef LOAD_ROM_FROM_PCI |
| |
| #define PCI_ROM_ADDRESS 0x30 /* Bits 31:11 address, 10:1 reserved */ |
| #define PCI_ROM_ADDRESS_ENABLE 0x00000001 |
| #define PCI_ROM_ADDRESS_MASK 0xfffff800 |
| |
| #define PCIBIOS_READ_WORD 0xb109 |
| #define PCIBIOS_READ_DWORD 0xb10a |
| #define PCIBIOS_WRITE_WORD 0xb10c |
| #define PCIBIOS_WRITE_DWORD 0xb10d |
| |
| /* Determine size of PCI BAR |
| * |
| * %bx : PCI bus:dev.fn to probe |
| * %di : Address of BAR to find size of |
| * %edx : Mask of address bits within BAR |
| * |
| * %ecx : Size for a memory resource, |
| * 1 for an I/O resource (bit 0 set). |
| * CF : Set on error or nonexistent device (all-ones read) |
| * |
| * All other registers saved. |
| */ |
| pci_bar_size: |
| /* Save registers */ |
| pushw %ax |
| pushl %esi |
| pushl %edx |
| |
| /* Read current BAR value */ |
| movw $PCIBIOS_READ_DWORD, %ax |
| int $0x1a |
| |
| /* Check for device existence and save it */ |
| testb $1, %cl /* I/O bit? */ |
| jz 1f |
| andl $1, %ecx /* If so, exit with %ecx = 1 */ |
| jmp 99f |
| 1: notl %ecx |
| testl %ecx, %ecx /* Set ZF iff %ecx was all-ones */ |
| notl %ecx |
| jnz 1f |
| stc /* All ones - exit with CF set */ |
| jmp 99f |
| 1: movl %ecx, %esi /* Save in %esi */ |
| |
| /* Write all ones to BAR */ |
| movl %edx, %ecx |
| movw $PCIBIOS_WRITE_DWORD, %ax |
| int $0x1a |
| |
| /* Read back BAR */ |
| movw $PCIBIOS_READ_DWORD, %ax |
| int $0x1a |
| |
| /* Find decode size from least set bit in mask BAR */ |
| bsfl %ecx, %ecx /* Find least set bit, log2(decode size) */ |
| jz 1f /* Mask BAR should not be zero */ |
| xorl %edx, %edx |
| incl %edx |
| shll %cl, %edx /* %edx = decode size */ |
| jmp 2f |
| 1: xorl %edx, %edx /* Return zero size for mask BAR zero */ |
| |
| /* Restore old BAR value */ |
| 2: movl %esi, %ecx |
| movw $PCIBIOS_WRITE_DWORD, %ax |
| int $0x1a |
| |
| movl %edx, %ecx /* Return size in %ecx */ |
| |
| /* Restore registers and return */ |
| 99: popl %edx |
| popl %esi |
| popw %ax |
| ret |
| |
| .size pci_bar_size, . - pci_bar_size |
| |
| /* PCI ROM loader |
| * |
| * Called from init in the .xrom case to load the non-prefix code |
| * using the PCI ROM BAR. |
| * |
| * Returns with carry flag set on error. All registers saved. |
| */ |
| load_from_pci: |
| /* |
| * Use PCI BIOS access to config space. The calls take |
| * |
| * %ah : 0xb1 %al : function |
| * %bx : bus/dev/fn |
| * %di : config space address |
| * %ecx : value to write (for writes) |
| * |
| * %ecx : value read (for reads) |
| * %ah : return code |
| * CF : error indication |
| * |
| * All registers not used for return are preserved. |
| */ |
| |
| /* Save registers and set up %es for big real mode */ |
| pushal |
| pushw %es |
| xorw %ax, %ax |
| movw %ax, %es |
| |
| /* Check PCI BIOS presence */ |
| cmpb $0, pcibios_present |
| jz err_pcibios |
| |
| /* Load existing PCI ROM BAR */ |
| movw $PCIBIOS_READ_DWORD, %ax |
| movw pci_busdevfn, %bx |
| movw $PCI_ROM_ADDRESS, %di |
| int $0x1a |
| |
| /* Maybe it's already enabled? */ |
| testb $PCI_ROM_ADDRESS_ENABLE, %cl |
| jz 1f |
| movb $1, %dl /* Flag indicating no deinit required */ |
| movl %ecx, %ebp |
| jmp check_rom |
| |
| /* Determine PCI BAR decode size */ |
| 1: movl $PCI_ROM_ADDRESS_MASK, %edx |
| call pci_bar_size /* Returns decode size in %ecx */ |
| jc err_size_insane /* CF => no ROM BAR, %ecx == ffffffff */ |
| |
| /* Check sanity of decode size */ |
| xorl %eax, %eax |
| movw real_size, %ax |
| shll $9, %eax /* %eax = ROM size */ |
| cmpl %ecx, %eax |
| ja err_size_insane /* Insane if decode size < ROM size */ |
| cmpl $0x100000, %ecx |
| jae err_size_insane /* Insane if decode size >= 1MB */ |
| |
| /* Find a place to map the BAR |
| * In theory we should examine e820 and all PCI BARs to find a |
| * free region. However, we run at POST when e820 may not be |
| * available, and memory reads of an unmapped location are |
| * de facto standardized to return all-ones. Thus, we can get |
| * away with searching high memory (0xf0000000 and up) on |
| * multiples of the ROM BAR decode size for a sufficiently |
| * large all-ones region. |
| */ |
| movl %ecx, %edx /* Save ROM BAR size in %edx */ |
| movl $0xf0000000, %ebp |
| xorl %eax, %eax |
| notl %eax /* %eax = all ones */ |
| bar_search: |
| movl %ebp, %edi |
| movl %edx, %ecx |
| shrl $2, %ecx |
| addr32 repe scasl /* Scan %es:edi for anything not all-ones */ |
| jz bar_found |
| addl %edx, %ebp |
| testl $0x80000000, %ebp |
| jz err_no_bar |
| jmp bar_search |
| |
| bar_found: |
| movl %edi, %ebp |
| /* Save current BAR value on stack to restore later */ |
| movw $PCIBIOS_READ_DWORD, %ax |
| movw $PCI_ROM_ADDRESS, %di |
| int $0x1a |
| pushl %ecx |
| |
| /* Map the ROM */ |
| movw $PCIBIOS_WRITE_DWORD, %ax |
| movl %ebp, %ecx |
| orb $PCI_ROM_ADDRESS_ENABLE, %cl |
| int $0x1a |
| |
| xorb %dl, %dl /* %dl = 0 : ROM was not already mapped */ |
| check_rom: |
| /* Check and copy ROM - enter with %dl set to skip unmapping, |
| * %ebp set to mapped ROM BAR address. |
| * We check up to prodstr_separator for equality, since anything past |
| * that may have been modified. Since our check includes the checksum |
| * byte over the whole ROM stub, that should be sufficient. |
| */ |
| xorb %dh, %dh /* %dh = 0 : ROM did not fail integrity check */ |
| |
| /* Verify ROM integrity */ |
| xorl %esi, %esi |
| movl %ebp, %edi |
| movl $prodstr_separator, %ecx |
| addr32 repe cmpsb |
| jz copy_rom |
| incb %dh /* ROM failed integrity check */ |
| movl %ecx, %ebp /* Save number of bytes left */ |
| jmp skip_load |
| |
| copy_rom: |
| /* Print BAR address and indicate whether we mapped it ourselves */ |
| movb $( ' ' ), %al |
| xorw %di, %di |
| call print_character |
| movl %ebp, %eax |
| call print_hex_dword |
| movb $( '-' ), %al /* '-' for self-mapped */ |
| subb %dl, %al |
| subb %dl, %al /* '+' = '-' - 2 for BIOS-mapped */ |
| call print_character |
| |
| /* Copy ROM at %ebp to PMM or highmem block */ |
| movl %ebp, %esi |
| movl image_source, %edi |
| movzwl real_size, %ecx |
| shll $9, %ecx |
| addr32 es rep movsb |
| movl %edi, decompress_to |
| skip_load: |
| testb %dl, %dl /* Was ROM already mapped? */ |
| jnz skip_unmap |
| |
| /* Unmap the ROM by restoring old ROM BAR */ |
| movw $PCIBIOS_WRITE_DWORD, %ax |
| movw $PCI_ROM_ADDRESS, %di |
| popl %ecx |
| int $0x1a |
| |
| skip_unmap: |
| /* Error handling */ |
| testb %dh, %dh |
| jnz err_rom_invalid |
| clc |
| jmp 99f |
| |
| err_pcibios: /* No PCI BIOS available */ |
| movw $load_message_no_pcibios, %si |
| xorl %eax, %eax /* "error code" is zero */ |
| jmp 1f |
| err_size_insane: /* BAR has size (%ecx) that is insane */ |
| movw $load_message_size_insane, %si |
| movl %ecx, %eax |
| jmp 1f |
| err_no_bar: /* No space of sufficient size (%edx) found */ |
| movw $load_message_no_bar, %si |
| movl %edx, %eax |
| jmp 1f |
| err_rom_invalid: /* Loaded ROM does not match (%ebp bytes left) */ |
| movw $load_message_rom_invalid, %si |
| movzbl romheader_size, %eax |
| shll $9, %eax |
| subl %ebp, %eax |
| decl %eax /* %eax is now byte index of failure */ |
| |
| 1: /* Error handler - print message at %si and dword in %eax */ |
| xorw %di, %di |
| call print_message |
| call print_hex_dword |
| stc |
| 99: popw %es |
| popal |
| ret |
| |
| .size load_from_pci, . - load_from_pci |
| |
| load_message_no_pcibios: |
| .asciz "\nNo PCI BIOS found! " |
| .size load_message_no_pcibios, . - load_message_no_pcibios |
| |
| load_message_size_insane: |
| .asciz "\nROM resource has invalid size " |
| .size load_message_size_insane, . - load_message_size_insane |
| |
| load_message_no_bar: |
| .asciz "\nNo memory hole of sufficient size " |
| .size load_message_no_bar, . - load_message_no_bar |
| |
| load_message_rom_invalid: |
| .asciz "\nLoaded ROM is invalid at " |
| .size load_message_rom_invalid, . - load_message_rom_invalid |
| |
| #endif /* LOAD_ROM_FROM_PCI */ |
| |
| |
| /* INT19 entry point |
| * |
| * Called via the hooked INT 19 if we detected a non-PnP BIOS. We |
| * attempt to return via the original INT 19 vector (if we were able |
| * to store it). |
| */ |
| int19_entry: |
| pushw %cs |
| popw %ds |
| /* Prompt user to press B to boot */ |
| movw $int19_message_prompt, %si |
| xorw %di, %di |
| call print_message |
| movw $prodstr, %si |
| call print_message |
| movw $int19_message_dots, %si |
| call print_message |
| movw $0xdf4e, %bx |
| call wait_for_key |
| pushf |
| xorw %di, %di |
| call print_kill_line |
| movw $int19_message_done, %si |
| call print_message |
| popf |
| jz 1f |
| /* Leave keypress in buffer and start gPXE. The keypress will |
| * cause the usual initial Ctrl-B prompt to be skipped. |
| */ |
| pushw %cs |
| call exec |
| 1: /* Try to call original INT 19 vector */ |
| movl %cs:orig_int19, %eax |
| testl %eax, %eax |
| je 2f |
| ljmp *%cs:orig_int19 |
| 2: /* No chained vector: issue INT 18 as a last resort */ |
| int $0x18 |
| .size int19_entry, . - int19_entry |
| orig_int19: |
| .long 0 |
| .size orig_int19, . - orig_int19 |
| |
| int19_message_prompt: |
| .asciz "Press N to skip booting from " |
| .size int19_message_prompt, . - int19_message_prompt |
| int19_message_dots: |
| .asciz "..." |
| .size int19_message_dots, . - int19_message_dots |
| int19_message_done: |
| .asciz "\n\n" |
| .size int19_message_done, . - int19_message_done |
| |
| /* Execute as a boot device |
| * |
| */ |
| exec: /* Set %ds = %cs */ |
| pushw %cs |
| popw %ds |
| |
| #ifdef LOAD_ROM_FROM_PCI |
| /* Don't execute if load was invalid */ |
| cmpl $0, decompress_to |
| jne 1f |
| lret |
| 1: |
| #endif |
| |
| /* Print message as soon as possible */ |
| movw $prodstr, %si |
| xorw %di, %di |
| call print_message |
| movw $exec_message, %si |
| call print_message |
| |
| /* Store magic word on BIOS stack and remember BIOS %ss:sp */ |
| pushl $STACK_MAGIC |
| movw %ss, %dx |
| movw %sp, %bp |
| |
| /* Obtain a reasonably-sized temporary stack */ |
| xorw %ax, %ax |
| movw %ax, %ss |
| movw $0x7c00, %sp |
| |
| /* Install gPXE */ |
| movl image_source, %esi |
| movl decompress_to, %edi |
| call alloc_basemem |
| call install_prealloc |
| |
| /* Set up real-mode stack */ |
| movw %bx, %ss |
| movw $_estack16, %sp |
| |
| /* Jump to .text16 segment */ |
| pushw %ax |
| pushw $1f |
| lret |
| .section ".text16", "awx", @progbits |
| 1: /* Call main() */ |
| pushl $main |
| pushw %cs |
| call prot_call |
| popl %ecx /* discard */ |
| |
| /* Uninstall gPXE */ |
| call uninstall |
| |
| /* Restore BIOS stack */ |
| movw %dx, %ss |
| movw %bp, %sp |
| |
| /* Check magic word on BIOS stack */ |
| popl %eax |
| cmpl $STACK_MAGIC, %eax |
| jne 1f |
| /* BIOS stack OK: return to caller */ |
| lret |
| 1: /* BIOS stack corrupt: use INT 18 */ |
| int $0x18 |
| .previous |
| |
| exec_message: |
| .asciz " starting execution\n" |
| .size exec_message, . - exec_message |
| |
| /* Wait for key press specified by %bl (masked by %bh) |
| * |
| * Used by init and INT19 code when prompting user. If the specified |
| * key is pressed, it is left in the keyboard buffer. |
| * |
| * Returns with ZF set iff specified key is pressed. |
| */ |
| wait_for_key: |
| /* Preserve registers */ |
| pushw %cx |
| pushw %ax |
| 1: /* Empty the keyboard buffer before waiting for input */ |
| movb $0x01, %ah |
| int $0x16 |
| jz 2f |
| xorw %ax, %ax |
| int $0x16 |
| jmp 1b |
| 2: /* Wait for a key press */ |
| movw $ROM_BANNER_TIMEOUT, %cx |
| 3: decw %cx |
| js 99f /* Exit with ZF clear */ |
| /* Wait for timer tick to be updated */ |
| call wait_for_tick |
| /* Check to see if a key was pressed */ |
| movb $0x01, %ah |
| int $0x16 |
| jz 3b |
| /* Check to see if key was the specified key */ |
| andb %bh, %al |
| cmpb %al, %bl |
| je 99f /* Exit with ZF set */ |
| /* Not the specified key: remove from buffer and stop waiting */ |
| pushfw |
| xorw %ax, %ax |
| int $0x16 |
| popfw /* Exit with ZF clear */ |
| 99: /* Restore registers and return */ |
| popw %ax |
| popw %cx |
| ret |
| .size wait_for_key, . - wait_for_key |
| |
| /* Wait for timer tick |
| * |
| * Used by wait_for_key |
| */ |
| wait_for_tick: |
| pushl %eax |
| pushw %fs |
| movw $0x40, %ax |
| movw %ax, %fs |
| movl %fs:(0x6c), %eax |
| 1: pushf |
| sti |
| hlt |
| popf |
| cmpl %fs:(0x6c), %eax |
| je 1b |
| popw %fs |
| popl %eax |
| ret |
| .size wait_for_tick, . - wait_for_tick |