From 56fe29cbbf9c201582b6c8059459ca22a08ecb2e Mon Sep 17 00:00:00 2001 From: imp Date: Mon, 7 Oct 2002 23:17:44 +0000 Subject: Introducing /dev/devctl. This device reports events in the configuration device hierarchy. Device arrival, departure and not matched are presently reported. This will be the basis for devd, which I still need to polish a little more before I commit it. If you don't use /dev/devctl, it will be a noop. --- sys/kern/subr_bus.c | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 326 insertions(+), 2 deletions(-) (limited to 'sys/kern/subr_bus.c') diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c index 410548b..f40839b 100644 --- a/sys/kern/subr_bus.c +++ b/sys/kern/subr_bus.c @@ -29,15 +29,25 @@ #include "opt_bus.h" #include +#include +#include +#include #include #include #include #include +#include +#include +#include +#include #include #include #include +#include +#include #include #include +#include #include #include @@ -169,6 +179,316 @@ void print_devclass_list(void); #define print_devclass_list() /* nop */ #endif +/* + * /dev/devctl implementation + */ + +/* + * This design allows only one reader for /dev/devctl. This is not desirable + * in the long run, but will get a lot of hair out of this implementation. + * Maybe we should make this device a clonable device. + * + * Also note: we specifically do not attach a device to the device_t tree + * to avoid potential chicken and egg problems. One could argue that all + * of this belongs to the root node. One could also further argue that the + * sysctl interface that we have not might more properly be a ioctl + * interface, but at this stage of the game, I'm not inclinde to rock that + * boat. + * + * I'm also not sure that the SIGIO support is done correctly or not, as + * I copied it from a driver that had SIGIO support that likely hasn't been + * tested since 3.4 or 2.2.8! + */ + +static d_open_t devopen; +static d_close_t devclose; +static d_read_t devread; +static d_ioctl_t devioctl; +static d_poll_t devpoll; + +#define CDEV_MAJOR 173 +static struct cdevsw dev_cdevsw = { + /* open */ devopen, + /* close */ devclose, + /* read */ devread, + /* write */ nowrite, + /* ioctl */ devioctl, + /* poll */ devpoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "devctl", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +struct dev_event_info +{ + char *dei_data; + TAILQ_ENTRY(dev_event_info) dei_link; +}; + +TAILQ_HEAD(devq, dev_event_info); + +struct dev_softc +{ + int inuse; + int nonblock; + int async; + struct mtx mtx; + struct cv cv; + struct selinfo sel; + struct devq devq; + d_thread_t *async_td; +} devsoftc; + +dev_t devctl_dev; + +static void +devinit(void) +{ + devctl_dev = make_dev(&dev_cdevsw, 0, 0, 0, 0644, "devctl"); + mtx_init(&devsoftc.mtx, "dev mtx", "devd", MTX_DEF); + cv_init(&devsoftc.cv, "dev cv"); + TAILQ_INIT(&devsoftc.devq); +} + +static int +devopen(dev_t dev, int oflags, int devtype, d_thread_t *td) +{ + if (devsoftc.inuse) + return (EBUSY); + /* move to init */ + devsoftc.inuse = 1; + return (0); +} + +static int +devclose(dev_t dev, int fflag, int devtype, d_thread_t *td) +{ + struct dev_event_info *n1; + + devsoftc.inuse = 0; + mtx_lock(&devsoftc.mtx); + cv_broadcast(&devsoftc.cv); + /* + * See note in devread. If we deside to keep data until read, then + * remove the following while loop. XXX + */ + while (!TAILQ_EMPTY(&devsoftc.devq)) { + n1 = TAILQ_FIRST(&devsoftc.devq); + TAILQ_REMOVE(&devsoftc.devq, n1, dei_link); + free(n1->dei_data, M_BUS); + free(n1, M_BUS); + } + mtx_unlock(&devsoftc.mtx); + + return (0); +} + +/* + * The read channel for this device is used to report changes to + * userland in realtime. We are required to free the data as well as + * the n1 object because we allocate them separately. Also note that + * we return one record at a time. If you try to read this device a + * character at a time, you will loose the rest of the data. Listening + * programs are expected to cope. + */ +static int +devread(dev_t dev, struct uio *uio, int ioflag) +{ + struct dev_event_info *n1; + int rv; + + mtx_lock(&devsoftc.mtx); + while (TAILQ_EMPTY(&devsoftc.devq)) { + if (devsoftc.nonblock) { + mtx_unlock(&devsoftc.mtx); + return (EAGAIN); + } + rv = cv_wait_sig(&devsoftc.cv, &devsoftc.mtx); + if (rv) { + /* + * Need to translate ERESTART to EINTR here? -- jake + */ + mtx_unlock(&devsoftc.mtx); + return (rv); + } + } + n1 = TAILQ_FIRST(&devsoftc.devq); + TAILQ_REMOVE(&devsoftc.devq, n1, dei_link); + mtx_unlock(&devsoftc.mtx); + rv = uiomove(n1->dei_data, strlen(n1->dei_data), uio); + free(n1->dei_data, M_BUS); + free(n1, M_BUS); + return (rv); +} + +static int +devioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, d_thread_t *td) +{ + switch (cmd) { + + case FIONBIO: + if (*(int*)data) + devsoftc.nonblock = 1; + else + devsoftc.nonblock = 0; + return (0); + case FIOASYNC: + if (*(int*)data) { + devsoftc.async = 1; + devsoftc.async_td = td; + } + else { + devsoftc.async = 0; + devsoftc.async_td = NULL; + } + return (0); + + /* (un)Support for other fcntl() calls. */ + case FIOCLEX: + case FIONCLEX: + case FIONREAD: + case FIOSETOWN: + case FIOGETOWN: + default: + break; + } + return (ENOTTY); +} + +static int +devpoll(dev_t dev, int events, d_thread_t *td) +{ + int revents = 0; + + if (events & (POLLIN | POLLRDNORM)) + revents |= events & (POLLIN | POLLRDNORM); + + if (events & (POLLOUT | POLLWRNORM)) + revents |= events & (POLLOUT | POLLWRNORM); + + mtx_lock(&devsoftc.mtx); + if (events & POLLRDBAND) + if (!TAILQ_EMPTY(&devsoftc.devq)) + revents |= POLLRDBAND; + mtx_unlock(&devsoftc.mtx); + + if (revents == 0) + selrecord(td, &devsoftc.sel); + + return (revents); +} + +/* + * Common routine that tries to make sending messages as easy as possible. + * We allocate memory for the data, copy strings into that, but do not + * free it unless there's an error. The dequeue part of the driver should + * free the data. We do not send any data if there is no listeners on the + * /dev/devctl device. We assume that on startup, any program that wishes + * to do things based on devices that have attached before it starts will + * query the tree to find out its current state. This decision may + * be revisited if there are difficulties determining if one should do an + * action or not (eg, are all actions that the listening program idempotent + * or not). This may also open up races as well (say if the listener + * dies just before a device goes away, and is run again just after, no + * detach action would happen). The flip side would be that we'd need to + * limit the size of the queue because otherwise if no listener is running + * then we'd have unbounded growth. Most systems have less than 100 (maybe + * even less than 50) devices, so maybe a limit of 200 or 300 wouldn't be + * too horrible. XXX + */ +static void +devaddq(const char *type, const char *what, device_t dev) +{ + struct dev_event_info *n1 = NULL; + char *data = NULL; + char *loc; + const char *parstr; + + if (!devsoftc.inuse) + return; + n1 = malloc(sizeof(*n1), M_BUS, M_NOWAIT); + if (n1 == NULL) + goto bad; + data = malloc(1024, M_BUS, M_NOWAIT); + if (data == NULL) + goto bad; + loc = malloc(1024, M_BUS, M_NOWAIT); + if (loc == NULL) + goto bad; + *loc = '\0'; + bus_child_location_str(dev, loc, 1024); + if (device_get_parent(dev) == NULL) + parstr = "."; /* Or '/' ? */ + else + parstr = device_get_nameunit(device_get_parent(dev)); + snprintf(data, 1024, "%s%s at %s on %s\n", type, what, loc, parstr); + free(loc, M_BUS); + n1->dei_data = data; + mtx_lock(&devsoftc.mtx); + TAILQ_INSERT_TAIL(&devsoftc.devq, n1, dei_link); + cv_broadcast(&devsoftc.cv); + mtx_unlock(&devsoftc.mtx); + selwakeup(&devsoftc.sel); + if (devsoftc.async_td) + psignal(devsoftc.async_td->td_proc, SIGIO); + return; +bad:; + free(data, M_BUS); + free(n1, M_BUS); + return; +} + +/* + * A device was added to the tree. We are called just after it successfully + * attaches (that is, probe and attach success for this device). No call + * is made if a device is merely parented into the tree. See devnomatch + * if probe fails. If attach fails, no notification is sent (but maybe + * we should have a different message for this). + */ +static void +devadded(device_t dev) +{ + devaddq("+", device_get_nameunit(dev), dev); +} + +/* + * A device was removed from the tree. We are called just before this + * happens. + */ +static void +devremoved(device_t dev) +{ + devaddq("-", device_get_nameunit(dev), dev); +} + +/* + * Called when there's no match for this device. This is only called + * the first time that no match happens, so we don't keep getitng this + * message. Should that prove to be undesirable, we can change it. + * This is called when all drivers that can attach to a given bus + * decline to accept this device. Other errrors may not be detected. + */ +static void +devnomatch(device_t dev) +{ + char *pnp = NULL; + + pnp = malloc(1024, M_BUS, M_NOWAIT); + if (pnp == NULL) + return; + *pnp = '\0'; + bus_child_pnpinfo_str(dev, pnp, 1024); + devaddq("?", pnp, dev); + free(pnp, M_BUS); + return; +} + +/* End of /dev/devctl code */ + TAILQ_HEAD(,device) bus_data_devices; static int bus_data_generation = 1; @@ -1108,9 +1428,10 @@ device_probe_and_attach(device_t dev) if (!device_is_quiet(dev)) device_print_child(bus, dev); error = DEVICE_ATTACH(dev); - if (!error) + if (!error) { dev->state = DS_ATTACHED; - else { + devadded(dev); + } else { printf("device_probe_and_attach: %s%d attach returned %d\n", dev->driver->name, dev->unit, error); /* Unset the class; set in device_probe_child */ @@ -1122,6 +1443,7 @@ device_probe_and_attach(device_t dev) } else { if (!(dev->flags & DF_DONENOMATCH)) { BUS_PROBE_NOMATCH(bus, dev); + devnomatch(dev); dev->flags |= DF_DONENOMATCH; } } @@ -1148,6 +1470,7 @@ device_detach(device_t dev) if ((error = DEVICE_DETACH(dev)) != 0) return (error); + devremoved(dev); device_printf(dev, "detached\n"); if (dev->parent) BUS_CHILD_DETACHED(dev->parent, dev); @@ -1889,6 +2212,7 @@ root_bus_module_handler(module_t mod, int what, void* arg) root_bus->driver = &root_driver; root_bus->state = DS_ATTACHED; root_devclass = devclass_find_internal("root", FALSE); + devinit(); return (0); case MOD_SHUTDOWN: -- cgit v1.1