Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 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 |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 6 | import time |
| 7 | |
| 8 | from autotest_lib.client.common_lib import error |
| 9 | from autotest_lib.client.common_lib.cros import dark_resume_xmlrpc_server |
| 10 | from autotest_lib.server import autotest |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 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 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 16 | RESUME_CTRL_RETRIES = 3 |
| 17 | RESUME_GRACE_PERIOD = 10 |
| 18 | XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60 |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 19 | |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 20 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 21 | class DarkResumeSuspend(object): |
| 22 | """Context manager which exposes the dark resume-specific suspend |
| 23 | functionality. |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 24 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 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. |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 30 | """ |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 31 | |
| 32 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 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|. |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 37 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 38 | @param proxy: a dark resume xmlrpc server proxy object for the DUT |
| 39 | @param servo: a servo host connected to the DUT |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 40 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 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. |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 64 | """ |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 65 | |
Eric Caruso | 1034fb4 | 2014-12-17 17:26:37 -0800 | [diff] [blame] | 66 | |
Eric Caruso | db83bfa | 2015-03-13 13:27:21 -0700 | [diff] [blame^] | 67 | 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 | |
| 165 | def _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') |