tizen 2.3.1 release
[external/qemu.git] / roms / ipxe / src / util / Option / ROM.pm
1 package Option::ROM;
2
3 # Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>.
4 #
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.
9 #
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.
14 #
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
18 # 02110-1301, USA.
19
20 =head1 NAME
21
22 Option::ROM - Option ROM manipulation
23
24 =head1 SYNOPSIS
25
26     use Option::ROM;
27
28     # Load a ROM image
29     my $rom = new Option::ROM;
30     $rom->load ( "rtl8139.rom" );
31
32     # Modify the PCI device ID
33     $rom->pci_header->{device_id} = 0x1234;
34     $rom->fix_checksum();
35
36     # Write ROM image out to a new file
37     $rom->save ( "rtl8139-modified.rom" );
38
39 =head1 DESCRIPTION
40
41 C<Option::ROM> provides a mechanism for manipulating Option ROM
42 images.
43
44 =head1 METHODS
45
46 =cut
47
48 ##############################################################################
49 #
50 # Option::ROM::Fields
51 #
52 ##############################################################################
53
54 package Option::ROM::Fields;
55
56 use strict;
57 use warnings;
58 use Carp;
59 use bytes;
60
61 sub TIEHASH {
62   my $class = shift;
63   my $self = shift;
64
65   bless $self, $class;
66   return $self;
67 }
68
69 sub FETCH {
70   my $self = shift;
71   my $key = shift;
72
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 );
81 }
82
83 sub STORE {
84   my $self = shift;
85   my $key = shift;
86   my $value = shift;
87
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;
96 }
97
98 sub DELETE {
99   my $self = shift;
100   my $key = shift;
101
102   $self->STORE ( $key, 0 );
103 }
104
105 sub CLEAR {
106   my $self = shift;
107
108   foreach my $key ( keys %{$self->{fields}} ) {
109     $self->DELETE ( $key );
110   }
111 }
112
113 sub EXISTS {
114   my $self = shift;
115   my $key = shift;
116
117   return ( exists $self->{fields}->{$key} &&
118            ( ( $self->{fields}->{$key}->{offset} +
119                $self->{fields}->{$key}->{length} ) <= $self->{length} ) );
120 }
121
122 sub FIRSTKEY {
123   my $self = shift;
124
125   keys %{$self->{fields}};
126   return each %{$self->{fields}};
127 }
128
129 sub NEXTKEY {
130   my $self = shift;
131   my $lastkey = shift;
132
133   return each %{$self->{fields}};
134 }
135
136 sub SCALAR {
137   my $self = shift;
138
139   return 1;
140 }
141
142 sub UNTIE {
143   my $self = shift;
144 }
145
146 sub DESTROY {
147   my $self = shift;
148 }
149
150 sub checksum {
151   my $self = shift;
152
153   my $raw = substr ( ${$self->{data}}, $self->{offset}, $self->{length} );
154   return unpack ( "%8C*", $raw );
155 }
156
157 ##############################################################################
158 #
159 # Option::ROM
160 #
161 ##############################################################################
162
163 package Option::ROM;
164
165 use strict;
166 use warnings;
167 use Carp;
168 use bytes;
169 use Exporter 'import';
170
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';
176
177 our @EXPORT_OK = qw ( ROM_SIGNATURE PCI_SIGNATURE PCI_LAST_IMAGE
178                       PNP_SIGNATURE IPXE_SIGNATURE );
179 our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
180
181 use constant JMP_SHORT => 0xeb;
182 use constant JMP_NEAR => 0xe9;
183 use constant CALL_NEAR => 0xe8;
184
185 sub pack_init {
186   my $dest = shift;
187
188   # Always create a near jump; it's simpler
189   if ( $dest ) {
190     return pack ( "CS", JMP_NEAR, ( $dest - 6 ) );
191   } else {
192     return pack ( "CS", 0, 0 );
193   }
194 }
195
196 sub unpack_init {
197   my $instr = shift;
198
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 ) {
211     return 0;
212   } else {
213     croak "Unrecognised jump instruction in init vector\n";
214   }
215 }
216
217 =pod
218
219 =item C<< new () >>
220
221 Construct a new C<Option::ROM> object.
222
223 =cut
224
225 sub new {
226   my $class = shift;
227
228   my $hash = {};
229   tie %$hash, "Option::ROM::Fields", {
230     data => undef,
231     offset => 0x00,
232     length => 0x20,
233     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" },
245     },
246   };
247   bless $hash, $class;
248   return $hash;
249 }
250
251 =pod
252
253 =item C<< set ( $data ) >>
254
255 Set option ROM contents.
256
257 =cut
258
259 sub set {
260   my $hash = shift;
261   my $self = tied(%$hash);
262   my $data = shift;
263
264   # Store data
265   $self->{data} = \$data;
266
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 );
278   }
279 }
280
281 =pod
282
283 =item C<< get () >>
284
285 Get option ROM contents.
286
287 =cut
288
289 sub get {
290   my $hash = shift;
291   my $self = tied(%$hash);
292
293   my $data = ${$self->{data}};
294   $data .= $self->{next_image}->get() if $self->{next_image};
295   return $data;
296 }
297
298 =pod
299
300 =item C<< load ( $filename ) >>
301
302 Load option ROM contents from the file C<$filename>.
303
304 =cut
305
306 sub load {
307   my $hash = shift;
308   my $self = tied(%$hash);
309   my $filename = shift;
310
311   $self->{filename} = $filename;
312
313   open my $fh, "<$filename"
314       or croak "Cannot open $filename for reading: $!";
315   read $fh, my $data, -s $fh;
316   $hash->set ( $data );
317   close $fh;
318 }
319
320 =pod
321
322 =item C<< save ( [ $filename ] ) >>
323
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.
326
327 =cut
328
329 sub save {
330   my $hash = shift;
331   my $self = tied(%$hash);
332   my $filename = shift;
333
334   $filename ||= $self->{filename};
335
336   open my $fh, ">$filename"
337       or croak "Cannot open $filename for writing: $!";
338   my $data = $hash->get();
339   print $fh $data;
340   close $fh;
341 }
342
343 =pod
344
345 =item C<< length () >>
346
347 Length of option ROM data.  This is the length of the file, not the
348 length from the ROM header length field.
349
350 =cut
351
352 sub length {
353   my $hash = shift;
354   my $self = tied(%$hash);
355
356   return length ${$self->{data}};
357 }
358
359 =pod
360
361 =item C<< pci_header () >>
362
363 Return a C<Option::ROM::PCI> object representing the ROM's PCI header,
364 if present.
365
366 =cut
367
368 sub pci_header {
369   my $hash = shift;
370   my $self = tied(%$hash);
371
372   my $offset = $hash->{pci_header};
373   return undef unless $offset != 0;
374
375   return Option::ROM::PCI->new ( $self->{data}, $offset );
376 }
377
378 =pod
379
380 =item C<< pnp_header () >>
381
382 Return a C<Option::ROM::PnP> object representing the ROM's PnP header,
383 if present.
384
385 =cut
386
387 sub pnp_header {
388   my $hash = shift;
389   my $self = tied(%$hash);
390
391   my $offset = $hash->{pnp_header};
392   return undef unless $offset != 0;
393
394   return Option::ROM::PnP->new ( $self->{data}, $offset );
395 }
396
397 =pod
398
399 =item C<< undi_header () >>
400
401 Return a C<Option::ROM::UNDI> object representing the ROM's UNDI header,
402 if present.
403
404 =cut
405
406 sub undi_header {
407   my $hash = shift;
408   my $self = tied(%$hash);
409
410   my $offset = $hash->{undi_header};
411   return undef unless $offset != 0;
412
413   return Option::ROM::UNDI->new ( $self->{data}, $offset );
414 }
415
416 =pod
417
418 =item C<< ipxe_header () >>
419
420 Return a C<Option::ROM::iPXE> object representing the ROM's iPXE
421 header, if present.
422
423 =cut
424
425 sub ipxe_header {
426   my $hash = shift;
427   my $self = tied(%$hash);
428
429   my $offset = $hash->{ipxe_header};
430   return undef unless $offset != 0;
431
432   return Option::ROM::iPXE->new ( $self->{data}, $offset );
433 }
434
435 =pod
436
437 =item C<< next_image () >>
438
439 Return a C<Option::ROM> object representing the next image within the
440 ROM, if present.
441
442 =cut
443
444 sub next_image {
445   my $hash = shift;
446   my $self = tied(%$hash);
447
448   return $self->{next_image};
449 }
450
451 =pod
452
453 =item C<< checksum () >>
454
455 Calculate the byte checksum of the ROM.
456
457 =cut
458
459 sub checksum {
460   my $hash = shift;
461   my $self = tied(%$hash);
462
463   my $raw = substr ( ${$self->{data}}, 0, ( $hash->{length} * 512 ) );
464   return unpack ( "%8C*", $raw );
465 }
466
467 =pod
468
469 =item C<< fix_checksum () >>
470
471 Fix the byte checksum of the ROM.
472
473 =cut
474
475 sub fix_checksum {
476   my $hash = shift;
477   my $self = tied(%$hash);
478
479   $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
480 }
481
482 ##############################################################################
483 #
484 # Option::ROM::PCI
485 #
486 ##############################################################################
487
488 package Option::ROM::PCI;
489
490 use strict;
491 use warnings;
492 use Carp;
493 use bytes;
494
495 sub new {
496   my $class = shift;
497   my $data = shift;
498   my $offset = shift;
499
500   my $hash = {};
501   tie %$hash, "Option::ROM::Fields", {
502     data => $data,
503     offset => $offset,
504     length => 0x0c,
505     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" },
522     },
523   };
524   bless $hash, $class;
525
526   # Retrieve true length of structure
527   my $self = tied ( %$hash );
528   $self->{length} = $hash->{struct_length};
529
530   return $hash;  
531 }
532
533 ##############################################################################
534 #
535 # Option::ROM::PnP
536 #
537 ##############################################################################
538
539 package Option::ROM::PnP;
540
541 use strict;
542 use warnings;
543 use Carp;
544 use bytes;
545
546 sub new {
547   my $class = shift;
548   my $data = shift;
549   my $offset = shift;
550
551   my $hash = {};
552   tie %$hash, "Option::ROM::Fields", {
553     data => $data,
554     offset => $offset,
555     length => 0x06,
556     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" },
566     },
567   };
568   bless $hash, $class;
569
570   # Retrieve true length of structure
571   my $self = tied ( %$hash );
572   $self->{length} = ( $hash->{struct_length} * 16 );
573
574   return $hash;  
575 }
576
577 sub checksum {
578   my $hash = shift;
579   my $self = tied(%$hash);
580
581   return $self->checksum();
582 }
583
584 sub fix_checksum {
585   my $hash = shift;
586   my $self = tied(%$hash);
587
588   $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
589 }
590
591 sub manufacturer {
592   my $hash = shift;
593   my $self = tied(%$hash);
594
595   my $manufacturer = $hash->{manufacturer};
596   return undef unless $manufacturer;
597
598   my $raw = substr ( ${$self->{data}}, $manufacturer );
599   return unpack ( "Z*", $raw );
600 }
601
602 sub product {
603   my $hash = shift;
604   my $self = tied(%$hash);
605
606   my $product = $hash->{product};
607   return undef unless $product;
608
609   my $raw = substr ( ${$self->{data}}, $product );
610   return unpack ( "Z*", $raw );
611 }
612
613 ##############################################################################
614 #
615 # Option::ROM::UNDI
616 #
617 ##############################################################################
618
619 package Option::ROM::UNDI;
620
621 use strict;
622 use warnings;
623 use Carp;
624 use bytes;
625
626 sub new {
627   my $class = shift;
628   my $data = shift;
629   my $offset = shift;
630
631   my $hash = {};
632   tie %$hash, "Option::ROM::Fields", {
633     data => $data,
634     offset => $offset,
635     length => 0x16,
636     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" },
649     },
650   };
651   bless $hash, $class;
652
653   # Retrieve true length of structure
654   my $self = tied ( %$hash );
655   $self->{length} = $hash->{struct_length};
656
657   return $hash;
658 }
659
660 sub checksum {
661   my $hash = shift;
662   my $self = tied(%$hash);
663
664   return $self->checksum();
665 }
666
667 sub fix_checksum {
668   my $hash = shift;
669   my $self = tied(%$hash);
670
671   $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
672 }
673
674 ##############################################################################
675 #
676 # Option::ROM::iPXE
677 #
678 ##############################################################################
679
680 package Option::ROM::iPXE;
681
682 use strict;
683 use warnings;
684 use Carp;
685 use bytes;
686
687 sub new {
688   my $class = shift;
689   my $data = shift;
690   my $offset = shift;
691
692   my $hash = {};
693   tie %$hash, "Option::ROM::Fields", {
694     data => $data,
695     offset => $offset,
696     length => 0x06,
697     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" },
703     },
704   };
705   bless $hash, $class;
706
707   # Retrieve true length of structure
708   my $self = tied ( %$hash );
709   $self->{length} = $hash->{struct_length};
710
711   return $hash;
712 }
713
714 sub checksum {
715   my $hash = shift;
716   my $self = tied(%$hash);
717
718   return $self->checksum();
719 }
720
721 sub fix_checksum {
722   my $hash = shift;
723   my $self = tied(%$hash);
724
725   $hash->{checksum} = ( ( $hash->{checksum} - $hash->checksum() ) & 0xff );
726 }
727
728 1;