1#!/usr/bin/perl -w 2 3use Getopt::Std; 4 5use constant MINROMSIZE => 8192; 6use constant MAXROMSIZE => 262144; 7 8use constant PCI_PTR_LOC => 0x18; # from beginning of ROM 9use constant PCI_HDR_SIZE => 0x18; 10use constant PNP_PTR_LOC => 0x1a; # from beginning of ROM 11use constant PNP_HDR_SIZE => 0x20; 12use constant PNP_CHKSUM_OFF => 0x9; # bytes from beginning of PnP header 13use constant PNP_DEVICE_OFF => 0x10; # bytes from beginning of PnP header 14use constant PCI_VEND_ID_OFF => 0x4; # bytes from beginning of PCI header 15use constant PCI_DEV_ID_OFF => 0x6; # bytes from beginning of PCI header 16use constant PCI_SIZE_OFF => 0x10; # bytes from beginning of PCI header 17 18use constant UNDI_PTR_LOC => 0x16; # from beginning of ROM 19use constant UNDI_HDR_SIZE => 0x16; 20use constant UNDI_CHKSUM_OFF => 0x05; 21 22use strict; 23 24use vars qw(%opts); 25 26use bytes; 27 28sub getromsize ($) { 29 my ($romref) = @_; 30 my $i; 31 32 print STDERR "BIOS extension ROM Image did not start with 0x55 0xAA\n" 33 if (substr($$romref, 0, 2) ne "\x55\xaa"); 34 my $size = ord(substr($$romref, 2, 1)) * 512; 35 for ($i = MINROMSIZE; $i < MAXROMSIZE and $i < $size; $i *= 2) { } 36 print STDERR "$size is a strange size for a boot ROM\n" 37 if ($size > 0 and $i > $size); 38 return ($size); 39} 40 41sub addident ($) { 42 my ($romref) = @_; 43 44 return (0) unless (my $s = $opts{'i'}); 45 # include the terminating NUL byte too 46 $s .= "\x00"; 47 my $len = length($s); 48 # Put the identifier in only if the space is blank 49 my $pos = length($$romref) - $len - 2; 50 return (0) if (substr($$romref, $pos, $len) ne ("\xFF" x $len)); 51 substr($$romref, $pos, $len) = $s; 52 return ($pos); 53} 54 55sub pcipnpheaders ($$) { 56 my ($romref, $identoffset) = @_; 57 my ($pci_hdr_offset, $pnp_hdr_offset); 58 59 $pci_hdr_offset = unpack('v', substr($$romref, PCI_PTR_LOC, 2)); 60 $pnp_hdr_offset = unpack('v', substr($$romref, PNP_PTR_LOC, 2)); 61 # Sanity checks 62 if ($pci_hdr_offset < PCI_PTR_LOC + 2 63 or $pci_hdr_offset > length($$romref) - PCI_HDR_SIZE 64 or $pnp_hdr_offset < PNP_PTR_LOC + 2 65 or $pnp_hdr_offset > length($$romref) - PNP_HDR_SIZE 66 or substr($$romref, $pci_hdr_offset, 4) ne 'PCIR' 67 or substr($$romref, $pnp_hdr_offset, 4) ne '$PnP') { 68 $pci_hdr_offset = $pnp_hdr_offset = 0; 69 } else { 70 printf "PCI header at %#x and PnP header at %#x\n", 71 $pci_hdr_offset, $pnp_hdr_offset if $opts{'v'}; 72 } 73 if ($pci_hdr_offset > 0) { 74 my ($pci_vendor_id, $pci_device_id); 75 # if no -p option, just report what's there 76 if (!defined($opts{'p'})) { 77 $pci_vendor_id = unpack('v', substr($$romref, $pci_hdr_offset+PCI_VEND_ID_OFF, 2)); 78 $pci_device_id = unpack('v', substr($$romref, $pci_hdr_offset+PCI_DEV_ID_OFF, 2)); 79 printf "PCI Vendor ID %#x Device ID %#x\n", 80 $pci_vendor_id, $pci_device_id; 81 } else { 82 substr($$romref, $pci_hdr_offset + PCI_SIZE_OFF, 2) 83 = pack('v', length($$romref) / 512); 84 ($pci_vendor_id, $pci_device_id) = split(/,/, $opts{'p'}); 85 substr($$romref, $pci_hdr_offset+PCI_VEND_ID_OFF, 2) 86 = pack('v', oct($pci_vendor_id)) if ($pci_vendor_id); 87 substr($$romref, $pci_hdr_offset+PCI_DEV_ID_OFF, 2) 88 = pack('v', oct($pci_device_id)) if ($pci_device_id); 89 } 90 } 91 if ($pnp_hdr_offset > 0) { 92 if (defined($identoffset)) { 93 # Point to device id string at end of ROM image 94 substr($$romref, $pnp_hdr_offset+PNP_DEVICE_OFF, 2) 95 = pack('v', $identoffset); 96 } 97 substr($$romref, $pnp_hdr_offset+PNP_CHKSUM_OFF, 1) = "\x00"; 98 my $sum = unpack('%8C*', substr($$romref, $pnp_hdr_offset, 99 PNP_HDR_SIZE)); 100 substr($$romref, $pnp_hdr_offset+PNP_CHKSUM_OFF, 1) = chr(256 - $sum); 101 } 102} 103 104sub undiheaders ($) { 105 my ($romref) = @_; 106 my ($undi_hdr_offset); 107 108 $undi_hdr_offset = unpack('v', substr($$romref, UNDI_PTR_LOC, 2)); 109 # Sanity checks 110 if ($undi_hdr_offset < UNDI_PTR_LOC + 2 111 or $undi_hdr_offset > length($$romref) - UNDI_HDR_SIZE 112 or substr($$romref, $undi_hdr_offset, 4) ne 'UNDI') { 113 $undi_hdr_offset = 0; 114 } else { 115 printf "UNDI header at %#x\n", $undi_hdr_offset if $opts{'v'}; 116 } 117 if ($undi_hdr_offset > 0) { 118 substr($$romref, $undi_hdr_offset+UNDI_CHKSUM_OFF, 1) = "\x00"; 119 my $sum = unpack('%8C*', substr($$romref, $undi_hdr_offset, 120 UNDI_HDR_SIZE)); 121 substr($$romref, $undi_hdr_offset+UNDI_CHKSUM_OFF, 1) = chr(256 - $sum); 122 } 123} 124 125sub writerom ($$) { 126 my ($filename, $romref) = @_; 127 128 open(R, ">$filename") or die "$filename: $!\n"; 129 print R $$romref; 130 close(R); 131} 132 133sub checksum ($$) { 134 my ($romref, $romsize) = @_; 135 136 substr($$romref, 6, 1) = "\x00"; 137 my $sum = unpack('%8C*', substr($$romref, 0, $romsize)); 138 substr($$romref, 6, 1) = chr(256 - $sum); 139 # Double check 140 $sum = unpack('%8C*', substr($$romref, 0, $romsize)); 141 if ($sum != 0) { 142 print "Checksum fails\n" 143 } elsif ($opts{'v'}) { 144 print "Checksum ok\n"; 145 } 146} 147 148sub makerom () { 149 my ($rom, $romsize, $stubsize); 150 151 getopts('3xni:p:s:v', \%opts); 152 $ARGV[0] or die "Usage: $0 [-s romsize] [-i ident] [-p vendorid,deviceid] [-n] [-x] [-3] rom-file\n"; 153 open(R, $ARGV[0]) or die "$ARGV[0]: $!\n"; 154 # Read in the whole ROM in one gulp 155 my $filesize = read(R, $rom, MAXROMSIZE+1); 156 close(R); 157 defined($filesize) and $filesize >= 3 or die "Cannot get first 3 bytes of file\n"; 158 print "$filesize bytes read\n" if $opts{'v'}; 159 # If PXE image, just fill the length field and write it out 160 if ($opts{'x'}) { 161 substr($rom, 2, 1) = chr((length($rom) + 511) / 512); 162 writerom($ARGV[0], \$rom); 163 return; 164 } 165 # Size specified with -s overrides value in 3rd byte in image 166 # -s 0 means round up to next 512 byte block 167 if (defined($opts{'s'})) { 168 if (($romsize = oct($opts{'s'})) <= 0) { 169 # NB: This roundup trick only works on powers of 2 170 $romsize = ($filesize + 511) & ~511 171 } 172 } else { 173 # Shrink romsize down to the smallest power of two that will do 174 for ($romsize = MAXROMSIZE; 175 $romsize > MINROMSIZE and $romsize >= 2*$filesize; 176 $romsize /= 2) { } 177 } 178 if ($filesize > $romsize) { 179 print STDERR "ROM size of $romsize not big enough for data, "; 180 # NB: This roundup trick only works on powers of 2 181 $romsize = ($filesize + 511) & ~511; 182 print "will use $romsize instead\n" 183 } 184 # Pad with 0xFF to $romsize 185 $rom .= "\xFF" x ($romsize - length($rom)); 186 # If this is a stub ROM, don't force header size to the full amount 187 if (!$opts{'n'}) { 188 if ($romsize >= 128 * 1024) { 189 print "Warning: ROM size exceeds extension BIOS limit\n"; 190 } 191 substr($rom, 2, 1) = chr(($romsize / 512) % 256); 192 } else { 193 $stubsize = ord(substr($rom, 2, 1)) * 512; 194 print "Stub size is $stubsize\n" if $opts{'v'}; 195 } 196 print "ROM size is $romsize\n" if $opts{'v'}; 197 # set the product string only if we don't have one yet 198 my $pnp_hdr_offset = unpack('v', substr($rom, PNP_PTR_LOC, 2)); 199 my $identoffset = substr($rom, $pnp_hdr_offset+PNP_DEVICE_OFF, 2) eq "\0\0" ? addident(\$rom) : undef; 200 pcipnpheaders(\$rom, $identoffset); 201 undiheaders(\$rom); 202 # 3c503 requires last two bytes to be 0x80 203 substr($rom, MINROMSIZE-2, 2) = "\x80\x80" 204 if ($opts{'3'} and $romsize == MINROMSIZE); 205 checksum(\$rom, $opts{'n'} ? $stubsize : $romsize); 206 writerom($ARGV[0], \$rom); 207} 208 209sub modrom () { 210 my ($rom); 211 212 getopts('p:v', \%opts); 213 $ARGV[0] or die "Usage: $0 [-p vendorid,deviceid] rom-file\n"; 214 open(R, $ARGV[0]) or die "$ARGV[0]: $!\n"; 215 # Read in the whole ROM in one gulp 216 my $filesize = read(R, $rom, MAXROMSIZE+1); 217 close(R); 218 defined($filesize) and $filesize >= 3 or die "Cannot get first 3 bytes of file\n"; 219 print "$filesize bytes read\n" if $opts{'v'}; 220 pcipnpheaders(\$rom, undef); 221 undiheaders(\$rom); 222 checksum(\$rom, ord(substr($rom, 2, 1)) * 512); 223 writerom($ARGV[0], \$rom); 224} 225 226# Main routine. See how we were called and behave accordingly 227if ($0 =~ m:modrom(\.pl)?$:) { 228 modrom(); 229} else { 230 makerom(); 231} 232exit(0); 233