1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2014 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 ctypes 9*9c5db199SXin Liimport logging 10*9c5db199SXin Liimport os 11*9c5db199SXin Liimport pprint 12*9c5db199SXin Liimport re 13*9c5db199SXin Liimport time 14*9c5db199SXin Liimport uuid 15*9c5db199SXin Lifrom xml.parsers import expat 16*9c5db199SXin Li 17*9c5db199SXin Liimport six 18*9c5db199SXin Lifrom autotest_lib.client.bin import utils 19*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, global_config 20*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import dev_server, retry, tpm_utils 21*9c5db199SXin Lifrom autotest_lib.server import test 22*9c5db199SXin Lifrom autotest_lib.server.cros import vboot_constants as vboot 23*9c5db199SXin Lifrom autotest_lib.server.cros.faft import telemetry 24*9c5db199SXin Lifrom autotest_lib.server.cros.faft.rpc_proxy import RPCProxy 25*9c5db199SXin Lifrom autotest_lib.server.cros.faft.utils import (menu_mode_switcher, 26*9c5db199SXin Li menu_navigator, mode_switcher) 27*9c5db199SXin Lifrom autotest_lib.server.cros.faft.utils.config import Config as FAFTConfig 28*9c5db199SXin Lifrom autotest_lib.server.cros.faft.utils.faft_checkers import FAFTCheckers 29*9c5db199SXin Lifrom autotest_lib.server.cros.power import utils as PowerUtils 30*9c5db199SXin Lifrom autotest_lib.server.cros.servo import (chrome_base_ec, chrome_cr50, 31*9c5db199SXin Li chrome_ec, chrome_ti50, servo) 32*9c5db199SXin Lifrom autotest_lib.site_utils import test_runner_utils 33*9c5db199SXin Li 34*9c5db199SXin Li# Experimentally tuned time in minutes to wait for partition device nodes on a 35*9c5db199SXin Li# USB stick to be ready after plugging in the stick. 36*9c5db199SXin LiPARTITION_TABLE_READINESS_TIMEOUT = 0.1 # minutes 37*9c5db199SXin Li# Experimentally tuned time in seconds to wait for the first retry of reading 38*9c5db199SXin Li# the sysfs node of a USB stick's partition device node. 39*9c5db199SXin LiPARTITION_TABLE_READINESS_FIRST_RETRY_DELAY = 1 # seconds 40*9c5db199SXin Li 41*9c5db199SXin LiConnectionError = mode_switcher.ConnectionError 42*9c5db199SXin Li 43*9c5db199SXin Li 44*9c5db199SXin Liclass FirmwareTest(test.test): 45*9c5db199SXin Li """ 46*9c5db199SXin Li Base class that sets up helper objects/functions for firmware tests. 47*9c5db199SXin Li 48*9c5db199SXin Li It launches the FAFTClient on DUT, such that the test can access its 49*9c5db199SXin Li firmware functions and interfaces. It also provides some methods to 50*9c5db199SXin Li handle the reboot mechanism, in order to ensure FAFTClient is still 51*9c5db199SXin Li connected after reboot. 52*9c5db199SXin Li @type servo: servo.Servo 53*9c5db199SXin Li @type _client: autotest_lib.server.hosts.ssh_host.SSHHost | 54*9c5db199SXin Li autotest_lib.server.hosts.cros_host.CrosHost 55*9c5db199SXin Li 56*9c5db199SXin Li TODO: add documentaion as the FAFT rework progresses. 57*9c5db199SXin Li """ 58*9c5db199SXin Li version = 1 59*9c5db199SXin Li 60*9c5db199SXin Li # Set this to True in test classes that need to boot from the USB stick. 61*9c5db199SXin Li # When True, initialize() will raise TestWarn if USB stick is marked bad. 62*9c5db199SXin Li NEEDS_SERVO_USB = False 63*9c5db199SXin Li 64*9c5db199SXin Li # Mapping of partition number of kernel and rootfs. 65*9c5db199SXin Li KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'} 66*9c5db199SXin Li ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'} 67*9c5db199SXin Li OTHER_KERNEL_MAP = {'a':'4', 'b':'2', '2':'4', '4':'2', '3':'4', '5':'2'} 68*9c5db199SXin Li OTHER_ROOTFS_MAP = {'a':'5', 'b':'3', '2':'5', '4':'3', '3':'5', '5':'3'} 69*9c5db199SXin Li 70*9c5db199SXin Li # Mapping of kernel type and name. 71*9c5db199SXin Li KERNEL_TYPE_NAME_MAP = {'KERN': 'kernel', 'MINIOS': 'minios'} 72*9c5db199SXin Li 73*9c5db199SXin Li CHROMEOS_MAGIC = "CHROMEOS" 74*9c5db199SXin Li CORRUPTED_MAGIC = "CORRUPTD" 75*9c5db199SXin Li 76*9c5db199SXin Li # System power states 77*9c5db199SXin Li POWER_STATE_S0 = 'S0' 78*9c5db199SXin Li POWER_STATE_S0IX = 'S0ix' 79*9c5db199SXin Li POWER_STATE_S3 = 'S3' 80*9c5db199SXin Li POWER_STATE_S5 = 'S5' 81*9c5db199SXin Li POWER_STATE_G3 = 'G3' 82*9c5db199SXin Li POWER_STATE_SUSPEND = '|'.join([POWER_STATE_S0IX, POWER_STATE_S3]) 83*9c5db199SXin Li 84*9c5db199SXin Li # Delay for waiting client to return before EC suspend 85*9c5db199SXin Li EC_SUSPEND_DELAY = 5 86*9c5db199SXin Li 87*9c5db199SXin Li # Delay between EC suspend and wake 88*9c5db199SXin Li WAKE_DELAY = 10 89*9c5db199SXin Li 90*9c5db199SXin Li # Delay between closing and opening lid 91*9c5db199SXin Li LID_DELAY = 1 92*9c5db199SXin Li 93*9c5db199SXin Li # Delay for establishing state after changing PD settings 94*9c5db199SXin Li PD_RESYNC_DELAY = 2 95*9c5db199SXin Li 96*9c5db199SXin Li # Delay to wait for servo USB to work after power role swap: 97*9c5db199SXin Li # tPSSourceOff (920ms) + tPSSourceOn (480ms) + buffer 98*9c5db199SXin Li POWER_ROLE_SWAP_DELAY = 2 99*9c5db199SXin Li 100*9c5db199SXin Li # The default number of power state check retries (each try takes 3 secs) 101*9c5db199SXin Li DEFAULT_PWR_RETRIES = 5 102*9c5db199SXin Li 103*9c5db199SXin Li # FWMP space constants 104*9c5db199SXin Li FWMP_CLEARED_EXIT_STATUS = 1 105*9c5db199SXin Li FWMP_CLEARED_ERROR_MSG = ('CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS' 106*9c5db199SXin Li '_INVALID') 107*9c5db199SXin Li 108*9c5db199SXin Li _ROOTFS_PARTITION_NUMBER = 3 109*9c5db199SXin Li 110*9c5db199SXin Li # Class level variable, keep track the states of one time setup. 111*9c5db199SXin Li # This variable is preserved across tests which inherit this class. 112*9c5db199SXin Li _global_setup_done = { 113*9c5db199SXin Li 'gbb_flags': False, 114*9c5db199SXin Li 'reimage': False, 115*9c5db199SXin Li 'usb_check': False, 116*9c5db199SXin Li } 117*9c5db199SXin Li 118*9c5db199SXin Li # CCD password used by tests. 119*9c5db199SXin Li CCD_PASSWORD = 'Password' 120*9c5db199SXin Li 121*9c5db199SXin Li RESPONSE_TIMEOUT = 180 122*9c5db199SXin Li 123*9c5db199SXin Li @classmethod 124*9c5db199SXin Li def check_setup_done(cls, label): 125*9c5db199SXin Li """Check if the given setup is done. 126*9c5db199SXin Li 127*9c5db199SXin Li @param label: The label of the setup. 128*9c5db199SXin Li """ 129*9c5db199SXin Li return cls._global_setup_done[label] 130*9c5db199SXin Li 131*9c5db199SXin Li @classmethod 132*9c5db199SXin Li def mark_setup_done(cls, label): 133*9c5db199SXin Li """Mark the given setup done. 134*9c5db199SXin Li 135*9c5db199SXin Li @param label: The label of the setup. 136*9c5db199SXin Li """ 137*9c5db199SXin Li cls._global_setup_done[label] = True 138*9c5db199SXin Li 139*9c5db199SXin Li @classmethod 140*9c5db199SXin Li def unmark_setup_done(cls, label): 141*9c5db199SXin Li """Mark the given setup not done. 142*9c5db199SXin Li 143*9c5db199SXin Li @param label: The label of the setup. 144*9c5db199SXin Li """ 145*9c5db199SXin Li cls._global_setup_done[label] = False 146*9c5db199SXin Li 147*9c5db199SXin Li def initialize(self, host, cmdline_args, ec_wp=None): 148*9c5db199SXin Li """Initialize the FirmwareTest. 149*9c5db199SXin Li 150*9c5db199SXin Li This method interacts with the Servo, FAFT RPC client, FAFT Config, 151*9c5db199SXin Li Mode Switcher, EC consoles, write-protection, GBB flags, and a lockfile. 152*9c5db199SXin Li 153*9c5db199SXin Li @type host: autotest_lib.server.hosts.CrosHost 154*9c5db199SXin Li """ 155*9c5db199SXin Li self.run_id = str(uuid.uuid4()) 156*9c5db199SXin Li self._client = host 157*9c5db199SXin Li self.servo = host.servo 158*9c5db199SXin Li if self.servo is None: 159*9c5db199SXin Li raise error.TestError('FirmwareTest failed to set up servo') 160*9c5db199SXin Li 161*9c5db199SXin Li self.lockfile = '/usr/local/tmp/faft/lock' 162*9c5db199SXin Li self._backup_gbb_flags = None 163*9c5db199SXin Li self._backup_firmware_identity = dict() 164*9c5db199SXin Li self._backup_kernel_sha = dict() 165*9c5db199SXin Li for kernel_type in self.KERNEL_TYPE_NAME_MAP: 166*9c5db199SXin Li self._backup_kernel_sha[kernel_type] = dict() 167*9c5db199SXin Li self._backup_cgpt_attr = dict() 168*9c5db199SXin Li self._backup_dev_mode = None 169*9c5db199SXin Li self._restore_power_mode = None 170*9c5db199SXin Li self._uart_file_dict = {} 171*9c5db199SXin Li 172*9c5db199SXin Li logging.info('FirmwareTest initialize begin (id=%s)', self.run_id) 173*9c5db199SXin Li 174*9c5db199SXin Li # Parse arguments from command line 175*9c5db199SXin Li args = {} 176*9c5db199SXin Li self.power_control = host.POWER_CONTROL_RPM 177*9c5db199SXin Li for arg in cmdline_args: 178*9c5db199SXin Li match = re.search("^(\w+)=(.+)", arg) 179*9c5db199SXin Li if match: 180*9c5db199SXin Li args[match.group(1)] = match.group(2) 181*9c5db199SXin Li 182*9c5db199SXin Li self._no_fw_rollback_check = False 183*9c5db199SXin Li if 'no_fw_rollback_check' in args: 184*9c5db199SXin Li if 'true' in args['no_fw_rollback_check'].lower(): 185*9c5db199SXin Li self._no_fw_rollback_check = True 186*9c5db199SXin Li 187*9c5db199SXin Li self._no_ec_sync = False 188*9c5db199SXin Li if 'no_ec_sync' in args: 189*9c5db199SXin Li if 'true' in args['no_ec_sync'].lower(): 190*9c5db199SXin Li self._no_ec_sync = True 191*9c5db199SXin Li 192*9c5db199SXin Li self._use_sync_script = global_config.global_config.get_config_value( 193*9c5db199SXin Li 'CROS', 'enable_fs_sync_script', type=bool, default=False) 194*9c5db199SXin Li 195*9c5db199SXin Li self.servo.initialize_dut() 196*9c5db199SXin Li self.faft_client = RPCProxy(host) 197*9c5db199SXin Li self.faft_config = FAFTConfig( 198*9c5db199SXin Li self.faft_client.system.get_platform_name(), 199*9c5db199SXin Li self.faft_client.system.get_model_name()) 200*9c5db199SXin Li self.checkers = FAFTCheckers(self) 201*9c5db199SXin Li 202*9c5db199SXin Li # Mapping of kernel type and kernel servicer class 203*9c5db199SXin Li self.kernel_servicer = { 204*9c5db199SXin Li 'KERN': self.faft_client.kernel, 205*9c5db199SXin Li 'MINIOS': self.faft_client.minios, 206*9c5db199SXin Li } 207*9c5db199SXin Li 208*9c5db199SXin Li if self.faft_config.chrome_ec: 209*9c5db199SXin Li self.ec = chrome_ec.ChromeEC(self.servo) 210*9c5db199SXin Li self.menu_navigator = menu_navigator.create_menu_navigator(self) 211*9c5db199SXin Li self.switcher = mode_switcher.create_mode_switcher( 212*9c5db199SXin Li self, self.menu_navigator) 213*9c5db199SXin Li # This will be None for menuless UI 214*9c5db199SXin Li self.menu_switcher = menu_mode_switcher.create_menu_mode_switcher( 215*9c5db199SXin Li self, self.menu_navigator) 216*9c5db199SXin Li # Check for presence of a USBPD console 217*9c5db199SXin Li if self.faft_config.chrome_usbpd: 218*9c5db199SXin Li self.usbpd = chrome_ec.ChromeUSBPD(self.servo) 219*9c5db199SXin Li elif self.faft_config.chrome_ec: 220*9c5db199SXin Li # If no separate USBPD console, then PD exists on EC console 221*9c5db199SXin Li self.usbpd = self.ec 222*9c5db199SXin Li # Get pdtester console 223*9c5db199SXin Li self.pdtester = host.pdtester 224*9c5db199SXin Li self.pdtester_host = host._pdtester_host 225*9c5db199SXin Li gsc = None 226*9c5db199SXin Li if self.servo.has_control('ti50_version') or \ 227*9c5db199SXin Li self.servo.has_control('ti50_version', 'ccd_gsc'): 228*9c5db199SXin Li gsc = chrome_ti50.ChromeTi50(self.servo, self.faft_config) 229*9c5db199SXin Li elif self.servo.has_control('cr50_version'): 230*9c5db199SXin Li gsc = chrome_cr50.ChromeCr50(self.servo, self.faft_config) 231*9c5db199SXin Li if gsc: 232*9c5db199SXin Li try: 233*9c5db199SXin Li # Check that the gsc console works before declaring the 234*9c5db199SXin Li # connection exists and enabling uart capture. 235*9c5db199SXin Li gsc.get_version() 236*9c5db199SXin Li self.cr50 = gsc 237*9c5db199SXin Li except servo.ControlUnavailableError: 238*9c5db199SXin Li logging.warning('gsc console not supported.') 239*9c5db199SXin Li except Exception as e: 240*9c5db199SXin Li logging.warning('Ignored unknown gsc version error: %s', 241*9c5db199SXin Li str(e)) 242*9c5db199SXin Li 243*9c5db199SXin Li if 'power_control' in args: 244*9c5db199SXin Li self.power_control = args['power_control'] 245*9c5db199SXin Li if self.power_control not in host.POWER_CONTROL_VALID_ARGS: 246*9c5db199SXin Li raise error.TestError('Valid values for --args=power_control ' 247*9c5db199SXin Li 'are %s. But you entered wrong argument ' 248*9c5db199SXin Li 'as "%s".' 249*9c5db199SXin Li % (host.POWER_CONTROL_VALID_ARGS, 250*9c5db199SXin Li self.power_control)) 251*9c5db199SXin Li 252*9c5db199SXin Li if self.NEEDS_SERVO_USB and not host.is_servo_usb_usable(): 253*9c5db199SXin Li usb_state = host.get_servo_usb_state() 254*9c5db199SXin Li raise error.TestWarn( 255*9c5db199SXin Li "Servo USB disk unusable (%s); canceling test." % 256*9c5db199SXin Li usb_state) 257*9c5db199SXin Li 258*9c5db199SXin Li if not self.faft_client.system.dev_tpm_present(): 259*9c5db199SXin Li raise error.TestError('/dev/tpm0 does not exist on the client') 260*9c5db199SXin Li 261*9c5db199SXin Li # Initialize servo role to src 262*9c5db199SXin Li self.servo.set_servo_v4_role('src') 263*9c5db199SXin Li 264*9c5db199SXin Li # Create the BaseEC object. None if not available. 265*9c5db199SXin Li self.base_ec = chrome_base_ec.create_base_ec(self.servo) 266*9c5db199SXin Li 267*9c5db199SXin Li self._record_uart_capture() 268*9c5db199SXin Li self._record_system_info() 269*9c5db199SXin Li self.faft_client.system.set_dev_default_boot() 270*9c5db199SXin Li self.fw_vboot2 = self.faft_client.system.get_fw_vboot2() 271*9c5db199SXin Li logging.info('vboot version: %d', 2 if self.fw_vboot2 else 1) 272*9c5db199SXin Li if self.fw_vboot2: 273*9c5db199SXin Li self.faft_client.system.set_fw_try_next('A') 274*9c5db199SXin Li if self.faft_client.system.get_crossystem_value( 275*9c5db199SXin Li 'mainfw_act') == 'B': 276*9c5db199SXin Li logging.info('mainfw_act is B. rebooting to set it A') 277*9c5db199SXin Li # TODO(crbug.com/1018322): remove try/catch once that bug is 278*9c5db199SXin Li # marked as fixed and verified. In that case the overlay for 279*9c5db199SXin Li # the board itself will map warm_reset to cold_reset. 280*9c5db199SXin Li try: 281*9c5db199SXin Li self.switcher.mode_aware_reboot() 282*9c5db199SXin Li except ConnectionError as e: 283*9c5db199SXin Li if 'DUT is still up unexpectedly' in str(e): 284*9c5db199SXin Li # In this case, try doing a cold_reset instead 285*9c5db199SXin Li self.switcher.mode_aware_reboot(reboot_type='cold') 286*9c5db199SXin Li else: 287*9c5db199SXin Li raise 288*9c5db199SXin Li 289*9c5db199SXin Li # Check flashrom before first use, to avoid xmlrpclib.Fault. 290*9c5db199SXin Li if not self.faft_client.bios.is_available(): 291*9c5db199SXin Li raise error.TestError( 292*9c5db199SXin Li "flashrom is broken; check 'flashrom -p host'" 293*9c5db199SXin Li "and rpc server log.") 294*9c5db199SXin Li 295*9c5db199SXin Li self._setup_gbb_flags() 296*9c5db199SXin Li self.faft_client.updater.stop_daemon() 297*9c5db199SXin Li self._create_faft_lockfile() 298*9c5db199SXin Li self._setup_ec_write_protect(ec_wp) 299*9c5db199SXin Li # See chromium:239034 regarding needing this sync. 300*9c5db199SXin Li self.blocking_sync() 301*9c5db199SXin Li logging.info('FirmwareTest initialize done (id=%s)', self.run_id) 302*9c5db199SXin Li 303*9c5db199SXin Li def stage_build_to_usbkey(self): 304*9c5db199SXin Li """Downloads host's build to the USB key attached to servo. 305*9c5db199SXin Li 306*9c5db199SXin Li @return: True if build is verified to be on USB key, False otherwise. 307*9c5db199SXin Li """ 308*9c5db199SXin Li info = self._client.host_info_store.get() 309*9c5db199SXin Li if info.build and info.build != test_runner_utils.NO_BUILD: 310*9c5db199SXin Li current_build = self._client._servo_host.validate_image_usbkey() 311*9c5db199SXin Li if current_build != info.build: 312*9c5db199SXin Li logging.debug('Current build on USB: %s differs from test' 313*9c5db199SXin Li ' build: %s, proceed with download.', 314*9c5db199SXin Li current_build, info.build) 315*9c5db199SXin Li try: 316*9c5db199SXin Li self._client.stage_build_to_usb(info.build) 317*9c5db199SXin Li return True 318*9c5db199SXin Li except (error.AutotestError, 319*9c5db199SXin Li dev_server.DevServerException) as e: 320*9c5db199SXin Li logging.warning( 321*9c5db199SXin Li 'Stage build to USB failed, tests that require' 322*9c5db199SXin Li ' test image on Servo USB may fail: {}'.format(e)) 323*9c5db199SXin Li return False 324*9c5db199SXin Li else: 325*9c5db199SXin Li logging.debug('Current build on USB: %s is same as test' 326*9c5db199SXin Li ' build, skip download.', current_build) 327*9c5db199SXin Li return True 328*9c5db199SXin Li else: 329*9c5db199SXin Li logging.warning('Failed to get build label from the DUT, will use' 330*9c5db199SXin Li ' existing image in Servo USB.') 331*9c5db199SXin Li return False 332*9c5db199SXin Li 333*9c5db199SXin Li def run_once(self, *args, **dargs): 334*9c5db199SXin Li """Delegates testing to a test method. 335*9c5db199SXin Li 336*9c5db199SXin Li test_name is either the 1st positional argument or a named argument. 337*9c5db199SXin Li 338*9c5db199SXin Li test_name will be mapped to a test method as follows: 339*9c5db199SXin Li test_name method 340*9c5db199SXin Li -------------- ----------- 341*9c5db199SXin Li <TestClass> test 342*9c5db199SXin Li <TestClass>.<Case> test_<Case> 343*9c5db199SXin Li <TestClass>.<Case>.<SubCase> test_<Case>_<SubCase> 344*9c5db199SXin Li 345*9c5db199SXin Li Any arguments not consumed by FirmwareTest are passed to the test method. 346*9c5db199SXin Li 347*9c5db199SXin Li @param test_name: Should be set to NAME in the control file. 348*9c5db199SXin Li 349*9c5db199SXin Li @raise TestError: If test_name wasn't found in args, does not start 350*9c5db199SXin Li with test class, or if the method is not found. 351*9c5db199SXin Li """ 352*9c5db199SXin Li self_name = type(self).__name__ 353*9c5db199SXin Li 354*9c5db199SXin Li # Parse and remove test name from args. 355*9c5db199SXin Li if 'test_name' in dargs: 356*9c5db199SXin Li test_name = dargs.pop('test_name') 357*9c5db199SXin Li elif len(args) >= 1: 358*9c5db199SXin Li test_name = args[0] 359*9c5db199SXin Li args = args[1:] 360*9c5db199SXin Li else: 361*9c5db199SXin Li raise error.TestError('"%s" class must define run_once, or the' 362*9c5db199SXin Li ' control file must specify "test_name".' % 363*9c5db199SXin Li self_name) 364*9c5db199SXin Li 365*9c5db199SXin Li # Check that test_name starts with the test class name. 366*9c5db199SXin Li name_parts = test_name.split('.') 367*9c5db199SXin Li 368*9c5db199SXin Li test_class = name_parts.pop(0) 369*9c5db199SXin Li if test_class != self_name: 370*9c5db199SXin Li raise error.TestError('Class "%s" does not match that found in test' 371*9c5db199SXin Li ' name "%s"' % (self_name, test_class)) 372*9c5db199SXin Li 373*9c5db199SXin Li # Construct and call the test method. 374*9c5db199SXin Li method_name = '_'.join(['test'] + name_parts) 375*9c5db199SXin Li if not hasattr(self, method_name): 376*9c5db199SXin Li raise error.TestError('Method "%s" for testing "%s" not found in' 377*9c5db199SXin Li ' "%s"' % (method_name, test_name, self_name)) 378*9c5db199SXin Li 379*9c5db199SXin Li logging.info('Starting test: "%s"', test_name) 380*9c5db199SXin Li utils.cherry_pick_call(getattr(self, method_name), *args, **dargs) 381*9c5db199SXin Li 382*9c5db199SXin Li def cleanup(self): 383*9c5db199SXin Li """Autotest cleanup function.""" 384*9c5db199SXin Li # Unset state checker in case it's set by subclass 385*9c5db199SXin Li logging.info('FirmwareTest cleaning up (id=%s)', self.run_id) 386*9c5db199SXin Li 387*9c5db199SXin Li # Capture UART before doing anything else, so we can guarantee we get 388*9c5db199SXin Li # some uart results. 389*9c5db199SXin Li try: 390*9c5db199SXin Li self._record_uart_capture() 391*9c5db199SXin Li except: 392*9c5db199SXin Li logging.warning('Failed initial uart capture during cleanup') 393*9c5db199SXin Li 394*9c5db199SXin Li try: 395*9c5db199SXin Li self.faft_client.system.is_available() 396*9c5db199SXin Li except: 397*9c5db199SXin Li # Remote is not responding. Revive DUT so that subsequent tests 398*9c5db199SXin Li # don't fail. 399*9c5db199SXin Li self._restore_routine_from_timeout() 400*9c5db199SXin Li 401*9c5db199SXin Li if hasattr(self, 'switcher'): 402*9c5db199SXin Li self.switcher.restore_mode() 403*9c5db199SXin Li 404*9c5db199SXin Li self._restore_ec_write_protect() 405*9c5db199SXin Li self._restore_servo_v4_role() 406*9c5db199SXin Li 407*9c5db199SXin Li if hasattr(self, 'faft_client'): 408*9c5db199SXin Li self._restore_gbb_flags() 409*9c5db199SXin Li self.faft_client.updater.start_daemon() 410*9c5db199SXin Li self.faft_client.updater.cleanup() 411*9c5db199SXin Li self._remove_faft_lockfile() 412*9c5db199SXin Li self.faft_client.quit() 413*9c5db199SXin Li self.faft_client.collect_logfiles(self.resultsdir) 414*9c5db199SXin Li 415*9c5db199SXin Li # Capture any new uart output, then discard log messages again. 416*9c5db199SXin Li self._cleanup_uart_capture() 417*9c5db199SXin Li 418*9c5db199SXin Li super(FirmwareTest, self).cleanup() 419*9c5db199SXin Li logging.info('FirmwareTest cleanup done (id=%s)', self.run_id) 420*9c5db199SXin Li 421*9c5db199SXin Li def _record_system_info(self): 422*9c5db199SXin Li """Record some critical system info to the attr keyval. 423*9c5db199SXin Li 424*9c5db199SXin Li This info is used by generate_test_report later. 425*9c5db199SXin Li """ 426*9c5db199SXin Li system_info = { 427*9c5db199SXin Li 'hwid': self.faft_client.system.get_crossystem_value('hwid'), 428*9c5db199SXin Li 'ec_version': self.faft_client.ec.get_version(), 429*9c5db199SXin Li 'ro_fwid': self.faft_client.system.get_crossystem_value('ro_fwid'), 430*9c5db199SXin Li 'rw_fwid': self.faft_client.system.get_crossystem_value('fwid'), 431*9c5db199SXin Li 'servo_host_os_version' : self.servo.get_os_version(), 432*9c5db199SXin Li 'servod_version': self.servo.get_servod_version(), 433*9c5db199SXin Li 'os_version': self._client.get_release_builder_path(), 434*9c5db199SXin Li 'servo_type': self.servo.get_servo_version() 435*9c5db199SXin Li } 436*9c5db199SXin Li 437*9c5db199SXin Li # Record the servo v4 and servo micro versions when possible 438*9c5db199SXin Li system_info.update(self.servo.get_servo_fw_versions()) 439*9c5db199SXin Li 440*9c5db199SXin Li if hasattr(self, 'cr50'): 441*9c5db199SXin Li system_info['cr50_version'] = self.cr50.get_full_version() 442*9c5db199SXin Li 443*9c5db199SXin Li logging.info('System info:\n%s', pprint.pformat(system_info)) 444*9c5db199SXin Li self.write_attr_keyval(system_info) 445*9c5db199SXin Li 446*9c5db199SXin Li def invalidate_firmware_setup(self): 447*9c5db199SXin Li """Invalidate all firmware related setup state. 448*9c5db199SXin Li 449*9c5db199SXin Li This method is called when the firmware is re-flashed. It resets all 450*9c5db199SXin Li firmware related setup states so that the next test setup properly 451*9c5db199SXin Li again. 452*9c5db199SXin Li """ 453*9c5db199SXin Li self.unmark_setup_done('gbb_flags') 454*9c5db199SXin Li 455*9c5db199SXin Li def _retrieve_recovery_reason_from_trap(self): 456*9c5db199SXin Li """Try to retrieve the recovery reason from a trapped recovery screen. 457*9c5db199SXin Li 458*9c5db199SXin Li @return: The recovery_reason, 0 if any error. 459*9c5db199SXin Li """ 460*9c5db199SXin Li recovery_reason = 0 461*9c5db199SXin Li logging.info('Try to retrieve recovery reason...') 462*9c5db199SXin Li if self.servo.get_usbkey_state() == 'dut': 463*9c5db199SXin Li self.switcher.bypass_rec_mode() 464*9c5db199SXin Li else: 465*9c5db199SXin Li self.servo.switch_usbkey('dut') 466*9c5db199SXin Li 467*9c5db199SXin Li try: 468*9c5db199SXin Li self.switcher.wait_for_client() 469*9c5db199SXin Li lines = self.faft_client.system.run_shell_command_get_output( 470*9c5db199SXin Li 'crossystem recovery_reason') 471*9c5db199SXin Li recovery_reason = int(lines[0]) 472*9c5db199SXin Li logging.info('Got the recovery reason %d.', recovery_reason) 473*9c5db199SXin Li except ConnectionError: 474*9c5db199SXin Li logging.error('Failed to get the recovery reason due to connection ' 475*9c5db199SXin Li 'error.') 476*9c5db199SXin Li return recovery_reason 477*9c5db199SXin Li 478*9c5db199SXin Li def _reset_client(self): 479*9c5db199SXin Li """Reset client to a workable state. 480*9c5db199SXin Li 481*9c5db199SXin Li This method is called when the client is not responsive. It may be 482*9c5db199SXin Li caused by the following cases: 483*9c5db199SXin Li - halt on a firmware screen without timeout, e.g. REC_INSERT screen; 484*9c5db199SXin Li - corrupted firmware; 485*9c5db199SXin Li - corrutped OS image. 486*9c5db199SXin Li """ 487*9c5db199SXin Li # DUT may halt on a firmware screen. Try cold reboot. 488*9c5db199SXin Li logging.info('Try cold reboot...') 489*9c5db199SXin Li self.switcher.mode_aware_reboot(reboot_type='cold', 490*9c5db199SXin Li sync_before_boot=False, 491*9c5db199SXin Li wait_for_dut_up=False) 492*9c5db199SXin Li self.switcher.wait_for_client_offline() 493*9c5db199SXin Li self.switcher.bypass_dev_mode() 494*9c5db199SXin Li try: 495*9c5db199SXin Li self.switcher.wait_for_client() 496*9c5db199SXin Li return 497*9c5db199SXin Li except ConnectionError: 498*9c5db199SXin Li logging.warning("Cold reboot didn't help, still connection error.") 499*9c5db199SXin Li 500*9c5db199SXin Li # DUT may be broken by a corrupted firmware. Restore firmware. 501*9c5db199SXin Li # We assume the recovery boot still works fine. Since the recovery 502*9c5db199SXin Li # code is in RO region and all FAFT tests don't change the RO region 503*9c5db199SXin Li # except GBB. 504*9c5db199SXin Li if self.is_firmware_saved(): 505*9c5db199SXin Li self._ensure_client_in_recovery() 506*9c5db199SXin Li logging.info('Try restoring the original firmware...') 507*9c5db199SXin Li try: 508*9c5db199SXin Li if self.restore_firmware(): 509*9c5db199SXin Li return 510*9c5db199SXin Li except ConnectionError: 511*9c5db199SXin Li logging.warning("Restoring firmware didn't help, still " 512*9c5db199SXin Li "connection error.") 513*9c5db199SXin Li 514*9c5db199SXin Li # Perhaps it's kernel that's broken. Let's try restoring it. 515*9c5db199SXin Li for kernel_type, kernel_name in self.KERNEL_TYPE_NAME_MAP.items(): 516*9c5db199SXin Li if self.is_kernel_saved(kernel_type): 517*9c5db199SXin Li self._ensure_client_in_recovery() 518*9c5db199SXin Li logging.info('Try restoring the original %s...', kernel_name) 519*9c5db199SXin Li try: 520*9c5db199SXin Li if self.restore_kernel(kernel_type=kernel_type): 521*9c5db199SXin Li return 522*9c5db199SXin Li except ConnectionError: 523*9c5db199SXin Li logging.warning( 524*9c5db199SXin Li "Restoring %s didn't help, still " 525*9c5db199SXin Li "connection error.", kernel_name) 526*9c5db199SXin Li 527*9c5db199SXin Li # DUT may be broken by a corrupted OS image. Restore OS image. 528*9c5db199SXin Li self._ensure_client_in_recovery() 529*9c5db199SXin Li logging.info('Try restoring the OS image...') 530*9c5db199SXin Li self.faft_client.system.run_shell_command('chromeos-install --yes') 531*9c5db199SXin Li self.switcher.mode_aware_reboot(wait_for_dut_up=False) 532*9c5db199SXin Li self.switcher.wait_for_client_offline() 533*9c5db199SXin Li self.switcher.bypass_dev_mode() 534*9c5db199SXin Li try: 535*9c5db199SXin Li self.switcher.wait_for_client() 536*9c5db199SXin Li logging.info('Successfully restored OS image.') 537*9c5db199SXin Li return 538*9c5db199SXin Li except ConnectionError: 539*9c5db199SXin Li logging.warning("Restoring OS image didn't help, still connection " 540*9c5db199SXin Li "error.") 541*9c5db199SXin Li 542*9c5db199SXin Li def _ensure_client_in_recovery(self): 543*9c5db199SXin Li """Ensure client in recovery boot; reboot into it if necessary. 544*9c5db199SXin Li 545*9c5db199SXin Li @raise TestError: if failed to boot the USB image. 546*9c5db199SXin Li """ 547*9c5db199SXin Li logging.info('Try booting into USB image...') 548*9c5db199SXin Li self.switcher.reboot_to_mode(to_mode='rec', sync_before_boot=False, 549*9c5db199SXin Li wait_for_dut_up=False) 550*9c5db199SXin Li self.servo.switch_usbkey('host') 551*9c5db199SXin Li self.switcher.bypass_rec_mode() 552*9c5db199SXin Li try: 553*9c5db199SXin Li self.switcher.wait_for_client() 554*9c5db199SXin Li except ConnectionError: 555*9c5db199SXin Li raise error.TestError('Failed to boot the USB image.') 556*9c5db199SXin Li 557*9c5db199SXin Li def _restore_routine_from_timeout(self): 558*9c5db199SXin Li """A routine to try to restore the system from a timeout error. 559*9c5db199SXin Li 560*9c5db199SXin Li This method is called when FAFT failed to connect DUT after reboot. 561*9c5db199SXin Li 562*9c5db199SXin Li @raise TestFail: This exception is already raised, with a decription 563*9c5db199SXin Li why it failed. 564*9c5db199SXin Li """ 565*9c5db199SXin Li # DUT is disconnected. Capture the UART output for debug. 566*9c5db199SXin Li self._record_uart_capture() 567*9c5db199SXin Li 568*9c5db199SXin Li # Replug the Ethernet to identify if it is a network flaky. 569*9c5db199SXin Li self.servo.eth_power_reset() 570*9c5db199SXin Li 571*9c5db199SXin Li recovery_reason = self._retrieve_recovery_reason_from_trap() 572*9c5db199SXin Li 573*9c5db199SXin Li # Reset client to a workable state. 574*9c5db199SXin Li self._reset_client() 575*9c5db199SXin Li 576*9c5db199SXin Li # Raise the proper TestFail exception. 577*9c5db199SXin Li if recovery_reason: 578*9c5db199SXin Li raise error.TestFail('Trapped in the recovery screen (reason: %d) ' 579*9c5db199SXin Li 'and timed out' % recovery_reason) 580*9c5db199SXin Li else: 581*9c5db199SXin Li raise error.TestFail('Timed out waiting for DUT reboot') 582*9c5db199SXin Li 583*9c5db199SXin Li def assert_test_image_in_usb_disk(self, usb_dev=None): 584*9c5db199SXin Li """Assert an USB disk plugged-in on servo and a test image inside. 585*9c5db199SXin Li 586*9c5db199SXin Li @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'. 587*9c5db199SXin Li If None, it is detected automatically. 588*9c5db199SXin Li @raise TestError: if USB disk not detected or not a test image. 589*9c5db199SXin Li """ 590*9c5db199SXin Li if self.check_setup_done('usb_check'): 591*9c5db199SXin Li return 592*9c5db199SXin Li if usb_dev: 593*9c5db199SXin Li assert self.servo.get_usbkey_state() == 'host' 594*9c5db199SXin Li else: 595*9c5db199SXin Li self.servo.switch_usbkey('host') 596*9c5db199SXin Li usb_dev = self.servo.probe_host_usb_dev() 597*9c5db199SXin Li if not usb_dev: 598*9c5db199SXin Li raise error.TestError( 599*9c5db199SXin Li 'An USB disk should be plugged in the servo board. %s' % 600*9c5db199SXin Li telemetry.collect_usb_state(self.servo)) 601*9c5db199SXin Li 602*9c5db199SXin Li rootfs = '%s%s' % (usb_dev, self._ROOTFS_PARTITION_NUMBER) 603*9c5db199SXin Li logging.info('usb dev is %s', usb_dev) 604*9c5db199SXin Li tmpd = self.servo.system_output('mktemp -d -t usbcheck.XXXX') 605*9c5db199SXin Li # After the USB key is muxed from the DUT to the servo host, there 606*9c5db199SXin Li # appears to be a delay between when servod can confirm that a sysfs 607*9c5db199SXin Li # entry exists for the disk (as done by probe_host_usb_dev) and when 608*9c5db199SXin Li # sysfs entries get populated for the disk's partitions. 609*9c5db199SXin Li @retry.retry(error.AutoservRunError, 610*9c5db199SXin Li timeout_min=PARTITION_TABLE_READINESS_TIMEOUT, 611*9c5db199SXin Li delay_sec=PARTITION_TABLE_READINESS_FIRST_RETRY_DELAY) 612*9c5db199SXin Li def confirm_rootfs_partition_device_node_readable(): 613*9c5db199SXin Li """Repeatedly poll for the RootFS partition sysfs node.""" 614*9c5db199SXin Li self.servo.system('ls {}'.format(rootfs)) 615*9c5db199SXin Li 616*9c5db199SXin Li try: 617*9c5db199SXin Li confirm_rootfs_partition_device_node_readable() 618*9c5db199SXin Li except error.AutoservRunError as e: 619*9c5db199SXin Li usb_info = telemetry.collect_usb_state(self.servo) 620*9c5db199SXin Li raise error.TestError( 621*9c5db199SXin Li ('Could not ls the device node for the RootFS on the USB ' 622*9c5db199SXin Li 'device. %s: %s\nMore telemetry: %s') % 623*9c5db199SXin Li (type(e).__name__, e, usb_info)) 624*9c5db199SXin Li try: 625*9c5db199SXin Li self.servo.system('mount -o ro %s %s' % (rootfs, tmpd)) 626*9c5db199SXin Li except error.AutoservRunError as e: 627*9c5db199SXin Li usb_info = telemetry.collect_usb_state(self.servo) 628*9c5db199SXin Li raise error.TestError( 629*9c5db199SXin Li ('Could not mount the partition on USB device. %s: %s\n' 630*9c5db199SXin Li 'More telemetry: %s') % (type(e).__name__, e, usb_info)) 631*9c5db199SXin Li 632*9c5db199SXin Li try: 633*9c5db199SXin Li usb_lsb = self.servo.system_output('cat %s' % 634*9c5db199SXin Li os.path.join(tmpd, 'etc/lsb-release')) 635*9c5db199SXin Li logging.debug('Dumping lsb-release on USB stick:\n%s', usb_lsb) 636*9c5db199SXin Li dut_lsb = '\n'.join(self.faft_client.system. 637*9c5db199SXin Li run_shell_command_get_output('cat /etc/lsb-release')) 638*9c5db199SXin Li logging.debug('Dumping lsb-release on DUT:\n%s', dut_lsb) 639*9c5db199SXin Li if not re.search(r'RELEASE_TRACK=.*test', usb_lsb): 640*9c5db199SXin Li raise error.TestError('USB stick in servo is no test image') 641*9c5db199SXin Li usb_board = re.search(r'BOARD=(.*)', usb_lsb).group(1) 642*9c5db199SXin Li dut_board = re.search(r'BOARD=(.*)', dut_lsb).group(1) 643*9c5db199SXin Li if usb_board != dut_board: 644*9c5db199SXin Li raise error.TestError('USB stick in servo contains a %s ' 645*9c5db199SXin Li 'image, but DUT is a %s' % (usb_board, dut_board)) 646*9c5db199SXin Li finally: 647*9c5db199SXin Li for cmd in ('umount -l %s' % tmpd, 'sync %s' % usb_dev, 648*9c5db199SXin Li 'rm -rf %s' % tmpd): 649*9c5db199SXin Li self.servo.system(cmd) 650*9c5db199SXin Li 651*9c5db199SXin Li self.mark_setup_done('usb_check') 652*9c5db199SXin Li 653*9c5db199SXin Li def setup_pdtester(self, flip_cc=False, dts_mode=False, pd_faft=True, 654*9c5db199SXin Li min_batt_level=None): 655*9c5db199SXin Li """Setup the PDTester to a given state. 656*9c5db199SXin Li 657*9c5db199SXin Li @param flip_cc: True to flip CC polarity; False to not flip it. 658*9c5db199SXin Li @param dts_mode: True to config PDTester to DTS mode; False to not. 659*9c5db199SXin Li @param pd_faft: True to config PD FAFT setup. 660*9c5db199SXin Li @param min_batt_level: An int for minimum battery level, or None for 661*9c5db199SXin Li skip. 662*9c5db199SXin Li @raise TestError: If Servo v4 not setup properly. 663*9c5db199SXin Li """ 664*9c5db199SXin Li 665*9c5db199SXin Li # PD FAFT is only tested with a combination of servo_v4 or servo_v4p1 666*9c5db199SXin Li # with servo micro or C2D2. 667*9c5db199SXin Li pd_setup = [] 668*9c5db199SXin Li for first in self.pdtester.FIRST_PD_SETUP_ELEMENT: 669*9c5db199SXin Li for second in self.pdtester.SECOND_PD_SETUP_ELEMENT: 670*9c5db199SXin Li pd_setup.append(first + '_with_' + second) 671*9c5db199SXin Li 672*9c5db199SXin Li if pd_faft and self.pdtester.servo_type not in pd_setup: 673*9c5db199SXin Li raise error.TestError(', '.join(pd_setup) + 674*9c5db199SXin Li ' is a mandatory setup ' 675*9c5db199SXin Li 'for PD FAFT. Got %s.' % 676*9c5db199SXin Li self.pdtester.servo_type) 677*9c5db199SXin Li 678*9c5db199SXin Li # Ensure the battery is enough for testing, this should be done before 679*9c5db199SXin Li # all the following setup. 680*9c5db199SXin Li if (min_batt_level is not None) and self._client.has_battery(): 681*9c5db199SXin Li logging.info('Start charging if batt level < %d', min_batt_level) 682*9c5db199SXin Li PowerUtils.put_host_battery_in_range(self._client, min_batt_level, 683*9c5db199SXin Li 100, 600) 684*9c5db199SXin Li 685*9c5db199SXin Li # Servo v4 by default has dts_mode enabled. Enabling dts_mode affects 686*9c5db199SXin Li # the behaviors of what PD FAFT tests. So we want it disabled. 687*9c5db199SXin Li pd_tester_device = self.pdtester.servo_type.split('_with_')[0] 688*9c5db199SXin Li if pd_tester_device in self.pdtester.FIRST_PD_SETUP_ELEMENT: 689*9c5db199SXin Li self.servo.set_dts_mode('on' if dts_mode else 'off') 690*9c5db199SXin Li else: 691*9c5db199SXin Li logging.warning('Configuring DTS mode only supported on %s', 692*9c5db199SXin Li pd_tester_device) 693*9c5db199SXin Li 694*9c5db199SXin Li self.pdtester.set('usbc_polarity', 'cc2' if flip_cc else 'cc1') 695*9c5db199SXin Li # Make it sourcing max voltage. 696*9c5db199SXin Li self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) 697*9c5db199SXin Li 698*9c5db199SXin Li time.sleep(self.PD_RESYNC_DELAY) 699*9c5db199SXin Li 700*9c5db199SXin Li # Servo v4 requires an external charger to source power. Make sure 701*9c5db199SXin Li # this setup is correct. 702*9c5db199SXin Li if pd_tester_device in self.pdtester.FIRST_PD_SETUP_ELEMENT: 703*9c5db199SXin Li role = self.pdtester.get('servo_pd_role') 704*9c5db199SXin Li if role != 'src': 705*9c5db199SXin Li raise error.TestError( 706*9c5db199SXin Li '%s is not sourcing power! Make sure the servo ' 707*9c5db199SXin Li '"DUT POWER" port is connected to a working charger. ' 708*9c5db199SXin Li 'servo_pd_role:%s' % (pd_tester_device, role)) 709*9c5db199SXin Li 710*9c5db199SXin Li def setup_usbkey(self, usbkey, host=None, used_for_recovery=None): 711*9c5db199SXin Li """Setup the USB disk for the test. 712*9c5db199SXin Li 713*9c5db199SXin Li It checks the setup of USB disk and a valid ChromeOS test image inside. 714*9c5db199SXin Li It also muxes the USB disk to either the host or DUT by request. 715*9c5db199SXin Li 716*9c5db199SXin Li @param usbkey: True if the USB disk is required for the test, False if 717*9c5db199SXin Li not required. 718*9c5db199SXin Li @param host: Optional, True to mux the USB disk to host, False to mux it 719*9c5db199SXin Li to DUT, default to do nothing. 720*9c5db199SXin Li @param used_for_recovery: Optional, True if the USB disk is used for 721*9c5db199SXin Li recovery boot; False if the USB disk is not 722*9c5db199SXin Li used for recovery boot, like Ctrl-U USB boot. 723*9c5db199SXin Li """ 724*9c5db199SXin Li if usbkey: 725*9c5db199SXin Li self.stage_build_to_usbkey() 726*9c5db199SXin Li self.assert_test_image_in_usb_disk() 727*9c5db199SXin Li elif host is None: 728*9c5db199SXin Li # USB disk is not required for the test. Better to mux it to host. 729*9c5db199SXin Li host = True 730*9c5db199SXin Li 731*9c5db199SXin Li if host is True: 732*9c5db199SXin Li self.servo.switch_usbkey('host') 733*9c5db199SXin Li elif host is False: 734*9c5db199SXin Li self.servo.switch_usbkey('dut') 735*9c5db199SXin Li 736*9c5db199SXin Li if used_for_recovery is None: 737*9c5db199SXin Li # Default value is True if usbkey == True. 738*9c5db199SXin Li # As the common usecase of USB disk is for recovery boot. Tests 739*9c5db199SXin Li # can define it explicitly if not. 740*9c5db199SXin Li used_for_recovery = usbkey 741*9c5db199SXin Li 742*9c5db199SXin Li if used_for_recovery: 743*9c5db199SXin Li # In recovery boot, the locked EC RO doesn't support PD for most 744*9c5db199SXin Li # of the CrOS devices. The default servo v4 power role is a SRC. 745*9c5db199SXin Li # The DUT becomes a SNK. Lack of PD makes CrOS unable to do the 746*9c5db199SXin Li # data role swap from UFP to DFP; as a result, DUT can't see the 747*9c5db199SXin Li # USB disk and the Ethernet dongle on servo v4. 748*9c5db199SXin Li # 749*9c5db199SXin Li # This is a workaround to set servo v4 as a SNK, for every FAFT 750*9c5db199SXin Li # test which boots into the USB disk in the recovery mode. 751*9c5db199SXin Li # 752*9c5db199SXin Li # TODO(waihong): Add a check to see if the battery level is too 753*9c5db199SXin Li # low and sleep for a while for charging. 754*9c5db199SXin Li self.set_servo_v4_role_to_snk() 755*9c5db199SXin Li 756*9c5db199SXin Li # Force reconnection; otherwise, the next RPC call will timeout 757*9c5db199SXin Li logging.info('Waiting for reconnection after power role swap...') 758*9c5db199SXin Li time.sleep(self.POWER_ROLE_SWAP_DELAY) 759*9c5db199SXin Li self.faft_client.disconnect() 760*9c5db199SXin Li self.faft_client.connect() 761*9c5db199SXin Li 762*9c5db199SXin Li def set_servo_v4_role_to_snk(self, pd_comm=False): 763*9c5db199SXin Li """Set the servo v4 role to SNK. 764*9c5db199SXin Li 765*9c5db199SXin Li @param pd_comm: a bool. Enable PD communication if True, else otherwise 766*9c5db199SXin Li """ 767*9c5db199SXin Li self._needed_restore_servo_v4_role = True 768*9c5db199SXin Li self.servo.set_servo_v4_role('snk') 769*9c5db199SXin Li if pd_comm: 770*9c5db199SXin Li self.servo.set_servo_v4_pd_comm('on') 771*9c5db199SXin Li 772*9c5db199SXin Li def _restore_servo_v4_role(self): 773*9c5db199SXin Li """Restore the servo v4 role to default SRC.""" 774*9c5db199SXin Li if not hasattr(self, '_needed_restore_servo_v4_role'): 775*9c5db199SXin Li return 776*9c5db199SXin Li if self._needed_restore_servo_v4_role: 777*9c5db199SXin Li self.servo.set_servo_v4_role('src') 778*9c5db199SXin Li 779*9c5db199SXin Li def set_dut_low_power_idle_delay(self, delay): 780*9c5db199SXin Li """Set EC low power idle delay 781*9c5db199SXin Li 782*9c5db199SXin Li @param delay: Delay in seconds 783*9c5db199SXin Li """ 784*9c5db199SXin Li if not self.ec.has_command('dsleep'): 785*9c5db199SXin Li logging.info("Can't set low power idle delay.") 786*9c5db199SXin Li return 787*9c5db199SXin Li self._previous_ec_low_power_delay = int( 788*9c5db199SXin Li self.ec.send_command_get_output("dsleep", 789*9c5db199SXin Li ["timeout:\s+(\d+)\ssec"])[0][1]) 790*9c5db199SXin Li self.ec.send_command("dsleep " + str(delay)) 791*9c5db199SXin Li 792*9c5db199SXin Li def restore_dut_low_power_idle_delay(self): 793*9c5db199SXin Li """Restore EC low power idle delay""" 794*9c5db199SXin Li if getattr(self, '_previous_ec_low_power_delay', None): 795*9c5db199SXin Li self.ec.send_command("dsleep " + str( 796*9c5db199SXin Li self._previous_ec_low_power_delay)) 797*9c5db199SXin Li 798*9c5db199SXin Li def get_usbdisk_path_on_dut(self): 799*9c5db199SXin Li """Get the path of the USB disk device plugged-in the servo on DUT. 800*9c5db199SXin Li 801*9c5db199SXin Li Returns: 802*9c5db199SXin Li A string representing USB disk path, like '/dev/sdb', or None if 803*9c5db199SXin Li no USB disk is found. 804*9c5db199SXin Li """ 805*9c5db199SXin Li cmd = 'ls -d /dev/s*[a-z]' 806*9c5db199SXin Li original_value = self.servo.get_usbkey_state() 807*9c5db199SXin Li 808*9c5db199SXin Li # Make the dut unable to see the USB disk. 809*9c5db199SXin Li self.servo.switch_usbkey('off') 810*9c5db199SXin Li time.sleep(self.faft_config.usb_unplug) 811*9c5db199SXin Li no_usb_set = set( 812*9c5db199SXin Li self.faft_client.system.run_shell_command_get_output(cmd)) 813*9c5db199SXin Li 814*9c5db199SXin Li # Make the dut able to see the USB disk. 815*9c5db199SXin Li self.servo.switch_usbkey('dut') 816*9c5db199SXin Li time.sleep(self.faft_config.usb_plug) 817*9c5db199SXin Li has_usb_set = set( 818*9c5db199SXin Li self.faft_client.system.run_shell_command_get_output(cmd)) 819*9c5db199SXin Li 820*9c5db199SXin Li # Back to its original value. 821*9c5db199SXin Li if original_value != self.servo.get_usbkey_state(): 822*9c5db199SXin Li self.servo.switch_usbkey(original_value) 823*9c5db199SXin Li 824*9c5db199SXin Li diff_set = has_usb_set - no_usb_set 825*9c5db199SXin Li if len(diff_set) == 1: 826*9c5db199SXin Li return diff_set.pop() 827*9c5db199SXin Li else: 828*9c5db199SXin Li return None 829*9c5db199SXin Li 830*9c5db199SXin Li def _create_faft_lockfile(self): 831*9c5db199SXin Li """Creates the FAFT lockfile.""" 832*9c5db199SXin Li logging.info('Creating FAFT lockfile...') 833*9c5db199SXin Li command = 'touch %s' % (self.lockfile) 834*9c5db199SXin Li self.faft_client.system.run_shell_command(command) 835*9c5db199SXin Li 836*9c5db199SXin Li def _remove_faft_lockfile(self): 837*9c5db199SXin Li """Removes the FAFT lockfile.""" 838*9c5db199SXin Li logging.info('Removing FAFT lockfile...') 839*9c5db199SXin Li command = 'rm -f %s' % (self.lockfile) 840*9c5db199SXin Li self.faft_client.system.run_shell_command(command) 841*9c5db199SXin Li 842*9c5db199SXin Li def clear_set_gbb_flags(self, clear_mask, set_mask, reboot=True): 843*9c5db199SXin Li """Clear and set the GBB flags in the current flashrom. 844*9c5db199SXin Li 845*9c5db199SXin Li @param clear_mask: A mask of flags to be cleared. 846*9c5db199SXin Li @param set_mask: A mask of flags to be set. 847*9c5db199SXin Li @param reboot: If true, then this method will reboot the DUT if certain 848*9c5db199SXin Li flags are modified. 849*9c5db199SXin Li """ 850*9c5db199SXin Li gbb_flags = self.faft_client.bios.get_gbb_flags() 851*9c5db199SXin Li new_flags = gbb_flags & ctypes.c_uint32(~clear_mask).value | set_mask 852*9c5db199SXin Li if new_flags == gbb_flags: 853*9c5db199SXin Li logging.info( 854*9c5db199SXin Li 'Current GBB flags (0x%x) match desired clear mask ' 855*9c5db199SXin Li '(0x%x) and desired set mask (0x%x)', gbb_flags, 856*9c5db199SXin Li clear_mask, set_mask) 857*9c5db199SXin Li return 858*9c5db199SXin Li 859*9c5db199SXin Li logging.info('Changing GBB flags from 0x%x to 0x%x.', gbb_flags, 860*9c5db199SXin Li new_flags) 861*9c5db199SXin Li if self._backup_gbb_flags is None: 862*9c5db199SXin Li self._backup_gbb_flags = gbb_flags 863*9c5db199SXin Li self.faft_client.bios.set_gbb_flags(new_flags) 864*9c5db199SXin Li 865*9c5db199SXin Li # If changing FORCE_DEV_SWITCH_ON or DISABLE_EC_SOFTWARE_SYNC flag, 866*9c5db199SXin Li # reboot to get a clear state 867*9c5db199SXin Li if reboot and bool((gbb_flags ^ new_flags) 868*9c5db199SXin Li & (vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON 869*9c5db199SXin Li | vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC)): 870*9c5db199SXin Li self.switcher.mode_aware_reboot() 871*9c5db199SXin Li 872*9c5db199SXin Li 873*9c5db199SXin Li def _check_capability(self, target, required_cap, suppress_warning): 874*9c5db199SXin Li """Check if current platform has required capabilities for the target. 875*9c5db199SXin Li 876*9c5db199SXin Li @param required_cap: A list containing required capabilities. 877*9c5db199SXin Li @param suppress_warning: True to suppress any warning messages. 878*9c5db199SXin Li @return: True if requirements are met. Otherwise, False. 879*9c5db199SXin Li """ 880*9c5db199SXin Li if not required_cap: 881*9c5db199SXin Li return True 882*9c5db199SXin Li 883*9c5db199SXin Li if target not in ['ec', 'cr50']: 884*9c5db199SXin Li raise error.TestError('Invalid capability target %r' % target) 885*9c5db199SXin Li 886*9c5db199SXin Li for cap in required_cap: 887*9c5db199SXin Li if cap not in getattr(self.faft_config, target + '_capability'): 888*9c5db199SXin Li if not suppress_warning: 889*9c5db199SXin Li logging.warning( 890*9c5db199SXin Li 'Requires %s capability "%s" to run this ' 891*9c5db199SXin Li 'test.', target, cap) 892*9c5db199SXin Li return False 893*9c5db199SXin Li 894*9c5db199SXin Li return True 895*9c5db199SXin Li 896*9c5db199SXin Li 897*9c5db199SXin Li def check_ec_capability(self, required_cap=None, suppress_warning=False): 898*9c5db199SXin Li """Check if current platform has required EC capabilities. 899*9c5db199SXin Li 900*9c5db199SXin Li @param required_cap: A list containing required EC capabilities. Pass in 901*9c5db199SXin Li None to only check for presence of Chrome EC. 902*9c5db199SXin Li @param suppress_warning: True to suppress any warning messages. 903*9c5db199SXin Li @return: True if requirements are met. Otherwise, False. 904*9c5db199SXin Li """ 905*9c5db199SXin Li if not self.faft_config.chrome_ec: 906*9c5db199SXin Li if not suppress_warning: 907*9c5db199SXin Li logging.warning('Requires Chrome EC to run this test.') 908*9c5db199SXin Li return False 909*9c5db199SXin Li return self._check_capability('ec', required_cap, suppress_warning) 910*9c5db199SXin Li 911*9c5db199SXin Li 912*9c5db199SXin Li def check_cr50_capability(self, required_cap=None, suppress_warning=False): 913*9c5db199SXin Li """Check if current platform has required Cr50 capabilities. 914*9c5db199SXin Li 915*9c5db199SXin Li @param required_cap: A list containing required Cr50 capabilities. Pass 916*9c5db199SXin Li in None to only check for presence of cr50 uart. 917*9c5db199SXin Li @param suppress_warning: True to suppress any warning messages. 918*9c5db199SXin Li @return: True if requirements are met. Otherwise, False. 919*9c5db199SXin Li """ 920*9c5db199SXin Li if not hasattr(self, 'cr50'): 921*9c5db199SXin Li if not suppress_warning: 922*9c5db199SXin Li logging.warning('Requires Chrome Cr50 to run this test.') 923*9c5db199SXin Li return False 924*9c5db199SXin Li return self._check_capability('cr50', required_cap, suppress_warning) 925*9c5db199SXin Li 926*9c5db199SXin Li 927*9c5db199SXin Li def check_root_part_on_non_recovery(self, part): 928*9c5db199SXin Li """Check the partition number of root device and on normal/dev boot. 929*9c5db199SXin Li 930*9c5db199SXin Li @param part: A string of partition number, e.g.'3'. 931*9c5db199SXin Li @return: True if the root device matched and on normal/dev boot; 932*9c5db199SXin Li otherwise, False. 933*9c5db199SXin Li """ 934*9c5db199SXin Li return self.checkers.root_part_checker(part) and \ 935*9c5db199SXin Li self.checkers.crossystem_checker({ 936*9c5db199SXin Li 'mainfw_type': ('normal', 'developer'), 937*9c5db199SXin Li }) 938*9c5db199SXin Li 939*9c5db199SXin Li def _join_part(self, dev, part): 940*9c5db199SXin Li """Return a concatenated string of device and partition number. 941*9c5db199SXin Li 942*9c5db199SXin Li @param dev: A string of device, e.g.'/dev/sda'. 943*9c5db199SXin Li @param part: A string of partition number, e.g.'3'. 944*9c5db199SXin Li @return: A concatenated string of device and partition number, 945*9c5db199SXin Li e.g.'/dev/sda3'. 946*9c5db199SXin Li 947*9c5db199SXin Li >>> seq = FirmwareTest() 948*9c5db199SXin Li >>> seq._join_part('/dev/sda', '3') 949*9c5db199SXin Li '/dev/sda3' 950*9c5db199SXin Li >>> seq._join_part('/dev/mmcblk0', '2') 951*9c5db199SXin Li '/dev/mmcblk0p2' 952*9c5db199SXin Li """ 953*9c5db199SXin Li if 'mmcblk' in dev: 954*9c5db199SXin Li return dev + 'p' + part 955*9c5db199SXin Li elif 'nvme' in dev: 956*9c5db199SXin Li return dev + 'p' + part 957*9c5db199SXin Li else: 958*9c5db199SXin Li return dev + part 959*9c5db199SXin Li 960*9c5db199SXin Li def copy_kernel_and_rootfs(self, from_part, to_part): 961*9c5db199SXin Li """Copy kernel and rootfs from from_part to to_part. 962*9c5db199SXin Li 963*9c5db199SXin Li @param from_part: A string of partition number to be copied from. 964*9c5db199SXin Li @param to_part: A string of partition number to be copied to. 965*9c5db199SXin Li """ 966*9c5db199SXin Li root_dev = self.faft_client.system.get_root_dev() 967*9c5db199SXin Li logging.info('Copying kernel from %s to %s. Please wait...', 968*9c5db199SXin Li from_part, to_part) 969*9c5db199SXin Li self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' % 970*9c5db199SXin Li (self._join_part(root_dev, self.KERNEL_MAP[from_part]), 971*9c5db199SXin Li self._join_part(root_dev, self.KERNEL_MAP[to_part]))) 972*9c5db199SXin Li logging.info('Copying rootfs from %s to %s. Please wait...', 973*9c5db199SXin Li from_part, to_part) 974*9c5db199SXin Li self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' % 975*9c5db199SXin Li (self._join_part(root_dev, self.ROOTFS_MAP[from_part]), 976*9c5db199SXin Li self._join_part(root_dev, self.ROOTFS_MAP[to_part]))) 977*9c5db199SXin Li 978*9c5db199SXin Li def ensure_kernel_boot(self, part): 979*9c5db199SXin Li """Ensure the request kernel boot. 980*9c5db199SXin Li 981*9c5db199SXin Li If not, it duplicates the current kernel to the requested kernel 982*9c5db199SXin Li and sets the requested higher priority to ensure it boot. 983*9c5db199SXin Li 984*9c5db199SXin Li @param part: A string of kernel partition number or 'a'/'b'. 985*9c5db199SXin Li """ 986*9c5db199SXin Li if not self.checkers.root_part_checker(part): 987*9c5db199SXin Li if self.faft_client.kernel.diff_a_b(): 988*9c5db199SXin Li self.copy_kernel_and_rootfs( 989*9c5db199SXin Li from_part=self.OTHER_KERNEL_MAP[part], 990*9c5db199SXin Li to_part=part) 991*9c5db199SXin Li self.reset_and_prioritize_kernel(part) 992*9c5db199SXin Li self.switcher.mode_aware_reboot() 993*9c5db199SXin Li 994*9c5db199SXin Li def ensure_dev_internal_boot(self, original_dev_boot_usb): 995*9c5db199SXin Li """Ensure internal device boot in developer mode. 996*9c5db199SXin Li 997*9c5db199SXin Li If not internal device boot, it will try to reboot the device and 998*9c5db199SXin Li bypass dev mode to boot into internal device. 999*9c5db199SXin Li 1000*9c5db199SXin Li @param original_dev_boot_usb: Original dev_boot_usb value. 1001*9c5db199SXin Li """ 1002*9c5db199SXin Li self.faft_client.system.set_dev_default_boot() 1003*9c5db199SXin Li if self.faft_client.system.is_removable_device_boot(): 1004*9c5db199SXin Li logging.info('Reboot into internal disk...') 1005*9c5db199SXin Li self.faft_client.system.set_dev_boot_usb(original_dev_boot_usb) 1006*9c5db199SXin Li self.switcher.mode_aware_reboot() 1007*9c5db199SXin Li self.check_state((self.checkers.dev_boot_usb_checker, False, 1008*9c5db199SXin Li 'Device not booted from internal disk properly.')) 1009*9c5db199SXin Li 1010*9c5db199SXin Li def set_hardware_write_protect(self, enable): 1011*9c5db199SXin Li """Set hardware write protect pin. 1012*9c5db199SXin Li 1013*9c5db199SXin Li @param enable: True if asserting write protect pin. Otherwise, False. 1014*9c5db199SXin Li """ 1015*9c5db199SXin Li self.servo.set('fw_wp_state', 'force_on' if enable else 'force_off') 1016*9c5db199SXin Li 1017*9c5db199SXin Li def set_ap_write_protect_and_reboot(self, enable): 1018*9c5db199SXin Li """Set AP write protect status and reboot to take effect. 1019*9c5db199SXin Li 1020*9c5db199SXin Li @param enable: True if asserting write protect. Otherwise, False. 1021*9c5db199SXin Li """ 1022*9c5db199SXin Li self.set_hardware_write_protect(enable) 1023*9c5db199SXin Li if hasattr(self, 'ec'): 1024*9c5db199SXin Li self.sync_and_ec_reboot() 1025*9c5db199SXin Li self.switcher.wait_for_client() 1026*9c5db199SXin Li 1027*9c5db199SXin Li def run_chromeos_firmwareupdate(self, mode, append=None, options=(), 1028*9c5db199SXin Li ignore_status=False): 1029*9c5db199SXin Li """Use RPC to get the command to run, but do the actual run via ssh. 1030*9c5db199SXin Li 1031*9c5db199SXin Li Running the command via SSH improves the reliability in cases where the 1032*9c5db199SXin Li USB network connection gets interrupted. SSH will still return the 1033*9c5db199SXin Li output, and won't hang like RPC would. 1034*9c5db199SXin Li """ 1035*9c5db199SXin Li update_cmd = self.faft_client.updater.get_firmwareupdate_command( 1036*9c5db199SXin Li mode, append, options) 1037*9c5db199SXin Li try: 1038*9c5db199SXin Li result = self._client.run( 1039*9c5db199SXin Li update_cmd, timeout=300, ignore_status=ignore_status) 1040*9c5db199SXin Li if result.exit_status == 255: 1041*9c5db199SXin Li self.faft_client.disconnect() 1042*9c5db199SXin Li return result 1043*9c5db199SXin Li except error.AutoservRunError as e: 1044*9c5db199SXin Li if e.result_obj.exit_status == 255: 1045*9c5db199SXin Li self.faft_client.disconnect() 1046*9c5db199SXin Li if ignore_status: 1047*9c5db199SXin Li return e.result_obj 1048*9c5db199SXin Li raise 1049*9c5db199SXin Li 1050*9c5db199SXin Li def set_ec_write_protect_and_reboot(self, enable): 1051*9c5db199SXin Li """Set EC write protect status and reboot to take effect. 1052*9c5db199SXin Li 1053*9c5db199SXin Li The write protect state is only activated if both hardware write 1054*9c5db199SXin Li protect pin is asserted and software write protect flag is set. 1055*9c5db199SXin Li This method asserts/deasserts hardware write protect pin first, and 1056*9c5db199SXin Li set corresponding EC software write protect flag. 1057*9c5db199SXin Li 1058*9c5db199SXin Li If the device uses non-Chrome EC, set the software write protect via 1059*9c5db199SXin Li flashrom. 1060*9c5db199SXin Li 1061*9c5db199SXin Li If the device uses Chrome EC, a reboot is required for write protect 1062*9c5db199SXin Li to take effect. Since the software write protect flag cannot be unset 1063*9c5db199SXin Li if hardware write protect pin is asserted, we need to deasserted the 1064*9c5db199SXin Li pin first if we are deactivating write protect. Similarly, a reboot 1065*9c5db199SXin Li is required before we can modify the software flag. 1066*9c5db199SXin Li 1067*9c5db199SXin Li @param enable: True if activating EC write protect. Otherwise, False. 1068*9c5db199SXin Li """ 1069*9c5db199SXin Li self.set_hardware_write_protect(enable) 1070*9c5db199SXin Li if self.faft_config.chrome_ec: 1071*9c5db199SXin Li self.set_chrome_ec_write_protect_and_reboot(enable) 1072*9c5db199SXin Li else: 1073*9c5db199SXin Li self.faft_client.ec.set_write_protect(enable) 1074*9c5db199SXin Li self.switcher.mode_aware_reboot() 1075*9c5db199SXin Li 1076*9c5db199SXin Li def set_chrome_ec_write_protect_and_reboot(self, enable): 1077*9c5db199SXin Li """Set Chrome EC write protect status and reboot to take effect. 1078*9c5db199SXin Li 1079*9c5db199SXin Li @param enable: True if activating EC write protect. Otherwise, False. 1080*9c5db199SXin Li """ 1081*9c5db199SXin Li if enable: 1082*9c5db199SXin Li # Set write protect flag and reboot to take effect. 1083*9c5db199SXin Li self.ec.set_flash_write_protect(enable) 1084*9c5db199SXin Li self.sync_and_ec_reboot( 1085*9c5db199SXin Li flags='hard', 1086*9c5db199SXin Li extra_sleep=self.faft_config.ec_boot_to_wp_en) 1087*9c5db199SXin Li else: 1088*9c5db199SXin Li # Reboot after deasserting hardware write protect pin to deactivate 1089*9c5db199SXin Li # write protect. And then remove software write protect flag. 1090*9c5db199SXin Li # Some ITE ECs can only clear their WP status on a power-on reset, 1091*9c5db199SXin Li # no software-initiated reset will do. 1092*9c5db199SXin Li self.sync_and_ec_reboot(flags='cold') 1093*9c5db199SXin Li self.ec.set_flash_write_protect(enable) 1094*9c5db199SXin Li 1095*9c5db199SXin Li def _setup_ec_write_protect(self, ec_wp): 1096*9c5db199SXin Li """Setup for EC write-protection. 1097*9c5db199SXin Li 1098*9c5db199SXin Li It makes sure the EC in the requested write-protection state. If not, it 1099*9c5db199SXin Li flips the state. Flipping the write-protection requires DUT reboot. 1100*9c5db199SXin Li 1101*9c5db199SXin Li @param ec_wp: True to request EC write-protected; False to request EC 1102*9c5db199SXin Li not write-protected; None to do nothing. 1103*9c5db199SXin Li """ 1104*9c5db199SXin Li if ec_wp is None: 1105*9c5db199SXin Li return 1106*9c5db199SXin Li self._old_wpsw_cur = self.checkers.crossystem_checker( 1107*9c5db199SXin Li {'wpsw_cur': '1'}, suppress_logging=True) 1108*9c5db199SXin Li if ec_wp != self._old_wpsw_cur: 1109*9c5db199SXin Li if not self.faft_config.ap_access_ec_flash: 1110*9c5db199SXin Li raise error.TestNAError( 1111*9c5db199SXin Li "Cannot change EC write-protect for this device") 1112*9c5db199SXin Li 1113*9c5db199SXin Li logging.info('The test required EC is %swrite-protected. Reboot ' 1114*9c5db199SXin Li 'and flip the state.', '' if ec_wp else 'not ') 1115*9c5db199SXin Li self.switcher.mode_aware_reboot( 1116*9c5db199SXin Li 'custom', 1117*9c5db199SXin Li lambda:self.set_ec_write_protect_and_reboot(ec_wp)) 1118*9c5db199SXin Li wpsw_cur = '1' if ec_wp else '0' 1119*9c5db199SXin Li self.check_state((self.checkers.crossystem_checker, { 1120*9c5db199SXin Li 'wpsw_cur': wpsw_cur})) 1121*9c5db199SXin Li 1122*9c5db199SXin Li def _restore_ec_write_protect(self): 1123*9c5db199SXin Li """Restore the original EC write-protection.""" 1124*9c5db199SXin Li if (not hasattr(self, '_old_wpsw_cur')) or (self._old_wpsw_cur is 1125*9c5db199SXin Li None): 1126*9c5db199SXin Li return 1127*9c5db199SXin Li if not self.checkers.crossystem_checker({'wpsw_cur': '1' if 1128*9c5db199SXin Li self._old_wpsw_cur else '0'}, suppress_logging=True): 1129*9c5db199SXin Li logging.info('Restore original EC write protection and reboot.') 1130*9c5db199SXin Li self.switcher.mode_aware_reboot( 1131*9c5db199SXin Li 'custom', 1132*9c5db199SXin Li lambda:self.set_ec_write_protect_and_reboot( 1133*9c5db199SXin Li self._old_wpsw_cur)) 1134*9c5db199SXin Li self.check_state((self.checkers.crossystem_checker, { 1135*9c5db199SXin Li 'wpsw_cur': '1' if self._old_wpsw_cur else '0'})) 1136*9c5db199SXin Li 1137*9c5db199SXin Li def _record_uart_capture(self): 1138*9c5db199SXin Li """Record the CPU/EC/PD UART output stream to files.""" 1139*9c5db199SXin Li self.servo.record_uart_capture(self.resultsdir) 1140*9c5db199SXin Li 1141*9c5db199SXin Li def _cleanup_uart_capture(self): 1142*9c5db199SXin Li """Cleanup the CPU/EC/PD UART capture.""" 1143*9c5db199SXin Li self.servo.close(self.resultsdir) 1144*9c5db199SXin Li 1145*9c5db199SXin Li def set_ap_off_power_mode(self, power_mode): 1146*9c5db199SXin Li """ 1147*9c5db199SXin Li Set the DUT power mode to suspend (S0ix/S3) or shutdown (G3/S5). 1148*9c5db199SXin Li The DUT must be in S0 when calling this method. 1149*9c5db199SXin Li 1150*9c5db199SXin Li @param power_mode: a string for the expected power mode, either 1151*9c5db199SXin Li 'suspend' or 'shutdown'. 1152*9c5db199SXin Li """ 1153*9c5db199SXin Li if power_mode == 'suspend': 1154*9c5db199SXin Li target_power_state = self.POWER_STATE_SUSPEND 1155*9c5db199SXin Li elif power_mode == 'shutdown': 1156*9c5db199SXin Li target_power_state = self.POWER_STATE_G3 1157*9c5db199SXin Li else: 1158*9c5db199SXin Li raise error.TestError('%s is not a valid ap-off power mode.' % 1159*9c5db199SXin Li power_mode) 1160*9c5db199SXin Li 1161*9c5db199SXin Li if self.get_power_state() != self.POWER_STATE_S0: 1162*9c5db199SXin Li raise error.TestError('The DUT is not in S0.') 1163*9c5db199SXin Li 1164*9c5db199SXin Li self._restore_power_mode = True 1165*9c5db199SXin Li 1166*9c5db199SXin Li if target_power_state == self.POWER_STATE_G3: 1167*9c5db199SXin Li self.run_shutdown_cmd() 1168*9c5db199SXin Li time.sleep(self.faft_config.shutdown) 1169*9c5db199SXin Li elif target_power_state == self.POWER_STATE_SUSPEND: 1170*9c5db199SXin Li self.suspend() 1171*9c5db199SXin Li 1172*9c5db199SXin Li if self.wait_power_state(target_power_state, self.DEFAULT_PWR_RETRIES): 1173*9c5db199SXin Li logging.info('System entered %s state.', target_power_state) 1174*9c5db199SXin Li else: 1175*9c5db199SXin Li self._restore_power_mode = False 1176*9c5db199SXin Li raise error.TestFail('System fail to enter %s state. ' 1177*9c5db199SXin Li 'Current state: %s' % 1178*9c5db199SXin Li (target_power_state, self.get_power_state())) 1179*9c5db199SXin Li 1180*9c5db199SXin Li def restore_ap_on_power_mode(self): 1181*9c5db199SXin Li """ 1182*9c5db199SXin Li Wake up the DUT to S0. If the DUT was not set to suspend or 1183*9c5db199SXin Li shutdown mode by set_ap_off_power_mode(), raise an error. 1184*9c5db199SXin Li """ 1185*9c5db199SXin Li if self.get_power_state() != self.POWER_STATE_S0: 1186*9c5db199SXin Li logging.info('Wake up the DUT to S0.') 1187*9c5db199SXin Li self.servo.power_normal_press() 1188*9c5db199SXin Li # If the DUT is ping-able, it must be in S0. 1189*9c5db199SXin Li self.switcher.wait_for_client() 1190*9c5db199SXin Li if self._restore_power_mode != True: 1191*9c5db199SXin Li raise error.TestFail('The DUT was not set to suspend/shutdown ' 1192*9c5db199SXin Li 'mode by set_ap_off_power_mode().') 1193*9c5db199SXin Li self._restore_power_mode = False 1194*9c5db199SXin Li 1195*9c5db199SXin Li def get_power_state(self): 1196*9c5db199SXin Li """ 1197*9c5db199SXin Li Return the current power state of the AP (via EC 'powerinfo' command) 1198*9c5db199SXin Li 1199*9c5db199SXin Li @return the name of the power state, or None if a problem occurred 1200*9c5db199SXin Li """ 1201*9c5db199SXin Li if not hasattr(self, 'ec'): 1202*9c5db199SXin Li # Don't fail when EC not present or not fully initialized 1203*9c5db199SXin Li return None 1204*9c5db199SXin Li 1205*9c5db199SXin Li pattern = r'power state (\w+) = (\w+),' 1206*9c5db199SXin Li 1207*9c5db199SXin Li try: 1208*9c5db199SXin Li match = self.ec.send_command_get_output("powerinfo", [pattern], 1209*9c5db199SXin Li retries=3) 1210*9c5db199SXin Li except (error.TestFail, expat.ExpatError) as err: 1211*9c5db199SXin Li logging.warning("powerinfo command encountered an error: %s", err) 1212*9c5db199SXin Li return None 1213*9c5db199SXin Li if not match: 1214*9c5db199SXin Li logging.warning("powerinfo output did not match pattern: %r", 1215*9c5db199SXin Li pattern) 1216*9c5db199SXin Li return None 1217*9c5db199SXin Li (line, state_num, state_name) = match[0] 1218*9c5db199SXin Li logging.debug("power state info %r", match) 1219*9c5db199SXin Li return state_name 1220*9c5db199SXin Li 1221*9c5db199SXin Li def _check_power_state(self, expected_power_state, actual_power_state): 1222*9c5db199SXin Li """ 1223*9c5db199SXin Li Check for correct power state of the AP (via EC 'powerinfo' command) 1224*9c5db199SXin Li 1225*9c5db199SXin Li @param expected_power_state: full-string regex of power state you are 1226*9c5db199SXin Li expecting 1227*9c5db199SXin Li @param actual_power_state: the power state returned from get_power_state 1228*9c5db199SXin Li @return: the line and the match, if the output matched. 1229*9c5db199SXin Li @raise error.TestFail: if output didn't match after the delay. 1230*9c5db199SXin Li """ 1231*9c5db199SXin Li if not isinstance(expected_power_state, six.string_types): 1232*9c5db199SXin Li raise error.TestError('%s is not a string while it should be.' % 1233*9c5db199SXin Li expected_power_state) 1234*9c5db199SXin Li if not isinstance(actual_power_state, six.string_types): 1235*9c5db199SXin Li raise error.TestError('%s is not a string while it should be.' % 1236*9c5db199SXin Li actual_power_state) 1237*9c5db199SXin Li if re.match('^' + expected_power_state + '$', actual_power_state): 1238*9c5db199SXin Li return True 1239*9c5db199SXin Li return False 1240*9c5db199SXin Li 1241*9c5db199SXin Li def wait_power_state(self, power_state, retries, retry_delay=3): 1242*9c5db199SXin Li """ 1243*9c5db199SXin Li Wait for certain power state. 1244*9c5db199SXin Li 1245*9c5db199SXin Li @param power_state: full-string regex of power state you are expecting 1246*9c5db199SXin Li @param retries: retries. This is necessary if AP is powering down 1247*9c5db199SXin Li and transitioning through different states. 1248*9c5db199SXin Li @param retry_delay: delay between retries in seconds 1249*9c5db199SXin Li """ 1250*9c5db199SXin Li logging.info('Checking power state "%s" maximum %d times.', 1251*9c5db199SXin Li power_state, retries) 1252*9c5db199SXin Li 1253*9c5db199SXin Li last_power_state = '' 1254*9c5db199SXin Li while retries > 0: 1255*9c5db199SXin Li logging.debug("try count: %d", retries) 1256*9c5db199SXin Li start_time = time.time() 1257*9c5db199SXin Li try: 1258*9c5db199SXin Li retries = retries - 1 1259*9c5db199SXin Li actual_power_state = self.get_power_state() 1260*9c5db199SXin Li if last_power_state != actual_power_state: 1261*9c5db199SXin Li logging.info("power state: %s", actual_power_state) 1262*9c5db199SXin Li if actual_power_state is None: 1263*9c5db199SXin Li continue 1264*9c5db199SXin Li if self._check_power_state(power_state, actual_power_state): 1265*9c5db199SXin Li return True 1266*9c5db199SXin Li last_power_state = actual_power_state 1267*9c5db199SXin Li except (error.TestFail, expat.ExpatError): 1268*9c5db199SXin Li pass 1269*9c5db199SXin Li delay_time = retry_delay - time.time() + start_time 1270*9c5db199SXin Li if delay_time > 0: 1271*9c5db199SXin Li time.sleep(delay_time) 1272*9c5db199SXin Li return False 1273*9c5db199SXin Li 1274*9c5db199SXin Li def run_shutdown_cmd(self, wait_for_offline=True): 1275*9c5db199SXin Li """Shut down the DUT by running '/sbin/shutdown -P now'.""" 1276*9c5db199SXin Li self.faft_client.disconnect() 1277*9c5db199SXin Li # Shut down in the background after sleeping so the call gets a reply. 1278*9c5db199SXin Li try: 1279*9c5db199SXin Li self._client.run_background('sleep 0.5; /sbin/shutdown -P now') 1280*9c5db199SXin Li except error.AutoservRunError as e: 1281*9c5db199SXin Li # From the ssh man page, error code 255 indicates ssh errors. 1282*9c5db199SXin Li if e.result_obj.exit_status == 255: 1283*9c5db199SXin Li logging.warning("Ignoring error from ssh: %s", e) 1284*9c5db199SXin Li else: 1285*9c5db199SXin Li raise 1286*9c5db199SXin Li if wait_for_offline: 1287*9c5db199SXin Li self.switcher.wait_for_client_offline() 1288*9c5db199SXin Li self._client.close_main_ssh() 1289*9c5db199SXin Li 1290*9c5db199SXin Li def suspend(self): 1291*9c5db199SXin Li """Suspends the DUT.""" 1292*9c5db199SXin Li cmd = 'sleep %d; powerd_dbus_suspend' % self.EC_SUSPEND_DELAY 1293*9c5db199SXin Li block = False 1294*9c5db199SXin Li self.faft_client.system.run_shell_command(cmd, block) 1295*9c5db199SXin Li time.sleep(self.EC_SUSPEND_DELAY) 1296*9c5db199SXin Li 1297*9c5db199SXin Li def _setup_gbb_flags(self): 1298*9c5db199SXin Li """Setup the GBB flags for FAFT test.""" 1299*9c5db199SXin Li if self.check_setup_done('gbb_flags'): 1300*9c5db199SXin Li return 1301*9c5db199SXin Li 1302*9c5db199SXin Li logging.info('Setting proper GBB flags for test.') 1303*9c5db199SXin Li # Ensure that GBB flags are set to 0x140. 1304*9c5db199SXin Li flags_to_set = (vboot.GBB_FLAG_RUNNING_FAFT | 1305*9c5db199SXin Li vboot.GBB_FLAG_ENTER_TRIGGERS_TONORM) 1306*9c5db199SXin Li # And if the "no_ec_sync" argument is set, then disable EC software 1307*9c5db199SXin Li # sync. 1308*9c5db199SXin Li if self._no_ec_sync: 1309*9c5db199SXin Li logging.info( 1310*9c5db199SXin Li 'User selected to disable EC software sync') 1311*9c5db199SXin Li flags_to_set |= vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC 1312*9c5db199SXin Li 1313*9c5db199SXin Li # And if the "no_fw_rollback_check" argument is set, then disable fw 1314*9c5db199SXin Li # rollback check. 1315*9c5db199SXin Li if self._no_fw_rollback_check: 1316*9c5db199SXin Li logging.info( 1317*9c5db199SXin Li 'User selected to disable FW rollback check') 1318*9c5db199SXin Li flags_to_set |= vboot.GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK 1319*9c5db199SXin Li 1320*9c5db199SXin Li self.clear_set_gbb_flags(0xffffffff, flags_to_set) 1321*9c5db199SXin Li self.mark_setup_done('gbb_flags') 1322*9c5db199SXin Li 1323*9c5db199SXin Li def _restore_gbb_flags(self): 1324*9c5db199SXin Li """Restore GBB flags to their original state.""" 1325*9c5db199SXin Li if self._backup_gbb_flags is None: 1326*9c5db199SXin Li return 1327*9c5db199SXin Li # Setting up and restoring the GBB flags take a lot of time. For 1328*9c5db199SXin Li # speed-up purpose, don't restore it. 1329*9c5db199SXin Li logging.info('***') 1330*9c5db199SXin Li logging.info('*** Please manually restore the original GBB flags to: ' 1331*9c5db199SXin Li '0x%x ***', self._backup_gbb_flags) 1332*9c5db199SXin Li logging.info('***') 1333*9c5db199SXin Li self.unmark_setup_done('gbb_flags') 1334*9c5db199SXin Li 1335*9c5db199SXin Li def setup_tried_fwb(self, tried_fwb): 1336*9c5db199SXin Li """Setup for fw B tried state. 1337*9c5db199SXin Li 1338*9c5db199SXin Li It makes sure the system in the requested fw B tried state. If not, it 1339*9c5db199SXin Li tries to do so. 1340*9c5db199SXin Li 1341*9c5db199SXin Li @param tried_fwb: True if requested in tried_fwb=1; 1342*9c5db199SXin Li False if tried_fwb=0. 1343*9c5db199SXin Li """ 1344*9c5db199SXin Li if tried_fwb: 1345*9c5db199SXin Li if not self.checkers.crossystem_checker({'tried_fwb': '1'}): 1346*9c5db199SXin Li logging.info( 1347*9c5db199SXin Li 'Firmware is not booted with tried_fwb. Reboot into it.') 1348*9c5db199SXin Li self.faft_client.system.set_try_fw_b() 1349*9c5db199SXin Li else: 1350*9c5db199SXin Li if not self.checkers.crossystem_checker({'tried_fwb': '0'}): 1351*9c5db199SXin Li logging.info( 1352*9c5db199SXin Li 'Firmware is booted with tried_fwb. Reboot to clear.') 1353*9c5db199SXin Li 1354*9c5db199SXin Li def power_on(self): 1355*9c5db199SXin Li """Switch DUT AC power on.""" 1356*9c5db199SXin Li self._client.power_on(self.power_control) 1357*9c5db199SXin Li 1358*9c5db199SXin Li def power_off(self): 1359*9c5db199SXin Li """Switch DUT AC power off.""" 1360*9c5db199SXin Li self._client.power_off(self.power_control) 1361*9c5db199SXin Li 1362*9c5db199SXin Li def power_cycle(self): 1363*9c5db199SXin Li """Power cycle DUT AC power.""" 1364*9c5db199SXin Li self._client.power_cycle(self.power_control) 1365*9c5db199SXin Li 1366*9c5db199SXin Li def setup_rw_boot(self, section='a'): 1367*9c5db199SXin Li """Make sure firmware is in RW-boot mode. 1368*9c5db199SXin Li 1369*9c5db199SXin Li If the given firmware section is in RO-boot mode, turn off the RO-boot 1370*9c5db199SXin Li flag and reboot DUT into RW-boot mode. 1371*9c5db199SXin Li 1372*9c5db199SXin Li @param section: A firmware section, either 'a' or 'b'. 1373*9c5db199SXin Li """ 1374*9c5db199SXin Li flags = self.faft_client.bios.get_preamble_flags(section) 1375*9c5db199SXin Li if flags & vboot.PREAMBLE_USE_RO_NORMAL: 1376*9c5db199SXin Li flags = flags ^ vboot.PREAMBLE_USE_RO_NORMAL 1377*9c5db199SXin Li self.faft_client.bios.set_preamble_flags(section, flags) 1378*9c5db199SXin Li self.switcher.mode_aware_reboot() 1379*9c5db199SXin Li 1380*9c5db199SXin Li def setup_kernel(self, part): 1381*9c5db199SXin Li """Setup for kernel test. 1382*9c5db199SXin Li 1383*9c5db199SXin Li It makes sure both kernel A and B bootable and the current boot is 1384*9c5db199SXin Li the requested kernel part. 1385*9c5db199SXin Li 1386*9c5db199SXin Li @param part: A string of kernel partition number or 'a'/'b'. 1387*9c5db199SXin Li """ 1388*9c5db199SXin Li self.ensure_kernel_boot(part) 1389*9c5db199SXin Li logging.info('Checking the integrity of kernel B and rootfs B...') 1390*9c5db199SXin Li if (self.faft_client.kernel.diff_a_b() or 1391*9c5db199SXin Li not self.faft_client.rootfs.verify_rootfs('B')): 1392*9c5db199SXin Li logging.info('Copying kernel and rootfs from A to B...') 1393*9c5db199SXin Li self.copy_kernel_and_rootfs(from_part=part, 1394*9c5db199SXin Li to_part=self.OTHER_KERNEL_MAP[part]) 1395*9c5db199SXin Li self.reset_and_prioritize_kernel(part) 1396*9c5db199SXin Li 1397*9c5db199SXin Li def reset_and_prioritize_kernel(self, part): 1398*9c5db199SXin Li """Make the requested partition highest priority. 1399*9c5db199SXin Li 1400*9c5db199SXin Li This function also reset kerenl A and B to bootable. 1401*9c5db199SXin Li 1402*9c5db199SXin Li @param part: A string of partition number to be prioritized. 1403*9c5db199SXin Li """ 1404*9c5db199SXin Li root_dev = self.faft_client.system.get_root_dev() 1405*9c5db199SXin Li # Reset kernel A and B to bootable. 1406*9c5db199SXin Li self.faft_client.system.run_shell_command( 1407*9c5db199SXin Li 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['a'], root_dev)) 1408*9c5db199SXin Li self.faft_client.system.run_shell_command( 1409*9c5db199SXin Li 'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['b'], root_dev)) 1410*9c5db199SXin Li # Set kernel part highest priority. 1411*9c5db199SXin Li self.faft_client.system.run_shell_command('cgpt prioritize -i%s %s' % 1412*9c5db199SXin Li (self.KERNEL_MAP[part], root_dev)) 1413*9c5db199SXin Li 1414*9c5db199SXin Li def do_blocking_sync(self, device): 1415*9c5db199SXin Li """Run a blocking sync command.""" 1416*9c5db199SXin Li logging.debug("Blocking sync for %s", device) 1417*9c5db199SXin Li 1418*9c5db199SXin Li if 'mmcblk' in device: 1419*9c5db199SXin Li # For mmc devices, use `mmc status get` command to send an 1420*9c5db199SXin Li # empty command to wait for the disk to be available again. 1421*9c5db199SXin Li self.faft_client.system.run_shell_command('mmc status get %s' % 1422*9c5db199SXin Li device) 1423*9c5db199SXin Li elif 'nvme' in device: 1424*9c5db199SXin Li # For NVMe devices, use `nvme flush` command to commit data 1425*9c5db199SXin Li # and metadata to non-volatile media. 1426*9c5db199SXin Li 1427*9c5db199SXin Li # Get a list of NVMe namespaces, and flush them individually. 1428*9c5db199SXin Li # The output is assumed to be in the following format: 1429*9c5db199SXin Li # [ 0]:0x1 1430*9c5db199SXin Li # [ 1]:0x2 1431*9c5db199SXin Li list_ns_cmd = "nvme list-ns %s" % device 1432*9c5db199SXin Li available_ns = self.faft_client.system.run_shell_command_get_output( 1433*9c5db199SXin Li list_ns_cmd) 1434*9c5db199SXin Li 1435*9c5db199SXin Li if not available_ns: 1436*9c5db199SXin Li raise error.TestError( 1437*9c5db199SXin Li "Listing namespaces failed (empty output): %s" 1438*9c5db199SXin Li % list_ns_cmd) 1439*9c5db199SXin Li 1440*9c5db199SXin Li for ns in available_ns: 1441*9c5db199SXin Li ns = ns.split(':')[-1] 1442*9c5db199SXin Li flush_cmd = 'nvme flush %s -n %s' % (device, ns) 1443*9c5db199SXin Li flush_rc = self.faft_client.system.run_shell_command_get_status( 1444*9c5db199SXin Li flush_cmd) 1445*9c5db199SXin Li if flush_rc != 0: 1446*9c5db199SXin Li raise error.TestError( 1447*9c5db199SXin Li "Flushing namespace %s failed (rc=%s): %s" 1448*9c5db199SXin Li % (ns, flush_rc, flush_cmd)) 1449*9c5db199SXin Li else: 1450*9c5db199SXin Li # For other devices, hdparm sends TUR to check if 1451*9c5db199SXin Li # a device is ready for transfer operation. 1452*9c5db199SXin Li self.faft_client.system.run_shell_command('hdparm -f %s' % device) 1453*9c5db199SXin Li 1454*9c5db199SXin Li def blocking_sync(self, freeze_for_reset=False): 1455*9c5db199SXin Li """Sync root device and internal device, via script if possible. 1456*9c5db199SXin Li 1457*9c5db199SXin Li The actual calls end up logged by the run() call, since they're printed 1458*9c5db199SXin Li to stdout/stderr in the script. 1459*9c5db199SXin Li 1460*9c5db199SXin Li @param freeze_for_reset: if True, prepare for reset by blocking writes 1461*9c5db199SXin Li (only if enable_fs_sync_fsfreeze=True) 1462*9c5db199SXin Li """ 1463*9c5db199SXin Li 1464*9c5db199SXin Li if self._use_sync_script: 1465*9c5db199SXin Li if freeze_for_reset: 1466*9c5db199SXin Li self.faft_client.quit() 1467*9c5db199SXin Li try: 1468*9c5db199SXin Li return self._client.blocking_sync(freeze_for_reset) 1469*9c5db199SXin Li except (AttributeError, ImportError, error.AutoservRunError) as e: 1470*9c5db199SXin Li logging.warning( 1471*9c5db199SXin Li 'Falling back to old sync method due to error: %s', e) 1472*9c5db199SXin Li 1473*9c5db199SXin Li # The double calls to sync fakes a blocking call 1474*9c5db199SXin Li # since the first call returns before the flush 1475*9c5db199SXin Li # is complete, but the second will wait for the 1476*9c5db199SXin Li # first to finish. 1477*9c5db199SXin Li self.faft_client.system.run_shell_command('sync') 1478*9c5db199SXin Li self.faft_client.system.run_shell_command('sync') 1479*9c5db199SXin Li 1480*9c5db199SXin Li # sync only sends SYNCHRONIZE_CACHE but doesn't check the status. 1481*9c5db199SXin Li # This function will perform a device-specific sync command. 1482*9c5db199SXin Li root_dev = self.faft_client.system.get_root_dev() 1483*9c5db199SXin Li self.do_blocking_sync(root_dev) 1484*9c5db199SXin Li 1485*9c5db199SXin Li # Also sync the internal device if booted from removable media. 1486*9c5db199SXin Li if self.faft_client.system.is_removable_device_boot(): 1487*9c5db199SXin Li internal_dev = self.faft_client.system.get_internal_device() 1488*9c5db199SXin Li self.do_blocking_sync(internal_dev) 1489*9c5db199SXin Li 1490*9c5db199SXin Li def sync_and_ec_reboot(self, flags='', extra_sleep=0): 1491*9c5db199SXin Li """Request the client sync and do a EC triggered reboot. 1492*9c5db199SXin Li 1493*9c5db199SXin Li @param flags: Optional, a space-separated string of flags passed to EC 1494*9c5db199SXin Li reboot command, including: 1495*9c5db199SXin Li default: EC soft reboot; 1496*9c5db199SXin Li 'hard': EC hard reboot. 1497*9c5db199SXin Li 'cold': Cold reboot via servo. 1498*9c5db199SXin Li @param extra_sleep: Optional, int or float for extra wait time for EC 1499*9c5db199SXin Li reboot in seconds. 1500*9c5db199SXin Li """ 1501*9c5db199SXin Li self.blocking_sync(freeze_for_reset=True) 1502*9c5db199SXin Li if flags == 'cold': 1503*9c5db199SXin Li self.servo.get_power_state_controller().reset() 1504*9c5db199SXin Li else: 1505*9c5db199SXin Li self.ec.reboot(flags) 1506*9c5db199SXin Li time.sleep(self.faft_config.ec_boot_to_console + extra_sleep) 1507*9c5db199SXin Li self.check_lid_and_power_on() 1508*9c5db199SXin Li 1509*9c5db199SXin Li def reboot_and_reset_tpm(self): 1510*9c5db199SXin Li """Reboot into recovery mode, reset TPM, then reboot back to disk.""" 1511*9c5db199SXin Li self.switcher.reboot_to_mode(to_mode='rec') 1512*9c5db199SXin Li self.faft_client.system.run_shell_command('chromeos-tpm-recovery') 1513*9c5db199SXin Li self.switcher.mode_aware_reboot() 1514*9c5db199SXin Li 1515*9c5db199SXin Li def full_power_off_and_on(self): 1516*9c5db199SXin Li """Shutdown the device by pressing power button and power on again.""" 1517*9c5db199SXin Li boot_id = self.get_bootid() 1518*9c5db199SXin Li self.faft_client.disconnect() 1519*9c5db199SXin Li 1520*9c5db199SXin Li # Press power button to trigger ChromeOS normal shutdown process. 1521*9c5db199SXin Li # We use a customized delay since the normal-press 1.2s is not enough. 1522*9c5db199SXin Li self.servo.power_key(self.faft_config.hold_pwr_button_poweroff) 1523*9c5db199SXin Li # device can take 44-51 seconds to restart, 1524*9c5db199SXin Li # add buffer from the default timeout of 60 seconds. 1525*9c5db199SXin Li self.switcher.wait_for_client_offline(timeout=100, orig_boot_id=boot_id) 1526*9c5db199SXin Li time.sleep(self.faft_config.shutdown) 1527*9c5db199SXin Li if self.faft_config.chrome_ec: 1528*9c5db199SXin Li self.check_shutdown_power_state(self.POWER_STATE_G3, 1529*9c5db199SXin Li orig_boot_id=boot_id) 1530*9c5db199SXin Li # Short press power button to boot DUT again. 1531*9c5db199SXin Li self.servo.power_key(self.faft_config.hold_pwr_button_poweron) 1532*9c5db199SXin Li 1533*9c5db199SXin Li def check_shutdown_power_state(self, power_state, 1534*9c5db199SXin Li pwr_retries=DEFAULT_PWR_RETRIES, 1535*9c5db199SXin Li orig_boot_id=None): 1536*9c5db199SXin Li """Check whether the device shut down and entered the given power state. 1537*9c5db199SXin Li 1538*9c5db199SXin Li If orig_boot_id is specified, it will check whether the DUT responds to 1539*9c5db199SXin Li ssh requests, then use orig_boot_id to check if it rebooted. 1540*9c5db199SXin Li 1541*9c5db199SXin Li @param power_state: EC power state has to be checked. Either S5 or G3. 1542*9c5db199SXin Li @param pwr_retries: Times to check if the DUT in expected power state. 1543*9c5db199SXin Li @param orig_boot_id: Old boot_id, to check for unexpected reboots. 1544*9c5db199SXin Li @raise TestFail: If device failed to enter into requested power state. 1545*9c5db199SXin Li """ 1546*9c5db199SXin Li if not self.wait_power_state(power_state, pwr_retries): 1547*9c5db199SXin Li current_state = self.get_power_state() 1548*9c5db199SXin Li if current_state == self.POWER_STATE_S0 and self._client.wait_up(): 1549*9c5db199SXin Li # DUT is unexpectedly up, so check whether it rebooted instead. 1550*9c5db199SXin Li new_boot_id = self.get_bootid() 1551*9c5db199SXin Li logging.debug('orig_boot_id=%s, new_boot_id=%s', 1552*9c5db199SXin Li orig_boot_id, new_boot_id) 1553*9c5db199SXin Li if orig_boot_id is None or new_boot_id is None: 1554*9c5db199SXin Li # Can't say anything more specific without values to compare 1555*9c5db199SXin Li raise error.TestFail( 1556*9c5db199SXin Li "Expected state %s, but the system is unexpectedly" 1557*9c5db199SXin Li " still up. Current state: %s" 1558*9c5db199SXin Li % (power_state, current_state)) 1559*9c5db199SXin Li if new_boot_id == orig_boot_id: 1560*9c5db199SXin Li raise error.TestFail( 1561*9c5db199SXin Li "Expected state %s, but the system didn't shut" 1562*9c5db199SXin Li " down. Current state: %s" 1563*9c5db199SXin Li % (power_state, current_state)) 1564*9c5db199SXin Li else: 1565*9c5db199SXin Li raise error.TestFail( 1566*9c5db199SXin Li "Expected state %s, but the system rebooted instead" 1567*9c5db199SXin Li " of shutting down. Current state: %s" 1568*9c5db199SXin Li % (power_state, current_state)) 1569*9c5db199SXin Li 1570*9c5db199SXin Li if current_state is None: 1571*9c5db199SXin Li current_state = '(unknown)' 1572*9c5db199SXin Li 1573*9c5db199SXin Li if current_state == power_state: 1574*9c5db199SXin Li raise error.TestFail( 1575*9c5db199SXin Li "Expected state %s, but the system didn't reach it" 1576*9c5db199SXin Li " until after the limit of %s tries." 1577*9c5db199SXin Li % (power_state, pwr_retries)) 1578*9c5db199SXin Li 1579*9c5db199SXin Li raise error.TestFail('System not shutdown properly and EC fails' 1580*9c5db199SXin Li ' to enter into %s state. Current state: %s' 1581*9c5db199SXin Li % (power_state, current_state)) 1582*9c5db199SXin Li logging.info('System entered into %s state..', power_state) 1583*9c5db199SXin Li 1584*9c5db199SXin Li def check_lid_and_power_on(self): 1585*9c5db199SXin Li """ 1586*9c5db199SXin Li On devices with EC software sync, system powers on after EC reboots if 1587*9c5db199SXin Li lid is open. Otherwise, the EC shuts down CPU after about 3 seconds. 1588*9c5db199SXin Li This method checks lid switch state and presses power button if 1589*9c5db199SXin Li necessary. 1590*9c5db199SXin Li """ 1591*9c5db199SXin Li if self.servo.get("lid_open") == "no": 1592*9c5db199SXin Li time.sleep(self.faft_config.software_sync) 1593*9c5db199SXin Li self.servo.power_short_press() 1594*9c5db199SXin Li 1595*9c5db199SXin Li def stop_powerd(self): 1596*9c5db199SXin Li """Stop the powerd daemon on the AP. 1597*9c5db199SXin Li 1598*9c5db199SXin Li This will cause the AP to ignore power button presses sent by the EC. 1599*9c5db199SXin Li """ 1600*9c5db199SXin Li powerd_running = self.faft_client.system.run_shell_command_check_output( 1601*9c5db199SXin Li 'status powerd', 'start/running') 1602*9c5db199SXin Li if powerd_running: 1603*9c5db199SXin Li logging.debug('Stopping powerd') 1604*9c5db199SXin Li self.faft_client.system.run_shell_command("stop powerd") 1605*9c5db199SXin Li 1606*9c5db199SXin Li def _modify_usb_kernel(self, usb_dev, from_magic, to_magic): 1607*9c5db199SXin Li """Modify the kernel header magic in USB stick. 1608*9c5db199SXin Li 1609*9c5db199SXin Li The kernel header magic is the first 8-byte of kernel partition. 1610*9c5db199SXin Li We modify it to make it fail on kernel verification check. 1611*9c5db199SXin Li 1612*9c5db199SXin Li @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'. 1613*9c5db199SXin Li @param from_magic: A string of magic which we change it from. 1614*9c5db199SXin Li @param to_magic: A string of magic which we change it to. 1615*9c5db199SXin Li @raise TestError: if failed to change magic. 1616*9c5db199SXin Li """ 1617*9c5db199SXin Li assert len(from_magic) == 8 1618*9c5db199SXin Li assert len(to_magic) == 8 1619*9c5db199SXin Li # USB image only contains one kernel. 1620*9c5db199SXin Li kernel_part = self._join_part(usb_dev, self.KERNEL_MAP['a']) 1621*9c5db199SXin Li read_cmd = "sudo dd if=%s bs=8 count=1 2>/dev/null" % kernel_part 1622*9c5db199SXin Li current_magic = self.servo.system_output(read_cmd) 1623*9c5db199SXin Li if current_magic == to_magic: 1624*9c5db199SXin Li logging.info("The kernel magic is already %s.", current_magic) 1625*9c5db199SXin Li return 1626*9c5db199SXin Li if current_magic != from_magic: 1627*9c5db199SXin Li raise error.TestError("Invalid kernel image on USB: wrong magic.") 1628*9c5db199SXin Li 1629*9c5db199SXin Li logging.info('Modify the kernel magic in USB, from %s to %s.', 1630*9c5db199SXin Li from_magic, to_magic) 1631*9c5db199SXin Li write_cmd = ("echo -n '%s' | sudo dd of=%s oflag=sync conv=notrunc " 1632*9c5db199SXin Li " 2>/dev/null" % (to_magic, kernel_part)) 1633*9c5db199SXin Li self.servo.system(write_cmd) 1634*9c5db199SXin Li 1635*9c5db199SXin Li if self.servo.system_output(read_cmd) != to_magic: 1636*9c5db199SXin Li raise error.TestError("Failed to write new magic.") 1637*9c5db199SXin Li 1638*9c5db199SXin Li def corrupt_usb_kernel(self, usb_dev): 1639*9c5db199SXin Li """Corrupt USB kernel by modifying its magic from CHROMEOS to CORRUPTD. 1640*9c5db199SXin Li 1641*9c5db199SXin Li @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'. 1642*9c5db199SXin Li """ 1643*9c5db199SXin Li self._modify_usb_kernel(usb_dev, self.CHROMEOS_MAGIC, 1644*9c5db199SXin Li self.CORRUPTED_MAGIC) 1645*9c5db199SXin Li 1646*9c5db199SXin Li def restore_usb_kernel(self, usb_dev): 1647*9c5db199SXin Li """Restore USB kernel by modifying its magic from CORRUPTD to CHROMEOS. 1648*9c5db199SXin Li 1649*9c5db199SXin Li @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'. 1650*9c5db199SXin Li """ 1651*9c5db199SXin Li self._modify_usb_kernel(usb_dev, self.CORRUPTED_MAGIC, 1652*9c5db199SXin Li self.CHROMEOS_MAGIC) 1653*9c5db199SXin Li 1654*9c5db199SXin Li def _call_action(self, action_tuple, check_status=False): 1655*9c5db199SXin Li """Call the action function with/without arguments. 1656*9c5db199SXin Li 1657*9c5db199SXin Li @param action_tuple: A function, or a tuple (function, args, error_msg), 1658*9c5db199SXin Li in which, args and error_msg are optional. args is 1659*9c5db199SXin Li either a value or a tuple if multiple arguments. 1660*9c5db199SXin Li This can also be a list containing multiple 1661*9c5db199SXin Li function or tuple. In this case, these actions are 1662*9c5db199SXin Li called in sequence. 1663*9c5db199SXin Li @param check_status: Check the return value of action function. If not 1664*9c5db199SXin Li succeed, raises a TestFail exception. 1665*9c5db199SXin Li @return: The result value of the action function. 1666*9c5db199SXin Li @raise TestError: An error when the action function is not callable. 1667*9c5db199SXin Li @raise TestFail: When check_status=True, action function not succeed. 1668*9c5db199SXin Li """ 1669*9c5db199SXin Li if isinstance(action_tuple, list): 1670*9c5db199SXin Li return all([self._call_action(action, check_status=check_status) 1671*9c5db199SXin Li for action in action_tuple]) 1672*9c5db199SXin Li 1673*9c5db199SXin Li action = action_tuple 1674*9c5db199SXin Li args = () 1675*9c5db199SXin Li error_msg = 'Not succeed' 1676*9c5db199SXin Li if isinstance(action_tuple, tuple): 1677*9c5db199SXin Li action = action_tuple[0] 1678*9c5db199SXin Li if len(action_tuple) >= 2: 1679*9c5db199SXin Li args = action_tuple[1] 1680*9c5db199SXin Li if not isinstance(args, tuple): 1681*9c5db199SXin Li args = (args,) 1682*9c5db199SXin Li if len(action_tuple) >= 3: 1683*9c5db199SXin Li error_msg = action_tuple[2] 1684*9c5db199SXin Li 1685*9c5db199SXin Li if action is None: 1686*9c5db199SXin Li return 1687*9c5db199SXin Li 1688*9c5db199SXin Li if not callable(action): 1689*9c5db199SXin Li raise error.TestError('action is not callable!') 1690*9c5db199SXin Li 1691*9c5db199SXin Li info_msg = 'calling %s' % action.__name__ 1692*9c5db199SXin Li if args: 1693*9c5db199SXin Li info_msg += ' with args %s' % str(args) 1694*9c5db199SXin Li logging.info(info_msg) 1695*9c5db199SXin Li ret = action(*args) 1696*9c5db199SXin Li 1697*9c5db199SXin Li if check_status and not ret: 1698*9c5db199SXin Li raise error.TestFail('%s: %s returning %s' % 1699*9c5db199SXin Li (error_msg, info_msg, str(ret))) 1700*9c5db199SXin Li return ret 1701*9c5db199SXin Li 1702*9c5db199SXin Li def run_shutdown_process(self, shutdown_action, pre_power_action=None, 1703*9c5db199SXin Li run_power_action=True, post_power_action=None, 1704*9c5db199SXin Li shutdown_timeout=None): 1705*9c5db199SXin Li """Run shutdown_action(), which makes DUT shutdown, and power it on. 1706*9c5db199SXin Li 1707*9c5db199SXin Li @param shutdown_action: function which makes DUT shutdown, like 1708*9c5db199SXin Li pressing power key. 1709*9c5db199SXin Li @param pre_power_action: function which is called before next power on. 1710*9c5db199SXin Li @param run_power_action: power_key press by default, set to None to skip. 1711*9c5db199SXin Li @param post_power_action: function which is called after next power on. 1712*9c5db199SXin Li @param shutdown_timeout: a timeout to confirm DUT shutdown. 1713*9c5db199SXin Li @raise TestFail: if the shutdown_action() failed to turn DUT off. 1714*9c5db199SXin Li """ 1715*9c5db199SXin Li self._call_action(shutdown_action) 1716*9c5db199SXin Li logging.info('Wait to ensure DUT shut down...') 1717*9c5db199SXin Li try: 1718*9c5db199SXin Li if shutdown_timeout is None: 1719*9c5db199SXin Li shutdown_timeout = self.faft_config.shutdown_timeout 1720*9c5db199SXin Li self.switcher.wait_for_client(timeout=shutdown_timeout) 1721*9c5db199SXin Li raise error.TestFail( 1722*9c5db199SXin Li 'Should shut the device down after calling %s.' % 1723*9c5db199SXin Li shutdown_action.__name__) 1724*9c5db199SXin Li except ConnectionError: 1725*9c5db199SXin Li if self.faft_config.chrome_ec: 1726*9c5db199SXin Li self.check_shutdown_power_state(self.POWER_STATE_G3) 1727*9c5db199SXin Li logging.info( 1728*9c5db199SXin Li 'DUT is surely shutdown. We are going to power it on again...') 1729*9c5db199SXin Li 1730*9c5db199SXin Li if pre_power_action: 1731*9c5db199SXin Li self._call_action(pre_power_action) 1732*9c5db199SXin Li if run_power_action: 1733*9c5db199SXin Li self.servo.power_key(self.faft_config.hold_pwr_button_poweron) 1734*9c5db199SXin Li if post_power_action: 1735*9c5db199SXin Li self._call_action(post_power_action) 1736*9c5db199SXin Li 1737*9c5db199SXin Li def get_bootid(self, retry=3): 1738*9c5db199SXin Li """ 1739*9c5db199SXin Li Return the bootid. 1740*9c5db199SXin Li """ 1741*9c5db199SXin Li boot_id = None 1742*9c5db199SXin Li while retry: 1743*9c5db199SXin Li try: 1744*9c5db199SXin Li boot_id = self._client.get_boot_id() 1745*9c5db199SXin Li break 1746*9c5db199SXin Li except error.AutoservRunError: 1747*9c5db199SXin Li retry -= 1 1748*9c5db199SXin Li if retry: 1749*9c5db199SXin Li logging.debug('Retry to get boot_id...') 1750*9c5db199SXin Li else: 1751*9c5db199SXin Li logging.warning('Failed to get boot_id.') 1752*9c5db199SXin Li logging.debug('boot_id: %s', boot_id) 1753*9c5db199SXin Li return boot_id 1754*9c5db199SXin Li 1755*9c5db199SXin Li def check_state(self, func): 1756*9c5db199SXin Li """ 1757*9c5db199SXin Li Wrapper around _call_action with check_status set to True. This is a 1758*9c5db199SXin Li helper function to be used by tests and is currently implemented by 1759*9c5db199SXin Li calling _call_action with check_status=True. 1760*9c5db199SXin Li 1761*9c5db199SXin Li TODO: This function's arguments need to be made more stringent. And 1762*9c5db199SXin Li its functionality should be moved over to check functions directly in 1763*9c5db199SXin Li the future. 1764*9c5db199SXin Li 1765*9c5db199SXin Li @param func: A function, or a tuple (function, args, error_msg), 1766*9c5db199SXin Li in which, args and error_msg are optional. args is 1767*9c5db199SXin Li either a value or a tuple if multiple arguments. 1768*9c5db199SXin Li This can also be a list containing multiple 1769*9c5db199SXin Li function or tuple. In this case, these actions are 1770*9c5db199SXin Li called in sequence. 1771*9c5db199SXin Li @return: The result value of the action function. 1772*9c5db199SXin Li @raise TestFail: If the function does notsucceed. 1773*9c5db199SXin Li """ 1774*9c5db199SXin Li self._call_action(func, check_status=True) 1775*9c5db199SXin Li 1776*9c5db199SXin Li def get_current_firmware_identity(self): 1777*9c5db199SXin Li """Get current firmware sha and fwids of body and vblock. 1778*9c5db199SXin Li 1779*9c5db199SXin Li @return: Current firmware checksums and fwids, as a dict 1780*9c5db199SXin Li """ 1781*9c5db199SXin Li 1782*9c5db199SXin Li current_checksums = { 1783*9c5db199SXin Li 'VBOOTA': self.faft_client.bios.get_sig_sha('a'), 1784*9c5db199SXin Li 'FVMAINA': self.faft_client.bios.get_body_sha('a'), 1785*9c5db199SXin Li 'VBOOTB': self.faft_client.bios.get_sig_sha('b'), 1786*9c5db199SXin Li 'FVMAINB': self.faft_client.bios.get_body_sha('b'), 1787*9c5db199SXin Li } 1788*9c5db199SXin Li if not all(current_checksums.values()): 1789*9c5db199SXin Li raise error.TestError( 1790*9c5db199SXin Li 'Failed to get firmware sha: %s', current_checksums) 1791*9c5db199SXin Li 1792*9c5db199SXin Li current_fwids = { 1793*9c5db199SXin Li 'RO_FRID': self.faft_client.bios.get_section_fwid('ro'), 1794*9c5db199SXin Li 'RW_FWID_A': self.faft_client.bios.get_section_fwid('a'), 1795*9c5db199SXin Li 'RW_FWID_B': self.faft_client.bios.get_section_fwid('b'), 1796*9c5db199SXin Li } 1797*9c5db199SXin Li if not all(current_fwids.values()): 1798*9c5db199SXin Li raise error.TestError( 1799*9c5db199SXin Li 'Failed to get firmware fwid(s): %s', current_fwids) 1800*9c5db199SXin Li 1801*9c5db199SXin Li identifying_info = dict(current_fwids) 1802*9c5db199SXin Li identifying_info.update(current_checksums) 1803*9c5db199SXin Li return identifying_info 1804*9c5db199SXin Li 1805*9c5db199SXin Li def is_firmware_changed(self): 1806*9c5db199SXin Li """Check if the current firmware changed, by comparing its SHA and fwid. 1807*9c5db199SXin Li 1808*9c5db199SXin Li @return: True if it is changed, otherwise False. 1809*9c5db199SXin Li """ 1810*9c5db199SXin Li # Device may not be rebooted after test. 1811*9c5db199SXin Li self.faft_client.bios.reload() 1812*9c5db199SXin Li 1813*9c5db199SXin Li current_info = self.get_current_firmware_identity() 1814*9c5db199SXin Li prev_info = self._backup_firmware_identity 1815*9c5db199SXin Li 1816*9c5db199SXin Li if current_info == prev_info: 1817*9c5db199SXin Li return False 1818*9c5db199SXin Li else: 1819*9c5db199SXin Li changed = set() 1820*9c5db199SXin Li for section in set(current_info.keys()) | set(prev_info.keys()): 1821*9c5db199SXin Li if current_info.get(section) != prev_info.get(section): 1822*9c5db199SXin Li changed.add(section) 1823*9c5db199SXin Li 1824*9c5db199SXin Li logging.info('Firmware changed: %s', ', '.join(sorted(changed))) 1825*9c5db199SXin Li return True 1826*9c5db199SXin Li 1827*9c5db199SXin Li def backup_firmware(self, suffix='.original'): 1828*9c5db199SXin Li """Backup firmware to file, and then send it to host. 1829*9c5db199SXin Li 1830*9c5db199SXin Li @param suffix: a string appended to backup file name 1831*9c5db199SXin Li """ 1832*9c5db199SXin Li remote_temp_dir = self.faft_client.system.create_temp_dir() 1833*9c5db199SXin Li remote_bios_path = os.path.join(remote_temp_dir, 'bios') 1834*9c5db199SXin Li self.faft_client.bios.dump_whole(remote_bios_path) 1835*9c5db199SXin Li self._client.get_file(remote_bios_path, 1836*9c5db199SXin Li os.path.join(self.resultsdir, 'bios' + suffix)) 1837*9c5db199SXin Li 1838*9c5db199SXin Li if self.faft_config.chrome_ec: 1839*9c5db199SXin Li remote_ec_path = os.path.join(remote_temp_dir, 'ec') 1840*9c5db199SXin Li self.faft_client.ec.dump_whole(remote_ec_path) 1841*9c5db199SXin Li self._client.get_file(remote_ec_path, 1842*9c5db199SXin Li os.path.join(self.resultsdir, 'ec' + suffix)) 1843*9c5db199SXin Li 1844*9c5db199SXin Li self._client.run('rm -rf %s' % remote_temp_dir) 1845*9c5db199SXin Li logging.info('Backup firmware stored in %s with suffix %s', 1846*9c5db199SXin Li self.resultsdir, suffix) 1847*9c5db199SXin Li 1848*9c5db199SXin Li self._backup_firmware_identity = self.get_current_firmware_identity() 1849*9c5db199SXin Li 1850*9c5db199SXin Li def is_firmware_saved(self): 1851*9c5db199SXin Li """Check if a firmware saved (called backup_firmware before). 1852*9c5db199SXin Li 1853*9c5db199SXin Li @return: True if the firmware is backed up; otherwise False. 1854*9c5db199SXin Li """ 1855*9c5db199SXin Li return bool(self._backup_firmware_identity) 1856*9c5db199SXin Li 1857*9c5db199SXin Li def restore_firmware(self, suffix='.original', restore_ec=True, 1858*9c5db199SXin Li reboot_ec=False): 1859*9c5db199SXin Li """Restore firmware from host in resultsdir. 1860*9c5db199SXin Li 1861*9c5db199SXin Li @param suffix: a string appended to backup file name 1862*9c5db199SXin Li @param restore_ec: True to restore the ec firmware; False not to do. 1863*9c5db199SXin Li @param reboot_ec: True to reboot EC after restore (if it was restored) 1864*9c5db199SXin Li @return: True if firmware needed to be restored 1865*9c5db199SXin Li """ 1866*9c5db199SXin Li if not self.is_firmware_changed(): 1867*9c5db199SXin Li return False 1868*9c5db199SXin Li 1869*9c5db199SXin Li # Backup current corrupted firmware. 1870*9c5db199SXin Li self.backup_firmware(suffix='.corrupt') 1871*9c5db199SXin Li 1872*9c5db199SXin Li # Restore firmware. 1873*9c5db199SXin Li remote_temp_dir = self.faft_client.system.create_temp_dir() 1874*9c5db199SXin Li 1875*9c5db199SXin Li bios_local = os.path.join(self.resultsdir, 'bios%s' % suffix) 1876*9c5db199SXin Li bios_remote = os.path.join(remote_temp_dir, 'bios%s' % suffix) 1877*9c5db199SXin Li self._client.send_file(bios_local, bios_remote) 1878*9c5db199SXin Li self.faft_client.bios.write_whole(bios_remote) 1879*9c5db199SXin Li 1880*9c5db199SXin Li if self.faft_config.chrome_ec and restore_ec: 1881*9c5db199SXin Li ec_local = os.path.join(self.resultsdir, 'ec%s' % suffix) 1882*9c5db199SXin Li ec_remote = os.path.join(remote_temp_dir, 'ec%s' % suffix) 1883*9c5db199SXin Li self._client.send_file(ec_local, ec_remote) 1884*9c5db199SXin Li ec_cmd = self.faft_client.ec.get_write_cmd(ec_remote) 1885*9c5db199SXin Li try: 1886*9c5db199SXin Li self._client.run(ec_cmd, timeout=300) 1887*9c5db199SXin Li except error.AutoservSSHTimeout: 1888*9c5db199SXin Li logging.warning("DUT connection died during EC restore") 1889*9c5db199SXin Li self.faft_client.disconnect() 1890*9c5db199SXin Li 1891*9c5db199SXin Li except error.GenericHostRunError: 1892*9c5db199SXin Li logging.warning("DUT command failed during EC restore") 1893*9c5db199SXin Li logging.debug("Full exception:", exc_info=True) 1894*9c5db199SXin Li if reboot_ec: 1895*9c5db199SXin Li self.switcher.mode_aware_reboot( 1896*9c5db199SXin Li 'custom', lambda: self.sync_and_ec_reboot('hard')) 1897*9c5db199SXin Li else: 1898*9c5db199SXin Li self.switcher.mode_aware_reboot() 1899*9c5db199SXin Li else: 1900*9c5db199SXin Li self.switcher.mode_aware_reboot() 1901*9c5db199SXin Li logging.info('Successfully restored firmware.') 1902*9c5db199SXin Li return True 1903*9c5db199SXin Li 1904*9c5db199SXin Li def setup_firmwareupdate_shellball(self, shellball=None): 1905*9c5db199SXin Li """Setup a shellball to use in firmware update test. 1906*9c5db199SXin Li 1907*9c5db199SXin Li Check if there is a given shellball, and it is a shell script. Then, 1908*9c5db199SXin Li send it to the remote host. Otherwise, use the 1909*9c5db199SXin Li /usr/sbin/chromeos-firmwareupdate in the image and replace its inside 1910*9c5db199SXin Li BIOS and EC images with the active firmware images. 1911*9c5db199SXin Li 1912*9c5db199SXin Li @param shellball: path of a shellball or default to None. 1913*9c5db199SXin Li """ 1914*9c5db199SXin Li if shellball: 1915*9c5db199SXin Li # Determine the firmware file is a shellball or a raw binary. 1916*9c5db199SXin Li is_shellball = (utils.system_output("file %s" % shellball).find( 1917*9c5db199SXin Li "shell script") != -1) 1918*9c5db199SXin Li if is_shellball: 1919*9c5db199SXin Li logging.info('Device will update firmware with shellball %s', 1920*9c5db199SXin Li shellball) 1921*9c5db199SXin Li temp_path = self.faft_client.updater.get_temp_path() 1922*9c5db199SXin Li working_shellball = os.path.join(temp_path, 1923*9c5db199SXin Li 'chromeos-firmwareupdate') 1924*9c5db199SXin Li self._client.send_file(shellball, working_shellball) 1925*9c5db199SXin Li self.faft_client.updater.extract_shellball() 1926*9c5db199SXin Li else: 1927*9c5db199SXin Li raise error.TestFail( 1928*9c5db199SXin Li 'The given shellball is not a shell script.') 1929*9c5db199SXin Li else: 1930*9c5db199SXin Li logging.info('No shellball given, use the original shellball and ' 1931*9c5db199SXin Li 'replace its BIOS and EC images.') 1932*9c5db199SXin Li work_path = self.faft_client.updater.get_work_path() 1933*9c5db199SXin Li bios_in_work_path = os.path.join( 1934*9c5db199SXin Li work_path, self.faft_client.updater.get_bios_relative_path()) 1935*9c5db199SXin Li ec_in_work_path = os.path.join( 1936*9c5db199SXin Li work_path, self.faft_client.updater.get_ec_relative_path()) 1937*9c5db199SXin Li logging.info('Writing current BIOS to: %s', bios_in_work_path) 1938*9c5db199SXin Li self.faft_client.bios.dump_whole(bios_in_work_path) 1939*9c5db199SXin Li if self.faft_config.chrome_ec: 1940*9c5db199SXin Li logging.info('Writing current EC to: %s', ec_in_work_path) 1941*9c5db199SXin Li self.faft_client.ec.dump_firmware(ec_in_work_path) 1942*9c5db199SXin Li self.faft_client.updater.repack_shellball() 1943*9c5db199SXin Li 1944*9c5db199SXin Li def is_kernel_changed(self, kernel_type='KERN'): 1945*9c5db199SXin Li """Check if the current kernel is changed, by comparing its SHA1 hash. 1946*9c5db199SXin Li 1947*9c5db199SXin Li @param kernel_type: The type name of kernel ('KERN' or 'MINIOS'). 1948*9c5db199SXin Li @return: True if it is changed; otherwise, False. 1949*9c5db199SXin Li """ 1950*9c5db199SXin Li changed = False 1951*9c5db199SXin Li for p in ('A', 'B'): 1952*9c5db199SXin Li backup_sha = self._backup_kernel_sha[kernel_type].get(p, None) 1953*9c5db199SXin Li current_sha = self.kernel_servicer[kernel_type].get_sha(p) 1954*9c5db199SXin Li if backup_sha != current_sha: 1955*9c5db199SXin Li changed = True 1956*9c5db199SXin Li logging.info('Kernel %s-%s is changed', kernel_type, p) 1957*9c5db199SXin Li return changed 1958*9c5db199SXin Li 1959*9c5db199SXin Li def backup_kernel(self, suffix='.original', kernel_type='KERN'): 1960*9c5db199SXin Li """Backup kernel to files, and the send them to host. 1961*9c5db199SXin Li 1962*9c5db199SXin Li @param kernel_type: The type name of kernel ('KERN' or 'MINIOS'). 1963*9c5db199SXin Li @param suffix: a string appended to backup file name. 1964*9c5db199SXin Li """ 1965*9c5db199SXin Li kernel_name = self.KERNEL_TYPE_NAME_MAP[kernel_type] 1966*9c5db199SXin Li servicer = self.kernel_servicer[kernel_type] 1967*9c5db199SXin Li remote_temp_dir = self.faft_client.system.create_temp_dir() 1968*9c5db199SXin Li for p in ('A', 'B'): 1969*9c5db199SXin Li remote_path = os.path.join(remote_temp_dir, 1970*9c5db199SXin Li '%s_%s' % (kernel_name, p)) 1971*9c5db199SXin Li servicer.dump(p, remote_path) 1972*9c5db199SXin Li self._client.get_file( 1973*9c5db199SXin Li remote_path, 1974*9c5db199SXin Li os.path.join(self.resultsdir, 1975*9c5db199SXin Li '%s_%s%s' % (kernel_name, p, suffix))) 1976*9c5db199SXin Li self._backup_kernel_sha[kernel_type][p] = servicer.get_sha(p) 1977*9c5db199SXin Li logging.info('Backup %s stored in %s with suffix %s', kernel_name, 1978*9c5db199SXin Li self.resultsdir, suffix) 1979*9c5db199SXin Li 1980*9c5db199SXin Li def is_kernel_saved(self, kernel_type='KERN'): 1981*9c5db199SXin Li """Check if kernel images are saved (backup_kernel called before). 1982*9c5db199SXin Li 1983*9c5db199SXin Li @param kernel_type: The type name of kernel ('KERN' or 'MINIOS'). 1984*9c5db199SXin Li @return: True if the kernel is saved; otherwise, False. 1985*9c5db199SXin Li """ 1986*9c5db199SXin Li return len(self._backup_kernel_sha[kernel_type]) != 0 1987*9c5db199SXin Li 1988*9c5db199SXin Li def restore_kernel(self, suffix='.original', kernel_type='KERN'): 1989*9c5db199SXin Li """Restore kernel from host in resultsdir. 1990*9c5db199SXin Li 1991*9c5db199SXin Li @param kernel_type: The type name of kernel ('KERN' or 'MINIOS'). 1992*9c5db199SXin Li @param suffix: a string appended to backup file name. 1993*9c5db199SXin Li @return: True if kernel needed to be restored 1994*9c5db199SXin Li """ 1995*9c5db199SXin Li if not self.is_kernel_changed(kernel_type): 1996*9c5db199SXin Li return False 1997*9c5db199SXin Li 1998*9c5db199SXin Li # Backup current corrupted kernel. 1999*9c5db199SXin Li self.backup_kernel(suffix='.corrupt', kernel_type=kernel_type) 2000*9c5db199SXin Li 2001*9c5db199SXin Li # Restore kernel. 2002*9c5db199SXin Li kernel_name = self.KERNEL_TYPE_NAME_MAP[kernel_type] 2003*9c5db199SXin Li servicer = self.kernel_servicer[kernel_type] 2004*9c5db199SXin Li remote_temp_dir = self.faft_client.system.create_temp_dir() 2005*9c5db199SXin Li for p in ('A', 'B'): 2006*9c5db199SXin Li remote_path = os.path.join(remote_temp_dir, 2007*9c5db199SXin Li '%s_%s' % (kernel_name, p)) 2008*9c5db199SXin Li servicer.dump(p, remote_path) 2009*9c5db199SXin Li self._client.send_file( 2010*9c5db199SXin Li os.path.join(self.resultsdir, 2011*9c5db199SXin Li '%s_%s%s' % (kernel_name, p, suffix)), 2012*9c5db199SXin Li remote_path) 2013*9c5db199SXin Li servicer.write(p, remote_path) 2014*9c5db199SXin Li 2015*9c5db199SXin Li self.switcher.mode_aware_reboot() 2016*9c5db199SXin Li logging.info('Successfully restored %s.', kernel_type) 2017*9c5db199SXin Li return True 2018*9c5db199SXin Li 2019*9c5db199SXin Li def backup_cgpt_attributes(self): 2020*9c5db199SXin Li """Backup CGPT partition table attributes.""" 2021*9c5db199SXin Li self._backup_cgpt_attr = self.faft_client.cgpt.get_attributes() 2022*9c5db199SXin Li 2023*9c5db199SXin Li def restore_cgpt_attributes(self): 2024*9c5db199SXin Li """Restore CGPT partition table attributes.""" 2025*9c5db199SXin Li current_table = self.faft_client.cgpt.get_attributes() 2026*9c5db199SXin Li if current_table == self._backup_cgpt_attr: 2027*9c5db199SXin Li return 2028*9c5db199SXin Li logging.info('CGPT table is changed. Original: %r. Current: %r.', 2029*9c5db199SXin Li self._backup_cgpt_attr, 2030*9c5db199SXin Li current_table) 2031*9c5db199SXin Li self.faft_client.cgpt.set_attributes( 2032*9c5db199SXin Li self._backup_cgpt_attr['A'], self._backup_cgpt_attr['B']) 2033*9c5db199SXin Li 2034*9c5db199SXin Li self.switcher.mode_aware_reboot() 2035*9c5db199SXin Li logging.info('Successfully restored CGPT table.') 2036*9c5db199SXin Li 2037*9c5db199SXin Li def try_fwb(self, count=0): 2038*9c5db199SXin Li """set to try booting FWB count # times 2039*9c5db199SXin Li 2040*9c5db199SXin Li Wrapper to set fwb_tries for vboot1 and fw_try_count,fw_try_next for 2041*9c5db199SXin Li vboot2 2042*9c5db199SXin Li 2043*9c5db199SXin Li @param count: an integer specifying value to program into 2044*9c5db199SXin Li fwb_tries(vb1)/fw_try_next(vb2) 2045*9c5db199SXin Li """ 2046*9c5db199SXin Li if self.fw_vboot2: 2047*9c5db199SXin Li self.faft_client.system.set_fw_try_next('B', count) 2048*9c5db199SXin Li else: 2049*9c5db199SXin Li # vboot1: we need to boot into fwb at least once 2050*9c5db199SXin Li if not count: 2051*9c5db199SXin Li count = count + 1 2052*9c5db199SXin Li self.faft_client.system.set_try_fw_b(count) 2053*9c5db199SXin Li 2054*9c5db199SXin Li def identify_shellball(self, include_ec=None): 2055*9c5db199SXin Li """Get the FWIDs of all targets and sections in the shellball 2056*9c5db199SXin Li 2057*9c5db199SXin Li @param include_ec: if True, get EC fwids. 2058*9c5db199SXin Li If None (default), assume True if board has an EC 2059*9c5db199SXin Li @return: the dict of versions in the shellball 2060*9c5db199SXin Li """ 2061*9c5db199SXin Li fwids = dict() 2062*9c5db199SXin Li fwids['bios'] = self.faft_client.updater.get_image_fwids('bios') 2063*9c5db199SXin Li 2064*9c5db199SXin Li if include_ec is None: 2065*9c5db199SXin Li if self.faft_config.platform.lower() == 'samus': 2066*9c5db199SXin Li include_ec = False # no ec.bin in shellball 2067*9c5db199SXin Li else: 2068*9c5db199SXin Li include_ec = self.faft_config.chrome_ec 2069*9c5db199SXin Li 2070*9c5db199SXin Li if include_ec: 2071*9c5db199SXin Li fwids['ec'] = self.faft_client.updater.get_image_fwids('ec') 2072*9c5db199SXin Li return fwids 2073*9c5db199SXin Li 2074*9c5db199SXin Li def modify_shellball(self, append, modify_ro=True, modify_ec=False): 2075*9c5db199SXin Li """Modify the FWIDs of targets and sections in the shellball 2076*9c5db199SXin Li 2077*9c5db199SXin Li @return: the full path of the shellball 2078*9c5db199SXin Li """ 2079*9c5db199SXin Li 2080*9c5db199SXin Li if modify_ro: 2081*9c5db199SXin Li self.faft_client.updater.modify_image_fwids( 2082*9c5db199SXin Li 'bios', ['ro', 'a', 'b']) 2083*9c5db199SXin Li else: 2084*9c5db199SXin Li self.faft_client.updater.modify_image_fwids( 2085*9c5db199SXin Li 'bios', ['a', 'b']) 2086*9c5db199SXin Li 2087*9c5db199SXin Li if modify_ec: 2088*9c5db199SXin Li if modify_ro: 2089*9c5db199SXin Li self.faft_client.updater.modify_image_fwids( 2090*9c5db199SXin Li 'ec', ['ro', 'rw']) 2091*9c5db199SXin Li else: 2092*9c5db199SXin Li self.faft_client.updater.modify_image_fwids( 2093*9c5db199SXin Li 'ec', ['rw']) 2094*9c5db199SXin Li 2095*9c5db199SXin Li modded_shellball = self.faft_client.updater.repack_shellball(append) 2096*9c5db199SXin Li 2097*9c5db199SXin Li return modded_shellball 2098*9c5db199SXin Li 2099*9c5db199SXin Li @staticmethod 2100*9c5db199SXin Li def check_fwids_written(before_fwids, image_fwids, after_fwids, 2101*9c5db199SXin Li expected_written): 2102*9c5db199SXin Li """Check the dicts of fwids for correctness after an update is applied. 2103*9c5db199SXin Li 2104*9c5db199SXin Li The targets checked come from the keys of expected_written. 2105*9c5db199SXin Li The sections checked come from the inner dicts of the fwids parameters. 2106*9c5db199SXin Li 2107*9c5db199SXin Li The fwids should be keyed by target (flash type), then by section: 2108*9c5db199SXin Li {'bios': {'ro': '<fwid>', 'a': '<fwid>', 'b': '<fwid>'}, 2109*9c5db199SXin Li 'ec': {'ro': '<fwid>', 'rw': '<fwid>'} 2110*9c5db199SXin Li 2111*9c5db199SXin Li For expected_written, the dict should be keyed by flash type only: 2112*9c5db199SXin Li {'bios': ['ro'], 'ec': ['ro', 'rw']} 2113*9c5db199SXin Li To expect the contents completely unchanged, give only the keys: 2114*9c5db199SXin Li {'bios': [], 'ec': []} or {'bios': None, 'ec': None} 2115*9c5db199SXin Li 2116*9c5db199SXin Li @param before_fwids: dict of versions from before the update 2117*9c5db199SXin Li @param image_fwids: dict of versions in the update 2118*9c5db199SXin Li @param after_fwids: dict of actual versions after the update 2119*9c5db199SXin Li @param expected_written: dict indicating which ones should have changed 2120*9c5db199SXin Li @return: list of error lines for mismatches 2121*9c5db199SXin Li 2122*9c5db199SXin Li @type before_fwids: dict 2123*9c5db199SXin Li @type image_fwids: dict | None 2124*9c5db199SXin Li @type after_fwids: dict 2125*9c5db199SXin Li @type expected_written: dict 2126*9c5db199SXin Li @rtype: list 2127*9c5db199SXin Li """ 2128*9c5db199SXin Li errors = [] 2129*9c5db199SXin Li 2130*9c5db199SXin Li if image_fwids is None: 2131*9c5db199SXin Li image_fwids = {} 2132*9c5db199SXin Li 2133*9c5db199SXin Li for target in sorted(expected_written.keys()): 2134*9c5db199SXin Li # target is BIOS or EC 2135*9c5db199SXin Li 2136*9c5db199SXin Li before_missing = (target not in before_fwids) 2137*9c5db199SXin Li after_missing = (target not in after_fwids) 2138*9c5db199SXin Li if before_missing or after_missing: 2139*9c5db199SXin Li if before_missing: 2140*9c5db199SXin Li errors.append("...no before_fwids[%s]" % target) 2141*9c5db199SXin Li if after_missing: 2142*9c5db199SXin Li errors.append("...no after_fwids[%s]" % target) 2143*9c5db199SXin Li continue 2144*9c5db199SXin Li 2145*9c5db199SXin Li written_sections = expected_written.get(target) or list() 2146*9c5db199SXin Li written_sections = set(written_sections) 2147*9c5db199SXin Li 2148*9c5db199SXin Li before_sections = set(before_fwids.get(target) or dict()) 2149*9c5db199SXin Li image_sections = set(image_fwids.get(target) or dict()) 2150*9c5db199SXin Li after_sections = set(after_fwids.get(target) or dict()) 2151*9c5db199SXin Li 2152*9c5db199SXin Li for section in before_sections | image_sections | after_sections: 2153*9c5db199SXin Li # section is RO, RW, A, or B 2154*9c5db199SXin Li 2155*9c5db199SXin Li before_fwid = before_fwids[target][section] 2156*9c5db199SXin Li image_fwid = image_fwids.get(target, {}).get(section, None) 2157*9c5db199SXin Li actual_fwid = after_fwids[target][section] 2158*9c5db199SXin Li 2159*9c5db199SXin Li if section in written_sections: 2160*9c5db199SXin Li expected_fwid = image_fwid 2161*9c5db199SXin Li expected_desc = 'rewritten fwid (%s)' % expected_fwid 2162*9c5db199SXin Li if image_fwid == before_fwid: 2163*9c5db199SXin Li expected_desc = ('rewritten (no changes) fwid (%s)' % 2164*9c5db199SXin Li expected_fwid) 2165*9c5db199SXin Li else: 2166*9c5db199SXin Li expected_fwid = before_fwid 2167*9c5db199SXin Li expected_desc = 'original fwid (%s)' % expected_fwid 2168*9c5db199SXin Li 2169*9c5db199SXin Li if actual_fwid == expected_fwid: 2170*9c5db199SXin Li actual_desc = 'correct value' 2171*9c5db199SXin Li 2172*9c5db199SXin Li elif actual_fwid == image_fwid: 2173*9c5db199SXin Li actual_desc = 'rewritten fwid (%s)' % actual_fwid 2174*9c5db199SXin Li if image_fwid == before_fwid: 2175*9c5db199SXin Li # The flash could have been rewritten with the same fwid 2176*9c5db199SXin Li actual_desc = 'possibly written fwid (%s)' % actual_fwid 2177*9c5db199SXin Li 2178*9c5db199SXin Li elif actual_fwid == before_fwid: 2179*9c5db199SXin Li actual_desc = 'original fwid (%s)' % actual_fwid 2180*9c5db199SXin Li 2181*9c5db199SXin Li else: 2182*9c5db199SXin Li actual_desc = 'unknown fwid (%s)' % actual_fwid 2183*9c5db199SXin Li 2184*9c5db199SXin Li msg = ("...FWID (%s %s): expected %s, got %s" % 2185*9c5db199SXin Li (target.upper(), section.upper(), 2186*9c5db199SXin Li expected_desc, actual_desc)) 2187*9c5db199SXin Li 2188*9c5db199SXin Li if actual_fwid != expected_fwid: 2189*9c5db199SXin Li errors.append(msg) 2190*9c5db199SXin Li return errors 2191*9c5db199SXin Li 2192*9c5db199SXin Li 2193*9c5db199SXin Li def fwmp_is_cleared(self): 2194*9c5db199SXin Li """Return True if the FWMP has been created""" 2195*9c5db199SXin Li res = self.host.run('cryptohome ' 2196*9c5db199SXin Li '--action=get_firmware_management_parameters', 2197*9c5db199SXin Li ignore_status=True) 2198*9c5db199SXin Li if res.exit_status and res.exit_status != self.FWMP_CLEARED_EXIT_STATUS: 2199*9c5db199SXin Li raise error.TestError('Could not run cryptohome command %r' % res) 2200*9c5db199SXin Li return (self.FWMP_CLEARED_ERROR_MSG in res.stdout 2201*9c5db199SXin Li or tpm_utils.FwmpIsAllZero(res.stdout)) 2202*9c5db199SXin Li 2203*9c5db199SXin Li 2204*9c5db199SXin Li def _tpm_is_owned(self): 2205*9c5db199SXin Li """Returns True if the tpm is owned""" 2206*9c5db199SXin Li result = self.host.run('tpm_manager_client status --nonsensitive', 2207*9c5db199SXin Li ignore_status=True) 2208*9c5db199SXin Li logging.debug(result) 2209*9c5db199SXin Li return result.exit_status == 0 and 'is_owned: true' in result.stdout 2210*9c5db199SXin Li 2211*9c5db199SXin Li def clear_fwmp(self): 2212*9c5db199SXin Li """Clear the FWMP""" 2213*9c5db199SXin Li if self.fwmp_is_cleared(): 2214*9c5db199SXin Li return 2215*9c5db199SXin Li tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True) 2216*9c5db199SXin Li self.host.run('tpm_manager_client take_ownership') 2217*9c5db199SXin Li if not utils.wait_for_value(self._tpm_is_owned, expected_value=True): 2218*9c5db199SXin Li raise error.TestError('Unable to own tpm while clearing fwmp.') 2219*9c5db199SXin Li self.host.run('cryptohome ' 2220*9c5db199SXin Li '--action=remove_firmware_management_parameters') 2221*9c5db199SXin Li 2222*9c5db199SXin Li def wait_for(self, cfg_field, action_msg=None, extra_time=0): 2223*9c5db199SXin Li """Waits for time specified in a config. 2224*9c5db199SXin Li 2225*9c5db199SXin Li @ivar cfg_field: The name of the config field that specifies the 2226*9c5db199SXin Li time to wait. 2227*9c5db199SXin Li @ivar action_msg: Optional log message describing the action that 2228*9c5db199SXin Li will occur after the wait. 2229*9c5db199SXin Li @ivar extra_time: Additional time to be added to time from config. 2230*9c5db199SXin Li """ 2231*9c5db199SXin Li wait_time = self.faft_config.__getattr__(cfg_field) + extra_time 2232*9c5db199SXin Li if extra_time: 2233*9c5db199SXin Li wait_src = "%s + %s" % (cfg_field, extra_time) 2234*9c5db199SXin Li else: 2235*9c5db199SXin Li wait_src = cfg_field 2236*9c5db199SXin Li 2237*9c5db199SXin Li units = 'second' if wait_time==1 else 'seconds' 2238*9c5db199SXin Li start_msg = "Waiting %s(%s) %s" % (wait_time, wait_src, units) 2239*9c5db199SXin Li if action_msg: 2240*9c5db199SXin Li start_msg += ", before '%s'" % action_msg 2241*9c5db199SXin Li start_msg += "." 2242*9c5db199SXin Li 2243*9c5db199SXin Li logging.info(start_msg) 2244*9c5db199SXin Li time.sleep(wait_time) 2245*9c5db199SXin Li logging.info("Done waiting.") 2246*9c5db199SXin Li 2247*9c5db199SXin Li def _try_to_bring_dut_up(self): 2248*9c5db199SXin Li """Try to quickly get the dut in a pingable state""" 2249*9c5db199SXin Li if not hasattr(self, 'cr50'): 2250*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2251*9c5db199SXin Li 'access to the Cr50 console') 2252*9c5db199SXin Li logging.info('Bringing DUT up') 2253*9c5db199SXin Li 2254*9c5db199SXin Li self.servo.set_nocheck('cold_reset', 'off') 2255*9c5db199SXin Li self.servo.set_nocheck('warm_reset', 'off') 2256*9c5db199SXin Li 2257*9c5db199SXin Li time.sleep(self.cr50.SHORT_WAIT) 2258*9c5db199SXin Li if not self.cr50.ap_is_on(): 2259*9c5db199SXin Li logging.info('Pressing power button to turn on AP') 2260*9c5db199SXin Li self.servo.power_short_press() 2261*9c5db199SXin Li 2262*9c5db199SXin Li end_time = time.time() + self.RESPONSE_TIMEOUT 2263*9c5db199SXin Li while not self.host.ping_wait_up( 2264*9c5db199SXin Li self.faft_config.delay_reboot_to_ping * 2): 2265*9c5db199SXin Li if time.time() > end_time: 2266*9c5db199SXin Li logging.warning( 2267*9c5db199SXin Li 'DUT is unresponsive after trying to bring it up') 2268*9c5db199SXin Li return 2269*9c5db199SXin Li self.servo.get_power_state_controller().reset() 2270*9c5db199SXin Li logging.info('DUT did not respond. Resetting it.') 2271*9c5db199SXin Li 2272*9c5db199SXin Li def _check_open_and_press_power_button(self): 2273*9c5db199SXin Li """Check stdout and press the power button if prompted. 2274*9c5db199SXin Li 2275*9c5db199SXin Li Returns: 2276*9c5db199SXin Li True if the process is still running. 2277*9c5db199SXin Li """ 2278*9c5db199SXin Li if not hasattr(self, 'cr50'): 2279*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2280*9c5db199SXin Li 'access to the Cr50 console') 2281*9c5db199SXin Li 2282*9c5db199SXin Li logging.info(self._get_ccd_open_output()) 2283*9c5db199SXin Li self.servo.power_short_press() 2284*9c5db199SXin Li logging.info('long int power button press') 2285*9c5db199SXin Li # power button press cr50 erases nvmem and resets the dut before setting 2286*9c5db199SXin Li # the state to open. Wait a bit so we don't check the ccd state in the 2287*9c5db199SXin Li # middle of this reset process. Power button requests happen once a 2288*9c5db199SXin Li # minute, so waiting 10 seconds isn't a big deal. 2289*9c5db199SXin Li time.sleep(10) 2290*9c5db199SXin Li return (self.cr50.OPEN == self.cr50.get_ccd_level() or 2291*9c5db199SXin Li self._ccd_open_job.sp.poll() is not None) 2292*9c5db199SXin Li 2293*9c5db199SXin Li def _get_ccd_open_output(self): 2294*9c5db199SXin Li """Read the new output.""" 2295*9c5db199SXin Li if not hasattr(self, 'cr50'): 2296*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2297*9c5db199SXin Li 'access to the Cr50 console') 2298*9c5db199SXin Li 2299*9c5db199SXin Li self._ccd_open_job.process_output() 2300*9c5db199SXin Li output = self._ccd_open_stdout.getvalue() 2301*9c5db199SXin Li self._ccd_open_stdout.seek(self._ccd_open_last_len) 2302*9c5db199SXin Li self._ccd_open_last_len = len(output) 2303*9c5db199SXin Li return self._ccd_open_stdout.read().strip() 2304*9c5db199SXin Li 2305*9c5db199SXin Li def _close_ccd_open_job(self): 2306*9c5db199SXin Li """Terminate the process and check the results.""" 2307*9c5db199SXin Li if not hasattr(self, 'cr50'): 2308*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2309*9c5db199SXin Li 'access to the Cr50 console') 2310*9c5db199SXin Li 2311*9c5db199SXin Li exit_status = utils.nuke_subprocess(self._ccd_open_job.sp) 2312*9c5db199SXin Li stdout = self._ccd_open_stdout.getvalue().strip() 2313*9c5db199SXin Li delattr(self, '_ccd_open_job') 2314*9c5db199SXin Li if stdout: 2315*9c5db199SXin Li logging.info('stdout of ccd open:\n%s', stdout) 2316*9c5db199SXin Li if exit_status: 2317*9c5db199SXin Li logging.info('exit status: %d', exit_status) 2318*9c5db199SXin Li if 'Error' in stdout: 2319*9c5db199SXin Li raise error.TestFail('ccd open Error %s' % 2320*9c5db199SXin Li stdout.split('Error')[-1]) 2321*9c5db199SXin Li if self.cr50.OPEN != self.cr50.get_ccd_level(): 2322*9c5db199SXin Li raise error.TestFail('unable to open cr50: %s' % stdout) 2323*9c5db199SXin Li else: 2324*9c5db199SXin Li logging.info('Opened Cr50') 2325*9c5db199SXin Li 2326*9c5db199SXin Li def ccd_open_from_ap(self): 2327*9c5db199SXin Li """Start the open process and press the power button.""" 2328*9c5db199SXin Li if not hasattr(self, 'cr50'): 2329*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2330*9c5db199SXin Li 'access to the Cr50 console') 2331*9c5db199SXin Li 2332*9c5db199SXin Li # Opening CCD requires power button presses. If those presses would 2333*9c5db199SXin Li # power off the AP and prevent CCD open from completing, ignore them. 2334*9c5db199SXin Li if self.faft_config.ec_forwards_short_pp_press: 2335*9c5db199SXin Li self.stop_powerd() 2336*9c5db199SXin Li 2337*9c5db199SXin Li # Make sure the test waits long enough to avoid ccd rate limiting. 2338*9c5db199SXin Li time.sleep(self.cr50.CCD_PASSWORD_RATE_LIMIT) 2339*9c5db199SXin Li 2340*9c5db199SXin Li self._ccd_open_last_len = 0 2341*9c5db199SXin Li 2342*9c5db199SXin Li self._ccd_open_stdout = six.StringIO() 2343*9c5db199SXin Li 2344*9c5db199SXin Li ccd_open_cmd = utils.sh_escape('gsctool -a -o') 2345*9c5db199SXin Li full_ssh_cmd = '%s "%s"' % (self.host.ssh_command(options='-tt'), 2346*9c5db199SXin Li ccd_open_cmd) 2347*9c5db199SXin Li # Start running the Cr50 Open process in the background. 2348*9c5db199SXin Li self._ccd_open_job = utils.BgJob(full_ssh_cmd, 2349*9c5db199SXin Li nickname='ccd_open', 2350*9c5db199SXin Li stdout_tee=self._ccd_open_stdout, 2351*9c5db199SXin Li stderr_tee=utils.TEE_TO_LOGS) 2352*9c5db199SXin Li if self._ccd_open_job == None: 2353*9c5db199SXin Li raise error.TestFail('could not start ccd open') 2354*9c5db199SXin Li 2355*9c5db199SXin Li try: 2356*9c5db199SXin Li # Wait for the first gsctool power button prompt before starting the 2357*9c5db199SXin Li # open process. 2358*9c5db199SXin Li logging.info(self._get_ccd_open_output()) 2359*9c5db199SXin Li # Cr50 starts out by requesting 5 quick presses then 4 longer 2360*9c5db199SXin Li # power button presses. Run the quick presses without looking at the 2361*9c5db199SXin Li # command output, because getting the output can take some time. For 2362*9c5db199SXin Li # the presses that require a 1 minute wait check the output between 2363*9c5db199SXin Li # presses, so we can catch errors 2364*9c5db199SXin Li # 2365*9c5db199SXin Li # run quick presses for 30 seconds. It may take a couple of seconds 2366*9c5db199SXin Li # for open to start. 10 seconds should be enough. 30 is just used 2367*9c5db199SXin Li # because it will definitely be enough, and this process takes 300 2368*9c5db199SXin Li # seconds, so doing quick presses for 30 seconds won't matter. 2369*9c5db199SXin Li end_time = time.time() + 30 2370*9c5db199SXin Li while time.time() < end_time: 2371*9c5db199SXin Li self.servo.power_short_press() 2372*9c5db199SXin Li logging.info('short int power button press') 2373*9c5db199SXin Li time.sleep(self.PP_SHORT_INTERVAL) 2374*9c5db199SXin Li # Poll the output and press the power button for the longer presses. 2375*9c5db199SXin Li utils.wait_for_value(self._check_open_and_press_power_button, 2376*9c5db199SXin Li expected_value=True, 2377*9c5db199SXin Li timeout_sec=self.cr50.PP_LONG) 2378*9c5db199SXin Li except Exception as e: 2379*9c5db199SXin Li logging.info(e) 2380*9c5db199SXin Li raise 2381*9c5db199SXin Li finally: 2382*9c5db199SXin Li self._close_ccd_open_job() 2383*9c5db199SXin Li self._try_to_bring_dut_up() 2384*9c5db199SXin Li logging.info(self.cr50.get_ccd_info()) 2385*9c5db199SXin Li 2386*9c5db199SXin Li def enter_mode_after_checking_cr50_state(self, mode): 2387*9c5db199SXin Li """Reboot to mode if cr50 doesn't already match the state""" 2388*9c5db199SXin Li if not hasattr(self, 'cr50'): 2389*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2390*9c5db199SXin Li 'access to the Cr50 console') 2391*9c5db199SXin Li 2392*9c5db199SXin Li # If the device is already in the correct mode, don't do anything 2393*9c5db199SXin Li if (mode == 'dev') == self.cr50.in_dev_mode(): 2394*9c5db199SXin Li logging.info('already in %r mode', mode) 2395*9c5db199SXin Li return 2396*9c5db199SXin Li 2397*9c5db199SXin Li self.switcher.reboot_to_mode(to_mode=mode) 2398*9c5db199SXin Li 2399*9c5db199SXin Li if (mode == 'dev') != self.cr50.in_dev_mode(): 2400*9c5db199SXin Li raise error.TestError('Unable to enter %r mode' % mode) 2401*9c5db199SXin Li 2402*9c5db199SXin Li def fast_ccd_open(self, enable_testlab=False, reset_ccd=True, 2403*9c5db199SXin Li dev_mode=False): 2404*9c5db199SXin Li """Try to use ccd testlab open. If that fails, do regular ap open. 2405*9c5db199SXin Li 2406*9c5db199SXin Li Args: 2407*9c5db199SXin Li enable_testlab: If True, enable testlab mode after cr50 is open. 2408*9c5db199SXin Li reset_ccd: If True, reset ccd after open. 2409*9c5db199SXin Li dev_mode: True if the device should be in dev mode after ccd is 2410*9c5db199SXin Li is opened. 2411*9c5db199SXin Li """ 2412*9c5db199SXin Li if not hasattr(self, 'cr50'): 2413*9c5db199SXin Li raise error.TestNAError('Test can only be run on devices with ' 2414*9c5db199SXin Li 'access to the Cr50 console') 2415*9c5db199SXin Li 2416*9c5db199SXin Li if self.servo.main_device_is_ccd() and not self.cr50.testlab_is_on(): 2417*9c5db199SXin Li error_txt = 'because the main servo device is CCD.' 2418*9c5db199SXin Li if enable_testlab: 2419*9c5db199SXin Li raise error.TestNAError('Cannot enable testlab: %s' % error_txt) 2420*9c5db199SXin Li elif reset_ccd: 2421*9c5db199SXin Li raise error.TestNAError('CCD reset not allowed: %s' % error_txt) 2422*9c5db199SXin Li 2423*9c5db199SXin Li if not self.faft_config.has_powerbutton: 2424*9c5db199SXin Li logging.warning('No power button', exc_info=True) 2425*9c5db199SXin Li enable_testlab = False 2426*9c5db199SXin Li 2427*9c5db199SXin Li # Try to use testlab open first, so we don't have to wait for the 2428*9c5db199SXin Li # physical presence check. 2429*9c5db199SXin Li self.cr50.send_command('ccd testlab open') 2430*9c5db199SXin Li if self.cr50.OPEN != self.cr50.get_ccd_level(): 2431*9c5db199SXin Li if self.servo.has_control('chassis_open'): 2432*9c5db199SXin Li self.servo.set('chassis_open', 'yes') 2433*9c5db199SXin Li pw = '' if self.cr50.password_is_reset() else self.CCD_PASSWORD 2434*9c5db199SXin Li # Use the console to open cr50 without entering dev mode if 2435*9c5db199SXin Li # possible. Ittakes longer and relies on more systems to enter dev 2436*9c5db199SXin Li # mode and ssh into the AP. Skip the steps that aren't required. 2437*9c5db199SXin Li if not (pw or self.cr50.get_cap( 2438*9c5db199SXin Li 'OpenNoDevMode')[self.cr50.CAP_IS_ACCESSIBLE]): 2439*9c5db199SXin Li self.enter_mode_after_checking_cr50_state('dev') 2440*9c5db199SXin Li 2441*9c5db199SXin Li if pw or self.cr50.get_cap( 2442*9c5db199SXin Li 'OpenFromUSB')[self.cr50.CAP_IS_ACCESSIBLE]: 2443*9c5db199SXin Li self.cr50.set_ccd_level(self.cr50.OPEN, pw) 2444*9c5db199SXin Li else: 2445*9c5db199SXin Li self.ccd_open_from_ap() 2446*9c5db199SXin Li 2447*9c5db199SXin Li if self.servo.has_control('chassis_open'): 2448*9c5db199SXin Li self.servo.set('chassis_open', 'no') 2449*9c5db199SXin Li 2450*9c5db199SXin Li if enable_testlab: 2451*9c5db199SXin Li self.cr50.set_ccd_testlab('on') 2452*9c5db199SXin Li 2453*9c5db199SXin Li if reset_ccd: 2454*9c5db199SXin Li self.cr50.ccd_reset() 2455*9c5db199SXin Li 2456*9c5db199SXin Li # In default, the device should be in normal mode. After opening cr50, 2457*9c5db199SXin Li # the TPM should be cleared and the device should automatically reset to 2458*9c5db199SXin Li # normal mode. However, some tests might want the device in 'dev' mode. 2459*9c5db199SXin Li self.enter_mode_after_checking_cr50_state('dev' if dev_mode else 2460*9c5db199SXin Li 'normal') 2461