blob: 0040b5c2a68a0f651acf73a291b899bc470b6b0f [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import time
from autotest_lib.client.common_lib import error
from autotest_lib.server import test
# Timeout for commands run on the host.
_COMMAND_TIMEOUT = 60
# Lock file created by flashrom to tell powerd not to suspend or shut down the
# system.
_LOCK_FILE = '/run/lock/power_override/flashrom.lock'
# Time in seconds to perform a flashrom write and to wait for the system to
# suspend and resume.
_SUSPEND_FLASHROM_SEC = 15
_SUSPEND_DOWN_SEC = 60
_SUSPEND_UP_SEC = 60
# Time in seconds to perform a flashrom write and to wait for the system to
# power down and reboot.
_REBOOT_FLASHROM_SEC = 15
_REBOOT_DOWN_SEC = 60
_REBOOT_UP_SEC = 60
class power_DeferForFlashrom(test.test):
"""Test that powerd defers suspend and shutdown for flashrom."""
version = 1
def initialize(self, host):
"""
Initial settings before running test.
@param host: Host/DUT object to use for test.
"""
self.host = host
def create_temp_file(self, base_name, source_path, size):
"""
Create a temporary file on the host and returns its path.
@param base_name: String containing the base name for the temp file.
@param source_path: String containing the path to the device from
which file contents should be read.
@param size: Number of bytes to write to the file.
"""
logging.info('Creating %d-byte temp file from %s', size, source_path)
temp_file = self.host.run(
'mktemp --tmpdir %s.XXXXXXXXXX' % base_name,
timeout=_COMMAND_TIMEOUT).stdout.strip()
self.host.run('dd if=%s of=%s bs=%d count=1 2>&1' %
(source_path, temp_file, size))
logging.info('Created %s', temp_file)
return temp_file
def run_in_background(self, cmd):
"""
Asynchronously run a command on the host.
@param cmd: Command to run (as a string).
"""
bg_cmd = '(%s) </dev/null >/dev/null 2>&1 &' % (cmd)
logging.info("Running %s", bg_cmd)
self.host.run(bg_cmd, timeout=_COMMAND_TIMEOUT)
def start_fake_flashrom_write(self, duration_sec):
"""
Start a fake flashrom write.
@param duration_sec: Duration for the write in seconds.
"""
# flashrom simulates a 4096-byte block size, so the file size needs to
# be a multiple of that.
BLOCK_SIZE = 4096
# flashrom will write one bit per cycle. Convert the block size to bits
# (yielding the frequency for a one-second write) and then scale it as
# needed.
frequency_hz = int(BLOCK_SIZE * 8 / float(duration_sec))
# To avoid flashrom needing to read (slowly) from the dummy device, pass
# a custom diff file filled with zeroes.
zero_file = self.create_temp_file(
'power_DeferForFlashrom.zero', '/dev/zero', BLOCK_SIZE)
rand_file = self.create_temp_file(
'power_DeferForFlashrom.rand', '/dev/urandom', BLOCK_SIZE)
# Start flashrom in the background and wait for it to create its lock
# file.
self.run_in_background(
('flashrom -w %s --diff %s --noverify '
'-p dummy:freq=%d,emulate=VARIABLE_SIZE,size=%d,'
'erase_to_zero=yes') %
(rand_file, zero_file, frequency_hz, BLOCK_SIZE))
logging.info("Waiting for flashrom to create %s...", _LOCK_FILE)
self.host.run(
'while [ ! -e %s ]; do sleep 0.1; done' % (_LOCK_FILE),
timeout=_COMMAND_TIMEOUT)
def send_suspend_request(self, wake_sec):
"""
Asynchronously ask powerd to suspend the system immediately.
@param wake_sec: Integer delay in seconds to use for setting a wake
alarm. Note that the alarm starts when the request is sent to
powerd, not when the system actually suspends.
"""
self.run_in_background(
'powerd_dbus_suspend --delay=0 --wakeup_timeout=%d' % (wake_sec))
def send_reboot_request(self):
"""Ask powerd to reboot the system immediately."""
logging.info('Calling powerd\'s RequestRestart method')
self.host.run(
('dbus-send --type=method_call --system '
'--dest=org.chromium.PowerManager /org/chromium/PowerManager '
'org.chromium.PowerManager.RequestRestart'),
timeout=_COMMAND_TIMEOUT)
def wait_for_system_to_cycle(self, down_sec, up_sec):
"""
Wait for the system to stop and then start responding to pings.
@param down_sec: Maximum delay for the system to go down.
@param up_sec: Maximum delay for the system to come back up.
@return: Floating-point time when system went down.
"""
logging.info("Waiting for host to go down...")
if not self.host.ping_wait_down(timeout=down_sec):
raise error.TestError(
'System hasn\'t gone down after %d seconds' % (down_sec))
down_timestamp = time.time()
logging.info("System went down at %.2f", down_timestamp)
logging.info("Waiting for host to come back up...")
if not self.host.ping_wait_up(timeout=up_sec) or \
not self.host.wait_up(timeout=up_sec):
raise error.TestError('System didn\'t come back up')
return down_timestamp
def run_once(self):
# Start flashrom and then request that the system be suspended. The
# suspend should be deferred until flashrom finishes writing but should
# happen eventually.
flashrom_time = time.time()
self.start_fake_flashrom_write(_SUSPEND_FLASHROM_SEC)
self.send_suspend_request(_SUSPEND_DOWN_SEC)
delay_sec = self.wait_for_system_to_cycle(
_SUSPEND_DOWN_SEC, _SUSPEND_UP_SEC) - flashrom_time
# Check that powerd waited for flashrom to finish.
if delay_sec < _SUSPEND_FLASHROM_SEC:
raise error.TestError(
('Suspend was blocked for %.2f sec; expected it to be blocked '
'for at least %d sec') % (delay_sec, _SUSPEND_FLASHROM_SEC))
# Now do the same thing, but with a reboot request.
flashrom_time = time.time()
self.start_fake_flashrom_write(_REBOOT_FLASHROM_SEC)
self.send_reboot_request()
delay_sec = self.wait_for_system_to_cycle(
_REBOOT_DOWN_SEC, _REBOOT_UP_SEC) - flashrom_time
if delay_sec < _REBOOT_FLASHROM_SEC:
raise error.TestError(
('Reboot was blocked for %.2f sec; expected it to be blocked '
'for at least %d sec') % (delay_sec, _REBOOT_FLASHROM_SEC))