1 /* $OpenBSD: hidms.c,v 1.6 2021/01/10 16:32:48 thfr 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 /* 195 * The Kensington Slimblade reports some of its buttons as binary 196 * inputs in the first vendor usage page (0xff00). Add such inputs 197 * as buttons if the device has this quirk. 198 */ 199 if (ms->sc_flags & HIDMS_VENDOR_BUTTONS) { 200 const int b = ms->sc_num_buttons; 201 for (i = 1; b + i <= MAX_BUTTONS; i++) 202 if (!hid_locate(desc, dlen, 203 HID_USAGE2(HUP_MICROSOFT, i), 204 id, hid_input, &ms->sc_loc_btn[b + i - 1], NULL)) 205 break; 206 ms->sc_num_buttons += i; 207 } 208 209 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 210 HUD_TIP_SWITCH), id, hid_input, 211 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 212 ms->sc_flags |= HIDMS_TIP; 213 ms->sc_num_buttons++; 214 } 215 216 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 217 HUD_ERASER), id, hid_input, 218 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 219 ms->sc_flags |= HIDMS_ERASER; 220 ms->sc_num_buttons++; 221 } 222 223 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 224 HUD_BARREL_SWITCH), id, hid_input, 225 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 226 ms->sc_flags |= HIDMS_BARREL; 227 ms->sc_num_buttons++; 228 } 229 230 /* 231 * The Microsoft Wireless Notebook Optical Mouse seems to be in worse 232 * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and 233 * all of its other button positions are all off. It also reports that 234 * it has two addional buttons and a tilt wheel. 235 */ 236 if (ms->sc_flags & HIDMS_MS_BAD_CLASS) { 237 /* HIDMS_LEADINGBYTE cleared on purpose */ 238 ms->sc_flags = HIDMS_Z | HIDMS_SPUR_BUT_UP; 239 ms->sc_num_buttons = 3; 240 /* XXX change sc_hdev isize to 5? */ 241 /* 1st byte of descriptor report contains garbage */ 242 ms->sc_loc_x.pos = 16; 243 ms->sc_loc_y.pos = 24; 244 ms->sc_loc_z.pos = 32; 245 ms->sc_loc_btn[0].pos = 8; 246 ms->sc_loc_btn[1].pos = 9; 247 ms->sc_loc_btn[2].pos = 10; 248 } 249 /* Parse descriptors to get touch panel bounds */ 250 d = hid_start_parse(desc, dlen, hid_input); 251 while (hid_get_item(d, &h)) { 252 if (h.kind != hid_input || 253 HID_GET_USAGE_PAGE(h.usage) != HUP_GENERIC_DESKTOP) 254 continue; 255 DPRINTF(("hidms: usage=0x%x range %d..%d\n", 256 h.usage, h.logical_minimum, h.logical_maximum)); 257 switch (HID_GET_USAGE(h.usage)) { 258 case HUG_X: 259 if (ms->sc_flags & HIDMS_ABSX) { 260 ms->sc_tsscale.minx = h.logical_minimum; 261 ms->sc_tsscale.maxx = h.logical_maximum; 262 } 263 break; 264 case HUG_Y: 265 if (ms->sc_flags & HIDMS_ABSY) { 266 ms->sc_tsscale.miny = h.logical_minimum; 267 ms->sc_tsscale.maxy = h.logical_maximum; 268 } 269 break; 270 } 271 } 272 hid_end_parse(d); 273 return 0; 274 } 275 276 void 277 hidms_attach(struct hidms *ms, const struct wsmouse_accessops *ops) 278 { 279 struct wsmousedev_attach_args a; 280 #ifdef HIDMS_DEBUG 281 int i; 282 #endif 283 284 printf(": %d button%s", 285 ms->sc_num_buttons, ms->sc_num_buttons <= 1 ? "" : "s"); 286 switch (ms->sc_flags & (HIDMS_Z | HIDMS_W)) { 287 case HIDMS_Z: 288 printf(", Z dir"); 289 break; 290 case HIDMS_W: 291 printf(", W dir"); 292 break; 293 case HIDMS_Z | HIDMS_W: 294 printf(", Z and W dir"); 295 break; 296 } 297 298 if (ms->sc_flags & HIDMS_TIP) 299 printf(", tip"); 300 if (ms->sc_flags & HIDMS_BARREL) 301 printf(", barrel"); 302 if (ms->sc_flags & HIDMS_ERASER) 303 printf(", eraser"); 304 305 printf("\n"); 306 307 #ifdef HIDMS_DEBUG 308 DPRINTF(("hidms_attach: ms=%p\n", ms)); 309 DPRINTF(("hidms_attach: X\t%d/%d\n", 310 ms->sc_loc_x.pos, ms->sc_loc_x.size)); 311 DPRINTF(("hidms_attach: Y\t%d/%d\n", 312 ms->sc_loc_y.pos, ms->sc_loc_y.size)); 313 if (ms->sc_flags & HIDMS_Z) 314 DPRINTF(("hidms_attach: Z\t%d/%d\n", 315 ms->sc_loc_z.pos, ms->sc_loc_z.size)); 316 if (ms->sc_flags & HIDMS_W) 317 DPRINTF(("hidms_attach: W\t%d/%d\n", 318 ms->sc_loc_w.pos, ms->sc_loc_w.size)); 319 for (i = 1; i <= ms->sc_num_buttons; i++) { 320 DPRINTF(("hidms_attach: B%d\t%d/%d\n", 321 i, ms->sc_loc_btn[i - 1].pos, ms->sc_loc_btn[i - 1].size)); 322 } 323 #endif 324 325 a.accessops = ops; 326 a.accesscookie = ms->sc_device; 327 ms->sc_wsmousedev = config_found(ms->sc_device, &a, wsmousedevprint); 328 } 329 330 int 331 hidms_detach(struct hidms *ms, int flags) 332 { 333 int rv = 0; 334 335 DPRINTF(("hidms_detach: ms=%p flags=%d\n", ms, flags)); 336 337 /* No need to do reference counting of hidms, wsmouse has all the goo */ 338 if (ms->sc_wsmousedev != NULL) 339 rv = config_detach(ms->sc_wsmousedev, flags); 340 341 return (rv); 342 } 343 344 void 345 hidms_input(struct hidms *ms, uint8_t *data, u_int len) 346 { 347 int dx, dy, dz, dw; 348 u_int32_t buttons = 0; 349 int i, s; 350 351 DPRINTFN(5,("hidms_input: len=%d\n", len)); 352 353 /* 354 * The Microsoft Wireless Intellimouse 2.0 sends one extra leading 355 * byte of data compared to most USB mice. This byte frequently 356 * switches from 0x01 (usual state) to 0x02. It may be used to 357 * report non-standard events (such as battery life). However, 358 * at the same time, it generates a left click event on the 359 * button byte, where there shouldn't be any. We simply discard 360 * the packet in this case. 361 * 362 * This problem affects the MS Wireless Notebook Optical Mouse, too. 363 * However, the leading byte for this mouse is normally 0x11, and 364 * the phantom mouse click occurs when it's 0x14. 365 */ 366 if (ms->sc_flags & HIDMS_LEADINGBYTE) { 367 if (*data++ == 0x02) 368 return; 369 /* len--; */ 370 } else if (ms->sc_flags & HIDMS_SPUR_BUT_UP) { 371 if (*data == 0x14 || *data == 0x15) 372 return; 373 } 374 375 dx = hid_get_data(data, len, &ms->sc_loc_x); 376 dy = -hid_get_data(data, len, &ms->sc_loc_y); 377 dz = hid_get_data(data, len, &ms->sc_loc_z); 378 dw = hid_get_data(data, len, &ms->sc_loc_w); 379 380 if (ms->sc_flags & HIDMS_ABSY) 381 dy = -dy; 382 if (ms->sc_flags & HIDMS_REVZ) 383 dz = -dz; 384 if (ms->sc_flags & HIDMS_REVW) 385 dw = -dw; 386 387 if (ms->sc_tsscale.swapxy && !ms->sc_rawmode) { 388 int tmp = dx; 389 dx = dy; 390 dy = tmp; 391 } 392 393 if (!ms->sc_rawmode && 394 (ms->sc_tsscale.maxx - ms->sc_tsscale.minx) != 0 && 395 (ms->sc_tsscale.maxy - ms->sc_tsscale.miny) != 0) { 396 /* Scale down to the screen resolution. */ 397 dx = ((dx - ms->sc_tsscale.minx) * ms->sc_tsscale.resx) / 398 (ms->sc_tsscale.maxx - ms->sc_tsscale.minx); 399 dy = ((dy - ms->sc_tsscale.miny) * ms->sc_tsscale.resy) / 400 (ms->sc_tsscale.maxy - ms->sc_tsscale.miny); 401 } 402 403 for (i = 0; i < ms->sc_num_buttons; i++) 404 if (hid_get_data(data, len, &ms->sc_loc_btn[i])) 405 buttons |= (1 << HIDMS_BUT(i)); 406 407 if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || 408 buttons != ms->sc_buttons) { 409 DPRINTFN(10, ("hidms_input: x:%d y:%d z:%d w:%d buttons:0x%x\n", 410 dx, dy, dz, dw, buttons)); 411 ms->sc_buttons = buttons; 412 if (ms->sc_wsmousedev != NULL) { 413 s = spltty(); 414 if (ms->sc_flags & HIDMS_ABSX) { 415 wsmouse_set(ms->sc_wsmousedev, 416 WSMOUSE_ABS_X, dx, 0); 417 dx = 0; 418 } 419 if (ms->sc_flags & HIDMS_ABSY) { 420 wsmouse_set(ms->sc_wsmousedev, 421 WSMOUSE_ABS_Y, dy, 0); 422 dy = 0; 423 } 424 WSMOUSE_INPUT(ms->sc_wsmousedev, 425 buttons, dx, dy, dz, dw); 426 splx(s); 427 } 428 } 429 } 430 431 int 432 hidms_enable(struct hidms *ms) 433 { 434 if (ms->sc_enabled) 435 return EBUSY; 436 437 ms->sc_enabled = 1; 438 ms->sc_buttons = 0; 439 return 0; 440 } 441 442 int 443 hidms_ioctl(struct hidms *ms, u_long cmd, caddr_t data, int flag, 444 struct proc *p) 445 { 446 struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; 447 448 switch (cmd) { 449 case WSMOUSEIO_SCALIBCOORDS: 450 if (!(wsmc->minx >= -32768 && wsmc->maxx >= -32768 && 451 wsmc->miny >= -32768 && wsmc->maxy >= -32768 && 452 wsmc->resx >= 0 && wsmc->resy >= 0 && 453 wsmc->minx < 32768 && wsmc->maxx < 32768 && 454 wsmc->miny < 32768 && wsmc->maxy < 32768 && 455 (wsmc->maxx - wsmc->minx) != 0 && 456 (wsmc->maxy - wsmc->miny) != 0 && 457 wsmc->resx < 32768 && wsmc->resy < 32768 && 458 wsmc->swapxy >= 0 && wsmc->swapxy <= 1 && 459 wsmc->samplelen >= 0 && wsmc->samplelen <= 1)) 460 return (EINVAL); 461 462 ms->sc_tsscale.minx = wsmc->minx; 463 ms->sc_tsscale.maxx = wsmc->maxx; 464 ms->sc_tsscale.miny = wsmc->miny; 465 ms->sc_tsscale.maxy = wsmc->maxy; 466 ms->sc_tsscale.swapxy = wsmc->swapxy; 467 ms->sc_tsscale.resx = wsmc->resx; 468 ms->sc_tsscale.resy = wsmc->resy; 469 ms->sc_rawmode = wsmc->samplelen; 470 return 0; 471 case WSMOUSEIO_GCALIBCOORDS: 472 wsmc->minx = ms->sc_tsscale.minx; 473 wsmc->maxx = ms->sc_tsscale.maxx; 474 wsmc->miny = ms->sc_tsscale.miny; 475 wsmc->maxy = ms->sc_tsscale.maxy; 476 wsmc->swapxy = ms->sc_tsscale.swapxy; 477 wsmc->resx = ms->sc_tsscale.resx; 478 wsmc->resy = ms->sc_tsscale.resy; 479 wsmc->samplelen = ms->sc_rawmode; 480 return 0; 481 case WSMOUSEIO_GTYPE: 482 if (ms->sc_flags & HIDMS_ABSX && ms->sc_flags & HIDMS_ABSY) { 483 *(u_int *)data = WSMOUSE_TYPE_TPANEL; 484 return 0; 485 } 486 /* FALLTHROUGH */ 487 default: 488 return -1; 489 } 490 } 491 492 void 493 hidms_disable(struct hidms *ms) 494 { 495 ms->sc_enabled = 0; 496 } 497