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