blob: 634db1b651b30b201749a7a12ed5a9fa47ced712 [file] [log] [blame]
Eric Caruso1034fb42014-12-17 17:26:37 -08001# 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
Eric Carusodb83bfa2015-03-13 13:27:21 -07006import time
7
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib.cros import dark_resume_xmlrpc_server
10from autotest_lib.server import autotest
Eric Caruso1034fb42014-12-17 17:26:37 -080011
12POWER_DIR = '/var/lib/power_manager'
13TMP_POWER_DIR = '/tmp/power_manager'
14POWER_DEFAULTS = '/usr/share/power_manager/board_specific'
15
Eric Carusodb83bfa2015-03-13 13:27:21 -070016RESUME_CTRL_RETRIES = 3
17RESUME_GRACE_PERIOD = 10
18XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
Eric Caruso1034fb42014-12-17 17:26:37 -080019
Eric Caruso1034fb42014-12-17 17:26:37 -080020
Eric Carusodb83bfa2015-03-13 13:27:21 -070021class DarkResumeSuspend(object):
22 """Context manager which exposes the dark resume-specific suspend
23 functionality.
Eric Caruso1034fb42014-12-17 17:26:37 -080024
Eric Carusodb83bfa2015-03-13 13:27:21 -070025 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.
Eric Caruso1034fb42014-12-17 17:26:37 -080030 """
Eric Caruso1034fb42014-12-17 17:26:37 -080031
32
Eric Carusodb83bfa2015-03-13 13:27:21 -070033 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|.
Eric Caruso1034fb42014-12-17 17:26:37 -080037
Eric Carusodb83bfa2015-03-13 13:27:21 -070038 @param proxy: a dark resume xmlrpc server proxy object for the DUT
39 @param servo: a servo host connected to the DUT
Eric Caruso1034fb42014-12-17 17:26:37 -080040
Eric Carusodb83bfa2015-03-13 13:27:21 -070041 """
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.
Eric Caruso1034fb42014-12-17 17:26:37 -080064 """
Eric Caruso1034fb42014-12-17 17:26:37 -080065
Eric Caruso1034fb42014-12-17 17:26:37 -080066
Eric Carusodb83bfa2015-03-13 13:27:21 -070067 def __init__(self, host):
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 # bind the tmp directory to the power preference directory
97 host.run('mount --bind %s %s' % (TMP_POWER_DIR, POWER_DIR))
98
99 logging.debug('Restarting powerd with new settings')
100 host.run('restart powerd')
101
102 logging.debug('Starting XMLRPC session to watch for dark resumes')
103 self._client_proxy = self._get_xmlrpc_proxy()
104
105
106 def teardown(self):
107 """Clean up changes made by DarkResumeUtils."""
108
109 logging.info('Tearing down dark resume preferences')
110
111 logging.debug('Cleaning up temporary powerd bind mounts')
112 self._host.run('umount %s' % POWER_DIR, ignore_status=True)
113
114 logging.debug('Restarting powerd to revert to old settings')
115 self._host.run('restart powerd')
116
117
118 def suspend(self):
119 """Returns a DarkResumeSuspend context manager that allows safe suspending
120 of the DUT."""
121 return DarkResumeSuspend(self._client_proxy, self._host)
122
123
124 def count_dark_resumes(self):
125 """Return the number of dark resumes that have occurred since the beginning
126 of the test. This will wake up the DUT, so make sure to put it back to
127 sleep if you need to keep it suspended for some reason.
128
129 This method will raise an error if the DUT does not wake up.
130
131 @return the number of dark resumes counted by this DarkResumeUtils
132
133 """
134 _wake_dut(self._host)
135
136 return self._client_proxy.get_dark_resume_count()
137
138
139 def _get_xmlrpc_proxy(self):
140 """Get a dark resume XMLRPC proxy for the host this DarkResumeUtils is
141 attached to.
142
143 The returned object has no particular type. Instead, when you call
144 a method on the object, it marshalls the objects passed as arguments
145 and uses them to make RPCs on the remote server. Thus, you should
146 read dark_resume_xmlrpc_server.py to find out what methods are supported.
147
148 @return proxy object for remote XMLRPC server.
149
150 """
151 # Make sure the client library is on the device so that the proxy
152 # code is there when we try to call it.
153 client_at = autotest.Autotest(self._host)
154 client_at.install()
155 # Start up the XMLRPC proxy on the client
156 proxy = self._host.xmlrpc_connect(
157 dark_resume_xmlrpc_server.SERVER_COMMAND,
158 dark_resume_xmlrpc_server.SERVER_PORT,
159 command_name=dark_resume_xmlrpc_server.CLEANUP_PATTERN,
160 ready_test_name=dark_resume_xmlrpc_server.READY_METHOD,
161 timeout_seconds=XMLRPC_BRINGUP_TIMEOUT_SECONDS)
162 return proxy
163
164
165def _wake_dut(host):
166 """Make sure |host| is up. If we can't wake it with normal keys, hit the
167 power button."""
168
169 woken = False
170 for i in range(RESUME_CTRL_RETRIES):
171 # Double tap servo key to make sure we signal user activity to Chrome.
172 # The first key might occur during the kernel suspend pathway, which
173 # causes the suspend to abort, but might put us in dark resume since
174 # the keypress is not forwarded to Chrome.
175 host.servo.ctrl_key()
176 time.sleep(0.5)
177 host.servo.ctrl_key()
178
179 if host.wait_up(timeout=RESUME_GRACE_PERIOD):
180 woken = True
181 break
182 logging.debug('Wake attempt #%d failed', i+1)
183
184 if not woken:
185 logging.warning('DUT did not wake -- trouble ahead')
186 host.servo.power_key()
187 raise error.TestFail('DUT did not wake')