NetBSD/sys/kern/kern_pmf.c
dyoung 36fffd8d02 In pmf(9), improve the implementation of device self-suspension
and make suspension by self, by drvctl(8), and by ACPI system sleep
play nice together.  Start solidifying some temporary API changes.

1. Extract a new header file, <sys/device_if.h>, from <sys/device.h> and
   #include it from <sys/pmf.h> instead of <sys/device.h> to break the
   circular dependency between <sys/device.h> and <sys/pmf.h>.

2. Introduce pmf_qual_t, an aggregate of qualifications on a PMF
   suspend/resume call.  Start to replace instances of PMF_FN_PROTO,
   PMF_FN_ARGS, et cetera, with a pmf_qual_t.

3. Introduce the notion of a "suspensor," an entity that holds a
   device in suspension.  More than one suspensor may hold a device
   at once.  A device stays suspended as long as at least one
   suspensor holds it.  A device resumes when the last suspensor
   releases it.

   Currently, the kernel defines three suspensors,

   3a the system-suspensor: for system suspension, initiated
      by 'sysctl -w machdep.sleep_state=3', by lid closure, by
      power-button press, et cetera,

   3b the drvctl-suspensor: for device suspension by /dev/drvctl
      ioctl, e.g., drvctl -S sip0.

   3c the system self-suspensor: for device drivers that suspend
      themselves and their children.  Several drivers for network
      interfaces put the network device to sleep while it is not
      administratively up, that is, after the kernel calls if_stop(,
      1).  The self-suspensor should not be used directly.  See
      the description of suspensor delegates, below.

   A suspensor can have one or more "delegates".  A suspensor can
   release devices that its delegates hold suspended.  Right now,
   only the system self-suspensor has delegates.  For each device
   that a self-suspending driver attaches, it creates the device's
   self-suspensor, a delegate of the system self-suspensor.

   Suspensors stop a system-wide suspend/resume cycle from waking
   devices that the operator put to sleep with drvctl before the cycle.
   They also help self-suspension to work more simply, safely, and in
   accord with expectations.

4. Add the notion of device activation level, devact_level_t,
   and a routine for checking the current activation level,
   device_activation().  Current activation levels are DEVACT_LEVEL_BUS,
   DEVACT_LEVEL_DRIVER, and DEVACT_LEVEL_CLASS, which respectively
   indicate that the device's bus is active, that the bus and device are
   active, and that the bus, device, and the functions of the device's
   class (network, audio) are active.

   Suspend/resume calls can be qualified with a devact_level_t.
   The power-management framework treats a devact_level_t that
   qualifies a device suspension as the device's current activation
   level; it only runs hooks to reduce the activation level from
   the presumed current level to the fully suspended state.  The
   framework treats a devact_level_t qualifying device resumption
   as the target activation level; it only runs hooks to raise the
   activation level to the target.

5. Use pmf_qual_t, devact_level_t, and self-suspensors in several
   drivers.

6. Temporarily add an unused power-management workqueue that I will
   remove or replace, soon.
2009-09-16 16:34:49 +00:00

1106 lines
25 KiB
C

/* $NetBSD: kern_pmf.c,v 1.29 2009/09/16 16:34:50 dyoung Exp $ */
/*-
* Copyright (c) 2007 Jared D. McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_pmf.c,v 1.29 2009/09/16 16:34:50 dyoung Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/buf.h>
#include <sys/callout.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/pmf.h>
#include <sys/queue.h>
#include <sys/sched.h>
#include <sys/syscallargs.h> /* for sys_sync */
#include <sys/workqueue.h>
#include <prop/proplib.h>
#include <sys/condvar.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/reboot.h> /* for RB_NOSYNC */
#include <sys/sched.h>
/* XXX ugly special case, but for now the only client */
#include "wsdisplay.h"
#if NWSDISPLAY > 0
#include <dev/wscons/wsdisplayvar.h>
#endif
#ifndef PMF_DEBUG
#define PMF_DEBUG
#endif
#ifdef PMF_DEBUG
int pmf_debug_event;
int pmf_debug_suspend;
int pmf_debug_suspensor;
int pmf_debug_idle;
int pmf_debug_transition;
#define PMF_SUSPENSOR_PRINTF(x) if (pmf_debug_suspensor) printf x
#define PMF_SUSPEND_PRINTF(x) if (pmf_debug_suspend) printf x
#define PMF_EVENT_PRINTF(x) if (pmf_debug_event) printf x
#define PMF_IDLE_PRINTF(x) if (pmf_debug_idle) printf x
#define PMF_TRANSITION_PRINTF(x) if (pmf_debug_transition) printf x
#define PMF_TRANSITION_PRINTF2(y,x) if (pmf_debug_transition>y) printf x
#else
#define PMF_SUSPENSOR_PRINTF(x) do { } while (0)
#define PMF_SUSPEND_PRINTF(x) do { } while (0)
#define PMF_EVENT_PRINTF(x) do { } while (0)
#define PMF_IDLE_PRINTF(x) do { } while (0)
#define PMF_TRANSITION_PRINTF(x) do { } while (0)
#define PMF_TRANSITION_PRINTF2(y,x) do { } while (0)
#endif
/* #define PMF_DEBUG */
MALLOC_DEFINE(M_PMF, "pmf", "device pmf messaging memory");
static prop_dictionary_t pmf_platform = NULL;
static struct workqueue *pmf_event_workqueue;
static struct workqueue *pmf_suspend_workqueue;
typedef struct pmf_event_handler {
TAILQ_ENTRY(pmf_event_handler) pmf_link;
pmf_generic_event_t pmf_event;
void (*pmf_handler)(device_t);
device_t pmf_device;
bool pmf_global;
} pmf_event_handler_t;
static TAILQ_HEAD(, pmf_event_handler) pmf_all_events =
TAILQ_HEAD_INITIALIZER(pmf_all_events);
typedef struct pmf_event_workitem {
struct work pew_work;
pmf_generic_event_t pew_event;
device_t pew_device;
SIMPLEQ_ENTRY(pmf_event_workitem) pew_next_free;
} pmf_event_workitem_t;
typedef struct pmf_suspend_workitem {
struct work psw_work;
device_t psw_dev;
struct pmf_qual psw_qual;
} pmf_suspend_workitem_t;
static pool_cache_t pew_pc;
static pmf_event_workitem_t *pmf_event_workitem_get(void);
static void pmf_event_workitem_put(pmf_event_workitem_t *);
bool pmf_device_resume_locked(device_t PMF_FN_PROTO);
bool pmf_device_suspend_locked(device_t PMF_FN_PROTO);
static bool device_pmf_any_suspensor(device_t, devact_level_t);
static bool
complete_suspension(device_t dev, device_suspensor_t *susp, pmf_qual_t pqp)
{
int i;
struct pmf_qual pq;
device_suspensor_t ds;
ds = pmf_qual_suspension(pqp);
KASSERT(ds->ds_delegator != NULL);
pq = *pqp;
pq.pq_suspensor = ds->ds_delegator;
for (i = 0; i < DEVICE_SUSPENSORS_MAX; i++) {
if (susp[i] != ds)
continue;
if (!pmf_device_suspend(dev, &pq))
return false;
}
return true;
}
static void
pmf_suspend_worker(struct work *wk, void *dummy)
{
pmf_suspend_workitem_t *psw;
deviter_t di;
device_t dev;
psw = (void *)wk;
KASSERT(wk == &psw->psw_work);
KASSERT(psw != NULL);
for (dev = deviter_first(&di, 0); dev != NULL;
dev = deviter_next(&di)) {
if (dev == psw->psw_dev && device_pmf_lock(dev))
break;
}
deviter_release(&di);
if (dev == NULL)
return;
switch (pmf_qual_depth(&psw->psw_qual)) {
case DEVACT_LEVEL_FULL:
if (!complete_suspension(dev, dev->dv_class_suspensors,
&psw->psw_qual))
break;
/*FALLTHROUGH*/
case DEVACT_LEVEL_DRIVER:
if (!complete_suspension(dev, dev->dv_driver_suspensors,
&psw->psw_qual))
break;
/*FALLTHROUGH*/
case DEVACT_LEVEL_BUS:
if (!complete_suspension(dev, dev->dv_bus_suspensors,
&psw->psw_qual))
break;
}
device_pmf_unlock(dev);
kmem_free(psw, sizeof(*psw));
}
static void
pmf_event_worker(struct work *wk, void *dummy)
{
pmf_event_workitem_t *pew;
pmf_event_handler_t *event;
pew = (void *)wk;
KASSERT(wk == &pew->pew_work);
KASSERT(pew != NULL);
TAILQ_FOREACH(event, &pmf_all_events, pmf_link) {
if (event->pmf_event != pew->pew_event)
continue;
if (event->pmf_device == pew->pew_device || event->pmf_global)
(*event->pmf_handler)(event->pmf_device);
}
pmf_event_workitem_put(pew);
}
static bool
pmf_check_system_drivers(void)
{
device_t curdev;
bool unsupported_devs;
deviter_t di;
unsupported_devs = false;
for (curdev = deviter_first(&di, 0); curdev != NULL;
curdev = deviter_next(&di)) {
if (device_pmf_is_registered(curdev))
continue;
if (!unsupported_devs)
printf("Devices without power management support:");
printf(" %s", device_xname(curdev));
unsupported_devs = true;
}
deviter_release(&di);
if (unsupported_devs) {
printf("\n");
return false;
}
return true;
}
bool
pmf_system_bus_resume(PMF_FN_ARGS1)
{
bool rv;
device_t curdev;
deviter_t di;
aprint_debug("Powering devices:");
/* D0 handlers are run in order */
rv = true;
for (curdev = deviter_first(&di, DEVITER_F_ROOT_FIRST); curdev != NULL;
curdev = deviter_next(&di)) {
if (!device_pmf_is_registered(curdev))
continue;
if (device_is_active(curdev) ||
!device_is_enabled(curdev))
continue;
aprint_debug(" %s", device_xname(curdev));
if (!device_pmf_bus_resume(curdev PMF_FN_CALL)) {
rv = false;
aprint_debug("(failed)");
}
}
deviter_release(&di);
aprint_debug("\n");
return rv;
}
bool
pmf_system_resume(PMF_FN_ARGS1)
{
bool rv;
device_t curdev, parent;
deviter_t di;
if (!pmf_check_system_drivers())
return false;
aprint_debug("Resuming devices:");
/* D0 handlers are run in order */
rv = true;
for (curdev = deviter_first(&di, DEVITER_F_ROOT_FIRST); curdev != NULL;
curdev = deviter_next(&di)) {
if (device_is_active(curdev) ||
!device_is_enabled(curdev))
continue;
parent = device_parent(curdev);
if (parent != NULL &&
!device_is_active(parent))
continue;
aprint_debug(" %s", device_xname(curdev));
if (!pmf_device_resume(curdev PMF_FN_CALL)) {
rv = false;
aprint_debug("(failed)");
}
}
deviter_release(&di);
aprint_debug(".\n");
KERNEL_UNLOCK_ONE(0);
#if NWSDISPLAY > 0
if (rv)
wsdisplay_handlex(1);
#endif
return rv;
}
bool
pmf_system_suspend(PMF_FN_ARGS1)
{
device_t curdev;
deviter_t di;
if (!pmf_check_system_drivers())
return false;
#if NWSDISPLAY > 0
if (wsdisplay_handlex(0))
return false;
#endif
KERNEL_LOCK(1, NULL);
/*
* Flush buffers only if the shutdown didn't do so
* already and if there was no panic.
*/
if (doing_shutdown == 0 && panicstr == NULL) {
printf("Flushing disk caches: ");
sys_sync(NULL, NULL, NULL);
if (buf_syncwait() != 0)
printf("giving up\n");
else
printf("done\n");
}
aprint_debug("Suspending devices:");
for (curdev = deviter_first(&di, DEVITER_F_LEAVES_FIRST);
curdev != NULL;
curdev = deviter_next(&di)) {
if (!device_is_active(curdev))
continue;
aprint_debug(" %s", device_xname(curdev));
/* XXX joerg check return value and abort suspend */
if (!pmf_device_suspend(curdev PMF_FN_CALL))
aprint_debug("(failed)");
}
deviter_release(&di);
aprint_debug(".\n");
return true;
}
static bool
shutdown_all(int how)
{
static struct shutdown_state s;
device_t curdev;
bool progress = false;
for (curdev = shutdown_first(&s); curdev != NULL;
curdev = shutdown_next(&s)) {
aprint_debug(" shutting down %s, ", device_xname(curdev));
if (!device_pmf_is_registered(curdev))
aprint_debug("skipped.");
#if 0 /* needed? */
else if (!device_pmf_class_shutdown(curdev, how))
aprint_debug("failed.");
#endif
else if (!device_pmf_driver_shutdown(curdev, how))
aprint_debug("failed.");
else if (!device_pmf_bus_shutdown(curdev, how))
aprint_debug("failed.");
else {
progress = true;
aprint_debug("success.");
}
}
return progress;
}
void
pmf_system_shutdown(int how)
{
aprint_debug("Shutting down devices:");
shutdown_all(how);
}
bool
pmf_set_platform(const char *key, const char *value)
{
if (pmf_platform == NULL)
pmf_platform = prop_dictionary_create();
if (pmf_platform == NULL)
return false;
return prop_dictionary_set_cstring(pmf_platform, key, value);
}
const char *
pmf_get_platform(const char *key)
{
const char *value;
if (pmf_platform == NULL)
return NULL;
if (!prop_dictionary_get_cstring_nocopy(pmf_platform, key, &value))
return NULL;
return value;
}
bool
pmf_device_register1(device_t dev,
bool (*suspend)(device_t PMF_FN_PROTO),
bool (*resume)(device_t PMF_FN_PROTO),
bool (*shutdown)(device_t, int))
{
if (!device_pmf_driver_register(dev, suspend, resume, shutdown))
return false;
if (!device_pmf_driver_child_register(dev)) {
device_pmf_driver_deregister(dev);
return false;
}
return true;
}
void
pmf_device_deregister(device_t dev)
{
device_pmf_class_deregister(dev);
device_pmf_bus_deregister(dev);
device_pmf_driver_deregister(dev);
}
static const struct device_suspensor _device_suspensor_drvctl = {
.ds_delegator = NULL
, .ds_name = "drvctl"
};
static const struct device_suspensor _device_suspensor_self = {
.ds_delegator = NULL
, .ds_name = "self"
};
#if 0
static const struct device_suspensor _device_suspensor_self_delegate = {
.ds_delegator = &_device_suspensor_self
, .ds_name = "self delegate"
};
#endif
static const struct device_suspensor _device_suspensor_system = {
.ds_delegator = NULL
, .ds_name = "system"
};
const struct device_suspensor
* const device_suspensor_self = &_device_suspensor_self,
#if 0
* const device_suspensor_self_delegate = &_device_suspensor_self_delegate,
#endif
* const device_suspensor_system = &_device_suspensor_system,
* const device_suspensor_drvctl = &_device_suspensor_drvctl;
static const struct pmf_qual _pmf_qual_system = {
.pq_actlvl = DEVACT_LEVEL_FULL
, .pq_suspensor = &_device_suspensor_system
};
static const struct pmf_qual _pmf_qual_drvctl = {
.pq_actlvl = DEVACT_LEVEL_FULL
, .pq_suspensor = &_device_suspensor_drvctl
};
static const struct pmf_qual _pmf_qual_self = {
.pq_actlvl = DEVACT_LEVEL_DRIVER
, .pq_suspensor = &_device_suspensor_self
};
const struct pmf_qual
* const PMF_Q_DRVCTL = &_pmf_qual_drvctl,
* const PMF_Q_NONE = &_pmf_qual_system,
* const PMF_Q_SELF = &_pmf_qual_self;
static bool
device_suspensor_delegates_to(device_suspensor_t ds,
device_suspensor_t delegate)
{
device_suspensor_t iter;
for (iter = delegate->ds_delegator; iter != NULL;
iter = iter->ds_delegator) {
if (ds == iter)
return true;
}
return false;
}
static bool
add_suspensor(device_t dev, const char *kind, device_suspensor_t *susp,
device_suspensor_t ds)
{
int i;
for (i = 0; i < DEVICE_SUSPENSORS_MAX; i++) {
if (susp[i] == NULL)
continue;
if (ds == susp[i]) {
PMF_SUSPENSOR_PRINTF((
"%s: %s-suspended by %s (delegator %s) already\n",
device_xname(dev), kind,
susp[i]->ds_name,
(susp[i]->ds_delegator != NULL) ?
susp[i]->ds_delegator->ds_name : "<none>"));
return true;
}
if (device_suspensor_delegates_to(ds, susp[i])) {
PMF_SUSPENSOR_PRINTF((
"%s: %s assumes %s-suspension by %s "
"(delegator %s)\n",
device_xname(dev), ds->ds_name, kind,
susp[i]->ds_name,
(susp[i]->ds_delegator != NULL) ?
susp[i]->ds_delegator->ds_name : "<none>"));
susp[i] = ds;
return true;
}
}
for (i = 0; i < DEVICE_SUSPENSORS_MAX; i++) {
if (susp[i] == NULL) {
susp[i] = ds;
PMF_SUSPENSOR_PRINTF((
"%s: newly %s-suspended by %s (delegator %s)\n",
device_xname(dev), kind,
susp[i]->ds_name,
(susp[i]->ds_delegator != NULL) ?
susp[i]->ds_delegator->ds_name : "<none>"));
return true;
}
}
return false;
}
static bool
device_pmf_add_suspensor(device_t dev, pmf_qual_t pq)
{
device_suspensor_t ds;
KASSERT(pq != NULL);
ds = pmf_qual_suspension(pq);
KASSERT(ds != NULL);
if (!add_suspensor(dev, "class", dev->dv_class_suspensors, ds))
return false;
if (!add_suspensor(dev, "driver", dev->dv_driver_suspensors, ds))
return false;
if (!add_suspensor(dev, "bus", dev->dv_bus_suspensors, ds))
return false;
return true;
}
#if 0
static bool
device_pmf_has_suspension(device_t dev, device_suspensor_t ds)
{
int i;
for (i = 0; i < DEVICE_SUSPENSORS_MAX; i++) {
if (dev->dv_suspensions[i] == ds)
return true;
if (device_suspensor_delegates_to(dev->dv_suspensions[i], ds))
return true;
}
return false;
}
#endif
static bool
any_suspensor(device_t dev, const char *kind, device_suspensor_t *susp)
{
int i;
bool suspended = false;
for (i = 0; i < DEVICE_SUSPENSORS_MAX; i++) {
if (susp[i] != NULL) {
PMF_SUSPENSOR_PRINTF(("%s: %s is suspended by %s "
"(delegator %s)\n",
device_xname(dev), kind,
susp[i]->ds_name,
(susp[i]->ds_delegator != NULL) ?
susp[i]->ds_delegator->ds_name : "<none>"));
suspended = true;
}
}
return suspended;
}
static bool
device_pmf_any_suspensor(device_t dev, devact_level_t depth)
{
switch (depth) {
case DEVACT_LEVEL_FULL:
if (any_suspensor(dev, "class", dev->dv_class_suspensors))
return true;
/*FALLTHROUGH*/
case DEVACT_LEVEL_DRIVER:
if (any_suspensor(dev, "driver", dev->dv_driver_suspensors))
return true;
/*FALLTHROUGH*/
case DEVACT_LEVEL_BUS:
if (any_suspensor(dev, "bus", dev->dv_bus_suspensors))
return true;
}
return false;
}
static bool
remove_suspensor(device_t dev, const char *kind, device_suspensor_t *susp,
device_suspensor_t ds)
{
int i;
for (i = 0; i < DEVICE_SUSPENSORS_MAX; i++) {
if (susp[i] == NULL)
continue;
if (ds == susp[i] ||
device_suspensor_delegates_to(ds, susp[i])) {
PMF_SUSPENSOR_PRINTF(("%s: %s suspension %s "
"(delegator %s) removed by %s\n",
device_xname(dev), kind,
susp[i]->ds_name,
(susp[i]->ds_delegator != NULL)
? susp[i]->ds_delegator->ds_name
: "<none>",
ds->ds_name));
susp[i] = NULL;
return true;
}
}
return false;
}
static bool
device_pmf_remove_suspensor(device_t dev, pmf_qual_t pq)
{
device_suspensor_t ds;
KASSERT(pq != NULL);
ds = pmf_qual_suspension(pq);
KASSERT(ds != NULL);
if (!remove_suspensor(dev, "class", dev->dv_class_suspensors, ds))
return false;
if (!remove_suspensor(dev, "driver", dev->dv_driver_suspensors, ds))
return false;
if (!remove_suspensor(dev, "bus", dev->dv_bus_suspensors, ds))
return false;
return true;
}
void
pmf_self_suspensor_init(device_t dev, struct device_suspensor *ds,
struct pmf_qual *pq)
{
ds->ds_delegator = device_suspensor_self;
snprintf(ds->ds_name, sizeof(ds->ds_name), "%s-self",
device_xname(dev));
pq->pq_actlvl = DEVACT_LEVEL_DRIVER;
pq->pq_suspensor = ds;
}
bool
pmf_device_suspend(device_t dev PMF_FN_ARGS)
{
bool rc;
PMF_TRANSITION_PRINTF(("%s: suspend enter\n", device_xname(dev)));
if (!device_pmf_is_registered(dev))
return false;
if (!device_pmf_lock(dev))
return false;
rc = pmf_device_suspend_locked(dev PMF_FN_CALL);
device_pmf_unlock(dev);
PMF_TRANSITION_PRINTF(("%s: suspend exit\n", device_xname(dev)));
return rc;
}
bool
pmf_device_suspend_locked(device_t dev PMF_FN_ARGS)
{
if (!device_pmf_add_suspensor(dev PMF_FN_CALL))
return false;
PMF_TRANSITION_PRINTF2(1, ("%s: class suspend\n", device_xname(dev)));
if (!device_pmf_class_suspend(dev PMF_FN_CALL))
return false;
PMF_TRANSITION_PRINTF2(1, ("%s: driver suspend\n", device_xname(dev)));
if (!device_pmf_driver_suspend(dev PMF_FN_CALL))
return false;
PMF_TRANSITION_PRINTF2(1, ("%s: bus suspend\n", device_xname(dev)));
if (!device_pmf_bus_suspend(dev PMF_FN_CALL))
return false;
return true;
}
bool
pmf_device_resume(device_t dev PMF_FN_ARGS)
{
bool rc;
PMF_TRANSITION_PRINTF(("%s: resume enter\n", device_xname(dev)));
if (!device_pmf_is_registered(dev))
return false;
if (!device_pmf_lock(dev))
return false;
rc = pmf_device_resume_locked(dev PMF_FN_CALL);
device_pmf_unlock(dev);
PMF_TRANSITION_PRINTF(("%s: resume exit\n", device_xname(dev)));
return rc;
}
bool
pmf_device_resume_locked(device_t dev PMF_FN_ARGS)
{
device_pmf_remove_suspensor(dev PMF_FN_CALL);
if (device_pmf_any_suspensor(dev, DEVACT_LEVEL_FULL))
return true;
PMF_TRANSITION_PRINTF2(1, ("%s: bus resume\n", device_xname(dev)));
if (!device_pmf_bus_resume(dev PMF_FN_CALL))
return false;
PMF_TRANSITION_PRINTF2(1, ("%s: driver resume\n", device_xname(dev)));
if (!device_pmf_driver_resume(dev PMF_FN_CALL))
return false;
PMF_TRANSITION_PRINTF2(1, ("%s: class resume\n", device_xname(dev)));
if (!device_pmf_class_resume(dev PMF_FN_CALL))
return false;
return true;
}
bool
pmf_device_recursive_suspend(device_t dv PMF_FN_ARGS)
{
bool rv = true;
device_t curdev;
deviter_t di;
struct pmf_qual pq;
pmf_qual_recursive_copy(&pq, qual);
for (curdev = deviter_first(&di, 0); curdev != NULL;
curdev = deviter_next(&di)) {
if (device_parent(curdev) != dv)
continue;
if (!pmf_device_recursive_suspend(curdev, &pq)) {
rv = false;
break;
}
}
deviter_release(&di);
return rv && pmf_device_suspend(dv PMF_FN_CALL);
}
void
pmf_qual_recursive_copy(struct pmf_qual *dst, pmf_qual_t src)
{
*dst = *src;
dst->pq_actlvl = DEVACT_LEVEL_FULL;
}
bool
pmf_device_recursive_resume(device_t dv PMF_FN_ARGS)
{
device_t parent;
struct pmf_qual pq;
if (device_is_active(dv))
return true;
pmf_qual_recursive_copy(&pq, qual);
parent = device_parent(dv);
if (parent != NULL) {
if (!pmf_device_recursive_resume(parent, &pq))
return false;
}
return pmf_device_resume(dv PMF_FN_CALL);
}
bool
pmf_device_descendants_release(device_t dv PMF_FN_ARGS)
{
bool rv = true;
device_t curdev;
deviter_t di;
for (curdev = deviter_first(&di, 0); curdev != NULL;
curdev = deviter_next(&di)) {
if (device_parent(curdev) != dv)
continue;
device_pmf_remove_suspensor(curdev PMF_FN_CALL);
if (!pmf_device_descendants_release(curdev PMF_FN_CALL)) {
rv = false;
break;
}
}
deviter_release(&di);
return rv;
}
bool
pmf_device_descendants_resume(device_t dv PMF_FN_ARGS)
{
bool rv = true;
device_t curdev;
deviter_t di;
KASSERT(pmf_qual_descend_ok(PMF_FN_CALL1));
for (curdev = deviter_first(&di, 0); curdev != NULL;
curdev = deviter_next(&di)) {
if (device_parent(curdev) != dv)
continue;
if (!pmf_device_resume(curdev PMF_FN_CALL) ||
!pmf_device_descendants_resume(curdev PMF_FN_CALL)) {
rv = false;
break;
}
}
deviter_release(&di);
return rv;
}
bool
pmf_device_subtree_release(device_t dv PMF_FN_ARGS)
{
struct pmf_qual pq;
device_pmf_remove_suspensor(dv PMF_FN_CALL);
return pmf_device_descendants_release(dv, &pq);
}
bool
pmf_device_subtree_resume(device_t dv PMF_FN_ARGS)
{
struct pmf_qual pq;
if (!pmf_device_subtree_release(dv PMF_FN_CALL))
return false;
if (!pmf_device_recursive_resume(dv PMF_FN_CALL))
return false;
pmf_qual_recursive_copy(&pq, qual);
return pmf_device_descendants_resume(dv, &pq);
}
#include <net/if.h>
static bool
pmf_class_network_suspend(device_t dev PMF_FN_ARGS)
{
struct ifnet *ifp = device_pmf_class_private(dev);
int s;
s = splnet();
(*ifp->if_stop)(ifp, 0);
splx(s);
return true;
}
static bool
pmf_class_network_resume(device_t dev PMF_FN_ARGS)
{
struct ifnet *ifp = device_pmf_class_private(dev);
int s;
s = splnet();
if (ifp->if_flags & IFF_UP) {
ifp->if_flags &= ~IFF_RUNNING;
if ((*ifp->if_init)(ifp) != 0)
aprint_normal_ifnet(ifp, "resume failed\n");
(*ifp->if_start)(ifp);
}
splx(s);
return true;
}
void
pmf_class_network_register(device_t dev, struct ifnet *ifp)
{
device_pmf_class_register(dev, ifp, pmf_class_network_suspend,
pmf_class_network_resume, NULL);
}
bool
pmf_event_inject(device_t dv, pmf_generic_event_t ev)
{
pmf_event_workitem_t *pew;
pew = pmf_event_workitem_get();
if (pew == NULL) {
PMF_EVENT_PRINTF(("%s: PMF event %d dropped (no memory)\n",
dv ? device_xname(dv) : "<anonymous>", ev));
return false;
}
pew->pew_event = ev;
pew->pew_device = dv;
workqueue_enqueue(pmf_event_workqueue, &pew->pew_work, NULL);
PMF_EVENT_PRINTF(("%s: PMF event %d injected\n",
dv ? device_xname(dv) : "<anonymous>", ev));
return true;
}
bool
pmf_event_register(device_t dv, pmf_generic_event_t ev,
void (*handler)(device_t), bool global)
{
pmf_event_handler_t *event;
event = kmem_alloc(sizeof(*event), KM_SLEEP);
event->pmf_event = ev;
event->pmf_handler = handler;
event->pmf_device = dv;
event->pmf_global = global;
TAILQ_INSERT_TAIL(&pmf_all_events, event, pmf_link);
return true;
}
void
pmf_event_deregister(device_t dv, pmf_generic_event_t ev,
void (*handler)(device_t), bool global)
{
pmf_event_handler_t *event;
TAILQ_FOREACH(event, &pmf_all_events, pmf_link) {
if (event->pmf_event != ev)
continue;
if (event->pmf_device != dv)
continue;
if (event->pmf_global != global)
continue;
if (event->pmf_handler != handler)
continue;
TAILQ_REMOVE(&pmf_all_events, event, pmf_link);
kmem_free(event, sizeof(*event));
return;
}
}
struct display_class_softc {
TAILQ_ENTRY(display_class_softc) dc_link;
device_t dc_dev;
};
static TAILQ_HEAD(, display_class_softc) all_displays;
static callout_t global_idle_counter;
static int idle_timeout = 30;
static void
input_idle(void *dummy)
{
PMF_IDLE_PRINTF(("Input idle handler called\n"));
pmf_event_inject(NULL, PMFE_DISPLAY_OFF);
}
static void
input_activity_handler(device_t dv, devactive_t type)
{
if (!TAILQ_EMPTY(&all_displays))
callout_schedule(&global_idle_counter, idle_timeout * hz);
}
static void
pmf_class_input_deregister(device_t dv)
{
device_active_deregister(dv, input_activity_handler);
}
bool
pmf_class_input_register(device_t dv)
{
if (!device_active_register(dv, input_activity_handler))
return false;
device_pmf_class_register(dv, NULL, NULL, NULL,
pmf_class_input_deregister);
return true;
}
static void
pmf_class_display_deregister(device_t dv)
{
struct display_class_softc *sc = device_pmf_class_private(dv);
int s;
s = splsoftclock();
TAILQ_REMOVE(&all_displays, sc, dc_link);
if (TAILQ_EMPTY(&all_displays))
callout_stop(&global_idle_counter);
splx(s);
kmem_free(sc, sizeof(*sc));
}
bool
pmf_class_display_register(device_t dv)
{
struct display_class_softc *sc;
int s;
sc = kmem_alloc(sizeof(*sc), KM_SLEEP);
s = splsoftclock();
if (TAILQ_EMPTY(&all_displays))
callout_schedule(&global_idle_counter, idle_timeout * hz);
TAILQ_INSERT_HEAD(&all_displays, sc, dc_link);
splx(s);
device_pmf_class_register(dv, sc, NULL, NULL,
pmf_class_display_deregister);
return true;
}
static void
pmf_event_workitem_put(pmf_event_workitem_t *pew)
{
KASSERT(pew != NULL);
pool_cache_put(pew_pc, pew);
}
static pmf_event_workitem_t *
pmf_event_workitem_get(void)
{
return pool_cache_get(pew_pc, PR_NOWAIT);
}
static int
pew_constructor(void *arg, void *obj, int flags)
{
memset(obj, 0, sizeof(pmf_event_workitem_t));
return 0;
}
void
pmf_init(void)
{
int err;
pew_pc = pool_cache_init(sizeof(pmf_event_workitem_t), 0, 0, 0,
"pew pool", NULL, IPL_HIGH, pew_constructor, NULL, NULL);
pool_cache_setlowat(pew_pc, 16);
pool_cache_sethiwat(pew_pc, 256);
KASSERT(pmf_event_workqueue == NULL);
err = workqueue_create(&pmf_event_workqueue, "pmfevent",
pmf_event_worker, NULL, PRI_NONE, IPL_VM, 0);
if (err)
panic("couldn't create pmfevent workqueue");
KASSERT(pmf_suspend_workqueue == NULL);
err = workqueue_create(&pmf_suspend_workqueue, "pmfsuspend",
pmf_suspend_worker, NULL, PRI_NONE, IPL_VM, 0);
if (err)
panic("couldn't create pmfsuspend workqueue");
callout_init(&global_idle_counter, 0);
callout_setfunc(&global_idle_counter, input_idle, NULL);
}