1 # Copyright 2017 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import logging 6 import pprint 7 import time 8 9 from autotest_lib.client.common_lib import error 10 from autotest_lib.server.cros.faft.cr50_test import Cr50Test 11 12 13 class firmware_Cr50CCDServoCap(Cr50Test): 14 """Verify Cr50 CCD output enable/disable when servo is connected. 15 16 Verify Cr50 will enable/disable the CCD servo output capabilities when servo 17 is attached/detached. 18 """ 19 version = 1 20 21 # Time used to wait for Cr50 to detect the servo state. Cr50 updates the ccd 22 # state once a second. Wait 2 seconds to be conservative. 23 SLEEP = 2 24 25 # A list of the actions we should verify 26 TEST_CASES = [ 27 'fake_servo on, cr50_run reboot', 28 'fake_servo on, rdd attach, cr50_run reboot', 29 30 'rdd attach, fake_servo on, cr50_run reboot, fake_servo off', 31 'rdd attach, fake_servo on, rdd detach', 32 'rdd attach, fake_servo off, rdd detach', 33 ] 34 35 ON = 0 36 OFF = 1 37 UNDETECTABLE = 2 38 STATUS_MAP = [ 'on', 'off', 'unknown' ] 39 # Create maps for the different ccd states. Mapping each state to 'on', 40 # 'off', and 'unknown'. These lists map to the acceptable [ on values, off 41 # values, and unknown state values] 42 ON_MAP = [ 'on', 'off', '' ] 43 ENABLED_MAP = [ 'enabled', 'disabled', '' ] 44 CONNECTED_MAP = [ 'connected', 'disconnected', 'undetectable' ] 45 VALID_STATES = { 46 'AP' : ON_MAP, 47 'EC' : ON_MAP, 48 'AP UART' : ON_MAP, 49 'Rdd' : CONNECTED_MAP, 50 'Servo' : CONNECTED_MAP, 51 'CCD EXT' : ENABLED_MAP, 52 } 53 # RESULT_ORDER is a list of the CCD state strings. The order corresponds 54 # with the order of the key states in EXPECTED_RESULTS. 55 RESULT_ORDER = ['Rdd', 'CCD EXT', 'Servo'] 56 # A dictionary containing an order of steps to verify and the expected ccd 57 # states as the value. 58 # 59 # The keys are a list of strings with the order of steps to run. 60 # 61 # The values are the expected state of [rdd, ccd ext, servo]. The ccdstate 62 # strings are in RESULT_ORDER. The order of the EXPECTED_RESULTS key states 63 # must match the order in RESULT_ORDER. 64 # 65 # There are three valid states: UNDETECTABLE, ON, or OFF. Undetectable only 66 # describes the servo state when EC uart is enabled. If the ec uart is 67 # enabled, cr50 cannot detect servo and the state becomes undetectable. All 68 # other ccdstates can only be off or on. Cr50 has a lot of different words 69 # for off off and on. So VALID_STATES can be used to convert off, on, and 70 # undetectable to the actual state strings. 71 EXPECTED_RESULTS = { 72 # The state all tests will start with. Servo and the ccd cable are 73 # disconnected. 74 'reset_ccd state' : [OFF, OFF, OFF], 75 76 # If rdd is attached all ccd functionality will be enabled, and servo 77 # will be undetectable. 78 'rdd attach' : [ON, ON, UNDETECTABLE], 79 80 # Cr50 cannot detect servo if ccd has been enabled first 81 'rdd attach, fake_servo off' : [ON, ON, UNDETECTABLE], 82 'rdd attach, fake_servo off, rdd detach' : [OFF, OFF, OFF], 83 'rdd attach, fake_servo on' : [ON, ON, UNDETECTABLE], 84 'rdd attach, fake_servo on, rdd detach' : [OFF, OFF, ON], 85 # Cr50 can detect servo after a reboot even if rdd was attached before 86 # servo. 87 'rdd attach, fake_servo on, cr50_run reboot' : [ON, ON, ON], 88 # Once servo is detached, Cr50 will immediately reenable the EC uart. 89 'rdd attach, fake_servo on, cr50_run reboot, fake_servo off' : 90 [ON, ON, UNDETECTABLE], 91 92 # Cr50 can detect a servo attach 93 'fake_servo on' : [OFF, OFF, ON], 94 # Cr50 knows servo is attached when ccd is enabled, so it wont enable 95 # uart. 96 'fake_servo on, rdd attach' : [ON, ON, ON], 97 'fake_servo on, rdd attach, cr50_run reboot' : [ON, ON, ON], 98 'fake_servo on, cr50_run reboot' : [OFF, OFF, ON], 99 } 100 101 102 def initialize(self, host, cmdline_args, full_args): 103 super(firmware_Cr50CCDServoCap, self).initialize(host, cmdline_args, 104 full_args) 105 if not hasattr(self, 'cr50'): 106 raise error.TestNAError('Test can only be run on devices with ' 107 'access to the Cr50 console') 108 109 if self.servo.get_servo_version() != 'servo_v4_with_servo_micro': 110 raise error.TestNAError('Must use servo v4 with servo micro') 111 112 if not self.cr50.has_command('ccdstate'): 113 raise error.TestNAError('Cannot test on Cr50 with old CCD version') 114 115 if not self.cr50.servo_v4_supports_dts_mode(): 116 raise error.TestNAError('Need working servo v4 DTS control') 117 118 self.check_servo_monitor() 119 # Make sure cr50 is open with testlab enabled. 120 self.fast_open(enable_testlab=True) 121 if not self.cr50.testlab_is_on(): 122 raise error.TestNAError('Cr50 testlab mode needs to be enabled') 123 logging.info('Cr50 is %s', self.servo.get('cr50_ccd_level')) 124 self.cr50.set_cap('UartGscTxECRx', 'Always') 125 126 127 def cleanup(self): 128 """Reenable the EC uart""" 129 try: 130 self.fake_servo('on') 131 self.rdd('detach') 132 self.rdd('attach') 133 finally: 134 super(firmware_Cr50CCDServoCap, self).cleanup() 135 136 137 def state_matches(self, state_dict, state_name, expected_value): 138 """Check the current state. Make sure it matches expected value""" 139 valid_state = self.VALID_STATES[state_name][expected_value] 140 current_state = state_dict[state_name] 141 if isinstance(valid_state, list): 142 return current_state in valid_state 143 return current_state == valid_state 144 145 146 def check_servo_monitor(self): 147 """Make sure cr50 can detect servo connect and disconnect""" 148 # Detach ccd so EC uart won't interfere with servo detection 149 self.rdd('detach') 150 servo_detect_error = error.TestNAError("Cannot run on device that does " 151 "not support servo dectection with ec_uart_en:off/on") 152 self.fake_servo('off') 153 if not self.state_matches(self.get_ccdstate(), 'Servo', self.OFF): 154 raise servo_detect_error 155 self.fake_servo('on') 156 if not self.state_matches(self.get_ccdstate(), 'Servo', self.ON): 157 raise servo_detect_error 158 159 160 def get_ccdstate(self): 161 """Get the current Cr50 CCD states""" 162 rv = self.cr50.send_command_get_output('ccdstate', 163 ['ccdstate(.*)>'])[0][0] 164 # I2C isn't a reliable flag, because the hardware often doesn't support 165 # it. Remove any I2C flags from the ccdstate output. 166 rv = rv.replace(' I2C', '') 167 # Extract only the ccdstate output from rv 168 ccdstate = {} 169 for line in rv.splitlines(): 170 line = line.strip() 171 if ':' in line: 172 k, v = line.split(':', 1) 173 ccdstate[k.strip()] = v.strip() 174 logging.info('Current CCD state:\n%s', pprint.pformat(ccdstate)) 175 return ccdstate 176 177 def state_is_on(self, ccdstate, state_name): 178 """Returns true if the state is on""" 179 return self.state_matches(ccdstate, state_name, self.ON) 180 181 182 def check_state_flags(self, ccdstate): 183 """Check the state flags against the reset of the device state 184 185 If there is any mismatch between the device state and state flags, 186 return a list of errors. 187 """ 188 flags = ccdstate['State flags'] 189 ap_uart_enabled = 'UARTAP' in flags 190 ec_uart_enabled = 'UARTEC' in flags 191 output_enabled = '+TX' in flags 192 ccd_enabled = ap_uart_enabled or ec_uart_enabled or output_enabled 193 ccd_ext_is_enabled = ccdstate['CCD EXT'] == 'enabled' 194 mismatch = [] 195 if ccd_enabled and not ccd_ext_is_enabled: 196 mismatch.append('CCD functionality enabled without CCD EXT') 197 if ccd_ext_is_enabled: 198 if output_enabled and self.state_is_on(ccdstate, 'Servo'): 199 mismatch.append('CCD output is enabled with servo attached') 200 if ap_uart_enabled != self.state_is_on(ccdstate, 'AP UART'): 201 mismatch.append('AP UART enabled without AP UART on') 202 if ec_uart_enabled != self.state_is_on(ccdstate, 'EC'): 203 mismatch.append('EC UART enabled without EC on') 204 return mismatch 205 206 207 208 def verify_ccdstate(self, run): 209 """Verify the current state matches the expected result from the run. 210 211 Args: 212 run: the string representing the actions that have been run. 213 214 Raises: 215 TestError if any of the states are not correct 216 """ 217 if run not in self.EXPECTED_RESULTS: 218 raise error.TestError('Add results for %s to EXPECTED_RESULTS', run) 219 expected_states = self.EXPECTED_RESULTS[run] 220 221 # Wait a short time for the ccd state to settle 222 time.sleep(self.SLEEP) 223 224 ccdstate = self.get_ccdstate() 225 # Check the state flags. Make sure they're in line with the rest of 226 # ccdstate 227 mismatch = self.check_state_flags(ccdstate) 228 for i, expected_state in enumerate(expected_states): 229 name = self.RESULT_ORDER[i] 230 if expected_state == None: 231 logging.info('No expected %s state skipping check', name) 232 continue 233 # Check that the current state matches the expected state 234 if not self.state_matches(ccdstate, name, expected_state): 235 mismatch.append('%s is %r not %r' % (name, ccdstate[name], 236 self.STATUS_MAP[expected_state])) 237 if mismatch: 238 logging.info(ccdstate) 239 raise error.TestFail('Unexpected states after %s: %s' % (run, 240 mismatch)) 241 242 243 def cr50_run(self, action): 244 """Reboot cr50 245 246 @param action: string 'reboot' 247 """ 248 if action == 'reboot': 249 self.cr50.reboot() 250 self.cr50.send_command('ccd testlab open') 251 time.sleep(self.SLEEP) 252 253 254 def reset_ccd(self, state=None): 255 """detach the ccd cable and disconnect servo. 256 257 State is ignored. It just exists to be consistent with the other action 258 functions. 259 260 @param state: a var that is ignored 261 """ 262 self.rdd('detach') 263 self.fake_servo('off') 264 265 266 def rdd(self, state): 267 """Attach or detach the ccd cable. 268 269 @param state: string 'attach' or 'detach' 270 """ 271 self.servo.set_nocheck('servo_v4_dts_mode', 272 'on' if state == 'attach' else 'off') 273 time.sleep(self.SLEEP) 274 275 276 def fake_servo(self, state): 277 """Mimic servo on/off 278 279 Cr50 monitors the servo EC uart tx signal to detect servo. If the signal 280 is pulled up, then Cr50 will think servo is connnected. Enable the ec 281 uart to enable the pullup. Disable the it to remove the pullup. 282 283 It takes some time for Cr50 to detect the servo state so wait 2 seconds 284 before returning. 285 """ 286 self.servo.set('ec_uart_en', state) 287 288 # Cr50 needs time to detect the servo state 289 time.sleep(self.SLEEP) 290 291 292 def run_steps(self, steps): 293 """Do each step in steps and then verify the uart state. 294 295 The uart state is order dependent, so we need to know all of the 296 previous steps to verify the state. This will do all of the steps in 297 the string and verify the Cr50 CCD uart state after each step. 298 299 @param steps: a comma separated string with the steps to run 300 """ 301 # The order of steps is separated by ', '. Remove the last step and 302 # run all of the steps before it. 303 separated_steps = steps.rsplit(', ', 1) 304 if len(separated_steps) > 1: 305 self.run_steps(separated_steps[0]) 306 307 step = separated_steps[-1] 308 # The func and state are separated by ' ' 309 func, state = step.split(' ') 310 logging.info('running %s', step) 311 getattr(self, func)(state) 312 313 # Verify the ccd state is correct 314 self.verify_ccdstate(steps) 315 316 317 def run_once(self): 318 """Run through TEST_CASES and verify that Cr50 enables/disables uart""" 319 for steps in self.TEST_CASES: 320 self.run_steps('reset_ccd state') 321 logging.info('TESTING: %s', steps) 322 self.run_steps(steps) 323 logging.info('VERIFIED: %s', steps) 324