--- /dev/null
+.\" $OpenBSD: hotplug.4,v 1.3 2007/05/31 19:19:50 jmc 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.
+.\"
+.Dd $Mdocdate$
+.Dt HOTPLUG 4
+.Os
+.Sh NAME
+.Nm hotplug
+.Nd devices hot plugging
+.Sh SYNOPSIS
+.Cd "pseudo-device hotplug 1"
+.Pp
+.Fd #include <sys/types.h>
+.Fd #include <sys/device.h>
+.Fd #include <sys/hotplug.h>
+.Sh DESCRIPTION
+The
+.Nm
+pseudo-device passes device attachment and detachment events to
+userland.
+When a device attaches or detaches, the corresponding event is queued.
+The events can then be obtained from the queue through the
+.Xr read 2
+call on the
+.Pa /dev/hotplug
+device file.
+Once an event has been read, it's deleted from the queue.
+The event queue has a limited size and if it's full all new events will be
+dropped.
+Each event is described with the following structure declared in the
+.Aq Pa sys/hotplug.h
+header file:
+.Bd -literal -offset indent
+struct hotplug_event {
+ int he_type; /* event type */
+ enum devclass he_devclass; /* device class */
+ char he_devname[16]; /* device name */
+};
+
+.Ed
+The
+.Va he_type
+field describes the event type and can be either
+.Dv HOTPLUG_DEVAT
+for device attachment or
+.Dv HOTPLUG_DEVDT
+for detachment.
+The
+.Va he_devclass
+field describes the device class.
+All device classes can be found in the
+.Aq Pa sys/device.h
+header file:
+.Bd -literal -offset indent
+enum devclass {
+ DV_DULL, /* generic, no special info */
+ DV_CPU, /* CPU (carries resource utilization) */
+ DV_DISK, /* disk drive (label, etc) */
+ DV_IFNET, /* network interface */
+ DV_TAPE, /* tape device */
+ DV_TTY /* serial line interface */
+};
+
+.Ed
+The
+.Va he_devname
+is a device name including unit number, e.g.\&
+.Pa sd1 .
+.Pp
+Only one structure can be read per call.
+If there are no events in the queue, the
+.Xr read 2
+call will block until an event appears.
+.Sh DIAGNOSTICS
+.Bl -diag
+.It "hotplug: event lost, queue full"
+New events will be dropped until all pending events have been read.
+.El
+.Sh SEE ALSO
+.Xr read 2 ,
+.Xr hotplugd 8
+.Sh HISTORY
+The
+.Nm
+device first appeared in
+.Ox 3.6 .
+The
+.Nm
+driver was imported to DragonFly 2.5.1.
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Alexander Yurchenko Aq grange@openbsd.org .
--- /dev/null
+/*
+ * 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/conf.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+#include <sys/device.h>
+#include <sys/lock.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+#include <sys/uio.h>
+#include <sys/thread.h>
+#include <sys/thread2.h>
+#include <sys/hotplug.h>
+
+#define HOTPLUG_MAXEVENTS 16
+
+#define CDEV_MAJOR 82
+
+static d_open_t hotplugopen;
+static d_close_t hotplugclose;
+static d_read_t hotplugread;
+static d_poll_t hotplugpoll;
+
+static struct dev_ops hotplug_ops = {
+ { "hotplug", CDEV_MAJOR, 0 },
+ .d_open = hotplugopen,
+ .d_close = hotplugclose,
+ .d_read = hotplugread,
+ .d_poll = hotplugpoll,
+};
+
+struct hotplug_event_info {
+ struct hotplug_event *he;
+ TAILQ_ENTRY(hotplug_event_info) hei_link;
+};
+
+TAILQ_HEAD(hpq, hotplug_event_info);
+
+static struct hotplug_softc
+{
+ cdev_t dev;
+ struct lock lock;
+ int opened;
+ int qcount;
+ struct hpq queue;
+ struct selinfo sel;
+ void (*old_devfs_node_added)(struct hotplug_device *hpdev);
+ void (*old_devfs_node_removed)(struct hotplug_device *hpdev);
+} hpsc;
+
+extern void (*devfs_node_added)(struct hotplug_device *hpdev);
+extern void (*devfs_node_removed)(struct hotplug_device *hpdev);
+
+void hotplug_devfs_node_added(struct hotplug_device *hpdev);
+void hotplug_devfs_node_removed(struct hotplug_device *hpdev);
+
+static int hotplug_get_event(struct hotplug_event *he);
+static int hotplug_put_event(struct hotplug_event *he);
+
+static int hotplug_uninit(void);
+static int hotplug_init(void);
+
+static int
+hotplugopen(struct dev_open_args *ap)
+{
+ if (hpsc.opened)
+ return (EBUSY);
+ hpsc.opened = 1;
+ return 0;
+}
+
+static int
+hotplugclose(struct dev_close_args *ap)
+{
+ hpsc.opened = 0;
+ lockmgr(&hpsc.lock, LK_EXCLUSIVE);
+ wakeup(&hpsc);
+ lockmgr(&hpsc.lock, LK_RELEASE);
+ return 0;
+}
+
+static int
+hotplugpoll(struct dev_poll_args *ap)
+{
+ int revents = 0;
+
+ lockmgr(&hpsc.lock, LK_EXCLUSIVE);
+ if (ap->a_events & (POLLIN | POLLRDNORM)) {
+ if (!TAILQ_EMPTY(&hpsc.queue))
+ revents = ap->a_events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(curthread, &hpsc.sel);
+ }
+ lockmgr(&hpsc.lock, LK_RELEASE);
+
+ ap->a_events = revents;
+ return (0);
+}
+
+int
+hotplug_get_event(struct hotplug_event *he)
+{
+ struct hotplug_event_info *hei;
+
+ /* shouldn't get there */
+ if(TAILQ_EMPTY(&hpsc.queue))
+ return EINVAL;
+ hpsc.qcount--;
+ /* we are under hotplugread() lock here */
+ hei = TAILQ_FIRST(&hpsc.queue);
+ memcpy(he, hei->he, sizeof(struct hotplug_event));
+ TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
+ kfree(hei->he, M_DEVBUF);
+ kfree(hei, M_DEVBUF);
+ return (0);
+}
+
+static int
+hotplugread(struct dev_read_args *ap)
+{
+ struct uio *uio = ap->a_uio;
+ struct hotplug_event *he;
+ int rv = EINVAL;
+
+ lockmgr(&hpsc.lock, LK_EXCLUSIVE);
+ while(TAILQ_EMPTY(&hpsc.queue)) {
+ tsleep_interlock(&hpsc, PCATCH);
+ lockmgr(&hpsc.lock, LK_RELEASE);
+ rv = tsleep(&hpsc, PCATCH | PINTERLOCKED, "hotplug", 0);
+ if(rv) {
+ lockmgr(&hpsc.lock, LK_RELEASE);
+ return (rv);
+ }
+ }
+ he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
+ if(hotplug_get_event(he) == 0) {
+ rv = uiomove((caddr_t)he, sizeof(struct hotplug_event), uio);
+ kfree(he, M_DEVBUF);
+ }
+ lockmgr(&hpsc.lock, LK_RELEASE);
+ return (rv);
+}
+
+static int
+hotplug_put_event(struct hotplug_event *he)
+{
+ struct hotplug_event_info *hei = NULL;
+
+ if (hpsc.qcount == HOTPLUG_MAXEVENTS && hpsc.opened) {
+ kprintf("hotplug: event lost, queue full\n");
+ return (1);
+ }
+ hei = kmalloc(sizeof(struct hotplug_event_info), M_DEVBUF, M_WAITOK);
+ hei->he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
+ memcpy(hei->he, he, sizeof(struct hotplug_event));
+ lockmgr(&hpsc.lock, LK_EXCLUSIVE);
+ TAILQ_INSERT_TAIL(&hpsc.queue, hei, hei_link);
+ hpsc.qcount++;
+ wakeup(&hpsc);
+ lockmgr(&hpsc.lock, LK_RELEASE);
+ selwakeup(&hpsc.sel);
+ return (0);
+}
+
+void
+hotplug_devfs_node_added(struct hotplug_device *hpdev) {
+ struct hotplug_event he;
+ u_int class;
+ char *name;
+
+ if(!hpdev->dev || !hpsc.opened)
+ return;
+ class = hpdev->dev->si_ops->head.flags;
+ name = hpdev->name;
+ he.he_type = HOTPLUG_DEVAT;
+ he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
+ strlcpy(he.he_devname, name, sizeof(he.he_devname));
+ hotplug_put_event(&he);
+}
+
+void
+hotplug_devfs_node_removed(struct hotplug_device *hpdev) {
+ struct hotplug_event he;
+ u_int class;
+ char *name;
+
+ if(!hpdev->dev || !hpsc.opened)
+ return;
+ class = hpdev->dev->si_ops->head.flags;
+ name = hpdev->name;
+ he.he_type = HOTPLUG_DEVDT;
+ he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
+ strlcpy(he.he_devname, name, sizeof(he.he_devname));
+ hotplug_put_event(&he);
+}
+
+static int
+hotplug_init()
+{
+ hpsc.dev = make_dev(&hotplug_ops, 0, UID_ROOT, GID_WHEEL, 0600, "hotplug");
+ hpsc.qcount = 0;
+ lockinit(&hpsc.lock, "hotplug mtx", 0, 0);
+ TAILQ_INIT(&hpsc.queue);
+ /* setup handlers */
+ hpsc.old_devfs_node_added = devfs_node_added;
+ hpsc.old_devfs_node_removed = devfs_node_removed;
+ devfs_node_added = hotplug_devfs_node_added;
+ devfs_node_removed = hotplug_devfs_node_removed;
+ return 0;
+}
+
+static int
+hotplug_uninit()
+{
+ struct hotplug_event_info *hei;
+
+ if(hpsc.opened)
+ return EBUSY;
+ devfs_node_added = hpsc.old_devfs_node_added;
+ devfs_node_removed = hpsc.old_devfs_node_removed;
+ /* Free the entire tail queue. */
+ while ((hei = TAILQ_FIRST(&hpsc.queue))) {
+ TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
+ kfree(hei->he, M_DEVBUF);
+ kfree(hei, M_DEVBUF);
+ }
+
+ /* The tail queue should now be empty. */
+ if (!TAILQ_EMPTY(&hpsc.queue))
+ kprintf("hotplug: queue not empty!\n");
+ destroy_dev(hpsc.dev);
+ return 0;
+}
+
+static int
+hotplug_modevh(struct module *m, int what, void *arg __unused)
+{
+ int error;
+
+ switch (what) {
+ case MOD_LOAD:
+ error = hotplug_init();
+ break;
+ case MOD_UNLOAD:
+ error = hotplug_uninit();
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+ return (error);
+}
+
+static moduledata_t hotplug_mod = {
+ "hotplug",
+ hotplug_modevh,
+ NULL,
+};
+
+DECLARE_MODULE(hotplug, hotplug_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
--- /dev/null
+/* $OpenBSD: hotplug.h,v 1.5 2006/05/28 16:52:34 mk 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.
+ */
+
+#ifndef _SYS_HOTPLUG_H_
+#define _SYS_HOTPLUG_H_
+
+#include <sys/bus.h>
+
+/*
+ * Minimal device structures.
+ * Note that all ``system'' device types are listed here.
+ */
+enum obsd_devclass {
+ DV_DULL, /* generic, no special info */
+ DV_CPU, /* CPU (carries resource utilization) */
+ DV_DISK, /* disk drive (label, etc) */
+ DV_IFNET, /* network interface */
+ DV_TAPE, /* tape device */
+ DV_TTY /* serial line interface (???) */
+};
+
+/*
+ * Public interface for enqueuing and dequeueing device
+ * attachment and detachment notifications.
+ */
+
+#define HOTPLUG_DEVAT 0x01 /* device attached */
+#define HOTPLUG_DEVDT 0x02 /* device detached */
+
+struct hotplug_event {
+ int he_type; /* event type */
+ enum obsd_devclass he_devclass; /* device class */
+ char he_devname[16]; /* device name */
+};
+
+#ifdef _KERNEL
+struct hotplug_device {
+ cdev_t dev;
+ char *name;
+};
+void hotplug_device_attach(device_t dev);
+void hotplug_device_detach(device_t dev);
+#endif
+
+#endif /* _SYS_HOTPLUG_H_ */
#include <sys/systm.h>
#include <sys/devfs.h>
#include <sys/devfs_rules.h>
+#include <sys/hotplug.h>
MALLOC_DEFINE(M_DEVFS, "devfs", "Device File System (devfs) allocations");
DEVFS_DECLARE_CLONE_BITMAP(ops_id);
static void *
devfs_inode_to_vnode_worker_callback(struct devfs_node *, ino_t *);
+/* hotplug */
+void (*devfs_node_added)(struct hotplug_device*) = NULL;
+void (*devfs_node_removed)(struct hotplug_device*) = NULL;
+
/*
* devfs_debug() is a SYSCTL and TUNABLE controlled debug output function
* using kvprintf
devfs_unlinkp(struct devfs_node *node)
{
struct devfs_node *parent;
+ struct hotplug_device *hpdev;
KKASSERT(node);
/*
KKASSERT((parent->nchildren >= 0));
node->flags &= ~DEVFS_NODE_LINKED;
}
+ /* hotplug handler */
+ if(devfs_node_removed) {
+ hpdev = kmalloc(sizeof(struct hotplug_device), M_TEMP, M_WAITOK);
+ hpdev->dev = node->d_dev;
+ if(hpdev->dev)
+ hpdev->name = node->d_dev->si_name;
+ devfs_node_removed(hpdev);
+ kfree(hpdev, M_TEMP);
+ }
node->parent = NULL;
return 0;
}
struct mount *mp = target->mp;
struct devfs_node *parent = DEVFS_MNTDATA(mp)->root_node;
struct devfs_node *linknode;
+ struct hotplug_device *hpdev;
char *create_path = NULL;
char *name;
char *name_buf;
linknode->flags |= DEVFS_RULE_CREATED;
done:
+ /* hotplug handler */
+ if(devfs_node_added) {
+ hpdev = kmalloc(sizeof(struct hotplug_device), M_TEMP, M_WAITOK);
+ hpdev->dev = target->d_dev;
+ hpdev->name = name_orig;
+ devfs_node_added(hpdev);
+ kfree(hpdev, M_TEMP);
+ }
kfree(name_buf, M_TEMP);
return (result);
}
char *dev_name, char *path_fmt, ...)
{
struct devfs_node *parent, *node = NULL;
+ struct hotplug_device *hpdev;
char *path = NULL;
char *name;
char *name_buf;
if (found)
node->flags |= (DEVFS_PTY | DEVFS_INVISIBLE);
}
+ /* hotplug handler */
+ if(devfs_node_added) {
+ hpdev = kmalloc(sizeof(struct hotplug_device), M_TEMP, M_WAITOK);
+ hpdev->dev = node->d_dev;
+ hpdev->name = node->d_dev->si_name;
+ devfs_node_added(hpdev);
+ kfree(hpdev, M_TEMP);
+ }
out:
kfree(name_buf, M_TEMP);
nanotime(&node->parent->mtime);
devfs_gc(node);
}
+
kfree(name_buf, M_TEMP);
return 0;