[prefix] Add .xrom prefix for a ROM that loads itself by PCI accesses
authorJoshua Oreman <oremanj@rwcr.net>
Sun, 18 Oct 2009 20:12:51 +0000 (16:12 -0400)
committerMarty Connor <mdc@etherboot.org>
Wed, 20 Jan 2010 22:46:48 +0000 (17:46 -0500)
The standard option ROM format provides a header indicating the size
of the entire ROM, which the BIOS will reserve space for, load, and
call as necessary. However, this space is strictly limited to 128k for
all ROMs. gPXE ameliorates this somewhat by reserving space for itself
in high memory and relocating the majority of its code there, but on
systems prior to PCI3 enough space must still be present to load the
ROM in the first place. Even on PCI3 systems, the BIOS often limits the
size of ROM it will load to a bit over 64kB.

These space problems can be solved by providing an artificially small
size in the ROM header: just enough to let the prefix code (at the
beginning of the ROM image) be loaded by the BIOS. To the BIOS, the
gPXE ROM will appear to be only a few kilobytes; it can then load
the rest of itself by accessing the ROM directly using the PCI
interface reserved for that task.

There are a few problems with this approach. First, gPXE needs to find
an unmapped region in memory to map the ROM so it can read from it;
this is done using the crude but effective approach of scanning high
memory (over 0xF0000000) for a sufficiently large region of all-ones
(0xFF) reads. (In x86 architecture, all-ones is returned for accesses
to memory regions that no mapped device can satisfy.) This is not
provably valid in all situations, but has worked well in practice.
More importantly, this type of ROM access can only work if the PCI ROM
BAR exists at all. NICs on physical add-in PCI cards generally must
have the BAR in order for the BIOS to be able to load their ROM, but
ISA cards and LAN-on-Motherboard cards will both fail to load gPXE
using this scheme.

Due to these uncertainties, it is recommended that .xrom only be used
when a regular .rom image is infeasible due to crowded option ROM
space. However, when it works it could allow loading gPXE images
as large as a flash chip one could find - 128kB or even higher.

Signed-off-by: Marty Connor <mdc@etherboot.org>
src/Makefile.housekeeping
src/arch/i386/Makefile.pcbios
src/arch/i386/prefix/romprefix.S
src/arch/i386/prefix/xromprefix.S [new file with mode: 0644]
src/arch/i386/scripts/i386.lds
src/util/makerom.pl

index 1642374..1f5e115 100644 (file)
@@ -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
 #
index 55ba11d..8b01085 100644 (file)
@@ -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 $@
 
index 952eccd..02e5497 100644 (file)
@@ -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 (file)
index 0000000..d7c861f
--- /dev/null
@@ -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"
index 77e8c7e..33c75f9 100644 (file)
@@ -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 );
index aed3a56..68c3be9 100755 (executable)
@@ -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);
 }