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