1# Copyright (c) 2012 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"""Provides utility methods for controlling powerd in ChromiumOS.""" 6 7import errno 8import fcntl 9import logging 10import multiprocessing 11import os 12import time 13 14import common 15from autotest_lib.client.cros import rtc 16from autotest_lib.client.cros import upstart 17 18SYSFS_POWER_STATE = '/sys/power/state' 19SYSFS_WAKEUP_COUNT = '/sys/power/wakeup_count' 20 21PAUSE_ETHERNET_HOOK_FILE = '/run/autotest_pause_ethernet_hook' 22pause_ethernet_fd = 0 23 24 25class SuspendFailure(Exception): 26 """Base class for a failure during a single suspend/resume cycle.""" 27 pass 28 29 30class SuspendTimeout(SuspendFailure): 31 """Suspend took too long, got wakeup event (RTC tick) before it was done.""" 32 pass 33 34 35class KernelError(SuspendFailure): 36 """Kernel problem encountered during suspend/resume. 37 38 Whitelist is an array of a 2-entry tuples consisting of (source regexp, text 39 regexp). For example, kernel output might look like, 40 41 [ 597.079950] WARNING: CPU: 0 PID: 21415 at \ 42 <path>/v3.18/drivers/gpu/drm/i915/intel_pm.c:3687 \ 43 skl_update_other_pipe_wm+0x136/0x18a() 44 [ 597.079962] WARN_ON(!wm_changed) 45 46 source regexp should match first line above while text regexp can match 47 up to 2 lines below the source. Note timestamps are stripped prior to 48 comparing regexp. 49 """ 50 WHITELIST = [ 51 # crosbug.com/37594: debug tracing clock desync we don't care about 52 (r'kernel/trace/ring_buffer.c:\d+ rb_reserve_next_event', 53 r'Delta way too big!'), 54 # TODO(crosbug.com/p/52008): Remove from whitelist once watermark 55 # implementation has landed. 56 (r'v3.18/\S+/intel_pm.c:\d+ skl_update_other_pipe_wm', 57 r'WARN_ON\(\!wm_changed\)') 58 ] 59 60 61class FirmwareError(SuspendFailure): 62 """String 'ERROR' found in firmware log after resume.""" 63 WHITELIST = [ 64 # crosbug.com/36762: no one knows, but it has always been there 65 ('^stumpy', r'PNP: 002e\.4 70 irq size: 0x0000000001 not assigned'), 66 # crbug.com/221538: no one knows what ME errors mean anyway 67 ('^parrot', r'ME failed to respond'), 68 # b/64684441: eve SKU without eMMC 69 ('^eve', r'Card did not respond to voltage select!'), 70 ] 71 72 73class SpuriousWakeupError(SuspendFailure): 74 """Received spurious wakeup while suspending or woke before schedule.""" 75 S3_WHITELIST = [ # (<board>, <eventlog wake source>, <syslog wake source>) 76 # crbug.com/220014: spurious trackpad IRQs 77 ('^link', 'Wake Source | GPIO | 12', ''), 78 # crbug.com/345327: unknown, probably same as crbug.com/290923 79 ('^x86-alex', '', ''), # alex can report neither, blanket ignore 80 # crbug.com/355106: unknown, possibly related to crbug.com/290923 81 ('^lumpy|^parrot', '', 'PM1_STS: WAK PWRBTN'), 82 ] 83 S0_WHITELIST = [ # (<board>, <kernel wake source>) 84 # crbug.com/290923: spurious keyboard IRQ, believed to be from Servo 85 ('^x86-alex|^lumpy|^parrot|^butterfly', 'serio0'), 86 # crosbug.com/p/46140: battery event caused by MKBP 87 ('^elm|^oak', 'spi32766.0'), 88 ] 89 90class MemoryError(SuspendFailure): 91 """memory_suspend_test found memory corruption.""" 92 pass 93 94 95class SuspendNotAllowed(SuspendFailure): 96 """Suspend was not allowed to be performed.""" 97 pass 98 99 100class S0ixResidencyNotChanged(SuspendFailure): 101 """power_SuspendStress test found CPU/SoC is unable to idle properly 102 when suspended to S0ix. """ 103 pass 104 105def prepare_wakeup(seconds): 106 """Prepare the device to wake up from an upcoming suspend. 107 108 @param seconds: The number of seconds to allow the device to suspend. 109 """ 110 # May cause DUT not wake from sleep if the suspend time is 1 second. 111 # It happens when the current clock (floating point) is close to the 112 # next integer, as the RTC sysfs interface only accepts integers. 113 # Make sure it is larger than or equal to 2. 114 assert seconds >= 2 115 wakeup_count = read_wakeup_count() 116 alarm = int(rtc.get_seconds() + seconds) 117 logging.debug('Suspend for %d seconds, wakealarm = %d', seconds, alarm) 118 rtc.set_wake_alarm(alarm) 119 return (alarm, wakeup_count) 120 121 122def check_wakeup(alarm): 123 """Verify that the device did not wakeup early. 124 125 @param alarm: The time at which the device was expected to wake up. 126 """ 127 now = rtc.get_seconds() 128 if now < alarm: 129 logging.error('Woke up early at %d', now) 130 raise SpuriousWakeupError('Woke from suspend early') 131 132 133def do_suspend(suspend_seconds, delay_seconds=0): 134 """Do a suspend using the power manager. 135 136 Wait for |delay_seconds|, suspend the system to RAM (S3), waking up again 137 after having been suspended for |suspend_seconds|, using the 138 powerd_dbus_suspend program. Function will block until suspend/resume 139 has completed or failed. Returns the wake alarm time from the RTC as epoch. 140 141 @param suspend_seconds: Number of seconds to suspend the DUT. 142 @param delay_seconds: Number of seconds wait before suspending the DUT. 143 144 """ 145 146 # stop check_ethernet.hook from running until the test exits 147 global pause_ethernet_fd 148 if pause_ethernet_fd == 0: 149 # we don't write to the file - but we might need to create it. 150 pause_ethernet_fd = open(PAUSE_ETHERNET_HOOK_FILE,'a+') 151 152 if pause_ethernet_fd > 0: 153 try: 154 # this is a blocking call unless an error occurs. 155 fcntl.flock(pause_ethernet_fd, fcntl.LOCK_SH) 156 except IOError: 157 pass 158 159 alarm, wakeup_count = prepare_wakeup(suspend_seconds) 160 upstart.ensure_running('powerd') 161 command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 162 '--wakeup_count=%d') % (delay_seconds, wakeup_count) 163 logging.info("Running '%s'", command) 164 os.system(command) 165 check_wakeup(alarm) 166 return alarm 167 168 169def suspend_bg_for_dark_resume(delay_seconds=0): 170 """Do a non-blocking indefinite suspend using power manager. ONLY USE THIS 171 IF YOU ARE ABSOLUTELY CERTAIN YOU NEED TO. 172 173 Wait for |delay_seconds|, then suspend to RAM (S3). This does not set an RTC 174 alarm and does not pass an external wakeup count. It is meant to be used for 175 dark resume testing, where the server-side API exposes it in such a fashion 176 that the DUT will be woken by the server no matter how the test is exited. 177 178 @param delay_seconds: Number of seconds wait before suspending the DUT. 179 180 """ 181 upstart.ensure_running('powerd') 182 command = ('/usr/bin/powerd_dbus_suspend --delay=%d ' 183 '--timeout=30') % delay_seconds 184 logging.info("Running '%s'", command) 185 process = multiprocessing.Process(target=os.system, args=(command,)) 186 process.start() 187 188 189def kernel_suspend(seconds, state='mem'): 190 """Do a kernel suspend. 191 192 Suspend the system to @state, waking up again after |seconds|, by directly 193 writing to /sys/power/state. Function will block until suspend/resume has 194 completed or failed. 195 196 @param seconds: The number of seconds to suspend the device. 197 @param state: power state to suspend to. DEFAULT mem. 198 """ 199 alarm, wakeup_count = prepare_wakeup(seconds) 200 logging.debug('Saving wakeup count: %d', wakeup_count) 201 write_wakeup_count(wakeup_count) 202 try: 203 logging.info('Suspending at %d', rtc.get_seconds()) 204 with open(SYSFS_POWER_STATE, 'w') as sysfs_file: 205 sysfs_file.write(state) 206 except IOError as e: 207 logging.exception('Writing to %s failed', SYSFS_POWER_STATE) 208 if e.errno == errno.EBUSY and rtc.get_seconds() >= alarm: 209 # The kernel returns EBUSY if it has to abort because 210 # another wakeup fires before we've reached suspend. 211 raise SpuriousWakeupError('Received spurious wakeup in kernel.') 212 else: 213 # Some driver probably failed to suspend properly. 214 # A hint as to what failed is in errno. 215 raise KernelError('Suspend failed: %s' % e.strerror) 216 else: 217 logging.info('Woke from suspend at %d', rtc.get_seconds()) 218 logging.debug('New wakeup count: %d', read_wakeup_count()) 219 check_wakeup(alarm) 220 221 222def idle_suspend(seconds): 223 """ 224 Wait for the system to suspend to RAM (S3), scheduling the RTC to wake up 225 |seconds| after this function was called. Caller must ensure that the system 226 will idle-suspend in time for this to happen. Returns the wake alarm time 227 from the RTC as epoch. 228 229 @param seconds: The number of seconds before wakeup. 230 """ 231 alarm, _ = prepare_wakeup(seconds) 232 while rtc.get_seconds() < alarm: 233 time.sleep(0.2) 234 235 # tell powerd something happened, or it will immediately idle-suspend again 236 # TODO: switch to cros.power_utils#call_powerd_dbus_method once this 237 # horrible mess with the WiFi tests and this file's imports is solved 238 logging.debug('Simulating user activity after idle suspend...') 239 os.system('dbus-send --type=method_call --system ' 240 '--dest=org.chromium.PowerManager /org/chromium/PowerManager ' 241 'org.chromium.PowerManager.HandleUserActivity') 242 243 return alarm 244 245 246def memory_suspend(seconds, size=0): 247 """Do a memory suspend. 248 249 Suspend the system to RAM (S3), waking up again after |seconds|, using 250 the memory_suspend_test tool. Function will block until suspend/resume has 251 completed or failed. Returns the wake alarm time from the RTC as epoch. 252 253 @param seconds: The number of seconds to suspend the device. 254 @param size: Amount of memory to allocate, in bytes. 255 Set to 0 to let memory_suspend_test determine amount of memory. 256 """ 257 # since we cannot have utils.system_output in here, we need a workaround 258 output = '/tmp/memory_suspend_output' 259 alarm, wakeup_count = prepare_wakeup(seconds) 260 status = os.system('/usr/bin/memory_suspend_test --wakeup_count=%d ' 261 '--size=%d > %s' % (wakeup_count, size, output)) 262 status = os.WEXITSTATUS(status) 263 if status == 2: 264 logging.error('memory_suspend_test found the following errors:') 265 for line in open(output, 'r'): 266 logging.error(line) 267 raise MemoryError('Memory corruption found after resume') 268 elif status == 1: 269 raise SuspendFailure('Failure in powerd_suspend during memory test') 270 elif status: 271 raise SuspendFailure('Unknown failure in memory_suspend_test (crash?)') 272 check_wakeup(alarm) 273 return alarm 274 275 276def read_wakeup_count(): 277 """ 278 Retrieves the current value of /sys/power/wakeup_count. 279 """ 280 with open(SYSFS_WAKEUP_COUNT) as sysfs_file: 281 return int(sysfs_file.read().rstrip('\n')) 282 283 284def write_wakeup_count(wakeup_count): 285 """Writes a value to /sys/power/wakeup_count. 286 287 @param wakeup_count: The wakeup count value to write. 288 """ 289 try: 290 with open(SYSFS_WAKEUP_COUNT, 'w') as sysfs_file: 291 sysfs_file.write(str(wakeup_count)) 292 except IOError as e: 293 if (e.errno == errno.EINVAL and read_wakeup_count() != wakeup_count): 294 raise SpuriousWakeupError('wakeup_count changed before suspend') 295 else: 296 raise 297