| # Copyright 2017 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import pprint |
| import time |
| import re |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server import autotest |
| from autotest_lib.server.cros.faft.cr50_test import Cr50Test |
| |
| class firmware_Cr50DeviceState(Cr50Test): |
| """Verify Cr50 tracks the EC and AP state correctly. |
| |
| Put the device through S0, S0ix, S3, and G3. Cr50 responds to these state |
| changes by enabling/disabling uart and changing its suspend type. Verify |
| that none of these cause any interrupt storms on Cr50. Make sure that there |
| aren't any interrupt storms and that Cr50 enters regular or deep sleep a |
| reasonable amount of times. |
| """ |
| version = 1 |
| |
| # If the system boots, it should be in normal or dev mode. These are the |
| # only valid pcr0 values. |
| VALID_PCR0_VALUES = [ |
| # Normal Mode |
| '89EAF35134B4B3C649F44C0C765B96AEAB8BB34EE83CC7A683C4E53D1581C8C7', |
| # Dev Mode |
| '23E14DD9BB51A50E16911F7E11DF1E1AAF0B17134DC739C5653607A1EC8DD37A', |
| ] |
| DEEP_SLEEP_STEP_SUFFIX = ' Num Deep Sleep Steps' |
| BOARD_HAS_SBU_ISSUES = ['octopus'] |
| |
| # Use negative numbers to keep track of counts not in the IRQ list. The |
| # actual number don't matter too much. Just make sure deep sleep is the |
| # lowest, so it's printed first. |
| KEY_DEEP_SLEEP = -10 |
| KEY_TPM_INIT = -5 |
| KEY_CMD_END_TIME = -4 |
| KEY_COLD_RESET_TIME = -3 |
| KEY_TIME = -2 |
| KEY_RESET = -1 |
| |
| IGNORED_KEYS = [KEY_CMD_END_TIME] |
| # TPM initialization time doesn't relate to the previous boot. Don't look |
| # at the difference between each step. Look at each value independently. |
| STEP_INDEPENDENT_KEYS = [KEY_TPM_INIT] |
| KEY_SURVIVES_DS = [KEY_DEEP_SLEEP, KEY_COLD_RESET_TIME] |
| GSC_STATUS_DICT = { |
| KEY_TPM_INIT: 'TPM init (us)', |
| KEY_RESET: 'Reset Count', |
| KEY_DEEP_SLEEP: 'Deep Sleep Count', |
| KEY_TIME: 'Time (DS)', |
| KEY_COLD_RESET_TIME: 'Time (cold reset)', |
| } |
| |
| ENTER_STATE_WAIT = 15 |
| POWER_STATE_CHECK_TRIES = 6 |
| |
| DEEP_SLEEP_MAX = 2 |
| ARM = 'ARM ' |
| # If there are over 100,000 interrupts, it is an interrupt storm. |
| DEFAULT_COUNTS = [0, 100000] |
| # A dictionary of ok count values for each irq that shouldn't follow the |
| # DEFAULT_COUNTS range. |
| EXPECTED_IRQ_COUNT_RANGE = { |
| KEY_RESET: [0, 0], |
| KEY_DEEP_SLEEP: [0, DEEP_SLEEP_MAX], |
| 'lid_close' + DEEP_SLEEP_STEP_SUFFIX: [0, 2], |
| 'default_suspend' + DEEP_SLEEP_STEP_SUFFIX: [0, 2], |
| 'S0ix' + DEEP_SLEEP_STEP_SUFFIX: [0, 0], |
| # Cr50 may enter deep sleep an extra time, because of how the test |
| # collects taskinfo counts. Just verify that it does enter deep sleep |
| 'S3' + DEEP_SLEEP_STEP_SUFFIX: [1, 2], |
| 'G3' + DEEP_SLEEP_STEP_SUFFIX: [1, 2], |
| 'S5' + DEEP_SLEEP_STEP_SUFFIX: [1, 2], |
| # ARM devices don't enter deep sleep in S3 |
| ARM + 'S3' + DEEP_SLEEP_STEP_SUFFIX: [0, 0], |
| ARM + 'G3' + DEEP_SLEEP_STEP_SUFFIX: [1, 2], |
| ARM + 'S5' + DEEP_SLEEP_STEP_SUFFIX: [1, 2], |
| ARM + 'lid_close' + DEEP_SLEEP_STEP_SUFFIX: [0, 2], |
| ARM + 'default_suspend' + DEEP_SLEEP_STEP_SUFFIX: [0, 2], |
| # Regular sleep is calculated based on the cr50 time |
| } |
| START = '' |
| INCREASE = '+' |
| DS_RESUME = 'DS' |
| # Keys like tpm initialization time aren't related to the previous boot. |
| # Don't look at the value from the previous step to determine anything. |
| # Use ' ' since it has to be different from START. |
| STEP_INDEPENDENT = ' ' |
| |
| TMP_POWER_MANAGER_PATH = '/tmp/power_manager' |
| POWER_MANAGER_PATH = '/var/lib/power_manager' |
| MEM_SLEEP_PATH = '/sys/power/mem_sleep' |
| MEM_SLEEP_S0IX = 'echo %s > %s ; sleep 1' % ('s2idle', MEM_SLEEP_PATH) |
| MEM_SLEEP_S3 = 'echo %s > %s ; sleep 1' % ('deep', MEM_SLEEP_PATH) |
| POWER_STATE_PATH = '/sys/power/state' |
| POWER_STATE_S0IX = 'echo %s > %s' % ('freeze', POWER_STATE_PATH) |
| POWER_STATE_S3 = 'echo %s > %s' % ('mem', POWER_STATE_PATH) |
| MOUNT_POWERD = ( |
| 'mkdir -p %s && echo 0 > %s/suspend_to_idle && mount --bind %s %s && restart powerd' |
| % (TMP_POWER_MANAGER_PATH, TMP_POWER_MANAGER_PATH, |
| TMP_POWER_MANAGER_PATH, POWER_MANAGER_PATH)) |
| |
| |
| # TODO(mruthven): remove ec chan restriction once soraka stops spamming host |
| # command output. The extra activity makes it look like a interrupt storm on |
| # the EC uart. |
| CHAN_ALL = 0xffffffff |
| CHAN_EVENTS = 0x20 |
| CHAN_ACPI = 0x400 |
| CHAN_HOSTCMD = 0x80 |
| CHAN_USBCHARGE = 0x200000 |
| CHAN_RESTRICTED = CHAN_ALL ^ (CHAN_EVENTS | CHAN_ACPI | CHAN_HOSTCMD |
| | CHAN_USBCHARGE) |
| |
| |
| def initialize(self, |
| host, |
| cmdline_args, |
| full_args, |
| fwmp=None, |
| pcr_extend=False, |
| default_suspend=True, |
| manual_suspend=True, |
| ccd_en=None): |
| if not host.servo or host.servo.main_device_is_ccd(): |
| raise error.TestNAError( |
| "Test can't run with ccd. Run with servo micro or c2d2") |
| |
| super(firmware_Cr50DeviceState, self).initialize(host, cmdline_args, |
| full_args) |
| # Don't bother if there is no Chrome EC or if EC hibernate doesn't work. |
| if not self.check_ec_capability(): |
| raise error.TestNAError("Nothing needs to be tested on this device") |
| |
| self.fwmp = fwmp |
| self.pcr_extend = pcr_extend |
| self.default_suspend = default_suspend |
| self.manual_suspend = manual_suspend |
| self.test_ccd_en = ccd_en |
| |
| if self.faft_config.ec_forwards_short_pp_press and self.manual_suspend: |
| logging.warning( |
| 'manual suspend not supported with ec_forwards_short_pp_press' |
| ) |
| self.manual_suspend = False |
| |
| # The time in seconds to wait in each state. Wait enough time for gsc |
| # to settle into whatever state. Make sure it's significantly longer |
| # than it takes for gsc to enable sleep. Wait long enough for gsc to |
| # enter deep sleep twice, so the test can catch extra wakeups. |
| self.sleep_time = (self.gsc.DEEP_SLEEP_DELAY * 2) + 10 |
| |
| self.INT_NAME = self.gsc.IRQ_DICT.copy() |
| self.INT_NAME.update(self.GSC_STATUS_DICT) |
| self.KEY_REGULAR_SLEEP = [k for k,v in self.INT_NAME.items() |
| if 'WAKEUP' in v][0] |
| self.SLEEP_KEYS = [ self.KEY_REGULAR_SLEEP, self.KEY_DEEP_SLEEP ] |
| |
| self.deep_sleep_in_s0i3 = self.check_cr50_capability( |
| ['deep_sleep_in_s0i3']) |
| |
| # If the TPM is reset in S0i3, the CR50 may enter deep sleep during S0i3. |
| # Cr50 may enter deep sleep an extra time, because of how the test |
| # collects taskinfo counts. So the range is set conservatively to 0-2. |
| if self.deep_sleep_in_s0i3: |
| irq_s0ix_deep_sleep_key = 'S0ix' + self.DEEP_SLEEP_STEP_SUFFIX |
| self.EXPECTED_IRQ_COUNT_RANGE[irq_s0ix_deep_sleep_key] = [0, 2] |
| # Set the maximum amount of time allowed for the gsc chip type or board type. |
| max_tpm_init = (getattr(self.faft_config, 'custom_max_tpm_init_us', |
| None) or self.gsc.TPM_INIT_MAX) |
| self.EXPECTED_IRQ_COUNT_RANGE[self.KEY_TPM_INIT] = [0, max_tpm_init] |
| # Use the sleep time or the amount of time it takes for the dut to respond |
| # to a ping after reboot. |
| # Multiply sleep time by 2 in case it takes a long time for the device |
| # to boot. It's supposed to be a conservative maximum. |
| sleep_time = self.sleep_time * 2 + self.faft_config.delay_reboot_to_ping |
| self.EXPECTED_IRQ_COUNT_RANGE[self.KEY_TIME] = [0, sleep_time] |
| |
| def get_tpm_init_time(self): |
| """If the AP is on, return the time it took the tpm to initialize.""" |
| # The board may not support cbmem after suspend. Ignore errors on those |
| # devices. |
| ignore_status = not getattr(self.faft_config, 'suspend_cbmem', True) |
| result = self.host.run('cbmem -t', ignore_status=ignore_status) |
| if result.exit_status: |
| return 0 |
| match = re.search('TPM initialization.*\((.*)\)', result.stdout) |
| return int(match.group(1).replace(',', '')) |
| |
| def mount_power_config(self): |
| """Mounts power_manager settings to tmp, |
| ensuring that any changes do not persist across reboots |
| """ |
| self.faft_client.system.run_shell_command(self.MOUNT_POWERD, True) |
| |
| def umount_power_config(self): |
| """Unmounts power_manager settings""" |
| self.faft_client.system.run_shell_command( |
| 'umount %s && restart powerd' % self.POWER_MANAGER_PATH, True) |
| |
| def set_suspend_to_idle(self, value): |
| """Set suspend_to_idle by writing to power_manager settings""" |
| # Suspend to idle expects 0/1 so %d is used |
| self.faft_client.system.run_shell_command( |
| 'echo %d > %s/suspend_to_idle' % |
| (value, self.TMP_POWER_MANAGER_PATH), True) |
| |
| def log_sleep_debug_information(self): |
| """Log some information used for debugging sleep issues""" |
| logging.debug( |
| self.gsc.send_command_retry_get_output('sleepmask', |
| ['sleepmask.*>'], |
| safe=True)[0]) |
| logging.debug( |
| self.gsc.send_command_retry_get_output('sysinfo', |
| ['sysinfo.*>'], |
| safe=True)[0]) |
| |
| |
| def get_irq_counts(self): |
| """Return a dict with the irq numbers as keys and counts as values""" |
| irq_counts = { self.KEY_REGULAR_SLEEP : 0 } |
| # Running all of these commands may take a while. Track how much time |
| # commands are running, so we can offset the cr50 time to set sleep |
| # expectations |
| start_cmd_time = int(self.gsc.gettime()) |
| irq_counts[self.KEY_TIME] = start_cmd_time |
| irq_counts[self. |
| KEY_COLD_RESET_TIME] = self.gsc.gettime_since_cold_reset() |
| |
| irq_counts.update(self.gsc.get_irq_counts()) |
| |
| irq_counts[self.KEY_RESET] = int(self.gsc.get_reset_count()) |
| irq_counts[self.KEY_DEEP_SLEEP] = int(self.gsc.get_deep_sleep_count()) |
| # Log some information, so we can debug issues with sleep. |
| self.log_sleep_debug_information() |
| # Track when the commands end, so the test can ignore the time spent |
| # running these console commands. |
| end_cmd_time = int(self.gsc.gettime()) |
| irq_counts[self.KEY_CMD_END_TIME] = end_cmd_time |
| logging.info('Commands finished in %d seconds', |
| end_cmd_time - start_cmd_time) |
| return irq_counts |
| |
| |
| def get_expected_count(self, irq_key, cr50_time, idle, ds_resume): |
| """Get the expected irq increase for the given irq and state |
| |
| Args: |
| irq_key: the irq int |
| cr50_time: the cr50 time in seconds |
| idle: Cr50 should enter regular sleep while the device is idle. |
| |
| Returns: |
| A list with the expected irq count range [min, max] |
| """ |
| # CCD will prevent sleep |
| if self.ccd_enabled and (irq_key in self.SLEEP_KEYS or |
| self.DEEP_SLEEP_STEP_SUFFIX in str(irq_key)): |
| # If the board has sbu issues, the device may enter sleep. Ignore |
| # it until the hardware is fixed. |
| if self.servo.get_board() in self.BOARD_HAS_SBU_ISSUES: |
| return self.DEFAULT_COUNTS |
| return [0, 0] |
| if irq_key == self.KEY_REGULAR_SLEEP: |
| # If cr50_time is really low, we probably woke cr50 up using |
| # taskinfo, which would be a pmu wakeup. |
| if cr50_time < 5: |
| return [0, 1] |
| # Only enforce the minimum regular sleep count if the device is |
| # idle. Cr50 may not enter regular sleep during power state |
| # transitions. |
| if idle and cr50_time > self.gsc.DEEP_SLEEP_DELAY: |
| if self.gsc.SLEEP_RATE == 0: |
| min_count = 1 |
| else: |
| min_count = cr50_time - self.gsc.DEEP_SLEEP_DELAY |
| else: |
| min_count = 0 |
| # Check that cr50 isn't continuously entering and exiting sleep. |
| # The PMU wakeups should happen around twice a second. Depending |
| # on TPM activity it may occur more often. Add 2 to the multiplier |
| # to allow for extra wakeups. This is mostly to catch issues that |
| # cause cr50 to wake up 100 times a second |
| max_count = cr50_time * (self.gsc.SLEEP_RATE + 2) |
| return [min_count, max_count] |
| # If ccd is disabled, ccd irq counts should not increase. |
| if not self.ccd_enabled and (irq_key in self.gsc.CCD_IRQS): |
| return [0, 0] |
| return self.EXPECTED_IRQ_COUNT_RANGE.get(irq_key, self.DEFAULT_COUNTS) |
| |
| |
| def check_increase(self, irq_key, name, increase, expected_range): |
| """Verify the irq count is within the expected range |
| |
| Args: |
| irq_key: the irq int |
| name: the irq name string |
| increase: the irq count |
| expected_range: A list with the valid irq count range [min, max] |
| |
| Returns: |
| '' if increase is in the given range. If the increase isn't in the |
| range, it returns an error message. |
| """ |
| min_count, max_count = expected_range |
| if min_count > increase or max_count < increase: |
| err_msg = '%s %s: %s not in range %s' % (name, irq_key, increase, |
| expected_range) |
| return err_msg |
| return '' |
| |
| |
| def get_step_events(self): |
| """Use the deep sleep counts to determine the step events""" |
| ds_counts = self.get_irq_step_counts(self.KEY_DEEP_SLEEP) |
| events = [] |
| for i, count in enumerate(ds_counts): |
| if not i: |
| events.append(self.START) |
| elif count != ds_counts[i - 1]: |
| # If the deep sleep count changed, Cr50 recovered deep sleep |
| # and the irq counts are reset. |
| events.append(self.DS_RESUME) |
| else: |
| events.append(self.INCREASE) |
| return events |
| |
| |
| def get_irq_step_counts(self, irq_key): |
| """Get a list of the all of the step counts for the given irq""" |
| return [ irq_dict.get(irq_key, 0) for irq_dict in self.steps ] |
| |
| |
| def check_for_errors(self, state): |
| """Check for unexpected IRQ counts at each step. |
| |
| Find the irq count errors and add them to run_errors. |
| |
| Args: |
| state: The power state: S0, S0ix, S3, or G3. |
| """ |
| num_steps = len(self.steps) |
| # Get all of the deep sleep counts |
| events = self.get_step_events() |
| |
| irq_list = list(self.irqs) |
| irq_list.sort() |
| |
| # Pad the start of the step names, so the names align with each step |
| # count. |
| irq_diff = ['%24s|%s|' % ('', '|'.join(self.step_names))] |
| step_errors = [ [] for i in range(num_steps) ] |
| |
| end_cmd_times = self.get_irq_step_counts(self.KEY_CMD_END_TIME) |
| start_cmd_times = self.get_irq_step_counts(self.KEY_TIME) |
| cr50_time_diff = [] |
| for i, start_time in enumerate(start_cmd_times): |
| # The test collects a lot of information using console commands. |
| # These take a long time to run and cr50 isn't idle. The idle |
| # time is the time this step started minus the time the last set of |
| # commands finished running. |
| if events[i] == self.INCREASE: |
| cr50_time_diff.append(start_time - end_cmd_times[i - 1]) |
| else: |
| cr50_time_diff.append(start_time) |
| |
| # Go through each irq and update its info in the progress dict |
| for irq_key in irq_list: |
| if irq_key in self.IGNORED_KEYS: |
| continue |
| name = self.INT_NAME.get(irq_key, 'Unknown') |
| # Print the IRQ name on the left of the column and the irq number |
| # on the right. |
| irq_progress_str = ['%-19s %3s' % (name, irq_key)] |
| |
| irq_counts = self.get_irq_step_counts(irq_key) |
| for step, count in enumerate(irq_counts): |
| event = events[step] |
| |
| # The deep sleep counts are not reset after deep sleep. Change |
| # the event to INCREASE. |
| if irq_key in self.KEY_SURVIVES_DS and event == self.DS_RESUME: |
| event = self.INCREASE |
| |
| if irq_key in self.STEP_INDEPENDENT_KEYS: |
| event = self.STEP_INDEPENDENT |
| |
| if event == self.INCREASE: |
| count -= irq_counts[step - 1] |
| |
| # Check that the count increase is within the expected value. |
| if event != self.START: |
| step_name = self.step_names[step].strip() |
| # TODO(b/153891388): raise actual error once the servo |
| # character loss issue is fixed. |
| if count < 0: |
| raise error.TestNAError('%s test found negative %s ' |
| 'count %r (likely due to servo ' |
| 'dropping characters)' % |
| (step, step_name, count)) |
| expected_range = self.get_expected_count(irq_key, |
| cr50_time_diff[step], 'idle' in step_name, |
| event == self.DS_RESUME) |
| |
| rv = self.check_increase(irq_key, name, count, |
| expected_range) |
| if rv: |
| logging.info('Unexpected count in %s test: %s %s', |
| state, step_name, rv) |
| step_errors[step].append(rv) |
| |
| irq_progress_str.append(' %2s %8d' % (event, count)) |
| |
| # Separate each step count with '|'. Add '|' to the start and end of |
| # the line. |
| irq_diff.append('|%s|' % '|'.join(irq_progress_str)) |
| |
| errors = {} |
| |
| ds_key = self.ARM if self.is_arm else '' |
| ds_key += state + self.DEEP_SLEEP_STEP_SUFFIX |
| expected_range = self.get_expected_count(ds_key, 0, False, False) |
| rv = self.check_increase(None, ds_key, events.count(self.DS_RESUME), |
| expected_range) |
| if rv: |
| logging.info('Unexpected count for %s %s', state, rv) |
| errors[ds_key] = rv |
| for i, step_error in enumerate(step_errors): |
| if step_error: |
| logging.error('Step %d errors:\n%s', i, |
| pprint.pformat(step_error)) |
| step = '%s step %d %s' % (state, i, self.step_names[i].strip()) |
| errors[step] = step_error |
| |
| if self._found_state: |
| state += ' (%s)' % self._found_state |
| logging.info('DIFF %s IRQ Counts - fwmp %s:\n%s', state, self.fwmp, |
| '\n'.join(irq_diff)) |
| if errors: |
| logging.info('ERRORS %s IRQ Counts:\n%s', state, |
| pprint.pformat(errors)) |
| self.run_errors.update(errors) |
| |
| |
| def ap_is_on_after_power_button_press(self): |
| """Returns True if the AP is on after pressing the power button""" |
| logging.info('Pressing the power button to turn on the AP') |
| self.servo.power_normal_press() |
| # Give the AP some time to turn on |
| time.sleep(self.gsc.SHORT_WAIT) |
| return self.gsc.ap_is_on() |
| |
| |
| def trigger_s0(self): |
| """Press the power button so the DUT will wake up.""" |
| if self.ap_is_on_after_power_button_press(): |
| return |
| # Try a second time to see if the AP comes up. |
| if not self.ap_is_on_after_power_button_press(): |
| raise error.TestError('Could not wake the AP using power button ' |
| '%s' % self.try_to_get_ap_state()) |
| logging.warning('Had to press power button twice to wake the AP') |
| |
| |
| def enter_state(self, state, from_state=''): |
| """Get the command to enter the power state""" |
| target_state = state |
| if state == 'S0': |
| if self.lid_closed: |
| logging.info('Open lid') |
| self.servo.set('lid_open', 'yes') |
| self.lid_closed = False |
| else: |
| self.trigger_s0() |
| # Suppress host command output, so it doesn't look like an interrupt |
| # storm. Set it whenever the system enters S0 to ensure the setting |
| # is restored if the EC enters hibernate. |
| time.sleep(2) |
| logging.info('Setting EC chan %x', self.CHAN_RESTRICTED) |
| self.ec.send_command('chan 0x%x' % self.CHAN_RESTRICTED) |
| else: |
| if state == 'lid_close': |
| self.lid_closed = True |
| logging.info('Open lid') |
| self.servo.set_nocheck('lid_open', 'no') |
| # It's difficult to tell the target state. Just log what it is. |
| target_state = None |
| elif state == 'default_suspend': |
| self.faft_client.system.run_shell_command( |
| 'powerd_dbus_suspend', False) |
| # It's difficult to tell the target state. Just log what it is. |
| target_state = None |
| elif state == 'S0ix': |
| self.enter_suspend(state) |
| elif state == 'S3': |
| self.enter_suspend(state) |
| elif state == 'G3': |
| self.faft_client.system.run_shell_command('poweroff', True) |
| |
| time.sleep(self.ENTER_STATE_WAIT) |
| # check state transition |
| if target_state and not self.wait_power_state( |
| state, self.POWER_STATE_CHECK_TRIES): |
| self._record_uart_capture() |
| from_state = 'from ' + from_state if from_state else '' |
| raise error.TestFail( |
| 'Platform failed to reach %s state %s. %s' % |
| (state, from_state, self.try_to_get_ap_state())) |
| power_state = self.get_power_state() |
| logging.info('%s: Entered %s', state, power_state) |
| # If the target state is unknown, track it for logging. |
| self._found_state = '' if target_state else power_state |
| |
| def enter_suspend(self, state): |
| """Enter S0ix or S3""" |
| # Different devices require different methods to enter S0ix or S3. The |
| # ones that deep_sleep_in_s0i3 must use power_manager, but other devices |
| # need to bypass power_manager. b/233898484 |
| if self.deep_sleep_in_s0i3: |
| self.set_suspend_to_idle(state == 'S0ix') |
| self.suspend() |
| else: |
| cmds = [] |
| if self.host.path_exists(self.MEM_SLEEP_PATH): |
| if state == 'S0ix': |
| cmds.append(self.MEM_SLEEP_S0IX) |
| else: |
| cmds.append(self.MEM_SLEEP_S3) |
| if state == 'S0ix': |
| cmds.append(self.POWER_STATE_S0IX) |
| else: |
| cmds.append(self.POWER_STATE_S3) |
| cmds = '; '.join(cmds) |
| logging.info('runing suspend commands %s', cmds) |
| self.faft_client.system.run_shell_command(cmds, False) |
| |
| def stage_irq_add(self, name): |
| """Add the current irq counts to the stored dictionary of irq info""" |
| logging.info('%s:', name.strip()) |
| irq_dict = self.get_irq_counts() |
| self.steps.append(irq_dict) |
| self.step_names.append(name.center(12)) |
| self.irqs.update(irq_dict.keys()) |
| |
| |
| def reset_irq_counts(self): |
| """Reset the test IRQ counts""" |
| self.steps = [] |
| self.step_names = [] |
| self.irqs = set(self.GSC_STATUS_DICT.keys()) |
| |
| |
| def run_transition(self, state): |
| """Enter the given power state and reenter s0 |
| |
| Enter the power state and return to S0. Wait long enough to ensure cr50 |
| will enter sleep mode, so we can verify that as well. |
| |
| Args: |
| state: the power state: S0ix, S3, or G3 |
| """ |
| self.reset_irq_counts() |
| |
| # Enter the given state |
| self.enter_state(state) |
| self.stage_irq_add('entered %s' % (self._found_state or state)) |
| |
| logging.info('waiting %d seconds', self.sleep_time) |
| time.sleep(self.sleep_time) |
| # Nothing is really happening. Cr50 should basically be idle during |
| # sleep_time. |
| self.stage_irq_add('idle in %s' % (self._found_state or state)) |
| |
| # If the AP is off, the system will resume through firmware and run |
| # TPM init. Verify it doesn't take too long to initialize the TPM. |
| self.check_tpm_init = not self.gsc.ap_is_on() |
| logging.info('Check TPM init: %s', |
| 'yes' if self.check_tpm_init else 'no') |
| |
| # Return to S0 |
| logging.info('entering S0 from %s', state) |
| self.enter_state('S0', state) |
| logging.info('entered S0 from %s', state) |
| self.stage_irq_add('entered S0') |
| |
| logging.info('waiting %d seconds', self.sleep_time) |
| time.sleep(self.sleep_time) |
| |
| self.stage_irq_add('idle in S0') |
| |
| # The DUT has already been up for 60 seconds. It should be pingable. |
| if not self.host.ping_wait_up(self.faft_config.delay_reboot_to_ping): |
| raise error.TestFail('Unable to ping dut after %s resume %s' % |
| (state, self.try_to_get_ap_state())) |
| |
| self.print_fwmp('%s resume' % state) |
| |
| # The AP has to be on and have cbmem support to get `cbmem -t`. |
| if self.check_tpm_init and self.gsc.ap_is_on(): |
| self.steps[-1][self.KEY_TPM_INIT] = self.get_tpm_init_time() |
| logging.info('Resume from %s tpm initialized in %dus', state, |
| self.steps[-1][self.KEY_TPM_INIT]) |
| |
| def print_fwmp(self, desc, initialized=True, check_pcr=True): |
| """Print FWMP and PCR0 state for debugging.""" |
| result = self.run_fwmp_cmd( |
| '--action=get_firmware_management_parameters', |
| ignore_status=True) |
| logging.info('FWMP %r exp(%s) act: %s', desc, self.fwmp, |
| result.stdout if result else None) |
| if result.exit_status and self.fwmp and initialized: |
| raise error.TestFail('Error getting FWMP: %r' % result) |
| result = self.host.run('trunks_client --read_pcr --index=0') |
| self._record_uart_capture() |
| if not result: |
| return |
| pcr = result.stdout.split(':')[-1].strip() |
| logging.info('PCR %r: %r', desc, pcr) |
| if check_pcr and pcr not in self.VALID_PCR0_VALUES: |
| raise error.TestFail('%s: invalid pcr0 value. %s not found in %r' % |
| (desc, pcr, self.VALID_PCR0_VALUES)) |
| |
| def verify_state(self, state): |
| """Verify cr50 behavior while running through the power state""" |
| |
| try: |
| self.run_transition(state) |
| finally: |
| # reset the system to S0 no matter what happens |
| self.trigger_s0() |
| # Reenable EC chan output. |
| time.sleep(2) |
| logging.info('Setting EC chan %x', self.CHAN_ALL) |
| self.ec.send_command('chan 0x%x' % self.CHAN_ALL) |
| |
| # Check that the progress of the irq counts seems reasonable |
| self.check_for_errors(state) |
| |
| def verify_sleep_state(self, state): |
| """Verify cr50 behavior while running through S0ix or S3""" |
| |
| boot_id = self.host.get_boot_id() |
| self.mount_power_config() |
| self.verify_state(state) |
| |
| # umount_power_config will fail if the DUT reboots after |
| # mount_power_config is called. Detect reboots and raise a clear error |
| # message when one occurs. |
| if boot_id == self.host.get_boot_id(): |
| self.umount_power_config() |
| else: |
| raise error.TestFail('DUT rebooted during %s test' % state) |
| |
| def is_arm_family(self): |
| """Returns True if the DUT is an ARM device.""" |
| arch = self.host.run('arch').stdout.strip() |
| return arch in ['aarch64', 'armv7l'] |
| |
| |
| def run_through_power_states(self): |
| """Go through S0ix, S3, and G3. Verify there are no interrupt storms""" |
| self._try_to_bring_dut_up() |
| self.run_errors = {} |
| self.ccd_str = 'ccd ' + ('enabled' if self.ccd_enabled else 'disabled') |
| logging.info('Running through states with %s and fwmp(%s)', |
| self.ccd_str, self.fwmp) |
| |
| self.gsc.get_ccdstate() |
| if not self.gsc.get_sleepmask() and self.ccd_enabled: |
| logging.info('Sleepmask is not keeping cr50 up with ccd enabled') |
| self.all_errors[self.ccd_str] = 'usb is not active with ccd enabled' |
| return |
| |
| self.print_fwmp('start %s' % self.ccd_str) |
| |
| # Enter G3 first. All boards support it and tpm initialization will take |
| # place. Entering G3 first ensures the tpm initialization data will be |
| # from this test run. |
| self.verify_state('G3') |
| |
| if self.default_suspend: |
| self.verify_state('default_suspend') |
| |
| if self.check_ec_capability(["lid"]): |
| try: |
| self.verify_state('lid_close') |
| finally: |
| # Make sure it ends with the lid open. |
| self.servo.set('lid_open', 'yes') |
| |
| # Manually enter the non-default power states. |
| if self.manual_suspend: |
| try: |
| if self.s0ix_supported: |
| self.verify_sleep_state('S0ix') |
| |
| if self.s3_supported: |
| # Entering S3 after S0ix might not work. Reboot the DUT |
| # for S3 test. |
| logging.info('Rebooting for S3 test') |
| self.host.reboot() |
| |
| if self.s3_supported: |
| self.verify_sleep_state('S3') |
| except Exception as e: |
| self._try_to_bring_dut_up() |
| logging.info('Caught exception %r', e) |
| self.run_errors['Caught Exception'] = str(e) |
| |
| if self.run_errors: |
| self.all_errors[self.ccd_str] = self.run_errors |
| |
| |
| def init_fwmp(self): |
| """Create the FWMP.""" |
| self.fast_ccd_open(True) |
| self.gsc.set_ccd_level('lock') |
| self.clear_tpm_owner_and_fwmp() |
| |
| self.print_fwmp('cleared tpm owner. Not initialized.', |
| initialized=False) |
| client_at = autotest.Autotest(self.host) |
| if self.fwmp: |
| logging.info('Setting FWMP flags to %s', self.fwmp) |
| client_at.run_test('firmware_SetFWMP', |
| flags=self.fwmp, |
| fwmp_cleared=True, |
| check_client_result=True) |
| else: |
| client_at.run_test('login_LoginSuccess') |
| |
| self.print_fwmp('init') |
| |
| def cleanup(self): |
| """Clear the fwmp.""" |
| try: |
| self._try_to_bring_dut_up() |
| self.clear_tpm_owner_and_fwmp() |
| self.gsc.ccd_reset_and_wipe_tpm() |
| finally: |
| super(firmware_Cr50DeviceState, self).cleanup() |
| |
| def is_s3_supported(self): |
| """ |
| The S3 detection method changes depending on the kernel version. |
| Newer kernels have a '/sys/power/mem_sleep' file that can be queried. |
| If 'deep' exists within the 'mem_sleep', then S3 is supported. |
| Older kernels do not have this file. Instead, one must look for 'mem' |
| in '/sys/power/state'. |
| See '{kernel-root}/Documentation/ABI/testing/sysfs-power' for more |
| information. |
| """ |
| supported = False |
| if self.host.path_exists(self.MEM_SLEEP_PATH): |
| supported = not self.host.run(f'grep -q deep ' |
| f'{self.MEM_SLEEP_PATH}', |
| ignore_status=True).exit_status |
| else: |
| # Assumes kernel cli pararm: relative_sleep_states==0 |
| supported = not self.host.run(f'grep -q mem ' |
| f'{self.POWER_STATE_PATH}', |
| ignore_status=True).exit_status |
| return supported |
| |
| def run_once(self, host): |
| """Go through S0ix, S3, and G3. Verify there are no interrupt storms""" |
| |
| self.all_errors = {} |
| self.host = host |
| self.lid_closed = False |
| self.is_arm = self.is_arm_family() |
| supports_dts_control = self.gsc.servo_dts_mode_is_valid() |
| |
| if supports_dts_control: |
| if self.test_ccd_en: |
| logging.info('Enable CCD for test') |
| self.gsc.ccd_enable(raise_error=True) |
| else: |
| logging.info('Disable CCD for test') |
| self.gsc.ccd_disable(raise_error=True) |
| |
| self.ccd_enabled = self.gsc.ccd_is_enabled() |
| |
| # If a ccd state was requested and the ccd state doesn't match, fail |
| # with test na. |
| if self.test_ccd_en != None and self.test_ccd_en != self.ccd_enabled: |
| raise error.TestNAError('Setup does not support dts mode. Cannot ' |
| 'verify state with ccd %sabled' % |
| ('en' if self.test_ccd_en else 'dis')) |
| # Check if the device supports S0ix. |
| self.s0ix_supported = not self.host.run( |
| 'check_powerd_config --suspend_to_idle', |
| ignore_status=True).exit_status |
| # Check the faft config to see if it overrides the command check. |
| skip_s0ix = getattr(self.faft_config, 's0ix_override_to_unsupported', |
| False) |
| if skip_s0ix and self.s0ix_supported: |
| logging.info('S0ix faft_config override') |
| self.s0ix_supported = False |
| logging.info('arm: %s', 'yes' if self.is_arm else 'no') |
| logging.info('dts: %ssupported', '' if supports_dts_control else 'un') |
| logging.info('ccd: %sabled', 'en' if self.ccd_enabled else 'dis') |
| logging.info('S0ix: %s', self.s0ix_supported) |
| # Check if the device supports S3. |
| self.s3_supported = self.is_s3_supported() |
| # Check the faft config to see if it overrides the command check. |
| skip_s3 = getattr(self.faft_config, 's3_override_to_unsupported', False) |
| if skip_s3 and self.s3_supported: |
| logging.info('S3 faft_config override') |
| self.s3_supported = False |
| logging.info('S3: %s', self.s3_supported) |
| |
| if (not self.default_suspend and not self.s3_supported and |
| not self.s0ix_supported): |
| raise error.TestNAError('Manual suspend is unsupported.') |
| |
| if self.pcr_extend: |
| self.fast_ccd_open(True) |
| # Extend pcr0. The index must be 0. The value doesn't matter. |
| self.host.run('trunks_client --extend_pcr --index=0 --value=7') |
| # Manually send TPM2_Shutdown(STATE) to ensure saving the PCR0 |
| # value in nvmem |
| self.host.run( |
| 'trunks_send --raw 80 01 00 00 00 0c 00 00 01 45 00 01') |
| self.print_fwmp('Extended PCR0', |
| initialized=False, |
| check_pcr=False) |
| self.host.reboot() |
| self.print_fwmp('PCR0 after reboot', initialized=False) |
| self.init_fwmp() |
| |
| # The sleep times should be sufficient. Don't wait 3 minutes for |
| # commands to timeout. |
| self.host.set_default_run_timeout(60) |
| self.run_through_power_states() |
| |
| self.trigger_s0() |
| if self.all_errors: |
| raise error.TestFail('Unexpected Device State (fwmp %r): %s' % |
| (self.fwmp, self.all_errors)) |