src/sys/dev/hotplug.c

258 lines
5.3 KiB
C

/* $OpenBSD: hotplug.c,v 1.24 2023/09/22 22:12:32 mvs Exp $ */
/*
* Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Device attachment and detachment notifications.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/event.h>
#include <sys/fcntl.h>
#include <sys/hotplug.h>
#include <sys/ioctl.h>
#include <sys/mutex.h>
#include <sys/vnode.h>
#define HOTPLUG_MAXEVENTS 64
/*
* Locks used to protect struct members and global data
* M hotplug_mtx
*/
static struct mutex hotplug_mtx = MUTEX_INITIALIZER(IPL_MPFLOOR);
static int opened;
static struct hotplug_event evqueue[HOTPLUG_MAXEVENTS];
static int evqueue_head, evqueue_tail, evqueue_count; /* [M] */
static struct klist hotplug_klist; /* [M] */
void filt_hotplugrdetach(struct knote *);
int filt_hotplugread(struct knote *, long);
int filt_hotplugmodify(struct kevent *, struct knote *);
int filt_hotplugprocess(struct knote *, struct kevent *);
const struct filterops hotplugread_filtops = {
.f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
.f_attach = NULL,
.f_detach = filt_hotplugrdetach,
.f_event = filt_hotplugread,
.f_modify = filt_hotplugmodify,
.f_process = filt_hotplugprocess,
};
#define EVQUEUE_NEXT(p) (p == HOTPLUG_MAXEVENTS - 1 ? 0 : p + 1)
int hotplug_put_event(struct hotplug_event *);
int hotplug_get_event(struct hotplug_event *);
void hotplugattach(int);
void
hotplugattach(int count)
{
opened = 0;
evqueue_head = 0;
evqueue_tail = 0;
evqueue_count = 0;
klist_init_mutex(&hotplug_klist, &hotplug_mtx);
}
void
hotplug_device_attach(enum devclass class, char *name)
{
struct hotplug_event he;
he.he_type = HOTPLUG_DEVAT;
he.he_devclass = class;
strlcpy(he.he_devname, name, sizeof(he.he_devname));
hotplug_put_event(&he);
}
void
hotplug_device_detach(enum devclass class, char *name)
{
struct hotplug_event he;
he.he_type = HOTPLUG_DEVDT;
he.he_devclass = class;
strlcpy(he.he_devname, name, sizeof(he.he_devname));
hotplug_put_event(&he);
}
int
hotplug_put_event(struct hotplug_event *he)
{
mtx_enter(&hotplug_mtx);
if (evqueue_count == HOTPLUG_MAXEVENTS && opened) {
mtx_leave(&hotplug_mtx);
printf("hotplug: event lost, queue full\n");
return (1);
}
evqueue[evqueue_head] = *he;
evqueue_head = EVQUEUE_NEXT(evqueue_head);
if (evqueue_count == HOTPLUG_MAXEVENTS)
evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
else
evqueue_count++;
knote_locked(&hotplug_klist, 0);
wakeup(&evqueue);
mtx_leave(&hotplug_mtx);
return (0);
}
int
hotplug_get_event(struct hotplug_event *he)
{
if (evqueue_count == 0)
return (1);
*he = evqueue[evqueue_tail];
evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
evqueue_count--;
return (0);
}
int
hotplugopen(dev_t dev, int flag, int mode, struct proc *p)
{
if (minor(dev) != 0)
return (ENXIO);
if ((flag & FWRITE))
return (EPERM);
if (opened)
return (EBUSY);
opened = 1;
return (0);
}
int
hotplugclose(dev_t dev, int flag, int mode, struct proc *p)
{
struct hotplug_event he;
mtx_enter(&hotplug_mtx);
while (hotplug_get_event(&he) == 0)
continue;
mtx_leave(&hotplug_mtx);
klist_invalidate(&hotplug_klist);
opened = 0;
return (0);
}
int
hotplugread(dev_t dev, struct uio *uio, int flags)
{
struct hotplug_event he;
int error;
if (uio->uio_resid != sizeof(he))
return (EINVAL);
mtx_enter(&hotplug_mtx);
while (hotplug_get_event(&he)) {
if (flags & IO_NDELAY) {
mtx_leave(&hotplug_mtx);
return (EAGAIN);
}
error = msleep_nsec(&evqueue, &hotplug_mtx, PRIBIO | PCATCH,
"htplev", INFSLP);
if (error) {
mtx_leave(&hotplug_mtx);
return (error);
}
}
mtx_leave(&hotplug_mtx);
return (uiomove(&he, sizeof(he), uio));
}
int
hotplugioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
{
switch (cmd) {
case FIOASYNC:
/* ignore */
case FIONBIO:
/* handled in the upper fs layer */
break;
default:
return (ENOTTY);
}
return (0);
}
int
hotplugkqfilter(dev_t dev, struct knote *kn)
{
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &hotplugread_filtops;
break;
default:
return (EINVAL);
}
klist_insert(&hotplug_klist, kn);
return (0);
}
void
filt_hotplugrdetach(struct knote *kn)
{
klist_remove(&hotplug_klist, kn);
}
int
filt_hotplugread(struct knote *kn, long hint)
{
kn->kn_data = evqueue_count;
return (evqueue_count > 0);
}
int
filt_hotplugmodify(struct kevent *kev, struct knote *kn)
{
int active;
mtx_enter(&hotplug_mtx);
active = knote_modify(kev, kn);
mtx_leave(&hotplug_mtx);
return (active);
}
int
filt_hotplugprocess(struct knote *kn, struct kevent *kev)
{
int active;
mtx_enter(&hotplug_mtx);
active = knote_process(kn, kev);
mtx_leave(&hotplug_mtx);
return (active);
}