diff options
-rw-r--r-- | src/Makefile.housekeeping | 2 | ||||
-rw-r--r-- | src/arch/i386/Makefile.pcbios | 2 | ||||
-rw-r--r-- | src/arch/i386/prefix/romprefix.S | 387 | ||||
-rw-r--r-- | src/arch/i386/prefix/xromprefix.S | 9 | ||||
-rw-r--r-- | src/arch/i386/scripts/i386.lds | 1 | ||||
-rwxr-xr-x | src/util/makerom.pl | 30 |
6 files changed, 409 insertions, 22 deletions
diff --git a/src/Makefile.housekeeping b/src/Makefile.housekeeping index 1642374c..1f5e115f 100644 --- a/src/Makefile.housekeeping +++ b/src/Makefile.housekeeping @@ -831,6 +831,8 @@ endif # defined(BIN) FINALISE_rom = $(MAKEROM) $(MAKEROM_FLAGS) $(TGT_MAKEROM_FLAGS) \ -i$(IDENT) -s 0 $@ FINALISE_hrom = $(FINALISE_rom) +FINALISE_xrom = $(MAKEROM) $(MAKEROM_FLAGS) $(TGT_MAKEROM_FLAGS) \ + -i$(IDENT) -n -s 0 $@ # Some ROMs require specific flags to be passed to makerom.pl # diff --git a/src/arch/i386/Makefile.pcbios b/src/arch/i386/Makefile.pcbios index 55ba11d7..8b010859 100644 --- a/src/arch/i386/Makefile.pcbios +++ b/src/arch/i386/Makefile.pcbios @@ -12,6 +12,7 @@ LDFLAGS += -N --no-check-sections # MEDIA += rom MEDIA += hrom +MEDIA += xrom MEDIA += pxe MEDIA += kpxe MEDIA += kkpxe @@ -32,6 +33,7 @@ MEDIA += exe # PAD_rom = $(PADIMG) --blksize=512 --byte=0xff $@ PAD_hrom = $(PAD_rom) +PAD_xrom = $(PAD_rom) PAD_dsk = $(PADIMG) --blksize=512 $@ PAD_hd = $(PADIMG) --blksize=32768 $@ diff --git a/src/arch/i386/prefix/romprefix.S b/src/arch/i386/prefix/romprefix.S index 952eccdd..02e54976 100644 --- a/src/arch/i386/prefix/romprefix.S +++ b/src/arch/i386/prefix/romprefix.S @@ -25,6 +25,19 @@ FILE_LICENCE ( GPL2_OR_LATER ) */ #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 @@ -33,10 +46,12 @@ FILE_LICENCE ( GPL2_OR_LATER ) .org 0x00 romheader: .word 0xAA55 /* BIOS extension signature */ -romheader_size: .byte 0 /* Size in 512-byte blocks */ +romheader_size: .byte ROM_SIZE_VALUE /* Size in 512-byte blocks */ jmp init /* Initialisation vector */ checksum: - .byte 0 + .byte 0, 0 +real_size: + .word 0 .org 0x16 .word undiheader .org 0x18 @@ -44,12 +59,18 @@ checksum: .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: @@ -61,17 +82,18 @@ pciheader: .byte 0x03 /* PCI data structure revision */ .byte 0x02, 0x00, 0x00 /* Class code */ pciheader_image_length: - .word 0 /* 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 0 /* Maximum run-time image 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 @@ -82,6 +104,7 @@ pciheader_runtime_length: .long 512 .long 0 .previous +#endif pnpheader: .ascii "$PnP" /* Signature */ @@ -175,6 +198,11 @@ init: 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 @@ -199,6 +227,9 @@ init: 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 @@ -310,7 +341,7 @@ pmm_scan: /* We have PMM and so a 1kB stack: preserve upper register halves */ pushal /* Calculate required allocation size in %esi */ - movzbl romheader_size, %eax + movzwl real_size, %eax shll $9, %eax addl $_textdata_memsz, %eax orw $0xffff, %ax /* Ensure allocation size is at least 64kB */ @@ -364,7 +395,7 @@ pmm_copy: movl %edi, decompress_to /* Shrink ROM */ movb $_prefix_memsz_sect, romheader_size -#ifdef SHRINK_WITHOUT_PMM +#if defined(SHRINK_WITHOUT_PMM) || defined(LOAD_ROM_FROM_PCI) jmp pmm_done pmm_fail: /* Print marker and copy ourselves to high memory */ @@ -379,8 +410,28 @@ 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 @@ -425,14 +476,14 @@ no_pmm: movw $init_message_done, %si call print_message popf - jnz 2f + 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 -2: +out: /* Restore registers */ popw %gs popw %fs @@ -479,6 +530,11 @@ 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 @@ -504,12 +560,32 @@ 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. @@ -528,6 +604,289 @@ bev_entry: 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 @@ -588,6 +947,14 @@ 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 diff --git a/src/arch/i386/prefix/xromprefix.S b/src/arch/i386/prefix/xromprefix.S new file mode 100644 index 00000000..d7c861f5 --- /dev/null +++ b/src/arch/i386/prefix/xromprefix.S @@ -0,0 +1,9 @@ +/* + * ROM prefix that loads the bulk of the ROM using direct PCI accesses, + * so as not to take up much option ROM space on PCI <3.0 systems. + */ + +FILE_LICENCE ( GPL2_OR_LATER ) + +#define LOAD_ROM_FROM_PCI +#include "romprefix.S" diff --git a/src/arch/i386/scripts/i386.lds b/src/arch/i386/scripts/i386.lds index 77e8c7e7..33c75f90 100644 --- a/src/arch/i386/scripts/i386.lds +++ b/src/arch/i386/scripts/i386.lds @@ -194,6 +194,7 @@ SECTIONS { * Values calculated to save code from doing it * */ + _prefix_filesz_sect = ( ( _prefix_filesz + 511 ) / 512 ); _prefix_memsz_pgh = ( ( _prefix_memsz + 15 ) / 16 ); _prefix_memsz_sect = ( ( _prefix_memsz + 511 ) / 512 ); _text16_memsz_pgh = ( ( _text16_memsz + 15 ) / 16 ); diff --git a/src/util/makerom.pl b/src/util/makerom.pl index aed3a569..68c3be98 100755 --- a/src/util/makerom.pl +++ b/src/util/makerom.pl @@ -130,14 +130,14 @@ sub writerom ($$) { close(R); } -sub checksum ($) { - my ($romref) = @_; +sub checksum ($$) { + my ($romref, $romsize) = @_; substr($$romref, 6, 1) = "\x00"; - my $sum = unpack('%8C*', $$romref); + my $sum = unpack('%8C*', substr($$romref, 0, $romsize)); substr($$romref, 6, 1) = chr(256 - $sum); # Double check - $sum = unpack('%8C*', $$romref); + $sum = unpack('%8C*', substr($$romref, 0, $romsize)); if ($sum != 0) { print "Checksum fails\n" } elsif ($opts{'v'}) { @@ -146,10 +146,10 @@ sub checksum ($) { } sub makerom () { - my ($rom, $romsize); + my ($rom, $romsize, $stubsize); - getopts('3xi:p:s:v', \%opts); - $ARGV[0] or die "Usage: $0 [-s romsize] [-i ident] [-p vendorid,deviceid] [-x] [-3] rom-file\n"; + getopts('3xni:p:s:v', \%opts); + $ARGV[0] or die "Usage: $0 [-s romsize] [-i ident] [-p vendorid,deviceid] [-n] [-x] [-3] rom-file\n"; open(R, $ARGV[0]) or die "$ARGV[0]: $!\n"; # Read in the whole ROM in one gulp my $filesize = read(R, $rom, MAXROMSIZE+1); @@ -183,10 +183,16 @@ sub makerom () { } # Pad with 0xFF to $romsize $rom .= "\xFF" x ($romsize - length($rom)); - if ($romsize >= 128 * 1024) { - print "Warning: ROM size exceeds extension BIOS limit\n"; + # If this is a stub ROM, don't force header size to the full amount + if (!$opts{'n'}) { + if ($romsize >= 128 * 1024) { + print "Warning: ROM size exceeds extension BIOS limit\n"; + } + substr($rom, 2, 1) = chr(($romsize / 512) % 256); + } else { + $stubsize = ord(substr($rom, 2, 1)) * 512; + print "Stub size is $stubsize\n" if $opts{'v'}; } - substr($rom, 2, 1) = chr(($romsize / 512) % 256); print "ROM size is $romsize\n" if $opts{'v'}; # set the product string only if we don't have one yet my $pnp_hdr_offset = unpack('v', substr($rom, PNP_PTR_LOC, 2)); @@ -196,7 +202,7 @@ sub makerom () { # 3c503 requires last two bytes to be 0x80 substr($rom, MINROMSIZE-2, 2) = "\x80\x80" if ($opts{'3'} and $romsize == MINROMSIZE); - checksum(\$rom); + checksum(\$rom, $opts{'n'} ? $stubsize : $romsize); writerom($ARGV[0], \$rom); } @@ -213,7 +219,7 @@ sub modrom () { print "$filesize bytes read\n" if $opts{'v'}; pcipnpheaders(\$rom, undef); undiheaders(\$rom); - checksum(\$rom); + checksum(\$rom, ord(substr($rom, 2, 1)) * 512); writerom($ARGV[0], \$rom); } |