#include <sys/cdefs.h>
#include "opt_bus.h"
#include "opt_ddb.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/eventhandler.h>
#include <sys/filio.h>
#include <sys/lock.h>
#include <sys/kernel.h>
#include <sys/malloc.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>
#include <machine/bus.h>
#include <sys/sbuf.h>
#include <sys/selinfo.h>
#include <sys/smp.h>
#include <sys/stdarg.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/bus.h>
#include <machine/cpu.h>
#include <vm/uma.h>
#include <vm/vm.h>
#include <ddb/ddb.h>
STAILQ_HEAD(devq, dev_event_info);
static struct dev_softc {
int inuse;
int nonblock;
int queued;
int async;
struct mtx mtx;
struct cv cv;
struct selinfo sel;
struct devq devq;
struct sigio *sigio;
uma_zone_t zone;
} devsoftc;
#define DEVCTL_DEFAULT_QUEUE_LEN 1000
static int sysctl_devctl_queue(SYSCTL_HANDLER_ARGS);
static int devctl_queue_length = DEVCTL_DEFAULT_QUEUE_LEN;
SYSCTL_PROC(_hw_bus, OID_AUTO, devctl_queue, CTLTYPE_INT | CTLFLAG_RWTUN |
CTLFLAG_MPSAFE, NULL, 0, sysctl_devctl_queue, "I", "devctl queue length");
static bool nomatch_enabled = true;
SYSCTL_BOOL(_hw_bus, OID_AUTO, devctl_nomatch_enabled, CTLFLAG_RWTUN,
&nomatch_enabled, 0, "enable nomatch events");
static void devctl_attach_handler(void *arg __unused, device_t dev);
static void devctl_detach_handler(void *arg __unused, device_t dev,
enum evhdev_detach state);
static void devctl_nomatch_handler(void *arg __unused, device_t dev);
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;
static d_kqfilter_t devkqfilter;
#define DEVCTL_BUFFER (1024 - sizeof(void *))
struct dev_event_info {
STAILQ_ENTRY(dev_event_info) dei_link;
char dei_data[DEVCTL_BUFFER];
};
static struct cdevsw dev_cdevsw = {
.d_version = D_VERSION,
.d_open = devopen,
.d_close = devclose,
.d_read = devread,
.d_ioctl = devioctl,
.d_poll = devpoll,
.d_kqfilter = devkqfilter,
.d_name = "devctl",
};
static void filt_devctl_detach(struct knote *kn);
static int filt_devctl_read(struct knote *kn, long hint);
static const struct filterops devctl_rfiltops = {
.f_isfd = 1,
.f_detach = filt_devctl_detach,
.f_event = filt_devctl_read,
};
static struct cdev *devctl_dev;
static void devaddq(const char *type, const char *what, device_t dev);
static struct devctlbridge {
send_event_f *send_f;
} devctl_notify_hook = { .send_f = NULL };
static void
devctl_init(void)
{
int reserve;
uma_zone_t z;
devctl_dev = make_dev_credf(MAKEDEV_ETERNAL, &dev_cdevsw, 0, NULL,
UID_ROOT, GID_WHEEL, 0600, "devctl");
mtx_init(&devsoftc.mtx, "dev mtx", "devd", MTX_DEF);
cv_init(&devsoftc.cv, "dev cv");
STAILQ_INIT(&devsoftc.devq);
knlist_init_mtx(&devsoftc.sel.si_note, &devsoftc.mtx);
if (devctl_queue_length > 0) {
z = devsoftc.zone = uma_zcreate("DEVCTL",
sizeof(struct dev_event_info), NULL, NULL, NULL, NULL,
UMA_ALIGN_PTR, 0);
reserve = max(devctl_queue_length / 50, 100);
uma_zone_set_max(z, devctl_queue_length);
uma_zone_set_maxcache(z, 0);
uma_zone_reserve(z, reserve);
uma_prealloc(z, reserve);
}
EVENTHANDLER_REGISTER(device_attach, devctl_attach_handler,
NULL, EVENTHANDLER_PRI_LAST);
EVENTHANDLER_REGISTER(device_detach, devctl_detach_handler,
NULL, EVENTHANDLER_PRI_LAST);
EVENTHANDLER_REGISTER(device_nomatch, devctl_nomatch_handler,
NULL, EVENTHANDLER_PRI_LAST);
}
SYSINIT(devctl_init, SI_SUB_DRIVERS, SI_ORDER_SECOND, devctl_init, NULL);
static void
devctl_attach_handler(void *arg __unused, device_t dev)
{
devaddq("+", device_get_nameunit(dev), dev);
}
static void
devctl_detach_handler(void *arg __unused, device_t dev, enum evhdev_detach state)
{
if (state == EVHDEV_DETACH_COMPLETE)
devaddq("-", device_get_nameunit(dev), dev);
}
static void
devctl_nomatch_handler(void *arg __unused, device_t dev)
{
if (nomatch_enabled)
devaddq("?", "", dev);
}
static int
devopen(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
mtx_lock(&devsoftc.mtx);
if (devsoftc.inuse) {
mtx_unlock(&devsoftc.mtx);
return (EBUSY);
}
devsoftc.inuse = 1;
mtx_unlock(&devsoftc.mtx);
return (0);
}
static int
devclose(struct cdev *dev, int fflag, int devtype, struct thread *td)
{
mtx_lock(&devsoftc.mtx);
devsoftc.inuse = 0;
devsoftc.nonblock = 0;
devsoftc.async = 0;
cv_broadcast(&devsoftc.cv);
funsetown(&devsoftc.sigio);
mtx_unlock(&devsoftc.mtx);
return (0);
}
static int
devread(struct cdev *dev, struct uio *uio, int ioflag)
{
struct dev_event_info *n1;
int rv;
mtx_lock(&devsoftc.mtx);
while (STAILQ_EMPTY(&devsoftc.devq)) {
if (devsoftc.nonblock) {
mtx_unlock(&devsoftc.mtx);
return (EAGAIN);
}
rv = cv_wait_sig(&devsoftc.cv, &devsoftc.mtx);
if (rv) {
mtx_unlock(&devsoftc.mtx);
return (rv);
}
}
n1 = STAILQ_FIRST(&devsoftc.devq);
STAILQ_REMOVE_HEAD(&devsoftc.devq, dei_link);
devsoftc.queued--;
mtx_unlock(&devsoftc.mtx);
rv = uiomove(n1->dei_data, strlen(n1->dei_data), uio);
uma_zfree(devsoftc.zone, n1);
return (rv);
}
static int
devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *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;
else
devsoftc.async = 0;
return (0);
case FIOSETOWN:
return fsetown(*(int *)data, &devsoftc.sigio);
case FIOGETOWN:
*(int *)data = fgetown(&devsoftc.sigio);
return (0);
case FIOCLEX:
case FIONCLEX:
case FIONREAD:
default:
break;
}
return (ENOTTY);
}
static int
devpoll(struct cdev *dev, int events, struct thread *td)
{
int revents = 0;
mtx_lock(&devsoftc.mtx);
if (events & (POLLIN | POLLRDNORM)) {
if (!STAILQ_EMPTY(&devsoftc.devq))
revents = events & (POLLIN | POLLRDNORM);
else
selrecord(td, &devsoftc.sel);
}
mtx_unlock(&devsoftc.mtx);
return (revents);
}
static int
devkqfilter(struct cdev *dev, struct knote *kn)
{
int error;
if (kn->kn_filter == EVFILT_READ) {
kn->kn_fop = &devctl_rfiltops;
knlist_add(&devsoftc.sel.si_note, kn, 0);
error = 0;
} else
error = EINVAL;
return (error);
}
static void
filt_devctl_detach(struct knote *kn)
{
knlist_remove(&devsoftc.sel.si_note, kn, 0);
}
static int
filt_devctl_read(struct knote *kn, long hint)
{
kn->kn_data = devsoftc.queued;
return (kn->kn_data != 0);
}
bool
devctl_process_running(void)
{
return (devsoftc.inuse == 1);
}
static struct dev_event_info *
devctl_alloc_dei(void)
{
struct dev_event_info *dei = NULL;
mtx_lock(&devsoftc.mtx);
if (devctl_queue_length == 0)
goto out;
dei = uma_zalloc(devsoftc.zone, M_NOWAIT);
if (dei == NULL)
dei = uma_zalloc(devsoftc.zone, M_NOWAIT | M_USE_RESERVE);
if (dei == NULL) {
if (devsoftc.queued == 0)
goto out;
dei = STAILQ_FIRST(&devsoftc.devq);
STAILQ_REMOVE_HEAD(&devsoftc.devq, dei_link);
devsoftc.queued--;
}
MPASS(dei != NULL);
*dei->dei_data = '\0';
out:
mtx_unlock(&devsoftc.mtx);
return (dei);
}
static struct dev_event_info *
devctl_alloc_dei_sb(struct sbuf *sb)
{
struct dev_event_info *dei;
dei = devctl_alloc_dei();
if (dei != NULL)
sbuf_new(sb, dei->dei_data, sizeof(dei->dei_data), SBUF_FIXEDLEN);
return (dei);
}
static void
devctl_free_dei(struct dev_event_info *dei)
{
uma_zfree(devsoftc.zone, dei);
}
static void
devctl_queue(struct dev_event_info *dei)
{
mtx_lock(&devsoftc.mtx);
STAILQ_INSERT_TAIL(&devsoftc.devq, dei, dei_link);
devsoftc.queued++;
cv_broadcast(&devsoftc.cv);
KNOTE_LOCKED(&devsoftc.sel.si_note, 0);
mtx_unlock(&devsoftc.mtx);
selwakeup(&devsoftc.sel);
if (devsoftc.async && devsoftc.sigio != NULL)
pgsigio(&devsoftc.sigio, SIGIO, 0);
}
void
devctl_notify(const char *system, const char *subsystem, const char *type,
const char *data)
{
struct dev_event_info *dei;
struct sbuf sb;
if (system == NULL || subsystem == NULL || type == NULL)
return;
if (devctl_notify_hook.send_f != NULL)
devctl_notify_hook.send_f(system, subsystem, type, data);
dei = devctl_alloc_dei_sb(&sb);
if (dei == NULL)
return;
sbuf_cpy(&sb, "!system=");
sbuf_cat(&sb, system);
sbuf_cat(&sb, " subsystem=");
sbuf_cat(&sb, subsystem);
sbuf_cat(&sb, " type=");
sbuf_cat(&sb, type);
if (data != NULL) {
sbuf_putc(&sb, ' ');
sbuf_cat(&sb, data);
}
sbuf_putc(&sb, '\n');
if (sbuf_finish(&sb) != 0)
devctl_free_dei(dei);
else
devctl_queue(dei);
}
static void
devaddq(const char *type, const char *what, device_t dev)
{
struct dev_event_info *dei;
const char *parstr;
struct sbuf sb;
size_t beginlen;
dei = devctl_alloc_dei_sb(&sb);
if (dei == NULL)
return;
sbuf_cpy(&sb, type);
sbuf_cat(&sb, what);
sbuf_cat(&sb, " at ");
beginlen = sbuf_len(&sb);
bus_child_location(dev, &sb);
sbuf_putc(&sb, ' ');
bus_child_pnpinfo(dev, &sb);
if (device_get_parent(dev) == NULL)
parstr = ".";
else
parstr = device_get_nameunit(device_get_parent(dev));
sbuf_cat(&sb, " on ");
sbuf_cat(&sb, parstr);
sbuf_putc(&sb, '\n');
if (sbuf_finish(&sb) != 0)
goto bad;
if (devctl_notify_hook.send_f != NULL) {
const char *t;
switch (*type) {
case '+':
t = "ATTACH";
break;
case '-':
t = "DETACH";
break;
default:
t = "NOMATCH";
break;
}
devctl_notify_hook.send_f("device",
what, t, sbuf_data(&sb) + beginlen);
}
devctl_queue(dei);
return;
bad:
devctl_free_dei(dei);
}
static int
sysctl_devctl_queue(SYSCTL_HANDLER_ARGS)
{
int q, error;
q = devctl_queue_length;
error = sysctl_handle_int(oidp, &q, 0, req);
if (error || !req->newptr)
return (error);
if (q < 0)
return (EINVAL);
if (!mtx_initialized(&devsoftc.mtx)) {
devctl_queue_length = q;
return (0);
}
if (q != 0)
return (EINVAL);
if (q == devctl_queue_length)
return (0);
mtx_lock(&devsoftc.mtx);
devctl_queue_length = 0;
uma_zdestroy(devsoftc.zone);
devsoftc.zone = 0;
mtx_unlock(&devsoftc.mtx);
return (0);
}
void
devctl_safe_quote_sb(struct sbuf *sb, const char *src)
{
while (*src != '\0') {
if (*src == '"' || *src == '\\')
sbuf_putc(sb, '\\');
sbuf_putc(sb, *src++);
}
}
void
devctl_set_notify_hook(send_event_f *hook)
{
devctl_notify_hook.send_f = hook;
}
void
devctl_unset_notify_hook(void)
{
devctl_notify_hook.send_f = NULL;
}