# 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 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 BIOS = 'bios.bin' HEXDUMP = 'hexdump -v -e \'1/1 "0x%02x\\n"\'' 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 in chip_utils.chip_id_map.itervalues(): chip_name = chip.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) c = chip() c.set_from_file(chip_file) if chip_name in self.req_chip_updates: raise error.TestError('multiple %s args' % chip_name) logging.info('request chip %s fw 0x%02x from command line', c.chip_name, c.fw_ver) self.req_chip_updates[chip_name] = c 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 %s', chip.chip_name, self.BIOS) if not self.faft_client.updater.cbfs_extract_chip(chip.fw_name): logging.warning('%s firmware not bundled in %s', chip.chip_name, self.BIOS) continue hashblob = self.faft_client.updater.cbfs_get_chip_hash( chip.fw_name) if not hashblob: logging.warning('%s firmware hash not extracted from %s', chip.chip_name, self.BIOS) continue bundled_fw_ver = chip.fw_ver_from_hash(hashblob) if not bundled_fw_ver: raise error.TestFail( 'could not decode %s firmware hash: %s' % ( chip.chip_name, hashblob)) self.cbfs_chip_types.add(type(chip)) logging.info('%s bundled firmware for %s is version %s', self.BIOS, chip.chip_name, bundled_fw_ver) 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 in self.cbfs_chip_types: chip_name = chip.chip_name logging.info('replacing %s firmware in %s', chip_name, self.BIOS) fw_update = self.req_chip_updates[chip_name] fw_hash = fw_update.compute_hash_bytes() (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): 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): # 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)