1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Lifrom __future__ import print_function 7*9c5db199SXin Li 8*9c5db199SXin Liimport logging 9*9c5db199SXin Liimport os 10*9c5db199SXin Liimport pprint 11*9c5db199SXin Liimport six 12*9c5db199SXin Liimport time 13*9c5db199SXin Li 14*9c5db199SXin Lifrom autotest_lib.client.bin import utils 15*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, utils 16*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import cr50_utils, tpm_utils 17*9c5db199SXin Lifrom autotest_lib.server.cros import filesystem_util, gsutil_wrapper 18*9c5db199SXin Lifrom autotest_lib.server.cros.faft.firmware_test import FirmwareTest 19*9c5db199SXin Li 20*9c5db199SXin Li 21*9c5db199SXin Liclass Cr50Test(FirmwareTest): 22*9c5db199SXin Li """Base class that sets up helper objects/functions for cr50 tests.""" 23*9c5db199SXin Li version = 1 24*9c5db199SXin Li 25*9c5db199SXin Li RELEASE_POOLS = ['faft-cr50-experimental', 'faft-cr50'] 26*9c5db199SXin Li RESPONSE_TIMEOUT = 180 27*9c5db199SXin Li GS_PRIVATE = 'gs://chromeos-localmirror-private/distfiles/' 28*9c5db199SXin Li # Prod signed test images are stored in the private cr50 directory. 29*9c5db199SXin Li GS_PRIVATE_PROD = GS_PRIVATE + 'cr50/' 30*9c5db199SXin Li # Node locked test images are in this private debug directory. 31*9c5db199SXin Li GS_PRIVATE_DBG = GS_PRIVATE + 'chromeos-cr50-debug-0.0.11/' 32*9c5db199SXin Li GS_PUBLIC = 'gs://chromeos-localmirror/distfiles/' 33*9c5db199SXin Li CR50_PROD_FILE = 'cr50.r0.0.1*.w%s%s.tbz2' 34*9c5db199SXin Li CR50_DEBUG_FILE = '*/cr50.dbg.%s.bin.*%s' 35*9c5db199SXin Li CR50_ERASEFLASHINFO_FILE = ( 36*9c5db199SXin Li '*/cr50_Unknown_NodeLocked-%s_cr50-accessory-mp.bin') 37*9c5db199SXin Li CR50_QUAL_VERSION_FILE = 'chromeos-cr50-QUAL_VERSION' 38*9c5db199SXin Li NONE = 0 39*9c5db199SXin Li # Saved the original device state during init. 40*9c5db199SXin Li INITIAL_IMAGE_STATE = 1 << 0 41*9c5db199SXin Li # Saved the original image, the device image, and the debug image. These 42*9c5db199SXin Li # images are needed to be able to restore the original image and board id. 43*9c5db199SXin Li DEVICE_IMAGES = 1 << 1 44*9c5db199SXin Li DBG_IMAGE = 1 << 2 45*9c5db199SXin Li ERASEFLASHINFO_IMAGE = 1 << 3 46*9c5db199SXin Li # Different attributes of the device state require the test to download 47*9c5db199SXin Li # different images. STATE_IMAGE_RESTORES is a dictionary of the state each 48*9c5db199SXin Li # image type can restore. 49*9c5db199SXin Li STATE_IMAGE_RESTORES = { 50*9c5db199SXin Li DEVICE_IMAGES: ['prod_version', 'prepvt_version'], 51*9c5db199SXin Li DBG_IMAGE: ['running_image_ver', 'running_image_bid', 'chip_bid'], 52*9c5db199SXin Li ERASEFLASHINFO_IMAGE: ['chip_bid'], 53*9c5db199SXin Li } 54*9c5db199SXin Li PP_SHORT_INTERVAL = 3 55*9c5db199SXin Li 56*9c5db199SXin Li def initialize(self, 57*9c5db199SXin Li host, 58*9c5db199SXin Li cmdline_args, 59*9c5db199SXin Li full_args, 60*9c5db199SXin Li restore_cr50_image=False, 61*9c5db199SXin Li restore_cr50_board_id=False, 62*9c5db199SXin Li provision_update=False): 63*9c5db199SXin Li self._saved_state = self.NONE 64*9c5db199SXin Li self._raise_error_on_mismatch = not restore_cr50_image 65*9c5db199SXin Li self._provision_update = provision_update 66*9c5db199SXin Li self.tot_test_run = full_args.get('tot_test_run', '').lower() == 'true' 67*9c5db199SXin Li super(Cr50Test, self).initialize(host, cmdline_args) 68*9c5db199SXin Li 69*9c5db199SXin Li if not hasattr(self, 'cr50'): 70*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 71*9c5db199SXin Li 'access to the Cr50 console') 72*9c5db199SXin Li # TODO(b/149948314): remove when dual-v4 is sorted out. 73*9c5db199SXin Li if 'ccd' in self.servo.get_servo_version(): 74*9c5db199SXin Li self.servo.disable_ccd_watchdog_for_test() 75*9c5db199SXin Li 76*9c5db199SXin Li logging.info('Test Args: %r', full_args) 77*9c5db199SXin Li 78*9c5db199SXin Li self._devid = self.cr50.get_devid() 79*9c5db199SXin Li self.can_set_ccd_level = (not self.servo.main_device_is_ccd() 80*9c5db199SXin Li or self.cr50.testlab_is_on()) 81*9c5db199SXin Li self.original_ccd_level = self.cr50.get_ccd_level() 82*9c5db199SXin Li self.original_ccd_settings = self.cr50.get_cap_dict( 83*9c5db199SXin Li info=self.cr50.CAP_SETTING) 84*9c5db199SXin Li 85*9c5db199SXin Li self.host = host 86*9c5db199SXin Li # SSH commands should complete within 3 minutes. Change the default, so 87*9c5db199SXin Li # it doesn't take half an hour for commands to timeout when the DUT is 88*9c5db199SXin Li # down. 89*9c5db199SXin Li self.host.set_default_run_timeout(180) 90*9c5db199SXin Li tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True) 91*9c5db199SXin Li # Clear the FWMP, so it can't disable CCD. 92*9c5db199SXin Li self.clear_fwmp() 93*9c5db199SXin Li 94*9c5db199SXin Li # TODO(b/218492933) : find better way to disable rddkeepalive 95*9c5db199SXin Li # Disable rddkeepalive, so the test can disable ccd. 96*9c5db199SXin Li self.cr50.send_command('ccd testlab open') 97*9c5db199SXin Li self.cr50.send_command('rddkeepalive disable') 98*9c5db199SXin Li # faft-cr50 locks and reopens ccd. This will restrict some capabilities 99*9c5db199SXin Li # c2d2 uses to control the duts. Set the capabilities to Always, so 100*9c5db199SXin Li # individiual tests don't need to care that much. 101*9c5db199SXin Li self.cr50.enable_servo_control_caps() 102*9c5db199SXin Li 103*9c5db199SXin Li if self.can_set_ccd_level: 104*9c5db199SXin Li # Lock cr50 so the console will be restricted 105*9c5db199SXin Li self.cr50.set_ccd_level('lock') 106*9c5db199SXin Li elif self.original_ccd_level != 'lock': 107*9c5db199SXin Li raise error.TestNAError( 108*9c5db199SXin Li 'Lock the console before running cr50 test') 109*9c5db199SXin Li 110*9c5db199SXin Li self._save_original_state(full_args.get('release_path', '')) 111*9c5db199SXin Li 112*9c5db199SXin Li # Try and download all images necessary to restore cr50 state. 113*9c5db199SXin Li try: 114*9c5db199SXin Li self._save_dbg_image(full_args.get('cr50_dbg_image_path', '')) 115*9c5db199SXin Li self._saved_state |= self.DBG_IMAGE 116*9c5db199SXin Li except Exception as e: 117*9c5db199SXin Li logging.warning('Error saving DBG image: %s', str(e)) 118*9c5db199SXin Li if restore_cr50_image: 119*9c5db199SXin Li raise error.TestNAError('Need DBG image: %s' % str(e)) 120*9c5db199SXin Li 121*9c5db199SXin Li try: 122*9c5db199SXin Li self._save_eraseflashinfo_image( 123*9c5db199SXin Li full_args.get('cr50_eraseflashinfo_image_path', '')) 124*9c5db199SXin Li if self.cr50.uses_board_property('BOARD_EC_CR50_COMM_SUPPORT'): 125*9c5db199SXin Li raise error.TestError('Board cannot boot EFI image') 126*9c5db199SXin Li self._saved_state |= self.ERASEFLASHINFO_IMAGE 127*9c5db199SXin Li except Exception as e: 128*9c5db199SXin Li logging.warning('Error saving eraseflashinfo image: %s', str(e)) 129*9c5db199SXin Li if restore_cr50_board_id: 130*9c5db199SXin Li raise error.TestNAError( 131*9c5db199SXin Li 'Need eraseflashinfo image: %s' % str(e)) 132*9c5db199SXin Li 133*9c5db199SXin Li # TODO(b/143888583): remove qual update during init once new design to 134*9c5db199SXin Li # to provision cr50 updates is in place. 135*9c5db199SXin Li # Make sure the release image is running before starting the test. 136*9c5db199SXin Li is_release_qual = full_args.get('is_release_qual', 137*9c5db199SXin Li '').lower() == 'true' 138*9c5db199SXin Li if is_release_qual or self.running_cr50_release_suite(): 139*9c5db199SXin Li release_ver_arg = full_args.get('release_ver', '') 140*9c5db199SXin Li release_path_arg = full_args.get('release_path', '') 141*9c5db199SXin Li self.ensure_qual_image_is_running(release_ver_arg, 142*9c5db199SXin Li release_path_arg) 143*9c5db199SXin Li 144*9c5db199SXin Li def running_cr50_release_suite(self): 145*9c5db199SXin Li """Return True if the DUT is in a release pool.""" 146*9c5db199SXin Li for pool in self.host.host_info_store.get().pools: 147*9c5db199SXin Li # TODO(b/149109740): remove once the pool values are verified. 148*9c5db199SXin Li # Change to run with faft-cr50 and faft-cr50-experimental suites. 149*9c5db199SXin Li logging.info('Checking pool: %s', pool) 150*9c5db199SXin Li if pool in self.RELEASE_POOLS: 151*9c5db199SXin Li logging.info('Running a release test.') 152*9c5db199SXin Li return True 153*9c5db199SXin Li return False 154*9c5db199SXin Li 155*9c5db199SXin Li def ensure_qual_image_is_running(self, qual_ver_str, qual_path): 156*9c5db199SXin Li """Update to the qualification image if it's not running. 157*9c5db199SXin Li 158*9c5db199SXin Li qual_ver_str and path are command line args that may be supplied to 159*9c5db199SXin Li specify a local version or path. If neither are supplied, the version 160*9c5db199SXin Li from gs will be used to determine what version cr50 should run. 161*9c5db199SXin Li 162*9c5db199SXin Li qual_ver_str and qual_path should not be supplied together. If they are, 163*9c5db199SXin Li the path will be used. It's not a big deal as long as they agree with 164*9c5db199SXin Li each other. 165*9c5db199SXin Li 166*9c5db199SXin Li @param qual_ver_str: qualification version string or None. 167*9c5db199SXin Li @param qual_path: local path to the qualification image or None. 168*9c5db199SXin Li """ 169*9c5db199SXin Li # Get the local image information. 170*9c5db199SXin Li if qual_path: 171*9c5db199SXin Li dest, qual_ver = cr50_utils.InstallImage(self.host, qual_path, 172*9c5db199SXin Li '/tmp/qual_cr50.bin') 173*9c5db199SXin Li self.host.run('rm ' + dest) 174*9c5db199SXin Li qual_bid_str = (cr50_utils.GetBoardIdInfoString( 175*9c5db199SXin Li qual_ver[2], False) if qual_ver[2] else '') 176*9c5db199SXin Li qual_ver_str = '%s/%s' % (qual_ver[1], qual_bid_str) 177*9c5db199SXin Li 178*9c5db199SXin Li # Determine the qualification version from. 179*9c5db199SXin Li if not qual_ver_str: 180*9c5db199SXin Li gsurl = os.path.join(self.GS_PRIVATE, self.CR50_QUAL_VERSION_FILE) 181*9c5db199SXin Li dut_path = self.download_cr50_gs_file(gsurl, False)[1] 182*9c5db199SXin Li qual_ver_str = self.host.run('cat ' + dut_path).stdout.strip() 183*9c5db199SXin Li 184*9c5db199SXin Li # Download the qualification image based on the version. 185*9c5db199SXin Li if not qual_path: 186*9c5db199SXin Li rw, bid = qual_ver_str.split('/') 187*9c5db199SXin Li qual_path, qual_ver = self.download_cr50_release_image(rw, bid) 188*9c5db199SXin Li 189*9c5db199SXin Li logging.info('Cr50 Qual Version: %s', qual_ver_str) 190*9c5db199SXin Li logging.info('Cr50 Qual Path: %s', qual_path) 191*9c5db199SXin Li qual_chip_bid = cr50_utils.GetChipBIDFromImageBID( 192*9c5db199SXin Li qual_ver[2], self.get_device_brand()) 193*9c5db199SXin Li logging.info('Cr50 Qual Chip BID: %s', qual_chip_bid) 194*9c5db199SXin Li 195*9c5db199SXin Li # Replace only the prod or prepvt image based on the major version. 196*9c5db199SXin Li if int(qual_ver[1].split('.')[1]) % 2: 197*9c5db199SXin Li prod_ver = qual_ver 198*9c5db199SXin Li prepvt_ver = self._original_image_state['prepvt_version'] 199*9c5db199SXin Li prod_path = qual_path 200*9c5db199SXin Li prepvt_path = self._device_prepvt_image 201*9c5db199SXin Li else: 202*9c5db199SXin Li prod_ver = self._original_image_state['prod_version'] 203*9c5db199SXin Li prepvt_ver = qual_ver 204*9c5db199SXin Li prod_path = self._device_prod_image 205*9c5db199SXin Li prepvt_path = qual_path 206*9c5db199SXin Li 207*9c5db199SXin Li # Generate a dictionary with all of the expected state. 208*9c5db199SXin Li qual_state = {} 209*9c5db199SXin Li qual_state['prod_version'] = prod_ver 210*9c5db199SXin Li qual_state['prepvt_version'] = prepvt_ver 211*9c5db199SXin Li qual_state['chip_bid'] = qual_chip_bid 212*9c5db199SXin Li qual_state['running_image_bid'] = qual_ver[2] 213*9c5db199SXin Li # The test can't rollback RO. The newest RO should be running at the end 214*9c5db199SXin Li # of the test. max_ro will be none if the versions are the same. Use the 215*9c5db199SXin Li # running_ro in that case. 216*9c5db199SXin Li running_ro = self.get_saved_cr50_original_version()[0] 217*9c5db199SXin Li max_ro = cr50_utils.GetNewestVersion(running_ro, qual_ver[0]) 218*9c5db199SXin Li qual_state['running_image_ver'] = (max_ro or running_ro, qual_ver[1], 219*9c5db199SXin Li None) 220*9c5db199SXin Li mismatch = self._check_running_image_and_board_id(qual_state) 221*9c5db199SXin Li if not mismatch: 222*9c5db199SXin Li logging.info('Running qual image. No update needed.') 223*9c5db199SXin Li return 224*9c5db199SXin Li logging.info('Cr50 qual update required.') 225*9c5db199SXin Li self.make_rootfs_writable() 226*9c5db199SXin Li self._update_device_images_and_running_cr50_firmware( 227*9c5db199SXin Li qual_state, qual_path, prod_path, prepvt_path) 228*9c5db199SXin Li logging.info("Recording qual device state as 'original' device state") 229*9c5db199SXin Li self._save_original_state(qual_path) 230*9c5db199SXin Li 231*9c5db199SXin Li def make_rootfs_writable(self): 232*9c5db199SXin Li """Make rootfs writeable. Recover the dut if necessary.""" 233*9c5db199SXin Li path = None 234*9c5db199SXin Li try: 235*9c5db199SXin Li filesystem_util.make_rootfs_writable(self.host) 236*9c5db199SXin Li return 237*9c5db199SXin Li except error.AutoservRunError as e: 238*9c5db199SXin Li if 'cannot remount' not in e.result_obj.stderr: 239*9c5db199SXin Li raise 240*9c5db199SXin Li path = e.result_obj.stderr.partition( 241*9c5db199SXin Li 'cannot remount')[2].split()[0] 242*9c5db199SXin Li # This shouldn't be possible. 243*9c5db199SXin Li if not path: 244*9c5db199SXin Li raise error.TestError('Need path to repair filesystem') 245*9c5db199SXin Li logging.info('repair %s', path) 246*9c5db199SXin Li # Repair the block. Assume yes to all questions. The exit status will be 247*9c5db199SXin Li # 3, so ignore errors. make_rootfs_writable will fail if something 248*9c5db199SXin Li # actually went wrong. 249*9c5db199SXin Li self.host.run('e2fsck -y %s' % path, ignore_status=True) 250*9c5db199SXin Li self.host.reboot() 251*9c5db199SXin Li filesystem_util.make_rootfs_writable(self.host) 252*9c5db199SXin Li 253*9c5db199SXin Li def _saved_cr50_state(self, state): 254*9c5db199SXin Li """Returns True if the test has saved the given state 255*9c5db199SXin Li 256*9c5db199SXin Li @param state: a integer representing the state to check. 257*9c5db199SXin Li """ 258*9c5db199SXin Li return state & self._saved_state 259*9c5db199SXin Li 260*9c5db199SXin Li def after_run_once(self): 261*9c5db199SXin Li """Log which iteration just ran""" 262*9c5db199SXin Li logging.info('successfully ran iteration %d', self.iteration) 263*9c5db199SXin Li self._try_to_bring_dut_up() 264*9c5db199SXin Li 265*9c5db199SXin Li def _save_dbg_image(self, cr50_dbg_image_path): 266*9c5db199SXin Li """Save or download the node locked dev image. 267*9c5db199SXin Li 268*9c5db199SXin Li @param cr50_dbg_image_path: The path to the node locked cr50 image. 269*9c5db199SXin Li """ 270*9c5db199SXin Li if os.path.isfile(cr50_dbg_image_path): 271*9c5db199SXin Li self._dbg_image_path = cr50_dbg_image_path 272*9c5db199SXin Li else: 273*9c5db199SXin Li self._dbg_image_path = self.download_cr50_debug_image()[0] 274*9c5db199SXin Li 275*9c5db199SXin Li def _save_eraseflashinfo_image(self, cr50_eraseflashinfo_image_path): 276*9c5db199SXin Li """Save or download the node locked eraseflashinfo image. 277*9c5db199SXin Li 278*9c5db199SXin Li @param cr50_eraseflashinfo_image_path: The path to the node locked cr50 279*9c5db199SXin Li image. 280*9c5db199SXin Li """ 281*9c5db199SXin Li if os.path.isfile(cr50_eraseflashinfo_image_path): 282*9c5db199SXin Li self._eraseflashinfo_image_path = cr50_eraseflashinfo_image_path 283*9c5db199SXin Li else: 284*9c5db199SXin Li self._eraseflashinfo_image_path = ( 285*9c5db199SXin Li self.download_cr50_eraseflashinfo_image()[0]) 286*9c5db199SXin Li 287*9c5db199SXin Li def _save_device_image(self, ext): 288*9c5db199SXin Li """Download the .prod or .prepvt device image and get the version. 289*9c5db199SXin Li 290*9c5db199SXin Li @param ext: The Cr50 file extension: prod or prepvt. 291*9c5db199SXin Li @returns (local_path, rw_version, bid_string) or (None, None, None) if 292*9c5db199SXin Li the file doesn't exist on the DUT. 293*9c5db199SXin Li """ 294*9c5db199SXin Li version = self._original_image_state[ext + '_version'] 295*9c5db199SXin Li if not version: 296*9c5db199SXin Li return None, None, None 297*9c5db199SXin Li _, rw_ver, bid = version 298*9c5db199SXin Li rw_filename = 'cr50.device.bin.%s.%s' % (ext, rw_ver) 299*9c5db199SXin Li local_path = os.path.join(self.resultsdir, rw_filename) 300*9c5db199SXin Li dut_path = cr50_utils.GetDevicePath(ext) 301*9c5db199SXin Li self.host.get_file(dut_path, local_path) 302*9c5db199SXin Li bid = cr50_utils.GetBoardIdInfoString(bid) 303*9c5db199SXin Li return local_path, rw_ver, bid 304*9c5db199SXin Li 305*9c5db199SXin Li def _save_original_images(self, release_path): 306*9c5db199SXin Li """Use the saved state to find all of the device images. 307*9c5db199SXin Li 308*9c5db199SXin Li This will download running cr50 image and the device image. 309*9c5db199SXin Li 310*9c5db199SXin Li @param release_path: The release path given by test args 311*9c5db199SXin Li """ 312*9c5db199SXin Li local_path, prod_rw, prod_bid = self._save_device_image('prod') 313*9c5db199SXin Li self._device_prod_image = local_path 314*9c5db199SXin Li 315*9c5db199SXin Li local_path, prepvt_rw, prepvt_bid = self._save_device_image('prepvt') 316*9c5db199SXin Li self._device_prepvt_image = local_path 317*9c5db199SXin Li 318*9c5db199SXin Li if os.path.isfile(release_path): 319*9c5db199SXin Li self._original_cr50_image = release_path 320*9c5db199SXin Li logging.info('using supplied image') 321*9c5db199SXin Li return 322*9c5db199SXin Li if self.tot_test_run: 323*9c5db199SXin Li self._original_cr50_image = self.download_cr50_tot_image() 324*9c5db199SXin Li return 325*9c5db199SXin Li 326*9c5db199SXin Li # If the running cr50 image version matches the image on the DUT use 327*9c5db199SXin Li # the DUT image as the original image. If the versions don't match 328*9c5db199SXin Li # download the image from google storage 329*9c5db199SXin Li _, running_rw, running_bid = self.get_saved_cr50_original_version() 330*9c5db199SXin Li 331*9c5db199SXin Li # Convert the running board id to the same format as the prod and 332*9c5db199SXin Li # prepvt board ids. 333*9c5db199SXin Li running_bid = cr50_utils.GetBoardIdInfoString(running_bid) 334*9c5db199SXin Li if running_rw == prod_rw and running_bid == prod_bid: 335*9c5db199SXin Li logging.info('Using device cr50 prod image %s %s', prod_rw, 336*9c5db199SXin Li prod_bid) 337*9c5db199SXin Li self._original_cr50_image = self._device_prod_image 338*9c5db199SXin Li elif running_rw == prepvt_rw and running_bid == prepvt_bid: 339*9c5db199SXin Li logging.info('Using device cr50 prepvt image %s %s', prepvt_rw, 340*9c5db199SXin Li prepvt_bid) 341*9c5db199SXin Li self._original_cr50_image = self._device_prepvt_image 342*9c5db199SXin Li else: 343*9c5db199SXin Li logging.info('Downloading cr50 image %s %s', running_rw, 344*9c5db199SXin Li running_bid) 345*9c5db199SXin Li self._original_cr50_image = self.download_cr50_release_image( 346*9c5db199SXin Li running_rw, running_bid)[0] 347*9c5db199SXin Li 348*9c5db199SXin Li def _save_original_state(self, release_path): 349*9c5db199SXin Li """Save the cr50 related state. 350*9c5db199SXin Li 351*9c5db199SXin Li Save the device's current cr50 version, cr50 board id, the running cr50 352*9c5db199SXin Li image, the prepvt, and prod cr50 images. These will be used to restore 353*9c5db199SXin Li the cr50 state during cleanup. 354*9c5db199SXin Li 355*9c5db199SXin Li @param release_path: the optional command line arg of path for the local 356*9c5db199SXin Li cr50 image. 357*9c5db199SXin Li """ 358*9c5db199SXin Li self._saved_state &= ~self.INITIAL_IMAGE_STATE 359*9c5db199SXin Li self._original_image_state = self.get_image_and_bid_state() 360*9c5db199SXin Li # We successfully saved the device state 361*9c5db199SXin Li self._saved_state |= self.INITIAL_IMAGE_STATE 362*9c5db199SXin Li self._saved_state &= ~self.DEVICE_IMAGES 363*9c5db199SXin Li try: 364*9c5db199SXin Li self._save_original_images(release_path) 365*9c5db199SXin Li self._saved_state |= self.DEVICE_IMAGES 366*9c5db199SXin Li except Exception as e: 367*9c5db199SXin Li logging.warning('Error saving ChromeOS image cr50 firmware: %s', 368*9c5db199SXin Li str(e)) 369*9c5db199SXin Li 370*9c5db199SXin Li def get_saved_cr50_original_version(self): 371*9c5db199SXin Li """Return (ro ver, rw ver, bid).""" 372*9c5db199SXin Li if ('running_image_ver' not in self._original_image_state 373*9c5db199SXin Li or 'running_image_bid' not in self._original_image_state): 374*9c5db199SXin Li raise error.TestError('No record of original cr50 image version') 375*9c5db199SXin Li return (self._original_image_state['running_image_ver'][0], 376*9c5db199SXin Li self._original_image_state['running_image_ver'][1], 377*9c5db199SXin Li self._original_image_state['running_image_bid']) 378*9c5db199SXin Li 379*9c5db199SXin Li def get_saved_cr50_original_path(self): 380*9c5db199SXin Li """Return the local path for the original cr50 image.""" 381*9c5db199SXin Li if not hasattr(self, '_original_cr50_image'): 382*9c5db199SXin Li raise error.TestError('No record of original image') 383*9c5db199SXin Li return self._original_cr50_image 384*9c5db199SXin Li 385*9c5db199SXin Li def has_saved_dbg_image_path(self): 386*9c5db199SXin Li """Returns true if we saved the node locked debug image.""" 387*9c5db199SXin Li return hasattr(self, '_dbg_image_path') 388*9c5db199SXin Li 389*9c5db199SXin Li def get_saved_dbg_image_path(self): 390*9c5db199SXin Li """Return the local path for the cr50 dev image.""" 391*9c5db199SXin Li if not self.has_saved_dbg_image_path(): 392*9c5db199SXin Li raise error.TestError('No record of debug image') 393*9c5db199SXin Li return self._dbg_image_path 394*9c5db199SXin Li 395*9c5db199SXin Li def get_saved_eraseflashinfo_image_path(self): 396*9c5db199SXin Li """Return the local path for the cr50 eraseflashinfo image.""" 397*9c5db199SXin Li if not hasattr(self, '_eraseflashinfo_image_path'): 398*9c5db199SXin Li raise error.TestError('No record of eraseflashinfo image') 399*9c5db199SXin Li return self._eraseflashinfo_image_path 400*9c5db199SXin Li 401*9c5db199SXin Li def get_device_brand(self): 402*9c5db199SXin Li """Returns the 4 character device brand.""" 403*9c5db199SXin Li return self._original_image_state['cros_config / brand-code'] 404*9c5db199SXin Li 405*9c5db199SXin Li def _retry_cr50_update(self, image, retries, rollback): 406*9c5db199SXin Li """Try to update to the given image retries amount of times. 407*9c5db199SXin Li 408*9c5db199SXin Li @param image: The image path. 409*9c5db199SXin Li @param retries: The number of times to try to update. 410*9c5db199SXin Li @param rollback: Run rollback after the update. 411*9c5db199SXin Li @raises TestFail if the update failed. 412*9c5db199SXin Li """ 413*9c5db199SXin Li for i in range(retries): 414*9c5db199SXin Li try: 415*9c5db199SXin Li return self.cr50_update(image, rollback=rollback) 416*9c5db199SXin Li except Exception as e: 417*9c5db199SXin Li logging.warning('Failed to update to %s attempt %d: %s', 418*9c5db199SXin Li os.path.basename(image), i, str(e)) 419*9c5db199SXin Li logging.info('Sleeping 60 seconds') 420*9c5db199SXin Li time.sleep(60) 421*9c5db199SXin Li raise error.TestError( 422*9c5db199SXin Li 'Failed to update to %s' % os.path.basename(image)) 423*9c5db199SXin Li 424*9c5db199SXin Li def run_update_to_eraseflashinfo(self): 425*9c5db199SXin Li """Erase flashinfo using the eraseflashinfo image. 426*9c5db199SXin Li 427*9c5db199SXin Li Update to the DBG image, rollback to the eraseflashinfo, and run 428*9c5db199SXin Li eraseflashinfo. 429*9c5db199SXin Li """ 430*9c5db199SXin Li self._retry_cr50_update(self._dbg_image_path, 3, False) 431*9c5db199SXin Li self._retry_cr50_update(self._eraseflashinfo_image_path, 3, True) 432*9c5db199SXin Li if not self.cr50.eraseflashinfo(): 433*9c5db199SXin Li raise error.TestError('Unable to erase the board id') 434*9c5db199SXin Li 435*9c5db199SXin Li def eraseflashinfo_and_restore_image(self, image=''): 436*9c5db199SXin Li """eraseflashinfo and update to the given the image. 437*9c5db199SXin Li 438*9c5db199SXin Li @param image: the image to end on. Use the original test image if no 439*9c5db199SXin Li image is given. 440*9c5db199SXin Li """ 441*9c5db199SXin Li image = image if image else self.get_saved_cr50_original_path() 442*9c5db199SXin Li self.run_update_to_eraseflashinfo() 443*9c5db199SXin Li self.cr50_update(image) 444*9c5db199SXin Li 445*9c5db199SXin Li def update_cr50_image_and_board_id(self, image_path, bid): 446*9c5db199SXin Li """Set the chip board id and updating the cr50 image. 447*9c5db199SXin Li 448*9c5db199SXin Li Make 3 attempts to update to the original image. Use a rollback from 449*9c5db199SXin Li the DBG image to erase the state that can only be erased by a DBG image. 450*9c5db199SXin Li Set the chip board id during rollback. 451*9c5db199SXin Li 452*9c5db199SXin Li @param image_path: path of the image to update to. 453*9c5db199SXin Li @param bid: the board id to set. 454*9c5db199SXin Li """ 455*9c5db199SXin Li current_bid = cr50_utils.GetChipBoardId(self.host) 456*9c5db199SXin Li bid_mismatch = current_bid != bid 457*9c5db199SXin Li set_bid = bid_mismatch and bid != cr50_utils.ERASED_CHIP_BID 458*9c5db199SXin Li bid_is_erased = current_bid == cr50_utils.ERASED_CHIP_BID 459*9c5db199SXin Li eraseflashinfo = bid_mismatch and not bid_is_erased 460*9c5db199SXin Li 461*9c5db199SXin Li if (eraseflashinfo 462*9c5db199SXin Li and not self._saved_cr50_state(self.ERASEFLASHINFO_IMAGE)): 463*9c5db199SXin Li raise error.TestFail('Did not save eraseflashinfo image') 464*9c5db199SXin Li 465*9c5db199SXin Li # Remove prepvt and prod iamges, so they don't interfere with the test 466*9c5db199SXin Li # rolling back and updating to images that my be older than the images 467*9c5db199SXin Li # on the device. 468*9c5db199SXin Li if filesystem_util.is_rootfs_writable(self.host): 469*9c5db199SXin Li self.host.run('rm %s' % cr50_utils.CR50_PREPVT, ignore_status=True) 470*9c5db199SXin Li self.host.run('rm %s' % cr50_utils.CR50_PROD, ignore_status=True) 471*9c5db199SXin Li 472*9c5db199SXin Li if eraseflashinfo: 473*9c5db199SXin Li self.run_update_to_eraseflashinfo() 474*9c5db199SXin Li 475*9c5db199SXin Li self._retry_cr50_update(self._dbg_image_path, 3, False) 476*9c5db199SXin Li 477*9c5db199SXin Li chip_bid = bid[0] 478*9c5db199SXin Li chip_flags = bid[2] 479*9c5db199SXin Li if set_bid: 480*9c5db199SXin Li self.cr50.set_board_id(chip_bid, chip_flags) 481*9c5db199SXin Li 482*9c5db199SXin Li self._retry_cr50_update(image_path, 3, True) 483*9c5db199SXin Li 484*9c5db199SXin Li def _discharging_factory_mode_cleanup(self): 485*9c5db199SXin Li """Try to get the dut back into charging mode. 486*9c5db199SXin Li 487*9c5db199SXin Li Shutdown the DUT, fake disconnect AC, and then turn on the DUT to 488*9c5db199SXin Li try to recover the EC. 489*9c5db199SXin Li 490*9c5db199SXin Li When Cr50 enters factory mode on Wilco, the EC disables charging. 491*9c5db199SXin Li Try to run the sequence to get the Wilco EC out of the factory mode 492*9c5db199SXin Li state, so it reenables charging. 493*9c5db199SXin Li """ 494*9c5db199SXin Li if self.faft_config.chrome_ec: 495*9c5db199SXin Li return 496*9c5db199SXin Li charge_state = self.host.get_power_supply_info()['Battery']['state'] 497*9c5db199SXin Li logging.info('Charge state: %r', charge_state) 498*9c5db199SXin Li if 'Discharging' not in charge_state: 499*9c5db199SXin Li logging.info('Charge state is ok') 500*9c5db199SXin Li return 501*9c5db199SXin Li 502*9c5db199SXin Li if not self.servo.is_servo_v4_type_c(): 503*9c5db199SXin Li raise error.TestError( 504*9c5db199SXin Li 'Cannot recover charging without Type C servo') 505*9c5db199SXin Li # Disconnect the charger and reset the dut to recover charging. 506*9c5db199SXin Li logging.info('Recovering charging') 507*9c5db199SXin Li self.faft_client.system.run_shell_command('poweroff') 508*9c5db199SXin Li time.sleep(self.cr50.SHORT_WAIT) 509*9c5db199SXin Li self.servo.set_nocheck('servo_v4_uart_cmd', 'fakedisconnect 100 20000') 510*9c5db199SXin Li time.sleep(self.cr50.SHORT_WAIT) 511*9c5db199SXin Li self._try_to_bring_dut_up() 512*9c5db199SXin Li charge_state = self.host.get_power_supply_info()['Battery']['state'] 513*9c5db199SXin Li logging.info('Charge state: %r', charge_state) 514*9c5db199SXin Li if 'Discharging' in charge_state: 515*9c5db199SXin Li logging.warning('DUT still discharging') 516*9c5db199SXin Li 517*9c5db199SXin Li def _cleanup_required(self, state_mismatch, image_type): 518*9c5db199SXin Li """Return True if the update can fix something in the mismatched state. 519*9c5db199SXin Li 520*9c5db199SXin Li @param state_mismatch: a dictionary of the mismatched state. 521*9c5db199SXin Li @param image_type: The integer representing the type of image 522*9c5db199SXin Li """ 523*9c5db199SXin Li state_image_restores = set(self.STATE_IMAGE_RESTORES[image_type]) 524*9c5db199SXin Li restore = state_image_restores.intersection(state_mismatch.keys()) 525*9c5db199SXin Li if restore and not self._saved_cr50_state(image_type): 526*9c5db199SXin Li raise error.TestError( 527*9c5db199SXin Li 'Did not save images to restore %s' % (', '.join(restore))) 528*9c5db199SXin Li return not not restore 529*9c5db199SXin Li 530*9c5db199SXin Li def _get_image_information(self, ext): 531*9c5db199SXin Li """Get the image information for the .prod or .prepvt image. 532*9c5db199SXin Li 533*9c5db199SXin Li @param ext: The extension string prod or prepvt 534*9c5db199SXin Li @param returns: The image version or None if the image doesn't exist. 535*9c5db199SXin Li """ 536*9c5db199SXin Li dut_path = cr50_utils.GetDevicePath(ext) 537*9c5db199SXin Li file_exists = self.host.path_exists(dut_path) 538*9c5db199SXin Li if file_exists: 539*9c5db199SXin Li return cr50_utils.GetBinVersion(self.host, dut_path) 540*9c5db199SXin Li return None 541*9c5db199SXin Li 542*9c5db199SXin Li def get_image_and_bid_state(self): 543*9c5db199SXin Li """Get a dict with the current device cr50 information. 544*9c5db199SXin Li 545*9c5db199SXin Li The state dict will include the platform brand, chip board id, the 546*9c5db199SXin Li running cr50 image version, the running cr50 image board id, and the 547*9c5db199SXin Li device cr50 image version. 548*9c5db199SXin Li """ 549*9c5db199SXin Li state = {} 550*9c5db199SXin Li state['cros_config / brand-code'] = self.host.run( 551*9c5db199SXin Li 'cros_config / brand-code', ignore_status=True).stdout.strip() 552*9c5db199SXin Li state['prod_version'] = self._get_image_information('prod') 553*9c5db199SXin Li state['prepvt_version'] = self._get_image_information('prepvt') 554*9c5db199SXin Li state['chip_bid'] = cr50_utils.GetChipBoardId(self.host) 555*9c5db199SXin Li state['chip_bid_str'] = '%08x:%08x:%08x' % state['chip_bid'] 556*9c5db199SXin Li state['running_image_ver'] = cr50_utils.GetRunningVersion(self.host) 557*9c5db199SXin Li state['running_image_bid'] = self.cr50.get_active_board_id_str() 558*9c5db199SXin Li 559*9c5db199SXin Li logging.debug('Current Cr50 state:\n%s', pprint.pformat(state)) 560*9c5db199SXin Li return state 561*9c5db199SXin Li 562*9c5db199SXin Li def _check_running_image_and_board_id(self, expected_state): 563*9c5db199SXin Li """Compare the current image and board id to the given state. 564*9c5db199SXin Li 565*9c5db199SXin Li @param expected_state: A dictionary of the state to compare to. 566*9c5db199SXin Li @return: A dictionary with the state that is wrong as the key and the 567*9c5db199SXin Li expected and current state as the value. 568*9c5db199SXin Li """ 569*9c5db199SXin Li if not (self._saved_state & self.INITIAL_IMAGE_STATE): 570*9c5db199SXin Li logging.warning( 571*9c5db199SXin Li 'Did not save the original state. Cannot verify it ' 572*9c5db199SXin Li 'matches') 573*9c5db199SXin Li return 574*9c5db199SXin Li # Make sure the /var/cache/cr50* state is up to date. 575*9c5db199SXin Li cr50_utils.ClearUpdateStateAndReboot(self.host) 576*9c5db199SXin Li 577*9c5db199SXin Li mismatch = {} 578*9c5db199SXin Li state = self.get_image_and_bid_state() 579*9c5db199SXin Li 580*9c5db199SXin Li for k, expected_val in six.iteritems(expected_state): 581*9c5db199SXin Li val = state[k] 582*9c5db199SXin Li if val != expected_val: 583*9c5db199SXin Li mismatch[k] = 'expected: %s, current: %s' % (expected_val, val) 584*9c5db199SXin Li 585*9c5db199SXin Li if mismatch: 586*9c5db199SXin Li logging.warning('State Mismatch:\n%s', pprint.pformat(mismatch)) 587*9c5db199SXin Li return mismatch 588*9c5db199SXin Li 589*9c5db199SXin Li def _check_original_image_state(self): 590*9c5db199SXin Li """Compare the current cr50 state to the original state. 591*9c5db199SXin Li 592*9c5db199SXin Li @return: A dictionary with the state that is wrong as the key and the 593*9c5db199SXin Li new and old state as the value 594*9c5db199SXin Li """ 595*9c5db199SXin Li mismatch = self._check_running_image_and_board_id( 596*9c5db199SXin Li self._original_image_state) 597*9c5db199SXin Li if not mismatch: 598*9c5db199SXin Li logging.info('The device is in the original state') 599*9c5db199SXin Li return mismatch 600*9c5db199SXin Li 601*9c5db199SXin Li def _reset_ccd_settings(self): 602*9c5db199SXin Li """Reset the ccd lock and capability states.""" 603*9c5db199SXin Li if not self.cr50.ccd_is_reset(): 604*9c5db199SXin Li # Try to open cr50 and enable testlab mode if it isn't enabled. 605*9c5db199SXin Li try: 606*9c5db199SXin Li self.fast_ccd_open(True) 607*9c5db199SXin Li except: 608*9c5db199SXin Li # Even if we can't open cr50, do our best to reset the rest of 609*9c5db199SXin Li # the system state. Log a warning here. 610*9c5db199SXin Li logging.warning('Unable to Open cr50', exc_info=True) 611*9c5db199SXin Li self.cr50.ccd_reset(servo_en=False) 612*9c5db199SXin Li if not self.cr50.ccd_is_reset(): 613*9c5db199SXin Li raise error.TestFail('Could not reset ccd') 614*9c5db199SXin Li 615*9c5db199SXin Li current_settings = self.cr50.get_cap_dict(info=self.cr50.CAP_SETTING) 616*9c5db199SXin Li if self.original_ccd_settings != current_settings: 617*9c5db199SXin Li if not self.can_set_ccd_level: 618*9c5db199SXin Li raise error.TestError("CCD state has changed, but we can't " 619*9c5db199SXin Li "restore it") 620*9c5db199SXin Li self.fast_ccd_open(True) 621*9c5db199SXin Li self.cr50.set_caps(self.original_ccd_settings) 622*9c5db199SXin Li 623*9c5db199SXin Li # First try using testlab open to open the device 624*9c5db199SXin Li if self.original_ccd_level == 'open': 625*9c5db199SXin Li self.fast_ccd_open(True) 626*9c5db199SXin Li elif self.original_ccd_level != self.cr50.get_ccd_level(): 627*9c5db199SXin Li self.cr50.set_ccd_level(self.original_ccd_level) 628*9c5db199SXin Li 629*9c5db199SXin Li def fast_ccd_open(self, 630*9c5db199SXin Li enable_testlab=False, 631*9c5db199SXin Li reset_ccd=True, 632*9c5db199SXin Li dev_mode=False): 633*9c5db199SXin Li """Check for watchdog resets after opening ccd. 634*9c5db199SXin Li 635*9c5db199SXin Li Args: 636*9c5db199SXin Li enable_testlab: If True, enable testlab mode after cr50 is open. 637*9c5db199SXin Li reset_ccd: If True, reset ccd after open. 638*9c5db199SXin Li dev_mode: True if the device should be in dev mode after ccd is 639*9c5db199SXin Li is opened. 640*9c5db199SXin Li """ 641*9c5db199SXin Li try: 642*9c5db199SXin Li super(Cr50Test, self).fast_ccd_open(enable_testlab, reset_ccd, 643*9c5db199SXin Li dev_mode) 644*9c5db199SXin Li except Exception as e: 645*9c5db199SXin Li # Check for cr50 watchdog resets. 646*9c5db199SXin Li self.cr50.check_for_console_errors('Fast ccd open') 647*9c5db199SXin Li raise 648*9c5db199SXin Li 649*9c5db199SXin Li def cleanup(self): 650*9c5db199SXin Li """Attempt to cleanup the cr50 state. Then run firmware cleanup""" 651*9c5db199SXin Li try: 652*9c5db199SXin Li # Reset the password as the first thing in cleanup. It is important 653*9c5db199SXin Li # that if some other part of cleanup fails, the password has at 654*9c5db199SXin Li # least been reset. 655*9c5db199SXin Li # DO NOT PUT ANYTHING BEFORE THIS. 656*9c5db199SXin Li self._try_quick_ccd_cleanup() 657*9c5db199SXin Li 658*9c5db199SXin Li self.servo.enable_main_servo_device() 659*9c5db199SXin Li 660*9c5db199SXin Li self._try_to_bring_dut_up() 661*9c5db199SXin Li self._restore_cr50_state() 662*9c5db199SXin Li 663*9c5db199SXin Li # Make sure the sarien EC isn't stuck in factory mode. 664*9c5db199SXin Li self._discharging_factory_mode_cleanup() 665*9c5db199SXin Li finally: 666*9c5db199SXin Li super(Cr50Test, self).cleanup() 667*9c5db199SXin Li 668*9c5db199SXin Li # Check the logs captured during firmware_test cleanup for cr50 errors. 669*9c5db199SXin Li self.cr50.check_for_console_errors('Test Cleanup') 670*9c5db199SXin Li self.servo.allow_ccd_watchdog_for_test() 671*9c5db199SXin Li 672*9c5db199SXin Li def _update_device_images_and_running_cr50_firmware( 673*9c5db199SXin Li self, state, release_path, prod_path, prepvt_path): 674*9c5db199SXin Li """Update cr50, set the board id, and copy firmware to the DUT. 675*9c5db199SXin Li 676*9c5db199SXin Li @param state: A dictionary with the expected running version, board id, 677*9c5db199SXin Li device cr50 firmware versions. 678*9c5db199SXin Li @param release_path: The image to update cr50 to 679*9c5db199SXin Li @param prod_path: The path to the .prod image 680*9c5db199SXin Li @param prepvt_path: The path to the .prepvt image 681*9c5db199SXin Li @raises TestError: if setting any state failed 682*9c5db199SXin Li """ 683*9c5db199SXin Li mismatch = self._check_running_image_and_board_id(state) 684*9c5db199SXin Li if not mismatch: 685*9c5db199SXin Li logging.info('Nothing to do.') 686*9c5db199SXin Li return 687*9c5db199SXin Li 688*9c5db199SXin Li # Use the DBG image to restore the original image. 689*9c5db199SXin Li if self._cleanup_required(mismatch, self.DBG_IMAGE): 690*9c5db199SXin Li self.update_cr50_image_and_board_id(release_path, 691*9c5db199SXin Li state['chip_bid']) 692*9c5db199SXin Li 693*9c5db199SXin Li self._try_to_bring_dut_up() 694*9c5db199SXin Li new_mismatch = self._check_running_image_and_board_id(state) 695*9c5db199SXin Li # Copy the original .prod and .prepvt images back onto the DUT. 696*9c5db199SXin Li if (self._cleanup_required(new_mismatch, self.DEVICE_IMAGES) 697*9c5db199SXin Li and filesystem_util.is_rootfs_writable(self.host)): 698*9c5db199SXin Li # Copy the .prod file onto the DUT. 699*9c5db199SXin Li if prod_path and 'prod_version' in new_mismatch: 700*9c5db199SXin Li cr50_utils.InstallImage(self.host, prod_path, 701*9c5db199SXin Li cr50_utils.CR50_PROD) 702*9c5db199SXin Li # Copy the .prepvt file onto the DUT. 703*9c5db199SXin Li if prepvt_path and 'prepvt_version' in new_mismatch: 704*9c5db199SXin Li cr50_utils.InstallImage(self.host, prepvt_path, 705*9c5db199SXin Li cr50_utils.CR50_PREPVT) 706*9c5db199SXin Li 707*9c5db199SXin Li final_mismatch = self._check_running_image_and_board_id(state) 708*9c5db199SXin Li if final_mismatch: 709*9c5db199SXin Li raise error.TestError( 710*9c5db199SXin Li 'Could not update cr50 image state: %s' % final_mismatch) 711*9c5db199SXin Li logging.info('Successfully updated all device cr50 firmware state.') 712*9c5db199SXin Li 713*9c5db199SXin Li def _restore_device_images_and_running_cr50_firmware(self): 714*9c5db199SXin Li """Restore the images on the device and the running cr50 image.""" 715*9c5db199SXin Li if self._provision_update: 716*9c5db199SXin Li return 717*9c5db199SXin Li mismatch = self._check_original_image_state() 718*9c5db199SXin Li if not mismatch: 719*9c5db199SXin Li return 720*9c5db199SXin Li self._update_device_images_and_running_cr50_firmware( 721*9c5db199SXin Li self._original_image_state, 722*9c5db199SXin Li self.get_saved_cr50_original_path(), self._device_prod_image, 723*9c5db199SXin Li self._device_prepvt_image) 724*9c5db199SXin Li 725*9c5db199SXin Li if self._raise_error_on_mismatch and mismatch: 726*9c5db199SXin Li raise error.TestError('Unexpected state mismatch during ' 727*9c5db199SXin Li 'cleanup %s' % mismatch) 728*9c5db199SXin Li 729*9c5db199SXin Li def _try_quick_ccd_cleanup(self): 730*9c5db199SXin Li """Try to clear all ccd state.""" 731*9c5db199SXin Li # This is just a first pass at cleanup. Don't raise any errors. 732*9c5db199SXin Li try: 733*9c5db199SXin Li self.cr50.ccd_enable() 734*9c5db199SXin Li except Exception as e: 735*9c5db199SXin Li logging.warning('Ignored exception enabling ccd %r', str(e)) 736*9c5db199SXin Li self.cr50.send_command('ccd testlab open') 737*9c5db199SXin Li self.cr50.send_command('rddkeepalive disable') 738*9c5db199SXin Li self.cr50.ccd_reset() 739*9c5db199SXin Li self.cr50.send_command('wp follow_batt_pres atboot') 740*9c5db199SXin Li 741*9c5db199SXin Li def _restore_ccd_settings(self): 742*9c5db199SXin Li """Restore the original ccd state.""" 743*9c5db199SXin Li self._try_quick_ccd_cleanup() 744*9c5db199SXin Li 745*9c5db199SXin Li # Reboot cr50 if the console is accessible. This will reset most state. 746*9c5db199SXin Li if self.cr50.get_cap('GscFullConsole')[self.cr50.CAP_IS_ACCESSIBLE]: 747*9c5db199SXin Li self.cr50.reboot() 748*9c5db199SXin Li 749*9c5db199SXin Li # Reenable servo v4 CCD 750*9c5db199SXin Li self.cr50.ccd_enable() 751*9c5db199SXin Li 752*9c5db199SXin Li # reboot to normal mode if the device is in dev mode. 753*9c5db199SXin Li self.enter_mode_after_checking_cr50_state('normal') 754*9c5db199SXin Li 755*9c5db199SXin Li self._try_to_bring_dut_up() 756*9c5db199SXin Li tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True) 757*9c5db199SXin Li self.clear_fwmp() 758*9c5db199SXin Li 759*9c5db199SXin Li # Restore the ccd privilege level 760*9c5db199SXin Li self._reset_ccd_settings() 761*9c5db199SXin Li 762*9c5db199SXin Li def _restore_cr50_state(self): 763*9c5db199SXin Li """Restore cr50 state, so the device can be used for further testing. 764*9c5db199SXin Li 765*9c5db199SXin Li Restore the cr50 image and board id first. Then CCD, because flashing 766*9c5db199SXin Li dev signed images completely clears the CCD state. 767*9c5db199SXin Li """ 768*9c5db199SXin Li try: 769*9c5db199SXin Li self._restore_device_images_and_running_cr50_firmware() 770*9c5db199SXin Li except Exception as e: 771*9c5db199SXin Li logging.warning('Issue restoring Cr50 image: %s', str(e)) 772*9c5db199SXin Li raise 773*9c5db199SXin Li finally: 774*9c5db199SXin Li self._restore_ccd_settings() 775*9c5db199SXin Li 776*9c5db199SXin Li def find_cr50_gs_image(self, gsurl): 777*9c5db199SXin Li """Find the cr50 gs image name. 778*9c5db199SXin Li 779*9c5db199SXin Li @param gsurl: the cr50 image location 780*9c5db199SXin Li @return: a list of the gsutil bucket, filename or None if the file 781*9c5db199SXin Li can't be found 782*9c5db199SXin Li """ 783*9c5db199SXin Li try: 784*9c5db199SXin Li return utils.gs_ls(gsurl)[0].rsplit('/', 1) 785*9c5db199SXin Li except error.CmdError: 786*9c5db199SXin Li logging.info('%s does not exist', gsurl) 787*9c5db199SXin Li return None 788*9c5db199SXin Li 789*9c5db199SXin Li def _extract_cr50_image(self, archive, fn): 790*9c5db199SXin Li """Extract the filename from the given archive 791*9c5db199SXin Li Aargs: 792*9c5db199SXin Li archive: the archive location on the host 793*9c5db199SXin Li fn: the file to extract 794*9c5db199SXin Li 795*9c5db199SXin Li Returns: 796*9c5db199SXin Li The location of the extracted file 797*9c5db199SXin Li """ 798*9c5db199SXin Li remote_dir = os.path.dirname(archive) 799*9c5db199SXin Li result = self.host.run('tar xfv %s -C %s' % (archive, remote_dir)) 800*9c5db199SXin Li for line in result.stdout.splitlines(): 801*9c5db199SXin Li if os.path.basename(line) == fn: 802*9c5db199SXin Li return os.path.join(remote_dir, line) 803*9c5db199SXin Li raise error.TestFail('%s was not extracted from %s' % (fn, archive)) 804*9c5db199SXin Li 805*9c5db199SXin Li def download_cr50_gs_file(self, gsurl, extract_fn): 806*9c5db199SXin Li """Download and extract the file at gsurl. 807*9c5db199SXin Li 808*9c5db199SXin Li @param gsurl: The gs url for the cr50 image 809*9c5db199SXin Li @param extract_fn: The name of the file to extract from the cr50 image 810*9c5db199SXin Li tarball. Don't extract anything if extract_fn is None. 811*9c5db199SXin Li @return: a tuple (local path, host path) 812*9c5db199SXin Li """ 813*9c5db199SXin Li file_info = self.find_cr50_gs_image(gsurl) 814*9c5db199SXin Li if not file_info: 815*9c5db199SXin Li raise error.TestFail('Could not find %s' % gsurl) 816*9c5db199SXin Li bucket, fn = file_info 817*9c5db199SXin Li 818*9c5db199SXin Li remote_temp_dir = '/tmp/' 819*9c5db199SXin Li src = os.path.join(remote_temp_dir, fn) 820*9c5db199SXin Li dest = os.path.join(self.resultsdir, fn) 821*9c5db199SXin Li 822*9c5db199SXin Li # Copy the image to the dut 823*9c5db199SXin Li gsutil_wrapper.copy_private_bucket( 824*9c5db199SXin Li host=self.host, 825*9c5db199SXin Li bucket=bucket, 826*9c5db199SXin Li filename=fn, 827*9c5db199SXin Li destination=remote_temp_dir) 828*9c5db199SXin Li if extract_fn: 829*9c5db199SXin Li src = self._extract_cr50_image(src, extract_fn) 830*9c5db199SXin Li logging.info('extracted %s', src) 831*9c5db199SXin Li # Remove .tbz2 from the local path. 832*9c5db199SXin Li dest = os.path.splitext(dest)[0] 833*9c5db199SXin Li 834*9c5db199SXin Li self.host.get_file(src, dest) 835*9c5db199SXin Li return dest, src 836*9c5db199SXin Li 837*9c5db199SXin Li def download_cr50_gs_image(self, gsurl, extract_fn, image_bid): 838*9c5db199SXin Li """Get the image from gs and save it in the autotest dir. 839*9c5db199SXin Li 840*9c5db199SXin Li @param gsurl: The gs url for the cr50 image 841*9c5db199SXin Li @param extract_fn: The name of the file to extract from the cr50 image 842*9c5db199SXin Li tarball. Don't extract anything if extract_fn is None. 843*9c5db199SXin Li @param image_bid: the image symbolic board id 844*9c5db199SXin Li @return: A tuple with the local path and version 845*9c5db199SXin Li """ 846*9c5db199SXin Li dest, src = self.download_cr50_gs_file(gsurl, extract_fn) 847*9c5db199SXin Li ver = cr50_utils.GetBinVersion(self.host, src) 848*9c5db199SXin Li 849*9c5db199SXin Li # Compare the image board id to the downloaded image to make sure we got 850*9c5db199SXin Li # the right file 851*9c5db199SXin Li downloaded_bid = cr50_utils.GetBoardIdInfoString(ver[2], symbolic=True) 852*9c5db199SXin Li if image_bid and image_bid != downloaded_bid: 853*9c5db199SXin Li raise error.TestError( 854*9c5db199SXin Li 'Could not download image with matching ' 855*9c5db199SXin Li 'board id wanted %s got %s' % (image_bid, downloaded_bid)) 856*9c5db199SXin Li return dest, ver 857*9c5db199SXin Li 858*9c5db199SXin Li def download_cr50_eraseflashinfo_image(self): 859*9c5db199SXin Li """download the cr50 image that allows erasing flashinfo. 860*9c5db199SXin Li 861*9c5db199SXin Li Get the file with the matching devid. 862*9c5db199SXin Li 863*9c5db199SXin Li @return: A tuple with the debug image local path and version 864*9c5db199SXin Li """ 865*9c5db199SXin Li devid = self._devid.replace(' ', '-').replace('0x', '') 866*9c5db199SXin Li gsurl = os.path.join(self.GS_PRIVATE_DBG, 867*9c5db199SXin Li self.CR50_ERASEFLASHINFO_FILE % devid) 868*9c5db199SXin Li return self.download_cr50_gs_image(gsurl, None, None) 869*9c5db199SXin Li 870*9c5db199SXin Li def download_cr50_debug_image(self, devid='', image_bid=''): 871*9c5db199SXin Li """download the cr50 debug file. 872*9c5db199SXin Li 873*9c5db199SXin Li Get the file with the matching devid and image board id info 874*9c5db199SXin Li 875*9c5db199SXin Li @param image_bid: the image board id info string or list 876*9c5db199SXin Li @return: A tuple with the debug image local path and version 877*9c5db199SXin Li """ 878*9c5db199SXin Li bid_ext = '' 879*9c5db199SXin Li # Add the image bid string to the filename 880*9c5db199SXin Li if image_bid: 881*9c5db199SXin Li image_bid = cr50_utils.GetBoardIdInfoString( 882*9c5db199SXin Li image_bid, symbolic=True) 883*9c5db199SXin Li bid_ext = '.' + image_bid.replace(':', '_') 884*9c5db199SXin Li 885*9c5db199SXin Li devid = devid if devid else self._devid 886*9c5db199SXin Li dbg_file = self.CR50_DEBUG_FILE % (devid.replace(' ', '_'), bid_ext) 887*9c5db199SXin Li gsurl = os.path.join(self.GS_PRIVATE_DBG, dbg_file) 888*9c5db199SXin Li return self.download_cr50_gs_image(gsurl, None, image_bid) 889*9c5db199SXin Li 890*9c5db199SXin Li def download_cr50_tot_image(self): 891*9c5db199SXin Li """download the cr50 TOT image. 892*9c5db199SXin Li 893*9c5db199SXin Li @return: the local path to the TOT image. 894*9c5db199SXin Li """ 895*9c5db199SXin Li # TODO(mruthven): use logic from provision_Cr50TOT 896*9c5db199SXin Li raise error.TestNAError('Could not download TOT image') 897*9c5db199SXin Li 898*9c5db199SXin Li def _find_release_image_gsurl(self, fn): 899*9c5db199SXin Li """Find the gs url for the release image""" 900*9c5db199SXin Li for gsbucket in [self.GS_PUBLIC, self.GS_PRIVATE_PROD]: 901*9c5db199SXin Li gsurl = os.path.join(gsbucket, fn) 902*9c5db199SXin Li if self.find_cr50_gs_image(gsurl): 903*9c5db199SXin Li return gsurl 904*9c5db199SXin Li raise error.TestFail('%s is not on google storage' % fn) 905*9c5db199SXin Li 906*9c5db199SXin Li def download_cr50_release_image(self, image_rw, image_bid=''): 907*9c5db199SXin Li """download the cr50 release file. 908*9c5db199SXin Li 909*9c5db199SXin Li Get the file with the matching version and image board id info 910*9c5db199SXin Li 911*9c5db199SXin Li @param image_rw: the rw version string 912*9c5db199SXin Li @param image_bid: the image board id info string or list 913*9c5db199SXin Li @return: A tuple with the release image local path and version 914*9c5db199SXin Li """ 915*9c5db199SXin Li bid_ext = '' 916*9c5db199SXin Li # Add the image bid string to the gsurl 917*9c5db199SXin Li if image_bid: 918*9c5db199SXin Li image_bid = cr50_utils.GetBoardIdInfoString( 919*9c5db199SXin Li image_bid, symbolic=True) 920*9c5db199SXin Li bid_ext = '_' + image_bid.replace(':', '_') 921*9c5db199SXin Li release_fn = self.CR50_PROD_FILE % (image_rw, bid_ext) 922*9c5db199SXin Li gsurl = self._find_release_image_gsurl(release_fn) 923*9c5db199SXin Li 924*9c5db199SXin Li # Release images can be found using the rw version 925*9c5db199SXin Li # Download the image 926*9c5db199SXin Li dest, ver = self.download_cr50_gs_image(gsurl, 'cr50.bin.prod', 927*9c5db199SXin Li image_bid) 928*9c5db199SXin Li 929*9c5db199SXin Li # Compare the rw version and board id info to make sure the right image 930*9c5db199SXin Li # was found 931*9c5db199SXin Li if image_rw != ver[1]: 932*9c5db199SXin Li raise error.TestError('Could not download image with matching ' 933*9c5db199SXin Li 'rw version') 934*9c5db199SXin Li return dest, ver 935*9c5db199SXin Li 936*9c5db199SXin Li def _cr50_verify_update(self, expected_rw, expect_rollback): 937*9c5db199SXin Li """Verify the expected version is running on cr50. 938*9c5db199SXin Li 939*9c5db199SXin Li @param expected_rw: The RW version string we expect to be running 940*9c5db199SXin Li @param expect_rollback: True if cr50 should have rolled back during the 941*9c5db199SXin Li update 942*9c5db199SXin Li @raise TestFail: if there is any unexpected update state 943*9c5db199SXin Li """ 944*9c5db199SXin Li errors = [] 945*9c5db199SXin Li running_rw = self.cr50.get_version() 946*9c5db199SXin Li if expected_rw != running_rw: 947*9c5db199SXin Li errors.append('running %s not %s' % (running_rw, expected_rw)) 948*9c5db199SXin Li 949*9c5db199SXin Li if expect_rollback != self.cr50.rolledback(): 950*9c5db199SXin Li errors.append('%srollback detected' % 951*9c5db199SXin Li 'no ' if expect_rollback else '') 952*9c5db199SXin Li if len(errors): 953*9c5db199SXin Li raise error.TestFail('cr50_update failed: %s' % ', '.join(errors)) 954*9c5db199SXin Li logging.info('RUNNING %s after %s', expected_rw, 955*9c5db199SXin Li 'rollback' if expect_rollback else 'update') 956*9c5db199SXin Li 957*9c5db199SXin Li def _cr50_run_update(self, path): 958*9c5db199SXin Li """Install the image at path onto cr50. 959*9c5db199SXin Li 960*9c5db199SXin Li @param path: the location of the image to update to 961*9c5db199SXin Li @return: the rw version of the image 962*9c5db199SXin Li """ 963*9c5db199SXin Li tmp_dest = '/tmp/' + os.path.basename(path) 964*9c5db199SXin Li 965*9c5db199SXin Li # Make sure the dut is sshable before installing the image. 966*9c5db199SXin Li self._try_to_bring_dut_up() 967*9c5db199SXin Li 968*9c5db199SXin Li dest, image_ver = cr50_utils.InstallImage(self.host, path, tmp_dest) 969*9c5db199SXin Li # Use the -p option to make sure the DUT does a clean reboot. 970*9c5db199SXin Li cr50_utils.GSCTool(self.host, ['-a', dest, '-p']) 971*9c5db199SXin Li # Reboot the DUT to finish the cr50 update. 972*9c5db199SXin Li self.host.reboot(wait=False) 973*9c5db199SXin Li return image_ver[1] 974*9c5db199SXin Li 975*9c5db199SXin Li def cr50_update(self, path, rollback=False, expect_rollback=False): 976*9c5db199SXin Li """Attempt to update to the given image. 977*9c5db199SXin Li 978*9c5db199SXin Li If rollback is True, we assume that cr50 is already running an image 979*9c5db199SXin Li that can rollback. 980*9c5db199SXin Li 981*9c5db199SXin Li @param path: the location of the update image 982*9c5db199SXin Li @param rollback: True if we need to force cr50 to rollback to update to 983*9c5db199SXin Li the given image 984*9c5db199SXin Li @param expect_rollback: True if cr50 should rollback on its own 985*9c5db199SXin Li @raise TestFail: if the update failed 986*9c5db199SXin Li """ 987*9c5db199SXin Li original_rw = self.cr50.get_version() 988*9c5db199SXin Li 989*9c5db199SXin Li # Cr50 is going to reject an update if it hasn't been up for more than 990*9c5db199SXin Li # 60 seconds. Wait until that passes before trying to run the update. 991*9c5db199SXin Li self.cr50.wait_until_update_is_allowed() 992*9c5db199SXin Li 993*9c5db199SXin Li image_rw = self._cr50_run_update(path) 994*9c5db199SXin Li 995*9c5db199SXin Li # Running the update may cause cr50 to reboot. Wait for that before 996*9c5db199SXin Li # sending more commands. The reboot should happen quickly. 997*9c5db199SXin Li self.cr50.wait_for_reboot( 998*9c5db199SXin Li timeout=self.faft_config.gsc_update_wait_for_reboot) 999*9c5db199SXin Li 1000*9c5db199SXin Li if rollback: 1001*9c5db199SXin Li self.cr50.rollback() 1002*9c5db199SXin Li 1003*9c5db199SXin Li expected_rw = original_rw if expect_rollback else image_rw 1004*9c5db199SXin Li # If we expect a rollback, the version should remain unchanged 1005*9c5db199SXin Li self._cr50_verify_update(expected_rw, rollback or expect_rollback) 1006*9c5db199SXin Li 1007*9c5db199SXin Li def run_gsctool_cmd_with_password(self, password, cmd, name, expect_error): 1008*9c5db199SXin Li """Run a gsctool command and input the password 1009*9c5db199SXin Li 1010*9c5db199SXin Li @param password: The cr50 password string 1011*9c5db199SXin Li @param cmd: The gsctool command 1012*9c5db199SXin Li @param name: The name to give the job 1013*9c5db199SXin Li @param expect_error: True if the command should fail 1014*9c5db199SXin Li """ 1015*9c5db199SXin Li logging.info('Running: %s', cmd) 1016*9c5db199SXin Li logging.info('Password: %s', password) 1017*9c5db199SXin Li # Make sure the test waits long enough to avoid ccd rate limiting. 1018*9c5db199SXin Li time.sleep(self.cr50.CCD_PASSWORD_RATE_LIMIT) 1019*9c5db199SXin Li full_cmd = "echo -e '%s\n%s\n' | %s" % (password, password, cmd) 1020*9c5db199SXin Li result = self.host.run(full_cmd, ignore_status=expect_error) 1021*9c5db199SXin Li if result.exit_status: 1022*9c5db199SXin Li message = ('gsctool %s failed using %r: %s %s' % 1023*9c5db199SXin Li (name, password, result.exit_status, result.stderr)) 1024*9c5db199SXin Li if expect_error: 1025*9c5db199SXin Li logging.info(message) 1026*9c5db199SXin Li else: 1027*9c5db199SXin Li raise error.TestFail(message) 1028*9c5db199SXin Li elif expect_error: 1029*9c5db199SXin Li raise error.TestFail('%s with %r did not fail when expected' % 1030*9c5db199SXin Li (name, password)) 1031*9c5db199SXin Li else: 1032*9c5db199SXin Li logging.info('ran %s password command: %r', name, result.stdout) 1033*9c5db199SXin Li 1034*9c5db199SXin Li def set_ccd_password(self, password, expect_error=False): 1035*9c5db199SXin Li """Set the ccd password""" 1036*9c5db199SXin Li # Testlab mode can't be enabled if there is no power button, so we 1037*9c5db199SXin Li # shouldn't allow setting the password. 1038*9c5db199SXin Li if not self.faft_config.has_powerbutton: 1039*9c5db199SXin Li raise error.TestError('No power button') 1040*9c5db199SXin Li 1041*9c5db199SXin Li # If for some reason the test sets a password and is interrupted before 1042*9c5db199SXin Li # we can clear it, we want testlab mode to be enabled, so it's possible 1043*9c5db199SXin Li # to clear the password without knowing it. 1044*9c5db199SXin Li if not self.cr50.testlab_is_on(): 1045*9c5db199SXin Li raise error.TestError('Will not set password unless testlab mode ' 1046*9c5db199SXin Li 'is enabled.') 1047*9c5db199SXin Li try: 1048*9c5db199SXin Li self.run_gsctool_cmd_with_password(password, 'gsctool -a -P', 1049*9c5db199SXin Li 'set_password', expect_error) 1050*9c5db199SXin Li finally: 1051*9c5db199SXin Li logging.info('Cr50 password is %s', 1052*9c5db199SXin Li 'cleared' if self.cr50.password_is_reset() else 'set') 1053*9c5db199SXin Li 1054*9c5db199SXin Li def ccd_unlock_from_ap(self, password=None, expect_error=False): 1055*9c5db199SXin Li """Unlock cr50""" 1056*9c5db199SXin Li if not password: 1057*9c5db199SXin Li self.host.run('gsctool -a -U') 1058*9c5db199SXin Li return 1059*9c5db199SXin Li self.run_gsctool_cmd_with_password(password, 'gsctool -a -U', 'unlock', 1060*9c5db199SXin Li expect_error) 1061*9c5db199SXin Li 1062*9c5db199SXin Li def tpm_is_responsive(self): 1063*9c5db199SXin Li """Check TPM responsiveness by running tpm_version.""" 1064*9c5db199SXin Li result = self.host.run('tpm_version', ignore_status=True) 1065*9c5db199SXin Li logging.debug(result.stdout.strip()) 1066*9c5db199SXin Li return not result.exit_status 1067