/* 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 PMM_ALLOCATE 0x0000 #define PMM_FIND 0x0001 #define PMM_HANDLE_BASE ( ( ( 'F' - 'A' + 1 ) << 26 ) + \ ( ( 'E' - 'A' + 1 ) << 21 ) + \ ( ( 'N' - 'A' + 1 ) << 16 ) ) #define PMM_HANDLE_BASE_IMAGE_SOURCE \ ( PMM_HANDLE_BASE | 0x00001000 ) #define PMM_HANDLE_BASE_DECOMPRESS_TO \ ( PMM_HANDLE_BASE | 0x00002000 ) #define PCI_FUNC_MASK 0x07 /* ROM banner timeout, converted to a number of (18Hz) timer ticks. */ #define ROM_BANNER_TIMEOUT_TICKS ( ( 18 * ROM_BANNER_TIMEOUT ) / 10 ) /* Allow payload to be excluded from ROM size */ #if ROMPREFIX_EXCLUDE_PAYLOAD #define ZINFO_TYPE_ADxB "ADHB" #define ZINFO_TYPE_ADxW "ADHW" #else #define ZINFO_TYPE_ADxB "ADDB" #define ZINFO_TYPE_ADxW "ADDW" #endif /* Allow ROM to be marked as containing multiple images */ #if ROMPREFIX_MORE_IMAGES #define INDICATOR 0x00 #else #define INDICATOR 0x80 #endif /* Default to building a PCI ROM if no bus type is specified */ #ifndef BUSTYPE #define BUSTYPE "PCIR" #endif .text .code16 .arch i386 .section ".prefix", "ax", @progbits .globl _rom_start _rom_start: .org 0x00 romheader: .word 0xAA55 /* BIOS extension signature */ romheader_size: .byte 0 /* Size in 512-byte blocks */ jmp init /* Initialisation vector */ checksum: .byte 0 .org 0x10 .word ipxeheader .org 0x16 .word undiheader .ifeqs BUSTYPE, "PCIR" .org 0x18 .word pciheader .endif .org 0x1a .word pnpheader .size romheader, . - romheader .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */ .ascii ZINFO_TYPE_ADxB .long romheader_size .long 512 .long 0 .previous .ifeqs BUSTYPE, "PCIR" 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 0 /* Image length */ .word 0x0001 /* Revision level */ .byte 0x00 /* Code type */ .byte INDICATOR /* Last image indicator */ pciheader_runtime_length: .word 0 /* Maximum run-time image length */ .word 0x0000 /* Configuration utility code header */ .word 0x0000 /* DMTF CLP entry point */ .equ pciheader_len, . - pciheader .size pciheader, . - pciheader .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */ .ascii ZINFO_TYPE_ADxW .long pciheader_image_length .long 512 .long 0 .ascii ZINFO_TYPE_ADxW .long pciheader_runtime_length .long 512 .long 0 .previous .endif /* PCIR */ /* PnP doesn't require any particular alignment, but IBM * BIOSes will scan on 16-byte boundaries rather than using * the offset stored at 0x1a */ .align 16 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 .ifeqs BUSTYPE, "PCIR" prodstr_separator: .byte 0 .ascii "(PCI " prodstr_pci_id: .ascii "xx:xx.x)" /* Filled in by init code */ .endif /* PCIR */ .byte 0 .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 BUSTYPE /* Bus type */ .equ undiheader_len, . - undiheader .size undiheader, . - undiheader ipxeheader: .ascii "iPXE" /* Signature */ .byte ipxeheader_len /* Length of structure */ .byte 0 /* Checksum */ shrunk_rom_size: .byte 0 /* Shrunk size (in 512-byte blocks) */ .byte 0 /* Reserved */ build_id: .long _build_id /* Randomly-generated build ID */ .equ ipxeheader_len, . - ipxeheader .size ipxeheader, . - ipxeheader .section ".zinfo.fixup", "a", @progbits /* Compressor fixups */ .ascii "ADHB" .long shrunk_rom_size .long 512 .long 0 .previous /* 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 /* Print message as early as possible */ movw $init_message, %si xorw %di, %di call print_message /* Store PCI 3.0 runtime segment address for later use, if * applicable. */ .ifeqs BUSTYPE, "PCIR" movw %bx, %gs .endif /* Store PCI bus:dev.fn address, print PCI bus:dev.fn, and add * PCI bus:dev.fn to product name string, if applicable. */ .ifeqs BUSTYPE, "PCIR" xorw %di, %di call print_space movw %ax, init_pci_busdevfn call print_pci_busdevfn movw $prodstr_pci_id, %di call print_pci_busdevfn movb $( ' ' ), prodstr_separator .endif /* Print segment address */ xorw %di, %di call print_space movw %cs, %ax call print_hex_word /* Check for PCI BIOS version, if applicable */ .ifeqs BUSTYPE, "PCIR" 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 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 .endif /* PCIR */ /* 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 jmp pnp_done no_pnp: /* Not PnP-compliant - hook INT 19 */ #ifdef NONPNP_HOOK_INT19 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 ) #endif /* NONPNP_HOOK_INT19 */ pnp_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 whole registers */ pushal /* Allocate image source PMM block. Round up the size to the * nearest 4kB (8 512-byte sectors) to work around AMI BIOS bugs. */ movzbl romheader_size, %ecx addw extra_size, %cx addw $0x0007, %cx /* Round up to multiple of 8 512-byte sectors */ andw $0xfff8, %cx shll $5, %ecx movl $PMM_HANDLE_BASE_IMAGE_SOURCE, %ebx movw $get_pmm_image_source, %bp call get_pmm movl %esi, image_source jz 1f /* Copy ROM to image source PMM block */ pushw %es xorw %ax, %ax movw %ax, %es movl %esi, %edi xorl %esi, %esi movzbl romheader_size, %ecx shll $7, %ecx addr32 rep movsl /* PMM presence implies flat real mode */ popw %es /* Shrink ROM */ movb shrunk_rom_size, %al movb %al, romheader_size 1: /* Allocate decompression PMM block. Round up the size to the * nearest 128kB and use the size within the PMM handle; this * allows the same decompression area to be shared between * multiple iPXE ROMs even with differing build IDs */ movl $_textdata_memsz_pgh, %ecx addl $0x00001fff, %ecx andl $0xffffe000, %ecx movl %ecx, %ebx shrw $12, %bx orl $PMM_HANDLE_BASE_DECOMPRESS_TO, %ebx movw $get_pmm_decompress_to, %bp call get_pmm movl %esi, decompress_to /* Restore registers */ popal no_pmm: /* 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, if applicable. Required for * PCI3.0, which loads us to a temporary location in low * memory. Will be a no-op for lower PCI versions. */ .ifeqs BUSTYPE, "PCIR" xorw %di, %di call print_space 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 .endif /* Skip prompt if this is not the first PCI function, if applicable */ .ifeqs BUSTYPE, "PCIR" testb $PCI_FUNC_MASK, init_pci_busdevfn jnz no_shell .endif /* 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 no_shell /* Ctrl-B was pressed: invoke iPXE. The keypress will be * picked up by the initial shell prompt, and we will drop * into a shell. */ xorl %ebp, %ebp /* Inhibit use of INT 15,e820 and INT 15,e801 */ pushw %cs call exec no_shell: movb $( '\n' ), %al xorw %di, %di call print_character /* 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 /* Attempt to find or allocate PMM block * * Parameters: * %ecx : size of block to allocate, in paragraphs * %ebx : PMM handle base * %bp : routine to check acceptability of found blocks * %es:0000 : PMM structure * Returns: * %ebx : PMM handle * %esi : allocated block address, or zero (with ZF set) if allocation failed */ get_pmm: /* Preserve registers */ pushl %eax pushw %di movw $( ' ' ), %di get_pmm_find: /* Try to find existing block */ pushl %ebx /* PMM handle */ pushw $PMM_FIND lcall *%es:7 addw $6, %sp pushw %dx pushw %ax popl %esi /* Treat 0xffffffff (not supported) as 0x00000000 (not found) */ incl %esi jz get_pmm_allocate decl %esi jz get_pmm_allocate /* Block found - check acceptability */ call *%bp jnc get_pmm_done /* Block not acceptable - increment handle and retry */ incl %ebx jmp get_pmm_find get_pmm_allocate: /* Block not found - try to allocate new block */ pushw $0x0002 /* Extended memory */ pushl %ebx /* PMM handle */ pushl %ecx /* Length */ pushw $PMM_ALLOCATE lcall *%es:7 addw $12, %sp pushw %dx pushw %ax popl %esi movw $( '+' ), %di /* Indicate allocation attempt */ get_pmm_done: /* Print block address */ movw %di, %ax xorw %di, %di call print_character movl %esi, %eax call print_hex_dword /* Treat 0xffffffff (not supported) as 0x00000000 (allocation * failed), and set ZF to indicate a zero result. */ incl %esi jz 1f decl %esi 1: /* Restore registers and return */ popw %di popl %eax ret .size get_pmm, . - get_pmm /* Check acceptability of image source block */ get_pmm_image_source: pushw %es xorw %ax, %ax movw %ax, %es movl build_id, %eax addr32 cmpl %es:build_id(%esi), %eax je 1f stc 1: popw %es ret .size get_pmm_image_source, . - get_pmm_image_source /* Check acceptability of decompression block */ get_pmm_decompress_to: clc ret .size get_pmm_decompress_to, . - get_pmm_decompress_to /* * 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 .ifeqs BUSTYPE, "PCIR" init_message_pci: .asciz " PCI" .size init_message_pci, . - init_message_pci .endif /* PCIR */ init_message_pnp: .asciz " PnP" .size init_message_pnp, . - init_message_pnp init_message_pmm: .asciz " PMM" .size init_message_pmm, . - init_message_pmm 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 /* PCI bus:dev.fn * */ .ifeqs BUSTYPE, "PCIR" init_pci_busdevfn: .word 0 .size init_pci_busdevfn, . - init_pci_busdevfn .endif /* PCIR */ /* Image source area * * May be either zero (indicating to use option ROM space as source), * or within a PMM-allocated block. */ .globl image_source image_source: .long 0 .size image_source, . - image_source /* Additional image source size (in 512-byte sectors) * */ extra_size: .word 0 .size extra_size, . - extra_size /* Temporary decompression area * * May be either zero (indicating to use default decompression area in * high memory), or within a PMM-allocated block. */ .globl decompress_to decompress_to: .long 0 .size decompress_to, . - decompress_to /* Boot Execution Vector entry point * * Called by the PnP BIOS when it wants to boot us. */ bev_entry: orl $0xffffffff, %ebp /* Allow arbitrary relocation */ pushw %cs call exec lret .size bev_entry, . - bev_entry /* 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. */ orl $0xffffffff, %ebp /* Allow arbitrary relocation */ 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 /* Print message as soon as possible */ movw $prodstr, %si xorw %di, %di call print_message movw $exec_message_pre_install, %si call print_message /* Store magic word on BIOS stack and remember BIOS %ss:sp */ pushl $STACK_MAGIC movw %ss, %cx movw %sp, %dx /* Obtain a reasonably-sized temporary stack */ xorw %bx, %bx movw %bx, %ss movw $0x7c00, %sp /* Install iPXE */ call alloc_basemem movl image_source, %esi movl decompress_to, %edi call install_prealloc /* Print message indicating successful installation */ movw $exec_message_post_install, %si xorw %di, %di call print_message /* 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: /* Retrieve PCI bus:dev.fn, if applicable */ .ifeqs BUSTYPE, "PCIR" movw init_pci_busdevfn, %ax .endif /* Set up %ds for access to .data16 */ movw %bx, %ds /* Store PCI bus:dev.fn, if applicable */ .ifeqs BUSTYPE, "PCIR" movw %ax, autoboot_busdevfn .endif /* Call main() */ pushl $main pushw %cs call prot_call popl %eax /* discard */ /* Set up flat real mode for return to BIOS */ call flatten_real_mode /* Uninstall iPXE */ call uninstall /* Restore BIOS stack */ movw %cx, %ss movw %dx, %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_pre_install: .asciz " starting execution..." .size exec_message_pre_install, . - exec_message_pre_install exec_message_post_install: .asciz "ok\n" .size exec_message_post_install, . - exec_message_post_install /* 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_TICKS, %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