Merge branch 'for-7.4/wacom' into for-linus

- UAF fixes (Lee Jones)
- SW_MUTE_DEVICE semantics fix (Lee Jones, Ping Cheng)
This commit is contained in:
Jiri Kosina 2026-10-21 21:30:38 +02:00
commit 8460eef1b9
4 changed files with 217 additions and 88 deletions

View File

@ -82,7 +82,7 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/device-id/hid.h>
#include <linux/hid.h>
#include <linux/kfifo.h>
#include <linux/leds.h>

View File

@ -758,27 +758,47 @@ static void wacom_retrieve_hid_descriptor(struct hid_device *hdev,
struct wacom_hdev_data {
struct list_head list;
struct kref kref;
struct hid_device *dev;
char phys[64];
__u32 vendor;
__u32 product;
__u32 device_type;
struct wacom_shared shared;
};
static bool wacom_compare_device_paths(struct hid_device *hdev_a,
const char *phys_b, char separator)
{
const char *p1 = strrchr(hdev_a->phys, separator);
const char *p2 = strrchr(phys_b, separator);
int n1, n2;
if (!p1 || !p2)
return false;
n1 = p1 - hdev_a->phys;
n2 = p2 - phys_b;
if (n1 != n2 || n1 <= 0 || n2 <= 0)
return false;
return !strncmp(hdev_a->phys, phys_b, n1);
}
static LIST_HEAD(wacom_udev_list);
static DEFINE_MUTEX(wacom_udev_list_lock);
static bool wacom_are_sibling(struct hid_device *hdev,
struct hid_device *sibling)
struct wacom_hdev_data *data)
{
struct wacom *wacom = hid_get_drvdata(hdev);
struct wacom_features *features = &wacom->wacom_wac.features;
struct wacom *sibling_wacom = hid_get_drvdata(sibling);
struct wacom_features *sibling_features = &sibling_wacom->wacom_wac.features;
__u32 oVid = features->oVid ? features->oVid : hdev->vendor;
__u32 oPid = features->oPid ? features->oPid : hdev->product;
/* The defined oVid/oPid must match that of the sibling */
if (features->oVid != HID_ANY_ID && sibling->vendor != oVid)
if (features->oVid != HID_ANY_ID && data->vendor != oVid)
return false;
if (features->oPid != HID_ANY_ID && sibling->product != oPid)
if (features->oPid != HID_ANY_ID && data->product != oPid)
return false;
/*
@ -786,11 +806,11 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* device path, while those with different VID/PID must share
* the same physical parent device path.
*/
if (hdev->vendor == sibling->vendor && hdev->product == sibling->product) {
if (!hid_compare_device_paths(hdev, sibling, '/'))
if (hdev->vendor == data->vendor && hdev->product == data->product) {
if (!wacom_compare_device_paths(hdev, data->phys, '/'))
return false;
} else {
if (!hid_compare_device_paths(hdev, sibling, '.'))
if (!wacom_compare_device_paths(hdev, data->phys, '.'))
return false;
}
@ -803,7 +823,7 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* devices.
*/
if ((features->device_type & WACOM_DEVICETYPE_DIRECT) &&
!(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
!(data->device_type & WACOM_DEVICETYPE_DIRECT))
return false;
/*
@ -811,17 +831,17 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* devices.
*/
if (!(features->device_type & WACOM_DEVICETYPE_DIRECT) &&
(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
(data->device_type & WACOM_DEVICETYPE_DIRECT))
return false;
/* Pen devices may only be siblings of touch devices */
if ((features->device_type & WACOM_DEVICETYPE_PEN) &&
!(sibling_features->device_type & WACOM_DEVICETYPE_TOUCH))
!(data->device_type & WACOM_DEVICETYPE_TOUCH))
return false;
/* Touch devices may only be siblings of pen devices */
if ((features->device_type & WACOM_DEVICETYPE_TOUCH) &&
!(sibling_features->device_type & WACOM_DEVICETYPE_PEN))
!(data->device_type & WACOM_DEVICETYPE_PEN))
return false;
/*
@ -837,7 +857,7 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
/* Try to find an already-probed interface from the same device */
list_for_each_entry(data, &wacom_udev_list, list) {
if (hid_compare_device_paths(hdev, data->dev, '/')) {
if (wacom_compare_device_paths(hdev, data->phys, '/')) {
kref_get(&data->kref);
return data;
}
@ -845,7 +865,7 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
/* Fallback to finding devices that appear to be "siblings" */
list_for_each_entry(data, &wacom_udev_list, list) {
if (wacom_are_sibling(hdev, data->dev)) {
if (wacom_are_sibling(hdev, data)) {
kref_get(&data->kref);
return data;
}
@ -859,29 +879,41 @@ static void wacom_release_shared_data(struct kref *kref)
struct wacom_hdev_data *data =
container_of(kref, struct wacom_hdev_data, kref);
mutex_lock(&wacom_udev_list_lock);
list_del(&data->list);
mutex_unlock(&wacom_udev_list_lock);
kfree(data);
}
static void wacom_remove_shared_data(void *res)
{
struct wacom *wacom = res;
struct wacom *res_wacom = res;
struct wacom_hdev_data *data;
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
struct wacom_wac *wacom_wac = &res_wacom->wacom_wac;
if (wacom_wac->shared) {
data = container_of(wacom_wac->shared, struct wacom_hdev_data,
shared);
if (wacom_wac->shared->touch == wacom->hdev)
wacom_wac->shared->touch = NULL;
else if (wacom_wac->shared->pen == wacom->hdev)
wacom_wac->shared->pen = NULL;
scoped_guard(mutex, &wacom_udev_list_lock) {
struct hid_device *touch =
rcu_dereference_protected(wacom_wac->shared->touch,
lockdep_is_held(&wacom_udev_list_lock));
struct hid_device *pen =
rcu_dereference_protected(wacom_wac->shared->pen,
lockdep_is_held(&wacom_udev_list_lock));
if (touch == res_wacom->hdev) {
rcu_assign_pointer(wacom_wac->shared->touch, NULL);
rcu_assign_pointer(wacom_wac->shared->touch_input, NULL);
} else if (pen == res_wacom->hdev) {
rcu_assign_pointer(wacom_wac->shared->pen, NULL);
}
}
synchronize_rcu();
if (kref_put_mutex(&data->kref, wacom_release_shared_data, &wacom_udev_list_lock))
mutex_unlock(&wacom_udev_list_lock);
kref_put(&data->kref, wacom_release_shared_data);
wacom_wac->shared = NULL;
}
}
@ -904,22 +936,21 @@ static int wacom_add_shared_data(struct hid_device *hdev)
}
kref_init(&data->kref);
data->dev = hdev;
strscpy(data->phys, hdev->phys, sizeof(data->phys));
data->vendor = hdev->vendor;
data->product = hdev->product;
data->device_type = wacom_wac->features.device_type;
list_add_tail(&data->list, &wacom_udev_list);
}
mutex_unlock(&wacom_udev_list_lock);
wacom_wac->shared = &data->shared;
retval = devm_add_action_or_reset(&hdev->dev, wacom_remove_shared_data, wacom);
if (retval)
return retval;
if (wacom_wac->has_mute_touch_switch)
WRITE_ONCE(wacom_wac->shared->has_mute_touch_switch, true);
if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH)
wacom_wac->shared->touch = hdev;
else if (wacom_wac->features.device_type & WACOM_DEVICETYPE_PEN)
wacom_wac->shared->pen = hdev;
mutex_unlock(&wacom_udev_list_lock);
retval = devm_add_action_or_reset(&hdev->dev, wacom_remove_shared_data, wacom);
return retval;
}
@ -2344,30 +2375,88 @@ static void wacom_release_resources(struct wacom *wacom)
static void wacom_set_shared_values(struct wacom_wac *wacom_wac)
{
if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH) {
wacom_wac->shared->type = wacom_wac->features.type;
wacom_wac->shared->touch_input = wacom_wac->touch_input;
}
struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
guard(mutex)(&wacom_udev_list_lock);
if (wacom_wac->has_mute_touch_switch) {
wacom_wac->shared->has_mute_touch_switch = true;
WRITE_ONCE(wacom_wac->shared->has_mute_touch_switch, true);
/* Hardware touch switch may be off. Wait until
* we know the switch state to decide is_touch_on.
* Softkey state should be initialized to "on" to
* match historic default.
*/
if (wacom_wac->is_soft_touch_switch)
wacom_wac->shared->is_touch_on = true;
WRITE_ONCE(wacom_wac->shared->is_touch_on, true);
}
if (wacom_wac->shared->has_mute_touch_switch &&
wacom_wac->shared->touch_input) {
set_bit(EV_SW, wacom_wac->shared->touch_input->evbit);
input_set_capability(wacom_wac->shared->touch_input, EV_SW,
SW_MUTE_DEVICE);
if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH) {
struct hid_device *touch =
rcu_dereference_protected(wacom_wac->shared->touch,
lockdep_is_held(&wacom_udev_list_lock));
if (touch == wacom->hdev || !touch) {
wacom_wac->shared->type = wacom_wac->features.type;
rcu_assign_pointer(wacom_wac->shared->touch, wacom->hdev);
rcu_assign_pointer(wacom_wac->shared->touch_input, wacom_wac->touch_input);
}
} else if (wacom_wac->features.device_type & WACOM_DEVICETYPE_PEN) {
rcu_assign_pointer(wacom_wac->shared->pen, wacom->hdev);
}
}
static bool wacom_sibling_pending(struct wacom *wacom)
{
const struct wacom_features *features = &wacom->wacom_wac.features;
struct hid_device *hdev = wacom->hdev;
struct usb_interface *sibling_intf;
int ifnum;
if (features->type != HID_GENERIC ||
!(features->device_type & WACOM_DEVICETYPE_TOUCH))
return false;
if (wacom->wacom_wac.shared) {
if (rcu_access_pointer(wacom->wacom_wac.shared->pen))
return false;
}
if (!hid_is_usb(hdev) || !wacom->usbdev || !wacom->intf ||
!wacom->intf->cur_altsetting)
return false;
ifnum = wacom->intf->cur_altsetting->desc.bInterfaceNumber;
/*
* On composite Wacom devices, the Pen interface is always interface 0.
* If the Touch interface is interface 0, there is no sibling Pen
* interface on this device (standalone touch device).
*/
if (ifnum == 0)
return false;
/* Look for the sibling Pen interface at interface 0 */
sibling_intf = usb_ifnum_to_if(wacom->usbdev, 0);
if (!sibling_intf || !sibling_intf->cur_altsetting)
return false;
if (sibling_intf->cur_altsetting->desc.bInterfaceClass !=
USB_INTERFACE_CLASS_HID)
return false;
if (sibling_intf->cur_altsetting->desc.bInterfaceSubClass == 1 &&
(sibling_intf->cur_altsetting->desc.bInterfaceProtocol == 1 ||
sibling_intf->cur_altsetting->desc.bInterfaceProtocol == 2))
return false;
/*
* Interface 0 is a candidate HID interface on this composite device
* whose probe has not completed yet (shared->pen is NULL). Defer until
* interface 0 finishes probing and registers shared values.
*/
return true;
}
static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
{
struct wacom_wac *wacom_wac = &wacom->wacom_wac;
@ -2445,6 +2534,11 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
if (error)
goto fail;
if (wacom_sibling_pending(wacom)) {
error = -EPROBE_DEFER;
goto fail;
}
error = wacom_setup_inputs(wacom);
if (error)
goto fail;
@ -2502,6 +2596,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
fail_hw_stop:
hid_hw_stop(hdev);
cancel_delayed_work_sync(&wacom->init_work);
fail:
wacom_release_resources(wacom);
return error;
@ -2789,16 +2884,19 @@ static void wacom_mode_change_work(struct work_struct *work)
bool is_direct = wacom->wacom_wac.is_direct_mode;
int error = 0;
if (shared->pen) {
wacom1 = hid_get_drvdata(shared->pen);
struct hid_device *pen = rcu_access_pointer(shared->pen);
struct hid_device *touch = rcu_access_pointer(shared->touch);
if (pen) {
wacom1 = hid_get_drvdata(pen);
wacom_release_resources(wacom1);
hid_hw_stop(wacom1->hdev);
wacom1->wacom_wac.has_mode_change = true;
wacom1->wacom_wac.is_direct_mode = is_direct;
}
if (shared->touch) {
wacom2 = hid_get_drvdata(shared->touch);
if (touch) {
wacom2 = hid_get_drvdata(touch);
wacom_release_resources(wacom2);
hid_hw_stop(wacom2->hdev);
wacom2->wacom_wac.has_mode_change = true;

View File

@ -510,6 +510,18 @@ static void wacom_intuos_schedule_prox_event(struct wacom_wac *wacom_wac)
}
}
static void wacom_report_touch_mute(struct wacom_wac *wacom_wac, bool mute)
{
struct input_dev *touch_input;
guard(rcu)();
touch_input = rcu_dereference(wacom_wac->shared->touch_input);
if (touch_input) {
input_report_switch(touch_input, SW_MUTE_DEVICE, mute);
input_sync(touch_input);
}
}
static int wacom_intuos_pad(struct wacom_wac *wacom)
{
struct wacom_features *features = &wacom->features;
@ -650,12 +662,8 @@ static int wacom_intuos_pad(struct wacom_wac *wacom)
input_report_key(input, KEY_CONTROLPANEL, menu);
input_report_key(input, KEY_INFO, info);
if (wacom->shared && wacom->shared->touch_input) {
input_report_switch(wacom->shared->touch_input,
SW_MUTE_DEVICE,
!wacom->shared->is_touch_on);
input_sync(wacom->shared->touch_input);
}
if (wacom->shared)
wacom_report_touch_mute(wacom, !wacom->shared->is_touch_on);
input_report_abs(input, ABS_RX, strip1);
input_report_abs(input, ABS_RY, strip2);
@ -2168,7 +2176,7 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
*/
if ((equivalent_usage == WACOM_HID_WD_MUTE_DEVICE) ||
(equivalent_usage == WACOM_HID_WD_TOUCHONOFF)) {
if (wacom_wac->shared->touch_input) {
if (wacom_wac->shared) {
bool *is_touch_on = &wacom_wac->shared->is_touch_on;
if (equivalent_usage == WACOM_HID_WD_MUTE_DEVICE && value)
@ -2176,9 +2184,7 @@ static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field
else if (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)
*is_touch_on = value;
input_report_switch(wacom_wac->shared->touch_input,
SW_MUTE_DEVICE, !(*is_touch_on));
input_sync(wacom_wac->shared->touch_input);
wacom_report_touch_mute(wacom_wac, !(*is_touch_on));
}
return;
}
@ -3301,6 +3307,7 @@ static int wacom_bpt_irq(struct wacom_wac *wacom, size_t len)
static void wacom_bamboo_pad_pen_event(struct wacom_wac *wacom,
unsigned char *data)
{
struct hid_device *pen;
unsigned char prefix;
/*
@ -3313,13 +3320,11 @@ static void wacom_bamboo_pad_pen_event(struct wacom_wac *wacom,
prefix = data[0];
data[0] = WACOM_REPORT_BPAD_PEN;
/*
* actually reroute the event.
* No need to check if wacom->shared->pen is valid, hid_input_report()
* will check for us.
*/
hid_input_report(wacom->shared->pen, HID_INPUT_REPORT, data,
WACOM_PKGLEN_PENABLED, 1);
guard(rcu)();
pen = rcu_dereference(wacom->shared->pen);
if (pen)
hid_input_report(pen, HID_INPUT_REPORT, data,
WACOM_PKGLEN_PENABLED, 1);
data[0] = prefix;
}
@ -3396,11 +3401,8 @@ static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
if ((wacom->shared->type == INTUOSHT ||
wacom->shared->type == INTUOSHT2) &&
wacom->shared->touch_input &&
wacom->shared->touch_max) {
input_report_switch(wacom->shared->touch_input,
SW_MUTE_DEVICE, data[5] & 0x40);
input_sync(wacom->shared->touch_input);
wacom_report_touch_mute(wacom, data[5] & 0x40);
}
pid = get_unaligned_be16(&data[6]);
@ -3435,11 +3437,8 @@ static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len)
if ((features->type == INTUOSHT ||
features->type == INTUOSHT2) &&
wacom_wac->shared->touch_input &&
features->touch_max) {
input_report_switch(wacom_wac->shared->touch_input,
SW_MUTE_DEVICE, data[8] & 0x40);
input_sync(wacom_wac->shared->touch_input);
wacom_report_touch_mute(wacom_wac, data[8] & 0x40);
}
if (data[9] & 0x02) { /* wireless module is attached */
@ -3966,8 +3965,8 @@ int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
struct wacom_wac *wacom_wac)
{
struct hid_device *hdev = container_of(wacom_wac, struct wacom, wacom_wac)->hdev;
struct wacom_features *features = &wacom_wac->features;
if (!(features->device_type & WACOM_DEVICETYPE_TOUCH))
return -ENODEV;
@ -3976,9 +3975,24 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
else
__set_bit(INPUT_PROP_POINTER, input_dev->propbit);
if (features->type == HID_GENERIC)
if (features->type == HID_GENERIC) {
hid_dbg(hdev, "generic touch setup\n");
/*
* This is to setup the touch switch for those devices that report
* pen interface first. The setup can only be done here since this
* routine is only called once. When we are here, the shared
* has_mute_touch_switch has been set by the pad.
* There are two ways of reporting inputs from firmware: stylus first
* or touch first. This path covers the stylus input first case.
*/
if (wacom_wac->shared && READ_ONCE(wacom_wac->shared->has_mute_touch_switch)) {
input_set_capability(input_dev, EV_SW, SW_MUTE_DEVICE);
}
/* setup has already been done */
return 0;
}
input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
__set_bit(BTN_TOUCH, input_dev->keybit);
@ -4010,19 +4024,17 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
input_dev->evbit[0] |= BIT_MASK(EV_SW);
__set_bit(SW_MUTE_DEVICE, input_dev->swbit);
if (wacom_wac->shared->touch->product == 0x361) {
if (hdev->product == 0x361) {
input_set_abs_params(input_dev, ABS_MT_POSITION_X,
0, 12440, 4, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
0, 8640, 4, 0);
}
else if (wacom_wac->shared->touch->product == 0x360) {
} else if (hdev->product == 0x360) {
input_set_abs_params(input_dev, ABS_MT_POSITION_X,
0, 8960, 4, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
0, 5920, 4, 0);
}
else if (wacom_wac->shared->touch->product == 0x393) {
} else if (hdev->product == 0x393) {
input_set_abs_params(input_dev, ABS_MT_POSITION_X,
0, 6400, 4, 0);
input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
@ -4052,8 +4064,8 @@ int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
fallthrough;
case WACOM_27QHDT:
if (wacom_wac->shared->touch->product == 0x32C ||
wacom_wac->shared->touch->product == 0xF6) {
if (hdev->product == 0x32C ||
hdev->product == 0xF6) {
input_dev->evbit[0] |= BIT_MASK(EV_SW);
__set_bit(SW_MUTE_DEVICE, input_dev->swbit);
wacom_wac->has_mute_touch_switch = true;
@ -4399,7 +4411,26 @@ int wacom_setup_pad_input_capabilities(struct input_dev *input_dev,
break;
case INTUOSHT3_BT:
break;
case HID_GENERIC:
if (wacom_wac->shared) {
/*
* The setup of touch switch capability can only be done
* here for those devices, such as Cintiq Pro 13 and 16,
* that have touch input registered before stylus and pad
* inputs.
*
* There are two ways of reporting inputs from firmware:
* stylus first or touch first. This path covers the touch
* input first case.
*/
if (wacom_wac->has_mute_touch_switch &&
wacom_wac->shared->touch_input) {
input_set_capability(wacom_wac->shared->touch_input,
EV_SW, SW_MUTE_DEVICE);
}
}
break;
default:

View File

@ -285,9 +285,9 @@ struct wacom_shared {
/* for wireless device to access USB interfaces */
unsigned touch_max;
int type;
struct input_dev *touch_input;
struct hid_device *pen;
struct hid_device *touch;
struct input_dev __rcu *touch_input;
struct hid_device __rcu *pen;
struct hid_device __rcu *touch;
bool has_mute_touch_switch;
bool is_touch_on;
};