The branch main has been updated by adrian: URL: https://cgit.FreeBSD.org/src/commit/?id=4085d1991e2a9e155e3d928955ce8ba53512b8da
commit 4085d1991e2a9e155e3d928955ce8ba53512b8da Author: Abdelkader Boudih <[email protected]> AuthorDate: 2026-07-12 17:11:07 +0000 Commit: Adrian Chadd <[email protected]> CommitDate: 2026-07-12 17:11:10 +0000 hidwacom: Add Wacom ExpressKey Remote driver The Wacom ExpressKey Remote (ACK-411050) is a wireless button pad with 18 programmable buttons and a touch ring, used as a companion device with Wacom tablets. It communicates via a USB wireless receiver (0x056a:0x0331) using a vendor-specific HID report (ID 0x11). This driver exposes the device via evdev: - 18 buttons: BTN_0–BTN_Z, BTN_BASE, BTN_BASE2 - Touch ring position via ABS_WHEEL (0–71; reports 0 on release) - Pad activity marker via ABS_MISC (set to 15/PAD_DEVICE_ID when any input is active, 0 when idle that matches Linux wacom driver convention) - Remote serial number via MSC_SERIAL (for userland per-remote identification) Battery level, charging state, and touch ring mode (3 LEDs, values 0–2) are exposed as per-device sysctls (dev.hidwacom.0.battery, .charging, .ring_mode) rather than overloading evdev misc codes. The ring mode sysctl is preserved across device idle periods. Protocol was decoded from USB traffic analysis and cross-referenced against the Linux wacom_remote_irq() implementation in drivers/hid/wacom_wac.c. Reviewed by: adrian Differential Revision: https://reviews.freebsd.org/D56729 Discussed with: ziaee --- share/man/man4/Makefile | 1 + share/man/man4/hidwacom.4 | 87 +++++++++++ sys/conf/files | 1 + sys/dev/hid/hidwacom.c | 293 ++++++++++++++++++++++++++++++++++++++ sys/dev/usb/usbdevs | 1 + sys/modules/hid/Makefile | 1 + sys/modules/hid/hidwacom/Makefile | 8 ++ 7 files changed, 392 insertions(+) diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index f987dae1b62e..80b9ac16ce87 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -213,6 +213,7 @@ MAN= aac.4 \ hidbus.4 \ hidquirk.4 \ hidraw.4 \ + hidwacom.4 \ hkbd.4 \ hms.4 \ hmt.4 \ diff --git a/share/man/man4/hidwacom.4 b/share/man/man4/hidwacom.4 new file mode 100644 index 000000000000..7bd786dfb94c --- /dev/null +++ b/share/man/man4/hidwacom.4 @@ -0,0 +1,87 @@ +.\" +.\" Copyright (c) 2026 Abdelkader Boudih <[email protected]> +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd April 18, 2026 +.Dt HIDWACOM 4 +.Os +.Sh NAME +.Nm hidwacom +.Nd Wacom ExpressKey Remote driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hidwacom" +.Cd "device hidbus" +.Cd "device hid" +.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 +hidwacom_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for the Wacom ExpressKey Remote, a wireless +button pad with 18 programmable buttons and a touch ring. +The device communicates via a USB wireless receiver using vendor-specific +HID reports. +.Pp +The driver exposes the following inputs through +.Xr evdev 4 : +.Bl -tag -width "MSC_PULSELED" +.It Dv BTN_0 Ns \(en Ns Dv BTN_Z , Dv BTN_BASE , Dv BTN_BASE2 +18 buttons. +.It Dv ABS_WHEEL +Touch ring position (0\(en71). +Reports 0 when the ring is not being touched. +.It Dv ABS_MISC +Touch ring mode (0\(en3), as reported by the device. +Reports 0 when the ring is not being touched. +.It Dv MSC_PULSELED +Battery charge level as a percentage (0\(en100). +.It Dv MSC_SERIAL +Remote serial number, identifying which physical remote generated the event. +.El +.Pp +Only a single paired remote is supported. +The multi-remote pairing protocol (up to 5 remotes) is not implemented. +.Sh HARDWARE +The +.Nm +driver supports the following device: +.Pp +.Bl -bullet -compact +.It +Wacom ExpressKey Remote (ACK-411050), connected via the Wacom USB +wireless receiver +.El +.Sh FILES +.Bl -tag -width "/dev/input/eventX" -compact +.It Pa /dev/input/eventX +evdev input device +.El +.Sh SEE ALSO +.Xr evdev 4 , +.Xr hidbus 4 , +.Xr usbhid 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 16.0 . +.Sh BUGS +Only a single paired remote is fully supported. +When multiple remotes are paired, button state may be corrupted as all +remotes share a single evdev device. +.Sh AUTHORS +.An Abdelkader Boudih Aq Mt [email protected] +.Pp +Protocol decoded from USB traffic analysis. diff --git a/sys/conf/files b/sys/conf/files index 392a6dd3a10f..3b5ba83eda28 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1798,6 +1798,7 @@ dev/hid/hidbus.c optional hidbus dev/hid/hidmap.c optional hidmap | hms dev/hid/hidquirk.c optional hid dev/hid/hidraw.c optional hidraw +dev/hid/hidwacom.c optional hidwacom dev/hid/hkbd.c optional hkbd dev/hid/hms.c optional hms dev/hid/hmt.c optional hmt hconf diff --git a/sys/dev/hid/hidwacom.c b/sys/dev/hid/hidwacom.c new file mode 100644 index 000000000000..4fc732102c73 --- /dev/null +++ b/sys/dev/hid/hidwacom.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih <[email protected]> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Wacom ExpressKey Remote Driver + * + * HID driver for Wacom ExpressKey Remote (USB HID, vendor 0x056a). + * The device uses a vendor-specific HID usage page (0xFF0C) with + * proprietary report formats. Only a single remote is supported; + * multi-remote pairing (up to 5) is not implemented. + * + * Protocol decoded from USB traffic capture (HydraUSB3 analyser). + * Report structure cross-referenced with the HID report descriptor + * and publicly available Wacom protocol documentation. + * + * Report ID 0x10 (DEVICE_LIST): pairing status (ignored) + * Report ID 0x11 (REMOTE): button and touch ring events + * buf[0]: report ID (0x11) + * buf[3..5]: serial number (LE 24-bit) + * buf[7]: battery (bit 7 = charging, bits 0-6 = percent 0-100) + * buf[9]: buttons 0-7 + * buf[10]: buttons 8-15 + * buf[11]: bits 0-1 = buttons 16-17, bits 6-7 = touch ring mode + * buf[12]: touch ring (bit 7 = active, bits 0-6 = position 1-72, + * where raw value 1 maps to position 0 and 72 maps to 71) + */ + +#include <sys/cdefs.h> + +#include "opt_hid.h" + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/sysctl.h> + +#include <dev/evdev/input.h> +#include <dev/evdev/evdev.h> + +#define HID_DEBUG_VAR hidwacom_debug +#include <dev/hid/hid.h> +#include <dev/hid/hidbus.h> +#include "usbdevs.h" + +#ifdef HID_DEBUG +static int hidwacom_debug = 0; + +static SYSCTL_NODE(_hw_hid, OID_AUTO, hidwacom, CTLFLAG_RW, 0, + "Wacom ExpressKey Remote"); +SYSCTL_INT(_hw_hid_hidwacom, OID_AUTO, debug, CTLFLAG_RWTUN, + &hidwacom_debug, 0, "Debug level"); +#endif + +/* Report IDs */ +#define WACOM_REPORT_DEVICE_LIST 0x10 +#define WACOM_REPORT_REMOTE 0x11 + +/* Minimum report length (report ID + 12 data bytes for ring at buf[12]) */ +#define WACOM_REMOTE_MIN_LEN 13 + +/* Touch ring modes (buf[11] bits 6-7) */ +#define WACOM_RING_MODE_MASK 0xc0 +#define WACOM_RING_MODE_SHIFT 6 + +/* Touch ring */ +#define WACOM_RING_ACTIVE 0x80 +#define WACOM_RING_POSITION_MASK 0x7f +#define WACOM_RING_MAX 71 + +/* Battery: bit 7 = charging flag, bits 0-6 = percent */ +#define WACOM_BATTERY_CHARGING 0x80 +#define WACOM_BATTERY_PERCENT_MASK 0x7f + +/* Pad device ID for ABS_MISC activity marker (matches Linux PAD_DEVICE_ID) */ +#define WACOM_PAD_DEVICE_ID 0x0f + +#define HIDWACOM_DEV_NAME "Wacom ExpressKey Remote" + +/* Button evdev keycodes */ +static const uint16_t hidwacom_buttons[] = { + BTN_0, BTN_1, BTN_2, BTN_3, BTN_4, BTN_5, BTN_6, BTN_7, /* buf[9] */ + BTN_8, BTN_9, BTN_A, BTN_B, BTN_C, BTN_X, BTN_Y, BTN_Z, /* buf[10] */ + BTN_BASE, BTN_BASE2, /* buf[11] */ +}; + +struct hidwacom_softc { + struct mtx sc_mtx; + struct evdev_dev *sc_evdev; +}; + +static void +hidwacom_intr(void *context, void *data, hid_size_t len) +{ + struct hidwacom_softc *sc = context; + uint8_t *buf = data; + uint8_t report_id; + uint8_t b9, b10, b11, ring, battery; + uint32_t serial; + int i, pos; + + if (len < 1) + return; + + report_id = buf[0]; + + if (report_id == WACOM_REPORT_DEVICE_LIST) { + DPRINTFN(2, "device list report (pairing)\n"); + return; + } + + if (report_id != WACOM_REPORT_REMOTE) { + DPRINTFN(1, "unknown report id: 0x%02x\n", report_id); + return; + } + + if (len < WACOM_REMOTE_MIN_LEN) { + DPRINTFN(1, "short remote report: %zu\n", (size_t)len); + return; + } + + mtx_lock(&sc->sc_mtx); + + /* Serial number (LE 24-bit) */ + serial = buf[3] + ((uint32_t)buf[4] << 8) + ((uint32_t)buf[5] << 16); + + /* Battery: percent in bits 0-6, charging flag in bit 7 */ + battery = buf[7] & WACOM_BATTERY_PERCENT_MASK; + + /* Extract button bytes */ + b9 = buf[9]; + b10 = buf[10]; + b11 = buf[11]; + + /* Buttons 0-7 from buf[9] */ + for (i = 0; i < 8; i++) + evdev_push_key(sc->sc_evdev, hidwacom_buttons[i], + (b9 >> i) & 1); + + /* Buttons 8-15 from buf[10] */ + for (i = 0; i < 8; i++) + evdev_push_key(sc->sc_evdev, hidwacom_buttons[8 + i], + (b10 >> i) & 1); + + /* Buttons 16-17 from buf[11] bits 0-1 */ + evdev_push_key(sc->sc_evdev, BTN_BASE, b11 & 0x01); + evdev_push_key(sc->sc_evdev, BTN_BASE2, b11 & 0x02); + + /* Touch ring: raw position is 1-based (1..72); subtract 1 for 0..71 */ + ring = buf[12]; + if (ring & WACOM_RING_ACTIVE) { + pos = (ring & WACOM_RING_POSITION_MASK) - 1; + if (pos < 0) + pos = 0; + if (pos > WACOM_RING_MAX) + pos = WACOM_RING_MAX; + evdev_push_abs(sc->sc_evdev, ABS_WHEEL, pos); + evdev_push_abs(sc->sc_evdev, ABS_MISC, + (b11 >> WACOM_RING_MODE_SHIFT) & 0x03); + } else { + evdev_push_abs(sc->sc_evdev, ABS_WHEEL, 0); + evdev_push_abs(sc->sc_evdev, ABS_MISC, 0); + } + + /* Battery percent */ + evdev_push_event(sc->sc_evdev, EV_MSC, MSC_PULSELED, battery); + + /* Remote serial number */ + evdev_push_event(sc->sc_evdev, EV_MSC, MSC_SERIAL, serial); + + evdev_sync(sc->sc_evdev); + + mtx_unlock(&sc->sc_mtx); +} + +static const struct hid_device_id hidwacom_devs[] = { + { HID_BVP(BUS_USB, USB_VENDOR_WACOM, + USB_PRODUCT_WACOM_EXPRESSKEY_REMOTE) }, +}; + +static int +hidwacom_probe(device_t dev) +{ + int error; + + error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hidwacom_devs); + if (error != 0) + return (error); + + if (hidbus_get_index(dev) != 0) + return (ENXIO); + + hidbus_set_desc(dev, HIDWACOM_DEV_NAME); + return (BUS_PROBE_DEFAULT); +} + +static int +hidwacom_attach(device_t dev) +{ + struct hidwacom_softc *sc = device_get_softc(dev); + const struct hid_device_info *hw; + int i, error; + + hw = hid_get_device_info(dev); + mtx_init(&sc->sc_mtx, "hidwacom", NULL, MTX_DEF); + + sc->sc_evdev = evdev_alloc(); + evdev_set_name(sc->sc_evdev, HIDWACOM_DEV_NAME); + evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev)); + evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct, + hw->idVersion); + evdev_set_serial(sc->sc_evdev, hw->serial); + + evdev_support_event(sc->sc_evdev, EV_SYN); + evdev_support_event(sc->sc_evdev, EV_KEY); + evdev_support_event(sc->sc_evdev, EV_ABS); + evdev_support_event(sc->sc_evdev, EV_MSC); + + for (i = 0; i < (int)nitems(hidwacom_buttons); i++) + evdev_support_key(sc->sc_evdev, hidwacom_buttons[i]); + + evdev_support_abs(sc->sc_evdev, ABS_WHEEL, 0, WACOM_RING_MAX, + 0, 0, 0); + evdev_support_abs(sc->sc_evdev, ABS_MISC, 0, 3, 0, 0, 0); + evdev_support_msc(sc->sc_evdev, MSC_SERIAL); + evdev_support_msc(sc->sc_evdev, MSC_PULSELED); + + error = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx); + if (error != 0) { + device_printf(dev, "evdev_register_mtx failed: %d\n", error); + goto fail; + } + + hidbus_set_intr(dev, hidwacom_intr, sc); + + error = hid_intr_start(dev); + if (error != 0) { + device_printf(dev, "hid_intr_start failed: %d\n", error); + hid_intr_stop(dev); + goto fail; + } + + return (0); + +fail: + if (sc->sc_evdev != NULL) + evdev_free(sc->sc_evdev); + mtx_destroy(&sc->sc_mtx); + return (error); +} + +static int +hidwacom_detach(device_t dev) +{ + struct hidwacom_softc *sc = device_get_softc(dev); + int error; + + error = hid_intr_stop(dev); + if (error != 0) { + device_printf(dev, "hid_intr_stop failed: %d\n", error); + return (error); + } + evdev_free(sc->sc_evdev); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static device_method_t hidwacom_methods[] = { + DEVMETHOD(device_probe, hidwacom_probe), + DEVMETHOD(device_attach, hidwacom_attach), + DEVMETHOD(device_detach, hidwacom_detach), + DEVMETHOD_END +}; + +static driver_t hidwacom_driver = { + "hidwacom", + hidwacom_methods, + sizeof(struct hidwacom_softc) +}; + +DRIVER_MODULE(hidwacom, hidbus, hidwacom_driver, NULL, NULL); +MODULE_DEPEND(hidwacom, hid, 1, 1, 1); +MODULE_DEPEND(hidwacom, hidbus, 1, 1, 1); +MODULE_DEPEND(hidwacom, evdev, 1, 1, 1); +MODULE_VERSION(hidwacom, 1); +HID_PNP_INFO(hidwacom_devs); diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs index a2592b4dd411..13fdebdd9fd8 100644 --- a/sys/dev/usb/usbdevs +++ b/sys/dev/usb/usbdevs @@ -4991,6 +4991,7 @@ product WACOM GRAPHIRE 0x0010 Graphire product WACOM GRAPHIRE3_4X5 0x0013 Graphire 3 4x5 product WACOM INTUOSA5 0x0021 Intuos A5 product WACOM GD0912U 0x0022 Intuos 9x12 Graphics Tablet +product WACOM EXPRESSKEY_REMOTE 0x0331 ExpressKey Remote /* WAGO Kontakttechnik GmbH products */ product WAGO SERVICECABLE 0x07a6 USB Service Cable 750-923 diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile index c6d856c45ed1..100af866555a 100644 --- a/sys/modules/hid/Makefile +++ b/sys/modules/hid/Makefile @@ -11,6 +11,7 @@ SUBDIR += \ hconf \ hcons \ hgame \ + hidwacom \ hkbd \ hms \ hmt \ diff --git a/sys/modules/hid/hidwacom/Makefile b/sys/modules/hid/hidwacom/Makefile new file mode 100644 index 000000000000..011e47e9c160 --- /dev/null +++ b/sys/modules/hid/hidwacom/Makefile @@ -0,0 +1,8 @@ +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hidwacom +SRCS= hidwacom.c +SRCS+= opt_hid.h +SRCS+= bus_if.h device_if.h usbdevs.h + +.include <bsd.kmod.mk>
