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 $pci_header = $hash->pci_header();
270 if ( ( defined $pci_header ) &&
271 ( ! ( $pci_header->{last_image
} & PCI_LAST_IMAGE
) ) ) {
272 my $length = ( $pci_header->{image_length
} * 512 );
273 my $remainder = substr ( $data, $length );
274 $data = substr ( $data, 0, $length );
275 $self->{next_image
} = new Option
::ROM
;
276 $self->{next_image
}->set ( $remainder );
284 Get option ROM contents.
290 my $self = tied(%$hash);
292 my $data = ${$self->{data
}};
293 $data .= $self->{next_image
}->get() if $self->{next_image
};
299 =item C<< load ( $filename ) >>
301 Load option ROM contents from the file C<$filename>.
307 my $self = tied(%$hash);
308 my $filename = shift;
310 $self->{filename
} = $filename;
312 open my $fh, "<$filename"
313 or croak
"Cannot open $filename for reading: $!";
314 read $fh, my $data, -s
$fh;
315 $hash->set ( $data );
321 =item C<< save ( [ $filename ] ) >>
323 Write the ROM data back out to the file C<$filename>. If C<$filename>
324 is omitted, the file used in the call to C<load()> will be used.
330 my $self = tied(%$hash);
331 my $filename = shift;
333 $filename ||= $self->{filename
};
335 open my $fh, ">$filename"
336 or croak
"Cannot open $filename for writing: $!";
337 my $data = $hash->get();
344 =item C<< length () >>
346 Length of option ROM data. This is the length of the file, not the
347 length from the ROM header length field.
353 my $self = tied(%$hash);
355 return length ${$self->{data
}};
360 =item C<< pci_header () >>
362 Return a C<Option::ROM::PCI> object representing the ROM's PCI header,
369 my $self = tied(%$hash);
371 my $offset = $hash->{pci_header
};
372 return undef unless $offset != 0;
374 return Option
::ROM
::PCI
->new ( $self->{data
}, $offset );
379 =item C<< pnp_header () >>
381 Return a C<Option::ROM::PnP> object representing the ROM's PnP header,
388 my $self = tied(%$hash);
390 my $offset = $hash->{pnp_header
};
391 return undef unless $offset != 0;
393 return Option
::ROM
::PnP
->new ( $self->{data
}, $offset );
398 =item C<< undi_header () >>
400 Return a C<Option::ROM::UNDI> object representing the ROM's UNDI header,
407 my $self = tied(%$hash);
409 my $offset = $hash->{undi_header
};
410 return undef unless $offset != 0;
412 return Option
::ROM
::UNDI
->new ( $self->{data
}, $offset );
417 =item C<< ipxe_header () >>
419 Return a C<Option::ROM::iPXE> object representing the ROM's iPXE
426 my $self = tied(%$hash);
428 my $offset = $hash->{ipxe_header
};
429 return undef unless $offset != 0;
431 return Option
::ROM
::iPXE
->new ( $self->{data
}, $offset );
436 =item C<< next_image () >>
438 Return a C<Option::ROM> object representing the next image within the
445 my $self = tied(%$hash);
447 return $self->{next_image
};
452 =item C<< checksum () >>
454 Calculate the byte checksum of the ROM.
460 my $self = tied(%$hash);
462 my $raw = substr ( ${$self->{data
}}, 0, ( $hash->{length} * 512 ) );
463 return unpack ( "%8C*", $raw );
468 =item C<< fix_checksum () >>
470 Fix the byte checksum of the ROM.
476 my $self = tied(%$hash);
478 $hash->{checksum
} = ( ( $hash->{checksum
} - $hash->checksum() ) & 0xff );
481 ##############################################################################
485 ##############################################################################
487 package Option
::ROM
::PCI
;
500 tie
%$hash, "Option::ROM::Fields", {
505 signature
=> { offset
=> 0x00, length => 0x04, pack => "a4" },
506 vendor_id
=> { offset
=> 0x04, length => 0x02, pack => "S" },
507 device_id
=> { offset
=> 0x06, length => 0x02, pack => "S" },
508 device_list
=> { offset
=> 0x08, length => 0x02, pack => "S" },
509 struct_length
=> { offset
=> 0x0a, length => 0x02, pack => "S" },
510 struct_revision
=>{ offset
=> 0x0c, length => 0x01, pack => "C" },
511 base_class
=> { offset
=> 0x0d, length => 0x01, pack => "C" },
512 sub_class
=> { offset
=> 0x0e, length => 0x01, pack => "C" },
513 prog_intf
=> { offset
=> 0x0f, length => 0x01, pack => "C" },
514 image_length
=> { offset
=> 0x10, length => 0x02, pack => "S" },
515 revision
=> { offset
=> 0x12, length => 0x02, pack => "S" },
516 code_type
=> { offset
=> 0x14, length => 0x01, pack => "C" },
517 last_image
=> { offset
=> 0x15, length => 0x01, pack => "C" },
518 runtime_length
=> { offset
=> 0x16, length => 0x02, pack => "S" },
519 conf_header
=> { offset
=> 0x18, length => 0x02, pack => "S" },
520 clp_entry
=> { offset
=> 0x1a, length => 0x02, pack => "S" },
525 # Retrieve true length of structure
526 my $self = tied ( %$hash );
527 $self->{length} = $hash->{struct_length
};
534 my $self = tied(%$hash);
536 my $device_list = $hash->{device_list
};
537 return undef unless $device_list;
540 my $offset = ( $self->{offset
} + $device_list );
542 my $raw = substr ( ${$self->{data
}}, $offset, 2 );
543 my $id = unpack ( "S", $raw );
552 ##############################################################################
556 ##############################################################################
558 package Option
::ROM
::PnP
;
571 tie
%$hash, "Option::ROM::Fields", {
576 signature
=> { offset
=> 0x00, length => 0x04, pack => "a4" },
577 struct_revision
=>{ offset
=> 0x04, length => 0x01, pack => "C" },
578 struct_length
=> { offset
=> 0x05, length => 0x01, pack => "C" },
579 checksum
=> { offset
=> 0x09, length => 0x01, pack => "C" },
580 manufacturer
=> { offset
=> 0x0e, length => 0x02, pack => "S" },
581 product
=> { offset
=> 0x10, length => 0x02, pack => "S" },
582 bcv
=> { offset
=> 0x16, length => 0x02, pack => "S" },
583 bdv
=> { offset
=> 0x18, length => 0x02, pack => "S" },
584 bev
=> { offset
=> 0x1a, length => 0x02, pack => "S" },
589 # Retrieve true length of structure
590 my $self = tied ( %$hash );
591 $self->{length} = ( $hash->{struct_length
} * 16 );
598 my $self = tied(%$hash);
600 return $self->checksum();
605 my $self = tied(%$hash);
607 $hash->{checksum
} = ( ( $hash->{checksum
} - $hash->checksum() ) & 0xff );
612 my $self = tied(%$hash);
614 my $manufacturer = $hash->{manufacturer
};
615 return undef unless $manufacturer;
617 my $raw = substr ( ${$self->{data
}}, $manufacturer );
618 return unpack ( "Z*", $raw );
623 my $self = tied(%$hash);
625 my $product = $hash->{product
};
626 return undef unless $product;
628 my $raw = substr ( ${$self->{data
}}, $product );
629 return unpack ( "Z*", $raw );
632 ##############################################################################
636 ##############################################################################
638 package Option
::ROM
::UNDI
;
651 tie
%$hash, "Option::ROM::Fields", {
656 signature
=> { offset
=> 0x00, length => 0x04, pack => "a4" },
657 struct_length
=> { offset
=> 0x04, length => 0x01, pack => "C" },
658 checksum
=> { offset
=> 0x05, length => 0x01, pack => "C" },
659 struct_revision
=>{ offset
=> 0x06, length => 0x01, pack => "C" },
660 version_revision
=>{ offset
=> 0x07, length => 0x01, pack => "C" },
661 version_minor
=> { offset
=> 0x08, length => 0x01, pack => "C" },
662 version_major
=> { offset
=> 0x09, length => 0x01, pack => "C" },
663 loader_entry
=> { offset
=> 0x0a, length => 0x02, pack => "S" },
664 stack_size
=> { offset
=> 0x0c, length => 0x02, pack => "S" },
665 data_size
=> { offset
=> 0x0e, length => 0x02, pack => "S" },
666 code_size
=> { offset
=> 0x10, length => 0x02, pack => "S" },
667 bus_type
=> { offset
=> 0x12, length => 0x04, pack => "a4" },
672 # Retrieve true length of structure
673 my $self = tied ( %$hash );
674 $self->{length} = $hash->{struct_length
};
681 my $self = tied(%$hash);
683 return $self->checksum();
688 my $self = tied(%$hash);
690 $hash->{checksum
} = ( ( $hash->{checksum
} - $hash->checksum() ) & 0xff );
693 ##############################################################################
697 ##############################################################################
699 package Option
::ROM
::iPXE
;
712 tie
%$hash, "Option::ROM::Fields", {
717 signature
=> { offset
=> 0x00, length => 0x04, pack => "a4" },
718 struct_length
=> { offset
=> 0x04, length => 0x01, pack => "C" },
719 checksum
=> { offset
=> 0x05, length => 0x01, pack => "C" },
720 shrunk_length
=> { offset
=> 0x06, length => 0x01, pack => "C" },
721 build_id
=> { offset
=> 0x08, length => 0x04, pack => "L" },
726 # Retrieve true length of structure
727 my $self = tied ( %$hash );
728 $self->{length} = $hash->{struct_length
};
735 my $self = tied(%$hash);
737 return $self->checksum();
742 my $self = tied(%$hash);
744 $hash->{checksum
} = ( ( $hash->{checksum
} - $hash->checksum() ) & 0xff );