You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
523 lines
20 KiB
523 lines
20 KiB
# Copyright (c) 2014 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.
|
|
|
|
"""A utility to program Chrome OS devices' firmware using servo.
|
|
|
|
This utility expects the DUT to be connected to a servo device. This allows us
|
|
to put the DUT into the required state and to actually program the DUT's
|
|
firmware using FTDI, USB and/or serial interfaces provided by servo.
|
|
|
|
Servo state is preserved across the programming process.
|
|
"""
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
import re
|
|
import site
|
|
import time
|
|
import xml.etree.ElementTree
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.server.cros.faft.utils.config import Config as FAFTConfig
|
|
|
|
|
|
# Number of seconds for program EC/BIOS to time out.
|
|
FIRMWARE_PROGRAM_TIMEOUT_SEC = 1800
|
|
|
|
class ProgrammerError(Exception):
|
|
"""Local exception class wrapper."""
|
|
pass
|
|
|
|
|
|
class _BaseProgrammer(object):
|
|
"""Class implementing base programmer services.
|
|
|
|
Private attributes:
|
|
_servo: a servo object controlling the servo device
|
|
_servo_host: a host object running commands like 'flashrom'
|
|
_servo_prog_state: a tuple of strings of "<control>:<value>" pairs,
|
|
listing servo controls and their required values for
|
|
programming
|
|
_servo_prog_state_delay: time in second to wait after changing servo
|
|
controls for programming.
|
|
_servo_saved_state: a list of the same elements as _servo_prog_state,
|
|
those which need to be restored after programming
|
|
_program_cmd: a string, the shell command to run on the servo host
|
|
to actually program the firmware. Dependent on
|
|
firmware/hardware type, set by subclasses.
|
|
"""
|
|
|
|
def __init__(self, servo, req_list, servo_host=None):
|
|
"""Base constructor.
|
|
@param servo: a servo object controlling the servo device
|
|
@param req_list: a list of strings, names of the utilities required
|
|
to be in the path for the programmer to succeed
|
|
@param servo_host: a host object to execute commands. Default to None,
|
|
using the host object from the above servo object
|
|
"""
|
|
self._servo = servo
|
|
self._servo_prog_state = ()
|
|
self._servo_prog_state_delay = 0
|
|
self._servo_saved_state = []
|
|
self._program_cmd = ''
|
|
self._servo_host = servo_host
|
|
if self._servo_host is None:
|
|
self._servo_host = self._servo._servo_host
|
|
|
|
try:
|
|
self._servo_host.run('which %s' % ' '.join(req_list))
|
|
except error.AutoservRunError:
|
|
# TODO: We turn this exception into a warn since the fw programmer
|
|
# is not working right now, and some systems do not package the
|
|
# required utilities its checking for.
|
|
# We should reinstate this exception once the programmer is working
|
|
# to indicate the missing utilities earlier in the test cycle.
|
|
# Bug chromium:371011 filed to track this.
|
|
logging.warn("Ignoring exception when verify required bins : %s",
|
|
' '.join(req_list))
|
|
|
|
|
|
def _set_servo_state(self):
|
|
"""Set servo for programming, while saving the current state."""
|
|
logging.debug("Setting servo state for programming")
|
|
for item in self._servo_prog_state:
|
|
key, value = item.split(':')
|
|
try:
|
|
present = self._servo.get(key)
|
|
except error.TestFail:
|
|
logging.warn('Missing servo control: %s', key)
|
|
continue
|
|
if present == 'not_applicable':
|
|
# control is has no bearing in this servo config so ignore it.
|
|
logging.debug('Servo control %s is NA .. skipping', key)
|
|
continue
|
|
if present != value:
|
|
self._servo_saved_state.append('%s:%s' % (key, present))
|
|
self._servo.set(key, value)
|
|
time.sleep(self._servo_prog_state_delay)
|
|
|
|
|
|
def _restore_servo_state(self):
|
|
"""Restore previously saved servo state."""
|
|
logging.debug("Restoring servo state after programming")
|
|
self._servo_saved_state.reverse() # Do it in the reverse order.
|
|
for item in self._servo_saved_state:
|
|
key, value = item.split(':')
|
|
self._servo.set(key, value)
|
|
|
|
|
|
def program(self):
|
|
"""Program the firmware as configured by a subclass."""
|
|
self._set_servo_state()
|
|
try:
|
|
logging.debug("Programmer command: %s", self._program_cmd)
|
|
self._servo_host.run(self._program_cmd,
|
|
timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
|
|
finally:
|
|
self._restore_servo_state()
|
|
|
|
|
|
class FlashromProgrammer(_BaseProgrammer):
|
|
"""Class for programming AP flashrom."""
|
|
|
|
def __init__(self, servo, keep_ro=False):
|
|
"""Configure required servo state.
|
|
|
|
@param servo: a servo object controlling the servo device
|
|
@param keep_ro: True to keep the RO portion unchanged
|
|
"""
|
|
super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
|
|
self._keep_ro = keep_ro
|
|
self._fw_path = None
|
|
self.init_section_paths('/tmp')
|
|
self._servo_version = self._servo.get_servo_version(active=True)
|
|
self._servo_serials = self._servo._server.get_servo_serials()
|
|
|
|
|
|
def init_section_paths(self, tmp_path):
|
|
"""Update section paths to use the tmp directory"""
|
|
self._tmp_path = tmp_path
|
|
self._fw_main = os.path.join(self._tmp_path, 'fw_main')
|
|
self._wp_ro = os.path.join(self._tmp_path, 'wp_ro')
|
|
self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
|
|
self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
|
|
self._gbb = os.path.join(self._tmp_path, 'gbb')
|
|
|
|
|
|
def program(self):
|
|
"""Program the firmware but preserve VPD and HWID."""
|
|
assert self._fw_path is not None
|
|
self._set_servo_state()
|
|
try:
|
|
wp_ro_section = [('WP_RO', self._wp_ro)]
|
|
rw_vpd_section = [('RW_VPD', self._rw_vpd)]
|
|
ro_vpd_section = [('RO_VPD', self._ro_vpd)]
|
|
gbb_section = [('GBB', self._gbb)]
|
|
if self._keep_ro:
|
|
# Keep the whole RO portion
|
|
preserved_sections = wp_ro_section + rw_vpd_section
|
|
else:
|
|
preserved_sections = ro_vpd_section + rw_vpd_section
|
|
|
|
servo_v2_programmer = 'ft2232_spi:type=google-servo-v2'
|
|
servo_v3_programmer = 'linux_spi'
|
|
servo_v4_with_micro_programmer = 'raiden_debug_spi'
|
|
servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP'
|
|
|
|
if self._servo_version == 'servo_v2':
|
|
programmer = servo_v2_programmer
|
|
servo_serial = self._servo_serials.get('main')
|
|
if servo_serial:
|
|
programmer += ',serial=%s' % servo_serial
|
|
elif self._servo_version == 'servo_v3':
|
|
programmer = servo_v3_programmer
|
|
elif self._servo_version == 'servo_v4_with_servo_micro':
|
|
# When a uServo is connected to a DUT with CCD support, the
|
|
# firmware programmer will always use the uServo to program.
|
|
servo_micro_serial = self._servo_serials.get('servo_micro')
|
|
programmer = servo_v4_with_micro_programmer
|
|
programmer += ':serial=%s' % servo_micro_serial
|
|
elif self._servo_version == 'servo_v4_with_ccd_cr50':
|
|
ccd_serial = self._servo_serials.get('ccd')
|
|
programmer = servo_v4_with_ccd_programmer
|
|
programmer += ',serial=%s' % ccd_serial
|
|
else:
|
|
raise Exception('Servo version %s is not supported.' %
|
|
self._servo_version)
|
|
# Save needed sections from current firmware
|
|
for section in preserved_sections + gbb_section:
|
|
self._servo_host.run(' '.join([
|
|
'flashrom', '-V', '-p', programmer, '-r',
|
|
'-i', '%s:%s' % section]),
|
|
timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
|
|
|
|
# Pack the saved VPD into new firmware
|
|
self._servo_host.run('cp %s %s' % (self._fw_path, self._fw_main))
|
|
img_size = self._servo_host.run_output(
|
|
"stat -c '%%s' %s" % self._fw_main)
|
|
pack_cmd = ['flashrom',
|
|
'-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
|
|
self._fw_main, img_size),
|
|
'-w', self._fw_main]
|
|
for section in preserved_sections:
|
|
pack_cmd.extend(['-i', '%s:%s' % section])
|
|
self._servo_host.run(' '.join(pack_cmd),
|
|
timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
|
|
|
|
# HWID is inside the RO portion. Don't preserve HWID if we keep RO.
|
|
if not self._keep_ro:
|
|
# Read original HWID. The output format is:
|
|
# hardware_id: RAMBI TEST A_A 0128
|
|
gbb_hwid_output = self._servo_host.run_output(
|
|
'futility gbb -g --hwid %s' % self._gbb)
|
|
original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
|
|
|
|
# Write HWID to new firmware
|
|
self._servo_host.run("futility gbb -s --hwid='%s' %s" %
|
|
(original_hwid, self._fw_main))
|
|
|
|
# Flash the new firmware
|
|
self._servo_host.run(' '.join([
|
|
'flashrom', '-V', '-p', programmer,
|
|
'-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
|
|
finally:
|
|
self._servo.get_power_state_controller().reset()
|
|
self._restore_servo_state()
|
|
|
|
|
|
def prepare_programmer(self, path):
|
|
"""Prepare programmer for programming.
|
|
|
|
@param path: a string, name of the file containing the firmware image.
|
|
"""
|
|
self._fw_path = path
|
|
self.init_section_paths(os.path.dirname(path))
|
|
|
|
# If servo is running with servo v4, there may be two programming
|
|
# devices. Determine the programmer based on the active one.
|
|
self._servo_version = self._servo.get_servo_version(active=True)
|
|
|
|
# CCD takes care holding AP/EC. Don't need the following steps.
|
|
if self._servo_version != 'servo_v4_with_ccd_cr50':
|
|
faft_config = FAFTConfig(self._servo.get_board())
|
|
self._servo_prog_state_delay = faft_config.servo_prog_state_delay
|
|
self._servo_prog_state = (
|
|
'spi2_vref:%s' % faft_config.spi_voltage,
|
|
'spi2_buf_en:on',
|
|
'spi2_buf_on_flex_en:on',
|
|
'spi_hold:off',
|
|
'cold_reset:on',
|
|
'usbpd_reset:on',
|
|
)
|
|
|
|
|
|
class FlashECProgrammer(_BaseProgrammer):
|
|
"""Class for programming AP flashrom."""
|
|
|
|
def __init__(self, servo, host=None, ec_chip=None):
|
|
"""Configure required servo state.
|
|
|
|
@param servo: a servo object controlling the servo device
|
|
@param host: a host object to execute commands. Default to None,
|
|
using the host object from the above servo object, i.e.
|
|
a servo host. A CrOS host object can be passed here
|
|
such that it executes commands on the CrOS device.
|
|
@param ec_chip: a string of EC chip. Default to None, using the
|
|
EC chip name reported by servo, the primary EC.
|
|
Can pass a different chip name, for the case of
|
|
the base EC.
|
|
|
|
"""
|
|
super(FlashECProgrammer, self).__init__(servo, ['flash_ec'], host)
|
|
self._servo_version = self._servo.get_servo_version()
|
|
self._ec_chip = ec_chip
|
|
|
|
def prepare_programmer(self, image):
|
|
"""Prepare programmer for programming.
|
|
|
|
@param image: string with the location of the image file
|
|
"""
|
|
if self._ec_chip is None:
|
|
self._ec_chip = self._servo.get('ec_chip')
|
|
|
|
# If servo is running with servo v4, there may be two programming
|
|
# devices. Determine the programmer based on the active one.
|
|
self._servo_version = self._servo.get_servo_version(active=True)
|
|
|
|
# Get the port of servod. flash_ec may use it to talk to servod.
|
|
port = self._servo._servo_host.servo_port
|
|
self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
|
|
(self._ec_chip, image, port))
|
|
if self._ec_chip == 'stm32':
|
|
self._program_cmd += ' --bitbang_rate=57600'
|
|
self._program_cmd += ' --verify'
|
|
self._program_cmd += ' --verbose'
|
|
|
|
|
|
class ProgrammerV2(object):
|
|
"""Main programmer class which provides programmer for BIOS and EC with
|
|
servo V2."""
|
|
|
|
def __init__(self, servo):
|
|
self._servo = servo
|
|
self._valid_boards = self._get_valid_v2_boards()
|
|
self._bios_programmer = self._factory_bios(self._servo)
|
|
self._ec_programmer = self._factory_ec(self._servo)
|
|
|
|
|
|
@staticmethod
|
|
def _get_valid_v2_boards():
|
|
"""Greps servod config files to look for valid v2 boards.
|
|
|
|
@return A list of valid board names.
|
|
"""
|
|
site_packages_paths = site.getsitepackages()
|
|
SERVOD_CONFIG_DATA_DIR = None
|
|
for p in site_packages_paths:
|
|
servo_data_path = os.path.join(p, 'servo', 'data')
|
|
if os.path.exists(servo_data_path):
|
|
SERVOD_CONFIG_DATA_DIR = servo_data_path
|
|
break
|
|
if not SERVOD_CONFIG_DATA_DIR:
|
|
raise ProgrammerError(
|
|
'Unable to locate data directory of Python servo module')
|
|
SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
|
|
SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
|
|
SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
|
|
|
|
def is_v2_compatible_board(board_config_path):
|
|
"""Check if the given board config file is v2-compatible.
|
|
|
|
@param board_config_path: Path to a board config XML file.
|
|
|
|
@return True if the board is v2-compatible; False otherwise.
|
|
"""
|
|
configs = []
|
|
def get_all_includes(config_path):
|
|
"""Get all included XML config names in the given config file.
|
|
|
|
@param config_path: Path to a servo config file.
|
|
"""
|
|
root = xml.etree.ElementTree.parse(config_path).getroot()
|
|
for element in root.findall('include'):
|
|
include_name = element.find('name').text
|
|
configs.append(include_name)
|
|
get_all_includes(os.path.join(
|
|
SERVOD_CONFIG_DATA_DIR, include_name))
|
|
|
|
get_all_includes(board_config_path)
|
|
return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
|
|
|
|
result = []
|
|
board_overlays = glob.glob(
|
|
os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
|
|
for overlay_path in board_overlays:
|
|
if is_v2_compatible_board(overlay_path):
|
|
result.append(re.search(SERVO_CONFIG_REGEXP,
|
|
overlay_path).group('board'))
|
|
return result
|
|
|
|
|
|
def _get_flashrom_programmer(self, servo):
|
|
"""Gets a proper flashrom programmer.
|
|
|
|
@param servo: A servo object.
|
|
|
|
@return A programmer for flashrom.
|
|
"""
|
|
return FlashromProgrammer(servo)
|
|
|
|
|
|
def _factory_bios(self, servo):
|
|
"""Instantiates and returns (bios, ec) programmers for the board.
|
|
|
|
@param servo: A servo object.
|
|
|
|
@return A programmer for ec. If the programmer is not supported
|
|
for the board, None will be returned.
|
|
"""
|
|
_bios_prog = None
|
|
_board = servo.get_board()
|
|
|
|
logging.debug('Setting up BIOS programmer for board: %s', _board)
|
|
if _board in self._valid_boards:
|
|
_bios_prog = self._get_flashrom_programmer(servo)
|
|
else:
|
|
logging.warning('No BIOS programmer found for board: %s', _board)
|
|
|
|
return _bios_prog
|
|
|
|
|
|
def _factory_ec(self, servo):
|
|
"""Instantiates and returns ec programmer for the board.
|
|
|
|
@param servo: A servo object.
|
|
|
|
@return A programmer for ec. If the programmer is not supported
|
|
for the board, None will be returned.
|
|
"""
|
|
_ec_prog = None
|
|
_board = servo.get_board()
|
|
|
|
logging.debug('Setting up EC programmer for board: %s', _board)
|
|
if _board in self._valid_boards:
|
|
_ec_prog = FlashECProgrammer(servo)
|
|
else:
|
|
logging.warning('No EC programmer found for board: %s', _board)
|
|
|
|
return _ec_prog
|
|
|
|
|
|
def program_bios(self, image):
|
|
"""Programs the DUT with provide bios image.
|
|
|
|
@param image: (required) location of bios image file.
|
|
|
|
"""
|
|
self._bios_programmer.prepare_programmer(image)
|
|
self._bios_programmer.program()
|
|
|
|
|
|
def program_ec(self, image):
|
|
"""Programs the DUT with provide ec image.
|
|
|
|
@param image: (required) location of ec image file.
|
|
|
|
"""
|
|
self._ec_programmer.prepare_programmer(image)
|
|
self._ec_programmer.program()
|
|
|
|
|
|
class ProgrammerV2RwOnly(ProgrammerV2):
|
|
"""Main programmer class which provides programmer for only updating the RW
|
|
portion of BIOS with servo V2.
|
|
|
|
It does nothing on EC, as EC software sync on the next boot will
|
|
automatically overwrite the EC RW portion, using the EC RW image inside
|
|
the BIOS RW image.
|
|
|
|
"""
|
|
|
|
def _get_flashrom_programmer(self, servo):
|
|
"""Gets a proper flashrom programmer.
|
|
|
|
@param servo: A servo object.
|
|
|
|
@return A programmer for flashrom.
|
|
"""
|
|
return FlashromProgrammer(servo, keep_ro=True)
|
|
|
|
|
|
def program_ec(self, image):
|
|
"""Programs the DUT with provide ec image.
|
|
|
|
@param image: (required) location of ec image file.
|
|
|
|
"""
|
|
# Do nothing. EC software sync will update the EC RW.
|
|
pass
|
|
|
|
|
|
class ProgrammerV3(object):
|
|
"""Main programmer class which provides programmer for BIOS and EC with
|
|
servo V3.
|
|
|
|
Different from programmer for servo v2, programmer for servo v3 does not
|
|
try to validate if the board can use servo V3 to update firmware. As long as
|
|
the servod process running in beagblebone with given board, the program will
|
|
attempt to flash bios and ec.
|
|
|
|
"""
|
|
|
|
def __init__(self, servo):
|
|
self._servo = servo
|
|
self._bios_programmer = FlashromProgrammer(servo)
|
|
self._ec_programmer = FlashECProgrammer(servo)
|
|
|
|
|
|
def program_bios(self, image):
|
|
"""Programs the DUT with provide bios image.
|
|
|
|
@param image: (required) location of bios image file.
|
|
|
|
"""
|
|
self._bios_programmer.prepare_programmer(image)
|
|
self._bios_programmer.program()
|
|
|
|
|
|
def program_ec(self, image):
|
|
"""Programs the DUT with provide ec image.
|
|
|
|
@param image: (required) location of ec image file.
|
|
|
|
"""
|
|
self._ec_programmer.prepare_programmer(image)
|
|
self._ec_programmer.program()
|
|
|
|
|
|
class ProgrammerV3RwOnly(ProgrammerV3):
|
|
"""Main programmer class which provides programmer for only updating the RW
|
|
portion of BIOS with servo V3.
|
|
|
|
It does nothing on EC, as EC software sync on the next boot will
|
|
automatically overwrite the EC RW portion, using the EC RW image inside
|
|
the BIOS RW image.
|
|
|
|
"""
|
|
|
|
def __init__(self, servo):
|
|
self._servo = servo
|
|
self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
|
|
|
|
|
|
def program_ec(self, image):
|
|
"""Programs the DUT with provide ec image.
|
|
|
|
@param image: (required) location of ec image file.
|
|
|
|
"""
|
|
# Do nothing. EC software sync will update the EC RW.
|
|
pass
|