diff options
Diffstat (limited to 'roms/ipxe/src/util/Option')
| -rw-r--r-- | roms/ipxe/src/util/Option/ROM.pm | 727 | 
1 files changed, 727 insertions, 0 deletions
| diff --git a/roms/ipxe/src/util/Option/ROM.pm b/roms/ipxe/src/util/Option/ROM.pm new file mode 100644 index 00000000..6c396730 --- /dev/null +++ b/roms/ipxe/src/util/Option/ROM.pm @@ -0,0 +1,727 @@ +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., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, 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} ); +  my $unpack = ( ref $self->{fields}->{$key}->{unpack} ? +		 $self->{fields}->{$key}->{unpack} : +		 sub { unpack ( $self->{fields}->{$key}->{pack}, shift ); } ); +  return &$unpack ( $raw ); +} + +sub STORE { +  my $self = shift; +  my $key = shift; +  my $value = shift; + +  croak "Nonexistent field \"$key\"" unless $self->EXISTS ( $key ); +  my $pack = ( ref $self->{fields}->{$key}->{pack} ? +	       $self->{fields}->{$key}->{pack} : +	       sub { pack ( $self->{fields}->{$key}->{pack}, shift ); } ); +  my $raw = &$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 PCI_LAST_IMAGE => 0x80; +use constant PNP_SIGNATURE => '$PnP'; +use constant IPXE_SIGNATURE => 'iPXE'; + +our @EXPORT_OK = qw ( ROM_SIGNATURE PCI_SIGNATURE PCI_LAST_IMAGE +		      PNP_SIGNATURE IPXE_SIGNATURE ); +our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] ); + +use constant JMP_SHORT => 0xeb; +use constant JMP_NEAR => 0xe9; +use constant CALL_NEAR => 0xe8; + +sub pack_init { +  my $dest = shift; + +  # Always create a near jump; it's simpler +  if ( $dest ) { +    return pack ( "CS", JMP_NEAR, ( $dest - 6 ) ); +  } else { +    return pack ( "CS", 0, 0 ); +  } +} + +sub unpack_init { +  my $instr = shift; + +  # Accept both short and near jumps +  my $jump = unpack ( "C", $instr ); +  if ( $jump == JMP_SHORT ) { +    my $offset = unpack ( "xC", $instr ); +    return ( $offset + 5 ); +  } elsif ( $jump == JMP_NEAR ) { +    my $offset = unpack ( "xS", $instr ); +    return ( $offset + 6 ); +  } elsif ( $jump == CALL_NEAR ) { +    my $offset = unpack ( "xS", $instr ); +    return ( $offset + 6 ); +  } elsif ( $jump == 0 ) { +    return 0; +  } else { +    croak "Unrecognised jump instruction in init vector\n"; +  } +} + +=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" }, +      # "init" is part of a jump instruction +      init =>		{ offset => 0x03, length => 0x03, +			  pack => \&pack_init, unpack => \&unpack_init }, +      checksum =>	{ offset => 0x06, length => 0x01, pack => "C" }, +      ipxe_header =>	{ offset => 0x10, length => 0x02, pack => "S" }, +      bofm_header =>	{ offset => 0x14, length => 0x02, pack => "S" }, +      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<< set ( $data ) >> + +Set option ROM contents. + +=cut + +sub set { +  my $hash = shift; +  my $self = tied(%$hash); +  my $data = shift; + +  # Store data +  $self->{data} = \$data; + +  # Split out any data belonging to the next image +  delete $self->{next_image}; +  my $pci_header = $hash->pci_header(); +  if ( ( defined $pci_header ) && +       ( ! ( $pci_header->{last_image} & PCI_LAST_IMAGE ) ) ) { +    my $length = ( $pci_header->{image_length} * 512 ); +    my $remainder = substr ( $data, $length ); +    $data = substr ( $data, 0, $length ); +    $self->{next_image} = new Option::ROM; +    $self->{next_image}->set ( $remainder ); +  } +} + +=pod + +=item C<< get () >> + +Get option ROM contents. + +=cut + +sub get { +  my $hash = shift; +  my $self = tied(%$hash); + +  my $data = ${$self->{data}}; +  $data .= $self->{next_image}->get() if $self->{next_image}; +  return $data; +} + +=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, -s $fh; +  $hash->set ( $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: $!"; +  my $data = $hash->get(); +  print $fh $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<< undi_header () >> + +Return a C<Option::ROM::UNDI> object representing the ROM's UNDI header, +if present. + +=cut + +sub undi_header { +  my $hash = shift; +  my $self = tied(%$hash); + +  my $offset = $hash->{undi_header}; +  return undef unless $offset != 0; + +  return Option::ROM::UNDI->new ( $self->{data}, $offset ); +} + +=pod + +=item C<< ipxe_header () >> + +Return a C<Option::ROM::iPXE> object representing the ROM's iPXE +header, if present. + +=cut + +sub ipxe_header { +  my $hash = shift; +  my $self = tied(%$hash); + +  my $offset = $hash->{ipxe_header}; +  return undef unless $offset != 0; + +  return Option::ROM::iPXE->new ( $self->{data}, $offset ); +} + +=pod + +=item C<< next_image () >> + +Return a C<Option::ROM> object representing the next image within the +ROM, if present. + +=cut + +sub next_image { +  my $hash = shift; +  my $self = tied(%$hash); + +  return $self->{next_image}; +} + +=pod + +=item C<< checksum () >> + +Calculate the byte checksum of the ROM. + +=cut + +sub checksum { +  my $hash = shift; +  my $self = tied(%$hash); + +  my $raw = substr ( ${$self->{data}}, 0, ( $hash->{length} * 512 ) ); +  return unpack ( "%8C*", $raw ); +} + +=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 ); +} + +############################################################################## +# +# Option::ROM::UNDI +# +############################################################################## + +package Option::ROM::UNDI; + +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 => 0x16, +    fields => { +      signature =>	{ offset => 0x00, length => 0x04, pack => "a4" }, +      struct_length =>	{ offset => 0x04, length => 0x01, pack => "C" }, +      checksum =>	{ offset => 0x05, length => 0x01, pack => "C" }, +      struct_revision =>{ offset => 0x06, length => 0x01, pack => "C" }, +      version_revision =>{ offset => 0x07, length => 0x01, pack => "C" }, +      version_minor =>	{ offset => 0x08, length => 0x01, pack => "C" }, +      version_major =>	{ offset => 0x09, length => 0x01, pack => "C" }, +      loader_entry =>	{ offset => 0x0a, length => 0x02, pack => "S" }, +      stack_size =>	{ offset => 0x0c, length => 0x02, pack => "S" }, +      data_size =>	{ offset => 0x0e, length => 0x02, pack => "S" }, +      code_size =>	{ offset => 0x10, length => 0x02, pack => "S" }, +      bus_type =>	{ offset => 0x12, length => 0x04, pack => "a4" }, +    }, +  }; +  bless $hash, $class; + +  # Retrieve true length of structure +  my $self = tied ( %$hash ); +  $self->{length} = $hash->{struct_length}; + +  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 ); +} + +############################################################################## +# +# Option::ROM::iPXE +# +############################################################################## + +package Option::ROM::iPXE; + +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_length =>	{ offset => 0x04, length => 0x01, pack => "C" }, +      checksum =>	{ offset => 0x05, length => 0x01, pack => "C" }, +      shrunk_length =>	{ offset => 0x06, length => 0x01, pack => "C" }, +      build_id =>	{ offset => 0x08, length => 0x04, pack => "L" }, +    }, +  }; +  bless $hash, $class; + +  # Retrieve true length of structure +  my $self = tied ( %$hash ); +  $self->{length} = $hash->{struct_length}; + +  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 ); +} + +1; | 
