| 1 | /* $NetBSD: uhidev.c,v 1.68 2016/07/07 06:55:42 msaitoh Exp $ */ |
| 2 | |
| 3 | /* |
| 4 | * Copyright (c) 2001, 2012 The NetBSD Foundation, Inc. |
| 5 | * All rights reserved. |
| 6 | * |
| 7 | * This code is derived from software contributed to The NetBSD Foundation |
| 8 | * by Lennart Augustsson (lennart@augustsson.net) at |
| 9 | * Carlstedt Research & Technology and Matthew R. Green (mrg@eterna.com.au). |
| 10 | * |
| 11 | * Redistribution and use in source and binary forms, with or without |
| 12 | * modification, are permitted provided that the following conditions |
| 13 | * are met: |
| 14 | * 1. Redistributions of source code must retain the above copyright |
| 15 | * notice, this list of conditions and the following disclaimer. |
| 16 | * 2. Redistributions in binary form must reproduce the above copyright |
| 17 | * notice, this list of conditions and the following disclaimer in the |
| 18 | * documentation and/or other materials provided with the distribution. |
| 19 | * |
| 20 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
| 21 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 22 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 23 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
| 24 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 30 | * POSSIBILITY OF SUCH DAMAGE. |
| 31 | */ |
| 32 | |
| 33 | /* |
| 34 | * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf |
| 35 | */ |
| 36 | |
| 37 | #include <sys/cdefs.h> |
| 38 | __KERNEL_RCSID(0, "$NetBSD: uhidev.c,v 1.68 2016/07/07 06:55:42 msaitoh Exp $" ); |
| 39 | |
| 40 | #ifdef _KERNEL_OPT |
| 41 | #include "opt_usb.h" |
| 42 | #endif |
| 43 | |
| 44 | #include <sys/param.h> |
| 45 | #include <sys/systm.h> |
| 46 | #include <sys/kernel.h> |
| 47 | #include <sys/kmem.h> |
| 48 | #include <sys/signalvar.h> |
| 49 | #include <sys/device.h> |
| 50 | #include <sys/ioctl.h> |
| 51 | #include <sys/conf.h> |
| 52 | #include <sys/rndsource.h> |
| 53 | |
| 54 | #include <dev/usb/usb.h> |
| 55 | #include <dev/usb/usbhid.h> |
| 56 | |
| 57 | #include <dev/usb/usbdevs.h> |
| 58 | #include <dev/usb/usbdi.h> |
| 59 | #include <dev/usb/usbdi_util.h> |
| 60 | #include <dev/usb/hid.h> |
| 61 | #include <dev/usb/usb_quirks.h> |
| 62 | |
| 63 | #include <dev/usb/uhidev.h> |
| 64 | |
| 65 | /* Report descriptor for broken Wacom Graphire */ |
| 66 | #include <dev/usb/ugraphire_rdesc.h> |
| 67 | /* Report descriptor for game controllers in "XInput" mode */ |
| 68 | #include <dev/usb/xinput_rdesc.h> |
| 69 | /* Report descriptor for Xbox One controllers */ |
| 70 | #include <dev/usb/x1input_rdesc.h> |
| 71 | |
| 72 | #include "locators.h" |
| 73 | |
| 74 | #ifdef UHIDEV_DEBUG |
| 75 | #define DPRINTF(x) if (uhidevdebug) printf x |
| 76 | #define DPRINTFN(n,x) if (uhidevdebug>(n)) printf x |
| 77 | int uhidevdebug = 0; |
| 78 | #else |
| 79 | #define DPRINTF(x) |
| 80 | #define DPRINTFN(n,x) |
| 81 | #endif |
| 82 | |
| 83 | Static void uhidev_intr(struct usbd_xfer *, void *, usbd_status); |
| 84 | |
| 85 | Static int uhidev_maxrepid(void *, int); |
| 86 | Static int uhidevprint(void *, const char *); |
| 87 | |
| 88 | int uhidev_match(device_t, cfdata_t, void *); |
| 89 | void uhidev_attach(device_t, device_t, void *); |
| 90 | void uhidev_childdet(device_t, device_t); |
| 91 | int uhidev_detach(device_t, int); |
| 92 | int uhidev_activate(device_t, enum devact); |
| 93 | extern struct cfdriver uhidev_cd; |
| 94 | CFATTACH_DECL2_NEW(uhidev, sizeof(struct uhidev_softc), uhidev_match, |
| 95 | uhidev_attach, uhidev_detach, uhidev_activate, NULL, uhidev_childdet); |
| 96 | |
| 97 | int |
| 98 | uhidev_match(device_t parent, cfdata_t match, void *aux) |
| 99 | { |
| 100 | struct usbif_attach_arg *uiaa = aux; |
| 101 | |
| 102 | /* Game controllers in "XInput" mode */ |
| 103 | if (USBIF_IS_XINPUT(uiaa)) |
| 104 | return UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO; |
| 105 | /* Xbox One controllers */ |
| 106 | if (USBIF_IS_X1INPUT(uiaa) && uiaa->uiaa_ifaceno == 0) |
| 107 | return UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO; |
| 108 | |
| 109 | if (uiaa->uiaa_class != UICLASS_HID) |
| 110 | return UMATCH_NONE; |
| 111 | if (usbd_get_quirks(uiaa->uiaa_device)->uq_flags & UQ_HID_IGNORE) |
| 112 | return UMATCH_NONE; |
| 113 | return UMATCH_IFACECLASS_GENERIC; |
| 114 | } |
| 115 | |
| 116 | void |
| 117 | uhidev_attach(device_t parent, device_t self, void *aux) |
| 118 | { |
| 119 | struct uhidev_softc *sc = device_private(self); |
| 120 | struct usbif_attach_arg *uiaa = aux; |
| 121 | struct usbd_interface *iface = uiaa->uiaa_iface; |
| 122 | usb_interface_descriptor_t *id; |
| 123 | usb_endpoint_descriptor_t *ed; |
| 124 | struct uhidev_attach_arg uha; |
| 125 | device_t dev; |
| 126 | struct uhidev *csc; |
| 127 | int maxinpktsize, size, nrepid, repid, repsz; |
| 128 | int *repsizes; |
| 129 | int i; |
| 130 | void *desc; |
| 131 | const void *descptr; |
| 132 | usbd_status err; |
| 133 | char *devinfop; |
| 134 | int locs[UHIDBUSCF_NLOCS]; |
| 135 | |
| 136 | sc->sc_dev = self; |
| 137 | sc->sc_udev = uiaa->uiaa_device; |
| 138 | sc->sc_iface = iface; |
| 139 | |
| 140 | aprint_naive("\n" ); |
| 141 | aprint_normal("\n" ); |
| 142 | |
| 143 | mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB); |
| 144 | |
| 145 | id = usbd_get_interface_descriptor(iface); |
| 146 | |
| 147 | devinfop = usbd_devinfo_alloc(uiaa->uiaa_device, 0); |
| 148 | aprint_normal_dev(self, "%s, iclass %d/%d\n" , |
| 149 | devinfop, id->bInterfaceClass, id->bInterfaceSubClass); |
| 150 | usbd_devinfo_free(devinfop); |
| 151 | |
| 152 | if (!pmf_device_register(self, NULL, NULL)) |
| 153 | aprint_error_dev(self, "couldn't establish power handler\n" ); |
| 154 | |
| 155 | (void)usbd_set_idle(iface, 0, 0); |
| 156 | #if 0 |
| 157 | |
| 158 | if ((usbd_get_quirks(sc->sc_udev)->uq_flags & UQ_NO_SET_PROTO) == 0 && |
| 159 | id->bInterfaceSubClass != UISUBCLASS_BOOT) |
| 160 | (void)usbd_set_protocol(iface, 1); |
| 161 | #endif |
| 162 | |
| 163 | maxinpktsize = 0; |
| 164 | sc->sc_iep_addr = sc->sc_oep_addr = -1; |
| 165 | for (i = 0; i < id->bNumEndpoints; i++) { |
| 166 | ed = usbd_interface2endpoint_descriptor(iface, i); |
| 167 | if (ed == NULL) { |
| 168 | aprint_error_dev(self, |
| 169 | "could not read endpoint descriptor\n" ); |
| 170 | sc->sc_dying = 1; |
| 171 | return; |
| 172 | } |
| 173 | |
| 174 | DPRINTFN(10,("uhidev_attach: bLength=%d bDescriptorType=%d " |
| 175 | "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d" |
| 176 | " bInterval=%d\n" , |
| 177 | ed->bLength, ed->bDescriptorType, |
| 178 | ed->bEndpointAddress & UE_ADDR, |
| 179 | UE_GET_DIR(ed->bEndpointAddress)==UE_DIR_IN? "in" : "out" , |
| 180 | ed->bmAttributes & UE_XFERTYPE, |
| 181 | UGETW(ed->wMaxPacketSize), ed->bInterval)); |
| 182 | |
| 183 | if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && |
| 184 | (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { |
| 185 | maxinpktsize = UGETW(ed->wMaxPacketSize); |
| 186 | sc->sc_iep_addr = ed->bEndpointAddress; |
| 187 | } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && |
| 188 | (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { |
| 189 | sc->sc_oep_addr = ed->bEndpointAddress; |
| 190 | } else { |
| 191 | aprint_verbose_dev(self, "endpoint %d: ignored\n" , i); |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | /* |
| 196 | * Check that we found an input interrupt endpoint. The output interrupt |
| 197 | * endpoint is optional |
| 198 | */ |
| 199 | if (sc->sc_iep_addr == -1) { |
| 200 | aprint_error_dev(self, "no input interrupt endpoint\n" ); |
| 201 | sc->sc_dying = 1; |
| 202 | return; |
| 203 | } |
| 204 | |
| 205 | /* XXX need to extend this */ |
| 206 | descptr = NULL; |
| 207 | if (uiaa->uiaa_vendor == USB_VENDOR_WACOM) { |
| 208 | static uByte reportbuf[] = {2, 2, 2}; |
| 209 | |
| 210 | /* The report descriptor for the Wacom Graphire is broken. */ |
| 211 | switch (uiaa->uiaa_product) { |
| 212 | case USB_PRODUCT_WACOM_GRAPHIRE: |
| 213 | case USB_PRODUCT_WACOM_GRAPHIRE2: |
| 214 | case USB_PRODUCT_WACOM_GRAPHIRE3_4X5: |
| 215 | case USB_PRODUCT_WACOM_GRAPHIRE3_6X8: |
| 216 | case USB_PRODUCT_WACOM_GRAPHIRE4_4X5: /* The 6x8 too? */ |
| 217 | /* |
| 218 | * The Graphire3 needs 0x0202 to be written to |
| 219 | * feature report ID 2 before it'll start |
| 220 | * returning digitizer data. |
| 221 | */ |
| 222 | usbd_set_report(uiaa->uiaa_iface, UHID_FEATURE_REPORT, 2, |
| 223 | &reportbuf, sizeof(reportbuf)); |
| 224 | |
| 225 | size = sizeof(uhid_graphire3_4x5_report_descr); |
| 226 | descptr = uhid_graphire3_4x5_report_descr; |
| 227 | break; |
| 228 | default: |
| 229 | /* Keep descriptor */ |
| 230 | break; |
| 231 | } |
| 232 | } |
| 233 | if (USBIF_IS_XINPUT(uiaa)) { |
| 234 | size = sizeof(uhid_xinput_report_descr); |
| 235 | descptr = uhid_xinput_report_descr; |
| 236 | } |
| 237 | if (USBIF_IS_X1INPUT(uiaa)) { |
| 238 | sc->sc_flags |= UHIDEV_F_XB1; |
| 239 | size = sizeof(uhid_x1input_report_descr); |
| 240 | descptr = uhid_x1input_report_descr; |
| 241 | } |
| 242 | |
| 243 | if (descptr) { |
| 244 | desc = kmem_alloc(size, KM_SLEEP); |
| 245 | if (desc == NULL) |
| 246 | err = USBD_NOMEM; |
| 247 | else { |
| 248 | err = USBD_NORMAL_COMPLETION; |
| 249 | memcpy(desc, descptr, size); |
| 250 | } |
| 251 | } else { |
| 252 | desc = NULL; |
| 253 | err = usbd_read_report_desc(uiaa->uiaa_iface, &desc, &size); |
| 254 | } |
| 255 | if (err) { |
| 256 | aprint_error_dev(self, "no report descriptor\n" ); |
| 257 | sc->sc_dying = 1; |
| 258 | return; |
| 259 | } |
| 260 | |
| 261 | if (uiaa->uiaa_vendor == USB_VENDOR_HOSIDEN && |
| 262 | uiaa->uiaa_product == USB_PRODUCT_HOSIDEN_PPP) { |
| 263 | static uByte reportbuf[] = { 1 }; |
| 264 | /* |
| 265 | * This device was sold by Konami with its ParaParaParadise |
| 266 | * game for PlayStation2. It needs to be "turned on" |
| 267 | * before it will send any reports. |
| 268 | */ |
| 269 | |
| 270 | usbd_set_report(uiaa->uiaa_iface, UHID_FEATURE_REPORT, 0, |
| 271 | &reportbuf, sizeof(reportbuf)); |
| 272 | } |
| 273 | |
| 274 | if (uiaa->uiaa_vendor == USB_VENDOR_LOGITECH && |
| 275 | uiaa->uiaa_product == USB_PRODUCT_LOGITECH_CBT44 && size == 0xb1) { |
| 276 | uint8_t *data = desc; |
| 277 | /* |
| 278 | * This device has a odd USAGE_MINIMUM value that would |
| 279 | * cause the multimedia keys to have their usage number |
| 280 | * shifted up one usage. Adjust so the usages are sane. |
| 281 | */ |
| 282 | |
| 283 | if (data[0x56] == 0x19 && data[0x57] == 0x01 && |
| 284 | data[0x58] == 0x2a && data[0x59] == 0x8c) |
| 285 | data[0x57] = 0x00; |
| 286 | } |
| 287 | |
| 288 | /* |
| 289 | * Enable the Six Axis and DualShock 3 controllers. |
| 290 | * See http://ps3.jim.sh/sixaxis/usb/ |
| 291 | */ |
| 292 | if (uiaa->uiaa_vendor == USB_VENDOR_SONY && |
| 293 | uiaa->uiaa_product == USB_PRODUCT_SONY_PS3CONTROLLER) { |
| 294 | usb_device_request_t req; |
| 295 | char data[17]; |
| 296 | int actlen; |
| 297 | |
| 298 | req.bmRequestType = UT_READ_CLASS_INTERFACE; |
| 299 | req.bRequest = 1; |
| 300 | USETW(req.wValue, 0x3f2); |
| 301 | USETW(req.wIndex, 0); |
| 302 | USETW(req.wLength, sizeof(data)); |
| 303 | |
| 304 | usbd_do_request_flags(sc->sc_udev, &req, data, |
| 305 | USBD_SHORT_XFER_OK, &actlen, USBD_DEFAULT_TIMEOUT); |
| 306 | } |
| 307 | |
| 308 | sc->sc_repdesc = desc; |
| 309 | sc->sc_repdesc_size = size; |
| 310 | |
| 311 | uha.uiaa = uiaa; |
| 312 | nrepid = uhidev_maxrepid(desc, size); |
| 313 | if (nrepid < 0) |
| 314 | return; |
| 315 | if (nrepid > 0) |
| 316 | aprint_normal_dev(self, "%d report ids\n" , nrepid); |
| 317 | nrepid++; |
| 318 | repsizes = kmem_alloc(nrepid * sizeof(*repsizes), KM_SLEEP); |
| 319 | if (repsizes == NULL) |
| 320 | goto nomem; |
| 321 | sc->sc_subdevs = kmem_zalloc(nrepid * sizeof(device_t), |
| 322 | KM_SLEEP); |
| 323 | if (sc->sc_subdevs == NULL) { |
| 324 | kmem_free(repsizes, nrepid * sizeof(*repsizes)); |
| 325 | nomem: |
| 326 | aprint_error_dev(self, "no memory\n" ); |
| 327 | return; |
| 328 | } |
| 329 | |
| 330 | /* Just request max packet size for the interrupt pipe */ |
| 331 | sc->sc_isize = maxinpktsize; |
| 332 | sc->sc_nrepid = nrepid; |
| 333 | |
| 334 | usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); |
| 335 | |
| 336 | for (repid = 0; repid < nrepid; repid++) { |
| 337 | repsz = hid_report_size(desc, size, hid_input, repid); |
| 338 | DPRINTF(("uhidev_match: repid=%d, repsz=%d\n" , repid, repsz)); |
| 339 | repsizes[repid] = repsz; |
| 340 | } |
| 341 | |
| 342 | DPRINTF(("uhidev_attach: isize=%d\n" , sc->sc_isize)); |
| 343 | |
| 344 | uha.parent = sc; |
| 345 | for (repid = 0; repid < nrepid; repid++) { |
| 346 | DPRINTF(("uhidev_match: try repid=%d\n" , repid)); |
| 347 | if (hid_report_size(desc, size, hid_input, repid) == 0 && |
| 348 | hid_report_size(desc, size, hid_output, repid) == 0 && |
| 349 | hid_report_size(desc, size, hid_feature, repid) == 0) { |
| 350 | ; /* already NULL in sc->sc_subdevs[repid] */ |
| 351 | } else { |
| 352 | uha.reportid = repid; |
| 353 | locs[UHIDBUSCF_REPORTID] = repid; |
| 354 | |
| 355 | dev = config_found_sm_loc(self, |
| 356 | "uhidbus" , locs, &uha, |
| 357 | uhidevprint, config_stdsubmatch); |
| 358 | sc->sc_subdevs[repid] = dev; |
| 359 | if (dev != NULL) { |
| 360 | csc = device_private(dev); |
| 361 | csc->sc_in_rep_size = repsizes[repid]; |
| 362 | #ifdef DIAGNOSTIC |
| 363 | DPRINTF(("uhidev_match: repid=%d dev=%p\n" , |
| 364 | repid, dev)); |
| 365 | if (csc->sc_intr == NULL) { |
| 366 | kmem_free(repsizes, |
| 367 | nrepid * sizeof(*repsizes)); |
| 368 | aprint_error_dev(self, |
| 369 | "sc_intr == NULL\n" ); |
| 370 | return; |
| 371 | } |
| 372 | #endif |
| 373 | rnd_attach_source(&csc->rnd_source, |
| 374 | device_xname(dev), |
| 375 | RND_TYPE_TTY, |
| 376 | RND_FLAG_DEFAULT); |
| 377 | } |
| 378 | } |
| 379 | } |
| 380 | kmem_free(repsizes, nrepid * sizeof(*repsizes)); |
| 381 | |
| 382 | return; |
| 383 | } |
| 384 | |
| 385 | int |
| 386 | uhidev_maxrepid(void *buf, int len) |
| 387 | { |
| 388 | struct hid_data *d; |
| 389 | struct hid_item h; |
| 390 | int maxid; |
| 391 | |
| 392 | maxid = -1; |
| 393 | h.report_ID = 0; |
| 394 | for (d = hid_start_parse(buf, len, hid_none); hid_get_item(d, &h); ) |
| 395 | if (h.report_ID > maxid) |
| 396 | maxid = h.report_ID; |
| 397 | hid_end_parse(d); |
| 398 | return maxid; |
| 399 | } |
| 400 | |
| 401 | int |
| 402 | uhidevprint(void *aux, const char *pnp) |
| 403 | { |
| 404 | struct uhidev_attach_arg *uha = aux; |
| 405 | |
| 406 | if (pnp) |
| 407 | aprint_normal("uhid at %s" , pnp); |
| 408 | if (uha->reportid != 0) |
| 409 | aprint_normal(" reportid %d" , uha->reportid); |
| 410 | return UNCONF; |
| 411 | } |
| 412 | |
| 413 | int |
| 414 | uhidev_activate(device_t self, enum devact act) |
| 415 | { |
| 416 | struct uhidev_softc *sc = device_private(self); |
| 417 | |
| 418 | switch (act) { |
| 419 | case DVACT_DEACTIVATE: |
| 420 | sc->sc_dying = 1; |
| 421 | return 0; |
| 422 | default: |
| 423 | return EOPNOTSUPP; |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | void |
| 428 | uhidev_childdet(device_t self, device_t child) |
| 429 | { |
| 430 | int i; |
| 431 | struct uhidev_softc *sc = device_private(self); |
| 432 | |
| 433 | for (i = 0; i < sc->sc_nrepid; i++) { |
| 434 | if (sc->sc_subdevs[i] == child) |
| 435 | break; |
| 436 | } |
| 437 | KASSERT(i < sc->sc_nrepid); |
| 438 | sc->sc_subdevs[i] = NULL; |
| 439 | } |
| 440 | |
| 441 | int |
| 442 | uhidev_detach(device_t self, int flags) |
| 443 | { |
| 444 | struct uhidev_softc *sc = device_private(self); |
| 445 | int i, rv; |
| 446 | struct uhidev *csc; |
| 447 | |
| 448 | DPRINTF(("uhidev_detach: sc=%p flags=%d\n" , sc, flags)); |
| 449 | |
| 450 | sc->sc_dying = 1; |
| 451 | if (sc->sc_ipipe != NULL) |
| 452 | usbd_abort_pipe(sc->sc_ipipe); |
| 453 | |
| 454 | if (sc->sc_repdesc != NULL) |
| 455 | kmem_free(sc->sc_repdesc, sc->sc_repdesc_size); |
| 456 | |
| 457 | rv = 0; |
| 458 | for (i = 0; i < sc->sc_nrepid; i++) { |
| 459 | if (sc->sc_subdevs[i] != NULL) { |
| 460 | csc = device_private(sc->sc_subdevs[i]); |
| 461 | rnd_detach_source(&csc->rnd_source); |
| 462 | rv |= config_detach(sc->sc_subdevs[i], flags); |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); |
| 467 | |
| 468 | pmf_device_deregister(self); |
| 469 | mutex_destroy(&sc->sc_lock); |
| 470 | |
| 471 | return rv; |
| 472 | } |
| 473 | |
| 474 | void |
| 475 | uhidev_intr(struct usbd_xfer *xfer, void *addr, usbd_status status) |
| 476 | { |
| 477 | struct uhidev_softc *sc = addr; |
| 478 | device_t cdev; |
| 479 | struct uhidev *scd; |
| 480 | u_char *p; |
| 481 | u_int rep; |
| 482 | uint32_t cc; |
| 483 | |
| 484 | usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); |
| 485 | |
| 486 | #ifdef UHIDEV_DEBUG |
| 487 | if (uhidevdebug > 5) { |
| 488 | uint32_t i; |
| 489 | |
| 490 | DPRINTF(("uhidev_intr: status=%d cc=%d\n" , status, cc)); |
| 491 | DPRINTF(("uhidev_intr: data =" )); |
| 492 | for (i = 0; i < cc; i++) |
| 493 | DPRINTF((" %02x" , sc->sc_ibuf[i])); |
| 494 | DPRINTF(("\n" )); |
| 495 | } |
| 496 | #endif |
| 497 | |
| 498 | if (status == USBD_CANCELLED) |
| 499 | return; |
| 500 | |
| 501 | if (status != USBD_NORMAL_COMPLETION) { |
| 502 | DPRINTF(("%s: interrupt status=%d\n" , device_xname(sc->sc_dev), |
| 503 | status)); |
| 504 | usbd_clear_endpoint_stall_async(sc->sc_ipipe); |
| 505 | return; |
| 506 | } |
| 507 | |
| 508 | p = sc->sc_ibuf; |
| 509 | if (sc->sc_nrepid != 1) |
| 510 | rep = *p++, cc--; |
| 511 | else |
| 512 | rep = 0; |
| 513 | if (rep >= sc->sc_nrepid) { |
| 514 | printf("uhidev_intr: bad repid %d\n" , rep); |
| 515 | return; |
| 516 | } |
| 517 | cdev = sc->sc_subdevs[rep]; |
| 518 | if (!cdev) |
| 519 | return; |
| 520 | scd = device_private(cdev); |
| 521 | DPRINTFN(5,("uhidev_intr: rep=%d, scd=%p state=0x%x\n" , |
| 522 | rep, scd, scd ? scd->sc_state : 0)); |
| 523 | if (!(scd->sc_state & UHIDEV_OPEN)) |
| 524 | return; |
| 525 | #ifdef UHIDEV_DEBUG |
| 526 | if (scd->sc_in_rep_size != cc) { |
| 527 | DPRINTF(("%s: expected %d bytes, got %d\n" , |
| 528 | device_xname(sc->sc_dev), scd->sc_in_rep_size, cc)); |
| 529 | } |
| 530 | #endif |
| 531 | if (cc == 0) { |
| 532 | DPRINTF(("%s: 0-length input ignored\n" , |
| 533 | device_xname(sc->sc_dev))); |
| 534 | return; |
| 535 | } |
| 536 | rnd_add_uint32(&scd->rnd_source, (uintptr_t)(sc->sc_ibuf)); |
| 537 | scd->sc_intr(scd, p, cc); |
| 538 | } |
| 539 | |
| 540 | void |
| 541 | uhidev_get_report_desc(struct uhidev_softc *sc, void **desc, int *size) |
| 542 | { |
| 543 | *desc = sc->sc_repdesc; |
| 544 | *size = sc->sc_repdesc_size; |
| 545 | } |
| 546 | |
| 547 | int |
| 548 | uhidev_open(struct uhidev *scd) |
| 549 | { |
| 550 | struct uhidev_softc *sc = scd->sc_parent; |
| 551 | usbd_status err; |
| 552 | int error; |
| 553 | |
| 554 | DPRINTF(("uhidev_open: open pipe, state=%d\n" , scd->sc_state)); |
| 555 | |
| 556 | mutex_enter(&sc->sc_lock); |
| 557 | if (scd->sc_state & UHIDEV_OPEN) { |
| 558 | mutex_exit(&sc->sc_lock); |
| 559 | return EBUSY; |
| 560 | } |
| 561 | scd->sc_state |= UHIDEV_OPEN; |
| 562 | if (sc->sc_refcnt++) { |
| 563 | mutex_exit(&sc->sc_lock); |
| 564 | return 0; |
| 565 | } |
| 566 | mutex_exit(&sc->sc_lock); |
| 567 | |
| 568 | if (sc->sc_isize == 0) |
| 569 | return 0; |
| 570 | |
| 571 | sc->sc_ibuf = kmem_alloc(sc->sc_isize, KM_SLEEP); |
| 572 | |
| 573 | /* Set up input interrupt pipe. */ |
| 574 | DPRINTF(("uhidev_open: isize=%d, ep=0x%02x\n" , sc->sc_isize, |
| 575 | sc->sc_iep_addr)); |
| 576 | |
| 577 | err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep_addr, |
| 578 | USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_ibuf, |
| 579 | sc->sc_isize, uhidev_intr, USBD_DEFAULT_INTERVAL); |
| 580 | if (err != USBD_NORMAL_COMPLETION) { |
| 581 | DPRINTF(("uhidopen: usbd_open_pipe_intr failed, " |
| 582 | "error=%d\n" , err)); |
| 583 | error = EIO; |
| 584 | goto out1; |
| 585 | } |
| 586 | |
| 587 | /* |
| 588 | * Set up output interrupt pipe if an output interrupt endpoint |
| 589 | * exists. |
| 590 | */ |
| 591 | if (sc->sc_oep_addr != -1) { |
| 592 | DPRINTF(("uhidev_open: oep=0x%02x\n" , sc->sc_oep_addr)); |
| 593 | |
| 594 | err = usbd_open_pipe(sc->sc_iface, sc->sc_oep_addr, |
| 595 | 0, &sc->sc_opipe); |
| 596 | |
| 597 | if (err != USBD_NORMAL_COMPLETION) { |
| 598 | DPRINTF(("uhidev_open: usbd_open_pipe failed, " |
| 599 | "error=%d\n" , err)); |
| 600 | error = EIO; |
| 601 | goto out2; |
| 602 | } |
| 603 | DPRINTF(("uhidev_open: sc->sc_opipe=%p\n" , sc->sc_opipe)); |
| 604 | |
| 605 | error = usbd_create_xfer(sc->sc_opipe, UHIDEV_OSIZE, 0, 0, |
| 606 | &sc->sc_oxfer); |
| 607 | if (error) { |
| 608 | DPRINTF(("uhidev_open: couldn't allocate an xfer\n" )); |
| 609 | goto out3; |
| 610 | } |
| 611 | |
| 612 | if (sc->sc_flags & UHIDEV_F_XB1) { |
| 613 | uint8_t init_data[] = { 0x05, 0x20 }; |
| 614 | int init_data_len = sizeof(init_data); |
| 615 | err = usbd_intr_transfer(sc->sc_oxfer, sc->sc_opipe, 0, |
| 616 | USBD_NO_TIMEOUT, init_data, &init_data_len); |
| 617 | if (err != USBD_NORMAL_COMPLETION) { |
| 618 | DPRINTF(("uhidev_open: xb1 init failed, " |
| 619 | "error=%d\n" , err)); |
| 620 | error = EIO; |
| 621 | goto out4; |
| 622 | } |
| 623 | } |
| 624 | } |
| 625 | |
| 626 | return 0; |
| 627 | out4: |
| 628 | /* Free output xfer */ |
| 629 | if (sc->sc_oxfer != NULL) |
| 630 | usbd_destroy_xfer(sc->sc_oxfer); |
| 631 | out3: |
| 632 | /* Abort output pipe */ |
| 633 | usbd_close_pipe(sc->sc_opipe); |
| 634 | out2: |
| 635 | /* Abort input pipe */ |
| 636 | usbd_close_pipe(sc->sc_ipipe); |
| 637 | out1: |
| 638 | DPRINTF(("uhidev_open: failed in someway" )); |
| 639 | kmem_free(sc->sc_ibuf, sc->sc_isize); |
| 640 | mutex_enter(&sc->sc_lock); |
| 641 | scd->sc_state &= ~UHIDEV_OPEN; |
| 642 | sc->sc_refcnt = 0; |
| 643 | sc->sc_ibuf = NULL; |
| 644 | sc->sc_ipipe = NULL; |
| 645 | sc->sc_opipe = NULL; |
| 646 | sc->sc_oxfer = NULL; |
| 647 | mutex_exit(&sc->sc_lock); |
| 648 | return error; |
| 649 | } |
| 650 | |
| 651 | void |
| 652 | uhidev_stop(struct uhidev *scd) |
| 653 | { |
| 654 | struct uhidev_softc *sc = scd->sc_parent; |
| 655 | |
| 656 | /* Disable interrupts. */ |
| 657 | if (sc->sc_opipe != NULL) { |
| 658 | usbd_abort_pipe(sc->sc_opipe); |
| 659 | usbd_close_pipe(sc->sc_opipe); |
| 660 | sc->sc_opipe = NULL; |
| 661 | } |
| 662 | |
| 663 | if (sc->sc_ipipe != NULL) { |
| 664 | usbd_abort_pipe(sc->sc_ipipe); |
| 665 | usbd_close_pipe(sc->sc_ipipe); |
| 666 | sc->sc_ipipe = NULL; |
| 667 | } |
| 668 | |
| 669 | if (sc->sc_ibuf != NULL) { |
| 670 | kmem_free(sc->sc_ibuf, sc->sc_isize); |
| 671 | sc->sc_ibuf = NULL; |
| 672 | } |
| 673 | } |
| 674 | |
| 675 | void |
| 676 | uhidev_close(struct uhidev *scd) |
| 677 | { |
| 678 | struct uhidev_softc *sc = scd->sc_parent; |
| 679 | |
| 680 | mutex_enter(&sc->sc_lock); |
| 681 | if (!(scd->sc_state & UHIDEV_OPEN)) { |
| 682 | mutex_exit(&sc->sc_lock); |
| 683 | return; |
| 684 | } |
| 685 | scd->sc_state &= ~UHIDEV_OPEN; |
| 686 | if (--sc->sc_refcnt) { |
| 687 | mutex_exit(&sc->sc_lock); |
| 688 | return; |
| 689 | } |
| 690 | mutex_exit(&sc->sc_lock); |
| 691 | |
| 692 | DPRINTF(("uhidev_close: close pipe\n" )); |
| 693 | |
| 694 | if (sc->sc_oxfer != NULL) { |
| 695 | usbd_destroy_xfer(sc->sc_oxfer); |
| 696 | sc->sc_oxfer = NULL; |
| 697 | } |
| 698 | |
| 699 | |
| 700 | /* Possibly redundant, but properly handled */ |
| 701 | uhidev_stop(scd); |
| 702 | } |
| 703 | |
| 704 | usbd_status |
| 705 | uhidev_set_report(struct uhidev *scd, int type, void *data, int len) |
| 706 | { |
| 707 | char *buf; |
| 708 | usbd_status retstat; |
| 709 | |
| 710 | if (scd->sc_report_id == 0) |
| 711 | return usbd_set_report(scd->sc_parent->sc_iface, type, |
| 712 | scd->sc_report_id, data, len); |
| 713 | |
| 714 | buf = kmem_alloc(len + 1, KM_SLEEP); |
| 715 | buf[0] = scd->sc_report_id; |
| 716 | memcpy(buf+1, data, len); |
| 717 | |
| 718 | retstat = usbd_set_report(scd->sc_parent->sc_iface, type, |
| 719 | scd->sc_report_id, buf, len + 1); |
| 720 | |
| 721 | kmem_free(buf, len + 1); |
| 722 | |
| 723 | return retstat; |
| 724 | } |
| 725 | |
| 726 | usbd_status |
| 727 | uhidev_get_report(struct uhidev *scd, int type, void *data, int len) |
| 728 | { |
| 729 | return usbd_get_report(scd->sc_parent->sc_iface, type, |
| 730 | scd->sc_report_id, data, len); |
| 731 | } |
| 732 | |
| 733 | usbd_status |
| 734 | uhidev_write(struct uhidev_softc *sc, void *data, int len) |
| 735 | { |
| 736 | |
| 737 | DPRINTF(("uhidev_write: data=%p, len=%d\n" , data, len)); |
| 738 | |
| 739 | if (sc->sc_opipe == NULL) |
| 740 | return USBD_INVAL; |
| 741 | |
| 742 | #ifdef UHIDEV_DEBUG |
| 743 | if (uhidevdebug > 50) { |
| 744 | |
| 745 | uint32_t i; |
| 746 | uint8_t *d = data; |
| 747 | |
| 748 | DPRINTF(("uhidev_write: data =" )); |
| 749 | for (i = 0; i < len; i++) |
| 750 | DPRINTF((" %02x" , d[i])); |
| 751 | DPRINTF(("\n" )); |
| 752 | } |
| 753 | #endif |
| 754 | return usbd_intr_transfer(sc->sc_oxfer, sc->sc_opipe, 0, USBD_NO_TIMEOUT, |
| 755 | data, &len); |
| 756 | } |
| 757 | |