summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Brown2008-07-31 06:28:11 +0200
committerMichael Brown2008-07-31 06:30:04 +0200
commit6f73bb5e003db7195952c0ac297ac859531fa8e8 (patch)
treeb4c4acef0c2f5532f1efb265fd1fd26934d0dd84
parent[ftp] Terminate processing after receiving an error (diff)
downloadipxe-6f73bb5e003db7195952c0ac297ac859531fa8e8.tar.gz
ipxe-6f73bb5e003db7195952c0ac297ac859531fa8e8.tar.xz
ipxe-6f73bb5e003db7195952c0ac297ac859531fa8e8.zip
[util] Add Option::ROM library and rewrite disrom.pl to use it.
The Option::ROM module provides an easy way to read and edit fields within option ROM headers.
-rw-r--r--src/util/Option/ROM.pm459
-rwxr-xr-xsrc/util/disrom.pl161
2 files changed, 520 insertions, 100 deletions
diff --git a/src/util/Option/ROM.pm b/src/util/Option/ROM.pm
new file mode 100644
index 00000000..f5c33f8a
--- /dev/null
+++ b/src/util/Option/ROM.pm
@@ -0,0 +1,459 @@
+package Option::ROM;
+
+# Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+=head1 NAME
+
+Option::ROM - Option ROM manipulation
+
+=head1 SYNOPSIS
+
+ use Option::ROM;
+
+ # Load a ROM image
+ my $rom = new Option::ROM;
+ $rom->load ( "rtl8139.rom" );
+
+ # Modify the PCI device ID
+ $rom->pci_header->{device_id} = 0x1234;
+ $rom->fix_checksum();
+
+ # Write ROM image out to a new file
+ $rom->save ( "rtl8139-modified.rom" );
+
+=head1 DESCRIPTION
+
+C<Option::ROM> provides a mechanism for manipulating Option ROM
+images.
+
+=head1 METHODS
+
+=cut
+
+##############################################################################
+#
+# Option::ROM::Fields
+#
+##############################################################################
+
+package Option::ROM::Fields;
+
+use strict;
+use warnings;
+use Carp;
+use bytes;
+
+sub TIEHASH {
+ my $class = shift;
+ my $self = shift;
+
+ bless $self, $class;
+ return $self;
+}
+
+sub FETCH {
+ my $self = shift;
+ my $key = shift;
+
+ return undef unless $self->EXISTS ( $key );
+ my $raw = substr ( ${$self->{data}},
+ ( $self->{offset} + $self->{fields}->{$key}->{offset} ),
+ $self->{fields}->{$key}->{length} );
+ return unpack ( $self->{fields}->{$key}->{pack}, $raw );
+}
+
+sub STORE {
+ my $self = shift;
+ my $key = shift;
+ my $value = shift;
+
+ croak "Nonexistent field \"$key\"" unless $self->EXISTS ( $key );
+ my $raw = pack ( $self->{fields}->{$key}->{pack}, $value );
+ substr ( ${$self->{data}},
+ ( $self->{offset} + $self->{fields}->{$key}->{offset} ),
+ $self->{fields}->{$key}->{length} ) = $raw;
+}
+
+sub DELETE {
+ my $self = shift;
+ my $key = shift;
+
+ $self->STORE ( $key, 0 );
+}
+
+sub CLEAR {
+ my $self = shift;
+
+ foreach my $key ( keys %{$self->{fields}} ) {
+ $self->DELETE ( $key );
+ }
+}
+
+sub EXISTS {
+ my $self = shift;
+ my $key = shift;
+
+ return ( exists $self->{fields}->{$key} &&
+ ( ( $self->{fields}->{$key}->{offset} +
+ $self->{fields}->{$key}->{length} ) <= $self->{length} ) );
+}
+
+sub FIRSTKEY {
+ my $self = shift;
+
+ keys %{$self->{fields}};
+ return each %{$self->{fields}};
+}
+
+sub NEXTKEY {
+ my $self = shift;
+ my $lastkey = shift;
+
+ return each %{$self->{fields}};
+}
+
+sub SCALAR {
+ my $self = shift;
+
+ return 1;
+}
+
+sub UNTIE {
+ my $self = shift;
+}
+
+sub DESTROY {
+ my $self = shift;
+}
+
+sub checksum {
+ my $self = shift;
+
+ my $raw = substr ( ${$self->{data}}, $self->{offset}, $self->{length} );
+ return unpack ( "%8C*", $raw );
+}
+
+##############################################################################
+#
+# Option::ROM
+#
+##############################################################################
+
+package Option::ROM;
+
+use strict;
+use warnings;
+use Carp;
+use bytes;
+use Exporter 'import';
+
+use constant ROM_SIGNATURE => 0xaa55;
+use constant PCI_SIGNATURE => 'PCIR';
+use constant PNP_SIGNATURE => '$PnP';
+
+our @EXPORT_OK = qw ( ROM_SIGNATURE PCI_SIGNATURE PNP_SIGNATURE );
+our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
+
+=pod
+
+=item C<< new () >>
+
+Construct a new C<Option::ROM> object.
+
+=cut
+
+sub new {
+ my $class = shift;
+
+ my $hash = {};
+ tie %$hash, "Option::ROM::Fields", {
+ data => undef,
+ offset => 0x00,
+ length => 0x20,
+ fields => {
+ signature => { offset => 0x00, length => 0x02, pack => "S" },
+ length => { offset => 0x02, length => 0x01, pack => "C" },
+ checksum => { offset => 0x06, length => 0x01, pack => "C" },
+ undi_header => { offset => 0x16, length => 0x02, pack => "S" },
+ pci_header => { offset => 0x18, length => 0x02, pack => "S" },
+ pnp_header => { offset => 0x1a, length => 0x02, pack => "S" },
+ },
+ };
+ bless $hash, $class;
+ return $hash;
+}
+
+=pod
+
+=item C<< load ( $filename ) >>
+
+Load option ROM contents from the file C<$filename>.
+
+=cut
+
+sub load {
+ my $hash = shift;
+ my $self = tied(%$hash);
+ my $filename = shift;
+
+ $self->{filename} = $filename;
+
+ open my $fh, "<$filename"
+ or croak "Cannot open $filename for reading: $!";
+ read $fh, my $data, ( 128 * 1024 ); # 128kB is theoretical max size
+ $self->{data} = \$data;
+ close $fh;
+}
+
+=pod
+
+=item C<< save ( [ $filename ] ) >>
+
+Write the ROM data back out to the file C<$filename>. If C<$filename>
+is omitted, the file used in the call to C<load()> will be used.
+
+=cut
+
+sub save {
+ my $hash = shift;
+ my $self = tied(%$hash);
+ my $filename = shift;
+
+ $filename ||= $self->{filename};
+
+ open my $fh, ">$filename"
+ or croak "Cannot open $filename for writing: $!";
+ print $fh ${$self->{data}};
+ close $fh;
+}
+
+=pod
+
+=item C<< length () >>
+
+Length of option ROM data. This is the length of the file, not the
+length from the ROM header length field.
+
+=cut
+
+sub length {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ return length ${$self->{data}};
+}
+
+=pod
+
+=item C<< pci_header () >>
+
+Return a C<Option::ROM::PCI> object representing the ROM's PCI header,
+if present.
+
+=cut
+
+sub pci_header {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ my $offset = $hash->{pci_header};
+ return undef unless $offset != 0;
+
+ return Option::ROM::PCI->new ( $self->{data}, $offset );
+}
+
+=pod
+
+=item C<< pnp_header () >>
+
+Return a C<Option::ROM::PnP> object representing the ROM's PnP header,
+if present.
+
+=cut
+
+sub pnp_header {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ my $offset = $hash->{pnp_header};
+ return undef unless $offset != 0;
+
+ return Option::ROM::PnP->new ( $self->{data}, $offset );
+}
+
+=pod
+
+=item C<< checksum () >>
+
+Calculate the byte checksum of the ROM.
+
+=cut
+
+sub checksum {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ return unpack ( "%8C*", ${$self->{data}} );
+}
+
+=pod
+
+=item C<< fix_checksum () >>
+
+Fix the byte checksum of the ROM.
+
+=cut
+
+sub fix_checksum {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
+}
+
+##############################################################################
+#
+# Option::ROM::PCI
+#
+##############################################################################
+
+package Option::ROM::PCI;
+
+use strict;
+use warnings;
+use Carp;
+use bytes;
+
+sub new {
+ my $class = shift;
+ my $data = shift;
+ my $offset = shift;
+
+ my $hash = {};
+ tie %$hash, "Option::ROM::Fields", {
+ data => $data,
+ offset => $offset,
+ length => 0x0c,
+ fields => {
+ signature => { offset => 0x00, length => 0x04, pack => "a4" },
+ vendor_id => { offset => 0x04, length => 0x02, pack => "S" },
+ device_id => { offset => 0x06, length => 0x02, pack => "S" },
+ device_list => { offset => 0x08, length => 0x02, pack => "S" },
+ struct_length => { offset => 0x0a, length => 0x02, pack => "S" },
+ struct_revision =>{ offset => 0x0c, length => 0x01, pack => "C" },
+ base_class => { offset => 0x0d, length => 0x01, pack => "C" },
+ sub_class => { offset => 0x0e, length => 0x01, pack => "C" },
+ prog_intf => { offset => 0x0f, length => 0x01, pack => "C" },
+ image_length => { offset => 0x10, length => 0x02, pack => "S" },
+ revision => { offset => 0x12, length => 0x02, pack => "S" },
+ code_type => { offset => 0x14, length => 0x01, pack => "C" },
+ last_image => { offset => 0x15, length => 0x01, pack => "C" },
+ runtime_length => { offset => 0x16, length => 0x02, pack => "S" },
+ conf_header => { offset => 0x18, length => 0x02, pack => "S" },
+ clp_entry => { offset => 0x1a, length => 0x02, pack => "S" },
+ },
+ };
+ bless $hash, $class;
+
+ # Retrieve true length of structure
+ my $self = tied ( %$hash );
+ $self->{length} = $hash->{struct_length};
+
+ return $hash;
+}
+
+##############################################################################
+#
+# Option::ROM::PnP
+#
+##############################################################################
+
+package Option::ROM::PnP;
+
+use strict;
+use warnings;
+use Carp;
+use bytes;
+
+sub new {
+ my $class = shift;
+ my $data = shift;
+ my $offset = shift;
+
+ my $hash = {};
+ tie %$hash, "Option::ROM::Fields", {
+ data => $data,
+ offset => $offset,
+ length => 0x06,
+ fields => {
+ signature => { offset => 0x00, length => 0x04, pack => "a4" },
+ struct_revision =>{ offset => 0x04, length => 0x01, pack => "C" },
+ struct_length => { offset => 0x05, length => 0x01, pack => "C" },
+ checksum => { offset => 0x09, length => 0x01, pack => "C" },
+ manufacturer => { offset => 0x0e, length => 0x02, pack => "S" },
+ product => { offset => 0x10, length => 0x02, pack => "S" },
+ bcv => { offset => 0x16, length => 0x02, pack => "S" },
+ bdv => { offset => 0x18, length => 0x02, pack => "S" },
+ bev => { offset => 0x1a, length => 0x02, pack => "S" },
+ },
+ };
+ bless $hash, $class;
+
+ # Retrieve true length of structure
+ my $self = tied ( %$hash );
+ $self->{length} = ( $hash->{struct_length} * 16 );
+
+ return $hash;
+}
+
+sub checksum {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ return $self->checksum();
+}
+
+sub fix_checksum {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
+}
+
+sub manufacturer {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ my $manufacturer = $hash->{manufacturer};
+ return undef unless $manufacturer;
+
+ my $raw = substr ( ${$self->{data}}, $manufacturer );
+ return unpack ( "Z*", $raw );
+}
+
+sub product {
+ my $hash = shift;
+ my $self = tied(%$hash);
+
+ my $product = $hash->{product};
+ return undef unless $product;
+
+ my $raw = substr ( ${$self->{data}}, $product );
+ return unpack ( "Z*", $raw );
+}
+
+1;
diff --git a/src/util/disrom.pl b/src/util/disrom.pl
index 64128698..c472037a 100755
--- a/src/util/disrom.pl
+++ b/src/util/disrom.pl
@@ -1,114 +1,75 @@
#!/usr/bin/perl -w
#
-# Program to display key information about a boot ROM
-# including PCI and PnP structures
+# Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
#
-# GPL, Ken Yap 2001
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or any later version.
#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-use bytes;
-
-sub getid ($)
-{
- my ($offset) = @_;
-
- return '' if ($offset == 0 or $offset > $len);
- my ($string) = unpack('Z32', substr($data, $offset, 32));
- return ($string);
-}
+use strict;
+use warnings;
-sub dispci
-{
- my ($pcidata) = substr($data, $pci, 0x18);
- my ($dummy, $vendorid, $deviceid, $vpd, $pcilen, $pcirev,
- $class1, $class2, $class3, $imglen, $coderev, $codetype,
- $indicator) = unpack('a4v4C4v2C2', $pcidata);
- $imglen *= 512;
- my $vendorstr = sprintf('%#04x', $vendorid);
- my $devicestr = sprintf('%#04x', $deviceid);
- my $coderevstr = sprintf('%#04x', $coderev);
- print <<EOF;
-PCI structure:
+use FindBin;
+use lib "$FindBin::Bin";
+use Option::ROM qw ( :all );
-Vital product data: $vpd
-Vendor ID: $vendorstr
-Device ID: $devicestr
-Device base type: $class1
-Device sub type: $class2
-Device interface type: $class3
-Image length: $imglen
-Code revision: $coderevstr
-Code type: $codetype
-Indicator: $indicator
+my $romfile = shift || "-";
+my $rom = new Option::ROM;
+$rom->load ( $romfile );
-EOF
-}
+die "Not an option ROM image\n"
+ unless $rom->{signature} == ROM_SIGNATURE;
-sub dispnp
-{
- my ($pnpdata) = substr($data, $pnp, 0x20);
- my ($dummy1, $pnprev, $pnplen, $nextpnp, $dummy2,
- $pnpcsum, $deviceid, $mfrid, $productid,
- $class1, $class2, $class3, $indicator,
- $bcv, $dv, $bev, $dummy, $sri) = unpack('a4C2vC2a4v2C4v5', $pnpdata);
- print <<EOF;
-PnP structure:
+my $romlength = ( $rom->{length} * 512 );
+my $filelength = $rom->length;
+die "ROM image truncated (is $filelength, should be $romlength)\n"
+ if $filelength < $romlength;
-EOF
- print 'Vendor: ', &getid($mfrid), "\n";
- print 'Device: ', &getid($productid), "\n";
- my $indicatorstr = sprintf('%#02x', $indicator);
- my $bcvstr = sprintf('%#04x', $bcv);
- my $dvstr = sprintf('%#04x', $dv);
- my $bevstr = sprintf('%#04x', $bev);
- my $sristr = sprintf('%#04x', $sri);
- my $checksum = unpack('%8C*', $pnpdata);
- print <<EOF;
-Device base type: $class1
-Device sub type: $class2
-Device interface type: $class3
-Device indicator: $indicatorstr
-Boot connection vector: $bcvstr
-Disconnect vector: $dvstr
-Bootstrap entry vector: $bevstr
-Static resource information vector: $sristr
-Checksum: $checksum
+printf "ROM header:\n\n";
+printf " Length:\t0x%02x (%d)\n", $rom->{length}, ( $rom->{length} * 512 );
+printf " Checksum:\t0x%02x (0x%02x)\n", $rom->{checksum}, $rom->checksum;
+printf " UNDI header:\t0x%04x\n", $rom->{undi_header};
+printf " PCI header:\t0x%04x\n", $rom->{pci_header};
+printf " PnP header:\t0x%04x\n", $rom->{pnp_header};
+printf "\n";
-EOF
+my $pci = $rom->pci_header();
+if ( $pci ) {
+ printf "PCI header:\n\n";
+ printf " Signature:\t%s\n", $pci->{signature};
+ printf " Vendor id:\t0x%04x\n", $pci->{vendor_id};
+ printf " Device id:\t0x%04x\n", $pci->{device_id};
+ printf " Device class:\t0x%02x%02x%02x\n",
+ $pci->{base_class}, $pci->{sub_class}, $pci->{prog_intf};
+ printf " Image length:\t0x%04x (%d)\n",
+ $pci->{image_length}, ( $pci->{image_length} * 512 );
+ printf " Runtime length:\t0x%04x (%d)\n",
+ $pci->{runtime_length}, ( $pci->{runtime_length} * 512 );
+ printf " Config header:\t0x%04x\n", $pci->{conf_header};
+ printf " CLP entry:\t0x%04x\n", $pci->{clp_entry};
+ printf "\n";
}
-sub pcipnp
-{
- ($pci, $pnp) = unpack('v2', substr($data, 0x18, 4));
- if ($pci >= $len or $pnp >= $len) {
- print "$file: Not a PCI PnP ROM image\n";
- return;
- }
- if (substr($data, $pci, 4) ne 'PCIR' or substr($data, $pnp, 4) ne '$PnP') {
- print "$file: No PCI and PNP structures, not a PCI PNP ROM image\n";
- return;
- }
- &dispci();
- &dispnp();
+my $pnp = $rom->pnp_header();
+if ( $pnp ) {
+ printf "PnP header:\n\n";
+ printf " Signature:\t%s\n", $pnp->{signature};
+ printf " Checksum:\t0x%02x (0x%02x)\n", $pnp->{checksum}, $pnp->checksum;
+ printf " Manufacturer:\t0x%04x \"%s\"\n",
+ $pnp->{manufacturer}, $pnp->manufacturer;
+ printf " Product:\t0x%04x \"%s\"\n", $pnp->{product}, $pnp->product;
+ printf " BCV:\t\t0x%04x\n", $pnp->{bcv};
+ printf " BDV:\t\t0x%04x\n", $pnp->{bdv};
+ printf " BEV:\t\t0x%04x\n", $pnp->{bev};
+ printf "\n";
}
-
-$file = $#ARGV >= 0 ? $ARGV[0] : '-';
-open(F, "$file") or die "$file: $!\n";
-binmode(F);
-# Handle up to 64kB ROM images
-$len = read(F, $data, 64*1024);
-close(F);
-defined($len) or die "$file: $!\n";
-substr($data, 0, 2) eq "\x55\xAA" or die "$file: Not a boot ROM image\n";
-my ($codelen) = unpack('C', substr($data, 2, 1));
-$codelen *= 512;
-if ($codelen < $len) {
- my $pad = $len - $codelen;
- print "Image is $codelen bytes and has $pad bytes of padding following\n";
- $data = substr($data, 0, $codelen);
-} elsif ($codelen > $len) {
- print "Image should be $codelen bytes but is truncated to $len bytes\n";}
-&pcipnp();
-($csum) = unpack('%8C*', $data);
-print "ROM checksum: $csum \n";
-exit(0);