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