kernel - Remove D_KQFILTER flag
[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/event.h>
31 #include <sys/uio.h>
32 #include <sys/thread.h>
33 #include <sys/thread2.h>
34 #include <sys/hotplug.h>
35
36 #define HOTPLUG_MAXEVENTS       16
37
38 #define CDEV_MAJOR              82
39
40 static d_open_t         hotplugopen;
41 static d_close_t        hotplugclose;
42 static d_read_t         hotplugread;
43 static d_kqfilter_t     hotplugkqfilter;
44
45 static void hotplugfiltdetach(struct knote *);
46 static int hotplugfilt(struct knote *, long);
47
48 static struct dev_ops hotplug_ops = {
49         { "hotplug", CDEV_MAJOR, 0 },
50         .d_open =       hotplugopen,
51         .d_close =      hotplugclose,
52         .d_read =       hotplugread,
53         .d_kqfilter =   hotplugkqfilter
54 };
55
56 struct hotplug_event_info {
57         struct hotplug_event *he;
58         TAILQ_ENTRY(hotplug_event_info) hei_link;
59 };
60
61 TAILQ_HEAD(hpq, hotplug_event_info);
62
63 static struct hotplug_softc
64 {
65         cdev_t dev;
66         struct lock lock;
67         int opened;
68         int qcount;
69         struct hpq queue;
70         struct kqinfo kq;
71         void (*old_devfs_node_added)(struct hotplug_device *hpdev);
72         void (*old_devfs_node_removed)(struct hotplug_device *hpdev);
73 } hpsc;
74
75 extern void (*devfs_node_added)(struct hotplug_device *hpdev);
76 extern void (*devfs_node_removed)(struct hotplug_device *hpdev);
77
78 void hotplug_devfs_node_added(struct hotplug_device *hpdev);
79 void hotplug_devfs_node_removed(struct hotplug_device *hpdev);
80
81 static int hotplug_get_event(struct hotplug_event *he);
82 static int hotplug_put_event(struct hotplug_event *he);
83
84 static int hotplug_uninit(void);
85 static int hotplug_init(void);
86
87 static int
88 hotplugopen(struct dev_open_args *ap)
89 {
90         if (hpsc.opened)
91                 return (EBUSY);
92         hpsc.opened = 1;
93         return 0;
94 }
95
96 static int
97 hotplugclose(struct dev_close_args *ap)
98 {
99         hpsc.opened = 0;
100         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
101         wakeup(&hpsc);
102         lockmgr(&hpsc.lock, LK_RELEASE);
103         return 0;
104 }
105
106 static struct filterops hotplugfiltops =
107         { FILTEROP_ISFD, NULL, hotplugfiltdetach, hotplugfilt };
108
109 static int
110 hotplugkqfilter(struct dev_kqfilter_args *ap)
111 {
112         struct knote *kn = ap->a_kn;
113         struct klist *klist;
114
115         ap->a_result = 0;
116
117         switch (kn->kn_filter) {
118         case EVFILT_READ:
119                 kn->kn_fop = &hotplugfiltops;
120                 break;
121         default:
122                 ap->a_result = EOPNOTSUPP;
123                 return (0);
124         }
125
126         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
127         klist = &hpsc.kq.ki_note;
128         knote_insert(klist, kn);
129         lockmgr(&hpsc.lock, LK_RELEASE);
130
131         return (0);
132 }
133
134 static void
135 hotplugfiltdetach(struct knote *kn)
136 {
137         struct klist *klist;
138
139         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
140         klist = &hpsc.kq.ki_note;
141         knote_remove(klist, kn);
142         lockmgr(&hpsc.lock, LK_RELEASE);
143 }
144
145 static int
146 hotplugfilt(struct knote *kn, long hint)
147 {
148         int ready = 0;
149
150         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
151         if (!TAILQ_EMPTY(&hpsc.queue))
152                 ready = 1;
153         lockmgr(&hpsc.lock, LK_RELEASE);
154
155         return (ready);
156 }
157
158 int
159 hotplug_get_event(struct hotplug_event *he)
160 {
161         struct hotplug_event_info *hei;
162
163         /* shouldn't get there */
164         if(TAILQ_EMPTY(&hpsc.queue))
165                 return EINVAL;
166         hpsc.qcount--;
167         /* we are under hotplugread() lock here */
168         hei = TAILQ_FIRST(&hpsc.queue);
169         memcpy(he, hei->he, sizeof(struct hotplug_event));
170         TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
171         kfree(hei->he, M_DEVBUF);
172         kfree(hei, M_DEVBUF);
173         return (0);
174 }
175
176 static int
177 hotplugread(struct dev_read_args *ap)
178 {
179         struct uio *uio = ap->a_uio;
180         struct hotplug_event *he;
181         int rv = EINVAL;
182
183         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
184         while(TAILQ_EMPTY(&hpsc.queue)) {
185                 tsleep_interlock(&hpsc, PCATCH);
186                 lockmgr(&hpsc.lock, LK_RELEASE);
187                 rv = tsleep(&hpsc, PCATCH | PINTERLOCKED, "hotplug", 0);
188                 if(rv) {
189                         lockmgr(&hpsc.lock, LK_RELEASE);
190                         return (rv);
191                 }
192         }
193         he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
194         if(hotplug_get_event(he) == 0) {
195                 rv = uiomove((caddr_t)he, sizeof(struct hotplug_event), uio);
196                 kfree(he, M_DEVBUF);
197         }
198         lockmgr(&hpsc.lock, LK_RELEASE);
199         return (rv);
200 }
201
202 static int
203 hotplug_put_event(struct hotplug_event *he)
204 {
205         struct hotplug_event_info *hei = NULL;
206
207         if (hpsc.qcount == HOTPLUG_MAXEVENTS && hpsc.opened) {
208                 kprintf("hotplug: event lost, queue full\n");
209                 return (1);
210         }
211         hei = kmalloc(sizeof(struct hotplug_event_info), M_DEVBUF, M_WAITOK);
212         hei->he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
213         memcpy(hei->he, he, sizeof(struct hotplug_event));
214         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
215         TAILQ_INSERT_TAIL(&hpsc.queue, hei, hei_link);
216         hpsc.qcount++;
217         wakeup(&hpsc);
218         lockmgr(&hpsc.lock, LK_RELEASE);
219         KNOTE(&hpsc.kq.ki_note, 0);
220         return (0);
221 }
222
223 void
224 hotplug_devfs_node_added(struct hotplug_device *hpdev) {
225         struct hotplug_event he;
226         u_int class;
227         char *name;
228
229         if(!hpdev->dev || !hpsc.opened)
230                 return;
231         class = hpdev->dev->si_ops->head.flags;
232         name = hpdev->name;
233         he.he_type = HOTPLUG_DEVAT;
234         he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
235         strlcpy(he.he_devname, name, sizeof(he.he_devname));
236         hotplug_put_event(&he);
237 }
238
239 void
240 hotplug_devfs_node_removed(struct hotplug_device *hpdev) {
241         struct hotplug_event he;
242         u_int class;
243         char *name;
244
245         if(!hpdev->dev || !hpsc.opened)
246                 return;
247         class = hpdev->dev->si_ops->head.flags;
248         name = hpdev->name;
249         he.he_type = HOTPLUG_DEVDT;
250         he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
251         strlcpy(he.he_devname, name, sizeof(he.he_devname));
252         hotplug_put_event(&he);
253 }
254
255 static int
256 hotplug_init()
257 {
258         hpsc.dev = make_dev(&hotplug_ops, 0, UID_ROOT, GID_WHEEL, 0600, "hotplug");
259         hpsc.qcount = 0;
260         lockinit(&hpsc.lock, "hotplug mtx", 0, 0);
261         TAILQ_INIT(&hpsc.queue);
262         /* setup handlers */
263         hpsc.old_devfs_node_added = devfs_node_added;
264         hpsc.old_devfs_node_removed = devfs_node_removed;
265         devfs_node_added = hotplug_devfs_node_added;
266         devfs_node_removed = hotplug_devfs_node_removed;
267         return 0;
268 }
269
270 static int
271 hotplug_uninit()
272 {
273         struct hotplug_event_info *hei;
274
275         if(hpsc.opened)
276                 return EBUSY;
277         devfs_node_added = hpsc.old_devfs_node_added;
278         devfs_node_removed = hpsc.old_devfs_node_removed;
279         /* Free the entire tail queue. */
280         while ((hei = TAILQ_FIRST(&hpsc.queue))) {
281                 TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
282                 kfree(hei->he, M_DEVBUF);
283                 kfree(hei, M_DEVBUF);
284         }
285
286         /* The tail queue should now be empty. */
287         if (!TAILQ_EMPTY(&hpsc.queue))
288                 kprintf("hotplug: queue not empty!\n");
289         destroy_dev(hpsc.dev);
290         return 0;
291 }
292
293 static int
294 hotplug_modevh(struct module *m, int what, void *arg __unused)
295 {
296         int             error;
297
298         switch (what) {
299         case MOD_LOAD:
300                 error = hotplug_init();
301                 break;
302         case MOD_UNLOAD:
303                 error = hotplug_uninit();
304                 break;
305         default:
306                 error = EINVAL;
307                 break;
308         }
309         return (error);
310 }
311
312 static moduledata_t hotplug_mod = {
313         "hotplug",
314         hotplug_modevh,
315         NULL,
316 };
317
318 DECLARE_MODULE(hotplug, hotplug_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);