/* 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 #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://ipxe.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 iPXE. 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 iPXE or http://ipxe.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 "iPXE (http://ipxe.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 iPXE. 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 iPXE */ 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 iPXE */ 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