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