| #!/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 |