1 /* $OpenBSD: ums.c,v 1.53 2024/05/26 20:06:27 mglocker 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: https://www.usb.org/sites/default/files/hid1_11.pdf
36 */
37
38 #include <sys/param.h>
39 #include <sys/systm.h>
40 #include <sys/device.h>
41
42 #include <dev/usb/usb.h>
43 #include <dev/usb/usbhid.h>
44
45 #include <dev/usb/usbdi.h>
46 #include <dev/usb/usbdi_util.h>
47 #include <dev/usb/usbdevs.h>
48 #include <dev/usb/usb_quirks.h>
49 #include <dev/usb/uhidev.h>
50
51 #include <dev/wscons/wsconsio.h>
52 #include <dev/wscons/wsmousevar.h>
53
54 #include <dev/hid/hidmsvar.h>
55
56 struct ums_softc {
57 struct uhidev sc_hdev;
58 struct hidms sc_ms;
59 uint32_t sc_quirks;
60 };
61
62 void ums_intr(struct uhidev *addr, void *ibuf, u_int len);
63
64 int ums_enable(void *);
65 void ums_disable(void *);
66 int ums_ioctl(void *, u_long, caddr_t, int, struct proc *);
67 void ums_fix_elecom_descriptor(struct ums_softc *, void *, int, int);
68
69 const struct wsmouse_accessops ums_accessops = {
70 ums_enable,
71 ums_ioctl,
72 ums_disable,
73 };
74
75 int ums_match(struct device *, void *, void *);
76 void ums_attach(struct device *, struct device *, void *);
77 int ums_detach(struct device *, int);
78
79 struct cfdriver ums_cd = {
80 NULL, "ums", DV_DULL
81 };
82
83 const struct cfattach ums_ca = {
84 sizeof(struct ums_softc), ums_match, ums_attach, ums_detach
85 };
86
87 int
ums_match(struct device * parent,void * match,void * aux)88 ums_match(struct device *parent, void *match, void *aux)
89 {
90 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
91 int size;
92 void *desc;
93
94 if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
95 return (UMATCH_NONE);
96
97 uhidev_get_report_desc(uha->parent, &desc, &size);
98
99 if (hid_is_collection(desc, size, uha->reportid,
100 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER)))
101 return (UMATCH_IFACECLASS);
102
103 if (hid_is_collection(desc, size, uha->reportid,
104 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)))
105 return (UMATCH_IFACECLASS);
106
107 if (hid_is_collection(desc, size, uha->reportid,
108 HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN)))
109 return (UMATCH_IFACECLASS);
110
111 if (hid_is_collection(desc, size, uha->reportid,
112 HID_USAGE2(HUP_DIGITIZERS, HUD_PEN)))
113 return (UMATCH_IFACECLASS);
114
115 return (UMATCH_NONE);
116 }
117
118 void
ums_attach(struct device * parent,struct device * self,void * aux)119 ums_attach(struct device *parent, struct device *self, void *aux)
120 {
121 struct ums_softc *sc = (struct ums_softc *)self;
122 struct hidms *ms = &sc->sc_ms;
123 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
124 struct usb_attach_arg *uaa = uha->uaa;
125 int size, repid;
126 void *desc;
127 u_int32_t qflags = 0;
128
129 sc->sc_hdev.sc_intr = ums_intr;
130 sc->sc_hdev.sc_parent = uha->parent;
131 sc->sc_hdev.sc_udev = uaa->device;
132 sc->sc_hdev.sc_report_id = uha->reportid;
133
134 usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0);
135
136 sc->sc_quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags;
137 uhidev_get_report_desc(uha->parent, &desc, &size);
138
139 if (uaa->vendor == USB_VENDOR_ELECOM)
140 ums_fix_elecom_descriptor(sc, desc, size, uaa->product);
141
142 repid = uha->reportid;
143 sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid);
144 sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid);
145 sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid);
146
147 if (sc->sc_quirks & UQ_MS_REVZ)
148 qflags |= HIDMS_REVZ;
149 if (sc->sc_quirks & UQ_SPUR_BUT_UP)
150 qflags |= HIDMS_SPUR_BUT_UP;
151 if (sc->sc_quirks & UQ_MS_BAD_CLASS)
152 qflags |= HIDMS_MS_BAD_CLASS;
153 if (sc->sc_quirks & UQ_MS_LEADING_BYTE)
154 qflags |= HIDMS_LEADINGBYTE;
155 if (sc->sc_quirks & UQ_MS_VENDOR_BUTTONS)
156 qflags |= HIDMS_VENDOR_BUTTONS;
157
158 if (hidms_setup(self, ms, qflags, uha->reportid, desc, size) != 0)
159 return;
160
161 /*
162 * The Microsoft Wireless Notebook Optical Mouse 3000 Model 1049 has
163 * five Report IDs: 19, 23, 24, 17, 18 (in the order they appear in
164 * report descriptor), it seems that report 17 contains the necessary
165 * mouse information (3-buttons, X, Y, wheel) so we specify it
166 * manually.
167 */
168 if (uaa->vendor == USB_VENDOR_MICROSOFT &&
169 uaa->product == USB_PRODUCT_MICROSOFT_WLNOTEBOOK3) {
170 ms->sc_flags = HIDMS_Z;
171 ms->sc_num_buttons = 3;
172 /* XXX change sc_hdev isize to 5? */
173 ms->sc_loc_x.pos = 8;
174 ms->sc_loc_y.pos = 16;
175 ms->sc_loc_z.pos = 24;
176 ms->sc_loc_btn[0].pos = 0;
177 ms->sc_loc_btn[1].pos = 1;
178 ms->sc_loc_btn[2].pos = 2;
179 }
180
181 if (sc->sc_quirks & UQ_ALWAYS_OPEN) {
182 /* open uhidev and keep it open */
183 ums_enable(sc);
184 /* but mark the hidms not in use */
185 ums_disable(sc);
186 }
187
188 hidms_attach(ms, &ums_accessops);
189 }
190
191 int
ums_detach(struct device * self,int flags)192 ums_detach(struct device *self, int flags)
193 {
194 struct ums_softc *sc = (struct ums_softc *)self;
195 struct hidms *ms = &sc->sc_ms;
196
197 return hidms_detach(ms, flags);
198 }
199
200 void
ums_intr(struct uhidev * addr,void * buf,u_int len)201 ums_intr(struct uhidev *addr, void *buf, u_int len)
202 {
203 struct ums_softc *sc = (struct ums_softc *)addr;
204 struct hidms *ms = &sc->sc_ms;
205
206 if (ms->sc_enabled != 0)
207 hidms_input(ms, (uint8_t *)buf, len);
208 }
209
210 int
ums_enable(void * v)211 ums_enable(void *v)
212 {
213 struct ums_softc *sc = v;
214 struct hidms *ms = &sc->sc_ms;
215 int rv;
216
217 if (usbd_is_dying(sc->sc_hdev.sc_udev))
218 return EIO;
219
220 if ((rv = hidms_enable(ms)) != 0)
221 return rv;
222
223 if ((sc->sc_quirks & UQ_ALWAYS_OPEN) &&
224 (sc->sc_hdev.sc_state & UHIDEV_OPEN))
225 rv = 0;
226 else
227 rv = uhidev_open(&sc->sc_hdev);
228
229 return rv;
230 }
231
232 void
ums_disable(void * v)233 ums_disable(void *v)
234 {
235 struct ums_softc *sc = v;
236 struct hidms *ms = &sc->sc_ms;
237
238 hidms_disable(ms);
239
240 if (sc->sc_quirks & UQ_ALWAYS_OPEN)
241 return;
242
243 uhidev_close(&sc->sc_hdev);
244 }
245
246 int
ums_ioctl(void * v,u_long cmd,caddr_t data,int flag,struct proc * p)247 ums_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p)
248 {
249 struct ums_softc *sc = v;
250 struct hidms *ms = &sc->sc_ms;
251 int rc;
252
253 rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p);
254 if (rc != -1)
255 return rc;
256 rc = hidms_ioctl(ms, cmd, data, flag, p);
257 if (rc != -1)
258 return rc;
259
260 switch (cmd) {
261 case WSMOUSEIO_GTYPE:
262 *(u_int *)data = WSMOUSE_TYPE_USB;
263 return 0;
264 default:
265 return -1;
266 }
267 }
268
269 /*
270 * Some ELECOM devices present flawed report descriptors. Instead of a 5-bit
271 * field and a 3-bit padding as defined by the descriptor they actually use
272 * 6 or 8 Bits to report button states, see
273 * https://flameeyes.blog/2017/04/24/elecom-deft-and-the-broken-descriptor
274 * This function adapts the Report Count value for the button page, its Usage
275 * Maximum and the report size for the padding bits (at offset 31).
276 */
277 void
ums_fix_elecom_descriptor(struct ums_softc * sc,void * desc,int size,int product)278 ums_fix_elecom_descriptor(struct ums_softc *sc, void *desc, int size,
279 int product)
280 {
281 static uByte match[] = { /* a descriptor fragment, offset: 12 */
282 0x95, 0x05, 0x75, 0x01, /* Report Count (5), Report Size (1) */
283 0x05, 0x09, 0x19, 0x01, /* Usage Page (Button), Usage Minimum (1) */
284 0x29, 0x05, /* Usage Maximum (5) */
285 };
286 uByte *udesc = desc;
287 int nbuttons;
288
289 switch (product) {
290 case USB_PRODUCT_ELECOM_MXT3URBK: /* EX-G Trackballs */
291 case USB_PRODUCT_ELECOM_MXT3DRBK:
292 case USB_PRODUCT_ELECOM_MXT4DRBK:
293 nbuttons = 6;
294 break;
295 case USB_PRODUCT_ELECOM_MDT1URBK: /* DEFT Trackballs */
296 case USB_PRODUCT_ELECOM_MDT1DRBK:
297 case USB_PRODUCT_ELECOM_MHT1URBK: /* HUGE Trackballs */
298 case USB_PRODUCT_ELECOM_MHT1DRBK:
299 nbuttons = 8;
300 break;
301 default:
302 return;
303 }
304
305 if (udesc == NULL || size < 32
306 || memcmp(&udesc[12], match, sizeof(match))
307 || udesc[30] != 0x75 || udesc[31] != 3)
308 return;
309
310 printf("%s: fixing Elecom report descriptor (buttons: %d)\n",
311 sc->sc_hdev.sc_dev.dv_xname, nbuttons);
312 udesc[13] = nbuttons;
313 udesc[21] = nbuttons;
314 udesc[31] = 8 - nbuttons;
315 }
316