Merge remote branch 'crater/master' into kq_devices
[dragonfly.git] / sys / dev / misc / hotplug / hotplug.c
1 /*
2  * Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 /*
18  * Device attachment and detachment notifications.
19  */
20
21 #include <sys/conf.h>
22 #include <sys/param.h>
23 #include <sys/systm.h>
24 #include <sys/malloc.h>
25 #include <sys/kernel.h>
26 #include <sys/module.h>
27 #include <sys/sysctl.h>
28 #include <sys/device.h>
29 #include <sys/lock.h>
30 #include <sys/selinfo.h>
31 #include <sys/poll.h>
32 #include <sys/event.h>
33 #include <sys/uio.h>
34 #include <sys/thread.h>
35 #include <sys/thread2.h>
36 #include <sys/hotplug.h>
37
38 #define HOTPLUG_MAXEVENTS       16
39
40 #define CDEV_MAJOR              82
41
42 static d_open_t         hotplugopen;
43 static d_close_t        hotplugclose;
44 static d_read_t         hotplugread;
45 static d_poll_t         hotplugpoll;
46 static d_kqfilter_t     hotplugkqfilter;
47
48 static void hotplugfiltdetach(struct knote *);
49 static int hotplugfilt(struct knote *, long);
50
51 static struct dev_ops hotplug_ops = {
52         { "hotplug", CDEV_MAJOR, D_KQFILTER },
53         .d_open =       hotplugopen,
54         .d_close =      hotplugclose,
55         .d_read =       hotplugread,
56         .d_poll =       hotplugpoll,
57         .d_kqfilter =   hotplugkqfilter
58 };
59
60 struct hotplug_event_info {
61         struct hotplug_event *he;
62         TAILQ_ENTRY(hotplug_event_info) hei_link;
63 };
64
65 TAILQ_HEAD(hpq, hotplug_event_info);
66
67 static struct hotplug_softc
68 {
69         cdev_t dev;
70         struct lock lock;
71         int opened;
72         int qcount;
73         struct hpq queue;
74         struct selinfo sel;
75         void (*old_devfs_node_added)(struct hotplug_device *hpdev);
76         void (*old_devfs_node_removed)(struct hotplug_device *hpdev);
77 } hpsc;
78
79 extern void (*devfs_node_added)(struct hotplug_device *hpdev);
80 extern void (*devfs_node_removed)(struct hotplug_device *hpdev);
81
82 void hotplug_devfs_node_added(struct hotplug_device *hpdev);
83 void hotplug_devfs_node_removed(struct hotplug_device *hpdev);
84
85 static int hotplug_get_event(struct hotplug_event *he);
86 static int hotplug_put_event(struct hotplug_event *he);
87
88 static int hotplug_uninit(void);
89 static int hotplug_init(void);
90
91 static int
92 hotplugopen(struct dev_open_args *ap)
93 {
94         if (hpsc.opened)
95                 return (EBUSY);
96         hpsc.opened = 1;
97         return 0;
98 }
99
100 static int
101 hotplugclose(struct dev_close_args *ap)
102 {
103         hpsc.opened = 0;
104         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
105         wakeup(&hpsc);
106         lockmgr(&hpsc.lock, LK_RELEASE);
107         return 0;
108 }
109
110 static int
111 hotplugpoll(struct dev_poll_args *ap)
112 {
113         int     revents = 0;
114
115         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
116         if (ap->a_events & (POLLIN | POLLRDNORM)) {
117                 if (!TAILQ_EMPTY(&hpsc.queue))
118                         revents = ap->a_events & (POLLIN | POLLRDNORM);
119                 else
120                         selrecord(curthread, &hpsc.sel);
121         }
122         lockmgr(&hpsc.lock, LK_RELEASE);
123
124         ap->a_events = revents;
125         return (0);
126 }
127
128 static struct filterops hotplugfiltops =
129         { 1, NULL, hotplugfiltdetach, hotplugfilt };
130
131 static int
132 hotplugkqfilter(struct dev_kqfilter_args *ap)
133 {
134         struct knote *kn = ap->a_kn;
135         struct klist *klist;
136
137         ap->a_result = 0;
138
139         switch (kn->kn_filter) {
140         case EVFILT_READ:
141                 kn->kn_fop = &hotplugfiltops;
142                 break;
143         default:
144                 ap->a_result = 1;
145                 return (0);
146         }
147
148         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
149         crit_enter();
150         klist = &hpsc.sel.si_note;
151         SLIST_INSERT_HEAD(klist, kn, kn_selnext);
152         crit_exit();
153         lockmgr(&hpsc.lock, LK_RELEASE);
154
155         return (0);
156 }
157
158 static void
159 hotplugfiltdetach(struct knote *kn)
160 {
161         struct klist *klist;
162
163         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
164         crit_enter();
165         klist = &hpsc.sel.si_note;
166         SLIST_REMOVE(klist, kn, knote, kn_selnext);
167         crit_exit();
168         lockmgr(&hpsc.lock, LK_RELEASE);
169 }
170
171 static int
172 hotplugfilt(struct knote *kn, long hint)
173 {
174         int ready = 0;
175
176         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
177         if (!TAILQ_EMPTY(&hpsc.queue))
178                 ready = 1;
179         lockmgr(&hpsc.lock, LK_RELEASE);
180
181         return (ready);
182 }
183
184 int
185 hotplug_get_event(struct hotplug_event *he)
186 {
187         struct hotplug_event_info *hei;
188
189         /* shouldn't get there */
190         if(TAILQ_EMPTY(&hpsc.queue))
191                 return EINVAL;
192         hpsc.qcount--;
193         /* we are under hotplugread() lock here */
194         hei = TAILQ_FIRST(&hpsc.queue);
195         memcpy(he, hei->he, sizeof(struct hotplug_event));
196         TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
197         kfree(hei->he, M_DEVBUF);
198         kfree(hei, M_DEVBUF);
199         return (0);
200 }
201
202 static int
203 hotplugread(struct dev_read_args *ap)
204 {
205         struct uio *uio = ap->a_uio;
206         struct hotplug_event *he;
207         int rv = EINVAL;
208
209         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
210         while(TAILQ_EMPTY(&hpsc.queue)) {
211                 tsleep_interlock(&hpsc, PCATCH);
212                 lockmgr(&hpsc.lock, LK_RELEASE);
213                 rv = tsleep(&hpsc, PCATCH | PINTERLOCKED, "hotplug", 0);
214                 if(rv) {
215                         lockmgr(&hpsc.lock, LK_RELEASE);
216                         return (rv);
217                 }
218         }
219         he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
220         if(hotplug_get_event(he) == 0) {
221                 rv = uiomove((caddr_t)he, sizeof(struct hotplug_event), uio);
222                 kfree(he, M_DEVBUF);
223         }
224         lockmgr(&hpsc.lock, LK_RELEASE);
225         return (rv);
226 }
227
228 static int
229 hotplug_put_event(struct hotplug_event *he)
230 {
231         struct hotplug_event_info *hei = NULL;
232
233         if (hpsc.qcount == HOTPLUG_MAXEVENTS && hpsc.opened) {
234                 kprintf("hotplug: event lost, queue full\n");
235                 return (1);
236         }
237         hei = kmalloc(sizeof(struct hotplug_event_info), M_DEVBUF, M_WAITOK);
238         hei->he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
239         memcpy(hei->he, he, sizeof(struct hotplug_event));
240         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
241         TAILQ_INSERT_TAIL(&hpsc.queue, hei, hei_link);
242         hpsc.qcount++;
243         wakeup(&hpsc);
244         lockmgr(&hpsc.lock, LK_RELEASE);
245         selwakeup(&hpsc.sel);
246         return (0);
247 }
248
249 void
250 hotplug_devfs_node_added(struct hotplug_device *hpdev) {
251         struct hotplug_event he;
252         u_int class;
253         char *name;
254
255         if(!hpdev->dev || !hpsc.opened)
256                 return;
257         class = hpdev->dev->si_ops->head.flags;
258         name = hpdev->name;
259         he.he_type = HOTPLUG_DEVAT;
260         he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
261         strlcpy(he.he_devname, name, sizeof(he.he_devname));
262         hotplug_put_event(&he);
263 }
264
265 void
266 hotplug_devfs_node_removed(struct hotplug_device *hpdev) {
267         struct hotplug_event he;
268         u_int class;
269         char *name;
270
271         if(!hpdev->dev || !hpsc.opened)
272                 return;
273         class = hpdev->dev->si_ops->head.flags;
274         name = hpdev->name;
275         he.he_type = HOTPLUG_DEVDT;
276         he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
277         strlcpy(he.he_devname, name, sizeof(he.he_devname));
278         hotplug_put_event(&he);
279 }
280
281 static int
282 hotplug_init()
283 {
284         hpsc.dev = make_dev(&hotplug_ops, 0, UID_ROOT, GID_WHEEL, 0600, "hotplug");
285         hpsc.qcount = 0;
286         lockinit(&hpsc.lock, "hotplug mtx", 0, 0);
287         TAILQ_INIT(&hpsc.queue);
288         /* setup handlers */
289         hpsc.old_devfs_node_added = devfs_node_added;
290         hpsc.old_devfs_node_removed = devfs_node_removed;
291         devfs_node_added = hotplug_devfs_node_added;
292         devfs_node_removed = hotplug_devfs_node_removed;
293         return 0;
294 }
295
296 static int
297 hotplug_uninit()
298 {
299         struct hotplug_event_info *hei;
300
301         if(hpsc.opened)
302                 return EBUSY;
303         devfs_node_added = hpsc.old_devfs_node_added;
304         devfs_node_removed = hpsc.old_devfs_node_removed;
305         /* Free the entire tail queue. */
306         while ((hei = TAILQ_FIRST(&hpsc.queue))) {
307                 TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
308                 kfree(hei->he, M_DEVBUF);
309                 kfree(hei, M_DEVBUF);
310         }
311
312         /* The tail queue should now be empty. */
313         if (!TAILQ_EMPTY(&hpsc.queue))
314                 kprintf("hotplug: queue not empty!\n");
315         destroy_dev(hpsc.dev);
316         return 0;
317 }
318
319 static int
320 hotplug_modevh(struct module *m, int what, void *arg __unused)
321 {
322         int             error;
323
324         switch (what) {
325         case MOD_LOAD:
326                 error = hotplug_init();
327                 break;
328         case MOD_UNLOAD:
329                 error = hotplug_uninit();
330                 break;
331         default:
332                 error = EINVAL;
333                 break;
334         }
335         return (error);
336 }
337
338 static moduledata_t hotplug_mod = {
339         "hotplug",
340         hotplug_modevh,
341         NULL,
342 };
343
344 DECLARE_MODULE(hotplug, hotplug_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);