Import hotplug(4) pseudo-device.
[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/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_poll_t         hotplugpoll;
45
46 static struct dev_ops hotplug_ops = {
47         { "hotplug", CDEV_MAJOR, 0 },
48         .d_open =       hotplugopen,
49         .d_close =      hotplugclose,
50         .d_read =       hotplugread,
51         .d_poll =       hotplugpoll,
52 };
53
54 struct hotplug_event_info {
55         struct hotplug_event *he;
56         TAILQ_ENTRY(hotplug_event_info) hei_link;
57 };
58
59 TAILQ_HEAD(hpq, hotplug_event_info);
60
61 static struct hotplug_softc
62 {
63         cdev_t dev;
64         struct lock lock;
65         int opened;
66         int qcount;
67         struct hpq queue;
68         struct selinfo sel;
69         void (*old_devfs_node_added)(struct hotplug_device *hpdev);
70         void (*old_devfs_node_removed)(struct hotplug_device *hpdev);
71 } hpsc;
72
73 extern void (*devfs_node_added)(struct hotplug_device *hpdev);
74 extern void (*devfs_node_removed)(struct hotplug_device *hpdev);
75
76 void hotplug_devfs_node_added(struct hotplug_device *hpdev);
77 void hotplug_devfs_node_removed(struct hotplug_device *hpdev);
78
79 static int hotplug_get_event(struct hotplug_event *he);
80 static int hotplug_put_event(struct hotplug_event *he);
81
82 static int hotplug_uninit(void);
83 static int hotplug_init(void);
84
85 static int
86 hotplugopen(struct dev_open_args *ap)
87 {
88         if (hpsc.opened)
89                 return (EBUSY);
90         hpsc.opened = 1;
91         return 0;
92 }
93
94 static int
95 hotplugclose(struct dev_close_args *ap)
96 {
97         hpsc.opened = 0;
98         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
99         wakeup(&hpsc);
100         lockmgr(&hpsc.lock, LK_RELEASE);
101         return 0;
102 }
103
104 static int
105 hotplugpoll(struct dev_poll_args *ap)
106 {
107         int     revents = 0;
108
109         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
110         if (ap->a_events & (POLLIN | POLLRDNORM)) {
111                 if (!TAILQ_EMPTY(&hpsc.queue))
112                         revents = ap->a_events & (POLLIN | POLLRDNORM);
113                 else
114                         selrecord(curthread, &hpsc.sel);
115         }
116         lockmgr(&hpsc.lock, LK_RELEASE);
117
118         ap->a_events = revents;
119         return (0);
120 }
121
122 int
123 hotplug_get_event(struct hotplug_event *he)
124 {
125         struct hotplug_event_info *hei;
126
127         /* shouldn't get there */
128         if(TAILQ_EMPTY(&hpsc.queue))
129                 return EINVAL;
130         hpsc.qcount--;
131         /* we are under hotplugread() lock here */
132         hei = TAILQ_FIRST(&hpsc.queue);
133         memcpy(he, hei->he, sizeof(struct hotplug_event));
134         TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
135         kfree(hei->he, M_DEVBUF);
136         kfree(hei, M_DEVBUF);
137         return (0);
138 }
139
140 static int
141 hotplugread(struct dev_read_args *ap)
142 {
143         struct uio *uio = ap->a_uio;
144         struct hotplug_event *he;
145         int rv = EINVAL;
146
147         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
148         while(TAILQ_EMPTY(&hpsc.queue)) {
149                 tsleep_interlock(&hpsc, PCATCH);
150                 lockmgr(&hpsc.lock, LK_RELEASE);
151                 rv = tsleep(&hpsc, PCATCH | PINTERLOCKED, "hotplug", 0);
152                 if(rv) {
153                         lockmgr(&hpsc.lock, LK_RELEASE);
154                         return (rv);
155                 }
156         }
157         he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
158         if(hotplug_get_event(he) == 0) {
159                 rv = uiomove((caddr_t)he, sizeof(struct hotplug_event), uio);
160                 kfree(he, M_DEVBUF);
161         }
162         lockmgr(&hpsc.lock, LK_RELEASE);
163         return (rv);
164 }
165
166 static int
167 hotplug_put_event(struct hotplug_event *he)
168 {
169         struct hotplug_event_info *hei = NULL;
170
171         if (hpsc.qcount == HOTPLUG_MAXEVENTS && hpsc.opened) {
172                 kprintf("hotplug: event lost, queue full\n");
173                 return (1);
174         }
175         hei = kmalloc(sizeof(struct hotplug_event_info), M_DEVBUF, M_WAITOK);
176         hei->he = kmalloc(sizeof(struct hotplug_event), M_DEVBUF, M_WAITOK);
177         memcpy(hei->he, he, sizeof(struct hotplug_event));
178         lockmgr(&hpsc.lock, LK_EXCLUSIVE);
179         TAILQ_INSERT_TAIL(&hpsc.queue, hei, hei_link);
180         hpsc.qcount++;
181         wakeup(&hpsc);
182         lockmgr(&hpsc.lock, LK_RELEASE);
183         selwakeup(&hpsc.sel);
184         return (0);
185 }
186
187 void
188 hotplug_devfs_node_added(struct hotplug_device *hpdev) {
189         struct hotplug_event he;
190         u_int class;
191         char *name;
192
193         if(!hpdev->dev || !hpsc.opened)
194                 return;
195         class = hpdev->dev->si_ops->head.flags;
196         name = hpdev->name;
197         he.he_type = HOTPLUG_DEVAT;
198         he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
199         strlcpy(he.he_devname, name, sizeof(he.he_devname));
200         hotplug_put_event(&he);
201 }
202
203 void
204 hotplug_devfs_node_removed(struct hotplug_device *hpdev) {
205         struct hotplug_event he;
206         u_int class;
207         char *name;
208
209         if(!hpdev->dev || !hpsc.opened)
210                 return;
211         class = hpdev->dev->si_ops->head.flags;
212         name = hpdev->name;
213         he.he_type = HOTPLUG_DEVDT;
214         he.he_devclass = ((class == D_TTY) ? DV_TTY : ((class == D_TAPE) ? DV_TAPE : ((class == D_DISK) ? DV_DISK : DV_DULL)));
215         strlcpy(he.he_devname, name, sizeof(he.he_devname));
216         hotplug_put_event(&he);
217 }
218
219 static int
220 hotplug_init()
221 {
222         hpsc.dev = make_dev(&hotplug_ops, 0, UID_ROOT, GID_WHEEL, 0600, "hotplug");
223         hpsc.qcount = 0;
224         lockinit(&hpsc.lock, "hotplug mtx", 0, 0);
225         TAILQ_INIT(&hpsc.queue);
226         /* setup handlers */
227         hpsc.old_devfs_node_added = devfs_node_added;
228         hpsc.old_devfs_node_removed = devfs_node_removed;
229         devfs_node_added = hotplug_devfs_node_added;
230         devfs_node_removed = hotplug_devfs_node_removed;
231         return 0;
232 }
233
234 static int
235 hotplug_uninit()
236 {
237         struct hotplug_event_info *hei;
238
239         if(hpsc.opened)
240                 return EBUSY;
241         devfs_node_added = hpsc.old_devfs_node_added;
242         devfs_node_removed = hpsc.old_devfs_node_removed;
243         /* Free the entire tail queue. */
244         while ((hei = TAILQ_FIRST(&hpsc.queue))) {
245                 TAILQ_REMOVE(&hpsc.queue, hei, hei_link);
246                 kfree(hei->he, M_DEVBUF);
247                 kfree(hei, M_DEVBUF);
248         }
249
250         /* The tail queue should now be empty. */
251         if (!TAILQ_EMPTY(&hpsc.queue))
252                 kprintf("hotplug: queue not empty!\n");
253         destroy_dev(hpsc.dev);
254         return 0;
255 }
256
257 static int
258 hotplug_modevh(struct module *m, int what, void *arg __unused)
259 {
260         int             error;
261
262         switch (what) {
263         case MOD_LOAD:
264                 error = hotplug_init();
265                 break;
266         case MOD_UNLOAD:
267                 error = hotplug_uninit();
268                 break;
269         default:
270                 error = EINVAL;
271                 break;
272         }
273         return (error);
274 }
275
276 static moduledata_t hotplug_mod = {
277         "hotplug",
278         hotplug_modevh,
279         NULL,
280 };
281
282 DECLARE_MODULE(hotplug, hotplug_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);