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