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