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