xref: /openbsd-src/sys/dev/hotplug.c (revision a0747c9f67a4ae71ccb71e62a28d1ea19e06a63c)
1 /*	$OpenBSD: hotplug.c,v 1.21 2020/12/25 12:59:52 visa 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/fcntl.h>
26 #include <sys/hotplug.h>
27 #include <sys/ioctl.h>
28 #include <sys/poll.h>
29 #include <sys/vnode.h>
30 
31 #define HOTPLUG_MAXEVENTS	64
32 
33 static int opened;
34 static struct hotplug_event evqueue[HOTPLUG_MAXEVENTS];
35 static int evqueue_head, evqueue_tail, evqueue_count;
36 static struct selinfo hotplug_sel;
37 
38 void filt_hotplugrdetach(struct knote *);
39 int  filt_hotplugread(struct knote *, long);
40 
41 const struct filterops hotplugread_filtops = {
42 	.f_flags	= FILTEROP_ISFD,
43 	.f_attach	= NULL,
44 	.f_detach	= filt_hotplugrdetach,
45 	.f_event	= filt_hotplugread,
46 };
47 
48 #define EVQUEUE_NEXT(p) (p == HOTPLUG_MAXEVENTS - 1 ? 0 : p + 1)
49 
50 
51 int hotplug_put_event(struct hotplug_event *);
52 int hotplug_get_event(struct hotplug_event *);
53 
54 void hotplugattach(int);
55 
56 void
57 hotplugattach(int count)
58 {
59 	opened = 0;
60 	evqueue_head = 0;
61 	evqueue_tail = 0;
62 	evqueue_count = 0;
63 }
64 
65 void
66 hotplug_device_attach(enum devclass class, char *name)
67 {
68 	struct hotplug_event he;
69 
70 	he.he_type = HOTPLUG_DEVAT;
71 	he.he_devclass = class;
72 	strlcpy(he.he_devname, name, sizeof(he.he_devname));
73 	hotplug_put_event(&he);
74 }
75 
76 void
77 hotplug_device_detach(enum devclass class, char *name)
78 {
79 	struct hotplug_event he;
80 
81 	he.he_type = HOTPLUG_DEVDT;
82 	he.he_devclass = class;
83 	strlcpy(he.he_devname, name, sizeof(he.he_devname));
84 	hotplug_put_event(&he);
85 }
86 
87 int
88 hotplug_put_event(struct hotplug_event *he)
89 {
90 	if (evqueue_count == HOTPLUG_MAXEVENTS && opened) {
91 		printf("hotplug: event lost, queue full\n");
92 		return (1);
93 	}
94 
95 	evqueue[evqueue_head] = *he;
96 	evqueue_head = EVQUEUE_NEXT(evqueue_head);
97 	if (evqueue_count == HOTPLUG_MAXEVENTS)
98 		evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
99 	else
100 		evqueue_count++;
101 	wakeup(&evqueue);
102 	selwakeup(&hotplug_sel);
103 	return (0);
104 }
105 
106 int
107 hotplug_get_event(struct hotplug_event *he)
108 {
109 	int s;
110 
111 	if (evqueue_count == 0)
112 		return (1);
113 
114 	s = splbio();
115 	*he = evqueue[evqueue_tail];
116 	evqueue_tail = EVQUEUE_NEXT(evqueue_tail);
117 	evqueue_count--;
118 	splx(s);
119 	return (0);
120 }
121 
122 int
123 hotplugopen(dev_t dev, int flag, int mode, struct proc *p)
124 {
125 	if (minor(dev) != 0)
126 		return (ENXIO);
127 	if ((flag & FWRITE))
128 		return (EPERM);
129 	if (opened)
130 		return (EBUSY);
131 	opened = 1;
132 	return (0);
133 }
134 
135 int
136 hotplugclose(dev_t dev, int flag, int mode, struct proc *p)
137 {
138 	struct hotplug_event he;
139 
140 	while (hotplug_get_event(&he) == 0)
141 		continue;
142 	opened = 0;
143 	return (0);
144 }
145 
146 int
147 hotplugread(dev_t dev, struct uio *uio, int flags)
148 {
149 	struct hotplug_event he;
150 	int error;
151 
152 	if (uio->uio_resid != sizeof(he))
153 		return (EINVAL);
154 
155 again:
156 	if (hotplug_get_event(&he) == 0)
157 		return (uiomove(&he, sizeof(he), uio));
158 	if (flags & IO_NDELAY)
159 		return (EAGAIN);
160 
161 	error = tsleep_nsec(&evqueue, PRIBIO | PCATCH, "htplev", INFSLP);
162 	if (error)
163 		return (error);
164 	goto again;
165 }
166 
167 int
168 hotplugioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
169 {
170 	switch (cmd) {
171 	case FIOASYNC:
172 		/* ignore */
173 	case FIONBIO:
174 		/* handled in the upper fs layer */
175 		break;
176 	default:
177 		return (ENOTTY);
178 	}
179 
180 	return (0);
181 }
182 
183 int
184 hotplugpoll(dev_t dev, int events, struct proc *p)
185 {
186 	int revents = 0;
187 
188 	if (events & (POLLIN | POLLRDNORM)) {
189 		if (evqueue_count > 0)
190 			revents |= events & (POLLIN | POLLRDNORM);
191 		else
192 			selrecord(p, &hotplug_sel);
193 	}
194 
195 	return (revents);
196 }
197 
198 int
199 hotplugkqfilter(dev_t dev, struct knote *kn)
200 {
201 	struct klist *klist;
202 	int s;
203 
204 	switch (kn->kn_filter) {
205 	case EVFILT_READ:
206 		klist = &hotplug_sel.si_note;
207 		kn->kn_fop = &hotplugread_filtops;
208 		break;
209 	default:
210 		return (EINVAL);
211 	}
212 
213 	s = splbio();
214 	klist_insert_locked(klist, kn);
215 	splx(s);
216 	return (0);
217 }
218 
219 void
220 filt_hotplugrdetach(struct knote *kn)
221 {
222 	int s;
223 
224 	s = splbio();
225 	klist_remove_locked(&hotplug_sel.si_note, kn);
226 	splx(s);
227 }
228 
229 int
230 filt_hotplugread(struct knote *kn, long hint)
231 {
232 	kn->kn_data = evqueue_count;
233 
234 	return (evqueue_count > 0);
235 }
236