285 lines
12 KiB
Python
285 lines
12 KiB
Python
# 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.
|
|
|
|
import logging
|
|
import time
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
|
|
from autotest_lib.server.cros.servo import pd_device
|
|
|
|
|
|
class firmware_PDPowerSwap(FirmwareTest):
|
|
"""
|
|
Servo based USB PD power role swap test.
|
|
|
|
Pass critera is all power role swaps are successful if the DUT
|
|
is dualrole capable. If not dualrole, then pass criteria is
|
|
the DUT sending a reject message in response to swap request.
|
|
|
|
"""
|
|
|
|
version = 1
|
|
PD_ROLE_DELAY = 1
|
|
PD_CONNECT_DELAY = 10
|
|
# Should be an even number; back to the original state at the end
|
|
POWER_SWAP_ITERATIONS = 10
|
|
|
|
def _set_pdtester_power_role_to_src(self):
|
|
"""Force PDTester to act as a source
|
|
|
|
@returns True if PDTester power role is source, false otherwise
|
|
"""
|
|
PDTESTER_SRC_VOLTAGE = 20
|
|
self.pdtester.charge(PDTESTER_SRC_VOLTAGE)
|
|
# Wait for change to take place
|
|
time.sleep(self.PD_CONNECT_DELAY)
|
|
# Current PDTester power role should be source
|
|
return self.pdtester_port.is_src()
|
|
|
|
def _send_power_swap_get_reply(self, port):
|
|
"""Send power swap request, get PD control msg reply
|
|
|
|
The PD console debug mode is enabled prior to sending
|
|
a pd power role swap request message. This allows the
|
|
control message reply to be extracted. The debug mode
|
|
is disabled prior to exiting.
|
|
|
|
@param port: pd device object
|
|
|
|
@returns: PD control header message
|
|
"""
|
|
# Enable PD console debug mode to show control messages
|
|
port.utils.enable_pd_console_debug()
|
|
cmd = 'pd %d swap power' % port.port
|
|
m = port.utils.send_pd_command_get_output(cmd, ['RECV\s([\w]+)'])
|
|
ctrl_msg = int(m[0][1], 16) & port.utils.PD_CONTROL_MSG_MASK
|
|
port.utils.disable_pd_console_debug()
|
|
return ctrl_msg
|
|
|
|
def _attempt_power_swap(self, direction):
|
|
"""Perform a power role swap request
|
|
|
|
Initiate a power role swap request on either the DUT or
|
|
PDTester depending on the direction parameter. The power
|
|
role swap is then verified to have taken place.
|
|
|
|
@param direction: rx or tx from the DUT perspective
|
|
|
|
@returns True if power swap is successful
|
|
"""
|
|
# Get DUT current power role
|
|
dut_pr = self.dut_port.get_pd_state()
|
|
if direction == 'rx':
|
|
port = self.pdtester_port
|
|
else:
|
|
port = self.dut_port
|
|
# Send power swap request
|
|
self._send_power_swap_get_reply(port)
|
|
time.sleep(self.PD_CONNECT_DELAY)
|
|
# Get PDTester power role
|
|
pdtester_pr = self.pdtester_port.get_pd_state()
|
|
if self.dut_port.is_src(dut_pr) and self.pdtester_port.is_src(pdtester_pr):
|
|
return True
|
|
elif self.dut_port.is_snk(dut_pr) and self.pdtester_port.is_snk(pdtester_pr):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def _test_power_swap_reject(self):
|
|
"""Verify that a power swap request is rejected
|
|
|
|
This tests the case where the DUT isn't in dualrole mode.
|
|
A power swap request is sent by PDTester, and then
|
|
the control message checked to ensure the request was rejected.
|
|
In addition, the connection state is verified to not have
|
|
changed.
|
|
"""
|
|
# Get current DUT power role
|
|
dut_power_role = self.dut_port.get_pd_state()
|
|
# Send swap command from PDTester and get reply
|
|
ctrl_msg = self._send_power_swap_get_reply(self.pdtester_port)
|
|
if ctrl_msg != self.dut_port.utils.PD_CONTROL_MSG_DICT['Reject']:
|
|
raise error.TestFail('Power Swap Req not rejected, returned %r' %
|
|
ctrl_msg)
|
|
# Get DUT current state
|
|
pd_state = self.dut_port.get_pd_state()
|
|
if pd_state != dut_power_role:
|
|
raise error.TestFail('PD not connected! pd_state = %r' %
|
|
pd_state)
|
|
|
|
def _test_one_way_power_swap_in_suspend(self):
|
|
"""Verify SRC-to-SNK power role swap on DUT PD port in S0ix/S3
|
|
|
|
Set the DUT power role to source and then suspend the DUT.
|
|
Verify SRC-to-SNK power role request from the PD tester works,
|
|
while SNK-to-SRC power role request fails. Note that this is
|
|
Chrome OS policy decision, not part of the PD spec.
|
|
|
|
When DUT doesn't provide power in suspend, set DUT power role
|
|
to sink, supend DUT and check if SNK-to-SRC power role request fails.
|
|
|
|
"""
|
|
# Ensure DUT PD port is sourcing power.
|
|
time.sleep(self.PD_CONNECT_DELAY)
|
|
if self.faft_config.dut_can_source_power_in_suspend:
|
|
if not self.dut_port.is_src():
|
|
if self._attempt_power_swap('rx') == False or not self.dut_port.is_src():
|
|
raise error.TestFail('Fail to set DUT power role to source.')
|
|
|
|
self.set_ap_off_power_mode('suspend')
|
|
|
|
new_state = self.dut_port.get_pd_state()
|
|
if not self.dut_port.is_src(new_state):
|
|
raise error.TestFail('DUT power role changed to %s '
|
|
'during S0-to-S3 transition!' % new_state)
|
|
|
|
# If the DUT PD port supports DRP in S0, it should supports SRC-to-SNK
|
|
# power role swap in suspend mode. The other way around (SNK-to-SRC) in
|
|
# suspend mode should fail.
|
|
logging.info('Request a SRC-to-SNK power role swap to DUT PD port '
|
|
'in suspend mode. Expect to succeed.')
|
|
if self._attempt_power_swap('rx') == False:
|
|
raise error.TestFail('SRC-to-SNK power role swap failed.')
|
|
|
|
# If we define AC insertion as a wake source for this board, the
|
|
# SRC-to-SNK power role swap would wake up the DUT. In this case,
|
|
# we need to suspend the DUT again to proceed the test.
|
|
if self.wait_power_state(self.POWER_STATE_S0,
|
|
self.DEFAULT_PWR_RETRIES):
|
|
self.set_ap_off_power_mode('suspend')
|
|
else:
|
|
# If DUT can't source power in suspend we are going to test only if
|
|
# SNK-to-SRC transition fails, so make sure that port acts as sink
|
|
logging.info('DUT is not sourcing power in suspend - '
|
|
'skip verification if SRC-to-SNK power swap works')
|
|
if not self.dut_port.is_snk():
|
|
if self._attempt_power_swap('rx') == False or not self.dut_port.is_snk():
|
|
raise error.TestFail('Fail to set DUT power role to sink.')
|
|
|
|
self.set_ap_off_power_mode('suspend')
|
|
|
|
new_state = self.dut_port.get_pd_state()
|
|
if not self.dut_port.is_snk(new_state):
|
|
raise error.TestFail('DUT power role changed to %s '
|
|
'during S0-to-S3 transition!' % new_state)
|
|
|
|
logging.info('Request a SNK-to-SRC power role swap to DUT PD port '
|
|
'in suspend mode. Expect to fail.')
|
|
self._test_power_swap_reject()
|
|
|
|
self.restore_ap_on_power_mode()
|
|
|
|
def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False,
|
|
init_power_mode=None):
|
|
super(firmware_PDPowerSwap, self).initialize(host, cmdline_args)
|
|
self.setup_pdtester(flip_cc, dts_mode, min_batt_level=10)
|
|
# Only run in normal mode
|
|
self.switcher.setup_mode('normal')
|
|
if init_power_mode:
|
|
# Set the DUT to suspend (S3) or shutdown (G3/S5) mode
|
|
self.set_ap_off_power_mode(init_power_mode)
|
|
# Turn off console prints, except for USBPD.
|
|
self.usbpd.enable_console_channel('usbpd')
|
|
|
|
def cleanup(self):
|
|
if hasattr(self, 'pd_port'):
|
|
# Restore DUT dual role operation
|
|
self.dut_port.drp_set('on')
|
|
if hasattr(self, 'pdtester_port'):
|
|
# Set connection back to default arrangement
|
|
self.pdtester_port.drp_set('off')
|
|
|
|
if hasattr(self, 'pd_port') and hasattr(self, 'pdtester_port'):
|
|
# Fake-disconnect to restore the original power role
|
|
self.pdtester_port.utils.send_pd_command('fakedisconnect 100 1000')
|
|
|
|
self.usbpd.send_command('chan 0xffffffff')
|
|
self.restore_ap_on_power_mode()
|
|
super(firmware_PDPowerSwap, self).cleanup()
|
|
|
|
def run_once(self):
|
|
"""Execute Power Role swap test.
|
|
|
|
1. Verify that pd console is accessible
|
|
2. Verify that DUT has a valid PD contract and connected to PDTester
|
|
3. Determine if DUT is in dualrole mode
|
|
4. If dualrole mode is not supported, verify DUT rejects power swap
|
|
request. If dualrole mode is supported, test power swap (tx/rx) in
|
|
S0 and one-way power swap (SRC-to-SNK on DUT PD port) in S3/S0ix.
|
|
Then force DUT to be sink or source only and verify rejection of
|
|
power swap request.
|
|
|
|
"""
|
|
# Create list of available UART consoles
|
|
consoles = [self.usbpd, self.pdtester]
|
|
port_partner = pd_device.PDPortPartner(consoles)
|
|
|
|
# Identify a valid test port pair
|
|
port_pair = port_partner.identify_pd_devices()
|
|
if not port_pair:
|
|
raise error.TestFail('No PD connection found!')
|
|
|
|
for port in port_pair:
|
|
if port.is_pdtester:
|
|
self.pdtester_port = port
|
|
else:
|
|
self.dut_port = port
|
|
|
|
dut_connect_state = self.dut_port.get_pd_state()
|
|
logging.info('Initial DUT connect state = %s', dut_connect_state)
|
|
|
|
# Get DUT dualrole status
|
|
if self.dut_port.is_drp() == False:
|
|
# DUT does not support dualrole mode, power swap
|
|
# requests to the DUT should be rejected.
|
|
logging.info('Power Swap support not advertised by DUT')
|
|
self._test_power_swap_reject()
|
|
logging.info('Power Swap request rejected by DUT as expected')
|
|
else:
|
|
if self.get_power_state() != 'S0':
|
|
raise error.TestFail('If the DUT is not is S0, the DUT port '
|
|
'shouldn\'t claim dualrole is enabled.')
|
|
# Start with PDTester as source
|
|
if self._set_pdtester_power_role_to_src() == False:
|
|
raise error.TestFail('PDTester not set to source')
|
|
# DUT is dualrole in dual role mode. Test power role swap
|
|
# operation intiated both by the DUT and PDTester.
|
|
success = 0
|
|
for attempt in xrange(self.POWER_SWAP_ITERATIONS):
|
|
if attempt & 1:
|
|
direction = 'rx'
|
|
else:
|
|
direction = 'tx'
|
|
if self._attempt_power_swap(direction):
|
|
success += 1
|
|
new_state = self.dut_port.get_pd_state()
|
|
logging.info('New DUT power role = %s', new_state)
|
|
|
|
if success != self.POWER_SWAP_ITERATIONS:
|
|
raise error.TestFail('Failed %r power swap attempts' %
|
|
(self.POWER_SWAP_ITERATIONS - success))
|
|
|
|
self._test_one_way_power_swap_in_suspend()
|
|
|
|
# Force DUT to only support current power role
|
|
if self.dut_port.is_src(new_state):
|
|
dual_mode = 'src'
|
|
else:
|
|
dual_mode = 'snk'
|
|
|
|
# Save current dual role setting
|
|
current_dual_role = self.dut_port.drp_get()
|
|
|
|
try:
|
|
logging.info('Setting dualrole mode to %s', dual_mode)
|
|
self.dut_port.drp_set(dual_mode)
|
|
time.sleep(self.PD_ROLE_DELAY)
|
|
# Expect behavior now is that DUT will reject power swap
|
|
self._test_power_swap_reject()
|
|
logging.info('Power Swap request rejected by DUT as expected')
|
|
finally:
|
|
# Restore dual role setting
|
|
self.dut_port.drp_set(current_dual_role)
|