36fffd8d02
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.
1106 lines
25 KiB
C
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);
|
|
}
|