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