1 /* $OpenBSD: hidms.c,v 1.5 2018/09/05 16:34:58 jcs Exp $ */ 2 /* $NetBSD: ums.c,v 1.60 2003/03/11 16:44:00 augustss Exp $ */ 3 4 /* 5 * Copyright (c) 1998 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Lennart Augustsson (lennart@augustsson.net) at 10 * Carlstedt Research & Technology. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 * POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 /* 35 * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf 36 */ 37 38 #include <sys/param.h> 39 #include <sys/systm.h> 40 #include <sys/kernel.h> 41 #include <sys/device.h> 42 #include <sys/ioctl.h> 43 44 #include <dev/wscons/wsconsio.h> 45 #include <dev/wscons/wsmousevar.h> 46 47 #include <dev/hid/hid.h> 48 #include <dev/hid/hidmsvar.h> 49 50 #ifdef HIDMS_DEBUG 51 #define DPRINTF(x) do { if (hidmsdebug) printf x; } while (0) 52 #define DPRINTFN(n,x) do { if (hidmsdebug>(n)) printf x; } while (0) 53 int hidmsdebug = 0; 54 #else 55 #define DPRINTF(x) 56 #define DPRINTFN(n,x) 57 #endif 58 59 #define HIDMS_BUT(i) ((i) == 1 || (i) == 2 ? 3 - (i) : i) 60 61 #define MOUSE_FLAGS_MASK (HIO_CONST | HIO_RELATIVE) 62 #define NOTMOUSE(f) (((f) & MOUSE_FLAGS_MASK) != HIO_RELATIVE) 63 64 int 65 hidms_setup(struct device *self, struct hidms *ms, uint32_t quirks, 66 int id, void *desc, int dlen) 67 { 68 struct hid_item h; 69 struct hid_data *d; 70 uint32_t flags; 71 int i, wheel, twheel; 72 73 ms->sc_device = self; 74 ms->sc_rawmode = 1; 75 76 ms->sc_flags = quirks; 77 78 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), id, 79 hid_input, &ms->sc_loc_x, &flags)) { 80 printf("\n%s: mouse has no X report\n", self->dv_xname); 81 return ENXIO; 82 } 83 switch(flags & MOUSE_FLAGS_MASK) { 84 case 0: 85 ms->sc_flags |= HIDMS_ABSX; 86 break; 87 case HIO_RELATIVE: 88 break; 89 default: 90 printf("\n%s: X report 0x%04x not supported\n", 91 self->dv_xname, flags); 92 return ENXIO; 93 } 94 95 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), id, 96 hid_input, &ms->sc_loc_y, &flags)) { 97 printf("\n%s: mouse has no Y report\n", self->dv_xname); 98 return ENXIO; 99 } 100 switch(flags & MOUSE_FLAGS_MASK) { 101 case 0: 102 ms->sc_flags |= HIDMS_ABSY; 103 break; 104 case HIO_RELATIVE: 105 break; 106 default: 107 printf("\n%s: Y report 0x%04x not supported\n", 108 self->dv_xname, flags); 109 return ENXIO; 110 } 111 112 /* 113 * Try to guess the Z activator: check WHEEL, TWHEEL, and Z, 114 * in that order. 115 */ 116 117 wheel = hid_locate(desc, dlen, 118 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), id, 119 hid_input, &ms->sc_loc_z, &flags); 120 if (wheel == 0) 121 twheel = hid_locate(desc, dlen, 122 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), id, 123 hid_input, &ms->sc_loc_z, &flags); 124 else 125 twheel = 0; 126 127 if (wheel || twheel) { 128 if (NOTMOUSE(flags)) { 129 DPRINTF(("\n%s: Wheel report 0x%04x not supported\n", 130 self->dv_xname, flags)); 131 ms->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ 132 } else { 133 ms->sc_flags |= HIDMS_Z; 134 /* Wheels need the Z axis reversed. */ 135 ms->sc_flags ^= HIDMS_REVZ; 136 } 137 /* 138 * We might have both a wheel and Z direction; in this case, 139 * report the Z direction on the W axis. 140 * 141 * Otherwise, check for a W direction as an AC Pan input used 142 * on some newer mice. 143 */ 144 if (hid_locate(desc, dlen, 145 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), id, 146 hid_input, &ms->sc_loc_w, &flags)) { 147 if (NOTMOUSE(flags)) { 148 DPRINTF(("\n%s: Z report 0x%04x not supported\n", 149 self->dv_xname, flags)); 150 /* Bad Z coord, ignore it */ 151 ms->sc_loc_w.size = 0; 152 } 153 else 154 ms->sc_flags |= HIDMS_W; 155 } else if (hid_locate(desc, dlen, 156 HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN), id, hid_input, 157 &ms->sc_loc_w, &flags)) { 158 ms->sc_flags |= HIDMS_W; 159 } 160 } else if (hid_locate(desc, dlen, 161 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), id, 162 hid_input, &ms->sc_loc_z, &flags)) { 163 if (NOTMOUSE(flags)) { 164 DPRINTF(("\n%s: Z report 0x%04x not supported\n", 165 self->dv_xname, flags)); 166 ms->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ 167 } else { 168 ms->sc_flags |= HIDMS_Z; 169 } 170 } 171 172 /* 173 * The Microsoft Wireless Intellimouse 2.0 reports its wheel 174 * using 0x0048 (I've called it HUG_TWHEEL) and seems to expect 175 * us to know that the byte after the wheel is the tilt axis. 176 * There are no other HID axis descriptors other than X, Y and 177 * TWHEEL, so we report TWHEEL on the W axis. 178 */ 179 if (twheel) { 180 ms->sc_loc_w = ms->sc_loc_z; 181 ms->sc_loc_w.pos = ms->sc_loc_w.pos + 8; 182 ms->sc_flags |= HIDMS_W | HIDMS_LEADINGBYTE; 183 /* Wheels need their axis reversed. */ 184 ms->sc_flags ^= HIDMS_REVW; 185 } 186 187 /* figure out the number of buttons */ 188 for (i = 1; i <= MAX_BUTTONS; i++) 189 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_BUTTON, i), id, 190 hid_input, &ms->sc_loc_btn[i - 1], NULL)) 191 break; 192 ms->sc_num_buttons = i - 1; 193 194 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 195 HUD_TIP_SWITCH), id, hid_input, 196 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 197 ms->sc_flags |= HIDMS_TIP; 198 ms->sc_num_buttons++; 199 } 200 201 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 202 HUD_ERASER), id, hid_input, 203 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 204 ms->sc_flags |= HIDMS_ERASER; 205 ms->sc_num_buttons++; 206 } 207 208 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 209 HUD_BARREL_SWITCH), id, hid_input, 210 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 211 ms->sc_flags |= HIDMS_BARREL; 212 ms->sc_num_buttons++; 213 } 214 215 /* 216 * The Microsoft Wireless Notebook Optical Mouse seems to be in worse 217 * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and 218 * all of its other button positions are all off. It also reports that 219 * it has two addional buttons and a tilt wheel. 220 */ 221 if (ms->sc_flags & HIDMS_MS_BAD_CLASS) { 222 /* HIDMS_LEADINGBYTE cleared on purpose */ 223 ms->sc_flags = HIDMS_Z | HIDMS_SPUR_BUT_UP; 224 ms->sc_num_buttons = 3; 225 /* XXX change sc_hdev isize to 5? */ 226 /* 1st byte of descriptor report contains garbage */ 227 ms->sc_loc_x.pos = 16; 228 ms->sc_loc_y.pos = 24; 229 ms->sc_loc_z.pos = 32; 230 ms->sc_loc_btn[0].pos = 8; 231 ms->sc_loc_btn[1].pos = 9; 232 ms->sc_loc_btn[2].pos = 10; 233 } 234 /* Parse descriptors to get touch panel bounds */ 235 d = hid_start_parse(desc, dlen, hid_input); 236 while (hid_get_item(d, &h)) { 237 if (h.kind != hid_input || 238 HID_GET_USAGE_PAGE(h.usage) != HUP_GENERIC_DESKTOP) 239 continue; 240 DPRINTF(("hidms: usage=0x%x range %d..%d\n", 241 h.usage, h.logical_minimum, h.logical_maximum)); 242 switch (HID_GET_USAGE(h.usage)) { 243 case HUG_X: 244 if (ms->sc_flags & HIDMS_ABSX) { 245 ms->sc_tsscale.minx = h.logical_minimum; 246 ms->sc_tsscale.maxx = h.logical_maximum; 247 } 248 break; 249 case HUG_Y: 250 if (ms->sc_flags & HIDMS_ABSY) { 251 ms->sc_tsscale.miny = h.logical_minimum; 252 ms->sc_tsscale.maxy = h.logical_maximum; 253 } 254 break; 255 } 256 } 257 hid_end_parse(d); 258 return 0; 259 } 260 261 void 262 hidms_attach(struct hidms *ms, const struct wsmouse_accessops *ops) 263 { 264 struct wsmousedev_attach_args a; 265 #ifdef HIDMS_DEBUG 266 int i; 267 #endif 268 269 printf(": %d button%s", 270 ms->sc_num_buttons, ms->sc_num_buttons <= 1 ? "" : "s"); 271 switch (ms->sc_flags & (HIDMS_Z | HIDMS_W)) { 272 case HIDMS_Z: 273 printf(", Z dir"); 274 break; 275 case HIDMS_W: 276 printf(", W dir"); 277 break; 278 case HIDMS_Z | HIDMS_W: 279 printf(", Z and W dir"); 280 break; 281 } 282 283 if (ms->sc_flags & HIDMS_TIP) 284 printf(", tip"); 285 if (ms->sc_flags & HIDMS_BARREL) 286 printf(", barrel"); 287 if (ms->sc_flags & HIDMS_ERASER) 288 printf(", eraser"); 289 290 printf("\n"); 291 292 #ifdef HIDMS_DEBUG 293 DPRINTF(("hidms_attach: ms=%p\n", ms)); 294 DPRINTF(("hidms_attach: X\t%d/%d\n", 295 ms->sc_loc_x.pos, ms->sc_loc_x.size)); 296 DPRINTF(("hidms_attach: Y\t%d/%d\n", 297 ms->sc_loc_y.pos, ms->sc_loc_y.size)); 298 if (ms->sc_flags & HIDMS_Z) 299 DPRINTF(("hidms_attach: Z\t%d/%d\n", 300 ms->sc_loc_z.pos, ms->sc_loc_z.size)); 301 if (ms->sc_flags & HIDMS_W) 302 DPRINTF(("hidms_attach: W\t%d/%d\n", 303 ms->sc_loc_w.pos, ms->sc_loc_w.size)); 304 for (i = 1; i <= ms->sc_num_buttons; i++) { 305 DPRINTF(("hidms_attach: B%d\t%d/%d\n", 306 i, ms->sc_loc_btn[i - 1].pos, ms->sc_loc_btn[i - 1].size)); 307 } 308 #endif 309 310 a.accessops = ops; 311 a.accesscookie = ms->sc_device; 312 ms->sc_wsmousedev = config_found(ms->sc_device, &a, wsmousedevprint); 313 } 314 315 int 316 hidms_detach(struct hidms *ms, int flags) 317 { 318 int rv = 0; 319 320 DPRINTF(("hidms_detach: ms=%p flags=%d\n", ms, flags)); 321 322 /* No need to do reference counting of hidms, wsmouse has all the goo */ 323 if (ms->sc_wsmousedev != NULL) 324 rv = config_detach(ms->sc_wsmousedev, flags); 325 326 return (rv); 327 } 328 329 void 330 hidms_input(struct hidms *ms, uint8_t *data, u_int len) 331 { 332 int dx, dy, dz, dw; 333 u_int32_t buttons = 0; 334 int i, s; 335 336 DPRINTFN(5,("hidms_input: len=%d\n", len)); 337 338 /* 339 * The Microsoft Wireless Intellimouse 2.0 sends one extra leading 340 * byte of data compared to most USB mice. This byte frequently 341 * switches from 0x01 (usual state) to 0x02. It may be used to 342 * report non-standard events (such as battery life). However, 343 * at the same time, it generates a left click event on the 344 * button byte, where there shouldn't be any. We simply discard 345 * the packet in this case. 346 * 347 * This problem affects the MS Wireless Notebook Optical Mouse, too. 348 * However, the leading byte for this mouse is normally 0x11, and 349 * the phantom mouse click occurs when it's 0x14. 350 */ 351 if (ms->sc_flags & HIDMS_LEADINGBYTE) { 352 if (*data++ == 0x02) 353 return; 354 /* len--; */ 355 } else if (ms->sc_flags & HIDMS_SPUR_BUT_UP) { 356 if (*data == 0x14 || *data == 0x15) 357 return; 358 } 359 360 dx = hid_get_data(data, len, &ms->sc_loc_x); 361 dy = -hid_get_data(data, len, &ms->sc_loc_y); 362 dz = hid_get_data(data, len, &ms->sc_loc_z); 363 dw = hid_get_data(data, len, &ms->sc_loc_w); 364 365 if (ms->sc_flags & HIDMS_ABSY) 366 dy = -dy; 367 if (ms->sc_flags & HIDMS_REVZ) 368 dz = -dz; 369 if (ms->sc_flags & HIDMS_REVW) 370 dw = -dw; 371 372 if (ms->sc_tsscale.swapxy && !ms->sc_rawmode) { 373 int tmp = dx; 374 dx = dy; 375 dy = tmp; 376 } 377 378 if (!ms->sc_rawmode && 379 (ms->sc_tsscale.maxx - ms->sc_tsscale.minx) != 0 && 380 (ms->sc_tsscale.maxy - ms->sc_tsscale.miny) != 0) { 381 /* Scale down to the screen resolution. */ 382 dx = ((dx - ms->sc_tsscale.minx) * ms->sc_tsscale.resx) / 383 (ms->sc_tsscale.maxx - ms->sc_tsscale.minx); 384 dy = ((dy - ms->sc_tsscale.miny) * ms->sc_tsscale.resy) / 385 (ms->sc_tsscale.maxy - ms->sc_tsscale.miny); 386 } 387 388 for (i = 0; i < ms->sc_num_buttons; i++) 389 if (hid_get_data(data, len, &ms->sc_loc_btn[i])) 390 buttons |= (1 << HIDMS_BUT(i)); 391 392 if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || 393 buttons != ms->sc_buttons) { 394 DPRINTFN(10, ("hidms_input: x:%d y:%d z:%d w:%d buttons:0x%x\n", 395 dx, dy, dz, dw, buttons)); 396 ms->sc_buttons = buttons; 397 if (ms->sc_wsmousedev != NULL) { 398 s = spltty(); 399 if (ms->sc_flags & HIDMS_ABSX) { 400 wsmouse_set(ms->sc_wsmousedev, 401 WSMOUSE_ABS_X, dx, 0); 402 dx = 0; 403 } 404 if (ms->sc_flags & HIDMS_ABSY) { 405 wsmouse_set(ms->sc_wsmousedev, 406 WSMOUSE_ABS_Y, dy, 0); 407 dy = 0; 408 } 409 WSMOUSE_INPUT(ms->sc_wsmousedev, 410 buttons, dx, dy, dz, dw); 411 splx(s); 412 } 413 } 414 } 415 416 int 417 hidms_enable(struct hidms *ms) 418 { 419 if (ms->sc_enabled) 420 return EBUSY; 421 422 ms->sc_enabled = 1; 423 ms->sc_buttons = 0; 424 return 0; 425 } 426 427 int 428 hidms_ioctl(struct hidms *ms, u_long cmd, caddr_t data, int flag, 429 struct proc *p) 430 { 431 struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; 432 433 switch (cmd) { 434 case WSMOUSEIO_SCALIBCOORDS: 435 if (!(wsmc->minx >= -32768 && wsmc->maxx >= -32768 && 436 wsmc->miny >= -32768 && wsmc->maxy >= -32768 && 437 wsmc->resx >= 0 && wsmc->resy >= 0 && 438 wsmc->minx < 32768 && wsmc->maxx < 32768 && 439 wsmc->miny < 32768 && wsmc->maxy < 32768 && 440 (wsmc->maxx - wsmc->minx) != 0 && 441 (wsmc->maxy - wsmc->miny) != 0 && 442 wsmc->resx < 32768 && wsmc->resy < 32768 && 443 wsmc->swapxy >= 0 && wsmc->swapxy <= 1 && 444 wsmc->samplelen >= 0 && wsmc->samplelen <= 1)) 445 return (EINVAL); 446 447 ms->sc_tsscale.minx = wsmc->minx; 448 ms->sc_tsscale.maxx = wsmc->maxx; 449 ms->sc_tsscale.miny = wsmc->miny; 450 ms->sc_tsscale.maxy = wsmc->maxy; 451 ms->sc_tsscale.swapxy = wsmc->swapxy; 452 ms->sc_tsscale.resx = wsmc->resx; 453 ms->sc_tsscale.resy = wsmc->resy; 454 ms->sc_rawmode = wsmc->samplelen; 455 return 0; 456 case WSMOUSEIO_GCALIBCOORDS: 457 wsmc->minx = ms->sc_tsscale.minx; 458 wsmc->maxx = ms->sc_tsscale.maxx; 459 wsmc->miny = ms->sc_tsscale.miny; 460 wsmc->maxy = ms->sc_tsscale.maxy; 461 wsmc->swapxy = ms->sc_tsscale.swapxy; 462 wsmc->resx = ms->sc_tsscale.resx; 463 wsmc->resy = ms->sc_tsscale.resy; 464 wsmc->samplelen = ms->sc_rawmode; 465 return 0; 466 case WSMOUSEIO_GTYPE: 467 if (ms->sc_flags & HIDMS_ABSX && ms->sc_flags & HIDMS_ABSY) { 468 *(u_int *)data = WSMOUSE_TYPE_TPANEL; 469 return 0; 470 } 471 /* FALLTHROUGH */ 472 default: 473 return -1; 474 } 475 } 476 477 void 478 hidms_disable(struct hidms *ms) 479 { 480 ms->sc_enabled = 0; 481 } 482