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( 161 chip.fw_name, chip.extension, chip.hash_extension): 162 logging.warning('%s firmware not bundled in %s', 163 chip.chip_name, self.BIOS) 164 continue 165 166 hashblob = self.faft_client.updater.cbfs_get_chip_hash( 167 chip.fw_name, chip.hash_extension) 168 if not hashblob: 169 logging.warning('%s firmware hash not extracted from %s', 170 chip.chip_name, self.BIOS) 171 continue 172 173 bundled_fw_ver = chip.fw_ver_from_hash(hashblob) 174 if not bundled_fw_ver: 175 raise error.TestFail( 176 'could not decode %s firmware hash: %s' % ( 177 chip.chip_name, hashblob)) 178 179 chip.set_fw_ver_from_string(bundled_fw_ver) 180 cbfs_chip_info[chip.chip_name] = chip 181 logging.info('%s bundled firmware for %s is version %s', 182 self.BIOS, chip.chip_name, bundled_fw_ver) 183 return cbfs_chip_info 184 185 def check_chip_versions(self, port2chip, ref_chip_info): 186 """Verifies DUT chips have expected firmware. 187 188 Iterates over found DUT chips and verifies their firmware version 189 matches the chips found in the reference ref_chip_info map. 190 191 Args: 192 port2chip: A list of chips to verify against ref_chip_info. 193 ref_chip_info: A dict of reference chip chip instances indexed 194 by chip name. 195 """ 196 197 for p, pinfo in enumerate(port2chip): 198 if not pinfo.fw_ver: 199 # must be an unknown chip 200 continue 201 msg = 'DUT port %s is a %s running firmware 0x%02x' % ( 202 p, pinfo.chip_name, pinfo.fw_ver) 203 if pinfo.chip_name not in ref_chip_info: 204 logging.warning('%s but there is no reference version', msg) 205 continue 206 expected_fw_ver = ref_chip_info[pinfo.chip_name].fw_ver 207 logging.info('%s%s', msg, 208 ('' if pinfo.fw_ver == expected_fw_ver else 209 ' (expected 0x%02x)' % expected_fw_ver)) 210 211 if pinfo.fw_ver != expected_fw_ver: 212 msg = '%s firmware was not updated to 0x%02x' % ( 213 pinfo.chip_name, expected_fw_ver) 214 raise error.TestFail(msg) 215 216 def run_once(self, host): 217 """Runs a single iteration of the test.""" 218 # Make sure the client library is on the device so that the proxy 219 # code is there when we try to call it. 220 221 (dut_chip_types, dut_chips) = self.dut_scan_chips() 222 if not dut_chip_types: 223 logging.info('mosys reported no chips on DUT, skipping test') 224 return 225 226 self.dut_prep_cbfs() 227 if self.new_bios_path: 228 host.send_file(self.new_bios_path, self.dut_bios_path) 229 230 ref_chip_info = self.dut_cbfs_extract_chips(dut_chip_types) 231 self.check_chip_versions(dut_chips, ref_chip_info) 232