2022-03-28 15:33:20 +03:00
|
|
|
/* $NetBSD: kern_drvctl.c,v 1.51 2022/03/28 12:33:22 riastradh Exp $ */
|
2004-08-18 16:19:29 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2004
|
|
|
|
* Matthias Drochner. 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 AUTHOR 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 AUTHOR 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>
|
2022-03-28 15:33:20 +03:00
|
|
|
__KERNEL_RCSID(0, "$NetBSD: kern_drvctl.c,v 1.51 2022/03/28 12:33:22 riastradh Exp $");
|
2004-08-18 16:19:29 +04:00
|
|
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/kernel.h>
|
|
|
|
#include <sys/conf.h>
|
|
|
|
#include <sys/device.h>
|
|
|
|
#include <sys/event.h>
|
2008-05-25 16:30:40 +04:00
|
|
|
#include <sys/kmem.h>
|
2004-08-18 16:19:29 +04:00
|
|
|
#include <sys/ioctl.h>
|
2006-09-22 08:37:36 +04:00
|
|
|
#include <sys/fcntl.h>
|
2008-05-25 16:30:40 +04:00
|
|
|
#include <sys/file.h>
|
|
|
|
#include <sys/filedesc.h>
|
2008-11-24 02:59:41 +03:00
|
|
|
#include <sys/select.h>
|
|
|
|
#include <sys/poll.h>
|
2004-08-18 16:19:29 +04:00
|
|
|
#include <sys/drvctlio.h>
|
2008-05-25 16:30:40 +04:00
|
|
|
#include <sys/devmon.h>
|
2009-04-11 19:47:33 +04:00
|
|
|
#include <sys/stat.h>
|
2009-04-12 03:05:26 +04:00
|
|
|
#include <sys/kauth.h>
|
2009-04-30 09:15:36 +04:00
|
|
|
#include <sys/lwp.h>
|
2015-12-07 14:38:46 +03:00
|
|
|
#include <sys/module.h>
|
2004-08-18 16:19:29 +04:00
|
|
|
|
2015-08-20 12:45:45 +03:00
|
|
|
#include "ioconf.h"
|
|
|
|
|
2008-05-25 16:30:40 +04:00
|
|
|
struct drvctl_event {
|
|
|
|
TAILQ_ENTRY(drvctl_event) dce_link;
|
|
|
|
prop_dictionary_t dce_event;
|
|
|
|
};
|
|
|
|
|
|
|
|
TAILQ_HEAD(drvctl_queue, drvctl_event);
|
|
|
|
|
|
|
|
static struct drvctl_queue drvctl_eventq; /* FIFO */
|
|
|
|
static kcondvar_t drvctl_cond;
|
|
|
|
static kmutex_t drvctl_lock;
|
|
|
|
static int drvctl_nopen = 0, drvctl_eventcnt = 0;
|
2008-11-24 02:59:41 +03:00
|
|
|
static struct selinfo drvctl_rdsel;
|
2008-05-25 16:30:40 +04:00
|
|
|
|
|
|
|
#define DRVCTL_EVENTQ_DEPTH 64 /* arbitrary queue limit */
|
|
|
|
|
|
|
|
dev_type_open(drvctlopen);
|
2004-08-18 16:19:29 +04:00
|
|
|
|
|
|
|
const struct cdevsw drvctl_cdevsw = {
|
2014-03-16 09:20:22 +04:00
|
|
|
.d_open = drvctlopen,
|
|
|
|
.d_close = nullclose,
|
|
|
|
.d_read = nullread,
|
|
|
|
.d_write = nullwrite,
|
|
|
|
.d_ioctl = noioctl,
|
|
|
|
.d_stop = nostop,
|
|
|
|
.d_tty = notty,
|
|
|
|
.d_poll = nopoll,
|
|
|
|
.d_mmap = nommap,
|
|
|
|
.d_kqfilter = nokqfilter,
|
2014-07-25 12:10:31 +04:00
|
|
|
.d_discard = nodiscard,
|
2014-03-16 09:20:22 +04:00
|
|
|
.d_flag = D_OTHER
|
2004-08-18 16:19:29 +04:00
|
|
|
};
|
|
|
|
|
2008-05-25 16:30:40 +04:00
|
|
|
static int drvctl_read(struct file *, off_t *, struct uio *,
|
|
|
|
kauth_cred_t, int);
|
|
|
|
static int drvctl_write(struct file *, off_t *, struct uio *,
|
|
|
|
kauth_cred_t, int);
|
|
|
|
static int drvctl_ioctl(struct file *, u_long, void *);
|
2008-11-24 02:59:41 +03:00
|
|
|
static int drvctl_poll(struct file *, int);
|
2009-04-11 19:47:33 +04:00
|
|
|
static int drvctl_stat(struct file *, struct stat *);
|
2008-05-25 16:30:40 +04:00
|
|
|
static int drvctl_close(struct file *);
|
|
|
|
|
|
|
|
static const struct fileops drvctl_fileops = {
|
2017-11-30 23:25:54 +03:00
|
|
|
.fo_name = "drvctl",
|
2009-04-04 14:12:51 +04:00
|
|
|
.fo_read = drvctl_read,
|
|
|
|
.fo_write = drvctl_write,
|
|
|
|
.fo_ioctl = drvctl_ioctl,
|
|
|
|
.fo_fcntl = fnullop_fcntl,
|
|
|
|
.fo_poll = drvctl_poll,
|
2009-04-11 19:47:33 +04:00
|
|
|
.fo_stat = drvctl_stat,
|
2009-04-04 14:12:51 +04:00
|
|
|
.fo_close = drvctl_close,
|
|
|
|
.fo_kqfilter = fnullop_kqfilter,
|
2009-12-20 12:36:05 +03:00
|
|
|
.fo_restart = fnullop_restart,
|
2008-05-25 16:30:40 +04:00
|
|
|
};
|
|
|
|
|
2004-08-18 16:19:29 +04:00
|
|
|
#define MAXLOCATORS 100
|
|
|
|
|
2015-12-07 14:38:46 +03:00
|
|
|
static int (*saved_insert_vec)(const char *, prop_dictionary_t) = NULL;
|
|
|
|
|
2008-05-25 16:30:40 +04:00
|
|
|
static int drvctl_command(struct lwp *, struct plistref *, u_long, int);
|
|
|
|
static int drvctl_getevent(struct lwp *, struct plistref *, u_long, int);
|
|
|
|
|
|
|
|
void
|
|
|
|
drvctl_init(void)
|
|
|
|
{
|
|
|
|
TAILQ_INIT(&drvctl_eventq);
|
|
|
|
mutex_init(&drvctl_lock, MUTEX_DEFAULT, IPL_NONE);
|
|
|
|
cv_init(&drvctl_cond, "devmon");
|
2008-11-24 02:59:41 +03:00
|
|
|
selinit(&drvctl_rdsel);
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-12-07 14:38:46 +03:00
|
|
|
drvctl_fini(void)
|
|
|
|
{
|
|
|
|
|
|
|
|
seldestroy(&drvctl_rdsel);
|
|
|
|
cv_destroy(&drvctl_cond);
|
|
|
|
mutex_destroy(&drvctl_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2008-05-25 16:30:40 +04:00
|
|
|
devmon_insert(const char *event, prop_dictionary_t ev)
|
|
|
|
{
|
2009-01-03 06:31:23 +03:00
|
|
|
struct drvctl_event *dce, *odce;
|
2008-05-25 16:30:40 +04:00
|
|
|
|
|
|
|
mutex_enter(&drvctl_lock);
|
|
|
|
|
|
|
|
if (drvctl_nopen == 0) {
|
2013-04-26 13:04:43 +04:00
|
|
|
prop_object_release(ev);
|
2008-05-25 16:30:40 +04:00
|
|
|
mutex_exit(&drvctl_lock);
|
2015-12-07 14:38:46 +03:00
|
|
|
return 0;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Fill in mandatory member */
|
2020-06-11 05:28:01 +03:00
|
|
|
if (!prop_dictionary_set_string_nocopy(ev, "event", event)) {
|
2008-05-25 16:30:40 +04:00
|
|
|
prop_object_release(ev);
|
|
|
|
mutex_exit(&drvctl_lock);
|
2015-12-07 14:38:46 +03:00
|
|
|
return 0;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
dce = kmem_alloc(sizeof(*dce), KM_SLEEP);
|
|
|
|
dce->dce_event = ev;
|
|
|
|
|
|
|
|
if (drvctl_eventcnt == DRVCTL_EVENTQ_DEPTH) {
|
|
|
|
odce = TAILQ_FIRST(&drvctl_eventq);
|
|
|
|
TAILQ_REMOVE(&drvctl_eventq, odce, dce_link);
|
2008-05-30 19:30:37 +04:00
|
|
|
prop_object_release(odce->dce_event);
|
|
|
|
kmem_free(odce, sizeof(*odce));
|
2008-05-25 16:30:40 +04:00
|
|
|
--drvctl_eventcnt;
|
|
|
|
}
|
|
|
|
|
|
|
|
TAILQ_INSERT_TAIL(&drvctl_eventq, dce, dce_link);
|
|
|
|
++drvctl_eventcnt;
|
|
|
|
cv_broadcast(&drvctl_cond);
|
2008-11-24 02:59:41 +03:00
|
|
|
selnotify(&drvctl_rdsel, 0, 0);
|
2008-05-25 16:30:40 +04:00
|
|
|
|
|
|
|
mutex_exit(&drvctl_lock);
|
2015-12-07 14:38:46 +03:00
|
|
|
return 0;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
drvctlopen(dev_t dev, int flags, int mode, struct lwp *l)
|
|
|
|
{
|
|
|
|
struct file *fp;
|
|
|
|
int fd;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = fd_allocfile(&fp, &fd);
|
|
|
|
if (ret)
|
2009-05-01 00:39:08 +04:00
|
|
|
return ret;
|
2008-05-25 16:30:40 +04:00
|
|
|
|
|
|
|
/* XXX setup context */
|
|
|
|
mutex_enter(&drvctl_lock);
|
|
|
|
ret = fd_clone(fp, fd, flags, &drvctl_fileops, /* context */NULL);
|
|
|
|
++drvctl_nopen;
|
|
|
|
mutex_exit(&drvctl_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2006-09-22 08:37:36 +04:00
|
|
|
|
2008-01-27 04:38:33 +03:00
|
|
|
static int
|
2008-06-24 14:24:21 +04:00
|
|
|
pmdevbyname(u_long cmd, struct devpmargs *a)
|
2008-01-27 04:38:33 +03:00
|
|
|
{
|
2012-10-27 21:17:22 +04:00
|
|
|
device_t d;
|
2008-01-27 04:38:33 +03:00
|
|
|
|
2021-06-12 15:12:11 +03:00
|
|
|
KASSERT(KERNEL_LOCKED_P());
|
|
|
|
|
2008-02-12 20:30:57 +03:00
|
|
|
if ((d = device_find_by_xname(a->devname)) == NULL)
|
2008-01-27 04:38:33 +03:00
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case DRVSUSPENDDEV:
|
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 20:34:49 +04:00
|
|
|
return pmf_device_recursive_suspend(d, PMF_Q_DRVCTL) ? 0 : EBUSY;
|
2008-01-27 04:38:33 +03:00
|
|
|
case DRVRESUMEDEV:
|
Use device_t and its accessors throughout. Use aprint_*_dev().
Improve PMF-ability.
Add a 'flags' argument to suspend/resume handlers and
callers such as pmf_system_suspend().
Define a flag, PMF_F_SELF, which indicates to PMF that a
device is suspending/resuming itself. Add helper routines,
pmf_device_suspend_self(dev) and pmf_device_resume_self(dev),
that call pmf_device_suspend(dev, PMF_F_SELF) and
pmf_device_resume(dev, PMF_F_SELF), respectively. Use
PMF_F_SELF to suspend/resume self in ath(4), audio(4),
rtw(4), and sip(4).
In ath(4) and in rtw(4), replace the icky sc_enable/sc_disable
callbacks, provided by the bus front-end, with
self-suspension/resumption. Also, clean up the bus
front-ends. Make sure that the interrupt handler is
disestablished during suspension. Get rid of driver-private
flags (e.g., RTW_F_ENABLED, ath_softc->sc_invalid); use
device_is_active()/device_has_power() calls, instead.
In the network-class suspend handler, call if_stop(, 0)
instead of if_stop(, 1), because the latter is superfluous
(bus- and driver-suspension hooks will 'disable' the NIC),
and it may cause recursion.
In the network-class resume handler, prevent infinite
recursion through if_init() by getting out early if we are
self-suspending (PMF_F_SELF).
rtw(4) improvements:
Destroy rtw(4) callouts when we detach it. Make rtw at
pci detachable. Print some more information with the "rx
frame too long" warning.
Remove activate() methods:
Get rid of rtw_activate() and ath_activate(). The device
activate() methods are not good for much these days.
Make ath at cardbus resume with crypto functions intact:
Introduce a boolean device property, "pmf-powerdown". If
pmf-powerdown is present and false, it indicates that a
bus back-end should not remove power from a device.
Honor this property in cardbus_child_suspend().
Set this property to 'false' in ath_attach(), since removing
power from an ath at cardbus seems to lobotomize the WPA
crypto engine. XXX Should the pmf-powerdown property
propagate toward the root of the device tree?
Miscellaneous ath(4) changes:
Warn if ath(4) tries to write crypto keys to suspended
hardware.
Reduce differences between FreeBSD and NetBSD in ath(4)
multicast filter setup.
Make ath_printrxbuf() print an rx descriptor's status &
key index, to help debug crypto errors.
Shorten a staircase in ath_ioctl(). Don't check for
ieee80211_ioctl() return code ERESTART, it never happens.
2008-03-12 21:02:21 +03:00
|
|
|
if (a->flags & DEVPM_F_SUBTREE) {
|
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 20:34:49 +04:00
|
|
|
return pmf_device_subtree_resume(d, PMF_Q_DRVCTL)
|
Use device_t and its accessors throughout. Use aprint_*_dev().
Improve PMF-ability.
Add a 'flags' argument to suspend/resume handlers and
callers such as pmf_system_suspend().
Define a flag, PMF_F_SELF, which indicates to PMF that a
device is suspending/resuming itself. Add helper routines,
pmf_device_suspend_self(dev) and pmf_device_resume_self(dev),
that call pmf_device_suspend(dev, PMF_F_SELF) and
pmf_device_resume(dev, PMF_F_SELF), respectively. Use
PMF_F_SELF to suspend/resume self in ath(4), audio(4),
rtw(4), and sip(4).
In ath(4) and in rtw(4), replace the icky sc_enable/sc_disable
callbacks, provided by the bus front-end, with
self-suspension/resumption. Also, clean up the bus
front-ends. Make sure that the interrupt handler is
disestablished during suspension. Get rid of driver-private
flags (e.g., RTW_F_ENABLED, ath_softc->sc_invalid); use
device_is_active()/device_has_power() calls, instead.
In the network-class suspend handler, call if_stop(, 0)
instead of if_stop(, 1), because the latter is superfluous
(bus- and driver-suspension hooks will 'disable' the NIC),
and it may cause recursion.
In the network-class resume handler, prevent infinite
recursion through if_init() by getting out early if we are
self-suspending (PMF_F_SELF).
rtw(4) improvements:
Destroy rtw(4) callouts when we detach it. Make rtw at
pci detachable. Print some more information with the "rx
frame too long" warning.
Remove activate() methods:
Get rid of rtw_activate() and ath_activate(). The device
activate() methods are not good for much these days.
Make ath at cardbus resume with crypto functions intact:
Introduce a boolean device property, "pmf-powerdown". If
pmf-powerdown is present and false, it indicates that a
bus back-end should not remove power from a device.
Honor this property in cardbus_child_suspend().
Set this property to 'false' in ath_attach(), since removing
power from an ath at cardbus seems to lobotomize the WPA
crypto engine. XXX Should the pmf-powerdown property
propagate toward the root of the device tree?
Miscellaneous ath(4) changes:
Warn if ath(4) tries to write crypto keys to suspended
hardware.
Reduce differences between FreeBSD and NetBSD in ath(4)
multicast filter setup.
Make ath_printrxbuf() print an rx descriptor's status &
key index, to help debug crypto errors.
Shorten a staircase in ath_ioctl(). Don't check for
ieee80211_ioctl() return code ERESTART, it never happens.
2008-03-12 21:02:21 +03:00
|
|
|
? 0 : EBUSY;
|
|
|
|
} else {
|
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 20:34:49 +04:00
|
|
|
return pmf_device_recursive_resume(d, PMF_Q_DRVCTL)
|
Use device_t and its accessors throughout. Use aprint_*_dev().
Improve PMF-ability.
Add a 'flags' argument to suspend/resume handlers and
callers such as pmf_system_suspend().
Define a flag, PMF_F_SELF, which indicates to PMF that a
device is suspending/resuming itself. Add helper routines,
pmf_device_suspend_self(dev) and pmf_device_resume_self(dev),
that call pmf_device_suspend(dev, PMF_F_SELF) and
pmf_device_resume(dev, PMF_F_SELF), respectively. Use
PMF_F_SELF to suspend/resume self in ath(4), audio(4),
rtw(4), and sip(4).
In ath(4) and in rtw(4), replace the icky sc_enable/sc_disable
callbacks, provided by the bus front-end, with
self-suspension/resumption. Also, clean up the bus
front-ends. Make sure that the interrupt handler is
disestablished during suspension. Get rid of driver-private
flags (e.g., RTW_F_ENABLED, ath_softc->sc_invalid); use
device_is_active()/device_has_power() calls, instead.
In the network-class suspend handler, call if_stop(, 0)
instead of if_stop(, 1), because the latter is superfluous
(bus- and driver-suspension hooks will 'disable' the NIC),
and it may cause recursion.
In the network-class resume handler, prevent infinite
recursion through if_init() by getting out early if we are
self-suspending (PMF_F_SELF).
rtw(4) improvements:
Destroy rtw(4) callouts when we detach it. Make rtw at
pci detachable. Print some more information with the "rx
frame too long" warning.
Remove activate() methods:
Get rid of rtw_activate() and ath_activate(). The device
activate() methods are not good for much these days.
Make ath at cardbus resume with crypto functions intact:
Introduce a boolean device property, "pmf-powerdown". If
pmf-powerdown is present and false, it indicates that a
bus back-end should not remove power from a device.
Honor this property in cardbus_child_suspend().
Set this property to 'false' in ath_attach(), since removing
power from an ath at cardbus seems to lobotomize the WPA
crypto engine. XXX Should the pmf-powerdown property
propagate toward the root of the device tree?
Miscellaneous ath(4) changes:
Warn if ath(4) tries to write crypto keys to suspended
hardware.
Reduce differences between FreeBSD and NetBSD in ath(4)
multicast filter setup.
Make ath_printrxbuf() print an rx descriptor's status &
key index, to help debug crypto errors.
Shorten a staircase in ath_ioctl(). Don't check for
ieee80211_ioctl() return code ERESTART, it never happens.
2008-03-12 21:02:21 +03:00
|
|
|
? 0 : EBUSY;
|
|
|
|
}
|
2008-01-27 04:38:33 +03:00
|
|
|
default:
|
|
|
|
return EPASSTHROUGH;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
listdevbyname(struct devlistargs *l)
|
|
|
|
{
|
2008-02-06 23:24:17 +03:00
|
|
|
device_t d, child;
|
2008-03-05 10:09:18 +03:00
|
|
|
deviter_t di;
|
|
|
|
int cnt = 0, idx, error = 0;
|
2008-01-27 04:38:33 +03:00
|
|
|
|
2021-06-12 15:12:11 +03:00
|
|
|
KASSERT(KERNEL_LOCKED_P());
|
|
|
|
|
2009-04-05 01:49:05 +04:00
|
|
|
if (*l->l_devname == '\0')
|
2011-08-31 22:31:02 +04:00
|
|
|
d = NULL;
|
2009-04-05 01:49:05 +04:00
|
|
|
else if (memchr(l->l_devname, 0, sizeof(l->l_devname)) == NULL)
|
|
|
|
return EINVAL;
|
|
|
|
else if ((d = device_find_by_xname(l->l_devname)) == NULL)
|
2008-01-27 04:38:33 +03:00
|
|
|
return ENXIO;
|
|
|
|
|
2008-03-05 10:09:18 +03:00
|
|
|
for (child = deviter_first(&di, 0); child != NULL;
|
|
|
|
child = deviter_next(&di)) {
|
2008-02-06 23:24:17 +03:00
|
|
|
if (device_parent(child) != d)
|
|
|
|
continue;
|
|
|
|
idx = cnt++;
|
|
|
|
if (l->l_childname == NULL || idx >= l->l_children)
|
|
|
|
continue;
|
|
|
|
error = copyoutstr(device_xname(child), l->l_childname[idx],
|
|
|
|
sizeof(l->l_childname[idx]), NULL);
|
2008-03-05 10:09:18 +03:00
|
|
|
if (error != 0)
|
|
|
|
break;
|
2008-02-06 23:24:17 +03:00
|
|
|
}
|
2008-03-05 10:09:18 +03:00
|
|
|
deviter_release(&di);
|
2008-01-27 04:38:33 +03:00
|
|
|
|
2008-02-06 23:24:17 +03:00
|
|
|
l->l_children = cnt;
|
2008-03-05 10:09:18 +03:00
|
|
|
return error;
|
2008-01-27 04:38:33 +03:00
|
|
|
}
|
|
|
|
|
2004-08-18 16:19:29 +04:00
|
|
|
static int
|
|
|
|
detachdevbyname(const char *devname)
|
|
|
|
{
|
2012-10-27 21:17:22 +04:00
|
|
|
device_t d;
|
2021-06-12 15:14:03 +03:00
|
|
|
deviter_t di;
|
|
|
|
int error;
|
2004-08-18 16:19:29 +04:00
|
|
|
|
2021-06-12 15:12:11 +03:00
|
|
|
KASSERT(KERNEL_LOCKED_P());
|
|
|
|
|
2021-06-12 15:14:03 +03:00
|
|
|
for (d = deviter_first(&di, DEVITER_F_RW);
|
|
|
|
d != NULL;
|
|
|
|
d = deviter_next(&di)) {
|
|
|
|
if (strcmp(device_xname(d), devname) == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (d == NULL) {
|
|
|
|
error = ENXIO;
|
|
|
|
goto out;
|
|
|
|
}
|
2008-01-27 04:38:33 +03:00
|
|
|
|
2004-08-18 16:19:29 +04:00
|
|
|
#ifndef XXXFULLRISK
|
2008-01-27 04:38:33 +03:00
|
|
|
/*
|
|
|
|
* If the parent cannot be notified, it might keep
|
|
|
|
* pointers to the detached device.
|
|
|
|
* There might be a private notification mechanism,
|
2009-05-01 00:39:08 +04:00
|
|
|
* but better play it safe here.
|
2008-01-27 04:38:33 +03:00
|
|
|
*/
|
2022-02-12 06:24:34 +03:00
|
|
|
if (device_parent(d) &&
|
|
|
|
!device_cfattach(device_parent(d))->ca_childdetached) {
|
2021-06-12 15:14:03 +03:00
|
|
|
error = ENOTSUP;
|
|
|
|
goto out;
|
|
|
|
}
|
2004-08-18 16:19:29 +04:00
|
|
|
#endif
|
2021-06-12 15:14:03 +03:00
|
|
|
|
|
|
|
error = config_detach(d, 0);
|
|
|
|
out: deviter_release(&di);
|
|
|
|
return error;
|
2004-08-18 16:19:29 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
rescanbus(const char *busname, const char *ifattr,
|
|
|
|
int numlocators, const int *locators)
|
|
|
|
{
|
2008-01-27 04:38:33 +03:00
|
|
|
int i, rc;
|
2012-10-27 21:17:22 +04:00
|
|
|
device_t d;
|
2005-08-25 19:06:28 +04:00
|
|
|
const struct cfiattrdata * const *ap;
|
2004-08-18 16:19:29 +04:00
|
|
|
|
2021-06-12 15:12:11 +03:00
|
|
|
KASSERT(KERNEL_LOCKED_P());
|
|
|
|
|
2004-08-18 16:19:29 +04:00
|
|
|
/* XXX there should be a way to get limits and defaults (per device)
|
|
|
|
from config generated data */
|
|
|
|
int locs[MAXLOCATORS];
|
|
|
|
for (i = 0; i < MAXLOCATORS; i++)
|
|
|
|
locs[i] = -1;
|
|
|
|
|
|
|
|
for (i = 0; i < numlocators;i++)
|
|
|
|
locs[i] = locators[i];
|
|
|
|
|
2008-02-12 20:30:57 +03:00
|
|
|
if ((d = device_find_by_xname(busname)) == NULL)
|
2008-01-27 04:38:33 +03:00
|
|
|
return ENXIO;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* must support rescan, and must have something
|
|
|
|
* to attach to
|
|
|
|
*/
|
2022-02-12 06:24:34 +03:00
|
|
|
if (!device_cfattach(d)->ca_rescan ||
|
|
|
|
!device_cfdriver(d)->cd_attrs)
|
2009-05-01 00:39:08 +04:00
|
|
|
return ENODEV;
|
2008-01-27 04:38:33 +03:00
|
|
|
|
2021-06-16 03:19:46 +03:00
|
|
|
/* rescan all ifattrs if none is specified */
|
2008-01-27 04:38:33 +03:00
|
|
|
if (!ifattr) {
|
2021-06-16 03:19:46 +03:00
|
|
|
rc = 0;
|
2022-02-12 06:24:34 +03:00
|
|
|
for (ap = device_cfdriver(d)->cd_attrs; *ap; ap++) {
|
|
|
|
rc = (*device_cfattach(d)->ca_rescan)(d,
|
|
|
|
(*ap)->ci_name, locs);
|
2021-06-16 03:19:46 +03:00
|
|
|
if (rc)
|
|
|
|
break;
|
|
|
|
}
|
2008-01-27 04:38:33 +03:00
|
|
|
} else {
|
|
|
|
/* check for valid attribute passed */
|
2022-02-12 06:24:34 +03:00
|
|
|
for (ap = device_cfdriver(d)->cd_attrs; *ap; ap++)
|
2008-01-27 04:38:33 +03:00
|
|
|
if (!strcmp((*ap)->ci_name, ifattr))
|
|
|
|
break;
|
|
|
|
if (!*ap)
|
2009-05-01 00:39:08 +04:00
|
|
|
return EINVAL;
|
2022-02-12 06:24:34 +03:00
|
|
|
rc = (*device_cfattach(d)->ca_rescan)(d, ifattr, locs);
|
2004-08-18 16:19:29 +04:00
|
|
|
}
|
|
|
|
|
2008-01-27 04:38:33 +03:00
|
|
|
config_deferred(NULL);
|
|
|
|
return rc;
|
2004-08-18 16:19:29 +04:00
|
|
|
}
|
|
|
|
|
2008-05-25 16:30:40 +04:00
|
|
|
static int
|
|
|
|
drvctl_read(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
|
|
|
|
int flags)
|
|
|
|
{
|
2009-05-01 00:39:08 +04:00
|
|
|
return ENODEV;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drvctl_write(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
|
|
|
|
int flags)
|
|
|
|
{
|
2009-05-01 00:39:08 +04:00
|
|
|
return ENODEV;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
drvctl_ioctl(struct file *fp, u_long cmd, void *data)
|
2004-08-18 16:19:29 +04:00
|
|
|
{
|
|
|
|
int res;
|
|
|
|
char *ifattr;
|
|
|
|
int *locs;
|
2009-01-17 10:02:35 +03:00
|
|
|
size_t locs_sz = 0; /* XXXgcc */
|
2004-08-18 16:19:29 +04:00
|
|
|
|
2021-06-12 15:11:59 +03:00
|
|
|
KERNEL_LOCK(1, NULL);
|
2004-08-18 16:19:29 +04:00
|
|
|
switch (cmd) {
|
2008-01-27 04:38:33 +03:00
|
|
|
case DRVSUSPENDDEV:
|
|
|
|
case DRVRESUMEDEV:
|
|
|
|
#define d ((struct devpmargs *)data)
|
|
|
|
res = pmdevbyname(cmd, d);
|
|
|
|
#undef d
|
|
|
|
break;
|
|
|
|
case DRVLISTDEV:
|
|
|
|
res = listdevbyname((struct devlistargs *)data);
|
|
|
|
break;
|
2004-08-18 16:19:29 +04:00
|
|
|
case DRVDETACHDEV:
|
|
|
|
#define d ((struct devdetachargs *)data)
|
|
|
|
res = detachdevbyname(d->devname);
|
|
|
|
#undef d
|
|
|
|
break;
|
|
|
|
case DRVRESCANBUS:
|
|
|
|
#define d ((struct devrescanargs *)data)
|
|
|
|
d->busname[sizeof(d->busname) - 1] = '\0';
|
|
|
|
|
|
|
|
/* XXX better copyin? */
|
|
|
|
if (d->ifattr[0]) {
|
|
|
|
d->ifattr[sizeof(d->ifattr) - 1] = '\0';
|
|
|
|
ifattr = d->ifattr;
|
|
|
|
} else
|
|
|
|
ifattr = 0;
|
|
|
|
|
|
|
|
if (d->numlocators) {
|
2021-06-12 15:11:59 +03:00
|
|
|
if (d->numlocators > MAXLOCATORS) {
|
|
|
|
res = EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
2009-01-17 10:02:35 +03:00
|
|
|
locs_sz = d->numlocators * sizeof(int);
|
|
|
|
locs = kmem_alloc(locs_sz, KM_SLEEP);
|
|
|
|
res = copyin(d->locators, locs, locs_sz);
|
2007-04-04 03:02:39 +04:00
|
|
|
if (res) {
|
2009-01-17 10:02:35 +03:00
|
|
|
kmem_free(locs, locs_sz);
|
2021-06-12 15:11:59 +03:00
|
|
|
goto out;
|
2007-04-04 03:02:39 +04:00
|
|
|
}
|
2004-08-18 16:19:29 +04:00
|
|
|
} else
|
2009-01-17 10:02:35 +03:00
|
|
|
locs = NULL;
|
2004-08-18 16:19:29 +04:00
|
|
|
res = rescanbus(d->busname, ifattr, d->numlocators, locs);
|
|
|
|
if (locs)
|
2009-01-17 10:02:35 +03:00
|
|
|
kmem_free(locs, locs_sz);
|
2004-08-18 16:19:29 +04:00
|
|
|
#undef d
|
2006-09-22 08:37:36 +04:00
|
|
|
break;
|
|
|
|
case DRVCTLCOMMAND:
|
2008-05-25 16:30:40 +04:00
|
|
|
res = drvctl_command(curlwp, (struct plistref *)data, cmd,
|
|
|
|
fp->f_flag);
|
2006-09-22 08:37:36 +04:00
|
|
|
break;
|
2008-05-25 16:30:40 +04:00
|
|
|
case DRVGETEVENT:
|
|
|
|
res = drvctl_getevent(curlwp, (struct plistref *)data, cmd,
|
|
|
|
fp->f_flag);
|
|
|
|
break;
|
2006-09-22 08:37:36 +04:00
|
|
|
default:
|
2021-06-12 15:11:59 +03:00
|
|
|
res = EPASSTHROUGH;
|
|
|
|
break;
|
2004-08-18 16:19:29 +04:00
|
|
|
}
|
2021-06-12 15:11:59 +03:00
|
|
|
out: KERNEL_UNLOCK_ONE(NULL);
|
2009-05-01 00:39:08 +04:00
|
|
|
return res;
|
2004-08-18 16:19:29 +04:00
|
|
|
}
|
|
|
|
|
2009-04-11 19:47:33 +04:00
|
|
|
static int
|
|
|
|
drvctl_stat(struct file *fp, struct stat *st)
|
|
|
|
{
|
|
|
|
(void)memset(st, 0, sizeof(*st));
|
2009-04-12 03:05:26 +04:00
|
|
|
st->st_uid = kauth_cred_geteuid(fp->f_cred);
|
|
|
|
st->st_gid = kauth_cred_getegid(fp->f_cred);
|
2009-04-11 19:47:33 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-24 02:59:41 +03:00
|
|
|
static int
|
|
|
|
drvctl_poll(struct file *fp, int events)
|
|
|
|
{
|
|
|
|
int revents = 0;
|
|
|
|
|
|
|
|
if (!TAILQ_EMPTY(&drvctl_eventq))
|
|
|
|
revents |= events & (POLLIN | POLLRDNORM);
|
|
|
|
else
|
|
|
|
selrecord(curlwp, &drvctl_rdsel);
|
|
|
|
|
|
|
|
return revents;
|
|
|
|
}
|
|
|
|
|
2008-05-25 16:30:40 +04:00
|
|
|
static int
|
|
|
|
drvctl_close(struct file *fp)
|
|
|
|
{
|
|
|
|
struct drvctl_event *dce;
|
|
|
|
|
|
|
|
/* XXX free context */
|
|
|
|
mutex_enter(&drvctl_lock);
|
|
|
|
KASSERT(drvctl_nopen > 0);
|
|
|
|
--drvctl_nopen;
|
|
|
|
if (drvctl_nopen == 0) {
|
|
|
|
/* flush queue */
|
|
|
|
while ((dce = TAILQ_FIRST(&drvctl_eventq)) != NULL) {
|
|
|
|
TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
|
|
|
|
KASSERT(drvctl_eventcnt > 0);
|
|
|
|
--drvctl_eventcnt;
|
2008-05-30 19:30:37 +04:00
|
|
|
prop_object_release(dce->dce_event);
|
2008-05-25 16:30:40 +04:00
|
|
|
kmem_free(dce, sizeof(*dce));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mutex_exit(&drvctl_lock);
|
|
|
|
|
2009-05-01 00:39:08 +04:00
|
|
|
return 0;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
2004-08-18 16:19:29 +04:00
|
|
|
void
|
2015-08-20 11:27:09 +03:00
|
|
|
drvctlattach(int arg __unused)
|
2004-08-18 16:19:29 +04:00
|
|
|
{
|
|
|
|
}
|
2006-09-22 08:37:36 +04:00
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
* Driver control command processing engine
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
static int
|
2006-11-01 13:17:58 +03:00
|
|
|
drvctl_command_get_properties(struct lwp *l,
|
2006-09-22 08:37:36 +04:00
|
|
|
prop_dictionary_t command_dict,
|
|
|
|
prop_dictionary_t results_dict)
|
|
|
|
{
|
|
|
|
prop_dictionary_t args_dict;
|
|
|
|
prop_string_t devname_string;
|
|
|
|
device_t dev;
|
2008-03-05 10:09:18 +03:00
|
|
|
deviter_t di;
|
2006-09-22 08:37:36 +04:00
|
|
|
|
|
|
|
args_dict = prop_dictionary_get(command_dict, "drvctl-arguments");
|
|
|
|
if (args_dict == NULL)
|
2009-05-01 00:39:08 +04:00
|
|
|
return EINVAL;
|
2006-09-22 08:37:36 +04:00
|
|
|
|
|
|
|
devname_string = prop_dictionary_get(args_dict, "device-name");
|
|
|
|
if (devname_string == NULL)
|
2009-05-01 00:39:08 +04:00
|
|
|
return EINVAL;
|
2006-09-22 08:37:36 +04:00
|
|
|
|
2008-03-05 10:09:18 +03:00
|
|
|
for (dev = deviter_first(&di, 0); dev != NULL;
|
|
|
|
dev = deviter_next(&di)) {
|
2020-06-11 05:28:01 +03:00
|
|
|
if (prop_string_equals_string(devname_string,
|
2008-03-05 10:09:18 +03:00
|
|
|
device_xname(dev))) {
|
|
|
|
prop_dictionary_set(results_dict, "drvctl-result-data",
|
|
|
|
device_properties(dev));
|
2006-09-22 08:37:36 +04:00
|
|
|
break;
|
2008-03-05 10:09:18 +03:00
|
|
|
}
|
2006-09-22 08:37:36 +04:00
|
|
|
}
|
|
|
|
|
2008-03-05 10:09:18 +03:00
|
|
|
deviter_release(&di);
|
|
|
|
|
2006-09-22 08:37:36 +04:00
|
|
|
if (dev == NULL)
|
2009-05-01 00:39:08 +04:00
|
|
|
return ESRCH;
|
2008-03-05 10:09:18 +03:00
|
|
|
|
2009-05-01 00:39:08 +04:00
|
|
|
return 0;
|
2006-09-22 08:37:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
struct drvctl_command_desc {
|
|
|
|
const char *dcd_name; /* command name */
|
|
|
|
int (*dcd_func)(struct lwp *, /* handler function */
|
|
|
|
prop_dictionary_t,
|
|
|
|
prop_dictionary_t);
|
|
|
|
int dcd_rw; /* read or write required */
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct drvctl_command_desc drvctl_command_table[] = {
|
|
|
|
{ .dcd_name = "get-properties",
|
|
|
|
.dcd_func = drvctl_command_get_properties,
|
|
|
|
.dcd_rw = FREAD,
|
|
|
|
},
|
|
|
|
|
|
|
|
{ .dcd_name = NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
drvctl_command(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
|
|
|
|
int fflag)
|
|
|
|
{
|
|
|
|
prop_dictionary_t command_dict, results_dict;
|
|
|
|
prop_string_t command_string;
|
|
|
|
const struct drvctl_command_desc *dcd;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
error = prop_dictionary_copyin_ioctl(pref, ioctl_cmd, &command_dict);
|
|
|
|
if (error)
|
2009-05-01 00:39:08 +04:00
|
|
|
return error;
|
2006-09-22 08:37:36 +04:00
|
|
|
|
|
|
|
results_dict = prop_dictionary_create();
|
|
|
|
if (results_dict == NULL) {
|
|
|
|
prop_object_release(command_dict);
|
2009-05-01 00:39:08 +04:00
|
|
|
return ENOMEM;
|
2006-09-22 08:37:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
command_string = prop_dictionary_get(command_dict, "drvctl-command");
|
|
|
|
if (command_string == NULL) {
|
|
|
|
error = EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (dcd = drvctl_command_table; dcd->dcd_name != NULL; dcd++) {
|
2020-06-11 05:28:01 +03:00
|
|
|
if (prop_string_equals_string(command_string,
|
|
|
|
dcd->dcd_name))
|
2006-09-22 08:37:36 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dcd->dcd_name == NULL) {
|
|
|
|
error = EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((fflag & dcd->dcd_rw) == 0) {
|
|
|
|
error = EPERM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = (*dcd->dcd_func)(l, command_dict, results_dict);
|
|
|
|
|
2006-10-26 09:08:01 +04:00
|
|
|
prop_dictionary_set_int32(results_dict, "drvctl-error", error);
|
2006-09-22 08:37:36 +04:00
|
|
|
|
|
|
|
error = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, results_dict);
|
|
|
|
out:
|
|
|
|
prop_object_release(command_dict);
|
|
|
|
prop_object_release(results_dict);
|
2009-05-01 00:39:08 +04:00
|
|
|
return error;
|
2006-09-22 08:37:36 +04:00
|
|
|
}
|
2008-05-25 16:30:40 +04:00
|
|
|
|
|
|
|
static int
|
|
|
|
drvctl_getevent(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
|
|
|
|
int fflag)
|
|
|
|
{
|
|
|
|
struct drvctl_event *dce;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if ((fflag & (FREAD|FWRITE)) != (FREAD|FWRITE))
|
2009-05-01 00:39:08 +04:00
|
|
|
return EPERM;
|
2008-05-25 16:30:40 +04:00
|
|
|
|
|
|
|
mutex_enter(&drvctl_lock);
|
|
|
|
while ((dce = TAILQ_FIRST(&drvctl_eventq)) == NULL) {
|
|
|
|
if (fflag & O_NONBLOCK) {
|
|
|
|
mutex_exit(&drvctl_lock);
|
2009-05-01 00:39:08 +04:00
|
|
|
return EWOULDBLOCK;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = cv_wait_sig(&drvctl_cond, &drvctl_lock);
|
|
|
|
if (ret) {
|
|
|
|
mutex_exit(&drvctl_lock);
|
2009-05-01 00:39:08 +04:00
|
|
|
return ret;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
|
|
|
|
KASSERT(drvctl_eventcnt > 0);
|
|
|
|
--drvctl_eventcnt;
|
|
|
|
mutex_exit(&drvctl_lock);
|
|
|
|
|
|
|
|
ret = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, dce->dce_event);
|
|
|
|
|
|
|
|
prop_object_release(dce->dce_event);
|
|
|
|
kmem_free(dce, sizeof(*dce));
|
|
|
|
|
2009-05-01 00:39:08 +04:00
|
|
|
return ret;
|
2008-05-25 16:30:40 +04:00
|
|
|
}
|
2015-12-07 14:38:46 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Module glue
|
|
|
|
*/
|
|
|
|
|
|
|
|
MODULE(MODULE_CLASS_DRIVER, drvctl, NULL);
|
|
|
|
|
|
|
|
int
|
|
|
|
drvctl_modcmd(modcmd_t cmd, void *arg)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
#ifdef _MODULE
|
|
|
|
int bmajor, cmajor;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
error = 0;
|
|
|
|
switch (cmd) {
|
|
|
|
case MODULE_CMD_INIT:
|
|
|
|
drvctl_init();
|
|
|
|
|
|
|
|
mutex_enter(&drvctl_lock);
|
2015-12-07 23:01:43 +03:00
|
|
|
#ifdef _MODULE
|
2015-12-07 14:38:46 +03:00
|
|
|
bmajor = cmajor = -1;
|
|
|
|
error = devsw_attach("drvctl", NULL, &bmajor,
|
|
|
|
&drvctl_cdevsw, &cmajor);
|
|
|
|
#endif
|
|
|
|
if (error == 0) {
|
|
|
|
KASSERT(saved_insert_vec == NULL);
|
|
|
|
saved_insert_vec = devmon_insert_vec;
|
|
|
|
devmon_insert_vec = devmon_insert;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_exit(&drvctl_lock);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MODULE_CMD_FINI:
|
|
|
|
mutex_enter(&drvctl_lock);
|
|
|
|
if (drvctl_nopen != 0 || drvctl_eventcnt != 0 ) {
|
|
|
|
mutex_exit(&drvctl_lock);
|
|
|
|
return EBUSY;
|
|
|
|
}
|
|
|
|
KASSERT(saved_insert_vec != NULL);
|
|
|
|
devmon_insert_vec = saved_insert_vec;
|
|
|
|
saved_insert_vec = NULL;
|
|
|
|
#ifdef _MODULE
|
2022-03-28 15:33:20 +03:00
|
|
|
devsw_detach(NULL, &drvctl_cdevsw);
|
2015-12-07 14:38:46 +03:00
|
|
|
#endif
|
|
|
|
mutex_exit(&drvctl_lock);
|
2022-03-28 15:33:20 +03:00
|
|
|
drvctl_fini();
|
2015-12-07 14:38:46 +03:00
|
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
error = ENOTTY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|