1 /* $NetBSD: ums.c,v 1.104 2023/07/20 20:00:34 mrg Exp $ */ 2 3 /* 4 * Copyright (c) 1998, 2017 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Lennart Augustsson (lennart@augustsson.net) at 9 * Carlstedt Research & Technology. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 /* 34 * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf 35 */ 36 37 #include <sys/cdefs.h> 38 __KERNEL_RCSID(0, "$NetBSD: ums.c,v 1.104 2023/07/20 20:00:34 mrg Exp $"); 39 40 #ifdef _KERNEL_OPT 41 #include "opt_usb.h" 42 #endif 43 44 #include <sys/param.h> 45 #include <sys/systm.h> 46 #include <sys/kernel.h> 47 #include <sys/device.h> 48 #include <sys/ioctl.h> 49 #include <sys/file.h> 50 #include <sys/select.h> 51 #include <sys/sysctl.h> 52 #include <sys/proc.h> 53 #include <sys/vnode.h> 54 #include <sys/poll.h> 55 56 #include <dev/usb/usb.h> 57 #include <dev/usb/usbhid.h> 58 59 #include <dev/usb/usbdi.h> 60 #include <dev/usb/usbdi_util.h> 61 #include <dev/usb/usbdevs.h> 62 #include <dev/usb/usb_quirks.h> 63 #include <dev/usb/uhidev.h> 64 #include <dev/usb/usbhist.h> 65 #include <dev/hid/hid.h> 66 #include <dev/hid/hidms.h> 67 68 #ifdef USB_DEBUG 69 #ifndef UMS_DEBUG 70 #define umsdebug 0 71 #else 72 73 #ifndef UMS_DEBUG_DEFAULT 74 #define UMS_DEBUG_DEFAULT 0 75 #endif 76 77 static int umsdebug = UMS_DEBUG_DEFAULT; 78 79 SYSCTL_SETUP(sysctl_hw_ums_setup, "sysctl hw.ums setup") 80 { 81 int err; 82 const struct sysctlnode *rnode; 83 const struct sysctlnode *cnode; 84 85 err = sysctl_createv(clog, 0, NULL, &rnode, 86 CTLFLAG_PERMANENT, CTLTYPE_NODE, "ums", 87 SYSCTL_DESCR("ums global controls"), 88 NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL); 89 90 if (err) 91 goto fail; 92 93 /* control debugging printfs */ 94 err = sysctl_createv(clog, 0, &rnode, &cnode, 95 CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, 96 "debug", SYSCTL_DESCR("Enable debugging output"), 97 NULL, 0, &umsdebug, sizeof(umsdebug), CTL_CREATE, CTL_EOL); 98 if (err) 99 goto fail; 100 101 return; 102 fail: 103 aprint_error("%s: sysctl_createv failed (err = %d)\n", __func__, err); 104 } 105 106 #endif /* UMS_DEBUG */ 107 #endif /* USB_DEBUG */ 108 109 #define DPRINTF(FMT,A,B,C,D) USBHIST_LOGN(umsdebug,1,FMT,A,B,C,D) 110 #define DPRINTFN(N,FMT,A,B,C,D) USBHIST_LOGN(umsdebug,N,FMT,A,B,C,D) 111 #define UMSHIST_FUNC() USBHIST_FUNC() 112 #define UMSHIST_CALLED(name) USBHIST_CALLED(umsdebug) 113 #define UMSHIST_CALLARGS(FMT,A,B,C,D) \ 114 USBHIST_CALLARGS(umsdebug,FMT,A,B,C,D) 115 #define UMSHIST_CALLARGSN(N,FMT,A,B,C,D) \ 116 USBHIST_CALLARGSN(umsdebug,N,FMT,A,B,C,D) 117 118 #define UMSUNIT(s) (minor(s)) 119 120 struct ums_softc { 121 struct uhidev *sc_hdev; 122 struct usbd_device *sc_udev; 123 struct hidms sc_ms; 124 125 bool sc_alwayson; 126 127 int sc_enabled; 128 char sc_dying; 129 }; 130 131 Static void ums_intr(void *, void *, u_int); 132 133 Static int ums_enable(void *); 134 Static void ums_disable(void *); 135 Static int ums_ioctl(void *, u_long, void *, int, struct lwp *); 136 137 static const struct wsmouse_accessops ums_accessops = { 138 ums_enable, 139 ums_ioctl, 140 ums_disable, 141 }; 142 143 static int ums_match(device_t, cfdata_t, void *); 144 static void ums_attach(device_t, device_t, void *); 145 static void ums_childdet(device_t, device_t); 146 static int ums_detach(device_t, int); 147 static int ums_activate(device_t, enum devact); 148 149 CFATTACH_DECL2_NEW(ums, sizeof(struct ums_softc), ums_match, ums_attach, 150 ums_detach, ums_activate, NULL, ums_childdet); 151 152 static int 153 ums_match(device_t parent, cfdata_t match, void *aux) 154 { 155 struct uhidev_attach_arg *uha = aux; 156 int size; 157 void *desc; 158 159 /* 160 * Some (older) Griffin PowerMate knobs may masquerade as a 161 * mouse, avoid treating them as such, they have only one axis. 162 */ 163 if (uha->uiaa->uiaa_vendor == USB_VENDOR_GRIFFIN && 164 uha->uiaa->uiaa_product == USB_PRODUCT_GRIFFIN_POWERMATE) 165 return UMATCH_NONE; 166 167 uhidev_get_report_desc(uha->parent, &desc, &size); 168 if (!hid_is_collection(desc, size, uha->reportid, 169 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) && 170 !hid_is_collection(desc, size, uha->reportid, 171 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER)) && 172 !hid_is_collection(desc, size, uha->reportid, 173 HID_USAGE2(HUP_DIGITIZERS, 0x0002))) 174 return UMATCH_NONE; 175 176 return UMATCH_IFACECLASS; 177 } 178 179 static void 180 ums_attach(device_t parent, device_t self, void *aux) 181 { 182 struct ums_softc *sc = device_private(self); 183 struct uhidev_attach_arg *uha = aux; 184 struct hid_data *d; 185 struct hid_item item; 186 int size, error; 187 void *desc; 188 uint32_t quirks; 189 190 aprint_naive("\n"); 191 192 sc->sc_hdev = uha->parent; 193 sc->sc_udev = uha->uiaa->uiaa_device; 194 195 quirks = usbd_get_quirks(sc->sc_udev)->uq_flags; 196 if (quirks & UQ_MS_REVZ) 197 sc->sc_ms.flags |= HIDMS_REVZ; 198 if (quirks & UQ_SPUR_BUT_UP) 199 sc->sc_ms.flags |= HIDMS_SPUR_BUT_UP; 200 if (quirks & UQ_ALWAYS_ON) 201 sc->sc_alwayson = true; 202 203 if (!pmf_device_register(self, NULL, NULL)) 204 aprint_error_dev(self, "couldn't establish power handler\n"); 205 206 uhidev_get_report_desc(uha->parent, &desc, &size); 207 208 if (!hidms_setup(self, &sc->sc_ms, uha->reportid, desc, size)) 209 return; 210 211 if (uha->uiaa->uiaa_vendor == USB_VENDOR_MICROSOFT) { 212 int fixpos; 213 int woffset = 8; 214 /* 215 * The Microsoft Wireless Laser Mouse 6000 v2.0 and the 216 * Microsoft Comfort Mouse 2.0 report a bad position for 217 * the wheel and wheel tilt controls -- should be in bytes 218 * 3 & 4 of the report. Fix this if necessary. 219 */ 220 switch (uha->uiaa->uiaa_product) { 221 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR10: 222 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR20: 223 case USB_PRODUCT_MICROSOFT_NATURAL_6000: 224 fixpos = 24; 225 break; 226 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR80: 227 fixpos = 40; 228 woffset = sc->sc_ms.hidms_loc_z.size; 229 break; 230 case USB_PRODUCT_MICROSOFT_CM6000: 231 fixpos = 40; 232 break; 233 default: 234 fixpos = 0; 235 break; 236 } 237 if (fixpos) { 238 if ((sc->sc_ms.flags & HIDMS_Z) && 239 sc->sc_ms.hidms_loc_z.pos == 0) 240 sc->sc_ms.hidms_loc_z.pos = fixpos; 241 if ((sc->sc_ms.flags & HIDMS_W) && 242 sc->sc_ms.hidms_loc_w.pos == 0) 243 sc->sc_ms.hidms_loc_w.pos = 244 sc->sc_ms.hidms_loc_z.pos + woffset; 245 } 246 } 247 248 tpcalib_init(&sc->sc_ms.sc_tpcalib); 249 250 /* calibrate the pointer if it reports absolute events */ 251 if (sc->sc_ms.flags & HIDMS_ABS) { 252 memset(&sc->sc_ms.sc_calibcoords, 0, sizeof(sc->sc_ms.sc_calibcoords)); 253 sc->sc_ms.sc_calibcoords.maxx = 0; 254 sc->sc_ms.sc_calibcoords.maxy = 0; 255 sc->sc_ms.sc_calibcoords.samplelen = WSMOUSE_CALIBCOORDS_RESET; 256 d = hid_start_parse(desc, size, hid_input); 257 if (d != NULL) { 258 while (hid_get_item(d, &item)) { 259 if (item.kind != hid_input 260 || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP 261 || item.report_ID != uha->reportid) 262 continue; 263 if (HID_GET_USAGE(item.usage) == HUG_X) { 264 sc->sc_ms.sc_calibcoords.minx = item.logical_minimum; 265 sc->sc_ms.sc_calibcoords.maxx = item.logical_maximum; 266 } 267 if (HID_GET_USAGE(item.usage) == HUG_Y) { 268 sc->sc_ms.sc_calibcoords.miny = item.logical_minimum; 269 sc->sc_ms.sc_calibcoords.maxy = item.logical_maximum; 270 } 271 } 272 hid_end_parse(d); 273 } 274 tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, WSMOUSEIO_SCALIBCOORDS, 275 (void *)&sc->sc_ms.sc_calibcoords, 0, 0); 276 } 277 278 hidms_attach(self, &sc->sc_ms, &ums_accessops); 279 280 if (sc->sc_alwayson) { 281 error = uhidev_open(sc->sc_hdev, &ums_intr, sc); 282 if (error != 0) { 283 aprint_error_dev(self, 284 "WARNING: couldn't open always-on device\n"); 285 sc->sc_alwayson = false; 286 } 287 } 288 } 289 290 static int 291 ums_activate(device_t self, enum devact act) 292 { 293 struct ums_softc *sc = device_private(self); 294 295 switch (act) { 296 case DVACT_DEACTIVATE: 297 sc->sc_dying = 1; 298 return 0; 299 default: 300 return EOPNOTSUPP; 301 } 302 } 303 304 static void 305 ums_childdet(device_t self, device_t child) 306 { 307 struct ums_softc *sc = device_private(self); 308 309 KASSERT(sc->sc_ms.hidms_wsmousedev == child); 310 sc->sc_ms.hidms_wsmousedev = NULL; 311 } 312 313 static int 314 ums_detach(device_t self, int flags) 315 { 316 struct ums_softc *sc = device_private(self); 317 int rv = 0; 318 319 UMSHIST_FUNC(); 320 UMSHIST_CALLARGS("ums_detach: sc=%qd flags=%qd\n", 321 (uintptr_t)sc, flags, 0, 0); 322 323 if (sc->sc_alwayson) 324 uhidev_close(sc->sc_hdev); 325 326 /* No need to do reference counting of ums, wsmouse has all the goo. */ 327 if (sc->sc_ms.hidms_wsmousedev != NULL) 328 rv = config_detach(sc->sc_ms.hidms_wsmousedev, flags); 329 330 pmf_device_deregister(self); 331 332 return rv; 333 } 334 335 Static void 336 ums_intr(void *cookie, void *ibuf, u_int len) 337 { 338 struct ums_softc *sc = cookie; 339 340 if (sc->sc_enabled) 341 hidms_intr(&sc->sc_ms, ibuf, len); 342 } 343 344 Static int 345 ums_enable(void *v) 346 { 347 struct ums_softc *sc = v; 348 int error = 0; 349 350 UMSHIST_FUNC(); UMSHIST_CALLARGS("sc=%jx\n", (uintptr_t)sc, 0, 0, 0); 351 352 if (sc->sc_dying) 353 return EIO; 354 355 if (sc->sc_enabled) 356 return EBUSY; 357 358 sc->sc_enabled = 1; 359 sc->sc_ms.hidms_buttons = 0; 360 361 if (!sc->sc_alwayson) { 362 error = uhidev_open(sc->sc_hdev, &ums_intr, sc); 363 if (error) 364 sc->sc_enabled = 0; 365 } 366 367 return error; 368 } 369 370 Static void 371 ums_disable(void *v) 372 { 373 struct ums_softc *sc = v; 374 375 UMSHIST_FUNC(); UMSHIST_CALLARGS("sc=%jx\n", (uintptr_t)sc, 0, 0, 0); 376 377 #ifdef DIAGNOSTIC 378 if (!sc->sc_enabled) { 379 printf("ums_disable: not enabled\n"); 380 return; 381 } 382 #endif 383 384 if (sc->sc_enabled) { 385 sc->sc_enabled = 0; 386 if (!sc->sc_alwayson) 387 uhidev_close(sc->sc_hdev); 388 } 389 } 390 391 Static int 392 ums_ioctl(void *v, u_long cmd, void *data, int flag, 393 struct lwp *l) 394 395 { 396 struct ums_softc *sc = v; 397 int error; 398 399 if (sc->sc_ms.flags & HIDMS_ABS) { 400 error = tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, cmd, data, 401 flag, l); 402 if (error != EPASSTHROUGH) 403 return error; 404 } 405 406 switch (cmd) { 407 case WSMOUSEIO_GTYPE: 408 if (sc->sc_ms.flags & HIDMS_ABS) 409 *(u_int *)data = WSMOUSE_TYPE_TPANEL; 410 else 411 *(u_int *)data = WSMOUSE_TYPE_USB; 412 return 0; 413 } 414 415 return EPASSTHROUGH; 416 } 417