blob: 904a8ffd68b6a61f85253976188cb788d89017cf [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import binascii
import collections
import hashlib
import optparse
import os
import re
import shutil
import struct
import sys
from tools import CmdError
from tools import Tools
from fdt import Fdt
# Attributes defined outside __init__
#pylint: disable=W0201
# TODO(sjg): Once we have a stable fdt, write some unit tests for this
# TODO(clchiou): Rewrite this part after official flashmap implementation is
# pulled into Chromium OS code base.
# Use this to find the FDT containing the flashmap
FDTMAP_SIGNATURE = '__FDTM__'
# constants imported from lib/fmap.h
FMAP_SIGNATURE = "__FMAP__"
FMAP_VER_MAJOR = 1
FMAP_VER_MINOR = 0
FMAP_STRLEN = 32
FMAP_AREA_STATIC = 1 << 0
FMAP_AREA_COMPRESSED = 1 << 1
FMAP_AREA_RO = 1 << 2
FMAP_HEADER_FORMAT = "<8sBBQI%dsH" % (FMAP_STRLEN)
FMAP_AREA_FORMAT = "<II%dsH" % (FMAP_STRLEN)
FMAP_HEADER_NAMES = (
'signature',
'ver_major',
'ver_minor',
'base',
'image_size',
'name',
'nareas',
)
FMAP_AREA_NAMES = (
'offset',
'size',
'name',
'flags',
)
# A useful named tuple.
Value = collections.namedtuple('Value', ['node', 'key', 'params'])
class ConfigError(Exception):
"""A configuration error, normally a mistake in the fdt."""
pass
class PackError(Exception):
"""A packing error, such as a file being too large for its area."""
pass
class Entry(dict):
"""The base class for an entry type.
All entry types are subclasses of this class. It is basically a dictionary
with a few methods to check that the supplied data is value.
Subclasses should implement the following methods:
GetData(): To return the data for this entry as a string.
RunTools(): To run any required tools to create the data.
Potentially in the future we could add PutData() to write data from a packed
image file back into an entry, to allow an image file to be updated.
Properties which we need in our dictionary:
pack: The PackFirmware object (essentially this is our parent)
offset: Byte offset of area.
size: Size of area in bytes.
name: Name of area.
required: True if this entry is required in the image, False if not
Properties which we create:
value: The value that we obtain for this entry. This is a list of
filenames.
"""
def __init__(self, props):
super(Entry, self).__init__(props)
self._CheckFields(('offset', 'size', 'name'))
def __getattr__(self, name):
return self[name]
def __setattr__(self, name, value):
self[name] = value
def _CheckFields(self, fields):
"""Check that listed fields are present.
Args:
fields: List of field names to check for.
Raises:
ConfigError: if a field is not present.
"""
for f in fields:
if f not in self:
raise ConfigError('Entry %s: missing required field: %s' %
(self['name'], f))
def _CheckFieldsInt(self, fields):
"""Check that listed fields are present, and convert them to ints.
Args:
fields: List of field names to check for.
Raises:
ConfigError: if a field is not present or cannot be converted to an int.
"""
self._CheckFields(fields)
for f in fields:
try:
self[f] = int(self[f])
except ValueError:
raise ConfigError("Entry %s, property %s: could not convert '%s'"
" to integer" % (self['name'], f, self[f]))
def GetOverlap(self, entry):
"""Returns the amount by which we overlap with the supplied entry.
Args:
entry: Entry to check.
Returns:
Amount of overlap:
0: the entries butt up together.
<0: there is a gap bewteen the entries.
>0: there is overlap.
"""
a = [self.offset, self.offset + self.size]
b = [entry.offset, entry.offset + entry.size]
return min(a[1], b[1]) - max(a[0], b[0])
def GetData(self):
"""Method implemented by subclasses to return data for the entry.
Returns:
String containing entry data.
"""
raise PackError('class Entry does not implement GetEntry()')
def RunTools(self, tools, out, tmpdir):
"""Method implemented by subclasses to run required tools.
Some entry types require running external tools to create their data.
This method provides a convenient way of doing this. The supplied
temporary directory can be used to store files. These should ideally
not be deleted by this method, since the user may wish to see them
later.
Args:
tools: Tools object to use to run tools.
out: Output object to send output to
tmpdir: Temporary directory to use to create required files.
"""
pass
class EntryFmapArea(Entry):
"""A base class for things that actually produce data for the image.
Properties:
flags: The FMAP flags, which is an integer made up from the bit values in
FMAP_AREA_...
"""
def __init__(self, props):
super(EntryFmapArea, self).__init__(props)
self._CheckFields(('flags',))
def GetData(self):
"""This section is empty"""
return ''
class EntryFmap(EntryFmapArea):
"""An entry which contains an Fmap section
Properties:
base: Base offset of flash device (normally 0).
size: Size of the flash device (2MB or 4MB typically).
nareas: Number of areas in the FMAP = number of sections.
entries: The list of entries to put in the FMAP.
"""
def __init__(self, props):
super(EntryFmap, self).__init__(props)
self._CheckFieldsInt(('ver_major', 'ver_minor'))
def SetEntries(self, base, image_size, entries):
self['base'] = base
self['image_size'] = image_size
self['nareas'] = len(entries)
self['entries'] = entries
def GetData(self):
def _FormatBlob(fmt, names, obj):
params = [obj[name] for name in names]
return struct.pack(fmt, *params)
self._CheckFields(('base', 'size'))
self['signature'] = FMAP_SIGNATURE
blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, self)
for entry in self.entries:
blob += _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, entry)
return blob
class EntryFdtMap(EntryFmapArea):
"""An entry which contains an FDT-based flashmap
Properties:
base: Base offset of flash device (normally 0).
size: Size of the flash device (2MB or 4MB typically).
"""
def __init__(self, props):
super(EntryFdtMap, self).__init__(props)
# We could potentially include only the flashmap node, but for now put
# the entire FDT in this region. It provides easy access to all
# configuration.
def GetData(self):
data = self.pack.tools.ReadFile(self.pack.props['fdtmap'])
crc32 = binascii.crc32(data) & 0xffffffff
blob = struct.pack('<8sLL', FDTMAP_SIGNATURE, len(data), crc32)
blob += data
return blob
class EntryWiped(EntryFmapArea):
"""This entry will be wiped to a particular byte value.
Properties:
wipe_value: byte value to set area to.
"""
def __init__(self, props):
super(EntryWiped, self).__init__(props)
self._CheckFields(('wipe_value',))
if self.wipe_value:
self.wipe_value = chr(int(self.wipe_value))
else:
self.wipe_value = chr(0)
if len(self.wipe_value) != 1:
raise ConfigError('wipe_value out of range [00:ff]: %s' %
repr(self.wipe_value))
def GetData(self):
return self.wipe_value * self.size
class EntryBlobString(EntryFmapArea):
"""This entry contains a single string.
The string is placed in the area.
Properties:
self.value: The string value to store.
"""
def __init__(self, props):
super(EntryBlobString, self).__init__(props)
def GetData(self):
return self.value[0]
class EntryIfd(EntryFmapArea):
"""This entry marks the use of an Intel Firmware Descriptor.
The entry itself covers the 'Intel' part of the firmware, containing the
firmware descriptor and management engine. When this entry appears in a
flash map, we strip it off the image, and use ifdtool to place the rest
of the image into a provided skeleton file (which contains the management
engine and a skeleton firmware descriptor).
"""
def __init__(self, props):
super(EntryIfd, self).__init__(props)
def ProduceFinalImage(self, tools, out, tmpdir, image_fname):
"""Produce the final image for an Intel ME system
Some Intel systems require that an image contains the Management Engine
firmware, and also a firmware descriptor.
This function takes the existing image, removes the front part of it,
and replaces it with these required pieces using ifdtool.
Args:
tools: Tools object to use to run tools.
out: Output object to send output to
tmpdir: Temporary directory to use to create required files.
image_fname: Output image filename
"""
out.Progress('Setting up Intel ME')
data = tools.ReadFile(image_fname)
# We can assume that the ifd section is at the start of the image.
if self.offset != 0:
raise ConfigError('IFD section must be at offset 0 in the image')
data = data[self.size:]
input_fname = os.path.join(tmpdir, 'ifd-input.bin')
tools.WriteFile(input_fname, data)
ifd_output = os.path.join(tmpdir, 'image.ifd')
# This works by modifying a skeleton file.
shutil.copyfile(tools.Filename(self.pack.props['skeleton']), ifd_output)
args = ['-i', 'BIOS:%s' % input_fname, ifd_output]
tools.Run('ifdtool', args)
# ifdtool puts the output in a file with '.new' tacked on the end.
shutil.move(ifd_output + '.new', image_fname)
tools.OutputSize('IFD image', image_fname)
class EntryBlob(EntryFmapArea):
"""This entry contains a binary blob.
Properties:
self.value: The filename to read to obtain the blob data.
"""
def __init__(self, props, params):
super(EntryBlob, self).__init__(props)
self.params = params
if 'compress' not in self:
self.compress = None
self.with_index = 'with_index' in props
def GetData(self):
return self.pack.tools.ReadFileAndConcat(
self.value, self.compress, self.with_index)[0]
class EntryKeyBlock(EntryFmapArea):
"""This entry contains a vblock
Properties:
keyblock: The filename of the keyblock to use (within the keydir)
(e.g. 'firmware.keyblock')
signprivate: Private key filename (e.g. 'firmware_data_key.vbprivk')
keynelkey: Kernel key filename (e.g. 'kernel_subkey.vbpubk')
version: Version of vblock (generally 1)
"""
def __init__(self, props):
super(EntryKeyBlock, self).__init__(props)
self._CheckFields(('keyblock', 'signprivate', 'kernelkey'))
self._CheckFieldsInt(('version', 'preamble_flags'))
if 'compress' not in self:
self.compress = None
self.with_index = 'with_index' in props
# pylint can't figure out that self.value is set by now
#pylint: disable=E0203
def RunTools(self, tools, out, tmpdir):
"""Create a vblock for the given firmware image"""
self.path = os.path.join(tmpdir, 'vblock.%s' % self.label)
input_data = os.path.join(tmpdir, 'input.%s' % self.label)
try:
prefix = self.pack.props['keydir'] + '/'
# Join up the data files to be signed
data = self.pack.tools.ReadFileAndConcat(
self.value, self.compress, self.with_index)[0]
tools.WriteFile(input_data, data)
args = [
'--vblock', self.path,
'--keyblock', prefix + self.keyblock,
'--signprivate', prefix + self.signprivate,
'--version', '%d' % self.version,
'--fv', input_data,
'--kernelkey', prefix + self.kernelkey,
'--flags', '%d' % self.preamble_flags,
]
out.Notice("Sign '%s' into %s" % (', '.join(self.value), self.label))
stdout = tools.Run('vbutil_firmware', args)
out.Debug(stdout)
# Update value to the actual filename to be used
self.value = [self.path]
except CmdError as err:
raise PackError('Cannot make key block: vbutil_firmware failed\n%s' %
err)
def GetData(self):
fd = open(self.path, 'rb')
data = fd.read()
fd.close()
return data
class PackFirmware:
"""Class for packing a firmware image
A firmware image consists of a number of areas, called entries here.
Together they cover the entire firmware device from start to finish.
We use the fdt to define the format of the output - things like the
name, start and size of each entry, as well as what type of thing
to pack in there.
We maintain a list of available data files (in self.props) and these can be
requested by each entry. Each entry is a subclass of Entry, such as
EntryBlob which handles the 'blob' entry type.
One of the entry types is the fmap (flash map). It basically has a header
and a representation of the list of sections. We create this from the fdt
auotmatically. You can find this format documented here:
http://code.google.com/p/flashmap/wiki/FmapSpec
The following methods must be called in order:
SetupFiles
SelectFdt
PackImage
"""
def __init__(self, tools, output):
"""
Args:
output: A method to call to diplay output:
def output(msg):
Args:
msg: Message to output.
tools: A tools object for calling external tools.
verbose: 0 for silent, 1 for progress only, 2 for some info, 3 for
full info, 4 for debug info including sub-tool output.
"""
self.props = {} # Properties / files we know about.
self.entries = [] # The entries in the flash image.
self._out = output
self.tools = tools
self.use_efs = False # Early firmware selection
def _GetFlags(self, props):
"""Create the fmap flags value from the given properties.
Args:
props: Dictionary containing properties.
Returns
Integer containing flags from _FMAP_AREA_...
"""
flags = FMAP_AREA_STATIC
if 'read-only' in props:
flags |= FMAP_AREA_RO
if 'compressed' in props:
flags |= FMAP_AREA_COMPRESSED
return flags
def _CreateEntry(self, node, props):
"""Create an entry based on a node in the fdt.
For example this fdt line:
type = "blob signed";
means it is a 'blob' type and will create an EntryBlob, and the blob
used will be self.props['signed'].
Args:
node: The node to use, e.g. /flash/ro-stub
props: Doctionary containing all properties from the node.
Returns:
An entry set up and ready for packing.
Raises:
ValueError: If fdt has an unknown entry type.
"""
entry_list = ['empty']
# Try to use the early-firmware-selection type if we are in that mode.
if self.use_efs:
entry_list = props.get('type,efs', 'empty').split()
if entry_list[0] == 'empty':
entry_list = props.get('type', 'empty').split()
ftype = entry_list[0]
if ftype == 'empty':
ftype = ''
key = None
if len(entry_list) > 1:
key = entry_list[1]
params = entry_list[2:]
# Create an entry of the correct type.
entry = None
if not ftype:
entry = EntryFmapArea(props)
elif ftype == 'blob':
entry = EntryBlob(props, params)
elif ftype == 'wiped':
entry = EntryWiped(props)
elif ftype == 'keyblock':
entry = EntryKeyBlock(props)
elif ftype == 'blobstring':
entry = EntryBlobString(props)
elif ftype == 'fmap':
entry = EntryFmap(props)
elif ftype == 'ifd':
entry = EntryIfd(props)
elif ftype == 'fdtmap':
entry = EntryFdtMap(props)
else:
raise ValueError('%s: unknown entry type' % ftype)
entry.pack = self
entry.node = node
entry.key = key
entry.required = 'required' in props
entry.ftype = ftype
return entry
def _IsRequired(self, entry):
"""Check if an entry is required in the final image.
Args:
entry: Entry to check
Returns:
True if this entry must be included, False if not.
"""
return entry.required
def SetupFiles(self, boot, signed, gbb, fwid, keydir):
"""Set up files required for packing the firmware.
Args:
boot: Filename of bootloader image.
signed: Filename of signed bootloader image.
gbb: Filename of Google Binary Block (GBB).
fwid: String containing the firmware ID.
keydir: Key directory to use (containing signing keys).
"""
self.props['boot'] = boot
self.props['signed'] = signed
self.props['gbb'] = gbb
self.props['fwid'] = fwid
self.keydir = keydir
def _CheckOverlap(self):
"""Check that no entries overlap each other.
We only allow section areas to overlap - anything with actual data in it
must not overlap.
Returns:
True if all entries are required in the image, else False
"""
required = filter(self._IsRequired, self.entries)
entries = sorted(required, key=lambda e: e.offset)
all_entries = len(self.entries) == len(required)
for e1, e2 in zip(entries, entries[1:]):
# Allow overlap between "pure" fmap areas, but not any of its subclasses
# Here we exploit the fact that Entry is a new-style class
if type(e1) is EntryFmapArea or type(e2) is EntryFmapArea:
continue
overlap = e1.GetOverlap(e2)
if overlap > 0:
raise ValueError('Flash map entries overlap by %d bytes: '
'%s: %08x-%08x, %s: %08x-%08x' %
(overlap, e1.label, e1.offset, e1.offset + e1.size,
e2.label, e2.offset, e2.offset + e2.size))
elif overlap is not 0:
self._out.Warning('Warning: Flash map has a gap of %d bytes: '
'%s: %08x-%08x, %s: %08x-%08x' %
(-overlap, e1.label, e1.offset, e1.offset + e1.size,
e2.label, e2.offset, e2.offset + e2.size))
return all_entries
def SelectFdt(self, fdt, board=None):
"""Scan FDT and build entry objects.
This creates a list of entry objects which we can later use to generate
a firmware image. Each entry's properties are set up correcty but at this
stage no data is processed. The result is a list of entries in
self.entries.
Args:
fdt: fdt object containing the device tree.
board: Name of board type if known (None if not known).
Raises:
ConfigError if an error is detected in the fdt configuration.
"""
def _AddNode(node, props):
"""Add a new flash map node to the entry list.
Args:
node: Name of node to read from
props: Dictionary containing properties of the node
"""
align = int(props.get('align', '1'))
align_mask = align - 1
if align == 0 or (align & align_mask) != 0:
raise ValueError("Invalid alignment %d in node '%s'" % props['label'])
# Read the two cells from the node's /reg property to get entry extent.
reg = props.get('reg', None)
if reg:
offset, size = fdt.DecodeIntList(node, 'reg', reg, 2)
if (offset & align_mask) or (size & align_mask):
raise ValueError("Alignment of %d conflicts with 'reg' setting in"
"node '%s': offset=%#08x, size=%#08x" %
(align, props['label'], offset, size))
else:
size = props.get('size', None)
if not size:
raise ValueError("Must specify either 'reg' or 'size' in flash"
"node '%s'" % props['label'])
size = int(size)
offset = self.upto_offset
offset = (offset + align_mask) & ~align_mask
props['node'] = node
props['offset'] = offset
props['size'] = size
# The section names must be upper case with underscores, for other tools
props['name'] = re.sub('-', '_', props['label']).upper()
props['flags'] = self._GetFlags(props)
try:
entry = self._CreateEntry(node, props)
self.entries.append(entry)
except ConfigError as err:
raise ValueError('Config error: %s' % err)
if entry.ftype:
self.upto_offset = offset + size
else:
self.upto_offset = offset
if entry.required:
self.required_count += 1
if entry.key == 'signed':
self.first_blob_entry = entry
self.fdt = fdt
root = '/flash'
# Current offset to use for entries with only a 'size' property.
self.upto_offset = 0
self.required_count = 0
self.first_blob_entry = None
if not fdt.GetProp(root, 'reg', ''):
raise ValueError("No /flash present in fdt, and no available default"
" for board '%s'" % board)
self.image_size = int(fdt.GetIntList(root, 'reg', 2)[1])
# Scan the flash map in the fdt, creating a list of Entry objects.
children = fdt.GetChildren(root)
for child in children:
node = root + '/' + child
props = fdt.GetProps(node, True)
_AddNode(node, props)
# If there was only a 'size' property, write a full 'reg' property
# based on the offset we calculated
if not props.get('reg'):
fdt.PutIntList(node, 'reg', [props['offset'], props['size']])
def GetBlobList(self):
"""Generate a list of blob types that we are going to need.
Returns:
List of blob type strings
"""
blob_set = set()
for entry in self.entries:
if isinstance(entry, EntryBlob):
blob_set.update(entry.key.split(','))
return list(blob_set)
def GetBlobParams(self, blob_type):
"""Returns the parameters for a blob of the given type.
There should be only one blob of this type in the entire flashmap.
Args:
blob_type: Type of the blob (e.g. 'exynos-bl2')
Raises:
ValueError if the blob cannot be found.
Returns:
The list of parameters for this blob, which may be empty
"""
for entry in self.entries:
if isinstance(entry, EntryBlob):
if entry.key == blob_type:
return entry.params
raise ValueError("Blob type '%s' cannot be found" % blob_type)
def AddProperty(self, name, value):
"""Add a new property which can be used by the fdt.
Args:
name: Name of property
value: Value of property (typically a filename)
"""
if not value:
raise CmdError("Cannot find value for entry property '%s'" % name)
self.props[name] = value
def GetProperty(self, name):
"""Get the value of a property required by the fdt.
Args:
name: Name of property
Returns:
Value of property, normally a filename string
"""
return self.props.get(name, None)
def ConcatPropContents(self, prop_list, compress, with_index):
"""Read, concatenate and return the contents of the listed props.
Each property references a filename. We read the contents of each
file and join it together.
Each section starts on a 32-bit boundary.
Args:
prop_list: List of properties to process
compress: compression type, like 'lzo', None for no compression
with_index: Wether an index structure should be prepended
See ReadFileAndConcat for more details.
Returns:
Tuple:
Contents of the files (as a string), optionally with an index
prepended (see ReadFileAndConcat for details)
Directory of the position of the contents, as a dictionary:
key: Name of the property
value: List containing:
offset of the start of this property's data
size of this property's data
"""
filenames = [self.props[prop] for prop in prop_list]
data, offset, length = \
self.tools.ReadFileAndConcat(filenames, compress, with_index)
directory = {}
for i in xrange(len(prop_list)):
directory[prop_list[i]] = [offset[i], length[i]]
return data, directory
# For some weird reason pylint presumes that sha256 is not in hashlib
#pylint: disable=E1101
def UpdateBlobPositionsAndHashes(self, fdt):
"""Record position and size of all blob members in the FDT.
Some blobs have multiple files within them. We want a way to
access these individiually. We do this by adding a subnode for
each, and putting the offset and size information in there.
This function scans for blobs with more than one file and adds
a 'reg' property to the subnode for each file. It also adds 'hash'
nodes with the sha 256 hash of the file's contents.
Note: Since one of the members may in fact be the fdt, and we are
updating the fdt, we may change the size it. To get around this,
we perform two passes of the algorithm. On the second pass we will
be writing data that is already there, so the fdt size will not
change.
"""
for _ in range(0, 2):
for entry in self.entries:
if isinstance(entry, EntryBlob) and entry.required:
self._out.Info("Updating blob positions in fdt for '%s'" % entry.key)
data, directory = self.ConcatPropContents(
entry.key.split(','), None, entry.with_index)
fdt.PutInteger(entry.node, 'used', len(data))
add_hash = fdt.GetBool(entry.node, 'add-hash')
if len(directory) > 1 or add_hash:
# Deprecate this: crbug.com/254311
# In fact entry.with_index should be deprecated - it is not needed
# since we can just look at the FDT. Ick.
fdt.PutInteger(entry.node, '#address-cells', 1)
fdt.PutInteger(entry.node, '#size-cells', 1)
for key, item in directory.iteritems():
fdt.PutIntList(entry.node + '/' + key, 'reg', item)
hasher = hashlib.sha256()
offset, size = item
hasher.update(data[offset:offset + size])
hash_value = hasher.digest()
byte_count = struct.unpack('%dB' % len(hash_value), hash_value)
fdt.PutBytes(entry.node + '/' + key, 'hash', byte_count)
fdt.PutBool(entry.node + '/' + key, 'this-is-deprecated', True)
if add_hash:
hasher = hashlib.sha256()
hasher.update(data)
hash_value = hasher.digest()
byte_count = struct.unpack('%dB' % len(hash_value), hash_value)
fdt.PutBytes(entry.node, 'hash', byte_count)
def CheckProperties(self):
"""Check that each entry has the properties that it needs.
Entries with a 'key' use that to look up properties. We need to make
sure that there is a property for that key. If not, then the entry will
not be able to access the data it needs during the packing stage.
Raises:
ConfigError: The property for a required key is missing
"""
for entry in self.entries:
if entry.required and entry.key:
if 'value' not in entry:
entry.value = []
for prop in entry.key.split(','):
if not self.props.get(prop):
raise ConfigError("%s: Requests property '%s' but we only "
"have %s" % (entry.node, prop, self.props.keys()))
entry.value.append(self.props[prop])
def RequireAllEntries(self):
"""Mark all entries as required, to produce a full image."""
for entry in self.entries:
entry.required = True
def GetMissingBlobs(self):
"""Returns a list of blobs that do not currently have data assigned.
Some blobs may not yet have data assigned, ie. self.props[] does not
contain a filename for one or more of the blob's data types. This
function returns a list of nodes that still need to be processed.
Returns:
List of device tree nodes that have no data assigned. Each is a
namedtuple with these members:
node: Full path to device tree node
key: Key name (e.g. exynos-bl2')
params: List of [arameters to the node - the first element is the
list of files within the blob, for example 'boot,dtb'
"""
missing = []
for entry in self.entries:
if entry.required and isinstance(entry, EntryBlob):
for prop in entry.key.split(','):
if not self.props.get(prop):
value = Value(entry.node, entry.key, entry.params)
missing.append(value)
break
return missing
def PackImage(self, tmpdir, output_path):
"""Pack the various components into a firmware image,
You must call SetupFiles() and SelectFdt() first, to set things up. Then
this function will create a firmware image for you.
Args:
output_path: Full path of file to contain the resulting image.
Raises:
PackError: If unable to fit something in the space available, or a
required file or setup piece is missing.
"""
self.tmpdir = tmpdir
all_entries = self._CheckOverlap()
image_used = 0
# Set up a zeroed file of the correct size.
with open(output_path, 'wb') as image:
if all_entries:
image.write('\0' * self.image_size)
# Pack all the entriess.
ifd = None
for entry in self.entries:
if not entry.required:
self._out.Info("Section '%s' is not required, skipping" % entry.name)
continue
# Add in the info for the fmap.
if type(entry) == EntryFmap:
entry.SetEntries(base=0, image_size=self.image_size,
entries=self.entries)
elif type(entry) == EntryIfd:
ifd = entry
try:
# First run any required tools.
entry.RunTools(self.tools, self._out, self.tmpdir)
if 'value' in entry:
self._out.Notice("Pack '%s' into %s" % (', '.join(entry.value),
entry.name))
# Now read out the data
data = entry.GetData()
self._out.Debug('Entry: %s' % entry.name)
self._out.Debug('Entry data: %s' % entry)
self._out.Debug('Data size: %s bytes, at %#x' %
(len(data), entry.offset))
if len(data) > entry.size:
raise PackError("Data for '%s' too large for area: %d/%#x >"
" %d/%#x" % (entry.name, len(data), len(data), entry.size,
entry.size))
if entry.size:
usage = len(data) * 100 / entry.size
else:
usage = 0
self._out.Notice('Entry: %s, size %#x, data %#x, usage %d%%' %
(entry.name, entry.size, len(data), usage))
entry.used = len(data)
image_used += entry.used
image.seek(entry.offset)
image.write(data)
except PackError as err:
raise ValueError('Packing error: %s' % err)
# If the image contain an IFD section, process it
if ifd:
ifd.ProduceFinalImage(self.tools, self._out, self.tmpdir, output_path)
self._out.Notice('Image size %#x, data %#x, usage %d%%' %
(self.image_size, image_used, image_used * 100 / self.image_size))
def _OutEntry(self, status, offset, size, name, fname):
"""Display a flash map entry.
Args:
status: Status character.
offset: Byte offset of entry.
size: Size of entry in bytes.
name: Name of entry.
fname: Filename of entry
"""
indent = '' if status == '-' else ' '
self._out.UserOutput('%s %08x %08x %s%-12s %s' % (status, offset, size,
indent, name, fname))
def ShowMap(self):
"""Show a map of the final image."""
self._out.UserOutput('Final Flash Map:')
self._out.UserOutput('%s %8s %8s %-12s %s' %
('S', 'Start', 'Size', 'Name', 'File'))
offset = 0
for entry in self.entries:
if not entry.ftype:
status = '-'
elif entry.required:
status = 'P'
else:
status = '.'
if offset != entry.offset:
self._OutEntry('!', offset, entry.offset - offset, '<gap>', '')
self._OutEntry(status, entry.offset, entry.size, entry.name,
entry.get('value', ''))
if entry.ftype:
offset = entry.offset + entry.size
else:
offset = entry.offset
def GetFmap(self):
"""Get FMAP offset and contents
Returns:
Tuple of (FMAP offset, FMAP data)
"""
for entry in self.entries:
if type(entry) != EntryFmap:
continue
entry.SetEntries(base=0, image_size=self.image_size,
entries=self.entries)
return (entry.offset, entry.GetData())
def _Test():
"""Run any built-in tests."""
import doctest
assert doctest.testmod().failed == 0
def _PackOutput(msg):
"""Helper function to write output from PackFirmware (verbose level 2).
This is passed to PackFirmware for it to use to write output.
Args:
msg: Message to display.
"""
print msg
def main():
"""Main function for pack_firmware.
We provide a way of packing firmware from the command line using this module
directly.
"""
parser = optparse.OptionParser()
parser.add_option('-v', '--verbosity', dest='verbosity', default=1,
type='int', help='Control verbosity: 0=silent, 1=progress, 3=full, '
'4=debug')
parser.add_option('-k', '--key', dest='key', type='string', action='store',
help='Path to signing key directory (default to dev key)',
default='##/usr/share/vboot/devkeys')
parser.add_option('-d', '--dt', dest='fdt', type='string', action='store',
help='Path to fdt file to use (binary ,dtb)', default='u-boot.dtb')
parser.add_option('-u', '--uboot', dest='uboot', type='string',
action='store', help='Executable bootloader file (U-Boot)',
default='u-boot.bin')
parser.add_option('-S', '--signed', dest='signed', type='string',
action='store', help='Path to signed boot binary (U-Boot + BCT + FDT)',
default='u-boot-fdt-signed.bin')
parser.add_option('-g', '--gbb', dest='gbb', type='string',
action='store', help='Path to Google Binary Block file',
default='gbb.bin')
parser.add_option('-o', '--outdir', dest='outdir', type='string',
action='store', help='Path to directory to use for intermediate and '
'output files', default='out')
(options, _) = parser.parse_args(sys.argv)
# Set up the output directory.
if not os.path.isdir(options.outdir):
os.makedirs(options.outdir)
# Get tools and fdt.
tools = Tools(options.verbosity)
fdt = Fdt(tools, options.fdt)
# Pack the firmware.
pack = PackFirmware(_PackOutput, tools, options.verbosity)
pack.SetupFiles(boot=options.uboot, signed=options.signed, gbb=options.gbb,
fwid=tools.GetChromeosVersion(), keydir=options.key)
pack.SelectFdt(fdt)
out_fname = os.path.join(options.outdir, 'image.bin')
pack.PackImage(options.outdir, out_fname)
print 'Output binary is %s' % out_fname
if __name__ == '__main__':
if sys.argv[1:2] == ["--test"]:
_Test(*sys.argv[2:])
else:
main()