1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li"""Provides utility methods for controlling powerd in ChromiumOS.""" 7*9c5db199SXin Li 8*9c5db199SXin Liimport errno 9*9c5db199SXin Liimport fcntl 10*9c5db199SXin Liimport logging 11*9c5db199SXin Liimport multiprocessing 12*9c5db199SXin Liimport os 13*9c5db199SXin Liimport time 14*9c5db199SXin Li 15*9c5db199SXin Liimport common 16*9c5db199SXin Lifrom autotest_lib.client.cros import rtc 17*9c5db199SXin Lifrom autotest_lib.client.cros import upstart 18*9c5db199SXin Li 19*9c5db199SXin LiSYSFS_POWER_STATE = '/sys/power/state' 20*9c5db199SXin LiSYSFS_WAKEUP_COUNT = '/sys/power/wakeup_count' 21*9c5db199SXin Li 22*9c5db199SXin LiPAUSE_ETHERNET_HOOK_FILE = '/run/autotest_pause_ethernet_hook' 23*9c5db199SXin Lipause_ethernet_file = None 24*9c5db199SXin Li 25*9c5db199SXin Li 26*9c5db199SXin Liclass SuspendFailure(Exception): 27*9c5db199SXin Li """Base class for a failure during a single suspend/resume cycle.""" 28*9c5db199SXin Li pass 29*9c5db199SXin Li 30*9c5db199SXin Li 31*9c5db199SXin Liclass SuspendTimeout(SuspendFailure): 32*9c5db199SXin Li """Suspend took too long, got wakeup event (RTC tick) before it was done.""" 33*9c5db199SXin Li pass 34*9c5db199SXin Li 35*9c5db199SXin Li 36*9c5db199SXin Liclass KernelError(SuspendFailure): 37*9c5db199SXin Li """Kernel problem encountered during suspend/resume. 38*9c5db199SXin Li 39*9c5db199SXin Li Allowlist is an array of a 2-entry tuples consisting of (source regexp, text 40*9c5db199SXin Li regexp). For example, kernel output might look like, 41*9c5db199SXin Li 42*9c5db199SXin Li [ 597.079950] WARNING: CPU: 0 PID: 21415 at \ 43*9c5db199SXin Li <path>/v3.18/drivers/gpu/drm/i915/intel_pm.c:3687 \ 44*9c5db199SXin Li skl_update_other_pipe_wm+0x136/0x18a() 45*9c5db199SXin Li [ 597.079962] WARN_ON(!wm_changed) 46*9c5db199SXin Li 47*9c5db199SXin Li source regexp should match first line above while text regexp can match 48*9c5db199SXin Li up to 2 lines below the source. Note timestamps are stripped prior to 49*9c5db199SXin Li comparing regexp. 50*9c5db199SXin Li """ 51*9c5db199SXin Li ALLOWLIST = [ 52*9c5db199SXin Li # crosbug.com/37594: debug tracing clock desync we don't care about 53*9c5db199SXin Li (r'kernel/trace/ring_buffer.c:\d+ rb_reserve_next_event', 54*9c5db199SXin Li r'Delta way too big!'), 55*9c5db199SXin Li # TODO(crosbug.com/p/52008): Remove from allowlist once watermark 56*9c5db199SXin Li # implementation has landed. 57*9c5db199SXin Li (r'v3.18/\S+/intel_pm.c:\d+ skl_update_other_pipe_wm', 58*9c5db199SXin Li r'WARN_ON\(\!wm_changed\)') 59*9c5db199SXin Li ] 60*9c5db199SXin Li 61*9c5db199SXin Li 62*9c5db199SXin Liclass FirmwareError(SuspendFailure): 63*9c5db199SXin Li """String 'ERROR' found in firmware log after resume.""" 64*9c5db199SXin Li ALLOWLIST = [ 65*9c5db199SXin Li # crosbug.com/36762: no one knows, but it has always been there 66*9c5db199SXin Li ('^stumpy', r'PNP: 002e\.4 70 irq size: 0x0000000001 not assigned' 67*9c5db199SXin Li ), 68*9c5db199SXin Li # crbug.com/221538: no one knows what ME errors mean anyway 69*9c5db199SXin Li ('^parrot', r'ME failed to respond'), 70*9c5db199SXin Li # b/64684441: eve SKU without eMMC 71*9c5db199SXin Li ('^eve', r'Card did not respond to voltage select!'), 72*9c5db199SXin Li # b/187561710#comment6: waive mcache error from volteer 73*9c5db199SXin Li ('^volteer', r'mcache overflow, should increase CBFS_MCACHE size!' 74*9c5db199SXin Li ), 75*9c5db199SXin Li # b/221113302: Guybrush do not save firmware log to cbmem during 76*9c5db199SXin Li # resume, so all errors seen actually occurred during boot. 77*9c5db199SXin Li # This workaround can be removed when b/221231786 is fixed. 78*9c5db199SXin Li ('^guybrush', r'.*'), 79*9c5db199SXin Li # b/195336611: waive the eMMC error for the non-emmc sku 80*9c5db199SXin Li ('^brya', r'No known Realtek reader found'), 81*9c5db199SXin Li ] 82*9c5db199SXin Li 83*9c5db199SXin Li 84*9c5db199SXin Liclass SpuriousWakeupError(SuspendFailure): 85*9c5db199SXin Li """Received spurious wakeup while suspending or woke before schedule.""" 86*9c5db199SXin Li S3_ALLOWLIST = [ # (<board>, <eventlog wake source>, <syslog wake source>) 87*9c5db199SXin Li # crbug.com/220014: spurious trackpad IRQs 88*9c5db199SXin Li ('^link', 'Wake Source | GPIO | 12', ''), 89*9c5db199SXin Li # crbug.com/345327: unknown, probably same as crbug.com/290923 90*9c5db199SXin Li ('^x86-alex', '', ''), # alex can report neither, blanket ignore 91*9c5db199SXin Li # crbug.com/355106: unknown, possibly related to crbug.com/290923 92*9c5db199SXin Li ('^lumpy|^parrot', '', 'PM1_STS: WAK PWRBTN'), 93*9c5db199SXin Li ] 94*9c5db199SXin Li S0_ALLOWLIST = [ # (<board>, <kernel wake source>) 95*9c5db199SXin Li # crbug.com/290923: spurious keyboard IRQ, believed to be from Servo 96*9c5db199SXin Li ('^x86-alex|^lumpy|^parrot|^butterfly', 'serio0'), 97*9c5db199SXin Li # crosbug.com/p/46140: battery event caused by MKBP 98*9c5db199SXin Li ('^elm|^oak|^hana', 'spi32766.0'), 99*9c5db199SXin Li ] 100*9c5db199SXin Li 101*9c5db199SXin Li 102*9c5db199SXin Liclass MemoryError(SuspendFailure): 103*9c5db199SXin Li """memory_suspend_test found memory corruption.""" 104*9c5db199SXin Li pass 105*9c5db199SXin Li 106*9c5db199SXin Li 107*9c5db199SXin Liclass SuspendNotAllowed(SuspendFailure): 108*9c5db199SXin Li """Suspend was not allowed to be performed.""" 109*9c5db199SXin Li pass 110*9c5db199SXin Li 111*9c5db199SXin Li 112*9c5db199SXin Liclass S0ixResidencyNotChanged(SuspendFailure): 113*9c5db199SXin Li """power_SuspendStress test found CPU/SoC is unable to idle properly 114*9c5db199SXin Li when suspended to S0ix. """ 115*9c5db199SXin Li pass 116*9c5db199SXin Li 117*9c5db199SXin Li 118*9c5db199SXin Liclass S2IdleResidencyNotChanged(SuspendFailure): 119*9c5db199SXin Li """power_SuspendStress test found CPU/SoC is unable to idle properly 120*9c5db199SXin Li when suspended to s2idle. """ 121*9c5db199SXin Li pass 122*9c5db199SXin Li 123*9c5db199SXin Li 124*9c5db199SXin Lidef prepare_wakeup(seconds): 125*9c5db199SXin Li """Prepare the device to wake up from an upcoming suspend. 126*9c5db199SXin Li 127*9c5db199SXin Li @param seconds: The number of seconds to allow the device to suspend. 128*9c5db199SXin Li """ 129*9c5db199SXin Li # May cause DUT not wake from sleep if the suspend time is 1 second. 130*9c5db199SXin Li # It happens when the current clock (floating point) is close to the 131*9c5db199SXin Li # next integer, as the RTC sysfs interface only accepts integers. 132*9c5db199SXin Li # Make sure it is larger than or equal to 2. 133*9c5db199SXin Li assert seconds >= 2 134*9c5db199SXin Li wakeup_count = read_wakeup_count() 135*9c5db199SXin Li estimated_alarm = int(rtc.get_seconds() + seconds) 136*9c5db199SXin Li logging.debug('Suspend for %d seconds, estimated wakealarm = %d', 137*9c5db199SXin Li seconds, estimated_alarm) 138*9c5db199SXin Li return (estimated_alarm, wakeup_count) 139*9c5db199SXin Li 140*9c5db199SXin Li 141*9c5db199SXin Lidef check_wakeup(estimated_alarm): 142*9c5db199SXin Li """Verify that the device did not wakeup early. 143*9c5db199SXin Li 144*9c5db199SXin Li @param estimated_alarm: The lower bound time at which the device was 145*9c5db199SXin Li expected to wake up. 146*9c5db199SXin Li """ 147*9c5db199SXin Li now = rtc.get_seconds() 148*9c5db199SXin Li if now < estimated_alarm: 149*9c5db199SXin Li logging.error('Woke up %d secs early', (estimated_alarm - now)) 150*9c5db199SXin Li raise SpuriousWakeupError('Woke from suspend early') 151*9c5db199SXin Li 152*9c5db199SXin Li 153*9c5db199SXin Lidef pause_check_network_hook(): 154*9c5db199SXin Li """Stop check_ethernet.hook from running. 155*9c5db199SXin Li 156*9c5db199SXin Li Lock will be held until test exits, or resume_check_network_hook() is 157*9c5db199SXin Li called. check_ethernet.hook may also "break" the lock if it judges you've 158*9c5db199SXin Li held it too long. 159*9c5db199SXin Li 160*9c5db199SXin Li Can be called multiple times, to refresh the lock. 161*9c5db199SXin Li """ 162*9c5db199SXin Li # If this function is called multiple times, we intentionally re-assign 163*9c5db199SXin Li # this global, which closes out the old lock and grabs it anew. 164*9c5db199SXin Li # We intentionally "touch" the file to update its mtime, so we can judge 165*9c5db199SXin Li # how long locks are held. 166*9c5db199SXin Li global pause_ethernet_file 167*9c5db199SXin Li pause_ethernet_file = open(PAUSE_ETHERNET_HOOK_FILE, 'w+') 168*9c5db199SXin Li try: 169*9c5db199SXin Li # This is a blocking call unless an error occurs. 170*9c5db199SXin Li fcntl.flock(pause_ethernet_file, fcntl.LOCK_EX) 171*9c5db199SXin Li except IOError: 172*9c5db199SXin Li pass 173*9c5db199SXin Li 174*9c5db199SXin Li 175*9c5db199SXin Lidef resume_check_network_hook(): 176*9c5db199SXin Li """Resume check_ethernet.hook. 177*9c5db199SXin Li 178*9c5db199SXin Li Must call pause_check_network_hook() at least once before calling this. 179*9c5db199SXin Li """ 180*9c5db199SXin Li global pause_ethernet_file 181*9c5db199SXin Li # Closing the file descriptor releases the lock. 182*9c5db199SXin Li pause_ethernet_file.close() 183*9c5db199SXin Li pause_ethernet_file = None 184*9c5db199SXin Li 185*9c5db199SXin Li 186*9c5db199SXin Lidef do_suspend(wakeup_timeout, delay_seconds=0): 187*9c5db199SXin Li """Suspend using the power manager with a wakeup timeout. 188*9c5db199SXin Li 189*9c5db199SXin Li Wait for |delay_seconds|, suspend the system(S3/S0iX) using 190*9c5db199SXin Li powerd_dbus_suspend program. powerd_dbus_suspend first sets a wakeup alarm 191*9c5db199SXin Li on the dut for |current_time + wakeup_timeout|. Thus the device tries to 192*9c5db199SXin Li resume at |current_time + wakeup_timeout| irrespective of when it suspended. 193*9c5db199SXin Li This means that RTC can trigger an early resume and prevent suspend. 194*9c5db199SXin Li 195*9c5db199SXin Li Function will block until suspend/resume has completed or failed. 196*9c5db199SXin Li Returns the wake alarm time from the RTC as epoch. 197*9c5db199SXin Li 198*9c5db199SXin Li @param wakeup_timeout: time from now after which the device has to. 199*9c5db199SXin Li @param delay_seconds: Number of seconds wait before suspending the DUT. 200*9c5db199SXin Li 201*9c5db199SXin Li """ 202*9c5db199SXin Li pause_check_network_hook() 203*9c5db199SXin Li estimated_alarm, wakeup_count = prepare_wakeup(wakeup_timeout) 204*9c5db199SXin Li upstart.ensure_running('powerd') 205*9c5db199SXin Li command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 206*9c5db199SXin Li '--wakeup_count=%d --wakeup_timeout=%d' % 207*9c5db199SXin Li (delay_seconds, wakeup_count, wakeup_timeout)) 208*9c5db199SXin Li logging.info("Running '%s'", command) 209*9c5db199SXin Li os.system(command) 210*9c5db199SXin Li check_wakeup(estimated_alarm) 211*9c5db199SXin Li return estimated_alarm 212*9c5db199SXin Li 213*9c5db199SXin Li 214*9c5db199SXin Lidef suspend_for(time_in_suspend, delay_seconds=0): 215*9c5db199SXin Li """Suspend using the power manager and spend |time_in_suspend| in suspend. 216*9c5db199SXin Li 217*9c5db199SXin Li Wait for |delay_seconds|, suspend the system(S3/S0iX) using 218*9c5db199SXin Li powerd_dbus_suspend program. power manager sets a wakeup alarm on the dut 219*9c5db199SXin Li for |time_in_suspend| just before suspending 220*9c5db199SXin Li (writing power state to /sys/power/state). Thus the device spends most of 221*9c5db199SXin Li |time_in_suspend| in S0iX/S3. 222*9c5db199SXin Li 223*9c5db199SXin Li Function will block until suspend/resume has completed or failed. 224*9c5db199SXin Li Returns the wake alarm time from the RTC as epoch. 225*9c5db199SXin Li 226*9c5db199SXin Li @param time_in_suspend: Number of seconds to suspend the DUT. 227*9c5db199SXin Li @param delay_seconds: Number of seconds wait before suspending the DUT. 228*9c5db199SXin Li """ 229*9c5db199SXin Li pause_check_network_hook() 230*9c5db199SXin Li 231*9c5db199SXin Li wakeup_count = read_wakeup_count() 232*9c5db199SXin Li upstart.ensure_running('powerd') 233*9c5db199SXin Li command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 234*9c5db199SXin Li '--wakeup_count=%d --suspend_for_sec=%d' % 235*9c5db199SXin Li (delay_seconds, wakeup_count, time_in_suspend)) 236*9c5db199SXin Li logging.info("Running '%s'", command) 237*9c5db199SXin Li os.system(command) 238*9c5db199SXin Li 239*9c5db199SXin Li 240*9c5db199SXin Lidef suspend_bg_for_dark_resume(suspend_seconds, delay_seconds=0): 241*9c5db199SXin Li """Do a non-blocking suspend using power manager. 242*9c5db199SXin Li 243*9c5db199SXin Li Wait for |delay_seconds|, then suspend with an rtc alarm for 244*9c5db199SXin Li waking up again after having been suspended for |suspend_seconds|, using 245*9c5db199SXin Li the powerd_dbus_suspend program all in the background. 246*9c5db199SXin Li 247*9c5db199SXin Li @param suspend_seconds: Number of seconds to suspend the DUT. 248*9c5db199SXin Li @param delay_seconds: Number of seconds wait before suspending the DUT. 249*9c5db199SXin Li 250*9c5db199SXin Li """ 251*9c5db199SXin Li upstart.ensure_running('powerd') 252*9c5db199SXin Li # Disarm any existing wake alarms so as to prevent early wakeups. 253*9c5db199SXin Li os.system('echo 0 > /sys/class/rtc/rtc0/wakealarm') 254*9c5db199SXin Li wakeup_count = read_wakeup_count() 255*9c5db199SXin Li command = ('/usr/bin/powerd_dbus_suspend --delay=%d --timeout=30 ' 256*9c5db199SXin Li '--wakeup_count=%d --wakeup_timeout=%d ' 257*9c5db199SXin Li '--disable_dark_resume=false' % 258*9c5db199SXin Li (delay_seconds, wakeup_count, suspend_seconds)) 259*9c5db199SXin Li logging.info("Running '%s'", command) 260*9c5db199SXin Li process = multiprocessing.Process(target=os.system, args=(command,)) 261*9c5db199SXin Li process.start() 262*9c5db199SXin Li 263*9c5db199SXin Li 264*9c5db199SXin Lidef kernel_suspend(seconds, state='mem'): 265*9c5db199SXin Li """Do a kernel suspend. 266*9c5db199SXin Li 267*9c5db199SXin Li Suspend the system to @state, waking up again after |seconds|, by directly 268*9c5db199SXin Li writing to /sys/power/state. Function will block until suspend/resume has 269*9c5db199SXin Li completed or failed. 270*9c5db199SXin Li 271*9c5db199SXin Li @param seconds: The number of seconds to suspend the device. 272*9c5db199SXin Li @param state: power state to suspend to. DEFAULT mem. 273*9c5db199SXin Li """ 274*9c5db199SXin Li alarm, wakeup_count = prepare_wakeup(seconds) 275*9c5db199SXin Li rtc.set_wake_alarm(alarm) 276*9c5db199SXin Li logging.debug('Saving wakeup count: %d', wakeup_count) 277*9c5db199SXin Li write_wakeup_count(wakeup_count) 278*9c5db199SXin Li try: 279*9c5db199SXin Li logging.info('Suspending at %d', rtc.get_seconds()) 280*9c5db199SXin Li with open(SYSFS_POWER_STATE, 'w') as sysfs_file: 281*9c5db199SXin Li sysfs_file.write(state) 282*9c5db199SXin Li except IOError as e: 283*9c5db199SXin Li logging.exception('Writing to %s failed', SYSFS_POWER_STATE) 284*9c5db199SXin Li if e.errno == errno.EBUSY and rtc.get_seconds() >= alarm: 285*9c5db199SXin Li # The kernel returns EBUSY if it has to abort because 286*9c5db199SXin Li # another wakeup fires before we've reached suspend. 287*9c5db199SXin Li raise SpuriousWakeupError('Received spurious wakeup in kernel.') 288*9c5db199SXin Li else: 289*9c5db199SXin Li # Some driver probably failed to suspend properly. 290*9c5db199SXin Li # A hint as to what failed is in errno. 291*9c5db199SXin Li raise KernelError('Suspend failed: %s' % e.strerror) 292*9c5db199SXin Li else: 293*9c5db199SXin Li logging.info('Woke from suspend at %d', rtc.get_seconds()) 294*9c5db199SXin Li logging.debug('New wakeup count: %d', read_wakeup_count()) 295*9c5db199SXin Li check_wakeup(alarm) 296*9c5db199SXin Li 297*9c5db199SXin Li 298*9c5db199SXin Lidef idle_suspend(seconds): 299*9c5db199SXin Li """ 300*9c5db199SXin Li Wait for the system to suspend to RAM (S3), scheduling the RTC to wake up 301*9c5db199SXin Li |seconds| after this function was called. Caller must ensure that the system 302*9c5db199SXin Li will idle-suspend in time for this to happen. Returns the wake alarm time 303*9c5db199SXin Li from the RTC as epoch. 304*9c5db199SXin Li 305*9c5db199SXin Li @param seconds: The number of seconds before wakeup. 306*9c5db199SXin Li """ 307*9c5db199SXin Li alarm, _ = prepare_wakeup(seconds) 308*9c5db199SXin Li rtc.set_wake_alarm(alarm) 309*9c5db199SXin Li while rtc.get_seconds() < alarm: 310*9c5db199SXin Li time.sleep(0.2) 311*9c5db199SXin Li 312*9c5db199SXin Li # tell powerd something happened, or it will immediately idle-suspend again 313*9c5db199SXin Li # TODO: switch to cros.power_utils#call_powerd_dbus_method once this 314*9c5db199SXin Li # horrible mess with the WiFi tests and this file's imports is solved 315*9c5db199SXin Li logging.debug('Simulating user activity after idle suspend...') 316*9c5db199SXin Li os.system('dbus-send --type=method_call --system ' 317*9c5db199SXin Li '--dest=org.chromium.PowerManager /org/chromium/PowerManager ' 318*9c5db199SXin Li 'org.chromium.PowerManager.HandleUserActivity') 319*9c5db199SXin Li 320*9c5db199SXin Li return alarm 321*9c5db199SXin Li 322*9c5db199SXin Li 323*9c5db199SXin Lidef memory_suspend(seconds, size=0): 324*9c5db199SXin Li """Do a memory suspend. 325*9c5db199SXin Li 326*9c5db199SXin Li Suspend the system to RAM (S3), waking up again after |seconds|, using 327*9c5db199SXin Li the memory_suspend_test tool. Function will block until suspend/resume has 328*9c5db199SXin Li completed or failed. 329*9c5db199SXin Li 330*9c5db199SXin Li @param seconds: The number of seconds to suspend the device. 331*9c5db199SXin Li @param size: Amount of memory to allocate, in bytes. 332*9c5db199SXin Li Set to 0 to let memory_suspend_test determine amount of memory. 333*9c5db199SXin Li """ 334*9c5db199SXin Li # since we cannot have utils.system_output in here, we need a workaround 335*9c5db199SXin Li output = '/tmp/memory_suspend_output' 336*9c5db199SXin Li wakeup_count = read_wakeup_count() 337*9c5db199SXin Li status = os.system('/usr/bin/memory_suspend_test --wakeup_count=%d ' 338*9c5db199SXin Li '--size=%d > %s --suspend_for_sec=%d' % 339*9c5db199SXin Li (wakeup_count, size, output, seconds)) 340*9c5db199SXin Li status = os.WEXITSTATUS(status) 341*9c5db199SXin Li if status == 2: 342*9c5db199SXin Li logging.error('memory_suspend_test found the following errors:') 343*9c5db199SXin Li for line in open(output, 'r'): 344*9c5db199SXin Li logging.error(line) 345*9c5db199SXin Li raise MemoryError('Memory corruption found after resume') 346*9c5db199SXin Li elif status == 1: 347*9c5db199SXin Li raise SuspendFailure('Failure in powerd_suspend during memory test') 348*9c5db199SXin Li elif status: 349*9c5db199SXin Li raise SuspendFailure('Unknown failure in memory_suspend_test (crash?)') 350*9c5db199SXin Li 351*9c5db199SXin Li 352*9c5db199SXin Lidef read_wakeup_count(): 353*9c5db199SXin Li """ 354*9c5db199SXin Li Retrieves the current value of /sys/power/wakeup_count. 355*9c5db199SXin Li """ 356*9c5db199SXin Li with open(SYSFS_WAKEUP_COUNT) as sysfs_file: 357*9c5db199SXin Li return int(sysfs_file.read().rstrip('\n')) 358*9c5db199SXin Li 359*9c5db199SXin Li 360*9c5db199SXin Lidef write_wakeup_count(wakeup_count): 361*9c5db199SXin Li """Writes a value to /sys/power/wakeup_count. 362*9c5db199SXin Li 363*9c5db199SXin Li @param wakeup_count: The wakeup count value to write. 364*9c5db199SXin Li """ 365*9c5db199SXin Li try: 366*9c5db199SXin Li with open(SYSFS_WAKEUP_COUNT, 'w') as sysfs_file: 367*9c5db199SXin Li sysfs_file.write(str(wakeup_count)) 368*9c5db199SXin Li except IOError as e: 369*9c5db199SXin Li if (e.errno == errno.EINVAL and read_wakeup_count() != wakeup_count): 370*9c5db199SXin Li raise SpuriousWakeupError('wakeup_count changed before suspend') 371*9c5db199SXin Li else: 372*9c5db199SXin Li raise 373