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""" 37 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 BIOS = 'bios.bin' 68 HEXDUMP = 'hexdump -v -e \'1/1 "0x%02x\\n"\'' 69 70 def initialize(self, host, cmdline_args): 71 dict_args = utils.args_to_dict(cmdline_args) 72 super(firmware_ChipFwUpdate, 73 self).initialize(host, cmdline_args) 74 75 self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None 76 77 self.clear_set_gbb_flags( 78 vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC | 79 vboot.GBB_FLAG_DISABLE_PD_SOFTWARE_SYNC, 0) 80 81 self.dut_bios_path = None 82 self.cbfs_work_dir = None 83 84 # set of chip types found in CBFS 85 self.cbfs_chip_types = set() 86 # dict of chip FW updates from the cmd line 87 self.req_chip_updates = {} 88 89 # see if comand line specified new firmware blobs 90 # for chips we know about 91 92 for chip in chip_utils.chip_id_map.itervalues(): 93 chip_name = chip.chip_name 94 if chip_name not in dict_args: 95 continue 96 chip_file = dict_args[chip_name] 97 if not os.path.exists(chip_file): 98 raise error.TestError('file %s not found' % chip_file) 99 c = chip() 100 c.set_from_file(chip_file) 101 if chip_name in self.req_chip_updates: 102 raise error.TestError('multiple %s args' % chip_name) 103 logging.info('request chip %s fw 0x%02x from command line', 104 c.chip_name, c.fw_ver) 105 self.req_chip_updates[chip_name] = c 106 107 def dut_setup_cbfs(self): 108 """Sets up a work dir for cbfstool. 109 110 Creates a fresh temp. dir for cbfstool to manipulate bios.bin. 111 """ 112 113 cbfs_path = self.faft_client.updater.cbfs_setup_work_dir() 114 bios_relative_path = self.faft_client.updater.get_bios_relative_path() 115 self.cbfs_work_dir = cbfs_path 116 self.dut_bios_path = os.path.join(cbfs_path, bios_relative_path) 117 118 def cbfs_extract_chips(self): 119 """Extracts interesting firmware blobs from cbfs. 120 121 Iterates over requested chip updates and looks for corresponding 122 firmware blobs in cbfs. Firmware blobs are then extracted into 123 cbfs_work_dir. 124 """ 125 126 for chip in self.req_chip_updates.itervalues(): 127 logging.info('checking for %s firmware in %s', 128 chip.chip_name, self.BIOS) 129 130 if not self.faft_client.updater.cbfs_extract_chip(chip.fw_name): 131 logging.warning('%s firmware not bundled in %s', 132 chip.chip_name, self.BIOS) 133 continue 134 135 hashblob = self.faft_client.updater.cbfs_get_chip_hash( 136 chip.fw_name) 137 if not hashblob: 138 logging.warning('%s firmware hash not extracted from %s', 139 chip.chip_name, self.BIOS) 140 continue 141 142 bundled_fw_ver = chip.fw_ver_from_hash(hashblob) 143 if not bundled_fw_ver: 144 raise error.TestFail( 145 'could not decode %s firmware hash: %s' % ( 146 chip.chip_name, hashblob)) 147 148 self.cbfs_chip_types.add(type(chip)) 149 logging.info('%s bundled firmware for %s is version %s', 150 self.BIOS, chip.chip_name, bundled_fw_ver) 151 152 def cbfs_replace_chips(self, host): 153 """Iterates over known chips in cbfs. 154 155 For each chip that has an update specified on the command line, 156 copies the firmware (bin, hash) to DUT and updates cbfs in 157 bios.bin. 158 159 Args: 160 host: host handle to the DUT. 161 """ 162 163 for chip in self.cbfs_chip_types: 164 chip_name = chip.chip_name 165 logging.info('replacing %s firmware in %s', chip_name, self.BIOS) 166 167 fw_update = self.req_chip_updates[chip_name] 168 fw_hash = fw_update.compute_hash_bytes() 169 (fd, n) = tempfile.mkstemp() 170 with os.fdopen(fd, 'wb') as f: 171 f.write(fw_hash) 172 173 try: 174 host.send_file(n, 175 os.path.join( 176 self.cbfs_work_dir, 177 fw_update.cbfs_hash_name)) 178 finally: 179 os.unlink(n) 180 181 host.send_file(fw_update.fw_file_name, 182 os.path.join( 183 self.cbfs_work_dir, 184 fw_update.cbfs_bin_name)) 185 186 if not self.faft_client.updater.cbfs_replace_chip( 187 fw_update.fw_name): 188 raise error.TestFail('could not replace %s blobs in cbfs' % 189 fw_update.chip_name) 190 191 def dut_sign_and_flash_bios(self, host): 192 """Signs the BIOS and flashes the DUT with it. 193 194 Args: 195 host: host handle to the DUT. 196 """ 197 198 if not self.faft_client.updater.cbfs_sign_and_flash(): 199 raise error.TestFail('could not re-sign %s' % self.dut_bios_path) 200 host.reboot() 201 202 def run_once(self, host): 203 # Make sure the client library is on the device so that the proxy 204 # code is there when we try to call it. 205 206 if not self.req_chip_updates: 207 logging.info('no FW updates requested, skipping test') 208 return 209 210 self.dut_setup_cbfs() 211 if self.new_bios_path: 212 host.send_file(self.new_bios_path, self.dut_bios_path) 213 214 self.cbfs_extract_chips() 215 if not self.cbfs_chip_types: 216 logging.info('firmware does not support requested updates, ' 217 'skipping test') 218 return 219 220 self.cbfs_replace_chips(host) 221 self.dut_sign_and_flash_bios(host) 222