1# Copyright 2018 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import print_function 6 7import logging 8import time 9 10from autotest_lib.client.common_lib import error 11from autotest_lib.client.common_lib.cros import tpm_utils 12from autotest_lib.server import autotest 13from autotest_lib.server.cros.faft.cr50_test import Cr50Test 14 15 16class firmware_Cr50FactoryResetVC(Cr50Test): 17 """A test verifying factory mode vendor command.""" 18 version = 1 19 20 FWMP_DEV_DISABLE_CCD_UNLOCK = (1 << 6) 21 # Short wait to make sure cr50 has had enough time to update the ccd state 22 SLEEP = 2 23 BOOL_VALUES = (True, False) 24 TPM_ERR = 'Problems reading from TPM' 25 26 def initialize(self, host, cmdline_args, full_args): 27 """Initialize servo check if cr50 exists.""" 28 super(firmware_Cr50FactoryResetVC, self).initialize(host, cmdline_args, 29 full_args) 30 if not self.cr50.has_command('bpforce'): 31 raise error.TestNAError('Cannot run test without bpforce') 32 self.fast_ccd_open(enable_testlab=True) 33 # Reset ccd completely. 34 self.cr50.ccd_reset() 35 36 # If we can fake battery connect/disconnect, then we can test the vendor 37 # command. 38 try: 39 self.bp_override(True) 40 self.bp_override(False) 41 except Exception as e: 42 logging.info(e) 43 raise error.TestNAError('Cannot fully test factory mode vendor ' 44 'command without the ability to fake battery presence') 45 46 def cleanup(self): 47 """Clear the FWMP and ccd state""" 48 try: 49 self.clear_state() 50 finally: 51 super(firmware_Cr50FactoryResetVC, self).cleanup() 52 53 54 def bp_override(self, connect): 55 """Deassert BATT_PRES signal, so cr50 will think wp is off.""" 56 self.cr50.send_command('ccd testlab open') 57 self.cr50.set_batt_pres_state('connect' if connect else 'disconnect', 58 False) 59 if self.cr50.get_batt_pres_state()[1] != connect: 60 raise error.TestError('Could not fake battery %sconnect' % 61 ('' if connect else 'dis')) 62 self.cr50.set_ccd_level('lock') 63 64 65 def fwmp_ccd_lockout(self): 66 """Returns True if FWMP is locking out CCD.""" 67 return 'fwmp_lock' in self.cr50.get_ccd_info('TPM') 68 69 70 def set_fwmp_lockout(self, enable): 71 """Change the FWMP to enable or disable ccd. 72 73 Args: 74 enable: True if FWMP flags should lock out ccd. 75 """ 76 logging.info('%sing FWMP ccd lockout', 'enabl' if enable else 'clear') 77 if enable: 78 flags = hex(self.FWMP_DEV_DISABLE_CCD_UNLOCK) 79 logging.info('Setting FWMP flags to %s', flags) 80 autotest.Autotest(self.host).run_test('firmware_SetFWMP', 81 flags=flags, fwmp_cleared=True, check_client_result=True) 82 83 if (not self.fwmp_ccd_lockout()) != (not enable): 84 raise error.TestError('Could not %s fwmp lockout' % 85 ('set' if enable else 'clear')) 86 87 88 def setup_ccd_password(self, set_password): 89 """Set the Cr50 CCD password. 90 91 Args: 92 set_password: if True set the password. The password is already 93 cleared, so if False just check the password is cleared 94 """ 95 if set_password: 96 self.cr50.send_command('ccd testlab open') 97 # Set the ccd password 98 self.set_ccd_password(self.CCD_PASSWORD) 99 if self.cr50.password_is_reset() == set_password: 100 raise error.TestError('Could not %s password' % 101 ('set' if set_password else 'clear')) 102 103 104 def factory_mode_enabled(self): 105 """Returns True if factory mode is enabled.""" 106 caps = self.cr50.get_cap_dict() 107 caps.pop('GscFullConsole') 108 return self.cr50.get_cap_overview(caps)[0] 109 110 111 def get_relevant_state(self): 112 """Returns cr50 state that can lock out factory mode. 113 114 FWMP, battery presence, or a password can all lock out enabling factory 115 mode using the vendor command. If any item in state is True, factory 116 mode should be locked out. 117 """ 118 state = [] 119 state.append(self.fwmp_ccd_lockout()) 120 state.append(self.cr50.get_batt_pres_state()[1]) 121 state.append(not self.cr50.password_is_reset()) 122 return state 123 124 125 def get_state_message(self): 126 """Convert relevant state into a useful log message.""" 127 fwmp, bp, password = self.get_relevant_state() 128 return ('fwmp %s bp %sconnected password %s' % 129 ('set' if fwmp else 'cleared', 130 '' if bp else 'dis', 131 'set' if password else 'cleared')) 132 133 134 def factory_locked_out(self): 135 """Returns True if any state preventing factory mode is True.""" 136 return True in self.get_relevant_state() 137 138 139 def set_factory_mode(self, enable): 140 """Use the vendor command to control factory mode. 141 142 Args: 143 enable: Enable factory mode if True. Disable it if False. 144 """ 145 enable_fail = self.factory_locked_out() and enable 146 time.sleep(self.SLEEP) 147 logging.info('%sABLING FACTORY MODE', 'EN' if enable else 'DIS') 148 if enable: 149 logging.info('EXPECT: %s', 'failure' if enable_fail else 'success') 150 cmd = 'enable' if enable else 'disable' 151 152 self.host.run('gsctool -a -F %s' % cmd, 153 ignore_status=(enable_fail or not enable)) 154 expect_enabled = enable and not enable_fail 155 156 # Wait long enoug for cr50 to update the ccd state. 157 time.sleep(self.SLEEP) 158 if expect_enabled: 159 # Verify the tpm is disabled. 160 result = self.host.run('gsctool -af', ignore_status=True) 161 if result.exit_status != 3 or self.TPM_ERR not in result.stderr: 162 raise error.TestFail('TPM enabled after entering factory mode') 163 # Reboot the DUT to reenable TPM communications. 164 self.host.reboot() 165 166 if self.factory_mode_enabled() != expect_enabled: 167 raise error.TestFail('Unexpected factory mode %s result' % cmd) 168 169 170 def clear_state(self): 171 """Clear the FWMP and reset CCD""" 172 self.host.reboot() 173 self._try_to_bring_dut_up() 174 # Clear the FWMP 175 self.clear_fwmp() 176 # make sure all of the ccd stuff is reset 177 self.cr50.send_command('ccd testlab open') 178 # Run ccd reset to make sure all ccd state is cleared 179 self.cr50.ccd_reset() 180 # Clear the TPM owner, so we can set the ccd password and 181 # create the FWMP 182 tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True) 183 184 185 def run_once(self): 186 """Verify FWMP disable with different flag values.""" 187 errors = [] 188 # Try enabling factory mode in each valid state. Cr50 checks write 189 # protect, password, and fwmp before allowing fwmp to be enabled. 190 for lockout_ccd_with_fwmp in self.BOOL_VALUES: 191 for set_password in self.BOOL_VALUES: 192 for connect in self.BOOL_VALUES: 193 # Clear relevant state, so we can set the fwmp and password 194 self.clear_state() 195 196 # Setup the cr50 state 197 self.setup_ccd_password(set_password) 198 self.bp_override(connect) 199 self.set_fwmp_lockout(lockout_ccd_with_fwmp) 200 self.cr50.set_ccd_level('lock') 201 202 logging.info('RUN: %s', self.get_state_message()) 203 204 try: 205 self.set_factory_mode(True) 206 self.set_factory_mode(False) 207 except Exception as e: 208 message = 'FAILURE %r %r' % (self.get_state_message(), 209 e) 210 logging.info(message) 211 errors.append(message) 212 if errors: 213 raise error.TestFail(errors) 214