| Commit | Line | Data |
|---|---|---|
| 1550dfd9 MD |
1 | /* |
| 2 | * $FreeBSD: src/sys/dev/usb/ums.c,v 1.64 2003/11/09 09:17:22 tanimura Exp $ | |
| 74781d8f | 3 | * $DragonFly: src/sys/dev/usbmisc/ums/ums.c,v 1.31 2008/08/14 20:55:54 hasso Exp $ |
| 1550dfd9 | 4 | */ |
| 984263bc MD |
5 | |
| 6 | /* | |
| 7 | * Copyright (c) 1998 The NetBSD Foundation, Inc. | |
| 8 | * All rights reserved. | |
| 9 | * | |
| 10 | * This code is derived from software contributed to The NetBSD Foundation | |
| 11 | * by Lennart Augustsson (lennart@augustsson.net) at | |
| 12 | * Carlstedt Research & Technology. | |
| 13 | * | |
| 14 | * Redistribution and use in source and binary forms, with or without | |
| 15 | * modification, are permitted provided that the following conditions | |
| 16 | * are met: | |
| 17 | * 1. Redistributions of source code must retain the above copyright | |
| 18 | * notice, this list of conditions and the following disclaimer. | |
| 19 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 20 | * notice, this list of conditions and the following disclaimer in the | |
| 21 | * documentation and/or other materials provided with the distribution. | |
| 22 | * 3. All advertising materials mentioning features or use of this software | |
| 23 | * must display the following acknowledgement: | |
| 24 | * This product includes software developed by the NetBSD | |
| 25 | * Foundation, Inc. and its contributors. | |
| 26 | * 4. Neither the name of The NetBSD Foundation nor the names of its | |
| 27 | * contributors may be used to endorse or promote products derived | |
| 28 | * from this software without specific prior written permission. | |
| 29 | * | |
| 30 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS | |
| 31 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
| 32 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| 33 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS | |
| 34 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| 35 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| 36 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| 37 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| 38 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| 39 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| 40 | * POSSIBILITY OF SUCH DAMAGE. | |
| 41 | */ | |
| 42 | ||
| 43 | /* | |
| 1550dfd9 | 44 | * HID spec: http://www.usb.org/developers/data/devclass/hid1_1.pdf |
| 984263bc MD |
45 | */ |
| 46 | ||
| 47 | #include <sys/param.h> | |
| 48 | #include <sys/systm.h> | |
| 49 | #include <sys/kernel.h> | |
| 50 | #include <sys/malloc.h> | |
| 51 | #include <sys/module.h> | |
| 52 | #include <sys/bus.h> | |
| 984263bc MD |
53 | #include <sys/conf.h> |
| 54 | #include <sys/tty.h> | |
| 55 | #include <sys/file.h> | |
| 56 | #include <sys/select.h> | |
| 984263bc MD |
57 | #include <sys/vnode.h> |
| 58 | #include <sys/poll.h> | |
| 59 | #include <sys/sysctl.h> | |
| 4e01b467 | 60 | #include <sys/thread2.h> |
| 984263bc | 61 | |
| 1f2de5d4 MD |
62 | #include <bus/usb/usb.h> |
| 63 | #include <bus/usb/usbhid.h> | |
| 984263bc | 64 | |
| 1f2de5d4 MD |
65 | #include <bus/usb/usbdi.h> |
| 66 | #include <bus/usb/usbdi_util.h> | |
| 1f2de5d4 MD |
67 | #include <bus/usb/usb_quirks.h> |
| 68 | #include <bus/usb/hid.h> | |
| 984263bc MD |
69 | |
| 70 | #include <machine/mouse.h> | |
| 71 | ||
| 72 | #ifdef USB_DEBUG | |
| fc1ef497 HT |
73 | #define DPRINTF(x) if (umsdebug) kprintf x |
| 74 | #define DPRINTFN(n,x) if (umsdebug>(n)) kprintf x | |
| 984263bc MD |
75 | int umsdebug = 0; |
| 76 | SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums"); | |
| 77 | SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RW, | |
| 78 | &umsdebug, 0, "ums debug level"); | |
| 79 | #else | |
| 80 | #define DPRINTF(x) | |
| 81 | #define DPRINTFN(n,x) | |
| 82 | #endif | |
| 83 | ||
| 84 | #define UMSUNIT(s) (minor(s)&0x1f) | |
| 85 | ||
| 1550dfd9 | 86 | #define MS_TO_TICKS(ms) ((ms) * hz / 1000) |
| 984263bc MD |
87 | |
| 88 | #define QUEUE_BUFSIZE 400 /* MUST be divisible by 5 _and_ 8 */ | |
| 89 | ||
| 90 | struct ums_softc { | |
| 91 | device_t sc_dev; /* base device */ | |
| 92 | usbd_interface_handle sc_iface; /* interface */ | |
| 93 | usbd_pipe_handle sc_intrpipe; /* interrupt pipe */ | |
| 94 | int sc_ep_addr; | |
| 95 | ||
| 96 | u_char *sc_ibuf; | |
| 97 | u_int8_t sc_iid; | |
| 98 | int sc_isize; | |
| 99 | struct hid_location sc_loc_x, sc_loc_y, sc_loc_z; | |
| 100 | struct hid_location *sc_loc_btn; | |
| 101 | ||
| 8c64e381 | 102 | struct callout sc_timeout; /* for spurious button ups */ |
| 984263bc MD |
103 | |
| 104 | int sc_enabled; | |
| 105 | int sc_disconnected; /* device is gone */ | |
| 106 | ||
| 107 | int flags; /* device configuration */ | |
| 108 | #define UMS_Z 0x01 /* z direction available */ | |
| 109 | #define UMS_SPUR_BUT_UP 0x02 /* spurious button up events */ | |
| 110 | int nbuttons; | |
| bc5a1c0b | 111 | #define MAX_BUTTONS 31 /* must not exceed size of sc_buttons */ |
| 984263bc MD |
112 | |
| 113 | u_char qbuf[QUEUE_BUFSIZE]; /* must be divisable by 3&4 */ | |
| 114 | u_char dummy[100]; /* XXX just for safety and for now */ | |
| 115 | int qcount, qhead, qtail; | |
| 116 | mousehw_t hw; | |
| 117 | mousemode_t mode; | |
| 118 | mousestatus_t status; | |
| 119 | ||
| 120 | int state; | |
| 121 | # define UMS_ASLEEP 0x01 /* readFromDevice is waiting */ | |
| 122 | # define UMS_SELECT 0x02 /* select is waiting */ | |
| 123 | struct selinfo rsel; /* process waiting in select */ | |
| 984263bc MD |
124 | }; |
| 125 | ||
| 126 | #define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE) | |
| 127 | #define MOUSE_FLAGS (HIO_RELATIVE) | |
| 128 | ||
| 6ed427ca | 129 | static void ums_intr(usbd_xfer_handle xfer, |
| 984263bc MD |
130 | usbd_private_handle priv, usbd_status status); |
| 131 | ||
| 6ed427ca | 132 | static void ums_add_to_queue(struct ums_softc *sc, |
| 984263bc | 133 | int dx, int dy, int dz, int buttons); |
| 6ed427ca | 134 | static void ums_add_to_queue_timeout(void *priv); |
| 984263bc | 135 | |
| 6ed427ca HT |
136 | static int ums_enable(void *); |
| 137 | static void ums_disable(void *); | |
| 984263bc | 138 | |
| 6ed427ca HT |
139 | static d_open_t ums_open; |
| 140 | static d_close_t ums_close; | |
| 141 | static d_read_t ums_read; | |
| 142 | static d_ioctl_t ums_ioctl; | |
| 143 | static d_poll_t ums_poll; | |
| 984263bc MD |
144 | |
| 145 | #define UMS_CDEV_MAJOR 111 | |
| 146 | ||
| 6ed427ca | 147 | static struct dev_ops ums_ops = { |
| fef8985e MD |
148 | { "ums", UMS_CDEV_MAJOR, 0 }, |
| 149 | .d_open = ums_open, | |
| 150 | .d_close = ums_close, | |
| 151 | .d_read = ums_read, | |
| 152 | .d_ioctl = ums_ioctl, | |
| 153 | .d_poll = ums_poll, | |
| 984263bc MD |
154 | }; |
| 155 | ||
| 61b69189 HT |
156 | static device_probe_t ums_match; |
| 157 | static device_attach_t ums_attach; | |
| 158 | static device_detach_t ums_detach; | |
| 159 | ||
| 160 | static devclass_t ums_devclass; | |
| 161 | ||
| 162 | static kobj_method_t ums_methods[] = { | |
| 163 | DEVMETHOD(device_probe, ums_match), | |
| 164 | DEVMETHOD(device_attach, ums_attach), | |
| 165 | DEVMETHOD(device_detach, ums_detach), | |
| 166 | {0,0} | |
| 167 | }; | |
| 168 | ||
| 169 | static driver_t ums_driver = { | |
| 170 | "ums", | |
| 171 | ums_methods, | |
| 172 | sizeof(struct ums_softc) | |
| 173 | }; | |
| 174 | ||
| 175 | MODULE_DEPEND(ums, usb, 1, 1, 1); | |
| 984263bc | 176 | |
| e785a5d9 HT |
177 | static int |
| 178 | ums_match(device_t self) | |
| 984263bc | 179 | { |
| e785a5d9 | 180 | struct usb_attach_arg *uaa = device_get_ivars(self); |
| 984263bc MD |
181 | usb_interface_descriptor_t *id; |
| 182 | int size, ret; | |
| 183 | void *desc; | |
| 184 | usbd_status err; | |
| 1550dfd9 | 185 | |
| 984263bc MD |
186 | if (!uaa->iface) |
| 187 | return (UMATCH_NONE); | |
| 188 | id = usbd_get_interface_descriptor(uaa->iface); | |
| 189 | if (!id || id->bInterfaceClass != UICLASS_HID) | |
| 190 | return (UMATCH_NONE); | |
| 191 | ||
| 1550dfd9 | 192 | err = usbd_read_report_desc(uaa->iface, &desc, &size, M_TEMP); |
| 984263bc MD |
193 | if (err) |
| 194 | return (UMATCH_NONE); | |
| 195 | ||
| 1550dfd9 | 196 | if (hid_is_collection(desc, size, |
| 984263bc MD |
197 | HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) |
| 198 | ret = UMATCH_IFACECLASS; | |
| 199 | else | |
| 200 | ret = UMATCH_NONE; | |
| 201 | ||
| efda3bd0 | 202 | kfree(desc, M_TEMP); |
| 984263bc MD |
203 | return (ret); |
| 204 | } | |
| 205 | ||
| e785a5d9 HT |
206 | static int |
| 207 | ums_attach(device_t self) | |
| 984263bc | 208 | { |
| e785a5d9 HT |
209 | struct ums_softc *sc = device_get_softc(self); |
| 210 | struct usb_attach_arg *uaa = device_get_ivars(self); | |
| 984263bc | 211 | usbd_interface_handle iface = uaa->iface; |
| 984263bc MD |
212 | usb_endpoint_descriptor_t *ed; |
| 213 | int size; | |
| 214 | void *desc; | |
| 215 | usbd_status err; | |
| 984263bc MD |
216 | u_int32_t flags; |
| 217 | int i; | |
| 218 | struct hid_location loc_btn; | |
| 1550dfd9 | 219 | |
| 984263bc MD |
220 | sc->sc_disconnected = 1; |
| 221 | sc->sc_iface = iface; | |
| e785a5d9 | 222 | sc->sc_dev = self; |
| 984263bc MD |
223 | ed = usbd_interface2endpoint_descriptor(iface, 0); |
| 224 | if (!ed) { | |
| e3869ec7 | 225 | kprintf("%s: could not read endpoint descriptor\n", |
| 6ed427ca | 226 | device_get_nameunit(sc->sc_dev)); |
| e785a5d9 | 227 | return ENXIO; |
| 984263bc MD |
228 | } |
| 229 | ||
| 230 | DPRINTFN(10,("ums_attach: bLength=%d bDescriptorType=%d " | |
| 231 | "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d" | |
| 232 | " bInterval=%d\n", | |
| 1550dfd9 | 233 | ed->bLength, ed->bDescriptorType, |
| 984263bc MD |
234 | UE_GET_ADDR(ed->bEndpointAddress), |
| 235 | UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in":"out", | |
| 236 | UE_GET_XFERTYPE(ed->bmAttributes), | |
| 237 | UGETW(ed->wMaxPacketSize), ed->bInterval)); | |
| 238 | ||
| 239 | if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || | |
| 240 | UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) { | |
| e3869ec7 | 241 | kprintf("%s: unexpected endpoint\n", |
| 6ed427ca | 242 | device_get_nameunit(sc->sc_dev)); |
| e785a5d9 | 243 | return ENXIO; |
| 984263bc MD |
244 | } |
| 245 | ||
| 1550dfd9 | 246 | err = usbd_read_report_desc(uaa->iface, &desc, &size, M_TEMP); |
| 984263bc | 247 | if (err) |
| e785a5d9 | 248 | return ENXIO; |
| 984263bc MD |
249 | |
| 250 | if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), | |
| 251 | hid_input, &sc->sc_loc_x, &flags)) { | |
| 6ed427ca | 252 | kprintf("%s: mouse has no X report\n", device_get_nameunit(sc->sc_dev)); |
| e785a5d9 | 253 | return ENXIO; |
| 984263bc MD |
254 | } |
| 255 | if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { | |
| e3869ec7 | 256 | kprintf("%s: X report 0x%04x not supported\n", |
| 6ed427ca | 257 | device_get_nameunit(sc->sc_dev), flags); |
| e785a5d9 | 258 | return ENXIO; |
| 984263bc MD |
259 | } |
| 260 | ||
| 261 | if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), | |
| 262 | hid_input, &sc->sc_loc_y, &flags)) { | |
| 6ed427ca | 263 | kprintf("%s: mouse has no Y report\n", device_get_nameunit(sc->sc_dev)); |
| e785a5d9 | 264 | return ENXIO; |
| 984263bc MD |
265 | } |
| 266 | if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { | |
| e3869ec7 | 267 | kprintf("%s: Y report 0x%04x not supported\n", |
| 6ed427ca | 268 | device_get_nameunit(sc->sc_dev), flags); |
| e785a5d9 | 269 | return ENXIO; |
| 984263bc MD |
270 | } |
| 271 | ||
| 272 | /* try to guess the Z activator: first check Z, then WHEEL */ | |
| 273 | if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), | |
| 274 | hid_input, &sc->sc_loc_z, &flags) || | |
| 275 | hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), | |
| 276 | hid_input, &sc->sc_loc_z, &flags)) { | |
| 277 | if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { | |
| 278 | sc->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ | |
| 279 | } else { | |
| 280 | sc->flags |= UMS_Z; | |
| 281 | } | |
| 282 | } | |
| 283 | ||
| 284 | /* figure out the number of buttons */ | |
| 285 | for (i = 1; i <= MAX_BUTTONS; i++) | |
| 286 | if (!hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i), | |
| 287 | hid_input, &loc_btn, 0)) | |
| 288 | break; | |
| 289 | sc->nbuttons = i - 1; | |
| 77652cad | 290 | sc->sc_loc_btn = kmalloc(sizeof(struct hid_location)*sc->nbuttons, |
| ad86f6b6 | 291 | M_USBDEV, M_INTWAIT); |
| 984263bc | 292 | |
| 6ed427ca | 293 | kprintf("%s: %d buttons%s\n", device_get_nameunit(sc->sc_dev), |
| 984263bc MD |
294 | sc->nbuttons, sc->flags & UMS_Z? " and Z dir." : ""); |
| 295 | ||
| 296 | for (i = 1; i <= sc->nbuttons; i++) | |
| 297 | hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i), | |
| 298 | hid_input, &sc->sc_loc_btn[i-1], 0); | |
| 299 | ||
| 300 | sc->sc_isize = hid_report_size(desc, size, hid_input, &sc->sc_iid); | |
| efda3bd0 | 301 | sc->sc_ibuf = kmalloc(sc->sc_isize, M_USB, M_INTWAIT); |
| 984263bc MD |
302 | sc->sc_ep_addr = ed->bEndpointAddress; |
| 303 | sc->sc_disconnected = 0; | |
| efda3bd0 | 304 | kfree(desc, M_TEMP); |
| 984263bc MD |
305 | |
| 306 | #ifdef USB_DEBUG | |
| 307 | DPRINTF(("ums_attach: sc=%p\n", sc)); | |
| 1550dfd9 | 308 | DPRINTF(("ums_attach: X\t%d/%d\n", |
| 984263bc | 309 | sc->sc_loc_x.pos, sc->sc_loc_x.size)); |
| 1550dfd9 | 310 | DPRINTF(("ums_attach: Y\t%d/%d\n", |
| 984263bc MD |
311 | sc->sc_loc_y.pos, sc->sc_loc_y.size)); |
| 312 | if (sc->flags & UMS_Z) | |
| 1550dfd9 | 313 | DPRINTF(("ums_attach: Z\t%d/%d\n", |
| 984263bc MD |
314 | sc->sc_loc_z.pos, sc->sc_loc_z.size)); |
| 315 | for (i = 1; i <= sc->nbuttons; i++) { | |
| 316 | DPRINTF(("ums_attach: B%d\t%d/%d\n", | |
| 317 | i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size)); | |
| 318 | } | |
| 319 | DPRINTF(("ums_attach: size=%d, id=%d\n", sc->sc_isize, sc->sc_iid)); | |
| 320 | #endif | |
| 321 | ||
| 322 | if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) | |
| 323 | sc->hw.buttons = MOUSE_MSC_MAXBUTTON; | |
| 324 | else | |
| 325 | sc->hw.buttons = sc->nbuttons; | |
| 326 | sc->hw.iftype = MOUSE_IF_USB; | |
| 327 | sc->hw.type = MOUSE_MOUSE; | |
| 328 | sc->hw.model = MOUSE_MODEL_GENERIC; | |
| 329 | sc->hw.hwid = 0; | |
| 330 | sc->mode.protocol = MOUSE_PROTO_MSC; | |
| 331 | sc->mode.rate = -1; | |
| 332 | sc->mode.resolution = MOUSE_RES_UNKNOWN; | |
| 333 | sc->mode.accelfactor = 0; | |
| 334 | sc->mode.level = 0; | |
| 335 | sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; | |
| 336 | sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; | |
| 337 | sc->mode.syncmask[1] = MOUSE_MSC_SYNC; | |
| 338 | ||
| 339 | sc->status.flags = 0; | |
| 340 | sc->status.button = sc->status.obutton = 0; | |
| 341 | sc->status.dx = sc->status.dy = sc->status.dz = 0; | |
| 342 | ||
| fef8985e | 343 | make_dev(&ums_ops, device_get_unit(self), |
| 3e82b46c MD |
344 | UID_ROOT, GID_OPERATOR, |
| 345 | 0644, "ums%d", device_get_unit(self)); | |
| 984263bc MD |
346 | |
| 347 | if (usbd_get_quirks(uaa->device)->uq_flags & UQ_SPUR_BUT_UP) { | |
| 348 | DPRINTF(("%s: Spurious button up events\n", | |
| 6ed427ca | 349 | device_get_nameunit(sc->sc_dev))); |
| 984263bc MD |
350 | sc->flags |= UMS_SPUR_BUT_UP; |
| 351 | } | |
| 352 | ||
| e785a5d9 | 353 | return 0; |
| 984263bc MD |
354 | } |
| 355 | ||
| 356 | ||
| 6ed427ca | 357 | static int |
| 984263bc MD |
358 | ums_detach(device_t self) |
| 359 | { | |
| 360 | struct ums_softc *sc = device_get_softc(self); | |
| 984263bc MD |
361 | |
| 362 | if (sc->sc_enabled) | |
| 363 | ums_disable(sc); | |
| 364 | ||
| 6ed427ca | 365 | DPRINTF(("%s: disconnected\n", device_get_nameunit(self))); |
| 984263bc | 366 | |
| efda3bd0 MD |
367 | kfree(sc->sc_loc_btn, M_USB); |
| 368 | kfree(sc->sc_ibuf, M_USB); | |
| 984263bc | 369 | |
| 984263bc MD |
370 | /* someone waiting for data */ |
| 371 | /* | |
| 372 | * XXX If we wakeup the process here, the device will be gone by | |
| 373 | * the time the process gets a chance to notice. *_close and friends | |
| 374 | * should be fixed to handle this case. | |
| 375 | * Or we should do a delayed detach for this. | |
| 376 | * Does this delay now force tsleep to exit with an error? | |
| 377 | */ | |
| 378 | if (sc->state & UMS_ASLEEP) { | |
| 379 | sc->state &= ~UMS_ASLEEP; | |
| 380 | wakeup(sc); | |
| 381 | } | |
| 382 | if (sc->state & UMS_SELECT) { | |
| 383 | sc->state &= ~UMS_SELECT; | |
| 375a9af6 | 384 | selwakeup(&sc->rsel); |
| 984263bc | 385 | } |
| cd29885a | 386 | dev_ops_remove_minor(&ums_ops, /*-1, */device_get_unit(self)); |
| 984263bc MD |
387 | |
| 388 | return 0; | |
| 389 | } | |
| 390 | ||
| 391 | void | |
| c436375a SW |
392 | ums_intr(usbd_xfer_handle xfer, usbd_private_handle addr, |
| 393 | usbd_status status) | |
| 984263bc MD |
394 | { |
| 395 | struct ums_softc *sc = addr; | |
| 396 | u_char *ibuf; | |
| 397 | int dx, dy, dz; | |
| 398 | u_char buttons = 0; | |
| 399 | int i; | |
| 400 | ||
| 401 | #define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i)) | |
| 402 | ||
| 403 | DPRINTFN(5, ("ums_intr: sc=%p status=%d\n", sc, status)); | |
| 404 | DPRINTFN(5, ("ums_intr: data = %02x %02x %02x\n", | |
| 405 | sc->sc_ibuf[0], sc->sc_ibuf[1], sc->sc_ibuf[2])); | |
| 406 | ||
| 407 | if (status == USBD_CANCELLED) | |
| 408 | return; | |
| 409 | ||
| 410 | if (status != USBD_NORMAL_COMPLETION) { | |
| 411 | DPRINTF(("ums_intr: status=%d\n", status)); | |
| 1550dfd9 MD |
412 | if (status == USBD_STALLED) |
| 413 | usbd_clear_endpoint_stall_async(sc->sc_intrpipe); | |
| 984263bc MD |
414 | return; |
| 415 | } | |
| 416 | ||
| 417 | ibuf = sc->sc_ibuf; | |
| 418 | if (sc->sc_iid) { | |
| 419 | if (*ibuf++ != sc->sc_iid) | |
| 420 | return; | |
| 421 | } | |
| 422 | ||
| 423 | dx = hid_get_data(ibuf, &sc->sc_loc_x); | |
| 424 | dy = -hid_get_data(ibuf, &sc->sc_loc_y); | |
| 425 | dz = -hid_get_data(ibuf, &sc->sc_loc_z); | |
| 426 | for (i = 0; i < sc->nbuttons; i++) | |
| 427 | if (hid_get_data(ibuf, &sc->sc_loc_btn[i])) | |
| 428 | buttons |= (1 << UMS_BUT(i)); | |
| 429 | ||
| 430 | if (dx || dy || dz || (sc->flags & UMS_Z) | |
| 431 | || buttons != sc->status.button) { | |
| 432 | DPRINTFN(5, ("ums_intr: x:%d y:%d z:%d buttons:0x%x\n", | |
| 433 | dx, dy, dz, buttons)); | |
| 434 | ||
| 435 | sc->status.button = buttons; | |
| 436 | sc->status.dx += dx; | |
| 437 | sc->status.dy += dy; | |
| 438 | sc->status.dz += dz; | |
| 439 | ||
| 440 | /* Discard data in case of full buffer */ | |
| 441 | if (sc->qcount == sizeof(sc->qbuf)) { | |
| 442 | DPRINTF(("Buffer full, discarded packet")); | |
| 443 | return; | |
| 444 | } | |
| 445 | ||
| 446 | /* | |
| 447 | * The Qtronix keyboard has a built in PS/2 port for a mouse. | |
| 448 | * The firmware once in a while posts a spurious button up | |
| 449 | * event. This event we ignore by doing a timeout for 50 msecs. | |
| 450 | * If we receive dx=dy=dz=buttons=0 before we add the event to | |
| 451 | * the queue. | |
| 452 | * In any other case we delete the timeout event. | |
| 453 | */ | |
| 454 | if (sc->flags & UMS_SPUR_BUT_UP && | |
| 455 | dx == 0 && dy == 0 && dz == 0 && buttons == 0) { | |
| 8c64e381 | 456 | callout_reset(&sc->sc_timeout, MS_TO_TICKS(50), |
| 1550dfd9 | 457 | ums_add_to_queue_timeout, (void *) sc); |
| 984263bc | 458 | } else { |
| 8c64e381 | 459 | callout_stop(&sc->sc_timeout); |
| 984263bc MD |
460 | ums_add_to_queue(sc, dx, dy, dz, buttons); |
| 461 | } | |
| 462 | } | |
| 463 | } | |
| 464 | ||
| 6ed427ca | 465 | static void |
| 984263bc MD |
466 | ums_add_to_queue_timeout(void *priv) |
| 467 | { | |
| 468 | struct ums_softc *sc = priv; | |
| 984263bc | 469 | |
| 4e01b467 | 470 | crit_enter(); |
| 984263bc | 471 | ums_add_to_queue(sc, 0, 0, 0, 0); |
| 4e01b467 | 472 | crit_exit(); |
| 984263bc MD |
473 | } |
| 474 | ||
| 6ed427ca | 475 | static void |
| 984263bc MD |
476 | ums_add_to_queue(struct ums_softc *sc, int dx, int dy, int dz, int buttons) |
| 477 | { | |
| 478 | /* Discard data in case of full buffer */ | |
| 479 | if (sc->qhead+sc->mode.packetsize > sizeof(sc->qbuf)) { | |
| 480 | DPRINTF(("Buffer full, discarded packet")); | |
| 481 | return; | |
| 482 | } | |
| 483 | ||
| 484 | if (dx > 254) dx = 254; | |
| 485 | if (dx < -256) dx = -256; | |
| 486 | if (dy > 254) dy = 254; | |
| 487 | if (dy < -256) dy = -256; | |
| 488 | if (dz > 126) dz = 126; | |
| 489 | if (dz < -128) dz = -128; | |
| 490 | ||
| 491 | sc->qbuf[sc->qhead] = sc->mode.syncmask[1]; | |
| 492 | sc->qbuf[sc->qhead] |= ~buttons & MOUSE_MSC_BUTTONS; | |
| 493 | sc->qbuf[sc->qhead+1] = dx >> 1; | |
| 494 | sc->qbuf[sc->qhead+2] = dy >> 1; | |
| 495 | sc->qbuf[sc->qhead+3] = dx - (dx >> 1); | |
| 496 | sc->qbuf[sc->qhead+4] = dy - (dy >> 1); | |
| 497 | ||
| 498 | if (sc->mode.level == 1) { | |
| 499 | sc->qbuf[sc->qhead+5] = dz >> 1; | |
| 500 | sc->qbuf[sc->qhead+6] = dz - (dz >> 1); | |
| 501 | sc->qbuf[sc->qhead+7] = ((~buttons >> 3) | |
| 502 | & MOUSE_SYS_EXTBUTTONS); | |
| 503 | } | |
| 504 | ||
| 505 | sc->qhead += sc->mode.packetsize; | |
| 506 | sc->qcount += sc->mode.packetsize; | |
| 507 | /* wrap round at end of buffer */ | |
| 508 | if (sc->qhead >= sizeof(sc->qbuf)) | |
| 509 | sc->qhead = 0; | |
| 510 | ||
| 511 | /* someone waiting for data */ | |
| 512 | if (sc->state & UMS_ASLEEP) { | |
| 513 | sc->state &= ~UMS_ASLEEP; | |
| 514 | wakeup(sc); | |
| 515 | } | |
| 516 | if (sc->state & UMS_SELECT) { | |
| 517 | sc->state &= ~UMS_SELECT; | |
| 375a9af6 | 518 | selwakeup(&sc->rsel); |
| 984263bc MD |
519 | } |
| 520 | } | |
| c436375a | 521 | |
| 6ed427ca | 522 | static int |
| c436375a | 523 | ums_enable(void *v) |
| 984263bc MD |
524 | { |
| 525 | struct ums_softc *sc = v; | |
| 526 | ||
| 527 | usbd_status err; | |
| 528 | ||
| 529 | if (sc->sc_enabled) | |
| 530 | return EBUSY; | |
| 531 | ||
| 532 | sc->sc_enabled = 1; | |
| 533 | sc->qcount = 0; | |
| 534 | sc->qhead = sc->qtail = 0; | |
| 535 | sc->status.flags = 0; | |
| 536 | sc->status.button = sc->status.obutton = 0; | |
| 537 | sc->status.dx = sc->status.dy = sc->status.dz = 0; | |
| 538 | ||
| 8c64e381 | 539 | callout_init(&sc->sc_timeout); |
| 984263bc MD |
540 | |
| 541 | /* Set up interrupt pipe. */ | |
| 1550dfd9 MD |
542 | err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr, |
| 543 | USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc, | |
| 984263bc MD |
544 | sc->sc_ibuf, sc->sc_isize, ums_intr, |
| 545 | USBD_DEFAULT_INTERVAL); | |
| 546 | if (err) { | |
| 547 | DPRINTF(("ums_enable: usbd_open_pipe_intr failed, error=%d\n", | |
| 548 | err)); | |
| 549 | sc->sc_enabled = 0; | |
| 550 | return (EIO); | |
| 551 | } | |
| 552 | return (0); | |
| 553 | } | |
| 554 | ||
| 6ed427ca | 555 | static void |
| c436375a | 556 | ums_disable(void *priv) |
| 984263bc MD |
557 | { |
| 558 | struct ums_softc *sc = priv; | |
| 559 | ||
| 8c64e381 | 560 | callout_stop(&sc->sc_timeout); |
| 984263bc MD |
561 | |
| 562 | /* Disable interrupts. */ | |
| 563 | usbd_abort_pipe(sc->sc_intrpipe); | |
| 564 | usbd_close_pipe(sc->sc_intrpipe); | |
| 565 | ||
| 566 | sc->sc_enabled = 0; | |
| 567 | ||
| 568 | if (sc->qcount != 0) | |
| 569 | DPRINTF(("Discarded %d bytes in queue\n", sc->qcount)); | |
| 570 | } | |
| 571 | ||
| 6ed427ca | 572 | static int |
| fef8985e | 573 | ums_open(struct dev_open_args *ap) |
| 984263bc | 574 | { |
| b13267a5 | 575 | cdev_t dev = ap->a_head.a_dev; |
| 984263bc MD |
576 | struct ums_softc *sc; |
| 577 | ||
| 1e089e7e HT |
578 | sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); |
| 579 | if (sc == NULL) | |
| 580 | return (ENXIO); | |
| 984263bc MD |
581 | |
| 582 | return ums_enable(sc); | |
| 583 | } | |
| 584 | ||
| 6ed427ca | 585 | static int |
| fef8985e | 586 | ums_close(struct dev_close_args *ap) |
| 984263bc | 587 | { |
| b13267a5 | 588 | cdev_t dev = ap->a_head.a_dev; |
| 984263bc MD |
589 | struct ums_softc *sc; |
| 590 | ||
| 1e089e7e | 591 | sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); |
| 984263bc MD |
592 | |
| 593 | if (!sc) | |
| 594 | return 0; | |
| 595 | ||
| 596 | if (sc->sc_enabled) | |
| 597 | ums_disable(sc); | |
| 598 | ||
| 599 | return 0; | |
| 600 | } | |
| 601 | ||
| 6ed427ca | 602 | static int |
| fef8985e | 603 | ums_read(struct dev_read_args *ap) |
| 984263bc | 604 | { |
| b13267a5 | 605 | cdev_t dev = ap->a_head.a_dev; |
| fef8985e | 606 | struct uio *uio = ap->a_uio; |
| 984263bc | 607 | struct ums_softc *sc; |
| 984263bc MD |
608 | char buf[sizeof(sc->qbuf)]; |
| 609 | int l = 0; | |
| 610 | int error; | |
| 611 | ||
| 1e089e7e | 612 | sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); |
| 984263bc | 613 | |
| 4e01b467 | 614 | crit_enter(); |
| 984263bc | 615 | if (!sc) { |
| 4e01b467 | 616 | crit_exit(); |
| 984263bc MD |
617 | return EIO; |
| 618 | } | |
| 619 | ||
| 620 | while (sc->qcount == 0 ) { | |
| fef8985e | 621 | if (ap->a_ioflag & IO_NDELAY) { /* non-blocking I/O */ |
| 4e01b467 | 622 | crit_exit(); |
| 984263bc MD |
623 | return EWOULDBLOCK; |
| 624 | } | |
| 1550dfd9 | 625 | |
| 984263bc | 626 | sc->state |= UMS_ASLEEP; /* blocking I/O */ |
| 377d4740 | 627 | error = tsleep(sc, PCATCH, "umsrea", 0); |
| 984263bc | 628 | if (error) { |
| 4e01b467 | 629 | crit_exit(); |
| 984263bc MD |
630 | return error; |
| 631 | } else if (!sc->sc_enabled) { | |
| 4e01b467 | 632 | crit_exit(); |
| 984263bc MD |
633 | return EINTR; |
| 634 | } | |
| 635 | /* check whether the device is still there */ | |
| 636 | ||
| 637 | sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); | |
| 638 | if (!sc) { | |
| 4e01b467 | 639 | crit_exit(); |
| 984263bc MD |
640 | return EIO; |
| 641 | } | |
| 642 | } | |
| 643 | ||
| 644 | /* | |
| 4e01b467 MD |
645 | * The writer process only extends qcount and qtail. We could copy |
| 646 | * them and use the copies to do the copying out of the queue. | |
| 984263bc MD |
647 | */ |
| 648 | ||
| 649 | while ((sc->qcount > 0) && (uio->uio_resid > 0)) { | |
| 650 | l = (sc->qcount < uio->uio_resid? sc->qcount:uio->uio_resid); | |
| 651 | if (l > sizeof(buf)) | |
| 652 | l = sizeof(buf); | |
| 653 | if (l > sizeof(sc->qbuf) - sc->qtail) /* transfer till end of buf */ | |
| 654 | l = sizeof(sc->qbuf) - sc->qtail; | |
| 655 | ||
| 4e01b467 | 656 | crit_exit(); |
| 984263bc | 657 | uiomove(&sc->qbuf[sc->qtail], l, uio); |
| 4e01b467 | 658 | crit_enter(); |
| 984263bc MD |
659 | |
| 660 | if ( sc->qcount - l < 0 ) { | |
| 661 | DPRINTF(("qcount below 0, count=%d l=%d\n", sc->qcount, l)); | |
| 662 | sc->qcount = l; | |
| 663 | } | |
| 664 | sc->qcount -= l; /* remove the bytes from the buffer */ | |
| 665 | sc->qtail = (sc->qtail + l) % sizeof(sc->qbuf); | |
| 666 | } | |
| 4e01b467 | 667 | crit_exit(); |
| 984263bc MD |
668 | |
| 669 | return 0; | |
| 670 | } | |
| 671 | ||
| 6ed427ca | 672 | static int |
| fef8985e | 673 | ums_poll(struct dev_poll_args *ap) |
| 984263bc | 674 | { |
| b13267a5 | 675 | cdev_t dev = ap->a_head.a_dev; |
| 984263bc MD |
676 | struct ums_softc *sc; |
| 677 | int revents = 0; | |
| 984263bc | 678 | |
| 1e089e7e | 679 | sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); |
| 984263bc | 680 | |
| fef8985e MD |
681 | if (!sc) { |
| 682 | ap->a_events = 0; | |
| 984263bc | 683 | return 0; |
| fef8985e | 684 | } |
| 984263bc | 685 | |
| 4e01b467 | 686 | crit_enter(); |
| fef8985e | 687 | if (ap->a_events & (POLLIN | POLLRDNORM)) { |
| 984263bc | 688 | if (sc->qcount) { |
| fef8985e | 689 | revents = ap->a_events & (POLLIN | POLLRDNORM); |
| 984263bc MD |
690 | } else { |
| 691 | sc->state |= UMS_SELECT; | |
| fef8985e | 692 | selrecord(curthread, &sc->rsel); |
| 984263bc MD |
693 | } |
| 694 | } | |
| 4e01b467 | 695 | crit_exit(); |
| fef8985e MD |
696 | ap->a_events = revents; |
| 697 | return (0); | |
| 984263bc | 698 | } |
| 1550dfd9 | 699 | |
| 984263bc | 700 | int |
| fef8985e | 701 | ums_ioctl(struct dev_ioctl_args *ap) |
| 984263bc | 702 | { |
| b13267a5 | 703 | cdev_t dev = ap->a_head.a_dev; |
| 984263bc MD |
704 | struct ums_softc *sc; |
| 705 | int error = 0; | |
| 984263bc MD |
706 | mousemode_t mode; |
| 707 | ||
| 1e089e7e | 708 | sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); |
| 984263bc MD |
709 | |
| 710 | if (!sc) | |
| 711 | return EIO; | |
| 712 | ||
| fef8985e | 713 | switch(ap->a_cmd) { |
| 984263bc | 714 | case MOUSE_GETHWINFO: |
| fef8985e | 715 | *(mousehw_t *)ap->a_data = sc->hw; |
| 984263bc MD |
716 | break; |
| 717 | case MOUSE_GETMODE: | |
| fef8985e | 718 | *(mousemode_t *)ap->a_data = sc->mode; |
| 984263bc MD |
719 | break; |
| 720 | case MOUSE_SETMODE: | |
| fef8985e | 721 | mode = *(mousemode_t *)ap->a_data; |
| 984263bc MD |
722 | |
| 723 | if (mode.level == -1) | |
| 724 | /* don't change the current setting */ | |
| 725 | ; | |
| 726 | else if ((mode.level < 0) || (mode.level > 1)) | |
| 727 | return (EINVAL); | |
| 728 | ||
| 4e01b467 | 729 | crit_enter(); |
| 984263bc MD |
730 | sc->mode.level = mode.level; |
| 731 | ||
| 732 | if (sc->mode.level == 0) { | |
| 733 | if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) | |
| 734 | sc->hw.buttons = MOUSE_MSC_MAXBUTTON; | |
| 735 | else | |
| 736 | sc->hw.buttons = sc->nbuttons; | |
| 737 | sc->mode.protocol = MOUSE_PROTO_MSC; | |
| 738 | sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; | |
| 739 | sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; | |
| 740 | sc->mode.syncmask[1] = MOUSE_MSC_SYNC; | |
| 741 | } else if (sc->mode.level == 1) { | |
| 742 | if (sc->nbuttons > MOUSE_SYS_MAXBUTTON) | |
| 743 | sc->hw.buttons = MOUSE_SYS_MAXBUTTON; | |
| 744 | else | |
| 745 | sc->hw.buttons = sc->nbuttons; | |
| 746 | sc->mode.protocol = MOUSE_PROTO_SYSMOUSE; | |
| 747 | sc->mode.packetsize = MOUSE_SYS_PACKETSIZE; | |
| 748 | sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK; | |
| 749 | sc->mode.syncmask[1] = MOUSE_SYS_SYNC; | |
| 750 | } | |
| 751 | ||
| 752 | bzero(sc->qbuf, sizeof(sc->qbuf)); | |
| 753 | sc->qhead = sc->qtail = sc->qcount = 0; | |
| 4e01b467 | 754 | crit_exit(); |
| 984263bc MD |
755 | |
| 756 | break; | |
| 757 | case MOUSE_GETLEVEL: | |
| fef8985e | 758 | *(int *)ap->a_data = sc->mode.level; |
| 984263bc MD |
759 | break; |
| 760 | case MOUSE_SETLEVEL: | |
| fef8985e | 761 | if (*(int *)ap->a_data < 0 || *(int *)ap->a_data > 1) |
| 984263bc MD |
762 | return (EINVAL); |
| 763 | ||
| 4e01b467 | 764 | crit_enter(); |
| fef8985e | 765 | sc->mode.level = *(int *)ap->a_data; |
| 984263bc MD |
766 | |
| 767 | if (sc->mode.level == 0) { | |
| 768 | if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) | |
| 769 | sc->hw.buttons = MOUSE_MSC_MAXBUTTON; | |
| 770 | else | |
| 771 | sc->hw.buttons = sc->nbuttons; | |
| 772 | sc->mode.protocol = MOUSE_PROTO_MSC; | |
| 773 | sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; | |
| 774 | sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; | |
| 775 | sc->mode.syncmask[1] = MOUSE_MSC_SYNC; | |
| 776 | } else if (sc->mode.level == 1) { | |
| 777 | if (sc->nbuttons > MOUSE_SYS_MAXBUTTON) | |
| 778 | sc->hw.buttons = MOUSE_SYS_MAXBUTTON; | |
| 779 | else | |
| 780 | sc->hw.buttons = sc->nbuttons; | |
| 781 | sc->mode.protocol = MOUSE_PROTO_SYSMOUSE; | |
| 782 | sc->mode.packetsize = MOUSE_SYS_PACKETSIZE; | |
| 783 | sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK; | |
| 784 | sc->mode.syncmask[1] = MOUSE_SYS_SYNC; | |
| 785 | } | |
| 786 | ||
| 787 | bzero(sc->qbuf, sizeof(sc->qbuf)); | |
| 788 | sc->qhead = sc->qtail = sc->qcount = 0; | |
| 4e01b467 | 789 | crit_exit(); |
| 984263bc MD |
790 | |
| 791 | break; | |
| 792 | case MOUSE_GETSTATUS: { | |
| fef8985e | 793 | mousestatus_t *status = (mousestatus_t *) ap->a_data; |
| 984263bc | 794 | |
| 4e01b467 | 795 | crit_enter(); |
| 984263bc MD |
796 | *status = sc->status; |
| 797 | sc->status.obutton = sc->status.button; | |
| 798 | sc->status.button = 0; | |
| 799 | sc->status.dx = sc->status.dy = sc->status.dz = 0; | |
| 4e01b467 | 800 | crit_exit(); |
| 984263bc MD |
801 | |
| 802 | if (status->dx || status->dy || status->dz) | |
| 803 | status->flags |= MOUSE_POSCHANGED; | |
| 804 | if (status->button != status->obutton) | |
| 805 | status->flags |= MOUSE_BUTTONSCHANGED; | |
| 806 | break; | |
| 807 | } | |
| 808 | default: | |
| 809 | error = ENOTTY; | |
| 810 | } | |
| 811 | ||
| 812 | return error; | |
| 813 | } | |
| 814 | ||
| 815 | DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, usbd_driver_load, 0); |