2018-01-18 22:01:42 +00:00
|
|
|
#!/usr/bin/perl
|
|
|
|
# Create a UEFI "Firmware File" (FFS) with optional features.
|
|
|
|
# Address Size Designation
|
|
|
|
# ------- ---- -----------
|
|
|
|
#
|
|
|
|
# EFI_FFS_FILE_HEADER:
|
|
|
|
# 0x0000 16 Name (EFI_GUID)
|
|
|
|
# 0x0010 1 IntegrityCheck.Header (Header Checksum)
|
|
|
|
# 0x0011 1 IntegrityCheck.File -> set to 0xAA (FFS_FIXED_CHECKSUM) and clear bit 0x40 of Attributes
|
|
|
|
# 0x0012 1 FileType -> 0x07 = EFI_FV_FILETYPE_DRIVER
|
|
|
|
# 0x0013 1 Attributes -> 0x00
|
|
|
|
# 0x0014 3 Size, including header and all other sections
|
|
|
|
# 0x0017 1 State (unused) -> 0X00
|
|
|
|
#
|
|
|
|
# EFI_COMMON_SECTION_HEADER:
|
|
|
|
# 0x0000 3 Size, including this header
|
|
|
|
# 0x0003 1 Type -> 0x10 (EFI_SECTION_PE32)
|
|
|
|
# 0x0004 #### <PE data>
|
|
|
|
#
|
|
|
|
# EFI_COMMON_SECTION_HEADER:
|
|
|
|
# 0x0000 3 Size, including this header
|
|
|
|
# 0x0003 1 Type -> 0x15 (EFI_SECTION_USER_INTERFACE)
|
|
|
|
# 0x0004 #### NUL terminated UTF-16 string (eg "FAT\0")
|
|
|
|
#
|
|
|
|
# EFI_COMMON_SECTION_HEADER:
|
|
|
|
# 0x0000 3 Size, including this header
|
|
|
|
# 0x0003 1 Type -> 0x14 (EFI_SECTION_VERSION)
|
|
|
|
# 0x0004 #### NUL terminated UTF-16 string (eg "1.0\0")
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
2018-01-20 20:59:38 +00:00
|
|
|
use FindBin;
|
|
|
|
use lib "$FindBin::Bin/../lib";
|
2018-01-18 22:01:42 +00:00
|
|
|
use Getopt::Long;
|
2018-01-19 14:55:21 +00:00
|
|
|
use File::Temp 'tempfile';
|
2018-01-18 22:01:42 +00:00
|
|
|
use Digest::SHA 'sha1';
|
2018-01-20 20:59:38 +00:00
|
|
|
use GUID;
|
2018-01-18 22:01:42 +00:00
|
|
|
|
|
|
|
my $usage = <<"";
|
|
|
|
Usage:
|
|
|
|
$0 -o output.ffs [options] file.efi [...]
|
|
|
|
Options:
|
|
|
|
-o | --output output.ffs Output file (default is stdout)
|
|
|
|
-n | --name FileName Name to include in UI Section
|
|
|
|
-t | --type Type FREEFORM|DRIVER|SMM|DXE_CORE|SMM_CORE|PEIM
|
|
|
|
-v | --version 1.0 Version section
|
|
|
|
-g | --guid GUID This file GUID (default is hash of Name)
|
|
|
|
-d | --depex 'guid guid..' Optional dependencies (all ANDed, or TRUE)
|
2018-01-19 14:55:21 +00:00
|
|
|
-z | --compress Enable LZMA compression
|
2018-01-18 22:01:42 +00:00
|
|
|
|
|
|
|
my $output = '-';
|
|
|
|
my $name;
|
|
|
|
my $type = 'FREEFORM';
|
|
|
|
my $version;
|
|
|
|
my $guid;
|
|
|
|
my $depex;
|
2018-01-19 14:55:21 +00:00
|
|
|
my $compress;
|
2018-01-18 22:01:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
GetOptions(
|
|
|
|
"o|output=s" => \$output,
|
|
|
|
"n|name=s" => \$name,
|
|
|
|
"t|type=s" => \$type,
|
|
|
|
"v|version=s" => \$version,
|
|
|
|
"g|guid=s" => \$guid,
|
|
|
|
"d|depex=s" => \$depex,
|
2018-01-19 14:55:21 +00:00
|
|
|
"z|compress+" => \$compress,
|
2018-01-18 22:01:42 +00:00
|
|
|
) or die $usage;
|
|
|
|
|
|
|
|
|
|
|
|
my %file_types = qw/
|
|
|
|
RAW 0x01
|
|
|
|
FREEFORM 0x02
|
|
|
|
SECURITY_CORE 0x03
|
|
|
|
PEI_CORE 0x04
|
|
|
|
DXE_CORE 0x05
|
|
|
|
PEIM 0x06
|
|
|
|
DRIVER 0x07
|
|
|
|
COMBINED_PEIM_DRIVER 0x08
|
|
|
|
APPLICATION 0x09
|
|
|
|
SMM 0x0A
|
|
|
|
FIRMWARE_VOLUME_IMAGE 0x0B
|
|
|
|
COMBINED_SMM_DXE 0x0C
|
|
|
|
SMM_CORE 0x0D
|
|
|
|
DEBUG_MIN 0xe0
|
|
|
|
DEBUG_MAX 0xef
|
|
|
|
FFS_PAD 0xf0
|
|
|
|
/;
|
|
|
|
|
|
|
|
my %section_types = qw/
|
2018-01-19 14:55:21 +00:00
|
|
|
GUID_DEFINED 0x02
|
2018-01-18 22:01:42 +00:00
|
|
|
PE32 0x10
|
|
|
|
PIC 0x11
|
|
|
|
TE 0x12
|
|
|
|
DXE_DEPEX 0x13
|
|
|
|
VERSION 0x14
|
|
|
|
USER_INTERFACE 0x15
|
|
|
|
COMPATIBILITY16 0x16
|
|
|
|
FIRMWARE_VOLUME_IMAGE 0x17
|
|
|
|
FREEFORM_SUBTYPE_GUID 0x18
|
|
|
|
RAW 0x19
|
|
|
|
PEI_DEPEX 0x1B
|
|
|
|
SMM_DEPEX 0x1C
|
|
|
|
/;
|
|
|
|
|
|
|
|
# Some special cases for non-PE32 sections
|
|
|
|
my %section_type_map = qw/
|
|
|
|
FREEFORM RAW
|
|
|
|
FIRMWARE_VOLUME_IMAGE FIRMWARE_VOLUME_IMAGE
|
|
|
|
/;
|
|
|
|
|
|
|
|
# Special cases for DEPEX sections
|
|
|
|
my %depex_type_map = qw/
|
|
|
|
PEIM PEI_DEPX
|
|
|
|
DRIVER DXE_DEPEX
|
|
|
|
SMM SMM_DEPEX
|
|
|
|
/;
|
|
|
|
|
|
|
|
my $data = '';
|
|
|
|
|
|
|
|
# Read entire files at a time and append a new section
|
|
|
|
# for each file read. Some special types have their own
|
|
|
|
# section type; otherwise we're adding a PE32
|
2018-01-20 22:28:50 +00:00
|
|
|
# The file goes first so that it will be aligned correctly
|
|
|
|
# on the 4 KB page inside the FV.
|
2018-01-18 22:01:42 +00:00
|
|
|
local $/ = undef;
|
|
|
|
while(<>)
|
|
|
|
{
|
|
|
|
$data .= section($section_type_map{$type} || 'PE32', $_);
|
|
|
|
}
|
|
|
|
|
2018-01-20 22:28:50 +00:00
|
|
|
# Put the optional parts after the input data
|
|
|
|
$data .= section(USER_INTERFACE => ucs16($name))
|
|
|
|
if $name;
|
|
|
|
|
|
|
|
$data .= section(VERSION => ucs16(chr(0x00) . $version))
|
|
|
|
if $version;
|
|
|
|
|
|
|
|
$data .= depex($type, split /\s+/, $depex)
|
|
|
|
if $depex;
|
|
|
|
|
2018-01-18 22:01:42 +00:00
|
|
|
|
|
|
|
# If no GUID was provided, make one from the name
|
|
|
|
# if there is no name from the data
|
|
|
|
if ($guid)
|
|
|
|
{
|
|
|
|
$guid = guid($guid);
|
|
|
|
} else {
|
|
|
|
# Generate a deterministic GUID based on either
|
|
|
|
# the UI name or the hash of the input data
|
|
|
|
$guid = substr(sha1($name || $data), 0, 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
my $file_type = $file_types{$type}
|
|
|
|
or die "$type: unknown file type\n";
|
|
|
|
|
2018-01-19 14:55:21 +00:00
|
|
|
# If we're compressing, compress the data and wrap it with a GUIDed header
|
|
|
|
if ($compress)
|
|
|
|
{
|
|
|
|
my ($fh,$filename) = tempfile();
|
|
|
|
print $fh $data;
|
|
|
|
close $fh;
|
|
|
|
|
|
|
|
# -7 produces the same bit-stream as the UEFI tools
|
|
|
|
my $lz_data = `lzma --compress --stdout -7 $filename`;
|
|
|
|
printf STDERR "%d compressed to %d\n", length($data), length($lz_data);
|
|
|
|
|
|
|
|
# fixup the size field in the lzma compressed data
|
|
|
|
substr($lz_data, 5, 8) = pack("VV", length($data), 0);
|
|
|
|
|
|
|
|
# wrap the lzdata in a GUIDed section
|
|
|
|
my $lz_header = ''
|
|
|
|
. guid('EE4E5898-3914-4259-9D6E-DC7BD79403CF')
|
|
|
|
. chr(0x18) # data offset
|
|
|
|
. chr(0x00)
|
|
|
|
. chr(0x01) # Processing required
|
|
|
|
. chr(0x00)
|
|
|
|
;
|
|
|
|
|
|
|
|
# and replace our data with the GUID defined LZ compressed data
|
|
|
|
$data = section(GUID_DEFINED => $lz_header . $lz_data);
|
|
|
|
}
|
|
|
|
|
2018-01-18 22:01:42 +00:00
|
|
|
# Generate the FFS header around the sections
|
|
|
|
my $len = length($data) + 0x18;
|
|
|
|
|
|
|
|
my $ffs = ''
|
|
|
|
. $guid # 0x00
|
|
|
|
. chr(0x00) # 0x10 header checksum
|
|
|
|
. chr(0x00) # 0x11 FFS_FIXED_CHECKSUM
|
|
|
|
. chr(hex $file_type) # 0x12
|
|
|
|
. chr(0x28) # 0x13 attributes
|
|
|
|
. chr(($len >> 0) & 0xFF) # 0x14 length
|
|
|
|
. chr(($len >> 8) & 0xFF)
|
|
|
|
. chr(($len >> 16) & 0xFF)
|
|
|
|
. chr(0x07) # 0x17 state (done?)
|
|
|
|
;
|
|
|
|
|
|
|
|
# fixup the header checksum
|
|
|
|
my $sum = 0;
|
|
|
|
for my $i (0..length($ffs)-2) {
|
|
|
|
$sum -= ord(substr($ffs, $i, 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
substr($ffs, 0x10, 2) = chr($sum & 0xFF) . chr(0xAA);
|
|
|
|
|
|
|
|
# Add the rest of the data
|
|
|
|
$ffs .= $data;
|
|
|
|
|
|
|
|
# should we pad to align the FFS length?
|
|
|
|
#my $unaligned = length($ffs) % 8;
|
|
|
|
#$ffs .= chr(0x00) x (8 - $unaligned)
|
|
|
|
# if $unaligned != 0;
|
|
|
|
|
|
|
|
|
|
|
|
if ($output eq '-')
|
|
|
|
{
|
|
|
|
print $ffs;
|
|
|
|
} else {
|
2018-01-18 22:22:01 +00:00
|
|
|
open OUTPUT, ">", $output
|
2018-01-18 22:01:42 +00:00
|
|
|
or die "$output: Unable to open: $!\n";
|
|
|
|
|
|
|
|
print OUTPUT $ffs;
|
|
|
|
close OUTPUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Convert a string to UCS-16 and add a nul terminator
|
|
|
|
sub ucs16
|
|
|
|
{
|
|
|
|
my $val = shift;
|
|
|
|
|
|
|
|
my $rc = '';
|
|
|
|
for(my $i = 0 ; $i < length $val ; $i++)
|
|
|
|
{
|
|
|
|
$rc .= substr($val, $i, 1) . chr(0x0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# nul terminate the string
|
|
|
|
$rc .= chr(0x0) . chr(0x0);
|
|
|
|
|
|
|
|
return $rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# output an EFI Common Section Header
|
2018-01-19 14:55:21 +00:00
|
|
|
# Since we might be dealing with ones larger than 16 MB, we should use extended
|
|
|
|
# section type that gives us a 4-byte length.
|
2018-01-18 22:01:42 +00:00
|
|
|
sub section
|
|
|
|
{
|
|
|
|
my $type = shift;
|
|
|
|
my $data = shift;
|
|
|
|
|
|
|
|
die "$type: Unknown section type\n"
|
|
|
|
unless exists $section_types{$type};
|
|
|
|
|
|
|
|
my $len = length($data) + 4;
|
|
|
|
|
2018-01-19 14:55:21 +00:00
|
|
|
die "Section length $len > 16 MB, can't include it in a section!\n"
|
|
|
|
if $len >= 0x1000000;
|
|
|
|
|
2018-01-18 22:01:42 +00:00
|
|
|
my $sec = ''
|
|
|
|
. chr(($len >> 0) & 0xFF)
|
|
|
|
. chr(($len >> 8) & 0xFF)
|
|
|
|
. chr(($len >> 16) & 0xFF)
|
|
|
|
. chr(hex $section_types{$type})
|
|
|
|
. $data;
|
|
|
|
|
|
|
|
my $unaligned = length($sec) % 4;
|
|
|
|
$sec .= chr(0x00) x (4 - $unaligned)
|
|
|
|
if $unaligned != 0;
|
|
|
|
|
|
|
|
return $sec;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Generate a DEPEX
|
|
|
|
sub depex
|
|
|
|
{
|
|
|
|
my $type = shift;
|
|
|
|
|
|
|
|
my $section_type = $depex_type_map{$type}
|
|
|
|
or die "$type: DEPEX is not supported\n";
|
|
|
|
|
|
|
|
if ($depex eq 'TRUE')
|
|
|
|
{
|
|
|
|
# Special case for short-circuit
|
|
|
|
return section($section_type, chr(0x06) . chr(0x08));
|
|
|
|
}
|
|
|
|
|
|
|
|
my $data = '';
|
|
|
|
my $count = 0;
|
|
|
|
|
|
|
|
for my $guid (@_)
|
|
|
|
{
|
|
|
|
# push the guid
|
|
|
|
$data .= chr(0x02) . guid($guid);
|
|
|
|
$count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
# AND them all together (1 minus the number of GUIDs)
|
|
|
|
$data .= chr(0x03) for 1..$count-1;
|
|
|
|
$data .= chr(0x08);
|
|
|
|
|
|
|
|
return section($section_type, $data);
|
|
|
|
}
|