diff options
Diffstat (limited to 'sys/kern/subr_bus.c')
-rw-r--r-- | sys/kern/subr_bus.c | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c index bb6806c..faf4aa0 100644 --- a/sys/kern/subr_bus.c +++ b/sys/kern/subr_bus.c @@ -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> @@ -147,6 +148,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; @@ -434,6 +437,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 @@ -4998,3 +5002,229 @@ 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_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_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"); +} |