kern_drvctl.c revision 1.23 1 /* $NetBSD: kern_drvctl.c,v 1.23 2009/04/04 10:12:51 ad 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.23 2009/04/04 10:12:51 ad 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 ((d = device_find_by_xname(l->l_devname)) == NULL)
201 return ENXIO;
202
203 for (child = deviter_first(&di, 0); child != NULL;
204 child = deviter_next(&di)) {
205 if (device_parent(child) != d)
206 continue;
207 idx = cnt++;
208 if (l->l_childname == NULL || idx >= l->l_children)
209 continue;
210 error = copyoutstr(device_xname(child), l->l_childname[idx],
211 sizeof(l->l_childname[idx]), NULL);
212 if (error != 0)
213 break;
214 }
215 deviter_release(&di);
216
217 l->l_children = cnt;
218 return error;
219 }
220
221 static int
222 detachdevbyname(const char *devname)
223 {
224 struct device *d;
225
226 if ((d = device_find_by_xname(devname)) == NULL)
227 return ENXIO;
228
229 #ifndef XXXFULLRISK
230 /*
231 * If the parent cannot be notified, it might keep
232 * pointers to the detached device.
233 * There might be a private notification mechanism,
234 * but better play save here.
235 */
236 if (d->dv_parent && !d->dv_parent->dv_cfattach->ca_childdetached)
237 return (ENOTSUP);
238 #endif
239 return (config_detach(d, 0));
240 }
241
242 static int
243 rescanbus(const char *busname, const char *ifattr,
244 int numlocators, const int *locators)
245 {
246 int i, rc;
247 struct device *d;
248 const struct cfiattrdata * const *ap;
249
250 /* XXX there should be a way to get limits and defaults (per device)
251 from config generated data */
252 int locs[MAXLOCATORS];
253 for (i = 0; i < MAXLOCATORS; i++)
254 locs[i] = -1;
255
256 for (i = 0; i < numlocators;i++)
257 locs[i] = locators[i];
258
259 if ((d = device_find_by_xname(busname)) == NULL)
260 return ENXIO;
261
262 /*
263 * must support rescan, and must have something
264 * to attach to
265 */
266 if (!d->dv_cfattach->ca_rescan ||
267 !d->dv_cfdriver->cd_attrs)
268 return (ENODEV);
269
270 /* allow to omit attribute if there is exactly one */
271 if (!ifattr) {
272 if (d->dv_cfdriver->cd_attrs[1])
273 return (EINVAL);
274 ifattr = d->dv_cfdriver->cd_attrs[0]->ci_name;
275 } else {
276 /* check for valid attribute passed */
277 for (ap = d->dv_cfdriver->cd_attrs; *ap; ap++)
278 if (!strcmp((*ap)->ci_name, ifattr))
279 break;
280 if (!*ap)
281 return (EINVAL);
282 }
283
284 rc = (*d->dv_cfattach->ca_rescan)(d, ifattr, locs);
285 config_deferred(NULL);
286 return rc;
287 }
288
289 static int
290 drvctl_read(struct file *fp, off_t *offp, struct uio *uio, kauth_cred_t cred,
291 int flags)
292 {
293 return (ENODEV);
294 }
295
296 static int
297 drvctl_write(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_ioctl(struct file *fp, u_long cmd, void *data)
305 {
306 int res;
307 char *ifattr;
308 int *locs;
309 size_t locs_sz = 0; /* XXXgcc */
310
311 switch (cmd) {
312 case DRVSUSPENDDEV:
313 case DRVRESUMEDEV:
314 #define d ((struct devpmargs *)data)
315 res = pmdevbyname(cmd, d);
316 #undef d
317 break;
318 case DRVLISTDEV:
319 res = listdevbyname((struct devlistargs *)data);
320 break;
321 case DRVDETACHDEV:
322 #define d ((struct devdetachargs *)data)
323 res = detachdevbyname(d->devname);
324 #undef d
325 break;
326 case DRVRESCANBUS:
327 #define d ((struct devrescanargs *)data)
328 d->busname[sizeof(d->busname) - 1] = '\0';
329
330 /* XXX better copyin? */
331 if (d->ifattr[0]) {
332 d->ifattr[sizeof(d->ifattr) - 1] = '\0';
333 ifattr = d->ifattr;
334 } else
335 ifattr = 0;
336
337 if (d->numlocators) {
338 if (d->numlocators > MAXLOCATORS)
339 return (EINVAL);
340 locs_sz = d->numlocators * sizeof(int);
341 locs = kmem_alloc(locs_sz, KM_SLEEP);
342 res = copyin(d->locators, locs, locs_sz);
343 if (res) {
344 kmem_free(locs, locs_sz);
345 return (res);
346 }
347 } else
348 locs = NULL;
349 res = rescanbus(d->busname, ifattr, d->numlocators, locs);
350 if (locs)
351 kmem_free(locs, locs_sz);
352 #undef d
353 break;
354 case DRVCTLCOMMAND:
355 res = drvctl_command(curlwp, (struct plistref *)data, cmd,
356 fp->f_flag);
357 break;
358 case DRVGETEVENT:
359 res = drvctl_getevent(curlwp, (struct plistref *)data, cmd,
360 fp->f_flag);
361 break;
362 default:
363 return (EPASSTHROUGH);
364 }
365 return (res);
366 }
367
368 static int
369 drvctl_poll(struct file *fp, int events)
370 {
371 int revents = 0;
372
373 if (!TAILQ_EMPTY(&drvctl_eventq))
374 revents |= events & (POLLIN | POLLRDNORM);
375 else
376 selrecord(curlwp, &drvctl_rdsel);
377
378 return revents;
379 }
380
381 static int
382 drvctl_close(struct file *fp)
383 {
384 struct drvctl_event *dce;
385
386 /* XXX free context */
387 mutex_enter(&drvctl_lock);
388 KASSERT(drvctl_nopen > 0);
389 --drvctl_nopen;
390 if (drvctl_nopen == 0) {
391 /* flush queue */
392 while ((dce = TAILQ_FIRST(&drvctl_eventq)) != NULL) {
393 TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
394 KASSERT(drvctl_eventcnt > 0);
395 --drvctl_eventcnt;
396 prop_object_release(dce->dce_event);
397 kmem_free(dce, sizeof(*dce));
398 }
399 }
400 mutex_exit(&drvctl_lock);
401
402 return (0);
403 }
404
405 void
406 drvctlattach(int arg)
407 {
408 }
409
410 /*****************************************************************************
411 * Driver control command processing engine
412 *****************************************************************************/
413
414 static int
415 drvctl_command_get_properties(struct lwp *l,
416 prop_dictionary_t command_dict,
417 prop_dictionary_t results_dict)
418 {
419 prop_dictionary_t args_dict;
420 prop_string_t devname_string;
421 device_t dev;
422 deviter_t di;
423
424 args_dict = prop_dictionary_get(command_dict, "drvctl-arguments");
425 if (args_dict == NULL)
426 return (EINVAL);
427
428 devname_string = prop_dictionary_get(args_dict, "device-name");
429 if (devname_string == NULL)
430 return (EINVAL);
431
432 for (dev = deviter_first(&di, 0); dev != NULL;
433 dev = deviter_next(&di)) {
434 if (prop_string_equals_cstring(devname_string,
435 device_xname(dev))) {
436 prop_dictionary_set(results_dict, "drvctl-result-data",
437 device_properties(dev));
438 break;
439 }
440 }
441
442 deviter_release(&di);
443
444 if (dev == NULL)
445 return (ESRCH);
446
447 return (0);
448 }
449
450 struct drvctl_command_desc {
451 const char *dcd_name; /* command name */
452 int (*dcd_func)(struct lwp *, /* handler function */
453 prop_dictionary_t,
454 prop_dictionary_t);
455 int dcd_rw; /* read or write required */
456 };
457
458 static const struct drvctl_command_desc drvctl_command_table[] = {
459 { .dcd_name = "get-properties",
460 .dcd_func = drvctl_command_get_properties,
461 .dcd_rw = FREAD,
462 },
463
464 { .dcd_name = NULL }
465 };
466
467 static int
468 drvctl_command(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
469 int fflag)
470 {
471 prop_dictionary_t command_dict, results_dict;
472 prop_string_t command_string;
473 const struct drvctl_command_desc *dcd;
474 int error;
475
476 error = prop_dictionary_copyin_ioctl(pref, ioctl_cmd, &command_dict);
477 if (error)
478 return (error);
479
480 results_dict = prop_dictionary_create();
481 if (results_dict == NULL) {
482 prop_object_release(command_dict);
483 return (ENOMEM);
484 }
485
486 command_string = prop_dictionary_get(command_dict, "drvctl-command");
487 if (command_string == NULL) {
488 error = EINVAL;
489 goto out;
490 }
491
492 for (dcd = drvctl_command_table; dcd->dcd_name != NULL; dcd++) {
493 if (prop_string_equals_cstring(command_string,
494 dcd->dcd_name))
495 break;
496 }
497
498 if (dcd->dcd_name == NULL) {
499 error = EINVAL;
500 goto out;
501 }
502
503 if ((fflag & dcd->dcd_rw) == 0) {
504 error = EPERM;
505 goto out;
506 }
507
508 error = (*dcd->dcd_func)(l, command_dict, results_dict);
509
510 prop_dictionary_set_int32(results_dict, "drvctl-error", error);
511
512 error = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, results_dict);
513 out:
514 prop_object_release(command_dict);
515 prop_object_release(results_dict);
516 return (error);
517 }
518
519 static int
520 drvctl_getevent(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
521 int fflag)
522 {
523 struct drvctl_event *dce;
524 int ret;
525
526 if ((fflag & (FREAD|FWRITE)) != (FREAD|FWRITE))
527 return (EPERM);
528
529 mutex_enter(&drvctl_lock);
530 while ((dce = TAILQ_FIRST(&drvctl_eventq)) == NULL) {
531 if (fflag & O_NONBLOCK) {
532 mutex_exit(&drvctl_lock);
533 return (EWOULDBLOCK);
534 }
535
536 ret = cv_wait_sig(&drvctl_cond, &drvctl_lock);
537 if (ret) {
538 mutex_exit(&drvctl_lock);
539 return (ret);
540 }
541 }
542 TAILQ_REMOVE(&drvctl_eventq, dce, dce_link);
543 KASSERT(drvctl_eventcnt > 0);
544 --drvctl_eventcnt;
545 mutex_exit(&drvctl_lock);
546
547 ret = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, dce->dce_event);
548
549 prop_object_release(dce->dce_event);
550 kmem_free(dce, sizeof(*dce));
551
552 return (ret);
553 }
554