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