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
5import logging
6import time
7
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros import constants
10from autotest_lib.server import autotest
11
12POWER_DIR = '/var/lib/power_manager'
13TMP_POWER_DIR = '/tmp/power_manager'
14POWER_DEFAULTS = '/usr/share/power_manager/board_specific'
15
16RESUME_CTRL_RETRIES = 3
17RESUME_GRACE_PERIOD = 10
18XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
19
20
21class 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
58class 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('restart 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('restart 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
173def _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