blob: eb06cd550046f7ce1f193bd832c590844250c201 [file] [log] [blame] [edit]
# 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):
"""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.
(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)