You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ROM.pm 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. package Option::ROM;
  2. # Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License as
  6. # published by the Free Software Foundation; either version 2 of the
  7. # License, or any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but
  10. # WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17. =head1 NAME
  18. Option::ROM - Option ROM manipulation
  19. =head1 SYNOPSIS
  20. use Option::ROM;
  21. # Load a ROM image
  22. my $rom = new Option::ROM;
  23. $rom->load ( "rtl8139.rom" );
  24. # Modify the PCI device ID
  25. $rom->pci_header->{device_id} = 0x1234;
  26. $rom->fix_checksum();
  27. # Write ROM image out to a new file
  28. $rom->save ( "rtl8139-modified.rom" );
  29. =head1 DESCRIPTION
  30. C<Option::ROM> provides a mechanism for manipulating Option ROM
  31. images.
  32. =head1 METHODS
  33. =cut
  34. ##############################################################################
  35. #
  36. # Option::ROM::Fields
  37. #
  38. ##############################################################################
  39. package Option::ROM::Fields;
  40. use strict;
  41. use warnings;
  42. use Carp;
  43. use bytes;
  44. sub TIEHASH {
  45. my $class = shift;
  46. my $self = shift;
  47. bless $self, $class;
  48. return $self;
  49. }
  50. sub FETCH {
  51. my $self = shift;
  52. my $key = shift;
  53. return undef unless $self->EXISTS ( $key );
  54. my $raw = substr ( ${$self->{data}},
  55. ( $self->{offset} + $self->{fields}->{$key}->{offset} ),
  56. $self->{fields}->{$key}->{length} );
  57. my $unpack = ( ref $self->{fields}->{$key}->{unpack} ?
  58. $self->{fields}->{$key}->{unpack} :
  59. sub { unpack ( $self->{fields}->{$key}->{pack}, shift ); } );
  60. return &$unpack ( $raw );
  61. }
  62. sub STORE {
  63. my $self = shift;
  64. my $key = shift;
  65. my $value = shift;
  66. croak "Nonexistent field \"$key\"" unless $self->EXISTS ( $key );
  67. my $pack = ( ref $self->{fields}->{$key}->{pack} ?
  68. $self->{fields}->{$key}->{pack} :
  69. sub { pack ( $self->{fields}->{$key}->{pack}, shift ); } );
  70. my $raw = &$pack ( $value );
  71. substr ( ${$self->{data}},
  72. ( $self->{offset} + $self->{fields}->{$key}->{offset} ),
  73. $self->{fields}->{$key}->{length} ) = $raw;
  74. }
  75. sub DELETE {
  76. my $self = shift;
  77. my $key = shift;
  78. $self->STORE ( $key, 0 );
  79. }
  80. sub CLEAR {
  81. my $self = shift;
  82. foreach my $key ( keys %{$self->{fields}} ) {
  83. $self->DELETE ( $key );
  84. }
  85. }
  86. sub EXISTS {
  87. my $self = shift;
  88. my $key = shift;
  89. return ( exists $self->{fields}->{$key} &&
  90. ( ( $self->{fields}->{$key}->{offset} +
  91. $self->{fields}->{$key}->{length} ) <= $self->{length} ) );
  92. }
  93. sub FIRSTKEY {
  94. my $self = shift;
  95. keys %{$self->{fields}};
  96. return each %{$self->{fields}};
  97. }
  98. sub NEXTKEY {
  99. my $self = shift;
  100. my $lastkey = shift;
  101. return each %{$self->{fields}};
  102. }
  103. sub SCALAR {
  104. my $self = shift;
  105. return 1;
  106. }
  107. sub UNTIE {
  108. my $self = shift;
  109. }
  110. sub DESTROY {
  111. my $self = shift;
  112. }
  113. sub checksum {
  114. my $self = shift;
  115. my $raw = substr ( ${$self->{data}}, $self->{offset}, $self->{length} );
  116. return unpack ( "%8C*", $raw );
  117. }
  118. ##############################################################################
  119. #
  120. # Option::ROM
  121. #
  122. ##############################################################################
  123. package Option::ROM;
  124. use strict;
  125. use warnings;
  126. use Carp;
  127. use bytes;
  128. use Exporter 'import';
  129. use constant ROM_SIGNATURE => 0xaa55;
  130. use constant PCI_SIGNATURE => 'PCIR';
  131. use constant PNP_SIGNATURE => '$PnP';
  132. our @EXPORT_OK = qw ( ROM_SIGNATURE PCI_SIGNATURE PNP_SIGNATURE );
  133. our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
  134. use constant JMP_SHORT => 0xeb;
  135. use constant JMP_NEAR => 0xe9;
  136. sub pack_init {
  137. my $dest = shift;
  138. # Always create a near jump; it's simpler
  139. if ( $dest ) {
  140. return pack ( "CS", JMP_NEAR, ( $dest - 6 ) );
  141. } else {
  142. return pack ( "CS", 0, 0 );
  143. }
  144. }
  145. sub unpack_init {
  146. my $instr = shift;
  147. # Accept both short and near jumps
  148. my $jump = unpack ( "C", $instr );
  149. if ( $jump == JMP_SHORT ) {
  150. my $offset = unpack ( "xC", $instr );
  151. return ( $offset + 5 );
  152. } elsif ( $jump == JMP_NEAR ) {
  153. my $offset = unpack ( "xS", $instr );
  154. return ( $offset + 6 );
  155. } elsif ( $jump == 0 ) {
  156. return 0;
  157. } else {
  158. croak "Unrecognised jump instruction in init vector\n";
  159. }
  160. }
  161. =pod
  162. =item C<< new () >>
  163. Construct a new C<Option::ROM> object.
  164. =cut
  165. sub new {
  166. my $class = shift;
  167. my $hash = {};
  168. tie %$hash, "Option::ROM::Fields", {
  169. data => undef,
  170. offset => 0x00,
  171. length => 0x20,
  172. fields => {
  173. signature => { offset => 0x00, length => 0x02, pack => "S" },
  174. length => { offset => 0x02, length => 0x01, pack => "C" },
  175. # "init" is part of a jump instruction
  176. init => { offset => 0x03, length => 0x03,
  177. pack => \&pack_init, unpack => \&unpack_init },
  178. checksum => { offset => 0x06, length => 0x01, pack => "C" },
  179. bofm_header => { offset => 0x14, length => 0x02, pack => "S" },
  180. undi_header => { offset => 0x16, length => 0x02, pack => "S" },
  181. pci_header => { offset => 0x18, length => 0x02, pack => "S" },
  182. pnp_header => { offset => 0x1a, length => 0x02, pack => "S" },
  183. },
  184. };
  185. bless $hash, $class;
  186. return $hash;
  187. }
  188. =pod
  189. =item C<< load ( $filename ) >>
  190. Load option ROM contents from the file C<$filename>.
  191. =cut
  192. sub load {
  193. my $hash = shift;
  194. my $self = tied(%$hash);
  195. my $filename = shift;
  196. $self->{filename} = $filename;
  197. open my $fh, "<$filename"
  198. or croak "Cannot open $filename for reading: $!";
  199. read $fh, my $data, ( 128 * 1024 ); # 128kB is theoretical max size
  200. $self->{data} = \$data;
  201. close $fh;
  202. }
  203. =pod
  204. =item C<< save ( [ $filename ] ) >>
  205. Write the ROM data back out to the file C<$filename>. If C<$filename>
  206. is omitted, the file used in the call to C<load()> will be used.
  207. =cut
  208. sub save {
  209. my $hash = shift;
  210. my $self = tied(%$hash);
  211. my $filename = shift;
  212. $filename ||= $self->{filename};
  213. open my $fh, ">$filename"
  214. or croak "Cannot open $filename for writing: $!";
  215. print $fh ${$self->{data}};
  216. close $fh;
  217. }
  218. =pod
  219. =item C<< length () >>
  220. Length of option ROM data. This is the length of the file, not the
  221. length from the ROM header length field.
  222. =cut
  223. sub length {
  224. my $hash = shift;
  225. my $self = tied(%$hash);
  226. return length ${$self->{data}};
  227. }
  228. =pod
  229. =item C<< pci_header () >>
  230. Return a C<Option::ROM::PCI> object representing the ROM's PCI header,
  231. if present.
  232. =cut
  233. sub pci_header {
  234. my $hash = shift;
  235. my $self = tied(%$hash);
  236. my $offset = $hash->{pci_header};
  237. return undef unless $offset != 0;
  238. return Option::ROM::PCI->new ( $self->{data}, $offset );
  239. }
  240. =pod
  241. =item C<< pnp_header () >>
  242. Return a C<Option::ROM::PnP> object representing the ROM's PnP header,
  243. if present.
  244. =cut
  245. sub pnp_header {
  246. my $hash = shift;
  247. my $self = tied(%$hash);
  248. my $offset = $hash->{pnp_header};
  249. return undef unless $offset != 0;
  250. return Option::ROM::PnP->new ( $self->{data}, $offset );
  251. }
  252. =pod
  253. =item C<< checksum () >>
  254. Calculate the byte checksum of the ROM.
  255. =cut
  256. sub checksum {
  257. my $hash = shift;
  258. my $self = tied(%$hash);
  259. return unpack ( "%8C*", ${$self->{data}} );
  260. }
  261. =pod
  262. =item C<< fix_checksum () >>
  263. Fix the byte checksum of the ROM.
  264. =cut
  265. sub fix_checksum {
  266. my $hash = shift;
  267. my $self = tied(%$hash);
  268. $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
  269. }
  270. ##############################################################################
  271. #
  272. # Option::ROM::PCI
  273. #
  274. ##############################################################################
  275. package Option::ROM::PCI;
  276. use strict;
  277. use warnings;
  278. use Carp;
  279. use bytes;
  280. sub new {
  281. my $class = shift;
  282. my $data = shift;
  283. my $offset = shift;
  284. my $hash = {};
  285. tie %$hash, "Option::ROM::Fields", {
  286. data => $data,
  287. offset => $offset,
  288. length => 0x0c,
  289. fields => {
  290. signature => { offset => 0x00, length => 0x04, pack => "a4" },
  291. vendor_id => { offset => 0x04, length => 0x02, pack => "S" },
  292. device_id => { offset => 0x06, length => 0x02, pack => "S" },
  293. device_list => { offset => 0x08, length => 0x02, pack => "S" },
  294. struct_length => { offset => 0x0a, length => 0x02, pack => "S" },
  295. struct_revision =>{ offset => 0x0c, length => 0x01, pack => "C" },
  296. base_class => { offset => 0x0d, length => 0x01, pack => "C" },
  297. sub_class => { offset => 0x0e, length => 0x01, pack => "C" },
  298. prog_intf => { offset => 0x0f, length => 0x01, pack => "C" },
  299. image_length => { offset => 0x10, length => 0x02, pack => "S" },
  300. revision => { offset => 0x12, length => 0x02, pack => "S" },
  301. code_type => { offset => 0x14, length => 0x01, pack => "C" },
  302. last_image => { offset => 0x15, length => 0x01, pack => "C" },
  303. runtime_length => { offset => 0x16, length => 0x02, pack => "S" },
  304. conf_header => { offset => 0x18, length => 0x02, pack => "S" },
  305. clp_entry => { offset => 0x1a, length => 0x02, pack => "S" },
  306. },
  307. };
  308. bless $hash, $class;
  309. # Retrieve true length of structure
  310. my $self = tied ( %$hash );
  311. $self->{length} = $hash->{struct_length};
  312. return $hash;
  313. }
  314. ##############################################################################
  315. #
  316. # Option::ROM::PnP
  317. #
  318. ##############################################################################
  319. package Option::ROM::PnP;
  320. use strict;
  321. use warnings;
  322. use Carp;
  323. use bytes;
  324. sub new {
  325. my $class = shift;
  326. my $data = shift;
  327. my $offset = shift;
  328. my $hash = {};
  329. tie %$hash, "Option::ROM::Fields", {
  330. data => $data,
  331. offset => $offset,
  332. length => 0x06,
  333. fields => {
  334. signature => { offset => 0x00, length => 0x04, pack => "a4" },
  335. struct_revision =>{ offset => 0x04, length => 0x01, pack => "C" },
  336. struct_length => { offset => 0x05, length => 0x01, pack => "C" },
  337. checksum => { offset => 0x09, length => 0x01, pack => "C" },
  338. manufacturer => { offset => 0x0e, length => 0x02, pack => "S" },
  339. product => { offset => 0x10, length => 0x02, pack => "S" },
  340. bcv => { offset => 0x16, length => 0x02, pack => "S" },
  341. bdv => { offset => 0x18, length => 0x02, pack => "S" },
  342. bev => { offset => 0x1a, length => 0x02, pack => "S" },
  343. },
  344. };
  345. bless $hash, $class;
  346. # Retrieve true length of structure
  347. my $self = tied ( %$hash );
  348. $self->{length} = ( $hash->{struct_length} * 16 );
  349. return $hash;
  350. }
  351. sub checksum {
  352. my $hash = shift;
  353. my $self = tied(%$hash);
  354. return $self->checksum();
  355. }
  356. sub fix_checksum {
  357. my $hash = shift;
  358. my $self = tied(%$hash);
  359. $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
  360. }
  361. sub manufacturer {
  362. my $hash = shift;
  363. my $self = tied(%$hash);
  364. my $manufacturer = $hash->{manufacturer};
  365. return undef unless $manufacturer;
  366. my $raw = substr ( ${$self->{data}}, $manufacturer );
  367. return unpack ( "Z*", $raw );
  368. }
  369. sub product {
  370. my $hash = shift;
  371. my $self = tied(%$hash);
  372. my $product = $hash->{product};
  373. return undef unless $product;
  374. my $raw = substr ( ${$self->{data}}, $product );
  375. return unpack ( "Z*", $raw );
  376. }
  377. 1;