Import hotplug(4) pseudo-device.
authorAlexander Polakov <polachok@gmail.com>
Fri, 4 Dec 2009 21:43:14 +0000 (00:43 +0300)
committerAlexander Polakov <polachok@gmail.com>
Fri, 4 Dec 2009 22:01:36 +0000 (01:01 +0300)
It passes device node creation and removal events to userland.

Obtained-from: OpenBSD.

share/man/man4/hotplug.4 [new file with mode: 0644]
sys/dev/misc/Makefile
sys/dev/misc/hotplug/Makefile [new file with mode: 0644]
sys/dev/misc/hotplug/hotplug.c [new file with mode: 0644]
sys/sys/hotplug.h [new file with mode: 0644]
sys/vfs/devfs/devfs_core.c

diff --git a/share/man/man4/hotplug.4 b/share/man/man4/hotplug.4
new file mode 100644 (file)
index 0000000..9e47300
--- /dev/null
@@ -0,0 +1,107 @@
+.\"    $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 .
index 772f21d..8bc099a 100644 (file)
@@ -1,6 +1,6 @@
 # $DragonFly: src/sys/dev/misc/Makefile,v 1.5 2008/04/23 08:57:10 hasso Exp $
 #
 
-SUBDIR=cmx dcons joy kbdmux pcfclock nmdm syscons snp
+SUBDIR=cmx dcons joy kbdmux pcfclock nmdm syscons snp hotplug
 
 .include <bsd.subdir.mk>
diff --git a/sys/dev/misc/hotplug/Makefile b/sys/dev/misc/hotplug/Makefile
new file mode 100644 (file)
index 0000000..daf34ab
--- /dev/null
@@ -0,0 +1,5 @@
+KMOD=  hotplug
+SRCS=  hotplug.c
+SRCS+= device_if.h bus_if.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/dev/misc/hotplug/hotplug.c b/sys/dev/misc/hotplug/hotplug.c
new file mode 100644 (file)
index 0000000..4faafbb
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * 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);
diff --git a/sys/sys/hotplug.h b/sys/sys/hotplug.h
new file mode 100644 (file)
index 0000000..deb5622
--- /dev/null
@@ -0,0 +1,59 @@
+/*     $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_ */
index 2a9d62d..5703c4f 100644 (file)
@@ -48,6 +48,7 @@
 #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);
@@ -143,6 +144,10 @@ static void *devfs_gc_links_callback(struct devfs_node *, struct devfs_node *);
 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
@@ -433,6 +438,7 @@ int
 devfs_unlinkp(struct devfs_node *node)
 {
        struct devfs_node *parent;
+       struct hotplug_device *hpdev;
        KKASSERT(node);
 
        /*
@@ -452,6 +458,15 @@ devfs_unlinkp(struct devfs_node *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;
 }
@@ -1540,6 +1555,7 @@ devfs_alias_create(char *name_orig, struct devfs_node *target, int rule_based)
        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;
@@ -1576,6 +1592,14 @@ devfs_alias_create(char *name_orig, struct devfs_node *target, int rule_based)
                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);
 }
@@ -1743,6 +1767,7 @@ devfs_create_device_node(struct devfs_node *root, cdev_t dev,
                         char *dev_name, char *path_fmt, ...)
 {
        struct devfs_node *parent, *node = NULL;
+       struct hotplug_device *hpdev;
        char *path = NULL;
        char *name;
        char *name_buf;
@@ -1806,6 +1831,14 @@ devfs_create_device_node(struct devfs_node *root, cdev_t dev,
                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);
@@ -1903,6 +1936,7 @@ devfs_destroy_device_node(struct devfs_node *root, cdev_t target)
                nanotime(&node->parent->mtime);
                devfs_gc(node);
        }
+
        kfree(name_buf, M_TEMP);
 
        return 0;