From 6f73bb5e003db7195952c0ac297ac859531fa8e8 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 31 Jul 2008 05:28:11 +0100 Subject: [PATCH] [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. --- src/util/Option/ROM.pm | 459 +++++++++++++++++++++++++++++++++++++++++ src/util/disrom.pl | 169 ++++++--------- 2 files changed, 524 insertions(+), 104 deletions(-) create mode 100644 src/util/Option/ROM.pm 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 . +# +# 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 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 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 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 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 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 . # -# 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; +use strict; +use warnings; -sub getid ($) -{ - my ($offset) = @_; +use FindBin; +use lib "$FindBin::Bin"; +use Option::ROM qw ( :all ); - return '' if ($offset == 0 or $offset > $len); - my ($string) = unpack('Z32', substr($data, $offset, 32)); - return ($string); +my $romfile = shift || "-"; +my $rom = new Option::ROM; +$rom->load ( $romfile ); + +die "Not an option ROM image\n" + unless $rom->{signature} == ROM_SIGNATURE; + +my $romlength = ( $rom->{length} * 512 ); +my $filelength = $rom->length; +die "ROM image truncated (is $filelength, should be $romlength)\n" + if $filelength < $romlength; + +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"; + +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 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 <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"; } - -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 <= $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(); -} - -$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);