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