1# Copyright 2017 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""This is a FAFT test to check if TCPCs are up-to-date. 6 7This test figures out which TCPCs exist on a DUT and matches 8these up with corresponding firmware blobs in the system 9image shellball. If mismatches are detected, the test fails. 10 11The test can optionally be invoked with --args bios=... to 12specify an alternate reference firmware image. 13""" 14 15import logging 16import os 17 18from autotest_lib.client.common_lib import error 19from autotest_lib.client.common_lib import utils 20from autotest_lib.client.common_lib.cros import chip_utils 21from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 22 23 24class firmware_CompareChipFwToShellBall(FirmwareTest): 25 26 """Compares the active DUT chip firmware with reference. 27 28 FAFT test to verify that a DUT runs the expected chip 29 firmware based on the system shellball or a specified 30 reference image. 31 """ 32 version = 1 33 34 BIOS = 'bios.bin' 35 MAXPORTS = 100 36 37 def initialize(self, host, cmdline_args): 38 super(firmware_CompareChipFwToShellBall, 39 self).initialize(host, cmdline_args) 40 dict_args = utils.args_to_dict(cmdline_args) 41 self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None 42 self.cbfs_work_dir = None 43 self.dut_bios_path = None 44 45 def cleanup(self): 46 try: 47 if self.cbfs_work_dir: 48 self.faft_client.system.remove_dir(self.cbfs_work_dir) 49 except Exception as e: 50 logging.error("Caught exception: %s", str(e)) 51 super(firmware_CompareChipFwToShellBall, self).cleanup() 52 53 def dut_get_chip(self, port): 54 """Gets the chip info for a port. 55 56 Args: 57 port: TCPC port number on DUT 58 59 Returns: 60 A chip object if available, else None. 61 """ 62 63 cmd = 'mosys -s product_id pd chip %d' % port 64 chip_id = self.faft_client.system.run_shell_command_get_output(cmd) 65 if not chip_id: 66 # chip probably does not exist 67 return None 68 chip_id = chip_id[0] 69 70 if chip_id not in chip_utils.chip_id_map: 71 logging.info('chip type %s not recognized', chip_id) 72 return chip_utils.generic_chip() 73 chip = chip_utils.chip_id_map[chip_id]() 74 75 cmd = 'mosys -s fw_version pd chip %d' % port 76 fw_rev = self.faft_client.system.run_shell_command_get_output(cmd) 77 if not fw_rev: 78 # chip probably does not exist 79 return None 80 fw_rev = fw_rev[0] 81 chip.set_fw_ver_from_string(fw_rev) 82 return chip 83 84 def dut_scan_chips(self): 85 """Scans for TCPC chips on DUT. 86 87 Returns: 88 A tuple (S, L) consisting of a set S of chip types and a list L 89 of chips indexed by port number found on on the DUT. 90 91 Raises: 92 TestFail: DUT has >= MAXPORTS pd ports. 93 """ 94 95 chip_types = set() 96 port2chip = [] 97 for port in xrange(self.MAXPORTS): 98 chip = self.dut_get_chip(port) 99 if not chip: 100 return (chip_types, port2chip) 101 port2chip.append(chip) 102 chip_types.add(type(chip)) 103 logging.error('found at least %u TCPC ports ' 104 '- please update test to handle more ports ' 105 'if this is expected.', self.MAXPORTS) 106 raise error.TestFail('MAXPORTS exceeded' % self.MAXPORTS) 107 108 def dut_locate_bios_bin(self): 109 """Finds bios.bin on DUT. 110 111 Figures out where FAFT unpacked the shellball 112 and return path to extracted bios.bin. 113 114 Returns: 115 Full path of bios.bin on DUT. 116 """ 117 118 work_path = self.faft_client.updater.get_work_path() 119 bios_relative_path = self.faft_client.updater.get_bios_relative_path() 120 bios_bin = os.path.join(work_path, bios_relative_path) 121 return bios_bin 122 123 def dut_prep_cbfs(self): 124 """Sets up cbfs on DUT. 125 126 Finds bios.bin on the DUT and sets up a temp dir to operate on 127 bios.bin. If a bios.bin was specified, it is copied to the DUT 128 and used instead of the native bios.bin. 129 """ 130 131 cbfs_path = self.faft_client.updater.cbfs_setup_work_dir() 132 bios_relative_path = self.faft_client.updater.get_bios_relative_path() 133 self.cbfs_work_dir = cbfs_path 134 self.dut_bios_path = os.path.join(cbfs_path, bios_relative_path) 135 136 def dut_cbfs_extract_chips(self, chip_types): 137 """Extracts firmware hash blobs from cbfs. 138 139 Iterates over requested chip types and looks for corresponding 140 firmware hash blobs in cbfs. These firmware hash blobs are 141 extracted into cbfs_work_dir. 142 143 Args: 144 chip_types: 145 A set of chip types for which the hash blobs will be 146 extracted. 147 148 Returns: 149 A dict mapping found chip names to chip instances. 150 """ 151 152 cbfs_chip_info = {} 153 for chip_type in chip_types: 154 chip = chip_type() 155 fw = chip.fw_name 156 if not fw: 157 # must be an unfamiliar chip 158 continue 159 160 if not self.faft_client.updater.cbfs_extract_chip(chip.fw_name): 161 logging.warning('%s firmware not bundled in %s', 162 chip.chip_name, self.BIOS) 163 continue 164 165 hashblob = self.faft_client.updater.cbfs_get_chip_hash( 166 chip.fw_name) 167 if not hashblob: 168 logging.warning('%s firmware hash not extracted from %s', 169 chip.chip_name, self.BIOS) 170 continue 171 172 bundled_fw_ver = chip.fw_ver_from_hash(hashblob) 173 if not bundled_fw_ver: 174 raise error.TestFail( 175 'could not decode %s firmware hash: %s' % ( 176 chip.chip_name, hashblob)) 177 178 chip.set_fw_ver_from_string(bundled_fw_ver) 179 cbfs_chip_info[chip.chip_name] = chip 180 logging.info('%s bundled firmware for %s is version %s', 181 self.BIOS, chip.chip_name, bundled_fw_ver) 182 return cbfs_chip_info 183 184 def check_chip_versions(self, port2chip, ref_chip_info): 185 """Verifies DUT chips have expected firmware. 186 187 Iterates over found DUT chips and verifies their firmware version 188 matches the chips found in the reference ref_chip_info map. 189 190 Args: 191 port2chip: A list of chips to verify against ref_chip_info. 192 ref_chip_info: A dict of reference chip chip instances indexed 193 by chip name. 194 """ 195 196 for p, pinfo in enumerate(port2chip): 197 if not pinfo.fw_ver: 198 # must be an unknown chip 199 continue 200 msg = 'DUT port %s is a %s running firmware 0x%02x' % ( 201 p, pinfo.chip_name, pinfo.fw_ver) 202 if pinfo.chip_name not in ref_chip_info: 203 logging.warning('%s but there is no reference version', msg) 204 continue 205 expected_fw_ver = ref_chip_info[pinfo.chip_name].fw_ver 206 logging.info('%s%s', msg, 207 ('' if pinfo.fw_ver == expected_fw_ver else 208 ' (expected 0x%02x)' % expected_fw_ver)) 209 210 if pinfo.fw_ver != expected_fw_ver: 211 msg = '%s firmware was not updated to 0x%02x' % ( 212 pinfo.chip_name, expected_fw_ver) 213 raise error.TestFail(msg) 214 215 def run_once(self, host): 216 # Make sure the client library is on the device so that the proxy 217 # code is there when we try to call it. 218 219 (dut_chip_types, dut_chips) = self.dut_scan_chips() 220 if not dut_chip_types: 221 logging.info('mosys reported no chips on DUT, skipping test') 222 return 223 224 self.dut_prep_cbfs() 225 if self.new_bios_path: 226 host.send_file(self.new_bios_path, self.dut_bios_path) 227 228 ref_chip_info = self.dut_cbfs_extract_chips(dut_chip_types) 229 self.check_chip_versions(dut_chips, ref_chip_info) 230