1 /* $OpenBSD: hotplug.c,v 1.25 2024/12/30 02:46:00 guenther Exp $ */
2 /*
3 * Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 /*
19 * Device attachment and detachment notifications.
20 */
21
22 #include <sys/param.h>
23 #include <sys/systm.h>
24 #include <sys/device.h>
25 #include <sys/event.h>
26 #include <sys/fcntl.h>
27 #include <sys/hotplug.h>
28 #include <sys/ioctl.h>
29 #include <sys/mutex.h>
30 #include <sys/vnode.h>
31
32 #define HOTPLUG_MAXEVENTS 64
33
34 /*
35 * Locks used to protect struct members and global data
36 * M hotplug_mtx
37 */
38
39 static struct mutex hotplug_mtx = MUTEX_INITIALIZER(IPL_MPFLOOR);
40
41 static int opened;
42 static struct hotplug_event evqueue[HOTPLUG_MAXEVENTS];
43 static int evqueue_head, evqueue_tail, evqueue_count; /* [M] */
44 static struct klist hotplug_klist; /* [M] */
45
46 void filt_hotplugrdetach(struct knote *);
47 int filt_hotplugread(struct knote *, long);
48 int filt_hotplugmodify(struct kevent *, struct knote *);
49 int filt_hotplugprocess(struct knote *, struct kevent *);
50
51 const struct filterops hotplugread_filtops = {
52 .f_flags = FILTEROP_ISFD | FILTEROP_MPSAFE,
53 .f_attach = NULL,
54 .f_detach = filt_hotplugrdetach,
55 .f_event = filt_hotplugread,
56 .f_modify = filt_hotplugmodify,
57 .f_process = filt_hotplugprocess,
58 };
59
60 #define EVQUEUE_NEXT(p) (p == HOTPLUG_MAXEVENTS - 1 ? 0 : p + 1)
61
62
63 int hotplug_put_event(struct hotplug_event *);
64 int hotplug_get_event(struct hotplug_event *);
65
66 void hotplugattach(int);
67
68 void
hotplugattach(int count)69 hotplugattach(int count)
70 {
71 opened = 0;
72 evqueue_head = 0;
73 evqueue_tail = 0;
74 evqueue_count = 0;
75
76 klist_init_mutex(&hotplug_klist, &hotplug_mtx);
77 }
78
79 void
hotplug_device_attach(enum devclass class,char * name)80 hotplug_device_attach(enum devclass class, char *name)
81 {
82 struct hotplug_event he;
83
84 he.he_type = HOTPLUG_DEVAT;
85 he.he_devclass = class;
86 strlcpy(he.he_devname, name, sizeof(he.he_devname));
87 hotplug_put_event(&he);
88 }
89
90 void
hotplug_device_detach(enum devclass class,char * name)91 hotplug_device_detach(enum devclass class, char *name)
92 {
93 struct hotplug_event he;
94
95 he.he_type = HOTPLUG_DEVDT;
96 he.he_devclass = class;
97 strlcpy(he.he_devname, name, sizeof(he.he_devname));
98 hotplug_put_event(&he);
99 }
100
101 int
hotplug_put_event(struct hotplug_event * he)102 hotplug_put_event(struct hotplug_event *he)
103 {
104 mtx_enter(&hotplug_mtx);
105 if (evqueue_count == HOTPLUG_MAXEVENTS && opened) {
106 mtx_leave(&hotplug_mtx);
107 printf("hotplug: event lost, queue full\n");
108 return (1);
109 }
110
111 evqueue[evqueue_head] = *he;
112 evqueue_head = EVQUEUE_NEXT(evqueue_head);
113 if (evqueue_count == HOTPLUG_MAXEVENTS)
114 evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
115 else
116 evqueue_count++;
117 knote_locked(&hotplug_klist, 0);
118 wakeup(&evqueue);
119 mtx_leave(&hotplug_mtx);
120 return (0);
121 }
122
123 int
hotplug_get_event(struct hotplug_event * he)124 hotplug_get_event(struct hotplug_event *he)
125 {
126 if (evqueue_count == 0)
127 return (1);
128
129 *he = evqueue[evqueue_tail];
130 evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
131 evqueue_count--;
132 return (0);
133 }
134
135 int
hotplugopen(dev_t dev,int flag,int mode,struct proc * p)136 hotplugopen(dev_t dev, int flag, int mode, struct proc *p)
137 {
138 if (minor(dev) != 0)
139 return (ENXIO);
140 if ((flag & FWRITE))
141 return (EPERM);
142 if (opened)
143 return (EBUSY);
144 opened = 1;
145 return (0);
146 }
147
148 int
hotplugclose(dev_t dev,int flag,int mode,struct proc * p)149 hotplugclose(dev_t dev, int flag, int mode, struct proc *p)
150 {
151 struct hotplug_event he;
152
153 mtx_enter(&hotplug_mtx);
154 while (hotplug_get_event(&he) == 0)
155 continue;
156 mtx_leave(&hotplug_mtx);
157 klist_invalidate(&hotplug_klist);
158 opened = 0;
159 return (0);
160 }
161
162 int
hotplugread(dev_t dev,struct uio * uio,int flags)163 hotplugread(dev_t dev, struct uio *uio, int flags)
164 {
165 struct hotplug_event he;
166 int error;
167
168 if (uio->uio_resid != sizeof(he))
169 return (EINVAL);
170
171 mtx_enter(&hotplug_mtx);
172 while (hotplug_get_event(&he)) {
173 if (flags & IO_NDELAY) {
174 mtx_leave(&hotplug_mtx);
175 return (EAGAIN);
176 }
177
178 error = msleep_nsec(&evqueue, &hotplug_mtx, PRIBIO | PCATCH,
179 "htplev", INFSLP);
180 if (error) {
181 mtx_leave(&hotplug_mtx);
182 return (error);
183 }
184 }
185 mtx_leave(&hotplug_mtx);
186
187 return (uiomove(&he, sizeof(he), uio));
188 }
189
190 int
hotplugioctl(dev_t dev,u_long cmd,caddr_t data,int flag,struct proc * p)191 hotplugioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
192 {
193 switch (cmd) {
194 case FIOASYNC:
195 /* ignore */
196 default:
197 return (ENOTTY);
198 }
199
200 return (0);
201 }
202
203 int
hotplugkqfilter(dev_t dev,struct knote * kn)204 hotplugkqfilter(dev_t dev, struct knote *kn)
205 {
206 switch (kn->kn_filter) {
207 case EVFILT_READ:
208 kn->kn_fop = &hotplugread_filtops;
209 break;
210 default:
211 return (EINVAL);
212 }
213
214 klist_insert(&hotplug_klist, kn);
215 return (0);
216 }
217
218 void
filt_hotplugrdetach(struct knote * kn)219 filt_hotplugrdetach(struct knote *kn)
220 {
221 klist_remove(&hotplug_klist, kn);
222 }
223
224 int
filt_hotplugread(struct knote * kn,long hint)225 filt_hotplugread(struct knote *kn, long hint)
226 {
227 kn->kn_data = evqueue_count;
228
229 return (evqueue_count > 0);
230 }
231
232 int
filt_hotplugmodify(struct kevent * kev,struct knote * kn)233 filt_hotplugmodify(struct kevent *kev, struct knote *kn)
234 {
235 int active;
236
237 mtx_enter(&hotplug_mtx);
238 active = knote_modify(kev, kn);
239 mtx_leave(&hotplug_mtx);
240
241 return (active);
242 }
243
244 int
filt_hotplugprocess(struct knote * kn,struct kevent * kev)245 filt_hotplugprocess(struct knote *kn, struct kevent *kev)
246 {
247 int active;
248
249 mtx_enter(&hotplug_mtx);
250 active = knote_process(kn, kev);
251 mtx_leave(&hotplug_mtx);
252
253 return (active);
254 }
255