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 5import logging 6import pprint 7import time 8import re 9 10from autotest_lib.client.common_lib import error 11from autotest_lib.server import autotest 12from autotest_lib.server.cros.faft.cr50_test import Cr50Test 13 14 15class firmware_Cr50DeviceState(Cr50Test): 16 """Verify Cr50 tracks the EC and AP state correctly. 17 18 Put the device through S0, S0ix, S3, and G3. Cr50 responds to these state 19 changes by enabling/disabling uart and changing its suspend type. Verify 20 that none of these cause any interrupt storms on Cr50. Make sure that there 21 aren't any interrupt storms and that Cr50 enters regular or deep sleep a 22 reasonable amount of times. 23 """ 24 version = 1 25 26 DEEP_SLEEP_STEP_SUFFIX = ' Num Deep Sleep Steps' 27 28 # Use negative numbers to keep track of counts not in the IRQ list. 29 KEY_DEEP_SLEEP = -3 30 KEY_TIME = -2 31 KEY_RESET = -1 32 KEY_REGULAR_SLEEP = 112 33 INT_NAME = { 34 KEY_RESET : 'Reset Count', 35 KEY_DEEP_SLEEP : 'Deep Sleep Count', 36 KEY_TIME : 'Cr50 Time', 37 4 : 'HOST_CMD_DONE', 38 81 : 'GPIO0', 39 98 : 'GPIO1', 40 103 : 'I2CS WRITE', 41 KEY_REGULAR_SLEEP : 'PMU WAKEUP', 42 113 : 'AC present FED', 43 114 : 'AC present RED', 44 124 : 'RBOX_INTR_PWRB', 45 130 : 'SPS CS deassert', 46 138 : 'SPS RXFIFO LVL', 47 159 : 'SPS RXFIFO overflow', 48 160 : 'EVENT TIMER', 49 174 : 'CR50_RX_SERVO_TX', 50 177 : 'CR50_TX_SERVO_RX', 51 181 : 'AP_TX_CR50_RX', 52 184 : 'AP_RX_CR50_TX', 53 188 : 'EC_TX_CR50_RX', 54 191 : 'EC_RX_CR50_TX', 55 193 : 'USB', 56 } 57 SLEEP_KEYS = [ KEY_REGULAR_SLEEP, KEY_DEEP_SLEEP ] 58 # Cr50 won't enable any form of sleep until it has been up for 20 seconds. 59 SLEEP_DELAY = 20 60 # The time in seconds to wait in each state 61 SLEEP_TIME = 30 62 SHORT_WAIT = 5 63 CONSERVATIVE_WAIT_TIME = SLEEP_TIME + SHORT_WAIT + 10 64 # Cr50 should wake up twice per second while in regular sleep 65 SLEEP_RATE = 2 66 67 DEEP_SLEEP_MAX = 2 68 ARM = 'ARM ' 69 70 # If there are over 100,000 interrupts, it is an interrupt storm. 71 DEFAULT_COUNTS = [0, 100000] 72 # A dictionary of ok count values for each irq that shouldn't follow the 73 # DEFAULT_COUNTS range. 74 EXPECTED_IRQ_COUNT_RANGE = { 75 KEY_RESET : [0, 0], 76 KEY_DEEP_SLEEP : [0, DEEP_SLEEP_MAX], 77 KEY_TIME : [0, CONSERVATIVE_WAIT_TIME], 78 'S0ix' + DEEP_SLEEP_STEP_SUFFIX : [0, 0], 79 # Cr50 may enter deep sleep an extra time, because of how the test 80 # collects taskinfo counts. Just verify that it does enter deep sleep 81 'S3' + DEEP_SLEEP_STEP_SUFFIX : [1, 2], 82 'G3' + DEEP_SLEEP_STEP_SUFFIX : [1, 2], 83 # ARM devices don't enter deep sleep in S3 84 ARM + 'S3' + DEEP_SLEEP_STEP_SUFFIX : [0, 0], 85 ARM + 'G3' + DEEP_SLEEP_STEP_SUFFIX : [1, 2], 86 # Regular sleep is calculated based on the cr50 time 87 } 88 89 GET_TASKINFO = ['IRQ counts by type:(.*)Service calls'] 90 91 START = '' 92 INCREASE = '+' 93 DS_RESUME = 'DS' 94 95 96 def initialize(self, host, cmdline_args, full_args): 97 super(firmware_Cr50DeviceState, self).initialize(host, cmdline_args, 98 full_args) 99 # Don't bother if there is no Chrome EC or if EC hibernate doesn't work. 100 if not self.check_ec_capability(): 101 raise error.TestNAError("Nothing needs to be tested on this device") 102 103 104 def get_taskinfo_output(self): 105 """Return a dict with the irq numbers as keys and counts as values""" 106 output = self.cr50.send_command_get_output('taskinfo', 107 self.GET_TASKINFO)[0][1].strip() 108 logging.info(output) 109 return output 110 111 112 def get_irq_counts(self): 113 """Return a dict with the irq numbers as keys and counts as values""" 114 output = self.get_taskinfo_output() 115 irq_list = re.findall('\d+\s+\d+\r', output) 116 # Make sure the regular sleep irq is in the dictionary, even if cr50 117 # hasn't seen any interrupts. It's important the test sees that there's 118 # never an interrupt. 119 irq_counts = { self.KEY_REGULAR_SLEEP : 0 } 120 for irq_info in irq_list: 121 logging.debug(irq_info) 122 num, count = irq_info.split() 123 irq_counts[int(num)] = int(count) 124 irq_counts[self.KEY_RESET] = int(self.servo.get('cr50_reset_count')) 125 irq_counts[self.KEY_DEEP_SLEEP] = int(self.cr50.get_deep_sleep_count()) 126 irq_counts[self.KEY_TIME] = int(self.cr50.gettime()) 127 return irq_counts 128 129 130 def get_expected_count(self, irq_key, cr50_time): 131 """Get the expected irq increase for the given irq and state 132 133 Args: 134 irq_key: the irq int 135 cr50_time: the cr50 time in seconds 136 137 Returns: 138 A list with the expected irq count range [min, max] 139 """ 140 # CCD will prevent sleep 141 if self.ccd_enabled and (irq_key in self.SLEEP_KEYS or 142 self.DEEP_SLEEP_STEP_SUFFIX in str(irq_key)): 143 return [0, 0] 144 if irq_key == self.KEY_REGULAR_SLEEP: 145 # If cr50_time is really low, we probably woke cr50 up using 146 # taskinfo, which would be a pmu wakeup. 147 if cr50_time == 0: 148 return [0, 1] 149 150 min_count = max(cr50_time - self.SLEEP_DELAY, 0) 151 # Just checking there is not a lot of extra sleep wakeups. Add 1 to 152 # the sleep rate so cr50 can have some extra wakeups, but not too 153 # many. 154 max_count = cr50_time * (self.SLEEP_RATE + 1) 155 return [min_count, max_count] 156 return self.EXPECTED_IRQ_COUNT_RANGE.get(irq_key, self.DEFAULT_COUNTS) 157 158 159 def check_increase(self, irq_key, name, increase, expected_range): 160 """Verify the irq count is within the expected range 161 162 Args: 163 irq_key: the irq int 164 name: the irq name string 165 increase: the irq count 166 expected_range: A list with the valid irq count range [min, max] 167 168 Returns: 169 '' if increase is in the given range. If the increase isn't in the 170 range, it returns an error message. 171 """ 172 min_count, max_count = expected_range 173 if min_count > increase or max_count < increase: 174 err_msg = '%s %s: %s not in range %s' % (name, irq_key, increase, 175 expected_range) 176 return err_msg 177 return '' 178 179 180 def get_step_events(self): 181 """Use the deep sleep counts to determine the step events""" 182 ds_counts = self.get_irq_step_counts(self.KEY_DEEP_SLEEP) 183 events = [] 184 for i, count in enumerate(ds_counts): 185 if not i: 186 events.append(self.START) 187 elif count != ds_counts[i - 1]: 188 # If the deep sleep count changed, Cr50 recovered deep sleep 189 # and the irq counts are reset. 190 events.append(self.DS_RESUME) 191 else: 192 events.append(self.INCREASE) 193 return events 194 195 196 def get_irq_step_counts(self, irq_key): 197 """Get a list of the all of the step counts for the given irq""" 198 return [ irq_dict.get(irq_key, 0) for irq_dict in self.steps ] 199 200 201 def check_for_errors(self, state): 202 """Check for unexpected IRQ counts at each step. 203 204 Find the irq count errors and add them to run_errors. 205 206 Args: 207 state: The power state: S0, S0ix, S3, or G3. 208 """ 209 num_steps = len(self.steps) 210 # Get all of the deep sleep counts 211 events = self.get_step_events() 212 213 irq_list = list(self.irqs) 214 irq_list.sort() 215 216 irq_diff = ['%23s' % 'step' + ''.join(self.step_names)] 217 step_errors = [ [] for i in range(num_steps) ] 218 219 cr50_times = self.get_irq_step_counts(self.KEY_TIME) 220 cr50_diffs = [] 221 for i, cr50_time in enumerate(cr50_times): 222 if events[i] == self.INCREASE: 223 cr50_time -= cr50_times[i - 1] 224 cr50_diffs.append(cr50_time) 225 226 # Go through each irq and update its info in the progress dict 227 for irq_key in irq_list: 228 name = self.INT_NAME.get(irq_key, 'Unknown') 229 irq_progress_str = ['%19s %3s:' % (name, irq_key)] 230 231 irq_counts = self.get_irq_step_counts(irq_key) 232 for step, count in enumerate(irq_counts): 233 event = events[step] 234 235 # The deep sleep counts are not reset after deep sleep. Change 236 # the event to INCREASE. 237 if irq_key == self.KEY_DEEP_SLEEP and event == self.DS_RESUME: 238 event = self.INCREASE 239 240 if event == self.INCREASE: 241 count -= irq_counts[step - 1] 242 243 # Check that the count increase is within the expected value. 244 if event != self.START: 245 expected_range = self.get_expected_count(irq_key, 246 cr50_diffs[step]) 247 248 rv = self.check_increase(irq_key, name, count, 249 expected_range) 250 if rv: 251 logging.info('Unexpected count for %s %s', state, rv) 252 step_errors[step].append(rv) 253 254 irq_progress_str.append(' %2s %7d' % (event, count)) 255 256 irq_diff.append(''.join(irq_progress_str)) 257 258 errors = {} 259 260 ds_key = self.ARM if self.is_arm else '' 261 ds_key += state + self.DEEP_SLEEP_STEP_SUFFIX 262 expected_range = self.get_expected_count(ds_key, 0) 263 rv = self.check_increase(None, ds_key, events.count(self.DS_RESUME), 264 expected_range) 265 if rv: 266 logging.info('Unexpected count for %s %s', state, rv) 267 errors[ds_key] = rv 268 for i, step_error in enumerate(step_errors): 269 if step_error: 270 logging.error('Step %d errors:\n%s', i, 271 pprint.pformat(step_error)) 272 step = '%s step %d %s' % (state, i, self.step_names[i].strip()) 273 errors[step] = step_error 274 275 logging.info('DIFF %s IRQ Counts:\n%s', state, pprint.pformat(irq_diff)) 276 if errors: 277 logging.info('ERRORS %s IRQ Counts:\n%s', state, 278 pprint.pformat(errors)) 279 self.run_errors.update(errors) 280 281 282 def trigger_s0(self): 283 """Press the power button so the DUT will wake up.""" 284 self.servo.power_short_press() 285 286 287 def enter_state(self, state): 288 """Get the command to enter the power state""" 289 self.stage_irq_add(self.get_irq_counts(), 'start %s' % state) 290 if state == 'S0': 291 self.trigger_s0() 292 else: 293 if state == 'S0ix': 294 full_command = 'echo freeze > /sys/power/state &' 295 elif state == 'S3': 296 full_command = 'echo mem > /sys/power/state &' 297 elif state == 'G3': 298 full_command = 'poweroff' 299 self.faft_client.system.run_shell_command(full_command) 300 301 time.sleep(self.SHORT_WAIT); 302 # check state transition 303 if not self.wait_power_state(state, self.SHORT_WAIT): 304 raise error.TestFail('Platform failed to reach %s state.' % state) 305 self.stage_irq_add(self.get_irq_counts(), 'in %s' % state) 306 307 308 def stage_irq_add(self, irq_dict, name=''): 309 """Add the current irq counts to the stored dictionary of irq info""" 310 self.steps.append(irq_dict) 311 self.step_names.append('%11s' % name) 312 self.irqs.update(irq_dict.keys()) 313 314 315 def reset_irq_counts(self): 316 """Reset the test IRQ counts""" 317 self.steps = [] 318 self.step_names = [] 319 self.irqs = set() 320 321 322 def run_transition(self, state): 323 """Enter the given power state and reenter s0 324 325 Enter the power state and return to S0. Wait long enough to ensure cr50 326 will enter sleep mode, so we can verify that as well. 327 328 Args: 329 state: the power state: S0ix, S3, or G3 330 """ 331 self.reset_irq_counts() 332 333 # Enter the given state 334 self.enter_state(state) 335 336 logging.info('waiting %d seconds', self.SLEEP_TIME) 337 time.sleep(self.SLEEP_TIME) 338 339 # Return to S0 340 self.enter_state('S0') 341 342 343 def verify_state(self, state): 344 """Verify cr50 behavior while running through the power state""" 345 346 try: 347 self.run_transition(state) 348 finally: 349 # reset the system to S0 no matter what happens 350 self.trigger_s0() 351 352 # Check that the progress of the irq counts seems reasonable 353 self.check_for_errors(state) 354 355 356 def is_arm_family(self): 357 """Returns True if the DUT is an ARM device.""" 358 arch = self.host.run('arch').stdout.strip() 359 return arch in ['aarch64', 'armv7l'] 360 361 362 def run_through_power_states(self): 363 """Go through S0ix, S3, and G3. Verify there are no interrupt storms""" 364 self.run_errors = {} 365 self.ccd_str = 'ccd ' + ('enabled' if self.ccd_enabled else 'disabled') 366 logging.info('Running through states with %s', self.ccd_str) 367 368 # Initialize the Test IRQ counts 369 self.reset_irq_counts() 370 371 # Make sure the DUT is in s0 372 self.enter_state('S0') 373 374 # Check if the device supports S0ix. The exit status will be 0 if it 375 # does 1 if it doesn't. 376 result = self.host.run('check_powerd_config --suspend_to_idle', 377 ignore_status=True) 378 if not result.exit_status: 379 # Login before entering S0ix so cr50 will be able to enter regular 380 # sleep 381 client_at = autotest.Autotest(self.host) 382 client_at.run_test('login_LoginSuccess') 383 self.verify_state('S0ix') 384 385 # Enter S3 386 self.verify_state('S3') 387 388 # Enter G3 389 self.verify_state('G3') 390 if self.run_errors: 391 self.all_errors[self.ccd_str] = self.run_errors 392 393 394 def run_once(self, host): 395 """Go through S0ix, S3, and G3. Verify there are no interrupt storms""" 396 self.all_errors = {} 397 self.host = host 398 self.is_arm = self.is_arm_family() 399 supports_dts_control = self.cr50.servo_v4_supports_dts_mode() 400 401 if supports_dts_control: 402 self.cr50.ccd_disable(raise_error=True) 403 404 self.ccd_enabled = self.cr50.ccd_is_enabled() 405 self.run_through_power_states() 406 407 if supports_dts_control: 408 ccd_was_enabled = self.ccd_enabled 409 self.cr50.ccd_enable(raise_error=supports_dts_control) 410 self.ccd_enabled = self.cr50.ccd_is_enabled() 411 # If the first run had ccd disabled, and the test was able to enable 412 # ccd, run through the states again to make sure there are no issues 413 # come up when ccd is enabled. 414 if not ccd_was_enabled and self.ccd_enabled: 415 self.run_through_power_states() 416 else: 417 logging.info('Current setup only supports test with ccd %sabled.', 418 'en' if self.ccd_enabled else 'dis') 419 420 self.trigger_s0() 421 if self.all_errors: 422 raise error.TestFail('Unexpected Device State: %s' % 423 self.all_errors) 424 if not supports_dts_control: 425 raise error.TestNAError('Verified device state with %s. Please ' 426 'run with type c servo v4 to test full device state.' % 427 self.ccd_str) 428