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