1 /* $OpenBSD: hidms.c,v 1.7 2021/06/10 13:34:37 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 ms->sc_loc_x.size = 0; 81 82 switch(flags & MOUSE_FLAGS_MASK) { 83 case 0: 84 ms->sc_flags |= HIDMS_ABSX; 85 break; 86 case HIO_RELATIVE: 87 break; 88 default: 89 printf("\n%s: X report 0x%04x not supported\n", 90 self->dv_xname, flags); 91 return ENXIO; 92 } 93 94 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), id, 95 hid_input, &ms->sc_loc_y, &flags)) 96 ms->sc_loc_y.size = 0; 97 98 switch(flags & MOUSE_FLAGS_MASK) { 99 case 0: 100 ms->sc_flags |= HIDMS_ABSY; 101 break; 102 case HIO_RELATIVE: 103 break; 104 default: 105 printf("\n%s: Y report 0x%04x not supported\n", 106 self->dv_xname, flags); 107 return ENXIO; 108 } 109 110 /* 111 * Try to guess the Z activator: check WHEEL, TWHEEL, and Z, 112 * in that order. 113 */ 114 115 wheel = hid_locate(desc, dlen, 116 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), id, 117 hid_input, &ms->sc_loc_z, &flags); 118 if (wheel == 0) 119 twheel = hid_locate(desc, dlen, 120 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), id, 121 hid_input, &ms->sc_loc_z, &flags); 122 else 123 twheel = 0; 124 125 if (wheel || twheel) { 126 if (NOTMOUSE(flags)) { 127 DPRINTF(("\n%s: Wheel report 0x%04x not supported\n", 128 self->dv_xname, flags)); 129 ms->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ 130 } else { 131 ms->sc_flags |= HIDMS_Z; 132 /* Wheels need the Z axis reversed. */ 133 ms->sc_flags ^= HIDMS_REVZ; 134 } 135 /* 136 * We might have both a wheel and Z direction; in this case, 137 * report the Z direction on the W axis. 138 * 139 * Otherwise, check for a W direction as an AC Pan input used 140 * on some newer mice. 141 */ 142 if (hid_locate(desc, dlen, 143 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), id, 144 hid_input, &ms->sc_loc_w, &flags)) { 145 if (NOTMOUSE(flags)) { 146 DPRINTF(("\n%s: Z report 0x%04x not supported\n", 147 self->dv_xname, flags)); 148 /* Bad Z coord, ignore it */ 149 ms->sc_loc_w.size = 0; 150 } 151 else 152 ms->sc_flags |= HIDMS_W; 153 } else if (hid_locate(desc, dlen, 154 HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN), id, hid_input, 155 &ms->sc_loc_w, &flags)) { 156 ms->sc_flags |= HIDMS_W; 157 } 158 } else if (hid_locate(desc, dlen, 159 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), id, 160 hid_input, &ms->sc_loc_z, &flags)) { 161 if (NOTMOUSE(flags)) { 162 DPRINTF(("\n%s: Z report 0x%04x not supported\n", 163 self->dv_xname, flags)); 164 ms->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ 165 } else { 166 ms->sc_flags |= HIDMS_Z; 167 } 168 } 169 170 /* 171 * The Microsoft Wireless Intellimouse 2.0 reports its wheel 172 * using 0x0048 (I've called it HUG_TWHEEL) and seems to expect 173 * us to know that the byte after the wheel is the tilt axis. 174 * There are no other HID axis descriptors other than X, Y and 175 * TWHEEL, so we report TWHEEL on the W axis. 176 */ 177 if (twheel) { 178 ms->sc_loc_w = ms->sc_loc_z; 179 ms->sc_loc_w.pos = ms->sc_loc_w.pos + 8; 180 ms->sc_flags |= HIDMS_W | HIDMS_LEADINGBYTE; 181 /* Wheels need their axis reversed. */ 182 ms->sc_flags ^= HIDMS_REVW; 183 } 184 185 /* figure out the number of buttons */ 186 for (i = 1; i <= MAX_BUTTONS; i++) 187 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_BUTTON, i), id, 188 hid_input, &ms->sc_loc_btn[i - 1], NULL)) 189 break; 190 ms->sc_num_buttons = i - 1; 191 192 /* 193 * The Kensington Slimblade reports some of its buttons as binary 194 * inputs in the first vendor usage page (0xff00). Add such inputs 195 * as buttons if the device has this quirk. 196 */ 197 if (ms->sc_flags & HIDMS_VENDOR_BUTTONS) { 198 const int b = ms->sc_num_buttons; 199 for (i = 1; b + i <= MAX_BUTTONS; i++) 200 if (!hid_locate(desc, dlen, 201 HID_USAGE2(HUP_MICROSOFT, i), 202 id, hid_input, &ms->sc_loc_btn[b + i - 1], NULL)) 203 break; 204 ms->sc_num_buttons += i; 205 } 206 207 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 208 HUD_TIP_SWITCH), id, hid_input, 209 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 210 ms->sc_flags |= HIDMS_TIP; 211 ms->sc_num_buttons++; 212 } 213 214 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 215 HUD_ERASER), id, hid_input, 216 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 217 ms->sc_flags |= HIDMS_ERASER; 218 ms->sc_num_buttons++; 219 } 220 221 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, 222 HUD_BARREL_SWITCH), id, hid_input, 223 &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){ 224 ms->sc_flags |= HIDMS_BARREL; 225 ms->sc_num_buttons++; 226 } 227 228 /* 229 * The Microsoft Wireless Notebook Optical Mouse seems to be in worse 230 * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and 231 * all of its other button positions are all off. It also reports that 232 * it has two addional buttons and a tilt wheel. 233 */ 234 if (ms->sc_flags & HIDMS_MS_BAD_CLASS) { 235 /* HIDMS_LEADINGBYTE cleared on purpose */ 236 ms->sc_flags = HIDMS_Z | HIDMS_SPUR_BUT_UP; 237 ms->sc_num_buttons = 3; 238 /* XXX change sc_hdev isize to 5? */ 239 /* 1st byte of descriptor report contains garbage */ 240 ms->sc_loc_x.pos = 16; 241 ms->sc_loc_y.pos = 24; 242 ms->sc_loc_z.pos = 32; 243 ms->sc_loc_btn[0].pos = 8; 244 ms->sc_loc_btn[1].pos = 9; 245 ms->sc_loc_btn[2].pos = 10; 246 } 247 /* Parse descriptors to get touch panel bounds */ 248 d = hid_start_parse(desc, dlen, hid_input); 249 while (hid_get_item(d, &h)) { 250 if (h.kind != hid_input || 251 HID_GET_USAGE_PAGE(h.usage) != HUP_GENERIC_DESKTOP) 252 continue; 253 DPRINTF(("hidms: usage=0x%x range %d..%d\n", 254 h.usage, h.logical_minimum, h.logical_maximum)); 255 switch (HID_GET_USAGE(h.usage)) { 256 case HUG_X: 257 if (ms->sc_flags & HIDMS_ABSX) { 258 ms->sc_tsscale.minx = h.logical_minimum; 259 ms->sc_tsscale.maxx = h.logical_maximum; 260 } 261 break; 262 case HUG_Y: 263 if (ms->sc_flags & HIDMS_ABSY) { 264 ms->sc_tsscale.miny = h.logical_minimum; 265 ms->sc_tsscale.maxy = h.logical_maximum; 266 } 267 break; 268 } 269 } 270 hid_end_parse(d); 271 return 0; 272 } 273 274 void 275 hidms_attach(struct hidms *ms, const struct wsmouse_accessops *ops) 276 { 277 struct wsmousedev_attach_args a; 278 #ifdef HIDMS_DEBUG 279 int i; 280 #endif 281 282 printf(": %d button%s", 283 ms->sc_num_buttons, ms->sc_num_buttons == 1 ? "" : "s"); 284 switch (ms->sc_flags & (HIDMS_Z | HIDMS_W)) { 285 case HIDMS_Z: 286 printf(", Z dir"); 287 break; 288 case HIDMS_W: 289 printf(", W dir"); 290 break; 291 case HIDMS_Z | HIDMS_W: 292 printf(", Z and W dir"); 293 break; 294 } 295 296 if (ms->sc_flags & HIDMS_TIP) 297 printf(", tip"); 298 if (ms->sc_flags & HIDMS_BARREL) 299 printf(", barrel"); 300 if (ms->sc_flags & HIDMS_ERASER) 301 printf(", eraser"); 302 303 printf("\n"); 304 305 #ifdef HIDMS_DEBUG 306 DPRINTF(("hidms_attach: ms=%p\n", ms)); 307 DPRINTF(("hidms_attach: X\t%d/%d\n", 308 ms->sc_loc_x.pos, ms->sc_loc_x.size)); 309 DPRINTF(("hidms_attach: Y\t%d/%d\n", 310 ms->sc_loc_y.pos, ms->sc_loc_y.size)); 311 if (ms->sc_flags & HIDMS_Z) 312 DPRINTF(("hidms_attach: Z\t%d/%d\n", 313 ms->sc_loc_z.pos, ms->sc_loc_z.size)); 314 if (ms->sc_flags & HIDMS_W) 315 DPRINTF(("hidms_attach: W\t%d/%d\n", 316 ms->sc_loc_w.pos, ms->sc_loc_w.size)); 317 for (i = 1; i <= ms->sc_num_buttons; i++) { 318 DPRINTF(("hidms_attach: B%d\t%d/%d\n", 319 i, ms->sc_loc_btn[i - 1].pos, ms->sc_loc_btn[i - 1].size)); 320 } 321 #endif 322 323 a.accessops = ops; 324 a.accesscookie = ms->sc_device; 325 ms->sc_wsmousedev = config_found(ms->sc_device, &a, wsmousedevprint); 326 } 327 328 int 329 hidms_detach(struct hidms *ms, int flags) 330 { 331 int rv = 0; 332 333 DPRINTF(("hidms_detach: ms=%p flags=%d\n", ms, flags)); 334 335 /* No need to do reference counting of hidms, wsmouse has all the goo */ 336 if (ms->sc_wsmousedev != NULL) 337 rv = config_detach(ms->sc_wsmousedev, flags); 338 339 return (rv); 340 } 341 342 void 343 hidms_input(struct hidms *ms, uint8_t *data, u_int len) 344 { 345 int dx, dy, dz, dw; 346 u_int32_t buttons = 0; 347 int i, s; 348 349 DPRINTFN(5,("hidms_input: len=%d\n", len)); 350 351 /* 352 * The Microsoft Wireless Intellimouse 2.0 sends one extra leading 353 * byte of data compared to most USB mice. This byte frequently 354 * switches from 0x01 (usual state) to 0x02. It may be used to 355 * report non-standard events (such as battery life). However, 356 * at the same time, it generates a left click event on the 357 * button byte, where there shouldn't be any. We simply discard 358 * the packet in this case. 359 * 360 * This problem affects the MS Wireless Notebook Optical Mouse, too. 361 * However, the leading byte for this mouse is normally 0x11, and 362 * the phantom mouse click occurs when it's 0x14. 363 */ 364 if (ms->sc_flags & HIDMS_LEADINGBYTE) { 365 if (*data++ == 0x02) 366 return; 367 /* len--; */ 368 } else if (ms->sc_flags & HIDMS_SPUR_BUT_UP) { 369 if (*data == 0x14 || *data == 0x15) 370 return; 371 } 372 373 dx = hid_get_data(data, len, &ms->sc_loc_x); 374 dy = -hid_get_data(data, len, &ms->sc_loc_y); 375 dz = hid_get_data(data, len, &ms->sc_loc_z); 376 dw = hid_get_data(data, len, &ms->sc_loc_w); 377 378 if (ms->sc_flags & HIDMS_ABSY) 379 dy = -dy; 380 if (ms->sc_flags & HIDMS_REVZ) 381 dz = -dz; 382 if (ms->sc_flags & HIDMS_REVW) 383 dw = -dw; 384 385 if (ms->sc_tsscale.swapxy && !ms->sc_rawmode) { 386 int tmp = dx; 387 dx = dy; 388 dy = tmp; 389 } 390 391 if (!ms->sc_rawmode && 392 (ms->sc_tsscale.maxx - ms->sc_tsscale.minx) != 0 && 393 (ms->sc_tsscale.maxy - ms->sc_tsscale.miny) != 0) { 394 /* Scale down to the screen resolution. */ 395 dx = ((dx - ms->sc_tsscale.minx) * ms->sc_tsscale.resx) / 396 (ms->sc_tsscale.maxx - ms->sc_tsscale.minx); 397 dy = ((dy - ms->sc_tsscale.miny) * ms->sc_tsscale.resy) / 398 (ms->sc_tsscale.maxy - ms->sc_tsscale.miny); 399 } 400 401 for (i = 0; i < ms->sc_num_buttons; i++) 402 if (hid_get_data(data, len, &ms->sc_loc_btn[i])) 403 buttons |= (1 << HIDMS_BUT(i)); 404 405 if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || 406 buttons != ms->sc_buttons) { 407 DPRINTFN(10, ("hidms_input: x:%d y:%d z:%d w:%d buttons:0x%x\n", 408 dx, dy, dz, dw, buttons)); 409 ms->sc_buttons = buttons; 410 if (ms->sc_wsmousedev != NULL) { 411 s = spltty(); 412 if (ms->sc_flags & HIDMS_ABSX) { 413 wsmouse_set(ms->sc_wsmousedev, 414 WSMOUSE_ABS_X, dx, 0); 415 dx = 0; 416 } 417 if (ms->sc_flags & HIDMS_ABSY) { 418 wsmouse_set(ms->sc_wsmousedev, 419 WSMOUSE_ABS_Y, dy, 0); 420 dy = 0; 421 } 422 WSMOUSE_INPUT(ms->sc_wsmousedev, 423 buttons, dx, dy, dz, dw); 424 splx(s); 425 } 426 } 427 } 428 429 int 430 hidms_enable(struct hidms *ms) 431 { 432 if (ms->sc_enabled) 433 return EBUSY; 434 435 ms->sc_enabled = 1; 436 ms->sc_buttons = 0; 437 return 0; 438 } 439 440 int 441 hidms_ioctl(struct hidms *ms, u_long cmd, caddr_t data, int flag, 442 struct proc *p) 443 { 444 struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; 445 446 switch (cmd) { 447 case WSMOUSEIO_SCALIBCOORDS: 448 if (!(wsmc->minx >= -32768 && wsmc->maxx >= -32768 && 449 wsmc->miny >= -32768 && wsmc->maxy >= -32768 && 450 wsmc->resx >= 0 && wsmc->resy >= 0 && 451 wsmc->minx < 32768 && wsmc->maxx < 32768 && 452 wsmc->miny < 32768 && wsmc->maxy < 32768 && 453 (wsmc->maxx - wsmc->minx) != 0 && 454 (wsmc->maxy - wsmc->miny) != 0 && 455 wsmc->resx < 32768 && wsmc->resy < 32768 && 456 wsmc->swapxy >= 0 && wsmc->swapxy <= 1 && 457 wsmc->samplelen >= 0 && wsmc->samplelen <= 1)) 458 return (EINVAL); 459 460 ms->sc_tsscale.minx = wsmc->minx; 461 ms->sc_tsscale.maxx = wsmc->maxx; 462 ms->sc_tsscale.miny = wsmc->miny; 463 ms->sc_tsscale.maxy = wsmc->maxy; 464 ms->sc_tsscale.swapxy = wsmc->swapxy; 465 ms->sc_tsscale.resx = wsmc->resx; 466 ms->sc_tsscale.resy = wsmc->resy; 467 ms->sc_rawmode = wsmc->samplelen; 468 return 0; 469 case WSMOUSEIO_GCALIBCOORDS: 470 wsmc->minx = ms->sc_tsscale.minx; 471 wsmc->maxx = ms->sc_tsscale.maxx; 472 wsmc->miny = ms->sc_tsscale.miny; 473 wsmc->maxy = ms->sc_tsscale.maxy; 474 wsmc->swapxy = ms->sc_tsscale.swapxy; 475 wsmc->resx = ms->sc_tsscale.resx; 476 wsmc->resy = ms->sc_tsscale.resy; 477 wsmc->samplelen = ms->sc_rawmode; 478 return 0; 479 case WSMOUSEIO_GTYPE: 480 if (ms->sc_flags & HIDMS_ABSX && ms->sc_flags & HIDMS_ABSY) { 481 *(u_int *)data = WSMOUSE_TYPE_TPANEL; 482 return 0; 483 } 484 /* FALLTHROUGH */ 485 default: 486 return -1; 487 } 488 } 489 490 void 491 hidms_disable(struct hidms *ms) 492 { 493 ms->sc_enabled = 0; 494 } 495