# 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 to check if TCPCs are up-to-date. This test figures out which TCPCs exist on a DUT and matches these up with corresponding firmware blobs in the system image shellball. If mismatches are detected, the test fails. The test can optionally be invoked with --args bios=... to specify an alternate reference firmware image. """ import logging import os 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.faft.firmware_test import FirmwareTest class firmware_CompareChipFwToShellBall(FirmwareTest): """Compares the active DUT chip firmware with reference. FAFT test to verify that a DUT runs the expected chip firmware based on the system shellball or a specified reference image. """ version = 1 BIOS = 'bios.bin' MAXPORTS = 100 def initialize(self, host, cmdline_args): super(firmware_CompareChipFwToShellBall, self).initialize(host, cmdline_args) dict_args = utils.args_to_dict(cmdline_args) self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None self.cbfs_work_dir = None self.dut_bios_path = None def cleanup(self): try: if self.cbfs_work_dir: self.faft_client.system.remove_dir(self.cbfs_work_dir) except Exception as e: logging.error("Caught exception: %s", str(e)) super(firmware_CompareChipFwToShellBall, self).cleanup() def dut_get_chip(self, port): """Gets the chip info for a port. Args: port: TCPC port number on DUT Returns: A chip object if available, else None. """ cmd = 'mosys -s product_id pd chip %d' % port chip_id = self.faft_client.system.run_shell_command_get_output(cmd) if not chip_id: # chip probably does not exist return None chip_id = chip_id[0] if chip_id not in chip_utils.chip_id_map: logging.info('chip type %s not recognized', chip_id) return chip_utils.generic_chip() chip = chip_utils.chip_id_map[chip_id]() cmd = 'mosys -s fw_version pd chip %d' % port fw_rev = self.faft_client.system.run_shell_command_get_output(cmd) if not fw_rev: # chip probably does not exist return None fw_rev = fw_rev[0] chip.set_fw_ver_from_string(fw_rev) return chip def dut_scan_chips(self): """Scans for TCPC chips on DUT. Returns: A tuple (S, L) consisting of a set S of chip types and a list L of chips indexed by port number found on on the DUT. Raises: TestFail: DUT has >= MAXPORTS pd ports. """ chip_types = set() port2chip = [] for port in xrange(self.MAXPORTS): chip = self.dut_get_chip(port) if not chip: return (chip_types, port2chip) port2chip.append(chip) chip_types.add(type(chip)) logging.error('found at least %u TCPC ports ' '- please update test to handle more ports ' 'if this is expected.', self.MAXPORTS) raise error.TestFail('MAXPORTS exceeded' % self.MAXPORTS) def dut_locate_bios_bin(self): """Finds bios.bin on DUT. Figures out where FAFT unpacked the shellball and return path to extracted bios.bin. Returns: Full path of bios.bin on DUT. """ work_path = self.faft_client.updater.get_work_path() bios_relative_path = self.faft_client.updater.get_bios_relative_path() bios_bin = os.path.join(work_path, bios_relative_path) return bios_bin def dut_prep_cbfs(self): """Sets up cbfs on DUT. Finds bios.bin on the DUT and sets up a temp dir to operate on bios.bin. If a bios.bin was specified, it is copied to the DUT and used instead of the native 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 dut_cbfs_extract_chips(self, chip_types): """Extracts firmware hash blobs from cbfs. Iterates over requested chip types and looks for corresponding firmware hash blobs in cbfs. These firmware hash blobs are extracted into cbfs_work_dir. Args: chip_types: A set of chip types for which the hash blobs will be extracted. Returns: A dict mapping found chip names to chip instances. """ cbfs_chip_info = {} for chip_type in chip_types: chip = chip_type() fw = chip.fw_name if not fw: # must be an unfamiliar chip continue 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)) chip.set_fw_ver_from_string(bundled_fw_ver) cbfs_chip_info[chip.chip_name] = chip logging.info('%s bundled firmware for %s is version %s', self.BIOS, chip.chip_name, bundled_fw_ver) return cbfs_chip_info def check_chip_versions(self, port2chip, ref_chip_info): """Verifies DUT chips have expected firmware. Iterates over found DUT chips and verifies their firmware version matches the chips found in the reference ref_chip_info map. Args: port2chip: A list of chips to verify against ref_chip_info. ref_chip_info: A dict of reference chip chip instances indexed by chip name. """ for p, pinfo in enumerate(port2chip): if not pinfo.fw_ver: # must be an unknown chip continue msg = 'DUT port %s is a %s running firmware 0x%02x' % ( p, pinfo.chip_name, pinfo.fw_ver) if pinfo.chip_name not in ref_chip_info: logging.warning('%s but there is no reference version', msg) continue expected_fw_ver = ref_chip_info[pinfo.chip_name].fw_ver logging.info('%s%s', msg, ('' if pinfo.fw_ver == expected_fw_ver else ' (expected 0x%02x)' % expected_fw_ver)) if pinfo.fw_ver != expected_fw_ver: msg = '%s firmware was not updated to 0x%02x' % ( pinfo.chip_name, expected_fw_ver) raise error.TestFail(msg) 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. (dut_chip_types, dut_chips) = self.dut_scan_chips() if not dut_chip_types: logging.info('mosys reported no chips on DUT, skipping test') return self.dut_prep_cbfs() if self.new_bios_path: host.send_file(self.new_bios_path, self.dut_bios_path) ref_chip_info = self.dut_cbfs_extract_chips(dut_chip_types) self.check_chip_versions(dut_chips, ref_chip_info)