From 81e4fc67415607fbd969ff518cc5cbd75e895738 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:11 +0300 Subject: lib/string_choices: Add str_write_read() helper Add an inversed variant of str_read_write(), i.e. str_write_read(). Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-2-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- include/linux/string_choices.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/string_choices.h b/include/linux/string_choices.h index 48120222b9b2..3c1091941eb8 100644 --- a/include/linux/string_choices.h +++ b/include/linux/string_choices.h @@ -30,6 +30,7 @@ static inline const char *str_read_write(bool v) { return v ? "read" : "write"; } +#define str_write_read(v) str_read_write(!(v)) static inline const char *str_on_off(bool v) { -- cgit From 4a3983d749a1cd2bd71c01a7ea6c4fc63f9df87a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:12 +0300 Subject: HID: cp2112: Use str_write_read() and str_read_write() Use str_write_read() and str_read_write() from string_choices.h. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-3-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 27cadadda7c9..37ccf4714ad1 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "hid-ids.h" @@ -532,15 +533,13 @@ static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, hid_dbg(hdev, "I2C %d messages\n", num); if (num == 1) { + hid_dbg(hdev, "I2C %s %#04x len %d\n", + str_read_write(msgs->flags & I2C_M_RD), msgs->addr, msgs->len); if (msgs->flags & I2C_M_RD) { - hid_dbg(hdev, "I2C read %#04x len %d\n", - msgs->addr, msgs->len); read_length = msgs->len; read_buf = msgs->buf; count = cp2112_read_req(buf, msgs->addr, msgs->len); } else { - hid_dbg(hdev, "I2C write %#04x len %d\n", - msgs->addr, msgs->len); count = cp2112_i2c_write_req(buf, msgs->addr, msgs->buf, msgs->len); } @@ -648,7 +647,7 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr, int ret; hid_dbg(hdev, "%s addr 0x%x flags 0x%x cmd 0x%x size %d\n", - read_write == I2C_SMBUS_WRITE ? "write" : "read", + str_write_read(read_write == I2C_SMBUS_WRITE), addr, flags, command, size); switch (size) { -- cgit From 3e2977c425ad2789ca18084fff913cceacae75a2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:13 +0300 Subject: HID: cp2112: Make irq_chip immutable Since recently, the kernel is nagging about mutable irq_chips: "not an immutable chip, please consider fixing it!" Drop the unneeded copy, flag it as IRQCHIP_IMMUTABLE, add the new helper functions and call the appropriate gpiolib functions. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-4-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 37ccf4714ad1..51399b231d36 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -164,7 +164,6 @@ struct cp2112_device { atomic_t read_avail; atomic_t xfer_avail; struct gpio_chip gc; - struct irq_chip irq; u8 *in_out_buffer; struct mutex lock; @@ -1079,16 +1078,20 @@ static void cp2112_gpio_irq_mask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct cp2112_device *dev = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); - __clear_bit(d->hwirq, &dev->irq_mask); + __clear_bit(hwirq, &dev->irq_mask); + gpiochip_disable_irq(gc, hwirq); } static void cp2112_gpio_irq_unmask(struct irq_data *d) { struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct cp2112_device *dev = gpiochip_get_data(gc); + irq_hw_number_t hwirq = irqd_to_hwirq(d); - __set_bit(d->hwirq, &dev->irq_mask); + gpiochip_enable_irq(gc, hwirq); + __set_bit(hwirq, &dev->irq_mask); } static void cp2112_gpio_poll_callback(struct work_struct *work) @@ -1174,6 +1177,7 @@ static void cp2112_gpio_irq_shutdown(struct irq_data *d) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct cp2112_device *dev = gpiochip_get_data(gc); + cp2112_gpio_irq_mask(d); cancel_delayed_work_sync(&dev->gpio_poll_worker); } @@ -1227,6 +1231,18 @@ err_desc: return ret; } +static const struct irq_chip cp2112_gpio_irqchip = { + .name = "cp2112-gpio", + .irq_startup = cp2112_gpio_irq_startup, + .irq_shutdown = cp2112_gpio_irq_shutdown, + .irq_ack = cp2112_gpio_irq_ack, + .irq_mask = cp2112_gpio_irq_mask, + .irq_unmask = cp2112_gpio_irq_unmask, + .irq_set_type = cp2112_gpio_irq_type, + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE, + GPIOCHIP_IRQ_RESOURCE_HELPERS, +}; + static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct cp2112_device *dev; @@ -1336,17 +1352,8 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) dev->gc.can_sleep = 1; dev->gc.parent = &hdev->dev; - dev->irq.name = "cp2112-gpio"; - dev->irq.irq_startup = cp2112_gpio_irq_startup; - dev->irq.irq_shutdown = cp2112_gpio_irq_shutdown; - dev->irq.irq_ack = cp2112_gpio_irq_ack; - dev->irq.irq_mask = cp2112_gpio_irq_mask; - dev->irq.irq_unmask = cp2112_gpio_irq_unmask; - dev->irq.irq_set_type = cp2112_gpio_irq_type; - dev->irq.flags = IRQCHIP_MASK_ON_SUSPEND; - girq = &dev->gc.irq; - girq->chip = &dev->irq; + gpio_irq_chip_set_chip(girq, &cp2112_gpio_irqchip); /* The event comes from the outside so no parent handler */ girq->parent_handler = NULL; girq->num_parents = 0; -- cgit From ecb42bb8035c9901684c40c87cf1ea23f62e055d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:14 +0300 Subject: HID: cp2112: Switch to for_each_set_bit() to simplify the code It's cleaner to use for_each_set_bit() than open coding it. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-5-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 51399b231d36..fb4548feb0c8 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -16,6 +16,7 @@ * https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf */ +#include #include #include #include @@ -1100,7 +1101,6 @@ static void cp2112_gpio_poll_callback(struct work_struct *work) gpio_poll_worker.work); struct irq_data *d; u8 gpio_mask; - u8 virqs = (u8)dev->irq_mask; u32 irq_type; int irq, virq, ret; @@ -1111,11 +1111,7 @@ static void cp2112_gpio_poll_callback(struct work_struct *work) goto exit; gpio_mask = ret; - - while (virqs) { - virq = ffs(virqs) - 1; - virqs &= ~BIT(virq); - + for_each_set_bit(virq, &dev->irq_mask, 8) { if (!dev->gc.to_irq) break; -- cgit From b5ac00880a57273ed4d8ba6d37c91c1fd261b191 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:15 +0300 Subject: HID: cp2112: Don't call ->to_irq() explicitly GPIO library guarantees that ->to_irq() is always exists. Moreover, it tending to become a nische thingy and has to not be used in ordinary drivers. Hence, replace that by irq_find_mapping(). Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-6-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index fb4548feb0c8..15b626359281 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -1112,10 +1112,9 @@ static void cp2112_gpio_poll_callback(struct work_struct *work) gpio_mask = ret; for_each_set_bit(virq, &dev->irq_mask, 8) { - if (!dev->gc.to_irq) - break; - - irq = dev->gc.to_irq(&dev->gc, virq); + irq = irq_find_mapping(dev->gc.irq.domain, virq); + if (!irq) + continue; d = irq_get_irq_data(irq); if (!d) -- cgit From ff3b9e4926b2e7eafbfdccf01e0f811898758b54 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:16 +0300 Subject: HID: cp2112: Remove dead code Remove cp2112_allocate_irq() and counterparts that seems to be a dead code from day 1. In case somebody needs it, it can be retrieved from Git index. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-7-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 54 ------------------------------------------------ 1 file changed, 54 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 15b626359281..45cd0d2fd3fd 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -17,8 +17,6 @@ */ #include -#include -#include #include #include #include @@ -168,7 +166,6 @@ struct cp2112_device { u8 *in_out_buffer; struct mutex lock; - struct gpio_desc *desc[8]; bool gpio_poll; struct delayed_work gpio_poll_worker; unsigned long irq_mask; @@ -1181,51 +1178,6 @@ static int cp2112_gpio_irq_type(struct irq_data *d, unsigned int type) return 0; } -static int __maybe_unused cp2112_allocate_irq(struct cp2112_device *dev, - int pin) -{ - int ret; - - if (dev->desc[pin]) - return -EINVAL; - - dev->desc[pin] = gpiochip_request_own_desc(&dev->gc, pin, - "HID/I2C:Event", - GPIO_ACTIVE_HIGH, - GPIOD_IN); - if (IS_ERR(dev->desc[pin])) { - dev_err(dev->gc.parent, "Failed to request GPIO\n"); - return PTR_ERR(dev->desc[pin]); - } - - ret = cp2112_gpio_direction_input(&dev->gc, pin); - if (ret < 0) { - dev_err(dev->gc.parent, "Failed to set GPIO to input dir\n"); - goto err_desc; - } - - ret = gpiochip_lock_as_irq(&dev->gc, pin); - if (ret) { - dev_err(dev->gc.parent, "Failed to lock GPIO as interrupt\n"); - goto err_desc; - } - - ret = gpiod_to_irq(dev->desc[pin]); - if (ret < 0) { - dev_err(dev->gc.parent, "Failed to translate GPIO to IRQ\n"); - goto err_lock; - } - - return ret; - -err_lock: - gpiochip_unlock_as_irq(&dev->gc, pin); -err_desc: - gpiochip_free_own_desc(dev->desc[pin]); - dev->desc[pin] = NULL; - return ret; -} - static const struct irq_chip cp2112_gpio_irqchip = { .name = "cp2112-gpio", .irq_startup = cp2112_gpio_irq_startup, @@ -1390,7 +1342,6 @@ err_hid_stop: static void cp2112_remove(struct hid_device *hdev) { struct cp2112_device *dev = hid_get_drvdata(hdev); - int i; sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group); i2c_del_adapter(&dev->adap); @@ -1400,11 +1351,6 @@ static void cp2112_remove(struct hid_device *hdev) cancel_delayed_work_sync(&dev->gpio_poll_worker); } - for (i = 0; i < ARRAY_SIZE(dev->desc); i++) { - gpiochip_unlock_as_irq(&dev->gc, i); - gpiochip_free_own_desc(dev->desc[i]); - } - gpiochip_remove(&dev->gc); /* i2c_del_adapter has finished removing all i2c devices from our * adapter. Well behaved devices should no longer call our cp2112_xfer -- cgit From e7378e09f6d59b4df026c7ae9847bbf98ce2b9ef Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:17 +0300 Subject: HID: cp2112: Define maximum GPIO constant and use it Define maximum GPIO constant and use it in the driver. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-8-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 45cd0d2fd3fd..c8c59d70500e 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -31,6 +31,7 @@ #define CP2112_GPIO_CONFIG_LENGTH 5 #define CP2112_GPIO_GET_LENGTH 2 #define CP2112_GPIO_SET_LENGTH 3 +#define CP2112_GPIO_MAX_GPIO 8 enum { CP2112_GPIO_CONFIG = 0x02, @@ -1108,7 +1109,7 @@ static void cp2112_gpio_poll_callback(struct work_struct *work) goto exit; gpio_mask = ret; - for_each_set_bit(virq, &dev->irq_mask, 8) { + for_each_set_bit(virq, &dev->irq_mask, CP2112_GPIO_MAX_GPIO) { irq = irq_find_mapping(dev->gc.irq.domain, virq); if (!irq) continue; @@ -1295,7 +1296,7 @@ static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id) dev->gc.set = cp2112_gpio_set; dev->gc.get = cp2112_gpio_get; dev->gc.base = -1; - dev->gc.ngpio = 8; + dev->gc.ngpio = CP2112_GPIO_MAX_GPIO; dev->gc.can_sleep = 1; dev->gc.parent = &hdev->dev; -- cgit From ee0682b0f241e35c3775f77acf71b20cf4d6830e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:18 +0300 Subject: HID: cp2112: Define all GPIO mask and use it Define all GPIO mask and use it in the driver. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-9-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index c8c59d70500e..37ed7fc54770 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -32,6 +32,7 @@ #define CP2112_GPIO_GET_LENGTH 2 #define CP2112_GPIO_SET_LENGTH 3 #define CP2112_GPIO_MAX_GPIO 8 +#define CP2112_GPIO_ALL_GPIO_MASK GENMASK(7, 0) enum { CP2112_GPIO_CONFIG = 0x02, @@ -173,7 +174,7 @@ struct cp2112_device { u8 gpio_prev_state; }; -static int gpio_push_pull = 0xFF; +static int gpio_push_pull = CP2112_GPIO_ALL_GPIO_MASK; module_param(gpio_push_pull, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask"); @@ -226,7 +227,7 @@ static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value) mutex_lock(&dev->lock); buf[0] = CP2112_GPIO_SET; - buf[1] = value ? 0xff : 0; + buf[1] = value ? CP2112_GPIO_ALL_GPIO_MASK : 0; buf[2] = 1 << offset; ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf, -- cgit From e19c6bd4e3760a37c59808da1ee3e7dd799348ae Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:19 +0300 Subject: HID: cp2112: Use BIT() in GPIO setter and getter Use BIT() in GPIO setter and getter for the sake of consistency with GENMASK() usage elsewhere in the driver. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-10-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 37ed7fc54770..2eebb2b19501 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -197,7 +197,7 @@ static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset) goto exit; } - buf[1] &= ~(1 << offset); + buf[1] &= ~BIT(offset); buf[2] = gpio_push_pull; ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf, @@ -228,7 +228,7 @@ static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value) buf[0] = CP2112_GPIO_SET; buf[1] = value ? CP2112_GPIO_ALL_GPIO_MASK : 0; - buf[2] = 1 << offset; + buf[2] = BIT(offset); ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf, CP2112_GPIO_SET_LENGTH, HID_FEATURE_REPORT, -- cgit From 5120bf04984a73c7fdae23bcfef8b1f45774e05b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:20 +0300 Subject: HID: cp2112: Use sysfs_emit() to instead of scnprintf() Follow the advice of the Documentation/filesystems/sysfs.rst and show() should only use sysfs_emit() or sysfs_emit_at() when formatting the value to be returned to user space. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-11-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 2eebb2b19501..1e45f5407d09 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -893,7 +893,7 @@ static ssize_t name##_show(struct device *kdev, \ int ret = cp2112_get_usb_config(hdev, &cfg); \ if (ret) \ return ret; \ - return scnprintf(buf, PAGE_SIZE, format, ##__VA_ARGS__); \ + return sysfs_emit(buf, format, ##__VA_ARGS__); \ } \ static DEVICE_ATTR_RW(name); -- cgit From 7f75812589d81d8db0867ee24b664f8b6a804363 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:21 +0300 Subject: HID: cp2112: Convert to DEVICE_ATTR_RW() Instead of custom wrapper, use DEVICE_tATTR_RW() directly. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-12-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 1e45f5407d09..3c6a3be8fc02 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -944,18 +944,10 @@ CP2112_CONFIG_ATTR(release_version, ({ #undef CP2112_CONFIG_ATTR -struct cp2112_pstring_attribute { - struct device_attribute attr; - unsigned char report; -}; - -static ssize_t pstr_store(struct device *kdev, - struct device_attribute *kattr, const char *buf, - size_t count) +static ssize_t pstr_store(struct device *kdev, struct device_attribute *kattr, + const char *buf, size_t count, int number) { struct hid_device *hdev = to_hid_device(kdev); - struct cp2112_pstring_attribute *attr = - container_of(kattr, struct cp2112_pstring_attribute, attr); struct cp2112_string_report report; int ret; @@ -963,7 +955,7 @@ static ssize_t pstr_store(struct device *kdev, ret = utf8s_to_utf16s(buf, count, UTF16_LITTLE_ENDIAN, report.string, ARRAY_SIZE(report.string)); - report.report = attr->report; + report.report = number; report.length = ret * sizeof(report.string[0]) + 2; report.type = USB_DT_STRING; @@ -981,17 +973,15 @@ static ssize_t pstr_store(struct device *kdev, return count; } -static ssize_t pstr_show(struct device *kdev, - struct device_attribute *kattr, char *buf) +static ssize_t pstr_show(struct device *kdev, struct device_attribute *kattr, + char *buf, int number) { struct hid_device *hdev = to_hid_device(kdev); - struct cp2112_pstring_attribute *attr = - container_of(kattr, struct cp2112_pstring_attribute, attr); struct cp2112_string_report report; u8 length; int ret; - ret = cp2112_hid_get(hdev, attr->report, (u8 *)&report.contents, + ret = cp2112_hid_get(hdev, number, (u8 *)&report.contents, sizeof(report.contents), HID_FEATURE_REPORT); if (ret < 3) { hid_err(hdev, "error reading %s string: %d\n", kattr->attr.name, @@ -1016,10 +1006,16 @@ static ssize_t pstr_show(struct device *kdev, } #define CP2112_PSTR_ATTR(name, _report) \ -static struct cp2112_pstring_attribute dev_attr_##name = { \ - .attr = __ATTR(name, (S_IWUSR | S_IRUGO), pstr_show, pstr_store), \ - .report = _report, \ -}; +static ssize_t name##_store(struct device *kdev, struct device_attribute *kattr, \ + const char *buf, size_t count) \ +{ \ + return pstr_store(kdev, kattr, buf, count, _report); \ +} \ +static ssize_t name##_show(struct device *kdev, struct device_attribute *kattr, char *buf) \ +{ \ + return pstr_show(kdev, kattr, buf, _report); \ +} \ +static DEVICE_ATTR_RW(name); CP2112_PSTR_ATTR(manufacturer, CP2112_MANUFACTURER_STRING); CP2112_PSTR_ATTR(product, CP2112_PRODUCT_STRING); @@ -1034,9 +1030,9 @@ static const struct attribute_group cp2112_attr_group = { &dev_attr_max_power.attr, &dev_attr_power_mode.attr, &dev_attr_release_version.attr, - &dev_attr_manufacturer.attr.attr, - &dev_attr_product.attr.attr, - &dev_attr_serial.attr.attr, + &dev_attr_manufacturer.attr, + &dev_attr_product.attr, + &dev_attr_serial.attr, NULL } }; -- cgit From a6a5eccc6e1d8b21c12e70cda7e15b6541e4eb37 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Jul 2023 21:52:22 +0300 Subject: HID: cp2112: Use octal permissions Octal permissions are preferred as stated in Documentation/dev-tools/checkpatch.rst. Replace symbolic permissions with octal permissions when creating the files. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20230703185222.50554-13-andriy.shevchenko@linux.intel.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-cp2112.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index 3c6a3be8fc02..54c33a24f844 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -175,7 +175,7 @@ struct cp2112_device { }; static int gpio_push_pull = CP2112_GPIO_ALL_GPIO_MASK; -module_param(gpio_push_pull, int, S_IRUGO | S_IWUSR); +module_param(gpio_push_pull, int, 0644); MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask"); static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset) @@ -1057,7 +1057,7 @@ static void chmod_sysfs_attrs(struct hid_device *hdev) } for (attr = cp2112_attr_group.attrs; *attr; ++attr) { - umode_t mode = (buf[1] & 1) ? S_IWUSR | S_IRUGO : S_IRUGO; + umode_t mode = (buf[1] & 1) ? 0644 : 0444; ret = sysfs_chmod_file(&hdev->dev.kobj, *attr, mode); if (ret < 0) hid_err(hdev, "error chmoding sysfs file %s\n", -- cgit From 2326dee41c01c8d31574a62045fb1c5f242885f0 Mon Sep 17 00:00:00 2001 From: Marco Morandini Date: Mon, 17 Jul 2023 18:40:52 +0200 Subject: HID: Add introduction about HID for non-kernel programmers Add an introduction about HID meant for the casual programmer that is trying either to fix his device or to understand what is going wrong. Signed-off-by: Marco Morandini Co-authored-by: Peter Hutterer Signed-off-by: Jiri Kosina --- Documentation/hid/hidintro.rst | 524 ++++++++++++++++++++++++++++++++ Documentation/hid/hidreport-parsing.rst | 49 +++ Documentation/hid/index.rst | 1 + include/linux/hid.h | 23 ++ 4 files changed, 597 insertions(+) create mode 100644 Documentation/hid/hidintro.rst create mode 100644 Documentation/hid/hidreport-parsing.rst diff --git a/Documentation/hid/hidintro.rst b/Documentation/hid/hidintro.rst new file mode 100644 index 000000000000..73523e315ebd --- /dev/null +++ b/Documentation/hid/hidintro.rst @@ -0,0 +1,524 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Introduction to HID report descriptors +====================================== + +This chapter is meant to give a broad overview of what HID report +descriptors are, and of how a casual (non-kernel) programmer can deal +with HID devices that are not working well with Linux. + +.. contents:: + :local: + :depth: 2 + +.. toctree:: + :maxdepth: 2 + + hidreport-parsing + + +Introduction +============ + +HID stands for Human Interface Device, and can be whatever device you +are using to interact with a computer, be it a mouse, a touchpad, a +tablet, a microphone. + +Many HID devices work out the box, even if their hardware is different. +For example, mice can have any number of buttons; they may have a +wheel; movement sensitivity differs between different models, and so +on. Nonetheless, most of the time everything just works, without the +need to have specialized code in the kernel for every mouse model +developed since 1970. + +This is because modern HID devices do advertise their capabilities +through the *HID report descriptor*, a fixed set of bytes describing +exactly what *HID reports* may be sent between the device and the host +and the meaning of each individual bit in those reports. For example, +a HID Report Descriptor may specify that "in a report with ID 3 the +bits from 8 to 15 is the delta x coordinate of a mouse". + +The HID report itself then merely carries the actual data values +without any extra meta information. Note that HID reports may be sent +from the device ("Input Reports", i.e. input events), to the device +("Output Reports" to e.g. change LEDs) or used for device configuration +("Feature reports"). A device may support one or more HID reports. + +The HID subsystem is in charge of parsing the HID report descriptors, +and converts HID events into normal input device interfaces (see +Documentation/hid/hid-transport.rst). Devices may misbehave because the +HID report descriptor provided by the device is wrong, or because it +needs to be dealt with in a special way, or because some special +device or interaction mode is not handled by the default code. + +The format of HID report descriptors is described by two documents, +available from the `USB Implementers Forum `_ +`HID web page `_ address: + + * the `HID USB Device Class Definition + `_ (HID Spec from now on) + * the `HID Usage Tables `_ (HUT from now on) + +The HID subsystem can deal with different transport drivers +(USB, I2C, Bluetooth, etc.). See Documentation/hid/hid-transport.rst. + +Parsing HID report descriptors +============================== + +The current list of HID devices can be found at ``/sys/bus/hid/devices/``. +For each device, say ``/sys/bus/hid/devices/0003\:093A\:2510.0002/``, +one can read the corresponding report descriptor:: + + $ hexdump -C /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor + 00000000 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 |..............).| + 00000010 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 |..%.u.....u.....| + 00000020 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 |...0.1.8..%.u...| + 00000030 81 06 c0 c0 |....| + 00000034 + +Optional: the HID report descriptor can be read also by +directly accessing the hidraw driver [#hidraw]_. + +The basic structure of HID report descriptors is defined in the HID +spec, while HUT "defines constants that can be interpreted by an +application to identify the purpose and meaning of a data field in a +HID report". Each entry is defined by at least two bytes, where the +first one defines what type of value is following and is described in +the HID spec, while the second one carries the actual value and is +described in the HUT. + +HID report descriptors can, in principle, be painstakingly parsed by +hand, byte by byte. + +A short introduction on how to do this is sketched in +Documentation/hid/hidreport-parsing.rst; you only need to understand it +if you need to patch HID report descriptors. + +In practice you should not parse HID report descriptors by hand; rather, +you should use an existing parser. Among all the available ones + + * the online `USB Descriptor and Request Parser + `_; + * `hidrdd `_, + that provides very detailed and somewhat verbose descriptions + (verbosity can be useful if you are not familiar with HID report + descriptors); + * `hid-tools `_, + a complete utility set that allows, among other things, + to record and replay the raw HID reports and to debug + and replay HID devices. + It is being actively developed by the Linux HID subsystem maintainers. + +Parsing the mouse HID report descriptor with `hid-tools +`_ leads to +(explanations interposed):: + + $ ./hid-decode /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor + # device 0:0 + # 0x05, 0x01, // Usage Page (Generic Desktop) 0 + # 0x09, 0x02, // Usage (Mouse) 2 + # 0xa1, 0x01, // Collection (Application) 4 + # 0x09, 0x01, // Usage (Pointer) 6 + # 0xa1, 0x00, // Collection (Physical) 8 + # 0x05, 0x09, // Usage Page (Button) 10 + +what follows is a button :: + + # 0x19, 0x01, // Usage Minimum (1) 12 + # 0x29, 0x03, // Usage Maximum (3) 14 + +first button is button number 1, last button is button number 3 :: + + # 0x15, 0x00, // Logical Minimum (0) 16 + # 0x25, 0x01, // Logical Maximum (1) 18 + +each button can send values from 0 up to including 1 +(i.e. they are binary buttons) :: + + # 0x75, 0x01, // Report Size (1) 20 + +each button is sent as exactly one bit :: + + # 0x95, 0x03, // Report Count (3) 22 + +and there are three of those bits (matching the three buttons) :: + + # 0x81, 0x02, // Input (Data,Var,Abs) 24 + +it's actual Data (not constant padding), they represent +a single variable (Var) and their values are Absolute (not relative); +See HID spec Sec. 6.2.2.5 "Input, Output, and Feature Items" :: + + # 0x75, 0x05, // Report Size (5) 26 + +five additional padding bits, needed to reach a byte :: + + # 0x95, 0x01, // Report Count (1) 28 + +those five bits are repeated only once :: + + # 0x81, 0x01, // Input (Cnst,Arr,Abs) 30 + +and take Constant (Cnst) values i.e. they can be ignored. :: + + # 0x05, 0x01, // Usage Page (Generic Desktop) 32 + # 0x09, 0x30, // Usage (X) 34 + # 0x09, 0x31, // Usage (Y) 36 + # 0x09, 0x38, // Usage (Wheel) 38 + +The mouse has also two physical positions (Usage (X), Usage (Y)) +and a wheel (Usage (Wheel)) :: + + # 0x15, 0x81, // Logical Minimum (-127) 40 + # 0x25, 0x7f, // Logical Maximum (127) 42 + +each of them can send values ranging from -127 up to including 127 :: + + # 0x75, 0x08, // Report Size (8) 44 + +which is represented by eight bits :: + + # 0x95, 0x03, // Report Count (3) 46 + +and there are three of those eight bits, matching X, Y and Wheel. :: + + # 0x81, 0x06, // Input (Data,Var,Rel) 48 + +This time the data values are Relative (Rel), i.e. they represent +the change from the previously sent report (event) :: + + # 0xc0, // End Collection 50 + # 0xc0, // End Collection 51 + # + R: 52 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 81 06 c0 c0 + N: device 0:0 + I: 3 0001 0001 + + +This Report Descriptor tells us that the mouse input will be +transmitted using four bytes: the first one for the buttons (three +bits used, five for padding), the last three for the mouse X, Y and +wheel changes, respectively. + +Indeed, for any event, the mouse will send a *report* of four bytes. +We can check the values sent by resorting e.g. to the `hid-recorder` +tool, from `hid-tools `_: +The sequence of bytes sent by clicking and releasing button 1, then button 2, then button 3 is:: + + $ sudo ./hid-recorder /dev/hidraw1 + + .... + output of hid-decode + .... + + # Button: 1 0 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000000.000000 4 01 00 00 00 + # Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000000.183949 4 00 00 00 00 + # Button: 0 1 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000001.959698 4 02 00 00 00 + # Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000002.103899 4 00 00 00 00 + # Button: 0 0 1 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000004.855799 4 04 00 00 00 + # Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000005.103864 4 00 00 00 00 + +This example shows that when button 2 is clicked, +the bytes ``02 00 00 00`` are sent, and the immediately subsequent +event (``00 00 00 00``) is the release of button 2 (no buttons are +pressed, remember that the data values are *absolute*). + +If instead one clicks and holds button 1, then clicks and holds button +2, releases button 1, and finally releases button 2, the reports are:: + + # Button: 1 0 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000044.175830 4 01 00 00 00 + # Button: 1 1 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000045.975997 4 03 00 00 00 + # Button: 0 1 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000047.407930 4 02 00 00 00 + # Button: 0 0 0 | # | X: 0 | Y: 0 | Wheel: 0 + E: 000049.199919 4 00 00 00 00 + +where with ``03 00 00 00`` both buttons are pressed, and with the +subsequent ``02 00 00 00`` button 1 is released while button 2 is still +active. + +Output, Input and Feature Reports +--------------------------------- + +HID devices can have Input Reports, like in the mouse example, Output +Reports, and Feature Reports. "Output" means that the information is +sent to the device. For example, a joystick with force feedback will +have some output; the led of a keyboard would need an output as well. +"Input" means that data come from the device. + +"Feature"s are not meant to be consumed by the end user and define +configuration options for the device. They can be queried from the host; +when declared as *Volatile* they should be changed by the host. + + +Collections, Report IDs and Evdev events +======================================== + +A single device can logically group data into different independent +sets, called a *Collection*. Collections can be nested and there are +different types of collections (see the HID spec 6.2.2.6 +"Collection, End Collection Items" for details). + +Different reports are identified by means of different *Report ID* +fields, i.e. a number identifying the structure of the immediately +following report. +Whenever a Report ID is needed it is transmitted as the first byte of +any report. A device with only one supported HID report (like the mouse +example above) may omit the report ID. + +Consider the following HID report descriptor:: + + 05 01 09 02 A1 01 85 01 05 09 19 01 29 05 15 00 + 25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01 + 09 30 09 31 16 00 F8 26 FF 07 75 0C 95 02 81 06 + 09 38 15 80 25 7F 75 08 95 01 81 06 05 0C 0A 38 + 02 15 80 25 7F 75 08 95 01 81 06 C0 05 01 09 02 + A1 01 85 02 05 09 19 01 29 05 15 00 25 01 95 05 + 75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31 + 16 00 F8 26 FF 07 75 0C 95 02 81 06 09 38 15 80 + 25 7F 75 08 95 01 81 06 05 0C 0A 38 02 15 80 25 + 7F 75 08 95 01 81 06 C0 05 01 09 07 A1 01 85 05 + 05 07 15 00 25 01 09 29 09 3E 09 4B 09 4E 09 E3 + 09 E8 09 E8 09 E8 75 01 95 08 81 02 95 00 81 01 + C0 05 0C 09 01 A1 01 85 06 15 00 25 01 75 01 95 + 01 09 3F 81 06 09 3F 81 06 09 3F 81 06 09 3F 81 + 06 09 3F 81 06 09 3F 81 06 09 3F 81 06 09 3F 81 + 06 C0 05 0C 09 01 A1 01 85 03 09 05 15 00 26 FF + 00 75 08 95 02 B1 02 C0 + +After parsing it (try to parse it on your own using the suggested +tools!) one can see that the device presents two ``Mouse`` Application +Collections (with reports identified by Reports IDs 1 and 2, +respectively), a ``Keypad`` Application Collection (whose report is +identified by the Report ID 5) and two ``Consumer Controls`` Application +Collections, (with Report IDs 6 and 3, respectively). Note, however, +that a device can have different Report IDs for the same Application +Collection. + +The data sent will begin with the Report ID byte, and will be followed +by the corresponding information. For example, the data transmitted for +the last consumer control:: + + 0x05, 0x0C, // Usage Page (Consumer) + 0x09, 0x01, // Usage (Consumer Control) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x03, // Report ID (3) + 0x09, 0x05, // Usage (Headphone) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + +will be of three bytes: the first for the Report ID (3), the next two +for the headphone, with two (``Report Count (2)``) bytes +(``Report Size (8)``), each ranging from 0 (``Logical Minimum (0)``) +to 255 (``Logical Maximum (255)``). + +All the Input data sent by the device should be translated into +corresponding Evdev events, so that the remaining part of the stack can +know what is going on, e.g. the bit for the first button translates into +the ``EV_KEY/BTN_LEFT`` evdev event and relative X movement translates +into the ``EV_REL/REL_X`` evdev event". + +Events +====== + +In Linux, one ``/dev/input/event*`` is created for each ``Application +Collection``. Going back to the mouse example, and repeating the +sequence where one clicks and holds button 1, then clicks and holds +button 2, releases button 1, and finally releases button 2, one gets:: + + $ sudo libinput record /dev/input/event1 + # libinput record + version: 1 + ndevices: 1 + libinput: + version: "1.23.0" + git: "unknown" + system: + os: "opensuse-tumbleweed:20230619" + kernel: "6.3.7-1-default" + dmi: "dmi:bvnHP:bvrU77Ver.01.05.00:bd03/24/2022:br5.0:efr20.29:svnHP:pnHPEliteBook64514inchG9NotebookPC:pvr:rvnHP:rn89D2:rvrKBCVersion14.1D.00:cvnHP:ct10:cvr:sku5Y3J1EA#ABZ:" + devices: + - node: /dev/input/event1 + evdev: + # Name: PixArt HP USB Optical Mouse + # ID: bus 0x3 vendor 0x3f0 product 0x94a version 0x111 + # Supported Events: + # Event type 0 (EV_SYN) + # Event type 1 (EV_KEY) + # Event code 272 (BTN_LEFT) + # Event code 273 (BTN_RIGHT) + # Event code 274 (BTN_MIDDLE) + # Event type 2 (EV_REL) + # Event code 0 (REL_X) + # Event code 1 (REL_Y) + # Event code 8 (REL_WHEEL) + # Event code 11 (REL_WHEEL_HI_RES) + # Event type 4 (EV_MSC) + # Event code 4 (MSC_SCAN) + # Properties: + name: "PixArt HP USB Optical Mouse" + id: [3, 1008, 2378, 273] + codes: + 0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] # EV_SYN + 1: [272, 273, 274] # EV_KEY + 2: [0, 1, 8, 11] # EV_REL + 4: [4] # EV_MSC + properties: [] + hid: [ + 0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, + 0x15, 0x00, 0x25, 0x01, 0x95, 0x08, 0x75, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, + 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06, 0xc0, 0xc0 + ] + udev: + properties: + - ID_INPUT=1 + - ID_INPUT_MOUSE=1 + - LIBINPUT_DEVICE_GROUP=3/3f0/94a:usb-0000:05:00.3-2 + quirks: + events: + # Current time is 12:31:56 + - evdev: + - [ 0, 0, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated) + - [ 0, 0, 1, 272, 1] # EV_KEY / BTN_LEFT 1 + - [ 0, 0, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +0ms + - evdev: + - [ 1, 207892, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated) + - [ 1, 207892, 1, 273, 1] # EV_KEY / BTN_RIGHT 1 + - [ 1, 207892, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +1207ms + - evdev: + - [ 2, 367823, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated) + - [ 2, 367823, 1, 272, 0] # EV_KEY / BTN_LEFT 0 + - [ 2, 367823, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +1160ms + # Current time is 12:32:00 + - evdev: + - [ 3, 247617, 4, 4, 30] # EV_MSC / MSC_SCAN 30 (obfuscated) + - [ 3, 247617, 1, 273, 0] # EV_KEY / BTN_RIGHT 0 + - [ 3, 247617, 0, 0, 0] # ------------ SYN_REPORT (0) ---------- +880ms + +Note: if ``libinput record`` is not available on your system try using +``evemu-record``. + +When something does not work +============================ + +There can be a number of reasons why a device does not behave +correctly. For example + +* The HID report descriptor provided by the HID device may be wrong + because e.g. + + * it does not follow the standard, so that the kernel + will not able to make sense of the HID report descriptor; + * the HID report descriptor *does not match* what is actually + sent by the device (this can be verified by reading the raw HID + data); +* the HID report descriptor may need some "quirks" (see later on). + +As a consequence, a ``/dev/input/event*`` may not be created +for each Application Collection, and/or the events +there may not match what you would expect. + + +Quirks +------ + +There are some known peculiarities of HID devices that the kernel +knows how to fix - these are called the HID quirks and a list of those +is available in `include/linux/hid.h`. + +Should this be the case, it should be enough to add the required quirk +in the kernel, for the HID device at hand. This can be done in the file +`drivers/hid/hid-quirks.c`. How to do it should be relatively +straightforward after looking into the file. + +The list of currently defined quirks, from `include/linux/hid.h`, is + +.. kernel-doc:: include/linux/hid.h + :doc: HID quirks + +Quirks for USB devices can be specified while loading the usbhid module, +see ``modinfo usbhid``, although the proper fix should go into +hid-quirks.c and **be submitted upstream**. +See Documentation/process/submitting-patches.rst for guidelines on how +to submit a patch. Quirks for other busses need to go into hid-quirks.c. + +Fixing HID report descriptors +----------------------------- + +Should you need to patch HID report descriptors the easiest way is to +resort to eBPF, as described in Documentation/hid/hid-bpf.rst. + +Basically, you can change any byte of the original HID report +descriptor. The examples in samples/hid should be a good starting point +for your code, see e.g. `samples/hid/hid_mouse.bpf.c`:: + + SEC("fmod_ret/hid_bpf_rdesc_fixup") + int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx) + { + .... + data[39] = 0x31; + data[41] = 0x30; + return 0; + } + +Of course this can be also done within the kernel source code, see e.g. +`drivers/hid/hid-aureal.c` or `drivers/hid/hid-samsung.c` for a slightly +more complex file. + +Check Documentation/hid/hidreport-parsing.rst if you need any help +navigating the HID manuals and understanding the exact meaning of +the HID report descriptor hex numbers. + +Whatever solution you come up with, please remember to **submit the +fix to the HID maintainers**, so that it can be directly integrated in +the kernel and that particular HID device will start working for +everyone else. See Documentation/process/submitting-patches.rst for +guidelines on how to do this. + + +Modifying the transmitted data on the fly +----------------------------------------- + +Using eBPF it is also possible to modify the data exchanged with the +device. See again the examples in `samples/hid`. + +Again, **please post your fix**, so that it can be integrated in the +kernel! + +Writing a specialized driver +---------------------------- + +This should really be your last resort. + + +.. rubric:: Footnotes + +.. [#hidraw] read hidraw: see Documentation/hid/hidraw.rst and + file `samples/hidraw/hid-example.c` for an example. + The output of ``hid-example`` would be, for the same mouse:: + + $ sudo ./hid-example + Report Descriptor Size: 52 + Report Descriptor: + 5 1 9 2 a1 1 9 1 a1 0 5 9 19 1 29 3 15 0 25 1 75 1 95 3 81 2 75 5 95 1 81 1 5 1 9 30 9 31 9 38 15 81 25 7f 75 8 95 3 81 6 c0 c0 + + Raw Name: PixArt USB Optical Mouse + Raw Phys: usb-0000:05:00.4-2.3/input0 + Raw Info: + bustype: 3 (USB) + vendor: 0x093a + product: 0x2510 + ... diff --git a/Documentation/hid/hidreport-parsing.rst b/Documentation/hid/hidreport-parsing.rst new file mode 100644 index 000000000000..1d3c17f29f2b --- /dev/null +++ b/Documentation/hid/hidreport-parsing.rst @@ -0,0 +1,49 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================== +Manual parsing of HID report descriptors +======================================== + +Consider again the mouse HID report descriptor +introduced in Documentation/hid/hidintro.rst:: + + $ hexdump -C /sys/bus/hid/devices/0003\:093A\:2510.0002/report_descriptor + 00000000 05 01 09 02 a1 01 09 01 a1 00 05 09 19 01 29 03 |..............).| + 00000010 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 |..%.u.....u.....| + 00000020 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 03 |...0.1.8..%.u...| + 00000030 81 06 c0 c0 |....| + 00000034 + +and try to parse it by hand. + +Start with the first number, 0x05: it carries 2 bits for the +length of the item, 2 bits for the type of the item and 4 bits for the +function:: + + +----------+ + | 00000101 | + +----------+ + ^^ + ---- Length of data (see HID spec 6.2.2.2) + ^^ + ------ Type of the item (see HID spec 6.2.2.2, then jump to 6.2.2.7) + ^^^^ + --------- Function of the item (see HID spec 6.2.2.7, then HUT Sec 3) + +In our case, the length is 1 byte, the type is ``Global`` and the +function is ``Usage Page``, thus for parsing the value 0x01 in the second byte +we need to refer to HUT Sec 3. + +The second number is the actual data, and its meaning can be found in +the HUT. We have a ``Usage Page``, thus we need to refer to HUT +Sec. 3, "Usage Pages"; from there, one sees that ``0x01`` stands for +``Generic Desktop Page``. + +Moving now to the second two bytes, and following the same scheme, +``0x09`` (i.e. ``00001001``) will be followed by one byte (``01``) +and is a ``Local`` item (``10``). Thus, the meaning of the remaining four bits +(``0000``) is given in the HID spec Sec. 6.2.2.8 "Local Items", so that +we have a ``Usage``. From HUT, Sec. 4, "Generic Desktop Page", we see that +0x02 stands for ``Mouse``. + +The following numbers can be parsed in the same way. diff --git a/Documentation/hid/index.rst b/Documentation/hid/index.rst index b2028f382f11..af02cf7cfa82 100644 --- a/Documentation/hid/index.rst +++ b/Documentation/hid/index.rst @@ -7,6 +7,7 @@ Human Interface Devices (HID) .. toctree:: :maxdepth: 1 + hidintro hiddev hidraw hid-sensor diff --git a/include/linux/hid.h b/include/linux/hid.h index 39e21e3815ad..463d2e66b2c3 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -341,6 +341,29 @@ struct hid_item { */ #define MAX_USBHID_BOOT_QUIRKS 4 +/** + * DOC: HID quirks + * | @HID_QUIRK_NOTOUCH: + * | @HID_QUIRK_IGNORE: ignore this device + * | @HID_QUIRK_NOGET: + * | @HID_QUIRK_HIDDEV_FORCE: + * | @HID_QUIRK_BADPAD: + * | @HID_QUIRK_MULTI_INPUT: + * | @HID_QUIRK_HIDINPUT_FORCE: + * | @HID_QUIRK_ALWAYS_POLL: + * | @HID_QUIRK_INPUT_PER_APP: + * | @HID_QUIRK_X_INVERT: + * | @HID_QUIRK_Y_INVERT: + * | @HID_QUIRK_SKIP_OUTPUT_REPORTS: + * | @HID_QUIRK_SKIP_OUTPUT_REPORT_ID: + * | @HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP: + * | @HID_QUIRK_HAVE_SPECIAL_DRIVER: + * | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE: + * | @HID_QUIRK_FULLSPEED_INTERVAL: + * | @HID_QUIRK_NO_INIT_REPORTS: + * | @HID_QUIRK_NO_IGNORE: + * | @HID_QUIRK_NO_INPUT_SYNC: + */ /* BIT(0) reserved for backward compatibility, was HID_QUIRK_INVERT */ #define HID_QUIRK_NOTOUCH BIT(1) #define HID_QUIRK_IGNORE BIT(2) -- cgit From 666cf30a589a00d89c3775e27a698a2370cbe3db Mon Sep 17 00:00:00 2001 From: Daniel Thompson Date: Sun, 28 May 2023 10:24:27 +0100 Subject: HID: sensor-hub: Allow multi-function sensor devices The Lenovo Yoga C630 has a combined keyboard and accelerometer that interfaces via i2c-hid. Currently this laptop either has a working keyboard (if CONFIG_HID_SENSOR_HUB is disabled) or a working accelerometer. only works on kernels. Put another way, most distro kernels enable CONFIG_HID_SENSOR_HUB and therefore cannot work on this device since the keyboard doesn't work! Fix this by providing a richer connect mask during the probe. With this change both keyboard and screen orientation sensors work correctly. Signed-off-by: Daniel Thompson Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/hid-sensor-hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c index 83237b86c8ff..2eba152e8b90 100644 --- a/drivers/hid/hid-sensor-hub.c +++ b/drivers/hid/hid-sensor-hub.c @@ -632,7 +632,7 @@ static int sensor_hub_probe(struct hid_device *hdev, } INIT_LIST_HEAD(&hdev->inputs); - ret = hid_hw_start(hdev, 0); + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); return ret; -- cgit From 9620a78fd1d3a0bab4ff78a97d54f6a976898eaa Mon Sep 17 00:00:00 2001 From: Osama Muhammad Date: Tue, 30 May 2023 20:42:52 +0500 Subject: HID: hid-wiimote-debug.c: Drop error checking for debugfs_create_file This patch removes the error checking for debugfs_create_file in hid-wiimote-debug.c.c. This is because the debugfs_create_file() does not return NULL but an ERR_PTR after an error. The DebugFS kernel API is developed in a way that the caller can safely ignore the errors that occur during the creation of DebugFS nodes.The debugfs Api handles it gracefully. The check is unnecessary. Link to the comment above debugfs_create_file: https://elixir.bootlin.com/linux/latest/source/fs/debugfs/inode.c#L451 Signed-off-by: Osama Muhammad Reviewed-by: David Rheinsberg Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote-debug.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c index a99dcca2e099..00f9be55f148 100644 --- a/drivers/hid/hid-wiimote-debug.c +++ b/drivers/hid/hid-wiimote-debug.c @@ -173,7 +173,6 @@ int wiidebug_init(struct wiimote_data *wdata) { struct wiimote_debug *dbg; unsigned long flags; - int ret = -ENOMEM; dbg = kzalloc(sizeof(*dbg), GFP_KERNEL); if (!dbg) @@ -183,13 +182,9 @@ int wiidebug_init(struct wiimote_data *wdata) dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR, dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops); - if (!dbg->eeprom) - goto err; dbg->drm = debugfs_create_file("drm", S_IRUSR, dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops); - if (!dbg->drm) - goto err_drm; spin_lock_irqsave(&wdata->state.lock, flags); wdata->debug = dbg; @@ -197,11 +192,6 @@ int wiidebug_init(struct wiimote_data *wdata) return 0; -err_drm: - debugfs_remove(dbg->eeprom); -err: - kfree(dbg); - return ret; } void wiidebug_deinit(struct wiimote_data *wdata) -- cgit From 276e14e6c3993317257e1787e93b7166fbc30905 Mon Sep 17 00:00:00 2001 From: Illia Ostapyshyn Date: Tue, 13 Jun 2023 17:26:00 +0200 Subject: HID: input: Support devices sending Eraser without Invert Some digitizers (notably XP-Pen Artist 24) do not report the Invert usage when erasing. This causes the device to be permanently stuck with the BTN_TOOL_RUBBER tool after sending Eraser, as Invert is the only usage that can release the tool. In this state, Touch and Inrange are no longer reported to userspace, rendering the pen unusable. Prior to commit 87562fcd1342 ("HID: input: remove the need for HID_QUIRK_INVERT"), BTN_TOOL_RUBBER was never set and Eraser events were simply translated into BTN_TOUCH without causing an inconsistent state. Introduce HID_QUIRK_NOINVERT for such digitizers and detect them during hidinput_configure_usage(). This quirk causes the tool to be released as soon as Eraser is reported as not set. Set BTN_TOOL_RUBBER in input->keybit when mapping Eraser. Fixes: 87562fcd1342 ("HID: input: remove the need for HID_QUIRK_INVERT") Co-developed-by: Nils Fuhler Signed-off-by: Nils Fuhler Signed-off-by: Illia Ostapyshyn Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 18 ++++++++++++++++-- include/linux/hid.h | 1 + 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 851ee86eff32..40a5645f8fe8 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -988,6 +988,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel return; case 0x3c: /* Invert */ + device->quirks &= ~HID_QUIRK_NOINVERT; map_key_clear(BTN_TOOL_RUBBER); break; @@ -1013,9 +1014,13 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel case 0x45: /* ERASER */ /* * This event is reported when eraser tip touches the surface. - * Actual eraser (BTN_TOOL_RUBBER) is set by Invert usage when - * tool gets in proximity. + * Actual eraser (BTN_TOOL_RUBBER) is set and released either + * by Invert if tool reports proximity or by Eraser directly. */ + if (!test_bit(BTN_TOOL_RUBBER, input->keybit)) { + device->quirks |= HID_QUIRK_NOINVERT; + set_bit(BTN_TOOL_RUBBER, input->keybit); + } map_key_clear(BTN_TOUCH); break; @@ -1580,6 +1585,15 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct else if (report->tool != BTN_TOOL_RUBBER) /* value is off, tool is not rubber, ignore */ return; + else if (*quirks & HID_QUIRK_NOINVERT && + !test_bit(BTN_TOUCH, input->key)) { + /* + * There is no invert to release the tool, let hid_input + * send BTN_TOUCH with scancode and release the tool after. + */ + hid_report_release_tool(report, input, BTN_TOOL_RUBBER); + return; + } /* let hid-input set BTN_TOUCH */ break; diff --git a/include/linux/hid.h b/include/linux/hid.h index 39e21e3815ad..9e8f87800e21 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -360,6 +360,7 @@ struct hid_item { #define HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP BIT(18) #define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19) #define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20) +#define HID_QUIRK_NOINVERT BIT(21) #define HID_QUIRK_FULLSPEED_INTERVAL BIT(28) #define HID_QUIRK_NO_INIT_REPORTS BIT(29) #define HID_QUIRK_NO_IGNORE BIT(30) -- cgit From 574d06ceb88fb735a7d88c22e6531f4849aada51 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 18 Jun 2023 11:11:58 +0200 Subject: HID: Reorder fields in 'struct hid_input' Group some variables based on their sizes to reduce hole and avoid padding. On x86_64, this shrinks the size of 'struct hid_input' from 72 to 64 bytes. It saves a few bytes of memory and is more cache-line friendly. Signed-off-by: Christophe JAILLET Signed-off-by: Jiri Kosina --- include/linux/hid.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/hid.h b/include/linux/hid.h index 9e8f87800e21..be9e16cb8bd1 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -556,9 +556,9 @@ struct hid_input { struct hid_report *report; struct input_dev *input; const char *name; - bool registered; struct list_head reports; /* the list of reports */ unsigned int application; /* application usage for this input */ + bool registered; }; enum hid_type { -- cgit From a7156d818179ddab36006a33d24182a870a81298 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Tue, 20 Jun 2023 10:50:14 +0200 Subject: HID: logitech-hidpp: Rename HID++ "internal" error constant As per the upstream "hidpp" helpers commit: " There has been some confusion about error value 5 but feature specs that refer to it generally use NOT_ALLOWED. " Signed-off-by: Bastien Nocera Link: https://github.com/mrubli2/hidpp/commit/80c3fecfcd89c5efe0f16feabe90a55ddfd25aaa Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 129b01be488d..3f3ad6a8fedc 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -228,7 +228,7 @@ struct hidpp_device { #define HIDPP20_ERROR_INVALID_ARGS 0x02 #define HIDPP20_ERROR_OUT_OF_RANGE 0x03 #define HIDPP20_ERROR_HW_ERROR 0x04 -#define HIDPP20_ERROR_LOGITECH_INTERNAL 0x05 +#define HIDPP20_ERROR_NOT_ALLOWED 0x05 #define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06 #define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07 #define HIDPP20_ERROR_BUSY 0x08 -- cgit From fadfcf36016100dc9da0f1ab062c758e41f76b11 Mon Sep 17 00:00:00 2001 From: Ivan Orlov Date: Tue, 20 Jun 2023 20:31:42 +0200 Subject: HID: roccat: make all 'class' structures const Now that the driver core allows for struct class to be in read-only memory, making all 'class' structures to be declared at build time placing them into read-only memory, instead of having to be dynamically allocated at load time. Cc: Stefan Achatz Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Suggested-by: Greg Kroah-Hartman Signed-off-by: Ivan Orlov Signed-off-by: Greg Kroah-Hartman Signed-off-by: Jiri Kosina --- drivers/hid/hid-roccat-arvo.c | 20 +++++++++++--------- drivers/hid/hid-roccat-isku.c | 21 ++++++++++++--------- drivers/hid/hid-roccat-kone.c | 24 +++++++++++++----------- drivers/hid/hid-roccat-koneplus.c | 22 ++++++++++++---------- drivers/hid/hid-roccat-konepure.c | 22 ++++++++++++---------- drivers/hid/hid-roccat-kovaplus.c | 22 ++++++++++++---------- drivers/hid/hid-roccat-pyra.c | 22 ++++++++++++---------- drivers/hid/hid-roccat-ryos.c | 20 +++++++++++--------- drivers/hid/hid-roccat-savu.c | 20 +++++++++++--------- drivers/hid/hid-roccat.c | 2 +- include/linux/hid-roccat.h | 2 +- 11 files changed, 108 insertions(+), 89 deletions(-) diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c index ea6b79b3aeeb..d55aaabab1ed 100644 --- a/drivers/hid/hid-roccat-arvo.c +++ b/drivers/hid/hid-roccat-arvo.c @@ -23,8 +23,6 @@ #include "hid-roccat-common.h" #include "hid-roccat-arvo.h" -static struct class *arvo_class; - static ssize_t arvo_sysfs_show_mode_key(struct device *dev, struct device_attribute *attr, char *buf) { @@ -268,6 +266,11 @@ static const struct attribute_group *arvo_groups[] = { NULL, }; +static const struct class arvo_class = { + .name = "arvo", + .dev_groups = arvo_groups, +}; + static int arvo_init_arvo_device_struct(struct usb_device *usb_dev, struct arvo_device *arvo) { @@ -309,7 +312,7 @@ static int arvo_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(arvo_class, hdev, + retval = roccat_connect(&arvo_class, hdev, sizeof(struct arvo_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); @@ -433,21 +436,20 @@ static int __init arvo_init(void) { int retval; - arvo_class = class_create("arvo"); - if (IS_ERR(arvo_class)) - return PTR_ERR(arvo_class); - arvo_class->dev_groups = arvo_groups; + retval = class_register(&arvo_class); + if (retval) + return retval; retval = hid_register_driver(&arvo_driver); if (retval) - class_destroy(arvo_class); + class_unregister(&arvo_class); return retval; } static void __exit arvo_exit(void) { hid_unregister_driver(&arvo_driver); - class_destroy(arvo_class); + class_unregister(&arvo_class); } module_init(arvo_init); diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c index 3903a2cea00c..458060403397 100644 --- a/drivers/hid/hid-roccat-isku.c +++ b/drivers/hid/hid-roccat-isku.c @@ -23,8 +23,6 @@ #include "hid-roccat-common.h" #include "hid-roccat-isku.h" -static struct class *isku_class; - static void isku_profile_activated(struct isku_device *isku, uint new_profile) { isku->actual_profile = new_profile; @@ -248,6 +246,11 @@ static const struct attribute_group *isku_groups[] = { NULL, }; +static const struct class isku_class = { + .name = "isku", + .dev_groups = isku_groups, +}; + static int isku_init_isku_device_struct(struct usb_device *usb_dev, struct isku_device *isku) { @@ -289,7 +292,7 @@ static int isku_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(isku_class, hdev, + retval = roccat_connect(&isku_class, hdev, sizeof(struct isku_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); @@ -435,21 +438,21 @@ static struct hid_driver isku_driver = { static int __init isku_init(void) { int retval; - isku_class = class_create("isku"); - if (IS_ERR(isku_class)) - return PTR_ERR(isku_class); - isku_class->dev_groups = isku_groups; + + retval = class_register(&isku_class); + if (retval) + return retval; retval = hid_register_driver(&isku_driver); if (retval) - class_destroy(isku_class); + class_unregister(&isku_class); return retval; } static void __exit isku_exit(void) { hid_unregister_driver(&isku_driver); - class_destroy(isku_class); + class_unregister(&isku_class); } module_init(isku_init); diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 945ae236fb45..00a1abc7e839 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -89,9 +89,6 @@ static int kone_send(struct usb_device *usb_dev, uint usb_command, return ((len < 0) ? len : ((len != size) ? -EIO : 0)); } -/* kone_class is used for creating sysfs attributes via roccat char device */ -static struct class *kone_class; - static void kone_set_settings_checksum(struct kone_settings *settings) { uint16_t checksum = 0; @@ -657,6 +654,12 @@ static const struct attribute_group *kone_groups[] = { NULL, }; +/* kone_class is used for creating sysfs attributes via roccat char device */ +static const struct class kone_class = { + .name = "kone", + .dev_groups = kone_groups, +}; + static int kone_init_kone_device_struct(struct usb_device *usb_dev, struct kone_device *kone) { @@ -712,8 +715,8 @@ static int kone_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(kone_class, hdev, - sizeof(struct kone_roccat_report)); + retval = roccat_connect(&kone_class, hdev, + sizeof(struct kone_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); /* be tolerant about not getting chrdev */ @@ -890,21 +893,20 @@ static int __init kone_init(void) int retval; /* class name has to be same as driver name */ - kone_class = class_create("kone"); - if (IS_ERR(kone_class)) - return PTR_ERR(kone_class); - kone_class->dev_groups = kone_groups; + retval = class_register(&kone_class); + if (retval) + return retval; retval = hid_register_driver(&kone_driver); if (retval) - class_destroy(kone_class); + class_unregister(&kone_class); return retval; } static void __exit kone_exit(void) { hid_unregister_driver(&kone_driver); - class_destroy(kone_class); + class_unregister(&kone_class); } module_init(kone_init); diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index 97b83b6f53dd..22b895436a7c 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -26,8 +26,6 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4}; -static struct class *koneplus_class; - static void koneplus_profile_activated(struct koneplus_device *koneplus, uint new_profile) { @@ -356,6 +354,11 @@ static const struct attribute_group *koneplus_groups[] = { NULL, }; +static const struct class koneplus_class = { + .name = "koneplus", + .dev_groups = koneplus_groups, +}; + static int koneplus_init_koneplus_device_struct(struct usb_device *usb_dev, struct koneplus_device *koneplus) { @@ -394,8 +397,8 @@ static int koneplus_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(koneplus_class, hdev, - sizeof(struct koneplus_roccat_report)); + retval = roccat_connect(&koneplus_class, hdev, + sizeof(struct koneplus_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); } else { @@ -549,21 +552,20 @@ static int __init koneplus_init(void) int retval; /* class name has to be same as driver name */ - koneplus_class = class_create("koneplus"); - if (IS_ERR(koneplus_class)) - return PTR_ERR(koneplus_class); - koneplus_class->dev_groups = koneplus_groups; + retval = class_register(&koneplus_class); + if (retval) + return retval; retval = hid_register_driver(&koneplus_driver); if (retval) - class_destroy(koneplus_class); + class_unregister(&koneplus_class); return retval; } static void __exit koneplus_exit(void) { hid_unregister_driver(&koneplus_driver); - class_destroy(koneplus_class); + class_unregister(&koneplus_class); } module_init(koneplus_init); diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c index a297756f2410..beca8aef8bbb 100644 --- a/drivers/hid/hid-roccat-konepure.c +++ b/drivers/hid/hid-roccat-konepure.c @@ -36,8 +36,6 @@ struct konepure_mouse_report_button { uint8_t unknown[2]; } __packed; -static struct class *konepure_class; - ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03); ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(actual_profile, 0x05, 0x03); ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile_settings, 0x06, 0x1f); @@ -72,6 +70,11 @@ static const struct attribute_group *konepure_groups[] = { NULL, }; +static const struct class konepure_class = { + .name = "konepure", + .dev_groups = konepure_groups, +}; + static int konepure_init_specials(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -98,8 +101,8 @@ static int konepure_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(konepure_class, hdev, - sizeof(struct konepure_mouse_report_button)); + retval = roccat_connect(&konepure_class, hdev, + sizeof(struct konepure_mouse_report_button)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); } else { @@ -207,21 +210,20 @@ static int __init konepure_init(void) { int retval; - konepure_class = class_create("konepure"); - if (IS_ERR(konepure_class)) - return PTR_ERR(konepure_class); - konepure_class->dev_groups = konepure_groups; + retval = class_register(&konepure_class); + if (retval) + return retval; retval = hid_register_driver(&konepure_driver); if (retval) - class_destroy(konepure_class); + class_unregister(&konepure_class); return retval; } static void __exit konepure_exit(void) { hid_unregister_driver(&konepure_driver); - class_destroy(konepure_class); + class_unregister(&konepure_class); } module_init(konepure_init); diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index 1a1d96e11683..86af538c10d6 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -24,8 +24,6 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4}; -static struct class *kovaplus_class; - static uint kovaplus_convert_event_cpi(uint value) { return (value == 7 ? 4 : (value == 4 ? 3 : value)); @@ -409,6 +407,11 @@ static const struct attribute_group *kovaplus_groups[] = { NULL, }; +static const struct class kovaplus_class = { + .name = "kovaplus", + .dev_groups = kovaplus_groups, +}; + static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev, struct kovaplus_device *kovaplus) { @@ -463,8 +466,8 @@ static int kovaplus_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(kovaplus_class, hdev, - sizeof(struct kovaplus_roccat_report)); + retval = roccat_connect(&kovaplus_class, hdev, + sizeof(struct kovaplus_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); } else { @@ -638,21 +641,20 @@ static int __init kovaplus_init(void) { int retval; - kovaplus_class = class_create("kovaplus"); - if (IS_ERR(kovaplus_class)) - return PTR_ERR(kovaplus_class); - kovaplus_class->dev_groups = kovaplus_groups; + retval = class_register(&kovaplus_class); + if (retval) + return retval; retval = hid_register_driver(&kovaplus_driver); if (retval) - class_destroy(kovaplus_class); + class_unregister(&kovaplus_class); return retval; } static void __exit kovaplus_exit(void) { hid_unregister_driver(&kovaplus_driver); - class_destroy(kovaplus_class); + class_unregister(&kovaplus_class); } module_init(kovaplus_init); diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index 15528c3b013c..5663b9cd9c69 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -26,9 +26,6 @@ static uint profile_numbers[5] = {0, 1, 2, 3, 4}; -/* pyra_class is used for creating sysfs attributes via roccat char device */ -static struct class *pyra_class; - static void profile_activated(struct pyra_device *pyra, unsigned int new_profile) { @@ -366,6 +363,12 @@ static const struct attribute_group *pyra_groups[] = { NULL, }; +/* pyra_class is used for creating sysfs attributes via roccat char device */ +static const struct class pyra_class = { + .name = "pyra", + .dev_groups = pyra_groups, +}; + static int pyra_init_pyra_device_struct(struct usb_device *usb_dev, struct pyra_device *pyra) { @@ -413,7 +416,7 @@ static int pyra_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(pyra_class, hdev, + retval = roccat_connect(&pyra_class, hdev, sizeof(struct pyra_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); @@ -585,21 +588,20 @@ static int __init pyra_init(void) int retval; /* class name has to be same as driver name */ - pyra_class = class_create("pyra"); - if (IS_ERR(pyra_class)) - return PTR_ERR(pyra_class); - pyra_class->dev_groups = pyra_groups; + retval = class_register(&pyra_class); + if (retval) + return retval; retval = hid_register_driver(&pyra_driver); if (retval) - class_destroy(pyra_class); + class_unregister(&pyra_class); return retval; } static void __exit pyra_exit(void) { hid_unregister_driver(&pyra_driver); - class_destroy(pyra_class); + class_unregister(&pyra_class); } module_init(pyra_init); diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c index 0eb17a3b925d..57714a4525e2 100644 --- a/drivers/hid/hid-roccat-ryos.c +++ b/drivers/hid/hid-roccat-ryos.c @@ -28,8 +28,6 @@ struct ryos_report_special { uint8_t data[4]; } __packed; -static struct class *ryos_class; - ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03); ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x05, 0x03); ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_primary, 0x06, 0x7d); @@ -80,6 +78,11 @@ static const struct attribute_group *ryos_groups[] = { NULL, }; +static const struct class ryos_class = { + .name = "ryos", + .dev_groups = ryos_groups, +}; + static int ryos_init_specials(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -106,7 +109,7 @@ static int ryos_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(ryos_class, hdev, + retval = roccat_connect(&ryos_class, hdev, sizeof(struct ryos_report_special)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); @@ -216,21 +219,20 @@ static int __init ryos_init(void) { int retval; - ryos_class = class_create("ryos"); - if (IS_ERR(ryos_class)) - return PTR_ERR(ryos_class); - ryos_class->dev_groups = ryos_groups; + retval = class_register(&ryos_class); + if (retval) + return retval; retval = hid_register_driver(&ryos_driver); if (retval) - class_destroy(ryos_class); + class_unregister(&ryos_class); return retval; } static void __exit ryos_exit(void) { hid_unregister_driver(&ryos_driver); - class_destroy(ryos_class); + class_unregister(&ryos_class); } module_init(ryos_init); diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c index 93be7acef673..2baa47a0efc5 100644 --- a/drivers/hid/hid-roccat-savu.c +++ b/drivers/hid/hid-roccat-savu.c @@ -22,8 +22,6 @@ #include "hid-roccat-common.h" #include "hid-roccat-savu.h" -static struct class *savu_class; - ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x4, 0x03); ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x5, 0x03); ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(general, 0x6, 0x10); @@ -52,6 +50,11 @@ static const struct attribute_group *savu_groups[] = { NULL, }; +static const struct class savu_class = { + .name = "savu", + .dev_groups = savu_groups, +}; + static int savu_init_specials(struct hid_device *hdev) { struct usb_interface *intf = to_usb_interface(hdev->dev.parent); @@ -78,7 +81,7 @@ static int savu_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(savu_class, hdev, + retval = roccat_connect(&savu_class, hdev, sizeof(struct savu_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); @@ -204,21 +207,20 @@ static int __init savu_init(void) { int retval; - savu_class = class_create("savu"); - if (IS_ERR(savu_class)) - return PTR_ERR(savu_class); - savu_class->dev_groups = savu_groups; + retval = class_register(&savu_class); + if (retval) + return retval; retval = hid_register_driver(&savu_driver); if (retval) - class_destroy(savu_class); + class_unregister(&savu_class); return retval; } static void __exit savu_exit(void) { hid_unregister_driver(&savu_driver); - class_destroy(savu_class); + class_unregister(&savu_class); } module_init(savu_init); diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index 6da80e442fdd..c7f7562e22e5 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -295,7 +295,7 @@ EXPORT_SYMBOL_GPL(roccat_report_event); * Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on * success, a negative error code on failure. */ -int roccat_connect(struct class *klass, struct hid_device *hid, int report_size) +int roccat_connect(const struct class *klass, struct hid_device *hid, int report_size) { unsigned int minor; struct roccat_device *device; diff --git a/include/linux/hid-roccat.h b/include/linux/hid-roccat.h index 3214fb0815fc..753654fff07f 100644 --- a/include/linux/hid-roccat.h +++ b/include/linux/hid-roccat.h @@ -16,7 +16,7 @@ #ifdef __KERNEL__ -int roccat_connect(struct class *klass, struct hid_device *hid, +int roccat_connect(const struct class *klass, struct hid_device *hid, int report_size); void roccat_disconnect(int minor); int roccat_report_event(int minor, u8 const *data); -- cgit From 21168bdba6eaa62b799c32a227a5f3915cebdaa6 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Tue, 20 Jun 2023 20:31:43 +0200 Subject: HID: hidraw: make hidraw_class structure const Now that the driver core allows for struct class to be in read-only memory, making all 'class' structures to be declared at build time placing them into read-only memory, instead of having to be dynamically allocated at load time. Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: linux-input@vger.kernel.org Cc: Ivan Orlov Signed-off-by: Greg Kroah-Hartman Signed-off-by: Jiri Kosina --- drivers/hid/hidraw.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index e63c56a0d57f..13c8dd8cd350 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -32,7 +32,9 @@ static int hidraw_major; static struct cdev hidraw_cdev; -static struct class *hidraw_class; +static const struct class hidraw_class = { + .name = "hidraw", +}; static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES]; static DECLARE_RWSEM(minors_rwsem); @@ -329,7 +331,7 @@ static void drop_ref(struct hidraw *hidraw, int exists_bit) hid_hw_close(hidraw->hid); wake_up_interruptible(&hidraw->wait); } - device_destroy(hidraw_class, + device_destroy(&hidraw_class, MKDEV(hidraw_major, hidraw->minor)); } else { --hidraw->open; @@ -569,7 +571,7 @@ int hidraw_connect(struct hid_device *hid) goto out; } - dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor), + dev->dev = device_create(&hidraw_class, &hid->dev, MKDEV(hidraw_major, minor), NULL, "%s%d", "hidraw", minor); if (IS_ERR(dev->dev)) { @@ -623,11 +625,9 @@ int __init hidraw_init(void) hidraw_major = MAJOR(dev_id); - hidraw_class = class_create("hidraw"); - if (IS_ERR(hidraw_class)) { - result = PTR_ERR(hidraw_class); + result = class_register(&hidraw_class); + if (result) goto error_cdev; - } cdev_init(&hidraw_cdev, &hidraw_ops); result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES); @@ -639,7 +639,7 @@ out: return result; error_class: - class_destroy(hidraw_class); + class_unregister(&hidraw_class); error_cdev: unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); goto out; @@ -650,7 +650,7 @@ void hidraw_exit(void) dev_t dev_id = MKDEV(hidraw_major, 0); cdev_del(&hidraw_cdev); - class_destroy(hidraw_class); + class_unregister(&hidraw_class); unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES); } -- cgit From 9c34660ee6d5f7a79c1519d7a865ffba9bf70e0c Mon Sep 17 00:00:00 2001 From: Nimish Gåtam Date: Sun, 25 Jun 2023 12:44:24 +0200 Subject: HID: input: Fix Apple Magic Trackpad 1 Bluetooth disconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When connecting the Trackpad 1 via any bluetooth adapter, the device disconnects whenever the battery level is queried. It reconnects again after a few seconds, but it's an incredibly jarring experience since it will just cut out in the middle of mouse operations. This patch ignores querying the battery and avoids disconnects. This bug is reported here: - https://bugzilla.kernel.org/show_bug.cgi?id=204589 - https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1834085 It looks like a similar approach (disabling battery checking) was also suggested for the Apple Magic Mouse before a custom driver was created: https://bugzilla.kernel.org/show_bug.cgi?id=103631 Signed-off-by: Nimish Gåtam Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 851ee86eff32..96659a7fb463 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -358,6 +358,9 @@ static const struct hid_device_id hid_battery_quirks[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI), HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_MAGICTRACKPAD), + HID_BATTERY_QUIRK_IGNORE }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084), HID_BATTERY_QUIRK_IGNORE }, -- cgit From a0c76896c3fbdc2e7c70b980b3bb38a6ac445971 Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Mon, 3 Jul 2023 20:10:21 +0200 Subject: HID: steelseries: Add support for Arctis 1 XBox Add support for the Steelseries Arctis 1 XBox headset. This driver will export the battery information from the headset, as well as the "wireless_status" property. Signed-off-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 6 +- drivers/hid/hid-steelseries.c | 311 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 300 insertions(+), 17 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e11c1c803676..5afdeffa6ba1 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1066,9 +1066,11 @@ config STEAM_FF Deck. config HID_STEELSERIES - tristate "Steelseries SRW-S1 steering wheel support" + tristate "Steelseries devices support" + depends on USB_HID help - Support for Steelseries SRW-S1 steering wheel + Support for Steelseries SRW-S1 steering wheel, and the Steelseries + Arctis 1 Wireless for XBox headset. config HID_SUNPLUS tristate "Sunplus wireless desktop" diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index aae3afc4107a..495377686123 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * HID driver for Steelseries SRW-S1 + * HID driver for Steelseries devices * * Copyright (c) 2013 Simon Wood + * Copyright (c) 2023 Bastien Nocera */ /* @@ -11,10 +12,28 @@ #include #include #include +#include #include #include "hid-ids.h" +#define STEELSERIES_SRWS1 BIT(0) +#define STEELSERIES_ARCTIS_1 BIT(1) + +struct steelseries_device { + struct hid_device *hdev; + unsigned long quirks; + + struct delayed_work battery_work; + spinlock_t lock; + bool removed; + + struct power_supply_desc battery_desc; + struct power_supply *battery; + uint8_t battery_capacity; + bool headset_connected; +}; + #if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) #define SRWS1_NUMBER_LEDS 15 @@ -353,9 +372,211 @@ static void steelseries_srws1_remove(struct hid_device *hdev) } #endif +#define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 + +#define ARCTIS_1_BATTERY_RESPONSE_LEN 8 +const char arctis_1_battery_request[] = { 0x06, 0x12 }; + +static int steelseries_headset_arctis_1_fetch_battery(struct hid_device *hdev) +{ + u8 *write_buf; + int ret; + + /* Request battery information */ + write_buf = kmemdup(arctis_1_battery_request, sizeof(arctis_1_battery_request), GFP_KERNEL); + if (!write_buf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, arctis_1_battery_request[0], + write_buf, sizeof(arctis_1_battery_request), + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + if (ret < sizeof(arctis_1_battery_request)) { + hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret); + ret = -ENODATA; + } + kfree(write_buf); + return ret; +} + +static void steelseries_headset_fetch_battery(struct hid_device *hdev) +{ + struct steelseries_device *sd = hid_get_drvdata(hdev); + int ret = 0; + + if (sd->quirks & STEELSERIES_ARCTIS_1) + ret = steelseries_headset_arctis_1_fetch_battery(hdev); + + if (ret < 0) + hid_dbg(hdev, + "Battery query failed (err: %d)\n", ret); +} + +static void steelseries_headset_battery_timer_tick(struct work_struct *work) +{ + struct steelseries_device *sd = container_of(work, + struct steelseries_device, battery_work.work); + struct hid_device *hdev = sd->hdev; + + steelseries_headset_fetch_battery(hdev); +} + +static int steelseries_headset_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct steelseries_device *sd = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = sd->headset_connected ? + POWER_SUPPLY_STATUS_DISCHARGING : + POWER_SUPPLY_STATUS_UNKNOWN; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = sd->battery_capacity; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static void +steelseries_headset_set_wireless_status(struct hid_device *hdev, + bool connected) +{ + struct usb_interface *intf; + + if (!hid_is_usb(hdev)) + return; + + intf = to_usb_interface(hdev->dev.parent); + usb_set_wireless_status(intf, connected ? + USB_WIRELESS_STATUS_CONNECTED : + USB_WIRELESS_STATUS_DISCONNECTED); +} + +static enum power_supply_property steelseries_headset_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_CAPACITY, +}; + +static int steelseries_headset_battery_register(struct steelseries_device *sd) +{ + static atomic_t battery_no = ATOMIC_INIT(0); + struct power_supply_config battery_cfg = { .drv_data = sd, }; + unsigned long n; + int ret; + + sd->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; + sd->battery_desc.properties = steelseries_headset_battery_props; + sd->battery_desc.num_properties = ARRAY_SIZE(steelseries_headset_battery_props); + sd->battery_desc.get_property = steelseries_headset_battery_get_property; + sd->battery_desc.use_for_apm = 0; + n = atomic_inc_return(&battery_no) - 1; + sd->battery_desc.name = devm_kasprintf(&sd->hdev->dev, GFP_KERNEL, + "steelseries_headset_battery_%ld", n); + if (!sd->battery_desc.name) + return -ENOMEM; + + /* avoid the warning of 0% battery while waiting for the first info */ + steelseries_headset_set_wireless_status(sd->hdev, false); + sd->battery_capacity = 100; + + sd->battery = devm_power_supply_register(&sd->hdev->dev, + &sd->battery_desc, &battery_cfg); + if (IS_ERR(sd->battery)) { + ret = PTR_ERR(sd->battery); + hid_err(sd->hdev, + "%s:power_supply_register failed with error %d\n", + __func__, ret); + return ret; + } + power_supply_powers(sd->battery, &sd->hdev->dev); + + INIT_DELAYED_WORK(&sd->battery_work, steelseries_headset_battery_timer_tick); + steelseries_headset_fetch_battery(sd->hdev); + + return 0; +} + +static int steelseries_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct steelseries_device *sd; + int ret; + + sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + hid_set_drvdata(hdev, sd); + sd->hdev = hdev; + sd->quirks = id->driver_data; + + if (sd->quirks & STEELSERIES_SRWS1) { +#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ + (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) + return steelseries_srws1_probe(hdev, id); +#else + return -ENODEV; +#endif + } + + ret = hid_parse(hdev); + if (ret) + return ret; + + spin_lock_init(&sd->lock); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + return ret; + + if (steelseries_headset_battery_register(sd) < 0) + hid_err(sd->hdev, + "Failed to register battery for headset\n"); + + return ret; +} + +static void steelseries_remove(struct hid_device *hdev) +{ + struct steelseries_device *sd = hid_get_drvdata(hdev); + unsigned long flags; + + if (sd->quirks & STEELSERIES_SRWS1) { +#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ + (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) + steelseries_srws1_remove(hdev); +#endif + return; + } + + spin_lock_irqsave(&sd->lock, flags); + sd->removed = true; + spin_unlock_irqrestore(&sd->lock, flags); + + cancel_delayed_work_sync(&sd->battery_work); + + hid_hw_stop(hdev); +} + static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { + if (hdev->vendor != USB_VENDOR_ID_STEELSERIES || + hdev->product != USB_DEVICE_ID_STEELSERIES_SRWS1) + return rdesc; + if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8 && rdesc[29] == 0xbb && rdesc[40] == 0xc5) { hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n"); @@ -365,22 +586,82 @@ static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc return rdesc; } -static const struct hid_device_id steelseries_srws1_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) }, +static int steelseries_headset_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *read_buf, + int size) +{ + struct steelseries_device *sd = hid_get_drvdata(hdev); + int capacity = sd->battery_capacity; + bool connected = sd->headset_connected; + unsigned long flags; + + /* Not a headset */ + if (sd->quirks & STEELSERIES_SRWS1) + return 0; + + if (sd->quirks & STEELSERIES_ARCTIS_1) { + hid_dbg(sd->hdev, + "Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf); + if (size < ARCTIS_1_BATTERY_RESPONSE_LEN || + memcmp (read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) + return 0; + if (read_buf[2] == 0x01) { + connected = false; + capacity = 100; + } else { + connected = true; + capacity = read_buf[3]; + } + } + + if (connected != sd->headset_connected) { + hid_dbg(sd->hdev, + "Connected status changed from %sconnected to %sconnected\n", + sd->headset_connected ? "" : "not ", + connected ? "" : "not "); + sd->headset_connected = connected; + steelseries_headset_set_wireless_status(hdev, connected); + } + + if (capacity != sd->battery_capacity) { + hid_dbg(sd->hdev, + "Battery capacity changed from %d%% to %d%%\n", + sd->battery_capacity, capacity); + sd->battery_capacity = capacity; + power_supply_changed(sd->battery); + } + + spin_lock_irqsave(&sd->lock, flags); + if (!sd->removed) + schedule_delayed_work(&sd->battery_work, + msecs_to_jiffies(STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS)); + spin_unlock_irqrestore(&sd->lock, flags); + + return 0; +} + +static const struct hid_device_id steelseries_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1), + .driver_data = STEELSERIES_SRWS1 }, + + { /* SteelSeries Arctis 1 Wireless for XBox */ + HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, 0x12b6), + .driver_data = STEELSERIES_ARCTIS_1 }, + { } }; -MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices); - -static struct hid_driver steelseries_srws1_driver = { - .name = "steelseries_srws1", - .id_table = steelseries_srws1_devices, -#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \ - (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES)) - .probe = steelseries_srws1_probe, - .remove = steelseries_srws1_remove, -#endif - .report_fixup = steelseries_srws1_report_fixup +MODULE_DEVICE_TABLE(hid, steelseries_devices); + +static struct hid_driver steelseries_driver = { + .name = "steelseries", + .id_table = steelseries_devices, + .probe = steelseries_probe, + .remove = steelseries_remove, + .report_fixup = steelseries_srws1_report_fixup, + .raw_event = steelseries_headset_raw_event, }; -module_hid_driver(steelseries_srws1_driver); +module_hid_driver(steelseries_driver); MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bastien Nocera "); +MODULE_AUTHOR("Simon Wood "); -- cgit From c05b8a939a179aca7ee9eb2cf03a322e83522e07 Mon Sep 17 00:00:00 2001 From: Mavroudis Chatzilazaridis Date: Sun, 16 Jul 2023 18:23:52 +0000 Subject: HID: logitech-hidpp: Add support for the Pro X Superlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for the Pro X Superlight over wired USB. The device now reports the status of its battery. Co-developed-by: Filipe Laíns Signed-off-by: Filipe Laíns Signed-off-by: Mavroudis Chatzilazaridis Reviewed-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 3f3ad6a8fedc..6ad344ba0d1f 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4620,6 +4620,8 @@ static const struct hid_device_id hidpp_devices[] = { .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS }, { /* Logitech G Pro Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) }, + { /* Logitech G Pro X Superlight Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) }, { /* G935 Gaming Headset */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0x0a87), -- cgit From 9d1bd9346241cd6963b58da7ffb7ed303285f684 Mon Sep 17 00:00:00 2001 From: Mavroudis Chatzilazaridis Date: Sun, 16 Jul 2023 18:23:44 +0000 Subject: HID: logitech-dj: Add support for a new lightspeed receiver iteration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lightspeed receiver for the Pro X Superlight uses 13 byte mouse reports without a report id. The workaround for such cases has been adjusted to handle these larger packets. The device now reports the status of its battery in wireless mode and libratbag now recognizes the device and it can be configured with Piper. https://github.com/libratbag/libratbag/pull/1122 Co-developed-by: Filipe Laíns Signed-off-by: Filipe Laíns Signed-off-by: Mavroudis Chatzilazaridis Reviewed-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-logitech-dj.c | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8a310f8ff20f..7ac7debe98f1 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -866,6 +866,7 @@ #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_2 0xc534 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f +#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc547 #define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a #define USB_DEVICE_ID_SPACETRAVELLER 0xc623 #define USB_DEVICE_ID_SPACENAVIGATOR 0xc626 diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 62180414efcc..fef67da0de53 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1692,11 +1692,12 @@ static int logi_dj_raw_event(struct hid_device *hdev, } /* * Mouse-only receivers send unnumbered mouse data. The 27 MHz - * receiver uses 6 byte packets, the nano receiver 8 bytes. + * receiver uses 6 byte packets, the nano receiver 8 bytes, + * the lightspeed receiver (Pro X Superlight) 13 bytes. */ if (djrcv_dev->unnumbered_application == HID_GD_MOUSE && - size <= 8) { - u8 mouse_report[9]; + size <= 13){ + u8 mouse_report[14]; /* Prepend report id */ mouse_report[0] = REPORT_TYPE_MOUSE; @@ -1980,6 +1981,10 @@ static const struct hid_device_id logi_dj_receivers[] = { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1), .driver_data = recvr_type_gaming_hidpp}, + { /* Logitech lightspeed receiver (0xc547) */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, + USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2), + .driver_data = recvr_type_gaming_hidpp}, { /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER), -- cgit From 24175157b8520de2ed6219676bddb08c846f2d0d Mon Sep 17 00:00:00 2001 From: Fabio Baltieri Date: Sun, 16 Jul 2023 20:48:34 +0000 Subject: HID: hid-google-stadiaff: add support for Stadia force feedback Add a hid-google-stadiaff module to support rumble based force feedback on the Google Stadia controller. This works using the HID output endpoint exposed on both the USB and BLE interface. Signed-off-by: Fabio Baltieri Reviewed-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 7 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-google-stadiaff.c | 158 ++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-ids.h | 1 + 4 files changed, 167 insertions(+) create mode 100644 drivers/hid/hid-google-stadiaff.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e11c1c803676..545e81c8f359 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -412,6 +412,13 @@ config HID_GOOGLE_HAMMER help Say Y here if you have a Google Hammer device. +config HID_GOOGLE_STADIA_FF + tristate "Google Stadia force feedback" + select INPUT_FF_MEMLESS + help + Say Y here if you want to enable force feedback support for the Google + Stadia controller. + config HID_VIVALDI tristate "Vivaldi Keyboard" select HID_VIVALDI_COMMON diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 7a9e160158f7..8a06d0f840bc 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_HID_GFRM) += hid-gfrm.o obj-$(CONFIG_HID_GLORIOUS) += hid-glorious.o obj-$(CONFIG_HID_VIVALDI_COMMON) += hid-vivaldi-common.o obj-$(CONFIG_HID_GOOGLE_HAMMER) += hid-google-hammer.o +obj-$(CONFIG_HID_GOOGLE_STADIA_FF) += hid-google-stadiaff.o obj-$(CONFIG_HID_VIVALDI) += hid-vivaldi.o obj-$(CONFIG_HID_GT683R) += hid-gt683r.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o diff --git a/drivers/hid/hid-google-stadiaff.c b/drivers/hid/hid-google-stadiaff.c new file mode 100644 index 000000000000..3731575562ab --- /dev/null +++ b/drivers/hid/hid-google-stadiaff.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Stadia controller rumble support. + * + * Copyright 2023 Google LLC + */ + +#include +#include +#include +#include + +#include "hid-ids.h" + +#define STADIA_FF_REPORT_ID 5 + +struct stadiaff_device { + struct hid_device *hid; + struct hid_report *report; + spinlock_t lock; + bool removed; + uint16_t strong_magnitude; + uint16_t weak_magnitude; + struct work_struct work; +}; + +static void stadiaff_work(struct work_struct *work) +{ + struct stadiaff_device *stadiaff = + container_of(work, struct stadiaff_device, work); + struct hid_field *rumble_field = stadiaff->report->field[0]; + unsigned long flags; + + spin_lock_irqsave(&stadiaff->lock, flags); + rumble_field->value[0] = stadiaff->strong_magnitude; + rumble_field->value[1] = stadiaff->weak_magnitude; + spin_unlock_irqrestore(&stadiaff->lock, flags); + + hid_hw_request(stadiaff->hid, stadiaff->report, HID_REQ_SET_REPORT); +} + +static int stadiaff_play(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hid = input_get_drvdata(dev); + struct stadiaff_device *stadiaff = hid_get_drvdata(hid); + unsigned long flags; + + spin_lock_irqsave(&stadiaff->lock, flags); + if (!stadiaff->removed) { + stadiaff->strong_magnitude = effect->u.rumble.strong_magnitude; + stadiaff->weak_magnitude = effect->u.rumble.weak_magnitude; + schedule_work(&stadiaff->work); + } + spin_unlock_irqrestore(&stadiaff->lock, flags); + + return 0; +} + +static int stadiaff_init(struct hid_device *hid) +{ + struct stadiaff_device *stadiaff; + struct hid_report *report; + struct hid_input *hidinput; + struct input_dev *dev; + int error; + + if (list_empty(&hid->inputs)) { + hid_err(hid, "no inputs found\n"); + return -ENODEV; + } + hidinput = list_entry(hid->inputs.next, struct hid_input, list); + dev = hidinput->input; + + report = hid_validate_values(hid, HID_OUTPUT_REPORT, + STADIA_FF_REPORT_ID, 0, 2); + if (!report) + return -ENODEV; + + stadiaff = devm_kzalloc(&hid->dev, sizeof(struct stadiaff_device), + GFP_KERNEL); + if (!stadiaff) + return -ENOMEM; + + hid_set_drvdata(hid, stadiaff); + + input_set_capability(dev, EV_FF, FF_RUMBLE); + + error = input_ff_create_memless(dev, NULL, stadiaff_play); + if (error) + return error; + + stadiaff->removed = false; + stadiaff->hid = hid; + stadiaff->report = report; + INIT_WORK(&stadiaff->work, stadiaff_work); + spin_lock_init(&stadiaff->lock); + + hid_info(hid, "Force Feedback for Google Stadia controller\n"); + + return 0; +} + +static int stadia_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + ret = stadiaff_init(hdev); + if (ret) { + hid_err(hdev, "force feedback init failed\n"); + hid_hw_stop(hdev); + return ret; + } + + return 0; +} + +static void stadia_remove(struct hid_device *hid) +{ + struct stadiaff_device *stadiaff = hid_get_drvdata(hid); + unsigned long flags; + + spin_lock_irqsave(&stadiaff->lock, flags); + stadiaff->removed = true; + spin_unlock_irqrestore(&stadiaff->lock, flags); + + cancel_work_sync(&stadiaff->work); + hid_hw_stop(hid); +} + +static const struct hid_device_id stadia_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) }, + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STADIA) }, + { } +}; +MODULE_DEVICE_TABLE(hid, stadia_devices); + +static struct hid_driver stadia_driver = { + .name = "stadia", + .id_table = stadia_devices, + .probe = stadia_probe, + .remove = stadia_remove, +}; +module_hid_driver(stadia_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8a310f8ff20f..42c43d309f98 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -531,6 +531,7 @@ #define USB_DEVICE_ID_GOOGLE_DON 0x5050 #define USB_DEVICE_ID_GOOGLE_EEL 0x5057 #define USB_DEVICE_ID_GOOGLE_JEWEL 0x5061 +#define USB_DEVICE_ID_GOOGLE_STADIA 0x9400 #define USB_VENDOR_ID_GOTOP 0x08f2 #define USB_DEVICE_ID_SUPER_Q2 0x007f -- cgit From cb818a047f2b95f3d9e08568ff7f8f513832ff2f Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Mon, 7 Aug 2023 09:36:18 -0700 Subject: HID: nvidia-shield: Remove led_classdev_unregister in thunderstrike_create Avoid calling thunderstrike_led_set_brightness from thunderstrike_create when led_classdev_unregister is called. led_classdev_unregister was called from thunderstrike_create in the error path. Calling thunderstrike_led_set_brightness in this situation is unsafe. Fixes: f88af60e74a5 ("HID: nvidia-shield: Support LED functionality for Thunderstrike") Signed-off-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- drivers/hid/hid-nvidia-shield.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index a928ad2be62d..4e183650c447 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -513,21 +513,22 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev) hid_set_drvdata(hdev, shield_dev); + ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect); + if (IS_ERR(ts->haptics_dev)) + return ERR_CAST(ts->haptics_dev); + ret = thunderstrike_led_create(ts); if (ret) { hid_err(hdev, "Failed to create Thunderstrike LED instance\n"); - return ERR_PTR(ret); - } - - ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect); - if (IS_ERR(ts->haptics_dev)) goto err; + } hid_info(hdev, "Registered Thunderstrike controller\n"); return shield_dev; err: - led_classdev_unregister(&ts->led_dev); + if (ts->haptics_dev) + input_unregister_device(ts->haptics_dev); return ERR_CAST(ts->haptics_dev); } -- cgit From 3ab196f882377ed5c2a946cf9f7ede8be4727f44 Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Mon, 7 Aug 2023 09:36:19 -0700 Subject: HID: nvidia-shield: Add battery support for Thunderstrike Use power supply API to expose battery information about connected Thunderstrike controllers to the system. Provide information on battery capacity, charge status, charger type, voltage, and temperature. Signed-off-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- drivers/hid/hid-nvidia-shield.c | 418 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 401 insertions(+), 17 deletions(-) diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index 4e183650c447..1612de3ef4c5 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -6,11 +6,15 @@ */ #include +#include #include #include +#include #include #include +#include #include +#include #include #include "hid-ids.h" @@ -30,6 +34,8 @@ enum { enum { SHIELD_FW_VERSION_INITIALIZED = 0, SHIELD_BOARD_INFO_INITIALIZED, + SHIELD_BATTERY_STATS_INITIALIZED, + SHIELD_CHARGER_STATE_INITIALIZED, }; enum { @@ -37,6 +43,7 @@ enum { THUNDERSTRIKE_BOARD_INFO_UPDATE, THUNDERSTRIKE_HAPTICS_UPDATE, THUNDERSTRIKE_LED_UPDATE, + THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, }; enum { @@ -48,10 +55,46 @@ enum { enum { THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION = 1, THUNDERSTRIKE_HOSTCMD_ID_LED = 6, + THUNDERSTRIKE_HOSTCMD_ID_BATTERY, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO = 16, THUNDERSTRIKE_HOSTCMD_ID_USB_INIT = 53, THUNDERSTRIKE_HOSTCMD_ID_HAPTICS = 57, - THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT = 58, + THUNDERSTRIKE_HOSTCMD_ID_CHARGER, +}; + +struct power_supply_dev { + struct power_supply *psy; + struct power_supply_desc desc; +}; + +struct thunderstrike_psy_prop_values { + int voltage_min; + int voltage_now; + int voltage_avg; + int voltage_boot; + int capacity; + int status; + int charge_type; + int temp; +}; + +static const enum power_supply_property thunderstrike_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_VOLTAGE_BOOT, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_MAX, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, }; enum thunderstrike_led_state { @@ -60,6 +103,38 @@ enum thunderstrike_led_state { } __packed; static_assert(sizeof(enum thunderstrike_led_state) == 1); +struct thunderstrike_hostcmd_battery { + __le16 voltage_avg; + u8 reserved_at_10; + __le16 thermistor; + __le16 voltage_min; + __le16 voltage_boot; + __le16 voltage_now; + u8 capacity; +} __packed; + +enum thunderstrike_charger_type { + THUNDERSTRIKE_CHARGER_TYPE_NONE = 0, + THUNDERSTRIKE_CHARGER_TYPE_TRICKLE, + THUNDERSTRIKE_CHARGER_TYPE_NORMAL, +} __packed; +static_assert(sizeof(enum thunderstrike_charger_type) == 1); + +enum thunderstrike_charger_state { + THUNDERSTRIKE_CHARGER_STATE_UNKNOWN = 0, + THUNDERSTRIKE_CHARGER_STATE_DISABLED, + THUNDERSTRIKE_CHARGER_STATE_CHARGING, + THUNDERSTRIKE_CHARGER_STATE_FULL, + THUNDERSTRIKE_CHARGER_STATE_FAILED = 8, +} __packed; +static_assert(sizeof(enum thunderstrike_charger_state) == 1); + +struct thunderstrike_hostcmd_charger { + u8 connected; + enum thunderstrike_charger_type type; + enum thunderstrike_charger_state state; +} __packed; + struct thunderstrike_hostcmd_board_info { __le16 revision; __le16 serial[7]; @@ -80,6 +155,8 @@ struct thunderstrike_hostcmd_resp_report { struct thunderstrike_hostcmd_haptics motors; __le16 fw_version; enum thunderstrike_led_state led_state; + struct thunderstrike_hostcmd_battery battery; + struct thunderstrike_hostcmd_charger charger; u8 payload[30]; } __packed; } __packed; @@ -109,6 +186,7 @@ static_assert(sizeof(struct thunderstrike_hostcmd_req_report) == /* Common struct for shield accessories. */ struct shield_device { struct hid_device *hdev; + struct power_supply_dev battery_dev; unsigned long initialized_flags; const char *codename; @@ -119,9 +197,17 @@ struct shield_device { } board_info; }; +/* + * Non-trivial to uniquely identify Thunderstrike controllers at initialization + * time. Use an ID allocator to help with this. + */ +static DEFINE_IDA(thunderstrike_ida); + struct thunderstrike { struct shield_device base; + int id; + /* Sub-devices */ struct input_dev *haptics_dev; struct led_classdev led_dev; @@ -133,6 +219,9 @@ struct thunderstrike { spinlock_t haptics_update_lock; u8 led_state : 1; enum thunderstrike_led_state led_value; + struct thunderstrike_psy_prop_values psy_stats; + spinlock_t psy_stats_lock; + struct timer_list psy_stats_timer; struct work_struct hostcmd_req_work; }; @@ -247,6 +336,16 @@ static void thunderstrike_hostcmd_req_work_handler(struct work_struct *work) thunderstrike_send_hostcmd_request(ts); } + if (test_and_clear_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags)) { + thunderstrike_hostcmd_req_report_init( + report, THUNDERSTRIKE_HOSTCMD_ID_BATTERY); + thunderstrike_send_hostcmd_request(ts); + + thunderstrike_hostcmd_req_report_init( + report, THUNDERSTRIKE_HOSTCMD_ID_CHARGER); + thunderstrike_send_hostcmd_request(ts); + } + if (test_and_clear_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags)) { thunderstrike_hostcmd_req_report_init( report, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO); @@ -352,6 +451,93 @@ static void thunderstrike_led_set_brightness(struct led_classdev *led, schedule_work(&ts->hostcmd_req_work); } +static int thunderstrike_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct shield_device *shield_dev = power_supply_get_drvdata(psy); + struct thunderstrike_psy_prop_values prop_values; + struct thunderstrike *ts; + int ret = 0; + + ts = container_of(shield_dev, struct thunderstrike, base); + spin_lock(&ts->psy_stats_lock); + prop_values = ts->psy_stats; + spin_unlock(&ts->psy_stats_lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = prop_values.status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = prop_values.charge_type; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = prop_values.voltage_min; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = 2900000; /* 2.9 V */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = 2200000; /* 2.2 V */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = prop_values.voltage_now; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + val->intval = prop_values.voltage_avg; + break; + case POWER_SUPPLY_PROP_VOLTAGE_BOOT: + val->intval = prop_values.voltage_boot; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = prop_values.capacity; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = prop_values.temp; + break; + case POWER_SUPPLY_PROP_TEMP_MIN: + val->intval = 0; /* 0 C */ + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + val->intval = 400; /* 40 C */ + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + val->intval = 15; /* 1.5 C */ + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + val->intval = 380; /* 38 C */ + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static inline void thunderstrike_request_psy_stats(struct thunderstrike *ts) +{ + set_bit(THUNDERSTRIKE_POWER_SUPPLY_STATS_UPDATE, &ts->update_flags); + schedule_work(&ts->hostcmd_req_work); +} + +static void thunderstrike_psy_stats_timer_handler(struct timer_list *timer) +{ + struct thunderstrike *ts = + container_of(timer, struct thunderstrike, psy_stats_timer); + + thunderstrike_request_psy_stats(ts); + /* Query battery statistics from device every five minutes */ + mod_timer(timer, jiffies + 300 * HZ); +} + static void thunderstrike_parse_fw_version_payload(struct shield_device *shield_dev, __le16 fw_version) @@ -416,13 +602,138 @@ thunderstrike_parse_led_payload(struct shield_device *shield_dev, hid_dbg(shield_dev->hdev, "Thunderstrike led HOSTCMD response, 0x%02X\n", led_state); } +static void thunderstrike_parse_battery_payload( + struct shield_device *shield_dev, + struct thunderstrike_hostcmd_battery *battery) +{ + struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base); + u16 hostcmd_voltage_boot = le16_to_cpu(battery->voltage_boot); + u16 hostcmd_voltage_avg = le16_to_cpu(battery->voltage_avg); + u16 hostcmd_voltage_min = le16_to_cpu(battery->voltage_min); + u16 hostcmd_voltage_now = le16_to_cpu(battery->voltage_now); + u16 hostcmd_thermistor = le16_to_cpu(battery->thermistor); + int voltage_boot, voltage_avg, voltage_min, voltage_now; + struct hid_device *hdev = shield_dev->hdev; + u8 capacity = battery->capacity; + int temp; + + /* Convert thunderstrike device values to µV and tenths of degree Celsius */ + voltage_boot = hostcmd_voltage_boot * 1000; + voltage_avg = hostcmd_voltage_avg * 1000; + voltage_min = hostcmd_voltage_min * 1000; + voltage_now = hostcmd_voltage_now * 1000; + temp = (1378 - (int)hostcmd_thermistor) * 10 / 19; + + /* Copy converted values */ + spin_lock(&ts->psy_stats_lock); + ts->psy_stats.voltage_boot = voltage_boot; + ts->psy_stats.voltage_avg = voltage_avg; + ts->psy_stats.voltage_min = voltage_min; + ts->psy_stats.voltage_now = voltage_now; + ts->psy_stats.capacity = capacity; + ts->psy_stats.temp = temp; + spin_unlock(&ts->psy_stats_lock); + + set_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags); + + hid_dbg(hdev, + "Thunderstrike battery HOSTCMD response, voltage_avg: %u voltage_now: %u\n", + hostcmd_voltage_avg, hostcmd_voltage_now); + hid_dbg(hdev, + "Thunderstrike battery HOSTCMD response, voltage_boot: %u voltage_min: %u\n", + hostcmd_voltage_boot, hostcmd_voltage_min); + hid_dbg(hdev, + "Thunderstrike battery HOSTCMD response, thermistor: %u\n", + hostcmd_thermistor); + hid_dbg(hdev, + "Thunderstrike battery HOSTCMD response, capacity: %u%%\n", + capacity); +} + +static void thunderstrike_parse_charger_payload( + struct shield_device *shield_dev, + struct thunderstrike_hostcmd_charger *charger) +{ + struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base); + int charge_type = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + struct hid_device *hdev = shield_dev->hdev; + int status = POWER_SUPPLY_STATUS_UNKNOWN; + + switch (charger->type) { + case THUNDERSTRIKE_CHARGER_TYPE_NONE: + charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case THUNDERSTRIKE_CHARGER_TYPE_TRICKLE: + charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case THUNDERSTRIKE_CHARGER_TYPE_NORMAL: + charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + default: + hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD type, %u\n", + charger->type); + break; + } + + switch (charger->state) { + case THUNDERSTRIKE_CHARGER_STATE_UNKNOWN: + status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + case THUNDERSTRIKE_CHARGER_STATE_DISABLED: + /* Indicates charger is disconnected */ + break; + case THUNDERSTRIKE_CHARGER_STATE_CHARGING: + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case THUNDERSTRIKE_CHARGER_STATE_FULL: + status = POWER_SUPPLY_STATUS_FULL; + break; + case THUNDERSTRIKE_CHARGER_STATE_FAILED: + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + hid_err(hdev, "Thunderstrike device failed to charge\n"); + break; + default: + hid_warn(hdev, "Unhandled Thunderstrike charger HOSTCMD state, %u\n", + charger->state); + break; + } + + if (!charger->connected) + status = POWER_SUPPLY_STATUS_DISCHARGING; + + spin_lock(&ts->psy_stats_lock); + ts->psy_stats.charge_type = charge_type; + ts->psy_stats.status = status; + spin_unlock(&ts->psy_stats_lock); + + set_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags); + + hid_dbg(hdev, + "Thunderstrike charger HOSTCMD response, connected: %u, type: %u, state: %u\n", + charger->connected, charger->type, charger->state); +} + +static inline void thunderstrike_device_init_info(struct shield_device *shield_dev) +{ + struct thunderstrike *ts = + container_of(shield_dev, struct thunderstrike, base); + + if (!test_bit(SHIELD_FW_VERSION_INITIALIZED, &shield_dev->initialized_flags)) + thunderstrike_request_firmware_version(ts); + + if (!test_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags)) + thunderstrike_request_board_info(ts); + + if (!test_bit(SHIELD_BATTERY_STATS_INITIALIZED, &shield_dev->initialized_flags) || + !test_bit(SHIELD_CHARGER_STATE_INITIALIZED, &shield_dev->initialized_flags)) + thunderstrike_psy_stats_timer_handler(&ts->psy_stats_timer); +} + static int thunderstrike_parse_report(struct shield_device *shield_dev, struct hid_report *report, u8 *data, int size) { struct thunderstrike_hostcmd_resp_report *hostcmd_resp_report; - struct thunderstrike *ts = - container_of(shield_dev, struct thunderstrike, base); struct hid_device *hdev = shield_dev->hdev; switch (report->id) { @@ -445,6 +756,10 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev, case THUNDERSTRIKE_HOSTCMD_ID_LED: thunderstrike_parse_led_payload(shield_dev, hostcmd_resp_report->led_state); break; + case THUNDERSTRIKE_HOSTCMD_ID_BATTERY: + thunderstrike_parse_battery_payload(shield_dev, + &hostcmd_resp_report->battery); + break; case THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO: thunderstrike_parse_board_info_payload( shield_dev, &hostcmd_resp_report->board_info); @@ -453,14 +768,17 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev, thunderstrike_parse_haptics_payload( shield_dev, &hostcmd_resp_report->motors); break; - case THUNDERSTRIKE_HOSTCMD_ID_USB_INIT: - case THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT: /* May block HOSTCMD requests till received initially */ - thunderstrike_request_firmware_version(ts); - thunderstrike_request_board_info(ts); - /* Only HOSTCMD that can be triggered without a request */ - return 0; + thunderstrike_device_init_info(shield_dev); + break; + case THUNDERSTRIKE_HOSTCMD_ID_CHARGER: + /* May block HOSTCMD requests till received initially */ + thunderstrike_device_init_info(shield_dev); + + thunderstrike_parse_charger_payload( + shield_dev, &hostcmd_resp_report->charger); + break; default: hid_warn(hdev, "Unhandled Thunderstrike HOSTCMD id %d\n", @@ -489,6 +807,50 @@ static inline int thunderstrike_led_create(struct thunderstrike *ts) return led_classdev_register(&ts->base.hdev->dev, led); } +static inline int thunderstrike_psy_create(struct shield_device *shield_dev) +{ + struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base); + struct power_supply_config psy_cfg = { .drv_data = shield_dev, }; + struct hid_device *hdev = shield_dev->hdev; + int ret; + + /* + * Set an initial capacity and temperature value to avoid prematurely + * triggering alerts. Will be replaced by values queried from initial + * HOSTCMD requests. + */ + ts->psy_stats.capacity = 100; + ts->psy_stats.temp = 182; + + shield_dev->battery_dev.desc.properties = thunderstrike_battery_props; + shield_dev->battery_dev.desc.num_properties = + ARRAY_SIZE(thunderstrike_battery_props); + shield_dev->battery_dev.desc.get_property = thunderstrike_battery_get_property; + shield_dev->battery_dev.desc.type = POWER_SUPPLY_TYPE_BATTERY; + shield_dev->battery_dev.desc.name = + devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL, + "thunderstrike_%d", ts->id); + + shield_dev->battery_dev.psy = power_supply_register( + &hdev->dev, &shield_dev->battery_dev.desc, &psy_cfg); + if (IS_ERR(shield_dev->battery_dev.psy)) { + hid_err(hdev, "Failed to register Thunderstrike battery device\n"); + return PTR_ERR(shield_dev->battery_dev.psy); + } + + ret = power_supply_powers(shield_dev->battery_dev.psy, &hdev->dev); + if (ret) { + hid_err(hdev, "Failed to associate battery device to Thunderstrike\n"); + goto err; + } + + return 0; + +err: + power_supply_unregister(shield_dev->battery_dev.psy); + return ret; +} + static struct shield_device *thunderstrike_create(struct hid_device *hdev) { struct shield_device *shield_dev; @@ -509,27 +871,47 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev) shield_dev->codename = "Thunderstrike"; spin_lock_init(&ts->haptics_update_lock); + spin_lock_init(&ts->psy_stats_lock); INIT_WORK(&ts->hostcmd_req_work, thunderstrike_hostcmd_req_work_handler); hid_set_drvdata(hdev, shield_dev); + ts->id = ida_alloc(&thunderstrike_ida, GFP_KERNEL); + if (ts->id < 0) + return ERR_PTR(ts->id); + ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect); - if (IS_ERR(ts->haptics_dev)) - return ERR_CAST(ts->haptics_dev); + if (IS_ERR(ts->haptics_dev)) { + hid_err(hdev, "Failed to create Thunderstrike haptics instance\n"); + ret = PTR_ERR(ts->haptics_dev); + goto err_id; + } + + ret = thunderstrike_psy_create(shield_dev); + if (ret) { + hid_err(hdev, "Failed to create Thunderstrike power supply instance\n"); + goto err_haptics; + } ret = thunderstrike_led_create(ts); if (ret) { hid_err(hdev, "Failed to create Thunderstrike LED instance\n"); - goto err; + goto err_psy; } + timer_setup(&ts->psy_stats_timer, thunderstrike_psy_stats_timer_handler, 0); + hid_info(hdev, "Registered Thunderstrike controller\n"); return shield_dev; -err: +err_psy: + power_supply_unregister(shield_dev->battery_dev.psy); +err_haptics: if (ts->haptics_dev) input_unregister_device(ts->haptics_dev); - return ERR_CAST(ts->haptics_dev); +err_id: + ida_free(&thunderstrike_ida, ts->id); + return ERR_PTR(ret); } static int android_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -684,8 +1066,7 @@ static int shield_probe(struct hid_device *hdev, const struct hid_device_id *id) goto err_stop; } - thunderstrike_request_firmware_version(ts); - thunderstrike_request_board_info(ts); + thunderstrike_device_init_info(shield_dev); return ret; @@ -705,9 +1086,12 @@ static void shield_remove(struct hid_device *hdev) ts = container_of(dev, struct thunderstrike, base); hid_hw_close(hdev); - led_classdev_unregister(&ts->led_dev); + power_supply_unregister(dev->battery_dev.psy); if (ts->haptics_dev) input_unregister_device(ts->haptics_dev); + led_classdev_unregister(&ts->led_dev); + ida_free(&thunderstrike_ida, ts->id); + del_timer_sync(&ts->psy_stats_timer); cancel_work_sync(&ts->hostcmd_req_work); hid_hw_stop(hdev); } -- cgit From 77fe1fed4741b14ccf5abf19dc794cc5928c1ac8 Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Mon, 7 Aug 2023 09:36:20 -0700 Subject: HID: nvidia-shield: Update Thunderstrike LED instance name to use id Previously would let led_classdev handle renaming when name collision occurred. Now that an ID allocator is used to uniquely identify multiple Thunderstrike controllers, generate unique led device names. Signed-off-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- drivers/hid/hid-nvidia-shield.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index 1612de3ef4c5..43784bb57d3f 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -798,7 +798,8 @@ static inline int thunderstrike_led_create(struct thunderstrike *ts) { struct led_classdev *led = &ts->led_dev; - led->name = "thunderstrike:blue:led"; + led->name = devm_kasprintf(&ts->base.hdev->dev, GFP_KERNEL, + "thunderstrike%d:blue:led", ts->id); led->max_brightness = 1; led->flags = LED_CORE_SUSPENDRESUME; led->brightness_get = &thunderstrike_led_get_brightness; -- cgit From 9ac6678b95b0dd9458a7a6869f46e51cd55a1d84 Mon Sep 17 00:00:00 2001 From: Aaron Armstrong Skomra Date: Tue, 25 Jul 2023 15:20:25 -0700 Subject: HID: wacom: remove the battery when the EKR is off Currently the EKR battery remains even after we stop getting information from the device. This can lead to a stale battery persisting indefinitely in userspace. The remote sends a heartbeat every 10 seconds. Delete the battery if we miss two heartbeats (after 21 seconds). Restore the battery once we see a heartbeat again. Signed-off-by: Aaron Skomra Signed-off-by: Aaron Armstrong Skomra Reviewed-by: Jason Gerecke Fixes: 9f1015d45f62 ("HID: wacom: EKR: attach the power_supply on first connection") CC: stable@vger.kernel.org Signed-off-by: Jiri Kosina --- drivers/hid/wacom.h | 1 + drivers/hid/wacom_sys.c | 25 +++++++++++++++++++++---- drivers/hid/wacom_wac.c | 1 + drivers/hid/wacom_wac.h | 1 + 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h index 4da50e19808e..166a76c9bcad 100644 --- a/drivers/hid/wacom.h +++ b/drivers/hid/wacom.h @@ -150,6 +150,7 @@ struct wacom_remote { struct input_dev *input; bool registered; struct wacom_battery battery; + ktime_t active_time; } remotes[WACOM_MAX_REMOTES]; }; diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 76e5353aca0c..eb833455abd5 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -2523,6 +2523,18 @@ fail: return; } +static void wacom_remote_destroy_battery(struct wacom *wacom, int index) +{ + struct wacom_remote *remote = wacom->remote; + + if (remote->remotes[index].battery.battery) { + devres_release_group(&wacom->hdev->dev, + &remote->remotes[index].battery.bat_desc); + remote->remotes[index].battery.battery = NULL; + remote->remotes[index].active_time = 0; + } +} + static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) { struct wacom_remote *remote = wacom->remote; @@ -2537,9 +2549,7 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) remote->remotes[i].registered = false; spin_unlock_irqrestore(&remote->remote_lock, flags); - if (remote->remotes[i].battery.battery) - devres_release_group(&wacom->hdev->dev, - &remote->remotes[i].battery.bat_desc); + wacom_remote_destroy_battery(wacom, i); if (remote->remotes[i].group.name) devres_release_group(&wacom->hdev->dev, @@ -2547,7 +2557,6 @@ static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index) remote->remotes[i].serial = 0; remote->remotes[i].group.name = NULL; - remote->remotes[i].battery.battery = NULL; wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN; } } @@ -2632,6 +2641,9 @@ static int wacom_remote_attach_battery(struct wacom *wacom, int index) if (remote->remotes[index].battery.battery) return 0; + if (!remote->remotes[index].active_time) + return 0; + if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN) return 0; @@ -2647,6 +2659,7 @@ static void wacom_remote_work(struct work_struct *work) { struct wacom *wacom = container_of(work, struct wacom, remote_work); struct wacom_remote *remote = wacom->remote; + ktime_t kt = ktime_get(); struct wacom_remote_data data; unsigned long flags; unsigned int count; @@ -2673,6 +2686,10 @@ static void wacom_remote_work(struct work_struct *work) serial = data.remote[i].serial; if (data.remote[i].connected) { + if (kt - remote->remotes[i].active_time > WACOM_REMOTE_BATTERY_TIMEOUT + && remote->remotes[i].active_time != 0) + wacom_remote_destroy_battery(wacom, i); + if (remote->remotes[i].serial == serial) { wacom_remote_attach_battery(wacom, i); continue; diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 174bf03908d7..6c056f8844e7 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1134,6 +1134,7 @@ static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len) if (index < 0 || !remote->remotes[index].registered) goto out; + remote->remotes[i].active_time = ktime_get(); input = remote->remotes[index].input; input_report_key(input, BTN_0, (data[9] & 0x01)); diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index ee21bb260f22..2e7cc5e7a0cb 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -13,6 +13,7 @@ #define WACOM_NAME_MAX 64 #define WACOM_MAX_REMOTES 5 #define WACOM_STATUS_UNKNOWN 255 +#define WACOM_REMOTE_BATTERY_TIMEOUT 21000000000ll /* packet length for individual models */ #define WACOM_PKGLEN_BBFUN 9 -- cgit From 2834e38048f1ef9bd7473fbc33e8266ad7a24fcb Mon Sep 17 00:00:00 2001 From: Aaron Armstrong Skomra Date: Tue, 25 Jul 2023 15:20:57 -0700 Subject: HID: wacom: remove unnecessary 'connected' variable from EKR The 'connected' variable was poorly named, and this has led to some confusion. We can get the same information by checking if a serial number exists in the specified EKR slot. Signed-off-by: Aaron Skomra Signed-off-by: Aaron Armstrong Skomra Reviewed-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 2 +- drivers/hid/wacom_wac.c | 2 -- drivers/hid/wacom_wac.h | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index eb833455abd5..16862f97c46c 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -2684,7 +2684,7 @@ static void wacom_remote_work(struct work_struct *work) for (i = 0; i < WACOM_MAX_REMOTES; i++) { serial = data.remote[i].serial; - if (data.remote[i].connected) { + if (serial) { if (kt - remote->remotes[i].active_time > WACOM_REMOTE_BATTERY_TIMEOUT && remote->remotes[i].active_time != 0) diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index 6c056f8844e7..c922c1c4c78f 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1209,10 +1209,8 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) for (i = 0; i < WACOM_MAX_REMOTES; i++) { int j = i * 6; int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4]; - bool connected = data[j+2]; remote_data.remote[i].serial = serial; - remote_data.remote[i].connected = connected; } spin_lock_irqsave(&remote->remote_lock, flags); diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index 2e7cc5e7a0cb..a10b19669947 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -331,7 +331,6 @@ struct hid_data { struct wacom_remote_data { struct { u32 serial; - bool connected; } remote[WACOM_MAX_REMOTES]; }; -- cgit From 55ab9b2c42f4ce34264213b608889c9fcbdf392f Mon Sep 17 00:00:00 2001 From: Aaron Armstrong Skomra Date: Tue, 25 Jul 2023 15:21:25 -0700 Subject: HID: wacom: struct name cleanup Help differentiate the two remote related "serial" struct variables by renaming "wacom_remote_data" to "wacom_remote_work_data". Signed-off-by: Aaron Skomra Signed-off-by: Aaron Armstrong Skomra Reviewed-by: Jason Gerecke Signed-off-by: Jiri Kosina --- drivers/hid/wacom_sys.c | 19 ++++++++++--------- drivers/hid/wacom_wac.c | 4 ++-- drivers/hid/wacom_wac.h | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c index 16862f97c46c..3f704b8072e8 100644 --- a/drivers/hid/wacom_sys.c +++ b/drivers/hid/wacom_sys.c @@ -1997,7 +1997,7 @@ static int wacom_initialize_remotes(struct wacom *wacom) spin_lock_init(&remote->remote_lock); error = kfifo_alloc(&remote->remote_fifo, - 5 * sizeof(struct wacom_remote_data), + 5 * sizeof(struct wacom_remote_work_data), GFP_KERNEL); if (error) { hid_err(wacom->hdev, "failed allocating remote_fifo\n"); @@ -2660,17 +2660,18 @@ static void wacom_remote_work(struct work_struct *work) struct wacom *wacom = container_of(work, struct wacom, remote_work); struct wacom_remote *remote = wacom->remote; ktime_t kt = ktime_get(); - struct wacom_remote_data data; + struct wacom_remote_work_data remote_work_data; unsigned long flags; unsigned int count; - u32 serial; + u32 work_serial; int i; spin_lock_irqsave(&remote->remote_lock, flags); - count = kfifo_out(&remote->remote_fifo, &data, sizeof(data)); + count = kfifo_out(&remote->remote_fifo, &remote_work_data, + sizeof(remote_work_data)); - if (count != sizeof(data)) { + if (count != sizeof(remote_work_data)) { hid_err(wacom->hdev, "workitem triggered without status available\n"); spin_unlock_irqrestore(&remote->remote_lock, flags); @@ -2683,14 +2684,14 @@ static void wacom_remote_work(struct work_struct *work) spin_unlock_irqrestore(&remote->remote_lock, flags); for (i = 0; i < WACOM_MAX_REMOTES; i++) { - serial = data.remote[i].serial; - if (serial) { + work_serial = remote_work_data.remote[i].serial; + if (work_serial) { if (kt - remote->remotes[i].active_time > WACOM_REMOTE_BATTERY_TIMEOUT && remote->remotes[i].active_time != 0) wacom_remote_destroy_battery(wacom, i); - if (remote->remotes[i].serial == serial) { + if (remote->remotes[i].serial == work_serial) { wacom_remote_attach_battery(wacom, i); continue; } @@ -2698,7 +2699,7 @@ static void wacom_remote_work(struct work_struct *work) if (remote->remotes[i].serial) wacom_remote_destroy_one(wacom, i); - wacom_remote_create_one(wacom, serial, i); + wacom_remote_create_one(wacom, work_serial, i); } else if (remote->remotes[i].serial) { wacom_remote_destroy_one(wacom, i); diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c index c922c1c4c78f..471db78dbbf0 100644 --- a/drivers/hid/wacom_wac.c +++ b/drivers/hid/wacom_wac.c @@ -1197,14 +1197,14 @@ static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len) struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac); unsigned char *data = wacom_wac->data; struct wacom_remote *remote = wacom->remote; - struct wacom_remote_data remote_data; + struct wacom_remote_work_data remote_data; unsigned long flags; int i, ret; if (data[0] != WACOM_REPORT_DEVICE_LIST) return; - memset(&remote_data, 0, sizeof(struct wacom_remote_data)); + memset(&remote_data, 0, sizeof(struct wacom_remote_work_data)); for (i = 0; i < WACOM_MAX_REMOTES; i++) { int j = i * 6; diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h index a10b19669947..57e185f18d53 100644 --- a/drivers/hid/wacom_wac.h +++ b/drivers/hid/wacom_wac.h @@ -328,7 +328,7 @@ struct hid_data { ktime_t time_delayed; }; -struct wacom_remote_data { +struct wacom_remote_work_data { struct { u32 serial; } remote[WACOM_MAX_REMOTES]; -- cgit From ed1fb63b6e45d933527c212c0b5339d613350a3a Mon Sep 17 00:00:00 2001 From: Maxim Mikityanskiy Date: Wed, 9 Aug 2023 14:38:32 +0300 Subject: HID: logitech-hidpp: Add support for Logitech MX Anywhere 3 mouse Add Logitech MX Anywhere 3 connected over Bluetooth to the device table to get hidpi scroll supported. USB connection over the Unifying receiver is already supported by the wildcard entry LDJ_DEVICE(HID_ANY_ID). Signed-off-by: Maxim Mikityanskiy Reviewed-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 6ad344ba0d1f..340c1ac442ad 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -4649,6 +4649,8 @@ static const struct hid_device_id hidpp_devices[] = { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) }, { /* MX Master 3 mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, + { /* MX Anywhere 3 mouse over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) }, { /* MX Master 3S mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) }, {} -- cgit From 0c4b9411f3630b17c51e31b1b4c50e9412c8da0a Mon Sep 17 00:00:00 2001 From: David Rheinsberg Date: Thu, 3 Aug 2023 15:40:01 +0200 Subject: MAINTAINERS: update my email address Update my email-address in MAINTAINERS to . Also add .mailmap entries to map my old surname and email-addresses. Signed-off-by: David Rheinsberg Signed-off-by: Jiri Kosina --- .mailmap | 3 +++ MAINTAINERS | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 1bce47a7f2ce..cea699139e6d 100644 --- a/.mailmap +++ b/.mailmap @@ -119,6 +119,9 @@ Daniel Borkmann Daniel Borkmann Daniel Borkmann David Brownell +David Rheinsberg +David Rheinsberg +David Rheinsberg David Woodhouse Dengcheng Zhu Dengcheng Zhu diff --git a/MAINTAINERS b/MAINTAINERS index 3be1bdfe8ecc..23f34560b54a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21778,7 +21778,7 @@ F: Documentation/admin-guide/ufs.rst F: fs/ufs/ UHID USERSPACE HID IO DRIVER -M: David Rheinsberg +M: David Rheinsberg L: linux-input@vger.kernel.org S: Maintained F: drivers/hid/uhid.c @@ -22902,7 +22902,7 @@ S: Maintained F: drivers/rtc/rtc-sd3078.c WIIMOTE HID DRIVER -M: David Rheinsberg +M: David Rheinsberg L: linux-input@vger.kernel.org S: Maintained F: drivers/hid/hid-wiimote* -- cgit From e90e7c2894fb6407b14edee7d159908a58492531 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Mon, 14 Aug 2023 21:19:28 +0200 Subject: HID: steelseries: arctis_1_battery_request[] should be static arctis_1_battery_request[] is not used outside of this module, and as such should be static. Fixes: a0c76896c3fbdc2 ("HID: steelseries: Add support for Arctis 1 XBox") Reported-by: kernel test robot Signed-off-by: Jiri Kosina --- drivers/hid/hid-steelseries.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index 495377686123..43d2cf7153d7 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -375,7 +375,7 @@ static void steelseries_srws1_remove(struct hid_device *hdev) #define STEELSERIES_HEADSET_BATTERY_TIMEOUT_MS 3000 #define ARCTIS_1_BATTERY_RESPONSE_LEN 8 -const char arctis_1_battery_request[] = { 0x06, 0x12 }; +static const char arctis_1_battery_request[] = { 0x06, 0x12 }; static int steelseries_headset_arctis_1_fetch_battery(struct hid_device *hdev) { -- cgit From c4444d8749f696384947192b602718fa310c1caf Mon Sep 17 00:00:00 2001 From: Nils Tonnaett Date: Tue, 15 Aug 2023 13:19:59 -0700 Subject: HID: apple: Add "Hailuck" to the list of non-apple keyboards Powzan keyboards KB750 and KB770 identify as "Hailuck Co.,Ltd USB Keyboard". Adding "Hailuck" to the list of non-apple keyboards fixes function keys for these models. Signed-off-by: Nils Tonnaett Reviewed-by: Rahul Rameshbabu --- drivers/hid/hid-apple.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index d7b932925730..3ca45975c686 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -343,7 +343,8 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = { { "SONiX USB DEVICE" }, { "Keychron" }, { "AONE" }, - { "GANSS" } + { "GANSS" }, + { "Hailuck" }, }; static bool apple_is_non_apple_keyboard(struct hid_device *hdev) -- cgit From 7d3b0d9ebddd90e5d541d8d2d81027581758548b Mon Sep 17 00:00:00 2001 From: Cong Yang Date: Wed, 2 Aug 2023 15:19:46 +0800 Subject: dt-bindings: input: i2c-hid: Introduce Ilitek ili9882t The ili9882t touch screen chip same as Elan eKTH6915 controller has a reset gpio. The difference is that ili9882t needs to use vccio-supply instead of vcc33-supply. Doug's series[1] allows panels and touchscreens to power on/off together, let's add a phandle for this. [1]: https://lore.kernel.org/r/20230607215224.2067679-1-dianders@chromium.org Reviewed-by: Krzysztof Kozlowski Signed-off-by: Cong Yang Reviewed-by: Douglas Anderson Link: https://lore.kernel.org/r/20230802071947.1683318-2-yangcong5@huaqin.corp-partner.google.com Signed-off-by: Benjamin Tissoires --- .../devicetree/bindings/input/ilitek,ili9882t.yaml | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml diff --git a/Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml b/Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml new file mode 100644 index 000000000000..c5d9e0e919f9 --- /dev/null +++ b/Documentation/devicetree/bindings/input/ilitek,ili9882t.yaml @@ -0,0 +1,67 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/input/ilitek,ili9882t.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ilitek ili9882t touchscreen controller + +maintainers: + - Cong Yang + +description: + Supports the Ilitek ili9882t touchscreen controller. + This touchscreen controller uses the i2c-hid protocol with a reset GPIO. + +allOf: + - $ref: /schemas/input/touchscreen/touchscreen.yaml# + +properties: + compatible: + const: ilitek,ili9882t + + reg: + const: 0x41 + + interrupts: + maxItems: 1 + + panel: true + + reset-gpios: + maxItems: 1 + description: Reset GPIO. + + vccio-supply: + description: The 1.8V supply to the touchscreen. + +required: + - compatible + - reg + - interrupts + - panel + - vccio-supply + +additionalProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + touchscreen: touchscreen@41 { + compatible = "ilitek,ili9882t"; + reg = <0x41>; + + interrupt-parent = <&pio>; + interrupts = <12 IRQ_TYPE_LEVEL_LOW>; + + panel = <&panel>; + reset-gpios = <&pio 60 GPIO_ACTIVE_LOW>; + vccio-supply = <&mt6366_vio18_reg>; + }; + }; -- cgit From f2f43bf15d7aa3286eced18d5199ee579e2c2614 Mon Sep 17 00:00:00 2001 From: Cong Yang Date: Wed, 2 Aug 2023 15:19:47 +0800 Subject: HID: i2c-hid: elan: Add ili9882t timing The ili9882t is a TDDI IC (Touch with Display Driver). The datasheet specifies there should be 60ms between touch SDA sleep and panel RESX. Doug's series[1] allows panels and touchscreens to power on/off together, so we can add the 65 ms delay in i2c_hid_core_suspend before panel_unprepare. Because ili9882t touchscrgeen is a panel follower, and needs to use vccio-supply instead of vcc33-supply, so set it NULL to ili9882t_chip_data, then not use vcc33 regulator. [1]: https://lore.kernel.org/all/20230727171750.633410-1-dianders@chromium.org Reviewed-by: Douglas Anderson Signed-off-by: Cong Yang Acked-by: Benjamin Tissoires Link: https://lore.kernel.org/r/20230802071947.1683318-3-yangcong5@huaqin.corp-partner.google.com Signed-off-by: Benjamin Tissoires --- drivers/hid/i2c-hid/i2c-hid-of-elan.c | 50 ++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c index 029045d9661c..31abab57ad44 100644 --- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c +++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c @@ -18,9 +18,11 @@ #include "i2c-hid.h" struct elan_i2c_hid_chip_data { - unsigned int post_gpio_reset_delay_ms; + unsigned int post_gpio_reset_on_delay_ms; + unsigned int post_gpio_reset_off_delay_ms; unsigned int post_power_delay_ms; u16 hid_descriptor_address; + const char *main_supply_name; }; struct i2c_hid_of_elan { @@ -38,9 +40,11 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops) container_of(ops, struct i2c_hid_of_elan, ops); int ret; - ret = regulator_enable(ihid_elan->vcc33); - if (ret) - return ret; + if (ihid_elan->vcc33) { + ret = regulator_enable(ihid_elan->vcc33); + if (ret) + return ret; + } ret = regulator_enable(ihid_elan->vccio); if (ret) { @@ -52,8 +56,8 @@ static int elan_i2c_hid_power_up(struct i2chid_ops *ops) msleep(ihid_elan->chip_data->post_power_delay_ms); gpiod_set_value_cansleep(ihid_elan->reset_gpio, 0); - if (ihid_elan->chip_data->post_gpio_reset_delay_ms) - msleep(ihid_elan->chip_data->post_gpio_reset_delay_ms); + if (ihid_elan->chip_data->post_gpio_reset_on_delay_ms) + msleep(ihid_elan->chip_data->post_gpio_reset_on_delay_ms); return 0; } @@ -64,8 +68,12 @@ static void elan_i2c_hid_power_down(struct i2chid_ops *ops) container_of(ops, struct i2c_hid_of_elan, ops); gpiod_set_value_cansleep(ihid_elan->reset_gpio, 1); + if (ihid_elan->chip_data->post_gpio_reset_off_delay_ms) + msleep(ihid_elan->chip_data->post_gpio_reset_off_delay_ms); + regulator_disable(ihid_elan->vccio); - regulator_disable(ihid_elan->vcc33); + if (ihid_elan->vcc33) + regulator_disable(ihid_elan->vcc33); } static int i2c_hid_of_elan_probe(struct i2c_client *client) @@ -89,24 +97,42 @@ static int i2c_hid_of_elan_probe(struct i2c_client *client) if (IS_ERR(ihid_elan->vccio)) return PTR_ERR(ihid_elan->vccio); - ihid_elan->vcc33 = devm_regulator_get(&client->dev, "vcc33"); - if (IS_ERR(ihid_elan->vcc33)) - return PTR_ERR(ihid_elan->vcc33); - ihid_elan->chip_data = device_get_match_data(&client->dev); + if (ihid_elan->chip_data->main_supply_name) { + ihid_elan->vcc33 = devm_regulator_get(&client->dev, + ihid_elan->chip_data->main_supply_name); + if (IS_ERR(ihid_elan->vcc33)) + return PTR_ERR(ihid_elan->vcc33); + } + return i2c_hid_core_probe(client, &ihid_elan->ops, ihid_elan->chip_data->hid_descriptor_address, 0); } static const struct elan_i2c_hid_chip_data elan_ekth6915_chip_data = { .post_power_delay_ms = 1, - .post_gpio_reset_delay_ms = 300, + .post_gpio_reset_on_delay_ms = 300, + .hid_descriptor_address = 0x0001, + .main_supply_name = "vcc33", +}; + +static const struct elan_i2c_hid_chip_data ilitek_ili9882t_chip_data = { + .post_power_delay_ms = 1, + .post_gpio_reset_on_delay_ms = 200, + .post_gpio_reset_off_delay_ms = 65, .hid_descriptor_address = 0x0001, + /* + * this touchscreen is tightly integrated with the panel and assumes + * that the relevant power rails (other than the IO rail) have already + * been turned on by the panel driver because we're a panel follower. + */ + .main_supply_name = NULL, }; static const struct of_device_id elan_i2c_hid_of_match[] = { { .compatible = "elan,ekth6915", .data = &elan_ekth6915_chip_data }, + { .compatible = "ilitek,ili9882t", .data = &ilitek_ili9882t_chip_data }, { } }; MODULE_DEVICE_TABLE(of, elan_i2c_hid_of_match); -- cgit From 6f20d3261265885f6a6be4cda49d7019728760e0 Mon Sep 17 00:00:00 2001 From: Nikita Zhandarovich Date: Tue, 13 Jun 2023 03:16:35 -0700 Subject: HID: logitech-dj: Fix error handling in logi_dj_recv_switch_to_dj_mode() Presently, if a call to logi_dj_recv_send_report() fails, we do not learn about the error until after sending short HID_OUTPUT_REPORT with hid_hw_raw_request(). To handle this somewhat unlikely issue, return on error in logi_dj_recv_send_report() (minding ugly sleep workaround) and take into account the result of hid_hw_raw_request(). Found by Linux Verification Center (linuxtesting.org) with static analysis tool SVACE. Fixes: 6a9ddc897883 ("HID: logitech-dj: enable notifications on connect/disconnect") Signed-off-by: Nikita Zhandarovich Link: https://lore.kernel.org/r/20230613101635.77820-1-n.zhandarovich@fintech.ru Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-dj.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index fef67da0de53..8afe3be683ba 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -1285,6 +1285,9 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, * 50 msec should gives enough time to the receiver to be ready. */ msleep(50); + + if (retval) + return retval; } /* @@ -1306,7 +1309,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev, buf[5] = 0x09; buf[6] = 0x00; - hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf, + retval = hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf, HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); -- cgit From dd613a4e45f8d35f49a63a2064e5308fa5619e29 Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Thu, 24 Aug 2023 06:14:17 +0000 Subject: HID: uclogic: Correct devm device reference for hidinput input_dev name Reference the HID device rather than the input device for the devm allocation of the input_dev name. Referencing the input_dev would lead to a use-after-free when the input_dev was unregistered and subsequently fires a uevent that depends on the name. At the point of firing the uevent, the name would be freed by devres management. Use devm_kasprintf to simplify the logic for allocating memory and formatting the input_dev name string. Reported-by: syzbot+3a0ebe8a52b89c63739d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-input/ZOZIZCND+L0P1wJc@penguin/T/ Reported-by: Maxime Ripard Closes: https://lore.kernel.org/linux-input/ZOZIZCND+L0P1wJc@penguin/T/#m443f3dce92520f74b6cf6ffa8653f9c92643d4ae Fixes: cce2dbdf258e ("HID: uclogic: name the input nodes based on their tool") Suggested-by: Maxime Ripard Suggested-by: Dmitry Torokhov Signed-off-by: Rahul Rameshbabu Reviewed-by: Maxime Ripard Link: https://lore.kernel.org/r/20230824061308.222021-2-sergeantsagara@protonmail.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-core.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index f67835f9ed4c..ad74cbc9a0aa 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -85,10 +85,8 @@ static int uclogic_input_configured(struct hid_device *hdev, { struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); struct uclogic_params *params = &drvdata->params; - char *name; const char *suffix = NULL; struct hid_field *field; - size_t len; size_t i; const struct uclogic_params_frame *frame; @@ -146,14 +144,9 @@ static int uclogic_input_configured(struct hid_device *hdev, } } - if (suffix) { - len = strlen(hdev->name) + 2 + strlen(suffix); - name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL); - if (name) { - snprintf(name, len, "%s %s", hdev->name, suffix); - hi->input->name = name; - } - } + if (suffix) + hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "%s %s", hdev->name, suffix); return 0; } -- cgit From 4794394635293a3e74591351fff469cea7ad15a2 Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Thu, 24 Aug 2023 06:14:33 +0000 Subject: HID: multitouch: Correct devm device reference for hidinput input_dev name Reference the HID device rather than the input device for the devm allocation of the input_dev name. Referencing the input_dev would lead to a use-after-free when the input_dev was unregistered and subsequently fires a uevent that depends on the name. At the point of firing the uevent, the name would be freed by devres management. Use devm_kasprintf to simplify the logic for allocating memory and formatting the input_dev name string. Reported-by: Maxime Ripard Closes: https://lore.kernel.org/linux-input/ZOZIZCND+L0P1wJc@penguin/T/#m443f3dce92520f74b6cf6ffa8653f9c92643d4ae Fixes: c08d46aa805b ("HID: multitouch: devm conversion") Suggested-by: Maxime Ripard Suggested-by: Dmitry Torokhov Signed-off-by: Rahul Rameshbabu Reviewed-by: Maxime Ripard Link: https://lore.kernel.org/r/20230824061308.222021-3-sergeantsagara@protonmail.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-multitouch.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index e31be0cb8b85..521b2ffb4244 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -1594,7 +1594,6 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct mt_device *td = hid_get_drvdata(hdev); - char *name; const char *suffix = NULL; struct mt_report_data *rdata; struct mt_application *mt_application = NULL; @@ -1645,15 +1644,9 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) break; } - if (suffix) { - name = devm_kzalloc(&hi->input->dev, - strlen(hdev->name) + strlen(suffix) + 2, - GFP_KERNEL); - if (name) { - sprintf(name, "%s %s", hdev->name, suffix); - hi->input->name = name; - } - } + if (suffix) + hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "%s %s", hdev->name, suffix); return 0; } -- cgit From 197d3143520fec9fde89aebabc9f0d7464f08e50 Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Thu, 24 Aug 2023 06:14:54 +0000 Subject: HID: nvidia-shield: Reference hid_device devm allocation of input_dev name Use hid_device for devm allocation of the input_dev name to avoid a use-after-free. input_unregister_device would trigger devres cleanup of all resources associated with the input_dev, free-ing the name. The name would subsequently be used in a uevent fired at the end of unregistering the input_dev. Reported-by: Maxime Ripard Closes: https://lore.kernel.org/linux-input/ZOZIZCND+L0P1wJc@penguin/T/#m443f3dce92520f74b6cf6ffa8653f9c92643d4ae Fixes: 09308562d4af ("HID: nvidia-shield: Initial driver implementation with Thunderstrike support") Suggested-by: Maxime Ripard Suggested-by: Dmitry Torokhov Signed-off-by: Rahul Rameshbabu Reviewed-by: Maxime Ripard Link: https://lore.kernel.org/r/20230824061308.222021-4-sergeantsagara@protonmail.com Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-nvidia-shield.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index a928ad2be62d..084179a6be86 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -164,7 +164,7 @@ static struct input_dev *shield_allocate_input_dev(struct hid_device *hdev, idev->id.product = hdev->product; idev->id.version = hdev->version; idev->uniq = hdev->uniq; - idev->name = devm_kasprintf(&idev->dev, GFP_KERNEL, "%s %s", hdev->name, + idev->name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s %s", hdev->name, name_suffix); if (!idev->name) goto err_name; -- cgit From 60165ab774cb0c509680a73cf826d0e158454653 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 12 Jul 2023 17:02:34 +0200 Subject: HID: logitech-hidpp: rework one more time the retries attempts Extract the internal code inside a helper function, fix the initialization of the parameters used in the helper function (`hidpp->answer_available` was not reset and `*response` wasn't either), and use a `do {...} while();` loop. Fixes: 586e8fede795 ("HID: logitech-hidpp: Retry commands when device is busy") Cc: stable@vger.kernel.org Reviewed-by: Bastien Nocera Signed-off-by: Benjamin Tissoires Link: https://lore.kernel.org/r/20230621-logitech-fixes-v2-1-3635f7f9c8af@kernel.org Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 115 +++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 40 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 340c1ac442ad..05f5b5f588a2 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -275,21 +275,22 @@ static int __hidpp_send_report(struct hid_device *hdev, } /* - * hidpp_send_message_sync() returns 0 in case of success, and something else - * in case of a failure. - * - If ' something else' is positive, that means that an error has been raised - * by the protocol itself. - * - If ' something else' is negative, that means that we had a classic error - * (-ENOMEM, -EPIPE, etc...) + * Effectively send the message to the device, waiting for its answer. + * + * Must be called with hidpp->send_mutex locked + * + * Same return protocol than hidpp_send_message_sync(): + * - success on 0 + * - negative error means transport error + * - positive value means protocol error */ -static int hidpp_send_message_sync(struct hidpp_device *hidpp, +static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp, struct hidpp_report *message, struct hidpp_report *response) { - int ret = -1; - int max_retries = 3; + int ret; - mutex_lock(&hidpp->send_mutex); + __must_hold(&hidpp->send_mutex); hidpp->send_receive_buf = response; hidpp->answer_available = false; @@ -300,47 +301,74 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, */ *response = *message; - for (; max_retries != 0 && ret; max_retries--) { - ret = __hidpp_send_report(hidpp->hid_dev, message); + ret = __hidpp_send_report(hidpp->hid_dev, message); + if (ret) { + dbg_hid("__hidpp_send_report returned err: %d\n", ret); + memset(response, 0, sizeof(struct hidpp_report)); + return ret; + } - if (ret) { - dbg_hid("__hidpp_send_report returned err: %d\n", ret); - memset(response, 0, sizeof(struct hidpp_report)); - break; - } + if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, + 5*HZ)) { + dbg_hid("%s:timeout waiting for response\n", __func__); + memset(response, 0, sizeof(struct hidpp_report)); + return -ETIMEDOUT; + } - if (!wait_event_timeout(hidpp->wait, hidpp->answer_available, - 5*HZ)) { - dbg_hid("%s:timeout waiting for response\n", __func__); - memset(response, 0, sizeof(struct hidpp_report)); - ret = -ETIMEDOUT; - break; - } + if (response->report_id == REPORT_ID_HIDPP_SHORT && + response->rap.sub_id == HIDPP_ERROR) { + ret = response->rap.params[1]; + dbg_hid("%s:got hidpp error %02X\n", __func__, ret); + return ret; + } - if (response->report_id == REPORT_ID_HIDPP_SHORT && - response->rap.sub_id == HIDPP_ERROR) { - ret = response->rap.params[1]; - dbg_hid("%s:got hidpp error %02X\n", __func__, ret); + if ((response->report_id == REPORT_ID_HIDPP_LONG || + response->report_id == REPORT_ID_HIDPP_VERY_LONG) && + response->fap.feature_index == HIDPP20_ERROR) { + ret = response->fap.params[1]; + dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); + return ret; + } + + return 0; +} + +/* + * hidpp_send_message_sync() returns 0 in case of success, and something else + * in case of a failure. + * + * See __do_hidpp_send_message_sync() for a detailed explanation of the returned + * value. + */ +static int hidpp_send_message_sync(struct hidpp_device *hidpp, + struct hidpp_report *message, + struct hidpp_report *response) +{ + int ret; + int max_retries = 3; + + mutex_lock(&hidpp->send_mutex); + + do { + ret = __do_hidpp_send_message_sync(hidpp, message, response); + if (ret != HIDPP20_ERROR_BUSY) break; - } - if ((response->report_id == REPORT_ID_HIDPP_LONG || - response->report_id == REPORT_ID_HIDPP_VERY_LONG) && - response->fap.feature_index == HIDPP20_ERROR) { - ret = response->fap.params[1]; - if (ret != HIDPP20_ERROR_BUSY) { - dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); - break; - } - dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret); - } - } + dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret); + } while (--max_retries); mutex_unlock(&hidpp->send_mutex); return ret; } +/* + * hidpp_send_fap_command_sync() returns 0 in case of success, and something else + * in case of a failure. + * + * See __do_hidpp_send_message_sync() for a detailed explanation of the returned + * value. + */ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count, struct hidpp_report *response) @@ -373,6 +401,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, return ret; } +/* + * hidpp_send_rap_command_sync() returns 0 in case of success, and something else + * in case of a failure. + * + * See __do_hidpp_send_message_sync() for a detailed explanation of the returned + * value. + */ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count, struct hidpp_report *response) -- cgit