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; 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 and defined($identoffset)) { 92 # Point to device id string at end of ROM image 93 substr($$romref, $pnp_hdr_offset+PNP_DEVICE_OFF, 2) 94 = pack('v', $identoffset); 95 substr($$romref, $pnp_hdr_offset+PNP_CHKSUM_OFF, 1) = "\x00"; 96 my $sum = unpack('%8C*', substr($$romref, $pnp_hdr_offset, 97 PNP_HDR_SIZE)); 98 substr($$romref, $pnp_hdr_offset+PNP_CHKSUM_OFF, 1) = chr(256 - $sum); 99 } 100} 101 102sub undiheaders ($) { 103 my ($romref) = @_; 104 my ($undi_hdr_offset); 105 106 $undi_hdr_offset = unpack('v', substr($$romref, UNDI_PTR_LOC, 2)); 107 # Sanity checks 108 if ($undi_hdr_offset < UNDI_PTR_LOC + 2 109 or $undi_hdr_offset > length($$romref) - UNDI_HDR_SIZE 110 or substr($$romref, $undi_hdr_offset, 4) ne 'UNDI') { 111 $undi_hdr_offset = 0; 112 } else { 113 printf "UNDI header at %#x\n", $undi_hdr_offset; 114 } 115 if ($undi_hdr_offset > 0) { 116 substr($$romref, $undi_hdr_offset+UNDI_CHKSUM_OFF, 1) = "\x00"; 117 my $sum = unpack('%8C*', substr($$romref, $undi_hdr_offset, 118 UNDI_HDR_SIZE)); 119 substr($$romref, $undi_hdr_offset+UNDI_CHKSUM_OFF, 1) = chr(256 - $sum); 120 } 121} 122 123sub writerom ($$) { 124 my ($filename, $romref) = @_; 125 126 open(R, ">$filename") or die "$filename: $!\n"; 127 print R $$romref; 128 close(R); 129} 130 131sub checksum ($) { 132 my ($romref) = @_; 133 134 substr($$romref, 6, 1) = "\x00"; 135 my $sum = unpack('%8C*', $$romref); 136 substr($$romref, 6, 1) = chr(256 - $sum); 137 # Double check 138 $sum = unpack('%8C*', $$romref); 139 if ($sum != 0) { 140 print "Checksum fails\n" 141 } elsif ($opts{'v'}) { 142 print "Checksum ok\n"; 143 } 144} 145 146sub makerom () { 147 my ($rom, $romsize); 148 149 getopts('3xi:p:s:v', \%opts); 150 $ARGV[0] or die "Usage: $0 [-s romsize] [-i ident] [-p vendorid,deviceid] [-x] [-3] rom-file\n"; 151 open(R, $ARGV[0]) or die "$ARGV[0]: $!\n"; 152 # Read in the whole ROM in one gulp 153 my $filesize = read(R, $rom, MAXROMSIZE+1); 154 close(R); 155 defined($filesize) and $filesize >= 3 or die "Cannot get first 3 bytes of file\n"; 156 print "$filesize bytes read\n" if $opts{'v'}; 157 # If PXE image, just fill the length field and write it out 158 if ($opts{'x'}) { 159 substr($rom, 2, 1) = chr((length($rom) + 511) / 512); 160 &writerom($ARGV[0], \$rom); 161 return; 162 } 163 # Size specified with -s overrides value in 3rd byte in image 164 # -s 0 means round up to next 512 byte block 165 if (defined($opts{'s'})) { 166 if (($romsize = oct($opts{'s'})) <= 0) { 167 # NB: This roundup trick only works on powers of 2 168 $romsize = ($filesize + 511) & ~511 169 } 170 } else { 171 $romsize = &getromsize(\$rom); 172 # 0 put there by *loader.S means makerom should pick the size 173 if ($romsize == 0) { 174 # Shrink romsize down to the smallest power of two that will do 175 for ($romsize = MAXROMSIZE; 176 $romsize > MINROMSIZE and $romsize >= 2*$filesize; 177 $romsize /= 2) { } 178 } 179 } 180 if ($filesize > $romsize) { 181 print STDERR "ROM size of $romsize not big enough for data, "; 182 # NB: This roundup trick only works on powers of 2 183 $romsize = ($filesize + 511) & ~511; 184 print "will use $romsize instead\n" 185 } 186 # Pad with 0xFF to $romsize 187 $rom .= "\xFF" x ($romsize - length($rom)); 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 print "ROM size is $romsize\n" if $opts{'v'}; 193 my $identoffset = &addident(\$rom); 194 &pcipnpheaders(\$rom, $identoffset); 195 &undiheaders(\$rom); 196 # 3c503 requires last two bytes to be 0x80 197 substr($rom, MINROMSIZE-2, 2) = "\x80\x80" 198 if ($opts{'3'} and $romsize == MINROMSIZE); 199 &checksum(\$rom); 200 &writerom($ARGV[0], \$rom); 201} 202 203sub modrom () { 204 my ($rom); 205 206 getopts('p:v', \%opts); 207 $ARGV[0] or die "Usage: $0 [-p vendorid,deviceid] rom-file\n"; 208 open(R, $ARGV[0]) or die "$ARGV[0]: $!\n"; 209 # Read in the whole ROM in one gulp 210 my $filesize = read(R, $rom, MAXROMSIZE+1); 211 close(R); 212 defined($filesize) and $filesize >= 3 or die "Cannot get first 3 bytes of file\n"; 213 print "$filesize bytes read\n" if $opts{'v'}; 214 &pcipnpheaders(\$rom); 215 &undiheaders(\$rom); 216 &checksum(\$rom); 217 &writerom($ARGV[0], \$rom); 218} 219 220# Main routine. See how we were called and behave accordingly 221if ($0 =~ m:modrom(\.pl)?$:) { 222 &modrom(); 223} else { 224 &makerom(); 225} 226exit(0); 227