blob: ef3880e5855195cf10ddcbb28978a9c130362817 [file] [log] [blame]
# 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.
"""This module builds a firmware image.
This modules uses a few rudimentary other libraries for its activity.
Here are the names we give to the various files we deal with. It is important
to keep these consistent!
uboot u-boot.bin (with no device tree)
fdt the fdt blob
bct the BCT file
bootstub uboot + fdt
signed (uboot + fdt + bct) signed blob
import glob
import hashlib
import os
import re
from fdt import Fdt
from pack_firmware import PackFirmware
import shutil
import struct
from flashmaps import default_flashmaps
from tools import CmdError
from exynos import ExynosBl2
# This data is required by bmpblk_utility. Does it ever change?
# It was stored with the chromeos-bootimage ebuild, but we want
# this utility to work outside the chroot.
yaml_data = '''
bmpblock: 1.0
devmode: DeveloperBmp/DeveloperBmp.bmp
recovery: RecoveryBmp/RecoveryBmp.bmp
rec_yuck: RecoveryNoOSBmp/RecoveryNoOSBmp.bmp
rec_insert: RecoveryMissingOSBmp/RecoveryMissingOSBmp.bmp
- [0, 0, devmode]
- [0, 0, recovery]
- [0, 0, rec_yuck]
- [0, 0, rec_insert]
- [ dev_en, rec_en, yuck_en, ins_en ]
# Build GBB flags.
# (src/platform/vboot_reference/firmware/include/gbb_header.h)
gbb_flag_properties = {
'dev-screen-short-delay': 0x00000001,
'load-option-roms': 0x00000002,
'enable-alternate-os': 0x00000004,
'force-dev-switch-on': 0x00000008,
'force-dev-boot-usb': 0x00000010,
'disable-fw-rollback-check': 0x00000020,
'enter-triggers-tonorm': 0x00000040,
'force-dev-boot-legacy': 0x00000080,
'faft-key-overide': 0x00000100,
'disable-ec-software-sync': 0x00000200,
'default-dev-boot-legacy': 0x00000400,
'disable-pd-software-sync': 0x00000800,
'force-dev-boot-fastboot-full-cap': 0x00002000,
'enable-serial': 0x00004000,
# Maps board name to Exynos product number
type_to_model = {
'peach' : '5420',
'daisy' : '5250'
def ListGoogleBinaryBlockFlags():
"""Print out a list of GBB flags."""
print ' %-30s %s' % ('Available GBB flags:', 'Hex')
for name, value in gbb_flag_properties.iteritems():
print ' %-30s %02x' % (name, value)
class BlobDeferral(Exception):
"""An error indicating deferal of blob generation."""
class Bundle:
"""This class encapsulates the entire bundle firmware logic.
Sequence of events:
bundle = Bundle(tools.Tools(), cros_output.Output())
.. can call bundle.AddConfigList(), AddEnableList() if required
Public properties:
fdt: The fdt object that we use for building our image. This wil be the
one specified by the user, except that we might add config options
to it. This is set up by SelectFdt() which must be called before
bundling starts.
uboot_fname: Full filename of the U-Boot binary we use.
bct_fname: Full filename of the BCT file we use.
spl_source: Source device to load U-Boot from, in SPL:
straps: Select device according to CPU strap pins
spi: Boot from SPI
emmc: Boot from eMMC
Private attributes:
_small: True to create a 'small' signed U-Boot, False to produce a
full image. The small U-Boot is enough to boot but will not have
access to GBB, RW U-Boot, etc.
def __init__(self, tools, output):
"""Set up a new Bundle object.
tools: A tools.Tools object to use for external tools.
output: A cros_output.Output object to use for program output.
self._tools = tools
self._out = output
# Set up the things we need to know in order to operate.
self._board = None # Board name, e.g. nyan.
self._fdt_fname = None # Filename of our FDT.
self._force_rw = None
self._force_efs = None
self._gbb_flags = None
self._keydir = None
self._small = False
self.bct_fname = None # Filename of our BCT file.
self.blobs = {} # Table of (type, filename) of arbitrary blobs
self.bmpblk_fname = None # Filename of our Bitmap Block
self.coreboot_elf = None
self.coreboot_fname = None # Filename of our coreboot binary.
self.ecro_fname = None # Filename of EC read-only file
self.ecrw_fname = None # Filename of EC file
self.pdrw_fname = None # Filename of PD file
self.exynos_bl1 = None # Filename of Exynos BL1 (pre-boot)
self.exynos_bl2 = None # Filename of Exynos BL2 (SPL)
self.fdt = None # Our Fdt object.
self.kernel_fname = None
self.postload_fname = None
self.seabios_fname = None # Filename of our SeaBIOS payload.
self.skeleton_fname = None # Filename of Coreboot skeleton file
self.uboot_fname = None # Filename of our U-Boot binary.
def SetDirs(self, keydir):
"""Set up directories required for Bundle.
keydir: Directory containing keys to use for signing firmware.
self._keydir = keydir
def SetFiles(self, board, bct, uboot=None, bmpblk=None, coreboot=None,
postload=None, seabios=None, exynos_bl1=None, exynos_bl2=None,
skeleton=None, ecrw=None, ecro=None, pdrw=None,
kernel=None, blobs=None, skip_bmpblk=False, cbfs_files=None,
"""Set up files required for Bundle.
board: The name of the board to target (e.g. nyan).
uboot: The filename of the u-boot.bin image to use.
bct: The filename of the binary BCT file to use.
bmpblk: The filename of bitmap block file to use.
coreboot: The filename of the coreboot image to use (on x86).
coreboot_elf: If not none, the ELF file to add as a Coreboot payload.
postload: The filename of the u-boot-post.bin image to use.
seabios: The filename of the SeaBIOS payload to use if any.
exynos_bl1: The filename of the exynos BL1 file
exynos_bl2: The filename of the exynos BL2 file (U-Boot spl)
skeleton: The filename of the coreboot skeleton file.
ecrw: The filename of the EC (Embedded Controller) read-write file.
ecro: The filename of the EC (Embedded Controller) read-only file.
pdrw: The filename of the PD (PD embedded controller) read-write file.
kernel: The filename of the kernel file if any.
blobs: List of (type, filename) of arbitrary blobs.
skip_bmpblk: True if no bmpblk is required
cbfs_files: Root directory of files to be stored in RO and RW CBFS
rocbfs_files: Root directory of files to be stored in RO CBFS
self._board = board
self.uboot_fname = uboot
self.bct_fname = bct
self.bmpblk_fname = bmpblk
self.coreboot_fname = coreboot
self.coreboot_elf = coreboot_elf
self.postload_fname = postload
self.seabios_fname = seabios
self.exynos_bl1 = exynos_bl1
self.exynos_bl2 = exynos_bl2
self.skeleton_fname = skeleton
self.ecrw_fname = ecrw
self.ecro_fname = ecro
self.pdrw_fname = pdrw
self.kernel_fname = kernel
self.blobs = dict(blobs or ())
self.skip_bmpblk = skip_bmpblk
self.cbfs_files = cbfs_files
self.rocbfs_files = rocbfs_files
def SetOptions(self, small, gbb_flags, force_rw=False, force_efs=False):
"""Set up options supported by Bundle.
small: Only create a signed U-Boot - don't produce the full packed
firmware image. This is useful for devs who want to replace just the
U-Boot part while keeping the keys, gbb, etc. the same.
gbb_flags: Specification for string containing adjustments to make.
force_rw: Force firmware into RW mode.
force_efs: Force firmware to use 'early firmware selection' feature,
where RW firmware is selected before SDRAM is initialized.
self._small = small
self._gbb_flags = gbb_flags
self._force_rw = force_rw
self._force_efs = force_efs
def _GetBuildRoot(self):
"""Get the path to this board's 'firmware' directory.
Path to firmware directory, with ## representing the path to the
if not self._board:
raise ValueError('No board defined - please define a board to use')
return os.path.join('##', 'build', self._board, 'firmware')
def _CheckFdtFilename(self, fname):
"""Check provided FDT filename and return the correct name if needed.
Where the filename lacks a path, add a default path for this board.
Where no FDT filename is provided, select a default one for this board.
fname: Proposed FDT filename.
Selected FDT filename, after validation.
build_root = self._GetBuildRoot()
dir_name = os.path.join(build_root, 'dtb')
if not fname:
# Figure out where the file should be, and the name we expect.
base_name = re.sub('_', '-', self._board)
# In case the name exists with a prefix or suffix, find it.
wildcard = os.path.join(dir_name, '*%s.dtb' % base_name)
found_list = glob.glob(self._tools.Filename(wildcard))
if len(found_list) == 1:
fname = found_list[0]
# We didn't find anything definite, so set up our expected name.
fname = os.path.join(dir_name, '%s.dtb' % base_name)
# Convert things like 'exynos5250-daisy' into a full path.
root, ext = os.path.splitext(fname)
if not ext and not os.path.dirname(root):
fname = os.path.join(dir_name, '%s.dtb' % root)
return fname
def CheckOptions(self):
"""Check provided options and select defaults."""
build_root = self._GetBuildRoot()
board_type = self._board.split('_')[0]
model = type_to_model.get(board_type)
if not self.uboot_fname:
self.uboot_fname = os.path.join(build_root, 'u-boot.bin')
if not self.bct_fname:
self.bct_fname = os.path.join(build_root, 'bct', 'board.bct')
if not self.bmpblk_fname:
self.bmpblk_fname = os.path.join(build_root, 'bmpblk.bin')
if model:
if not self.exynos_bl1:
self.exynos_bl1 = os.path.join(build_root, 'u-boot.bl1.bin')
if not self.exynos_bl2:
self.exynos_bl2 = os.path.join(build_root, 'u-boot-spl.wrapped.bin')
if not self.coreboot_fname:
self.coreboot_fname = os.path.join(build_root, 'coreboot.rom')
if not self.skeleton_fname:
self.skeleton_fname = os.path.join(build_root, 'coreboot.rom')
if not self.seabios_fname:
self.seabios_fname = 'seabios.cbfs'
if not self.ecrw_fname:
self.ecrw_fname = os.path.join(build_root, 'ec.RW.bin')
if not self.pdrw_fname:
self.pdrw_fname = os.path.join(build_root, 'pd.RW.bin')
if not self.ecro_fname:
self.ecro_fname = os.path.join(build_root, 'ec.RO.bin')
def GetFiles(self):
"""Get a list of files that we know about.
This is the opposite of SetFiles except that we may have put in some
default names. It returns a dictionary containing the filename for
each of a number of pre-defined files.
Dictionary, with one entry for each file.
file_list = {
'bct' : self.bct_fname,
'exynos-bl1' : self.exynos_bl1,
'exynos-bl2' : self.exynos_bl2,
return file_list
def DecodeGBBFlagsFromFdt(self):
"""Get Google Binary Block flags from the FDT.
These should be in the chromeos-config node, like this:
chromeos-config {
GBB flags value from FDT.
chromeos_config = self.fdt.GetProps("/chromeos-config")
gbb_flags = 0
for name in chromeos_config:
if name.startswith('gbb-flag-'):
flag_value = gbb_flag_properties.get(name[9:])
if flag_value:
gbb_flags |= flag_value
self._out.Notice("FDT: Enabling %s." % name)
raise ValueError("FDT contains invalid GBB flags '%s'" % name)
return gbb_flags
def DecodeGBBFlagsFromOptions(self, gbb_flags, adjustments):
"""Decode ajustments to the provided GBB flags.
We support three options:
hex value: c2
defined value: force-dev-boot-usb,load-option-roms
adjust default value: -load-option-roms,+force-dev-boot-usb
The last option starts from the passed-in GBB flags and adds or removes
gbb_flags: Base (default) FDT flags.
adjustments: String containing adjustments to make.
Updated FDT flags.
use_base_value = True
if adjustments:
return int(adjustments, base=16)
except (ValueError, TypeError):
for flag in adjustments.split(','):
oper = None
if flag[0] in ['-', '+']:
oper = flag[0]
flag = flag[1:]
value = gbb_flag_properties.get(flag)
if not value:
raise ValueError("Invalid GBB flag '%s'" % flag)
if oper == '+':
gbb_flags |= value
self._out.Notice("Cmdline: Enabling %s." % flag)
elif oper == '-':
gbb_flags &= ~value
self._out.Notice("Cmdline: Disabling %s." % flag)
if use_base_value:
gbb_flags = 0
use_base_value = False
self._out.Notice('Cmdline: Resetting flags to 0')
gbb_flags |= value
self._out.Notice("Cmdline: Enabling %s." % flag)
return gbb_flags
def _CreateGoogleBinaryBlock(self, hardware_id):
"""Create a GBB for the image.
hardware_id: Hardware ID to use for this board. If None, then the
default from the Fdt will be used
Path of the created GBB file.
if not hardware_id:
hardware_id = self.fdt.GetString('/config', 'hwid')
gbb_size = self.fdt.GetFlashPartSize('ro', 'gbb')
odir = self._tools.outdir
gbb_flags = self.DecodeGBBFlagsFromFdt()
# Allow command line to override flags
gbb_flags = self.DecodeGBBFlagsFromOptions(gbb_flags, self._gbb_flags)
self._out.Notice("GBB flags value %#x" % gbb_flags)
self._out.Progress('Creating GBB')
if self.skip_bmpblk:
bmpfv_size = 0
bmpfv_size = gbb_size - 0x2180
sizes = [0x100, 0x1000, bmpfv_size, 0x1000]
sizes = ['%#x' % size for size in sizes]
gbb = 'gbb.bin'
keydir = self._tools.Filename(self._keydir)
gbb_set_command = ['-s',
'--hwid=%s' % hardware_id,
'--rootkey=%s/root_key.vbpubk' % keydir,
'--recoverykey=%s/recovery_key.vbpubk' % keydir,
'--flags=%d' % gbb_flags,
if not self.skip_bmpblk:
gbb_set_command[-1:-1] = ['--bmpfv=%s' % self._tools.Filename(
self._tools.Run('gbb_utility', ['-c', ','.join(sizes), gbb], cwd=odir)
self._tools.Run('gbb_utility', gbb_set_command, cwd=odir)
return os.path.join(odir, gbb)
def _SignBootstub(self, bct, bootstub, text_base):
"""Sign an image so that the Tegra SOC will boot it.
bct: BCT file to use.
bootstub: Boot stub (U-Boot + fdt) file to sign.
text_base: Address of text base for image.
filename of signed image.
# First create a config file - this is how we instruct cbootimage
signed = os.path.join(self._tools.outdir, 'signed.bin')
self._out.Progress('Signing Bootstub')
config = os.path.join(self._tools.outdir, 'boot.cfg')
fd = open(config, 'w')
fd.write('Version = 1;\n')
fd.write('Redundancy = 1;\n')
fd.write('Bctfile = %s;\n' % bct)
# TODO(dianders): Right now, we don't have enough space in our flash map
# for two copies of the BCT when we're using NAND, so hack it to 1. Not
# sure what this does for reliability, but at least things will fit...
is_nand = "NvBootDevType_Nand" in self._tools.Run('bct_dump', [bct])
if is_nand:
fd.write('Bctcopy = 1;\n')
fd.write('BootLoader = %s,%#x,%#x,Complete;\n' % (bootstub, text_base,
self._tools.Run('cbootimage', [config, signed])
self._tools.OutputSize('BCT', bct)
self._tools.OutputSize('Signed image', signed)
return signed
def SetBootcmd(self, bootcmd, bootsecure):
"""Set the boot command for U-Boot.
bootcmd: Boot command to use, as a string (if None this this is a nop).
bootsecure: We'll set '/config/bootsecure' to 1 if True and 0 if False.
if bootcmd is not None:
if bootcmd == 'none':
bootcmd = ''
self.fdt.PutString('/config', 'bootcmd', bootcmd)
self.fdt.PutInteger('/config', 'bootsecure', int(bootsecure))
self._out.Info('Boot command: %s' % bootcmd)
def SetNodeEnabled(self, node_name, enabled):
"""Set whether an node is enabled or disabled.
This simply sets the 'status' property of a node to "ok", or "disabled".
The node should either be a full path to the node (like '/uart@10200000')
or an alias property.
Aliases are supported like this:
aliases {
console = "/uart@10200000";
pointing to a node:
uart@10200000 {
status = "okay";
In this case, this function takes the name of the alias ('console' in
this case) and updates the status of the node that is pointed to, to
either ok or disabled. If the alias does not exist, a warning is
node_name: Name of node (e.g. '/uart@10200000') or alias alias
(e.g. 'console') to adjust
enabled: True to enable, False to disable
# Look up the alias if this is an alias reference
if not node_name.startswith('/'):
lookup = self.fdt.GetString('/aliases', node_name, '')
if not lookup:
self._out.Warning("Cannot find alias '%s' - ignoring" % node_name)
node_name = lookup
if enabled:
status = 'okay'
status = 'disabled'
self.fdt.PutString(node_name, 'status', status)
def AddEnableList(self, enable_list):
"""Process a list of nodes to enable/disable.
enable_list: List of (node, value) tuples to add to the fdt. For each
node: The fdt node to write to will be <node> or pointed to by
/aliases/<node>. We can tell which
value: 0 to disable the node, 1 to enable it
CmdError if a command fails.
if enable_list:
for node_name, enabled in enable_list:
enabled = int(enabled)
if enabled not in (0, 1):
raise ValueError
except ValueError:
raise CmdError("Invalid enable option value '%s' "
"(should be 0 or 1)" % str(enabled))
self.SetNodeEnabled(node_name, enabled)
def AddConfigList(self, config_list, use_int=False):
"""Add a list of config items to the fdt.
Normally these values are written to the fdt as strings, but integers
are also supported, in which case the values will be converted to integers
(if necessary) before being stored.
config_list: List of (config, value) tuples to add to the fdt. For each
config: The fdt node to write to will be /config/<config>.
value: An integer or string value to write.
use_int: True to only write integer values.
CmdError: if a value is required to be converted to integer but can't be.
if config_list:
for config in config_list:
value = config[1]
if use_int:
value = int(value)
except ValueError:
raise CmdError("Cannot convert config option '%s' to integer" %
if type(value) == type(1):
self.fdt.PutInteger('/config', '%s' % config[0], value)
self.fdt.PutString('/config', '%s' % config[0], value)
def DecodeTextBase(self, data):
"""Look at a U-Boot image and try to decode its TEXT_BASE.
This works because U-Boot has a header with the value 0x12345678
immediately followed by the TEXT_BASE value. We can therefore read this
from the image with some certainty. We check only the first 40 words
since the header should be within that region.
Since upstream Tegra has moved to having a 16KB SPL region at the start,
and currently this does holds the U-Boot text base (e.g. 0x10c000) instead
of the SPL one (e.g. 0x108000), we search in the U-Boot part as well.
data: U-Boot binary data
Text base (integer) or None if none was found
found = False
for start in (0, 0x4000):
for i in range(start, start + 160, 4):
word = data[i:i + 4]
# TODO(sjg): This does not cope with a big-endian target
value = struct.unpack('<I', word)[0]
if found:
return value - start
if value == 0x12345678:
found = True
return None
def CalcTextBase(self, name, fdt, fname):
"""Calculate the TEXT_BASE to use for U-Boot.
Normally this value is in the fdt, so we just read it from there. But as
a second check we look at the image itself in case this is different, and
switch to that if it is.
This allows us to flash any U-Boot even if its TEXT_BASE is different.
This is particularly useful with upstream U-Boot which uses a different
value (which we will move to).
data = self._tools.ReadFile(fname)
# The value that comes back from fdt.GetInt is signed, which makes no
# sense for an address base. Force it to unsigned.
fdt_text_base = fdt.GetInt('/chromeos-config', 'textbase', 0) & 0xffffffff
text_base = self.DecodeTextBase(data)
text_base_str = '%#x' % text_base if text_base else 'None'
self._out.Info('TEXT_BASE: fdt says %#x, %s says %s' % (fdt_text_base,
fname, text_base_str))
# If they are different, issue a warning and switch over.
if text_base and text_base != fdt_text_base:
self._out.Warning("TEXT_BASE %x in %sU-Boot doesn't match "
"fdt value of %x. Using %x" % (text_base, name,
fdt_text_base, text_base))
fdt_text_base = text_base
return fdt_text_base
def _CreateBootStub(self, uboot, base_fdt, postload):
"""Create a boot stub and a signed boot stub.
For postload:
We add a /config/postload-text-offset entry to the signed bootstub's
fdt so that U-Boot can find the postload code.
The raw (unsigned) bootstub will have a value of -1 for this since we will
simply append the postload code to the bootstub and it can find it there.
This will be used for RW A/B firmware.
For the signed case this value will specify where in the flash to find
the postload code. This will be used for RO firmware.
uboot: Path to u-boot.bin (may be chroot-relative)
base_fdt: Fdt object containing the flat device tree.
postload: Path to u-boot-post.bin, or None if none.
Tuple containing:
Full path to bootstub (uboot + fdt(-1) + postload).
Full path to signed (uboot + fdt(flash pos) + bct) + postload.
CmdError if a command fails.
bootstub = os.path.join(self._tools.outdir, 'u-boot-fdt.bin')
text_base = self.CalcTextBase('', self.fdt, uboot)
uboot_data = self._tools.ReadFile(uboot)
# Make a copy of the fdt for the bootstub
fdt = base_fdt.Copy(os.path.join(self._tools.outdir, 'bootstub.dtb'))
fdt.PutInteger('/config', 'postload-text-offset', 0xffffffff)
fdt_data = self._tools.ReadFile(fdt.fname)
self._tools.WriteFile(bootstub, uboot_data + fdt_data)
self._tools.OutputSize('U-Boot binary', self.uboot_fname)
self._tools.OutputSize('U-Boot fdt', self._fdt_fname)
self._tools.OutputSize('Combined binary', bootstub)
# Sign the bootstub; this is a combination of the board specific
# bct and the stub u-boot image.
signed = self._SignBootstub(self._tools.Filename(self.bct_fname),
bootstub, text_base)
signed_postload = os.path.join(self._tools.outdir, 'signed-postload.bin')
data = self._tools.ReadFile(signed)
if postload:
# We must add postload to the bootstub since A and B will need to
# be able to find it without the /config/postload-text-offset mechanism.
bs_data = self._tools.ReadFile(bootstub)
bs_data += self._tools.ReadFile(postload)
bootstub = os.path.join(self._tools.outdir, 'u-boot-fdt-postload.bin')
self._tools.WriteFile(bootstub, bs_data)
self._tools.OutputSize('Combined binary with postload', bootstub)
# Now that we know the file size, adjust the fdt and re-sign
postload_bootstub = os.path.join(self._tools.outdir, 'postload.bin')
fdt.PutInteger('/config', 'postload-text-offset', len(data))
fdt_data = self._tools.ReadFile(fdt.fname)
self._tools.WriteFile(postload_bootstub, uboot_data + fdt_data)
signed = self._SignBootstub(self._tools.Filename(self.bct_fname),
postload_bootstub, text_base)
if len(data) != os.path.getsize(signed):
raise CmdError('Signed file size changed from %d to %d after updating '
'fdt' % (len(data), os.path.getsize(signed)))
# Re-read the signed image, and add the post-load binary.
data = self._tools.ReadFile(signed)
data += self._tools.ReadFile(postload)
self._tools.OutputSize('Post-load binary', postload)
self._tools.WriteFile(signed_postload, data)
self._tools.OutputSize('Final bootstub with postload', signed_postload)
return bootstub, signed_postload
def _AddCbfsFiles(self, bootstub, cbfs_files):
for dir, subs, files in os.walk(cbfs_files):
for file in files:
file = os.path.join(dir, file)
cbfs_name = file.replace(cbfs_files, '', 1).strip('/')
self._tools.Run('cbfstool', [bootstub, 'add', '-f', file,
'-n', cbfs_name, '-t', 'raw', '-c', 'lzma'])
def _ProcessCbfsFileProperty(self, cb_copy, node):
"""Add files to CBFS in RW regions using a specification in fmap.dts
rw-a-boot {
cbfs-files {
ecfoo = "add -n ecrw-copy -f ec.RW.bin -t raw -A sha256";
Adds a file called "ecrw-copy" of raw type to FW_MAIN_A with the
content of ec.RW.bin in the build root, with a SHA256 hash.
The dts property name ("ecfoo") is ignored but should be unique,
all cbfstool commands that start with "add" are allowed, as is "remove".
The second and third argument need to be "-n <cbfs file name>".
cbfs_config = self.fdt.GetProps(node + '/cbfs-files')
except CmdError:
cbfs_config = None
# Ignore first character of node string,
# so both /flash/foo and flash/foo work.
region = node[1:].split('/')[1]
part_sections = region.split('-', 1)
fmap_dst = self._FmapNameByPath(part_sections)
if cbfs_config != None:
# remove all files slated for addition, in case they already exist
for val in cbfs_config.itervalues():
f = val.split(' ')
command = f[0]
if command[:3] != 'add' and command != 'remove':
raise CmdError("'%s' doesn't add or remove a file", f)
if f[1] != '-n':
raise CmdError("second argument in '%s' must be '-n'", f)
cbfsname = f[2]
# Calling through shell isn't strictly necessary here, but we still
# do it to keep operation more similar to the invocation in the next
# loop.
self._tools.Run('sh', [ '-c',
' '.join(['cbfstool', cb_copy, 'remove', '-r', fmap_dst,
'-n', cbfsname]) ])
except CmdError:
pass # the most likely error is that the file doesn't already exist
# now add the files
for val in cbfs_config.itervalues():
f = val.split(' ')
command = f[0]
cbfsname = f[2]
args = f[3:]
if command == 'remove':
# Call through shell so variable expansion can happen. With a change
# to the ebuild this enables specifying filename arguments to
# cbfstool as -f romstage.elf${COREBOOT_VARIANT} and have that be
# resolved to romstage.elf.serial when appropriate.
self._tools.Run('sh', [ '-c',
' '.join(['cbfstool', cb_copy, command, '-r', fmap_dst,
'-n', cbfsname] + args)],
def _CreateCorebootStub(self, pack, coreboot):
"""Create a coreboot boot stub and add pack properties.
pack: a PackFirmware object describing the firmware image to build.
coreboot: Path to coreboot.rom
bootstub = os.path.join(self._tools.outdir, 'coreboot-full.rom')
shutil.copyfile(self._tools.Filename(coreboot), bootstub)
pack.AddProperty('coreboot', bootstub)
pack.AddProperty('image', bootstub)
# Add files to to RO and RW CBFS if provided.
if self.cbfs_files:
self._AddCbfsFiles(bootstub, self.cbfs_files)
# Create a coreboot copy to use as a scratch pad. Order matters. The
# cbfs_files were added prior to this action. That's so the RW CBFS
# regions inherit the files from the RO CBFS region. Additionally,
# include the full FMAP within the file.
cb_copy = os.path.abspath(os.path.join(self._tools.outdir, 'cb_with_fmap'))
self._tools.WriteFile(cb_copy, self._tools.ReadFile(bootstub))
binary = self._tools.ReadFile(bootstub)
fmap_offset, fmap = pack.GetFmap()
if len(binary) < fmap_offset + len(fmap):
raise CmdError('FMAP will not fit')
# Splice in FMAP data.
binary = binary[:fmap_offset] + fmap + binary[fmap_offset + len(fmap):]
self._tools.WriteFile(cb_copy, binary)
# Publish where coreboot is with the FMAP data.
pack.AddProperty('cb_with_fmap', cb_copy)
# Add files to to RO CBFS if provided. This done here such that the
# copy above does not contain the RO CBFS files.
if self.rocbfs_files:
self._AddCbfsFiles(bootstub, self.rocbfs_files)
# As a final step, do whatever is requested in /flash/ro-boot/cbfs-files.
# It's done after creating the copy for the RW regions so that ro-boot's
# cbfs-files property has no side-effects on the RW regions.
node = self.fdt.GetFlashNode('ro', 'boot')
self._ProcessCbfsFileProperty(os.path.abspath(bootstub), node)
def _PackOutput(self, msg):
"""Helper function to write output from PackFirmware (verbose level 2).
This is passed to PackFirmware for it to use to write output.
msg: Message to display.
def _FmapNameByPath(self, path):
""" Take list of names to form node path. Return FMAP name.
Obtain the FMAP name described by the node path.
path: list forming a node path.
FMAP name of fdt node.
CmdError if path not found.
lbl = self.fdt.GetLabel(self.fdt.GetFlashNode(*path))
return re.sub('-', '_', lbl).upper()
def _PrepareCbfsHash(self, pack, blob_name):
"""Create blob in rw-{a,b}-boothash with 'cbfstool hashcbfs'.
When the blob name is defined as cbfshash/<section>/<subsection>, fill the
<section>_<subsection> area in the flash map with CBFS hash generated
using the 'cbfstool hashcbfs' command.
pack: a PackFirmware object describing the firmware image to build.
blob_name: a string, blob name describing what FMAP section this CBFS
copy is destined to
CmdError if cbfs-files node has incorrect parameters.
BlobDeferral if the CBFS region is not populated yet or if the coreboot
image with fmap is not available yet.
cb_copy = pack.GetProperty('cb_with_fmap')
if cb_copy is None:
raise BlobDeferral("Waiting for '%s'" % cb_copy)
part_sections = blob_name.split('/')[1:]
fmap_dst = self._FmapNameByPath(part_sections)
# Example of FDT ndoes asking for CBFS hash:
# rw-b-boot {
# label = "fw-main-b";
# reg = <0x00700000 0x002dff80>;
# type = "blob cbfs/rw/b-boot";
# };
# rw-b-boothash {
# label = "fw-main-b-hash";
# reg = <0x009dff80 0x00000040>;
# type = "blob cbfshash/rw/b-boothash";
# cbfs-node = "cbfs/rw/b-boot";
# };
hash_node = self.fdt.GetFlashNode(*part_sections)
cbfs_blob_name = self.fdt.GetString(hash_node, 'cbfs-node')
if not pack.GetProperty(cbfs_blob_name):
raise BlobDeferral("Waiting for '%s'" % cbfs_blob_name)
cbfs_node_path = cbfs_blob_name.split('/')[1:]
fmap_src = self._FmapNameByPath(cbfs_node_path)
# Compute CBFS hash and place it in the corect spot.
self._tools.Run('cbfstool', [cb_copy, 'hashcbfs', '-r', fmap_dst,
'-R', fmap_src, '-A', 'sha256'])
# Base address and size of the desitnation partition
base, size = self.fdt.GetFlashPart(*part_sections)
# And extract the blob for the FW section
rw_section = os.path.join(self._tools.outdir, '_'.join(part_sections))
pack.AddProperty(blob_name, rw_section)
def _PrepareCbfs(self, pack, blob_name):
"""Create CBFS blob in rw-boot-{a,b} FMAP sections.
When the blob name is defined as cbfs#<section>#<subsection>, fill the
<section>_<subsection> area in the flash map with a CBFS copy, putting the
CBFS header of the copy at the base of the section.
If --coreboot-elf parameter was specified during cros_bumdle_firmware
invocation, add the parameter of this option as the payload to the new
CBFS instance.
pack: a PackFirmware object describing the firmware image to build.
blob_name: a string, blob name describing what FMAP section this CBFS
copy is destined to
CmdError if cbfs-files node has incorrect parameters.
BlobDeferral if coreboot image with fmap is not available yet.
cb_copy = pack.GetProperty('cb_with_fmap')
if cb_copy is None:
raise BlobDeferral("Waiting for 'cb_with_fmap' property")
part_sections = blob_name.split('/')[1:]
fmap_src = self._FmapNameByPath('ro-boot'.split('-'))
fmap_dst = self._FmapNameByPath(part_sections)
# Base address and size of the desitnation partition
base, size = self.fdt.GetFlashPart(*part_sections)
# Copy CBFS to the required offset
self._tools.Run('cbfstool', [cb_copy, 'copy', '-r', fmap_dst,
'-R', fmap_src])
# Add a CBFS master header for good measure
self._tools.Run('cbfstool', [cb_copy, 'add-master-header',
'-r', fmap_dst])
# Add coreboot payload if so requested. Note that the some images use
# different payload for the rw sections, which is passed in as the value
# of the --uboot option in the command line.
if self.uboot_fname:
payload_fname = self.uboot_fname
elif self.coreboot_elf:
payload_fname = self.coreboot_elf
payload_fname = None
if payload_fname:
self._tools.Run('cbfstool', [
cb_copy, 'add-payload', '-f', payload_fname,
'-n', 'fallback/payload', '-c', 'lzma' , '-r', fmap_dst])
if self.ecrw_fname:
self._tools.Run('cbfstool', [
cb_copy, 'add', '-f', self.ecrw_fname, '-t', 'raw',
'-n', 'ecrw', '-A', 'sha256', '-r', fmap_dst ])
if self.pdrw_fname:
self._tools.Run('cbfstool', [
cb_copy, 'add', '-f', self.pdrw_fname, '-t', 'raw',
'-n', 'pdrw', '-A', 'sha256', '-r', fmap_dst ])
# Check if there's an advanced CBFS configuration request
node = self.fdt.GetFlashNode(*part_sections)
self._ProcessCbfsFileProperty(cb_copy, node)
# And extract the blob for the FW section
rw_section = os.path.join(self._tools.outdir, '_'.join(part_sections))
pack.AddProperty(blob_name, rw_section)
def _BuildBlob(self, pack, fdt, blob_type):
"""Build the blob data for a particular blob type.
pack: a PackFirmware object describing the firmware image to build.
fdt: an fdt object including image layout information
blob_type: The type of blob to create data for. Supported types are:
coreboot A coreboot image (ROM plus U-boot and .dtb payloads).
signed Nvidia T20/T30 signed image (BCT, U-Boot, .dtb).
CmdError if a command fails.
BlobDeferral if a blob is waiting for a dependency.
# stupid pylint insists that sha256 is not in hashlib.
# pylint: disable=E1101
if blob_type == 'coreboot':
self._CreateCorebootStub(pack, self.coreboot_fname)
elif blob_type == 'legacy':
pack.AddProperty('legacy', self.seabios_fname)
elif blob_type == 'signed':
bootstub, signed = self._CreateBootStub(self.uboot_fname, fdt,
pack.AddProperty('bootstub', bootstub)
pack.AddProperty('signed', signed)
pack.AddProperty('image', signed)
elif blob_type == 'exynos-bl1':
pack.AddProperty(blob_type, self.exynos_bl1)
# TODO( Deprecate ecbin
elif blob_type in ['ecrw', 'ecbin']:
pack.AddProperty('ecrw', self.ecrw_fname)
pack.AddProperty('ecbin', self.ecrw_fname)
elif blob_type == 'pdrw':
pack.AddProperty('pdrw', self.pdrw_fname)
elif blob_type == 'ecrwhash':
ec_hash_file = os.path.join(self._tools.outdir, 'ec_hash.bin')
ecrw = self._tools.ReadFile(self.ecrw_fname)
hasher = hashlib.sha256()
self._tools.WriteFile(ec_hash_file, hasher.digest())
pack.AddProperty(blob_type, ec_hash_file)
elif blob_type == 'pdrwhash':
pd_hash_file = os.path.join(self._tools.outdir, 'pd_hash.bin')
pdrw = self._tools.ReadFile(self.pdrw_fname)
hasher = hashlib.sha256()
self._tools.WriteFile(pd_hash_file, hasher.digest())
pack.AddProperty(blob_type, pd_hash_file)
elif blob_type == 'ecro':
# We cannot have an fmap in the EC image since there can be only one,
# which is the main fmap describing the whole image.
# Ultimately the EC will not have an fmap, since with software sync
# there is no flashrom involvement in updating the EC flash, and thus
# no need for the fmap.
# For now, mangle the fmap name to avoid problems.
updated_ecro = os.path.join(self._tools.outdir, 'updated-ecro.bin')
data = self._tools.ReadFile(self.ecro_fname)
data = re.sub('__FMAP__', '__fMAP__', data)
self._tools.WriteFile(updated_ecro, data)
pack.AddProperty(blob_type, updated_ecro)
elif blob_type.startswith('exynos-bl2'):
# We need to configure this per node, so do it later
elif blob_type.startswith('cbfshash'):
self._PrepareCbfsHash(pack, blob_type)
elif blob_type.startswith('cbfs'):
self._PrepareCbfs(pack, blob_type)
elif pack.GetProperty(blob_type):
elif blob_type in self.blobs:
pack.AddProperty(blob_type, self.blobs[blob_type])
raise CmdError("Unknown blob type '%s' required in flash map" %
def _BuildBlobs(self, pack, fdt):
"""Build the blob data for the list of blobs in the pack.
pack: a PackFirmware object describing the firmware image to build.
fdt: an fdt object including image layout information
CmdError if a command fails.
BlobDeferral if dependencies cannot be met because of cycles.
blob_list = pack.GetBlobList()
self._out.Info('Building blobs %s\n' % blob_list)
complete = False
deferred_list = []
# Build blobs allowing for dependencies between blobs. While this is
# an potential O(n^2) operation, in practice most blobs aren't dependent
# and should resolve in a few passes.
while not complete:
orig = set(blob_list)
for blob_type in blob_list:
self._BuildBlob(pack, fdt, blob_type)
except (BlobDeferral):
if not deferred_list:
complete = True
# If deferred is the same as the original no progress is being made.
if not orig - set(deferred_list):
raise BlobDeferral("Blob cyle '%s'" % orig)
# Process the deferred blobs
blob_list = deferred_list[:]
deferred_list = []
def _CreateImage(self, gbb, fdt):
"""Create a full firmware image, along with various by-products.
This uses the provided u-boot.bin, fdt and bct to create a firmware
image containing all the required parts. If the GBB is not supplied
then this will just return a signed U-Boot as the image.
gbb: a string, full path to the GBB file, or empty if a GBB is not
fdt: an fdt object containing required information.
Path to image file
self._out.Notice("Model: %s" % fdt.GetString('/', 'model'))
pack = PackFirmware(self._tools, self._out)
if self._force_rw:
fdt.PutInteger('/flash/rw-a-vblock', 'preamble-flags', 0)
fdt.PutInteger('/flash/rw-b-vblock', 'preamble-flags', 0)
if self._force_efs:
fdt.PutInteger('/chromeos-config', 'early-firmware-selection', 1)
pack.use_efs = fdt.GetInt('/chromeos-config', 'early-firmware-selection',
pack.SelectFdt(fdt, self._board)
# Get all our blobs ready
if self.uboot_fname:
pack.AddProperty('boot', self.uboot_fname)
if self.skeleton_fname:
pack.AddProperty('skeleton', self.skeleton_fname)
pack.AddProperty('dtb', fdt.fname)
# If we are writing a kernel, add its offset from TEXT_BASE to the fdt.
if self.kernel_fname:
fdt.PutInteger('/config', 'kernel-offset', pack.image_size)
if gbb:
pack.AddProperty('gbb', gbb)
# Build the blobs out.
self._BuildBlobs(pack, fdt)
self._out.Progress('Packing image')
if gbb:
fwid = '.'.join([
re.sub('[ ,]+', '_', fdt.GetString('/', 'model')),
self._out.Notice('Firmware ID: %s' % fwid)
pack.AddProperty('fwid', fwid)
pack.AddProperty('keydir', self._keydir)
# Some blobs need to be configured according to the node they are in.
todo = pack.GetMissingBlobs()
for blob in todo:
if blob.key.startswith('exynos-bl2'):
bl2 = ExynosBl2(self._tools, self._out)
pack.AddProperty(blob.key, bl2.MakeSpl(pack, fdt, blob,
# Record position and size of all blob members in the FDT
# Recalculate the Exynos BL2, since it may have a hash. The call to
# UpdateBlobPositionsAndHashes() may have updated the hash-target so we
# need to recalculate the hash.
for blob in todo:
if blob.key.startswith('exynos-bl2'):
bl2 = ExynosBl2(self._tools, self._out)
pack.AddProperty(blob.key, bl2.MakeSpl(pack, fdt, blob,
# Make a copy of the fdt for the bootstub
fdt_data = self._tools.ReadFile(fdt.fname)
if self.uboot_fname:
uboot_data = self._tools.ReadFile(self.uboot_fname)
uboot_copy = os.path.join(self._tools.outdir, 'u-boot.bin')
self._tools.WriteFile(uboot_copy, uboot_data)
uboot_dtb = os.path.join(self._tools.outdir, 'u-boot-dtb.bin')
self._tools.WriteFile(uboot_dtb, uboot_data + fdt_data)
# Fix up the coreboot image here, since we can't do this until we have
# a final device tree binary.
if 'coreboot' in pack.GetBlobList():
bootstub = pack.GetProperty('coreboot')
fdt = fdt.Copy(os.path.join(self._tools.outdir, 'bootstub.dtb'))
if self.coreboot_elf:
self._tools.Run('cbfstool', [bootstub, 'add-payload', '-f',
self.coreboot_elf, '-n', 'fallback/payload', '-c', 'lzma'])
elif self.uboot_fname:
text_base = 0x1110000
# This is the the 'movw $GD_FLG_COLD_BOOT, %bx' instruction
# 1110015: 66 bb 00 01 mov $0x100,%bx
marker = struct.pack('<L', 0x0100bb66)
pos = uboot_data.find(marker)
if pos == -1 or pos > 0x100:
raise ValueError('Cannot find U-Boot cold boot entry point')
entry = text_base + pos
self._out.Notice('U-Boot entry point %#08x' % entry)
self._tools.Run('cbfstool', [bootstub, 'add-flat-binary', '-f',
uboot_dtb, '-n', 'fallback/payload', '-c', 'lzma',
'-l', '%#x' % text_base, '-e', '%#x' % entry])
self._tools.Run('cbfstool', [bootstub, 'add', '-f', fdt.fname,
'-n', 'u-boot.dtb', '-t', '0xac'])
data = self._tools.ReadFile(bootstub)
bootstub_copy = os.path.join(self._tools.outdir, 'coreboot-8mb.rom')
self._tools.WriteFile(bootstub_copy, data)
# Use offset and size from fmap.dts to extract CBFS area from coreboot.rom
cbfs_offset, cbfs_size = fdt.GetFlashPart('ro', 'boot')
self._tools.WriteFile(bootstub, data[cbfs_offset:cbfs_offset+cbfs_size])
pack.AddProperty('fdtmap', fdt.fname)
image = os.path.join(self._tools.outdir, 'image.bin')
pack.PackImage(self._tools.outdir, image)
pack.AddProperty('image', image)
image = pack.GetProperty('image')
self._tools.OutputSize('Final image', image)
return image, pack
def SelectFdt(self, fdt_fname, use_defaults):
"""Select an FDT to control the firmware bundling
We make a copy of this which will include any on-the-fly changes we want
to make.
fdt_fname: The filename of the fdt to use.
use_defaults: True to use a default FDT name if available, and to add
a full path to the provided filename if necessary.
The Fdt object of the original fdt file, which we will not modify.
ValueError if no FDT is provided (fdt_fname is None and use_defaults is
if use_defaults:
fdt_fname = self._CheckFdtFilename(fdt_fname)
if not fdt_fname:
raise ValueError('Please provide an FDT filename')
fdt = Fdt(self._tools, fdt_fname)
self._fdt_fname = fdt_fname
# For upstream, select the correct architecture .dtsi manually.
if self._board == 'link' or 'x86' in self._board:
arch_dts = 'coreboot.dtsi'
elif self._board == 'daisy':
arch_dts = 'exynos5250.dtsi'
arch_dts = 'tegra124.dtsi'
fdt = fdt.Copy(os.path.join(self._tools.outdir, 'updated.dtb'))
# Get the flashmap so we know what to build. For board variants use the
# main board name as the key (drop the _<variant> suffix).
default_flashmap = default_flashmaps.get(self._board.split('_')[0], [])
if not fdt.GetProp('/flash', 'reg', ''):
# Only check for /iram and /config nodes for boards that require it.
if self._board in ('daisy', 'peach'):
# Insert default values for any essential properties that are missing.
# This should only happen for upstream U-Boot, until our changes are
# upstreamed.
if not fdt.GetProp('/iram', 'reg', ''):
self._out.Warning('Cannot find /iram, using default')
fdt.InsertNodes([i for i in default_flashmap if i['path'] == '/iram'])
# Sadly the pit branch has an invalid /memory node. Work around it
# for now.
if (not fdt.GetProp('/memory', 'reg', '') or
fdt.GetIntList('/memory', 'reg')[0] == 0):
self._out.Warning('Cannot find /memory, using default')
fdt.InsertNodes([i for i in default_flashmap if i['path'] == '/memory'])
if not fdt.GetProp('/config', 'samsung,bl1-offset', ''):
self._out.Warning('Missing properties in /config, using defaults')
fdt.InsertNodes([i for i in default_flashmap if i['path'] == '/config'])
# Remember our board type.
fdt.PutString('/chromeos-config', 'board', self._board)
self.fdt = fdt
return fdt
def Start(self, hardware_id, output_fname, show_map):
"""This creates a firmware bundle according to settings provided.
- Checks options, tools, output directory, fdt.
- Creates GBB and image.
hardware_id: Hardware ID to use for this board. If None, then the
default from the Fdt will be used
output_fname: Output filename for the image. If this is not None, then
the final image will be copied here.
show_map: Show a flash map, with each area's name and position
Filename of the resulting image (not the output_fname copy).
if self._small or self.fdt.GetProp('/config', 'nogbb', 'any') != 'any':
gbb = '' # Building a small image or `nogbb' is requested in device tree.
gbb = self._CreateGoogleBinaryBlock(hardware_id)
# This creates the actual image.
image, pack = self._CreateImage(gbb, self.fdt)
if show_map:
if output_fname:
shutil.copyfile(image, output_fname)
self._out.Notice("Output image '%s'" % output_fname)
return image, pack.props