Add support for generic MS Windows 7/8/10-compatible USB HID touchscreens

found in many laptops.

Reviewed by:		hps, gonzo, bcr (manpages)
Approved by:		gonzo (mentor)
Differential Revision:	https://reviews.freebsd.org/D12017
This commit is contained in:
Vladimir Kondratyev 2017-08-19 17:00:10 +00:00
parent d5d62a7a89
commit 76136d200d
11 changed files with 928 additions and 2 deletions

View File

@ -562,6 +562,7 @@ MAN= aac.4 \
wlan_tkip.4 \
wlan_wep.4 \
wlan_xauth.4 \
wmt.4 \
${_wpi.4} \
wsp.4 \
xe.4 \

View File

@ -16,7 +16,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd January 17, 2017
.Dd August 19, 2017
.Dt USB_QUIRK 4
.Os
.Sh NAME
@ -98,6 +98,8 @@ select configuration index 4 by default
select configuration index 0 by default
.It UQ_ASSUME_CM_OVER_DATA
assume cm over data feature
.It UQ_WMT_IGNORE
device should be ignored by wmt driver
.El
.Sh USB Mass Storage quirks:
.Bl -tag -width Ds

84
share/man/man4/wmt.4 Normal file
View File

@ -0,0 +1,84 @@
.\" Copyright (c) 2014-2017 Vladimir Kondratyev <wulf@FreeBSD.org>
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd August 19, 2017
.Dt WMT 4
.Os
.Sh NAME
.Nm wmt
.Nd MS Windows 7/8/10 - compatible USB HID multi-touch device driver
.Sh SYNOPSIS
To compile this driver into the kernel, place the following lines into
your kernel configuration file:
.Bd -ragged -offset indent
.Cd "device wmt"
.Cd "device usb"
.Cd "device evdev"
.Ed
.Pp
Alternatively, to load the driver as a
module at boot time, place the following line in
.Xr loader.conf 5 :
.Bd -literal -offset indent
wmt_load="YES"
.Ed
.Sh DESCRIPTION
The
.Nm
driver provides support for the MS Windows 7/8/10 - compatible USB HID
multi-touch devices found in many laptops.
.Pp
To get multi-touch device working in
.Xr X 7 ,
install
.Pa ports/x11-drivers/xf86-input-evdev .
.Sh FILES
.Nm
creates a pseudo-device file,
.Pa /dev/input/eventX
which presents the multi-touch device as an input event device.
.Sh SEE ALSO
.Xr usb 4 ,
.Xr loader.conf 5 ,
.Xr xorg.conf 5 Pq Pa ports/x11/xorg ,
.Xr evdev 4 Pq Pa ports/x11-drivers/xf86-input-evdev .
.Sh AUTHORS
.An -nosplit
The
.Nm
driver was written by
.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
.Sh BUGS
.Nm
works only with touchscreens now.
Neither pens nor touchpads are supported.
.Pp
.Nm
cannot act like
.Xr sysmouse 4 ,
as
.Xr sysmouse 4
does not support absolute motion events.

View File

@ -3220,6 +3220,7 @@ dev/usb/input/uep.c optional uep
dev/usb/input/uhid.c optional uhid
dev/usb/input/ukbd.c optional ukbd
dev/usb/input/ums.c optional ums
dev/usb/input/wmt.c optional wmt
dev/usb/input/wsp.c optional wsp
#
# USB quirks

729
sys/dev/usb/input/wmt.c Normal file
View File

@ -0,0 +1,729 @@
/*-
* Copyright (c) 2014-2017 Vladimir Kondratyev <wulf@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* MS Windows 7/8/10 compatible USB HID Multi-touch Device driver.
* https://msdn.microsoft.com/en-us/library/windows/hardware/jj151569(v=vs.85).aspx
* https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
*/
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/stddef.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include "usbdevs.h"
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbhid.h>
#include <dev/usb/quirk/usb_quirk.h>
#include <dev/evdev/evdev.h>
#include <dev/evdev/input.h>
#define USB_DEBUG_VAR wmt_debug
#include <dev/usb/usb_debug.h>
#ifdef USB_DEBUG
static int wmt_debug = 0;
static SYSCTL_NODE(_hw_usb, OID_AUTO, wmt, CTLFLAG_RW, 0,
"USB MSWindows 7/8/10 compatible Multi-touch Device");
SYSCTL_INT(_hw_usb_wmt, OID_AUTO, debug, CTLFLAG_RWTUN,
&wmt_debug, 1, "Debug level");
#endif
#define WMT_BSIZE 1024 /* bytes, buffer size */
enum {
WMT_INTR_DT,
WMT_N_TRANSFER,
};
enum {
WMT_TIP_SWITCH,
#define WMT_SLOT WMT_TIP_SWITCH
WMT_WIDTH,
#define WMT_MAJOR WMT_WIDTH
WMT_HEIGHT,
#define WMT_MINOR WMT_HEIGHT
WMT_ORIENTATION,
WMT_X,
WMT_Y,
WMT_CONTACTID,
WMT_PRESSURE,
WMT_IN_RANGE,
WMT_CONFIDENCE,
WMT_TOOL_X,
WMT_TOOL_Y,
WMT_N_USAGES,
};
#define WMT_NO_CODE (ABS_MAX + 10)
#define WMT_NO_USAGE -1
struct wmt_hid_map_item {
char name[5];
int32_t usage; /* HID usage */
uint32_t code; /* Evdev event code */
bool required; /* Required for MT Digitizers */
};
static const struct wmt_hid_map_item wmt_hid_map[WMT_N_USAGES] = {
[WMT_TIP_SWITCH] = { /* WMT_SLOT */
.name = "TIP",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH),
.code = ABS_MT_SLOT,
.required = true,
},
[WMT_WIDTH] = { /* WMT_MAJOR */
.name = "WDTH",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH),
.code = ABS_MT_TOUCH_MAJOR,
.required = false,
},
[WMT_HEIGHT] = { /* WMT_MINOR */
.name = "HGHT",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT),
.code = ABS_MT_TOUCH_MINOR,
.required = false,
},
[WMT_ORIENTATION] = {
.name = "ORIE",
.usage = WMT_NO_USAGE,
.code = ABS_MT_ORIENTATION,
.required = false,
},
[WMT_X] = {
.name = "X",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
.code = ABS_MT_POSITION_X,
.required = true,
},
[WMT_Y] = {
.name = "Y",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
.code = ABS_MT_POSITION_Y,
.required = true,
},
[WMT_CONTACTID] = {
.name = "C_ID",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID),
.code = ABS_MT_TRACKING_ID,
.required = true,
},
[WMT_PRESSURE] = {
.name = "PRES",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_PRESSURE),
.code = ABS_MT_PRESSURE,
.required = false,
},
[WMT_IN_RANGE] = {
.name = "RANG",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE),
.code = ABS_MT_DISTANCE,
.required = false,
},
[WMT_CONFIDENCE] = {
.name = "CONF",
.usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE),
.code = WMT_NO_CODE,
.required = false,
},
[WMT_TOOL_X] = { /* Shares HID usage with WMT_X */
.name = "TL_X",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
.code = ABS_MT_TOOL_X,
.required = false,
},
[WMT_TOOL_Y] = { /* Shares HID usage with WMT_Y */
.name = "TL_Y",
.usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
.code = ABS_MT_TOOL_Y,
.required = false,
},
};
struct wmt_absinfo {
int32_t min;
int32_t max;
int32_t res;
};
struct wmt_softc
{
device_t dev;
struct mtx mtx;
struct wmt_absinfo ai[WMT_N_USAGES];
struct hid_location locs[MAX_MT_SLOTS][WMT_N_USAGES];
struct hid_location nconts_loc;
struct usb_xfer *xfer[WMT_N_TRANSFER];
struct evdev_dev *evdev;
uint32_t slot_data[WMT_N_USAGES];
uint32_t caps;
uint32_t isize;
uint32_t nconts_max;
uint8_t report_id;
uint8_t buf[WMT_BSIZE] __aligned(4);
};
#define USAGE_SUPPORTED(caps, usage) ((caps) & (1 << (usage)))
#define WMT_FOREACH_USAGE(caps, usage) \
for ((usage) = 0; (usage) < WMT_N_USAGES; ++(usage)) \
if (USAGE_SUPPORTED((caps), (usage)))
static bool wmt_hid_parse(struct wmt_softc *, const void *, uint16_t);
static usb_callback_t wmt_intr_callback;
static device_probe_t wmt_probe;
static device_attach_t wmt_attach;
static device_detach_t wmt_detach;
static evdev_open_t wmt_ev_open;
static evdev_close_t wmt_ev_close;
static const struct evdev_methods wmt_evdev_methods = {
.ev_open = &wmt_ev_open,
.ev_close = &wmt_ev_close,
};
static const struct usb_config wmt_config[WMT_N_TRANSFER] = {
[WMT_INTR_DT] = {
.type = UE_INTERRUPT,
.endpoint = UE_ADDR_ANY,
.direction = UE_DIR_IN,
.flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
.bufsize = WMT_BSIZE,
.callback = &wmt_intr_callback,
},
};
static int
wmt_probe(device_t dev)
{
struct usb_attach_arg *uaa = device_get_ivars(dev);
void *d_ptr;
uint16_t d_len;
int err;
if (uaa->usb_mode != USB_MODE_HOST)
return (ENXIO);
if (uaa->info.bInterfaceClass != UICLASS_HID)
return (ENXIO);
if (usb_test_quirk(uaa, UQ_WMT_IGNORE))
return (ENXIO);
err = usbd_req_get_hid_desc(uaa->device, NULL,
&d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
if (err)
return (ENXIO);
if (wmt_hid_parse(NULL, d_ptr, d_len))
err = BUS_PROBE_DEFAULT;
else
err = ENXIO;
free(d_ptr, M_TEMP);
return (err);
}
static int
wmt_attach(device_t dev)
{
struct usb_attach_arg *uaa = device_get_ivars(dev);
struct wmt_softc *sc = device_get_softc(dev);
void *d_ptr;
uint16_t d_len;
size_t i;
int err;
device_set_usb_desc(dev);
sc->dev = dev;
/* Get HID descriptor */
err = usbd_req_get_hid_desc(uaa->device, NULL,
&d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
if (err) {
DPRINTF("usbd_req_get_hid_desc error=%s\n", usbd_errstr(err));
return (ENXIO);
}
mtx_init(&sc->mtx, "wmt lock", NULL, MTX_DEF);
/* Get HID report length */
sc->isize = hid_report_size(d_ptr, d_len, hid_input, NULL);
if (sc->isize <= 0 || sc->isize > WMT_BSIZE) {
DPRINTF("Input size invalid or too large: %d\n", sc->isize);
goto detach;
}
err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
sc->xfer, wmt_config, WMT_N_TRANSFER, sc, &sc->mtx);
if (err) {
DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err));
goto detach;
}
if (!wmt_hid_parse(sc, d_ptr, d_len))
goto detach;
sc->evdev = evdev_alloc();
evdev_set_name(sc->evdev, device_get_desc(dev));
evdev_set_phys(sc->evdev, device_get_nameunit(dev));
evdev_set_id(sc->evdev, BUS_USB, uaa->info.idVendor,
uaa->info.idProduct, 0);
evdev_set_serial(sc->evdev, usb_get_serial(uaa->device));
evdev_set_methods(sc->evdev, sc, &wmt_evdev_methods);
evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT);
evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT);
evdev_support_event(sc->evdev, EV_SYN);
evdev_support_event(sc->evdev, EV_ABS);
WMT_FOREACH_USAGE(sc->caps, i) {
if (wmt_hid_map[i].code != WMT_NO_CODE)
evdev_support_abs(sc->evdev, wmt_hid_map[i].code, 0,
sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res);
}
err = evdev_register_mtx(sc->evdev, &sc->mtx);
if (err)
goto detach;
return (0);
detach:
free(d_ptr, M_TEMP);
wmt_detach(dev);
return (ENXIO);
}
static int
wmt_detach(device_t dev)
{
struct wmt_softc *sc = device_get_softc(dev);
evdev_free(sc->evdev);
usbd_transfer_unsetup(sc->xfer, WMT_N_TRANSFER);
mtx_destroy(&sc->mtx);
return (0);
}
static void
wmt_process_report(struct wmt_softc *sc, uint8_t *buf, int len)
{
size_t usage;
uint32_t *slot_data = sc->slot_data;
uint32_t cont;
uint32_t nconts;
uint32_t width;
uint32_t height;
int32_t slot;
nconts = hid_get_data_unsigned(buf, len, &sc->nconts_loc);
#ifdef USB_DEBUG
DPRINTFN(6, "nconts = %u ", (unsigned)nconts);
if (wmt_debug >= 6) {
WMT_FOREACH_USAGE(sc->caps, usage) {
if (wmt_hid_map[usage].usage != WMT_NO_USAGE)
printf(" %-4s", wmt_hid_map[usage].name);
}
printf("\n");
}
#endif
if (nconts > sc->nconts_max) {
DPRINTF("Contact count overflow %u\n", (unsigned)nconts);
nconts = sc->nconts_max;
}
/* Use protocol Type B for reporting events */
for (cont = 0; cont < nconts; cont++) {
bzero(slot_data, sizeof(sc->slot_data));
WMT_FOREACH_USAGE(sc->caps, usage) {
if (sc->locs[cont][usage].size > 0)
slot_data[usage] = hid_get_data_unsigned(
buf, len, &sc->locs[cont][usage]);
}
slot = evdev_get_mt_slot_by_tracking_id(sc->evdev,
slot_data[WMT_CONTACTID]);
#ifdef USB_DEBUG
DPRINTFN(6, "cont%01x: data = ", cont);
if (wmt_debug >= 6) {
WMT_FOREACH_USAGE(sc->caps, usage) {
if (wmt_hid_map[usage].usage != WMT_NO_USAGE)
printf("%04x ", slot_data[usage]);
}
printf("slot = %d\n", (int)slot);
}
#endif
if (slot == -1) {
DPRINTF("Slot overflow for contact_id %u\n",
(unsigned)slot_data[WMT_CONTACTID]);
continue;
}
if (slot_data[WMT_TIP_SWITCH] != 0 &&
!(USAGE_SUPPORTED(sc->caps, WMT_CONFIDENCE) &&
slot_data[WMT_CONFIDENCE] == 0)) {
/* This finger is in proximity of the sensor */
slot_data[WMT_SLOT] = slot;
slot_data[WMT_IN_RANGE] = !slot_data[WMT_IN_RANGE];
/* Divided by two to match visual scale of touch */
width = slot_data[WMT_WIDTH] >> 1;
height = slot_data[WMT_HEIGHT] >> 1;
slot_data[WMT_ORIENTATION] = width > height;
slot_data[WMT_MAJOR] = MAX(width, height);
slot_data[WMT_MINOR] = MIN(width, height);
WMT_FOREACH_USAGE(sc->caps, usage)
if (wmt_hid_map[usage].code != WMT_NO_CODE)
evdev_push_abs(sc->evdev,
wmt_hid_map[usage].code,
slot_data[usage]);
} else {
evdev_push_abs(sc->evdev, ABS_MT_SLOT, slot);
evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1);
}
}
evdev_sync(sc->evdev);
}
static void
wmt_intr_callback(struct usb_xfer *xfer, usb_error_t error)
{
struct wmt_softc *sc = usbd_xfer_softc(xfer);
struct usb_page_cache *pc;
uint8_t *buf = sc->buf;
int len;
usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
switch (USB_GET_STATE(xfer)) {
case USB_ST_TRANSFERRED:
pc = usbd_xfer_get_frame(xfer, 0);
DPRINTFN(6, "sc=%p actlen=%d\n", sc, len);
if (len >= (int)sc->isize || (len > 0 && sc->report_id != 0)) {
/* Limit report length to the maximum */
if (len > (int)sc->isize)
len = sc->isize;
usbd_copy_out(pc, 0, buf, len);
/* Ignore irrelevant reports */
if (sc->report_id && *buf != sc->report_id)
goto tr_ignore;
/* Make sure we don't process old data */
if (len < sc->isize)
bzero(buf + len, sc->isize - len);
/* Strip leading "report ID" byte */
if (sc->report_id) {
len--;
buf++;
}
wmt_process_report(sc, buf, len);
} else {
tr_ignore:
DPRINTF("Ignored transfer, %d bytes\n", len);
}
case USB_ST_SETUP:
tr_setup:
usbd_xfer_set_frame_len(xfer, 0, sc->isize);
usbd_transfer_submit(xfer);
break;
default:
if (error != USB_ERR_CANCELLED) {
/* Try clear stall first */
usbd_xfer_set_stall(xfer);
goto tr_setup;
}
break;
}
}
static void
wmt_ev_close(struct evdev_dev *evdev, void *ev_softc)
{
struct wmt_softc *sc = (struct wmt_softc *)ev_softc;
mtx_assert(&sc->mtx, MA_OWNED);
usbd_transfer_stop(sc->xfer[WMT_INTR_DT]);
}
static int
wmt_ev_open(struct evdev_dev *evdev, void *ev_softc)
{
struct wmt_softc *sc = (struct wmt_softc *)ev_softc;
mtx_assert(&sc->mtx, MA_OWNED);
usbd_transfer_start(sc->xfer[WMT_INTR_DT]);
return (0);
}
static bool
wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
{
struct hid_item hi;
struct hid_data *hd;
size_t i;
size_t cont = 0;
uint32_t caps = 0;
int32_t cont_count_max = 0;
uint8_t report_id = 0;
bool touch_coll = false;
bool finger_coll = false;
bool cont_count_found = false;
bool scan_time_found = false;
#define WMT_HI_ABSOLUTE(hi) \
(((hi).flags & (HIO_CONST|HIO_VARIABLE|HIO_RELATIVE)) == HIO_VARIABLE)
/* Parse features for maximum contact count */
hd = hid_start_parse(d_ptr, d_len, 1 << hid_feature);
while (hid_get_item(hd, &hi)) {
switch (hi.kind) {
case hid_collection:
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN))
touch_coll = true;
break;
case hid_endcollection:
if (hi.collevel == 0 && touch_coll)
touch_coll = false;
break;
case hid_feature:
if (hi.collevel == 1 && touch_coll &&
WMT_HI_ABSOLUTE(hi) && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX))
cont_count_max = hi.logical_maximum;
break;
default:
break;
}
}
hid_end_parse(hd);
/* Maximum contact count is required usage */
if (cont_count_max < 1)
return (false);
touch_coll = false;
/* Parse input for other parameters */
hd = hid_start_parse(d_ptr, d_len, 1 << hid_input);
while (hid_get_item(hd, &hi)) {
switch (hi.kind) {
case hid_collection:
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN))
touch_coll = true;
else if (touch_coll && hi.collevel == 2 &&
(report_id == 0 || report_id == hi.report_ID) &&
hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER))
finger_coll = true;
break;
case hid_endcollection:
if (hi.collevel == 1 && finger_coll) {
finger_coll = false;
cont++;
} else if (hi.collevel == 0 && touch_coll)
touch_coll = false;
break;
case hid_input:
/*
* Ensure that all usages are located within the same
* report and proper collection.
*/
if (WMT_HI_ABSOLUTE(hi) && touch_coll &&
(report_id == 0 || report_id == hi.report_ID))
report_id = hi.report_ID;
else
break;
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) {
cont_count_found = true;
if (sc != NULL)
sc->nconts_loc = hi.loc;
break;
}
/* Scan time is required but clobbered by evdev */
if (hi.collevel == 1 && hi.usage ==
HID_USAGE2(HUP_DIGITIZERS, HUD_SCAN_TIME)) {
scan_time_found = true;
break;
}
if (!finger_coll || hi.collevel != 2)
break;
if (sc == NULL && cont > 0)
break;
if (cont >= MAX_MT_SLOTS) {
DPRINTF("Finger %zu ignored\n", cont);
break;
}
for (i = 0; i < WMT_N_USAGES; i++) {
if (hi.usage == wmt_hid_map[i].usage) {
if (sc == NULL) {
if (USAGE_SUPPORTED(caps, i))
continue;
caps |= 1 << i;
break;
}
/*
* HUG_X usage is an array mapped to
* both ABS_MT_POSITION and ABS_MT_TOOL
* events. So don`t stop search if we
* already have HUG_X mapping done.
*/
if (sc->locs[cont][i].size)
continue;
sc->locs[cont][i] = hi.loc;
/*
* Hid parser returns valid logical and
* physical sizes for first finger only
* at least on ElanTS 0x04f3:0x0012.
*/
if (cont > 0)
break;
caps |= 1 << i;
sc->ai[i] = (struct wmt_absinfo) {
.max = hi.logical_maximum,
.min = hi.logical_minimum,
.res = hid_item_resolution(&hi),
};
break;
}
}
break;
default:
break;
}
}
hid_end_parse(hd);
/* Check for required HID Usages */
if (!cont_count_found || !scan_time_found || cont == 0)
return (false);
for (i = 0; i < WMT_N_USAGES; i++) {
if (wmt_hid_map[i].required && !USAGE_SUPPORTED(caps, i))
return (false);
}
/* Stop probing here */
if (sc == NULL)
return (true);
/* Cap contact count maximum to MAX_MT_SLOTS */
if (cont_count_max > MAX_MT_SLOTS) {
DPRINTF("Hardware reported %d contacts while only %d is "
"supported\n", (int)cont_count_max, MAX_MT_SLOTS);
cont_count_max = MAX_MT_SLOTS;
}
/* Set number of MT protocol type B slots */
sc->ai[WMT_SLOT] = (struct wmt_absinfo) {
.min = 0,
.max = cont_count_max - 1,
.res = 0,
};
/* Report touch orientation if both width and height are supported */
if (USAGE_SUPPORTED(caps, WMT_WIDTH) &&
USAGE_SUPPORTED(caps, WMT_HEIGHT)) {
caps |= (1 << WMT_ORIENTATION);
sc->ai[WMT_ORIENTATION].max = 1;
}
sc->report_id = report_id;
sc->caps = caps;
sc->nconts_max = cont;
/* Announce information about the touch device */
device_printf(sc->dev,
"%d contacts and [%s%s%s%s%s]. Report range [%d:%d] - [%d:%d]\n",
(int)cont_count_max,
USAGE_SUPPORTED(sc->caps, WMT_IN_RANGE) ? "R" : "",
USAGE_SUPPORTED(sc->caps, WMT_CONFIDENCE) ? "C" : "",
USAGE_SUPPORTED(sc->caps, WMT_WIDTH) ? "W" : "",
USAGE_SUPPORTED(sc->caps, WMT_HEIGHT) ? "H" : "",
USAGE_SUPPORTED(sc->caps, WMT_PRESSURE) ? "P" : "",
(int)sc->ai[WMT_X].min, (int)sc->ai[WMT_Y].min,
(int)sc->ai[WMT_X].max, (int)sc->ai[WMT_Y].max);
return (true);
}
static devclass_t wmt_devclass;
static device_method_t wmt_methods[] = {
DEVMETHOD(device_probe, wmt_probe),
DEVMETHOD(device_attach, wmt_attach),
DEVMETHOD(device_detach, wmt_detach),
DEVMETHOD_END
};
static driver_t wmt_driver = {
.name = "wmt",
.methods = wmt_methods,
.size = sizeof(struct wmt_softc),
};
DRIVER_MODULE(wmt, uhub, wmt_driver, wmt_devclass, NULL, 0);
MODULE_DEPEND(wmt, usb, 1, 1, 1);
MODULE_DEPEND(wmt, evdev, 1, 1, 1);
MODULE_VERSION(wmt, 1);

View File

@ -606,6 +606,7 @@ static const char *usb_quirk_str[USB_QUIRK_MAX] = {
[UQ_SINGLE_CMD_MIDI] = "UQ_SINGLE_CMD_MIDI",
[UQ_MSC_DYMO_EJECT] = "UQ_MSC_DYMO_EJECT",
[UQ_AU_SET_SPDIF_CM6206] = "UQ_AU_SET_SPDIF_CM6206",
[UQ_WMT_IGNORE] = "UQ_WMT_IGNORE",
};
/*------------------------------------------------------------------------*

View File

@ -110,6 +110,7 @@ enum {
UQ_SINGLE_CMD_MIDI, /* at most one command per USB packet */
UQ_MSC_DYMO_EJECT, /* ejects Dymo MSC device */
UQ_AU_SET_SPDIF_CM6206, /* enable S/PDIF audio output */
UQ_WMT_IGNORE, /* device should be ignored by wmt driver */
USB_QUIRK_MAX
};

View File

@ -848,6 +848,80 @@ usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx,
return (USB_ERR_NORMAL_COMPLETION);
}
/*------------------------------------------------------------------------*
* calculate HID item resolution. unit/mm for distances, unit/rad for angles
*------------------------------------------------------------------------*/
int32_t
hid_item_resolution(struct hid_item *hi)
{
/*
* hid unit scaling table according to HID Usage Table Review
* Request 39 Tbl 17 http://www.usb.org/developers/hidpage/HUTRR39b.pdf
*/
static const int64_t scale[0x10][2] = {
[0x00] = { 1, 1 },
[0x01] = { 1, 10 },
[0x02] = { 1, 100 },
[0x03] = { 1, 1000 },
[0x04] = { 1, 10000 },
[0x05] = { 1, 100000 },
[0x06] = { 1, 1000000 },
[0x07] = { 1, 10000000 },
[0x08] = { 100000000, 1 },
[0x09] = { 10000000, 1 },
[0x0A] = { 1000000, 1 },
[0x0B] = { 100000, 1 },
[0x0C] = { 10000, 1 },
[0x0D] = { 1000, 1 },
[0x0E] = { 100, 1 },
[0x0F] = { 10, 1 },
};
int64_t logical_size;
int64_t physical_size;
int64_t multiplier;
int64_t divisor;
int64_t resolution;
switch (hi->unit) {
case HUM_CENTIMETER:
multiplier = 1;
divisor = 10;
break;
case HUM_INCH:
multiplier = 10;
divisor = 254;
break;
case HUM_RADIAN:
multiplier = 1;
divisor = 1;
break;
case HUM_DEGREE:
multiplier = 573;
divisor = 10;
break;
default:
return (0);
}
if ((hi->logical_maximum <= hi->logical_minimum) ||
(hi->physical_maximum <= hi->physical_minimum) ||
(hi->unit_exponent < 0) || (hi->unit_exponent >= nitems(scale)))
return (0);
logical_size = (int64_t)hi->logical_maximum -
(int64_t)hi->logical_minimum;
physical_size = (int64_t)hi->physical_maximum -
(int64_t)hi->physical_minimum;
/* Round to ceiling */
resolution = logical_size * multiplier * scale[hi->unit_exponent][0] /
(physical_size * divisor * scale[hi->unit_exponent][1]);
if (resolution > INT32_MAX)
return (0);
return (resolution);
}
/*------------------------------------------------------------------------*
* hid_is_mouse
*

View File

@ -134,6 +134,12 @@ struct usb_hid_descriptor {
/* Usages Digitizers */
#define HUD_UNDEFINED 0x0000
#define HUD_DIGITIZER 0x0001
#define HUD_PEN 0x0002
#define HUD_TOUCHSCREEN 0x0004
#define HUD_TOUCHPAD 0x0005
#define HUD_CONFIG 0x000e
#define HUD_FINGER 0x0022
#define HUD_TIP_PRESSURE 0x0030
#define HUD_BARREL_PRESSURE 0x0031
#define HUD_IN_RANGE 0x0032
@ -157,6 +163,16 @@ struct usb_hid_descriptor {
#define HUD_BARREL_SWITCH 0x0044
#define HUD_ERASER 0x0045
#define HUD_TABLET_PICK 0x0046
#define HUD_CONFIDENCE 0x0047
#define HUD_WIDTH 0x0048
#define HUD_HEIGHT 0x0049
#define HUD_CONTACTID 0x0051
#define HUD_INPUT_MODE 0x0052
#define HUD_DEVICE_INDEX 0x0053
#define HUD_CONTACTCOUNT 0x0054
#define HUD_CONTACT_MAX 0x0055
#define HUD_SCAN_TIME 0x0056
#define HUD_BUTTON_TYPE 0x0059
/* Usages, Consumer */
#define HUC_AC_PAN 0x0238
@ -178,6 +194,12 @@ struct usb_hid_descriptor {
#define HIO_VOLATILE 0x080
#define HIO_BUFBYTES 0x100
/* Units of Measure */
#define HUM_CENTIMETER 0x11
#define HUM_RADIAN 0x12
#define HUM_INCH 0x13
#define HUM_DEGREE 0x14
#ifdef _KERNEL
struct usb_config_descriptor;
@ -244,6 +266,7 @@ struct usb_hid_descriptor *hid_get_descriptor_from_usb(
usb_error_t usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx,
void **descp, uint16_t *sizep, struct malloc_type *mem,
uint8_t iface_index);
int32_t hid_item_resolution(struct hid_item *hi);
int hid_is_mouse(const void *d_ptr, uint16_t d_len);
int hid_is_keyboard(const void *d_ptr, uint16_t d_len);
#endif /* _KERNEL */

View File

@ -47,7 +47,7 @@ SUBDIR = usb
SUBDIR += ${_dwc_otg} ehci ${_musb} ohci uhci xhci ${_uss820dci} ${_at91dci} \
${_atmegadci} ${_avr32dci} ${_rsu} ${_rsufw} ${_saf1761otg}
SUBDIR += ${_rum} ${_run} ${_runfw} ${_uath} upgt usie ural ${_zyd} ${_urtw}
SUBDIR += atp cfumass uhid ukbd ums udbp ufm uep wsp ugold uled
SUBDIR += atp cfumass uhid ukbd ums udbp ufm uep wmt wsp ugold uled
SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \
umct umcs umodem umoscom uplcom uslcom uvisor uvscom
SUBDIR += udl

View File

@ -0,0 +1,10 @@
# $FreeBSD$
S= ${SRCTOP}/sys
.PATH: $S/dev/usb/input
KMOD= wmt
SRCS= opt_bus.h opt_usb.h device_if.h bus_if.h usb_if.h usbdevs.h wmt.c
.include <bsd.kmod.mk>