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