3 # Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License as
7 # published by the Free Software Foundation; either version 2 of the
8 # License, or any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 Option::ROM - Option ROM manipulation
29 my $rom = new Option::ROM;
30 $rom->load ( "rtl8139.rom" );
32 # Modify the PCI device ID
33 $rom->pci_header->{device_id} = 0x1234;
36 # Write ROM image out to a new file
37 $rom->save ( "rtl8139-modified.rom" );
41 C<Option::ROM> provides a mechanism for manipulating Option ROM
48 ##############################################################################
52 ##############################################################################
54 package Option::ROM::Fields;
73 return undef unless $self->EXISTS ( $key );
74 my $raw = substr ( ${$self->{data}},
75 ( $self->{offset} + $self->{fields}->{$key}->{offset} ),
76 $self->{fields}->{$key}->{length} );
77 my $unpack = ( ref $self->{fields}->{$key}->{unpack} ?
78 $self->{fields}->{$key}->{unpack} :
79 sub { unpack ( $self->{fields}->{$key}->{pack}, shift ); } );
80 return &$unpack ( $raw );
88 croak "Nonexistent field \"$key\"" unless $self->EXISTS ( $key );
89 my $pack = ( ref $self->{fields}->{$key}->{pack} ?
90 $self->{fields}->{$key}->{pack} :
91 sub { pack ( $self->{fields}->{$key}->{pack}, shift ); } );
92 my $raw = &$pack ( $value );
93 substr ( ${$self->{data}},
94 ( $self->{offset} + $self->{fields}->{$key}->{offset} ),
95 $self->{fields}->{$key}->{length} ) = $raw;
102 $self->STORE ( $key, 0 );
108 foreach my $key ( keys %{$self->{fields}} ) {
109 $self->DELETE ( $key );
117 return ( exists $self->{fields}->{$key} &&
118 ( ( $self->{fields}->{$key}->{offset} +
119 $self->{fields}->{$key}->{length} ) <= $self->{length} ) );
125 keys %{$self->{fields}};
126 return each %{$self->{fields}};
133 return each %{$self->{fields}};
153 my $raw = substr ( ${$self->{data}}, $self->{offset}, $self->{length} );
154 return unpack ( "%8C*", $raw );
157 ##############################################################################
161 ##############################################################################
169 use Exporter 'import';
171 use constant ROM_SIGNATURE => 0xaa55;
172 use constant PCI_SIGNATURE => 'PCIR';
173 use constant PCI_LAST_IMAGE => 0x80;
174 use constant PNP_SIGNATURE => '$PnP';
175 use constant IPXE_SIGNATURE => 'iPXE';
177 our @EXPORT_OK = qw ( ROM_SIGNATURE PCI_SIGNATURE PCI_LAST_IMAGE
178 PNP_SIGNATURE IPXE_SIGNATURE );
179 our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
181 use constant JMP_SHORT => 0xeb;
182 use constant JMP_NEAR => 0xe9;
183 use constant CALL_NEAR => 0xe8;
188 # Always create a near jump; it's simpler
190 return pack ( "CS", JMP_NEAR, ( $dest - 6 ) );
192 return pack ( "CS", 0, 0 );
199 # Accept both short and near jumps
200 my $jump = unpack ( "C", $instr );
201 if ( $jump == JMP_SHORT ) {
202 my $offset = unpack ( "xC", $instr );
203 return ( $offset + 5 );
204 } elsif ( $jump == JMP_NEAR ) {
205 my $offset = unpack ( "xS", $instr );
206 return ( $offset + 6 );
207 } elsif ( $jump == CALL_NEAR ) {
208 my $offset = unpack ( "xS", $instr );
209 return ( $offset + 6 );
210 } elsif ( $jump == 0 ) {
213 croak "Unrecognised jump instruction in init vector\n";
221 Construct a new C<Option::ROM> object.
229 tie %$hash, "Option::ROM::Fields", {
234 signature => { offset => 0x00, length => 0x02, pack => "S" },
235 length => { offset => 0x02, length => 0x01, pack => "C" },
236 # "init" is part of a jump instruction
237 init => { offset => 0x03, length => 0x03,
238 pack => \&pack_init, unpack => \&unpack_init },
239 checksum => { offset => 0x06, length => 0x01, pack => "C" },
240 ipxe_header => { offset => 0x10, length => 0x02, pack => "S" },
241 bofm_header => { offset => 0x14, length => 0x02, pack => "S" },
242 undi_header => { offset => 0x16, length => 0x02, pack => "S" },
243 pci_header => { offset => 0x18, length => 0x02, pack => "S" },
244 pnp_header => { offset => 0x1a, length => 0x02, pack => "S" },
253 =item C<< set ( $data ) >>
255 Set option ROM contents.
261 my $self = tied(%$hash);
265 $self->{data} = \$data;
267 # Split out any data belonging to the next image
268 delete $self->{next_image};
269 my $length = ( $hash->{length} * 512 );
270 my $pci_header = $hash->pci_header();
271 if ( ( $length < length $data ) &&
272 ( defined $pci_header ) &&
273 ( ! ( $pci_header->{last_image} & PCI_LAST_IMAGE ) ) ) {
274 my $remainder = substr ( $data, $length );
275 $data = substr ( $data, 0, $length );
276 $self->{next_image} = new Option::ROM;
277 $self->{next_image}->set ( $remainder );
285 Get option ROM contents.
291 my $self = tied(%$hash);
293 my $data = ${$self->{data}};
294 $data .= $self->{next_image}->get() if $self->{next_image};
300 =item C<< load ( $filename ) >>
302 Load option ROM contents from the file C<$filename>.
308 my $self = tied(%$hash);
309 my $filename = shift;
311 $self->{filename} = $filename;
313 open my $fh, "<$filename"
314 or croak "Cannot open $filename for reading: $!";
315 read $fh, my $data, -s $fh;
316 $hash->set ( $data );
322 =item C<< save ( [ $filename ] ) >>
324 Write the ROM data back out to the file C<$filename>. If C<$filename>
325 is omitted, the file used in the call to C<load()> will be used.
331 my $self = tied(%$hash);
332 my $filename = shift;
334 $filename ||= $self->{filename};
336 open my $fh, ">$filename"
337 or croak "Cannot open $filename for writing: $!";
338 my $data = $hash->get();
345 =item C<< length () >>
347 Length of option ROM data. This is the length of the file, not the
348 length from the ROM header length field.
354 my $self = tied(%$hash);
356 return length ${$self->{data}};
361 =item C<< pci_header () >>
363 Return a C<Option::ROM::PCI> object representing the ROM's PCI header,
370 my $self = tied(%$hash);
372 my $offset = $hash->{pci_header};
373 return undef unless $offset != 0;
375 return Option::ROM::PCI->new ( $self->{data}, $offset );
380 =item C<< pnp_header () >>
382 Return a C<Option::ROM::PnP> object representing the ROM's PnP header,
389 my $self = tied(%$hash);
391 my $offset = $hash->{pnp_header};
392 return undef unless $offset != 0;
394 return Option::ROM::PnP->new ( $self->{data}, $offset );
399 =item C<< undi_header () >>
401 Return a C<Option::ROM::UNDI> object representing the ROM's UNDI header,
408 my $self = tied(%$hash);
410 my $offset = $hash->{undi_header};
411 return undef unless $offset != 0;
413 return Option::ROM::UNDI->new ( $self->{data}, $offset );
418 =item C<< ipxe_header () >>
420 Return a C<Option::ROM::iPXE> object representing the ROM's iPXE
427 my $self = tied(%$hash);
429 my $offset = $hash->{ipxe_header};
430 return undef unless $offset != 0;
432 return Option::ROM::iPXE->new ( $self->{data}, $offset );
437 =item C<< next_image () >>
439 Return a C<Option::ROM> object representing the next image within the
446 my $self = tied(%$hash);
448 return $self->{next_image};
453 =item C<< checksum () >>
455 Calculate the byte checksum of the ROM.
461 my $self = tied(%$hash);
463 my $raw = substr ( ${$self->{data}}, 0, ( $hash->{length} * 512 ) );
464 return unpack ( "%8C*", $raw );
469 =item C<< fix_checksum () >>
471 Fix the byte checksum of the ROM.
477 my $self = tied(%$hash);
479 $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
482 ##############################################################################
486 ##############################################################################
488 package Option::ROM::PCI;
501 tie %$hash, "Option::ROM::Fields", {
506 signature => { offset => 0x00, length => 0x04, pack => "a4" },
507 vendor_id => { offset => 0x04, length => 0x02, pack => "S" },
508 device_id => { offset => 0x06, length => 0x02, pack => "S" },
509 device_list => { offset => 0x08, length => 0x02, pack => "S" },
510 struct_length => { offset => 0x0a, length => 0x02, pack => "S" },
511 struct_revision =>{ offset => 0x0c, length => 0x01, pack => "C" },
512 base_class => { offset => 0x0d, length => 0x01, pack => "C" },
513 sub_class => { offset => 0x0e, length => 0x01, pack => "C" },
514 prog_intf => { offset => 0x0f, length => 0x01, pack => "C" },
515 image_length => { offset => 0x10, length => 0x02, pack => "S" },
516 revision => { offset => 0x12, length => 0x02, pack => "S" },
517 code_type => { offset => 0x14, length => 0x01, pack => "C" },
518 last_image => { offset => 0x15, length => 0x01, pack => "C" },
519 runtime_length => { offset => 0x16, length => 0x02, pack => "S" },
520 conf_header => { offset => 0x18, length => 0x02, pack => "S" },
521 clp_entry => { offset => 0x1a, length => 0x02, pack => "S" },
526 # Retrieve true length of structure
527 my $self = tied ( %$hash );
528 $self->{length} = $hash->{struct_length};
533 ##############################################################################
537 ##############################################################################
539 package Option::ROM::PnP;
552 tie %$hash, "Option::ROM::Fields", {
557 signature => { offset => 0x00, length => 0x04, pack => "a4" },
558 struct_revision =>{ offset => 0x04, length => 0x01, pack => "C" },
559 struct_length => { offset => 0x05, length => 0x01, pack => "C" },
560 checksum => { offset => 0x09, length => 0x01, pack => "C" },
561 manufacturer => { offset => 0x0e, length => 0x02, pack => "S" },
562 product => { offset => 0x10, length => 0x02, pack => "S" },
563 bcv => { offset => 0x16, length => 0x02, pack => "S" },
564 bdv => { offset => 0x18, length => 0x02, pack => "S" },
565 bev => { offset => 0x1a, length => 0x02, pack => "S" },
570 # Retrieve true length of structure
571 my $self = tied ( %$hash );
572 $self->{length} = ( $hash->{struct_length} * 16 );
579 my $self = tied(%$hash);
581 return $self->checksum();
586 my $self = tied(%$hash);
588 $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
593 my $self = tied(%$hash);
595 my $manufacturer = $hash->{manufacturer};
596 return undef unless $manufacturer;
598 my $raw = substr ( ${$self->{data}}, $manufacturer );
599 return unpack ( "Z*", $raw );
604 my $self = tied(%$hash);
606 my $product = $hash->{product};
607 return undef unless $product;
609 my $raw = substr ( ${$self->{data}}, $product );
610 return unpack ( "Z*", $raw );
613 ##############################################################################
617 ##############################################################################
619 package Option::ROM::UNDI;
632 tie %$hash, "Option::ROM::Fields", {
637 signature => { offset => 0x00, length => 0x04, pack => "a4" },
638 struct_length => { offset => 0x04, length => 0x01, pack => "C" },
639 checksum => { offset => 0x05, length => 0x01, pack => "C" },
640 struct_revision =>{ offset => 0x06, length => 0x01, pack => "C" },
641 version_revision =>{ offset => 0x07, length => 0x01, pack => "C" },
642 version_minor => { offset => 0x08, length => 0x01, pack => "C" },
643 version_major => { offset => 0x09, length => 0x01, pack => "C" },
644 loader_entry => { offset => 0x0a, length => 0x02, pack => "S" },
645 stack_size => { offset => 0x0c, length => 0x02, pack => "S" },
646 data_size => { offset => 0x0e, length => 0x02, pack => "S" },
647 code_size => { offset => 0x10, length => 0x02, pack => "S" },
648 bus_type => { offset => 0x12, length => 0x04, pack => "a4" },
653 # Retrieve true length of structure
654 my $self = tied ( %$hash );
655 $self->{length} = $hash->{struct_length};
662 my $self = tied(%$hash);
664 return $self->checksum();
669 my $self = tied(%$hash);
671 $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
674 ##############################################################################
678 ##############################################################################
680 package Option::ROM::iPXE;
693 tie %$hash, "Option::ROM::Fields", {
698 signature => { offset => 0x00, length => 0x04, pack => "a4" },
699 struct_length => { offset => 0x04, length => 0x01, pack => "C" },
700 checksum => { offset => 0x05, length => 0x01, pack => "C" },
701 shrunk_length => { offset => 0x06, length => 0x01, pack => "C" },
702 build_id => { offset => 0x08, length => 0x04, pack => "L" },
707 # Retrieve true length of structure
708 my $self = tied ( %$hash );
709 $self->{length} = $hash->{struct_length};
716 my $self = tied(%$hash);
718 return $self->checksum();
723 my $self = tied(%$hash);
725 $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );