xref: /netbsd-src/sys/kern/kern_drvctl.c (revision 4b71a66d0f279143147d63ebfcfd8a59499a3684)
1 /* $NetBSD: kern_drvctl.c,v 1.18 2008/05/30 15:30:37 freza Exp $ */
2 
3 /*
4  * Copyright (c) 2004
5  * 	Matthias Drochner.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions, and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: kern_drvctl.c,v 1.18 2008/05/30 15:30:37 freza Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/kernel.h>
35 #include <sys/conf.h>
36 #include <sys/device.h>
37 #include <sys/event.h>
38 #include <sys/malloc.h>
39 #include <sys/kmem.h>
40 #include <sys/ioctl.h>
41 #include <sys/fcntl.h>
42 #include <sys/file.h>
43 #include <sys/filedesc.h>
44 #include <sys/drvctlio.h>
45 #include <sys/devmon.h>
46 
47 struct drvctl_event {
48 	TAILQ_ENTRY(drvctl_event) dce_link;
49 	prop_dictionary_t	dce_event;
50 };
51 
52 TAILQ_HEAD(drvctl_queue, drvctl_event);
53 
54 static struct drvctl_queue	drvctl_eventq;		/* FIFO */
55 static kcondvar_t		drvctl_cond;
56 static kmutex_t			drvctl_lock;
57 static int			drvctl_nopen = 0, drvctl_eventcnt = 0;
58 
59 #define DRVCTL_EVENTQ_DEPTH	64	/* arbitrary queue limit */
60 
61 dev_type_open(drvctlopen);
62 
63 const struct cdevsw drvctl_cdevsw = {
64 	drvctlopen, nullclose, nullread, nullwrite, noioctl,
65 	nostop, notty, nopoll, nommap, nokqfilter, D_OTHER
66 };
67 
68 void drvctlattach(int);
69 
70 static int	drvctl_read(struct file *, off_t *, struct uio *,
71 			    kauth_cred_t, int);
72 static int	drvctl_write(struct file *, off_t *, struct uio *,
73 			     kauth_cred_t, int);
74 static int	drvctl_ioctl(struct file *, u_long, void *);
75 static int	drvctl_close(struct file *);
76 
77 static const struct fileops drvctl_fileops = {
78 	drvctl_read,
79 	drvctl_write,
80 	drvctl_ioctl,
81 	fnullop_fcntl,
82 	fnullop_poll,
83 	fbadop_stat,
84 	drvctl_close,
85 	fnullop_kqfilter
86 };
87 
88 #define MAXLOCATORS 100
89 
90 static int drvctl_command(struct lwp *, struct plistref *, u_long, int);
91 static int drvctl_getevent(struct lwp *, struct plistref *, u_long, int);
92 
93 void
94 drvctl_init(void)
95 {
96 	TAILQ_INIT(&drvctl_eventq);
97 	mutex_init(&drvctl_lock, MUTEX_DEFAULT, IPL_NONE);
98 	cv_init(&drvctl_cond, "devmon");
99 }
100 
101 void
102 devmon_insert(const char *event, prop_dictionary_t ev)
103 {
104 	struct drvctl_event *dce, *odce;;
105 
106 	mutex_enter(&drvctl_lock);
107 
108 	if (drvctl_nopen == 0) {
109 		mutex_exit(&drvctl_lock);
110 		return;
111 	}
112 
113 	/* Fill in mandatory member */
114 	if (!prop_dictionary_set_cstring_nocopy(ev, "event", event)) {
115 		prop_object_release(ev);
116 		mutex_exit(&drvctl_lock);
117 		return;
118 	}
119 
120 	dce = kmem_alloc(sizeof(*dce), KM_SLEEP);
121 	if (dce == NULL) {
122 		mutex_exit(&drvctl_lock);
123 		return;
124 	}
125 
126 	dce->dce_event = ev;
127 
128 	if (drvctl_eventcnt == DRVCTL_EVENTQ_DEPTH) {
129 		odce = TAILQ_FIRST(&drvctl_eventq);
130 		TAILQ_REMOVE(&drvctl_eventq, odce, dce_link);
131 		prop_object_release(odce->dce_event);
132 		kmem_free(odce, sizeof(*odce));
133 		--drvctl_eventcnt;
134 	}
135 
136 	TAILQ_INSERT_TAIL(&drvctl_eventq, dce, dce_link);
137 	++drvctl_eventcnt;
138 	cv_broadcast(&drvctl_cond);
139 
140 	mutex_exit(&drvctl_lock);
141 }
142 
143 int
144 drvctlopen(dev_t dev, int flags, int mode, struct lwp *l)
145 {
146 	struct file *fp;
147 	int fd;
148 	int ret;
149 
150 	ret = fd_allocfile(&fp, &fd);
151 	if (ret)
152 		return (ret);
153 
154 	/* XXX setup context */
155 	mutex_enter(&drvctl_lock);
156 	ret = fd_clone(fp, fd, flags, &drvctl_fileops, /* context */NULL);
157 	++drvctl_nopen;
158 	mutex_exit(&drvctl_lock);
159 
160 	return ret;
161 }
162 
163 static int
164 pmdevbyname(int cmd, struct devpmargs *a)
165 {
166 	struct device *d;
167 
168 	if ((d = device_find_by_xname(a->devname)) == NULL)
169 		return ENXIO;
170 
171 	switch (cmd) {
172 	case DRVSUSPENDDEV:
173 		return pmf_device_recursive_suspend(d, PMF_F_NONE) ? 0 : EBUSY;
174 	case DRVRESUMEDEV:
175 		if (a->flags & DEVPM_F_SUBTREE) {
176 			return pmf_device_resume_subtree(d, PMF_F_NONE)
177 			    ? 0 : EBUSY;
178 		} else {
179 			return pmf_device_recursive_resume(d, PMF_F_NONE)
180 			    ? 0 : EBUSY;
181 		}
182 	default:
183 		return EPASSTHROUGH;
184 	}
185 }
186 
187 static int
188 listdevbyname(struct devlistargs *l)
189 {
190 	device_t d, child;
191 	deviter_t di;
192 	int cnt = 0, idx, error = 0;
193 
194 	if ((d = device_find_by_xname(l->l_devname)) == NULL)
195 		return ENXIO;
196 
197 	for (child = deviter_first(&di, 0); child != NULL;
198 	     child = deviter_next(&di)) {
199 		if (device_parent(child) != d)
200 			continue;
201 		idx = cnt++;
202 		if (l->l_childname == NULL || idx >= l->l_children)
203 			continue;
204 		error = copyoutstr(device_xname(child), l->l_childname[idx],
205 				sizeof(l->l_childname[idx]), NULL);
206 		if (error != 0)
207 			break;
208 	}
209 	deviter_release(&di);
210 
211 	l->l_children = cnt;
212 	return error;
213 }
214 
215 static int
216 detachdevbyname(const char *devname)
217 {
218 	struct device *d;
219 
220 	if ((d = device_find_by_xname(devname)) == NULL)
221 		return ENXIO;
222 
223 #ifndef XXXFULLRISK
224 	/*
225 	 * If the parent cannot be notified, it might keep
226 	 * pointers to the detached device.
227 	 * There might be a private notification mechanism,
228 	 * but better play save here.
229 	 */
230 	if (d->dv_parent && !d->dv_parent->dv_cfattach->ca_childdetached)
231 		return (ENOTSUP);
232 #endif
233 	return (config_detach(d, 0));
234 }
235 
236 static int
237 rescanbus(const char *busname, const char *ifattr,
238 	  int numlocators, const int *locators)
239 {
240 	int i, rc;
241 	struct device *d;
242 	const struct cfiattrdata * const *ap;
243 
244 	/* XXX there should be a way to get limits and defaults (per device)
245 	   from config generated data */
246 	int locs[MAXLOCATORS];
247 	for (i = 0; i < MAXLOCATORS; i++)
248 		locs[i] = -1;
249 
250 	for (i = 0; i < numlocators;i++)
251 		locs[i] = locators[i];
252 
253 	if ((d = device_find_by_xname(busname)) == NULL)
254 		return ENXIO;
255 
256 	/*
257 	 * must support rescan, and must have something
258 	 * to attach to
259 	 */
260 	if (!d->dv_cfattach->ca_rescan ||
261 	    !d->dv_cfdriver->cd_attrs)
262 		return (ENODEV);
263 
264 	/* allow to omit attribute if there is exactly one */
265 	if (!ifattr) {
266 		if (d->dv_cfdriver->cd_attrs[1])
267 			return (EINVAL);
268 		ifattr = d->dv_cfdriver->cd_attrs[0]->ci_name;
269 	} else {
270 		/* check for valid attribute passed */
271 		for (ap = d->dv_cfdriver->cd_attrs; *ap; ap++)
272 			if (!strcmp((*ap)->ci_name, ifattr))
273 				break;
274 		if (!*ap)
275 			return (EINVAL);
276 	}
277 
278 	rc = (*d->dv_cfattach->ca_rescan)(d, ifattr, locs);
279 	config_deferred(NULL);
280 	return rc;
281 }
282 
283 static int
284 drvctl_read(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
285     int flags)
286 {
287 	return (ENODEV);
288 }
289 
290 static int
291 drvctl_write(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
292     int flags)
293 {
294 	return (ENODEV);
295 }
296 
297 static int
298 drvctl_ioctl(struct file *fp, u_long cmd, void *data)
299 {
300 	int res;
301 	char *ifattr;
302 	int *locs;
303 
304 	switch (cmd) {
305 	case DRVSUSPENDDEV:
306 	case DRVRESUMEDEV:
307 #define d ((struct devpmargs *)data)
308 		res = pmdevbyname(cmd, d);
309 #undef d
310 		break;
311 	case DRVLISTDEV:
312 		res = listdevbyname((struct devlistargs *)data);
313 		break;
314 	case DRVDETACHDEV:
315 #define d ((struct devdetachargs *)data)
316 		res = detachdevbyname(d->devname);
317 #undef d
318 		break;
319 	case DRVRESCANBUS:
320 #define d ((struct devrescanargs *)data)
321 		d->busname[sizeof(d->busname) - 1] = '\0';
322 
323 		/* XXX better copyin? */
324 		if (d->ifattr[0]) {
325 			d->ifattr[sizeof(d->ifattr) - 1] = '\0';
326 			ifattr = d->ifattr;
327 		} else
328 			ifattr = 0;
329 
330 		if (d->numlocators) {
331 			if (d->numlocators > MAXLOCATORS)
332 				return (EINVAL);
333 			locs = malloc(d->numlocators * sizeof(int), M_DEVBUF,
334 				      M_WAITOK);
335 			res = copyin(d->locators, locs,
336 				     d->numlocators * sizeof(int));
337 			if (res) {
338 				free(locs, M_DEVBUF);
339 				return (res);
340 			}
341 		} else
342 			locs = 0;
343 		res = rescanbus(d->busname, ifattr, d->numlocators, locs);
344 		if (locs)
345 			free(locs, M_DEVBUF);
346 #undef d
347 		break;
348 	case DRVCTLCOMMAND:
349 	    	res = drvctl_command(curlwp, (struct plistref *)data, cmd,
350 		    fp->f_flag);
351 	    	break;
352 	case DRVGETEVENT:
353 		res = drvctl_getevent(curlwp, (struct plistref *)data, cmd,
354 		    fp->f_flag);
355 		break;
356 	default:
357 		return (EPASSTHROUGH);
358 	}
359 	return (res);
360 }
361 
362 static int
363 drvctl_close(struct file *fp)
364 {
365 	struct drvctl_event *dce;
366 
367 	/* XXX free context */
368 	mutex_enter(&drvctl_lock);
369 	KASSERT(drvctl_nopen > 0);
370 	--drvctl_nopen;
371 	if (drvctl_nopen == 0) {
372 		/* flush queue */
373 		while ((dce = TAILQ_FIRST(&drvctl_eventq)) != NULL) {
374 			TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
375 			KASSERT(drvctl_eventcnt > 0);
376 			--drvctl_eventcnt;
377 			prop_object_release(dce->dce_event);
378 			kmem_free(dce, sizeof(*dce));
379 		}
380 	}
381 	mutex_exit(&drvctl_lock);
382 
383 	return (0);
384 }
385 
386 void
387 drvctlattach(int arg)
388 {
389 }
390 
391 /*****************************************************************************
392  * Driver control command processing engine
393  *****************************************************************************/
394 
395 static int
396 drvctl_command_get_properties(struct lwp *l,
397 			      prop_dictionary_t command_dict,
398 			      prop_dictionary_t results_dict)
399 {
400 	prop_dictionary_t args_dict;
401 	prop_string_t devname_string;
402 	device_t dev;
403 	deviter_t di;
404 
405 	args_dict = prop_dictionary_get(command_dict, "drvctl-arguments");
406 	if (args_dict == NULL)
407 		return (EINVAL);
408 
409 	devname_string = prop_dictionary_get(args_dict, "device-name");
410 	if (devname_string == NULL)
411 		return (EINVAL);
412 
413 	for (dev = deviter_first(&di, 0); dev != NULL;
414 	     dev = deviter_next(&di)) {
415 		if (prop_string_equals_cstring(devname_string,
416 					       device_xname(dev))) {
417 			prop_dictionary_set(results_dict, "drvctl-result-data",
418 			    device_properties(dev));
419 			break;
420 		}
421 	}
422 
423 	deviter_release(&di);
424 
425 	if (dev == NULL)
426 		return (ESRCH);
427 
428 	return (0);
429 }
430 
431 struct drvctl_command_desc {
432 	const char *dcd_name;		/* command name */
433 	int (*dcd_func)(struct lwp *,	/* handler function */
434 			prop_dictionary_t,
435 			prop_dictionary_t);
436 	int dcd_rw;			/* read or write required */
437 };
438 
439 static const struct drvctl_command_desc drvctl_command_table[] = {
440 	{ .dcd_name = "get-properties",
441 	  .dcd_func = drvctl_command_get_properties,
442 	  .dcd_rw   = FREAD,
443 	},
444 
445 	{ .dcd_name = NULL }
446 };
447 
448 static int
449 drvctl_command(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
450 	       int fflag)
451 {
452 	prop_dictionary_t command_dict, results_dict;
453 	prop_string_t command_string;
454 	const struct drvctl_command_desc *dcd;
455 	int error;
456 
457 	error = prop_dictionary_copyin_ioctl(pref, ioctl_cmd, &command_dict);
458 	if (error)
459 		return (error);
460 
461 	results_dict = prop_dictionary_create();
462 	if (results_dict == NULL) {
463 		prop_object_release(command_dict);
464 		return (ENOMEM);
465 	}
466 
467 	command_string = prop_dictionary_get(command_dict, "drvctl-command");
468 	if (command_string == NULL) {
469 		error = EINVAL;
470 		goto out;
471 	}
472 
473 	for (dcd = drvctl_command_table; dcd->dcd_name != NULL; dcd++) {
474 		if (prop_string_equals_cstring(command_string,
475 					       dcd->dcd_name))
476 			break;
477 	}
478 
479 	if (dcd->dcd_name == NULL) {
480 		error = EINVAL;
481 		goto out;
482 	}
483 
484 	if ((fflag & dcd->dcd_rw) == 0) {
485 		error = EPERM;
486 		goto out;
487 	}
488 
489 	error = (*dcd->dcd_func)(l, command_dict, results_dict);
490 
491 	prop_dictionary_set_int32(results_dict, "drvctl-error", error);
492 
493 	error = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, results_dict);
494  out:
495 	prop_object_release(command_dict);
496 	prop_object_release(results_dict);
497 	return (error);
498 }
499 
500 static int
501 drvctl_getevent(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
502 	        int fflag)
503 {
504 	struct drvctl_event *dce;
505 	int ret;
506 
507 	if ((fflag & (FREAD|FWRITE)) != (FREAD|FWRITE))
508 		return (EPERM);
509 
510 	mutex_enter(&drvctl_lock);
511 	while ((dce = TAILQ_FIRST(&drvctl_eventq)) == NULL) {
512 		if (fflag & O_NONBLOCK) {
513 			mutex_exit(&drvctl_lock);
514 			return (EWOULDBLOCK);
515 		}
516 
517 		ret = cv_wait_sig(&drvctl_cond, &drvctl_lock);
518 		if (ret) {
519 			mutex_exit(&drvctl_lock);
520 			return (ret);
521 		}
522 	}
523 	TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
524 	KASSERT(drvctl_eventcnt > 0);
525 	--drvctl_eventcnt;
526 	mutex_exit(&drvctl_lock);
527 
528 	ret = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, dce->dce_event);
529 
530 	prop_object_release(dce->dce_event);
531 	kmem_free(dce, sizeof(*dce));
532 
533 	return (ret);
534 }
535