blob: 0f1e8bbf892acea30b7bb7e856c9e667a2922da6 [file] [log] [blame]
#!/bin/sh
# Copyright (c) 2010 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.
# This script is responsible for suspending and resuming the system. It is run
# as root by powerd_setuid_helper, which is run by powerd.
. /usr/share/misc/shflags
DEFINE_integer wakeup_count -1 \
"wakeup event count from start of suspend. -1 to disable" w
DEFINE_integer suspend_duration -1 \
"indicates that the system should wake up after this many seconds" d
# Exit codes returned by this script and reported via the
# Power.SuspendResult histogram:
# The system successfully suspended and resumed.
readonly RESULT_SUCCESS=0
# A suspend failure occurred in the kernel after writing to /sys/power/state
# (i.e. the write to /sys/power/state failed with a non-EBUSY reason).
readonly RESULT_FAILURE=1
# The suspend attempt was canceled as the result of a wakeup event occurring
# between powerd's read from /sys/power/wakeup_count and this script's write to
# it (i.e. the write to wakeup_count failed).
readonly RESULT_CANCELED_EARLY=2
# The suspend attempt was canceled as the result of a wakeup event occurring
# between the write to /sys/power/wakeup_count and the write to
# /sys/power/state (i.e. the write to /sys/power/state failed with EBUSY).
readonly RESULT_CANCELED_LATE=3
# Histogram-only max value.
readonly RESULT_MAX=4
# Directory where this script and send_metrics_on_resume (running as root)
# write temporary state files.
readonly ROOT_RUN_DIR=/var/run/power_manager/root
# For testing: write RTC resume timestamp to this file if it exists
readonly TIMESTAMP_FILE=${ROOT_RUN_DIR}/hwclock-on-resume
# File used to record the power status (AC vs. battery) before suspending. Read
# by send_metrics_on_resume (but only when run after resuming; this doesn't
# need to persist across reboots).
readonly POWER_STATUS_ON_SUSPEND_FILE=${ROOT_RUN_DIR}/power-status-on-suspend
# File containing resume-related timing information. Written by
# send_metrics_on_resume and read by autotests; doesn't need to persist
# across reboots.
readonly LAST_RESUME_TIMINGS_FILE=${ROOT_RUN_DIR}/last_resume_timings
# Directory where this script (running as root) writes files that must
# persist across reboots.
readonly ROOT_SPOOL_DIR=/var/spool/power_manager/root
# File containing the time at which we started suspending. Read by
# send_metrics_on_resume.
readonly SUSPEND_TO_RAM_TIME_FILE=${ROOT_SPOOL_DIR}/suspend-to-ram-time
log_msg() {
logger -t "powerd_suspend[$$]" -- $@
}
log_msg_from_file() {
logger -t "powerd_suspend[$$]" -f $1
}
log_wakeup_count() {
log_msg "wakeup_count is $(cat /sys/power/wakeup_count)"
}
enable_debug() {
# Enable debug info.
if [ -e /sys/module/printk/parameters/console_suspend ]; then
echo -n 'N' > /sys/module/printk/parameters/console_suspend
fi
if [ -e /sys/power/pm_print_times ]; then
echo -n '1' > /sys/power/pm_print_times
fi
}
is_lumpy() {
crossystem hwid | grep -q "LUMPY"
}
# Checks if the system has a Novatel Wireless Gobi 3000 E396/E396U modem.
has_gobi_modem() {
lsusb -d 1410:a021 || lsusb -d 1410:a023
}
# Toggles the power and off GPIOs connected to gobi modem in order to
# revive it.
do_lumpy_gobi_modem_revive() {
cd /sys/class/gpio
# TODO(snanda): Get the gpio base from crossystem instead of assuming
# that it will always be 160 for lumpy.
if [ ! -d gpio198 ]; then
echo 198 > export
fi
if [ ! -d gpio203 ]; then
echo 203 > export
fi
echo out > gpio198/direction
echo out > gpio203/direction
echo 1 > gpio203/value
echo 0 > gpio198/value
echo 0 > gpio203/value
echo 1 > gpio198/value
}
# Writes the wakeup count that was passed to this script to
# /sys/power/wakeup_count. Returns success if the write fails, indicating
# that one or more wakeup events occurred in the meantime.
saw_wakeup_event() {
if [ "${FLAGS_wakeup_count}" -eq -1 ] ||
echo "${FLAGS_wakeup_count}" > "/sys/power/wakeup_count"; then
return 1
else
log_msg "Aborting suspend, wake event received"
log_wakeup_count
return 0
fi
}
# Sends suspend result (one of the above RESULT_* values) to UMA.
send_uma_result() {
metrics_client -e Power.SuspendResult $1 $RESULT_MAX &
}
setup_dark_resume() {
if [ "${FLAGS_suspend_duration}" -ne -1 ]; then
echo 0 > /sys/class/rtc/rtc0/wakealarm
echo "+${FLAGS_suspend_duration}" > /sys/class/rtc/rtc0/wakealarm
fi
}
# Encourage people to use powerd_dbus_suspend instead of running this
# script directly so that e.g. suspend delays and VT locking will happen.
if [ -z "$POWERD_SETUID_HELPER" ]; then
echo "This script is called by powerd. Please run powerd_dbus_suspend" 1>&2
echo "to manually exercise the full suspend path." 1>&2
exit 1
fi
# Note: Don't change or remove this line without also updating
# send_metrics_on_resume.
log_msg "Going to suspend-to-RAM state: args=$@"
# Parse command line.
FLAGS "$@" || exit 1
eval set -- "$FLAGS_ARGV"
# The metrics library requires a max value of 2 rather than 1
# (http://crbug.com/338015).
metrics_client -e Power.SuspendAttempt 0 2 &
# Log the time before going to suspend (no-op if no RTC).
cp /sys/class/rtc/rtc0/since_epoch $SUSPEND_TO_RAM_TIME_FILE \
2> /dev/null || true
# Remove last_resume_timings to ensure the file is fresh on resume.
rm -f $LAST_RESUME_TIMINGS_FILE
# Store the current power status.
power_supply_info 2> /dev/null \
| /bin/grep -Eq '^[[:space:]]+online:[[:space:]]+no$'
if [ $? -eq 0 ]; then
echo OnBattery > $POWER_STATUS_ON_SUSPEND_FILE
else
echo OnAC > $POWER_STATUS_ON_SUSPEND_FILE
fi
# Is this a Lumpy system with Gobi modem?
lumpy_with_gobi_modem=0
if is_lumpy && has_gobi_modem; then
log_msg "Lumpy with gobi modem"
lumpy_with_gobi_modem=1
fi
enable_debug
log_msg "Explicit sync"
sync
result=$RESULT_FAILURE
if saw_wakeup_event; then
# Note: The client/cros/power_suspend.py module in autotest depends on
# this message.
log_msg "Cancel suspend at kernel"
# This file is usually removed by send_metrics_on_resume, but that script
# isn't run if the suspend attempt is unsuccessful.
rm -f $SUSPEND_TO_RAM_TIME_FILE
result=$RESULT_CANCELED_EARLY
else
log_msg "Finalizing suspend"
# Make sure that hwclock is cached for when we wake up, this way we don't
# wait for io and get a more accurate time for wakeup
# -V prints a little version information and exits, loading .so but not
# making actual RTC accesses. Do *NOT* change this to a normal execution...
# it will trigger an RTC interrupt that may count as a wakeup reason, abort
# your suspend and cause you hours of pain and confusion!
if [ -f "${TIMESTAMP_FILE}" ]; then
/sbin/hwclock -V > /dev/null
else
# We don't want to overwrite the alarm for whatever test might be
# running.
setup_dark_resume
fi
# Suspend to RAM. This is piped through cat since we want to determine
# the causes of failures -- dash's "echo" command appears to not print
# anything in response to write errors.
error=$( (echo -n mem | cat > /sys/power/state) 2>&1)
if [ $? = 0 ]; then
# On resume:
result=$RESULT_SUCCESS
if [ -f "${TIMESTAMP_FILE}" ]; then
# Record the hwclock time at resume for the power_Resume test.
TIME=$(/sbin/hwclock --utc --debug)
echo ${TIME} > "${TIMESTAMP_FILE}"
log_msg "Recorded hwclock time for test"
fi
if [ -e "/sys/firmware/log" ]; then
# Grep /sys/firmware/log for WAK
wakeup_source=$(grep -A1 -B1 "PM1_STS: WAK" /sys/firmware/log)
if [ -z "${wakeup_source}" ]; then
wakeup_source="unknown"
fi
log_msg "wake source: ${wakeup_source}"
fi
# Send UMA metrics.
send_metrics_on_resume &
# Revive the modem if it was present prior to suspending but is absent
# now.
if [ $lumpy_with_gobi_modem -eq 1 ]; then
if ! has_gobi_modem ; then
log_msg "Reviving lumpy gobi modem"
do_lumpy_gobi_modem_revive
fi
fi
else
log_msg "Error writing to /sys/power/state: ${error}"
log_msg_from_file /sys/kernel/debug/suspend_stats
# The write will fail with EBUSY if additional wakeup events have
# occurred since the earlier write to /sys/power/wakeup_count.
if echo "${error}" | grep -q 'Device or resource busy'; then
log_wakeup_count
result=$RESULT_CANCELED_LATE
else
result=$RESULT_FAILURE
fi
fi
fi
send_uma_result $result
initctl emit system-resumed &
# Refresh mosys eventlog to help feedback reports pick up the latest snapshot.
/usr/share/userfeedback/scripts/eventlog &
log_msg "Resume finished"
return $result