#!/bin/bash
#   (search for POSIX, below, for notes on shell compatibility)
#
# powerd -- power management for the XO familiy of laptops
#
# This daemon manages the aggressive suspend and wakeup policies
# for the OLPC laptops.
#
# powerd provides:
# -- 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
# -- different power management behavior when on wall power vs. battery.
# -- different behavior when in ebook mode
# -- automatic backlight quench, based on input from ambient light sensor
# -- 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 any time that file is modified.
#
# 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_INITIAL_BRIGHT_LEVEL" specifies the initial brightness
# setting for the screen.  The range is 0-15.  The scale is
# linear, and the full-on power consumption of the backlight is
# 1W, so each level increases power consumption by about 67mW.
#
# "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_WAKE_WHEN_LID_OPENED" will allow simply opening the lid to
# wake the laptop, which can be convenient.  However, if laptops are
# stacked on top of one another, or side-by-side like on a bookcase,
# their lid switches (which are magnetic) may interfere with one
# another, causing them to wake even though the lids are still closed. 
# So this setting is off by default -- a power button push will be
# required to wake the laptop.
#
# "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_WAKE_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
# (requires the power-logger helper script).  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.
#
# a new power log file may be forced with the command:
#  powerd-config =new-pwrlog "comment describing reason for a new log"
#
#
######
# Ambient light sensor
#
# "config_AMBIENT_BRIGHT" and "config_AMBIENT_NORMAL" set the
# hysteresis limits for the light sensor (currently only XO-1.75
# has such a sensor).  Smaller sensor readings imply brighter
# light, so the BRIGHT limit should be lower than the NORMAL
# limit.  The backlight will be turned off at the BRIGHT limit,
# and turned back on at the NORMAL limit.  Setting either limit
# to 0 will disable light sensor events.
#
#
######
#
# 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
#
#   When suspend is inhibited in this fashion, powerd internally
#   pretends that suspend has been rescheduled a very long time in
#   the future.  Dimming and blanking will still occur at their
#   normally scheduled intervals.  To inhibit dimming and blanking,
#   create a file in
#     /var/run/powerd-inhibit-dim
#   as instead, or in addition.
#
#   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 checked from the
#   "laptop_busy" and "snooze" functions, 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.
#
######
#
# Suspend and resume scripts
#
# Post-resume scripts can be put in /etc/powerd/postresume.d -- the
# scripts will be run in lexicographic order, with "resume" given as
# the first and only argument.  The scripts are run detached from
# powerd (i.e.  "in the background"), so they won't impede its
# operation -- however, they should be kept as short as possible.
#
# Similarly, scripts can be run before each suspend by putting them
# in /etc/powerd/presuspend.d.  Again, they will be run in lexicographic
# order, with "suspend" as their only argument.  In this case, the
# scripts must all complete before suspend can continue.
#
# NOTE:  these hooks are currently called at the top and bottom of the
# snooze() function.  It's entirely possible for the system to decide
# not to suspend (e.g., system busy, suspend inhibited, etc) sometime
# after the suspend scripts have run.  It's currently also possible
# that the system will wake and go right back to sleep (e.g.  for a
# battery level check) without running the resume/suspend hooks.  No
# guarantees.
# 
#
######
#
# 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, on 2.6.35 and earlier kernels,
#   /sys/power/wakeup-source reports the cause.  On later 3.0 and later
#   kernels, the source is deduced from per-source counters.
#
# - 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 -- however, a few bash-specific features have crept in. 
#   Specifically:  a) I couldn't resist using $(< file) instead of
#   $(cat file) -- that's fixable with a simple search and replace,
#   and b) 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
#   power-logger, which is heavily dependent on non-POSIX
#   features.
#
# - Configuration monitoring
#
#   powerd makes use of an inotify partner process to keep track
#   of changes to its configuration.  powerd will learn immediately
#   about any changes to the configuration file:
#       /etc/powerd/powerd.conf
#   or to the contents of these directories:
#       /etc/powerd/flags/
#       /var/run/powerd-inhibit-suspend/
#       /var/run/powerd-inhibit-dim/
#
######
#
# Please send commends/suggestions/patches to pgf@laptop.org.
#
# -----------------------------------------------
#
# Copyright (C) 2009,2010,2011,2012, 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.
#

######
# 
# Logging/Tracing/Debug
# 
# powerd will always log at least minimal information to syslog,
# as well as to /var/log/powerd.trace (controlled by $LOGFILE,
# below).
#
# powerd can optionally do diagnostic tracing, and/or full
# execution tracing.  Choose one of the lines below to get either
# a trace of important events and wakeups, or a full "sh -x" style
# execution trace.  to accomplish the same thing while powerd is
# running, use:  "powerd-config =trace-on", "powerd-config =trace-debug",
# or "powerd-config =trace-off" (disables both types of output)

# uncomment one:
# tracing=1
# debug=1

XO=;
CONFIGDIR=/etc/powerd
DATADIR=$CONFIGDIR
LIBEXECDIR=/usr/libexec/powerd

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

RESUMEHOOKS=$CONFIGDIR/postresume.d
SUSPENDHOOKS=$CONFIGDIR/presuspend.d

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

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

# the splash screen images
CONFIRMSPLASH=$DATADIR/pleaseconfirm.pgm
SHUTSPLASH=$DATADIR/shuttingdown.pgm
BATTSHUTSPLASH=$DATADIR/shuttingdown.pgm

# our incoming event fifo
EVENTFIFO=/var/run/powerevents

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

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

# 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
if [ -d $BATTERY_INFO ]
then
    CAPACITY=$BATTERY_INFO/capacity  # percentage
    MICROVOLTS=$BATTERY_INFO/voltage_avg  # microvolts
    no_battery=;
else
    no_battery=true
fi

# dcon display control -- almost always present
if [ -d /sys/devices/platform/dcon ]
then
    DCON_FREEZE=/sys/devices/platform/dcon/freeze
    DCON_SLEEP=/sys/class/backlight/dcon-bl/device/sleep
    BRIGHTNESS=/sys/class/backlight/dcon-bl/brightness  # 0-15
    if [ -e /sys/devices/platform/dcon/monochrome ]
    then
        MONO_MODE=/sys/devices/platform/dcon/monochrome
    else
        MONO_MODE=/sys/devices/platform/dcon/output
    fi
    no_dcon=;
    no_brightness=;
else
    no_dcon=true
    no_brightness=true;
fi

# try for another backlight device
if [ "$no_brightness" -a -d /sys/class/backlight ]
then # choose the first
    BACKLIGHT=$(ls /sys/class/backlight | sed 1q )
    if [ "$BACKLIGHT" ]
    then
        # assume levels of 0-15 for now -- use max_brightness later
        BRIGHTNESS=/sys/class/backlight/$BACKLIGHT/brightness
        no_brightness=;
    fi
fi


# only on laptops with the original ALPS touchpads
TPAD_RECAL=/sys/devices/platform/i8042/serio1/recalibrate




# XO-1.75/XO-4 has two RTC clocks, but only one of them has alarm capabilities.
# the /dev/rtcalarm symlink makes this explicit (though the default rtc0
# would probably be correct in any case -- it's the one internal to the SoC.
# (rtcwake can't use the symlink directly, because it parses it
# to find /sys nodes)
rtca=$(readlink -e /dev/rtcalarm)
rtcdevice=${rtca:-/dev/rtc0}

no_ac=;
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=;
    ac=online
    no_ac=true
fi

have_olpc_brightness=;
if [ -x /usr/bin/olpc-brightness ]
then
    have_olpc_brightness=true
fi

if [ -e /sys/power/wake-on-lid ]   # 2.6.3x vintage XO-1 kernels
then
    wake_on_lid()
    {
        # "never", "open", "close", "always"
        echo $1 > /sys/power/wake-on-lid
    }

elif [ -e /sys/devices/pci0000:00/0000:00:0f.0/olpc-xo1-sci-acpi/input/input2 ]
then
    LID_DEVICE=/sys/devices/pci0000:00/0000:00:0f.0/olpc-xo1-sci-acpi/input/input2
    wake_on_lid()
    {
        case "$1" in
        never)
            echo disabled > $LID_DEVICE/power/wakeup
            ;;
        *) # "open", "close", "always"
            echo enabled > $LID_DEVICE/power/wakeup
            echo -n $1 > $LID_DEVICE/lid_wake_mode
            ;;
        esac
    }

elif [ -e /proc/acpi/button/lid/LID ]  # XO-1.5 (wake-on-open is via acpi)
then

    if [ -e /sys/power/wake-on-close ]
    then
        LID_WAKE_ON_CLOSE=/sys/power/wake-on-close
    else
        LID_WAKE_ON_CLOSE=/sys/devices/LNXSYSTM:00/device:00/PNP0A03:00/XO15EC:00/lid_wake_on_close
    fi
    wake_on_lid()
    {
        # "never", "open", "close", "always"
        case "$1" in
        never)
            echo 0 > $LID_WAKE_ON_CLOSE
            set_acpi_wakeupevents LID disable
            ;;
        open)
            echo 0 > $LID_WAKE_ON_CLOSE
            set_acpi_wakeupevents LID enable
            ;;
        close)
            echo 1 > $LID_WAKE_ON_CLOSE
            set_acpi_wakeupevents LID disable
            ;;
        always)
            echo 1 > $LID_WAKE_ON_CLOSE
            set_acpi_wakeupevents LID enable
            ;;
        esac
    }
elif [ -d $SWITCH_DRIVER ] # XO-1.75, XO-4
then
    wake_on_lid()
    {
        case "$1" in
        never)  # ebook disabled too
            echo disabled > $SWITCH_WAKEUP
            ;;
        open)
            echo enabled > $SWITCH_WAKEUP
            echo 0 > $SWITCH_DRIVER/wake-on-close
            echo 0 > $SWITCH_DRIVER/wake-on-ebook
            ;;
        always|close)  # can't do just wake-on-close on 1.75
            echo enabled > $SWITCH_WAKEUP
            echo 1 > $SWITCH_DRIVER/wake-on-close
            echo 1 > $SWITCH_DRIVER/wake-on-ebook
            ;;
        esac

    }
else  # XO-3
    wake_on_lid()
    {
        :
    }
fi

if [ -e /proc/acpi/wakeup ]   # acpi events only on XO-1.5
then
    wake_on_ebook()
    {
        case "$1" in
        never)
            set_acpi_wakeupevents EBK disable
            ;;
        always)
            set_acpi_wakeupevents EBK enable
            ;;
        esac
    }
else
    # controlled by EC mask on XO-1
    # controlled along with lid on 1.75
    # immaterial on XO-3
    wake_on_ebook()
    {
        :
    }
fi

if [ -e /proc/acpi/button/lid/LID/state ]  # XO-1.5
then
    LID_STATE=/proc/acpi/button/lid/LID/state 
elif [ -e /sys/power/lid-state ]  # older XO-1
then
    LID_STATE=/sys/power/lid-state 
elif [ -x /usr/bin/evtest ]  # XO-1.75, and others, with later kernels
then
    LID_STATE=use_evtest
else
    LID_STATE=;
fi

if [ -e /proc/acpi/olpc-switch/ebook/EBK/state ]  # XO-1.5
then
    EBOOK_STATE=/proc/acpi/olpc-switch/ebook/EBK/state 
elif [ -e /sys/power/ebook-state ]  # older XO-1
then
    EBOOK_STATE=/sys/power/ebook-state 
elif [ -x /usr/bin/evtest ]  # XO-1.75, and others, with later kernels
then
    EBOOK_STATE=use_evtest
else
    EBOOK_STATE=;
fi

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

set -u

# default to off
: ${debug:=}
: ${tracing:=}

set_tracing()
{
    case $1 in
    on)
        tracing=1
        trace tracing begun
        ;;
    debug)
        set -x
        debug=1
        : @ debug begun, $powerd_version
        cat /proc/version >&2
        cat /boot/olpc_build >&2
        ;;
    off)
        if [ "$tracing" ]
        then
            trace tracing stopped
            tracing=;
        fi
        if [ "$debug" ]
        then
            : @ debug stopped
            set +x
            debug=;
        fi
        ;;
    *)
        log unknown tracing control "$1"
        ;;
    esac
}

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

trace()
{
    if [ "$debug" ]
    then
        : @ $(seconds) $@ >&2
    elif [ "$tracing" ]
    then
        echo : @ $(seconds) $@ >&2
    fi
}

# don't call out to 'date' if we don't need to
if printf '%(%s)T\n' -1 >/dev/null
then
    # we used to use $SECONDS, but it doesn't track clock
    # changes.  this matters because we sometimes compare the
    # output of seconds() against external timestamps.
    seconds()
    {
        printf '%(%s)T\n' -1
    }
else
    seconds()
    {
        date +%s
    }
fi

test "$debug" && set_tracing debug
test "$tracing" && set_tracing on

read_hwinfo()
{
    hwversion=;

    if [ -x /usr/bin/olpc-hwinfo ]
    then
        hwvendor=OLPC
        hwname=XO
        hwversion=$(olpc-hwinfo model)
        return
    fi

    if [ -r /proc/device-tree/banner-name ]
    then
        case "$(</proc/device-tree/banner-name)" in
        OLPC\ [BC][0-9])    hwversion=1 ;;
        OLPC\ D[0-9A-Z])       hwversion=1.5 ;;
        OLPC\ 1[ABC][0-9A-Z])  hwversion=1.75 ;;
        OLPC\ 2[ABC][0-9A-Z])  hwversion=3 ;;
        OLPC\ 3[ABC][0-9A-Z])  hwversion=1.75 ;; # CL4-A1
        OLPC\ 4[ABC][0-9A-Z])  hwversion=4 ;;
        esac
        if [ "$hwversion" ]
        then
            hwvendor=OLPC
            hwname=XO
            return;
        fi
    fi

    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-*)
        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
}
        
choose_xo_model

# driver_list is the subset of full_driver_list for which we
# can (or want to) actually control wakeups using .../power/wakeup
full_driver_list=;
driver_list=;

# legacy XO-1 and XO-1.5 -- won't exist on 3.0 and later kernels
WAKEUP_EVENTS=/sys/power/wakeup_events # directory of mask bits
WAKEUP_SOURCE=/sys/power/wakeup-source # most recent wakeup source.

setup_common_xo1_xo15_device_paths()
{
    # actual wakeup control occurs here, for both keyboard and
    # touchpad...
    KBD_MASTER_DRIVER=/sys/devices/platform/i8042
    KBD_DRIVER=$KBD_MASTER_DRIVER/serio0
    TPAD_DRIVER=$KBD_MASTER_DRIVER/serio1

    BATT_DRIVER=/sys/devices/0.battery/power_supply/olpc-ac
    AC_DRIVER=/sys/devices/0.battery/power_supply/olpc-battery
    RTC_DRIVER=/sys/class/rtc/rtc0/device

    TSCREEN_DRIVER=;    # no touchscreen on 1.5
    OLS_DRIVER=;        # no outdoor light sensor on 1.5

    driver_list="BATT AC KBD_MASTER KBD TPAD EBOOK"
    full_driver_list="PWRBTN RTC TSCREEN $driver_list"
}

# don't set nullglob above here!
case $XO in
1)
    setup_common_xo1_xo15_device_paths
    EBOOK_DRIVER=$(egrep -li 'ebook' \
        /sys/class/input/input*/name | sed 's;/name$;;')
    PWRBTN_DRIVER=/sys/class/input/input0

    ;;

1.5)
    setup_common_xo1_xo15_device_paths
    EBOOK_DRIVER=/sys/devices/LNXSYSTM:00/device:00/PNP0A03:00/XO15EBK:00
    PWRBTN_DRIVER=/sys/devices/LNXSYSTM:00/LNXPWRBN:00
    ;;

1.75|3)
    SWITCH_DRIVER=/sys/devices/platform/olpc-switch.0
    PWRBTN_DRIVER=/sys/devices/platform/olpc-ec-1.75.0
    RTC_DRIVER=/sys/devices/platform/mmp-rtc
    OLS_DRIVER=/sys/devices/platform/olpc-ols.0
    BATT_DRIVER=/sys/devices/platform/olpc-battery.0/power_supply/olpc-battery
    AC_DRIVER=/sys/devices/platform/olpc-battery.0/power_supply/olpc-ac
    KBD_DRIVER=/sys/devices/platform/olpc-kbd.0
    TPAD_DRIVER=;    # no separate tpad driver, shares with KBD_DRIVER

    TSCREEN_DRIVER=$(egrep -l 'raydium_ts|eeti_ts' \
            /sys/devices/platform/pxa2xx-i2c*/i2c-*/*/name | sed 's;/name$;;')

    EBOOK_DRIVER=;   # no separate ebook driver

    driver_list="OLS BATT AC KBD ${TSCREEN_DRIVER:+TSCREEN}"
    full_driver_list="PWRBTN RTC SWITCH EBOOK TPAD $driver_list"
    ;;
4)
    SWITCH_DRIVER=/sys/devices/0.switches
    PWRBTN_DRIVER=/sys/devices/d4037000.ec-spi/input/input1
    RTC_DRIVER=/sys/devices/d4010000.rtc
    OLS_DRIVER=/sys/devices/ols.3
    BATT_DRIVER=/sys/class/power_supply/olpc-battery
    AC_DRIVER=/sys/class/power_supply/olpc-ac
    KBD_DRIVER=/sys/devices/d4290000.ap-sp
    TPAD_DRIVER=;    # no separate tpad driver, shares with KBD_DRIVER

    TSCREEN_DRIVER=/sys/devices/d4033000.i2c

    EBOOK_DRIVER=;   # no separate ebook driver

    driver_list="OLS BATT AC KBD ${TSCREEN_DRIVER:+TSCREEN}"
    full_driver_list="PWRBTN RTC SWITCH EBOOK TPAD $driver_list"
    ;;

esac

# using contents of $full_driver_list, create appropriate values of
# FOO_WAKEUP and FOO_WAKE_COUNT
for d in $full_driver_list
do
    eval dir=\$${d}_DRIVER
    if [ -e $dir/power/wakeup ]
    then
        eval ${d}_WAKEUP=$dir/power/wakeup
        eval ${d}_WAKE_COUNT=$dir/power/wakeup_count
    else
        eval ${d}_WAKEUP=;
        eval ${d}_WAKE_COUNT=/dev/null
    fi
done

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

log powerd $powerd_version startup at $(date), on XO-$XO
log $(< /proc/version)
log olpc build: $(< /boot/olpc_build)

# make a file pattern match of "foo*" to return nothing if foo* doesn't exist
shopt -s nullglob

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

yes_or_true_to_boolean()
{
    if yes_or_true "$1"
    then
        echo true
    fi
    # else echo nothing
}

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

# pull the (first) wlan interface name from the output of iwconfig.
# if we find none, wlan may be rfkilled, in which case choose eth0.
WLANIFACE=$(iwconfig 2>/dev/null |
    sed -n -e 's/^\(eth\|wlan\)\([0-9]\+\).*/\1\2/p' | sed 1q)
: ${WLANIFACE:=eth0}


splash()
{
    if [ ! "$FRAMEBUFFER" ]
    then
        splashpid=;
        return
    fi

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

    $LIBEXECDIR/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
}

if [ $XO != 1.5 ]
then
    if [ $XO = 1 ]
    then
        camera_interrupts()
        {
            # print the current count of CAFE interrupts
            sed -n \
                '/ 11:.*cafe-ccic/s/ *11: *\([[:digit:]]\+\).*/\1/p' \
                    /proc/interrupts
        }
    elif grep -q mmp-camera /proc/interrupts   # XO-1.75/XO-3/XO-4
    then
        camera_interrupts()
        {
            # print the current count of mmp-camera interrupts
            sed -n \
                '/ 42:.*mmp-camera/s/ *42: *\([[:digit:]]\+\).*/\1/p' \
                    /proc/interrupts
        }
    else
        # in case of no camera driver
        camera_interrupts()
        {
            echo 1
        }
    fi

    check_camera_activity()
    {
        case $1 in
        start)
            camera_ints=$(camera_interrupts start)
            ;;

        finish)
            new_camera_ints=$(camera_interrupts end)

            # the ov7670 camera generates about 25 or 30 ints/sec. 
            # the siv120d does variable frame rates, depending on
            # light level -- i've seen as low as 10 ints/sec.  so we
            # set the threshold to 2/sec.
            if [ $(( (new_camera_ints - camera_ints) > BUSYCHECK * 2 )) = 1 ]
            then
                trace camera busy
                return 0
            fi
            ;;
        esac

        return 1
    }

else
    # the XO-1.5 driver only acquires the interrupt handler when
    # active, so we only need the "final" check -- no comparisons required.
    check_camera_activity()
    {
        test $1 = "finish" && \
            grep -q via-dma /proc/interrupts && \
            trace camera busy
    }
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 

}

check_network_activity()
{
    test "$monitor_network_activity" || return 1

    case $1 in
    start)
        # take initial network stats
        netactivity=$(netactivity_snapshot)
        ;;
    finish)
        # second network stats -- any network activity?
        newnetactivity=$(netactivity_snapshot)
        if [ "$newnetactivity" != "$netactivity" ]
        then
            trace network busy
            netactivity="$newnetactivity"
            return 0
        fi
        ;;
    esac

    return 1
}

check_cpu_activity()
{
    # monitor cpu stats -- search for /proc/stat in  "man 5 proc"
    case $1 in
    start)
        stat1=( $(< /proc/stat) )
        jifs1=$(( stat1[1] + stat1[2] + stat1[3] + stat1[4] ))
        idlejifs1=${stat1[4]}
        ;;

    finish)
        stat2=( $(< /proc/stat) )
        jifs2=$(( stat2[1] + stat2[2] + stat2[3] + stat2[4] ))
        idlejifs2=${stat2[4]}

        # what fraction of total elapsed jiffies were idle jiffies?
        idle=$(( (100 * ( idlejifs2 - idlejifs1 ) / ( jifs2 - jifs1 ) ) ))
        if [ $idle -lt $config_CPU_IDLE_LIMIT ]
        then
            trace cpu busy
            return 0
        fi
        ;;
    esac

    return 1
}

cpu_or_network_busy()
{
    check_cpu_activity start
    check_network_activity start
    check_camera_activity start

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

    check_cpu_activity finish && return 0
    check_network_activity finish && return 0
    check_camera_activity finish && return 0

    return 1
}

audio_busy()
{
    # defend against nullglob
    test -d /proc/asound/card0 || return 1

    egrep -q 'RUNNING|DRAINING' \
        /proc/asound/card0/pcm0?/sub0/status && trace audio busy
}

# see if there's been activity during the recent idle period --
# check all interesting files with one stat command.
# vty devices can be (and used to be) included here, i.e.,
#   touchfiles="/dev/tty1 /dev/tty2 $INHIBIT_BY_TOUCH"
# but sometimes the console screens can be chatty, and we don't
# want them to keep us awake.  (#10492)
touchfiles="$INHIBIT_BY_TOUCH"

filetimes_busy()
{
    timer=$1
    now=$(seconds)
    for touched in $(stat -c '%Y' $touchfiles 2>/dev/null )
    do
        # NB: a date change could screw this up.
        test $(( touched > now - timer )) = 1 && \
                trace touchfile busy && return 0
    done
    return 1
}

# return success if a live pid file is present to prevent
# the suspend or screen action indicated by $1
inhibit_files_present()
{
    action=$1

    # check the persistent inhibit flag first
    if [ -e $CONFIGFLAGS/inhibit-$action ]
    then
        trace found $CONFIGFLAGS/inhibit-$action
        return 0
    fi

    # check for pid files, return if any live ones found.
    # remove any stale, or non-pid files
    for f in $INHIBITDIR_PREFIX-$action/*
    do
        if kill -0 ${f##*/} 2>/dev/null
        then
            trace found $f
            return 0
        fi
        rm -f $f
    done

    return 1
}

# return success if removal of a stale pid file may have caused
# a change in our behavior.
reap_inhibitors()
{
    local f

    # check for and remove all dead pid files
    for d in $INHIBITDIR_PREFIX-*
    do
        for f in $d/*
        do
            kill -0 ${f##*/} 2>/dev/null || rm -f $f
        done
    done
}

create_inhibit_dir()
{
    # most inhibit-like flags go in /var/run, and aren't persistent
    for x in suspend dim
    do
        mkdir -p $INHIBITDIR_PREFIX-$x
        chmod a+rwt $INHIBITDIR_PREFIX-$x
    done
    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 suspend || filetimes_busy $t1
}

usb_inhibit()
{

    usbconf=$CONFIGFLAGS/usb-inhibits

    test -e $usbconf || return 1
    test -x $LIBEXECDIR/usblist || return 1

    # check all USB class, vendor, and vendor:id values against
    # the user-supplied strings.
    gotusb=$( $LIBEXECDIR/usblist | grep -ilxf $usbconf )

    # implicit return value
    test "$gotusb" && trace usb busy
}

ttyusb_inhibit()
{
    # inhibit suspend if a USB serial adapter is in use, because
    # it will likely change names after suspend/resume.
    # note:  assumes nullglob is set
    test "$(echo -n /var/lock/LCK..ttyUSB* )" && trace ttyUSB in use
}

general_inhibit()
{
    trace general inhibit: $no_suspend
    [ "$no_suspend" -gt 0 ]
}

laptop_busy()
{
    general_inhibit || usb_inhibit || ttyusb_inhibit ||
        audio_busy || cpu_or_network_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

if [ "$XO" = 1 ]
then
    # only on XO-1 (on XO-1.5/1.75/3, wlan power is controlled
    # automatically -- power is turned off if no wake-on-wlan
    # options are set.)
    set_xo1_wlan_power()
    {
        # Record previous state
        rfkill list 0 | grep -q "Soft blocked: yes"
        local w=$?

        if [ $w != $1 ]
        then
            if [ $1 = 1 ]
            then
                rfkill unblock 0
            else
                rfkill block 0
            fi
        fi

        echo $w
    }
fi

set_wake_on_wlan()
{
    case $1 in
    yes)
        # detect telepathy/salut, and wake on multicast if present.
        # "netstat -l --inet -n | grep -q ':5298 '"
        if grep -qi ": 00000000:14B2" /proc/net/tcp
        then
            ethtool -s $WLANIFACE wol um;
        else
            ethtool -s $WLANIFACE wol u;
        fi
        ;;
    no)
        ethtool -s $WLANIFACE 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 $WLANIFACE |
        sed -n \
            -e '/Mode:Ad-Hoc/d' \
            -e '/radio off\|Tx-Power=off\|[ut][n-][aA]ssociated/p'
    )

    test ! "$unassociated"   # set return value
}

runparts()
{
    local rc_action rc_dir rc_script

    rc_action=$1
    case $rc_action in
    resume) rc_dir=$RESUMEHOOKS ;;
    suspend) rc_dir=$SUSPENDHOOKS ;;
    *) return ;;
    esac

    test -d $rc_dir || return

    cd $rc_dir

    for rc_script in *
    do
        case $rc_script in
        *~) continue;;  # skip ~ backup files
        esac

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

    # switch back to our original directory
    cd - >/dev/null

}

if [ -e /proc/acpi/wakeup ]   # acpi events only on XO-1.5
then
    set_acpi_wakeupevents()
    {
        # 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
    }
else
    set_acpi_wakeupevents()
    {
        :
    }
fi


if [ -e ${WAKEUP_EVENTS:-/does/not/exist} ]
then  # OLPC legacy:  /sys/power/wakeup_events/*

    set_ec_wakeup_mask()
    {
        case $1 in
        enable)
            echo 1 > $WAKEUP_EVENTS/$2
            ;;
        disable)
            echo 0 > $WAKEUP_EVENTS/$2
            ;;
        esac
    }

    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).

        # we use only none, none_but_lid, all, allsci,  battery
        case $1 in
        all)        wake_on_lid always
                    wake_on_ebook always
                    set_ec_wakeup_mask enable 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.
                    set_ec_wakeup_mask enable all
                    ;;
        none)       wake_on_lid never
                    wake_on_ebook never
                    set_ec_wakeup_mask disable all
                    ;;
        none_but_lid)
                    wake_on_lid open
                    wake_on_ebook never
                    set_ec_wakeup_mask disable all
                    ;;
        ac)         set_ec_wakeup_mask enable ac_power
                    ;;
        battery)    set_ec_wakeup_mask enable battery_soc
                    set_ec_wakeup_mask enable battery_state
                    set_ec_wakeup_mask enable battery_error
                    # next one not always present in very old kernels
                    set_ec_wakeup_mask enable battery_critical 2>&1
                    ;;
        esac
    }

else # kernel 3.0 and later:  individual .../power/wakeup controls

    driver_list_wakeup()
    {
        which=$1 ; shift
        for d
        do
            eval wakeup=\$${d}_WAKEUP
            test "$wakeup" && echo $which > $wakeup
        done
    }

    set_wakeupevents()
    {
        case $1 in
        all)            wake_on_lid always
                        driver_list_wakeup enabled $driver_list
                        ;;
        none)           wake_on_lid never
                        driver_list_wakeup disabled $driver_list
                        ;;
        none_but_lid)   wake_on_lid open
                        driver_list_wakeup disabled $driver_list
                        ;;
        ac)             driver_list_wakeup enabled AC
                        ;;
        battery)        driver_list_wakeup enabled BATT
                        ;;
        esac
    }
fi

if [ -e ${WAKEUP_SOURCE:-/does/not/exist} ]
then
    # on XO-1/XO-1.5 kernels up through at least 2.6.35, wakeups
    # set a field in the kernel that tells the reason for the
    # wakeup.  while convenient, clearly if two wakeups happen at
    # nearly the same time, the information might not be quite
    # what we need to know, and we can be vulnerable to races if
    # we go to sleep again without noticing the more important
    # source.
    prepare_for_wakeupsource()
    {
        :
    }

    get_wakeupsource()   # XO-1 and XO-1.5 only
    {
        local x
        read x < $WAKEUP_SOURCE

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

    grab_global_wake_count()
    {
        :
    }

    check_global_wake_count()
    {
        :
    }

else
    # XO-1.75/XO-3/XO-4, running a 3.0 kernel, doesn't get told a
    # specific wakeup source, but does get counters on most of
    # the possible wakeup generators.  (some counters are missing
    # (e.g., battery wakeups, light sensor wakeups) because we
    # don't care about them currently, and the drivers aren't
    # instrumented properly.) those counters are used to find the
    # most important source, and to deal with that first.

    # note!  these counters return nothing at all if the corresponding
    # wakeup events aren't enabled.  so be prepared for nulls.

    prepare_for_wakeupsource()
    {
        lid_was_closed=$am_closed
        power_wake_count=$(< $PWRBTN_WAKE_COUNT)
        kbd_wake_count=$(< $KBD_WAKE_COUNT)
        tpad_wake_count=$(< $TPAD_WAKE_COUNT)
        batt_wake_count=$(< $BATT_WAKE_COUNT)
        ac_wake_count=$(< $AC_WAKE_COUNT)
        tscreen_wake_count=$(< $TSCREEN_WAKE_COUNT)
        ebook_wake_count=$(< $EBOOK_WAKE_COUNT)
        ols_wake_count=$(< $OLS_WAKE_COUNT)
        rtc_wake_count=$(< $RTC_WAKE_COUNT)
    }

    get_wakeupsource()
    {
        # order matters, at least somewhat.  the most
        # interesting/critical wakeup events should come first,
        # in case two are observed together.
        if [ "$power_wake_count" != "$(< $PWRBTN_WAKE_COUNT)" ]
        then
            wakeupsource=powerbutton
        elif [ "$lid_was_closed" ] && ! is_lid_closed && [ "$wake_on_open" ]
        then
            wakeupsource=lid
        elif [ "$kbd_wake_count" != "$(< $KBD_WAKE_COUNT)" ]
        then
            wakeupsource=keypress
        elif [ "$ebook_wake_count" != "$(< $EBOOK_WAKE_COUNT)" ]
        then
            wakeupsource=ebook
        elif [ "$tpad_wake_count" != "$(< $TPAD_WAKE_COUNT)" ]
        then
            wakeupsource=keypress
        elif [ "$tscreen_wake_count" != "$(< $TSCREEN_WAKE_COUNT)" ]
        then
            wakeupsource=keypress
        elif [ "$batt_wake_count" != "$(< $BATT_WAKE_COUNT)" ]
        then
            wakeupsource=battery
        elif [ "$ac_wake_count" != "$(< $AC_WAKE_COUNT)" ]
        then
            wakeupsource=acpower
        elif [ "$ols_wake_count" != "$(< $OLS_WAKE_COUNT)" ]
        then
            wakeupsource=ambientlight
        elif [ "$rtc_wake_count" != "$(< $RTC_WAKE_COUNT)" ]
        then
            wakeupsource=rtcalarm
        else
            wakeupsource=unknown
        fi
    }

    grab_global_wake_count()
    {
        global_wake_count=$(< /sys/power/wakeup_count)
    }

    check_global_wake_count()
    {
        echo $global_wake_count > /sys/power/wakeup_count
    }
fi

event_fifo_lookahead()
{
    local type amb_adj
    type=$1
    amb_adj=;

    # 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.

    # guard against date change into the past
    test "$eventcutoff" -gt $(seconds) && eventcutoff=0

    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.
        trace s_event-type is $s_event-$type

        # we don't honor lidopen events unless $wake_on_open is set.
        case $s_event in
         lidopen) test "$wake_on_open" && s_event=lidopenwake ;;
        esac

        case $s_event-$type in

         ambient-bright-*) amb_adj=bright ;;
         ambient-dark-*) amb_adj=dark ;;

         powerbutton-*|\
         fake_powerbutton-*|\
         lidopenwake-*|lidclose-soft|\
         inhibit-suspend-soft|\
         check_inhibits-soft|\
         ebook*-soft|ac-*-soft|useractive-soft|\
         reconfig-*|trace-*)
            if [ ! "$s_tstamp" ] || \
                [ "$s_tstamp" -ge "$eventcutoff" ]
            then
                test "$amb_adj" && ambient_adjust $amb_adj
                return 0
            fi
            ;;
        esac
    done <&6  # from the fifo

    test "$amb_adj" && ambient_adjust $amb_adj
    return 1

}

snooze()
{

    local shutdowntime dimtime blanktime orig_rtctime rtctime
    local sleep_type cur_sleep_type until 
    local prev_xo1_wlan_power sleep_started

    test "$prevent_sleep" && return

    trace "snoozing: $@"

    prev_xo1_wlan_power=;

    runparts suspend

    # we only use rtc alarms to either a) turn off 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

    if [ $orig_rtctime -gt $shutdowntime ]
    then
        trace converting $until to until_shutdown
        orig_rtctime=$shutdowntime
        until=until_shutdown
    fi

    rtctime=$orig_rtctime

    while : snooze loop
    do

        pwrlog_take_reading suspend

        trace until-sleep_type is $until-$sleep_type 

        case $until-$sleep_type in
        until_shutdown-hard)
            cur_sleep_type=hard

            # if we're waiting for a shutdown, then this is a pretty
            # sound sleep.  allow wakeups from a) the power button,
            # optionally b) opening the lid, and c) the battery, to
            # keep track of battery level.
            if [ "$wake_on_open" ]
            then
                set_wakeupevents none_but_lid
            else
                set_wakeupevents none
            fi

            # we want to wake up periodically to see if we still have
            # enough juice when discharging.  we used to only enable
            # this below a certain threshold, but that's too fallible.
            set_wakeupevents battery

            if [ "$XO" = 1 -a ! "$prev_xo1_wlan_power" ] && \
                 ! yes_or_true "$config_MESH_DURING_SUSPEND"
            then
                prev_xo1_wlan_power=$(set_xo1_wlan_power 0)
            else
                # the next test looks wrong, but it's not:  we only
                # need to disable wake-on-wlan if it might have
                # been enabled in the first place.
                if [ "$wake_on_wlan" ]
                then
                    set_wake_on_wlan no
                fi
            fi
            ;;
        *)
            # otherwise we want most anything to wake us.
            cur_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 already 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

        while :
        do
            # on 3.0 and later, the wake counter helps make sure we
            # deal with recent wakeup events (i.e., those that have arrived
            # but we might not have noticed) properly.  see: 
            # http://lwn.net/Articles/393314/ and/or
            # http://lkml.indiana.edu/hypermail/linux/kernel/1006.3/00777.html
            # on earlier kernels, this loop will only run once.
            grab_global_wake_count

            ambient_force

            # we did a sync, $BUSYCHECK seconds ago, so this should be quick
            killall -q -0 sync || sync
            if event_fifo_lookahead $cur_sleep_type
            then
                break 2
            fi

            if [ "$cur_sleep_type" = soft ]
            then
                inhibited_by_files && break 2

                # recheck network packets.
                check_network_activity finish && break 2

                # check for audio again
                audio_busy && break 2
            fi

            # the kernel will do a sync, but if it's going to stall
            # (SD writes can take a looong time) we want the stall to
            # be at user-level, not in the kernel, to avoid a
            # user-visible freeze.   we also want data to be flushed
            # early, so that devices like USB sticks that may still be
            # emptying buffers after the sync completes can do so before
            # their power is yanked.
            sync

            prepare_for_wakeupsource

            # always breaks out on 2.6.35 and earlier
            check_global_wake_count && break

            trace looping on global_wake_count $global_wake_count 
        done

        sleep_started=$(seconds)

        # this is it.  here's where we suspend or sleep.
        rtcwake -s$rtctime -mmem -d $rtcdevice

        # we need SCI events, because they do more than just
        # provide wakeup -- they drive input events as well.
        set_wakeupevents all_sci

        # prepare for the default case of simply re-sleeping for
        # the remaining time.  may be overridden.
        lastwakeup=$(seconds)
        rtctime=$(( rtctime - ( lastwakeup - sleep_started ) ))

        # this 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.
        probably_rtcalarm=;
        if [ $rtctime -le 1 ]
        then
            rtctime=2
            probably_rtcalarm=1
        fi

        # 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
        ambient_force

        get_wakeupsource

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

        pwrlog_take_reading resume-$wakeupsource

        # if we don't know what woke us, and it was very close
        # to the alarm time, treat it as such.  (useful esp. during
        # development, when rtc driver doesn't yet support wakeup
        # counters, and so shows up as "unknown".)
        if [ $wakeupsource = unknown -a "$probably_rtcalarm" ]
        then
            wakeupsource="rtcalarm"
        fi

        case $wakeupsource in
        "powerbutton")
            trace 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
            ;;

        "rtcalarm")
            trace rtcalarm during $until
            case $until in
            until_dim)
                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")
            trace 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")
            if [ "$wake_on_open" ]
            then
                am_ebook=;
                selfinject fake_useractive $lastwakeup "$wakeupsource"
                touchpad_recalibrate
                holdoff_start=$lastwakeup
                break
            fi
            ;;

        keypress)
            selfinject fake_useractive $lastwakeup "$wakeupsource"
            break
            ;;

        *)  # ebook|ambientlight|battery*|unknown|wlanpacket|...)
            #
            # in most case, all we do here is loop around and
            # sleep again -- we do battery and ambient light
            # checks on every loop anyway, so there's no special
            # handling for those.  in the wlan case (which may
            # appear as "unknown" on 1.75), if it's traffic we're
            # interested in staying awake for, we'll catch it
            # just before we sleep again.
            ;;

        esac

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

    done

    test "$prev_xo1_wlan_power" && \
        set_xo1_wlan_power $prev_xo1_wlan_power >/dev/null

    reset_idlecounters
    runparts resume &
}

if [ "$no_dcon" ]
then
    am_mono()
    {
        return 1
    }
else
    am_mono()
    {
        test "$(< $MONO_MODE)" = 1
    }
fi

set_brightness()
{
    if [ "$non_dcon_blanked" ]
    then
        non_dcon_save_bright=$1  # just remember the change
    else
        echo $1 >$BRIGHTNESS
        if [ $1 != 0 -a "$have_olpc_brightness" ] && am_mono
        then
            olpc-brightness maybe_color
        fi
    fi
}

read_brightness()
{
    if [ "$non_dcon_blanked" ]
    then
        eval $1=\"$non_dcon_save_bright\"
    else
        local x
        read x < $BRIGHTNESS
        eval $1=\"$x\"
    fi
}

brightness_ramp()
{
    local i

    trace ramp from $1 to $2
    test $1 = $2 && return

    if [ "$non_dcon_blanked" ]
    then
        non_dcon_save_bright=$2
        return
    fi

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

    if [ $2 != 0 -a "$have_olpc_brightness" ] && am_mono
    then
        olpc-brightness maybe_color
    fi

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

backlight()
{
    test "$no_brightness" && return

    trace backlight $1
    case $1 in
    init)
        read_brightness tmpbright
        brightness_ramp $tmpbright $config_INITIAL_BRIGHT_LEVEL
        savebright=$config_INITIAL_BRIGHT_LEVEL
        ambient_adjust_init
        ;;
    restore)
        if [ "$dimmed" ]
        then
            # could ramp here, but it wastes time while
            # the user is starting to work again.
            if [ ! "$ambient_savebright" ]
            then
                set_brightness $savebright
            else
                ambient_savebright=$savebright
            fi
            dimmed=;
        fi
        if [ "$no_dcon" ]
        then
            if [ "$non_dcon_blanked" ]
            then
                non_dcon_blanked=;
                set_brightness $non_dcon_save_bright
            # else do nothing
            fi
        else
            dcon wake
        fi
        ;;
    dim)
        if [ ! "${dimmed}" ]
        then
            read_brightness curbright
            savebright=${ambient_savebright:-$curbright}
            if [ $curbright -gt $config_IDLE_DIM_LEVEL ]
            then
                brightness_ramp $curbright $config_IDLE_DIM_LEVEL
            fi
            dimmed=true
        fi
        ;;
    off)
        # backlight is turned off while dcon sleeps, but
        # brightness level is retained, and can be changed. 
        # ambient dimming makes use of this.
        if [ "$no_dcon" ]
        then
            read_brightness non_dcon_save_bright
            set_brightness 0
            non_dcon_blanked=true;
        else
            dcon sleep
        fi
        ;;

    is_off)
        if [ "$no_dcon" ]
        then
            test "$non_dcon_blanked"
            return
        else
            local d
            read d < $DCON_SLEEP
            test $d = 1
            return
        fi
        ;;
    esac
}

if [ -d ${OLS_DRIVER:-/does/not/exist} ]
then

    # the light sensor input device acts as a virtual "switch",
    # which turns on when it's bright out, and off when it gets
    # "dark" (i.e., the backlight makes a difference).  the
    # low_lim and high_lim values set the hysteresis range.

    ambient_adjust_init()
    {
        echo $config_AMBIENT_BRIGHT >$OLS_DRIVER/low_lim
        echo $config_AMBIENT_NORMAL >$OLS_DRIVER/high_lim
    }

    ambient_adjust()
    {
        test "$no_brightness" && return

        read_brightness curbright
        case $1-$ambient in
        dark-dark|bright-bright)  # redundant event
            ;;
        dark-*)  # it's shady -- adjust the backlight up
            ambient=dark

            test "$ambient_savebright" || return

            # adjust to either full or dimmed, depending on where we started
            if [ "${dimmed}" -a $config_IDLE_DIM_LEVEL -lt $ambient_savebright ]
            then
                target=$config_IDLE_DIM_LEVEL
            else
                target=${ambient_savebright:-$savebright}
            fi

            # and in either case, don't dim -- could happen if the user
            # manually adjusted upward while we were auto-dimmed.
            if [ $curbright -lt $target ]
            then
                brightness_ramp $curbright $target
            fi
            ambient_savebright=;
            ;;
        bright-*)  #  it's sunny --adjust the backlight down
            ambient=bright
            ambient_savebright=$curbright
            brightness_ramp $curbright 0
            ;;
        esac
    }

# we're currently reusing one of the pre-defined switch types.  if
# this light sensor solution is permanent, we'll want to define a
# new type.  (affects the kernel driver and olpc-switchd, as well.)
SW_BRIGHT_SUN=SW_CAMERA_LENS_COVER
    ambient_force()
    {
        test "$ols_device" || return

        evtest --query $ols_device EV_SW $SW_BRIGHT_SUN
        case $? in
        10) level=bright ;;
        0)  level=dark   ;;
        *)  return       ;;
        esac
        ambient_adjust $level
    }
else
    OLS_WAKE_COUNT=/dev/null

    ambient_adjust_init()
    {
        :
    }
    ambient_adjust()
    {
        :
    }
    ambient_force()
    {
        :
    }
fi

dcon()
{
    test "$no_dcon" && return 1

    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
}

do_lid_closed()
{
    am_ebook=;
    am_closed=true;
    backlight off
    if [ "$sleep_on_close" ]
    then
        invalidate_powertimer
        # hard sleep until it's time to shut down
        snooze until_shutdown "$shutdowntimer" hard
    fi
}

if [ "$LID_STATE" = use_evtest ]
then
    is_lid_closed()
    {
        test "$lid_device" || return

        am_closed=;
        evtest --query $lid_device EV_SW SW_LID
        if [ $? != 10 ]
        then
            return 1
        fi
        am_closed=true;
        return 0
    }
elif [ "$LID_STATE" ]
then
    read_lidstate()
    {
        local x
        read x < $LID_STATE
        eval $1=\"$x\"
    }

    is_lid_closed()
    {
        read_lidstate lidstate
        case $lidstate in
        *closed)     # checking against "state:   closed"
            am_closed=true
            return 0
            ;;
        *) 
            am_closed=;
            return 1
            ;;
        esac
    }
else
    is_lid_closed()
    {
        am_closed=;
        return 1
    }
fi

lid_check()
{
    is_lid_closed && do_lid_closed
}

if [ "$EBOOK_STATE" = use_evtest ]
then
    ebook_check()
    {
        am_ebook=;
        test "$ebook_device" || return

        evtest --query $ebook_device EV_SW SW_TABLET_MODE
        case $? in
        10) am_ebook=true ;;
        esac
    }
elif [ "$EBOOK_STATE" ]
then
    read_ebookstate()
    {
        local x
        read x < $EBOOK_STATE
        eval $1=\"$x\"
    }

    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_INITIAL_BRIGHT_LEVEL=15

    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_WAKE_WHEN_LID_OPENED=no
    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_OWNER=olpc:olpc
    config_PWRLOG_LOGSIZE=50            # Kbytes
    config_PWRLOG_LOGDIRSIZE=1000       # Kbytes

    config_AMBIENT_BRIGHT=50    # low OLS sensor trigger level, for sunshine
    config_AMBIENT_NORMAL=80    # high trigger level, for shade or indoors
}

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

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

    if [ "$prevent_sleep" ]
    then
        log suppressing sleep configurations
        config_BATTERY_TIME_SLEEP=0
        config_EBOOK_TIME_SLEEP=0
        config_PLUGGED_TIME_SLEEP=0
        config_SLEEP_WHEN_LID_CLOSED=no
    fi

    # force w-o-l off, because later we skip turning it off if it
    # shouldn't be on according to current config. 
    set_wake_on_wlan no
    wake_on_wlan=$(yes_or_true_to_boolean "$config_WAKE_ON_WLAN")

    sleep_on_close=$(yes_or_true_to_boolean "$config_SLEEP_WHEN_LID_CLOSED")

    wake_on_open=$(yes_or_true_to_boolean "$config_WAKE_WHEN_LID_OPENED")

}

battery_shutdown()
{
    splash critical
    sleep 1
    do_shutdown "low battery"
}

low_battery()
{
    test "$no_battery" && return 0  # no driver, assume battery ok

    if [ $battery_capacity -le 40 ]
    then  # 40% or less, check battery voltage

        # if capacity is really very low, just shutdown
        test $battery_capacity -le 1 && return 0

        read uvolts < $MICROVOLTS
        # voltage less than critical (5.7V)
        test $uvolts -le 5700000 && return 0
    fi

    return 1
}

read_battery()
{
    read battery_capacity < $CAPACITY || battery_capacity=;
    if [ ! "$battery_capacity" ]
    then
        log failed to read battery capacity, assuming full charge
        battery_capacity=100
    fi
}

plugged_in()
{
    read on_ac < $AC_ONLINE || on_ac=;
    case $on_ac in
    "")
        log "failed to read external power status, assuming plugged in"
        ac="online"
        return 0
        ;;
    1)
        ac="online"
        return 0
        ;;
    0)
        ac="offline"
        return 1
        ;;
    esac
}

power_check()
{
    test "$no_ac" && return
    test "$no_battery" && return

    if ! plugged_in
    then
        read_battery
        low_battery && 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
    local BTS ETS PTS
    local BTB ETB PTB
    local BTD ETD PTD

    # see if there are inhibit files which cause us to override the
    # configured timers -- i.e., set them to infinity.
    #
    if inhibit_files_present suspend
    then
        BTS=0; ETS=0; PTS=0
    fi

    if inhibit_files_present dim
    then
        BTB=0; ETB=0; PTB=0
        BTD=0; ETD=0; PTD=0
    fi

    # 
    # then, choose the right set of timers -- online, ebook, or battery
    #
    if [ "$ac" = "online" ]
    then
        dt=${PTD:-$config_PLUGGED_TIME_DIM}
        st=${PTS:-$config_PLUGGED_TIME_SLEEP}
        bt=${PTB:-$config_PLUGGED_TIME_BLANK}
    else
        if [ "$am_ebook" ]
        then
            dt=${ETD:-$config_EBOOK_TIME_DIM}
            st=${ETS:-$config_EBOOK_TIME_SLEEP}
            bt=${ETB:-$config_EBOOK_TIME_BLANK}
        else
            dt=${BTD:-$config_BATTERY_TIME_DIM}
            st=${BTS:-$config_BATTERY_TIME_SLEEP}
            bt=${BTB:-$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 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()
{
    # unfreeze the dcon if nothing else unfroze it within a timeout
    # this is an "emergency fallback" to make sure that the dcon
    # unfreezes if the system crashes in unpredictable ways
    (
        sleep $config_UNFREEZE_SECONDS
        selfinject unfreeze_dcon $(seconds) from_timer
    ) &
}

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()
{
    reap_inhibitors
    backlight is_off || backlight dim
}

blank_action()
{
    reap_inhibitors
    backlight off
}

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

    reap_inhibitors

    # 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
        trace busy: dcon frozen
        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)
    # (it would be nice if this went away, someday.)
    : ${holdoff_start:=}
    if [ "$holdoff_start" -a $(($(seconds) - holdoff_start)) -lt 60 ]
    then
        trace busy: holdoff active
        reset_idlecounters
        return
    fi

    # if we've put up a confirmation splash screen, and the sleep
    # timer is very short, we may try and sleep while it's still
    # up.  don't do that.
    if [ "${splashpid:-}" ]
    then
        trace busy: splash active
        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()
{
    [ -n "$powerd_dbus_pid" ] && kill $powerd_dbus_pid
    [ -n "$inotify_pid" ] && kill $inotify_pid
    pwrlog_take_reading shutdown
    set_wakeupevents none
    unsplash
    dcon thaw
    dcon wake
    rm -f $EVENTFIFO
    rm -rf $INHIBITDIR_PREFIX-*
    exit
}

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

event_loop()
{
    local event tstamp arg2 arg3 more

    trace starting eventloop

    set_idletimes

    unsplash

    killall -USR1 olpc-switchd

    while : event loop
    do

        # event reporting for AC insertion/removal seems to be
        # unreliable, and this check is relatively cheap, so do
        # it regularly.
        power_check

        # likewise, if you crack open the lid then quickly shut
        # it, the event isn't reported properly.  so check frequently.
        lid_check

        # calculate/set new idle timeouts if necessary
        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

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

        # guard against date change into the past
        test "$eventcutoff" -gt $(seconds) && eventcutoff=0

        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
            ;;

        lidcheck)
            lid_device=$arg2
            lid_check
            ;;

        ebookcheck)
            ebook_device=$arg2
            ebook_check
            ;;

        ambientcheck)
            ols_device=$arg2
            ambient_force
            ;;

        lidclose)
            lid_device=$arg2
            do_lid_closed
            ;;

        lidopen|lidopenwake)
            lid_device=$arg2
            if [ "$wake_on_open" ]
            then
                touchpad_recalibrate
            fi
            gotactivity
            backlight restore
            am_ebook=;
            ;;

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

        ebookopen)
            ebook_device=$arg2
            am_ebook=;
            ;;

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

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

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

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

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

        ac-online|ac-offline)
            # no need -- assigned via power_check, above
            # ac=${event#ac-}
            ;;

        battery)
            # no need to do these here -- done for all events, above
            # battery_capacity=$arg2
            # power_check
            ;;

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

        check_inhibits)
            if [ "$arg2" = $CONFIGFILE ]
            then
                read_config
            fi
            reevaluate all
            ;;

        reconfig)
            read_config
            reevaluate all
            ;;

        freeze_dcon)
            reset_idlecounters
            dcon freeze
            ;;

        unfreeze_dcon)
            reset_idlecounters
            case "$arg2" in
            from_timer)
                test "$dcon_thawed" || dcon thaw
                ;;
            *)
                dcon thaw
                ;;
            esac
            dcon_thawed=true
            ;;

        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
            ;;

        inhibit-suspend)
            : $(( no_suspend++ ))
            ;;

        allow-suspend)
            : $(( no_suspend = (no_suspend > 0) ? no_suspend - 1 : 0 ))
            ;;

        ambient-adjust)
            ambient_adjust $arg2
            ;;

        ambient-bright)
            ols_device=$arg2
            ambient_adjust bright
            ;;

        ambient-dark)
            ols_device=$arg2
            ambient_adjust dark
            ;;

        #trace-on)
        #    set_tracing on
        #    ;;
        #trace-off)
        #    set_tracing off
        #    ;;
        trace-*)
            set_tracing ${event##trace-}
            ;;
        esac

    done <&6  # from the fifo
}

configure_pwrlog()
{
    if [ $config_PWRLOG_INTERVAL -gt 30 -a \
            -s $LIBEXECDIR/power-logger \
            -a -z "$no_battery" ]
    then
        pwrlog_inside_powerd=yes

        mkdir -p $config_PWRLOG_DIR
        chown $config_PWRLOG_OWNER $config_PWRLOG_DIR

        . $LIBEXECDIR/power-logger
        pwrlog_init $config_PWRLOG_INTERVAL $config_PWRLOG_DIR \
            $config_PWRLOG_OWNER \
            $config_PWRLOG_LOGSIZE $config_PWRLOG_LOGDIRSIZE

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

# powerd runs "set -u".  variables that aren't initialized
# naturally are set here.
am_ebook=;
am_closed=;
o_ebook=xxx;
ebook_device=;
lid_device=;
dimmed=;
savebright=;
non_dcon_blanked=;
non_dcon_save_bright=;
ambient_savebright=;
ambient=;
last_t_values=;
timerseqno=1;
powerseqno=0;
eventcutoff=0;
dcon_thawed=;
no_suspend=0;

prevent_sleep=;
if [ "$XO" = 4 ]  # FIXME
then
    prevent_sleep=true
fi


log starting

test "$no_ac" && log "no AC (external power) presence driver found"
test "$no_battery" && log "no battery driver found"
test "$no_dcon" && log "no DCON driver found"

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

create_inhibit_dir
reap_inhibitors

read_config

backlight init

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
test "$no_dcon" || chmod a+w $MONO_MODE
test "$no_brightness" || chmod a+w $BRIGHTNESS

set_acpi_wakeupevents USB1 disable
set_acpi_wakeupevents USB2 disable
set_acpi_wakeupevents USB3 disable
set_acpi_wakeupevents EHCI disable

set_acpi_wakeupevents EC enable
set_wakeupevents all_sci

create_event_fifo

powerd_dbus_pid=;
if [ -x $LIBEXECDIR/powerd-dbus ]
then
    $LIBEXECDIR/powerd-dbus &

    powerd_dbus_pid=$!
fi

inotify_pid=;
if [ -x /usr/bin/inotifywait ]
then
    inotifywait -m --timefmt '%s' --format 'check_inhibits %T %w%f %e' \
        -e CLOSE_WRITE -e DELETE -e MOVE \
        $INHIBITDIR_PREFIX-* $CONFIGFLAGS $CONFIGFILE > $EVENTFIFO &

    inotify_pid=$!
fi

is_lid_closed   # sets $am_closed

event_loop

#
# vile:noti:sw=4
#
