]> git.proxmox.com Git - mirror_edk2.git/blob - EdkCompatibilityPkg/Sample/Tools/Source/EfiRom/EfiRom.c
Add in the 1st version of ECP.
[mirror_edk2.git] / EdkCompatibilityPkg / Sample / Tools / Source / EfiRom / EfiRom.c
1 /*++
2
3 Copyright (c) 2007, Intel Corporation
4 All rights reserved. This program and the accompanying materials
5 are licensed and made available under the terms and conditions of the BSD License
6 which accompanies this distribution. The full text of the license may be found at
7 http://opensource.org/licenses/bsd-license.php
8
9 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
10 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
11
12
13 Module Name:
14
15 EfiRom.c
16
17 Abstract:
18
19 Utility program to create an EFI option ROM image from binary and
20 EFI PE32 files.
21
22
23 --*/
24
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28
29 //
30 // Includes for EFI 1.1 build
31 //
32 // #include "Tiano.h" // required defines for Compress.h
33 // #include "EfiImage.h" // for PE32 structure definitions
34 // #include "Compress.h" // for compression function
35 // Includes for Tiano build
36 //
37 #include "TianoCommon.h"
38 #include "EfiImage.h" // for PE32 structure definitions
39 #include "Compress.h"
40
41 //
42 // END include differences
43 //
44 #include "Pci22.h" // for option ROM header structures
45 //
46 // Version of this utility
47 //
48 #define UTILITY_VERSION "v2.5"
49
50 //
51 // Define some status return values
52 //
53 #define STATUS_SUCCESS 0
54 #define STATUS_WARNING 1
55 #define STATUS_ERROR 2
56
57 //
58 // Define the max length of a filename
59 //
60 #define MAX_PATH 200
61
62 #define DEFAULT_OUTPUT_EXTENSION ".rom"
63
64 //
65 // Max size for an option ROM image
66 //
67 #define MAX_OPTION_ROM_SIZE (1024 * 1024 * 16) // 16MB
68 //
69 // Values for the indicator field in the PCI data structure
70 //
71 #define INDICATOR_LAST 0x80 // last file in series of files
72 //
73 // Masks for the FILE_LIST.FileFlags field
74 //
75 #define FILE_FLAG_BINARY 0x01
76 #define FILE_FLAG_EFI 0x02
77 #define FILE_FLAG_COMPRESS 0x04
78
79 //
80 // Use this linked list structure to keep track of all the filenames
81 // specified on the command line.
82 //
83 typedef struct _FILE_LIST {
84 struct _FILE_LIST *Next;
85 INT8 *FileName;
86 UINT32 FileFlags;
87 UINT32 ClassCode;
88 UINT16 CodeRevision;
89 } FILE_LIST;
90
91 //
92 // Use this to track our command-line options
93 //
94 typedef struct {
95 INT8 OutFileName[MAX_PATH];
96 INT8 NoLast;
97 INT8 Verbose;
98 INT8 DumpOption;
99 UINT8 DevIdValid;
100 UINT8 VendIdValid;
101 UINT16 VendId;
102 UINT16 DevId;
103 FILE_LIST *FileList;
104 } OPTIONS;
105
106 //
107 // Make a global structure to keep track of command-line options
108 //
109 static OPTIONS mOptions;
110
111 //
112 // Use these to convert from machine type value to a named type
113 //
114 typedef struct {
115 UINT16 Value;
116 char *Name;
117 } STRING_LOOKUP;
118
119 static STRING_LOOKUP mMachineTypes[] = {
120 EFI_IMAGE_MACHINE_IA32,
121 "IA32",
122 EFI_IMAGE_MACHINE_IA64,
123 "IA64",
124 EFI_IMAGE_MACHINE_EBC,
125 "EBC",
126 0,
127 NULL
128 };
129
130 static STRING_LOOKUP mSubsystemTypes[] = {
131 EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION,
132 "EFI application",
133 EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER,
134 "EFI boot service driver",
135 EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER,
136 "EFI runtime driver",
137 0,
138 NULL
139 };
140 //
141 // Function prototypes
142 //
143 static
144 void
145 Usage (
146 VOID
147 );
148
149 static
150 int
151 ParseCommandLine (
152 int Argc,
153 char *Argv[],
154 OPTIONS *Options
155 );
156
157 static
158 int
159 CheckPE32File (
160 FILE *Fptr,
161 UINT16 *MachineType,
162 UINT16 *SubSystem
163 );
164
165 static
166 int
167 ProcessEfiFile (
168 FILE *OutFptr,
169 FILE_LIST *InFile,
170 UINT16 VendId,
171 UINT16 DevId,
172 UINT32 *Size
173 );
174
175 static
176 int
177 ProcessBinFile (
178 FILE *OutFptr,
179 FILE_LIST *InFile,
180 UINT32 *Size
181 );
182
183 static
184 void
185 DumpImage (
186 FILE_LIST *InFile
187 );
188
189 char *
190 GetMachineTypeStr (
191 UINT16 MachineType
192 );
193
194 static
195 char *
196 GetSubsystemTypeStr (
197 UINT16 SubsystemType
198 );
199
200 int
201 main (
202 int Argc,
203 char *Argv[]
204 )
205 /*++
206
207 Routine Description:
208
209 Given an EFI image filename, create a ROM-able image by creating an option
210 ROM header and PCI data structure, filling them in, and then writing the
211 option ROM header + PCI data structure + EFI image out to the output file.
212
213 Arguments:
214
215 Argc - standard C main() argument count
216
217 Argv - standard C main() argument list
218
219 Returns:
220
221 0 success
222 non-zero otherwise
223
224 --*/
225 // GC_TODO: ] - add argument and description to function comment
226 {
227 INT8 *Ext;
228 FILE *FptrOut;
229 UINT32 Status;
230 FILE_LIST *FList;
231 UINT32 TotalSize;
232 UINT32 Size;
233
234 Status = STATUS_SUCCESS;
235 FptrOut = NULL;
236
237 //
238 // Parse the command line arguments
239 //
240 if (ParseCommandLine (Argc, Argv, &mOptions)) {
241 return STATUS_ERROR;
242 }
243 //
244 // If dumping an image, then do that and quit
245 //
246 if (mOptions.DumpOption) {
247 DumpImage (mOptions.FileList);
248 goto BailOut;
249 }
250 //
251 // Determine the output filename. Either what they specified on
252 // the command line, or the first input filename with a different extension.
253 //
254 if (!mOptions.OutFileName[0]) {
255 strcpy (mOptions.OutFileName, mOptions.FileList->FileName);
256 //
257 // Find the last . on the line and replace the filename extension with
258 // the default
259 //
260 for (Ext = mOptions.OutFileName + strlen (mOptions.OutFileName) - 1;
261 (Ext >= mOptions.OutFileName) && (*Ext != '.') && (*Ext != '\\');
262 Ext--
263 )
264 ;
265 //
266 // If dot here, then insert extension here, otherwise append
267 //
268 if (*Ext != '.') {
269 Ext = mOptions.OutFileName + strlen (mOptions.OutFileName);
270 }
271
272 strcpy (Ext, DEFAULT_OUTPUT_EXTENSION);
273 }
274 //
275 // Make sure we don't have the same filename for input and output files
276 //
277 for (FList = mOptions.FileList; FList != NULL; FList = FList->Next) {
278 if (_stricmp (mOptions.OutFileName, FList->FileName) == 0) {
279 Status = STATUS_ERROR;
280 fprintf (
281 stdout,
282 "ERROR: Input and output file names must be different - %s = %s\n",
283 FList->FileName,
284 mOptions.OutFileName
285 );
286 goto BailOut;
287 }
288 }
289 //
290 // Now open our output file
291 //
292 if ((FptrOut = fopen (mOptions.OutFileName, "w+b")) == NULL) {
293 fprintf (stdout, "ERROR: Failed to open output file %s\n", mOptions.OutFileName);
294 goto BailOut;
295 }
296 //
297 // Process all our files
298 //
299 TotalSize = 0;
300 for (FList = mOptions.FileList; FList != NULL; FList = FList->Next) {
301 Size = 0;
302 if (FList->FileFlags & FILE_FLAG_EFI) {
303 if (mOptions.Verbose) {
304 fprintf (stdout, "Processing EFI file %s\n", FList->FileName);
305 }
306
307 Status = ProcessEfiFile (FptrOut, FList, mOptions.VendId, mOptions.DevId, &Size);
308 } else if (FList->FileFlags & FILE_FLAG_BINARY) {
309 if (mOptions.Verbose) {
310 fprintf (stdout, "Processing binary file %s\n", FList->FileName);
311 }
312
313 Status = ProcessBinFile (FptrOut, FList, &Size);
314 } else {
315 fprintf (stdout, "ERROR: File not specified as EFI or binary: %s\n", FList->FileName);
316 Status = STATUS_ERROR;
317 }
318
319 if (mOptions.Verbose) {
320 fprintf (stdout, " Output size = 0x%X\n", Size);
321 }
322
323 if (Status != STATUS_SUCCESS) {
324 break;
325 }
326
327 TotalSize += Size;
328 }
329 //
330 // Check total size
331 //
332 if (TotalSize > MAX_OPTION_ROM_SIZE) {
333 fprintf (
334 stdout,
335 "ERROR: Option ROM image size exceeds limit 0x%X bytes\n",
336 MAX_OPTION_ROM_SIZE
337 );
338 Status = STATUS_ERROR;
339 }
340
341 BailOut:
342 if (FptrOut != NULL) {
343 fclose (FptrOut);
344 }
345 //
346 // Clean up our file list
347 //
348 while (mOptions.FileList != NULL) {
349 FList = mOptions.FileList->Next;
350 free (mOptions.FileList);
351 mOptions.FileList = FList;
352 }
353
354 return Status;
355 }
356
357 static
358 int
359 ProcessBinFile (
360 FILE *OutFptr,
361 FILE_LIST *InFile,
362 UINT32 *Size
363 )
364 /*++
365
366 Routine Description:
367
368 Process a binary input file.
369
370 Arguments:
371
372 OutFptr - file pointer to output binary ROM image file we're creating
373 InFile - structure contains information on the binary file to process
374 Size - pointer to where to return the size added to the output file
375
376 Returns:
377
378 0 - successful
379
380 --*/
381 {
382 FILE *InFptr;
383 UINT32 TotalSize;
384 UINT32 FileSize;
385 UINT8 *Buffer;
386 UINT32 Status;
387 PCI_EXPANSION_ROM_HEADER *RomHdr;
388 PCI_DATA_STRUCTURE *PciDs;
389 UINT32 Index;
390 UINT8 ByteCheckSum;
391
392 Status = STATUS_SUCCESS;
393
394 //
395 // Try to open the input file
396 //
397 if ((InFptr = fopen (InFile->FileName, "rb")) == NULL) {
398 fprintf (stdout, "ERROR: Failed to open input file %s\n", InFile->FileName);
399 return STATUS_ERROR;
400 }
401 //
402 // Seek to the end of the input file and get the file size. Then allocate
403 // a buffer to read it in to.
404 //
405 fseek (InFptr, 0, SEEK_END);
406 FileSize = ftell (InFptr);
407 if (mOptions.Verbose) {
408 fprintf (stdout, " File size = 0x%X\n", FileSize);
409 }
410
411 fseek (InFptr, 0, SEEK_SET);
412 Buffer = (INT8 *) malloc (FileSize);
413 if (Buffer == NULL) {
414 fprintf (stdout, "ERROR: Memory allocation failed\n");
415 Status = STATUS_ERROR;
416 goto BailOut;
417 }
418
419 if (fread (Buffer, FileSize, 1, InFptr) != 1) {
420 fprintf (stdout, "ERROR: Failed to read all bytes from input file\n");
421 Status = STATUS_ERROR;
422 goto BailOut;
423 }
424 //
425 // Total size must be an even multiple of 512 bytes, and can't exceed
426 // the option ROM image size.
427 //
428 TotalSize = FileSize;
429 if (TotalSize & 0x1FF) {
430 TotalSize = (TotalSize + 0x200) &~0x1ff;
431 }
432
433 if (TotalSize > MAX_OPTION_ROM_SIZE) {
434 fprintf (
435 stdout,
436 "ERROR: Option ROM image %s size exceeds limit 0x%X bytes\n",
437 InFile->FileName,
438 MAX_OPTION_ROM_SIZE
439 );
440 Status = STATUS_ERROR;
441 goto BailOut;
442 }
443 //
444 // Return the size to the caller so they can keep track of the running total.
445 //
446 *Size = TotalSize;
447
448 //
449 // Crude check to make sure it's a legitimate ROM image
450 //
451 RomHdr = (PCI_EXPANSION_ROM_HEADER *) Buffer;
452 if (RomHdr->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
453 fprintf (stdout, "ERROR: ROM image file has invalid ROM signature\n");
454 Status = STATUS_ERROR;
455 goto BailOut;
456 }
457 //
458 // Make sure the pointer to the PCI data structure is within the size of the image.
459 // Then check it for valid signature.
460 //
461 if ((RomHdr->PcirOffset > FileSize) || (RomHdr->PcirOffset == 0)) {
462 fprintf (stdout, "ERROR: Invalid PCI data structure offset\n");
463 Status = STATUS_ERROR;
464 goto BailOut;
465 }
466
467 PciDs = (PCI_DATA_STRUCTURE *) (Buffer + RomHdr->PcirOffset);
468 if (PciDs->Signature != PCI_DATA_STRUCTURE_SIGNATURE) {
469 fprintf (stdout, "ERROR: PCI data structure has invalid signature\n");
470 Status = STATUS_ERROR;
471 goto BailOut;
472 }
473 //
474 // If this is the last image, then set the LAST bit unless requested not
475 // to via the command-line -l argument. Otherwise, make sure you clear it.
476 //
477 if ((InFile->Next == NULL) && (mOptions.NoLast == 0)) {
478 PciDs->Indicator = INDICATOR_LAST;
479 } else {
480 PciDs->Indicator = 0;
481 }
482
483 ByteCheckSum = 0;
484 for (Index = 0; Index < FileSize - 1; Index++) {
485 ByteCheckSum = (UINT8) (ByteCheckSum + Buffer[Index]);
486 }
487
488 Buffer[FileSize - 1] = (UINT8) ((~ByteCheckSum) + 1);
489 fprintf (stdout, "CheckSUm = %02x\n", (UINT32) Buffer[FileSize - 1]);
490
491 //
492 // Now copy the input file contents out to the output file
493 //
494 if (fwrite (Buffer, FileSize, 1, OutFptr) != 1) {
495 fprintf (stdout, "ERROR: Failed to write all file bytes to output file\n");
496 Status = STATUS_ERROR;
497 goto BailOut;
498 }
499
500 TotalSize -= FileSize;
501 //
502 // Pad the rest of the image to make it a multiple of 512 bytes
503 //
504 while (TotalSize > 0) {
505 putc (~0, OutFptr);
506 TotalSize--;
507 }
508
509 BailOut:
510 if (InFptr != NULL) {
511 fclose (InFptr);
512 }
513
514 if (Buffer != NULL) {
515 free (Buffer);
516 }
517 //
518 // Print the file name if errors occurred
519 //
520 if (Status != STATUS_SUCCESS) {
521 fprintf (stdout, "Error processing binary file %s\n", InFile->FileName);
522 }
523
524 return Status;
525 }
526
527 static
528 int
529 ProcessEfiFile (
530 FILE *OutFptr,
531 FILE_LIST *InFile,
532 UINT16 VendId,
533 UINT16 DevId,
534 UINT32 *Size
535 )
536 /*++
537
538 Routine Description:
539
540 Process a PE32 EFI file.
541
542 Arguments:
543
544 OutFptr - file pointer to output binary ROM image file we're creating
545 InFile - structure contains information on the PE32 file to process
546 VendId - vendor ID as required in the option ROM header
547 DevId - device ID as required in the option ROM header
548 Size - pointer to where to return the size added to the output file
549
550 Returns:
551
552 0 - successful
553
554 --*/
555 {
556 UINT32 Status;
557 FILE *InFptr;
558 EFI_PCI_EXPANSION_ROM_HEADER RomHdr;
559 PCI_DATA_STRUCTURE PciDs;
560 UINT32 FileSize;
561 UINT32 CompressedFileSize;
562 UINT8 *Buffer;
563 UINT8 *CompressedBuffer;
564 UINT8 *TempBufferPtr;
565 UINT32 TotalSize;
566 UINT32 HeaderSize;
567 UINT16 MachineType;
568 UINT16 SubSystem;
569 UINT32 HeaderPadBytes;
570
571 //
572 // Try to open the input file
573 //
574 if ((InFptr = fopen (InFile->FileName, "rb")) == NULL) {
575 fprintf (stdout, "ERROR: Failed to open input file %s\n", InFile->FileName);
576 return STATUS_ERROR;
577 }
578 //
579 // Initialize our buffer pointers to null.
580 //
581 Buffer = NULL;
582 CompressedBuffer = NULL;
583
584 //
585 // Double-check the file to make sure it's what we expect it to be
586 //
587 Status = CheckPE32File (InFptr, &MachineType, &SubSystem);
588 if (Status != STATUS_SUCCESS) {
589 goto BailOut;
590 }
591 //
592 // Seek to the end of the input file and get the file size
593 //
594 fseek (InFptr, 0, SEEK_END);
595 FileSize = ftell (InFptr);
596
597 //
598 // Get the size of the headers we're going to put in front of the image. The
599 // EFI header must be aligned on a 4-byte boundary, so pad accordingly.
600 //
601 if (sizeof (RomHdr) & 0x03) {
602 HeaderPadBytes = 4 - (sizeof (RomHdr) & 0x03);
603 } else {
604 HeaderPadBytes = 0;
605 }
606
607 HeaderSize = sizeof (PCI_DATA_STRUCTURE) + HeaderPadBytes + sizeof (EFI_PCI_EXPANSION_ROM_HEADER);
608 if (mOptions.Verbose) {
609 fprintf (stdout, " File size = 0x%X\n", FileSize);
610 }
611 //
612 // Allocate memory for the entire file (in case we have to compress), then
613 // seek back to the beginning of the file and read it into our buffer.
614 //
615 Buffer = (INT8 *) malloc (FileSize);
616 if (Buffer == NULL) {
617 fprintf (stdout, "ERROR: Memory allocation failed\n");
618 Status = STATUS_ERROR;
619 goto BailOut;
620 }
621
622 fseek (InFptr, 0, SEEK_SET);
623 if (fread (Buffer, FileSize, 1, InFptr) != 1) {
624 fprintf (stdout, "ERROR: Failed to read all bytes from input file\n");
625 Status = STATUS_ERROR;
626 goto BailOut;
627 }
628 //
629 // Now determine the size of the final output file. It's either the header size
630 // plus the file's size, or the header size plus the compressed file size.
631 //
632 if (InFile->FileFlags & FILE_FLAG_COMPRESS) {
633 //
634 // Allocate a buffer into which we can compress the image, compress it,
635 // and use that size as the new size.
636 //
637 CompressedBuffer = (INT8 *) malloc (FileSize);
638 if (CompressedBuffer == NULL) {
639 fprintf (stdout, "ERROR: Memory allocation failed\n");
640 Status = STATUS_ERROR;
641 goto BailOut;
642 }
643
644 CompressedFileSize = FileSize;
645 Status = EfiCompress (Buffer, FileSize, CompressedBuffer, &CompressedFileSize);
646 if (Status != STATUS_SUCCESS) {
647 fprintf (stdout, "ERROR: Compression failed\n");
648 goto BailOut;
649 }
650 //
651 // Now compute the size, then swap buffer pointers.
652 //
653 if (mOptions.Verbose) {
654 fprintf (stdout, " Comp size = 0x%X\n", CompressedFileSize);
655 }
656
657 TotalSize = CompressedFileSize + HeaderSize;
658 FileSize = CompressedFileSize;
659 TempBufferPtr = Buffer;
660 Buffer = CompressedBuffer;
661 CompressedBuffer = TempBufferPtr;
662 } else {
663 TotalSize = FileSize + HeaderSize;
664 }
665 //
666 // Total size must be an even multiple of 512 bytes
667 //
668 if (TotalSize & 0x1FF) {
669 TotalSize = (TotalSize + 0x200) &~0x1ff;
670 }
671 //
672 // Check size
673 //
674 if (TotalSize > MAX_OPTION_ROM_SIZE) {
675 fprintf (
676 stdout,
677 "ERROR: Option ROM image %s size exceeds limit 0x%X bytes\n",
678 InFile->FileName,
679 MAX_OPTION_ROM_SIZE
680 );
681 Status = STATUS_ERROR;
682 goto BailOut;
683 }
684 //
685 // Return the size to the caller so they can keep track of the running total.
686 //
687 *Size = TotalSize;
688
689 //
690 // Now fill in the ROM header. These values come from chapter 18 of the
691 // EFI 1.02 specification.
692 //
693 memset (&RomHdr, 0, sizeof (RomHdr));
694 RomHdr.Signature = PCI_EXPANSION_ROM_HEADER_SIGNATURE;
695 RomHdr.InitializationSize = (UINT16) (TotalSize / 512);
696 RomHdr.EfiSignature = EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE;
697 RomHdr.EfiSubsystem = SubSystem;
698 RomHdr.EfiMachineType = MachineType;
699 RomHdr.EfiImageHeaderOffset = (UINT16) HeaderSize;
700 RomHdr.PcirOffset = (UINT16) (sizeof (RomHdr) + HeaderPadBytes);
701 //
702 // Set image as compressed or not
703 //
704 if (InFile->FileFlags & FILE_FLAG_COMPRESS) {
705 RomHdr.CompressionType = EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED;
706 }
707 //
708 // Fill in the PCI data structure
709 //
710 memset (&PciDs, 0, sizeof (PCI_DATA_STRUCTURE));
711
712 PciDs.Signature = PCI_DATA_STRUCTURE_SIGNATURE;
713 PciDs.VendorId = VendId;
714 PciDs.DeviceId = DevId;
715 PciDs.Length = (UINT16) sizeof (PCI_DATA_STRUCTURE);
716 PciDs.Revision = 0;
717 //
718 // Class code and code revision from the command line (optional)
719 //
720 PciDs.ClassCode[0] = (UINT8) InFile->ClassCode;
721 PciDs.ClassCode[1] = (UINT8) (InFile->ClassCode >> 8);
722 PciDs.ClassCode[2] = (UINT8) (InFile->ClassCode >> 16);
723 PciDs.ImageLength = RomHdr.InitializationSize;
724 PciDs.CodeRevision = InFile->CodeRevision;
725 PciDs.CodeType = PCI_CODE_TYPE_EFI_IMAGE;
726
727 //
728 // If this is the last image, then set the LAST bit unless requested not
729 // to via the command-line -l argument.
730 //
731 if ((InFile->Next == NULL) && (mOptions.NoLast == 0)) {
732 PciDs.Indicator = INDICATOR_LAST;
733 }
734 //
735 // Write the ROM header to the output file
736 //
737 if (fwrite (&RomHdr, sizeof (RomHdr), 1, OutFptr) != 1) {
738 fprintf (stdout, "ERROR: Failed to write ROM header to output file\n");
739 Status = STATUS_ERROR;
740 goto BailOut;
741 }
742
743 //
744 // Write pad bytes to align the PciDs
745 //
746 while (HeaderPadBytes > 0) {
747 if (putc (0, OutFptr) == EOF) {
748 fprintf (stdout, "ERROR: Failed to write ROM header pad bytes to output file\n");
749 Status = STATUS_ERROR;
750 goto BailOut;
751 }
752
753 HeaderPadBytes--;
754 }
755 //
756 // Write the PCI data structure header to the output file
757 //
758 if (fwrite (&PciDs, sizeof (PciDs), 1, OutFptr) != 1) {
759 fprintf (stdout, "ERROR: Failed to write PCI ROM header to output file\n");
760 Status = STATUS_ERROR;
761 goto BailOut;
762 }
763 //
764 // Keep track of how many bytes left to write
765 //
766 TotalSize -= HeaderSize;
767
768 //
769 // Now dump the input file's contents to the output file
770 //
771 if (fwrite (Buffer, FileSize, 1, OutFptr) != 1) {
772 fprintf (stdout, "ERROR: Failed to write all file bytes to output file\n");
773 Status = STATUS_ERROR;
774 goto BailOut;
775 }
776
777 TotalSize -= FileSize;
778 //
779 // Pad the rest of the image to make it a multiple of 512 bytes
780 //
781 while (TotalSize > 0) {
782 if (putc (~0, OutFptr) == EOF) {
783 fprintf (stdout, "ERROR: Failed to write trailing pad bytes output file\n");
784 Status = STATUS_ERROR;
785 goto BailOut;
786 }
787
788 TotalSize--;
789 }
790
791 BailOut:
792 if (InFptr != NULL) {
793 fclose (InFptr);
794 }
795
796 //
797 // Free up our buffers
798 //
799 if (Buffer != NULL) {
800 free (Buffer);
801 }
802
803 if (CompressedBuffer != NULL) {
804 free (CompressedBuffer);
805 }
806 //
807 // Print the file name if errors occurred
808 //
809 if (Status != STATUS_SUCCESS) {
810 fprintf (stdout, "Error processing EFI file %s\n", InFile->FileName);
811 }
812
813 return Status;
814 }
815
816 static
817 int
818 CheckPE32File (
819 FILE *Fptr,
820 UINT16 *MachineType,
821 UINT16 *SubSystem
822 )
823 /*++
824
825 Routine Description:
826
827 GC_TODO: Add function description
828
829 Arguments:
830
831 Fptr - GC_TODO: add argument description
832 MachineType - GC_TODO: add argument description
833 SubSystem - GC_TODO: add argument description
834
835 Returns:
836
837 GC_TODO: add return values
838
839 --*/
840 {
841 /*++
842
843 Routine Description:
844
845 Given a file pointer to a supposed PE32 image file, verify that it is indeed a
846 PE32 image file, and then return the machine type in the supplied pointer.
847
848 Arguments:
849
850 Fptr File pointer to the already-opened PE32 file
851 MachineType Location to stuff the machine type of the PE32 file. This is needed
852 because the image may be Itanium-based, IA32, or EBC.
853
854 Returns:
855
856 0 success
857 non-zero otherwise
858
859 --*/
860 EFI_IMAGE_DOS_HEADER DosHeader;
861 EFI_IMAGE_FILE_HEADER FileHdr;
862 EFI_IMAGE_OPTIONAL_HEADER OptionalHdr;
863 UINT32 PESig;
864
865 //
866 // Position to the start of the file
867 //
868 fseek (Fptr, 0, SEEK_SET);
869
870 //
871 // Read the DOS header
872 //
873 if (fread (&DosHeader, sizeof (DosHeader), 1, Fptr) != 1) {
874 fprintf (stdout, "ERROR: Failed to read the DOS stub from the input file\n");
875 return STATUS_ERROR;
876 }
877 //
878 // Check the magic number (0x5A4D)
879 //
880 if (DosHeader.e_magic != EFI_IMAGE_DOS_SIGNATURE) {
881 fprintf (stdout, "ERROR: Input file does not appear to be a PE32 image (magic number)\n");
882 return STATUS_ERROR;
883 }
884 //
885 // Position into the file and check the PE signature
886 //
887 fseek (Fptr, (long) DosHeader.e_lfanew, SEEK_SET);
888 if (fread (&PESig, sizeof (PESig), 1, Fptr) != 1) {
889 fprintf (stdout, "ERROR: Failed to read PE signature bytes from input file\n");
890 return STATUS_ERROR;
891 }
892 //
893 // Check the PE signature in the header "PE\0\0"
894 //
895 if (PESig != EFI_IMAGE_NT_SIGNATURE) {
896 fprintf (stdout, "ERROR: Input file does not appear to be a PE32 image (signature)\n");
897 return STATUS_ERROR;
898 }
899 //
900 // Read the file header and stuff their MachineType
901 //
902 if (fread (&FileHdr, sizeof (FileHdr), 1, Fptr) != 1) {
903 fprintf (stdout, "ERROR: Failed to read PE file header from input file\n");
904 return STATUS_ERROR;
905 }
906
907 memcpy ((char *) MachineType, &FileHdr.Machine, 2);
908
909 //
910 // Read the optional header so we can get the subsystem
911 //
912 if (fread (&OptionalHdr, sizeof (OptionalHdr), 1, Fptr) != 1) {
913 fprintf (stdout, "ERROR: Failed to read COFF optional header from input file\n");
914 return STATUS_ERROR;
915 }
916
917 *SubSystem = OptionalHdr.Subsystem;
918 if (mOptions.Verbose) {
919 fprintf (stdout, " Got subsystem = 0x%X from image\n", (int) *SubSystem);
920 }
921 //
922 // Good to go
923 //
924 return STATUS_SUCCESS;
925 }
926
927 static
928 int
929 ParseCommandLine (
930 int Argc,
931 char *Argv[],
932 OPTIONS *Options
933 )
934 /*++
935
936 Routine Description:
937
938 Given the Argc/Argv program arguments, and a pointer to an options structure,
939 parse the command-line options and check their validity.
940
941
942 Arguments:
943
944 Argc - standard C main() argument count
945 Argv[] - standard C main() argument list
946 Options - pointer to a structure to store the options in
947
948 Returns:
949
950 STATUS_SUCCESS success
951 non-zero otherwise
952
953 --*/
954 //
955 {
956 FILE_LIST *FileList;
957
958 FILE_LIST *PrevFileList;
959 UINT32 FileFlags;
960 UINT32 ClassCode;
961 UINT32 CodeRevision;
962
963 FileFlags = 0;
964
965 //
966 // Clear out the options
967 //
968 memset ((char *) Options, 0, sizeof (OPTIONS));
969
970 //
971 // To avoid compile warnings
972 //
973 FileList = PrevFileList = NULL;
974
975 ClassCode = 0;
976 CodeRevision = 0;
977 //
978 // Skip over the program name
979 //
980 Argc--;
981 Argv++;
982
983 //
984 // If no arguments, assume they want usage info
985 //
986 if (Argc == 0) {
987 Usage ();
988 return STATUS_ERROR;
989 }
990 //
991 // Process until no more arguments
992 //
993 while (Argc > 0) {
994 if ((Argv[0][0] == '-') || (Argv[0][0] == '/')) {
995 //
996 // To simplify string comparisons, replace slashes with dashes
997 //
998 Argv[0][0] = '-';
999
1000 //
1001 // Vendor ID specified with -v
1002 //
1003 if (_stricmp (Argv[0], "-v") == 0) {
1004 //
1005 // Make sure there's another parameter
1006 //
1007 if (Argc > 1) {
1008 Options->VendId = (UINT16) strtol (Argv[1], NULL, 16);
1009 Options->VendIdValid = 1;
1010 } else {
1011 fprintf (
1012 stdout,
1013 "ERROR: Missing Vendor ID with %s\n\n",
1014 Argv[0]
1015 );
1016 Usage ();
1017 return STATUS_ERROR;
1018 }
1019
1020 Argv++;
1021 Argc--;
1022 } else if (_stricmp (Argv[0], "-d") == 0) {
1023 //
1024 // Device ID specified with -d
1025 // Make sure there's another parameter
1026 //
1027 if (Argc > 1) {
1028 Options->DevId = (UINT16) strtol (Argv[1], NULL, 16);
1029 Options->DevIdValid = 1;
1030 } else {
1031 fprintf (
1032 stdout,
1033 "ERROR: Missing Device ID with %s\n\n",
1034 Argv[0]
1035 );
1036 Usage ();
1037 return STATUS_ERROR;
1038 }
1039
1040 Argv++;
1041 Argc--;
1042 } else if (_stricmp (Argv[0], "-o") == 0) {
1043 //
1044 // Output filename specified with -o
1045 // Make sure there's another parameter
1046 //
1047 if (Argc > 1) {
1048 strcpy (Options->OutFileName, Argv[1]);
1049 } else {
1050 fprintf (
1051 stdout,
1052 "ERROR: Missing output file name with %s\n\n",
1053 Argv[0]
1054 );
1055 Usage ();
1056 return STATUS_ERROR;
1057 }
1058
1059 Argv++;
1060 Argc--;
1061 } else if ((_stricmp (Argv[0], "-h") == 0) || (strcmp (Argv[0], "-?") == 0)) {
1062 //
1063 // Help option
1064 //
1065 Usage ();
1066 return STATUS_ERROR;
1067 } else if (_stricmp (Argv[0], "-b") == 0) {
1068 //
1069 // Specify binary files with -b
1070 //
1071 FileFlags = (FileFlags &~FILE_FLAG_EFI) | FILE_FLAG_BINARY;
1072 } else if ((_stricmp (Argv[0], "-e") == 0) || (_stricmp (Argv[0], "-ec") == 0)) {
1073 //
1074 // Specify EFI files with -e. Specify EFI-compressed with -ec.
1075 //
1076 FileFlags = (FileFlags &~FILE_FLAG_BINARY) | FILE_FLAG_EFI;
1077 if ((Argv[0][2] == 'c') || (Argv[0][2] == 'C')) {
1078 FileFlags |= FILE_FLAG_COMPRESS;
1079 }
1080 //
1081 // Specify not to set the LAST bit in the last file with -l
1082 //
1083 } else if (_stricmp (Argv[0], "-l") == 0) {
1084 Options->NoLast = 1;
1085 } else if (_stricmp (Argv[0], "-p") == 0) {
1086 //
1087 // -v for verbose would have been nicer, but it's already used. Let's use
1088 // -p for prolix (wordy) output
1089 //
1090 Options->Verbose = 1;
1091 } else if (_stricmp (Argv[0], "-dump") == 0) {
1092 //
1093 // -dump for dumping a ROM image. In this case, say that the device id
1094 // and vendor id are valid so we don't have to specify bogus ones on the
1095 // command line.
1096 //
1097 Options->DumpOption = 1;
1098
1099 Options->VendIdValid = 1;
1100 Options->DevIdValid = 1;
1101 FileFlags = FILE_FLAG_BINARY;
1102 } else if (_stricmp (Argv[0], "-cc") == 0) {
1103 //
1104 // Class code value for the next file in the list.
1105 // Make sure there's another parameter
1106 //
1107 if (Argc > 1) {
1108 //
1109 // No error checking on the return value. Could check for LONG_MAX,
1110 // LONG_MIN, or 0 class code value if desired. Check range (3 bytes)
1111 // at least.
1112 //
1113 ClassCode = (UINT32) strtol (Argv[1], NULL, 16);
1114 if (ClassCode & 0xFF000000) {
1115 fprintf (stdout, "ERROR: Class code %s out of range\n", Argv[1]);
1116 return STATUS_ERROR;
1117 }
1118 } else {
1119 fprintf (
1120 stdout,
1121 "ERROR: Missing class code value with %s\n\n",
1122 Argv[0]
1123 );
1124 Usage ();
1125 return STATUS_ERROR;
1126 }
1127
1128 Argv++;
1129 Argc--;
1130 } else if (_stricmp (Argv[0], "-rev") == 0) {
1131 //
1132 // Code revision in the PCI data structure. The value is for the next
1133 // file in the list.
1134 // Make sure there's another parameter
1135 //
1136 if (Argc > 1) {
1137 //
1138 // No error checking on the return value. Could check for LONG_MAX,
1139 // LONG_MIN, or 0 value if desired. Check range (2 bytes)
1140 // at least.
1141 //
1142 CodeRevision = (UINT32) strtol (Argv[1], NULL, 16);
1143 if (CodeRevision & 0xFFFF0000) {
1144 fprintf (stdout, "ERROR: Code revision %s out of range\n", Argv[1]);
1145 return STATUS_ERROR;
1146 }
1147 } else {
1148 fprintf (
1149 stdout,
1150 "ERROR: Missing code revision value with %s\n\n",
1151 Argv[0]
1152 );
1153 Usage ();
1154 return STATUS_ERROR;
1155 }
1156
1157 Argv++;
1158 Argc--;
1159 } else {
1160 fprintf (stdout, "ERROR: Invalid option specified: %s\n\n", Argv[0]);
1161 Usage ();
1162 return STATUS_ERROR;
1163 }
1164 } else {
1165 //
1166 // Not a slash-option argument. Must be a file name. Make sure they've specified
1167 // -e or -b already.
1168 //
1169 if ((FileFlags & (FILE_FLAG_BINARY | FILE_FLAG_EFI)) == 0) {
1170 fprintf (stdout, "ERROR: Missing -e or -b with input file %s\n", Argv[0]);
1171 return STATUS_ERROR;
1172 }
1173 //
1174 // Create a new file structure
1175 //
1176 FileList = (FILE_LIST *) malloc (sizeof (FILE_LIST));
1177 if (FileList == NULL) {
1178 fprintf (stdout, "ERROR: Memory allocation failure\n");
1179 return STATUS_ERROR;
1180 }
1181
1182 memset ((char *) FileList, 0, sizeof (FILE_LIST));
1183 FileList->FileName = Argv[0];
1184 FileList->FileFlags = FileFlags;
1185 if (Options->FileList == NULL) {
1186 Options->FileList = FileList;
1187 } else {
1188 if (PrevFileList == NULL) {
1189 PrevFileList = FileList;
1190 } else {
1191 PrevFileList->Next = FileList;
1192 }
1193 }
1194
1195 PrevFileList = FileList;
1196 //
1197 // Set the class code and code revision for this file, then reset the values.
1198 //
1199 FileList->ClassCode = ClassCode;
1200 FileList->CodeRevision = (UINT16) CodeRevision;
1201 ClassCode = 0;
1202 CodeRevision = 0;
1203 }
1204 //
1205 // Next argument
1206 //
1207 Argv++;
1208 Argc--;
1209 }
1210 //
1211 // Make sure they specified a device ID and vendor ID
1212 //
1213 if (!Options->VendIdValid) {
1214 fprintf (stdout, "ERROR: Missing Vendor ID on command line\n\n");
1215 Usage ();
1216 return STATUS_ERROR;
1217 }
1218
1219 if (!Options->DevIdValid) {
1220 fprintf (stdout, "ERROR: Missing Device ID on command line\n\n");
1221 Usage ();
1222 return STATUS_ERROR;
1223 }
1224 //
1225 // Must have specified some files
1226 //
1227 if (Options->FileList == NULL) {
1228 fprintf (stdout, "ERROR: Missing input file name\n");
1229 Usage ();
1230 return STATUS_ERROR;
1231 }
1232
1233 return 0;
1234 }
1235
1236 static
1237 void
1238 Usage (
1239 VOID
1240 )
1241 /*++
1242
1243 Routine Description:
1244
1245 Print usage information for this utility.
1246
1247 Arguments:
1248
1249 None.
1250
1251 Returns:
1252
1253 Nothing.
1254
1255 --*/
1256 {
1257 int Index;
1258 static const char *Msg[] = {
1259 "EfiRom "UTILITY_VERSION" - Intel EFI Make Option ROM utility",
1260 " Copyright (C), 1999 - 2002 Intel Coproration\n",
1261 " Create an option ROM image from a list of input files",
1262 " Usage: efirom {-p} [-v VendorId] [-d DeviceId] {-o OutFileName} ",
1263 " [-e|-b] [FileName(s)]",
1264 " where:",
1265 " VendorId - required hex PCI Vendor ID for the device",
1266 " DeviceId - required hex PCI Device ID for the device",
1267 " OutFileName - optional output file name. Default is the first input",
1268 " file name with a "DEFAULT_OUTPUT_EXTENSION" file extension",
1269 " FileNames - input PE32 or binary file name(s)",
1270 " BinFileName - input binary file name(s)",
1271 " -p - for verbose output",
1272 " -l - to not automatically set the LAST bit on the last file",
1273 " -b - following FileNames are binary files",
1274 " -e - following FileNames are EFI PE32 image files",
1275 " -ec - following FileNames are EFI PE32 image files, and should",
1276 " be compressed by this utility",
1277 " -cc ClassCode - to use hex ClassCode in the PCI data structure header for",
1278 " the following FileName",
1279 " -rev Revision - to use hex Revision in the PCI data structure header for",
1280 " the following FileName",
1281 " -dump - to dump the headers of an existing option ROM image",
1282 "",
1283 "Example usage: EfiRom -v 0xABCD -d 0x1234 -b File1.bin File2.bin -e File1.efi File2.efi ",
1284 "",
1285 NULL
1286 };
1287
1288 for (Index = 0; Msg[Index] != NULL; Index++) {
1289 fprintf (stdout, "%s\n", Msg[Index]);
1290 }
1291 }
1292
1293 static
1294 void
1295 DumpImage (
1296 FILE_LIST *InFile
1297 )
1298 /*++
1299
1300 Routine Description:
1301
1302 GC_TODO: Add function description
1303
1304 Arguments:
1305
1306 InFile - GC_TODO: add argument description
1307
1308 Returns:
1309
1310 GC_TODO: add return values
1311
1312 --*/
1313 {
1314 PCI_EXPANSION_ROM_HEADER PciRomHdr;
1315 FILE *InFptr;
1316 UINT32 ImageStart;
1317 UINT32 ImageCount;
1318 EFI_PCI_EXPANSION_ROM_HEADER EfiRomHdr;
1319 PCI_DATA_STRUCTURE PciDs;
1320
1321 //
1322 // Open the input file
1323 //
1324 if ((InFptr = fopen (InFile->FileName, "rb")) == NULL) {
1325 fprintf (
1326 stdout,
1327 "ERROR: Could not open input file %s\n",
1328 InFile->FileName
1329 );
1330 return ;
1331 }
1332 //
1333 // Go through the image and dump the header stuff for each
1334 //
1335 ImageCount = 0;
1336 for (;;) {
1337 //
1338 // Save our postition in the file, since offsets in the headers
1339 // are relative to the particular image.
1340 //
1341 ImageStart = ftell (InFptr);
1342 ImageCount++;
1343
1344 //
1345 // Read the option ROM header. Have to assume a raw binary image for now.
1346 //
1347 if (fread (&PciRomHdr, sizeof (PciRomHdr), 1, InFptr) != 1) {
1348 fprintf (stdout, "ERROR: Failed to read PCI ROM header from file\n");
1349 goto BailOut;
1350 }
1351
1352 //
1353 // Dump the contents of the header
1354 //
1355 fprintf (stdout, "Image %d -- Offset 0x%X\n", ImageCount, ImageStart);
1356 fprintf (stdout, " ROM header contents\n");
1357 fprintf (stdout, " Signature 0x%04X\n", (UINT32) PciRomHdr.Signature);
1358 fprintf (stdout, " PCIR offset 0x%04X\n", (UINT32) PciRomHdr.PcirOffset);
1359 //
1360 // Find PCI data structure
1361 //
1362 if (fseek (InFptr, ImageStart + PciRomHdr.PcirOffset, SEEK_SET)) {
1363 fprintf (stdout, "ERROR: Failed to seek to PCI data structure\n");
1364 goto BailOut;
1365 }
1366 //
1367 // Read and dump the PCI data structure
1368 //
1369 if (fread (&PciDs, sizeof (PciDs), 1, InFptr) != 1) {
1370 fprintf (stdout, "ERROR: Failed to read PCI data structure from file\n");
1371 goto BailOut;
1372 }
1373
1374 fprintf (stdout, " PCI Data Structure\n");
1375 fprintf (
1376 stdout,
1377 " Signature %c%c%c%c\n",
1378 (char) PciDs.Signature,
1379 (char) (PciDs.Signature >> 8),
1380 (char) (PciDs.Signature >> 16),
1381 (char) (PciDs.Signature >> 24)
1382 );
1383 fprintf (stdout, " Vendor ID 0x%04X\n", PciDs.VendorId);
1384 fprintf (stdout, " Device ID 0x%04X\n", PciDs.DeviceId);
1385 fprintf (
1386 stdout,
1387 " Class Code 0x%06X\n",
1388 (UINT32) (PciDs.ClassCode[0] | (PciDs.ClassCode[1] << 8) | (PciDs.ClassCode[2] << 16))
1389 );
1390 fprintf (stdout, " Image size 0x%X\n", PciDs.ImageLength * 512);
1391 fprintf (stdout, " Code revision: 0x%04X\n", PciDs.CodeRevision);
1392 fprintf (stdout, " Indicator 0x%02X", (UINT32) PciDs.Indicator);
1393 //
1394 // Print the indicator, used to flag the last image
1395 //
1396 if (PciDs.Indicator == INDICATOR_LAST) {
1397 fprintf (stdout, " (last image)\n");
1398 } else {
1399 fprintf (stdout, "\n");
1400 }
1401 //
1402 // Print the code type. If EFI code, then we can provide more info.
1403 //
1404 fprintf (stdout, " Code type 0x%02X", (UINT32) PciDs.CodeType);
1405 if (PciDs.CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
1406 fprintf (stdout, " (EFI image)\n");
1407 //
1408 // Re-read the header as an EFI ROM header, then dump more info
1409 //
1410 fprintf (stdout, " EFI ROM header contents\n");
1411 if (fseek (InFptr, ImageStart, SEEK_SET)) {
1412 fprintf (stdout, "ERROR: Failed to re-seek to ROM header structure\n");
1413 goto BailOut;
1414 }
1415
1416 if (fread (&EfiRomHdr, sizeof (EfiRomHdr), 1, InFptr) != 1) {
1417 fprintf (stdout, "ERROR: Failed to read EFI PCI ROM header from file\n");
1418 goto BailOut;
1419 }
1420 //
1421 // Now dump more info
1422 //
1423 fprintf (stdout, " EFI Signature 0x%04X\n", EfiRomHdr.EfiSignature);
1424 fprintf (
1425 stdout,
1426 " Compression Type 0x%04X ",
1427 (UINT32) EfiRomHdr.CompressionType
1428 );
1429 if (EfiRomHdr.CompressionType == EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED) {
1430 fprintf (stdout, "(compressed)\n");
1431 } else {
1432 fprintf (stdout, "(not compressed)\n");
1433 }
1434
1435 fprintf (
1436 stdout,
1437 " Machine type 0x%04X (%s)\n",
1438 EfiRomHdr.EfiMachineType,
1439 GetMachineTypeStr (EfiRomHdr.EfiMachineType)
1440 );
1441 fprintf (
1442 stdout,
1443 " Subsystem 0x%04X (%s)\n",
1444 EfiRomHdr.EfiSubsystem,
1445 GetSubsystemTypeStr (EfiRomHdr.EfiSubsystem)
1446 );
1447 fprintf (
1448 stdout,
1449 " EFI image offset 0x%04X (@0x%X)\n",
1450 (UINT32) EfiRomHdr.EfiImageHeaderOffset,
1451 (UINT32) (EfiRomHdr.EfiImageHeaderOffset + ImageStart)
1452 );
1453
1454 } else {
1455 //
1456 // Not an EFI image
1457 //
1458 fprintf (stdout, "\n");
1459 }
1460 //
1461 // If code type is EFI image, then dump it as well?
1462 //
1463 // if (PciDs.CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
1464 // }
1465 //
1466 // If last image, then we're done
1467 //
1468 if (PciDs.Indicator == INDICATOR_LAST) {
1469 goto BailOut;
1470 }
1471 //
1472 // Seek to the start of the next image
1473 //
1474 if (fseek (InFptr, ImageStart + (PciDs.ImageLength * 512), SEEK_SET)) {
1475 fprintf (stdout, "ERROR: Failed to seek to next image\n");
1476 goto BailOut;
1477 }
1478 }
1479
1480 BailOut:
1481 fclose (InFptr);
1482 }
1483
1484 char *
1485 GetMachineTypeStr (
1486 UINT16 MachineType
1487 )
1488 /*++
1489
1490 Routine Description:
1491
1492 GC_TODO: Add function description
1493
1494 Arguments:
1495
1496 MachineType - GC_TODO: add argument description
1497
1498 Returns:
1499
1500 GC_TODO: add return values
1501
1502 --*/
1503 {
1504 int Index;
1505
1506 for (Index = 0; mMachineTypes[Index].Name != NULL; Index++) {
1507 if (mMachineTypes[Index].Value == MachineType) {
1508 return mMachineTypes[Index].Name;
1509 }
1510 }
1511
1512 return "unknown";
1513 }
1514
1515 static
1516 char *
1517 GetSubsystemTypeStr (
1518 UINT16 SubsystemType
1519 )
1520 /*++
1521
1522 Routine Description:
1523
1524 GC_TODO: Add function description
1525
1526 Arguments:
1527
1528 SubsystemType - GC_TODO: add argument description
1529
1530 Returns:
1531
1532 GC_TODO: add return values
1533
1534 --*/
1535 {
1536 int Index;
1537
1538 for (Index = 0; mSubsystemTypes[Index].Name != NULL; Index++) {
1539 if (mSubsystemTypes[Index].Value == SubsystemType) {
1540 return mSubsystemTypes[Index].Name;
1541 }
1542 }
1543
1544 return "unknown";
1545 }