diff options
Diffstat (limited to 'src/VBox/Devices/PC/ipxe/src/util/Option/ROM.pm')
-rw-r--r-- | src/VBox/Devices/PC/ipxe/src/util/Option/ROM.pm | 880 |
1 files changed, 880 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/ipxe/src/util/Option/ROM.pm b/src/VBox/Devices/PC/ipxe/src/util/Option/ROM.pm new file mode 100644 index 00000000..51831adf --- /dev/null +++ b/src/VBox/Devices/PC/ipxe/src/util/Option/ROM.pm @@ -0,0 +1,880 @@ +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} ) && + ( ! defined $self->{fields}->{$key}->{check} || + &{$self->{fields}->{$key}->{check}} ( $self, $key ) ) ); +} + +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 UNDI_SIGNATURE => 'UNDI'; +use constant IPXE_SIGNATURE => 'iPXE'; +use constant EFI_SIGNATURE => 0x00000ef1; + +our @EXPORT_OK = qw ( ROM_SIGNATURE PCI_SIGNATURE PCI_LAST_IMAGE + PNP_SIGNATURE UNDI_SIGNATURE IPXE_SIGNATURE EFI_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 { + carp "Unrecognised jump instruction in init vector\n"; + return 0; + } +} + +sub check_pcat_rom { + my $self = shift; + my $key = shift; + + my $pci = $self->{rom}->pci_header (); + + return ! defined $pci || $pci->{code_type} == 0x00; +} + +=pod + +=item C<< new () >> + +Construct a new C<Option::ROM> object. + +=cut + +sub new { + my $class = shift; + + my $hash = {}; + tie %$hash, "Option::ROM::Fields", { + rom => $hash, # ROM object itself + data => undef, + offset => 0x00, + length => 0x20, + file_offset => 0x0, + 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, + check => \&check_pcat_rom }, + checksum => { offset => 0x06, length => 0x01, pack => "C", + check => \&check_pcat_rom }, + ipxe_header => { offset => 0x10, length => 0x02, pack => "S", + check => \&check_pcat_rom }, + bofm_header => { offset => 0x14, length => 0x02, pack => "S", + check => \&check_pcat_rom }, + undi_header => { offset => 0x16, length => 0x02, pack => "S", + check => \&check_pcat_rom }, + pci_header => { offset => 0x18, length => 0x02, pack => "S" }, + pnp_header => { offset => 0x1a, length => 0x02, pack => "S", + check => \&check_pcat_rom }, + }, + }; + bless $hash, $class; + return $hash; +} + +=pod + +=item C<< set ( $data [, $file_offset ] ) >> + +Set option ROM contents, optionally sets original file offset. + +=cut + +sub set { + my $hash = shift; + my $self = tied(%$hash); + my $data = shift; + my $file_offset = shift // 0x0; + + # Store data + $self->{data} = \$data; + $self->{file_offset} = $file_offset; + + # 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, $self->{file_offset} + $length ); + } +} + +=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: $!"; + binmode $fh; + 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(); + binmode $fh; + 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; + + return Option::ROM::PCI->new ( $self, $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; + + return Option::ROM::PnP->new ( $self, $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; + + return Option::ROM::UNDI->new ( $self, $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; + + return Option::ROM::iPXE->new ( $self, $offset ); +} + +=pod + +=item C<< efi_header () >> + +Return a C<Option::ROM::EFI> object representing the ROM's EFI header, +if present. + +=cut + +sub efi_header { + my $hash = shift; + my $self = tied(%$hash); + + my $pci = $hash->pci_header (); + return undef unless defined $pci; + + return Option::ROM::EFI->new ( $self, $pci ); +} + +=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); + + return unless ( exists $hash->{checksum} ); + $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff ); +} + +=pod + +=item C<< file_offset () >> + +Get file offset of image. + +=cut + +sub file_offset { + my $hash = shift; + my $self = tied(%$hash); + + return $self->{file_offset}; +} + +############################################################################## +# +# Option::ROM::PCI +# +############################################################################## + +package Option::ROM::PCI; + +use strict; +use warnings; +use Carp; +use bytes; + +sub new { + my $class = shift; + my $rom = shift; + my $offset = shift; + + my $hash = {}; + tie %$hash, "Option::ROM::Fields", { + rom => $rom, + data => $rom->{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" }, + prog_intf => { offset => 0x0d, length => 0x01, pack => "C" }, + sub_class => { offset => 0x0e, length => 0x01, pack => "C" }, + base_class => { 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; + + my $self = tied ( %$hash ); + my $length = $rom->{rom}->length (); + + return undef unless ( $offset + $self->{length} <= $length && + $hash->{signature} eq Option::ROM::PCI_SIGNATURE && + $offset + $hash->{struct_length} <= $length ); + + # Retrieve true length of structure + $self->{length} = $hash->{struct_length}; + + return $hash; +} + +sub device_list { + my $hash = shift; + my $self = tied(%$hash); + + my $device_list = $hash->{device_list}; + return undef unless $device_list; + + my @ids; + my $offset = ( $self->{offset} + $device_list ); + while ( 1 ) { + my $raw = substr ( ${$self->{data}}, $offset, 2 ); + my $id = unpack ( "S", $raw ); + last unless $id; + push @ids, $id; + $offset += 2; + } + + return @ids; +} + +############################################################################## +# +# Option::ROM::PnP +# +############################################################################## + +package Option::ROM::PnP; + +use strict; +use warnings; +use Carp; +use bytes; + +sub new { + my $class = shift; + my $rom = shift; + my $offset = shift; + + my $hash = {}; + tie %$hash, "Option::ROM::Fields", { + rom => $rom, + data => $rom->{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; + + my $self = tied ( %$hash ); + my $length = $rom->{rom}->length (); + + return undef unless ( $offset + $self->{length} <= $length && + $hash->{signature} eq Option::ROM::PNP_SIGNATURE && + $offset + $hash->{struct_length} * 16 <= $length ); + + # Retrieve true length of structure + $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 $rom = shift; + my $offset = shift; + + my $hash = {}; + tie %$hash, "Option::ROM::Fields", { + rom => $rom, + data => $rom->{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; + + my $self = tied ( %$hash ); + my $length = $rom->{rom}->length (); + + return undef unless ( $offset + $self->{length} <= $length && + $hash->{signature} eq Option::ROM::UNDI_SIGNATURE && + $offset + $hash->{struct_length} <= $length ); + + # Retrieve true length of structure + $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 $rom = shift; + my $offset = shift; + + my $hash = {}; + tie %$hash, "Option::ROM::Fields", { + rom => $rom, + data => $rom->{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; + + my $self = tied ( %$hash ); + my $length = $rom->{rom}->length (); + + return undef unless ( $offset + $self->{length} <= $length && + $hash->{signature} eq Option::ROM::IPXE_SIGNATURE && + $offset + $hash->{struct_length} <= $length ); + + # Retrieve true length of structure + $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::EFI +# +############################################################################## + +package Option::ROM::EFI; + +use strict; +use warnings; +use Carp; +use bytes; + +sub new { + my $class = shift; + my $rom = shift; + my $pci = shift; + + my $hash = {}; + tie %$hash, "Option::ROM::Fields", { + rom => $rom, + data => $rom->{data}, + offset => 0x00, + length => 0x18, + fields => { + signature => { offset => 0x00, length => 0x02, pack => "S" }, + init_size => { offset => 0x02, length => 0x02, pack => "S" }, + efi_signature => { offset => 0x04, length => 0x04, pack => "L" }, + efi_subsystem => { offset => 0x08, length => 0x02, pack => "S" }, + efi_machine_type => { offset => 0x0a, length => 0x02, pack => "S" }, + compression_type => { offset => 0x0c, length => 0x02, pack => "S" }, + efi_image_offset => { offset => 0x16, length => 0x02, pack => "S" }, + }, + }; + bless $hash, $class; + + my $self = tied ( %$hash ); + + return undef unless ( $hash->{efi_signature} == Option::ROM::EFI_SIGNATURE && + $pci->{code_type} == 0x03 ); + + return $hash; +} + +1; |