aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/hid/hid-core.c
diff options
context:
space:
mode:
authorJiri Kosina <jkosina@suse.com>2024-11-18 22:02:17 +0100
committerJiri Kosina <jkosina@suse.com>2024-11-18 22:02:17 +0100
commit903796855b6152c479bae07dcebded77897f9e1c (patch)
tree66e8119c9a6b7af98009a2ef3ed18316c000df60 /drivers/hid/hid-core.c
parentMerge branch 'for-6.13/bug-on-to-warn-on' into for-linus (diff)
parentHID: bpf: drop use of Logical|Physical|UsageRange (diff)
downloadwireguard-linux-903796855b6152c479bae07dcebded77897f9e1c.tar.xz
wireguard-linux-903796855b6152c479bae07dcebded77897f9e1c.zip
Merge branch 'for-6.13/bpf' into for-linus
- improvement of the way hid-bpf coexists with specific drivers (others than hid-generic) that are already bound to devices (Benjamin Tissoires)
Diffstat (limited to 'drivers/hid/hid-core.c')
-rw-r--r--drivers/hid/hid-core.c84
1 files changed, 65 insertions, 19 deletions
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 910cf0728d0d..81d6c734c8bc 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -713,7 +713,14 @@ static void hid_close_report(struct hid_device *device)
INIT_LIST_HEAD(&report_enum->report_list);
}
- kfree(device->rdesc);
+ /*
+ * If the HID driver had a rdesc_fixup() callback, dev->rdesc
+ * will be allocated by hid-core and needs to be freed.
+ * Otherwise, it is either equal to dev_rdesc or bpf_rdesc, in
+ * which cases it'll be freed later on device removal or destroy.
+ */
+ if (device->rdesc != device->dev_rdesc && device->rdesc != device->bpf_rdesc)
+ kfree(device->rdesc);
device->rdesc = NULL;
device->rsize = 0;
@@ -726,6 +733,14 @@ static void hid_close_report(struct hid_device *device)
device->status &= ~HID_STAT_PARSED;
}
+static inline void hid_free_bpf_rdesc(struct hid_device *hdev)
+{
+ /* bpf_rdesc is either equal to dev_rdesc or allocated by call_hid_bpf_rdesc_fixup() */
+ if (hdev->bpf_rdesc != hdev->dev_rdesc)
+ kfree(hdev->bpf_rdesc);
+ hdev->bpf_rdesc = NULL;
+}
+
/*
* Free a device structure, all reports, and all fields.
*/
@@ -735,6 +750,7 @@ void hiddev_free(struct kref *ref)
struct hid_device *hid = container_of(ref, struct hid_device, ref);
hid_close_report(hid);
+ hid_free_bpf_rdesc(hid);
kfree(hid->dev_rdesc);
kfree(hid);
}
@@ -1227,7 +1243,6 @@ int hid_open_report(struct hid_device *device)
struct hid_item item;
unsigned int size;
const __u8 *start;
- __u8 *buf;
const __u8 *end;
const __u8 *next;
int ret;
@@ -1243,25 +1258,34 @@ int hid_open_report(struct hid_device *device)
if (WARN_ON(device->status & HID_STAT_PARSED))
return -EBUSY;
- start = device->dev_rdesc;
+ start = device->bpf_rdesc;
if (WARN_ON(!start))
return -ENODEV;
- size = device->dev_rsize;
+ size = device->bpf_rsize;
- /* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
- buf = call_hid_bpf_rdesc_fixup(device, start, &size);
- if (buf == NULL)
- return -ENOMEM;
+ if (device->driver->report_fixup) {
+ /*
+ * device->driver->report_fixup() needs to work
+ * on a copy of our report descriptor so it can
+ * change it.
+ */
+ __u8 *buf = kmemdup(start, size, GFP_KERNEL);
+
+ if (buf == NULL)
+ return -ENOMEM;
- if (device->driver->report_fixup)
start = device->driver->report_fixup(device, buf, &size);
- else
- start = buf;
- start = kmemdup(start, size, GFP_KERNEL);
- kfree(buf);
- if (start == NULL)
- return -ENOMEM;
+ /*
+ * The second kmemdup is required in case report_fixup() returns
+ * a static read-only memory, but we have no idea if that memory
+ * needs to be cleaned up or not at the end.
+ */
+ start = kmemdup(start, size, GFP_KERNEL);
+ kfree(buf);
+ if (start == NULL)
+ return -ENOMEM;
+ }
device->rdesc = start;
device->rsize = size;
@@ -2656,9 +2680,10 @@ static bool hid_check_device_match(struct hid_device *hdev,
/*
* hid-generic implements .match(), so we must be dealing with a
* different HID driver here, and can simply check if
- * hid_ignore_special_drivers is set or not.
+ * hid_ignore_special_drivers or HID_QUIRK_IGNORE_SPECIAL_DRIVER
+ * are set or not.
*/
- return !hid_ignore_special_drivers;
+ return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
}
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
@@ -2666,6 +2691,27 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
const struct hid_device_id *id;
int ret;
+ if (!hdev->bpf_rsize) {
+ unsigned int quirks;
+
+ /* reset the quirks that has been previously set */
+ quirks = hid_lookup_quirk(hdev);
+ hdev->quirks = quirks;
+
+ /* in case a bpf program gets detached, we need to free the old one */
+ hid_free_bpf_rdesc(hdev);
+
+ /* keep this around so we know we called it once */
+ hdev->bpf_rsize = hdev->dev_rsize;
+
+ /* call_hid_bpf_rdesc_fixup will always return a valid pointer */
+ hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc,
+ &hdev->bpf_rsize);
+ if (quirks ^ hdev->quirks)
+ hid_info(hdev, "HID-BPF toggled quirks on the device: %04x",
+ quirks ^ hdev->quirks);
+ }
+
if (!hid_check_device_match(hdev, hdrv, &id))
return -ENODEV;
@@ -2673,8 +2719,6 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
if (!hdev->devres_group_id)
return -ENOMEM;
- /* reset the quirks that has been previously set */
- hdev->quirks = hid_lookup_quirk(hdev);
hdev->driver = hdrv;
if (hdrv->probe) {
@@ -2922,9 +2966,11 @@ static void hid_remove_device(struct hid_device *hdev)
hid_debug_unregister(hdev);
hdev->status &= ~HID_STAT_ADDED;
}
+ hid_free_bpf_rdesc(hdev);
kfree(hdev->dev_rdesc);
hdev->dev_rdesc = NULL;
hdev->dev_rsize = 0;
+ hdev->bpf_rsize = 0;
}
/**