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