1 /* $NetBSD: kern_drvctl.c,v 1.30 2009/12/09 21:32:59 dsl Exp $ */
5 * Matthias Drochner. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: kern_drvctl.c,v 1.30 2009/12/09 21:32:59 dsl Exp $");
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/kernel.h>
36 #include <sys/device.h>
37 #include <sys/event.h>
39 #include <sys/ioctl.h>
40 #include <sys/fcntl.h>
42 #include <sys/filedesc.h>
43 #include <sys/select.h>
45 #include <sys/drvctlio.h>
46 #include <sys/devmon.h>
48 #include <sys/kauth.h>
52 TAILQ_ENTRY(drvctl_event
) dce_link
;
53 prop_dictionary_t dce_event
;
56 TAILQ_HEAD(drvctl_queue
, drvctl_event
);
58 static struct drvctl_queue drvctl_eventq
; /* FIFO */
59 static kcondvar_t drvctl_cond
;
60 static kmutex_t drvctl_lock
;
61 static int drvctl_nopen
= 0, drvctl_eventcnt
= 0;
62 static struct selinfo drvctl_rdsel
;
64 #define DRVCTL_EVENTQ_DEPTH 64 /* arbitrary queue limit */
66 dev_type_open(drvctlopen
);
68 const struct cdevsw drvctl_cdevsw
= {
69 drvctlopen
, nullclose
, nullread
, nullwrite
, noioctl
,
70 nostop
, notty
, nopoll
, nommap
, nokqfilter
, D_OTHER
73 void drvctlattach(int);
75 static int drvctl_read(struct file
*, off_t
*, struct uio
*,
77 static int drvctl_write(struct file
*, off_t
*, struct uio
*,
79 static int drvctl_ioctl(struct file
*, u_long
, void *);
80 static int drvctl_poll(struct file
*, int);
81 static int drvctl_stat(struct file
*, struct stat
*);
82 static int drvctl_close(struct file
*);
84 static const struct fileops drvctl_fileops
= {
85 .fo_read
= drvctl_read
,
86 .fo_write
= drvctl_write
,
87 .fo_ioctl
= drvctl_ioctl
,
88 .fo_fcntl
= fnullop_fcntl
,
89 .fo_poll
= drvctl_poll
,
90 .fo_stat
= drvctl_stat
,
91 .fo_close
= drvctl_close
,
92 .fo_kqfilter
= fnullop_kqfilter
,
93 .fo_restart
= fnullop_restart
,
96 #define MAXLOCATORS 100
98 static int drvctl_command(struct lwp
*, struct plistref
*, u_long
, int);
99 static int drvctl_getevent(struct lwp
*, struct plistref
*, u_long
, int);
104 TAILQ_INIT(&drvctl_eventq
);
105 mutex_init(&drvctl_lock
, MUTEX_DEFAULT
, IPL_NONE
);
106 cv_init(&drvctl_cond
, "devmon");
107 selinit(&drvctl_rdsel
);
111 devmon_insert(const char *event
, prop_dictionary_t ev
)
113 struct drvctl_event
*dce
, *odce
;
115 mutex_enter(&drvctl_lock
);
117 if (drvctl_nopen
== 0) {
118 mutex_exit(&drvctl_lock
);
122 /* Fill in mandatory member */
123 if (!prop_dictionary_set_cstring_nocopy(ev
, "event", event
)) {
124 prop_object_release(ev
);
125 mutex_exit(&drvctl_lock
);
129 dce
= kmem_alloc(sizeof(*dce
), KM_SLEEP
);
131 mutex_exit(&drvctl_lock
);
137 if (drvctl_eventcnt
== DRVCTL_EVENTQ_DEPTH
) {
138 odce
= TAILQ_FIRST(&drvctl_eventq
);
139 TAILQ_REMOVE(&drvctl_eventq
, odce
, dce_link
);
140 prop_object_release(odce
->dce_event
);
141 kmem_free(odce
, sizeof(*odce
));
145 TAILQ_INSERT_TAIL(&drvctl_eventq
, dce
, dce_link
);
147 cv_broadcast(&drvctl_cond
);
148 selnotify(&drvctl_rdsel
, 0, 0);
150 mutex_exit(&drvctl_lock
);
154 drvctlopen(dev_t dev
, int flags
, int mode
, struct lwp
*l
)
160 ret
= fd_allocfile(&fp
, &fd
);
164 /* XXX setup context */
165 mutex_enter(&drvctl_lock
);
166 ret
= fd_clone(fp
, fd
, flags
, &drvctl_fileops
, /* context */NULL
);
168 mutex_exit(&drvctl_lock
);
174 pmdevbyname(u_long cmd
, struct devpmargs
*a
)
178 if ((d
= device_find_by_xname(a
->devname
)) == NULL
)
183 return pmf_device_recursive_suspend(d
, PMF_Q_DRVCTL
) ? 0 : EBUSY
;
185 if (a
->flags
& DEVPM_F_SUBTREE
) {
186 return pmf_device_subtree_resume(d
, PMF_Q_DRVCTL
)
189 return pmf_device_recursive_resume(d
, PMF_Q_DRVCTL
)
198 listdevbyname(struct devlistargs
*l
)
202 int cnt
= 0, idx
, error
= 0;
204 if (*l
->l_devname
== '\0')
206 else if (memchr(l
->l_devname
, 0, sizeof(l
->l_devname
)) == NULL
)
208 else if ((d
= device_find_by_xname(l
->l_devname
)) == NULL
)
211 for (child
= deviter_first(&di
, 0); child
!= NULL
;
212 child
= deviter_next(&di
)) {
213 if (device_parent(child
) != d
)
216 if (l
->l_childname
== NULL
|| idx
>= l
->l_children
)
218 error
= copyoutstr(device_xname(child
), l
->l_childname
[idx
],
219 sizeof(l
->l_childname
[idx
]), NULL
);
223 deviter_release(&di
);
230 detachdevbyname(const char *devname
)
234 if ((d
= device_find_by_xname(devname
)) == NULL
)
239 * If the parent cannot be notified, it might keep
240 * pointers to the detached device.
241 * There might be a private notification mechanism,
242 * but better play it safe here.
244 if (d
->dv_parent
&& !d
->dv_parent
->dv_cfattach
->ca_childdetached
)
247 return config_detach(d
, 0);
251 rescanbus(const char *busname
, const char *ifattr
,
252 int numlocators
, const int *locators
)
256 const struct cfiattrdata
* const *ap
;
258 /* XXX there should be a way to get limits and defaults (per device)
259 from config generated data */
260 int locs
[MAXLOCATORS
];
261 for (i
= 0; i
< MAXLOCATORS
; i
++)
264 for (i
= 0; i
< numlocators
;i
++)
265 locs
[i
] = locators
[i
];
267 if ((d
= device_find_by_xname(busname
)) == NULL
)
271 * must support rescan, and must have something
274 if (!d
->dv_cfattach
->ca_rescan
||
275 !d
->dv_cfdriver
->cd_attrs
)
278 /* allow to omit attribute if there is exactly one */
280 if (d
->dv_cfdriver
->cd_attrs
[1])
282 ifattr
= d
->dv_cfdriver
->cd_attrs
[0]->ci_name
;
284 /* check for valid attribute passed */
285 for (ap
= d
->dv_cfdriver
->cd_attrs
; *ap
; ap
++)
286 if (!strcmp((*ap
)->ci_name
, ifattr
))
292 rc
= (*d
->dv_cfattach
->ca_rescan
)(d
, ifattr
, locs
);
293 config_deferred(NULL
);
298 drvctl_read(struct file
*fp
, off_t
*offp
, struct uio
*uio
, kauth_cred_t cred
,
305 drvctl_write(struct file
*fp
, off_t
*offp
, struct uio
*uio
, kauth_cred_t cred
,
312 drvctl_ioctl(struct file
*fp
, u_long cmd
, void *data
)
317 size_t locs_sz
= 0; /* XXXgcc */
322 #define d ((struct devpmargs *)data)
323 res
= pmdevbyname(cmd
, d
);
327 res
= listdevbyname((struct devlistargs
*)data
);
330 #define d ((struct devdetachargs *)data)
331 res
= detachdevbyname(d
->devname
);
335 #define d ((struct devrescanargs *)data)
336 d
->busname
[sizeof(d
->busname
) - 1] = '\0';
338 /* XXX better copyin? */
340 d
->ifattr
[sizeof(d
->ifattr
) - 1] = '\0';
345 if (d
->numlocators
) {
346 if (d
->numlocators
> MAXLOCATORS
)
348 locs_sz
= d
->numlocators
* sizeof(int);
349 locs
= kmem_alloc(locs_sz
, KM_SLEEP
);
350 res
= copyin(d
->locators
, locs
, locs_sz
);
352 kmem_free(locs
, locs_sz
);
357 res
= rescanbus(d
->busname
, ifattr
, d
->numlocators
, locs
);
359 kmem_free(locs
, locs_sz
);
363 res
= drvctl_command(curlwp
, (struct plistref
*)data
, cmd
,
367 res
= drvctl_getevent(curlwp
, (struct plistref
*)data
, cmd
,
377 drvctl_stat(struct file
*fp
, struct stat
*st
)
379 (void)memset(st
, 0, sizeof(*st
));
380 st
->st_uid
= kauth_cred_geteuid(fp
->f_cred
);
381 st
->st_gid
= kauth_cred_getegid(fp
->f_cred
);
386 drvctl_poll(struct file
*fp
, int events
)
390 if (!TAILQ_EMPTY(&drvctl_eventq
))
391 revents
|= events
& (POLLIN
| POLLRDNORM
);
393 selrecord(curlwp
, &drvctl_rdsel
);
399 drvctl_close(struct file
*fp
)
401 struct drvctl_event
*dce
;
403 /* XXX free context */
404 mutex_enter(&drvctl_lock
);
405 KASSERT(drvctl_nopen
> 0);
407 if (drvctl_nopen
== 0) {
409 while ((dce
= TAILQ_FIRST(&drvctl_eventq
)) != NULL
) {
410 TAILQ_REMOVE(&drvctl_eventq
, dce
, dce_link
);
411 KASSERT(drvctl_eventcnt
> 0);
413 prop_object_release(dce
->dce_event
);
414 kmem_free(dce
, sizeof(*dce
));
417 mutex_exit(&drvctl_lock
);
423 drvctlattach(int arg
)
427 /*****************************************************************************
428 * Driver control command processing engine
429 *****************************************************************************/
432 drvctl_command_get_properties(struct lwp
*l
,
433 prop_dictionary_t command_dict
,
434 prop_dictionary_t results_dict
)
436 prop_dictionary_t args_dict
;
437 prop_string_t devname_string
;
441 args_dict
= prop_dictionary_get(command_dict
, "drvctl-arguments");
442 if (args_dict
== NULL
)
445 devname_string
= prop_dictionary_get(args_dict
, "device-name");
446 if (devname_string
== NULL
)
449 for (dev
= deviter_first(&di
, 0); dev
!= NULL
;
450 dev
= deviter_next(&di
)) {
451 if (prop_string_equals_cstring(devname_string
,
452 device_xname(dev
))) {
453 prop_dictionary_set(results_dict
, "drvctl-result-data",
454 device_properties(dev
));
459 deviter_release(&di
);
467 struct drvctl_command_desc
{
468 const char *dcd_name
; /* command name */
469 int (*dcd_func
)(struct lwp
*, /* handler function */
472 int dcd_rw
; /* read or write required */
475 static const struct drvctl_command_desc drvctl_command_table
[] = {
476 { .dcd_name
= "get-properties",
477 .dcd_func
= drvctl_command_get_properties
,
485 drvctl_command(struct lwp
*l
, struct plistref
*pref
, u_long ioctl_cmd
,
488 prop_dictionary_t command_dict
, results_dict
;
489 prop_string_t command_string
;
490 const struct drvctl_command_desc
*dcd
;
493 error
= prop_dictionary_copyin_ioctl(pref
, ioctl_cmd
, &command_dict
);
497 results_dict
= prop_dictionary_create();
498 if (results_dict
== NULL
) {
499 prop_object_release(command_dict
);
503 command_string
= prop_dictionary_get(command_dict
, "drvctl-command");
504 if (command_string
== NULL
) {
509 for (dcd
= drvctl_command_table
; dcd
->dcd_name
!= NULL
; dcd
++) {
510 if (prop_string_equals_cstring(command_string
,
515 if (dcd
->dcd_name
== NULL
) {
520 if ((fflag
& dcd
->dcd_rw
) == 0) {
525 error
= (*dcd
->dcd_func
)(l
, command_dict
, results_dict
);
527 prop_dictionary_set_int32(results_dict
, "drvctl-error", error
);
529 error
= prop_dictionary_copyout_ioctl(pref
, ioctl_cmd
, results_dict
);
531 prop_object_release(command_dict
);
532 prop_object_release(results_dict
);
537 drvctl_getevent(struct lwp
*l
, struct plistref
*pref
, u_long ioctl_cmd
,
540 struct drvctl_event
*dce
;
543 if ((fflag
& (FREAD
|FWRITE
)) != (FREAD
|FWRITE
))
546 mutex_enter(&drvctl_lock
);
547 while ((dce
= TAILQ_FIRST(&drvctl_eventq
)) == NULL
) {
548 if (fflag
& O_NONBLOCK
) {
549 mutex_exit(&drvctl_lock
);
553 ret
= cv_wait_sig(&drvctl_cond
, &drvctl_lock
);
555 mutex_exit(&drvctl_lock
);
559 TAILQ_REMOVE(&drvctl_eventq
, dce
, dce_link
);
560 KASSERT(drvctl_eventcnt
> 0);
562 mutex_exit(&drvctl_lock
);
564 ret
= prop_dictionary_copyout_ioctl(pref
, ioctl_cmd
, dce
->dce_event
);
566 prop_object_release(dce
->dce_event
);
567 kmem_free(dce
, sizeof(*dce
));