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_file = None 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 Allowlist 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 ALLOWLIST = [ 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 allowlist 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 ALLOWLIST = [ 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_ALLOWLIST = [ # (<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_ALLOWLIST = [ # (<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|^hana', 'spi32766.0'), 88 ] 89 90 91class MemoryError(SuspendFailure): 92 """memory_suspend_test found memory corruption.""" 93 pass 94 95 96class SuspendNotAllowed(SuspendFailure): 97 """Suspend was not allowed to be performed.""" 98 pass 99 100 101class S0ixResidencyNotChanged(SuspendFailure): 102 """power_SuspendStress test found CPU/SoC is unable to idle properly 103 when suspended to S0ix. """ 104 pass 105 106 107class S2IdleResidencyNotChanged(SuspendFailure): 108 """power_SuspendStress test found CPU/SoC is unable to idle properly 109 when suspended to s2idle. """ 110 pass 111 112 113def prepare_wakeup(seconds): 114 """Prepare the device to wake up from an upcoming suspend. 115 116 @param seconds: The number of seconds to allow the device to suspend. 117 """ 118 # May cause DUT not wake from sleep if the suspend time is 1 second. 119 # It happens when the current clock (floating point) is close to the 120 # next integer, as the RTC sysfs interface only accepts integers. 121 # Make sure it is larger than or equal to 2. 122 assert seconds >= 2 123 wakeup_count = read_wakeup_count() 124 estimated_alarm = int(rtc.get_seconds() + seconds) 125 logging.debug('Suspend for %d seconds, estimated wakealarm = %d', 126 seconds, estimated_alarm) 127 return (estimated_alarm, wakeup_count) 128 129 130def check_wakeup(estimated_alarm): 131 """Verify that the device did not wakeup early. 132 133 @param estimated_alarm: The lower bound time at which the device was 134 expected to wake up. 135 """ 136 now = rtc.get_seconds() 137 if now < estimated_alarm: 138 logging.error('Woke up %d secs early', (estimated_alarm - now)) 139 raise SpuriousWakeupError('Woke from suspend early') 140 141 142def pause_check_network_hook(): 143 """Stop check_ethernet.hook from running. 144 145 Lock will be held until test exits, or resume_check_network_hook() is 146 called. check_ethernet.hook may also "break" the lock if it judges you've 147 held it too long. 148 149 Can be called multiple times, to refresh the lock. 150 """ 151 # If this function is called multiple times, we intentionally re-assign 152 # this global, which closes out the old lock and grabs it anew. 153 # We intentionally "touch" the file to update its mtime, so we can judge 154 # how long locks are held. 155 global pause_ethernet_file 156 pause_ethernet_file = open(PAUSE_ETHERNET_HOOK_FILE, 'w+') 157 try: 158 # This is a blocking call unless an error occurs. 159 fcntl.flock(pause_ethernet_file, fcntl.LOCK_EX) 160 except IOError: 161 pass 162 163 164def resume_check_network_hook(): 165 """Resume check_ethernet.hook. 166 167 Must call pause_check_network_hook() at least once before calling this. 168 """ 169 global pause_ethernet_file 170 # Closing the file descriptor releases the lock. 171 pause_ethernet_file.close() 172 pause_ethernet_file = None 173 174 175def do_suspend(wakeup_timeout, delay_seconds=0): 176 """Suspend using the power manager with a wakeup timeout. 177 178 Wait for |delay_seconds|, suspend the system(S3/S0iX) using 179 powerd_dbus_suspend program. powerd_dbus_suspend first sets a wakeup alarm 180 on the dut for |current_time + wakeup_timeout|. Thus the device tries to 181 resume at |current_time + wakeup_timeout| irrespective of when it suspended. 182 This means that RTC can trigger an early resume and prevent suspend. 183 184 Function will block until suspend/resume has completed or failed. 185 Returns the wake alarm time from the RTC as epoch. 186 187 @param wakeup_timeout: time from now after which the device has to. 188 @param delay_seconds: Number of seconds wait before suspending the DUT. 189 190 """ 191 pause_check_network_hook() 192 estimated_alarm, wakeup_count = prepare_wakeup(wakeup_timeout) 193 upstart.ensure_running('powerd') 194 command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 195 '--wakeup_count=%d --wakeup_timeout=%d' % 196 (delay_seconds, wakeup_count, wakeup_timeout)) 197 logging.info("Running '%s'", command) 198 os.system(command) 199 check_wakeup(estimated_alarm) 200 return estimated_alarm 201 202 203def suspend_for(time_in_suspend, delay_seconds=0): 204 """Suspend using the power manager and spend |time_in_suspend| in suspend. 205 206 Wait for |delay_seconds|, suspend the system(S3/S0iX) using 207 powerd_dbus_suspend program. power manager sets a wakeup alarm on the dut 208 for |time_in_suspend| just before suspending 209 (writing power state to /sys/power/state). Thus the device spends most of 210 |time_in_suspend| in S0iX/S3. 211 212 Function will block until suspend/resume has completed or failed. 213 Returns the wake alarm time from the RTC as epoch. 214 215 @param time_in_suspend: Number of seconds to suspend the DUT. 216 @param delay_seconds: Number of seconds wait before suspending the DUT. 217 """ 218 pause_check_network_hook() 219 220 wakeup_count = read_wakeup_count() 221 upstart.ensure_running('powerd') 222 command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 223 '--wakeup_count=%d --suspend_for_sec=%d' % 224 (delay_seconds, wakeup_count, time_in_suspend)) 225 logging.info("Running '%s'", command) 226 os.system(command) 227 228 229def suspend_bg_for_dark_resume(suspend_seconds, delay_seconds=0): 230 """Do a non-blocking suspend using power manager. 231 232 Wait for |delay_seconds|, then suspend with an rtc alarm for 233 waking up again after having been suspended for |suspend_seconds|, using 234 the powerd_dbus_suspend program all in the background. 235 236 @param suspend_seconds: Number of seconds to suspend the DUT. 237 @param delay_seconds: Number of seconds wait before suspending the DUT. 238 239 """ 240 upstart.ensure_running('powerd') 241 # Disarm any existing wake alarms so as to prevent early wakeups. 242 os.system('echo 0 > /sys/class/rtc/rtc0/wakealarm') 243 wakeup_count = read_wakeup_count() 244 command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 245 '--wakeup_count=%d --wakeup_timeout=%d ' 246 '--disable_dark_resume=false' % 247 (delay_seconds, wakeup_count, suspend_seconds)) 248 logging.info("Running '%s'", command) 249 process = multiprocessing.Process(target=os.system, args=(command,)) 250 process.start() 251 252 253def kernel_suspend(seconds, state='mem'): 254 """Do a kernel suspend. 255 256 Suspend the system to @state, waking up again after |seconds|, by directly 257 writing to /sys/power/state. Function will block until suspend/resume has 258 completed or failed. 259 260 @param seconds: The number of seconds to suspend the device. 261 @param state: power state to suspend to. DEFAULT mem. 262 """ 263 alarm, wakeup_count = prepare_wakeup(seconds) 264 rtc.set_wake_alarm(alarm) 265 logging.debug('Saving wakeup count: %d', wakeup_count) 266 write_wakeup_count(wakeup_count) 267 try: 268 logging.info('Suspending at %d', rtc.get_seconds()) 269 with open(SYSFS_POWER_STATE, 'w') as sysfs_file: 270 sysfs_file.write(state) 271 except IOError as e: 272 logging.exception('Writing to %s failed', SYSFS_POWER_STATE) 273 if e.errno == errno.EBUSY and rtc.get_seconds() >= alarm: 274 # The kernel returns EBUSY if it has to abort because 275 # another wakeup fires before we've reached suspend. 276 raise SpuriousWakeupError('Received spurious wakeup in kernel.') 277 else: 278 # Some driver probably failed to suspend properly. 279 # A hint as to what failed is in errno. 280 raise KernelError('Suspend failed: %s' % e.strerror) 281 else: 282 logging.info('Woke from suspend at %d', rtc.get_seconds()) 283 logging.debug('New wakeup count: %d', read_wakeup_count()) 284 check_wakeup(alarm) 285 286 287def idle_suspend(seconds): 288 """ 289 Wait for the system to suspend to RAM (S3), scheduling the RTC to wake up 290 |seconds| after this function was called. Caller must ensure that the system 291 will idle-suspend in time for this to happen. Returns the wake alarm time 292 from the RTC as epoch. 293 294 @param seconds: The number of seconds before wakeup. 295 """ 296 alarm, _ = prepare_wakeup(seconds) 297 rtc.set_wake_alarm(alarm) 298 while rtc.get_seconds() < alarm: 299 time.sleep(0.2) 300 301 # tell powerd something happened, or it will immediately idle-suspend again 302 # TODO: switch to cros.power_utils#call_powerd_dbus_method once this 303 # horrible mess with the WiFi tests and this file's imports is solved 304 logging.debug('Simulating user activity after idle suspend...') 305 os.system('dbus-send --type=method_call --system ' 306 '--dest=org.chromium.PowerManager /org/chromium/PowerManager ' 307 'org.chromium.PowerManager.HandleUserActivity') 308 309 return alarm 310 311 312def memory_suspend(seconds, size=0): 313 """Do a memory suspend. 314 315 Suspend the system to RAM (S3), waking up again after |seconds|, using 316 the memory_suspend_test tool. Function will block until suspend/resume has 317 completed or failed. 318 319 @param seconds: The number of seconds to suspend the device. 320 @param size: Amount of memory to allocate, in bytes. 321 Set to 0 to let memory_suspend_test determine amount of memory. 322 """ 323 # since we cannot have utils.system_output in here, we need a workaround 324 output = '/tmp/memory_suspend_output' 325 wakeup_count = read_wakeup_count() 326 status = os.system('/usr/bin/memory_suspend_test --wakeup_count=%d ' 327 '--size=%d > %s --suspend_for_sec=%d' % 328 (wakeup_count, size, output, seconds)) 329 status = os.WEXITSTATUS(status) 330 if status == 2: 331 logging.error('memory_suspend_test found the following errors:') 332 for line in open(output, 'r'): 333 logging.error(line) 334 raise MemoryError('Memory corruption found after resume') 335 elif status == 1: 336 raise SuspendFailure('Failure in powerd_suspend during memory test') 337 elif status: 338 raise SuspendFailure('Unknown failure in memory_suspend_test (crash?)') 339 340 341def read_wakeup_count(): 342 """ 343 Retrieves the current value of /sys/power/wakeup_count. 344 """ 345 with open(SYSFS_WAKEUP_COUNT) as sysfs_file: 346 return int(sysfs_file.read().rstrip('\n')) 347 348 349def write_wakeup_count(wakeup_count): 350 """Writes a value to /sys/power/wakeup_count. 351 352 @param wakeup_count: The wakeup count value to write. 353 """ 354 try: 355 with open(SYSFS_WAKEUP_COUNT, 'w') as sysfs_file: 356 sysfs_file.write(str(wakeup_count)) 357 except IOError as e: 358 if (e.errno == errno.EINVAL and read_wakeup_count() != wakeup_count): 359 raise SpuriousWakeupError('wakeup_count changed before suspend') 360 else: 361 raise 362