blob: 809e577c92bd9aec592a9b900fbd97a9666ce962 [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.
import binascii
import glob
import os
import re
import struct
import time
from exynos import ExynosBl2
from tools import CmdError
def RoundUp(value, boundary):
"""Align a value to the next power of 2 boundary.
Args:
value: The value to align.
boundary: The boundary value, e.g. 4096. Must be a power of 2.
Returns:
The rounded-up value.
"""
return (value + boundary - 1) & ~(boundary - 1)
class WriteFirmware:
"""Write firmware to a Chrome OS device using USB A-A cable or servo board
This class handles re-reflashing a board with new firmware using the Tegra's
built-in boot ROM feature. This works by putting the chip into a special mode
where it ignores any available firmware and instead reads it from a connected
host machine over USB.
In our case we use that feature to send U-Boot along with a suitable payload
and instructions to flash it to SPI flash. The payload is itself normally a
full Chrome OS image consisting of U-Boot, some keys and verification
information, images and a map of the flash memory.
Private attributes:
_servo_port: Port number to use to talk to servo with dut-control.
Special values are:
None: servo is not available.
0: any servo will do.
_preserved_dut_hub_sel: a string, preserved state of the dut_hub_sel
control
"""
_DOWNLOAD_FAILURE_MESSAGE = '** Load checksum error: check download tool **'
_SKIP_VERIFY_MESSAGE = 'Skipping verify'
_WRITE_FAILURE_MESSAGE = '** Readback checksum error, programming failed!! **'
_WRITE_SUCCESS_MESSAGE = 'Image Programmed Successfully'
def __init__(self, tools, fdt, output, bundle, update, verify):
"""Set up a new WriteFirmware object.
Args:
tools: A tools library for us to use.
fdt: An fdt which gives us some info that we need.
output: An output object to use for printing progress and messages.
bundle: A BundleFirmware object which created the image.
update: Use faster update algorithm rather then full device erase.
verify: Verify the write by doing a readback and CRC.
"""
self._tools = tools
self._fdt = fdt
self._out = output
self._bundle = bundle
self.text_base = self._fdt.GetInt('/chromeos-config', 'textbase', -1)
self._preserved_dut_hub_sel = ''
# For speed, use the 'update' algorithm and don't verify
self.update = update
self.verify = verify
# Use default servo port
self._servo_port = 0
# By default, no early firmware selection.
self.use_efs = False
def EnableUsbProgramming(self, args):
"""Reboot DUT in A-A mode via Servo, saving dut_hub_sel if necessary
This function starts the USB A-A boot by issuing a warm reset and pulling
some SoC-specific control pins. It also sets dut_hub_sel to
'dut_sees_servo' and preserves the old state of that control.
Args:
args - a list of strings in <control>:<value> format to be passed to
dut-control that contain the SoC-specific A-A pin settings
"""
if self._servo_port is None:
return
# Preserve and configure dut_hub_sel state.
self._out.Progress('Reseting board via servo')
required_dut_hub_sel = 'dut_sees_servo'
preserved_dut_hub_sel = self.DutControl(['dut_hub_sel',]
).strip().split(':')[-1]
if required_dut_hub_sel != preserved_dut_hub_sel:
args += ['dut_hub_sel:%s' % required_dut_hub_sel,]
self._preserved_dut_hub_sel = preserved_dut_hub_sel
args += ['warm_reset:on', 'sleep:.1', 'warm_reset:off', 'sleep:.5']
self.DutControl(args)
def DisableUsbProgramming(self, args):
"""Restore dut_hub_sel and SoC-specific pins to normal operation
Restores the dut_hub_sel value stored by EnableUsbProgramming and returns
additional SoC-specific pins to their default state.
Args:
args - a list of strings in <control>:<value> format to be passed to
dut-control that contain defaults for SoC-specific pin settings
"""
if self._servo_port is None:
return
if self._preserved_dut_hub_sel:
args += ['dut_hub_sel:%s' % self._preserved_dut_hub_sel,]
self._preserved_dut_hub_sel = ''
self.DutControl(args)
def SelectServo(self, servo):
"""Select the servo to use for writing firmware.
Args:
servo: String containing description of servo to use:
'none' : Don't use servo, generate an error on any attempt.
'any' : Use any available servo.
'<port>': Use servo with that port number.
"""
if servo == 'none':
self._servo_port = None
elif servo == 'any':
self._servo_port = 0
else:
self._servo_port = int(servo)
self._out.Notice('Servo port %s' % str(self._servo_port))
def _GetFlashScript(self, payload_size, flash_dest, checksum, ro_size=None):
"""Get the U-Boot boot command needed to flash U-Boot.
We leave a marker in the string for the load address of the image,
since this depends on the size of this script. This can be replaced by
the caller provided that the marker length is unchanged.
Args:
payload_size: Size of payload in bytes.
flash_dest: A dictionary of strings keyed by 'type' (nand, sdmmc,
or spi), 'bus', and 'dev'.
checksum: The checksum of the payload (an integer)
ro_size: Size of read-only partition. If set, split MMC image between
partition 1 (ro) and partition 2 (rw).
Returns:
A tuple containing:
The script, as a string ready to use as a U-Boot boot command, with an
embedded marker for the load address.
The marker string, which the caller should replace with the correct
load address as 8 hex digits, without changing its length.
The marker RW string, which the caller should replace with the correct
load address for the RW section as 8 hex digits, without changing
its length. This is only required if ro_size is set.
"""
replace_me = 'zsHEXYla'
replace_me_rw = 'zsHEXYrw'
page_size = 4096
boot_type = flash_dest['type']
if boot_type == 'sdmmc':
page_size = 512
update = self.update and boot_type == 'spi'
cmds = [
'setenv address 0x%s' % replace_me,
'setenv firmware_size %#x' % payload_size,
'setenv length %#x' % RoundUp(payload_size, page_size),
'setenv blocks %#x' % (RoundUp(payload_size, page_size) / page_size),
'setenv _crc "crc32 -v ${address} ${firmware_size} %08x"' %
checksum,
'setenv _clear "echo Clearing RAM; mw.b ${address} 0 ${length}"',
]
if ro_size:
rw_size = payload_size - ro_size
cmds.extend([
'setenv address_ro ${address}',
'setenv address_rw 0x%s' % replace_me_rw,
'setenv blocks_ro %#x' % (RoundUp(ro_size, page_size) / page_size),
'setenv blocks_rw %#x' % (RoundUp(rw_size, page_size) / page_size),
])
if boot_type == 'nand':
cmds.extend([
'setenv _init "echo Init NAND; nand info"',
'setenv _erase "echo Erase NAND; nand erase 0 ${length}"',
'setenv _write "echo Write NAND; nand write ${address} 0 ${length}"',
'setenv _read "echo Read NAND; nand read ${address} 0 ${length}"',
])
elif boot_type == 'sdmmc':
# In U-Boot, strings in double quotes have variables expanded to actual
# values. For reasons unclear, this expansion splits the single quoted
# argument into separate arguements for each word, which on exynos
# boards causes the command to exceed the maximum configured argument
# count. Passing the string in single quotes prevents this expansion,
# allowing variables to be expanded when run is called.
# crbug.com/260294
cmds.extend([
'setenv _init "echo Init EMMC; mmc rescan"',
'setenv _erase "echo Erase EMMC; "',
])
if ro_size:
# Write RO section to partition 1, RW to partition 2
cmds.extend([
"setenv _write 'echo Write EMMC;" \
" mmc open 0 1;" \
" mmc write ${address_ro} 0 ${blocks_ro};" \
" mmc close 0 1;" \
" mmc open 0 2;" \
" mmc write ${address_rw} 0 ${blocks_rw};" \
" mmc close 0 2'",
"setenv _read 'echo Read EMMC;" \
" mmc open 0 1;" \
" mmc read ${address_ro} 0 ${blocks_ro};" \
" mmc close 0 1;" \
" mmc open 0 2;" \
" mmc read ${address_rw} 0 ${blocks_rw};" \
" mmc close 0 2'",
])
else:
cmds.extend([
"setenv _write 'echo Write EMMC;" \
" mmc open 0 1;" \
" mmc write ${address} 0 ${blocks};" \
" mmc close 0 1'",
"setenv _read 'echo Read EMMC;" \
" mmc open 0 1;" \
" mmc read ${address} 0 ${blocks};" \
" mmc close 0 1'",
])
else:
if flash_dest['bus'] is None:
flash_dest['bus'] = '0'
if flash_dest['dev'] is None:
flash_dest['dev'] = '0'
cmds.extend([
'setenv _init "echo Init SPI; sf probe %s:%s"' %
(flash_dest['bus'], flash_dest['dev']),
'setenv _erase "echo Erase SPI; sf erase 0 ${length}"',
'setenv _write "echo Write SPI; sf write ${address} 0 ${length}"',
'setenv _read "echo Read SPI; sf read ${address} 0 ${length}"',
'setenv _update "echo Update SPI; sf update ${address} 0 ${length}"',
])
cmds.extend([
'echo Firmware loaded to ${address}, size ${firmware_size}, '
'length ${length}',
'if run _crc; then',
'run _init',
])
if update:
cmds += ['time run _update']
else:
cmds += ['run _erase', 'run _write']
if self.verify:
cmds += [
'run _clear',
'run _read',
'if run _crc; then',
'echo "%s"' % self._WRITE_SUCCESS_MESSAGE,
'else',
'echo',
'echo "%s"' % self._WRITE_FAILURE_MESSAGE,
'echo',
'fi',
]
else:
cmds += ['echo %s' % self._SKIP_VERIFY_MESSAGE]
cmds.extend([
'else',
'echo',
'echo "%s"' % self._DOWNLOAD_FAILURE_MESSAGE,
'fi',
])
script = '; '.join(cmds)
return script, replace_me, replace_me_rw
def _ReplaceAddr(self, data, replace_me, replacement):
"""Replace address in FDT
Detect and replace a placeholder address in the FDT.
Args:
data: FDT data to do replacement on
replace_me: String currently in FDT to replace
replacement: Replacement string
Returns:
Updated data with replacement
"""
if len(replace_me) is not len(replacement):
raise ValueError("Internal error: replacement string '%s' length does "
"not match new string '%s'" % (replace_me, replacement))
matches = len(re.findall(replace_me, data))
if matches != 1:
raise ValueError("Internal error: replacement string '%s' already "
"exists in the fdt (%d matches)" % (replace_me, matches))
return re.sub(replace_me, replacement, data)
def _PrepareFlasher(self, uboot, payload, flash_dest, ro_size=None):
"""Get a flasher ready for sending to the board.
The flasher is an executable image consisting of:
- U-Boot (u-boot.bin);
- a special FDT to tell it what to do in the form of a run command;
- (we could add some empty space here, in case U-Boot is not built to
be relocatable);
- the payload (which is a full flash image, or signed U-Boot + fdt).
Args:
uboot: Full path to u-boot.bin.
payload: Full path to payload.
flash_dest: A dictionary of strings keyed by 'type' (nand, sdmmc,
or spi), 'bus', and 'dev'.
boot_type: the src for bootdevice (nand, sdmmc, or spi)
ro_size: Size of read-only partition on emmc. If set, indicates that
the image should be split, with half written to partition 1, and
half written to partition 2.
Returns:
Filename of the flasher binary created.
"""
fdt = self._fdt.Copy(os.path.join(self._tools.outdir, 'flasher.dtb'))
fdt.PutInteger('/config', 'bootsecure', 0)
fdt.PutInteger('/config', 'silent-console', 0)
payload_data = self._tools.ReadFile(payload)
# Make sure that the checksum is not negative
checksum = binascii.crc32(payload_data) & 0xffffffff
script, replace_start, replace_rw = self._GetFlashScript(len(payload_data),
flash_dest, checksum, ro_size)
data = self._tools.ReadFile(uboot)
fdt.PutString('/config', 'bootcmd', script)
fdt_data = self._tools.ReadFile(fdt.fname)
# Work out where to place the payload in memory. This is a chicken-and-egg
# problem (although in case you haven't heard, it was the chicken that
# came first), so we resolve it by replacing the string after
# fdt.PutString has done its job.
#
# Correction: Technically, the egg came first. Whatever genetic mutation
# created the new species would have been present in the egg, but not the
# parent (since if it was in the parent, it would have been present in the
# parent when it was an egg).
#
# Question: ok so who laid the egg then?
payload_offset = len(data) + len(fdt_data)
# NAND driver expects 4-byte alignment. Just go whole hog and do 4K.
alignment = 0x1000
payload_offset = (payload_offset + alignment - 1) & ~(alignment - 1)
load_address = self.text_base + payload_offset,
new_str = '%08x' % load_address
fdt_data = self._ReplaceAddr(fdt_data, replace_start, new_str)
if ro_size:
new_str_rw = '%08x' % (load_address[0] + ro_size)
fdt_data = self._ReplaceAddr(fdt_data, replace_rw, new_str_rw)
# Now put it together.
data += fdt_data
data += "\0" * (payload_offset - len(data))
data += payload_data
flasher = os.path.join(self._tools.outdir, 'flasher-for-image.bin')
self._tools.WriteFile(flasher, data)
# Tell the user about a few things.
self._tools.OutputSize('U-Boot', uboot)
self._tools.OutputSize('Payload', payload)
self._out.Notice('Payload checksum %08x' % checksum)
self._tools.OutputSize('Flasher', flasher)
return flasher
def NvidiaFlashImage(self, flash_dest, uboot, bct, payload, bootstub):
"""Flash the image to SPI flash.
This creates a special Flasher binary, with the image to be flashed as
a payload. This is then sent to the board using the tegrarcm utility.
Args:
flash_dest: Destination for flasher, or None to not create a flasher.
This value is a dictionary of strings keyed by 'type', 'bus', and
'dev'.
uboot: Full path to u-boot.bin.
bct: Full path to BCT file (binary chip timings file for Nvidia SOCs).
payload: Full path to payload.
bootstub: Full path to bootstub, which is the payload without the
signing information (i.e. bootstub is u-boot.bin + the FDT)
Returns:
True if ok, False if failed.
"""
# Use a Regex to pull Boot type from BCT file.
match = re.compile('DevType\[0\] = NvBootDevType_(?P<boot>([a-zA-Z])+);')
bct_dumped = self._tools.Run('bct_dump', [bct]).splitlines()
# TODO(sjg): The boot type is currently selected by the bct, rather than
# flash_dest['type'] selecting which bct to use. This is a bit backwards.
# For now we go with the bct's idea.
flash_dest['type'] = filter(match.match, bct_dumped)
flash_dest['type'] = match.match(
flash_dest['type'][0]).group('boot').lower()
if flash_dest:
image = self._PrepareFlasher(uboot, payload, flash_dest)
elif bootstub:
image = bootstub
else:
image = payload
# If we don't know the textbase, extract it from the payload.
if self.text_base == -1:
data = self._tools.ReadFile(payload)
# Skip the BCT which is the first 64KB
self.text_base = self._bundle.DecodeTextBase(data[0x10000:])
self._out.Notice('TEXT_BASE is %#x' % self.text_base)
self._out.Progress('Uploading flasher image')
args = [
'--bct', bct,
'--bootloader', image,
'--loadaddr', "%#x" % self.text_base
]
# TODO(sjg): Check for existence of board - but chroot has no lsusb!
last_err = None
try:
# Set DUT into programmable state
self.EnableUsbProgramming(['t20_rec:on'])
for _ in range(10):
try:
# TODO(sjg): Use Chromite library so we can monitor output
self._tools.Run('tegrarcm', args, sudo=True)
self._out.Notice('Flasher downloaded - please see serial output '
'for progress.')
return True
except CmdError as cmdErr:
err = cmdErr.message
if 'retreiving platform info' in err:
self._out.Notice('tegrarcm failed (known bug, retrying):\n%s' % err)
continue
if not self._out.stdout_is_tty:
return False
# Only show the error output once unless it changes.
if not 'could not open USB device' in err:
raise CmdError('tegrarcm failed: %s' % err)
if err != last_err:
self._out.Notice(err)
last_err = err
if self._servo_port is None:
self._out.Progress('Please connect USB A-A cable and do a '
'recovery-reset', True)
time.sleep(1)
return False
finally:
# Restore servo state
self.DisableUsbProgramming(['t20_rec:off',
'spi2_buf_en:off',
'spi2_buf_on_flex_en:off',
'spi2_vref:off'])
def _WaitForUSBDevice(self, name, vendor_id, product_id, timeout=10):
"""Wait until we see a device on the USB bus.
Args:
name: Board type name
vendor_id: USB vendor ID to look for
product_id: USB product ID to look for
timeout: Timeout to wait in seconds
Returns
True if the device was found, False if we timed out.
"""
self._out.Progress('Waiting for board to appear on USB bus')
start_time = time.time()
while time.time() - start_time < timeout:
try:
args = ['-d', '%04x:%04x' % (vendor_id, product_id)]
self._tools.Run('lsusb', args, sudo=True)
self._out.Progress('Found %s board' % name)
return True
except CmdError:
pass
return False
def DutControl(self, args):
"""Run dut-control with supplied arguments.
The correct servo will be used based on self._servo_port. If servo use is
disabled, this function does nothing.
Args:
args: List of arguments to dut-control.
Returns:
a string, stdout generated by running the command
"""
if self._servo_port is None:
return '' # User has requested not to use servo
if self._servo_port:
args.extend(['-p', '%s' % self._servo_port])
return self._tools.Run('dut-control', args)
def WaitForCompletion(self):
"""Verify flash programming operation success.
The DUT is presumed to be programming flash with console capture mode on.
This function scans console output for the success or failure strings.
Raises:
CmdError if the following cases:
- none of the strings show up in the allotted time (2 minutes)
- console goes silent for more than 10 seconds
- one of the error messages seen in the stream
- misformatted output is seen in the stream
"""
_SOFT_DEADLINE_LIMIT = 10
_HARD_DEADLINE_LIMIT = 120
string_leftover = ''
soft_deadline = time.time() + _SOFT_DEADLINE_LIMIT
hard_deadline = soft_deadline + _HARD_DEADLINE_LIMIT - _SOFT_DEADLINE_LIMIT
if self.verify:
done_line = self._WRITE_SUCCESS_MESSAGE
else:
done_line = self._SKIP_VERIFY_MESSAGE
while True:
now = time.time()
if now > hard_deadline:
raise CmdError('Target console flooded, programming failed')
if now > soft_deadline:
raise CmdError('Target console dead, programming failed')
stream = self.DutControl(['cpu_uart_stream',])
match = re.search("^cpu_uart_stream:['\"](.*)['\"]\n", stream)
if not match:
raise CmdError('Misformatted console output: \n%s\n' % stream)
text = string_leftover + match.group(1)
strings = text.split('\\r')
string_leftover = strings.pop()
if strings:
soft_deadline = now + _SOFT_DEADLINE_LIMIT
for string in strings:
if done_line in string:
return True
if self._WRITE_FAILURE_MESSAGE in string:
raise CmdError('Readback verification failed!')
if self._DOWNLOAD_FAILURE_MESSAGE in string:
raise CmdError('Download failed!')
def _ExtractPayloadParts(self, payload, truncate_to_fdt):
"""Extract the BL1, BL2 and U-Boot parts from a payload.
An exynos image consists of 3 parts: BL1, BL2 and U-Boot/FDT.
This pulls out the various parts, puts them into files and returns
these files.
Args:
payload: Full path to payload.
truncate_to_fdt: Truncate the U-Boot image at the start of its
embedded FDT
Returns:
(bl1, bl2, image, uboot_offset) where:
bl1 is the filename of the extracted BL1
bl2 is the filename of the extracted BL2
image is the filename of the extracted U-Boot image
uboot_offset is the offset of U-Boot in the image
"""
# Pull out the parts from the payload
bl1 = os.path.join(self._tools.outdir, 'bl1.bin')
bl2 = os.path.join(self._tools.outdir, 'bl2.bin')
image = os.path.join(self._tools.outdir, 'u-boot-from-image.bin')
data = self._tools.ReadFile(payload)
try:
bl1_size = int(self._fdt.GetProps('/flash/pre-boot')['size'])
bl2_size = int(self._fdt.GetProps('/flash/spl')['size'])
uboot_offset = bl1_size + bl2_size
except (CmdError, KeyError):
self._out.Warning('No component nodes in the device tree')
# The BL1 is always 8KB - extract that part into a new file
# TODO(sjg@chromium.org): Perhaps pick these up from the fdt?
bl1_size = 0x2000
# Try to detect the BL2 size. We look for 0xea000014 or 0xea000013
# which is the 'B reset' instruction at the start of U-Boot. When
# U-Boot is LZO compressed, we look for a LZO magic instead.
start_data = [struct.pack('<L', 0xea000014),
struct.pack('<L', 0xea000013),
struct.pack('>B3s', 0x89, 'LZO')]
starts = [data.find(magic, bl1_size + 0x3800) for magic in start_data]
uboot_offset = None
for start in starts:
if start != -1 and (not uboot_offset or start < uboot_offset):
uboot_offset = start
if not uboot_offset:
raise ValueError('Could not locate start of U-Boot')
bl2_size = uboot_offset - bl1_size - 0x800 # 2KB gap after BL2
# Sanity check: At present we only allow 14KB and 30KB for SPL
allowed = [14, 30]
if (bl2_size >> 10) not in allowed:
raise ValueError('BL2 size is %dK - only %s supported' %
(bl2_size >> 10, ', '.join(
[str(size) for size in allowed])))
self._out.Notice('BL2 size is %dKB' % (bl2_size >> 10))
self._tools.WriteFile(bl1, data[:bl1_size])
self._tools.WriteFile(bl2, data[bl1_size:bl1_size + bl2_size])
# U-Boot itself starts at 24KB, after the gap. As a hack, truncate it
# to an assumed maximum size. As a secondary hack, locate the FDT
# and truncate U-Boot from that point. The correct FDT will be added
# when the image is written to the board.
# TODO(sjg@chromium.org): Get a proper flash map here so we know how
# large it is
uboot_data = data[uboot_offset:uboot_offset + 0xa0000]
if truncate_to_fdt:
fdt_magic = struct.pack('>L', 0xd00dfeed)
fdt_offset = uboot_data.rfind(fdt_magic)
uboot_data = uboot_data[:fdt_offset]
self._tools.WriteFile(image, uboot_data)
return bl1, bl2, image, uboot_offset
def ExynosFlashImage(self, flash_dest, flash_uboot, bl1, bl2, payload,
kernel):
"""Flash the image to SPI flash.
This creates a special Flasher binary, with the image to be flashed as
a payload. This is then sent to the board using the tegrarcm utility.
Args:
flash_dest: Destination for flasher, or None to not create a flasher.
This is a dictionary of strings keyed by 'type', 'bus', and 'dev'.
Valid options for 'type' are spi, sdmmc.
flash_uboot: Full path to u-boot.bin to use for flasher.
bl1: Full path to file containing BL1 (pre-boot).
bl2: Full path to file containing BL2 (SPL).
payload: Full path to payload.
kernel: Kernel to send after the payload, or None.
Returns:
True if ok, False if failed.
Raises:
CmdError if a supported Exynos model is not detected in the device tree.
"""
use_efs_memory = False
tools = self._tools
payload_bl1, payload_bl2, payload_image, spl_load_offset = (
self._ExtractPayloadParts(payload, flash_dest is not None))
bl2_handler = ExynosBl2(tools, self._out)
if flash_dest:
# If we don't have some bits, get them from the image
if not flash_uboot or not os.path.exists(tools.Filename(flash_uboot)):
self._out.Warning('Extracting U-Boot from payload')
flash_uboot = payload_image
if not bl1 or not os.path.exists(tools.Filename(bl1)):
self._out.Warning('Extracting BL1 from payload')
bl1 = payload_bl1
if not bl2 or not os.path.exists(tools.Filename(bl2)):
self._out.Warning('Extracting BL2 from payload')
bl2 = payload_bl2
else:
# Update BL2 machine parameters, as the BL2 passed in may not be
# updated. Also make sure it doesn't use early-firmware-selection
# since our flasher needs to run from SDRAM.
spl_load_size = os.stat(tools.Filename(bl2)).st_size
bl2 = bl2_handler.Configure(self._fdt, spl_load_offset, spl_load_size,
bl2, 'flasher', loose_check=True,
use_efs_memory=False)
# Set default values for Exynos targets.
if flash_dest['bus'] is None:
flash_dest['bus'] = 1
if flash_dest['dev'] is None:
flash_dest['dev'] = 0
# Try to determine RO section size
ro_size = None
if flash_dest['type'] == 'sdmmc':
try:
ro_size = self._fdt.GetFlashPartSize('wp', 'ro')
except (CmdError, ValueError):
self._out.Warning('Unable to detect RO section size')
image = self._PrepareFlasher(flash_uboot, payload, flash_dest, ro_size)
else:
bl1, bl2, image = payload_bl1, payload_bl2, payload_image
vendor_id = 0x04e8
product_id = 0x1234
self.EnableUsbProgramming(['fw_up:on', 'pwr_button:press'])
# If we have a kernel to write, create a new image with that added.
if kernel:
dl_image = os.path.join(self._tools.outdir, 'image-plus-kernel.bin')
data = self._tools.ReadFile(image)
# Pad the original payload out to the original length
data += '\0' * (os.stat(payload).st_size - len(data))
data += self._tools.ReadFile(kernel)
self._tools.WriteFile(dl_image, data)
else:
# See which type of memory we should load U-Boot to
used_size = self._fdt.GetFlashPartUsed('ro', 'boot')
if self.use_efs and used_size:
node = self._fdt.GetFlashNode('ro', 'boot')
props = self._fdt.GetProp(node, 'type,efs', 'none')
if props != 'none' and props[0] == 'blob':
parts = props[1].split(',')
use_efs_memory = 'ro-boot' in parts
# Truncate the image to just the size actually used
if use_efs_memory:
dl_image = os.path.join(self._tools.outdir, 'image-used.bin')
data = self._tools.ReadFile(image)
self._tools.WriteFile(dl_image, data[:used_size])
self._out.Warning('Truncating to used size %#x' % used_size)
else:
dl_image = image
self._out.Progress('Uploading image')
# This list tells us the memory region to use, the offset into that
# region and the filename to upload.
upload_list = [
['/iram', 'samsung,bl1-offset', bl1],
['/iram', 'samsung,bl2-offset', bl2],
['/memory', 'u-boot-offset', dl_image]
]
try:
for upto in range(len(upload_list)):
item = upload_list[upto]
if not self._WaitForUSBDevice('exynos', vendor_id, product_id, 4):
if upto == 0:
raise CmdError('Could not find Exynos board on USB port')
raise CmdError("Stage '%s' did not complete" % item[1])
self._out.Notice(item[2])
if upto == 0:
# The IROM needs roughly 200ms here to be ready for USB download
time.sleep(.5)
# Load U-Boot either to IRAM or SDRAM depending on EFS
if upto == 2:
addr = bl2_handler.GetUBootAddress(self._fdt, use_efs_memory)
else:
base = self._fdt.GetIntList(item[0], 'reg')[0]
offset = self._fdt.GetIntList('/config', item[1])[0]
addr = base + offset
self._out.Progress("Uploading stage '%s' to %x" % (item[1], addr))
args = ['-a', '%#x' % addr, '-f', item[2]]
self._tools.Run('smdk-usbdl', args, sudo=True)
finally:
# Make sure that the power button is released and dut_sel_hub state is
# restored, whatever happens
self.DisableUsbProgramming(['fw_up:off', 'pwr_button:release'])
if flash_dest is None:
self._out.Notice('Image downloaded - please see serial output '
'for progress.')
return True
def _GetDiskInfo(self, disk, item):
"""Returns information about a SCSI disk device.
Args:
disk: a block device name in sys/block, like '/sys/block/sdf'.
item: the item of disk information that is required.
Returns:
The information obtained, as a string, or '[Unknown]' if not found
"""
dev_path = os.path.join(disk, 'device')
# Search upwards and through symlinks looking for the item.
while os.path.isdir(dev_path) and dev_path != '/sys':
fname = os.path.join(dev_path, item)
if os.path.exists(fname):
with open(fname, 'r') as fd:
return fd.readline().rstrip()
# Move up a level and follow any symlink.
new_path = os.path.join(dev_path, '..')
if os.path.islink(new_path):
new_path = os.path.abspath(os.readlink(os.path.dirname(dev_path)))
dev_path = new_path
return '[Unknown]'
def _GetDiskCapacity(self, device):
"""Returns the disk capacity in tenth of GB, or 0 if not known.
Args:
device: Device to check, like '/dev/sdf'.
Returns:
Capacity of device in GB, or 0 if not known.
"""
re_capacity = re.compile('Disk %s: .* (\d+) bytes' % device)
args = ['-l', device]
stdout = self._tools.Run('fdisk', args, sudo=True)
for line in stdout.splitlines():
m = re_capacity.match(line)
if m:
return int(int(m.group(1)) / 1e8)
return 0
def _ListUsbDisks(self):
"""Return a list of available removable USB disks.
Returns:
List of USB devices, each element is itself a list containing:
device ('/dev/sdx')
manufacturer name
product name
capacity in tenth of GB (an integer)
"""
disk_list = []
for disk in glob.glob('/sys/block/sd*'):
with open(disk + '/removable', 'r') as fd:
if int(fd.readline()) == 1:
device = '/dev/%s' % disk.split('/')[-1]
manuf = self._GetDiskInfo(disk, 'manufacturer')
product = self._GetDiskInfo(disk, 'product')
capacity = self._GetDiskCapacity(device)
if capacity:
disk_list.append([device, manuf, product, capacity])
return disk_list
def WriteToSd(self, flash_dest, disk, uboot, payload):
if flash_dest:
# Set default values for sd.
if flash_dest['bus'] is None:
flash_dest['bus'] = 1
if flash_dest['dev'] is None:
flash_dest['dev'] = 0
raw_image = self._PrepareFlasher(uboot, payload, flash_dest)
bl1, bl2, _, spl_load_offset = self._ExtractPayloadParts(payload, True)
spl_load_size = os.stat(raw_image).st_size
bl2_handler = ExynosBl2(self._tools, self._out)
bl2_file = bl2_handler.Configure(self._fdt, spl_load_offset,
spl_load_size, bl2, 'flasher', True,
use_efs_memory=False)
data = self._tools.ReadFile(bl1) + self._tools.ReadFile(bl2_file)
# Pad BL2 out to the required size. Its size could be either 14K or 30K
# bytes, but the next object in the file needs to be aligned at an 8K
# boundary. The BL1 size is also known to be 8K bytes, so the total BL1
# + BL2 size needs to be aligned to 8K (0x2000) boundary.
aligned_size = (len(data) + 0x1fff) & ~0x1fff
pad_size = aligned_size - len(data)
data += '\0' * pad_size
data += self._tools.ReadFile(raw_image)
image = os.path.join(self._tools.outdir, 'flasher-with-bl.bin')
self._tools.WriteFile(image, data)
self._out.Progress('Writing flasher to %s' % disk)
else:
image = payload
self._out.Progress('Writing image to %s' % disk)
args = ['if=%s' % image, 'of=%s' % disk, 'bs=512', 'seek=1']
self._tools.Run('dd', args, sudo=True)
self._out.Progress('Syncing')
self._tools.Run('sync', [], sudo=True)
def SendToSdCard(self, dest, flash_dest, uboot, payload):
"""Write a flasher to an SD card.
Args:
dest: Destination in one of these forms:
':.' selects the only available device, fails if more than one option
':<device>' select deivce
Examples:
':.'
':/dev/sdd'
flash_dest: Destination for flasher, or None to not create a flasher:
Valid options are spi, sdmmc.
uboot: Full path to u-boot.bin.
payload: Full path to payload.
"""
disk = None
# If no removable devices found - prompt user and wait for one to appear.
disks = self._ListUsbDisks()
try:
spinner = '|/-\\'
index = 0
while not disks:
self._out.ClearProgress()
self._out.Progress('No removable devices found, plug something in %s '
% spinner[index], trailer='')
index = (index + 1) % len(spinner)
disks = self._ListUsbDisks()
time.sleep(.2)
except KeyboardInterrupt:
raise CmdError("No removable device found, interrupted")
if dest.startswith(':'):
name = dest[1:]
# A '.' just means to use the only available disk.
if name == '.' and len(disks) == 1:
disk = disks[0][0]
for disk_info in disks:
# Use the device name.
if disk_info[0] == name:
disk = disk_info[0]
if disk:
self.WriteToSd(flash_dest, disk, uboot, payload)
else:
msg = ["Please specify destination as '-w sd:<disk_description>'",]
msg.append(' - <disk_description> can be either . for the only disk,')
msg.append(' or the full device name, one of listed below:')
# List available disks as a convenience.
for disk in disks:
msg.append(' %s - %s %.1f GB' % (
disk[0],
' '.join(str(x) for x in disk[1:3]),
disk[3] / 10.0))
raise CmdError('\n'.join(msg))
def Em100FlashImage(self, image_fname):
"""Send an image to an attached EM100 device.
This is a Dediprog EM100 SPI flash emulation device. We set up servo2
to do the SPI emulation, then write the image, then boot the board.
All going well, this is enough to get U-Boot running.
Args:
image_fname: Filename of image to send
"""
args = ['spi2_vref:off', 'spi2_buf_en:off', 'spi2_buf_on_flex_en:off']
args.append('spi_hold:on')
self.DutControl(args)
# TODO(sjg@chromium.org): This is for link. We could make this
# configurable from the fdt.
args = ['-c', 'W25Q64CV', '-d', self._tools.Filename(image_fname), '-r']
self._out.Progress('Writing image to em100')
self._tools.Run('em100', args, sudo=True)
if self._servo_port is not None:
self._out.Progress('Resetting board via servo')
args = ['cold_reset:on', 'sleep:.2', 'cold_reset:off', 'sleep:.5']
args.extend(['pwr_button:press', 'sleep:.2', 'pwr_button:release'])
self.DutControl(args)
def DoWriteFirmware(output, tools, fdt, flasher, file_list, image_fname,
bundle, update=True, verify=False, dest=None,
flasher_dest=None, kernel=None, bootstub=None,
servo='any', method='tegra'):
"""A simple function to write firmware to a device.
This creates a WriteFirmware object and uses it to write the firmware image
to the given destination device.
Args:
output: cros_output object to use.
tools: Tools object to use.
fdt: Fdt object to use as our device tree.
flasher: U-Boot binary to use as the flasher.
file_list: Dictionary containing files that we might need.
image_fname: Filename of image to write.
bundle: The bundle object which created the image.
update: Use faster update algorithm rather then full device erase.
verify: Verify the write by doing a readback and CRC.
dest: Destination device to write firmware to (usb, sd).
flasher_dest: a string, destination device for flasher to program payload
into. This string has the form <type>:[bus]:[dev], where
bus and dev are optional (and default to device and target
specific defaults when absent).
kernel: Kernel file to write after U-Boot
bootstub: string, file name of the boot stub, if present
servo: Describes the servo unit to use: none=none; any=any; otherwise
port number of servo to use.
"""
write = WriteFirmware(tools, fdt, output, bundle, update, verify)
write.SelectServo(servo)
flash_dest = None
if flasher_dest:
# Parse flasher_dest and store into a dictionary.
flash_dest_list = flasher_dest.split(":")
flash_dest = {'type': flash_dest_list[0], 'bus': None, 'dev': None}
if len(flash_dest_list) > 1:
flash_dest['bus'] = flash_dest_list[1]
if len(flash_dest_list) > 2:
flash_dest['dev'] = flash_dest_list[2]
write.text_base = bundle.CalcTextBase('flasher ', fdt, flasher)
elif bootstub:
write.text_base = bundle.CalcTextBase('bootstub ', fdt, bootstub)
if dest == 'usb':
try:
write.DutControl(['cpu_uart_capture:on',])
method = fdt.GetString('/chromeos-config', 'flash-method', method)
if method == 'tegra':
tools.CheckTool('tegrarcm')
ok = write.NvidiaFlashImage(flash_dest, flasher, file_list['bct'],
image_fname, bootstub)
elif method == 'exynos':
tools.CheckTool('lsusb', 'usbutils')
tools.CheckTool('smdk-usbdl', 'smdk-dltool')
ok = write.ExynosFlashImage(flash_dest, flasher,
file_list['exynos-bl1'], file_list['exynos-bl2'], image_fname,
kernel)
else:
raise CmdError("Unknown flash method '%s'" % method)
if not ok:
raise CmdError('Image upload failed - please check board connection')
output.Progress('Image uploaded, waiting for completion')
if flash_dest is not None and servo != 'none':
write.WaitForCompletion()
output.Progress('Done!')
finally:
write.DutControl(['cpu_uart_capture:off',])
elif dest == 'em100':
# crosbug.com/31625
tools.CheckTool('em100')
write.Em100FlashImage(image_fname)
elif dest.startswith('sd'):
write.SendToSdCard(dest[2:], flash_dest, flasher, image_fname)
else:
raise CmdError("Unknown destination device '%s'" % dest)