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