1784add5524d39c2f1d64157a13ee53b4898bcd3
[people/mcb30/gpxe.git] / src / util / efirom.c
1 /*
2  * Copyright (C) 2009 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  */
18
19 #include <stdint.h>
20 #include <stddef.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <assert.h>
27 #include <getopt.h>
28 #include <gpxe/efi/efi.h>
29 #include <gpxe/efi/IndustryStandard/PeImage.h>
30 #include <gpxe/efi/IndustryStandard/Pci22.h>
31
32 #define eprintf(...) fprintf ( stderr, __VA_ARGS__ )
33
34 /** Command-line options */
35 struct options {
36         uint16_t vendor;
37         uint16_t device;
38 };
39
40 /**
41  * Allocate memory
42  *
43  * @v len               Length of memory to allocate
44  * @ret ptr             Pointer to allocated memory
45  */
46 static void * xmalloc ( size_t len ) {
47         void *ptr;
48
49         ptr = malloc ( len );
50         if ( ! ptr ) {
51                 eprintf ( "Could not allocate %zd bytes\n", len );
52                 exit ( 1 );
53         }
54
55         return ptr;
56 }
57
58 /**
59  * Get file size
60  *
61  * @v file              File
62  * @v len               File size
63  */
64 static size_t file_size ( FILE *file ) {
65         ssize_t len;
66
67         if ( fseek ( file, 0, SEEK_END ) != 0 ) {
68                 eprintf ( "Could not seek: %s\n", strerror ( errno ) );
69                 exit ( 1 );
70         }
71         len = ftell ( file );
72         if ( len < 0 ) {
73                 eprintf ( "Could not determine file size: %s\n",
74                           strerror ( errno ) );
75                 exit ( 1 );
76         }
77         return len;
78 }
79
80 /**
81  * Copy file
82  *
83  * @v in                Input file
84  * @v out               Output file
85  * @v len               Length to copy
86  */
87 static void file_copy ( FILE *in, FILE *out, size_t len ) {
88         char buf[4096];
89         size_t frag_len;
90
91         while ( len ) {
92                 frag_len = len;
93                 if ( frag_len > sizeof ( buf ) )
94                         frag_len = sizeof ( buf );
95                 if ( fread ( buf, frag_len, 1, in ) != 1 ) {
96                         eprintf ( "Could not read: %s\n",
97                                   strerror ( errno ) );
98                         exit ( 1 );
99                 }
100                 if ( fwrite ( buf, frag_len, 1, out ) != 1 ) {
101                         eprintf ( "Could not write: %s\n",
102                                   strerror ( errno ) );
103                         exit ( 1 );
104                 }
105                 len -= frag_len;
106         }
107 }
108
109 /**
110  * Read information from PE headers
111  *
112  * @v pe                PE file
113  * @ret machine         Machine type
114  * @ret subsystem       EFI subsystem
115  */
116 static void read_pe_info ( FILE *pe, uint16_t *machine,
117                            uint16_t *subsystem ) {
118         EFI_IMAGE_DOS_HEADER dos;
119         union {
120                 EFI_IMAGE_NT_HEADERS32 nt32;
121                 EFI_IMAGE_NT_HEADERS64 nt64;
122         } nt;
123
124         /* Read DOS header */
125         if ( fseek ( pe, 0, SEEK_SET ) != 0 ) {
126                 eprintf ( "Could not seek: %s\n", strerror ( errno ) );
127                 exit ( 1 );
128         }
129         if ( fread ( &dos, sizeof ( dos ), 1, pe ) != 1 ) {
130                 eprintf ( "Could not read: %s\n", strerror ( errno ) );
131                 exit ( 1 );
132         }
133
134         /* Read NT header */
135         if ( fseek ( pe, dos.e_lfanew, SEEK_SET ) != 0 ) {
136                 eprintf ( "Could not seek: %s\n", strerror ( errno ) );
137                 exit ( 1 );
138         }
139         if ( fread ( &nt, sizeof ( nt ), 1, pe ) != 1 ) {
140                 eprintf ( "Could not read: %s\n", strerror ( errno ) );
141                 exit ( 1 );
142         }
143
144         /* Locate NT header */
145         *machine = nt.nt32.FileHeader.Machine;
146         switch ( *machine ) {
147         case EFI_IMAGE_MACHINE_IA32:
148                 *subsystem = nt.nt32.OptionalHeader.Subsystem;
149                 break;
150         case EFI_IMAGE_MACHINE_X64:
151                 *subsystem = nt.nt64.OptionalHeader.Subsystem;
152                 break;
153         default:
154                 eprintf ( "Unrecognised machine type %04x\n", *machine );
155                 exit ( 1 );
156         }
157 }
158
159 /**
160  * Convert EFI image to ROM image
161  *
162  * @v pe                EFI file
163  * @v rom               ROM file
164  */
165 static void make_efi_rom ( FILE *pe, FILE *rom, struct options *opts ) {
166         struct {
167                 EFI_PCI_EXPANSION_ROM_HEADER rom;
168                 PCI_DATA_STRUCTURE pci __attribute__ (( aligned ( 4 ) ));
169         } headers;
170         size_t pe_size;
171         size_t rom_size;
172         unsigned int rom_size_sectors;
173
174         /* Determine output file size */
175         pe_size = file_size ( pe );
176         rom_size = ( pe_size + sizeof ( headers ) );
177         rom_size_sectors = ( ( rom_size + 511 ) / 512 );
178
179         /* Construct ROM header */
180         memset ( &headers, 0, sizeof ( headers ) );
181         headers.rom.Signature = PCI_EXPANSION_ROM_HEADER_SIGNATURE;
182         headers.rom.InitializationSize = rom_size_sectors;
183         headers.rom.EfiSignature = EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE;
184         read_pe_info ( pe, &headers.rom.EfiMachineType,
185                        &headers.rom.EfiSubsystem );
186         headers.rom.EfiImageHeaderOffset = sizeof ( headers );
187         headers.rom.PcirOffset =
188                 offsetof ( typeof ( headers ), pci );
189         headers.pci.Signature = PCI_DATA_STRUCTURE_SIGNATURE;
190         headers.pci.VendorId = opts->vendor;
191         headers.pci.DeviceId = opts->device;
192         headers.pci.Length = sizeof ( headers.pci );
193         headers.pci.ClassCode[0] = PCI_CLASS_NETWORK;
194         headers.pci.ImageLength = rom_size_sectors;
195         headers.pci.CodeType = 0x03; /* No constant in EFI headers? */
196         headers.pci.Indicator = 0x80; /* No constant in EFI headers? */
197
198         /* Write out ROM header */
199         if ( fwrite ( &headers, sizeof ( headers ), 1, rom ) != 1 ) {
200                 eprintf ( "Could not write headers: %s\n",
201                           strerror ( errno ) );
202                 exit ( 1 );
203         }
204
205         /* Write out payload */
206         if ( fseek ( pe, 0, SEEK_SET ) != 0 ) {
207                 eprintf ( "Could not seek: %s\n", strerror ( errno ) );
208                 exit ( 1 );
209         }
210         file_copy ( pe, rom, pe_size );
211
212         /* Round up to 512-byte boundary */
213         if ( ftruncate ( fileno ( rom ), ( rom_size_sectors * 512 ) ) != 0 ) {
214                 eprintf ( "Could not set length: %s\n", strerror ( errno ) );
215                 exit ( 1 );
216         }
217 }
218
219 /**
220  * Print help
221  *
222  * @v program_name      Program name
223  */
224 static void print_help ( const char *program_name ) {
225         eprintf ( "Syntax: %s [--vendor=VVVV] [--device=DDDD] "
226                   "infile outfile\n", program_name );
227 }
228
229 /**
230  * Parse command-line options
231  *
232  * @v argc              Argument count
233  * @v argv              Argument list
234  * @v opts              Options structure to populate
235  */
236 static int parse_options ( const int argc, char **argv,
237                            struct options *opts ) {
238         char *end;
239         int c;
240
241         while (1) {
242                 int option_index = 0;
243                 static struct option long_options[] = {
244                         { "vendor", required_argument, NULL, 'v' },
245                         { "device", required_argument, NULL, 'd' },
246                         { "help", 0, NULL, 'h' },
247                         { 0, 0, 0, 0 }
248                 };
249
250                 if ( ( c = getopt_long ( argc, argv, "v:d:h",
251                                          long_options,
252                                          &option_index ) ) == -1 ) {
253                         break;
254                 }
255
256                 switch ( c ) {
257                 case 'v':
258                         opts->vendor = strtoul ( optarg, &end, 16 );
259                         if ( *end ) {
260                                 eprintf ( "Invalid vendor \"%s\"\n", optarg );
261                                 exit ( 2 );
262                         }
263                         break;
264                 case 'd':
265                         opts->device = strtoul ( optarg, &end, 16 );
266                         if ( *end ) {
267                                 eprintf ( "Invalid device \"%s\"\n", optarg );
268                                 exit ( 2 );
269                         }
270                         break;
271                 case 'h':
272                         print_help ( argv[0] );
273                         exit ( 0 );
274                 case '?':
275                 default:
276                         exit ( 2 );
277                 }
278         }
279         return optind;
280 }
281
282 int main ( int argc, char **argv ) {
283         struct options opts = {
284         };
285         unsigned int infile_index;
286         const char *infile_name;
287         const char *outfile_name;
288         FILE *infile;
289         FILE *outfile;
290
291         /* Parse command-line arguments */
292         infile_index = parse_options ( argc, argv, &opts );
293         if ( argc != ( infile_index + 2 ) ) {
294                 print_help ( argv[0] );
295                 exit ( 2 );
296         }
297         infile_name = argv[infile_index];
298         outfile_name = argv[infile_index + 1];
299
300         /* Open input and output files */
301         infile = fopen ( infile_name, "r" );
302         if ( ! infile ) {
303                 eprintf ( "Could not open %s for reading: %s\n",
304                           infile_name, strerror ( errno ) );
305                 exit ( 1 );
306         }
307         outfile = fopen ( outfile_name, "w" );
308         if ( ! outfile ) {
309                 eprintf ( "Could not open %s for writing: %s\n",
310                           outfile_name, strerror ( errno ) );
311                 exit ( 1 );
312         }
313
314         /* Convert file */
315         make_efi_rom ( infile, outfile, &opts );
316
317         fclose ( outfile );
318         fclose ( infile );
319
320         return 0;
321 }