Add a new device control utility for new-bus devices called devctl. This

allows the user to request administrative changes to individual devices
such as attach or detaching drivers or disabling and re-enabling devices.
- Add a new /dev/devctl2 character device which uses ioctls for device
  requests.  The ioctls use a common 'struct devreq' which is somewhat
  similar to 'struct ifreq'.
- The ioctls identify the device to operate on via a string.  This
  string can either by the device's name, or it can be a bus-specific
  address.  (For unattached devices, a bus address is the only way to
  locate a device.)  Bus drivers register an eventhandler to claim
  unrecognized device names that the driver recognizes as a valid address.
  Two buses currently support addresses: ACPI recognizes any device
  in the ACPI namespace via its full path starting with "\" and
  the PCI bus driver recognizes an address specification of
  'pci[<domain>:]<bus>:<slot>:<func>' (identical to the PCI selector
  strings supported by pciconf).
- To make it easier to cut and paste, change the PnP location string
  in the PCI bus driver to output a full PCI selector string rather
  than 'slot=<slot> function=<func>'.
- Add a devctl(3) interface in libdevctl which provides a wrapper around
  the ioctls and is the preferred interface for other userland code.
- Add a devctl(8) program which is a simple wrapper around the requests
  supported by devctl(3).
- Add a device_is_suspended() function to check DF_SUSPENDED.
- Add a resource_unset_value() function that can be used to remove a
  hint from the kernel environment.  This is used to clear a
  hint.<driver>.<unit>.disabled hint when re-enabling a boot-time
  disabled device.

Reviewed by:	imp (parts)
Requested by:	imp (changing PCI location string)
Relnotes:	yes
This commit is contained in:
John Baldwin 2015-02-06 16:09:01 +00:00
parent b01589c956
commit 64de80195b
17 changed files with 1328 additions and 4 deletions

View File

@ -41,6 +41,7 @@ LINE("libcrypt", "Crypt Library (libcrypt, \\-lcrypt)")
LINE("libcurses", "Curses Library (libcurses, \\-lcurses)")
LINE("libcuse", "Userland Character Device Library (libcuse, \\-lcuse)")
LINE("libdevattr", "Device attribute and event library (libdevattr, \\-ldevattr)")
LINE("libdevctl", "Device Control Library (libdevctl, \\-ldevctl)")
LINE("libdevinfo", "Device and Resource Information Utility Library (libdevinfo, \\-ldevinfo)")
LINE("libdevstat", "Device Statistics Library (libdevstat, \\-ldevstat)")
LINE("libdisk", "Interface to Slice and Partition Labels Library (libdisk, \\-ldisk)")

View File

@ -41,6 +41,7 @@ SUBDIR= ${SUBDIR_ORDERED} \
${_libcom_err} \
libcompat \
libcrypt \
libdevctl \
libdevinfo \
libdevstat \
libdpv \

8
lib/libdevctl/Makefile Normal file
View File

@ -0,0 +1,8 @@
# $FreeBSD$
LIB= devctl
SRCS= devctl.c
INCS= devctl.h
MAN= devctl.3
.include <bsd.lib.mk>

295
lib/libdevctl/devctl.3 Normal file
View File

@ -0,0 +1,295 @@
.\"
.\" Copyright (c) 2014 John Baldwin <jhb@FreeBSD.org>
.\" 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.
.\"
.\" $FreeBSD$
.\"
.Dd December 26, 2014
.Dt DEVCTL 3
.Os
.Sh NAME
.Nm devctl ,
.Nm devctl_attach ,
.Nm devctl_detach ,
.Nm devctl_disable ,
.Nm devctl_enable ,
.Nm devctl_resume ,
.Nm devctl_suspend
.Nd device control library
.Sh LIBRARY
.Lb libdevctl
.Sh SYNOPSIS
.In devctl.h
.Ft int
.Fn devctl_attach "const char *device"
.Ft int
.Fn devctl_detach "const char *device" "bool force"
.Ft int
.Fn devctl_disable "const char *device" "bool force_detach"
.Ft int
.Fn devctl_enable "const char *device"
.Ft int
.Fn devctl_resume "const char *device"
.Ft int
.Fn devctl_suspend "const char *device"
.Ft int
.Fn devctl_set_driver "const char *device" "const char *driver" "bool force"
.Sh DESCRIPTION
The
.Nm
library adjusts the state of devices in the kernel's internal device
hierarchy.
Each control operation accepts a
.Fa device
argument that identifies the device to adjust.
The
.Fa device
may be specified as either the name of an existing device or as a
bus-specific address.
The following bus-specific address formats are currently supported:
.Bl -tag -offset indent
.It Sy pci Ns Fa domain Ns : Ns Fa bus Ns : Ns Fa slot Ns : Ns Fa function
A PCI device with the specified
.Fa domain ,
.Fa bus ,
.Fa slot ,
and
.Fa function .
.It Sy pci Ns Fa bus Ns : Ns Fa slot Ns : Ns Fa function
A PCI device in domain zero with the specified
.Fa bus ,
.Fa slot ,
and
.Fa function .
.It Fa handle
A device with an ACPI handle of
.Fa handle .
The handle must be specified as an absolute path and must begin with a
.Dq \e .
.El
.Pp
The
.Fn devctl_attach
function probes a device and attaches a suitable device driver if one is
found.
.Pp
The
.Fn devctl_detach
function detaches a device from its current device driver.
The device is left detached until either a new driver for its parent
bus is loaded or the device is explicitly probed via
.Fn devctl_attach .
If
.Fa force
is true,
the current device driver will be detached even if the device is busy.
.Pp
The
.Fn devctl_disable
function disables a device.
If the device is currently attached to a device driver,
the device driver will be detached from the device,
but the device will retain its current name.
If
.Fa force_detach
is true,
the current device driver will be detached even if the device is busy.
The device will remain disabled and detached until it is explicitly enabled
via
.Fn devctl_enable .
.Pp
The
.Fn devctl_enable
function re-enables a disabled device.
The device will probe and attach if a suitable device driver is found.
.Pp
The
.Fn devctl_suspend
function suspends a device.
This may include placing the device in a reduced power state,
but any device driver currently attached to the device will remain attached.
.Pp
The
.Fn devctl_resume
function resumes a suspended device to a fully working state.
.Pp
The
.Fn devctl_set_driver
function attaches a device driver named
.Fa driver
to a device.
If the device is already attached and
.Fa force
is false,
the request will fail.
If the device is already attached and
.Fa force
is true,
the device will be detached from its current device driver before it is
attached to the new device driver.
.Sh RETURN VALUES
.Rv -std devctl_attach devctl_detach devctl_disable devctl_enable \
devctl_suspend devctl_resume devctl_set_driver
.Sh ERRORS
In addition to specific errors noted below,
all of the
.Nm
functions may fail for any of the errors described in
.Xr open 2
as well as:
.Bl -tag -width Er
.It Bq Er EINVAL
The device name is too long.
.It Bq Er ENOENT
No existing device matches the specified name or location.
.It Bq Er EPERM
The current process is not permitted to adjust the state of
.Fa device .
.El
.Pp
The
.Fn devctl_attach
function may fail if:
.Bl -tag -width Er
.It Bq Er EBUSY
The device is already attached.
.It Bq Er ENOMEM
An internal memory allocation request failed.
.It Bq Er ENXIO
The device is disabled.
.It Bq Er ENXIO
No suitable driver for the device could be found,
or the driver failed to attach.
.El
.Pp
The
.Fn devctl_detach
function may fail if:
.Bl -tag -width Er
.It Bq Er EBUSY
The current device driver for
.Fa device
is busy and cannot detach at this time.
Note that some drivers may return this even if
.Fa force
is true.
.It Bq Er ENXIO
The device is not attached to a driver.
.It Bq Er ENXIO
The current device driver for
.Fa device
does not support detaching.
.El
.Pp
The
.Fn devctl_enable
function may fail if:
.Bl -tag -width Er
.It Bq Er EBUSY
The device is already enabled.
.It Bq Er ENOMEM
An internal memory allocation request failed.
.It Bq Er ENXIO
No suitable driver for the device could be found,
or the driver failed to attach.
.El
.Pp
The
.Fn devctl_disable
function may fail if:
.Bl -tag -width Er
.It Bq Er EBUSY
The current device driver for
.Fa device
is busy and cannot detach at this time.
Note that some drivers may return this even if
.Fa force_detach
is true.
.It Bq Er ENXIO
The device is already disabled.
.It Bq Er ENXIO
The current device driver for
.Fa device
does not support detaching.
.El
.Pp
The
.Fn devctl_suspend
function may fail if:
.Bl -tag -width Er
.It Bq Er EBUSY
The device is already suspended.
.It Bq Er EINVAL
The device to be suspended is the root bus device.
.El
.Pp
The
.Fn devctl_resume
function may fail if:
.Bl -tag -width Er
.It Bq Er EINVAL
The device is not suspended.
.It Bq Er EINVAL
The device to be resumed is the root bus device.
.El
.Pp
The
.Fn devctl_set_driver
function may fail if:
.Bl -tag -width Er
.It Bq Er EBUSY
The device is currently attached to a device driver and
.Fa force
is false.
.It Bq Er EBUSY
The current device driver for
.Fa device
is busy and cannot detach at this time.
.It Bq Er EFAULT
The
.Fa driver
argument points outside the process' allocated address space.
.It Bq Er ENOENT
No device driver with the requested name exists.
.It Bq Er ENOMEM
An internal memory allocation request failed.
.It Bq Er ENXIO
The device is disabled.
.It Bq Er ENXIO
The new device driver failed to attach.
.El
.Sh SEE ALSO
.Xr devinfo 3 ,
.Xr devstat 3 ,
.Xr devctl 8
.Sh HISTORY
The
.Nm
library first appeared in
.Fx 11.0 .
.Sh BUGS
If a device is suspended individually via
.Fn devctl_suspend
and the entire machine is subsequently suspended,
the device will be resumed when the machine resumes.

124
lib/libdevctl/devctl.c Normal file
View File

@ -0,0 +1,124 @@
/*-
* Copyright (c) 2014 John Baldwin <jhb@FreeBSD.org>
* 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>
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/bus.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include "devctl.h"
static int
devctl_request(u_long cmd, struct devreq *req)
{
static int devctl2_fd = -1;
if (devctl2_fd == -1) {
devctl2_fd = open("/dev/devctl2", O_RDONLY);
if (devctl2_fd == -1)
return (-1);
}
return (ioctl(devctl2_fd, cmd, req));
}
static int
devctl_simple_request(u_long cmd, const char *name, int flags)
{
struct devreq req;
memset(&req, 0, sizeof(req));
if (strlcpy(req.dr_name, name, sizeof(req.dr_name)) >=
sizeof(req.dr_name)) {
errno = EINVAL;
return (-1);
}
req.dr_flags = flags;
return (devctl_request(cmd, &req));
}
int
devctl_attach(const char *device)
{
return (devctl_simple_request(DEV_ATTACH, device, 0));
}
int
devctl_detach(const char *device, bool force)
{
return (devctl_simple_request(DEV_DETACH, device, force ?
DEVF_FORCE_DETACH : 0));
}
int
devctl_enable(const char *device)
{
return (devctl_simple_request(DEV_ENABLE, device, 0));
}
int
devctl_disable(const char *device, bool force_detach)
{
return (devctl_simple_request(DEV_DISABLE, device, force_detach ?
DEVF_FORCE_DETACH : 0));
}
int
devctl_suspend(const char *device)
{
return (devctl_simple_request(DEV_SUSPEND, device, 0));
}
int
devctl_resume(const char *device)
{
return (devctl_simple_request(DEV_RESUME, device, 0));
}
int
devctl_set_driver(const char *device, const char *driver, bool force)
{
struct devreq req;
memset(&req, 0, sizeof(req));
if (strlcpy(req.dr_name, device, sizeof(req.dr_name)) >=
sizeof(req.dr_name)) {
errno = EINVAL;
return (-1);
}
req.dr_data = __DECONST(char *, driver);
if (force)
req.dr_flags |= DEVF_SET_DRIVER_DETACH;
return (devctl_request(DEV_SET_DRIVER, &req));
}

42
lib/libdevctl/devctl.h Normal file
View File

@ -0,0 +1,42 @@
/*-
* Copyright (c) 2014 John Baldwin <jhb@FreeBSD.org>
* 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.
*
* $FreeBSD$
*/
#ifndef __DEVCTL_H__
#define __DEVCTL_H__
#include <stdbool.h>
int devctl_attach(const char *device);
int devctl_detach(const char *device, bool force);
int devctl_enable(const char *device);
int devctl_disable(const char *device, bool force_detach);
int devctl_suspend(const char *device);
int devctl_resume(const char *device);
int devctl_set_driver(const char *device, const char *driver, bool force);
#endif /* !__DEVCTL_H__ */

View File

@ -39,6 +39,7 @@ LIBCRYPT?= ${DESTDIR}${LIBDIR}/libcrypt.a
LIBCRYPTO?= ${DESTDIR}${LIBDIR}/libcrypto.a
LIBCTF?= ${DESTDIR}${LIBDIR}/libctf.a
LIBCURSES?= ${DESTDIR}${LIBDIR}/libcurses.a
LIBDEVCTL?= ${DESTDIR}${LIBDIR}/libdevctl.a
LIBDEVINFO?= ${DESTDIR}${LIBDIR}/libdevinfo.a
LIBDEVSTAT?= ${DESTDIR}${LIBDIR}/libdevstat.a
LIBDIALOG?= ${DESTDIR}${LIBDIR}/libdialog.a

View File

@ -72,6 +72,7 @@ _LIBRARIES= \
ctf \
cuse \
cxxrt \
devctl \
devinfo \
devstat \
dialog \

View File

@ -101,6 +101,7 @@ int acpi_quirks;
/* Supported sleep states. */
static BOOLEAN acpi_sleep_states[ACPI_S_STATE_COUNT];
static void acpi_lookup(void *arg, const char *name, device_t *dev);
static int acpi_modevent(struct module *mod, int event, void *junk);
static int acpi_probe(device_t dev);
static int acpi_attach(device_t dev);
@ -671,8 +672,10 @@ acpi_attach(device_t dev)
/* Register ACPI again to pass the correct argument of pm_func. */
power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, sc);
if (!acpi_disabled("bus"))
if (!acpi_disabled("bus")) {
EVENTHANDLER_REGISTER(dev_lookup, acpi_lookup, NULL, 1000);
acpi_probe_children(dev);
}
/* Update all GPEs and enable runtime GPEs. */
status = AcpiUpdateAllGpes();
@ -3401,6 +3404,31 @@ acpi_disabled(char *subsys)
return (0);
}
static void
acpi_lookup(void *arg, const char *name, device_t *dev)
{
ACPI_HANDLE handle;
if (*dev != NULL)
return;
/*
* Allow any handle name that is specified as an absolute path and
* starts with '\'. We could restrict this to \_SB and friends,
* but see acpi_probe_children() for notes on why we scan the entire
* namespace for devices.
*
* XXX: The pathname argument to AcpiGetHandle() should be fixed to
* be const.
*/
if (name[0] != '\\')
return;
if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT, __DECONST(char *, name),
&handle)))
return;
*dev = acpi_get_device(handle);
}
/*
* Control interface.
*

View File

@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/limits.h>
#include <sys/linker.h>
#include <sys/fcntl.h>
#include <sys/conf.h>
@ -4824,8 +4825,8 @@ pci_child_location_str_method(device_t dev, device_t child, char *buf,
size_t buflen)
{
snprintf(buf, buflen, "slot=%d function=%d", pci_get_slot(child),
pci_get_function(child));
snprintf(buf, buflen, "pci%d:%d:%d:%d", pci_get_domain(child),
pci_get_bus(child), pci_get_slot(child), pci_get_function(child));
return (0);
}
@ -4855,10 +4856,60 @@ pci_assign_interrupt_method(device_t dev, device_t child)
cfg->intpin));
}
static void
pci_lookup(void *arg, const char *name, device_t *dev)
{
long val;
char *end;
int domain, bus, slot, func;
if (*dev != NULL)
return;
/*
* Accept pciconf-style selectors of either pciD:B:S:F or
* pciB:S:F. In the latter case, the domain is assumed to
* be zero.
*/
if (strncmp(name, "pci", 3) != 0)
return;
val = strtol(name + 3, &end, 10);
if (val < 0 || val > INT_MAX || *end != ':')
return;
domain = val;
val = strtol(end + 1, &end, 10);
if (val < 0 || val > INT_MAX || *end != ':')
return;
bus = val;
val = strtol(end + 1, &end, 10);
if (val < 0 || val > INT_MAX)
return;
slot = val;
if (*end == ':') {
val = strtol(end + 1, &end, 10);
if (val < 0 || val > INT_MAX || *end != '\0')
return;
func = val;
} else if (*end == '\0') {
func = slot;
slot = bus;
bus = domain;
domain = 0;
} else
return;
if (domain > PCI_DOMAINMAX || bus > PCI_BUSMAX || slot > PCI_SLOTMAX ||
func > PCIE_ARI_FUNCMAX || (slot != 0 && func > PCI_FUNCMAX))
return;
*dev = pci_find_dbsf(domain, bus, slot, func);
}
static int
pci_modevent(module_t mod, int what, void *arg)
{
static struct cdev *pci_cdev;
static eventhandler_tag tag;
switch (what) {
case MOD_LOAD:
@ -4867,9 +4918,13 @@ pci_modevent(module_t mod, int what, void *arg)
pci_cdev = make_dev(&pcicdev, 0, UID_ROOT, GID_WHEEL, 0644,
"pci");
pci_load_vendor_data();
tag = EVENTHANDLER_REGISTER(dev_lookup, pci_lookup, NULL,
1000);
break;
case MOD_UNLOAD:
if (tag != NULL)
EVENTHANDLER_DEREGISTER(dev_lookup, tag);
destroy_dev(pci_cdev);
break;
}

View File

@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$");
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/poll.h>
#include <sys/priv.h>
#include <sys/proc.h>
#include <sys/condvar.h>
#include <sys/queue.h>
@ -139,6 +140,8 @@ struct device {
static MALLOC_DEFINE(M_BUS, "bus", "Bus data structures");
static MALLOC_DEFINE(M_BUS_SC, "bus-sc", "Bus data structures, softc");
static void devctl2_init(void);
#ifdef BUS_DEBUG
static int bus_debug = 1;
@ -423,6 +426,7 @@ devinit(void)
cv_init(&devsoftc.cv, "dev cv");
TAILQ_INIT(&devsoftc.devq);
knlist_init_mtx(&devsoftc.sel.si_note, &devsoftc.mtx);
devctl2_init();
}
static int
@ -2638,6 +2642,15 @@ device_is_attached(device_t dev)
return (dev->state >= DS_ATTACHED);
}
/**
* @brief Return non-zero if the device is currently suspended.
*/
int
device_is_suspended(device_t dev)
{
return ((dev->flags & DF_SUSPENDED) != 0);
}
/**
* @brief Set the devclass of a device
* @see devclass_add_device().
@ -5022,3 +5035,253 @@ bus_free_resource(device_t dev, int type, struct resource *r)
return (0);
return (bus_release_resource(dev, type, rman_get_rid(r), r));
}
/*
* /dev/devctl2 implementation. The existing /dev/devctl device has
* implicit semantics on open, so it could not be reused for this.
* Another option would be to call this /dev/bus?
*/
static int
find_device(struct devreq *req, device_t *devp)
{
device_t dev;
/*
* First, ensure that the name is nul terminated.
*/
if (memchr(req->dr_name, '\0', sizeof(req->dr_name)) == NULL)
return (EINVAL);
/*
* Second, try to find an attached device whose name matches
* 'name'.
*/
TAILQ_FOREACH(dev, &bus_data_devices, devlink) {
if (dev->nameunit != NULL &&
strcmp(dev->nameunit, req->dr_name) == 0) {
*devp = dev;
return (0);
}
}
/* Finally, give device enumerators a chance. */
dev = NULL;
EVENTHANDLER_INVOKE(dev_lookup, req->dr_name, &dev);
if (dev == NULL)
return (ENOENT);
*devp = dev;
return (0);
}
static bool
driver_exists(struct device *bus, const char *driver)
{
devclass_t dc;
for (dc = bus->devclass; dc != NULL; dc = dc->parent) {
if (devclass_find_driver_internal(dc, driver) != NULL)
return (true);
}
return (false);
}
static int
devctl2_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag,
struct thread *td)
{
struct devreq *req;
device_t dev;
int error, old;
/* Locate the device to control. */
mtx_lock(&Giant);
req = (struct devreq *)data;
switch (cmd) {
case DEV_ATTACH:
case DEV_DETACH:
case DEV_ENABLE:
case DEV_DISABLE:
case DEV_SUSPEND:
case DEV_RESUME:
case DEV_SET_DRIVER:
error = priv_check(td, PRIV_DRIVER);
if (error == 0)
error = find_device(req, &dev);
break;
default:
error = ENOTTY;
break;
}
if (error) {
mtx_unlock(&Giant);
return (error);
}
/* Perform the requested operation. */
switch (cmd) {
case DEV_ATTACH:
if (device_is_attached(dev) && (dev->flags & DF_REBID) == 0)
error = EBUSY;
else if (!device_is_enabled(dev))
error = ENXIO;
else
error = device_probe_and_attach(dev);
break;
case DEV_DETACH:
if (!device_is_attached(dev)) {
error = ENXIO;
break;
}
if (!(req->dr_flags & DEVF_FORCE_DETACH)) {
error = device_quiesce(dev);
if (error)
break;
}
error = device_detach(dev);
break;
case DEV_ENABLE:
if (device_is_enabled(dev)) {
error = EBUSY;
break;
}
/*
* If the device has been probed but not attached (e.g.
* when it has been disabled by a loader hint), just
* attach the device rather than doing a full probe.
*/
device_enable(dev);
if (device_is_alive(dev)) {
/*
* If the device was disabled via a hint, clear
* the hint.
*/
if (resource_disabled(dev->driver->name, dev->unit))
resource_unset_value(dev->driver->name,
dev->unit, "disabled");
error = device_attach(dev);
} else
error = device_probe_and_attach(dev);
break;
case DEV_DISABLE:
if (!device_is_enabled(dev)) {
error = ENXIO;
break;
}
if (!(req->dr_flags & DEVF_FORCE_DETACH)) {
error = device_quiesce(dev);
if (error)
break;
}
/*
* Force DF_FIXEDCLASS on around detach to preserve
* the existing name.
*/
old = dev->flags;
dev->flags |= DF_FIXEDCLASS;
error = device_detach(dev);
if (!(old & DF_FIXEDCLASS))
dev->flags &= ~DF_FIXEDCLASS;
if (error == 0)
device_disable(dev);
break;
case DEV_SUSPEND:
if (device_is_suspended(dev)) {
error = EBUSY;
break;
}
if (device_get_parent(dev) == NULL) {
error = EINVAL;
break;
}
error = BUS_SUSPEND_CHILD(device_get_parent(dev), dev);
break;
case DEV_RESUME:
if (!device_is_suspended(dev)) {
error = EINVAL;
break;
}
if (device_get_parent(dev) == NULL) {
error = EINVAL;
break;
}
error = BUS_RESUME_CHILD(device_get_parent(dev), dev);
break;
case DEV_SET_DRIVER: {
devclass_t dc;
char driver[128];
error = copyinstr(req->dr_data, driver, sizeof(driver), NULL);
if (error)
break;
if (driver[0] == '\0') {
error = EINVAL;
break;
}
if (dev->devclass != NULL &&
strcmp(driver, dev->devclass->name) == 0)
/* XXX: Could possibly force DF_FIXEDCLASS on? */
break;
/*
* Scan drivers for this device's bus looking for at
* least one matching driver.
*/
if (dev->parent == NULL) {
error = EINVAL;
break;
}
if (!driver_exists(dev->parent, driver)) {
error = ENOENT;
break;
}
dc = devclass_create(driver);
if (dc == NULL) {
error = ENOMEM;
break;
}
/* Detach device if necessary. */
if (device_is_attached(dev)) {
if (req->dr_flags & DEVF_SET_DRIVER_DETACH)
error = device_detach(dev);
else
error = EBUSY;
if (error)
break;
}
/* Clear any previously-fixed device class and unit. */
if (dev->flags & DF_FIXEDCLASS)
devclass_delete_device(dev->devclass, dev);
dev->flags |= DF_WILDCARD;
dev->unit = -1;
/* Force the new device class. */
error = devclass_add_device(dc, dev);
if (error)
break;
dev->flags |= DF_FIXEDCLASS;
error = device_probe_and_attach(dev);
break;
}
}
mtx_unlock(&Giant);
return (error);
}
static struct cdevsw devctl2_cdevsw = {
.d_version = D_VERSION,
.d_ioctl = devctl2_ioctl,
.d_name = "devctl2",
};
static void
devctl2_init(void)
{
make_dev_credf(MAKEDEV_ETERNAL, &devctl2_cdevsw, 0, NULL,
UID_ROOT, GID_WHEEL, 0600, "devctl2");
}

View File

@ -461,3 +461,31 @@ resource_disabled(const char *name, int unit)
return (0);
return (value);
}
/*
* Clear a value associated with a device by removing it from
* the kernel environment. This only removes a hint for an
* exact unit.
*/
int
resource_unset_value(const char *name, int unit, const char *resname)
{
char varname[128];
const char *retname, *retvalue;
int error, line;
size_t len;
line = 0;
error = resource_find(&line, NULL, name, &unit, resname, NULL,
&retname, NULL, NULL, NULL, NULL, &retvalue);
if (error)
return (error);
retname -= strlen("hint.");
len = retvalue - retname - 1;
if (len > sizeof(varname) - 1)
return (ENAMETOOLONG);
memcpy(varname, retname, len);
varname[len] = '\0';
return (kern_unsetenv(varname));
}

View File

@ -31,6 +31,7 @@
#include <machine/_limits.h>
#include <sys/_bus_dma.h>
#include <sys/ioccom.h>
/**
* @defgroup NEWBUS newbus - a generic framework for managing devices
@ -86,9 +87,45 @@ struct u_device {
#define DF_REBID 0x80 /* Can rebid after attach */
#define DF_SUSPENDED 0x100 /* Device is suspended. */
/**
* @brief Device request structure used for ioctl's.
*
* Used for ioctl's on /dev/devctl2. All device ioctl's
* must have parameter definitions which begin with dr_name.
*/
struct devreq_buffer {
void *buffer;
size_t length;
};
struct devreq {
char dr_name[128];
int dr_flags; /* request-specific flags */
union {
struct devreq_buffer dru_buffer;
void *dru_data;
} dr_dru;
#define dr_buffer dr_dru.dru_buffer /* variable-sized buffer */
#define dr_data dr_dru.dru_data /* fixed-size buffer */
};
#define DEV_ATTACH _IOW('D', 1, struct devreq)
#define DEV_DETACH _IOW('D', 2, struct devreq)
#define DEV_ENABLE _IOW('D', 3, struct devreq)
#define DEV_DISABLE _IOW('D', 4, struct devreq)
#define DEV_SUSPEND _IOW('D', 5, struct devreq)
#define DEV_RESUME _IOW('D', 6, struct devreq)
#define DEV_SET_DRIVER _IOW('D', 7, struct devreq)
/* Flags for DEV_DETACH and DEV_DISABLE. */
#define DEVF_FORCE_DETACH 0x0000001
/* Flags for DEV_SET_DRIVER. */
#define DEVF_SET_DRIVER_DETACH 0x0000001 /* Detach existing driver. */
#ifdef _KERNEL
#include <sys/queue.h>
#include <sys/eventhandler.h>
#include <sys/kobj.h>
/**
@ -104,6 +141,14 @@ void devctl_notify(const char *__system, const char *__subsystem,
void devctl_queue_data_f(char *__data, int __flags);
void devctl_queue_data(char *__data);
/**
* Device name parsers. Hook to allow device enumerators to map
* scheme-specific names to a device.
*/
typedef void (*dev_lookup_fn)(void *arg, const char *name,
device_t *result);
EVENTHANDLER_DECLARE(dev_lookup, dev_lookup_fn);
/**
* @brief A device driver (included mainly for compatibility with
* FreeBSD 4.x).
@ -465,6 +510,7 @@ struct sysctl_oid *device_get_sysctl_tree(device_t dev);
int device_is_alive(device_t dev); /* did probe succeed? */
int device_is_attached(device_t dev); /* did attach succeed? */
int device_is_enabled(device_t dev);
int device_is_suspended(device_t dev);
int device_is_quiet(device_t dev);
int device_print_prettyname(device_t dev);
int device_printf(device_t dev, const char *, ...) __printflike(2, 3);
@ -528,6 +574,8 @@ int resource_set_long(const char *name, int unit, const char *resname,
long value);
int resource_set_string(const char *name, int unit, const char *resname,
const char *value);
int resource_unset_value(const char *name, int unit, const char *resname);
/*
* Functions for maintaining and checking consistency of
* bus information exported to userspace.

View File

@ -19,6 +19,7 @@ SUBDIR= adduser \
ctld \
daemon \
dconschat \
devctl \
devinfo \
digictl \
diskinfo \

9
usr.sbin/devctl/Makefile Normal file
View File

@ -0,0 +1,9 @@
# $FreeBSD$
PROG= devctl
MAN= devctl.8
MAN=
LIBADD= devctl
.include <bsd.prog.mk>

137
usr.sbin/devctl/devctl.8 Normal file
View File

@ -0,0 +1,137 @@
.\"
.\" Copyright (c) 2015 John Baldwin <jhb@FreeBSD.org>
.\" 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.
.\"
.\" $FreeBSD$
.\"
.Dd February 5, 2015
.Dt DEVCTL 8
.Os
.Sh NAME
.Nm devctl
.Nd device control utility
.Sh SYNOPSIS
.Nm
.Cm attach
.Ar device
.Nm
.Cm detach
.Op Fl f
.Ar device
.Nm
.Cm disable
.Op Fl f
.Ar device
.Nm
.Cm enable
.Ar device
.Nm
.Cm suspend
.Ar device
.Nm
.Cm resume
.Ar device
.Nm
.Cm set driver
.Op Fl f
.Ar device driver
.Sh DESCRIPTION
The
.Nm
utility adjusts the state of individual devices in the kernel's
internal device hierarchy.
Each invocation of
.Nm
consists of a single command followed by command-specific arguments.
Each command operates on a single device specified via the
.Ar device
argument.
The
.Ar device
may be specified either as the name of an existing device or as a
bus-specific address.
More details on supported address formats can be found in
.Xr devctl 3 .
.Pp
The following commands are supported:
.Bl -tag -width indent
.It Cm attach Ar device
Force the kernel to re-probe the device.
If a suitable driver is found,
it is attached to the device.
.It Xo Cm detach
.Op Fl f
.Ar device
.Xc
Detach the device from its current device driver.
If the
.Fl f
flag is specified,
the device driver will be detached even if the device is busy.
.It Xo Cm disable
.Op Fl f
.Ar device
.Xc
Disable a device.
If the device is currently attached to a device driver,
the device driver will be detached from the device,
but the device will retain its current name.
If the
.Fl f
flag is specified,
the device driver will be detached even if the device is busy.
.It Cm enable Ar device
Enable a device.
The device will probe and attach if a suitable device driver is found.
Note that this can re-enable a device disabled at boot time via a
loader tunable.
.It Cm suspend Ar device
Suspend a device.
This may include placing the device in a reduced power state.
.It Cm resume device
Resume a suspended device to a fully working state.
.It Xo Cm set driver
.Op Fl f
.Ar device driver
.Xc
Force the device to use a device driver named
.Ar driver .
If the device is already attached to a device driver and the
.Fl f
flag is specified,
the device will be detached from its current device driver before it is
attached to the new device driver.
If the device is already attached to a device driver and the
.Fl f
flag is not specified,
the device will not be changed.
.El
.Sh SEE ALSO
.Xr devctl 3 ,
.Xr devinfo 8
.Sh HISTORY
The
.Nm
utility first appeared in
.Fx 11.0 .

282
usr.sbin/devctl/devctl.c Normal file
View File

@ -0,0 +1,282 @@
/*-
* Copyright (c) 2014 John Baldwin <jhb@FreeBSD.org>
* 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>
__FBSDID("$FreeBSD$");
#include <sys/linker_set.h>
#include <devctl.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
struct devctl_command {
const char *name;
int (*handler)(int ac, char **av);
};
#define DEVCTL_DATASET(name) devctl_ ## name ## _table
#define DEVCTL_COMMAND(set, name, function) \
static struct devctl_command function ## _devctl_command = \
{ #name, function }; \
DATA_SET(DEVCTL_DATASET(set), function ## _devctl_command)
#define DEVCTL_TABLE(set, name) \
SET_DECLARE(DEVCTL_DATASET(name), struct devctl_command); \
\
static int \
devctl_ ## name ## _table_handler(int ac, char **av) \
{ \
return (devctl_table_handler(SET_BEGIN(DEVCTL_DATASET(name)), \
SET_LIMIT(DEVCTL_DATASET(name)), ac, av)); \
} \
DEVCTL_COMMAND(set, name, devctl_ ## name ## _table_handler)
static int devctl_table_handler(struct devctl_command **start,
struct devctl_command **end, int ac, char **av);
SET_DECLARE(DEVCTL_DATASET(top), struct devctl_command);
DEVCTL_TABLE(top, set);
static void
usage(void)
{
fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
"usage: devctl attach device",
" devctl detach [-f] device",
" devctl disable [-f] device",
" devctl enable device",
" devctl suspend device",
" devctl resume device",
" devctl set driver [-f] device driver");
exit(1);
}
static int
devctl_table_handler(struct devctl_command **start,
struct devctl_command **end, int ac, char **av)
{
struct devctl_command **cmd;
if (ac < 2) {
warnx("The %s command requires a sub-command.", av[0]);
return (EINVAL);
}
for (cmd = start; cmd < end; cmd++) {
if (strcmp((*cmd)->name, av[1]) == 0)
return ((*cmd)->handler(ac - 1, av + 1));
}
warnx("%s is not a valid sub-command of %s.", av[1], av[0]);
return (ENOENT);
}
static int
help(int ac __unused, char **av __unused)
{
usage();
return (0);
}
DEVCTL_COMMAND(top, help, help);
static int
attach(int ac, char **av)
{
if (ac != 2)
usage();
if (devctl_attach(av[1]) < 0)
err(1, "Failed to attach %s", av[1]);
return (0);
}
DEVCTL_COMMAND(top, attach, attach);
static void
detach_usage(void)
{
fprintf(stderr, "usage: devctl detach [-f] device\n");
exit(1);
}
static int
detach(int ac, char **av)
{
bool force;
int ch;
force = false;
while ((ch = getopt(ac, av, "f")) != -1)
switch (ch) {
case 'f':
force = true;
break;
default:
detach_usage();
}
ac -= optind;
av += optind;
if (ac != 1)
detach_usage();
if (devctl_detach(av[0], force) < 0)
err(1, "Failed to detach %s", av[0]);
return (0);
}
DEVCTL_COMMAND(top, detach, detach);
static void
disable_usage(void)
{
fprintf(stderr, "usage: devctl disable [-f] device\n");
exit(1);
}
static int
disable(int ac, char **av)
{
bool force;
int ch;
force = false;
while ((ch = getopt(ac, av, "f")) != -1)
switch (ch) {
case 'f':
force = true;
break;
default:
disable_usage();
}
ac -= optind;
av += optind;
if (ac != 1)
disable_usage();
if (devctl_disable(av[0], force) < 0)
err(1, "Failed to disable %s", av[0]);
return (0);
}
DEVCTL_COMMAND(top, disable, disable);
static int
enable(int ac, char **av)
{
if (ac != 2)
usage();
if (devctl_enable(av[1]) < 0)
err(1, "Failed to enable %s", av[1]);
return (0);
}
DEVCTL_COMMAND(top, enable, enable);
static int
suspend(int ac, char **av)
{
if (ac != 2)
usage();
if (devctl_suspend(av[1]) < 0)
err(1, "Failed to suspend %s", av[1]);
return (0);
}
DEVCTL_COMMAND(top, suspend, suspend);
static int
resume(int ac, char **av)
{
if (ac != 2)
usage();
if (devctl_resume(av[1]) < 0)
err(1, "Failed to resume %s", av[1]);
return (0);
}
DEVCTL_COMMAND(top, resume, resume);
static void
set_driver_usage(void)
{
fprintf(stderr, "usage: devctl set driver [-f] device driver\n");
exit(1);
}
static int
set_driver(int ac, char **av)
{
bool force;
int ch;
force = false;
while ((ch = getopt(ac, av, "f")) != -1)
switch (ch) {
case 'f':
force = true;
break;
default:
set_driver_usage();
}
ac -= optind;
av += optind;
if (ac != 2)
set_driver_usage();
if (devctl_set_driver(av[0], av[1], force) < 0)
err(1, "Failed to set %s driver to %s", av[0], av[1]);
return (0);
}
DEVCTL_COMMAND(set, driver, set_driver);
int
main(int ac, char *av[])
{
struct devctl_command **cmd;
if (ac == 1)
usage();
ac--;
av++;
SET_FOREACH(cmd, DEVCTL_DATASET(top)) {
if (strcmp((*cmd)->name, av[0]) == 0) {
if ((*cmd)->handler(ac, av) != 0)
return (1);
else
return (0);
}
}
warnx("Unknown command %s.", av[0]);
return (1);
}