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 for TCPC firmware updates. 6 7This test forces TCPC firmware updates for the specified TCPCs. 8 9The test is invoked with additional arguments to specify alternate 10TCPC firmware blobs. These are "edited" into the DUT's bios.bin 11normally extracted from the system shellball. Then, the bios.bin is 12flashed into the DUT and the DUT is rebooted. 13 14Under normal conditions, the TCPC firmware blobs will be updated as 15part of software sync when the DUT reboots. Software sync checks that 16the new firmware is actually running on the TCPCs, however it can also 17be audited after the fact using the firmware_CompareChipFwToShellBall 18FAFT test for independent verification. 19 20This test should be invoked twice: the 1st time to "downgrade" the 21TCPC firmware, then a 2nd time to restore the production TCPC 22firmware. Alternatively, the system can be reflashed with a 23production bios.bin (and rebooted) to restore the TCPC firmware. 24 25The parade ps8751 (and similar) parts can be re-flashed indefinitely. 26However, the analogix parts can only be updated about 100 times which 27means it is not feasible to include them in continuous automated 28testing. 29 30This test will only replace existing TCPC firmware blobs in bios.bin. 31If the corresponding binary blobs are not found in cbfs, it is assumed 32that the release does not support the requested TCPCs. Alternatively, 33a bios.bin can be specified when invoking the test that will be used 34insteade of the bios.bin normally extracted from the DUT's system 35shellball. 36""" 37import binascii 38import logging 39import os 40import tempfile 41 42from autotest_lib.client.common_lib import error 43from autotest_lib.client.common_lib import utils 44from autotest_lib.client.common_lib.cros import chip_utils 45from autotest_lib.server.cros import vboot_constants as vboot 46from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 47 48 49class firmware_ChipFwUpdate(FirmwareTest): 50 51 """Updates DUT firmware image with specified firmware blobs. 52 53 If a new bios.bin is offered, it replaces the 54 existing bios.bin. 55 Then, if new chip firmware blobs are offered, they 56 replace existing firmware blobs in bios.bin. 57 Finally the system shellball is repacked. 58 59 A reboot must be issued for the new firmware to be applied 60 during software sync. 61 62 Use the firmware_ChipFwUpdate test to verify that the new 63 firmware was applied. 64 """ 65 version = 1 66 67 def initialize(self, host, cmdline_args): 68 dict_args = utils.args_to_dict(cmdline_args) 69 super(firmware_ChipFwUpdate, 70 self).initialize(host, cmdline_args) 71 72 self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None 73 74 self.clear_set_gbb_flags( 75 vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC | 76 vboot.GBB_FLAG_DISABLE_AUXFW_SOFTWARE_SYNC, 0) 77 78 self.dut_bios_path = None 79 self.cbfs_work_dir = None 80 81 # set of chip types found in CBFS 82 self.cbfs_chip_types = set() 83 # dict of chip FW updates from the cmd line 84 self.req_chip_updates = {} 85 86 # see if comand line specified new firmware blobs 87 # for chips we know about 88 89 for chip_type in chip_utils.chip_id_map.values(): 90 chip_name = chip_type.chip_name 91 if chip_name not in dict_args: 92 continue 93 chip_file = dict_args[chip_name] 94 if not os.path.exists(chip_file): 95 raise error.TestError('file %s not found' % chip_file) 96 97 chip = chip_type() 98 chip.set_from_file(chip_file) 99 100 if chip_name in self.req_chip_updates: 101 raise error.TestError('multiple %s args' % chip_name) 102 103 fw_ver_desc = '' 104 if chip.fw_ver: 105 fw_ver_desc = ' (version 0x%02x)' % chip.fw_ver 106 logging.info('got %s firmware from args: %s%s', chip.chip_name, 107 chip_file, fw_ver_desc) 108 self.req_chip_updates[chip_name] = chip 109 110 def dut_setup_cbfs(self): 111 """Sets up a work dir for cbfstool. 112 113 Creates a fresh temp. dir for cbfstool to manipulate bios.bin. 114 """ 115 116 cbfs_path = self.faft_client.updater.cbfs_setup_work_dir() 117 bios_relative_path = self.faft_client.updater.get_bios_relative_path() 118 self.cbfs_work_dir = cbfs_path 119 self.dut_bios_path = os.path.join(cbfs_path, bios_relative_path) 120 121 def cbfs_extract_chips(self): 122 """Extracts interesting firmware blobs from cbfs. 123 124 Iterates over requested chip updates and looks for corresponding 125 firmware blobs in cbfs. Firmware blobs are then extracted into 126 cbfs_work_dir. 127 """ 128 129 for chip in self.req_chip_updates.values(): 130 logging.info('checking for %s firmware in CBFS', chip.chip_name) 131 132 if not self.faft_client.updater.cbfs_extract_chip( 133 chip.fw_name, chip.extension, chip.hash_extension): 134 logging.warning('%s firmware not bundled in CBFS', 135 chip.chip_name) 136 continue 137 138 hashblob = self.faft_client.updater.cbfs_get_chip_hash( 139 chip.fw_name, chip.hash_extension) 140 if hashblob: 141 if hasattr(chip, 'fw_ver_from_hash'): 142 bundled_fw_ver = chip.fw_ver_from_hash(hashblob) 143 if bundled_fw_ver is None: 144 raise error.TestFail( 145 'could not determine version from %s firmware ' 146 'hash: %s' % (chip.chip_name, hashblob)) 147 logging.info('CBFS bundled firmware for %s is version %s', 148 chip.chip_name, bundled_fw_ver) 149 else: 150 logging.info('CBFS bundled firmware for %s has hash %s', 151 chip.chip_name, hashblob) 152 else: 153 logging.warning('%s firmware hash not extracted from CBFS', 154 chip.chip_name) 155 self.cbfs_chip_types.add(type(chip)) 156 157 def cbfs_replace_chips(self, host): 158 """Iterates over known chips in cbfs. 159 160 For each chip that has an update specified on the command line, 161 copies the firmware (bin, hash) to DUT and updates cbfs in 162 bios.bin. 163 164 Args: 165 host: host handle to the DUT. 166 """ 167 168 for chip_type in self.cbfs_chip_types: 169 chip_name = chip_type.chip_name 170 logging.info('replacing %s firmware in CBFS', chip_name) 171 172 fw_update = self.req_chip_updates[chip_name] 173 fw_hash = fw_update.compute_hash_bytes() 174 logging.info("New file's hash is: %s", binascii.hexlify(fw_hash)) 175 (fd, n) = tempfile.mkstemp() 176 with os.fdopen(fd, 'wb') as f: 177 f.write(fw_hash) 178 179 try: 180 host.send_file(n, 181 os.path.join( 182 self.cbfs_work_dir, 183 fw_update.cbfs_hash_name)) 184 finally: 185 os.unlink(n) 186 187 host.send_file(fw_update.fw_file_name, 188 os.path.join( 189 self.cbfs_work_dir, 190 fw_update.cbfs_bin_name)) 191 192 if not self.faft_client.updater.cbfs_replace_chip( 193 fw_update.fw_name, fw_update.extension, 194 fw_update.hash_extension): 195 raise error.TestFail('could not replace %s blobs in cbfs' % 196 fw_update.chip_name) 197 198 def dut_sign_and_flash_bios(self, host): 199 """Signs the BIOS and flashes the DUT with it. 200 201 Args: 202 host: host handle to the DUT. 203 """ 204 205 if not self.faft_client.updater.cbfs_sign_and_flash(): 206 raise error.TestFail('could not re-sign %s' % self.dut_bios_path) 207 host.reboot() 208 209 def run_once(self, host): 210 """Runs a single iteration of the test.""" 211 # Make sure the client library is on the device so that the proxy 212 # code is there when we try to call it. 213 214 if not self.req_chip_updates: 215 logging.info('no FW updates requested, skipping test') 216 return 217 218 self.dut_setup_cbfs() 219 if self.new_bios_path: 220 host.send_file(self.new_bios_path, self.dut_bios_path) 221 222 self.cbfs_extract_chips() 223 if not self.cbfs_chip_types: 224 logging.info('firmware does not support requested updates, ' 225 'skipping test') 226 return 227 228 self.cbfs_replace_chips(host) 229 self.dut_sign_and_flash_bios(host) 230