xref: /netbsd-src/sys/kern/kern_drvctl.c (revision c9496f6b604074a9451a67df576a5b423068e71e)
1 /* $NetBSD: kern_drvctl.c,v 1.43 2017/11/30 20:25:55 christos 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.43 2017/11/30 20:25:55 christos 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/kmem.h>
39 #include <sys/ioctl.h>
40 #include <sys/fcntl.h>
41 #include <sys/file.h>
42 #include <sys/filedesc.h>
43 #include <sys/select.h>
44 #include <sys/poll.h>
45 #include <sys/drvctlio.h>
46 #include <sys/devmon.h>
47 #include <sys/stat.h>
48 #include <sys/kauth.h>
49 #include <sys/lwp.h>
50 #include <sys/module.h>
51 
52 #include "ioconf.h"
53 
54 struct drvctl_event {
55 	TAILQ_ENTRY(drvctl_event) dce_link;
56 	prop_dictionary_t	dce_event;
57 };
58 
59 TAILQ_HEAD(drvctl_queue, drvctl_event);
60 
61 static struct drvctl_queue	drvctl_eventq;		/* FIFO */
62 static kcondvar_t		drvctl_cond;
63 static kmutex_t			drvctl_lock;
64 static int			drvctl_nopen = 0, drvctl_eventcnt = 0;
65 static struct selinfo		drvctl_rdsel;
66 
67 #define DRVCTL_EVENTQ_DEPTH	64	/* arbitrary queue limit */
68 
69 dev_type_open(drvctlopen);
70 
71 const struct cdevsw drvctl_cdevsw = {
72 	.d_open = drvctlopen,
73 	.d_close = nullclose,
74 	.d_read = nullread,
75 	.d_write = nullwrite,
76 	.d_ioctl = noioctl,
77 	.d_stop = nostop,
78 	.d_tty = notty,
79 	.d_poll = nopoll,
80 	.d_mmap = nommap,
81 	.d_kqfilter = nokqfilter,
82 	.d_discard = nodiscard,
83 	.d_flag = D_OTHER
84 };
85 
86 static int	drvctl_read(struct file *, off_t *, struct uio *,
87 			    kauth_cred_t, int);
88 static int	drvctl_write(struct file *, off_t *, struct uio *,
89 			     kauth_cred_t, int);
90 static int	drvctl_ioctl(struct file *, u_long, void *);
91 static int	drvctl_poll(struct file *, int);
92 static int	drvctl_stat(struct file *, struct stat *);
93 static int	drvctl_close(struct file *);
94 
95 static const struct fileops drvctl_fileops = {
96 	.fo_name = "drvctl",
97 	.fo_read = drvctl_read,
98 	.fo_write = drvctl_write,
99 	.fo_ioctl = drvctl_ioctl,
100 	.fo_fcntl = fnullop_fcntl,
101 	.fo_poll = drvctl_poll,
102 	.fo_stat = drvctl_stat,
103 	.fo_close = drvctl_close,
104 	.fo_kqfilter = fnullop_kqfilter,
105 	.fo_restart = fnullop_restart,
106 };
107 
108 #define MAXLOCATORS 100
109 
110 extern int (*devmon_insert_vec)(const char *, prop_dictionary_t);
111 static int (*saved_insert_vec)(const char *, prop_dictionary_t) = NULL;
112 
113 static int drvctl_command(struct lwp *, struct plistref *, u_long, int);
114 static int drvctl_getevent(struct lwp *, struct plistref *, u_long, int);
115 
116 void
117 drvctl_init(void)
118 {
119 	TAILQ_INIT(&drvctl_eventq);
120 	mutex_init(&drvctl_lock, MUTEX_DEFAULT, IPL_NONE);
121 	cv_init(&drvctl_cond, "devmon");
122 	selinit(&drvctl_rdsel);
123 }
124 
125 void
126 drvctl_fini(void)
127 {
128 
129 	seldestroy(&drvctl_rdsel);
130 	cv_destroy(&drvctl_cond);
131 	mutex_destroy(&drvctl_lock);
132 }
133 
134 int
135 devmon_insert(const char *event, prop_dictionary_t ev)
136 {
137 	struct drvctl_event *dce, *odce;
138 
139 	mutex_enter(&drvctl_lock);
140 
141 	if (drvctl_nopen == 0) {
142 		prop_object_release(ev);
143 		mutex_exit(&drvctl_lock);
144 		return 0;
145 	}
146 
147 	/* Fill in mandatory member */
148 	if (!prop_dictionary_set_cstring_nocopy(ev, "event", event)) {
149 		prop_object_release(ev);
150 		mutex_exit(&drvctl_lock);
151 		return 0;
152 	}
153 
154 	dce = kmem_alloc(sizeof(*dce), KM_SLEEP);
155 	dce->dce_event = ev;
156 
157 	if (drvctl_eventcnt == DRVCTL_EVENTQ_DEPTH) {
158 		odce = TAILQ_FIRST(&drvctl_eventq);
159 		TAILQ_REMOVE(&drvctl_eventq, odce, dce_link);
160 		prop_object_release(odce->dce_event);
161 		kmem_free(odce, sizeof(*odce));
162 		--drvctl_eventcnt;
163 	}
164 
165 	TAILQ_INSERT_TAIL(&drvctl_eventq, dce, dce_link);
166 	++drvctl_eventcnt;
167 	cv_broadcast(&drvctl_cond);
168 	selnotify(&drvctl_rdsel, 0, 0);
169 
170 	mutex_exit(&drvctl_lock);
171 	return 0;
172 }
173 
174 int
175 drvctlopen(dev_t dev, int flags, int mode, struct lwp *l)
176 {
177 	struct file *fp;
178 	int fd;
179 	int ret;
180 
181 	ret = fd_allocfile(&fp, &fd);
182 	if (ret)
183 		return ret;
184 
185 	/* XXX setup context */
186 	mutex_enter(&drvctl_lock);
187 	ret = fd_clone(fp, fd, flags, &drvctl_fileops, /* context */NULL);
188 	++drvctl_nopen;
189 	mutex_exit(&drvctl_lock);
190 
191 	return ret;
192 }
193 
194 static int
195 pmdevbyname(u_long cmd, struct devpmargs *a)
196 {
197 	device_t d;
198 
199 	if ((d = device_find_by_xname(a->devname)) == NULL)
200 		return ENXIO;
201 
202 	switch (cmd) {
203 	case DRVSUSPENDDEV:
204 		return pmf_device_recursive_suspend(d, PMF_Q_DRVCTL) ? 0 : EBUSY;
205 	case DRVRESUMEDEV:
206 		if (a->flags & DEVPM_F_SUBTREE) {
207 			return pmf_device_subtree_resume(d, PMF_Q_DRVCTL)
208 			    ? 0 : EBUSY;
209 		} else {
210 			return pmf_device_recursive_resume(d, PMF_Q_DRVCTL)
211 			    ? 0 : EBUSY;
212 		}
213 	default:
214 		return EPASSTHROUGH;
215 	}
216 }
217 
218 static int
219 listdevbyname(struct devlistargs *l)
220 {
221 	device_t d, child;
222 	deviter_t di;
223 	int cnt = 0, idx, error = 0;
224 
225 	if (*l->l_devname == '\0')
226 		d = NULL;
227 	else if (memchr(l->l_devname, 0, sizeof(l->l_devname)) == NULL)
228 		return EINVAL;
229 	else if ((d = device_find_by_xname(l->l_devname)) == NULL)
230 		return ENXIO;
231 
232 	for (child = deviter_first(&di, 0); child != NULL;
233 	     child = deviter_next(&di)) {
234 		if (device_parent(child) != d)
235 			continue;
236 		idx = cnt++;
237 		if (l->l_childname == NULL || idx >= l->l_children)
238 			continue;
239 		error = copyoutstr(device_xname(child), l->l_childname[idx],
240 				sizeof(l->l_childname[idx]), NULL);
241 		if (error != 0)
242 			break;
243 	}
244 	deviter_release(&di);
245 
246 	l->l_children = cnt;
247 	return error;
248 }
249 
250 static int
251 detachdevbyname(const char *devname)
252 {
253 	device_t d;
254 
255 	if ((d = device_find_by_xname(devname)) == NULL)
256 		return ENXIO;
257 
258 #ifndef XXXFULLRISK
259 	/*
260 	 * If the parent cannot be notified, it might keep
261 	 * pointers to the detached device.
262 	 * There might be a private notification mechanism,
263 	 * but better play it safe here.
264 	 */
265 	if (d->dv_parent && !d->dv_parent->dv_cfattach->ca_childdetached)
266 		return ENOTSUP;
267 #endif
268 	return config_detach(d, 0);
269 }
270 
271 static int
272 rescanbus(const char *busname, const char *ifattr,
273 	  int numlocators, const int *locators)
274 {
275 	int i, rc;
276 	device_t d;
277 	const struct cfiattrdata * const *ap;
278 
279 	/* XXX there should be a way to get limits and defaults (per device)
280 	   from config generated data */
281 	int locs[MAXLOCATORS];
282 	for (i = 0; i < MAXLOCATORS; i++)
283 		locs[i] = -1;
284 
285 	for (i = 0; i < numlocators;i++)
286 		locs[i] = locators[i];
287 
288 	if ((d = device_find_by_xname(busname)) == NULL)
289 		return ENXIO;
290 
291 	/*
292 	 * must support rescan, and must have something
293 	 * to attach to
294 	 */
295 	if (!d->dv_cfattach->ca_rescan ||
296 	    !d->dv_cfdriver->cd_attrs)
297 		return ENODEV;
298 
299 	/* allow to omit attribute if there is exactly one */
300 	if (!ifattr) {
301 		if (d->dv_cfdriver->cd_attrs[1])
302 			return EINVAL;
303 		ifattr = d->dv_cfdriver->cd_attrs[0]->ci_name;
304 	} else {
305 		/* check for valid attribute passed */
306 		for (ap = d->dv_cfdriver->cd_attrs; *ap; ap++)
307 			if (!strcmp((*ap)->ci_name, ifattr))
308 				break;
309 		if (!*ap)
310 			return EINVAL;
311 	}
312 
313 	rc = (*d->dv_cfattach->ca_rescan)(d, ifattr, locs);
314 	config_deferred(NULL);
315 	return rc;
316 }
317 
318 static int
319 drvctl_read(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
320     int flags)
321 {
322 	return ENODEV;
323 }
324 
325 static int
326 drvctl_write(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
327     int flags)
328 {
329 	return ENODEV;
330 }
331 
332 static int
333 drvctl_ioctl(struct file *fp, u_long cmd, void *data)
334 {
335 	int res;
336 	char *ifattr;
337 	int *locs;
338 	size_t locs_sz = 0; /* XXXgcc */
339 
340 	switch (cmd) {
341 	case DRVSUSPENDDEV:
342 	case DRVRESUMEDEV:
343 #define d ((struct devpmargs *)data)
344 		res = pmdevbyname(cmd, d);
345 #undef d
346 		break;
347 	case DRVLISTDEV:
348 		res = listdevbyname((struct devlistargs *)data);
349 		break;
350 	case DRVDETACHDEV:
351 #define d ((struct devdetachargs *)data)
352 		res = detachdevbyname(d->devname);
353 #undef d
354 		break;
355 	case DRVRESCANBUS:
356 #define d ((struct devrescanargs *)data)
357 		d->busname[sizeof(d->busname) - 1] = '\0';
358 
359 		/* XXX better copyin? */
360 		if (d->ifattr[0]) {
361 			d->ifattr[sizeof(d->ifattr) - 1] = '\0';
362 			ifattr = d->ifattr;
363 		} else
364 			ifattr = 0;
365 
366 		if (d->numlocators) {
367 			if (d->numlocators > MAXLOCATORS)
368 				return EINVAL;
369 			locs_sz = d->numlocators * sizeof(int);
370 			locs = kmem_alloc(locs_sz, KM_SLEEP);
371 			res = copyin(d->locators, locs, locs_sz);
372 			if (res) {
373 				kmem_free(locs, locs_sz);
374 				return res;
375 			}
376 		} else
377 			locs = NULL;
378 		res = rescanbus(d->busname, ifattr, d->numlocators, locs);
379 		if (locs)
380 			kmem_free(locs, locs_sz);
381 #undef d
382 		break;
383 	case DRVCTLCOMMAND:
384 	    	res = drvctl_command(curlwp, (struct plistref *)data, cmd,
385 		    fp->f_flag);
386 	    	break;
387 	case DRVGETEVENT:
388 		res = drvctl_getevent(curlwp, (struct plistref *)data, cmd,
389 		    fp->f_flag);
390 		break;
391 	default:
392 		return EPASSTHROUGH;
393 	}
394 	return res;
395 }
396 
397 static int
398 drvctl_stat(struct file *fp, struct stat *st)
399 {
400 	(void)memset(st, 0, sizeof(*st));
401 	st->st_uid = kauth_cred_geteuid(fp->f_cred);
402 	st->st_gid = kauth_cred_getegid(fp->f_cred);
403 	return 0;
404 }
405 
406 static int
407 drvctl_poll(struct file *fp, int events)
408 {
409 	int revents = 0;
410 
411 	if (!TAILQ_EMPTY(&drvctl_eventq))
412 		revents |= events & (POLLIN | POLLRDNORM);
413 	else
414 		selrecord(curlwp, &drvctl_rdsel);
415 
416 	return revents;
417 }
418 
419 static int
420 drvctl_close(struct file *fp)
421 {
422 	struct drvctl_event *dce;
423 
424 	/* XXX free context */
425 	mutex_enter(&drvctl_lock);
426 	KASSERT(drvctl_nopen > 0);
427 	--drvctl_nopen;
428 	if (drvctl_nopen == 0) {
429 		/* flush queue */
430 		while ((dce = TAILQ_FIRST(&drvctl_eventq)) != NULL) {
431 			TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
432 			KASSERT(drvctl_eventcnt > 0);
433 			--drvctl_eventcnt;
434 			prop_object_release(dce->dce_event);
435 			kmem_free(dce, sizeof(*dce));
436 		}
437 	}
438 	mutex_exit(&drvctl_lock);
439 
440 	return 0;
441 }
442 
443 void
444 drvctlattach(int arg __unused)
445 {
446 }
447 
448 /*****************************************************************************
449  * Driver control command processing engine
450  *****************************************************************************/
451 
452 static int
453 drvctl_command_get_properties(struct lwp *l,
454 			      prop_dictionary_t command_dict,
455 			      prop_dictionary_t results_dict)
456 {
457 	prop_dictionary_t args_dict;
458 	prop_string_t devname_string;
459 	device_t dev;
460 	deviter_t di;
461 
462 	args_dict = prop_dictionary_get(command_dict, "drvctl-arguments");
463 	if (args_dict == NULL)
464 		return EINVAL;
465 
466 	devname_string = prop_dictionary_get(args_dict, "device-name");
467 	if (devname_string == NULL)
468 		return EINVAL;
469 
470 	for (dev = deviter_first(&di, 0); dev != NULL;
471 	     dev = deviter_next(&di)) {
472 		if (prop_string_equals_cstring(devname_string,
473 					       device_xname(dev))) {
474 			prop_dictionary_set(results_dict, "drvctl-result-data",
475 			    device_properties(dev));
476 			break;
477 		}
478 	}
479 
480 	deviter_release(&di);
481 
482 	if (dev == NULL)
483 		return ESRCH;
484 
485 	return 0;
486 }
487 
488 struct drvctl_command_desc {
489 	const char *dcd_name;		/* command name */
490 	int (*dcd_func)(struct lwp *,	/* handler function */
491 			prop_dictionary_t,
492 			prop_dictionary_t);
493 	int dcd_rw;			/* read or write required */
494 };
495 
496 static const struct drvctl_command_desc drvctl_command_table[] = {
497 	{ .dcd_name = "get-properties",
498 	  .dcd_func = drvctl_command_get_properties,
499 	  .dcd_rw   = FREAD,
500 	},
501 
502 	{ .dcd_name = NULL }
503 };
504 
505 static int
506 drvctl_command(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
507 	       int fflag)
508 {
509 	prop_dictionary_t command_dict, results_dict;
510 	prop_string_t command_string;
511 	const struct drvctl_command_desc *dcd;
512 	int error;
513 
514 	error = prop_dictionary_copyin_ioctl(pref, ioctl_cmd, &command_dict);
515 	if (error)
516 		return error;
517 
518 	results_dict = prop_dictionary_create();
519 	if (results_dict == NULL) {
520 		prop_object_release(command_dict);
521 		return ENOMEM;
522 	}
523 
524 	command_string = prop_dictionary_get(command_dict, "drvctl-command");
525 	if (command_string == NULL) {
526 		error = EINVAL;
527 		goto out;
528 	}
529 
530 	for (dcd = drvctl_command_table; dcd->dcd_name != NULL; dcd++) {
531 		if (prop_string_equals_cstring(command_string,
532 					       dcd->dcd_name))
533 			break;
534 	}
535 
536 	if (dcd->dcd_name == NULL) {
537 		error = EINVAL;
538 		goto out;
539 	}
540 
541 	if ((fflag & dcd->dcd_rw) == 0) {
542 		error = EPERM;
543 		goto out;
544 	}
545 
546 	error = (*dcd->dcd_func)(l, command_dict, results_dict);
547 
548 	prop_dictionary_set_int32(results_dict, "drvctl-error", error);
549 
550 	error = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, results_dict);
551  out:
552 	prop_object_release(command_dict);
553 	prop_object_release(results_dict);
554 	return error;
555 }
556 
557 static int
558 drvctl_getevent(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
559 	        int fflag)
560 {
561 	struct drvctl_event *dce;
562 	int ret;
563 
564 	if ((fflag & (FREAD|FWRITE)) != (FREAD|FWRITE))
565 		return EPERM;
566 
567 	mutex_enter(&drvctl_lock);
568 	while ((dce = TAILQ_FIRST(&drvctl_eventq)) == NULL) {
569 		if (fflag & O_NONBLOCK) {
570 			mutex_exit(&drvctl_lock);
571 			return EWOULDBLOCK;
572 		}
573 
574 		ret = cv_wait_sig(&drvctl_cond, &drvctl_lock);
575 		if (ret) {
576 			mutex_exit(&drvctl_lock);
577 			return ret;
578 		}
579 	}
580 	TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
581 	KASSERT(drvctl_eventcnt > 0);
582 	--drvctl_eventcnt;
583 	mutex_exit(&drvctl_lock);
584 
585 	ret = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, dce->dce_event);
586 
587 	prop_object_release(dce->dce_event);
588 	kmem_free(dce, sizeof(*dce));
589 
590 	return ret;
591 }
592 
593 /*
594  * Module glue
595  */
596 
597 MODULE(MODULE_CLASS_DRIVER, drvctl, NULL);
598 
599 int
600 drvctl_modcmd(modcmd_t cmd, void *arg)
601 {
602 	int error;
603 #ifdef _MODULE
604 	int bmajor, cmajor;
605 #endif
606 
607 	error = 0;
608 	switch (cmd) {
609 	case MODULE_CMD_INIT:
610 		drvctl_init();
611 
612 		mutex_enter(&drvctl_lock);
613 #ifdef _MODULE
614 		bmajor = cmajor = -1;
615 		error = devsw_attach("drvctl", NULL, &bmajor,
616 		    &drvctl_cdevsw, &cmajor);
617 #endif
618 		if (error == 0) {
619 			KASSERT(saved_insert_vec == NULL);
620 			saved_insert_vec = devmon_insert_vec;
621 			devmon_insert_vec = devmon_insert;
622 		}
623 
624 		mutex_exit(&drvctl_lock);
625 		break;
626 
627 	case MODULE_CMD_FINI:
628 		mutex_enter(&drvctl_lock);
629 		if (drvctl_nopen != 0 || drvctl_eventcnt != 0 ) {
630 			mutex_exit(&drvctl_lock);
631 			return EBUSY;
632 		}
633 		KASSERT(saved_insert_vec != NULL);
634 		devmon_insert_vec = saved_insert_vec;
635 		saved_insert_vec = NULL;
636 #ifdef _MODULE
637 		error = devsw_detach(NULL, &drvctl_cdevsw);
638 		if (error != 0) {
639 			saved_insert_vec = devmon_insert_vec;
640 			devmon_insert_vec = devmon_insert;
641 		}
642 #endif
643 		mutex_exit(&drvctl_lock);
644 		if (error == 0)
645 			drvctl_fini();
646 
647 		break;
648 	default:
649 		error = ENOTTY;
650 		break;
651 	}
652 
653 	return error;
654 }
655