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