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.
907 lines
32 KiB
907 lines
32 KiB
# Lint as: python2, python3
|
|
# Copyright 2016 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 re
|
|
import logging
|
|
from six.moves import range
|
|
import time
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.server.cros.servo import pd_console
|
|
|
|
|
|
class PDDevice(object):
|
|
"""Base clase for all PD devices
|
|
|
|
This class provides a set of APIs for expected Type C PD required actions
|
|
in TypeC FAFT tests. The base class is specific for Type C devices that
|
|
do not have any console access.
|
|
|
|
"""
|
|
|
|
def is_src(self, state=None):
|
|
"""Checks if the port is connected as a source
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'is_src should be implemented in derived class')
|
|
|
|
def is_snk(self, state=None):
|
|
"""Checks if the port is connected as a sink
|
|
|
|
@returns None
|
|
"""
|
|
raise NotImplementedError(
|
|
'is_snk should be implemented in derived class')
|
|
|
|
def is_connected(self, state=None):
|
|
"""Checks if the port is connected
|
|
|
|
@returns True if in a connected state, False otherwise
|
|
"""
|
|
return self.is_src(state) or self.is_snk(state)
|
|
|
|
def is_disconnected(self, state=None):
|
|
"""Checks if the port is disconnected
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'is_disconnected should be implemented in derived class')
|
|
|
|
def is_ufp(self):
|
|
"""Checks if data role is UFP
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'is_ufp should be implemented in derived class')
|
|
|
|
def is_dfp(self):
|
|
"""Checks if data role is DFP
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'is_dfp should be implemented in derived class')
|
|
|
|
def is_drp(self):
|
|
"""Checks if dual role mode is supported
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'is_drp should be implemented in derived class')
|
|
|
|
def dr_swap(self):
|
|
"""Attempts a data role swap
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'dr_swap should be implemented in derived class')
|
|
|
|
def pr_swap(self):
|
|
"""Attempts a power role swap
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'pr_swap should be implemented in derived class')
|
|
|
|
def vbus_request(self, voltage):
|
|
"""Requests a specific VBUS voltage from SRC
|
|
|
|
@param voltage: requested voltage level (5, 12, 20) in volts
|
|
"""
|
|
raise NotImplementedError(
|
|
'vbus_request should be implemented in derived class')
|
|
|
|
def soft_reset(self):
|
|
"""Initates a PD soft reset sequence
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'soft_reset should be implemented in derived class')
|
|
|
|
def hard_reset(self):
|
|
"""Initates a PD hard reset sequence
|
|
|
|
"""
|
|
raise NotImplementedError(
|
|
'hard_reset should be implemented in derived class')
|
|
|
|
def drp_set(self, mode):
|
|
"""Sets dualrole mode
|
|
|
|
@param mode: desired dual role setting (on, off, snk, src)
|
|
"""
|
|
raise NotImplementedError(
|
|
'drp_set should be implemented in derived class')
|
|
|
|
def drp_get(self):
|
|
"""Gets dualrole mode
|
|
|
|
@returns one of the modes (on, off, snk, src)
|
|
"""
|
|
raise NotImplementedError(
|
|
'drp_set should be implemented in derived class')
|
|
|
|
def drp_disconnect_connect(self, disc_time_sec):
|
|
"""Force PD disconnect/connect via drp settings
|
|
|
|
@param disc_time_sec: Time in seconds between disconnect and reconnect
|
|
"""
|
|
raise NotImplementedError(
|
|
'drp_disconnect_connect should be implemented in derived class'
|
|
)
|
|
|
|
def cc_disconnect_connect(self, disc_time_sec):
|
|
"""Force PD disconnect/connect
|
|
|
|
@param disc_time_sec: Time in seconds between disconnect and reconnect
|
|
"""
|
|
raise NotImplementedError(
|
|
'cc_disconnect_connect should be implemented in derived class')
|
|
|
|
def get_connected_state_after_cc_reconnect(self, disc_time_sec):
|
|
"""Get the connected state after disconnect/reconnect
|
|
|
|
@param disc_time_sec: Time in seconds for disconnect period.
|
|
@returns: The connected PD state.
|
|
"""
|
|
raise NotImplementedError(
|
|
'get_connected_state_after_cc_reconnect should be implemented'
|
|
'in derived class')
|
|
|
|
|
|
class PDConsoleDevice(PDDevice):
|
|
"""Class for PD devices that have console access
|
|
|
|
This class contains methods for common PD actions for any PD device which
|
|
has UART console access. It inherits the PD device base class. In addition,
|
|
it stores both the UART console and port for the PD device.
|
|
"""
|
|
|
|
def __init__(self, console, port, utils):
|
|
"""Initialization method
|
|
|
|
@param console: UART console object
|
|
@param port: USB PD port number
|
|
"""
|
|
# Save UART console
|
|
self.console = console
|
|
# Instantiate PD utilities used by methods in this class
|
|
self.utils = utils
|
|
# Save the PD port number for this device
|
|
self.port = port
|
|
# Not a PDTester device
|
|
self.is_pdtester = False
|
|
|
|
def get_pd_state(self):
|
|
"""Get the state of the PD port"""
|
|
return self.utils.get_pd_state(self.port)
|
|
|
|
def get_pd_role(self):
|
|
"""Get the current PD power role (source or sink)
|
|
|
|
@returns: current pd state
|
|
"""
|
|
return self.utils.get_pd_role(self.port)
|
|
|
|
def is_pd_flag_set(self, key):
|
|
"""Test a bit in PD protocol state flags
|
|
|
|
The flag word contains various PD protocol state information.
|
|
This method allows for a specific flag to be tested.
|
|
|
|
@param key: dict key to retrieve the flag bit mapping
|
|
|
|
@returns True if the bit to be tested is set
|
|
"""
|
|
return self.utils.is_pd_flag_set(self.port, key)
|
|
|
|
def is_src(self, state=None):
|
|
"""Checks if the port is connected as a source.
|
|
|
|
The "state" argument allows the caller to get_pd_state() once, and then
|
|
evaluate multiple conditions without re-getting the state.
|
|
|
|
@param state: the state to check (None to get current state)
|
|
@returns True if connected as SRC, False otherwise
|
|
"""
|
|
return self.utils.is_src_connected(self.port, state)
|
|
|
|
def is_snk(self, state=None):
|
|
"""Checks if the port is connected as a sink
|
|
|
|
The "state" argument allows the caller to get_pd_state() once, and then
|
|
evaluate multiple conditions without re-getting the state.
|
|
|
|
@param state: the state to check (None to get current state)
|
|
@returns True if connected as SNK, False otherwise
|
|
"""
|
|
return self.utils.is_snk_connected(self.port, state)
|
|
|
|
def is_connected(self, state=None):
|
|
"""Checks if the port is connected
|
|
|
|
The "state" argument allows the caller to get_pd_state() once, and then
|
|
evaluate multiple conditions without re-getting the state.
|
|
|
|
@param state: the state to check (None to get current state)
|
|
@returns True if in a connected state, False otherwise
|
|
"""
|
|
return self.is_snk(state) or self.is_src(state)
|
|
|
|
def is_disconnected(self, state=None):
|
|
"""Checks if the port is disconnected
|
|
|
|
@returns True if in a disconnected state, False otherwise
|
|
"""
|
|
return self.utils.is_disconnected(self.port, state)
|
|
|
|
def __repr__(self):
|
|
"""String representation of the object"""
|
|
return "<%s %r port %s>" % (
|
|
self.__class__.__name__, self.console.name, self.port)
|
|
|
|
def is_drp(self):
|
|
"""Checks if dual role mode is supported
|
|
|
|
@returns True if dual role mode is 'on', False otherwise
|
|
"""
|
|
return self.utils.is_pd_dual_role_enabled(self.port)
|
|
|
|
def drp_disconnect_connect(self, disc_time_sec):
|
|
"""Disconnect/reconnect using drp mode settings
|
|
|
|
A PD console device doesn't have an explicit connect/disconnect
|
|
command. Instead, the dualrole mode setting is used to force
|
|
disconnects in devices which support this feature. To disconnect,
|
|
force the dualrole mode to be the opposite role of the current
|
|
connected state.
|
|
|
|
@param disc_time_sec: time in seconds to wait to reconnect
|
|
|
|
@returns True if device disconnects, then returns to a connected
|
|
state. False if either step fails.
|
|
"""
|
|
# Dualrole mode must be supported
|
|
if self.is_drp() is False:
|
|
logging.warn('Device not DRP capable, unabled to force disconnect')
|
|
return False
|
|
# Force state will be the opposite of current connect state
|
|
if self.is_src():
|
|
drp_mode = 'snk'
|
|
swap_state = self.utils.get_snk_connect_states()
|
|
else:
|
|
drp_mode = 'src'
|
|
swap_state = self.utils.get_src_connect_states()
|
|
# Force disconnect
|
|
self.drp_set(drp_mode)
|
|
# Wait for disconnect time
|
|
time.sleep(disc_time_sec)
|
|
# Verify that the device is disconnected
|
|
disconnect = self.is_disconnected()
|
|
|
|
# If the other device is dualrole, then forcing dualrole mode will
|
|
# only cause the disconnect to appear momentarily and reconnect
|
|
# in the power role forced by the drp_set() call. For this case,
|
|
# the role swap verifies that a disconnect/connect sequence occurred.
|
|
if disconnect == False:
|
|
time.sleep(self.utils.CONNECT_TIME)
|
|
# Connected, verify if power role swap has occurred
|
|
if self.utils.get_pd_state(self.port) in swap_state:
|
|
# Restore default dualrole mode
|
|
self.drp_set('on')
|
|
# Restore orignal power role
|
|
connect = self.pr_swap()
|
|
if connect == False:
|
|
logging.warn('DRP on both devices, 2nd power swap failed')
|
|
return connect
|
|
|
|
# Restore default dualrole mode
|
|
self.drp_set('on')
|
|
# Allow enough time for protocol state machine
|
|
time.sleep(self.utils.CONNECT_TIME)
|
|
# Check if connected
|
|
connect = self.is_connected()
|
|
logging.info('Disconnect = %r, Connect = %r', disconnect, connect)
|
|
return bool(disconnect and connect)
|
|
|
|
def drp_set(self, mode):
|
|
"""Sets dualrole mode
|
|
|
|
@param mode: desired dual role setting (on, off, snk, src)
|
|
|
|
@returns True is set was successful, False otherwise
|
|
"""
|
|
# Set desired dualrole mode
|
|
self.utils.set_pd_dualrole(self.port, mode)
|
|
# Get current setting
|
|
current = self.utils.get_pd_dualrole(self.port)
|
|
# Verify that setting is correct
|
|
return bool(mode == current)
|
|
|
|
def drp_get(self):
|
|
"""Gets dualrole mode
|
|
|
|
@returns one of the modes (on, off, snk, src)
|
|
"""
|
|
return self.utils.get_pd_dualrole(self.port)
|
|
|
|
def try_src(self, enable):
|
|
"""Enables/Disables Try.SRC PD protocol setting
|
|
|
|
@param enable: True to enable, False to disable
|
|
|
|
@returns True is setting was successful, False if feature not
|
|
supported by the device, or not set as desired.
|
|
"""
|
|
# Create Try.SRC pd command
|
|
cmd = 'pd trysrc %d' % int(enable)
|
|
# TCPMv1 indicates Try.SRC is on by returning 'on'
|
|
# TCPMv2 indicates Try.SRC is on by returning 'Forced ON'
|
|
on_vals = ('on', 'Forced ON')
|
|
# TCPMv1 indicates Try.SRC is off by returning 'off'
|
|
# TCPMv2 indicates Try.SRC is off by returning 'Forced OFF'
|
|
off_vals = ('off', 'Forced OFF')
|
|
|
|
# Try.SRC on/off is output, if supported feature
|
|
regex = ['Try\.SRC\s(%s)|(Parameter)' % ('|'.join(on_vals + off_vals))]
|
|
m = self.utils.send_pd_command_get_output(cmd, regex)
|
|
|
|
# Determine if Try.SRC feature is supported
|
|
if 'Try.SRC' not in m[0][0]:
|
|
logging.warn('Try.SRC not supported on this PD device')
|
|
return False
|
|
|
|
# TrySRC is supported on this PD device, verify setting.
|
|
trysrc_val = m[0][1]
|
|
logging.info('Try.SRC mode = %s', trysrc_val)
|
|
if enable:
|
|
vals = on_vals
|
|
else:
|
|
vals = off_vals
|
|
|
|
return trysrc_val in vals
|
|
|
|
def soft_reset(self):
|
|
"""Initates a PD soft reset sequence
|
|
|
|
To verify that a soft reset sequence was initiated, the
|
|
reply message is checked to verify that the reset command
|
|
was acknowledged by its port pair. The connect state should
|
|
be same as it was prior to issuing the reset command.
|
|
|
|
@returns True if the port pair acknowledges the the reset message
|
|
and if following the command, the device returns to the same
|
|
connected state. False otherwise.
|
|
"""
|
|
RESET_DELAY = 0.5
|
|
cmd = 'pd %d soft' % self.port
|
|
state_before = self.utils.get_pd_state(self.port)
|
|
reply = self.utils.send_pd_command_get_reply_msg(cmd)
|
|
if reply != self.utils.PD_CONTROL_MSG_DICT['Accept']:
|
|
return False
|
|
time.sleep(RESET_DELAY)
|
|
state_after = self.utils.get_pd_state(self.port)
|
|
return state_before == state_after
|
|
|
|
def hard_reset(self):
|
|
"""Initates a PD hard reset sequence
|
|
|
|
To verify that a hard reset sequence was initiated, the
|
|
console ouput is scanned for HARD RST TX. In addition, the connect
|
|
state should be same as it was prior to issuing the reset command.
|
|
|
|
@returns True if the port pair acknowledges that hard reset was
|
|
initiated and if following the command, the device returns to the same
|
|
connected state. False otherwise.
|
|
"""
|
|
RESET_DELAY = 1.0
|
|
cmd = 'pd %d hard' % self.port
|
|
state_before = self.utils.get_pd_state(self.port)
|
|
self.utils.enable_pd_console_debug()
|
|
try:
|
|
tcpmv1_pattern = '.*(HARD\sRST\sTX)'
|
|
tcpmv2_pattern = '.*(PE_SNK_Hard_Reset)|.*(PE_SRC_Hard_Reset)'
|
|
pattern = '|'.join((tcpmv1_pattern, tcpmv2_pattern))
|
|
self.utils.send_pd_command_get_output(cmd, [pattern])
|
|
except error.TestFail:
|
|
logging.warn('HARD RST TX not found')
|
|
return False
|
|
finally:
|
|
self.utils.disable_pd_console_debug()
|
|
|
|
time.sleep(RESET_DELAY)
|
|
state_after = self.utils.get_pd_state(self.port)
|
|
return state_before == state_after
|
|
|
|
def pr_swap(self):
|
|
"""Attempts a power role swap
|
|
|
|
In order to attempt a power role swap the device must be
|
|
connected and support dualrole mode. Once these two criteria
|
|
are checked a power role command is issued. Following a delay
|
|
to allow for a reconnection the new power role is checked
|
|
against the power role prior to issuing the command.
|
|
|
|
@returns True if the device has swapped power roles, False otherwise.
|
|
"""
|
|
# Get starting state
|
|
if not self.is_drp():
|
|
logging.warn('Dualrole Mode not enabled!')
|
|
return False
|
|
if self.is_connected() == False:
|
|
logging.warn('PD contract not established!')
|
|
return False
|
|
current_pr = self.utils.get_pd_state(self.port)
|
|
swap_cmd = 'pd %d swap power' % self.port
|
|
self.utils.send_pd_command(swap_cmd)
|
|
time.sleep(self.utils.CONNECT_TIME)
|
|
new_pr = self.utils.get_pd_state(self.port)
|
|
logging.info('Power swap: %s -> %s', current_pr, new_pr)
|
|
if self.is_connected() == False:
|
|
logging.warn('Device not connected following PR swap attempt.')
|
|
return False
|
|
return current_pr != new_pr
|
|
|
|
|
|
class PDTesterDevice(PDConsoleDevice):
|
|
"""Class for PDTester devices
|
|
|
|
This class contains methods for PD funtions which are unique to the
|
|
PDTester board, e.g. Plankton or Servo v4. It inherits all the methods
|
|
for PD console devices.
|
|
"""
|
|
|
|
def __init__(self, console, port, utils):
|
|
"""Initialization method
|
|
|
|
@param console: UART console for this device
|
|
@param port: USB PD port number
|
|
"""
|
|
# Instantiate the PD console object
|
|
super(PDTesterDevice, self).__init__(console, port, utils)
|
|
# Indicate this is PDTester device
|
|
self.is_pdtester = True
|
|
|
|
def _toggle_pdtester_drp(self):
|
|
"""Issue 'usbc_action drp' PDTester command
|
|
|
|
@returns value of drp_enable in PDTester FW
|
|
"""
|
|
drp_cmd = 'usbc_action drp'
|
|
drp_re = ['DRP\s=\s(\d)']
|
|
# Send DRP toggle command to PDTester and get value of 'drp_enable'
|
|
m = self.utils.send_pd_command_get_output(drp_cmd, drp_re)
|
|
return int(m[0][1])
|
|
|
|
def _enable_pdtester_drp(self):
|
|
"""Enable DRP mode on PDTester
|
|
|
|
DRP mode can only be toggled and is not able to be explicitly
|
|
enabled/disabled via the console. Therefore, this method will
|
|
toggle DRP mode until the console reply indicates that this
|
|
mode is enabled. The toggle happens a maximum of two times
|
|
in case this is called when it's already enabled.
|
|
|
|
@returns True when DRP mode is enabled, False if not successful
|
|
"""
|
|
for attempt in range(2):
|
|
if self._toggle_pdtester_drp() == True:
|
|
logging.info('PDTester DRP mode enabled')
|
|
return True
|
|
logging.error('PDTester DRP mode set failure')
|
|
return False
|
|
|
|
def _verify_state_sequence(self, states_list, console_log):
|
|
"""Compare PD state transitions to expected values
|
|
|
|
@param states_list: list of expected PD state transitions
|
|
@param console_log: console output which contains state names
|
|
|
|
@returns True if the sequence matches, False otherwise
|
|
"""
|
|
# For each state in the expected state transiton table, build
|
|
# the regexp and search for it in the state transition log.
|
|
for state in states_list:
|
|
state_regx = r'C{0}\s+[\w]+:?\s({1})'.format(self.port,
|
|
state)
|
|
if re.search(state_regx, console_log) is None:
|
|
return False
|
|
return True
|
|
|
|
def cc_disconnect_connect(self, disc_time_sec):
|
|
"""Disconnect/reconnect using PDTester
|
|
|
|
PDTester supports a feature which simulates a USB Type C disconnect
|
|
and reconnect.
|
|
|
|
@param disc_time_sec: Time in seconds for disconnect period.
|
|
"""
|
|
DISC_DELAY = 100
|
|
disc_cmd = 'fakedisconnect %d %d' % (DISC_DELAY,
|
|
disc_time_sec * 1000)
|
|
self.utils.send_pd_command(disc_cmd)
|
|
|
|
def get_connected_state_after_cc_reconnect(self, disc_time_sec):
|
|
"""Get the connected state after disconnect/reconnect using PDTester
|
|
|
|
PDTester supports a feature which simulates a USB Type C disconnect
|
|
and reconnect. It returns the first connected state (either source or
|
|
sink) after reconnect.
|
|
|
|
@param disc_time_sec: Time in seconds for disconnect period.
|
|
@returns: The connected PD state.
|
|
"""
|
|
DISC_DELAY = 100
|
|
disc_cmd = 'fakedisconnect %d %d' % (DISC_DELAY, disc_time_sec * 1000)
|
|
src_connected_tuple = self.utils.get_src_connect_states()
|
|
snk_connected_tuple = self.utils.get_snk_connect_states()
|
|
connected_exp = '|'.join(src_connected_tuple + snk_connected_tuple)
|
|
reply_exp = ['(.*)(C%d)\s+[\w]+:?\s(%s)' % (self.port, connected_exp)]
|
|
m = self.utils.send_pd_command_get_output(disc_cmd, reply_exp)
|
|
return m[0][3]
|
|
|
|
def drp_disconnect_connect(self, disc_time_sec):
|
|
"""Disconnect/reconnect using PDTester
|
|
|
|
Utilize PDTester disconnect/connect utility and verify
|
|
that both disconnect and reconnect actions were successful.
|
|
|
|
@param disc_time_sec: Time in seconds for disconnect period.
|
|
|
|
@returns True if device disconnects, then returns to a connected
|
|
state. False if either step fails.
|
|
"""
|
|
self.cc_disconnect_connect(disc_time_sec)
|
|
time.sleep(disc_time_sec / 2)
|
|
disconnect = self.is_disconnected()
|
|
time.sleep(disc_time_sec / 2 + self.utils.CONNECT_TIME)
|
|
connect = self.is_connected()
|
|
return disconnect and connect
|
|
|
|
def drp_set(self, mode):
|
|
"""Sets dualrole mode
|
|
|
|
@param mode: desired dual role setting (on, off, snk, src)
|
|
|
|
@returns True if dualrole mode matches the requested value or
|
|
is successfully set to that value. False, otherwise.
|
|
"""
|
|
# Get current value of dualrole
|
|
drp = self.utils.get_pd_dualrole(self.port)
|
|
if drp == mode:
|
|
return True
|
|
|
|
if mode == 'on':
|
|
# Setting dpr_enable on PDTester will set dualrole mode to on
|
|
return self._enable_pdtester_drp()
|
|
else:
|
|
# If desired setting is other than 'on', need to ensure that
|
|
# drp mode on PDTester is disabled.
|
|
if drp == 'on':
|
|
# This will turn off drp_enable flag and set dualmode to 'off'
|
|
return self._toggle_pdtester_drp()
|
|
# With drp_enable flag off, can set to desired setting
|
|
return self.utils.set_pd_dualrole(self.port, mode)
|
|
|
|
def _reset(self, cmd, states_list):
|
|
"""Initates a PD reset sequence
|
|
|
|
PDTester device has state names available on the console. When
|
|
a soft reset is issued the console log is extracted and then
|
|
compared against the expected state transisitons.
|
|
|
|
@param cmd: reset type (soft or hard)
|
|
@param states_list: list of expected PD state transitions
|
|
|
|
@returns True if state transitions match, False otherwise
|
|
"""
|
|
# Want to grab all output until either SRC_READY or SNK_READY
|
|
reply_exp = ['(.*)(C%d)\s+[\w]+:?\s([\w]+_READY)' % self.port]
|
|
m = self.utils.send_pd_command_get_output(cmd, reply_exp)
|
|
return self._verify_state_sequence(states_list, m[0][0])
|
|
|
|
def soft_reset(self):
|
|
"""Initates a PD soft reset sequence
|
|
|
|
@returns True if state transitions match, False otherwise
|
|
"""
|
|
snk_reset_states = [
|
|
'SOFT_RESET',
|
|
'SNK_DISCOVERY',
|
|
'SNK_REQUESTED',
|
|
'SNK_TRANSITION',
|
|
'SNK_READY'
|
|
]
|
|
|
|
src_reset_states = [
|
|
'SOFT_RESET',
|
|
'SRC_DISCOVERY',
|
|
'SRC_NEGOCIATE',
|
|
'SRC_ACCEPTED',
|
|
'SRC_POWERED',
|
|
'SRC_TRANSITION',
|
|
'SRC_READY'
|
|
]
|
|
|
|
if self.is_src():
|
|
states_list = src_reset_states
|
|
elif self.is_snk():
|
|
states_list = snk_reset_states
|
|
else:
|
|
raise error.TestFail('Port Pair not in a connected state')
|
|
|
|
cmd = 'pd %d soft' % self.port
|
|
return self._reset(cmd, states_list)
|
|
|
|
def hard_reset(self):
|
|
"""Initates a PD hard reset sequence
|
|
|
|
@returns True if state transitions match, False otherwise
|
|
"""
|
|
snk_reset_states = [
|
|
'HARD_RESET_SEND',
|
|
'HARD_RESET_EXECUTE',
|
|
'SNK_HARD_RESET_RECOVER',
|
|
'SNK_DISCOVERY',
|
|
'SNK_REQUESTED',
|
|
'SNK_TRANSITION',
|
|
'SNK_READY'
|
|
]
|
|
|
|
src_reset_states = [
|
|
'HARD_RESET_SEND',
|
|
'HARD_RESET_EXECUTE',
|
|
'SRC_HARD_RESET_RECOVER',
|
|
'SRC_DISCOVERY',
|
|
'SRC_NEGOCIATE',
|
|
'SRC_ACCEPTED',
|
|
'SRC_POWERED',
|
|
'SRC_TRANSITION',
|
|
'SRC_READY'
|
|
]
|
|
|
|
if self.is_src():
|
|
states_list = src_reset_states
|
|
elif self.is_snk():
|
|
states_list = snk_reset_states
|
|
else:
|
|
raise error.TestFail('Port Pair not in a connected state')
|
|
|
|
cmd = 'pd %d hard' % self.port
|
|
return self._reset(cmd, states_list)
|
|
|
|
|
|
class PDPortPartner(object):
|
|
"""Methods used to instantiate PD device objects
|
|
|
|
This class is initalized with a list of servo consoles. It
|
|
contains methods to determine if USB PD devices are accessible
|
|
via the consoles and attempts to determine USB PD port partners.
|
|
A PD device is USB PD port specific, a single console may access
|
|
multiple PD devices.
|
|
|
|
"""
|
|
|
|
def __init__(self, consoles):
|
|
"""Initialization method
|
|
|
|
@param consoles: list of servo consoles
|
|
"""
|
|
self.consoles = consoles
|
|
|
|
def __repr__(self):
|
|
"""String representation of the object"""
|
|
return "<%s %r>" % (self.__class__.__name__, self.consoles)
|
|
|
|
def _send_pd_state(self, port, console):
|
|
"""Tests if PD device exists on a given port number
|
|
|
|
@param port: USB PD port number to try
|
|
@param console: servo UART console
|
|
|
|
@returns True if 'pd <port> state' command gives a valid
|
|
response, False otherwise
|
|
"""
|
|
cmd = 'pd %d state' % port
|
|
regex = r'(Port C\d)|(Parameter)'
|
|
m = console.send_command_get_output(cmd, [regex])
|
|
# If PD port exists, then output will be Port C0 or C1
|
|
regex = r'Port C{0}'.format(port)
|
|
if re.search(regex, m[0][0]):
|
|
return True
|
|
return False
|
|
|
|
def _find_num_pd_ports(self, console):
|
|
"""Determine number of PD ports for a given console
|
|
|
|
@param console: uart console accssed via servo
|
|
|
|
@returns: number of PD ports accessible via console
|
|
"""
|
|
MAX_PORTS = 2
|
|
num_ports = 0
|
|
for port in range(MAX_PORTS):
|
|
if self._send_pd_state(port, console):
|
|
num_ports += 1
|
|
return num_ports
|
|
|
|
def _is_pd_console(self, console):
|
|
"""Check if pd option exists in console
|
|
|
|
@param console: uart console accssed via servo
|
|
|
|
@returns: True if 'pd' is found, False otherwise
|
|
"""
|
|
try:
|
|
m = console.send_command_get_output('help', [r'(pd)\s+'])
|
|
return True
|
|
except error.TestFail:
|
|
return False
|
|
|
|
def _is_pdtester_console(self, console):
|
|
"""Check for PDTester console
|
|
|
|
This method looks for a console command option 'usbc_action' which
|
|
is unique to PDTester PD devices.
|
|
|
|
@param console: uart console accssed via servo
|
|
|
|
@returns True if usbc_action command is present, False otherwise
|
|
"""
|
|
try:
|
|
m = console.send_command_get_output('help', [r'(usbc_action)'])
|
|
return True
|
|
except error.TestFail:
|
|
return False
|
|
|
|
def _check_port_pair(self, port1, port2):
|
|
"""Check if two PD devices could be connected
|
|
|
|
If two USB PD devices are connected, then they should be in
|
|
either the SRC_READY or SNK_READY states and have opposite
|
|
power roles. In addition, they must be on different servo
|
|
consoles.
|
|
|
|
@param: list of two possible PD port parters
|
|
|
|
@returns True if not the same console and both PD devices
|
|
are a plausible pair based only on their PD states.
|
|
"""
|
|
# Don't test if on the same servo console
|
|
if port1.console == port2.console:
|
|
logging.info("PD Devices are on same platform -> can't be a pair")
|
|
return False
|
|
|
|
state1 = port1.get_pd_state()
|
|
port1_is_snk = port1.is_snk(state1)
|
|
port1_is_src = port1.is_src(state1)
|
|
|
|
state2 = port2.get_pd_state()
|
|
port2_is_snk = port2.is_snk(state2)
|
|
port2_is_src = port2.is_src(state2)
|
|
|
|
# Must be SRC <--> SNK or SNK <--> SRC
|
|
if (port1_is_src and port2_is_snk) or (port1_is_snk and port2_is_src):
|
|
logging.debug("SRC+SNK pair: %s (%s) <--> (%s) %s",
|
|
port1, state1, state2, port2)
|
|
return True
|
|
else:
|
|
logging.debug("Not a SRC+SNK pair: %s (%s) <--> (%s) %s",
|
|
port1, state1, state2, port2)
|
|
return False
|
|
|
|
def _verify_pdtester_connection(self, tester_port, dut_port):
|
|
"""Verify DUT to PDTester PD connection
|
|
|
|
This method checks for a PDTester PD connection for the
|
|
given port by first verifying if a PD connection is present.
|
|
If found, then it uses a PDTester feature to force a PD disconnect.
|
|
If the port is no longer in the connected state, and following
|
|
a delay, is found to be back in the connected state, then
|
|
a DUT pd to PDTester connection is verified.
|
|
|
|
@param dev_pair: list of two PD devices
|
|
|
|
@returns True if DUT to PDTester pd connection is verified
|
|
"""
|
|
DISC_CHECK_TIME = 10
|
|
DISC_WAIT_TIME = 20
|
|
CONNECT_TIME = 4
|
|
|
|
logging.info("Check: %s <--> %s", tester_port, dut_port)
|
|
|
|
if not self._check_port_pair(tester_port, dut_port):
|
|
return False
|
|
|
|
# Force PD disconnect
|
|
logging.debug('Disconnecting to check if devices are partners')
|
|
tester_port.cc_disconnect_connect(DISC_WAIT_TIME)
|
|
time.sleep(DISC_CHECK_TIME)
|
|
|
|
# Verify that both devices are now disconnected
|
|
tester_state = tester_port.get_pd_state()
|
|
dut_state = dut_port.get_pd_state()
|
|
logging.debug("Recheck: %s (%s) <--> (%s) %s",
|
|
tester_port, tester_state, dut_state, dut_port)
|
|
|
|
if not (tester_port.is_disconnected(tester_state) and
|
|
dut_port.is_disconnected(dut_state)):
|
|
logging.info("Ports did not disconnect at the same time, so"
|
|
" they aren't considered a pair.")
|
|
# Delay to allow non-pair devices to reconnect
|
|
time.sleep(DISC_WAIT_TIME - DISC_CHECK_TIME + CONNECT_TIME)
|
|
return False
|
|
|
|
logging.debug('Pair disconnected. Waiting for reconnect...')
|
|
|
|
# Allow enough time for reconnection
|
|
time.sleep(DISC_WAIT_TIME - DISC_CHECK_TIME + CONNECT_TIME)
|
|
if self._check_port_pair(tester_port, dut_port):
|
|
# Have verified a pd disconnect/reconnect sequence
|
|
logging.info('PDTester <--> DUT pair found')
|
|
return True
|
|
|
|
logging.info("Ports did not reconnect at the same time, so"
|
|
" they aren't considered a pair.")
|
|
return False
|
|
|
|
def identify_pd_devices(self):
|
|
"""Instantiate PD devices present in test setup
|
|
|
|
@return: list of 2 PD devices if a DUT <-> PDTester found.
|
|
If not found, then returns an empty list.
|
|
"""
|
|
tester_devports = []
|
|
dut_devports = []
|
|
|
|
# For each possible uart console, check to see if a PD console
|
|
# is present and determine the number of PD ports.
|
|
for console in self.consoles:
|
|
if self._is_pd_console(console):
|
|
is_tester = self._is_pdtester_console(console)
|
|
num_ports = self._find_num_pd_ports(console)
|
|
# For each PD port that can be accessed via the console,
|
|
# instantiate either PDConsole or PDTester device.
|
|
for port in range(num_ports):
|
|
if is_tester:
|
|
logging.info('PDTesterDevice on %s port %d',
|
|
console.name, port)
|
|
tester_utils = pd_console.create_pd_console_utils(
|
|
console)
|
|
tester_devports.append(PDTesterDevice(console,
|
|
port, tester_utils))
|
|
else:
|
|
logging.info('PDConsoleDevice on %s port %d',
|
|
console.name, port)
|
|
dut_utils = pd_console.create_pd_console_utils(console)
|
|
dut_devports.append(PDConsoleDevice(console,
|
|
port, dut_utils))
|
|
|
|
if not tester_devports:
|
|
logging.error('The specified consoles did not include any'
|
|
' PD testers: %s', self.consoles)
|
|
|
|
if not dut_devports:
|
|
logging.error('The specified consoles did not contain any'
|
|
' DUTs: %s', self.consoles)
|
|
|
|
# Determine PD port partners in the list of PD devices. Note that
|
|
# there can be PD devices which are not accessible via a uart console,
|
|
# but are connected to a PD port which is accessible.
|
|
for tester in reversed(tester_devports):
|
|
for dut in dut_devports:
|
|
if tester.console == dut.console:
|
|
# PD Devices are on same servo console -> can't be a pair
|
|
continue
|
|
if self._verify_pdtester_connection(tester, dut):
|
|
dut_devports.remove(dut)
|
|
return [tester, dut]
|
|
|
|
return []
|