You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1248 lines
49 KiB
1248 lines
49 KiB
# Lint as: python2, python3
|
|
# Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import functools
|
|
import logging
|
|
import pprint
|
|
import re
|
|
import six
|
|
from six.moves import range
|
|
import time
|
|
|
|
from autotest_lib.client.bin import utils
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib.cros import cr50_utils
|
|
from autotest_lib.server.cros.servo import chrome_ec
|
|
from autotest_lib.server.cros.servo import servo
|
|
|
|
|
|
def dts_control_command(func):
|
|
"""For methods that should only run when dts mode control is supported."""
|
|
@functools.wraps(func)
|
|
def wrapper(instance, *args, **kwargs):
|
|
"""Ignore those functions if dts mode control is not supported."""
|
|
if instance._servo.dts_mode_is_valid():
|
|
return func(instance, *args, **kwargs)
|
|
logging.info('Servo setup does not support DTS mode. ignoring %s',
|
|
func.__name__)
|
|
return wrapper
|
|
|
|
|
|
class ChromeCr50(chrome_ec.ChromeConsole):
|
|
"""Manages control of a Chrome Cr50.
|
|
|
|
We control the Chrome Cr50 via the console of a Servo board. Chrome Cr50
|
|
provides many interfaces to set and get its behavior via console commands.
|
|
This class is to abstract these interfaces.
|
|
"""
|
|
PROD_RW_KEYIDS = ['0x87b73b67', '0xde88588d']
|
|
PROD_RO_KEYIDS = ['0xaa66150f']
|
|
OPEN = 'open'
|
|
UNLOCK = 'unlock'
|
|
LOCK = 'lock'
|
|
# The amount of time you need to show physical presence.
|
|
PP_SHORT = 15
|
|
PP_LONG = 300
|
|
CCD_PASSWORD_RATE_LIMIT = 3
|
|
IDLE_COUNT = 'count: (\d+)\s'
|
|
SHORT_WAIT = 3
|
|
# The version has four groups: the partition, the header version, debug
|
|
# descriptor and then version string.
|
|
# There are two partitions A and B. The active partition is marked with a
|
|
# '*'. If it is a debug image '/DBG' is added to the version string. If the
|
|
# image has been corrupted, the version information will be replaced with
|
|
# 'Error'.
|
|
# So the output may look something like this.
|
|
# RW_A: 0.0.21/cr50_v1.1.6133-fd788b
|
|
# RW_B: * 0.0.22/DBG/cr50_v1.1.6138-b9f0b1d
|
|
# Or like this if the region was corrupted.
|
|
# RW_A: * 0.0.21/cr50_v1.1.6133-fd788b
|
|
# RW_B: Error
|
|
VERSION_FORMAT = '\nRW_(A|B): +%s +(\d+\.\d+\.\d+|Error)(/DBG)?(\S+)?\s'
|
|
INACTIVE_VERSION = VERSION_FORMAT % ''
|
|
ACTIVE_VERSION = VERSION_FORMAT % '\*'
|
|
# Following lines of the version output may print the image board id
|
|
# information. eg.
|
|
# BID A: 5a5a4146:ffffffff:00007f00 Yes
|
|
# BID B: 00000000:00000000:00000000 Yes
|
|
# Use the first group from ACTIVE_VERSION to match the active board id
|
|
# partition.
|
|
BID_ERROR = 'read_board_id: failed'
|
|
BID_FORMAT = ':\s+[a-f0-9:]{26} '
|
|
ACTIVE_BID = r'%s.*(\1%s|%s.*>)' % (ACTIVE_VERSION, BID_FORMAT,
|
|
BID_ERROR)
|
|
WAKE_CHAR = '\n\n\n\n'
|
|
WAKE_RESPONSE = ['(>|Console is enabled)']
|
|
START_UNLOCK_TIMEOUT = 20
|
|
GETTIME = ['= (\S+)']
|
|
FWMP_LOCKED_PROD = ["Managed device console can't be unlocked"]
|
|
FWMP_LOCKED_DBG = ['Ignoring FWMP unlock setting']
|
|
MAX_RETRY_COUNT = 5
|
|
CCDSTATE_MAX_RETRY_COUNT = 20
|
|
START_STR = ['((Havn|UART).*Console is enabled;)']
|
|
REBOOT_DELAY_WITH_CCD = 60
|
|
REBOOT_DELAY_WITH_FLEX = 3
|
|
ON_STRINGS = ['enable', 'enabled', 'on']
|
|
CONSERVATIVE_CCD_WAIT = 10
|
|
CCD_SHORT_PRESSES = 5
|
|
CAP_IS_ACCESSIBLE = 0
|
|
CAP_SETTING = 1
|
|
CAP_REQ = 2
|
|
GET_CAP_TRIES = 20
|
|
# Regex to match the valid capability settings.
|
|
CAP_STATES = '(Always|Default|IfOpened|UnlessLocked)'
|
|
# List of all cr50 ccd capabilities. Same order of 'ccd' output
|
|
CAP_NAMES = [
|
|
'UartGscRxAPTx', 'UartGscTxAPRx', 'UartGscRxECTx', 'UartGscTxECRx',
|
|
'FlashAP', 'FlashEC', 'OverrideWP', 'RebootECAP', 'GscFullConsole',
|
|
'UnlockNoReboot', 'UnlockNoShortPP', 'OpenNoTPMWipe', 'OpenNoLongPP',
|
|
'BatteryBypassPP', 'UpdateNoTPMWipe', 'I2C', 'FlashRead',
|
|
'OpenNoDevMode', 'OpenFromUSB', 'OverrideBatt'
|
|
]
|
|
# There are two capability formats. Match both.
|
|
# UartGscRxECTx Y 3=IfOpened
|
|
# or
|
|
# UartGscRxECTx Y 0=Default (Always)
|
|
# Make sure the last word is at the end of the line. The next line will
|
|
# start with some whitespace, so account for that too.
|
|
CAP_FORMAT = '\s+(Y|-) \d\=%s( \(%s\))?[\r\n]+\s*' % (CAP_STATES,
|
|
CAP_STATES)
|
|
# Be as specific as possible with the 'ccd' output, so the test will notice
|
|
# missing characters and retry getting the output. Name each group, so the
|
|
# test can extract the field information into a dictionary.
|
|
# CCD_FIELDS is used to order the regex when searching for multiple fields
|
|
CCD_FIELDS = ['State', 'Password', 'Flags', 'Capabilities', 'TPM']
|
|
# CCD_FORMAT has the field names as keys and the expected output as the
|
|
# value.
|
|
CCD_FORMAT = {
|
|
'State' : '(State: (?P<State>Opened|Locked|Unlocked))',
|
|
'Password' : '(Password: (?P<Password>set|none))',
|
|
'Flags' : '(Flags: (?P<Flags>\S*))',
|
|
'Capabilities' : '(Capabilities:.*(?P<Capabilities>%s))' %
|
|
(CAP_FORMAT.join(CAP_NAMES) + CAP_FORMAT),
|
|
'TPM' : '(TPM:(?P<TPM>[ \S]*)\r)',
|
|
}
|
|
|
|
# CR50 Board Properties as defined in platform/ec/board/cr50/scratch-reg1.h
|
|
BOARD_PROP = {
|
|
'BOARD_SLAVE_CONFIG_SPI' : 1 << 0,
|
|
'BOARD_SLAVE_CONFIG_I2C' : 1 << 1,
|
|
'BOARD_NEEDS_SYS_RST_PULL_UP' : 1 << 5,
|
|
'BOARD_USE_PLT_RESET' : 1 << 6,
|
|
'BOARD_WP_ASSERTED' : 1 << 8,
|
|
'BOARD_FORCING_WP' : 1 << 9,
|
|
'BOARD_NO_RO_UART' : 1 << 10,
|
|
'BOARD_CCD_STATE_MASK' : 3 << 11,
|
|
'BOARD_DEEP_SLEEP_DISABLED' : 1 << 13,
|
|
'BOARD_DETECT_AP_WITH_UART' : 1 << 14,
|
|
'BOARD_ITE_EC_SYNC_NEEDED' : 1 << 15,
|
|
'BOARD_WP_DISABLE_DELAY' : 1 << 16,
|
|
'BOARD_CLOSED_SOURCE_SET1' : 1 << 17,
|
|
'BOARD_CLOSED_LOOP_RESET' : 1 << 18,
|
|
'BOARD_NO_INA_SUPPORT' : 1 << 19,
|
|
'BOARD_ALLOW_CHANGE_TPM_MODE' : 1 << 20,
|
|
'BOARD_EC_CR50_COMM_SUPPORT' : 1 << 21,
|
|
'BOARD_CCD_REC_LID_PIN_DIOA1' : 0x01 << 22,
|
|
'BOARD_CCD_REC_LID_PIN_DIOA9' : 0x02 << 22,
|
|
'BOARD_CCD_REC_LID_PIN_DIOA12': 0x03 << 22,
|
|
}
|
|
|
|
# CR50 reset flags as defined in platform ec_commands.h. These are only the
|
|
# flags used by cr50.
|
|
RESET_FLAGS = {
|
|
'RESET_FLAG_OTHER' : 1 << 0,
|
|
'RESET_FLAG_BROWNOUT' : 1 << 2,
|
|
'RESET_FLAG_POWER_ON' : 1 << 3,
|
|
'RESET_FLAG_SOFT' : 1 << 5,
|
|
'RESET_FLAG_HIBERNATE' : 1 << 6,
|
|
'RESET_FLAG_RTC_ALARM' : 3 << 7,
|
|
'RESET_FLAG_WAKE_PIN' : 1 << 8,
|
|
'RESET_FLAG_HARD' : 1 << 11,
|
|
'RESET_FLAG_USB_RESUME' : 1 << 14,
|
|
'RESET_FLAG_RDD' : 1 << 15,
|
|
'RESET_FLAG_RBOX' : 1 << 16,
|
|
'RESET_FLAG_SECURITY' : 1 << 17,
|
|
}
|
|
|
|
def __init__(self, servo, faft_config):
|
|
"""Initializes a ChromeCr50 object.
|
|
|
|
@param servo: A servo object.
|
|
@param faft_config: A faft config object.
|
|
"""
|
|
super(ChromeCr50, self).__init__(servo, 'cr50_uart')
|
|
self.faft_config = faft_config
|
|
|
|
|
|
def wake_cr50(self):
|
|
"""Wake up cr50 by sending some linebreaks and wait for the response"""
|
|
for i in range(self.MAX_RETRY_COUNT):
|
|
try:
|
|
rv = super(ChromeCr50, self).send_command_get_output(
|
|
self.WAKE_CHAR, self.WAKE_RESPONSE)
|
|
logging.debug('wake result %r', rv)
|
|
return
|
|
except servo.ResponsiveConsoleError as e:
|
|
logging.info("Console responsive, but couldn't match wake "
|
|
"response %s", e)
|
|
raise servo.ResponsiveConsoleError('Unable to wake cr50')
|
|
|
|
|
|
def send_command(self, commands):
|
|
"""Send command through UART.
|
|
|
|
Cr50 will drop characters input to the UART when it resumes from sleep.
|
|
If servo is not using ccd, send some dummy characters before sending the
|
|
real command to make sure cr50 is awake.
|
|
|
|
@param commands: the command string to send to cr50
|
|
"""
|
|
if self._servo.main_device_is_flex():
|
|
self.wake_cr50()
|
|
super(ChromeCr50, self).send_command(commands)
|
|
|
|
|
|
def set_cap(self, cap, setting):
|
|
"""Set the capability to setting
|
|
|
|
@param cap: The capability string
|
|
@param setting: The setting to set the capability to.
|
|
"""
|
|
self.set_caps({ cap : setting })
|
|
|
|
|
|
def set_caps(self, cap_dict):
|
|
"""Use cap_dict to set all the cap values
|
|
|
|
Set all of the capabilities in cap_dict to the correct config.
|
|
|
|
@param cap_dict: A dictionary with the capability as key and the desired
|
|
setting as values
|
|
"""
|
|
for cap, config in six.iteritems(cap_dict):
|
|
self.send_command('ccd set %s %s' % (cap, config))
|
|
current_cap_settings = self.get_cap_dict(info=self.CAP_SETTING)
|
|
for cap, config in six.iteritems(cap_dict):
|
|
if (current_cap_settings[cap].lower() !=
|
|
config.lower()):
|
|
raise error.TestFail('Failed to set %s to %s' % (cap, config))
|
|
|
|
|
|
def get_cap_overview(self, cap_dict):
|
|
"""Get a basic overview of the capability dictionary
|
|
|
|
If all capabilities are set to Default, ccd has been reset to default.
|
|
If all capabilities are set to Always, ccd is in factory mode.
|
|
|
|
@param cap_dict: A dictionary of the capability settings
|
|
@return: A tuple of the capability overview (in factory mode, is reset)
|
|
"""
|
|
in_factory_mode = True
|
|
is_reset = True
|
|
for cap, cap_info in six.iteritems(cap_dict):
|
|
cap_setting = cap_info[self.CAP_SETTING]
|
|
if cap_setting != 'Always':
|
|
in_factory_mode = False
|
|
if cap_setting != 'Default':
|
|
is_reset = False
|
|
return in_factory_mode, is_reset
|
|
|
|
|
|
def password_is_reset(self):
|
|
"""Returns True if the password is cleared"""
|
|
return self.get_ccd_info('Password') == 'none'
|
|
|
|
|
|
def ccd_is_reset(self):
|
|
"""Returns True if the ccd is reset
|
|
|
|
The password must be cleared, write protect and battery presence must
|
|
follow battery presence, and all capabilities must be Always
|
|
"""
|
|
return (self.password_is_reset() and self.wp_is_reset() and
|
|
self.batt_pres_is_reset() and
|
|
self.get_cap_overview(self.get_cap_dict())[1])
|
|
|
|
|
|
def wp_is_reset(self):
|
|
"""Returns True if wp is reset to follow batt pres at all times"""
|
|
follow_batt_pres, _, follow_batt_pres_atboot, _ = self.get_wp_state()
|
|
return follow_batt_pres and follow_batt_pres_atboot
|
|
|
|
|
|
def get_wp_state(self):
|
|
"""Get the current write protect and atboot state
|
|
|
|
The atboot setting cannot really be determined now if it is set to
|
|
follow battery presence. It is likely to remain the same after reboot,
|
|
but who knows. If the third element of the tuple is True, the last
|
|
element will not be that useful
|
|
|
|
@return: a tuple with the current write protect state
|
|
(True if current state is to follow batt presence,
|
|
True if write protect is enabled,
|
|
True if current state is to follow batt presence atboot,
|
|
True if write protect is enabled atboot)
|
|
"""
|
|
rv = self.send_command_retry_get_output('wp',
|
|
['Flash WP: (forced )?(enabled|disabled).*at boot: (forced )?'
|
|
'(follow|enabled|disabled)'], safe=True)[0]
|
|
_, forced, enabled, _, atboot = rv
|
|
logging.debug(rv)
|
|
return (not forced, enabled =='enabled',
|
|
atboot == 'follow', atboot == 'enabled')
|
|
|
|
|
|
def in_dev_mode(self):
|
|
"""Return True if cr50 thinks the device is in dev mode"""
|
|
return 'dev_mode' in self.get_ccd_info('TPM')
|
|
|
|
|
|
def get_ccd_info(self, field=None):
|
|
"""Get the current ccd state.
|
|
|
|
Take the output of 'ccd' and convert it to a dictionary.
|
|
|
|
@param: the ccd info param to get or None to get the full ccd output
|
|
dictionary.
|
|
@return: the field value or a dictionary with the ccd field name as the
|
|
key and the setting as the value.
|
|
"""
|
|
|
|
if field:
|
|
match_value = self.CCD_FORMAT[field]
|
|
else:
|
|
values = [ self.CCD_FORMAT[field] for field in self.CCD_FIELDS ]
|
|
match_value = '.*'.join(values)
|
|
matched_output = None
|
|
original_timeout = float(self._servo.get('cr50_uart_timeout'))
|
|
# Change the console timeout to 10s, it may take longer than 3s to read
|
|
# ccd info
|
|
self._servo.set_nocheck('cr50_uart_timeout', self.CONSERVATIVE_CCD_WAIT)
|
|
for i in range(self.GET_CAP_TRIES):
|
|
try:
|
|
# If some ccd output is dropped and the output doesn't match the
|
|
# expected ccd output format, send_command_get_output will wait the
|
|
# full CONSERVATIVE_CCD_WAIT even though ccd is done printing. Use
|
|
# re to search the command output instead of
|
|
# send_safe_command_get_output, so we don't have to wait the full
|
|
# timeout if output is dropped.
|
|
rv = self.send_command_retry_get_output('ccd', ['ccd.*>'],
|
|
safe=True)[0]
|
|
matched_output = re.search(match_value, rv, re.DOTALL)
|
|
if matched_output:
|
|
break
|
|
logging.info('try %d: could not match ccd output %s', i, rv)
|
|
except Exception as e:
|
|
logging.info('try %d got error %s', i, str(e))
|
|
|
|
self._servo.set_nocheck('cr50_uart_timeout', original_timeout)
|
|
if not matched_output:
|
|
raise error.TestFail('Could not get ccd output')
|
|
matched_dict = matched_output.groupdict()
|
|
logging.info('Current CCD settings:\n%s', pprint.pformat(matched_dict))
|
|
if field:
|
|
return matched_dict.get(field)
|
|
return matched_dict
|
|
|
|
|
|
def get_cap(self, cap):
|
|
"""Returns the capabilitiy from the capability dictionary"""
|
|
return self.get_cap_dict()[cap]
|
|
|
|
|
|
def get_cap_dict(self, info=None):
|
|
"""Get the current ccd capability settings.
|
|
|
|
The capability may be using the 'Default' setting. That doesn't say much
|
|
about the ccd state required to use the capability. Return all ccd
|
|
information in the cap_dict
|
|
[is accessible, setting, requirement]
|
|
|
|
@param info: Only fill the cap_dict with the requested information:
|
|
CAP_IS_ACCESSIBLE, CAP_SETTING, or CAP_REQ
|
|
@return: A dictionary with the capability as the key a list of the
|
|
current settings as the value [is_accessible, setting,
|
|
requirement]
|
|
"""
|
|
# Add whitespace at the end, so we can still match the last line.
|
|
cap_info_str = self.get_ccd_info('Capabilities') + '\r\n'
|
|
cap_settings = re.findall('(\S+) ' + self.CAP_FORMAT,
|
|
cap_info_str)
|
|
caps = {}
|
|
for cap, accessible, setting, _, required in cap_settings:
|
|
# If there's only 1 value after =, then the setting is the
|
|
# requirement.
|
|
if not required:
|
|
required = setting
|
|
cap_info = [accessible == 'Y', setting, required]
|
|
if info is not None:
|
|
caps[cap] = cap_info[info]
|
|
else:
|
|
caps[cap] = cap_info
|
|
logging.debug(pprint.pformat(caps))
|
|
return caps
|
|
|
|
|
|
def send_command_get_output(self, command, regexp_list):
|
|
"""Send command through UART and wait for response.
|
|
|
|
Cr50 will drop characters input to the UART when it resumes from sleep.
|
|
If servo is not using ccd, send some dummy characters before sending the
|
|
real command to make sure cr50 is awake.
|
|
|
|
@param command: the command to send
|
|
@param regexp_list: The list of regular expressions to match in the
|
|
command output
|
|
@return: A list of matched output
|
|
"""
|
|
if self._servo.main_device_is_flex():
|
|
self.wake_cr50()
|
|
|
|
# We have started prepending '\n' to separate cr50 console junk from
|
|
# the real command. If someone is just searching for .*>, then they will
|
|
# only get the output from the first '\n' we added. Raise an error to
|
|
# change the test to look for something more specific ex command.*>.
|
|
# cr50 will print the command in the output, so that is an easy way to
|
|
# modify '.*>' to match the real command output.
|
|
if '.*>' in regexp_list:
|
|
raise error.TestError('Send more specific regexp %r %r' % (command,
|
|
regexp_list))
|
|
|
|
# prepend \n to separate the command from any junk that may have been
|
|
# sent to the cr50 uart.
|
|
command = '\n' + command
|
|
return super(ChromeCr50, self).send_command_get_output(command,
|
|
regexp_list)
|
|
|
|
|
|
def send_safe_command_get_output(self, command, regexp_list,
|
|
channel_mask=0x1):
|
|
"""Restrict the console channels while sending console commands.
|
|
|
|
@param command: the command to send
|
|
@param regexp_list: The list of regular expressions to match in the
|
|
command output
|
|
@param channel_mask: The mask to pass to 'chan' prior to running the
|
|
command, indicating which channels should remain
|
|
enabled (0x1 is command output)
|
|
@return: A list of matched output
|
|
"""
|
|
self.send_command('chan save')
|
|
self.send_command('chan 0x%x' % channel_mask)
|
|
try:
|
|
rv = self.send_command_get_output(command, regexp_list)
|
|
finally:
|
|
self.send_command('chan restore')
|
|
return rv
|
|
|
|
|
|
def send_command_retry_get_output(self, command, regexp_list, safe=False,
|
|
compare_output=False, retries=MAX_RETRY_COUNT):
|
|
"""Retry the command 5 times if you get a timeout or drop some output
|
|
|
|
|
|
@param command: the command string
|
|
@param regexp_list: the regex to search for
|
|
@param safe: use send_safe_command_get_output if True otherwise use
|
|
send_command_get_output
|
|
@param compare_output: look for reproducible output
|
|
"""
|
|
send_command = (self.send_safe_command_get_output if safe else
|
|
self.send_command_get_output)
|
|
err = 'no consistent output' if compare_output else 'unknown'
|
|
past_rv = []
|
|
for i in range(retries):
|
|
try:
|
|
rv = send_command(command, regexp_list)
|
|
if not compare_output or rv in past_rv:
|
|
return rv
|
|
if past_rv:
|
|
logging.debug('%d %s not in %s', i, rv, past_rv)
|
|
past_rv.append(rv)
|
|
except Exception as e:
|
|
err = e
|
|
logging.info('attempt %d %r: %s %s', i, command, type(e),
|
|
str(e))
|
|
if compare_output:
|
|
logging.info('No consistent output for %r %s', command,
|
|
pprint.pformat(past_rv))
|
|
raise error.TestError('Issue sending %r command: %r' % (command, err))
|
|
|
|
|
|
def get_deep_sleep_count(self):
|
|
"""Get the deep sleep count from the idle task"""
|
|
result = self.send_command_retry_get_output('idle', [self.IDLE_COUNT],
|
|
safe=True)
|
|
return int(result[0][1])
|
|
|
|
|
|
def clear_deep_sleep_count(self):
|
|
"""Clear the deep sleep count"""
|
|
self.send_command('idle c')
|
|
if self.get_deep_sleep_count():
|
|
raise error.TestFail("Could not clear deep sleep count")
|
|
|
|
|
|
def get_board_properties(self):
|
|
"""Get information from the version command"""
|
|
rv = self.send_command_retry_get_output('brdprop',
|
|
['properties = (\S+)\s'], safe=True)
|
|
return int(rv[0][1], 16)
|
|
|
|
|
|
def uses_board_property(self, prop_name):
|
|
"""Returns 1 if the given property is set, or 0 otherwise
|
|
|
|
@param prop_name: a property name in string type.
|
|
"""
|
|
brdprop = self.get_board_properties()
|
|
prop = self.BOARD_PROP[prop_name]
|
|
return (brdprop & prop) == prop
|
|
|
|
|
|
def has_command(self, cmd):
|
|
"""Returns 1 if cr50 has the command 0 if it doesn't"""
|
|
try:
|
|
self.send_safe_command_get_output('help', [cmd])
|
|
except:
|
|
logging.info("Image does not include '%s' command", cmd)
|
|
return 0
|
|
return 1
|
|
|
|
|
|
def reboot(self):
|
|
"""Reboot Cr50 and wait for cr50 to reset"""
|
|
self.wait_for_reboot(cmd='reboot', timeout=10)
|
|
|
|
|
|
def _uart_wait_for_reboot(self, cmd='\n', timeout=60):
|
|
"""Use uart to wait for cr50 to reboot.
|
|
|
|
If a command is given run it and wait for cr50 to reboot. Monitor
|
|
the cr50 uart to detect the reset. Wait up to timeout seconds
|
|
for the reset.
|
|
|
|
@param cmd: the command to run to reset cr50.
|
|
@param timeout: seconds to wait to detect the reboot.
|
|
"""
|
|
original_timeout = float(self._servo.get('cr50_uart_timeout'))
|
|
# Change the console timeout to timeout, so we wait at least that long
|
|
# for cr50 to print the start string.
|
|
self._servo.set_nocheck('cr50_uart_timeout', timeout)
|
|
try:
|
|
self.send_command_get_output(cmd, self.START_STR)
|
|
logging.debug('Detected cr50 reboot')
|
|
except error.TestFail as e:
|
|
logging.debug('Failed to detect cr50 reboot')
|
|
# Reset the timeout.
|
|
self._servo.set_nocheck('cr50_uart_timeout', original_timeout)
|
|
|
|
|
|
def wait_for_reboot(self, cmd='\n', timeout=60):
|
|
"""Wait for cr50 to reboot
|
|
|
|
Run the cr50 reset command. Wait for cr50 to reset and reenable ccd if
|
|
necessary.
|
|
|
|
@param cmd: the command to run to reset cr50.
|
|
@param timeout: seconds to wait to detect the reboot.
|
|
"""
|
|
logging.info('Wait up to %s seconds for reboot (%s)', timeout,
|
|
cmd.strip())
|
|
if self._servo.main_device_is_ccd():
|
|
self.send_command(cmd)
|
|
# Cr50 USB is reset when it reboots. Wait for the CCD connection to
|
|
# go down to detect the reboot.
|
|
self.wait_for_ccd_disable(timeout, raise_error=False)
|
|
self.ccd_enable()
|
|
else:
|
|
self._uart_wait_for_reboot(cmd, timeout)
|
|
|
|
# On most devices, a Cr50 reset will cause an AP reset. Force this to
|
|
# happen on devices where the AP is left down.
|
|
if not self.faft_config.ap_up_after_cr50_reboot:
|
|
# Reset the DUT a few seconds after cr50 reboot.
|
|
time.sleep(self.SHORT_WAIT)
|
|
logging.info('Resetting DUT after Cr50 reset')
|
|
self._servo.get_power_state_controller().reset()
|
|
|
|
|
|
def set_board_id(self, chip_bid, chip_flags):
|
|
"""Set the chip board id type and flags."""
|
|
self.send_command('bid 0x%x 0x%x' % (chip_bid, chip_flags))
|
|
|
|
|
|
def get_board_id(self):
|
|
"""Get the chip board id type and flags.
|
|
|
|
bid_type_inv will be '' if the bid output doesn't show it. If no board
|
|
id type inv is shown, then board id is erased will just check the type
|
|
and flags.
|
|
|
|
@returns a tuple (A string of bid_type:bid_type_inv:bid_flags,
|
|
True if board id is erased)
|
|
"""
|
|
bid = self.send_command_retry_get_output('bid',
|
|
['Board ID: (\S{8}):?(|\S{8}), flags (\S{8})\s'],
|
|
safe=True)[0][1:]
|
|
bid_str = ':'.join(bid)
|
|
bid_is_erased = set(bid).issubset({'', 'ffffffff'})
|
|
logging.info('chip board id: %s', bid_str)
|
|
logging.info('chip board id is erased: %s',
|
|
'yes' if bid_is_erased else 'no')
|
|
return bid_str, bid_is_erased
|
|
|
|
|
|
def eraseflashinfo(self, retries=10):
|
|
"""Run eraseflashinfo.
|
|
|
|
@returns True if the board id is erased
|
|
"""
|
|
for i in range(retries):
|
|
# The console could drop characters while matching 'eraseflashinfo'.
|
|
# Retry if the command times out. It's ok to run eraseflashinfo
|
|
# multiple times.
|
|
rv = self.send_command_retry_get_output(
|
|
'eraseflashinfo', ['eraseflashinfo(.*)>'])[0][1].strip()
|
|
logging.info('eraseflashinfo output: %r', rv)
|
|
bid_erased = self.get_board_id()[1]
|
|
eraseflashinfo_issue = 'Busy' in rv or 'do_flash_op' in rv
|
|
if not eraseflashinfo_issue and bid_erased:
|
|
break
|
|
logging.info('Retrying eraseflashinfo')
|
|
return bid_erased
|
|
|
|
|
|
def rollback(self):
|
|
"""Set the reset counter high enough to force a rollback and reboot."""
|
|
if not self.has_command('rollback'):
|
|
raise error.TestError("need image with 'rollback'")
|
|
|
|
inactive_partition = self.get_inactive_version_info()[0]
|
|
|
|
self.wait_for_reboot(cmd='rollback', timeout=10)
|
|
|
|
running_partition = self.get_active_version_info()[0]
|
|
if inactive_partition != running_partition:
|
|
raise error.TestError("Failed to rollback to inactive image")
|
|
|
|
|
|
def rolledback(self):
|
|
"""Returns true if cr50 just rolled back"""
|
|
return 'Rollback detected' in self.send_command_retry_get_output(
|
|
'sysinfo', ['sysinfo.*>'], safe=True)[0]
|
|
|
|
|
|
def get_version_info(self, regexp):
|
|
"""Get information from the version command"""
|
|
return self.send_command_retry_get_output('version', [regexp],
|
|
safe=True,
|
|
compare_output=True)[0][1::]
|
|
|
|
|
|
def get_inactive_version_info(self):
|
|
"""Get the active partition, version, and hash"""
|
|
return self.get_version_info(self.INACTIVE_VERSION)
|
|
|
|
|
|
def get_active_version_info(self):
|
|
"""Get the active partition, version, and hash"""
|
|
return self.get_version_info(self.ACTIVE_VERSION)
|
|
|
|
|
|
def using_prod_rw_keys(self):
|
|
"""Returns True if the RW keyid is prod"""
|
|
rv = self.send_command_retry_get_output('sysinfo',
|
|
['RW keyid:\s+(0x[0-9a-f]{8})'], safe=True)[0][1]
|
|
logging.info('RW Keyid: 0x%s', rv)
|
|
return rv in self.PROD_RW_KEYIDS
|
|
|
|
|
|
def get_active_board_id_str(self):
|
|
"""Get the running image board id.
|
|
|
|
@return: The board id string or None if the image does not support board
|
|
id or the image is not board id locked.
|
|
"""
|
|
# Getting the board id from the version console command is only
|
|
# supported in board id locked images .22 and above. Any image that is
|
|
# board id locked will have support for getting the image board id.
|
|
#
|
|
# If board id is not supported on the device, return None. This is
|
|
# still expected on all current non board id locked release images.
|
|
try:
|
|
version_info = self.get_version_info(self.ACTIVE_BID)
|
|
except error.TestFail as e:
|
|
logging.info(str(e))
|
|
logging.info('Cannot use the version to get the board id')
|
|
return None
|
|
|
|
if self.BID_ERROR in version_info[4]:
|
|
raise error.TestError(version_info)
|
|
bid = version_info[4].split()[1]
|
|
return cr50_utils.GetBoardIdInfoString(bid)
|
|
|
|
|
|
def get_version(self):
|
|
"""Get the RW version"""
|
|
return self.get_active_version_info()[1].strip()
|
|
|
|
|
|
def get_full_version(self):
|
|
"""Get the complete RW version string."""
|
|
_, rw_ver, dbg, ver_str = self.get_active_version_info()
|
|
return rw_ver + (dbg if dbg else '') + ver_str
|
|
|
|
|
|
def ccd_is_enabled(self):
|
|
"""Return True if ccd is enabled.
|
|
|
|
If the test is running through ccd, return the ccd_state value. If
|
|
a flex cable is being used, use the CCD_MODE_L gpio setting to determine
|
|
if Cr50 has ccd enabled.
|
|
|
|
@return: 'off' or 'on' based on whether the cr50 console is working.
|
|
"""
|
|
if self._servo.main_device_is_ccd():
|
|
return self._servo.get('ccd_state') == 'on'
|
|
else:
|
|
return not bool(self.gpioget('CCD_MODE_L'))
|
|
|
|
|
|
@dts_control_command
|
|
def wait_for_stable_ccd_state(self, state, timeout, raise_error):
|
|
"""Wait up to timeout seconds for CCD to be 'on' or 'off'
|
|
|
|
Verify ccd is off or on and remains in that state for 3 seconds.
|
|
|
|
@param state: a string either 'on' or 'off'.
|
|
@param timeout: time in seconds to wait
|
|
@param raise_error: Raise TestFail if the value is state is not reached.
|
|
@raise TestFail: if ccd never reaches the specified state
|
|
"""
|
|
wait_for_enable = state == 'on'
|
|
logging.info("Wait until ccd is %s", 'on' if wait_for_enable else 'off')
|
|
enabled = utils.wait_for_value(self.ccd_is_enabled, wait_for_enable,
|
|
timeout_sec=timeout)
|
|
if enabled != wait_for_enable:
|
|
error_msg = ("timed out before detecting ccd '%s'" %
|
|
('on' if wait_for_enable else 'off'))
|
|
if raise_error:
|
|
raise error.TestFail(error_msg)
|
|
logging.warning(error_msg)
|
|
else:
|
|
# Make sure the state doesn't change.
|
|
enabled = utils.wait_for_value(self.ccd_is_enabled, not enabled,
|
|
timeout_sec=self.SHORT_WAIT)
|
|
if enabled != wait_for_enable:
|
|
error_msg = ("CCD switched %r after briefly being %r" %
|
|
('on' if enabled else 'off', state))
|
|
if raise_error:
|
|
raise error.TestFail(error_msg)
|
|
logging.info(error_msg)
|
|
logging.info("ccd is %r", 'on' if enabled else 'off')
|
|
|
|
|
|
@dts_control_command
|
|
def wait_for_ccd_disable(self, timeout=60, raise_error=True):
|
|
"""Wait for the cr50 console to stop working"""
|
|
self.wait_for_stable_ccd_state('off', timeout, raise_error)
|
|
|
|
|
|
@dts_control_command
|
|
def wait_for_ccd_enable(self, timeout=60, raise_error=False):
|
|
"""Wait for the cr50 console to start working"""
|
|
self.wait_for_stable_ccd_state('on', timeout, raise_error)
|
|
|
|
|
|
@dts_control_command
|
|
def ccd_disable(self, raise_error=True):
|
|
"""Change the values of the CC lines to disable CCD"""
|
|
logging.info("disable ccd")
|
|
self._servo.set_dts_mode('off')
|
|
self.wait_for_ccd_disable(raise_error=raise_error)
|
|
|
|
|
|
@dts_control_command
|
|
def ccd_enable(self, raise_error=False):
|
|
"""Reenable CCD and reset servo interfaces"""
|
|
logging.info("reenable ccd")
|
|
self._servo.set_dts_mode('on')
|
|
# If the test is actually running with ccd, wait for USB communication
|
|
# to come up after reset.
|
|
if self._servo.main_device_is_ccd():
|
|
time.sleep(self._servo.USB_DETECTION_DELAY)
|
|
self.wait_for_ccd_enable(raise_error=raise_error)
|
|
|
|
|
|
def _level_change_req_pp(self, level):
|
|
"""Returns True if setting the level will require physical presence"""
|
|
testlab_pp = level != 'testlab open' and 'testlab' in level
|
|
# If the level is open and the ccd capabilities say physical presence
|
|
# is required, then physical presence will be required.
|
|
open_pp = (level == 'open' and
|
|
not self.get_cap('OpenNoLongPP')[self.CAP_IS_ACCESSIBLE])
|
|
return testlab_pp or open_pp
|
|
|
|
|
|
def _state_to_bool(self, state):
|
|
"""Converts the state string to True or False"""
|
|
# TODO(mruthven): compare to 'on' once servo is up to date in the lab
|
|
return state.lower() in self.ON_STRINGS
|
|
|
|
|
|
def testlab_is_on(self):
|
|
"""Returns True of testlab mode is on"""
|
|
return self._state_to_bool(self._servo.get('cr50_testlab'))
|
|
|
|
|
|
def set_ccd_testlab(self, state):
|
|
"""Set the testlab mode
|
|
|
|
@param state: the desired testlab mode string: 'on' or 'off'
|
|
@raise TestFail: if testlab mode was not changed
|
|
"""
|
|
if self._servo.main_device_is_ccd():
|
|
raise error.TestError('Cannot set testlab mode with CCD. Use flex '
|
|
'cable instead.')
|
|
if not self.faft_config.has_powerbutton:
|
|
raise error.TestError('No power button on device')
|
|
|
|
request_on = self._state_to_bool(state)
|
|
testlab_on = self.testlab_is_on()
|
|
request_str = 'on' if request_on else 'off'
|
|
|
|
if testlab_on == request_on:
|
|
logging.info('ccd testlab already set to %s', request_str)
|
|
return
|
|
|
|
original_level = self.get_ccd_level()
|
|
|
|
# We can only change the testlab mode when the device is open. If
|
|
# testlab mode is already enabled, we can go directly to open using 'ccd
|
|
# testlab open'. This will save 5 minutes, because we can skip the
|
|
# physical presence check.
|
|
if testlab_on:
|
|
self.send_command('ccd testlab open')
|
|
else:
|
|
self.set_ccd_level('open')
|
|
|
|
ap_is_on = self.ap_is_on()
|
|
# Set testlab mode
|
|
rv = self.send_command_get_output('ccd testlab %s' % request_str,
|
|
['ccd.*>'])[0]
|
|
if 'Access Denied' in rv:
|
|
raise error.TestFail("'ccd %s' %s" % (request_str, rv))
|
|
|
|
# Press the power button once a second for 15 seconds. If the AP is
|
|
# currently on, make sure it's on at the end of the open process.
|
|
self.run_pp(self.PP_SHORT, ensure_ap_on=ap_is_on)
|
|
|
|
self.set_ccd_level(original_level)
|
|
if request_on != self.testlab_is_on():
|
|
raise error.TestFail('Failed to set ccd testlab to %s' % state)
|
|
|
|
|
|
def get_ccd_level(self):
|
|
"""Returns the current ccd privilege level"""
|
|
return self.get_ccd_info('State').lower().rstrip('ed')
|
|
|
|
|
|
def set_ccd_level(self, level, password=''):
|
|
"""Set the Cr50 CCD privilege level.
|
|
|
|
@param level: a string of the ccd privilege level: 'open', 'lock', or
|
|
'unlock'.
|
|
@param password: send the ccd command with password. This will still
|
|
require the same physical presence.
|
|
@raise TestFail: if the level couldn't be set
|
|
"""
|
|
# TODO(mruthven): add support for CCD password
|
|
level = level.lower()
|
|
|
|
if level == self.get_ccd_level():
|
|
logging.info('CCD privilege level is already %s', level)
|
|
return
|
|
|
|
if 'testlab' in level:
|
|
raise error.TestError("Can't change testlab mode using "
|
|
"ccd_set_level")
|
|
|
|
testlab_on = self._state_to_bool(self._servo.get('cr50_testlab'))
|
|
batt_is_disconnected = self.get_batt_pres_state()[1]
|
|
req_pp = self._level_change_req_pp(level)
|
|
has_pp = not self._servo.main_device_is_ccd()
|
|
dbg_en = self.get_active_version_info()[2]
|
|
|
|
if req_pp and not has_pp:
|
|
raise error.TestError("Can't change privilege level to '%s' "
|
|
"without physical presence." % level)
|
|
|
|
if not testlab_on and not has_pp:
|
|
raise error.TestError("Wont change privilege level without "
|
|
"physical presence or testlab mode enabled")
|
|
|
|
original_timeout = float(self._servo.get('cr50_uart_timeout'))
|
|
# Change the console timeout to CONSERVATIVE_CCD_WAIT, running 'ccd' may
|
|
# take more than 3 seconds.
|
|
self._servo.set_nocheck('cr50_uart_timeout', self.CONSERVATIVE_CCD_WAIT)
|
|
# Start the unlock process.
|
|
|
|
if level == 'open' or level == 'unlock':
|
|
logging.info('waiting %d seconds, the minimum time between'
|
|
' ccd password attempts',
|
|
self.CCD_PASSWORD_RATE_LIMIT)
|
|
time.sleep(self.CCD_PASSWORD_RATE_LIMIT)
|
|
|
|
ap_is_on = self.ap_is_on()
|
|
try:
|
|
cmd = 'ccd %s%s' % (level, (' ' + password) if password else '')
|
|
# ccd command outputs on the rbox, ccd, and console channels,
|
|
# respectively. Cr50 uses these channels to print relevant ccd
|
|
# information.
|
|
# Restrict all other channels.
|
|
ccd_output_channels = 0x20000 | 0x8 | 0x1
|
|
rv = self.send_safe_command_get_output(
|
|
cmd, [cmd + '(.*)>'],
|
|
channel_mask=ccd_output_channels)[0][1]
|
|
finally:
|
|
self._servo.set('cr50_uart_timeout', original_timeout)
|
|
logging.info(rv)
|
|
if 'ccd_open denied: fwmp' in rv:
|
|
raise error.TestFail('FWMP disabled %r: %s' % (cmd, rv))
|
|
if 'Access Denied' in rv:
|
|
raise error.TestFail("%r %s" % (cmd, rv))
|
|
if 'Busy' in rv:
|
|
raise error.TestFail("cr50 is too busy to run %r: %s" % (cmd, rv))
|
|
|
|
# Press the power button once a second, if we need physical presence.
|
|
if req_pp and batt_is_disconnected:
|
|
# DBG images have shorter unlock processes. If the AP is currently
|
|
# on, make sure it's on at the end of the open process.
|
|
self.run_pp(self.PP_SHORT if dbg_en else self.PP_LONG,
|
|
ensure_ap_on=ap_is_on)
|
|
|
|
if level != self.get_ccd_level():
|
|
raise error.TestFail('Could not set privilege level to %s' % level)
|
|
|
|
logging.info('Successfully set CCD privelege level to %s', level)
|
|
|
|
|
|
def run_pp(self, unlock_timeout, ensure_ap_on=False):
|
|
"""Press the power button a for unlock_timeout seconds.
|
|
|
|
This will press the power button many more times than it needs to be
|
|
pressed. Cr50 doesn't care if you press it too often. It just cares that
|
|
you press the power button at least once within the detect interval.
|
|
|
|
For privilege level changes you need to press the power button 5 times
|
|
in the short interval and then 4 times within the long interval.
|
|
Short Interval
|
|
100msec < power button press < 5 seconds
|
|
Long Interval
|
|
60s < power button press < 300s
|
|
|
|
For testlab enable/disable you must press the power button 5 times
|
|
spaced between 100msec and 5 seconds apart.
|
|
|
|
@param unlock_timeout: time to press the power button in seconds.
|
|
@param ensure_ap_on: If true, press the power to turn on the AP.
|
|
"""
|
|
end_time = time.time() + unlock_timeout
|
|
|
|
logging.info('Pressing power button for %ds to unlock the console.',
|
|
unlock_timeout)
|
|
logging.info('The process should end at %s', time.ctime(end_time))
|
|
|
|
# Press the power button once a second to unlock the console.
|
|
while time.time() < end_time:
|
|
self._servo.power_short_press()
|
|
time.sleep(1)
|
|
|
|
# If the last power button press left the AP powered off, and it was on
|
|
# before, turn it back on.
|
|
time.sleep(self.faft_config.shutdown)
|
|
if ensure_ap_on and not self.ap_is_on():
|
|
logging.info('AP is off. Pressing the power button to turn it on')
|
|
self._servo.power_short_press()
|
|
logging.debug('Pressing PP to turn back on')
|
|
|
|
|
|
def gettime(self):
|
|
"""Get the current cr50 system time"""
|
|
result = self.send_safe_command_get_output('gettime', [' = (.*) s'])
|
|
return float(result[0][1])
|
|
|
|
|
|
def servo_dts_mode_is_valid(self):
|
|
"""Returns True if cr50 registers change in servo dts mode."""
|
|
# This is to test that Cr50 actually recognizes the change in ccd state
|
|
# We cant do that with tests using ccd, because the cr50 communication
|
|
# goes down once ccd is enabled.
|
|
if not self._servo.dts_mode_is_safe():
|
|
return False
|
|
|
|
ccd_start = 'on' if self.ccd_is_enabled() else 'off'
|
|
dts_start = self._servo.get_dts_mode()
|
|
try:
|
|
# Verify both ccd enable and disable
|
|
self.ccd_disable(raise_error=True)
|
|
self.ccd_enable(raise_error=True)
|
|
rv = True
|
|
except Exception as e:
|
|
logging.info(e)
|
|
rv = False
|
|
self._servo.set_dts_mode(dts_start)
|
|
self.wait_for_stable_ccd_state(ccd_start, 60, True)
|
|
logging.info('Test setup does%s support servo DTS mode',
|
|
'' if rv else 'n\'t')
|
|
return rv
|
|
|
|
|
|
def wait_until_update_is_allowed(self):
|
|
"""Wait until cr50 will be able to accept an update.
|
|
|
|
Cr50 rejects any attempt to update if it has been less than 60 seconds
|
|
since it last recovered from deep sleep or came up from reboot. This
|
|
will wait until cr50 gettime shows a time greater than 60.
|
|
"""
|
|
if self.get_active_version_info()[2]:
|
|
logging.info("Running DBG image. Don't need to wait for update.")
|
|
return
|
|
cr50_time = self.gettime()
|
|
if cr50_time < 60:
|
|
sleep_time = 61 - cr50_time
|
|
logging.info('Cr50 has been up for %ds waiting %ds before update',
|
|
cr50_time, sleep_time)
|
|
time.sleep(sleep_time)
|
|
|
|
|
|
def tpm_is_enabled(self):
|
|
"""Query the current TPM mode.
|
|
|
|
@return: True if TPM is enabled, False otherwise.
|
|
"""
|
|
result = self.send_command_retry_get_output('sysinfo',
|
|
['(?i)TPM\s+MODE:\s+(enabled|disabled)'], safe=True)[0][1]
|
|
logging.debug(result)
|
|
|
|
return result.lower() == 'enabled'
|
|
|
|
|
|
def get_keyladder_state(self):
|
|
"""Get the status of H1 Key Ladder.
|
|
|
|
@return: The keyladder state string. prod or dev both mean enabled.
|
|
"""
|
|
result = self.send_command_retry_get_output('sysinfo',
|
|
['(?i)Key\s+Ladder:\s+(enabled|prod|dev|disabled)'],
|
|
safe=True)[0][1]
|
|
logging.debug(result)
|
|
return result
|
|
|
|
|
|
def keyladder_is_disabled(self):
|
|
"""Get the status of H1 Key Ladder.
|
|
|
|
@return: True if H1 Key Ladder is disabled. False otherwise.
|
|
"""
|
|
return self.get_keyladder_state() == 'disabled'
|
|
|
|
|
|
def get_sleepmask(self):
|
|
"""Returns the sleepmask as an int"""
|
|
rv = self.send_command_retry_get_output('sleepmask',
|
|
['sleep mask: (\S{8})\s+'], safe=True)[0][1]
|
|
logging.info('sleepmask %s', rv)
|
|
return int(rv, 16)
|
|
|
|
|
|
def get_ccdstate(self):
|
|
"""Return a dictionary of the ccdstate once it's done debouncing"""
|
|
for i in range(self.CCDSTATE_MAX_RETRY_COUNT):
|
|
rv = self.send_command_retry_get_output('ccdstate',
|
|
['ccdstate(.*)>'], safe=True, compare_output=True)[0][0]
|
|
|
|
# Look for a line like 'AP: on' or 'AP: off'. 'debouncing' or
|
|
# 'unknown' may appear transiently. 'debouncing' should transition
|
|
# to 'on' or 'off' within 1 second, and 'unknown' should do so
|
|
# within 20 seconds.
|
|
if 'debouncing' not in rv and 'unknown' not in rv:
|
|
break
|
|
time.sleep(self.SHORT_WAIT)
|
|
ccdstate = {}
|
|
for line in rv.splitlines():
|
|
line = line.strip()
|
|
if ':' in line:
|
|
k, v = line.split(':', 1)
|
|
ccdstate[k.strip()] = v.strip()
|
|
logging.info('Current CCD state:\n%s', pprint.pformat(ccdstate))
|
|
return ccdstate
|
|
|
|
|
|
def ap_is_on(self):
|
|
"""Get the power state of the AP.
|
|
|
|
@return: True if the AP is on; False otherwise.
|
|
"""
|
|
ap_state = self.get_ccdstate()['AP']
|
|
if ap_state == 'on':
|
|
return True
|
|
elif ap_state == 'off':
|
|
return False
|
|
else:
|
|
raise error.TestFail('Read unusable AP state from ccdstate: %r' %
|
|
ap_state)
|
|
|
|
|
|
def gpioget(self, signal_name):
|
|
"""Get the current state of the signal
|
|
|
|
@return an integer 1 or 0 based on the gpioget value
|
|
"""
|
|
result = self.send_command_retry_get_output('gpioget',
|
|
['(0|1)[ \S]*%s' % signal_name], safe=True)
|
|
return int(result[0][1])
|
|
|
|
|
|
def batt_pres_is_reset(self):
|
|
"""Returns True if batt pres is reset to always follow batt pres"""
|
|
follow_bp, _, follow_bp_atboot, _ = self.get_batt_pres_state()
|
|
return follow_bp and follow_bp_atboot
|
|
|
|
|
|
def get_batt_pres_state(self):
|
|
"""Get the current and atboot battery presence state
|
|
|
|
The atboot setting cannot really be determined now if it is set to
|
|
follow battery presence. It is likely to remain the same after reboot,
|
|
but who knows. If the third element of the tuple is True, the last
|
|
element will not be that useful
|
|
|
|
@return: a tuple of the current battery presence state
|
|
(True if current state is to follow batt presence,
|
|
True if battery is connected,
|
|
True if current state is to follow batt presence atboot,
|
|
True if battery is connected atboot)
|
|
"""
|
|
# bpforce is added in 4.16. If the image doesn't have the command, cr50
|
|
# always follows battery presence. In these images 'gpioget BATT_PRES_L'
|
|
# accurately represents the battery presence state, because it can't be
|
|
# overidden.
|
|
if not self.has_command('bpforce'):
|
|
batt_pres = not bool(self.gpioget('BATT_PRES_L'))
|
|
return (True, batt_pres, True, batt_pres)
|
|
|
|
# The bpforce command is very similar to the wp command. It just
|
|
# substitutes 'connected' for 'enabled' and 'disconnected' for
|
|
# 'disabled'.
|
|
rv = self.send_command_retry_get_output('bpforce',
|
|
['batt pres: (forced )?(con|dis).*at boot: (forced )?'
|
|
'(follow|discon|con)'], safe=True)[0]
|
|
_, forced, connected, _, atboot = rv
|
|
logging.info(rv)
|
|
return (not forced, connected == 'con', atboot == 'follow',
|
|
atboot == 'con')
|
|
|
|
|
|
def set_batt_pres_state(self, state, atboot):
|
|
"""Override the battery presence state.
|
|
|
|
@param state: a string of the battery presence setting: 'connected',
|
|
'disconnected', or 'follow_batt_pres'
|
|
@param atboot: True if we're overriding battery presence atboot
|
|
"""
|
|
cmd = 'bpforce %s%s' % (state, ' atboot' if atboot else '')
|
|
logging.info('running %r', cmd)
|
|
self.send_command(cmd)
|
|
|
|
|
|
def dump_nvmem(self):
|
|
"""Print nvmem objects."""
|
|
rv = self.send_safe_command_get_output('dump_nvmem',
|
|
['dump_nvmem(.*)>'])[0][1]
|
|
logging.info('NVMEM OUTPUT:\n%s', rv)
|
|
|
|
|
|
def get_reset_cause(self):
|
|
"""Returns the reset flags for the last reset."""
|
|
rv = self.send_command_retry_get_output('sysinfo',
|
|
['Reset flags:\s+0x([0-9a-f]{8})\s'], compare_output=True)[0][1]
|
|
logging.info('reset cause: %s', rv)
|
|
return int(rv, 16)
|
|
|
|
|
|
def was_reset(self, reset_type):
|
|
"""Returns 1 if the reset type is found in the reset_cause.
|
|
|
|
@param reset_type: reset name in string type.
|
|
"""
|
|
reset_cause = self.get_reset_cause()
|
|
reset_flag = self.RESET_FLAGS[reset_type]
|
|
return bool(reset_cause & reset_flag)
|
|
|
|
|
|
def get_devid(self):
|
|
"""Returns the cr50 serial number."""
|
|
return self.send_command_retry_get_output('sysinfo',
|
|
['DEV_ID:\s+(0x[0-9a-f]{8} 0x[0-9a-f]{8})'])[0][1]
|
|
|
|
|
|
def get_serial(self):
|
|
"""Returns the cr50 serial number."""
|
|
serial = self.get_devid().replace('0x', '').replace(' ', '-').upper()
|
|
logging.info('CCD serial: %s', serial)
|
|
return serial
|
|
|
|
def check_boot_mode(self, mode_exp='NORMAL'):
|
|
"""Query the boot mode to Cr50, and compare it against mode_exp.
|
|
|
|
Args:
|
|
mode_exp: expecting boot mode. It should be either 'NORMAL'
|
|
or 'NO_BOOT'.
|
|
Returns:
|
|
True if the boot mode matches mode_exp.
|
|
False, otherwise.
|
|
Raises:
|
|
TestError: Input parameter is not valid.
|
|
"""
|
|
|
|
if mode_exp not in ['NORMAL', 'NO_BOOT']:
|
|
raise error.TestError('parameter, mode_exp is not valid: %s' %
|
|
mode_exp)
|
|
rv = self.send_command_retry_get_output('ec_comm',
|
|
['boot_mode\s*:\s*(NORMAL|NO_BOOT)'], safe=True)
|
|
return mode_exp == rv[0][1]
|
|
|
|
def get_reset_count(self):
|
|
"""Returns the cr50 reset count"""
|
|
return self.send_command_retry_get_output('sysinfo',
|
|
['Reset count: (\d+)'],
|
|
safe=True)[0][1]
|
|
|
|
def check_servo_monitor(self):
|
|
"""Returns True if cr50 can detect servo connect/disconnect"""
|
|
orig_dts = self._servo.get('servo_v4_dts_mode')
|
|
# Detach ccd so EC uart won't interfere with servo detection
|
|
self._servo.set_dts_mode('off')
|
|
self._servo.set('ec_uart_en', 'off')
|
|
time.sleep(self.SHORT_WAIT)
|
|
if self.get_ccdstate()['Servo'] != 'disconnected':
|
|
self._servo.set_dts_mode(orig_dts)
|
|
return False
|
|
|
|
self._servo.set('ec_uart_en', 'on')
|
|
time.sleep(self.SHORT_WAIT)
|
|
if self.get_ccdstate()['Servo'] != 'connected':
|
|
self._servo.set_dts_mode(orig_dts)
|
|
return False
|
|
self._servo.set_dts_mode(orig_dts)
|
|
return True
|