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