#!/bin/bash
#   (search for POSIX, below, for notes on shell compatibility)
#
# powerd -- power management for the XO
#
# This daemon is a more transparent and flexible (if somewhat
# incompatible) replacement for ohmd on the XO.  powerd is
# independent of X, dbus, and hald, and so might be attractive to
# very lightweight software distributions.
#
# In addition to "standard" XO/ohmd functionality, powerd adds:
# -- a power button splash screen (allowing cancel, suspend, or shutdown).
# -- the ability to run arbitrary scripts after a resume.
# -- configurable timeouts until screen dim and sleep, and configurable
#    dim level.
# -- screen dimming and blanking after laptop has slept
# -- shutdown after laptop has slept
# -- low battery shutdown (only when not sleeping)
# -- different power management behavior when on wall power vs. battery.
# -- different behavior when in ebook mode
# -- ease of customization, given that its written in shell.
# -- integrated power logging, to assist in analyzing XO power usage
#
#
#####
#
# Configuration:
#
# Major config variables are documented below.  powerd reads
# its configuration from /etc/powerd/powerd.conf once at startup,
# and again only when it receives a "reconfig" event on its
# event fifo.  (short answer:  "sudo powerd-config =restart")
#
# If /etc/powerd/powerd.conf is a symlink to another file, then
# powerd-config can be used to easily manage a set of
# named configuration "profiles".  So different configurations
# can be used at different times of day, or during different
# tasks, etc.
#
# All times are expressed in seconds (though a UI could do
# whatever it wants, of course).  Booleans can be enabled with
# "yes", "Yes", "true", "True", or "1".  Anything else will turn
# them off.
#
# There are "dim", "sleep", and "blank" timers for each of
# "plugged in", "battery", and "ebook" modes.  (If the laptop is
# plugged in while in ebook mode, the plugged in timers are
# used.) The nine idle timers are:
#
#   config_BATTERY_TIME_{DIM,SLEEP,BLANK}
#   config_EBOOK_TIME_{DIM,SLEEP,BLANK}
#   config_PLUGGED_TIME_{DIM,SLEEP,BLANK}
#
# Dimming, blanking, and sleep can be scheduled in any order.  If
# "sleep" happens first, the screen will stay on (potentially
# dimmed) while the rest of the system sleeps, but any keystroke
# or touchpad activity will awaken it.  If "sleep" is scheduled
# after "dim" or "blank", the screen will go dim and/or dark
# while the laptop remains otherwise fully active.  Obviously,
# scheduling dimming after blanking only serves to skip the
# dimming state, since a blanked screen can't be dimmed.
#
# Setting any of the timers to '0' suppresses that action.  (The
# value is converted to 999999999 seconds internally, so you may
# see that in the logs.)
#
# Depending on the setting of # "config_KEYPRESS_WAKE_FROM_BLANK_IDLE_SLEEP",
# when the screen is dark _and_ the laptop is sleeping, it may or
# may not be in a "deep" sleep -- keystrokes may or may not wake it
# up.  Likewise, "config_WLAN_WAKE_FROM_BLANK_IDLE_SLEEP" controls
# whether wlan packets may wake the laptop.  (See below for more
# information.)  The maximum time that this state (i.e., screen off, cpu
# sleeping) will last before the laptop shuts down is given by
# "config_MAX_SLEEP_BEFORE_SHUTDOWN".  (This is arguably mis-named,
# since it's not really "max sleep time before shutdown", but "max
# sleeping-and-blank time before shutdown".) Again, a setting of '0'
# will be internally converted to 999999999.
#
# Turning off "config_ALLOW_SHUTDOWN_WHEN_PLUGGED" will keep the
# laptop from ever completely shutting down when it has external
# power.  It may still shut down when using battery power.
#
# "config_IDLE_DIM_LEVEL" specifies how far the screen will dim
# when the dim timer fires.  The range is 0-15.  If set to 15, the
# screen will never dim.
#
# "config_CONFIRM_SECONDS" determines how long the shutdown/suspend
# confirmation splash screen stays visible before the laptop
# automatically suspends.  If this is set to zero, there will be
# no splash screen, and the laptop will suspend immediately on a
# power button push.  In this case, shutdown must be done via
# a menu entry or other command.
#
# "config_UNFREEZE_SECONDS" specifies the delay before powerd
# will unfreeze the DCON, after starting up.
#
# "config_MESH_DURING_SUSPEND" (boolean) controls whether the
# wireless remains powered while the laptop is fully suspended
# (i.e., sleeping with a dark screen).  The only reason to set
# this to "yes" is if you want the laptop to forward packets for
# other mesh users while you're not using it.
#
# "config_CPU_IDLE_LIMIT" (integer)  If the cpu is idle less than
# this percentage, then the laptop won't suspend, on the
# assumption that the system is doing something "important" for the
# user.  Set this to 0 to to allow the system to sleep no matter
# how busy the CPU.
#
# "config_SLEEP_WHEN_LID_CLOSED" causes the laptop to go to sleep
# when closed, and is usually desirable.  One might want to turn this
# off if using the laptop as a server, or perhaps when running an
# application which needs to keep running while the laptop is
# transported.  The screen will be turned off in any case, of course.
#
# "config_KEYPRESS_WAKE_FROM_BLANK_IDLE_SLEEP" says that if the screen
# is blank and the laptop has "idle suspended" (i.e., gone to sleep
# due to inactivity), that a keypress (or touchpad gesture) will wake
# the laptop.  Otherwise this blank-sleeping condition (which is
# visually the same as a power button-induced sleep) will require the
# power button to wake it.  If you like having the keyboard completely
# disabled during any "dark" sleep, then set this to "no".  If the
# power button is used to put the laptop to sleep, this setting will
# have no effect.
#
# "config_WAKE_ON_WLAN" -- determines whether a packet destined for
# your laptop will wake it up from idle suspend, in much the same
# way that a keystroke or mouse movement will wake it up.  see
# also config_WLAN_WAKE_FROM_BLANK_IDLE_SLEEP.
#
# "config_WLAN_WAKE_FROM_BLANK_IDLE_SLEEP" controls whether the
# config_WAKE_ON_WLAN setting also applies once the screen has
# blanked.  (The "blanked" state is the final idle state, and usually
# means the laptop simply isn't being used.  Laptops being access
# remotely, however, should probably have this set to "yes".) This
# setting is conditional on enabling keypress wakeups with
# config_KEYPRESS_E_FROM_BLANK_IDLE_SLEEP.  If the power button is
# used to put the laptop to sleep, this setting will have no effect.
#
#
#####
# Power logging
#
# powerd can optionally perform detailed power usage logging.  powerd
# must be restarted for any changes to the following config_PWRLOG_*
# settings to take effect:
#
# "config_PWRLOG_INTERVAL" determines whether, and how often, logging
# will be performed.  A value of less than 30 seconds will disable
# power logging.  Anything else gives the approximate maximum interval
# between logged samples.  5 minutes (i.e., 300 seconds) is a useful
# setting.  (The value is approximate, because the actual logging
# depends on the granularity of the timer events received from
# olpc-switchd.)
#
# "config_PWRLOG_DIR" is the final destination for the power logs.
# Logs are initially written to /var/log, and copied every 30 minutes
# to the final directory destination.
#
# "config_PWRLOG_LOGSIZE" is the maximum size, in Kbytes, of any single
# log file.  When a log file crosses this threshold, it will be
# copied to $config_PWRLOG_DIR, removed, and a new log started.
#
# "config_PWRLOG_LOGDIRSIZE" is the maximum total space alloted for
# $config_PWRLOG_DIR.  When the directory grows too large, the oldest
# files are removed until the size is below this threshold.
#
#
######
#
# Inhibiting sleep
#
# Idle dim/blank/sleep can be inhibited externally by three separate
# means:
#
# 1) A file may be created in
#     /var/run/powerd-inhibit-suspend/
#   The file must be named after the PID of the process doing the
#   inhibiting (and may be empty).  Stale inhibit files (i.e.,
#   their owner pid no longer exists) will be removed the next time
#   the laptop might sleep.  To manually inhibit dim/blank/sleep one
#   can simply create a file named after a long-running process: 
#   e.g., "touch /var/run/powerd-inhibit-suspend/1".  (Such a file
#   will need to be manually removed, of course.) To assist with the
#   common case of inhibiting suspend while a given program is
#   running, the "olpc-nosleep" wrapper is provided.  e.g.,
#       $ olpc-nosleep wget http://veryslow.downloads.com/bigfile
#
#   The above pid-based flags aren't persistent.  To inhibit suspend
#   in a persistent way (i.e., across reboots), create the file:
#     /etc/powerd/flags/inhibit-suspend
#   (This is the mechanism used by the Sugar control panel UI.)
#
# 2) The modification time of the file
#     /var/run/powerd-inhibit-suspend/.fake_activity
#   will be checked before any sleep occurs, and will be compared to
#   the time of the last "real" user activity.  If it is newer, the
#   dim/blank/sleep will be skipped.  So a program wishing to indicate
#   that it is "active" can simply touch this file periodically to
#   keep it up-to-date.  As a convenience, the command:
#     powerd-config -a
#   will do this touch.  For example, to help keep the laptop alive while
#   an ssh serial console session is in use, add:
#     export PROMPT_COMMAND='powerd-config -a'
#   to your .bash_profile.  This will cause the file to be touched
#   at every shell prompt.
#
# 3) Finally, powerd will try and keep the laptop awake based on
#   various system conditions:  CPU utilizations, network activity, VT
#   console usage, camera and audio usage, and usb device presence. 
#   Network and CPU activity are monitored for 5 seconds before
#   sleep should occur.  These conditions are all checked from the
#   "laptop_busy" function, so look there for details.
#
#   For USB device presence, if the file /etc/powerd/flags/usb-inhibits
#   is present, it will be consulted for matches against USB device
#   id (as hex vend:prod format), just the vendor (as 4 hex
#   characters) or USB device class (as either two byte hex or
#   kernel-reported string.  (i.e., "03" or "hid" will both work to
#   match all HID devices, while "0603:00f2" might match a specific
#   keyboard, and "0603" will match all devices by that vendor.
#
# 4) Idle-suspend will be unconditionally inhibited for 60 seconds
#   after a wakeup from a "dark" (lid or power button) sleep.  In
#   these sleeps, the wlan is powered down, and it sometimes takes
#   some time to reassociate after wakeup.  We don't really want to
#   sleep while the user is waiting for that association to happen.
#
#
# Post-resume scripts can be put in /etc/powerd/postresume.d -- the
# scripts will be run in lexicographic order.  The scripts are run
# detached from powerd, so they won't impede its operation -- however,
# they should be kept as short as possible.
#
#
######
#
# Background discussion:
#
# The inputs that contribute to power management are:
#       power button
#       ebook
#       lid
#       AC plugged/unplugged
#       battery capacity (numeric)
#   Changes on these inputs cause input events on evdev devices
#   /dev/input/eventN.  powerd relies on the companion program
#   "olpc-switchd" to report these events via the /var/run/powerevents
#   fifo it creates for the purpose.  The state of these conditions can
#   also be read from nodes in /sys.  (Some are only available as events
#   on XO-1.)
#
# - Wakeups:
#       rtc wakeup
#       ac_power
#       battery_error
#       battery_soc (i.e., "state of charge" --> capacity)
#       battery_state
#       ebook_mode_change
#       ps2event
#       wlan
#       lid
#   When the system wakes from sleep, /sys/power/wakeup-source
#   reports the cause.
#
# - User activity/idleness:
#   powerd expects user activity and user idle events to be delivered via
#   the olpc-kbdshim daemon. The possible kbdshim events are:
#       useractivity
#       useridle1
#       useridle2
#       useridle3
#
#   These events may be used to dim the screen, blank the screen, and/or
#   put the CPU to sleep, in any order.
#
# - Other system activity: cpu load, network load, etc.
#
#
######
#
# Implementation notes:
#
# - System transitions
#
#   The system can be configured to go through the following 6
#   transition sequences:
#
#       #1      dim     sleep    {blank}  {shutdown}
#       #2      dim     blank    sleep    {shutdown}
#       #3      blank   [dim]    sleep    {shutdown}
#       #4      blank   sleep    {[dim]}  {shutdown}
#       #5      sleep   {blank}  {[dim]}  {shutdown}
#       #6      sleep   {dim}    {blank}  {shutdown}
#
#      {xxx} -- any transition that follows "sleep" is handled in
#           the snooze() function, and not by the useridleN events.
#           That's what the {braces} mean:  "handled by snooze".
#      [dim] -- this state is a no-op, since dimming after blanking
#           doesn't make sense.
#
#   Transitions up through and including "sleep" are driven by the
#   useridle1,2,3 events, and handled by the action routines
#   (dim_action(), blank_action(), and sleep_action()).  These are
#   relatively straightforward.  Once the system is sleeping (using
#   rtcwake), the transitions are handled within the snooze()
#   function.
#
#   As a result of the no-op "[dim]" transition, the
#   implementation of snooze() for sequences #1 and #5 is
#   identical, as is the implementation for #2, #3 and #4.  So
#   snooze() only really has three separate cases to deal with,
#   represented by sequences #2, #1, and #6.  These cases are:
#      - dim then blank then shutdown
#      - blank then shutdown
#      - shutdown
#
#
# - X11
#
#   Note that X11 DPMS is neither consulted nor modified, and it
#   may still blank the display after some period of inactivity.
#   (This won't happen, of course, unless the DPMS "standby" time
#   is shorter than powerd's "sleep" time.)  The effect of DPMS
#   doing this will result in confusion, since when DPMS blanks the
#   screen, powerd can't tell that it's happened.  I.e., a laptop
#   with a screen blanked by DPMS may behave differently than one
#   blanked by powerd.  DPMS should be disabled, either in
#   xorg.conf, or with "xset -dpms".
#
#
# - POSIX shell
#
#   The intention is that powerd be runnable with most any modern
#   shell.  No bash-specific features were used, unless by accident.
#   One possible exception:  in order to implement a non-blocking
#   read of the event fifo, I had to use "read -t 1" (in
#   event_fifo_lookahead()).  Most newer shells implement this, but
#   it's not POSIX.  (The Debian "dash" shell does not, the "ash"
#   shell in busybox works fine.) If run with a shell that doesn't
#   support "-t", the only failure will be the possibility of lost
#   wakeups:  hitting a key just as the system wakes just to dim
#   the screen may not prevent the subsequent sleep.
#
#   Another exception is the (optional) inclusion of olpc-pwr-log.sh,
#   which is heavily dependent on non-POSIX features.
#
#
######
#
# Please send commends/suggestions/patches to pgf@laptop.org.
#
# -----------------------------------------------
#
# Copyright (C) 2009,2010, Paul G Fox
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
# USA.
#

XO=;
CONFIGDIR=/etc/powerd

VERSIONFILE=$CONFIGDIR/version
CONFIGFILE=$CONFIGDIR/powerd.conf
CONFIGFLAGS=$CONFIGDIR/flags

RUNPARTSDIR=$CONFIGDIR/postresume.d

# logging only used for debug, currently
LOGFILE=/var/log/powerd.trace
# LOGFILE=/home/olpc/log/powerd.trace

INHIBITDIR=/var/run/powerd-inhibit-suspend
INHIBIT_BY_TOUCH=$INHIBITDIR/.fake_activity

CONFIRMSPLASH=$CONFIGDIR/pleaseconfirm.pgm
SHUTSPLASH=$CONFIGDIR/shuttingdown.pgm
BATTSHUTSPLASH=$CONFIGDIR/shuttingdown.pgm

EVENTFIFO=/var/run/powerevents

# note re: battery capacity:
# i'd use /sys/.../capacity_level here, but we won't get
# "critical" -- the EC and kernel can only currently indicate
# "low", "normal", or "full", and "low" lasts for another half
# hour -- way too early for shutdown.  using /sys/.../capacity
# (which gives a percentage), and cutting off at 5% means we're
# shutting down with (on my machine and battery under 2 minutes
# of runtime left.  the right answer is to use battery voltage --
# however, we can't get woken for that.  so we fudge, and use
# a combination.
BATTERY_INFO=/sys/class/power_supply/olpc-battery
CAPACITY=$BATTERY_INFO/capacity  # percentage
MICROVOLTS=$BATTERY_INFO/voltage_avg  # microvolts

if [ -d /sys/class/power_supply/olpc-ac ]
then
    AC_ONLINE=/sys/class/power_supply/olpc-ac/online  # 1/0
elif [ -d /sys/class/power_supply/AC ]
then
    AC_ONLINE=/sys/class/power_supply/AC/online  # 1/0
else
    AC_ONLINE=;
fi
BRIGHTNESS=/sys/class/backlight/dcon-bl/brightness  # 0-15
MONO_MODE=/sys/devices/platform/dcon/output

WLAN_ENABLED=/sys/power/wlan-enabled    # XO-1 only

WAKEUP_EVENTS=/sys/power/wakeup_events # directory of mask bits
WAKEUP_SOURCE=/sys/power/wakeup-source # contains name of last wakeup source.

if [ -e /sys/power/wake-on-close ]
then
    WAKE_ON_CLOSE=/sys/power/wake-on-close
else
    WAKE_ON_CLOSE=;
fi

if [ -e /proc/acpi/button/lid/LID/state ]
then
    LID_STATE=/proc/acpi/button/lid/LID/state 
else
    LID_STATE=;
fi

if [ -e /proc/acpi/olpc-switch/ebook/EBK/state ]
then
    EBOOK_STATE=/proc/acpi/olpc-switch/ebook/EBK/state 
else
    EBOOK_STATE=;
fi

DCON_FREEZE=/sys/devices/platform/dcon/freeze
DCON_SLEEP=/sys/class/backlight/dcon-bl/device/sleep


# experimental driver features -- not present in currently
# distributed kernels.
TPAD_RECAL=/sys/devices/platform/i8042/serio1/recalibrate

# command fifo for olpc-kbdshim
USER_ACTIVITY_CMDS=/var/run/olpc-kbdshim_command


# we signal ourselves early, so we have time to see if it's
# really okay to sleep.  units are seconds. 
BUSYCHECK=5   


set -u

log()
{
    logger -t powerd -p daemon.info -- "$@"
    echo : @ $(date +'%F %T') $@ >&2
}

powerd_version="version unknown"
test -e $VERSIONFILE && . $VERSIONFILE

tracing=;
# the trace file will collect stderr, regardless of whether tracing is really on
exec 2>>$LOGFILE
exec 1>&2   # capture stdout, too. (prevents console spew)

log powerd $powerd_version startup at $(date)

set_tracing()
{
    case $1 in
    on)
        set -x
        tracing=1
        : @ tracing begun, $powerd_version
        ;;
    off)
        if [ "$tracing" ]
        then
            : @ tracing stopped
            set +x
            tracing=;
        fi
        ;;
    esac
}

# uncomment for full tracing.  (or, use "powerd-control =tracing-on"
# to enable at runtime.)
# set_tracing on

yes_or_true()
{
    case $1 in
    1|[yYtT]*)  return 0 ;;
    *)          return 1 ;;
    esac
}

# use $SECONDS for timing when possible
if [ "${SECONDS:-}" ]
then
    SECONDS=$(date +%s)
    seconds()
    {
        echo $SECONDS
    }
else
    seconds()
    {
        date +%s
    }
fi

if [ -e /dev/fb ]
then
    framebuffer=/dev/fb
elif [ -e /dev/fb0 ]
then
    framebuffer=/dev/fb0
else
    log "no framebuffer?"
fi


splash()
{
    case $1 in
    confirm)
        args="$CONFIRMSPLASH $SHUTSPLASH"
        ;;
    critical)
        args="$BATTSHUTSPLASH"
        ;;
    shutdown)
        args="$SHUTSPLASH"
        ;;
    esac

    pnmto565fb -d -f $framebuffer -s 9999999 $args &
    splashpid=$!
}

nextsplash()
{
    # kill -USR1 will cause pnmto565fb to display next image (stays alive)
    test "${splashpid:-}" && kill -USR1 $splashpid
}

unsplash()
{
    # kill -INT will cause pnmto565fb to restore original VT
    test "${splashpid:-}" && kill -INT $splashpid
    splashpid=;
}

leavesplash()
{
    # kill -TERM will cause pnmto565fb to just die
    test "${splashpid:-}" && kill -TERM $splashpid
    splashpid=;
}

do_shutdown()
{
    log shutting down due to $*
    leavesplash # kill the splasher (leaving splash visible)
    sleep .05s
    /sbin/poweroff &
    sleep 9999999
}

#
# both cpu_busy and network_busy integrated below, since they
# both need a time span over which to work.
#cpu_busy()
#{
#    # pull the cpu idle percentage (over one second) out of the
#    # 15th field of vmstat output.  (this metric borrowed from ohmd.)
#    field='\([[:space:]]\+[[:digit:]]\+\)'
#    idle=$(vmstat 1 2  | sed -n "\$s/$field\{15\}.*/\1/p")
#
#    # ensure numeric.  if failure, assume we're not busy.
#    test "$idle" && test $idle -eq $idle >/dev/null || return 1
#
#    test $idle -lt $config_CPU_IDLE_LIMIT && : @ cpu busy && return 0
#
#    return 1
#}
#
#network_busy()
#{
#    # TBD
#    return 1
#}


cpu_or_network_busy()
{
    # initial cpu stats -- search for /proc/stat in  "man 5 proc"
    procstat1=( $(< /proc/stat) )
    jiffies1=$(( procstat1[1] + procstat1[2] + procstat1[3] + procstat1[4] ))
    idlejifs1=${procstat1[4]}

    # take initial network stats
    if  [ "$monitor_network_activity" ]
    then
        netactivity=$(netactivity_snapshot)
    fi

    # wait a while ($BUSYCHECK, or 5 seconds, currently), and
    # check for events while doing so.  we can wait this 5
    # seconds because we arranged to be signalled 5 seconds
    # before actually wanting to suspend.
    start=$(seconds)
    while :
    do
        event_fifo_lookahead soft && return 0
        test $(($(seconds) - start)) -ge $BUSYCHECK && break
    done

    # second cpu stats
    procstat2=( $(< /proc/stat) )
    jiffies2=$(( procstat2[1] + procstat2[2] + procstat2[3] + procstat2[4] ))
    idlejifs2=${procstat2[4]}

    # what fraction of total elapsed jiffies were idle jiffies?
    idle=$(( (100 * ( idlejifs2 - idlejifs1 ) / ( jiffies2 - jiffies1 ) ) ))
    if [ $idle -lt $config_CPU_IDLE_LIMIT ]
    then
        : @ cpu busy
        return 0
    fi

    if [ "$monitor_network_activity" ]
    then
        # second network stats -- any network activity?
        newnetactivity=$(netactivity_snapshot)
        if [ "$newnetactivity" != "$netactivity" ]
        then
            : @ network busy
            return 0
        fi
    fi

    return 1
}


# unused for now, because too many sound apps leave the
# device open:  the tamtams, record (after playing), browse
# and firefox (after playing)
#audio_busy()
#{
#    grep -q RUNNING /proc/asound/card0/pcm0?/sub0/status && : @ audio busy
#}

camera_busy()
{
    grep -q via-dma /proc/interrupts && : @ camera busy
}


# if using both of the above, combine them into a single grep:
#audio_or_camera_busy()
#{
#    egrep -q 'RUNNING|via-dma' \
#        /proc/asound/card0/pcm0?/sub0/status /proc/interrupts && \
#            : @ audio or camera busy
#}


# see if there's been activity during the recent idle period.
# checks vty devices and our special touchfile, with one stat.
touchfiles="/dev/tty1 /dev/tty2 $INHIBIT_BY_TOUCH"

filetimes_busy()
{
    timer=$1
    now=$(seconds)
    for touched in $(stat -c '%Y' $touchfiles 2>/dev/null )
    do
        test $(( touched > now - timer )) = 1 && \
                : @ touchfile busy && return 0
    done
    return 1
}

inhibit_files_present()
{

    # check the persistent inhibit flag first
    test -e $CONFIGFLAGS/inhibit-suspend && return 0

    inhibit_ret=1

    # check for pid files.  remove any non-pid files
    for f in $(ls $INHIBITDIR )
    do
        if kill -0 $f 2>/dev/null
        then
            inhibit_ret=0
            break
        fi
        rm -f $INHIBITDIR/$f
    done

    return $inhibit_ret
}

create_inhibit_dir()
{
    # most inhibit-like flags go in /var/run, and aren't persistent
    mkdir -p $INHIBITDIR
    chmod a+rwt $INHIBITDIR
    touch $INHIBIT_BY_TOUCH
    chmod a+w $INHIBIT_BY_TOUCH

    # some might need to be persistent (mainly for the sake of UI
    # simplicity, where parsing and writing the config file would
    # be overkill).
    mkdir -p $CONFIGFLAGS
    chmod a+rw $CONFIGFLAGS
}

# checking inhibit files is much faster then checking laptop
# "busyness", so it's separated out.
inhibited_by_files()
{
    inhibit_files_present || filetimes_busy $t1
}

usb_inhibit()
{

    usbpats=/var/run/powerd-usbpatterns 
    usbconf=$CONFIGFLAGS/usb-inhibits

    test -e $usbconf || return 1

    # convert user "strings" to "^strings$", only when user config changes.
    if [ ! -e $usbpats -o $usbconf -nt $usbpats ]
    then
	sed -e 's/^/\^/' -e 's/$/\$/' $usbconf > $usbpats
    fi

    # pull out all USB class, vendor, and vendor:id values, and
    # make them available for comparison, one per line, against
    # the user-supplied strings.
    gotusb=$(sed -n -e 's/^.*Cls=\([[:xdigit:]]\{2\}\)(\([^ )]\+\).*/\1\
\2/p' \
	-e 's/^.*Vendor=\([[:xdigit:]]\{4\}\).*ProdID=\([[:xdigit:]]\{4\}\).*/\1:\2\
\1/p' \
	    /proc/bus/usb/devices |
	grep -if $usbpats )

    # implicit return value
    test "$gotusb" && : @ got usb $gotusb
}


laptop_busy()
{
    usb_inhibit || cpu_or_network_busy || camera_busy
}

if [ -e $TPAD_RECAL ]
then
    touchpad_recalibrate()
    {
        # take advantage of lid open events, which indicate that a)
        # someone is quite unlikely to be using the touchpad, and b)
        # the physical environment may have changed since last use,
        # to force a recalibration:
        echo 1 > $TPAD_RECAL
    }
else
    touchpad_recalibrate()
    {
        :
    }
fi

read_wlan_power()
{
    local x
    read x < $WLAN_ENABLED
    eval $1=\"$x\"
}

set_wlan_power()
{
    # only on XO-1.  (no echo, but that's okay with the callers)
    test "$XO" != 1 && return

    # very important not to do extra writes to wlan-enabled -- the
    # node is sort of misnamed.  writing a 0 puts the wlan into reset.
    # writing a 1 puts it into reset, but immediately releases the
    # reset.  so a reset is always involved, even if brief.
    read_wlan_power w
    if [ "$w" != "$1" ]
    then
        echo $1 > $WLAN_ENABLED
    fi
    echo $w
}

set_wake_on_wlan()
{
    # note:  ethtool settings aren't remembered after a resume,
    # i.e., they're essentially a "one-shot" configuration.  the
    # good news is that this means we never have to clear
    # wake-on-lan settings.

    #ethtool -s eth0 wol ua   # we don't support wake-on-arp (yet?)
    case $1 in
    yes) ethtool -s eth0 wol u ;;
    *)   ethtool -s eth0 wol d ;;
    esac
}

wlan_associated()
{
    # do some crude "editor logic" on the iwconfig output. 
    # produce a string that's empty if we're associated, i.e.,
    # not empty if we're unassociated:  try and produce output if
    # the radio is off, or if it says "unassociated" or
    # "Not-Associated", but scratch that second part if we're in
    # ad-hoc mode.

    unassociated=$(
        iwconfig eth0 |
        sed -n \
            -e '/Mode:Ad-Hoc/d' \
            -e '/radio off\|Tx-Power=off\|[ut][n-][aA]ssociated/p'
    )

    test ! "$unassociated"   # set return value
}

runparts()
{
    rc_action=$1
    test $rc_action = "resume" || return

    rc_dir=$RUNPARTSDIR
    test -d $rc_dir || return

    cd $rc_dir

    for s in *[^~]
    do
        case $s in
        '*[^~]') return;;  # s is the literal pattern
        esac

        if [ -x $s ]; then
            echo "Running $s $rc_action" >&2
            ./$s $rc_action || echo "*** $s failed"
        elif [ -f $s ]; then
            echo "Skipping $s (disabled, not executable)" >&2
        else
            if [ -L $s ]; then
                echo "Removing dangling symlink $s" >&2
                rm $s
            else
                echo "Skipping $s (not an executable file)" >&2
            fi
        fi
    done

}

set_acpi_wakeupevents()
{
    # no acpi events on XO-1
    test "$XO" = 1 && return

    # enable --> enabled, disable --> disabled
    skip=${2}d

    # fetch the current contents of /proc/acpi/wakeup.  if
    # we're asked to disable FOO, delete its line if it's
    # already disabled, else print just FOO if it's enabled,
    # (and vice versa) and write that back to the file.
    sed -n -e '/^Device\>/d' \
            -e "/^$1[[:space:]].*\<$skip\>/d" \
            -e "s/^\($1\)[[:space:]].*/\1/p" \
        /proc/acpi/wakeup >/proc/acpi/wakeup
}

set_wakeupevents()
{
    # this routine was simpler when only the XO-1 was around -- all
    # it dealt with was the nodes in /sys/power/wakeup_events.
    # note that /sys/power/wakeup_events isn't just masking wakeups --
    # it's masking SCI events, which also provide battery and EC input
    # events (and ebook on XO-1).
    case $1 in
    all)        set_acpi_wakeupevents EBK enable
                test "$WAKE_ON_CLOSE" && echo 1 > $WAKE_ON_CLOSE
                echo 1 >$WAKEUP_EVENTS/all
                ;;
    all_sci)    # we want to unmask all SCI events at the EC when
                # we waken after having masked them, but we don't
                # need to massage the acpi settings, etc.  i.e.,
                # this is used when we awake, not when we go to sleep.
                echo 1 >$WAKEUP_EVENTS/all
                ;;
    none)       set_acpi_wakeupevents EBK disable
                test "$WAKE_ON_CLOSE" && echo 0 > $WAKE_ON_CLOSE
                echo 0 >$WAKEUP_EVENTS/all
                ;;
    ac)         echo 1 >$WAKEUP_EVENTS/ac_power
                ;;
    battery)    echo 1 >$WAKEUP_EVENTS/battery_soc
                echo 1 >$WAKEUP_EVENTS/battery_state
                echo 1 >$WAKEUP_EVENTS/battery_error
                # next one not always present
                echo 1 >$WAKEUP_EVENTS/battery_critical 2>&1
                ;;
    battery_critical)
                echo 1 >$WAKEUP_EVENTS/battery_critical 2>&1
                ;;
    esac
}

read_wakeupsource()
{
    local x
    read x < $WAKEUP_SOURCE
    eval $1=\"$x\"
}

event_fifo_lookahead()
{
    local type
    type=$1

    # in some cases in snooze() we wake just to perform
    # a short action or status check, and then we sleep again
    # immediately.  if we don't check the event fifo, we may   
    # well miss an event that would normally wake us (or
    # keep us from sleeping).  there's no way to close this
    # race completely, but this is way better than nothing.

    while read -t 1 s_event s_tstamp s_arg2 s_arg3 s_more
    do
        # if the event we read is "interesting enough" that
        # it would have woken us if it had arrived just a little
        # later, break out.  otherwise discard it.
        : @ s_event-type is $s_event-$type
        case $s_event-$type in
        powerbutton-*|lidopen-hard|lid*-soft|\
         ebook*-soft|ac-*-soft|useractive-soft)
            if [ ! "$s_tstamp" ] || \
                [ "$s_tstamp" -ge "$eventcutoff" ]
            then
                return 0
            fi
            ;;
        esac
    done <&6  # from the fifo

    return 1

}

snooze()
{

    local shutdowntime dimtime blanktime orig_rtctime rtctime
    local until sleep_type wakeupsource prev_wlan_power sleep_started

    prev_wlan_power=;

    runparts suspend

    # we only use rtc alarms to either a) turn of the screen when
    # we've been sleeping for a while, or b) shutdown after we've been
    # sleeping for a while.  if we do a), we'll do b) next.
    until=$1   # "until_dim", "until_blank", or "until_shutdown"
    sleep_type=$keypress_sleep;
    shutdowntime=${2:-}
    case $until in
    until_dim)
        dimtime=${3:-}
        blanktime=${4:-}
        orig_rtctime=$dimtime
        ;;
    until_blank)
        blanktime=${3:-}
        orig_rtctime=$blanktime
        ;;
    until_shutdown)
        orig_rtctime=$shutdowntime
        sleep_type=$3  # "hard" or "soft", i.e., can kbd/tpad wake us up
        ;;  
    esac

    rtctime=$orig_rtctime

    while :
    do

        pwrlog_take_reading suspend

        : @ until-sleep_type is $until-$sleep_type 

        case $until-$sleep_type in
        until_shutdown-hard)
            # if we're waiting for a shutdown, then this is a
            # pretty sound sleep.  only let the lid wake us up
            # (we can't actually prevent that), as well as battery
            # events if we're close to shutdown levels.

            set_wakeupevents none
            # note:  it's tempting to allow AC wakeups here, but in
            # practice a) there's no advantage -- we won't do anything
            # differently if we wake and then go back to sleep, and b) it's
            # tricky, when woken for one reason, for which we might
            # simply sleep again (i.e., AC), to know that we shouldn't
            # also have awoken for another reason (i.e., LID) without
            # draining the event queue.  far simpler to not take AC
            # and LID wakeups at the same time.

            # regardless, take battery_critical events
            set_wakeupevents battery_critical

            # obviously if we ignore AC wakeups, we won't do the following
            # if we're subsequently unplugged.  hopefully the critical
            # wakeup above will save the day.
            if [ "$ac" != "online" ]
            then
                read_battery
                if [ "$battery_capacity" -le 20 ]
                then
                    set_wakeupevents battery
                fi
            fi
            # ohmd uses this:
            #    nm-tool | grep msh0 -A4 | grep Active | awk '{print $2}'\"
            # to determine whether mesh is active before turning off wireless.
            # i guess we could do the same.
            if [ ! "$prev_wlan_power" ] && \
                 ! yes_or_true "$config_MESH_DURING_SUSPEND"
            then
                prev_wlan_power=$(set_wlan_power 0)
            fi

            if [ "$wake_on_wlan" ]
            then
                set_wake_on_wlan no
            fi
            ;;
        *)
            # otherwise we want most anything to wake us.
            sleep_type=soft
            set_wakeupevents all

            if [ "$wake_on_wlan" ]
            then
                # wake on wlan if we're only waiting for dim or
                # blank.  else we're blank, so w-o-l if we've
                # been asked to and we're currently associated with
                # something.
                if [ "$until" != until_shutdown ] || \
                    ( yes_or_true $config_WLAN_WAKE_FROM_BLANK_IDLE_SLEEP && \
                        wlan_associated )
                then
                    set_wake_on_wlan yes
                fi
            fi
            ;;
        esac

        reset_idlecounters
        event_fifo_lookahead $sleep_type && break

        if [ "$sleep_type" = soft ]
        then
            inhibited_by_files && break
        fi

        sleep_started=$(seconds)

        # this is it.  here's where we suspend or sleep.
        rtcwake -m mem -a -s $rtctime >/dev/null

        set_wakeupevents all_sci

        # prepare for the default case of simply re-sleeping for
        # the remaining time.  may be overridden.  note that this
        # also protects against a) sleeping for less than a second,
        # which is buggy in the kernel, and b) trying to sleep for
        # 0 seconds, which might happen if we wake up due to an AC
        # event at just the same time that rtcwake should have expired.
        lastwakeup=$(seconds)
        rtctime=$(( rtctime - ( lastwakeup - sleep_started ) ))
        rtctime=$(( (rtctime <= 1) ? 2 : rtctime ))

        # we want to toss extra events that happened long ago, as
        # we were sleeping, but not those that happened as we
        # were waking up.  choose the middle of our nap as the
        # cutoff:
        eventcutoff=$(( ( lastwakeup + sleep_started ) / 2 ))

        if [ "$XO" = 1.5 ]
        then
            # for some reason our ebook and ec interrupts get disabled
            # across a suspend/resume cycle
            echo enable >/sys/firmware/acpi/interrupts/gpe01
            echo enable >/sys/firmware/acpi/interrupts/gpe0A
        fi

        power_check

        read_wakeupsource wakeupsource

        # compress out spaces
        set -- $wakeupsource
        wakeupsource=$1${2:-}${3:-}

        : @ got wakeup $wakeupsource @ $lastwakeup, \
                slept $(( lastwakeup - sleep_started ))

        pwrlog_take_reading resume-$wakeupsource

        case $wakeupsource in
        "powerbutton")
            : @ power button during $until
            case $until in
            until_shutdown)
                # this takes care of the "screen blanked but not sleeping"
                # case.  we only want the button to give the splash menu
                # when the screen was lit when it was pushed.
                selfinject fake_useractive $lastwakeup "$wakeupsource"
                holdoff_start=$lastwakeup
                ;;
            *)
                # waiting until_dim or until_blank, so screen is still on
                selfinject fake_powerbutton $lastwakeup "$wakeupsource"
                ;;
            esac
            break
            ;;

        "wlanpacket")
            if yes_or_true "$config_WAKE_ON_WLAN"
            then
                selfinject fake_useractive $lastwakeup "$wakeupsource"
                break
            fi
            ;;

        "rtcalarm")
            : @ rtcalarm during $until
            case $until in
            until_dim)
                # we don't get woken on lid close, so check here.
                # if we can't tell, assume we're still open
                backlight dim
                until=until_blank
                orig_rtctime=$blanktime
                rtctime=$blanktime
                ;;

            until_blank)
                backlight off
                until=until_shutdown
                orig_rtctime=$shutdowntime
                rtctime=$shutdowntime
                ;;

            until_shutdown)
                if [ $rtctime -le 2 ]  # time really expired
                then
                    if [ "$ac" != "online" ] || \
                            yes_or_true "$config_ALLOW_SHUTDOWN_WHEN_PLUGGED"
                    then
                        splash shutdown
                        do_shutdown "idle timeout"
                    fi
                    # if we didn't shut down, we'll sleep again for
                    # the same time as before, before we check again.
                    rtctime=$orig_rtctime
                    log found external power, sleeping instead of shutdown
                else
                    # not sure why this happens, but i've seen a
                    # sleep of 999999999 turn into 69506 seconds
                    : awoke for shutdown too early, sleeping again
                fi
                ;;
            esac
            ;;

        "acpower")
            : @ acpower during $until
            case $until in
            until_shutdown)
                # we don't wake on AC, so this shouldn't happen.  if it did: 
                # restart the shutdown timer.  the choice of sleep time is
                # kind of a guess, since we're not tracking how much
                # charging we're getting while plugged in.  so if we're
                # plugged in, we sleep forever, otherwise, we sleep (again)
                # for the original time.
                if [ "$ac" = "online" ]
                then
                    rtctime=999999999
                else
                    rtctime=$orig_rtctime
                fi
                ;;

            *)  # we were waiting for dim or blank, and we got plugged in.
                # we'll wake up fully, so we can re-decide whether we
                # should really be sleeping aggressively.
                break;
                ;;
            esac
            ;;

        "lid"|"emptysci")
            # assume "empty sci" comes from lid (kernel bug on XO-1).
            # in older kernels an rtc wakeup will report "empty
            # sci" as well.  powerd doesn't support those kernels
            # very well.
            am_ebook=;
            selfinject fake_useractive $lastwakeup "$wakeupsource"
            touchpad_recalibrate
            holdoff_start=$lastwakeup
            break
            ;;

        battery*)  # all we do is check battery level, and sleep again
            ;;

        *)  # "keypress"|"ebook"|"unknown")
            selfinject fake_useractive $lastwakeup "$wakeupsource"
            break
            ;;
        esac

        # unless we've broken out of the loop, we're going to sleep
        # again.

    done

    test "$prev_wlan_power" && set_wlan_power $prev_wlan_power

    reset_idlecounters
    runparts resume &
}

set_brightness()
{
    echo $1 >$BRIGHTNESS
    echo $(( $1 == 0 )) >$MONO_MODE
}

read_brightness()
{
    local x
    read x < $BRIGHTNESS
    eval $1=\"$x\"
}

brightness_ramp()
{
    local i
    test $1 = $2 && return

    # ramp in either direction
    incr=1
    test $1 -gt $2 && incr=-1

    i=$1
    while :
    do
        : $((i += incr))
        echo $i >$BRIGHTNESS
        test $i = $2 && break
        sleep .025s
    done
    echo $(( $i == 0 )) >$MONO_MODE
}

backlight()
{
    case $1 in
    restore)
        if [ "$dimmed" -a "$savebright" ]
        then
            # could ramp here, but it wastes time while
            # the user is starting to work again
            # read_brightness curbright
            # if [ $curbright -lt $savebright ]
            # then
            #     brightness_ramp $curbright $savebright
            # fi
            set_brightness $savebright
            dimmed=;
        fi
        dcon wake
        ;;
    dim)
        if [ ! "${dimmed}" ]
        then
            read_brightness savebright
            if [ $savebright -gt $config_IDLE_DIM_LEVEL ]
            then
                brightness_ramp $savebright $config_IDLE_DIM_LEVEL
            fi
            dimmed=true
        fi
        ;;
    off)
        # this is all unneeded: sleeping the dcon disables BL.
        #if [ ! "${dimmed}" ]
        #then
        #    read_brightness savebright
        #    dimmed=true
        #fi
        # set_brightness 0
        dcon sleep
        ;;

    is_off)
        local d
        read d < $DCON_SLEEP
        test $d = 1
        return
        ;;
    esac
}

dcon()
{
    case $1 in
    freeze) echo 1 > $DCON_FREEZE ;;
    thaw)   echo 0 > $DCON_FREEZE ;;
    is_frozen)
        local d
        read d < $DCON_FREEZE
        test $d = 1
        return
        ;;
    sleep)  echo 1 > $DCON_SLEEP ;;
    wake)   echo 0 > $DCON_SLEEP ;;
    esac
}

lid_closed()
{
    am_ebook=;
    backlight off
    if yes_or_true "$config_SLEEP_WHEN_LID_CLOSED"
    then
        invalidate_powertimer
        # hard sleep until it's time to shut down
        snooze until_shutdown "$shutdowntimer" hard
    fi
}

read_lidstate()
{
    local x
    read x < $LID_STATE
    eval $1=\"$x\"
}

if [ "$LID_STATE" ]
then
    lid_check()
    {
        read_lidstate lidstate
        case $lidstate in
        *closed) lid_closed ;;     # check against "state:   closed"
        esac
    }
else
    lid_check()
    {
        :
    }
fi

read_ebookstate()
{
    local x
    read x < $EBOOK_STATE
    eval $1=\"$x\"
}

if [ "$EBOOK_STATE" ]
then
    ebook_check()
    {
        read_ebookstate ebookstate
        case $ebookstate in
        *closed) am_ebook=true ;;     # check against "state:   closed"
        *)       am_ebook=;    ;;
        esac
    }
else
    ebook_check()
    {
        :
    }
fi

set_config_defaults()
{
    # give all config values defaults, so we'll
    # never find them unset
    config_BATTERY_TIME_DIM=120
    config_BATTERY_TIME_SLEEP=130
    config_BATTERY_TIME_BLANK=240
    config_EBOOK_TIME_DIM=120
    config_EBOOK_TIME_SLEEP=130
    config_EBOOK_TIME_BLANK=240
    config_PLUGGED_TIME_DIM=120
    config_PLUGGED_TIME_SLEEP=130
    config_PLUGGED_TIME_BLANK=240

    config_IDLE_DIM_LEVEL=5

    config_WAKE_ON_WLAN=yes
    config_WLAN_WAKE_FROM_BLANK_IDLE_SLEEP=no
    config_KEYPRESS_WAKE_FROM_BLANK_IDLE_SLEEP=yes
    config_MAX_SLEEP_BEFORE_SHUTDOWN=3600
    config_ALLOW_SHUTDOWN_WHEN_PLUGGED=yes
    config_SLEEP_WHEN_LID_CLOSED=yes
    config_MESH_DURING_SUSPEND=no

    config_CONFIRM_SECONDS=7
    config_UNFREEZE_SECONDS=1

    config_CPU_IDLE_LIMIT=10
    config_MONITOR_NETWORK_ACTIVITY=yes

    config_PWRLOG_INTERVAL=0
    config_PWRLOG_DIR=/home/olpc/power-logs
    config_PWRLOG_LOGSIZE=50            # Kbytes
    config_PWRLOG_LOGDIRSIZE=1000       # Kbytes
}

read_config()
{
    if [ ! -e $CONFIGFILE ]
    then
        log "cannot find $CONFIGFILE, recreating (empty)"
        touch $CONFIGFILE
    fi
    . $CONFIGFILE

    if [ "$config_CONFIRM_SECONDS" -eq 0 ]
    then
        logger configured so power button will cause immediate sleep
    fi

    if ! yes_or_true "$config_ALLOW_SHUTDOWN_WHEN_PLUGGED"
    then
        log configured to never shutdown while plugged in
    fi

    wake_on_wlan=;
    # no wake on wlan on XO-1
    if [ "$XO" = 1.5 ] && yes_or_true "$config_WAKE_ON_WLAN"
    then
        wake_on_wlan=true
        log configured to wake on wireless activity
    fi

    if yes_or_true "$config_KEYPRESS_WAKE_FROM_BLANK_IDLE_SLEEP"
    then
        keypress_sleep=soft
    else
        keypress_sleep=hard
    fi

}

maybe_battery_shutdown()
{

    test "$ac" = "online" && return

    case $battery_capacity in
    [987654321])
        read uvolts < $MICROVOLTS
        if [ $uvolts -le 5700000 ]  # EC's idea of critical voltage: 5.7V
        then
            splash critical
            sleep 1
            do_shutdown "low battery"
        fi
        ;;
    esac

}

read_battery()
{
    read battery_capacity < $CAPACITY || battery_capacity=0
}

plugged_in()
{
    test "$AC_ONLINE" || return 1

    read on_ac < $AC_ONLINE
    if [ "$on_ac" = 1 ]
    then
        ac="online"
        return 0
    else
        ac="offline"
        return 1
    fi
}

power_check()
{
    if ! plugged_in
    then
        read_battery
        maybe_battery_shutdown
    fi
}

reset_idlecounters()
{
    # if given arguments, remember then for reuse later on.
    # (olpc-kbdshim will reset its idle timers when the deadlines are reset)
    test "${1:-}" && last_idlecounters="$*"
    echo "I $last_idlecounters" >$USER_ACTIVITY_CMDS
}

set_idletimes()
{
    local dt st bt nt1 nt2 nt3 d

    if [ "$ac" = "online" ]
    then
        dt=$config_PLUGGED_TIME_DIM
        st=$config_PLUGGED_TIME_SLEEP
        bt=$config_PLUGGED_TIME_BLANK
    else
        if [ "$am_ebook" ]
        then
            dt=$config_EBOOK_TIME_DIM
            st=$config_EBOOK_TIME_SLEEP
            bt=$config_EBOOK_TIME_BLANK
        else
            dt=$config_BATTERY_TIME_DIM
            st=$config_BATTERY_TIME_SLEEP
            bt=$config_BATTERY_TIME_BLANK
        fi
    fi

    # have the values changed?
    if [ "$dt $st $bt" = "$last_t_values" ]
    then
        return 1
    fi
    last_t_values="$dt $st $bt"

    # now validate the configured times...

    # force all to be numeric:
    test "$dt" -eq "$dt" || \
        { logger non-numeric dim time found; dt=120; }
    test "$st" -eq "$st" || \
        { logger non-numeric sleep time found; st=120; }
    test "$bt" -eq "$bt" || \
        { logger non-numeric blank time found; bt=120; }

    # change '0' to infinite
    test "$dt" -eq 0 && \
        { logger zero dim time found, now 999999999; dt=999999999; }
    test "$st" -eq 0 && \
        { logger zero sleep time found, now 999999999; st=999999999; }
    test "$bt" -eq 0 && \
        { logger zero blank time found, now 999999999; bt=999999999; }

    # we want the timers in time order, so we sort them, along
    # with their associated action routines and names (for logging).
    set -- $( (
        echo $dt  "dim"   dim_action
        echo $st  "sleep" sleep_action
        echo $bt  "blank" blank_action
                    ) | sort -n )

    # extract the sorted values
    t1=$1; aname1=$2; useridle1_action=$3
    t2=$4; aname2=$5; useridle2_action=$6
    t3=$7; aname3=$8; useridle3_action=$9

    # this is a pain.  first, we want the timers to be at least a
    # second apart.  in addition, if the timer will trigger a
    # sleep, we want to have advance it by an extra $BUSYCHECK
    # seconds, so we can do a cpu and network activity check
    # before actually sleeping.  so for that one, the gap needs
    # to be 6 (i.e., $BUSYCHECK + 1) seconds.

    test "$aname1" = "sleep" && d=$BUSYCHECK || d=0
    test $(( (t1 - d) )) -le 0 && : $(( t1 += (1 + d) ))
    test "$aname1" = "sleep" && nt1=$(( t1 - d ))

    test "$aname2" = "sleep" && d=$BUSYCHECK || d=0
    test $(( (t2 - d) )) -le $t1 && : $(( t2 = t1 + (1 + d) ))
    test "$aname2" = "sleep" && nt2=$(( t2 - d ))

    test "$aname3" = "sleep" && d=$BUSYCHECK || d=0
    test $(( (t3 - d) )) -le $t2 && : $(( t3 = t2 + (1 + d) ))
    test "$aname3" = "sleep" && nt3=$(( t3 - d ))

    useridle1_delta=$((t2 - t1))
    useridle2_delta=$((t3 - t2))
    useridle1_next=$useridle2_action
    useridle2_next=$useridle3_action

    # give olpc-kbdshim the timeouts
    reset_idlecounters ${nt1:-$t1} ${nt2:-$t2} ${nt3:-$t3}

    t4=${config_MAX_SLEEP_BEFORE_SHUTDOWN:-0}
    test "$t4" -eq "$t4" || t4=300
    test "$t4" -eq 0 && \
        { logger zero max-sleep time found, now 999999999; t4=999999999; }
    test "$t4" -le "5" && t4=300

    log $aname1 $t1, $aname2 $t2, $aname3 $t3, shutdown after $t4
    dimtimer=;
    blanktimer=;
    shutdowntimer=$t4

    return 0
}

reevaluate()
{
    if [ "${1:-}" = all -o \
         "${o_ac:-}" != "$ac" -o \
         "${o_ebook:-}" != "$am_ebook" ]
    then
        set_idletimes || reset_idlecounters
    fi
    o_ebook=$am_ebook
    o_ac=$ac
}

selfinject()
{
    test -p $EVENTFIFO || exit 1
    echo $* >$EVENTFIFO
}

sched_powertimer()
{
    # we can't cancel timers, so give them sequence numbers
    : $((timerseqno += 1))
    (sleep $1; shift 1; selfinject powertimerdone $(seconds) $timerseqno $*) &
}

sched_unfreezetimer()
{
    (sleep $config_UNFREEZE_SECONDS; selfinject unfreeze_dcon ) &
}

invalidate_powertimer()
{
    # bump the seq number so we won't match a scheduled timer
    # when it arrives.
    : $((timerseqno += 1))
}

gotactivity()
{
    invalidate_powertimer
    unsplash
}

dim_action()
{
    backlight is_off || backlight dim
}

blank_action()
{
    backlight off
}

sleep_action()
{
    nextaction=$1
    delta1=$2
    delta2=$3

    # if we're still booting, the dcon may still be frozen.
    # don't sleep until we're sort of all the way up.
    if dcon is_frozen
    then
        reset_idlecounters
        return
    fi

    # extend the first wakeup after power or lid sleep to at
    # least 60 seconds to work around slow wlan startup (#9854)
    : ${holdoff_start:=}
    if [ "$holdoff_start" -a $(($(seconds) - holdoff_start)) -lt 60 ]
    then
        reset_idlecounters
        return
    fi

    # we'll check the files again later, just before sleeping.
    # checking now keeps from doing extra work, and checking
    # later narrows the race condition (laptop_busy() takes many
    # seconds)
    if inhibited_by_files
    then
        reset_idlecounters
        return
    fi

    # kick off a sync if there isn't one running.  it will be
    # less disruptive to the user now than if it happens during
    # the actual suspend.
    killall -q -0 sync || { sync & }

    # we've allowed an extra $BUSYCHECK seconds for this action. 
    # use it here.
    if laptop_busy
    then
        reset_idlecounters  # restart the timers
        return
    fi

    # sleep until it's time to shut down
    if [ $nextaction = dim_action ] && ! backlight is_off
    then
        snooze until_dim "$shutdowntimer" "$delta1" "$delta2"
    elif [ $nextaction = blank_action ]
    then
        snooze until_blank "$shutdowntimer" "$delta1"
    else
        snooze until_shutdown "$shutdowntimer" $keypress_sleep
    fi
}

exit_actions()
{
    pwrlog_take_reading shutdown
    set_wakeupevents none
    unsplash
    dcon thaw
    dcon wake
    rm -f $EVENTFIFO
    rm -rf $INHIBITDIR
    exit
}

event_loop()
{
    local event tstamp arg2 arg3 more

    : @ starting eventloop

    set_idletimes

    unsplash

    # recreate to flush, and to make sure it's a fifo
    rm -f $EVENTFIFO
    mkfifo $EVENTFIFO
    chmod 600 $EVENTFIFO
    exec 6<> $EVENTFIFO

    lid_check

    while :
    do

        reevaluate

        # if we did lookahead on events in snooze(), use the result here
        if [ "${s_event:-}" ]
        then
            event=$s_event
            tstamp=$s_tstamp
            arg2=$s_arg2
            arg3=$s_arg3
            more=$s_more
            unset s_event s_tstamp s_arg2 s_arg3 s_more
        else
            read event tstamp arg2 arg3 more
        fi

        : -------------------------
        : @ got event $event, $tstamp, $arg2, $arg3, $more.

        if [ "$tstamp" -a "$tstamp" != '-' ] && [ "$tstamp" -lt "$eventcutoff" ]
        then
            # it's possible for olpc-switchd to generate multiple
            # hardware-related events (e.g., lidopen/lidclose/lidopen)
            # before we go to sleep due the first one.  toss any
            # that are stale.
            continue
        fi

        pwrlog_take_reading $event-event $arg2 $arg3 $more

        case $event in

        powerbutton|fake_powerbutton)
            # sync, in case the user keeps holding the button.
            # hope it's finished in 4 seconds.
            killall -q -0 sync || { sync & }

            if backlight is_off
            then
                # if the screen is off, the power button should simply
                # wake the system up.
                gotactivity
                backlight restore
                reset_idlecounters
            else
                if [ $config_CONFIRM_SECONDS = 0 ]
                then
                    backlight off
                    # hard sleep until it's time to shut down
                    snooze until_shutdown "$shutdowntimer" hard
                else
                    if [ "$powerseqno" != "$timerseqno" ]
                    then # first press
                        splash confirm
                        reset_idlecounters # reprime for "useractivity"
                        sched_powertimer $config_CONFIRM_SECONDS gotosleep
                        powerseqno=$timerseqno
                        backlight restore
                    else # second press
                        nextsplash  # advance to next splash screen
                        sleep 1
                        do_shutdown  "power press"
                    fi
                fi
            fi
            ;;

        powertimerdone)
            # are we still waiting for this timer?
            if [ "$timerseqno" = "$arg2" ]
            then
                echo $arg3 $(seconds) >&6
                : $((timerseqno += 1))
            fi
            ;;

        gotosleep)
            invalidate_powertimer
            backlight off
            # hard sleep until it's time to shut down
            snooze until_shutdown "$shutdowntimer" hard
            ;;

        lidclose)
            lid_closed
            ;;

        lidopen)
            touchpad_recalibrate
            gotactivity
            am_ebook=;
            backlight restore
            ;;

        ebookclose)  # i.e., fully flat in ebook mode
            am_ebook=true
            ;;

        ebookopen)
            am_ebook=;
            ;;

        useractive)
            test "$tracing" && : $(seconds)
            gotactivity
            backlight restore
            ;;

        fake_useractive)
            test "$tracing" && : $(seconds)
            gotactivity
            case "$arg2" in
            wlanpacket|unknown|ectimer)
                ;;  # don't brighten for wlan or other unknowns
            *)
                backlight restore
                ;;
            esac
            reset_idlecounters
            ;;

        useridle1)
            test "$tracing" && : $(seconds)
            $useridle1_action \
                $useridle1_next $useridle1_delta $useridle2_delta
            ;;

        useridle2)
            test "$tracing" && : $(seconds)
            $useridle2_action $useridle2_next $useridle2_delta ""
            ;;

        useridle3)
            test "$tracing" && : $(seconds)
            $useridle3_action none "" ""
            ;;

        ac-online|ac-offline)
            ac=${event#ac-}
            ;;

        battery)
            battery_capacity=$arg2
            maybe_battery_shutdown
            ;;

        timer)
            # olpc-switchd will emit a "timer" event periodically
            # if asked to.  the only purpose is to make sure
            # olpc-pwr-log.sh has a reason to log once in a while.
            test "$tracing" && : $(seconds)
            ;;

        reconfig)
            read_config
            power_check
            reevaluate all
            ;;

        unfreeze_dcon)
            # this is a bit of a hack.  on the XO, the bootanim
            # script contrives to leave the dcon frozen at the end
            # of the init sequence.  it remains frozen until sugar
            # first becomes idle, at which point sugar sends a dbus
            # message to ohmd, causing ohmd to unfreeze the dcon.
            # since we don't do dbus, we unfreeze the dcon after 10
            # seconds.
            reset_idlecounters
            dcon thaw
            ;;

        dark-suspend)
            # we have some external power measurement scripts that
            # want to quiesce the laptop without killing off powerd.
            # this lets them work together.
            backlight off
            snooze until_shutdown "$shutdowntimer" soft
            ;;

        trace-on)
            set_tracing on
            ;;
        trace-off)
            set_tracing off
            ;;
        esac

    done <&6  # from the fifo
}

read_hwinfo()
{
    read hwvendor < /sys/class/dmi/id/sys_vendor ||
        hwvendor="n/a"
    read hwname < /sys/class/dmi/id/product_name ||
        hwname="n/a"
    read hwversion < /sys/class/dmi/id/product_version ||
        hwversion="n/a"
}

choose_xo_model()
{
    read_hwinfo

    case $hwvendor-$hwname-$hwversion in
    OLPC-XO-1|OLPC-XO-1.5)
        XO=$hwversion
        ;;
    *)  # pre-F11 releases didn't have the /sys/class/dmi tree, so we
        # also check for an olpc-only node in /sys
        if [ -e /sys/power/wakeup_events/ebook_mode_change ]
        then
            XO=1
        fi
        ;;
    esac

    if [ ! "$XO" ]
    then
        log Unsupported hardware: vendor "$hwvendor", version "$hwversion"
        exit 1
    fi
}
        
configure_pwrlog()
{
    if [ $config_PWRLOG_INTERVAL -gt 30 -a \
            -s $CONFIGDIR/olpc-pwr-log.sh ]
    then
        pwrlog_inside_powerd=yes

        mkdir -p $config_PWRLOG_DIR
        chown olpc:olpc $config_PWRLOG_DIR

        . $CONFIGDIR/olpc-pwr-log.sh
        pwrlog_init $config_PWRLOG_INTERVAL $config_PWRLOG_DIR \
            $config_PWRLOG_LOGSIZE $config_PWRLOG_LOGDIRSIZE

    else
        # install a null handler
        pwrlog_take_reading()
        {
            : @ pwrlog unconfigured, or unavailable
        }
    fi
}

netactivity_snapshot()
{
    iptables --list netactivity --verbose --exact --numeric
}

init_netactivity_tracking()
{
    local iface oface

    monitor_network_activity=;

    if ! yes_or_true "$config_MONITOR_NETWORK_ACTIVITY" || \
        ! test -x /sbin/iptables
    then
        return
    fi
    monitor_network_activity=true

    iface="! --in-interface lo"
    oface="! --out-interface lo"

    # this initialization may interfere with other future
    # uses of iptables.  but for now it's the right thing.
    iptables --flush INPUT
    iptables --policy INPUT ACCEPT
    iptables --flush OUTPUT
    iptables --policy OUTPUT ACCEPT


    iptables --flush netactivity        2>/dev/null
    iptables --delete-chain netactivity 2>/dev/null
    iptables --new-chain netactivity  # (re)create the chain

    # count incoming pings
    iptables --insert INPUT -p icmp $iface -j netactivity

    # count incoming, already-connected tcp
    iptables --insert INPUT -p tcp ! --syn $iface -j netactivity

    # count outgoing already-connected tcp
    # redundant with next rule
    #iptables --insert OUTPUT -p tcp ! --syn $oface -j netactivity

    # count all outgoing packets _except_ mdns
    iptables --insert OUTPUT -p tcp ! --dport mdns $oface -j netactivity
    iptables --insert OUTPUT -p udp ! --dport mdns $oface -j netactivity

    ## count all incoming packets _except_ mdns
    #iptables --insert INPUT -p tcp ! --dport mdns $iface -j netactivity
    #iptables --insert INPUT -p udp ! --dport mdns $iface -j netactivity

    # this is the null "rule" that aggregates the above counts,
    iptables --insert netactivity 

}


am_ebook=;
o_ebook=xxx;
dimmed=;
savebright=;
last_t_values=;
timerseqno=1;
powerseqno=0;
eventcutoff=0;

choose_xo_model

log starting

set_config_defaults
if [ -r $CONFIGFILE ]
then
    log configuring from $CONFIGFILE
fi

create_inhibit_dir

read_config

init_netactivity_tracking

configure_pwrlog

pwrlog_take_reading startup

sched_unfreezetimer

trap "exit_actions" 0

power_check
ebook_check

reevaluate all

# let user programs control brightness
chmod a+w $BRIGHTNESS $MONO_MODE

set_acpi_wakeupevents LID enable
set_acpi_wakeupevents EC enable
set_wakeupevents all_sci


event_loop
