blob: ab12d7db46521cee4cd6776b1d2451be37d7550f [file] [log] [blame]
# Copyright (c) 2013 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 supports creating Exynos bootprom images."""
import hashlib
import os
import struct
from tools import CmdError
HASH_ALGO_LEN = 32
HASH_LEN = 32
HASH_SIGNATURE = 0xdead4eef
HASH_VERSION = 1
HASH_FLAGS = 0
HASH_HEADER_LEN = 16
class ExynosBl2(object):
"""Class for processing Exynos SPL blob.
Second phase loader (SPL) is also called boot loader 2 (BL2), these terms
mean the same and are used in this class interchangeably.
SPL is a binary blob which is in fact a short program running from internal
SRAM. It initializes main DRAM and loads the actual boot loader after that.
The program is encapsulated using one of two methods - fixed or variable
size. Both methods provide rudimentary checksum protection.
SPL is supposed to know some details about the hardware it runs on. This
information is stored in the so called machine parameters structure in the
blob. Some of it is available at compile time, but most of it comes form the
platform specific flat device tree. SPL generated by u-boot make file
includes machine parameter structure with default configuration values, not
suitable to run on actual hardware.
This class provides the following services:
- check integrity of the passed in SPL blob, and determining its
the encapsulation method along the way.
- parse the passed in device tree and pack the retrieved information into
the machine parameters structure. The structure location in the blob is
identified by a 4 byte structure header signature.
Note that this method of finding the structure in the blob is quite
brittle: it would silently produce catastrophically wrong result if for
some reason, this same pattern is present anywhere in the blob above the
structure.
- update the checksum as appropriate for the detected format, and save the
modified SPL in a file.
Attributes:
_tools: A tools.Tools object to use for external tools, provided by the
caller
_out: A cros_output.Output object to use for console output, provided by
the caller
_spl_type: an enum defined below, describing type (fixed of variable size)
of the SPL being handled
_spl_data: a binary blob representing the SPL data being handled. It comes
as the value read from the u-boot makefile generated image and
goes as an enhanced image including the updated machine parameters
structure and possibly concatenated with the hash and revision
table.
"""
VAR_SPL = 1
FIXED_SPL = 2
def __init__(self, tools, output):
"""Set up a new object."""
self._tools = tools
self._out = output
self._spl_type = None
self.spl_source = 'straps' # SPL boot according to board settings
self._spl_data = None # SPL blob to configure
def _BootingUsingEFS(self, fdt, use_efs_memory):
"""Check if we are booting using early-firmware-selection.
This is just a helper function to avoid using the same logic in many
places.
Args:
fdt: Device tree file to use.
use_efs_memory: True to use early-firmware-selection memory (i.e. IRAM),
False, to ignore it.
Returns:
True if EFS is enabled and we are configured to use EFS memory, else
False.
"""
return (use_efs_memory and
fdt.GetInt('/chromeos-config', 'early-firmware-selection', 0))
def _GetAddress(self, fdt, use_efs_memory, name, config_node='/config',
allow_none=False):
"""Work out the correct address for a region of memory.
This deals with EFS and the memory map automatically.
Args:
fdt: Device tree file containing memory map.
use_efs_memory: True to return the address in EFS memory (i.e. SRAM),
False to use SDRAM
name: Name of the region to look up, e.g. 'u-boot'
config_node: Node containing configuration information
allow_none: True if it is OK to find nothing.
Returns:
Address to load that region, or None if none.
"""
efs_suffix = ''
if self._BootingUsingEFS(fdt, use_efs_memory):
efs_suffix = ',efs'
# Use the correct memory section, and then find the offset in that.
default = 'none' if allow_none else None
memory = fdt.GetString(config_node, '%s-memory%s' % (name, efs_suffix),
default)
if memory == 'none':
return None
base = fdt.GetIntList(memory, 'reg')[0]
offset = fdt.GetIntList(config_node, '%s-offset%s' % (name, efs_suffix))[0]
addr = base + offset
return addr
def GetUBootAddress(self, fdt, use_efs_memory):
"""Work out the correct address for loading U-Boot.
This deals with EFS and the memory map automatically.
Args:
fdt: Device tree file containing memory map.
use_efs_memory: True to return the address in EFS memory (i.e. SRAM),
False to use SDRAM
Returns:
Address to load U-Boot
"""
addr = self._GetAddress(fdt, use_efs_memory, 'u-boot')
self._out.Notice('EFS: Loading U-Boot to %x' % addr)
return addr
def _GetRWSPLDetails(self, fdt, use_efs_memory):
if not self._BootingUsingEFS(fdt, use_efs_memory):
return 0, 0, 0
memory = fdt.GetString('/chromeos-config', 'rw-spl-memory,efs')
base = fdt.GetIntList(memory, 'reg')[0]
offset, size = fdt.GetIntList('/chromeos-config', 'rw-spl-offset,efs')
addr = base + offset
return 1, addr, size
def _MpRevMap(self, _unused1, offset, fdt, pos):
"""Add the revision map to the SPL blob.
Read the revison map table from the device tree, and place it in the SPL
blob. If we detect that there wasn't already space allocated for the
revision map we'll append it to the end of the SPL blob. If there was
already space for the revision map we'll overwrite the existing one.
The revision map could be anywhere in the SPL. We store an offset from the
beginning of the machine params structure as the value in machine params
to allow us to find the map. If the offset is 0 it means that there is no
revision map space allocated.
Args:
_unused1 - a single character string, machine parameter name
offset - If there's alreasy space for the revision map, this will be non-
zero and we can find it in the SPL data at "pos + offset".
fdt - the Fdt object representing the target device tree
pos - an int, offset of the machine parameter structure into data
Returns:
offset - The new location of the revision map.
"""
rev_map = 'google,board-rev-map'
try:
rev_table = fdt.GetIntList('/board-rev', rev_map)
except CmdError:
self._out.Info('No value for %s' % rev_map)
return 0
extra = struct.pack('%dB' % len(rev_table), *rev_table)
if offset:
self._spl_data = (self._spl_data[:pos + offset] +
extra +
self._spl_data[pos + offset + len(extra):])
else:
# offset of the revision table from machine param table
offset = len(self._spl_data) - pos
self._spl_data += extra
return offset
def _UpdateParameters(self, fdt, spl_load_offset, spl_load_size, pos,
use_efs_memory, skip_sdram_init):
"""Update the parameters in a BL2 blob.
We look at the list in the parameter block, extract the value of each
from the device tree, and write that value to the parameter block.
Args:
fdt: Device tree containing the parameter values.
spl_load_offset: Offset in boot media that SPL must start loading (bytes)
spl_load_size: Size of U-Boot image that SPL must load
pos: The position of the start of the parameter block.
use_efs_memory: True to return the address in EFS memory (i.e. SRAM),
False to use SDRAM
skip_sdram_init: True to skip SDRAM initialization.
"""
version, size = struct.unpack('<2L', self._spl_data[pos + 4:pos + 12])
if version != 1:
raise CmdError("Cannot update machine parameter block version '%d'" %
version)
if size < 0 or pos + size > len(self._spl_data):
raise CmdError('Machine parameter block size %d is invalid: '
'pos=%d, size=%d, space=%d, len=%d' %
(size, pos, size, len(self._spl_data)
- pos, len(self._spl_data)))
#
# A dictionary of functions processing machine parameters. This is being
# introduced after more than 20 parameters have been already defined and
# are handled by the ugly if/elif/... construct below. It will be
# refactored eventually (one should hope), no new parameters' processing
# should be added there.
#
# The key of the dictionary is the parameter name, the value is a list.
# the first element of the list is the function to call to process the
# parameter, the rest of the elements are parameters to pass to the
# function.
#
# The first three parameters passed to the function are always prepended
# to those obtained from the list and are as follows:
#
# - the machine parameter name (one character string)
# - value read from the appropriate spot of the machine param structure,
# as generated by the u-boot makefile, a - 32 bit int
# - fdt object representing the target FDT
#
# The function is expected to return a 32 bit value to plug into the
# machine parameters structure.
#
mp_router = {
't': [self._MpRevMap, pos]
}
# Move past the header and read the parameter list, which is terminated
# with \0.
pos += 12
param_list = struct.unpack('<%ds' % (len(self._spl_data) - pos),
self._spl_data[pos:])[0]
param_len = param_list.find('\0')
param_list = param_list[:param_len]
pos += (param_len + 4) & ~3
# Use this to detect a missing value from the fdt.
not_given = 'not-given-invalid-value'
# Work through the parameters one at a time, adding each value
new_data = ''
upto = 0
for param in param_list:
value = struct.unpack('<1L', self._spl_data[pos + upto:pos + upto + 4])[0]
if param in mp_router:
value = mp_router[param][0](param, value, fdt, *mp_router[param][1:])
elif param == 'm':
mem_type = fdt.GetString('/dmc', 'mem-type', not_given)
if mem_type == not_given:
mem_type = 'ddr3'
self._out.Warning("No value for memory type: using '%s'" % mem_type)
mem_types = ['ddr2', 'ddr3', 'lpddr2', 'lpddr3']
if mem_type not in mem_types:
raise CmdError("Unknown memory type '%s'" % mem_type)
value = mem_types.index(mem_type)
self._out.Info(' Memory type: %s (%d)' % (mem_type, value))
elif param == 'M':
mem_manuf = fdt.GetString('/dmc', 'mem-manuf', not_given)
if mem_manuf == not_given:
mem_manuf = 'samsung'
self._out.Warning("No value for memory manufacturer: using '%s'" %
mem_manuf)
mem_manufs = ['autodetect', 'elpida', 'samsung']
if mem_manuf not in mem_manufs:
raise CmdError("Unknown memory manufacturer: '%s'" % mem_manuf)
value = mem_manufs.index(mem_manuf)
self._out.Info(' Memory manufacturer: %s (%d)' % (mem_manuf, value))
elif param == 'f':
mem_freq = fdt.GetInt('/dmc', 'clock-frequency', -1)
if mem_freq == -1:
mem_freq = 800000000
self._out.Warning("No value for memory frequency: using '%s'" %
mem_freq)
mem_freq /= 1000000
if mem_freq not in [533, 667, 800]:
self._out.Warning("Unexpected memory speed '%s'" % mem_freq)
value = mem_freq
self._out.Info(' Memory speed: %d' % mem_freq)
elif param == 'a':
arm_freq = fdt.GetInt('/dmc', 'arm-frequency', -1)
if arm_freq == -1:
arm_freq = 1700000000
self._out.Warning("No value for ARM frequency: using '%s'" %
arm_freq)
arm_freq /= 1000000
value = arm_freq
self._out.Info(' ARM speed: %d' % arm_freq)
elif param == 'i':
i2c_addr = -1
lookup = fdt.GetString('/aliases', 'pmic', '')
if lookup:
i2c_addr, size = fdt.GetIntList(lookup, 'reg', 2)
if i2c_addr == -1:
self._out.Warning('No value for PMIC I2C address: using %#08x' %
value)
else:
value = i2c_addr
self._out.Info(' PMIC I2C Address: %#08x' % value)
elif param == 's':
serial_addr = -1
lookup = fdt.GetString('/aliases', 'console', '')
if lookup:
serial_addr, size = fdt.GetIntList(lookup, 'reg', 2)
if serial_addr == -1:
self._out.Warning('No value for Console address: using %#08x' %
value)
else:
value = serial_addr
self._out.Info(' Console Address: %#08x' % value)
elif param == 'v':
value = 31
self._out.Info(' Memory interleave: %#0x' % value)
elif param == 'u':
value = spl_load_size
self._out.Info(' U-Boot size: %#0x' % value)
elif param == 'S':
value = self.GetUBootAddress(fdt, use_efs_memory)
self._out.Info(' U-Boot start: %#0x' % value)
elif param == 'o':
value = spl_load_offset
self._out.Info(' U-Boot offset: %#0x' % value)
elif param == 'l':
load_addr = fdt.GetInt('/config', 'u-boot-load-addr', -1)
if load_addr == -1:
self._out.Warning("No value for U-Boot load address: using '%08x'" %
value)
else:
value = load_addr
self._out.Info(' U-Boot load address: %#0x' % value)
elif param == 'b':
# These values come from enum boot_mode in U-Boot's cpu.h
# For EFS we select SPI as the boot source always. We could support
# eMMC if we want to add EFS support for eMMC.
if (self.spl_source == 'spi' or
self._BootingUsingEFS(fdt, use_efs_memory)):
value = 20
elif self.spl_source == 'straps':
value = 32
elif self.spl_source == 'emmc':
value = 4
elif self.spl_source == 'usb':
value = 33
else:
raise CmdError("Invalid boot source '%s'" % self.spl_source)
self._out.Info(' Boot source: %#0x' % value)
elif param in ['r', 'R']:
records = fdt.GetIntList('/board-rev', 'google,board-rev-gpios',
None, '0 0')
gpios = []
for i in range(1, len(records), 3):
gpios.append(records[i])
gpios.extend([0, 0, 0, 0])
if param == 'r':
value = gpios[0] + (gpios[1] << 16)
self._out.Info(' Board ID GPIOs: tit0=%d, tit1=%d' % (gpios[0],
gpios[1]))
else:
value = gpios[2] + (gpios[3] << 16)
self._out.Info(' Board ID GPIOs: tit2=%d, tit3=%d' % (gpios[2],
gpios[3]))
elif param == 'w':
records = fdt.GetIntList('/config', 'google,bad-wake-gpios',
3, '0 0xffffffff 0')
value = records[1]
self._out.Info(' Bad Wake GPIO: %#x' % value)
elif param == 'z':
compress = fdt.GetString('/flash/ro-boot', 'compress', 'none')
compress_types = ['none', 'lzo']
if compress not in compress_types:
raise CmdError("Unknown compression type '%s'" % compress)
value = compress_types.index(compress)
self._out.Info(' Compression type: %#0x' % value)
elif param == 'c':
rtc_type = 0
try:
rtc_alias = fdt.GetString('/aliases/', 'rtc')
rtc_compat = fdt.GetString(rtc_alias, 'compatible')
if rtc_compat == 'samsung,s5m8767-pmic':
rtc_type = 1
elif rtc_compat == 'maxim,max77802-pmic':
rtc_type = 2
except CmdError:
self._out.Warning('Failed to find rtc')
value = rtc_type
elif param == 'W':
try:
records = fdt.GetIntList('/chromeos-config/vboot-flag-write-protect',
'gpio', 3)
value = records[1]
self._out.Info(' Write Protect GPIO: %#x' % value)
except CmdError:
self._out.Warning('No value for write protect GPIO: using %#x' %
value)
elif param in ['j', 'A', 'U']:
jump, addr, size = self._GetRWSPLDetails(fdt, use_efs_memory)
if param == 'j':
value = jump
self._out.Info(' Jump to RW SPL: %d' % value)
elif param == 'A':
value = addr
self._out.Info(' RW SPL addr: %#x' % value)
elif param == 'U':
value = size
self._out.Info(' RW SPL size: %#x' % value)
elif param == 'd':
value = 1 if skip_sdram_init else 0
self._out.Info(' Skip SDRAM init: %d' % value)
elif param == 'p':
addr = self._GetAddress(fdt, use_efs_memory, 'vboot-persist',
'/chromeos-config', True)
if addr is None:
value = 0
else:
value = addr
self._out.Info(' Vboot persist addr: %x' % value)
elif param == 'D':
value = fdt.GetBool('/config', 'spl-debug')
self._out.Info(' SPL debug: %d' % value)
else:
self._out.Warning("Unknown machine parameter type '%s'" % param)
self._out.Info(' Unknown value: %#0x' % value)
new_data += struct.pack('<L', value)
upto += 4
# Put the data into our block.
self._spl_data = self._spl_data[:pos] + new_data + self._spl_data[
pos + len(new_data):]
def _UpdateChecksum(self):
"""Update the BL2 size and checksum.
For the fixed size spl the checksum is a 4 byte sum of all the bytes in
the image before the last 4 bytes (which hold the checksum).
For the variable size SPL the first four bytes of the blob is the size of
the blob and the second four bytes is the sum of bytes in the rest of the
blob.
Raises:
CmdError if spl type is not set properly.
"""
if self._spl_type == self.FIXED_SPL:
checksum = sum(ord(x) for x in self._spl_data[:-4])
checksum_offset = len(self._spl_data) - 4
elif self._spl_type == self.VAR_SPL:
# Data size could have changed (the rev table could have been added).
if len(self._spl_data) % 4:
# Bl1 expects data size to be divisible by 4
self._spl_data += '\0' * (4 - len(self._spl_data) % 4)
self._spl_data = struct.pack('<L',
len(self._spl_data)) + self._spl_data[4:]
checksum = sum(ord(x) for x in self._spl_data[8:])
checksum_offset = 4
else:
raise CmdError('SPL type not set')
self._spl_data = self._spl_data[:checksum_offset] + struct.pack(
'<L', checksum) + self._spl_data[checksum_offset+4:]
def _UpdateHash(self, digest):
"""Update the BL2 hash.
The BL2 header may have a pointer to the hash block, but if not, then we
add it (at the end of SPL).
Args:
digest: The hash digest to write.
Raises:
CmdError if spl type is not variable size. We don't support this
function with fixed-sized SPL.
"""
if self._spl_type != self.VAR_SPL:
raise CmdError('Hash is only supported for variable-size SPL')
# See if there is already a hash there.
hash_offset = struct.unpack('<L', self._spl_data[8:12])[0]
if not hash_offset:
hash_offset = len(self._spl_data)
algo = 'sha256'.ljust(HASH_ALGO_LEN, '\x00')
hash_block = algo + digest
hash_block_len = len(hash_block) + HASH_HEADER_LEN
hash_hdr = struct.pack('<4L', HASH_SIGNATURE, HASH_VERSION, hash_block_len,
HASH_FLAGS)
self._spl_data = (self._spl_data[:hash_offset] + hash_hdr + hash_block +
self._spl_data[hash_offset + hash_block_len:])
# Update the size and hash_offset.
self._spl_data = struct.pack('<LLL', len(self._spl_data), 0, hash_offset
) + self._spl_data[12:]
self._out.Info(' Added hash: %s' % ''.
join(['%02x' % ord(d) for d in digest]))
def _VerifyBl2(self, loose_check):
"""Verify BL2 integrity.
Fixed size and variable size SPL have different formats. Determine format,
verify SPL integrity and save its type (fixed or variable size) for future
reference.
Args:
loose_check: a Boolean, if true - the variable size SPL blob could be
larger than the size value in the header
Raises:
CmdError if SPL blob is of unrecognizable format.
"""
data = self._spl_data # Cache it to improve readability.
# Variable size format is more sophisticated, check it first.
try:
size = struct.unpack('<I', data[:4])[0]
if size == len(data) or (loose_check and (size < len(data))):
check_sum = sum(ord(x) for x in data[8:size])
# Compare with header checksum
if check_sum == struct.unpack('<I', data[4:8])[0]:
# this is a variable size SPL
self._out.Progress('Variable size BL2 detected')
self._spl_type = self.VAR_SPL
return
# This is not var size spl, let's see if it's the fixed size one.
# Checksum is placed at a fixed location in the blob, as defined in
# tools/mkexynosspl.c in the u--boot tree. There are two possibilities
# for blob sizes - 14K or 30K. The checksum is put in the last 4 bytes
# of the blob.
#
# To complicate things further the blob here could have come not from
# mkexynosspl directly, it could have been pulled out of a previously
# bundled image. I that case it the blob will be in a chunk aligned to
# the closest 16K boundary.
blob_size = ((len(data) + 0x3fff) & ~0x3fff) - 2 * 1024
if blob_size == len(data) or (loose_check and (blob_size < len(data))):
check_sum = sum(ord(x) for x in data[:blob_size - 4])
if check_sum == struct.unpack('<I', data[blob_size - 4:blob_size])[0]:
self._spl_type = self.FIXED_SPL
self._out.Progress('Fixed size BL2 detected')
return
except IndexError:
# This will be thrown if bl2 is too small
pass
raise CmdError('Unrecognizable bl2 format')
def Configure(self, fdt, spl_load_offset, spl_load_size, orig_bl2, name='',
loose_check=False, digest=None, use_efs_memory=True,
skip_sdram_init=False):
"""Configure an Exynos BL2 binary for our needs.
We create a new modified BL2 and return its file name.
Args:
fdt: Device tree containing the parameter values.
spl_load_offset: Offset in boot media that SPL must start loading (bytes)
spl_load_size: Size of U-Boot image that SPL must load
orig_bl2: Filename of original BL2 file to modify.
name: a string, suffix to add to the generated file name
loose_check: if True - allow var size SPL blob to be larger, then the
size value in the header. This is necessary for cases when
SPL is pulled out of an image (and is padded).
digest: If not None, hash digest to add to this BL2 (a string of bytes).
use_efs_memory: True to return the address in EFS memory (i.e. SRAM),
False to use SDRAM
skip_sdram_init: True to skip SDRAM initialization.
Returns:
Filename of configured bl2.
Raises:
CmdError if machine parameter block could not be found.
"""
bl2 = os.path.join(self._tools.outdir, 'updated-spl%s.bin' % name)
self._out.Info('Configuring BL2 %s' % bl2)
self._spl_data = self._tools.ReadFile(orig_bl2)
self._VerifyBl2(loose_check)
# Locate the parameter block
marker = struct.pack('<L', 0xdeadbeef)
pos = self._spl_data.rfind(marker)
if not pos:
raise CmdError("Could not find machine parameter block in '%s'" %
orig_bl2)
self._UpdateParameters(fdt, spl_load_offset, spl_load_size,
pos, use_efs_memory, skip_sdram_init)
if digest:
self._UpdateHash(digest)
self._UpdateChecksum()
self._tools.WriteFile(bl2, self._spl_data)
return bl2
# pylint: disable=E1101
def MakeSpl(self, pack, fdt, blob, vanilla_bl2):
"""Create a configured SPL based on the supplied vanilla one.
This handles the process of working out what to configure in a SPL
and also doing it. The settings of what to configure are in the
flash map node which requested this SPL to be included. For example
a 'compress' property sets the type of compresion to use for the
payload that SPL loads.
Args:
pack: The PackFirmware object, providing access to the contents.
fdt: The device tree containing the flash map.
blob: A namedtuple with these members:
node - Full path to device tree node
key - Key name (e.g. exynos-bl2')
params - List of parameters to the node - the first element is the
list of files within the blob, for example 'boot,dtb'
vanilla_bl2: The original SPL that needs configuring.
Returns:
Filename of the configured SPL.
Raises:
CmdError if there are no parameters provided (and therefore no payload).
"""
spl_payload = blob.params
if not spl_payload:
raise CmdError('No parameters provided for Exynos SPL/BL2')
prop_list = spl_payload[0].split(',')
name = blob.key.split('.')
# At this stage name may be plain 'exynos-bl2', but it is possible to have
# different versions, named 'exynos-bl2.rw' for RW SPL, and
# 'exynos-bl2.rec' for recovery SPL (in fact anything else can be used).
# This logic selects the string to append to the standard name, i.e. we
# want '.rw' or '.rec', or an empty string if there is no suffix.
if len(name) > 1:
name = '.' + name[1]
else:
name = ''
compress = fdt.GetString(blob.node, 'compress', 'none')
if compress == 'none':
compress = None
data = pack.ConcatPropContents(prop_list, compress, False)[0]
spl_load_size = len(data)
# Figure out what flash region SPL needs to load.
payload = fdt.GetString(blob.node, 'payload', 'none')
if payload == 'none':
payload = '/flash/ro-boot'
self._out.Warning("No payload boot media for '%s' - using %s" %
(blob.node, payload))
spl_load_offset = fdt.GetIntList(payload, 'reg', 2)[0]
# Tell this SPL to use EFS memory (i.e. SRAM, if available) if we are
# loading ro-boot. Otherwise we are loading normal U-Boot, so will use
# SDRAM.
use_efs_memory = 'ro-boot' in prop_list
self._out.Info("BL2/SPL contains '%s', size is %d / %#x" %
(', '.join(prop_list), spl_load_size, spl_load_size))
if fdt.GetBool(blob.node, 'hash-target'):
hasher = hashlib.sha256()
hasher.update(data)
digest = hasher.digest()
else:
digest = None
skip_sdram_init = fdt.GetBool(blob.node, 'skip-sdram-init')
bl2 = self.Configure(fdt, spl_load_offset, spl_load_size, vanilla_bl2,
name=name, digest=digest,
use_efs_memory=use_efs_memory,
skip_sdram_init=skip_sdram_init)
return bl2