From: Alexander Polakov Date: Fri, 4 Dec 2009 21:43:14 +0000 (+0300) Subject: Import hotplug(4) pseudo-device. X-Git-Tag: v2.7.1~360 X-Git-Url: http://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/115f9a7253667c2f7197dc354b75baa17f5f331a Import hotplug(4) pseudo-device. Obtained-from: OpenBSD. --- diff --git a/share/man/man4/hotplug.4 b/share/man/man4/hotplug.4 new file mode 100644 index 0000000..9e47300 --- /dev/null +++ b/share/man/man4/hotplug.4 @@ -0,0 +1,107 @@ +.\" $OpenBSD: hotplug.4,v 1.3 2007/05/31 19:19:50 jmc Exp $ +.\" +.\" Copyright (c) 2004 Alexander Yurchenko +.\" +.\" 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 +.Fd #include +.Fd #include +.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 . diff --git a/sys/dev/misc/Makefile b/sys/dev/misc/Makefile index 772f21d..8bc099a 100644 --- a/sys/dev/misc/Makefile +++ b/sys/dev/misc/Makefile @@ -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 diff --git a/sys/dev/misc/hotplug/Makefile b/sys/dev/misc/hotplug/Makefile new file mode 100644 index 0000000..daf34ab --- /dev/null +++ b/sys/dev/misc/hotplug/Makefile @@ -0,0 +1,5 @@ +KMOD= hotplug +SRCS= hotplug.c +SRCS+= device_if.h bus_if.h + +.include diff --git a/sys/dev/misc/hotplug/hotplug.c b/sys/dev/misc/hotplug/hotplug.c new file mode 100644 index 0000000..4faafbb --- /dev/null +++ b/sys/dev/misc/hotplug/hotplug.c @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2004 Alexander Yurchenko + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 0000000..deb5622 --- /dev/null +++ b/sys/sys/hotplug.h @@ -0,0 +1,59 @@ +/* $OpenBSD: hotplug.h,v 1.5 2006/05/28 16:52:34 mk Exp $ */ +/* + * Copyright (c) 2004 Alexander Yurchenko + * + * 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 + +/* + * 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_ */ diff --git a/sys/vfs/devfs/devfs_core.c b/sys/vfs/devfs/devfs_core.c index 2a9d62d..5703c4f 100644 --- a/sys/vfs/devfs/devfs_core.c +++ b/sys/vfs/devfs/devfs_core.c @@ -48,6 +48,7 @@ #include #include #include +#include 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;