230 lines
8.7 KiB
Python
230 lines
8.7 KiB
Python
# Copyright 2017 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 is a FAFT test for TCPC firmware updates.
|
|
|
|
This test forces TCPC firmware updates for the specified TCPCs.
|
|
|
|
The test is invoked with additional arguments to specify alternate
|
|
TCPC firmware blobs. These are "edited" into the DUT's bios.bin
|
|
normally extracted from the system shellball. Then, the bios.bin is
|
|
flashed into the DUT and the DUT is rebooted.
|
|
|
|
Under normal conditions, the TCPC firmware blobs will be updated as
|
|
part of software sync when the DUT reboots. Software sync checks that
|
|
the new firmware is actually running on the TCPCs, however it can also
|
|
be audited after the fact using the firmware_CompareChipFwToShellBall
|
|
FAFT test for independent verification.
|
|
|
|
This test should be invoked twice: the 1st time to "downgrade" the
|
|
TCPC firmware, then a 2nd time to restore the production TCPC
|
|
firmware. Alternatively, the system can be reflashed with a
|
|
production bios.bin (and rebooted) to restore the TCPC firmware.
|
|
|
|
The parade ps8751 (and similar) parts can be re-flashed indefinitely.
|
|
However, the analogix parts can only be updated about 100 times which
|
|
means it is not feasible to include them in continuous automated
|
|
testing.
|
|
|
|
This test will only replace existing TCPC firmware blobs in bios.bin.
|
|
If the corresponding binary blobs are not found in cbfs, it is assumed
|
|
that the release does not support the requested TCPCs. Alternatively,
|
|
a bios.bin can be specified when invoking the test that will be used
|
|
insteade of the bios.bin normally extracted from the DUT's system
|
|
shellball.
|
|
"""
|
|
import binascii
|
|
import logging
|
|
import os
|
|
import tempfile
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib import utils
|
|
from autotest_lib.client.common_lib.cros import chip_utils
|
|
from autotest_lib.server.cros import vboot_constants as vboot
|
|
from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
|
|
|
|
|
|
class firmware_ChipFwUpdate(FirmwareTest):
|
|
|
|
"""Updates DUT firmware image with specified firmware blobs.
|
|
|
|
If a new bios.bin is offered, it replaces the
|
|
existing bios.bin.
|
|
Then, if new chip firmware blobs are offered, they
|
|
replace existing firmware blobs in bios.bin.
|
|
Finally the system shellball is repacked.
|
|
|
|
A reboot must be issued for the new firmware to be applied
|
|
during software sync.
|
|
|
|
Use the firmware_ChipFwUpdate test to verify that the new
|
|
firmware was applied.
|
|
"""
|
|
version = 1
|
|
|
|
def initialize(self, host, cmdline_args):
|
|
dict_args = utils.args_to_dict(cmdline_args)
|
|
super(firmware_ChipFwUpdate,
|
|
self).initialize(host, cmdline_args)
|
|
|
|
self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None
|
|
|
|
self.clear_set_gbb_flags(
|
|
vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC |
|
|
vboot.GBB_FLAG_DISABLE_PD_SOFTWARE_SYNC, 0)
|
|
|
|
self.dut_bios_path = None
|
|
self.cbfs_work_dir = None
|
|
|
|
# set of chip types found in CBFS
|
|
self.cbfs_chip_types = set()
|
|
# dict of chip FW updates from the cmd line
|
|
self.req_chip_updates = {}
|
|
|
|
# see if comand line specified new firmware blobs
|
|
# for chips we know about
|
|
|
|
for chip_type in chip_utils.chip_id_map.values():
|
|
chip_name = chip_type.chip_name
|
|
if chip_name not in dict_args:
|
|
continue
|
|
chip_file = dict_args[chip_name]
|
|
if not os.path.exists(chip_file):
|
|
raise error.TestError('file %s not found' % chip_file)
|
|
|
|
chip = chip_type()
|
|
chip.set_from_file(chip_file)
|
|
|
|
if chip_name in self.req_chip_updates:
|
|
raise error.TestError('multiple %s args' % chip_name)
|
|
|
|
fw_ver_desc = ''
|
|
if chip.fw_ver:
|
|
fw_ver_desc = ' (version 0x%02x)' % chip.fw_ver
|
|
logging.info('got %s firmware from args: %s%s', chip.chip_name,
|
|
chip_file, fw_ver_desc)
|
|
self.req_chip_updates[chip_name] = chip
|
|
|
|
def dut_setup_cbfs(self):
|
|
"""Sets up a work dir for cbfstool.
|
|
|
|
Creates a fresh temp. dir for cbfstool to manipulate bios.bin.
|
|
"""
|
|
|
|
cbfs_path = self.faft_client.updater.cbfs_setup_work_dir()
|
|
bios_relative_path = self.faft_client.updater.get_bios_relative_path()
|
|
self.cbfs_work_dir = cbfs_path
|
|
self.dut_bios_path = os.path.join(cbfs_path, bios_relative_path)
|
|
|
|
def cbfs_extract_chips(self):
|
|
"""Extracts interesting firmware blobs from cbfs.
|
|
|
|
Iterates over requested chip updates and looks for corresponding
|
|
firmware blobs in cbfs. Firmware blobs are then extracted into
|
|
cbfs_work_dir.
|
|
"""
|
|
|
|
for chip in self.req_chip_updates.itervalues():
|
|
logging.info('checking for %s firmware in CBFS', chip.chip_name)
|
|
|
|
if not self.faft_client.updater.cbfs_extract_chip(
|
|
chip.fw_name, chip.extension, chip.hash_extension):
|
|
logging.warning('%s firmware not bundled in CBFS',
|
|
chip.chip_name)
|
|
continue
|
|
|
|
hashblob = self.faft_client.updater.cbfs_get_chip_hash(
|
|
chip.fw_name, chip.hash_extension)
|
|
if hashblob:
|
|
if hasattr(chip, 'fw_ver_from_hash'):
|
|
bundled_fw_ver = chip.fw_ver_from_hash(hashblob)
|
|
if bundled_fw_ver is None:
|
|
raise error.TestFail(
|
|
'could not determine version from %s firmware '
|
|
'hash: %s' % (chip.chip_name, hashblob))
|
|
logging.info('CBFS bundled firmware for %s is version %s',
|
|
chip.chip_name, bundled_fw_ver)
|
|
else:
|
|
logging.info('CBFS bundled firmware for %s has hash %s',
|
|
chip.chip_name, hashblob)
|
|
else:
|
|
logging.warning('%s firmware hash not extracted from CBFS',
|
|
chip.chip_name)
|
|
self.cbfs_chip_types.add(type(chip))
|
|
|
|
def cbfs_replace_chips(self, host):
|
|
"""Iterates over known chips in cbfs.
|
|
|
|
For each chip that has an update specified on the command line,
|
|
copies the firmware (bin, hash) to DUT and updates cbfs in
|
|
bios.bin.
|
|
|
|
Args:
|
|
host: host handle to the DUT.
|
|
"""
|
|
|
|
for chip_type in self.cbfs_chip_types:
|
|
chip_name = chip_type.chip_name
|
|
logging.info('replacing %s firmware in CBFS', chip_name)
|
|
|
|
fw_update = self.req_chip_updates[chip_name]
|
|
fw_hash = fw_update.compute_hash_bytes()
|
|
logging.info("New file's hash is: %s", binascii.hexlify(fw_hash))
|
|
(fd, n) = tempfile.mkstemp()
|
|
with os.fdopen(fd, 'wb') as f:
|
|
f.write(fw_hash)
|
|
|
|
try:
|
|
host.send_file(n,
|
|
os.path.join(
|
|
self.cbfs_work_dir,
|
|
fw_update.cbfs_hash_name))
|
|
finally:
|
|
os.unlink(n)
|
|
|
|
host.send_file(fw_update.fw_file_name,
|
|
os.path.join(
|
|
self.cbfs_work_dir,
|
|
fw_update.cbfs_bin_name))
|
|
|
|
if not self.faft_client.updater.cbfs_replace_chip(
|
|
fw_update.fw_name, fw_update.extension,
|
|
fw_update.hash_extension):
|
|
raise error.TestFail('could not replace %s blobs in cbfs' %
|
|
fw_update.chip_name)
|
|
|
|
def dut_sign_and_flash_bios(self, host):
|
|
"""Signs the BIOS and flashes the DUT with it.
|
|
|
|
Args:
|
|
host: host handle to the DUT.
|
|
"""
|
|
|
|
if not self.faft_client.updater.cbfs_sign_and_flash():
|
|
raise error.TestFail('could not re-sign %s' % self.dut_bios_path)
|
|
host.reboot()
|
|
|
|
def run_once(self, host):
|
|
"""Runs a single iteration of the test."""
|
|
# Make sure the client library is on the device so that the proxy
|
|
# code is there when we try to call it.
|
|
|
|
if not self.req_chip_updates:
|
|
logging.info('no FW updates requested, skipping test')
|
|
return
|
|
|
|
self.dut_setup_cbfs()
|
|
if self.new_bios_path:
|
|
host.send_file(self.new_bios_path, self.dut_bios_path)
|
|
|
|
self.cbfs_extract_chips()
|
|
if not self.cbfs_chip_types:
|
|
logging.info('firmware does not support requested updates, '
|
|
'skipping test')
|
|
return
|
|
|
|
self.cbfs_replace_chips(host)
|
|
self.dut_sign_and_flash_bios(host)
|