xref: /aosp_15_r20/external/autotest/client/cros/power/sys_power.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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