[util] Add Option::ROM library and rewrite disrom.pl to use it.
authorMichael Brown <mcb30@etherboot.org>
Thu, 31 Jul 2008 04:28:11 +0000 (05:28 +0100)
committerMichael Brown <mcb30@etherboot.org>
Thu, 31 Jul 2008 04:30:04 +0000 (05:30 +0100)
The Option::ROM module provides an easy way to read and edit fields
within option ROM headers.

src/util/Option/ROM.pm [new file with mode: 0644]
src/util/disrom.pl

diff --git a/src/util/Option/ROM.pm b/src/util/Option/ROM.pm
new file mode 100644 (file)
index 0000000..f5c33f8
--- /dev/null
@@ -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;
index 6412869..c472037 100755 (executable)
 #!/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);