xref: /openbsd-src/sys/dev/hotplug.c (revision b9ae17a00bed12afbf856d60e03f648694a9de20)
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
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
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
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
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
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
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
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
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
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
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
219 filt_hotplugrdetach(struct knote *kn)
220 {
221 	klist_remove(&hotplug_klist, kn);
222 }
223 
224 int
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
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
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