xref: /netbsd-src/sys/kern/kern_drvctl.c (revision ce099b40997c43048fb78bd578195f81d2456523)
1 /* $NetBSD: kern_drvctl.c,v 1.16 2008/03/12 18:02:21 dyoung 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.16 2008/03/12 18:02:21 dyoung 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/ioctl.h>
40 #include <sys/fcntl.h>
41 #include <sys/drvctlio.h>
42 
43 dev_type_ioctl(drvctlioctl);
44 
45 const struct cdevsw drvctl_cdevsw = {
46 	nullopen, nullclose, nullread, nullwrite, drvctlioctl,
47 	nostop, notty, nopoll, nommap, nokqfilter, D_OTHER
48 };
49 
50 void drvctlattach(int);
51 
52 #define MAXLOCATORS 100
53 
54 static int drvctl_command(struct lwp *, struct plistref *, u_long, int flag);
55 
56 static int
57 pmdevbyname(int cmd, struct devpmargs *a)
58 {
59 	struct device *d;
60 
61 	if ((d = device_find_by_xname(a->devname)) == NULL)
62 		return ENXIO;
63 
64 	switch (cmd) {
65 	case DRVSUSPENDDEV:
66 		return pmf_device_recursive_suspend(d, PMF_F_NONE) ? 0 : EBUSY;
67 	case DRVRESUMEDEV:
68 		if (a->flags & DEVPM_F_SUBTREE) {
69 			return pmf_device_resume_subtree(d, PMF_F_NONE)
70 			    ? 0 : EBUSY;
71 		} else {
72 			return pmf_device_recursive_resume(d, PMF_F_NONE)
73 			    ? 0 : EBUSY;
74 		}
75 	default:
76 		return EPASSTHROUGH;
77 	}
78 }
79 
80 static int
81 listdevbyname(struct devlistargs *l)
82 {
83 	device_t d, child;
84 	deviter_t di;
85 	int cnt = 0, idx, error = 0;
86 
87 	if ((d = device_find_by_xname(l->l_devname)) == NULL)
88 		return ENXIO;
89 
90 	for (child = deviter_first(&di, 0); child != NULL;
91 	     child = deviter_next(&di)) {
92 		if (device_parent(child) != d)
93 			continue;
94 		idx = cnt++;
95 		if (l->l_childname == NULL || idx >= l->l_children)
96 			continue;
97 		error = copyoutstr(device_xname(child), l->l_childname[idx],
98 				sizeof(l->l_childname[idx]), NULL);
99 		if (error != 0)
100 			break;
101 	}
102 	deviter_release(&di);
103 
104 	l->l_children = cnt;
105 	return error;
106 }
107 
108 static int
109 detachdevbyname(const char *devname)
110 {
111 	struct device *d;
112 
113 	if ((d = device_find_by_xname(devname)) == NULL)
114 		return ENXIO;
115 
116 #ifndef XXXFULLRISK
117 	/*
118 	 * If the parent cannot be notified, it might keep
119 	 * pointers to the detached device.
120 	 * There might be a private notification mechanism,
121 	 * but better play save here.
122 	 */
123 	if (d->dv_parent && !d->dv_parent->dv_cfattach->ca_childdetached)
124 		return (ENOTSUP);
125 #endif
126 	return (config_detach(d, 0));
127 }
128 
129 static int
130 rescanbus(const char *busname, const char *ifattr,
131 	  int numlocators, const int *locators)
132 {
133 	int i, rc;
134 	struct device *d;
135 	const struct cfiattrdata * const *ap;
136 
137 	/* XXX there should be a way to get limits and defaults (per device)
138 	   from config generated data */
139 	int locs[MAXLOCATORS];
140 	for (i = 0; i < MAXLOCATORS; i++)
141 		locs[i] = -1;
142 
143 	for (i = 0; i < numlocators;i++)
144 		locs[i] = locators[i];
145 
146 	if ((d = device_find_by_xname(busname)) == NULL)
147 		return ENXIO;
148 
149 	/*
150 	 * must support rescan, and must have something
151 	 * to attach to
152 	 */
153 	if (!d->dv_cfattach->ca_rescan ||
154 	    !d->dv_cfdriver->cd_attrs)
155 		return (ENODEV);
156 
157 	/* allow to omit attribute if there is exactly one */
158 	if (!ifattr) {
159 		if (d->dv_cfdriver->cd_attrs[1])
160 			return (EINVAL);
161 		ifattr = d->dv_cfdriver->cd_attrs[0]->ci_name;
162 	} else {
163 		/* check for valid attribute passed */
164 		for (ap = d->dv_cfdriver->cd_attrs; *ap; ap++)
165 			if (!strcmp((*ap)->ci_name, ifattr))
166 				break;
167 		if (!*ap)
168 			return (EINVAL);
169 	}
170 
171 	rc = (*d->dv_cfattach->ca_rescan)(d, ifattr, locs);
172 	config_deferred(NULL);
173 	return rc;
174 }
175 
176 int
177 drvctlioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *p)
178 {
179 	int res;
180 	char *ifattr;
181 	int *locs;
182 
183 	switch (cmd) {
184 	case DRVSUSPENDDEV:
185 	case DRVRESUMEDEV:
186 #define d ((struct devpmargs *)data)
187 		res = pmdevbyname(cmd, d);
188 #undef d
189 		break;
190 	case DRVLISTDEV:
191 		res = listdevbyname((struct devlistargs *)data);
192 		break;
193 	case DRVDETACHDEV:
194 #define d ((struct devdetachargs *)data)
195 		res = detachdevbyname(d->devname);
196 #undef d
197 		break;
198 	case DRVRESCANBUS:
199 #define d ((struct devrescanargs *)data)
200 		d->busname[sizeof(d->busname) - 1] = '\0';
201 
202 		/* XXX better copyin? */
203 		if (d->ifattr[0]) {
204 			d->ifattr[sizeof(d->ifattr) - 1] = '\0';
205 			ifattr = d->ifattr;
206 		} else
207 			ifattr = 0;
208 
209 		if (d->numlocators) {
210 			if (d->numlocators > MAXLOCATORS)
211 				return (EINVAL);
212 			locs = malloc(d->numlocators * sizeof(int), M_DEVBUF,
213 				      M_WAITOK);
214 			res = copyin(d->locators, locs,
215 				     d->numlocators * sizeof(int));
216 			if (res) {
217 				free(locs, M_DEVBUF);
218 				return (res);
219 			}
220 		} else
221 			locs = 0;
222 		res = rescanbus(d->busname, ifattr, d->numlocators, locs);
223 		if (locs)
224 			free(locs, M_DEVBUF);
225 #undef d
226 		break;
227 	case DRVCTLCOMMAND:
228 	    	res = drvctl_command(p, (struct plistref *)data, cmd, flag);
229 	    	break;
230 	default:
231 		return (EPASSTHROUGH);
232 	}
233 	return (res);
234 }
235 
236 void
237 drvctlattach(int arg)
238 {
239 }
240 
241 /*****************************************************************************
242  * Driver control command processing engine
243  *****************************************************************************/
244 
245 static int
246 drvctl_command_get_properties(struct lwp *l,
247 			      prop_dictionary_t command_dict,
248 			      prop_dictionary_t results_dict)
249 {
250 	prop_dictionary_t args_dict;
251 	prop_string_t devname_string;
252 	device_t dev;
253 	deviter_t di;
254 
255 	args_dict = prop_dictionary_get(command_dict, "drvctl-arguments");
256 	if (args_dict == NULL)
257 		return (EINVAL);
258 
259 	devname_string = prop_dictionary_get(args_dict, "device-name");
260 	if (devname_string == NULL)
261 		return (EINVAL);
262 
263 	for (dev = deviter_first(&di, 0); dev != NULL;
264 	     dev = deviter_next(&di)) {
265 		if (prop_string_equals_cstring(devname_string,
266 					       device_xname(dev))) {
267 			prop_dictionary_set(results_dict, "drvctl-result-data",
268 			    device_properties(dev));
269 			break;
270 		}
271 	}
272 
273 	deviter_release(&di);
274 
275 	if (dev == NULL)
276 		return (ESRCH);
277 
278 	return (0);
279 }
280 
281 struct drvctl_command_desc {
282 	const char *dcd_name;		/* command name */
283 	int (*dcd_func)(struct lwp *,	/* handler function */
284 			prop_dictionary_t,
285 			prop_dictionary_t);
286 	int dcd_rw;			/* read or write required */
287 };
288 
289 static const struct drvctl_command_desc drvctl_command_table[] = {
290 	{ .dcd_name = "get-properties",
291 	  .dcd_func = drvctl_command_get_properties,
292 	  .dcd_rw   = FREAD,
293 	},
294 
295 	{ .dcd_name = NULL }
296 };
297 
298 static int
299 drvctl_command(struct lwp *l, struct plistref *pref, u_long ioctl_cmd,
300 	       int fflag)
301 {
302 	prop_dictionary_t command_dict, results_dict;
303 	prop_string_t command_string;
304 	const struct drvctl_command_desc *dcd;
305 	int error;
306 
307 	error = prop_dictionary_copyin_ioctl(pref, ioctl_cmd, &command_dict);
308 	if (error)
309 		return (error);
310 
311 	results_dict = prop_dictionary_create();
312 	if (results_dict == NULL) {
313 		prop_object_release(command_dict);
314 		return (ENOMEM);
315 	}
316 
317 	command_string = prop_dictionary_get(command_dict, "drvctl-command");
318 	if (command_string == NULL) {
319 		error = EINVAL;
320 		goto out;
321 	}
322 
323 	for (dcd = drvctl_command_table; dcd->dcd_name != NULL; dcd++) {
324 		if (prop_string_equals_cstring(command_string,
325 					       dcd->dcd_name))
326 			break;
327 	}
328 
329 	if (dcd->dcd_name == NULL) {
330 		error = EINVAL;
331 		goto out;
332 	}
333 
334 	if ((fflag & dcd->dcd_rw) == 0) {
335 		error = EPERM;
336 		goto out;
337 	}
338 
339 	error = (*dcd->dcd_func)(l, command_dict, results_dict);
340 
341 	prop_dictionary_set_int32(results_dict, "drvctl-error", error);
342 
343 	error = prop_dictionary_copyout_ioctl(pref, ioctl_cmd, results_dict);
344  out:
345 	prop_object_release(command_dict);
346 	prop_object_release(results_dict);
347 	return (error);
348 }
349