1 2 /* $NetBSD: ums.c,v 1.108 2025/03/25 10:37:39 hans Exp $ */ 3 4 /* 5 * Copyright (c) 1998, 2017 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 (at) 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/cdefs.h> 39 __KERNEL_RCSID(0, "$NetBSD: ums.c,v 1.108 2025/03/25 10:37:39 hans Exp $"); 40 41 #ifdef _KERNEL_OPT 42 #include "opt_usb.h" 43 #endif 44 45 #include <sys/param.h> 46 #include <sys/systm.h> 47 #include <sys/kernel.h> 48 #include <sys/device.h> 49 #include <sys/ioctl.h> 50 #include <sys/file.h> 51 #include <sys/select.h> 52 #include <sys/sysctl.h> 53 #include <sys/proc.h> 54 #include <sys/vnode.h> 55 #include <sys/poll.h> 56 57 #include <dev/usb/usb.h> 58 #include <dev/usb/usbhid.h> 59 60 #include <dev/usb/usbdi.h> 61 #include <dev/usb/usbdi_util.h> 62 #include <dev/usb/usbdevs.h> 63 #include <dev/usb/usb_quirks.h> 64 #include <dev/usb/uhidev.h> 65 #include <dev/usb/usbhist.h> 66 #include <dev/hid/hid.h> 67 #include <dev/hid/hidms.h> 68 69 #ifdef USB_DEBUG 70 #ifndef UMS_DEBUG 71 #define umsdebug 0 72 #else 73 74 #ifndef UMS_DEBUG_DEFAULT 75 #define UMS_DEBUG_DEFAULT 0 76 #endif 77 78 static int umsdebug = UMS_DEBUG_DEFAULT; 79 80 SYSCTL_SETUP(sysctl_hw_ums_setup, "sysctl hw.ums setup") 81 { 82 int err; 83 const struct sysctlnode *rnode; 84 const struct sysctlnode *cnode; 85 86 err = sysctl_createv(clog, 0, NULL, &rnode, 87 CTLFLAG_PERMANENT, CTLTYPE_NODE, "ums", 88 SYSCTL_DESCR("ums global controls"), 89 NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL); 90 91 if (err) 92 goto fail; 93 94 /* control debugging printfs */ 95 err = sysctl_createv(clog, 0, &rnode, &cnode, 96 CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT, 97 "debug", SYSCTL_DESCR("Enable debugging output"), 98 NULL, 0, &umsdebug, sizeof(umsdebug), CTL_CREATE, CTL_EOL); 99 if (err) 100 goto fail; 101 102 return; 103 fail: 104 aprint_error("%s: sysctl_createv failed (err = %d)\n", __func__, err); 105 } 106 107 #endif /* UMS_DEBUG */ 108 #endif /* USB_DEBUG */ 109 110 #define DPRINTF(FMT,A,B,C,D) USBHIST_LOGN(umsdebug,1,FMT,A,B,C,D) 111 #define DPRINTFN(N,FMT,A,B,C,D) USBHIST_LOGN(umsdebug,N,FMT,A,B,C,D) 112 #define UMSHIST_FUNC() USBHIST_FUNC() 113 #define UMSHIST_CALLED(name) USBHIST_CALLED(umsdebug) 114 #define UMSHIST_CALLARGS(FMT,A,B,C,D) \ 115 USBHIST_CALLARGS(umsdebug,FMT,A,B,C,D) 116 #define UMSHIST_CALLARGSN(N,FMT,A,B,C,D) \ 117 USBHIST_CALLARGSN(umsdebug,N,FMT,A,B,C,D) 118 119 #define UMSUNIT(s) (minor(s)) 120 121 struct ums_softc { 122 struct uhidev *sc_hdev; 123 struct usbd_device *sc_udev; 124 struct hidms sc_ms; 125 126 bool sc_alwayson; 127 128 int sc_enabled; 129 char sc_dying; 130 }; 131 132 Static void ums_intr(void *, void *, u_int); 133 134 Static int ums_enable(void *); 135 Static void ums_disable(void *); 136 Static int ums_ioctl(void *, u_long, void *, int, struct lwp *); 137 138 static const struct wsmouse_accessops ums_accessops = { 139 ums_enable, 140 ums_ioctl, 141 ums_disable, 142 }; 143 144 static int ums_match(device_t, cfdata_t, void *); 145 static void ums_attach(device_t, device_t, void *); 146 static void ums_childdet(device_t, device_t); 147 static int ums_detach(device_t, int); 148 static int ums_activate(device_t, enum devact); 149 150 CFATTACH_DECL2_NEW(ums, sizeof(struct ums_softc), ums_match, ums_attach, 151 ums_detach, ums_activate, NULL, ums_childdet); 152 153 static int 154 ums_match(device_t parent, cfdata_t match, void *aux) 155 { 156 struct uhidev_attach_arg *uha = aux; 157 int size; 158 void *desc; 159 160 /* 161 * Some (older) Griffin PowerMate knobs may masquerade as a 162 * mouse, avoid treating them as such, they have only one axis. 163 */ 164 if (uha->uiaa->uiaa_vendor == USB_VENDOR_GRIFFIN && 165 uha->uiaa->uiaa_product == USB_PRODUCT_GRIFFIN_POWERMATE) 166 return UMATCH_NONE; 167 168 uhidev_get_report_desc(uha->parent, &desc, &size); 169 if (!hid_is_collection(desc, size, uha->reportid, 170 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) && 171 !hid_is_collection(desc, size, uha->reportid, 172 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER)) && 173 !hid_is_collection(desc, size, uha->reportid, 174 HID_USAGE2(HUP_DIGITIZERS, HUD_PEN))) 175 return UMATCH_NONE; 176 177 return UMATCH_IFACECLASS; 178 } 179 180 static void 181 ums_attach(device_t parent, device_t self, void *aux) 182 { 183 struct ums_softc *sc = device_private(self); 184 struct uhidev_attach_arg *uha = aux; 185 struct hid_data *d; 186 struct hid_item item; 187 int size, error; 188 void *desc; 189 uint32_t quirks; 190 191 aprint_naive("\n"); 192 193 sc->sc_hdev = uha->parent; 194 sc->sc_udev = uha->uiaa->uiaa_device; 195 196 quirks = usbd_get_quirks(sc->sc_udev)->uq_flags; 197 if (quirks & UQ_MS_REVZ) 198 sc->sc_ms.flags |= HIDMS_REVZ; 199 if (quirks & UQ_SPUR_BUT_UP) 200 sc->sc_ms.flags |= HIDMS_SPUR_BUT_UP; 201 if (quirks & UQ_ALWAYS_ON) 202 sc->sc_alwayson = true; 203 204 if (!pmf_device_register(self, NULL, NULL)) 205 aprint_error_dev(self, "couldn't establish power handler\n"); 206 207 uhidev_get_report_desc(uha->parent, &desc, &size); 208 209 if (!hidms_setup(self, &sc->sc_ms, uha->reportid, desc, size)) 210 return; 211 212 if (uha->uiaa->uiaa_vendor == USB_VENDOR_MICROSOFT) { 213 int fixpos; 214 int woffset = 8; 215 /* 216 * The Microsoft Wireless Laser Mouse 6000 v2.0 and the 217 * Microsoft Comfort Mouse 2.0 report a bad position for 218 * the wheel and wheel tilt controls -- should be in bytes 219 * 3 & 4 of the report. Fix this if necessary. 220 */ 221 switch (uha->uiaa->uiaa_product) { 222 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR10: 223 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR20: 224 case USB_PRODUCT_MICROSOFT_NATURAL_6000: 225 fixpos = 24; 226 break; 227 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR80: 228 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR90: 229 fixpos = 40; 230 woffset = sc->sc_ms.hidms_loc_z.size; 231 break; 232 case USB_PRODUCT_MICROSOFT_CM6000: 233 fixpos = 40; 234 break; 235 default: 236 fixpos = 0; 237 break; 238 } 239 if (fixpos) { 240 if ((sc->sc_ms.flags & HIDMS_Z) && 241 sc->sc_ms.hidms_loc_z.pos == 0) 242 sc->sc_ms.hidms_loc_z.pos = fixpos; 243 if ((sc->sc_ms.flags & HIDMS_W) && 244 sc->sc_ms.hidms_loc_w.pos == 0) 245 sc->sc_ms.hidms_loc_w.pos = 246 sc->sc_ms.hidms_loc_z.pos + woffset; 247 } 248 } 249 250 tpcalib_init(&sc->sc_ms.sc_tpcalib); 251 252 /* calibrate the pointer if it reports absolute events */ 253 if (sc->sc_ms.flags & HIDMS_ABS) { 254 memset(&sc->sc_ms.sc_calibcoords, 0, sizeof(sc->sc_ms.sc_calibcoords)); 255 sc->sc_ms.sc_calibcoords.maxx = 0; 256 sc->sc_ms.sc_calibcoords.maxy = 0; 257 sc->sc_ms.sc_calibcoords.samplelen = WSMOUSE_CALIBCOORDS_RESET; 258 d = hid_start_parse(desc, size, hid_input); 259 if (d != NULL) { 260 while (hid_get_item(d, &item)) { 261 if (item.kind != hid_input 262 || HID_GET_USAGE_PAGE(item.usage) != HUP_GENERIC_DESKTOP 263 || item.report_ID != uha->reportid) 264 continue; 265 if (HID_GET_USAGE(item.usage) == HUG_X) { 266 sc->sc_ms.sc_calibcoords.minx = item.logical_minimum; 267 sc->sc_ms.sc_calibcoords.maxx = item.logical_maximum; 268 } 269 if (HID_GET_USAGE(item.usage) == HUG_Y) { 270 sc->sc_ms.sc_calibcoords.miny = item.logical_minimum; 271 sc->sc_ms.sc_calibcoords.maxy = item.logical_maximum; 272 } 273 } 274 hid_end_parse(d); 275 } 276 tpcalib_ioctl(&sc->sc_ms.sc_tpcalib, WSMOUSEIO_SCALIBCOORDS, 277 (void *)&sc->sc_ms.sc_calibcoords, 0, 0); 278 } 279 280 hidms_attach(self, &sc->sc_ms, &ums_accessops); 281 282 if (sc->sc_alwayson) { 283 error = uhidev_open(sc->sc_hdev, &ums_intr, sc); 284 if (error != 0) { 285 aprint_error_dev(self, 286 "WARNING: couldn't open always-on device\n"); 287 sc->sc_alwayson = false; 288 } 289 } 290 } 291 292 static int 293 ums_activate(device_t self, enum devact act) 294 { 295 struct ums_softc *sc = device_private(self); 296 297 switch (act) { 298 case DVACT_DEACTIVATE: 299 sc->sc_dying = 1; 300 return 0; 301 default: 302 return EOPNOTSUPP; 303 } 304 } 305 306 static void 307 ums_childdet(device_t self, device_t child) 308 { 309 struct ums_softc *sc = device_private(self); 310 311 KASSERT(sc->sc_ms.hidms_wsmousedev == child); 312 sc->sc_ms.hidms_wsmousedev = NULL; 313 } 314 315 static int 316 ums_detach(device_t self, int flags) 317 { 318 struct ums_softc *sc = device_private(self); 319 int rv = 0; 320 321 UMSHIST_FUNC(); 322 UMSHIST_CALLARGS("ums_detach: sc=%qd flags=%qd\n", 323 (uintptr_t)sc, flags, 0, 0); 324 325 if (sc->sc_alwayson) 326 uhidev_close(sc->sc_hdev); 327 328 /* No need to do reference counting of ums, wsmouse has all the goo. */ 329 if (sc->sc_ms.hidms_wsmousedev != NULL) 330 rv = config_detach(sc->sc_ms.hidms_wsmousedev, flags); 331 332 pmf_device_deregister(self); 333 334 return rv; 335 } 336 337 Static void 338 ums_intr(void *cookie, void *ibuf, u_int len) 339 { 340 struct ums_softc *sc = cookie; 341 342 if (sc->sc_enabled) 343 hidms_intr(&sc->sc_ms, ibuf, len); 344 } 345 346 Static int 347 ums_enable(void *v) 348 { 349 struct ums_softc *sc = v; 350 int error = 0; 351 352 UMSHIST_FUNC(); UMSHIST_CALLARGS("sc=%jx\n", (uintptr_t)sc, 0, 0, 0); 353 354 if (sc->sc_dying) 355 return EIO; 356 357 if (sc->sc_enabled) 358 return EBUSY; 359 360 sc->sc_enabled = 1; 361 sc->sc_ms.hidms_buttons = 0; 362 363 if (!sc->sc_alwayson) { 364 error = uhidev_open(sc->sc_hdev, &ums_intr, sc); 365 if (error) 366 sc->sc_enabled = 0; 367 } 368 369 return error; 370 } 371 372 Static void 373 ums_disable(void *v) 374 { 375 struct ums_softc *sc = v; 376 377 UMSHIST_FUNC(); UMSHIST_CALLARGS("sc=%jx\n", (uintptr_t)sc, 0, 0, 0); 378 379 if (!sc->sc_enabled) { 380 #ifdef DIAGNOSTIC 381 printf("ums_disable: not enabled\n"); 382 #endif 383 return; 384 } 385 386 sc->sc_enabled = 0; 387 if (!sc->sc_alwayson) 388 uhidev_close(sc->sc_hdev); 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