392 lines
10 KiB
Bash
Executable File
392 lines
10 KiB
Bash
Executable File
#!/bin/sh
|
|
# vim:noexpandtab
|
|
|
|
|
|
# Default values go here. It is important to _not_ initialize some
|
|
# variables here. They are:
|
|
#
|
|
# PM_CMDLINE
|
|
# RESUME_MODULES
|
|
#
|
|
# for great debugging!
|
|
[ "${PM_DEBUG}" = "true" ] && {
|
|
export PM_DEBUG
|
|
set -x
|
|
}
|
|
set -a
|
|
PM_UTILS_LIBDIR="/usr/lib/pm-utils"
|
|
PM_UTILS_ETCDIR="/etc/pm"
|
|
PM_UTILS_RUNDIR="/var/run/pm-utils"
|
|
|
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin:"${PM_UTILS_LIBDIR}"/bin
|
|
PM_LOGFILE="/var/log/${STASHNAME}.log"
|
|
TEMPORARY_CPUFREQ_GOVERNOR="performance"
|
|
LOCKDIR="${PM_UTILS_RUNDIR}/locks"
|
|
STORAGEDIR="${PM_UTILS_RUNDIR}/${STASHNAME}/storage"
|
|
NA=254
|
|
NX=253
|
|
DX=252
|
|
PM_FUNCTIONS="$PM_UTILS_LIBDIR/functions"
|
|
PM_QUIRKDB="$PM_UTILS_LIBDIR/video-quirks"
|
|
PM_LKW_QUIRKS="/var/cache/pm-utils/last_known_working.quirkdb"
|
|
# Use c sort order
|
|
LC_COLLATE=C
|
|
|
|
# These should be overridden by defaults and/or config.d settings.
|
|
# Specifically, distros should override these by modifying defaults,
|
|
# and end users should modify these using files in /etc/pm/config.
|
|
HIBERNATE_MODE=""
|
|
HIBERNATE_RESUME_POST_VIDEO="no"
|
|
SLEEP_MODULE="auto"
|
|
# These variables will be handled specially when we load the config file.
|
|
SUSPEND_MODULES=""
|
|
HOOK_BLACKLIST=""
|
|
ADD_PARAMETERS=""
|
|
DROP_PARAMETERS=""
|
|
PARAMETERS="${STORAGEDIR}/parameters"
|
|
INHIBIT="${STORAGEDIR}/inhibit"
|
|
PM_CMDLINE="$*"
|
|
BEFORE_HOOKS=""
|
|
MODULE_HELP=""
|
|
SUSPEND_MODULE=""
|
|
HIBERNATE_MODULE=""
|
|
SUSPEND_HYBRID_MODULE=""
|
|
|
|
# variables to handle hibernate after suspend support
|
|
PM_HIBERNATE_DELAY=900 # 15 minutes
|
|
PM_RTC=/sys/class/rtc/rtc0
|
|
|
|
# when loading configuration files, allow stash-specific ones
|
|
# to override the pm-utils global ones.
|
|
[ -f "${PM_UTILS_LIBDIR}"/defaults ] && . "${PM_UTILS_LIBDIR}"/defaults
|
|
[ -f "${PM_UTILS_LIBDIR}/${STASHNAME}.defaults" ] && \
|
|
. "${PM_UTILS_LIBDIR}/${STASHNAME}.defaults"
|
|
set +a
|
|
|
|
for cfg in "${PM_UTILS_ETCDIR}"/config.d/*[!~] \
|
|
"${PM_UTILS_ETCDIR}/${STASHNAME}.config.d"/*[!~]; do
|
|
[ -f "$cfg" ] || continue
|
|
# Ugly, I know. The goal here is to allow multiple files in
|
|
# /etc/pm/config.d declare these variables and have those
|
|
# declarations add together instead of the last one overwriting
|
|
# all the others.
|
|
[ "$SUSPEND_MODULES" ] && REAL_SUSPEND_MODULES="$SUSPEND_MODULES"
|
|
[ "$HOOK_BLACKLIST" ] && REAL_HOOK_BLACKLIST="$HOOK_BLACKLIST"
|
|
[ "$ADD_PARAMETERS" ] && REAL_ADD_PARAMETERS="$ADD_PARAMETERS"
|
|
[ "$DROP_PARAMETERS" ] && REAL_DROP_PARAMETERS="$DROP_PARAMETERS"
|
|
set -a
|
|
. "${cfg}"
|
|
SUSPEND_MODULES="$REAL_SUSPEND_MODULES $SUSPEND_MODULES"
|
|
HOOK_BLACKLIST="$REAL_HOOK_BLACKLIST $HOOK_BLACKLIST"
|
|
ADD_PARAMETERS="$REAL_ADD_PARAMETERS $ADD_PARAMETERS"
|
|
DROP_PARAMETERS="$REAL_DROP_PARAMETERS $DROP_PARAMETERS"
|
|
set +a
|
|
done
|
|
|
|
. "${PM_FUNCTIONS}"
|
|
|
|
# Simple little logging function.
|
|
# We do it this way because 'echo -n' is not posix.
|
|
log()
|
|
{
|
|
is_set "$LOGGING" || return 0;
|
|
local fmt='%s\n'
|
|
[ "$1" = "-n" ] && { fmt='%s'; shift; }
|
|
printf "$fmt" "$*"
|
|
}
|
|
|
|
profiling() { [ "$PM_PROFILE" = "true" ]; }
|
|
|
|
if profiling; then
|
|
profile() {
|
|
local t1 t2 status msg elapsed
|
|
msg="$1"
|
|
shift
|
|
t1="$(date '+%s.%N')"
|
|
"$@"
|
|
status=$?
|
|
t2="$(date '+%s.%N')"
|
|
elapsed="$(printf "%s %s - p q" "$t2" "$t1" |dc)"
|
|
log "$msg: $(printf "%.3f" $elapsed) ($t2 - $t1)"
|
|
return $status
|
|
}
|
|
|
|
else
|
|
profile() { shift; "$@"; }
|
|
fi
|
|
|
|
add_before_hooks() {
|
|
[ -z "$BEFORE_HOOKS" ] && BEFORE_HOOKS="$*" || \
|
|
BEFORE_HOOKS="$BEFORE_HOOKS $*"
|
|
}
|
|
|
|
add_module_help() {
|
|
[ -z "$MODULE_HELP" ] && MODULE_HELP="$*" || \
|
|
MODULE_HELP="$MODULE_HELP $*"
|
|
}
|
|
|
|
before_hooks()
|
|
{
|
|
[ -z "$BEFORE_HOOKS" ] && return 0
|
|
local meth
|
|
for meth in $BEFORE_HOOKS; do
|
|
command_exists "$meth" && "$meth"
|
|
done
|
|
}
|
|
|
|
sleep_module_help()
|
|
{
|
|
[ -z "$MODULE_HELP" ] && return 0
|
|
local meth
|
|
for meth in $MODULE_HELP; do
|
|
command_exists "$meth" && "$meth"
|
|
done
|
|
}
|
|
|
|
# update PM_CMDLINE iff someone changed our parameters
|
|
update_parameters()
|
|
{
|
|
[ -f "$PARAMETERS.new" ] || return
|
|
export PM_CMDLINE="$(get_parameters)"
|
|
rm -f "$PARAMETERS.new"
|
|
}
|
|
|
|
# if the user asked us to blacklist any hooks, do it.
|
|
load_hook_blacklist()
|
|
{
|
|
[ "$HOOK_BLACKLIST" ] || return
|
|
local hook
|
|
for hook in $HOOK_BLACKLIST; do
|
|
disablehook "${hook}" "blacklisted by user"
|
|
log "Blacklisting ${hook}."
|
|
done
|
|
}
|
|
|
|
load_hook_parameters()
|
|
{
|
|
[ "$DROP_PARAMETERS" ] && remove_parameters $DROP_PARAMETERS
|
|
[ "$ADD_PARAMETERS" ] && add_parameters $ADD_PARAMETERS
|
|
update_parameters
|
|
}
|
|
|
|
|
|
hook_exit_status(){
|
|
case $1 in
|
|
0) log "success." ;;
|
|
$NA) log "not applicable." ;;
|
|
$NX) log "not executable." ;;
|
|
$DX) log "disabled." ;;
|
|
*) log "Returned exit code $1."; return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# check to see if a hook is a candidate for being run.
|
|
hook_ok()
|
|
{
|
|
local hook="${1##*/}"
|
|
# the actual name as passed to us.
|
|
[ -f "$STORAGEDIR/disable_hook:$hook" ] && return $DX
|
|
# name with leading digits chopped off the filename
|
|
[ -f "$STORAGEDIR/disable_hook:${hook#[0-9][0-9]}" ] && return $DX
|
|
[ -x "$1" ] || return $NX
|
|
return 0
|
|
}
|
|
|
|
_run_hook() {
|
|
# $1 = hook to run
|
|
# rest of args passed to hook unchanged.
|
|
log "Running hook $*:"
|
|
hook_ok "$1" && "$@"
|
|
# log() changes the return value, so save it for later
|
|
local status=$?
|
|
log -n "$*: "
|
|
hook_exit_status $status && LAST_HOOK="${1##*/}" || inhibit
|
|
log ""
|
|
}
|
|
|
|
if profiling; then
|
|
run_hook() { profile "$1:" _run_hook "$@"; }
|
|
else
|
|
run_hook() { _run_hook "$@"; }
|
|
fi
|
|
|
|
# Run all applicable hooks, logging success and failure as we go.
|
|
_run_hooks() {
|
|
# $1 = type of hook to find.
|
|
# $2 = paramaters to pass to hooks.
|
|
# $3 = if present and equal to "reverse", run hooks backwards.
|
|
# Currently only power and sleep are meaningful.
|
|
local syshooks="${PM_UTILS_ETCDIR}/$1.d"
|
|
local phooks="${PM_UTILS_LIBDIR}/$1.d"
|
|
command_exists before_hooks && before_hooks
|
|
local sort="sort"
|
|
local base
|
|
local hook
|
|
local oifs="${IFS}"
|
|
# the next two lines are not a typo or a formatting error!
|
|
local nifs="
|
|
"
|
|
IFS="${nifs}" # tolerate spaces in filenames.
|
|
[ "$3" = "reverse" ] && sort="sort -r"
|
|
for base in $(IFS="${oifs}"; for f in "$syshooks/"*[!~] "$phooks/"*[!~];
|
|
do [ -O "$f" ] && echo ${f##*/} ; done | $sort | uniq) ;
|
|
do
|
|
IFS="${oifs}"
|
|
# if we are running backwards, skip hooks that we did not
|
|
# run going forwards due to a hook failing.
|
|
[ "$3" -a "$3" = "reverse" -a "$LAST_HOOK" ] && \
|
|
[ "$base" \> "$LAST_HOOK" ] && continue
|
|
# if we have already inhibited suspend/resume,
|
|
# don't run any more hooks.
|
|
[ ! "$3" ] && inhibited && break
|
|
update_parameters
|
|
if [ -f "$syshooks/$base" ]; then
|
|
hook="$syshooks/$base"
|
|
elif [ -f "$phooks/$base" ]; then
|
|
hook="$phooks/$base"
|
|
fi
|
|
run_hook "$hook" $2
|
|
IFS="${nifs}"
|
|
done
|
|
IFS="${oifs}"
|
|
# return value is 1 if something was inhibited, 0 otherwise.
|
|
inhibited && return 1 || return 0
|
|
}
|
|
|
|
if profiling; then
|
|
run_hooks() { profile "$1 $2:" _run_hooks "$@"; }
|
|
else
|
|
run_hooks() { _run_hooks "$@"; }
|
|
fi
|
|
|
|
# Try to reinitalize the logfile. Fail unless certian criteria are met.
|
|
init_logfile()
|
|
{
|
|
if [ -z "$1" ]; then
|
|
echo "Please pass a filename to init_logfile."
|
|
return 1
|
|
elif [ -h "$1" ]; then
|
|
echo "$1 is a symbolic link, refusing to overwrite."
|
|
return 1
|
|
elif [ -f "$1" -a ! -O "$1" ]; then
|
|
echo "We do not own $1, refusing to overwrite."
|
|
return 1
|
|
fi
|
|
export LOGGING=true
|
|
exec >> "$1" 2>&1
|
|
}
|
|
|
|
check_suspend() { [ -n "$SUSPEND_MODULE" ]; }
|
|
check_hibernate() { [ -n "$HIBERNATE_MODULE" ]; }
|
|
check_suspend_hybrid() { [ -n "$SUSPEND_HYBRID_MODULE" ]; }
|
|
|
|
|
|
check_suspend_pmu()
|
|
{
|
|
perl << EOF
|
|
sub PMU_IOC_CAN_SLEEP { 0x40044205; }
|
|
open PMU, '/dev/pmu' or die "open /dev/pmu: \$!";
|
|
\$p = pack 'l', 0;
|
|
ioctl PMU, &PMU_IOC_CAN_SLEEP, \$p or die "ioctl: \$!";
|
|
(\$v) = unpack 'l', \$p;
|
|
exit (\$v ? 0 : 1);
|
|
EOF
|
|
}
|
|
|
|
do_suspend_pmu()
|
|
{
|
|
perl << EOF
|
|
sub PMU_IOC_SLEEP { 0x20004200; }
|
|
open PMU, "/dev/pmu" or die "open /dev/pmu: \$!";
|
|
ioctl PMU, &PMU_IOC_SLEEP, 0;
|
|
EOF
|
|
}
|
|
|
|
# allow autodetection of sleep methods
|
|
if [ "$SLEEP_MODULE" = "auto" ]; then
|
|
SLEEP_MODULE="tuxonice uswsusp"
|
|
fi
|
|
|
|
for mod in $SLEEP_MODULE; do
|
|
mod="${PM_UTILS_LIBDIR}/module.d/${mod}"
|
|
[ -f "$mod" ] || continue
|
|
. "$mod"
|
|
done
|
|
|
|
# always fall back to kernel methods if nothing else was declared
|
|
|
|
if [ -z "$SUSPEND_MODULE" ]; then
|
|
if grep -q mem /sys/power/state; then
|
|
SUSPEND_MODULE="kernel"
|
|
do_suspend() { echo -n "mem" >/sys/power/state; }
|
|
elif [ -c /dev/pmu ] && check_suspend_pmu; then
|
|
SUSPEND_MODULE="kernel"
|
|
do_suspend() { do_suspend_pmu; }
|
|
elif grep -q standby /sys/power/state; then
|
|
SUSPEND_MODULE="kernel"
|
|
do_suspend() { echo -n "standby" >/sys/power/state; }
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$HIBERNATE_MODULE" ] && \
|
|
[ -f /sys/power/disk ] && \
|
|
grep -q disk /sys/power/state; then
|
|
HIBERNATE_MODULE="kernel"
|
|
do_hibernate()
|
|
{
|
|
[ -n "${HIBERNATE_MODE}" ] && \
|
|
grep -qw "${HIBERNATE_MODE}" /sys/power/disk && \
|
|
HIBERNATE_MODE_SAVE=$(cat /sys/power/disk) && \
|
|
HIBERNATE_MODE_SAVE="${HIBERNATE_MODE_SAVE##*[}" && \
|
|
HIBERNATE_MODE_SAVE="${HIBERNATE_MODE_SAVE%%]*}" && \
|
|
echo -n "${HIBERNATE_MODE}" > /sys/power/disk
|
|
echo -n "disk" > /sys/power/state
|
|
RET=$?
|
|
echo -n "$HIBERNATE_MODE_SAVE" > /sys/power/disk
|
|
return "$RET"
|
|
}
|
|
fi
|
|
|
|
# for kernels that support suspend to both (i.e. hybrid suspend)
|
|
# since kernel 3.6
|
|
if [ -z "$SUSPEND_HYBRID_MODULE" ] && \
|
|
[ -f /sys/power/disk ] && \
|
|
grep -q disk /sys/power/state && \
|
|
grep -q suspend /sys/power/disk; then
|
|
SUSPEND_HYBRID_MODULE="kernel"
|
|
do_suspend_hybrid()
|
|
{
|
|
HIBERNATE_MODE="suspend"
|
|
do_hibernate
|
|
}
|
|
fi
|
|
|
|
# since the kernel does not directly support hybrid sleep, we do
|
|
# something else -- suspend and schedule an alarm to go into
|
|
# hibernate if we have slept long enough.
|
|
# Only do this if we do not need to do any special video hackery on resume
|
|
# from hibernate, though.
|
|
if [ -z "$SUSPEND_HYBRID_MODULE" -a -w "$PM_RTC/wakealarm" ] && \
|
|
check_suspend && check_hibernate && ! is_set $HIBERNATE_RESUME_POST_VIDEO; \
|
|
then
|
|
SUSPEND_HYBRID_MODULE="kernel"
|
|
do_suspend_hybrid() {
|
|
WAKETIME=$(( $(cat "$PM_RTC/since_epoch") + PM_HIBERNATE_DELAY))
|
|
echo >"$PM_RTC/wakealarm"
|
|
echo $WAKETIME > "$PM_RTC/wakealarm"
|
|
if do_suspend; then
|
|
NOW=$(cat "$PM_RTC/since_epoch")
|
|
if [ "$NOW" -ge "$WAKETIME" -a "$NOW" -lt $((WAKETIME + 30)) ]; then
|
|
log "Woken by RTC alarm, hibernating."
|
|
# if hibernate fails for any reason, go back to suspend.
|
|
do_hibernate || do_suspend
|
|
else
|
|
echo > "$PM_RTC/wakealarm"
|
|
fi
|
|
else
|
|
# if we cannot suspend, just try to hibernate.
|
|
do_hibernate
|
|
fi
|
|
}
|
|
fi
|