195 lines
7 KiB
Python
195 lines
7 KiB
Python
# Copyright 2014 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.client.cros import constants
|
|
from autotest_lib.server import autotest
|
|
|
|
POWER_DIR = '/var/lib/power_manager'
|
|
TMP_POWER_DIR = '/tmp/power_manager'
|
|
POWER_DEFAULTS = '/usr/share/power_manager/board_specific'
|
|
|
|
RESUME_CTRL_RETRIES = 3
|
|
RESUME_GRACE_PERIOD = 10
|
|
XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
|
|
|
|
|
|
class DarkResumeSuspend(object):
|
|
"""Context manager which exposes the dark resume-specific suspend
|
|
functionality.
|
|
|
|
This is required because using the RTC for a dark resume test will
|
|
cause the system to wake up in dark resume and resuspend, which is
|
|
not what we want. Instead, we suspend indefinitely, but make sure we
|
|
don't leave the DUT asleep by always running code to wake it up via
|
|
servo.
|
|
"""
|
|
|
|
|
|
def __init__(self, proxy, host):
|
|
"""Set up for a dark-resume-ready suspend to be carried out using
|
|
|proxy| and for the subsequent wakeup to be carried out using
|
|
|host|.
|
|
|
|
@param proxy: a dark resume xmlrpc server proxy object for the DUT
|
|
@param host: a servo host connected to the DUT
|
|
|
|
"""
|
|
self._client_proxy = proxy
|
|
self._host = host
|
|
|
|
|
|
def __enter__(self):
|
|
"""Suspend the DUT."""
|
|
logging.info('Suspending DUT (in background)...')
|
|
self._client_proxy.suspend_bg_for_dark_resume()
|
|
|
|
|
|
def __exit__(self, exception, value, traceback):
|
|
"""Wake up the DUT."""
|
|
logging.info('Waking DUT from server.')
|
|
_wake_dut(self._host)
|
|
|
|
|
|
class DarkResumeUtils(object):
|
|
"""Class containing common functionality for tests which exercise dark
|
|
resume pathways. We set up powerd to allow dark resume and also configure
|
|
the suspended devices so that the backchannel can stay up. We can also
|
|
check for the number of dark resumes that have happened in a particular
|
|
suspend request.
|
|
"""
|
|
|
|
|
|
def __init__(self, host, duration=0):
|
|
"""Set up powerd preferences so we will properly go into dark resume,
|
|
and still be able to communicate with the DUT.
|
|
|
|
@param host: the DUT to set up dark resume for
|
|
|
|
"""
|
|
self._host = host
|
|
logging.info('Setting up dark resume preferences')
|
|
|
|
# Make temporary directory, which will be used to hold
|
|
# temporary preferences. We want to avoid writing into
|
|
# /var/lib so we don't have to save any state.
|
|
logging.debug('Creating temporary powerd prefs at %s', TMP_POWER_DIR)
|
|
host.run('mkdir -p %s' % TMP_POWER_DIR)
|
|
|
|
logging.debug('Enabling dark resume')
|
|
host.run('echo 0 > %s/disable_dark_resume' % TMP_POWER_DIR)
|
|
|
|
logging.debug('Enabling USB ports in dark resume')
|
|
|
|
dev_contents = host.run('cat %s/dark_resume_devices' % POWER_DEFAULTS,
|
|
ignore_status=True).stdout
|
|
dev_list = dev_contents.split('\n')
|
|
new_dev_list = filter(lambda dev: dev.find('usb') == -1, dev_list)
|
|
new_dev_contents = '\n'.join(new_dev_list)
|
|
host.run('echo -e \'%s\' > %s/dark_resume_devices' %
|
|
(new_dev_contents, TMP_POWER_DIR))
|
|
|
|
if duration > 0:
|
|
# override suspend durations preference for dark resume
|
|
logging.info('setting dark_resume_suspend_durations=%d', duration)
|
|
host.run('echo 0.0 %d > %s/dark_resume_suspend_durations' %
|
|
(duration, TMP_POWER_DIR))
|
|
|
|
# bind the tmp directory to the power preference directory
|
|
host.run('mount --bind %s %s' % (TMP_POWER_DIR, POWER_DIR))
|
|
|
|
logging.debug('Restarting powerd with new settings')
|
|
host.run('stop powerd; start powerd')
|
|
|
|
logging.debug('Starting XMLRPC session to watch for dark resumes')
|
|
self._client_proxy = self._get_xmlrpc_proxy()
|
|
|
|
|
|
def teardown(self):
|
|
"""Clean up changes made by DarkResumeUtils."""
|
|
|
|
logging.info('Tearing down dark resume preferences')
|
|
|
|
logging.debug('Cleaning up temporary powerd bind mounts')
|
|
self._host.run('umount %s' % POWER_DIR)
|
|
|
|
logging.debug('Restarting powerd to revert to old settings')
|
|
self._host.run('stop powerd; start powerd')
|
|
|
|
|
|
def suspend(self):
|
|
"""Returns a DarkResumeSuspend context manager that allows safe suspending
|
|
of the DUT."""
|
|
return DarkResumeSuspend(self._client_proxy, self._host)
|
|
|
|
|
|
def count_dark_resumes(self):
|
|
"""Return the number of dark resumes that have occurred since the beginning
|
|
of the test. This will wake up the DUT, so make sure to put it back to
|
|
sleep if you need to keep it suspended for some reason.
|
|
|
|
This method will raise an error if the DUT does not wake up.
|
|
|
|
@return the number of dark resumes counted by this DarkResumeUtils
|
|
|
|
"""
|
|
_wake_dut(self._host)
|
|
|
|
return self._client_proxy.get_dark_resume_count()
|
|
|
|
|
|
def _get_xmlrpc_proxy(self):
|
|
"""Get a dark resume XMLRPC proxy for the host this DarkResumeUtils is
|
|
attached to.
|
|
|
|
The returned object has no particular type. Instead, when you call
|
|
a method on the object, it marshalls the objects passed as arguments
|
|
and uses them to make RPCs on the remote server. Thus, you should
|
|
read dark_resume_xmlrpc_server.py to find out what methods are supported.
|
|
|
|
@return proxy object for remote XMLRPC server.
|
|
|
|
"""
|
|
# Make sure the client library is on the device so that the proxy
|
|
# code is there when we try to call it.
|
|
client_at = autotest.Autotest(self._host)
|
|
client_at.install()
|
|
# Start up the XMLRPC proxy on the client
|
|
proxy = self._host.rpc_server_tracker.xmlrpc_connect(
|
|
constants.DARK_RESUME_XMLRPC_SERVER_COMMAND,
|
|
constants.DARK_RESUME_XMLRPC_SERVER_PORT,
|
|
command_name=
|
|
constants.DARK_RESUME_XMLRPC_SERVER_CLEANUP_PATTERN,
|
|
ready_test_name=
|
|
constants.DARK_RESUME_XMLRPC_SERVER_READY_METHOD,
|
|
timeout_seconds=XMLRPC_BRINGUP_TIMEOUT_SECONDS)
|
|
return proxy
|
|
|
|
|
|
def _wake_dut(host):
|
|
"""Make sure |host| is up. If we can't wake it with normal keys, hit the
|
|
power button."""
|
|
|
|
woken = False
|
|
for i in range(RESUME_CTRL_RETRIES):
|
|
# Double tap servo key to make sure we signal user activity to Chrome.
|
|
# The first key might occur during the kernel suspend pathway, which
|
|
# causes the suspend to abort, but might put us in dark resume since
|
|
# the keypress is not forwarded to Chrome.
|
|
host.servo.ctrl_key()
|
|
time.sleep(0.5)
|
|
host.servo.ctrl_key()
|
|
|
|
if host.wait_up(timeout=RESUME_GRACE_PERIOD):
|
|
woken = True
|
|
break
|
|
logging.debug('Wake attempt #%d failed', i+1)
|
|
|
|
if not woken:
|
|
logging.warning('DUT did not wake -- trouble ahead')
|
|
host.servo.power_key()
|
|
raise error.TestFail('DUT did not wake')
|