Home | History | Annotate | Download | only in cros
      1 # Copyright 2014 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import time
      7 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.cros import constants
     10 from autotest_lib.server import autotest
     11 
     12 POWER_DIR = '/var/lib/power_manager'
     13 TMP_POWER_DIR = '/tmp/power_manager'
     14 POWER_DEFAULTS = '/usr/share/power_manager/board_specific'
     15 
     16 RESUME_CTRL_RETRIES = 3
     17 RESUME_GRACE_PERIOD = 10
     18 XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
     19 
     20 
     21 class DarkResumeSuspend(object):
     22     """Context manager which exposes the dark resume-specific suspend
     23     functionality.
     24 
     25     This is required because using the RTC for a dark resume test will
     26     cause the system to wake up in dark resume and resuspend, which is
     27     not what we want. Instead, we suspend indefinitely, but make sure we
     28     don't leave the DUT asleep by always running code to wake it up via
     29     servo.
     30     """
     31 
     32 
     33     def __init__(self, proxy, host):
     34         """Set up for a dark-resume-ready suspend to be carried out using
     35         |proxy| and for the subsequent wakeup to be carried out using
     36         |servo|.
     37 
     38         @param proxy: a dark resume xmlrpc server proxy object for the DUT
     39         @param servo: a servo host connected to the DUT
     40 
     41         """
     42         self._client_proxy = proxy
     43         self._host = host
     44 
     45 
     46     def __enter__(self):
     47         """Suspend the DUT."""
     48         logging.info('Suspending DUT (in background)...')
     49         self._client_proxy.suspend_bg_for_dark_resume()
     50 
     51 
     52     def __exit__(self, exception, value, traceback):
     53         """Wake up the DUT."""
     54         logging.info('Waking DUT from server.')
     55         _wake_dut(self._host)
     56 
     57 
     58 class DarkResumeUtils(object):
     59     """Class containing common functionality for tests which exercise dark
     60     resume pathways. We set up powerd to allow dark resume and also configure
     61     the suspended devices so that the backchannel can stay up. We can also
     62     check for the number of dark resumes that have happened in a particular
     63     suspend request.
     64     """
     65 
     66 
     67     def __init__(self, host, duration=0):
     68         """Set up powerd preferences so we will properly go into dark resume,
     69         and still be able to communicate with the DUT.
     70 
     71         @param host: the DUT to set up dark resume for
     72 
     73         """
     74         self._host = host
     75         logging.info('Setting up dark resume preferences')
     76 
     77         # Make temporary directory, which will be used to hold
     78         # temporary preferences. We want to avoid writing into
     79         # /var/lib so we don't have to save any state.
     80         logging.debug('Creating temporary powerd prefs at %s', TMP_POWER_DIR)
     81         host.run('mkdir -p %s' % TMP_POWER_DIR)
     82 
     83         logging.debug('Enabling dark resume')
     84         host.run('echo 0 > %s/disable_dark_resume' % TMP_POWER_DIR)
     85 
     86         logging.debug('Enabling USB ports in dark resume')
     87 
     88         dev_contents = host.run('cat %s/dark_resume_devices' % POWER_DEFAULTS,
     89                                 ignore_status=True).stdout
     90         dev_list = dev_contents.split('\n')
     91         new_dev_list = filter(lambda dev: dev.find('usb') == -1, dev_list)
     92         new_dev_contents = '\n'.join(new_dev_list)
     93         host.run('echo -e \'%s\' > %s/dark_resume_devices' %
     94                  (new_dev_contents, TMP_POWER_DIR))
     95 
     96         if duration > 0:
     97             # override suspend durations preference for dark resume
     98             logging.info('setting dark_resume_suspend_durations=%d', duration)
     99             host.run('echo 0.0 %d > %s/dark_resume_suspend_durations' %
    100                      (duration, TMP_POWER_DIR))
    101 
    102         # bind the tmp directory to the power preference directory
    103         host.run('mount --bind %s %s' % (TMP_POWER_DIR, POWER_DIR))
    104 
    105         logging.debug('Restarting powerd with new settings')
    106         host.run('stop powerd; start powerd')
    107 
    108         logging.debug('Starting XMLRPC session to watch for dark resumes')
    109         self._client_proxy = self._get_xmlrpc_proxy()
    110 
    111 
    112     def teardown(self):
    113         """Clean up changes made by DarkResumeUtils."""
    114 
    115         logging.info('Tearing down dark resume preferences')
    116 
    117         logging.debug('Cleaning up temporary powerd bind mounts')
    118         self._host.run('umount %s' % POWER_DIR)
    119 
    120         logging.debug('Restarting powerd to revert to old settings')
    121         self._host.run('stop powerd; start powerd')
    122 
    123 
    124     def suspend(self):
    125         """Returns a DarkResumeSuspend context manager that allows safe suspending
    126         of the DUT."""
    127         return DarkResumeSuspend(self._client_proxy, self._host)
    128 
    129 
    130     def count_dark_resumes(self):
    131         """Return the number of dark resumes that have occurred since the beginning
    132         of the test. This will wake up the DUT, so make sure to put it back to
    133         sleep if you need to keep it suspended for some reason.
    134 
    135         This method will raise an error if the DUT does not wake up.
    136 
    137         @return the number of dark resumes counted by this DarkResumeUtils
    138 
    139         """
    140         _wake_dut(self._host)
    141 
    142         return self._client_proxy.get_dark_resume_count()
    143 
    144 
    145     def _get_xmlrpc_proxy(self):
    146         """Get a dark resume XMLRPC proxy for the host this DarkResumeUtils is
    147         attached to.
    148 
    149         The returned object has no particular type.  Instead, when you call
    150         a method on the object, it marshalls the objects passed as arguments
    151         and uses them to make RPCs on the remote server.  Thus, you should
    152         read dark_resume_xmlrpc_server.py to find out what methods are supported.
    153 
    154         @return proxy object for remote XMLRPC server.
    155 
    156         """
    157         # Make sure the client library is on the device so that the proxy
    158         # code is there when we try to call it.
    159         client_at = autotest.Autotest(self._host)
    160         client_at.install()
    161         # Start up the XMLRPC proxy on the client
    162         proxy = self._host.rpc_server_tracker.xmlrpc_connect(
    163                 constants.DARK_RESUME_XMLRPC_SERVER_COMMAND,
    164                 constants.DARK_RESUME_XMLRPC_SERVER_PORT,
    165                 command_name=
    166                     constants.DARK_RESUME_XMLRPC_SERVER_CLEANUP_PATTERN,
    167                 ready_test_name=
    168                     constants.DARK_RESUME_XMLRPC_SERVER_READY_METHOD,
    169                 timeout_seconds=XMLRPC_BRINGUP_TIMEOUT_SECONDS)
    170         return proxy
    171 
    172 
    173 def _wake_dut(host):
    174     """Make sure |host| is up. If we can't wake it with normal keys, hit the
    175     power button."""
    176 
    177     woken = False
    178     for i in range(RESUME_CTRL_RETRIES):
    179         # Double tap servo key to make sure we signal user activity to Chrome.
    180         # The first key might occur during the kernel suspend pathway, which
    181         # causes the suspend to abort, but might put us in dark resume since
    182         # the keypress is not forwarded to Chrome.
    183         host.servo.ctrl_key()
    184         time.sleep(0.5)
    185         host.servo.ctrl_key()
    186 
    187         if host.wait_up(timeout=RESUME_GRACE_PERIOD):
    188             woken = True
    189             break
    190         logging.debug('Wake attempt #%d failed', i+1)
    191 
    192     if not woken:
    193         logging.warning('DUT did not wake -- trouble ahead')
    194         host.servo.power_key()
    195         raise error.TestFail('DUT did not wake')
    196