* Re: [PATCH 3/3] HID: asus: avoid memory leak in asus_report_fixup()
From: Günther Noack @ 2026-02-17 19:51 UTC (permalink / raw)
To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <aZSzASB_TC2RyQsR@plouf>
Hello!
On Tue, Feb 17, 2026 at 07:31:23PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > The asus_report_fixup() function was allocating a new buffer with kmemdup()
> > when growing the report descriptor but never freeing it. Switch to
> > devm_kzalloc() to ensure the memory is managed and freed automatically when
> > the device is removed.
>
> Actually this one is even worse: you can't use devm_kzalloc because
> hid-core.c will later call kfree(dev->rdesc) if dev->rdesc is different
> from the one provided by the low level driver. So we are going to have
> a double free.
The buffer returned by report_fixup() is duplicated first before
hid-core stores it in dev->rdesc. The pointer that report_fixup()
returns is not managed by the caller.
I elaborated in the response to the other patch in [1]. You can see
it in the source code in the position marked with (4).
[1] https://lore.kernel.org/all/aZTEnPEHcWEkoTJR@google.com/
> I really wonder if this was ever tested.
I only convinced myself by staring at the code, because I do not
happen to have the matching USB devices here. What it your usual
approach to verifying such changes? raw-gadget?
—Günther
^ permalink raw reply
* Re: [PATCH 0/3] HID: Fix some memory leaks in drivers/hid
From: Günther Noack @ 2026-02-17 20:08 UTC (permalink / raw)
To: Benjamin Tissoires; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <aZS0OAaSPhX2pJ6l@plouf>
Hello!
On Tue, Feb 17, 2026 at 07:36:46PM +0100, Benjamin Tissoires wrote:
> On Feb 17 2026, Günther Noack wrote:
> > These patches fix a few memory leaks in HID report descriptor fixups.
> >
> > FWIW, a good ad-hoc way to look for usages of allocation functions in
> > these is:
> >
> > awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c \
> > | grep -E '(malloc|kzalloc|kcalloc|kmemdup)'
> >
> > The devm_* variants are safe in this context, because they tie the
> > allocated memory to the lifetime of the driver.
>
> No. Look at hid_close_report() in drivers/hid/hid-core.c.
>
> HID still hasn't fully migrated to devm, so as a rule of thumb, if you
> change a kzalloc into a devm_kzalloc, you are getting into troubles
> unless you fix the all the kfree path.
OK, I have not verified where the devm-allocated objects get freed up.
If devm_*() is not possible here, then the drivers hid-asus.c and
hid-gembird.c have two additional memory leaks, because they do that.
$ awk '/static.*report_fixup.*/,/^}/ { print FILENAME, $0 }' drivers/hid/hid-*.c | grep -E 'alloc'
drivers/hid/hid-asus.c new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
drivers/hid/hid-gembird.c new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
(That is without my threee patches)
> > For transparency, I generated these commits with Gemini-CLI,
> > starting with this prompt:
> >
> > We are working in the Linux kernel. In the HID drivers in
> > `drivers/hid/hid-*.c`, the `report_fixup` driver hook is a function
> > that gets a byte buffer (with size) as input and that may modify that
> > byte buffer, and optionally return a pointer to a new byte buffer and
> > update the size. The returned value is *not* memory-managed by the
> > caller though and will not be freed subsequently. When the
>
> If the memory is *not* managed, why would gemini converts kzalloc into
> devm variants without changing the kfree paths????
I'm not sure I understand the question, it's not clear to me what you
mean by the "kfree paths".
I have seen usages of devm in other HID drivers and I was under the
impression that devm_* allocations would work in the HID subsystem to
allocate objects which are then freed automatically at a later point
when the device gets removed. Is that inaccurate?
> > `report_fixup` implementation allocates a new buffer and returns that,
> > that will not get freed by the caller.
>
> This is wrong. See hid_close_report(): if the new rdesc (after fixup)
> differs from the one initially set, there is an explicit call to
> kfree().
>
> -> there is no memleak AFAICT, and your prompt is wrong.
See my discussion in [1]. The pointer returned by report_fixup() is
immediately discarded in the position marked with (4). This is still
in the hid_open_report() function where the leak happens.
Let me know whether this makes sense. I'm happy to be corrected, but
so far, I still have the feeling that my reasoning is sound.
—Günther
[1] https://lore.kernel.org/all/aZTEnPEHcWEkoTJR@google.com/
^ permalink raw reply
* [syzbot] Monthly input report (Feb 2026)
From: syzbot @ 2026-02-17 22:04 UTC (permalink / raw)
To: linux-input, linux-kernel, syzkaller-bugs
Hello input maintainers/developers,
This is a 31-day syzbot report for the input subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/input
During the period, 0 new issues were detected and 0 were fixed.
In total, 22 issues are still open and 63 have already been fixed.
Some of the still happening issues:
Ref Crashes Repro Title
<1> 3448 Yes WARNING in cm109_urb_irq_callback/usb_submit_urb
https://syzkaller.appspot.com/bug?extid=2d6d691af5ab4b7e66df
<2> 1677 No possible deadlock in evdev_pass_values (2)
https://syzkaller.appspot.com/bug?extid=13d3cb2a3dc61e6092f5
<3> 430 Yes KASAN: slab-out-of-bounds Read in mcp2221_raw_event (2)
https://syzkaller.appspot.com/bug?extid=1018672fe70298606e5f
<4> 116 Yes WARNING in cm109_input_open/usb_submit_urb (3)
https://syzkaller.appspot.com/bug?extid=ac0f9c4cc1e034160492
<5> 91 Yes possible deadlock in uinput_request_submit
https://syzkaller.appspot.com/bug?extid=159077b1355b8cd72757
<6> 59 No KASAN: slab-use-after-free Read in report_descriptor_read
https://syzkaller.appspot.com/bug?extid=bc537ca7a0efe33988eb
<7> 20 Yes INFO: task hung in __input_unregister_device (5)
https://syzkaller.appspot.com/bug?extid=78e2288f58b881ed3c45
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders
To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem
You may send multiple commands in a single email message.
^ permalink raw reply
* [PATCH 3/3] MAINTAINERS: add an entry for Goodix GTX8 Touchscreen driver
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans de Goede, Neil Armstrong, Henrik Rydberg
Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
~postmarketos/upstreaming, Aelin Reidel
In-Reply-To: <20260218-gtx8-v1-0-0d575b3dedc5@mainlining.org>
Add MAINTAINERS entry for the Goodix GTX8 Touchscreen IC driver.
Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index afbba2fdc0f49abb6d0d1877a5e161266715f275..cb0f19d622e25cd9a5ceb8fc1e781a1f2232c64a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10831,6 +10831,13 @@ M: Maud Spierings <maudspierings@gocontroll.com>
S: Maintained
F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml
+GOODIX GTX8 TOUCHSCREEN
+M: Aelin Reidel <aelin@mainlining.org>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml
+F: drivers/input/touchscreen/goodix_gtx8*
+
GOODIX TOUCHSCREEN
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
--
2.53.0
^ permalink raw reply related
* [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans de Goede, Neil Armstrong, Henrik Rydberg
Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
~postmarketos/upstreaming, Aelin Reidel, Piyush Raj Chouhan
In-Reply-To: <20260218-gtx8-v1-0-0d575b3dedc5@mainlining.org>
Add initial support for the Goodix GTX8 touchscreen ICs.
These ICs support SPI and I2C interfaces, up to 10 finger touch, stylus
and gesture events.
This driver is derived from the Goodix gtx8_driver_linux available at
[1] and only supports the GT9886 and GT9896 ICs present in the Xiaomi
Mi 9T and Xiaomi Redmi Note 10 Pro smartphones.
The current implementation only supports Normandy and Yellowstone type
ICs, aka only GT9886 and GT9896. It is also limited to I2C only, since I
don't have a device with GTX8 over SPI at hand. Adding support for SPI
should be fairly easy in the future, since the code uses a regmap.
Support for advanced features like:
- Firmware updates
- Stylus events
- Gesture events
- Nanjing IC support
is not included in current version.
The current support requires a previously flashed firmware to be
present.
As I did not have access to datasheets for these ICs, I extracted the
addresses from a couple of config files using a small tool [2]. The
addresses are identical for the same IC families in all configs I
observed, however not all of them make sense and I stubbed out firmware
request support due to this.
[1] https://github.com/goodix/gtx8_driver_linux
[2] https://github.com/sm7150-mainline/goodix-cfg-bin
Tested-by: Piyush Raj Chouhan <pc1598@mainlining.org>
Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
drivers/input/touchscreen/Kconfig | 15 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/goodix_gtx8.c | 562 ++++++++++++++++++++++++++++++++
drivers/input/touchscreen/goodix_gtx8.h | 137 ++++++++
4 files changed, 715 insertions(+)
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 7d5b72ee07fa1313da39a625b5129a0459720865..099ccd3679383dcf037bc7c6e6a3dbf0741722b4 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -429,6 +429,21 @@ config TOUCHSCREEN_GOODIX_BERLIN_SPI
To compile this driver as a module, choose M here: the
module will be called goodix_berlin_spi.
+config TOUCHSCREEN_GOODIX_GTX8
+ tristate "Goodix GTX8 touchscreen"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here if you have a Goodix GTX8 IC connected to
+ your system via I2C. This driver supports Normandy and
+ Yellowstone ICs like the GT9886 and GT9896.
+ They are commonly found in mobile phones.
+
+ if unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called goodix_gtx8.
+
config TOUCHSCREEN_HIDEEP
tristate "HiDeep Touch IC"
depends on I2C
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index ab9abd151078831a4b22d6998e00ef74fe01c356..9bcb8f01ea785dcbe2a22bd3293601dd4259ba1d 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_TOUCHSCREEN_GOODIX) += goodix_ts.o
obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_CORE) += goodix_berlin_core.o
obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_I2C) += goodix_berlin_i2c.o
obj-$(CONFIG_TOUCHSCREEN_GOODIX_BERLIN_SPI) += goodix_berlin_spi.o
+obj-$(CONFIG_TOUCHSCREEN_GOODIX_GTX8) += goodix_gtx8.o
obj-$(CONFIG_TOUCHSCREEN_HIDEEP) += hideep.o
obj-$(CONFIG_TOUCHSCREEN_HIMAX_HX852X) += himax_hx852x.o
obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX) += hynitron_cstxxx.o
diff --git a/drivers/input/touchscreen/goodix_gtx8.c b/drivers/input/touchscreen/goodix_gtx8.c
new file mode 100644
index 0000000000000000000000000000000000000000..b210e866974e423b413ca1741b3b6c7df117b92f
--- /dev/null
+++ b/drivers/input/touchscreen/goodix_gtx8.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Goodix GTX8 Touchscreens
+ *
+ * Copyright (c) 2019 - 2020 Goodix, Inc.
+ * Copyright (C) 2023 Linaro Ltd.
+ * Copyright (c) 2025 Aelin Reidel <aelin@mainlining.org>
+ *
+ * Based on gtx8_driver_linux vendor driver and goodix_berlin kernel driver.
+ *
+ * The driver currently relies on the pre-flashed firmware and only supports
+ * Normandy / Yellowstone ICs.
+ * Pen support is also missing.
+ */
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/unaligned.h>
+
+#include "goodix_gtx8.h"
+
+static const struct regmap_config goodix_gtx8_regmap_conf = {
+ .reg_bits = 16,
+ .val_bits = 8,
+ .max_raw_read = I2C_MAX_TRANSFER_SIZE,
+ .max_raw_write = I2C_MAX_TRANSFER_SIZE,
+};
+
+/* vendor & product left unassigned here, should probably be updated from fw info */
+static const struct input_id goodix_gtx8_input_id = {
+ .bustype = BUS_I2C,
+};
+
+static bool goodix_gtx8_checksum_valid_normandy(const u8 *data, int size)
+{
+ u8 cal_checksum = 0;
+ int i;
+
+ if (size < GOODIX_GTX8_CHECKSUM_SIZE)
+ return false;
+
+ for (i = 0; i < size; i++)
+ cal_checksum += data[i];
+
+ return cal_checksum == 0;
+}
+
+static bool goodix_gtx8_checksum_valid_yellowstone(const u8 *data, int size)
+{
+ u16 cal_checksum = 0;
+ u16 r_checksum;
+ int i;
+
+ if (size < GOODIX_GTX8_CHECKSUM_SIZE)
+ return false;
+
+ for (i = 0; i < size - GOODIX_GTX8_CHECKSUM_SIZE; i++)
+ cal_checksum += data[i];
+
+ r_checksum = get_unaligned_be16(&data[i]);
+
+ return cal_checksum == r_checksum;
+}
+
+static int goodix_gtx8_get_remaining_contacts(struct goodix_gtx8_core *cd,
+ int n)
+{
+ size_t offset = cd->ic_data->header_size + GOODIX_GTX8_TOUCH_SIZE +
+ GOODIX_GTX8_CHECKSUM_SIZE;
+ u32 addr = cd->ic_data->touch_data_addr + offset;
+ int error;
+
+ error = regmap_raw_read(cd->regmap, addr, &cd->event_buffer[offset],
+ (n - 1) * GOODIX_GTX8_TOUCH_SIZE);
+ if (error) {
+ dev_err_ratelimited(cd->dev, "failed to get touch data, %d\n",
+ error);
+ return error;
+ }
+
+ return 0;
+}
+
+static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
+ union goodix_gtx8_touch *touch_data)
+{
+ union goodix_gtx8_touch *t;
+ int i;
+ u8 finger_id;
+
+ for (i = 0; i < touch_num; i++) {
+ t = &touch_data[i];
+
+ if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
+ input_mt_slot(cd->input_dev, t->normandy.finger_id);
+ input_mt_report_slot_state(cd->input_dev,
+ MT_TOOL_FINGER, true);
+
+ touchscreen_report_pos(cd->input_dev, &cd->props,
+ __le16_to_cpu(t->normandy.x),
+ __le16_to_cpu(t->normandy.y),
+ true);
+ input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
+ t->normandy.w);
+ } else {
+ finger_id = FIELD_GET(
+ GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
+ t->yellowstone.finger_id);
+ input_mt_slot(cd->input_dev, finger_id);
+ input_mt_report_slot_state(cd->input_dev,
+ MT_TOOL_FINGER, true);
+
+ touchscreen_report_pos(cd->input_dev, &cd->props,
+ __be16_to_cpu(t->yellowstone.x),
+ __be16_to_cpu(t->yellowstone.y),
+ true);
+ input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
+ t->yellowstone.w);
+ }
+ }
+
+ input_mt_sync_frame(cd->input_dev);
+ input_sync(cd->input_dev);
+}
+
+static void goodix_gtx8_touch_handler(struct goodix_gtx8_core *cd, u8 touch_num,
+ union goodix_gtx8_touch *touch_data)
+{
+ int error;
+
+ touch_num = FIELD_GET(GOODIX_GTX8_TOUCH_COUNT_MASK, touch_num);
+
+ if (touch_num > GOODIX_GTX8_MAX_TOUCH) {
+ dev_warn(cd->dev, "invalid touch num %d\n", touch_num);
+ return;
+ }
+
+ if (touch_num > 1) {
+ /* read additional contact data if more than 1 touch event */
+ error = goodix_gtx8_get_remaining_contacts(cd, touch_num);
+ if (error)
+ return;
+ }
+
+ if (touch_num) {
+ /*
+ * Normandy checksum is for the entire read buffer,
+ * Yellowstone is only for the touch data (since header
+ * has a separate checksum)
+ */
+ if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
+ int len = GOODIX_GTX8_HEADER_SIZE_NORMANDY +
+ touch_num * GOODIX_GTX8_TOUCH_SIZE +
+ GOODIX_GTX8_CHECKSUM_SIZE;
+ if (!goodix_gtx8_checksum_valid_normandy(
+ cd->event_buffer, len)) {
+ dev_err(cd->dev,
+ "touch data checksum error: %*ph\n",
+ len, cd->event_buffer);
+ return;
+ }
+ } else {
+ int len = touch_num * GOODIX_GTX8_TOUCH_SIZE +
+ GOODIX_GTX8_CHECKSUM_SIZE;
+ if (!goodix_gtx8_checksum_valid_yellowstone(
+ (u8 *)touch_data, len)) {
+ dev_err(cd->dev,
+ "touch data checksum error: %*ph\n",
+ len, (u8 *)touch_data);
+ return;
+ }
+ }
+ }
+
+ goodix_gtx8_report_state(cd, touch_num, touch_data);
+}
+
+static irqreturn_t goodix_gtx8_irq(int irq, void *data)
+{
+ struct goodix_gtx8_core *cd = data;
+ struct goodix_gtx8_event_normandy *ev_normandy;
+ struct goodix_gtx8_event_yellowstone *ev_yellowstone;
+ union goodix_gtx8_touch *touch_data;
+ int error;
+ u8 status, touch_num;
+
+ error = regmap_raw_read(
+ cd->regmap, cd->ic_data->touch_data_addr, cd->event_buffer,
+ cd->ic_data->header_size + GOODIX_GTX8_TOUCH_SIZE +
+ GOODIX_GTX8_CHECKSUM_SIZE);
+ if (error) {
+ dev_warn_ratelimited(
+ cd->dev, "failed to get event head data: %d\n", error);
+ goto out;
+ }
+
+ /*
+ * Both IC types have the same data in the header, just at different
+ * offsets
+ */
+ if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
+ ev_normandy =
+ (struct goodix_gtx8_event_normandy *)cd->event_buffer;
+ status = ev_normandy->hdr.status;
+ touch_num = ev_normandy->hdr.touch_num;
+ touch_data = (union goodix_gtx8_touch *)ev_normandy->data;
+ } else {
+ ev_yellowstone = (struct goodix_gtx8_event_yellowstone *)
+ cd->event_buffer;
+ status = ev_yellowstone->hdr.status;
+ touch_num = ev_yellowstone->hdr.touch_num;
+ touch_data = (union goodix_gtx8_touch *)ev_yellowstone->data;
+ }
+
+ if (status == 0)
+ goto out;
+
+ /* Yellowstone ICs have a checksum for the header */
+ if (cd->ic_data->ic_type == IC_TYPE_YELLOWSTONE &&
+ !goodix_gtx8_checksum_valid_yellowstone(
+ cd->event_buffer, GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE)) {
+ dev_warn_ratelimited(cd->dev,
+ "touch head checksum error: %*ph\n",
+ (int)GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE,
+ cd->event_buffer);
+ goto out_clear;
+ }
+
+ if (status & GOODIX_GTX8_TOUCH_EVENT)
+ goodix_gtx8_touch_handler(cd, touch_num, touch_data);
+
+ if (status & GOODIX_GTX8_REQUEST_EVENT) {
+ /*
+ * All configs seen so far either set the firmware request
+ * address to 0 (Normandy) or have it equal the touch data
+ * address (Yellowstone). Neither seems correct, and this
+ * is not testable. Therefore it is currently omitted.
+ */
+ dev_dbg(cd->dev, "received request event, ignoring\n");
+ }
+
+out_clear:
+ /* Clear up status field */
+ regmap_write(cd->regmap, cd->ic_data->touch_data_addr, 0);
+
+out:
+ return IRQ_HANDLED;
+}
+
+static int goodix_gtx8_input_dev_config(struct goodix_gtx8_core *cd)
+{
+ struct input_dev *input_dev;
+ int error;
+
+ input_dev = devm_input_allocate_device(cd->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ cd->input_dev = input_dev;
+ input_set_drvdata(input_dev, cd);
+
+ input_dev->name = "Goodix GTX8 Capacitive TouchScreen";
+ input_dev->phys = "input/ts";
+
+ input_dev->id = goodix_gtx8_input_id;
+
+ input_set_abs_params(cd->input_dev, ABS_MT_POSITION_X, 0, SZ_64K - 1, 0,
+ 0);
+ input_set_abs_params(cd->input_dev, ABS_MT_POSITION_Y, 0, SZ_64K - 1, 0,
+ 0);
+ input_set_abs_params(cd->input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
+
+ touchscreen_parse_properties(cd->input_dev, true, &cd->props);
+
+ error = input_mt_init_slots(cd->input_dev, GOODIX_GTX8_MAX_TOUCH,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ if (error)
+ return error;
+
+ error = input_register_device(cd->input_dev);
+ if (error)
+ return error;
+
+ return 0;
+}
+
+static int goodix_gtx8_read_version(struct goodix_gtx8_core *cd)
+{
+ int error;
+
+ /*
+ * The vendor driver reads a whole lot more data to calculate and
+ * verify a checksum. Without documentation, we don't know what
+ * most of that data is, so we only read the parts we know about
+ * and instead ensure their values are as expected
+ */
+ error = regmap_raw_read(cd->regmap, cd->ic_data->fw_version_addr,
+ &cd->fw_version, sizeof(cd->fw_version));
+ if (error) {
+ dev_err(cd->dev, "error reading fw version, %d\n", error);
+ return error;
+ }
+
+ /*
+ * Since we don't verify the checksum, do a basic check that the
+ * product ID meets expectations
+ */
+ if (memcmp(cd->fw_version.product_id, cd->ic_data->product_id,
+ sizeof(cd->fw_version.product_id))) {
+ dev_err(cd->dev, "unexpected product ID, got: %c%c%c%c\n",
+ cd->fw_version.product_id[0],
+ cd->fw_version.product_id[1],
+ cd->fw_version.product_id[2],
+ cd->fw_version.product_id[3]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int goodix_gtx8_dev_confirm(struct goodix_gtx8_core *cd)
+{
+ u8 rx_buf[1];
+ int retry = 3;
+ int error;
+
+ while (retry--) {
+ /*
+ * test_addr appears to always be the touch_data_addr for
+ * Normandy, but it doesn't really matter since all we
+ * need is a valid address
+ */
+ error = regmap_raw_read(cd->regmap,
+ cd->ic_data->touch_data_addr, rx_buf,
+ sizeof(rx_buf));
+
+ if (!error)
+ return 0;
+
+ usleep_range(5000, 5100);
+ }
+
+ dev_err(cd->dev, "device confirm failed\n");
+
+ return -EINVAL;
+}
+
+static int goodix_gtx8_power_on(struct goodix_gtx8_core *cd)
+{
+ int error;
+
+ error = regulator_enable(cd->vddio);
+ if (error) {
+ dev_err(cd->dev, "Failed to enable VDDIO: %d\n", error);
+ return error;
+ }
+
+ error = regulator_enable(cd->avdd);
+ if (error) {
+ dev_err(cd->dev, "Failed to enable AVDD: %d\n", error);
+ goto err_vddio_disable;
+ }
+
+ /* Vendors usually configure the power on delay as 300ms */
+ msleep(GOODIX_GTX8_POWER_ON_DELAY_MS);
+
+ gpiod_set_value_cansleep(cd->reset_gpio, 0);
+
+ /* Vendor waits 5ms for firmware to initialize */
+ usleep_range(5000, 5100);
+
+ error = goodix_gtx8_dev_confirm(cd);
+ if (error)
+ goto err_dev_reset;
+
+ /* Vendor waits 100ms for firmware to fully boot */
+ msleep(GOODIX_GTX8_NORMAL_RESET_DELAY_MS);
+
+ return 0;
+
+err_dev_reset:
+ gpiod_set_value_cansleep(cd->reset_gpio, 1);
+ regulator_disable(cd->avdd);
+err_vddio_disable:
+ regulator_disable(cd->vddio);
+ return error;
+}
+
+static void goodix_gtx8_power_off(struct goodix_gtx8_core *cd)
+{
+ gpiod_set_value_cansleep(cd->reset_gpio, 1);
+ regulator_disable(cd->avdd);
+ regulator_disable(cd->vddio);
+}
+
+static int goodix_gtx8_suspend(struct device *dev)
+{
+ struct goodix_gtx8_core *cd = dev_get_drvdata(dev);
+
+ disable_irq(cd->irq);
+ goodix_gtx8_power_off(cd);
+
+ return 0;
+}
+
+static int goodix_gtx8_resume(struct device *dev)
+{
+ struct goodix_gtx8_core *cd = dev_get_drvdata(dev);
+ int error;
+
+ error = goodix_gtx8_power_on(cd);
+ if (error)
+ return error;
+
+ enable_irq(cd->irq);
+
+ return 0;
+}
+
+EXPORT_GPL_SIMPLE_DEV_PM_OPS(goodix_gtx8_pm_ops, goodix_gtx8_suspend,
+ goodix_gtx8_resume);
+
+static void goodix_gtx8_power_off_act(void *data)
+{
+ struct goodix_gtx8_core *cd = data;
+
+ goodix_gtx8_power_off(cd);
+}
+
+static int goodix_gtx8_probe(struct i2c_client *client)
+{
+ struct goodix_gtx8_core *cd;
+ struct regmap *regmap;
+ int error;
+
+ cd = devm_kzalloc(&client->dev, sizeof(*cd), GFP_KERNEL);
+ if (!cd)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &goodix_gtx8_regmap_conf);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ cd->dev = &client->dev;
+ cd->irq = client->irq;
+ cd->regmap = regmap;
+ cd->ic_data = i2c_get_match_data(client);
+
+ cd->event_buffer =
+ devm_kzalloc(cd->dev, cd->ic_data->event_size, GFP_KERNEL);
+ if (!cd->event_buffer)
+ return -ENOMEM;
+
+ cd->reset_gpio =
+ devm_gpiod_get_optional(cd->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(cd->reset_gpio))
+ return dev_err_probe(cd->dev, PTR_ERR(cd->reset_gpio),
+ "Failed to request reset GPIO\n");
+
+ cd->avdd = devm_regulator_get(cd->dev, "avdd");
+ if (IS_ERR(cd->avdd))
+ return dev_err_probe(cd->dev, PTR_ERR(cd->avdd),
+ "Failed to request AVDD regulator\n");
+
+ cd->vddio = devm_regulator_get(cd->dev, "vddio");
+ if (IS_ERR(cd->vddio))
+ return dev_err_probe(cd->dev, PTR_ERR(cd->vddio),
+ "Failed to request VDDIO regulator\n");
+
+ error = goodix_gtx8_power_on(cd);
+ if (error) {
+ dev_err(cd->dev, "failed power on");
+ return error;
+ }
+
+ error = devm_add_action_or_reset(cd->dev, goodix_gtx8_power_off_act,
+ cd);
+ if (error)
+ return error;
+
+ error = goodix_gtx8_read_version(cd);
+ if (error) {
+ dev_err(cd->dev, "failed to get version info");
+ return error;
+ }
+
+ error = goodix_gtx8_input_dev_config(cd);
+ if (error) {
+ dev_err(cd->dev, "failed to set input device");
+ return error;
+ }
+
+ error = devm_request_threaded_irq(cd->dev, cd->irq, NULL,
+ goodix_gtx8_irq, IRQF_ONESHOT,
+ "goodix-gtx8", cd);
+ if (error) {
+ dev_err(cd->dev, "request threaded IRQ failed: %d\n", error);
+ return error;
+ }
+
+ dev_set_drvdata(cd->dev, cd);
+
+ dev_dbg(cd->dev,
+ "Goodix GT%c%c%c%c Touchscreen Controller, Version %d.%d.%d.%d\n",
+ cd->fw_version.product_id[0], cd->fw_version.product_id[1],
+ cd->fw_version.product_id[2], cd->fw_version.product_id[3],
+ cd->fw_version.fw_version[0], cd->fw_version.fw_version[1],
+ cd->fw_version.fw_version[2], cd->fw_version.fw_version[3]);
+
+ return 0;
+}
+
+static const struct goodix_gtx8_ic_data gt9886_data = {
+ .event_size = GOODIX_GTX8_EVENT_SIZE_NORMANDY,
+ .fw_version_addr = GOODIX_GTX8_FW_VERSION_ADDR_NORMANDY,
+ .header_size = GOODIX_GTX8_HEADER_SIZE_NORMANDY,
+ .ic_type = IC_TYPE_NORMANDY,
+ .product_id = { '9', '8', '8', '6' },
+ .touch_data_addr = GOODIX_GTX8_TOUCH_DATA_ADDR_NORMANDY,
+};
+
+static const struct goodix_gtx8_ic_data gt9896_data = {
+ .event_size = GOODIX_GTX8_EVENT_SIZE_YELLOWSTONE,
+ .fw_version_addr = GOODIX_GTX8_FW_VERSION_ADDR_YELLOWSTONE,
+ .header_size = GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE,
+ .ic_type = IC_TYPE_YELLOWSTONE,
+ .product_id = { '9', '8', '9', '6' },
+ .touch_data_addr = GOODIX_GTX8_TOUCH_DATA_ADDR_YELLOWSTONE,
+};
+
+static const struct i2c_device_id goodix_gtx8_i2c_id[] = {
+ { .name = "gt9886", .driver_data = (long)>9886_data },
+ { .name = "gt9896", .driver_data = (long)>9896_data },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, goodix_gtx8_i2c_id);
+
+static const struct of_device_id goodix_gtx8_of_match[] = {
+ { .compatible = "goodix,gt9886", .data = >9886_data },
+ { .compatible = "goodix,gt9896", .data = >9896_data },
+ {},
+};
+MODULE_DEVICE_TABLE(of, goodix_gtx8_of_match);
+
+static struct i2c_driver goodix_gtx8_driver = {
+ .probe = goodix_gtx8_probe,
+ .id_table = goodix_gtx8_i2c_id,
+ .driver = {
+ .name = "goodix-gtx8",
+ .of_match_table = of_match_ptr(goodix_gtx8_of_match),
+ .pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
+ },
+};
+module_i2c_driver(goodix_gtx8_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Goodix GTX8 Touchscreen driver");
+MODULE_AUTHOR("Aelin Reidel <aelin@mainlining.org>");
diff --git a/drivers/input/touchscreen/goodix_gtx8.h b/drivers/input/touchscreen/goodix_gtx8.h
new file mode 100644
index 0000000000000000000000000000000000000000..79e79988869b46a3fc70fa64b4698cdb1d7a0394
--- /dev/null
+++ b/drivers/input/touchscreen/goodix_gtx8.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __GOODIX_GTX8_H__
+#define __GOODIX_GTX8_H__
+
+#define GOODIX_GTX8_NORMAL_RESET_DELAY_MS 100
+#define GOODIX_GTX8_POWER_ON_DELAY_MS 300
+
+#define GOODIX_GTX8_TOUCH_EVENT BIT(7)
+#define GOODIX_GTX8_REQUEST_EVENT BIT(6)
+#define GOODIX_GTX8_TOUCH_COUNT_MASK GENMASK(3, 0)
+#define GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE GENMASK(7, 4)
+
+#define GOODIX_GTX8_MAX_TOUCH 10
+#define GOODIX_GTX8_CHECKSUM_SIZE sizeof(u16)
+
+#define GOODIX_GTX8_FW_VERSION_ADDR_NORMANDY 0x4535
+#define GOODIX_GTX8_FW_VERSION_ADDR_YELLOWSTONE 0x4022
+#define GOODIX_GTX8_TOUCH_DATA_ADDR_NORMANDY 0x4100
+#define GOODIX_GTX8_TOUCH_DATA_ADDR_YELLOWSTONE 0x4180
+
+#define I2C_MAX_TRANSFER_SIZE 256
+
+enum goodix_gtx8_ic_type {
+ IC_TYPE_NORMANDY,
+ IC_TYPE_YELLOWSTONE,
+};
+
+struct goodix_gtx8_ic_data {
+ size_t event_size;
+ /*
+ * This is technically not the firmware version address
+ * referenced in the vendor driver, but rather the
+ * address of the product ID part. The meaning of the
+ * other parts is unknown and they are therefore omitted
+ * for now.
+ */
+ int fw_version_addr;
+ size_t header_size;
+ enum goodix_gtx8_ic_type ic_type;
+ char product_id[4];
+ int touch_data_addr;
+};
+
+struct goodix_gtx8_header_normandy {
+ u8 status;
+ /* Only the lower 4 bits are actually used */
+ u8 touch_num;
+};
+#define GOODIX_GTX8_HEADER_SIZE_NORMANDY \
+ sizeof(struct goodix_gtx8_header_normandy)
+
+struct goodix_gtx8_header_yellowstone {
+ u8 status;
+ /* Most likely unused */
+ u8 __unknown1;
+ /* Only the lower 4 bits are actually used */
+ u8 touch_num;
+ /* Most likely unused */
+ u8 __unknown2[3];
+ __le16 checksum;
+} __packed __aligned(1);
+#define GOODIX_GTX8_HEADER_SIZE_YELLOWSTONE \
+ sizeof(struct goodix_gtx8_header_yellowstone)
+
+struct goodix_gtx8_touch_normandy {
+ u8 finger_id;
+ __le16 x;
+ __le16 y;
+ u8 w;
+ u8 __unknown[2];
+} __packed __aligned(1);
+
+struct goodix_gtx8_touch_yellowstone {
+ /*
+ * Only the upper 4 bits are used, lower 4 bits are
+ * probably the sensor ID.
+ */
+ u8 finger_id;
+ u8 __unknown1;
+ __be16 x;
+ __be16 y;
+ /*
+ * Vendor driver claims that this is a single __be16,
+ * but testing shows that it likely isn't.
+ */
+ u8 __unknown2;
+ u8 w;
+} __packed __aligned(1);
+
+union goodix_gtx8_touch {
+ struct goodix_gtx8_touch_normandy normandy;
+ struct goodix_gtx8_touch_yellowstone yellowstone;
+};
+#define GOODIX_GTX8_TOUCH_SIZE sizeof(union goodix_gtx8_touch)
+
+struct goodix_gtx8_event_normandy {
+ struct goodix_gtx8_header_normandy hdr;
+ /* The data below is u16 aligned */
+ u8 data[GOODIX_GTX8_TOUCH_SIZE * GOODIX_GTX8_MAX_TOUCH +
+ GOODIX_GTX8_CHECKSUM_SIZE];
+};
+#define GOODIX_GTX8_EVENT_SIZE_NORMANDY \
+ sizeof(struct goodix_gtx8_event_normandy)
+
+struct goodix_gtx8_event_yellowstone {
+ struct goodix_gtx8_header_yellowstone hdr;
+ /* The data below is u16 aligned */
+ u8 data[GOODIX_GTX8_TOUCH_SIZE * GOODIX_GTX8_MAX_TOUCH +
+ GOODIX_GTX8_CHECKSUM_SIZE];
+};
+#define GOODIX_GTX8_EVENT_SIZE_YELLOWSTONE \
+ sizeof(struct goodix_gtx8_event_yellowstone)
+
+struct goodix_gtx8_fw_version {
+ /* 4 digits IC number */
+ char product_id[4];
+ /* Most likely unused */
+ u8 __unknown[4];
+ /* Four components version number */
+ u8 fw_version[4];
+};
+
+struct goodix_gtx8_core {
+ struct device *dev;
+ struct regmap *regmap;
+ struct regulator *avdd;
+ struct regulator *vddio;
+ struct gpio_desc *reset_gpio;
+ struct touchscreen_properties props;
+ struct goodix_gtx8_fw_version fw_version;
+ struct input_dev *input_dev;
+ int irq;
+ const struct goodix_gtx8_ic_data *ic_data;
+ u8 *event_buffer;
+};
+
+#endif
--
2.53.0
^ permalink raw reply related
* [PATCH 0/3] Input: add initial support for Goodix GTX8 touchscreen ICs
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans de Goede, Neil Armstrong, Henrik Rydberg
Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
~postmarketos/upstreaming, Aelin Reidel, Piyush Raj Chouhan
These ICs support SPI and I2C interfaces, up to 10 finger touch, stylus
and gesture events.
This driver is derived from the Goodix gtx8_driver_linux available at
[1] and only supports the GT9886 and GT9896 ICs present in the Xiaomi
Mi 9T and Xiaomi Redmi Note 10 Pro smartphones.
The current implementation only supports Normandy and Yellowstone type
ICs, aka only GT9886 and GT9896. It is also limited to I2C only, since I
don't have a device with GTX8 over SPI at hand. Adding support for SPI
should be fairly easy in the future, since the code uses a regmap.
Support for advanced features like:
- Firmware updates
- Stylus events
- Gesture events
- Nanjing IC support
is not included in current version.
The current support requires a previously flashed firmware to be
present.
As I did not have access to datasheets for these ICs, I extracted the
addresses from a couple of config files using a small tool [2]. The
addresses are identical for the same IC families in all configs I
observed, however not all of them make sense and I stubbed out firmware
request support due to this.
I've taken a lot of inspiration from the goodix_berlin driver, but the
Berlin and GTX8 series of touchscreen ICs differ quite a bit. The driver
architecture is the same overall, i.e. the power-up sequence and general
concepts are the mostly same, but it is very clear that they are
different generations when looking at it in more detail.
Some of the differences:
- There is no equivalent to the bootoption reg that I can find in the
public GTX8 drivers
- Firmware version struct layout is different yet again
- GTX8 does not expose IC information at runtime as far as I can tell
- The checksum method differs yet again
- The vendor driver reads only 1 touch upfront rather than 2
- Register addresses are 16-bit on GTX8 and 32-bit on Berlin
- Firmware requests don't appear to really exist on GTX8
From what I can tell, the evolution seems to be:
Normandy -> Yellowstone -> Berlin
since Normandy and Yellowstone are already quite different (especially
with the way checksums work) and Yellowstone has a couple of things
(checksum, fw_version) that appear similar to Berlin series ICs.
I've tried to make the Berlin driver work for GTX8 ICs before, but
they're so different (and I lack documentation for registers to perhaps
make some parts work on GTX8) that I'd rather support these ICs in a new
and tiny driver. I hope that makes sense. I took heavy inspiration from
the Berlin driver, but the only parts that are really common between
them are very trivial things like e.g. the input dev config or power on,
which I don't think are worth putting in a separate header.
[1] https://github.com/goodix/gtx8_driver_linux
[2] https://github.com/sm7150-mainline/goodix-cfg-bin
Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
Changes in v1 (post-RFC):
- Drop RFC prefix, the series has been tested enough and works well
as-is
- Update my name and email address
- Add some reasoning for a new driver to the cover letter
- Add Rob's R-b on the dt-bindings patch
- Add Piyush's T-b to the driver patch
- Link to RFC: https://lore.kernel.org/r/20250918-gtx8-v1-0-cba879c84775@mainlining.org
---
Aelin Reidel (3):
dt-bindings: input: document Goodix GTX8 Touchscreen ICs
Input: add support for Goodix GTX8 Touchscreen ICs
MAINTAINERS: add an entry for Goodix GTX8 Touchscreen driver
.../bindings/input/touchscreen/goodix,gt9886.yaml | 71 +++
MAINTAINERS | 7 +
drivers/input/touchscreen/Kconfig | 15 +
drivers/input/touchscreen/Makefile | 1 +
drivers/input/touchscreen/goodix_gtx8.c | 562 +++++++++++++++++++++
drivers/input/touchscreen/goodix_gtx8.h | 137 +++++
6 files changed, 793 insertions(+)
---
base-commit: fe9e3edb6a215515d1148d32a5c445c5bdd7916f
change-id: 20250918-gtx8-59a50ccd78a5
Best regards,
--
Aelin Reidel <aelin@mainlining.org>
^ permalink raw reply
* [PATCH 1/3] dt-bindings: input: document Goodix GTX8 Touchscreen ICs
From: Aelin Reidel @ 2026-02-17 23:50 UTC (permalink / raw)
To: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hans de Goede, Neil Armstrong, Henrik Rydberg
Cc: linux-input, devicetree, linux-kernel, linux, phone-devel,
~postmarketos/upstreaming, Aelin Reidel
In-Reply-To: <20260218-gtx8-v1-0-0d575b3dedc5@mainlining.org>
Document the Goodix GT9886 and GT9896 which are part of the GTX8 series
of Touchscreen controller ICs from Goodix.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Aelin Reidel <aelin@mainlining.org>
---
.../bindings/input/touchscreen/goodix,gt9886.yaml | 71 ++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml b/Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6307495c2746313cfc32cdbb701455d1596be435
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/goodix,gt9886.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/goodix,gt9886.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Goodix GTX8 series touchscreen controller
+
+maintainers:
+ - Aelin Reidel <aelin@mainlining.org>
+
+allOf:
+ - $ref: touchscreen.yaml#
+
+properties:
+ compatible:
+ enum:
+ - goodix,gt9886
+ - goodix,gt9896
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ avdd-supply:
+ description: Analog power supply regulator on AVDD pin
+
+ vddio-supply:
+ description: power supply regulator on VDDIO pin
+
+ touchscreen-inverted-x: true
+ touchscreen-inverted-y: true
+ touchscreen-size-x: true
+ touchscreen-size-y: true
+ touchscreen-swapped-x-y: true
+
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - avdd-supply
+ - touchscreen-size-x
+ - touchscreen-size-y
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ touchscreen@5d {
+ compatible = "goodix,gt9886";
+ reg = <0x5d>;
+ interrupt-parent = <&gpio>;
+ interrupts = <9 IRQ_TYPE_LEVEL_LOW>;
+ reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
+ avdd-supply = <&ts_avdd>;
+ touchscreen-size-x = <1080>;
+ touchscreen-size-y = <2340>;
+ };
+ };
+
+...
--
2.53.0
^ permalink raw reply related
* Re: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: Alexander Koskovich @ 2026-02-18 0:19 UTC (permalink / raw)
To: aelin
Cc: conor+dt, devicetree, dmitry.torokhov, hansg, krzk+dt,
linux-input, linux-kernel, linux, neil.armstrong, pc1598,
phone-devel, robh, rydberg, ~postmarketos/upstreaming,
Alexander Koskovich
Validated on the ASUS ROG Phone 3 (GT9896).
Tested-by: Alexander Koskovich <AKoskovich@pm.me>
^ permalink raw reply
* [PATCH] Input: atkbd: add keymap fixup for notebooks using 0x6e as Fn modifier
From: Mikhail Novosyolov @ 2026-02-18 4:13 UTC (permalink / raw)
To: dmitry.torokhov; +Cc: linux-input, mpearson-lenovo, m.novosyolov
Commit dc8c9c171ef3 ("Input: atkbd - map F23 key to support default
copilot shortcut") mapped scancode 0x6e to KEY_F23 to support the
Microsoft Copilot key on Lenovo, HP, and Dell notebooks.
However, some notebook platforms (including Positron Proxima 15 and
possibly others based on the same OEM design) use scancode 0x6e for the
Fn modifier key instead of a dedicated Copilot key. When 0x6e generates
KEY_F23 events, the Fn key breaks Fn combinations such as Fn+F5
(touchpad toggle).
On these platforms, the hardware relies on 0x6e being unmapped to
properly handle Fn combinations at the firmware level. When the kernel
maps it to KEY_F23, desktop environments intercept this as a global
hotkey and toggle the touchpad, but cannot re-enable it because the
firmware no longer recognizes Fn as a valid modifier.
Userspace solutions (systemd hwdb) cannot fix this because the keycode
mapping happens in the atkbd driver before events reach userspace.
A kernel-level quirk is required.
Add a DMI-based keymap fixup to remap scancode 0x6e to 0 for affected
systems. Currently only Positron Proxima 15 is known to be affected,
but other notebooks based on the same OEM platform may exhibit the same
behavior and can be added to the quirk table.
Scancode 0x6e has different meanings on different hardware:
- Lenovo/HP/Dell: F23 (Copilot key) - correctly mapped to KEY_F23
- Some OEM platforms: Fn modifier - must be unmapped
A generic solution is not feasible without breaking Copilot support
on other vendors.
Link: https://linux-hardware.org/?probe=7aca7ed668
Link: https://bugzilla.rosa.ru/show_bug.cgi?id=19950
Fixes: dc8c9c171ef3 ("Input: atkbd - map F23 key to support default copilot shortcut")
Co-developed-by: GLM-4.7 AI <noreply@z.ai>
Signed-off-by: Mikhail Novosyolov <m.novosyolov@rosa.ru>
---
drivers/input/keyboard/atkbd.c | 51 ++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index 6c999d89ee4b..e4ecdd9fc07b 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -1068,6 +1068,40 @@ static unsigned int atkbd_volume_forced_release_keys[] = {
0xae, 0xb0, -1U
};
+/*
+ * Positron notebooks where scancode 0x6e is used by the Fn key and should
+ * not generate KEY_F23 events.
+ *
+ * Commit dc8c9c171ef3 ("Input: atkbd - map F23 key to support default
+ * copilot shortcut") mapped scancode 0x6e to KEY_F23 for Copilot support.
+ * However, on Positron notebooks scancode 0x6e is generated by the Fn
+ * modifier key and should be unmapped to allow firmware/hardware to
+ * process Fn+F5 and other Fn combinations properly.
+ */
+static unsigned int atkbd_positron_fn_keymap_fixup_scancodes[] = {
+ 0x6e, -1U
+};
+
+/*
+ * Fixup to remap scancodes to 0 (no keycode) for machines where
+ * the default keymap incorrectly assigns keys that should be unused.
+ * This is needed when BIOS/firmware uses certain scancodes for
+ * internal purposes (e.g., Fn modifier) and they should not generate
+ * keyboard events.
+ */
+static void atkbd_apply_keymap_fixup(struct atkbd *atkbd, const void *data)
+{
+ const unsigned int *scancodes = data;
+ unsigned int i;
+
+ for (i = 0; scancodes[i] != -1U; i++) {
+ unsigned int scancode = scancodes[i];
+
+ if (scancode < ATKBD_KEYMAP_SIZE)
+ atkbd->keycode[scancode] = 0;
+ }
+}
+
/*
* OQO 01+ multimedia keys (64--66) generate e0 6x upon release whereas
* they should be generating e4-e6 (0x80 | code).
@@ -1775,6 +1809,14 @@ static int __init atkbd_setup_forced_release(const struct dmi_system_id *id)
return 1;
}
+static int __init atkbd_setup_keymap_fixup(const struct dmi_system_id *id)
+{
+ atkbd_platform_fixup = atkbd_apply_keymap_fixup;
+ atkbd_platform_fixup_data = id->driver_data;
+
+ return 1;
+}
+
static int __init atkbd_setup_scancode_fixup(const struct dmi_system_id *id)
{
atkbd_platform_scancode_fixup = id->driver_data;
@@ -1937,6 +1979,15 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = {
},
.callback = atkbd_deactivate_fixup,
},
+ {
+ /* Positron Proxima 15 - Fn key (0x6e) should not generate F23 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LNPO Positron LLC"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "G1569"),
+ },
+ .callback = atkbd_setup_keymap_fixup,
+ .driver_data = atkbd_positron_fn_keymap_fixup_scancodes,
+ },
{ }
};
--
2.51.0
^ permalink raw reply related
* Re: [PATCH] Input: atkbd: add keymap fixup for notebooks using 0x6e as Fn modifier
From: Dmitry Torokhov @ 2026-02-18 6:26 UTC (permalink / raw)
To: Mikhail Novosyolov; +Cc: linux-input, mpearson-lenovo
In-Reply-To: <20260218041352.797625-1-m.novosyolov@rosa.ru>
On Wed, Feb 18, 2026 at 07:13:52AM +0300, Mikhail Novosyolov wrote:
> Commit dc8c9c171ef3 ("Input: atkbd - map F23 key to support default
> copilot shortcut") mapped scancode 0x6e to KEY_F23 to support the
> Microsoft Copilot key on Lenovo, HP, and Dell notebooks.
>
> However, some notebook platforms (including Positron Proxima 15 and
> possibly others based on the same OEM design) use scancode 0x6e for the
> Fn modifier key instead of a dedicated Copilot key. When 0x6e generates
> KEY_F23 events, the Fn key breaks Fn combinations such as Fn+F5
> (touchpad toggle).
>
> On these platforms, the hardware relies on 0x6e being unmapped to
> properly handle Fn combinations at the firmware level. When the kernel
> maps it to KEY_F23, desktop environments intercept this as a global
> hotkey and toggle the touchpad, but cannot re-enable it because the
> firmware no longer recognizes Fn as a valid modifier.
>
> Userspace solutions (systemd hwdb) cannot fix this because the keycode
> mapping happens in the atkbd driver before events reach userspace.
> A kernel-level quirk is required.
? That is exactly what udev hwdb is for. Use it. Check 60-keyboard.hwdb
for examples.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: kernel test robot @ 2026-02-18 10:40 UTC (permalink / raw)
To: Aelin Reidel, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Neil Armstrong, Henrik Rydberg
Cc: oe-kbuild-all, linux-input, devicetree, linux-kernel, linux,
phone-devel, ~postmarketos/upstreaming, Aelin Reidel,
Piyush Raj Chouhan
In-Reply-To: <20260218-gtx8-v1-2-0d575b3dedc5@mainlining.org>
Hi Aelin,
kernel test robot noticed the following build errors:
[auto build test ERROR on fe9e3edb6a215515d1148d32a5c445c5bdd7916f]
url: https://github.com/intel-lab-lkp/linux/commits/Aelin-Reidel/dt-bindings-input-document-Goodix-GTX8-Touchscreen-ICs/20260218-075424
base: fe9e3edb6a215515d1148d32a5c445c5bdd7916f
patch link: https://lore.kernel.org/r/20260218-gtx8-v1-2-0d575b3dedc5%40mainlining.org
patch subject: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20260218/202602181848.DK5Wc0iI-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 15.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260218/202602181848.DK5Wc0iI-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602181848.DK5Wc0iI-lkp@intel.com/
All errors (new ones prefixed by >>):
drivers/input/touchscreen/goodix_gtx8.c: In function 'goodix_gtx8_report_state':
>> drivers/input/touchscreen/goodix_gtx8.c:110:37: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration]
110 | finger_id = FIELD_GET(
| ^~~~~~~~~
In file included from include/linux/kernel.h:35,
from include/linux/random.h:7,
from include/linux/nodemask.h:94,
from include/linux/numa.h:6,
from include/linux/cpumask.h:15,
from include/linux/smp.h:13,
from include/linux/lockdep.h:14,
from include/linux/spinlock.h:63,
from include/linux/mmzone.h:8,
from include/linux/gfp.h:7,
from include/linux/slab.h:17,
from include/linux/resource_ext.h:11,
from include/linux/acpi.h:14,
from include/linux/i2c.h:13,
from drivers/input/touchscreen/goodix_gtx8.c:16:
drivers/input/touchscreen/goodix_gtx8.c: At top level:
>> drivers/input/touchscreen/goodix_gtx8.c:555:37: error: 'goodix_gtx8_pm_ops' undeclared here (not in a function); did you mean 'goodix_gtx8_probe'?
555 | .pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
| ^~~~~~~~~~~~~~~~~~
include/linux/util_macros.h:136:44: note: in definition of macro 'PTR_IF'
136 | #define PTR_IF(cond, ptr) ((cond) ? (ptr) : NULL)
| ^~~
drivers/input/touchscreen/goodix_gtx8.c:555:23: note: in expansion of macro 'pm_sleep_ptr'
555 | .pm = pm_sleep_ptr(&goodix_gtx8_pm_ops),
| ^~~~~~~~~~~~
vim +/FIELD_GET +110 drivers/input/touchscreen/goodix_gtx8.c
87
88 static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
89 union goodix_gtx8_touch *touch_data)
90 {
91 union goodix_gtx8_touch *t;
92 int i;
93 u8 finger_id;
94
95 for (i = 0; i < touch_num; i++) {
96 t = &touch_data[i];
97
98 if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
99 input_mt_slot(cd->input_dev, t->normandy.finger_id);
100 input_mt_report_slot_state(cd->input_dev,
101 MT_TOOL_FINGER, true);
102
103 touchscreen_report_pos(cd->input_dev, &cd->props,
104 __le16_to_cpu(t->normandy.x),
105 __le16_to_cpu(t->normandy.y),
106 true);
107 input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
108 t->normandy.w);
109 } else {
> 110 finger_id = FIELD_GET(
111 GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
112 t->yellowstone.finger_id);
113 input_mt_slot(cd->input_dev, finger_id);
114 input_mt_report_slot_state(cd->input_dev,
115 MT_TOOL_FINGER, true);
116
117 touchscreen_report_pos(cd->input_dev, &cd->props,
118 __be16_to_cpu(t->yellowstone.x),
119 __be16_to_cpu(t->yellowstone.y),
120 true);
121 input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
122 t->yellowstone.w);
123 }
124 }
125
126 input_mt_sync_frame(cd->input_dev);
127 input_sync(cd->input_dev);
128 }
129
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
From: kernel test robot @ 2026-02-18 13:28 UTC (permalink / raw)
To: Aelin Reidel, Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Hans de Goede, Neil Armstrong, Henrik Rydberg
Cc: llvm, oe-kbuild-all, linux-input, devicetree, linux-kernel, linux,
phone-devel, ~postmarketos/upstreaming, Aelin Reidel,
Piyush Raj Chouhan
In-Reply-To: <20260218-gtx8-v1-2-0d575b3dedc5@mainlining.org>
Hi Aelin,
kernel test robot noticed the following build errors:
[auto build test ERROR on fe9e3edb6a215515d1148d32a5c445c5bdd7916f]
url: https://github.com/intel-lab-lkp/linux/commits/Aelin-Reidel/dt-bindings-input-document-Goodix-GTX8-Touchscreen-ICs/20260218-075424
base: fe9e3edb6a215515d1148d32a5c445c5bdd7916f
patch link: https://lore.kernel.org/r/20260218-gtx8-v1-2-0d575b3dedc5%40mainlining.org
patch subject: [PATCH 2/3] Input: add support for Goodix GTX8 Touchscreen ICs
config: sparc64-allmodconfig (https://download.01.org/0day-ci/archive/20260218/202602182104.ONBwXzkn-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project e86750b29fa0ff207cd43213d66dabe565417638)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260218/202602182104.ONBwXzkn-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602182104.ONBwXzkn-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/input/touchscreen/goodix_gtx8.c:110:16: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
110 | finger_id = FIELD_GET(
| ^
drivers/input/touchscreen/goodix_gtx8.c:135:14: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
135 | touch_num = FIELD_GET(GOODIX_GTX8_TOUCH_COUNT_MASK, touch_num);
| ^
2 errors generated.
vim +/FIELD_GET +110 drivers/input/touchscreen/goodix_gtx8.c
87
88 static void goodix_gtx8_report_state(struct goodix_gtx8_core *cd, u8 touch_num,
89 union goodix_gtx8_touch *touch_data)
90 {
91 union goodix_gtx8_touch *t;
92 int i;
93 u8 finger_id;
94
95 for (i = 0; i < touch_num; i++) {
96 t = &touch_data[i];
97
98 if (cd->ic_data->ic_type == IC_TYPE_NORMANDY) {
99 input_mt_slot(cd->input_dev, t->normandy.finger_id);
100 input_mt_report_slot_state(cd->input_dev,
101 MT_TOOL_FINGER, true);
102
103 touchscreen_report_pos(cd->input_dev, &cd->props,
104 __le16_to_cpu(t->normandy.x),
105 __le16_to_cpu(t->normandy.y),
106 true);
107 input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
108 t->normandy.w);
109 } else {
> 110 finger_id = FIELD_GET(
111 GOODIX_GTX8_FINGER_ID_MASK_YELLOWSTONE,
112 t->yellowstone.finger_id);
113 input_mt_slot(cd->input_dev, finger_id);
114 input_mt_report_slot_state(cd->input_dev,
115 MT_TOOL_FINGER, true);
116
117 touchscreen_report_pos(cd->input_dev, &cd->props,
118 __be16_to_cpu(t->yellowstone.x),
119 __be16_to_cpu(t->yellowstone.y),
120 true);
121 input_report_abs(cd->input_dev, ABS_MT_TOUCH_MAJOR,
122 t->yellowstone.w);
123 }
124 }
125
126 input_mt_sync_frame(cd->input_dev);
127 input_sync(cd->input_dev);
128 }
129
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] HID: mcp2221: Add module parameter to enfoce GPIO mode
From: Linus Walleij @ 2026-02-18 13:44 UTC (permalink / raw)
To: Rishi Gupta, Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-gpio, Linus Walleij
Add a module parameter to MCP2221 to enfor GPIO mode on the
general purpose pins GP0 thru GP3.
If I plug a device of this type into my machine, it will use the
GP0, GP1, GP2 and GP3 pins for IIO voltage readings by default
if CONFIG_IIO is set.
However there may be cases where IIO is available but we want to
use the GP0 thru GP3 lines for GPIO anyway.
Example use:
insmode hid-mcp2221.ko gpio_mode_enforce=1
Result in dmesg:
mcp2221 0003:04D8:00DD.0005: GPIO 0 not in gpio mode
mcp2221 0003:04D8:00DD.0005: GPIO 1 not in gpio mode
mcp2221 0003:04D8:00DD.0005: GPIO 2 not in gpio mode
mcp2221 0003:04D8:00DD.0005: GPIO 3 not in gpio mode
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 0!
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 1!
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 2!
mcp2221 0003:04D8:00DD.0005: Set GPIO mode for gpio pin 3!
After this the gpiolib tools such as gpioset can be used to alter
the GPIO line values successfully.
Signed-off-by: Linus Walleij <linusw@kernel.org>
---
drivers/hid/hid-mcp2221.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c
index 33603b019f97..58695f3f9e9d 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -19,8 +19,15 @@
#include <linux/gpio/driver.h>
#include <linux/iio/iio.h>
#include <linux/minmax.h>
+#include <linux/moduleparam.h>
#include "hid-ids.h"
+static bool gpio_mode_enforce;
+
+module_param(gpio_mode_enforce, bool, 0644);
+MODULE_PARM_DESC(gpio_mode_enforce,
+ "Enfore GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)");
+
/* Commands codes in a raw output report */
enum {
MCP2221_I2C_WR_DATA = 0x90,
@@ -648,7 +655,7 @@ static int mcp2221_check_gpio_pinfunc(struct mcp2221 *mcp)
int needgpiofix = 0;
int ret;
- if (IS_ENABLED(CONFIG_IIO))
+ if (IS_ENABLED(CONFIG_IIO) && !gpio_mode_enforce)
return 0;
ret = mcp_gpio_read_sram(mcp);
@@ -1043,7 +1050,8 @@ static void mcp2221_remove(struct hid_device *hdev)
#if IS_REACHABLE(CONFIG_IIO)
struct mcp2221 *mcp = hid_get_drvdata(hdev);
- cancel_delayed_work_sync(&mcp->init_work);
+ if (!gpio_mode_enforce)
+ cancel_delayed_work_sync(&mcp->init_work);
#endif
}
@@ -1317,8 +1325,10 @@ static int mcp2221_probe(struct hid_device *hdev,
#endif
#if IS_REACHABLE(CONFIG_IIO)
- INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
- schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+ if (!gpio_mode_enforce) {
+ INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
+ schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+ }
#endif
return 0;
---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260218-hid-mcp2221-gpio-46f7c1a638c4
Best regards,
--
Linus Walleij <linusw@kernel.org>
^ permalink raw reply related
* Re: [PATCH 1/3] HID: apple: avoid memory leak in apple_report_fixup()
From: Benjamin Tissoires @ 2026-02-18 19:04 UTC (permalink / raw)
To: Günther Noack; +Cc: Jiri Kosina, linux-input, linux-kernel
In-Reply-To: <aZTEnPEHcWEkoTJR@google.com>
On Feb 17 2026, Günther Noack wrote:
> Hello Benjamin!
>
> On Tue, Feb 17, 2026 at 07:22:20PM +0100, Benjamin Tissoires wrote:
> > On Feb 17 2026, Günther Noack wrote:
> > > The apple_report_fixup() function was allocating a new buffer with
> > > kmemdup() but never freeing it. Since the caller of report_fixup() already
> > > provides a writable buffer and allows returning a pointer within that
> > > buffer, we can just modify the descriptor in-place and return the adjusted
> > > pointer.
> > >
> > > Assisted-by: Gemini-CLI:Google Gemini 3
> > > Signed-off-by: Günther Noack <gnoack@google.com>
> > > ---
> > > drivers/hid/hid-apple.c | 4 +---
> > > 1 file changed, 1 insertion(+), 3 deletions(-)
> > >
> > > diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
> > > index 233e367cce1d..894adc23367b 100644
> > > --- a/drivers/hid/hid-apple.c
> > > +++ b/drivers/hid/hid-apple.c
> > > @@ -686,9 +686,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> > > hid_info(hdev,
> > > "fixing up Magic Keyboard battery report descriptor\n");
> > > *rsize = *rsize - 1;
> > > - rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
> > > - if (!rdesc)
> > > - return NULL;
> > > + rdesc = rdesc + 1;
> >
> > I might be wrong, but later we call free(dev->rdesc) on device removal.
> > AFAICT, incrementing the pointer is undefined behavior.
> >
> > What we should do instead is probably a krealloc instead of a kmemdup.
> >
> > Same for all 3 patches.
>
> Thanks for the review.
>
> Let me try to address your three responses in one reply.
>
> I am happy to accept it if I am wrong about this, but I don't
> understand your reasoning. (This should go without saying, but maybe
> is worth reiterating, I would not have sent this if I had not
> convinced myself independently of LLM-assisted reasoning.)
>
> Let me explain my reasoning based on the place where .report_fixup()
> is called from, which is hid_open_report() in hid-core.c:
>
>
> start = device->bpf_rdesc; /* (1) */
> if (WARN_ON(!start))
> return -ENODEV;
> size = device->bpf_rsize;
>
> if (device->driver->report_fixup) {
> /*
> * device->driver->report_fixup() needs to work
> * on a copy of our report descriptor so it can
> * change it.
> */
> __u8 *buf = kmemdup(start, size, GFP_KERNEL); /* (2) */
>
> if (buf == NULL)
> return -ENOMEM;
>
> start = device->driver->report_fixup(device, buf, &size); /* (3) */
>
> /*
> * The second kmemdup is required in case report_fixup() returns
> * a static read-only memory, but we have no idea if that memory
> * needs to be cleaned up or not at the end.
> */
> start = kmemdup(start, size, GFP_KERNEL); /* (4) */
> kfree(buf); /* (5) */
> if (start == NULL)
> return -ENOMEM;
> }
>
> device->rdesc = start;
> device->rsize = size;
>
>
> This function uses a slightly elaborate scheme to call .report_fixup:
>
> (1) start is assigned to the original device->bpf_rdesc
> (2) buf is assigned to a copy of the 'start' buffer (deallocated in (5)).
> . It is done because buf is meant to be mutated by .report_fixup()
> . (3) start = ...->report_fixup(..., buf, ...)
> . (4) start = kmemdup(start, ...)
> (5) deallocate buf
Ouch. Yeah, sorry. I wrote that code and it seemed I completely paged
it out. Your code is actually correct (all three) but it would be nice
to have a longer commit message explaining this above.
The main point of this alloc before calling fixup is because some
drivers are using a static array as the new report descriptor. So we can
not free it later on. Working on a known copy allows to handle the kfree
correctly.
So yes, sorry, returning rdesc+1 in 1/3 and 2/3 is correct, and using a
devm_kzalloc is too in 3/3.
Cheers,
Benjamin
>
> Importantly:
>
> (a) The buffer buf passed to report_fixup() is a copy of the report
> descriptor whose lifetime spans only from (2) to (5).
> (b) The result of .report_fixup(), start, is immediately discarded in
> (4) and reassigned to the start variable again.
>
> From (b), we can see that the result of .report_fixup() does *not* get
> deallocated by the caller, and thus, when the driver wants to return a
> newly allocated array, is must also hold a reference to it so that it
> can deallocate it later.
>
> From (a), we can see that the report_fixup hook is free to manipulate
> the contents of the buffer that it receives, but if we were to
> *reallocate* it within report_fixup, as you are suggesting above, it
> could become a double free:
>
> * During realloc, the allocator would potentially have to move the
> buffer to a place where there is enough space, freeing up the old
> place and allocating a new place. [1]
> * In (5), we would then pass the original (now deallocated) buf
> pointer to kfree, leading to a double free.
>
> If I were to describe the current interface of the hook
> .report_fixup(dev, rdesc, rsize), it would be:
>
> * report_fixup may modify rdesc[0] to rdesc[rsize-1]
> * report_fixup may *not* deallocate rdesc
> (ownership of rdesc stays with the caller)
> * specifically, it may also not reallocate rdesc
> (because that may imply a deallocation)
> * report_fixup returns a pointer to a buffer for which it can
> guarantee that it lives long enough for the kmemdup in (4), but
> which will *not* be deallocated by the caller (see (b) above). The
> three techniques I have found for that are:
> * returning a subsection of the rdesc that it received
> * returning a pointer into a statically allocated array
> (e.g. motion_fixup() and ps3remote_fixup() in hid-sony.c)
> * allocating it with a devm_*() function. My understanding was
> that this ties it to the lifetime of the device. (e.g. the
> QUIRK_G752_KEYBOARD case in hid-asus.c)
>
> Honestly, I still think that this reasoning holds, but I am happy to
> be convinced otherwise. Please let me know what you think.
>
> —Günther
^ permalink raw reply
* [dtor-input:next] BUILD SUCCESS e7b53288d9ea899abc6d47a7f20065ab511a810c
From: kernel test robot @ 2026-02-18 19:38 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: e7b53288d9ea899abc6d47a7f20065ab511a810c Input: drv260x - fix unbalanced regulator_disable() call
elapsed time: 720m
configs tested: 269
configs skipped: 3
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc axs103_defconfig gcc-15.2.0
arc axs103_smp_defconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc haps_hs_smp_defconfig clang-23
arc haps_hs_smp_defconfig gcc-15.2.0
arc nsim_700_defconfig clang-23
arc randconfig-001-20260218 clang-23
arc randconfig-001-20260219 clang-23
arc randconfig-002-20260218 clang-23
arc randconfig-002-20260219 clang-23
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm dove_defconfig gcc-15.2.0
arm exynos_defconfig clang-23
arm hisi_defconfig gcc-15.2.0
arm jornada720_defconfig clang-23
arm mps2_defconfig clang-23
arm multi_v5_defconfig gcc-15.2.0
arm mvebu_v5_defconfig clang-23
arm neponset_defconfig gcc-15.2.0
arm netwinder_defconfig clang-23
arm orion5x_defconfig clang-23
arm pxa_defconfig gcc-15.2.0
arm randconfig-001-20260218 clang-23
arm randconfig-001-20260219 clang-23
arm randconfig-002-20260218 clang-23
arm randconfig-002-20260219 clang-23
arm randconfig-003-20260218 clang-23
arm randconfig-003-20260219 clang-23
arm randconfig-004-20260218 clang-23
arm randconfig-004-20260219 clang-23
arm socfpga_defconfig clang-23
arm spitz_defconfig gcc-15.2.0
arm stm32_defconfig gcc-15.2.0
arm versatile_defconfig gcc-15.2.0
arm vt8500_v6_v7_defconfig gcc-15.2.0
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260218 clang-23
arm64 randconfig-002-20260218 clang-23
arm64 randconfig-003-20260218 clang-23
arm64 randconfig-004-20260218 clang-23
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260218 clang-23
csky randconfig-002-20260218 clang-23
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260218 clang-16
hexagon randconfig-001-20260219 clang-17
hexagon randconfig-002-20260218 clang-16
hexagon randconfig-002-20260219 clang-17
i386 allmodconfig clang-20
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260218 clang-20
i386 buildonly-randconfig-002-20260218 clang-20
i386 buildonly-randconfig-003-20260218 clang-20
i386 buildonly-randconfig-004-20260218 clang-20
i386 buildonly-randconfig-005-20260218 clang-20
i386 buildonly-randconfig-006-20260218 clang-20
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260218 clang-20
i386 randconfig-002-20260218 clang-20
i386 randconfig-003-20260218 clang-20
i386 randconfig-004-20260218 clang-20
i386 randconfig-005-20260218 clang-20
i386 randconfig-006-20260218 clang-20
i386 randconfig-007-20260218 clang-20
i386 randconfig-011-20260218 gcc-14
i386 randconfig-012-20260218 gcc-14
i386 randconfig-013-20260218 gcc-14
i386 randconfig-014-20260218 gcc-14
i386 randconfig-015-20260218 gcc-14
i386 randconfig-016-20260218 gcc-14
i386 randconfig-017-20260218 gcc-14
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260218 clang-16
loongarch randconfig-001-20260219 clang-17
loongarch randconfig-002-20260218 clang-16
loongarch randconfig-002-20260219 clang-17
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
m68k m5249evb_defconfig gcc-15.2.0
m68k mvme16x_defconfig gcc-15.2.0
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips ath25_defconfig clang-23
mips bigsur_defconfig clang-23
mips cobalt_defconfig clang-23
mips decstation_r4k_defconfig clang-23
mips ip28_defconfig gcc-15.2.0
mips malta_qemu_32r6_defconfig clang-23
mips pic32mzda_defconfig clang-23
mips rm200_defconfig clang-23
mips sb1250_swarm_defconfig gcc-15.2.0
mips xway_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260218 clang-16
nios2 randconfig-001-20260219 clang-17
nios2 randconfig-002-20260218 clang-16
nios2 randconfig-002-20260219 clang-17
openrisc allmodconfig clang-23
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc de0_nano_multicore_defconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc generic-32bit_defconfig gcc-15.2.0
parisc randconfig-001-20260218 gcc-8.5.0
parisc randconfig-002-20260218 gcc-8.5.0
parisc64 defconfig clang-19
powerpc adder875_defconfig gcc-15.2.0
powerpc akebono_defconfig gcc-15.2.0
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc amigaone_defconfig clang-23
powerpc chrp32_defconfig clang-23
powerpc cm5200_defconfig clang-23
powerpc currituck_defconfig gcc-15.2.0
powerpc ep88xc_defconfig clang-23
powerpc fsp2_defconfig gcc-15.2.0
powerpc icon_defconfig gcc-15.2.0
powerpc katmai_defconfig gcc-15.2.0
powerpc kmeter1_defconfig clang-23
powerpc mpc885_ads_defconfig gcc-15.2.0
powerpc pcm030_defconfig clang-23
powerpc ppa8548_defconfig gcc-15.2.0
powerpc randconfig-001-20260218 gcc-8.5.0
powerpc randconfig-002-20260218 gcc-8.5.0
powerpc redwood_defconfig gcc-15.2.0
powerpc storcenter_defconfig clang-23
powerpc tqm8540_defconfig clang-23
powerpc tqm8555_defconfig clang-23
powerpc warp_defconfig clang-23
powerpc xes_mpc85xx_defconfig clang-23
powerpc64 randconfig-001-20260218 gcc-8.5.0
powerpc64 randconfig-002-20260218 gcc-8.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260218 clang-23
riscv randconfig-002-20260218 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260218 clang-23
s390 randconfig-002-20260218 clang-23
s390 zfcpdump_defconfig gcc-15.2.0
sh alldefconfig clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh defconfig gcc-14
sh kfr2r09_defconfig gcc-15.2.0
sh landisk_defconfig gcc-15.2.0
sh polaris_defconfig gcc-15.2.0
sh r7785rp_defconfig gcc-15.2.0
sh randconfig-001-20260218 clang-23
sh randconfig-002-20260218 clang-23
sh rsk7269_defconfig gcc-15.2.0
sh sh7770_generic_defconfig clang-23
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260218 gcc-10.5.0
sparc randconfig-002-20260218 gcc-10.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260218 gcc-10.5.0
sparc64 randconfig-002-20260218 gcc-10.5.0
um alldefconfig clang-23
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260218 gcc-10.5.0
um randconfig-002-20260218 gcc-10.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260218 gcc-14
x86_64 buildonly-randconfig-001-20260219 gcc-14
x86_64 buildonly-randconfig-002-20260218 gcc-14
x86_64 buildonly-randconfig-002-20260219 gcc-14
x86_64 buildonly-randconfig-003-20260218 gcc-14
x86_64 buildonly-randconfig-003-20260219 gcc-14
x86_64 buildonly-randconfig-004-20260218 gcc-14
x86_64 buildonly-randconfig-004-20260219 gcc-14
x86_64 buildonly-randconfig-005-20260218 gcc-14
x86_64 buildonly-randconfig-005-20260219 gcc-14
x86_64 buildonly-randconfig-006-20260218 gcc-14
x86_64 buildonly-randconfig-006-20260219 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260218 gcc-14
x86_64 randconfig-002-20260218 gcc-14
x86_64 randconfig-003-20260218 gcc-14
x86_64 randconfig-004-20260218 gcc-14
x86_64 randconfig-005-20260218 gcc-14
x86_64 randconfig-006-20260218 gcc-14
x86_64 randconfig-011-20260218 gcc-13
x86_64 randconfig-012-20260218 gcc-13
x86_64 randconfig-013-20260218 gcc-13
x86_64 randconfig-014-20260218 gcc-13
x86_64 randconfig-015-20260218 gcc-13
x86_64 randconfig-016-20260218 gcc-13
x86_64 randconfig-071-20260218 clang-20
x86_64 randconfig-072-20260218 clang-20
x86_64 randconfig-073-20260218 clang-20
x86_64 randconfig-074-20260218 clang-20
x86_64 randconfig-075-20260218 clang-20
x86_64 randconfig-076-20260218 clang-20
x86_64 randconfig-076-20260218 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa allyesconfig gcc-15.2.0
xtensa common_defconfig gcc-15.2.0
xtensa generic_kc705_defconfig clang-23
xtensa randconfig-001-20260218 gcc-10.5.0
xtensa randconfig-002-20260218 gcc-10.5.0
xtensa smp_lx200_defconfig gcc-15.2.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH 0/2] iio: orientation: hid-sensor-rotation: fix quaternion alignment
From: Jonathan Cameron @ 2026-02-18 19:52 UTC (permalink / raw)
To: Andy Shevchenko
Cc: David Lechner, Nuno Sá, Andy Shevchenko, Jiri Kosina,
Srinivas Pandruvada, linux-iio, linux-kernel, Jonathan Cameron,
linux-input, Lixu Zhang
In-Reply-To: <aZQijRLVjzuWcU2W@smile.fi.intel.com>
On Tue, 17 Feb 2026 10:10:53 +0200
Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> On Mon, Feb 16, 2026 at 09:25:58AM -0600, David Lechner wrote:
> > On 2/16/26 1:44 AM, Andy Shevchenko wrote:
> > > On Sat, Feb 14, 2026 at 03:00:19PM -0600, David Lechner wrote:
> > >> The main point of this series is to fix a regression reported in
> > >> hid-sensor-rotation where the alignment of the quaternion field in the
> > >> data was inadvertently changed from 16 bytes to 8 bytes. This is an
> > >> unusually case (one of only 2 in the kernel) where the .repeat field of
> > >> struct iio_scan_type is used and we have such a requirement. (The other
> > >> case uses u16 instead of u32, so it wasn't affected.)
> > >>
> > >> To make the reason for the alignment more explicit to future readers,
> > >> we introduce a new macro, IIO_DECLARE_REPEATED_ELEMENT, to declare the
> > >> array with proper allignment. This is meant to follow the pattern of
> > >> the similar IIO_DECLARE_BUFFER_WITH_TS() macro.
> > >
> > > In both cases it's quaternion, maybe be more explicit and define
> > > IIO_DECLARE_QUATERNION() ?
I like this. It's special and this shouts that nicely.
> >
> > It is really the fact that the scan_type has .repeat > 1 that requires
> > this, so I was trying to make a name that shows that link.
> >
> > But right now, quaternion is the only thing that has .repeat > 1, so
> > I guess it would be OK either way. We'll see if Jonathan has an
> > opinion on the naming.
>
> I think we should solve the problems when they appear. Naming explicitly
> for quaternion makes it easier to get from the core reading without having
> a variable name to repeat that. Magic 4 might not always be a quaternion.
>
> Do we have "repeat" to be used in other cases, btw?
>
Don't think so.
Note there is a second older bug here.
The timestamp is landing at the wrong location :( See discussion of
original bug. I suspect that applies to the bno055 as well (that avoids
the bug we are fixing in this patch because helpfully the quaternion is
a total of 8 bytes.
Short story - it is just another channel, so should be naturally aligned
at first valid location after the previous channel. That's bytes 32-39 not
55-63 which is where iio_push_to_buffers_with_timestamp() puts it.
Jonathan
^ permalink raw reply
* Re: [PATCH v2 4/5] input: drv260x: Fix unbalanced regulator_disable() call
From: Dmitry Torokhov @ 2026-02-19 2:43 UTC (permalink / raw)
To: Yauhen Kharuzhy; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <20260215141435.727872-5-jekhor@gmail.com>
Hi Yauhen,
On Sun, Feb 15, 2026 at 04:14:34PM +0200, Yauhen Kharuzhy wrote:
> +static void drv260x_remove(struct i2c_client *client)
> +{
> + struct drv260x_data *haptics = i2c_get_clientdata(client);
> +
> + regulator_disable(haptics->regulator);
> }
This will result in power being shut off the first thing during
unbinding, which is too early.
I switched this to devm_add_action_or_reset() and applied.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2 0/5] DRV260x: Support ACPI-enumerated devices
From: Dmitry Torokhov @ 2026-02-19 2:44 UTC (permalink / raw)
To: Yauhen Kharuzhy; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <20260215141435.727872-1-jekhor@gmail.com>
On Sun, Feb 15, 2026 at 04:14:30PM +0200, Yauhen Kharuzhy wrote:
> Lenovo Yoga Book YB1-X90 and YB1-X91 tablets use haptics controllers
> DRV2604L. The X91 (Windows tablet) uses ACPI to define its configuration,
> such as I2C address and GPIO connections. The X90 (Android tablet)
> doesn't have it in the ACPI, but the device may be defined as an
> i2c_board in the x86-android-tablets driver.
>
> To support these variants, add an ACPI matching table and add additional
> I2C IDs to the I2C matching table (the driver supports DRV2604(L),
> DRV2605(L) devices).
>
> Also, implement a timeout for waiting for calibration,
> and fix the non-working suspend due to unbalanced regulator_disable() call.
Applied the series with a small modification in #4.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2] bcm5974: recover from failed mode switch
From: Dmitry Torokhov @ 2026-02-19 2:45 UTC (permalink / raw)
To: Liam Mitchell; +Cc: Henrik Rydberg, linux-input, linux-kernel
In-Reply-To: <20260213-bcm5974-reset-v2-1-1837851336b0@gmail.com>
Hi Liam,
On Fri, Feb 13, 2026 at 10:25:40AM +0100, Liam Mitchell wrote:
> Mode switches sent before control response are ignored.
> On receiving unknown 8-byte packets, assume that mode switch was ignored
> and reset by switching to normal mode, waiting then switching back to
> wellspring mode.
>
> ---
> This patch addresses an issue where the bcm5974 driver switches modes
> before the device is ready, resulting in an unresponsive trackpad and
> "bcm5974: bad trackpad package, length: 8" repeated in logs.
>
> Discussion of issue in the thread:
> https://lore.kernel.org/linux-input/CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com/
>
> This fix is conservative, avoiding changing existing mode-switch
> behavior because I cannot test all variations of hardware.
>
> On receiving an unknown 8-byte packet, we assume the device is not in
> wellspring mode and schedule an asynchronous mode reset.
>
> Signed-off-by: Liam Mitchell <mitchell.liam@gmail.com>
> Link: https://lore.kernel.org/linux-input/CAOQ1CL4+DP1TuLAGNsz5GdFBTHvnTg=5q=Dr2Z1OQc6RXydSYA@mail.gmail.com/
> ---
> Changes in v2:
> - mutex_lock -> guard(mutex)
> - dprintk -> dev_err
> - msleep -> fsleep
> - removed 0 init
> - cancel_work_sync -> disable_delayed_work_sync
> - work_struct -> delayed_work
My apologies, I did not mean to request switch to delayed work, just to
switch to disable_work_sync().
I fixed this up on my side and applied.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2 4/5] input: drv260x: Fix unbalanced regulator_disable() call
From: Yauhen Kharuzhy @ 2026-02-19 10:11 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, Hans de Goede
In-Reply-To: <aZZ4jqZQZ4vA1SFO@google.com>
On Wed, Feb 18, 2026 at 06:43:30PM -0800, Dmitry Torokhov wrote:
> Hi Yauhen,
>
> On Sun, Feb 15, 2026 at 04:14:34PM +0200, Yauhen Kharuzhy wrote:
> > +static void drv260x_remove(struct i2c_client *client)
> > +{
> > + struct drv260x_data *haptics = i2c_get_clientdata(client);
> > +
> > + regulator_disable(haptics->regulator);
> > }
>
> This will result in power being shut off the first thing during
> unbinding, which is too early.
>
> I switched this to devm_add_action_or_reset() and applied.
Do you mean that, in this case, regulator_disable() may be called before any
devm_* registered actions? Good point, thanks.
--
Yauhen Kharuzhy
^ permalink raw reply
* Re: [PATCH v2 1/3] dt-bindings: power: reset: qcom-pon: Add new compatible PMM8654AU
From: Rakesh Kota @ 2026-02-19 10:48 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Dmitry Baryshkov, Krzysztof Kozlowski, Sebastian Reichel,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Vinod Koul,
Dmitry Torokhov, Courtney Cavin, Bjorn Andersson, Konrad Dybcio,
linux-pm, devicetree, linux-kernel, linux-arm-msm, linux-input,
Rakesh Kota
In-Reply-To: <bd10782b-444c-417b-bf27-9fc6a2117567@oss.qualcomm.com>
On Tue, Feb 17, 2026 at 01:27:29PM +0100, Konrad Dybcio wrote:
> On 2/13/26 7:17 PM, Dmitry Baryshkov wrote:
> > On Tue, Feb 10, 2026 at 01:56:12PM +0530, Rakesh Kota wrote:
> >> On Mon, Feb 09, 2026 at 02:49:24PM +0100, Krzysztof Kozlowski wrote:
> >>> On 09/02/2026 14:23, Rakesh Kota wrote:
> >>>> Add the compatible string "qcom,pmm8654au-pon" for the PMM8654AU PMIC.
> >>>> The PON peripheral on PMM8654AU is compatible with PMK8350, so it is
> >>>> documented as a fallback to "qcom,pmk8350-pon".
> >>>
> >>> Drop everything after ,. Do not explain WHAT you did. We see it.
> >>>
> >>>>
> >>>> While PMM8654AU supports additional registers compared to the baseline,
> >
> > I can't find PMM8654AU either on Qualcomm.com or in the catalog. Is it
> > an actual name for the chip?
>
> Right, I would like to see some clarity on that too.
>
> I see there's a PMM8650AU and there's two variants of it, perhaps that's
> one of them?
>
To clarify, PMM8654AU is a different PMIC from the PMM8650AU, though
they do share the same PMIC subtype.
We are specifically using the "PMM8654AU" name because that is what has
been defined and used in the upstream pinctrl-spmi-gpio.c driver
compatible. To ensure consistency with the kernel driver
representation, we are maintaining that naming convention here as well.
regards
Rakesh kota
> Konrad
^ permalink raw reply
* Re: [PATCH v2 3/3] arm64: dts: qcom: monaco-pmics: Add PON power key and reset inputs
From: Rakesh Kota @ 2026-02-19 11:17 UTC (permalink / raw)
To: Konrad Dybcio
Cc: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Vinod Koul, Dmitry Torokhov, Courtney Cavin, Bjorn Andersson,
Konrad Dybcio, linux-pm, devicetree, linux-kernel, linux-arm-msm,
linux-input, Rakesh Kota
In-Reply-To: <253ca948-07cd-4ded-8e15-619589f2d314@oss.qualcomm.com>
On Tue, Feb 17, 2026 at 01:30:19PM +0100, Konrad Dybcio wrote:
> On 2/9/26 2:23 PM, Rakesh Kota wrote:
> > Add the Power On (PON) peripheral with power key and reset input
> > support for the PMM8654AU PMIC on Monaco platforms.
> >
> > Signed-off-by: Rakesh Kota <rakesh.kota@oss.qualcomm.com>
> > ---
> > arch/arm64/boot/dts/qcom/monaco-pmics.dtsi | 20 ++++++++++++++++++++
> > 1 file changed, 20 insertions(+)
> >
> > diff --git a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi
> > index e990d7367719beaa9e0cea87d9c183ae18c3ebc8..182c2339bb11af40275050a36c4688227e89497a 100644
> > --- a/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi
> > +++ b/arch/arm64/boot/dts/qcom/monaco-pmics.dtsi
> > @@ -13,6 +13,26 @@ pmm8620au_0: pmic@0 {
> > #address-cells = <1>;
> > #size-cells = <0>;
> >
> > + pmm8654au_0_pon: pon@1200 {
> > + compatible = "qcom,pmm8654au-pon", "qcom,pmk8350-pon";
> > + reg = <0x1200>, <0x800>;
> > + reg-names = "hlos", "pbs";
> > +
> > + pmm8654au_0_pon_pwrkey: pwrkey {
> > + compatible = "qcom,pmm8654au-pwrkey", "qcom,pmk8350-pwrkey";
> > + interrupts-extended = <&spmi_bus 0x0 0x12 0x7 IRQ_TYPE_EDGE_BOTH>;
> > + linux,code = <KEY_POWER>;
> > + debounce = <15625>;
> > + };
> > +
> > + pmm8654au_0_pon_resin: resin {
> > + compatible = "qcom,pmm8654au-resin", "qcom,pmk8350-resin";
> > + interrupts-extended = <&spmi_bus 0x0 0x12 0x6 IRQ_TYPE_EDGE_BOTH>;
> > + linux,code = <KEY_VOLUMEDOWN>;
> > + debounce = <15625>;
>
> FWIW we tend to disable RESIN by default, as it's not as ubiquitous as
> the power key and only assign the keycode in board DT, since it may
> commonly be reused for either of the volume keys (or something else
> entirely)
>
Ok, i will disable the RESIN key by default and remove the assigned
keycode in the next patch revision.
regards
Rakesh Kota
> Konrad
^ permalink raw reply
* [PATCH] HID: generic: add LampArray support via hid-lamparray helper
From: Tim Guttzeit @ 2026-02-19 13:02 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: wse, Tim Guttzeit, linux-kernel, linux-input
Add a new hid-lamparray helper module and integrate it with the
hid-generic driver.
The helper provides basic support for devices exposing a
Lighting/LampArray application collection (usage page 0x59) and
registers a single-zone RGB LED representation via the LED
subsystem.
hid-generic now checks for LampArray support after hid_parse() and
optionally registers a lamparray instance. Failures in the helper
do not abort device probe to keep the device functional.
LampArray resources are released on driver remove.
Signed-off-by: Tim Guttzeit <tgu@tuxedocomputers.com>
---
drivers/hid/Makefile | 1 +
drivers/hid/hid-generic.c | 41 +-
drivers/hid/hid-lamparray.c | 855 ++++++++++++++++++++++++++++++++++++
drivers/hid/hid-lamparray.h | 68 +++
4 files changed, 963 insertions(+), 2 deletions(-)
create mode 100644 drivers/hid/hid-lamparray.c
create mode 100644 drivers/hid/hid-lamparray.h
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 361a7daedeb8..e6903be9c4df 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o
obj-$(CONFIG_HID_GENERIC) += hid-generic.o
+obj-$(CONFIG_HID_GENERIC) += hid-lamparray.o
hid-$(CONFIG_HIDRAW) += hidraw.o
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index c2de916747de..650c2121b403 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -20,6 +20,7 @@
#include <asm/byteorder.h>
#include <linux/hid.h>
+#include "hid-lamparray.h"
static struct hid_driver hid_generic;
@@ -31,7 +32,7 @@ static int __check_hid_generic(struct device_driver *drv, void *data)
if (hdrv == &hid_generic)
return 0;
- return hid_match_device(hdev, hdrv) != NULL;
+ return !!hid_match_device(hdev, hdrv);
}
static bool hid_generic_match(struct hid_device *hdev,
@@ -60,6 +61,7 @@ static int hid_generic_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
+ struct lamparray *la = NULL;
hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
@@ -67,7 +69,31 @@ static int hid_generic_probe(struct hid_device *hdev,
if (ret)
return ret;
- return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ return ret;
+
+ /*
+ * Optional: attach LampArray support if present.
+ * Never fail probe on LampArray errors; keep device functional.
+ */
+ if (lamparray_is_supported_device(hdev)) {
+ const struct lamparray_init_state init = {
+ .r = 0xff,
+ .g = 0xff,
+ .b = 0xff,
+ .brightness = LED_FULL,
+ };
+
+ la = lamparray_register(hdev, &init);
+ if (IS_ERR(la)) {
+ hid_warn(hdev, "LampArray init failed: %ld\n", PTR_ERR(la));
+ la = NULL;
+ }
+ }
+
+ hid_set_drvdata(hdev, la);
+ return 0;
}
static int hid_generic_reset_resume(struct hid_device *hdev)
@@ -78,6 +104,16 @@ static int hid_generic_reset_resume(struct hid_device *hdev)
return 0;
}
+static void hid_generic_remove(struct hid_device *hdev)
+{
+ struct lamparray *la = hid_get_drvdata(hdev);
+
+ if (la)
+ lamparray_unregister(la);
+
+ hid_hw_stop(hdev);
+}
+
static const struct hid_device_id hid_table[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
{ }
@@ -90,6 +126,7 @@ static struct hid_driver hid_generic = {
.match = hid_generic_match,
.probe = hid_generic_probe,
.reset_resume = hid_generic_reset_resume,
+ .remove = hid_generic_remove,
};
module_hid_driver(hid_generic);
diff --git a/drivers/hid/hid-lamparray.c b/drivers/hid/hid-lamparray.c
new file mode 100644
index 000000000000..5be46fff0191
--- /dev/null
+++ b/drivers/hid/hid-lamparray.c
@@ -0,0 +1,855 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * hid-lamparray.c - HID LampArray helper module (single-zone RGB)
+ *
+ * Helper module for HID drivers supporting devices that expose a
+ * Lighting and Illumination (LampArray) application collection
+ * (usage page 0x59).
+ *
+ * The module provides a minimal integration with the LED subsystem
+ * and treats the device as a single zone: all lamps share one RGB
+ * value and a global brightness level. It does not implement multi-
+ * zone layouts or hardware effects.
+ *
+ *
+ * If enabled, one multicolor LED class device is registered under
+ * /sys/class/leds/<HID-ID> to expose the single-zone RGB control.
+ *
+ * The use_leds_uapi sysfs attribute is attached directly to the HID device
+ * under /sys/bus/hid/devices/<HID-ID>/use_leds_uapi.Writing 0 to use_leds_uapi
+ * unregisters the LED class device. The last state is kept cached. Writing 1
+ * registers it again and restores the cached state to hardware.
+ *
+ * State is cached as last known RGB + brightness. Setting sends a HID
+ * SET_REPORT. Getting issues a HID GET_REPORT and updates the cache on
+ * mismatch. Since the device is handled as single-zone, readback only queries
+ * lamp 0 when a lamp range is available.
+ *
+ * The module does not bind to devices on its own. Instead, a HID
+ * driver may query support via lamparray_is_supported_device() after
+ * hid_parse() and create an instance using lamparray_register().
+ *
+ * Copyright (C) 2026 Tim Guttzeit <tgu@tuxedocomputers.com>
+ */
+
+#include "hid-lamparray.h"
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/bitops.h>
+#include <linux/xarray.h>
+
+/* Constants */
+
+/* HID usages (LampArray, etc.) */
+#define HID_LIGHTING_ILLUMINATION_USAGE_PAGE 0x0059
+
+#define HID_LAIP_LAMP_COUNT 0x0003
+#define HID_LAIP_LAMP_ID 0x0021
+#define HID_LAIP_RED_UPDATE_CHANNEL 0x0051
+#define HID_LAIP_GREEN_UPDATE_CHANNEL 0x0052
+#define HID_LAIP_BLUE_UPDATE_CHANNEL 0x0053
+#define HID_LAIP_INTENSITY_UPDATE_CHANNEL 0x0054
+#define HID_LAIP_LAMP_ID_START 0x0061
+#define HID_LAIP_LAMP_ID_END 0x0062
+#define HID_LAIP_AUTONOMOUS_MODE 0x0071
+
+#define HID_APPLICATION_COLLECTION_USAGE_TYPE 0x0001
+
+/* Device state */
+
+struct lamparray_quirks {
+ unsigned long flags;
+ int fixed_lamp_count;
+};
+
+#define LAMPARRAY_QUIRK_LAMPCOUNT_FIXED BIT(0)
+
+struct lamparray_quirk_entry {
+ u16 vendor;
+ u16 product;
+ unsigned long flags;
+ int fixed_lamp_count;
+};
+
+static const struct lamparray_quirk_entry lamparray_quirk_table[] = {
+ { 0xcafe, 0x4005, LAMPARRAY_QUIRK_LAMPCOUNT_FIXED, 12 },
+ {}
+};
+
+static const struct lamparray_quirk_entry *
+lamparray_lookup_quirks(struct hid_device *hdev)
+{
+ const struct lamparray_quirk_entry *e;
+
+ for (e = lamparray_quirk_table; e->vendor; e++) {
+ if (hdev->vendor == e->vendor && hdev->product == e->product)
+ return e;
+ }
+ return NULL;
+}
+
+struct lamparray_device {
+ const struct lamparray_quirk_entry *quirks;
+
+ struct hid_device *hdev;
+ struct hid_report *update_report;
+
+ struct hid_field *red_field;
+ int red_index;
+ struct hid_field *green_field;
+ int green_index;
+ struct hid_field *blue_field;
+ int blue_index;
+ struct hid_field *intensity_field;
+ int intensity_index;
+
+ struct hid_report *autonomous_report;
+ struct hid_field *autonomous_field;
+
+ struct hid_field *range_start_field;
+ int range_start_index;
+
+ struct hid_field *range_end_field;
+ int range_end_index;
+
+ struct hid_field *lamp_count_field;
+ int lamp_count;
+ int lamp_count_index;
+
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subleds[3];
+
+ struct mutex lock; /* protects cached state and HID access */
+
+ u8 last_r;
+ u8 last_g;
+ u8 last_b;
+ enum led_brightness last_brightness;
+
+ bool use_leds_uapi;
+ bool led_registered;
+};
+
+/*
+ * Opaque handle exposed to callers via the header.
+ * Keep the actual state in lamparray_device, but return a stable pointer.
+ */
+struct lamparray {
+ struct lamparray_device ldev;
+};
+
+static DEFINE_XARRAY(lamparray_by_hdev);
+
+/* HID helper functions */
+
+#ifdef DEBUG
+static void lamparray_dump_reports(struct hid_device *hdev)
+{
+ struct hid_report_enum *re;
+ struct hid_report *report;
+ int i, j, report_type;
+
+ for (report_type = 0; report_type < HID_REPORT_TYPES; report_type++) {
+ re = &hdev->report_enum[report_type];
+ hid_dbg(hdev, "Dumping reports for report type %d",
+ report_type);
+ list_for_each_entry(report, &re->report_list, list) {
+ hid_dbg(hdev,
+ "lamparray: report id=%u type=%d maxfield=%u\n",
+ report->id, report->type, report->maxfield);
+
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+
+ for (j = 0; j < field->maxusage; j++) {
+ u32 usage = field->usage[j].hid;
+ u16 page = usage >> 16;
+ u16 id = usage & 0xFFFF;
+
+ hid_dbg(hdev,
+ "lamparray: report %u field %d usage[%d]: page=0x%04x id=0x%04x\n",
+ report->id, i, j, page, id);
+ }
+ }
+ }
+ }
+}
+#else
+static inline void lamparray_dump_reports(struct hid_device *hdev)
+{}
+#endif
+
+static int lamparray_read_lamp_count(struct lamparray_device *ldev)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report *report;
+
+ if (ldev->quirks &&
+ (ldev->quirks->flags & LAMPARRAY_QUIRK_LAMPCOUNT_FIXED)) {
+ ldev->lamp_count = ldev->quirks->fixed_lamp_count;
+ hid_dbg(hdev, "LampCount from quirk: %d\n", ldev->lamp_count);
+ return 0;
+ }
+ if (!ldev->lamp_count_field) {
+ hid_warn(hdev, "No LampCount field found\n");
+ return -ENODEV;
+ }
+
+ report = ldev->lamp_count_field->report;
+
+ if (!report) {
+ hid_warn(hdev, "LampCount field has no report\n");
+ return -ENODEV;
+ }
+ hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+ ldev->lamp_count =
+ ldev->lamp_count_field->value[ldev->lamp_count_index];
+
+ hid_dbg(hdev, "LampCount from device: %d\n", ldev->lamp_count);
+
+ if (ldev->lamp_count <= 0) {
+ hid_warn(hdev, "LampCount is %d (invalid)\n", ldev->lamp_count);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lamparray_parse_update_report(struct lamparray_device *ldev)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report_enum *re;
+ struct hid_report *report;
+ struct hid_field *field;
+ int i, j;
+
+ lamparray_dump_reports(hdev);
+
+ re = &hdev->report_enum[HID_FEATURE_REPORT];
+
+ list_for_each_entry(report, &re->report_list, list) {
+ for (i = 0; i < report->maxfield; i++) {
+ field = report->field[i];
+ if (!field)
+ continue;
+
+ if (!field->usage || !field->maxusage)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ u32 usage = field->usage[j].hid;
+ u16 page = usage >> 16;
+ u16 id = usage & 0xffff;
+
+ if (page !=
+ HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
+ continue;
+ switch (id) {
+ case HID_LAIP_LAMP_COUNT:
+ ldev->lamp_count_field = field;
+ ldev->lamp_count_index = j;
+ break;
+ case HID_LAIP_RED_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->red_field = field;
+ ldev->red_index = j;
+ break;
+ case HID_LAIP_GREEN_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->green_field = field;
+ ldev->green_index = j;
+ break;
+ case HID_LAIP_BLUE_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->blue_field = field;
+ ldev->blue_index = j;
+ break;
+ case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
+ ldev->update_report = report;
+ ldev->intensity_field = field;
+ ldev->intensity_index = j;
+ break;
+ case HID_LAIP_LAMP_ID_START:
+ ldev->range_start_field = field;
+ ldev->range_start_index = j;
+ break;
+ case HID_LAIP_LAMP_ID_END:
+ ldev->range_end_field = field;
+ ldev->range_end_index = j;
+ break;
+ case HID_LAIP_AUTONOMOUS_MODE:
+ ldev->autonomous_field = field;
+ ldev->autonomous_report = report;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ if (!ldev->update_report || !ldev->red_field || !ldev->green_field ||
+ !ldev->blue_field || !ldev->autonomous_report || !ldev->autonomous_field)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int lamparray_hw_set_autonomous(struct lamparray_device *ldev,
+ bool enable)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_field *field = ldev->autonomous_field;
+ struct hid_report *report = ldev->autonomous_report;
+
+ if (!field || !report)
+ return -ENODEV;
+
+ field->value[0] = enable ? 1 : 0;
+
+ hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int lamparray_hw_set_state(struct lamparray_device *ldev, u8 r, u8 g,
+ u8 b, u8 intensity)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report *report = ldev->update_report;
+ int i, j;
+
+ if (!report || !ldev->red_field || !ldev->green_field ||
+ !ldev->blue_field || !ldev->intensity_field)
+ return -ENODEV;
+
+ if (ldev->range_start_field && ldev->range_end_field) {
+ ldev->range_start_field->value[ldev->range_start_index] = 0;
+ ldev->range_end_field->value[ldev->range_end_index] = ldev->lamp_count - 1;
+ }
+
+ for (i = 0; i < report->maxfield; i++) {
+ struct hid_field *field = report->field[i];
+
+ if (!field || !field->usage || !field->maxusage)
+ continue;
+
+ for (j = 0; j < field->maxusage; j++) {
+ u32 usage = field->usage[j].hid;
+ u16 page = usage >> 16;
+ u16 id = usage & 0xffff;
+
+ if (page != HID_LIGHTING_ILLUMINATION_USAGE_PAGE)
+ continue;
+
+ switch (id) {
+ case HID_LAIP_RED_UPDATE_CHANNEL:
+ field->value[j] = r;
+ break;
+ case HID_LAIP_GREEN_UPDATE_CHANNEL:
+ field->value[j] = g;
+ break;
+ case HID_LAIP_BLUE_UPDATE_CHANNEL:
+ field->value[j] = b;
+ break;
+ case HID_LAIP_INTENSITY_UPDATE_CHANNEL:
+ field->value[j] = intensity;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+ return 0;
+}
+
+static int lamparray_hw_get_state(struct lamparray_device *ldev, u8 *r, u8 *g,
+ u8 *b, enum led_brightness *brightness)
+{
+ struct hid_device *hdev = ldev->hdev;
+ struct hid_report *report = ldev->update_report;
+
+ if (!report || !ldev->red_field || !ldev->green_field ||
+ !ldev->blue_field || !ldev->intensity_field)
+ return -ENODEV;
+
+ if (!r || !g || !b || !brightness)
+ return -EINVAL;
+
+ /* Single-zone: Reading lamp 0 only suffices */
+ if (ldev->range_start_field && ldev->range_end_field) {
+ ldev->range_start_field->value[ldev->range_start_index] = 0;
+ ldev->range_end_field->value[ldev->range_end_index] = 0;
+ }
+
+ hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+
+ *r = ldev->red_field->value[ldev->red_index];
+ *g = ldev->green_field->value[ldev->green_index];
+ *b = ldev->blue_field->value[ldev->blue_index];
+ *brightness = ldev->intensity_field->value[ldev->intensity_index];
+
+ return 0;
+}
+
+/* Helper functions */
+
+static int lamparray_restore_state(struct lamparray_device *ldev)
+{
+ u8 r, g, b;
+ int ret;
+ enum led_brightness brightness;
+
+ mutex_lock(&ldev->lock);
+
+ if (!ldev->use_leds_uapi) {
+ mutex_unlock(&ldev->lock);
+ return 0;
+ }
+
+ r = ldev->last_r;
+ g = ldev->last_g;
+ b = ldev->last_b;
+ brightness = ldev->last_brightness;
+
+ ldev->mc_cdev.subled_info[0].brightness = r;
+ ldev->mc_cdev.subled_info[1].brightness = g;
+ ldev->mc_cdev.subled_info[2].brightness = b;
+ ldev->mc_cdev.led_cdev.brightness = brightness;
+
+ mutex_unlock(&ldev->lock);
+
+ ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
+ return ret;
+}
+
+/* LEDs API */
+
+static int lamparray_led_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lamparray_device *ldev =
+ container_of(mc, struct lamparray_device, mc_cdev);
+ struct lamparray *la = container_of(ldev, struct lamparray, ldev);
+ u8 r, g, b;
+ int ret;
+
+ if (!la)
+ return -ENODEV;
+ ldev = &la->ldev;
+
+ ret = led_mc_calc_color_components(mc, brightness);
+ if (ret)
+ return ret;
+
+ r = mc->subled_info[0].brightness;
+ g = mc->subled_info[1].brightness;
+ b = mc->subled_info[2].brightness;
+
+ ret = lamparray_hw_set_state(ldev, r, g, b, brightness);
+ if (ret)
+ hid_err(ldev->hdev, "Failed to send LampArray update: %d\n",
+ ret);
+
+ mutex_lock(&ldev->lock);
+ ldev->last_r = r;
+ ldev->last_g = g;
+ ldev->last_b = b;
+ ldev->last_brightness = brightness;
+ mutex_unlock(&ldev->lock);
+
+ return 0;
+}
+
+static enum led_brightness
+lamparray_led_brightness_get(struct led_classdev *cdev)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lamparray_device *ldev =
+ container_of(mc, struct lamparray_device, mc_cdev);
+ enum led_brightness brightness;
+ struct lamparray *la = container_of(ldev, struct lamparray, ldev);
+ u8 rr, gg, bb;
+ enum led_brightness br;
+ int ret;
+
+ /* Default: cache (also used while registering LED classdev) */
+ mutex_lock(&ldev->lock);
+ brightness = ldev->last_brightness;
+ mutex_unlock(&ldev->lock);
+
+ /* Only do HID readback after registration completed */
+ if (READ_ONCE(ldev->led_registered)) {
+ if (!la)
+ return brightness;
+ ldev = &la->ldev;
+
+ ret = lamparray_hw_get_state(ldev, &rr, &gg, &bb, &br);
+ if (ret) {
+ hid_warn(ldev->hdev,
+ "Failed to read LampArray state (%d), using cached brightness %u\n",
+ ret, brightness);
+ return brightness;
+ }
+
+ mutex_lock(&ldev->lock);
+ if (ldev->last_r != rr || ldev->last_g != gg ||
+ ldev->last_b != bb || ldev->last_brightness != br) {
+ ldev->last_r = rr;
+ ldev->last_g = gg;
+ ldev->last_b = bb;
+ ldev->last_brightness = br;
+
+ if (ldev->led_registered && ldev->mc_cdev.subled_info) {
+ ldev->mc_cdev.subled_info[0].brightness = rr;
+ ldev->mc_cdev.subled_info[1].brightness = gg;
+ ldev->mc_cdev.subled_info[2].brightness = bb;
+ }
+ }
+ mutex_unlock(&ldev->lock);
+ return br;
+ }
+ return brightness;
+}
+
+static int lamparray_register_led(struct lamparray_device *ldev)
+{
+ struct device *dev = &ldev->hdev->dev;
+ struct led_classdev *cdev = &ldev->mc_cdev.led_cdev;
+ u8 r_i, g_i, b_i;
+ int ret;
+
+ mutex_lock(&ldev->lock);
+
+ if (ldev->led_registered) {
+ mutex_unlock(&ldev->lock);
+ return 0;
+ }
+
+ if (!cdev->name) {
+ cdev->name =
+ devm_kasprintf(dev, GFP_KERNEL, "%s", dev_name(dev));
+ if (!cdev->name) {
+ mutex_unlock(&ldev->lock);
+ return -ENOMEM;
+ }
+ }
+
+ cdev->max_brightness = 255;
+ cdev->brightness_set_blocking = lamparray_led_brightness_set;
+ cdev->brightness_get = lamparray_led_brightness_get;
+ cdev->brightness = ldev->last_brightness;
+
+ ldev->subleds[0].color_index = LED_COLOR_ID_RED;
+ ldev->subleds[1].color_index = LED_COLOR_ID_GREEN;
+ ldev->subleds[2].color_index = LED_COLOR_ID_BLUE;
+
+ /*
+ * Initialize the color mix (multi_intensity) from the last known HW/init
+ * state so that writing only /brightness scales the expected default color
+ * instead of white.
+ *
+ * If last_brightness is non-zero, treat last_r/g/b as per-channel
+ * brightness and normalize back to intensities (0..255).
+ * If last_brightness is zero, keep last_r/g/b as the intended mix.
+ */
+ if (ldev->last_brightness) {
+ r_i = (u8)min_t(unsigned int, 255,
+ (ldev->last_r * 255u) / ldev->last_brightness);
+ g_i = (u8)min_t(unsigned int, 255,
+ (ldev->last_g * 255u) / ldev->last_brightness);
+ b_i = (u8)min_t(unsigned int, 255,
+ (ldev->last_b * 255u) / ldev->last_brightness);
+ } else {
+ r_i = ldev->last_r;
+ g_i = ldev->last_g;
+ b_i = ldev->last_b;
+ }
+
+ ldev->subleds[0].intensity = r_i;
+ ldev->subleds[1].intensity = g_i;
+ ldev->subleds[2].intensity = b_i;
+
+ ldev->mc_cdev.subled_info = ldev->subleds;
+ ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
+
+ /* Ensure subled_info[].brightness matches intensity + brightness */
+ led_mc_calc_color_components(&ldev->mc_cdev, cdev->brightness);
+
+ ldev->mc_cdev.subled_info = ldev->subleds;
+ ldev->mc_cdev.num_colors = ARRAY_SIZE(ldev->subleds);
+
+ mutex_unlock(&ldev->lock);
+
+ ret = led_classdev_multicolor_register(dev, &ldev->mc_cdev);
+ if (ret)
+ return ret;
+
+ mutex_lock(&ldev->lock);
+ ldev->led_registered = true;
+ mutex_unlock(&ldev->lock);
+
+ return 0;
+}
+
+static void lamparray_unregister_led(struct lamparray_device *ldev)
+{
+ bool was_registered;
+
+ mutex_lock(&ldev->lock);
+ was_registered = ldev->led_registered;
+ ldev->led_registered = false;
+ mutex_unlock(&ldev->lock);
+
+ if (!was_registered)
+ return;
+
+ led_classdev_multicolor_unregister(&ldev->mc_cdev);
+}
+
+/* Sysfs */
+
+static struct lamparray_device *
+lamparray_ldev_from_sysfs_dev(struct device *dev)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+
+ return xa_load(&lamparray_by_hdev, (unsigned long)hdev);
+}
+
+static ssize_t use_leds_uapi_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+
+ if (!ldev)
+ return -ENODEV;
+
+ return sysfs_emit(buf, "%d\n", ldev->use_leds_uapi);
+}
+
+static ssize_t use_leds_uapi_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lamparray_device *ldev = lamparray_ldev_from_sysfs_dev(dev);
+ int val;
+ int old_val;
+ int ret;
+
+ if (!ldev)
+ return -ENODEV;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (val != 0 && val != 1)
+ return -EINVAL;
+
+ mutex_lock(&ldev->lock);
+ old_val = ldev->use_leds_uapi;
+
+ if (val == old_val) {
+ mutex_unlock(&ldev->lock);
+ return count;
+ }
+
+ ldev->use_leds_uapi = val;
+ mutex_unlock(&ldev->lock);
+
+ if (val == 1) {
+ ret = lamparray_register_led(ldev);
+ if (ret) {
+ mutex_lock(&ldev->lock);
+ ldev->use_leds_uapi = old_val;
+ mutex_unlock(&ldev->lock);
+ return ret;
+ }
+ ret = lamparray_restore_state(ldev);
+ if (ret) {
+ hid_err(ldev->hdev, "Could not restore state: %d", ret);
+ return ret;
+ }
+
+ } else {
+ lamparray_unregister_led(ldev);
+ }
+
+ return count;
+}
+static DEVICE_ATTR_RW(use_leds_uapi);
+
+static struct attribute *lamparray_attrs[] = {
+ &dev_attr_use_leds_uapi.attr,
+ NULL,
+};
+
+static const struct attribute_group lamparray_attr_group = {
+ .attrs = lamparray_attrs,
+};
+
+static int lamparray_register_sysfs(struct lamparray_device *ldev)
+{
+ struct device *dev = &ldev->hdev->dev;
+ int ret;
+
+ ret = sysfs_create_group(&dev->kobj, &lamparray_attr_group);
+ if (ret)
+ hid_err(ldev->hdev,
+ "Failed to create lamparray sysfs group: %d\n", ret);
+
+ return ret;
+}
+
+static void lamparray_remove_sysfs(struct lamparray_device *ldev)
+{
+ sysfs_remove_group(&ldev->hdev->dev.kobj, &lamparray_attr_group);
+}
+
+/* Public API */
+
+bool lamparray_is_supported_device(struct hid_device *hdev)
+{
+ unsigned int i;
+
+ hid_dbg(hdev, "lamparray: walking %u collections\n",
+ hdev->maxcollection);
+
+ for (i = 0; i < hdev->maxcollection; i++) {
+ struct hid_collection *col = &hdev->collection[i];
+ u16 page = (col->usage & HID_USAGE_PAGE) >> 16;
+ u16 code = col->usage & HID_USAGE;
+
+ hid_dbg(hdev,
+ "lamparray: collection[%u]: type=%u level=%u usage=0x%08x page=0x%04x code=0x%04x\n",
+ i, col->type, col->level, col->usage, page, code);
+
+ if (col->type == HID_COLLECTION_APPLICATION &&
+ page == HID_LIGHTING_ILLUMINATION_USAGE_PAGE &&
+ code == HID_APPLICATION_COLLECTION_USAGE_TYPE) {
+ return true;
+ }
+ }
+ return false;
+}
+EXPORT_SYMBOL_GPL(lamparray_is_supported_device);
+
+struct lamparray *
+lamparray_register(struct hid_device *hdev,
+ const struct lamparray_init_state *led_init_state)
+{
+ int ret;
+ struct lamparray *la;
+ struct lamparray_device *ldev;
+
+ if (!hdev)
+ return ERR_PTR(-ENODEV);
+
+ la = kzalloc(sizeof(*la), GFP_KERNEL);
+ if (!la)
+ return ERR_PTR(-ENOMEM);
+
+ ldev = &la->ldev;
+
+ mutex_init(&ldev->lock);
+ ldev->hdev = hdev;
+ ldev->quirks = lamparray_lookup_quirks(hdev);
+ ldev->use_leds_uapi = 1;
+ ldev->led_registered = false;
+ if (!led_init_state) {
+ ldev->last_r = 255;
+ ldev->last_g = 255;
+ ldev->last_b = 255;
+ ldev->last_brightness = LED_OFF;
+ } else {
+ ldev->last_r = led_init_state->r;
+ ldev->last_g = led_init_state->g;
+ ldev->last_b = led_init_state->b;
+ ldev->last_brightness = led_init_state->brightness;
+ }
+ ret = lamparray_parse_update_report(ldev);
+ if (ret) {
+ hid_err(hdev, "No LampArray update report found: %d\n", ret);
+ goto err_free;
+ }
+
+ ret = lamparray_read_lamp_count(ldev);
+ if (ret) {
+ hid_err(hdev,
+ "Could not determine LampCount. This device needs a quirk for a fixed LampCount: %d\n",
+ ret);
+ goto err_unregister_led;
+ }
+
+ ret = lamparray_register_led(ldev);
+ if (ret) {
+ hid_warn(hdev, "Failed to register LED UAPI: %d\n", ret);
+ mutex_lock(&ldev->lock);
+ ldev->use_leds_uapi = 0;
+ mutex_unlock(&ldev->lock);
+ }
+
+ ret = xa_err(xa_store(&lamparray_by_hdev, (unsigned long)hdev, ldev,
+ GFP_KERNEL));
+ if (ret)
+ goto err_unregister_led;
+
+ ret = lamparray_register_sysfs(ldev);
+ if (ret)
+ goto err_xa_erase;
+
+ ret = lamparray_hw_set_autonomous(ldev, false);
+ if (ret) {
+ hid_err(hdev, "Could not disable autonomous mode: %d", ret);
+ goto err_remove_sysfs;
+ }
+
+ hid_info(hdev, "LampArray device registered\n");
+
+ ret = lamparray_restore_state(ldev);
+ if (ret) {
+ hid_err(hdev, "Failed to set standard state: %d", ret);
+ goto err_remove_sysfs;
+ }
+ return la;
+
+err_remove_sysfs:
+ lamparray_remove_sysfs(ldev);
+err_xa_erase:
+ xa_erase(&lamparray_by_hdev, (unsigned long)hdev);
+err_unregister_led:
+ lamparray_unregister_led(ldev);
+err_free:
+ kfree(la);
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(lamparray_register);
+
+void lamparray_unregister(struct lamparray *la)
+{
+ struct lamparray_device *ldev;
+
+ if (!la)
+ return;
+
+ ldev = &la->ldev;
+
+ lamparray_unregister_led(ldev);
+ lamparray_remove_sysfs(ldev);
+ xa_erase(&lamparray_by_hdev, (unsigned long)ldev->hdev);
+
+ kfree(la);
+}
+EXPORT_SYMBOL_GPL(lamparray_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("HID LampArray helper module (single-zone RGB)");
diff --git a/drivers/hid/hid-lamparray.h b/drivers/hid/hid-lamparray.h
new file mode 100644
index 000000000000..b786ca00c404
--- /dev/null
+++ b/drivers/hid/hid-lamparray.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _HID_LAMPARRAY_H
+#define _HID_LAMPARRAY_H
+
+#include <linux/types.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+
+struct hid_device;
+struct lamparray;
+
+/*
+ * Optional initial LED state for lamparray_register().
+ * Used to define the initial state of a LampArray's LEDs.
+ */
+struct lamparray_init_state {
+ u8 r;
+ u8 g;
+ u8 b;
+ enum led_brightness brightness;
+};
+
+/**
+ * lamparray_is_supported_device() - check whether a HID device supports LampArray
+ * @hdev: HID device to inspect
+ *
+ * Check whether the given HID device exposes a Lighting/LampArray application
+ * collection as defined by the HID Lighting specification.
+ *
+ * This helper can be used by HID drivers to determine whether LampArray
+ * functionality should be enabled for a device.
+ *
+ * Return: %true if LampArray support is detected, %false otherwise.
+ */
+bool lamparray_is_supported_device(struct hid_device *hdev);
+
+/**
+ * lamparray_register() - initialize LampArray support for a HID device
+ * @hdev: HID device
+ * @led_init_state: Optional LED state at init specification
+ *
+ * Allocate and initialize internal LampArray state for the given HID device.
+ * The function parses required HID reports and fields and registers the
+ * associated miscdevice and sysfs attributes.
+ *
+ * If enabled, a multicolor LED class device is also registered to expose the
+ * LampArray functionality via the LED subsystem. If specified, the desired
+ * initial LED state is applied. If led_init_state is NULL, a default state is
+ * applied.
+ *
+ * Return: pointer to a LampArray handle on success, or ERR_PTR() on failure.
+ */
+struct lamparray *lamparray_register(struct hid_device *hdev,
+ const struct lamparray_init_state *led_init_state);
+
+/**
+ * lamparray_unregister() - tear down LampArray support
+ * @la: LampArray handle returned by lamparray_register()
+ *
+ * Remove all resources associated with a LampArray instance.
+ *
+ * This unregisters the LED class device (if present), removes the miscdevice
+ * and sysfs interfaces and frees all internal state associated with @la.
+ */
+void lamparray_unregister(struct lamparray *la);
+
+#endif
--
2.43.0
^ permalink raw reply related
* [dtor-input:for-linus] BUILD SUCCESS fc1e8a6f129d87c64ac8e58b50d9dfa66217cfda
From: kernel test robot @ 2026-02-19 13:18 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: fc1e8a6f129d87c64ac8e58b50d9dfa66217cfda Input: bcm5974 - recover from failed mode switch
elapsed time: 842m
configs tested: 233
configs skipped: 4
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc axs101_defconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc nsimosci_hs_smp_defconfig clang-23
arc randconfig-001-20260219 clang-23
arc randconfig-002-20260219 clang-23
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm clps711x_defconfig clang-23
arm defconfig gcc-15.2.0
arm h3600_defconfig clang-18
arm imx_v4_v5_defconfig clang-18
arm mps2_defconfig clang-23
arm multi_v5_defconfig gcc-15.2.0
arm mv78xx0_defconfig clang-18
arm omap1_defconfig clang-23
arm pxa3xx_defconfig gcc-15.2.0
arm randconfig-001-20260219 clang-23
arm randconfig-002-20260219 clang-23
arm randconfig-003-20260219 clang-23
arm randconfig-004-20260219 clang-23
arm s5pv210_defconfig gcc-15.2.0
arm vf610m4_defconfig clang-23
arm vt8500_v6_v7_defconfig gcc-15.2.0
arm64 alldefconfig clang-23
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260219 gcc-8.5.0
arm64 randconfig-002-20260219 gcc-8.5.0
arm64 randconfig-003-20260219 gcc-8.5.0
arm64 randconfig-004-20260219 gcc-8.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260219 gcc-8.5.0
csky randconfig-002-20260219 gcc-8.5.0
hexagon alldefconfig gcc-15.2.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260219 clang-17
hexagon randconfig-002-20260219 clang-17
i386 allmodconfig clang-20
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001-20260219 gcc-14
i386 buildonly-randconfig-002-20260219 gcc-14
i386 buildonly-randconfig-003-20260219 gcc-14
i386 buildonly-randconfig-004-20260219 gcc-14
i386 buildonly-randconfig-005-20260219 gcc-14
i386 buildonly-randconfig-006-20260219 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260219 gcc-14
i386 randconfig-002-20260219 gcc-14
i386 randconfig-003-20260219 gcc-14
i386 randconfig-004-20260219 gcc-14
i386 randconfig-005-20260219 gcc-14
i386 randconfig-006-20260219 gcc-14
i386 randconfig-007-20260219 gcc-14
i386 randconfig-011-20260219 clang-20
i386 randconfig-012-20260219 clang-20
i386 randconfig-013-20260219 clang-20
i386 randconfig-014-20260219 clang-20
i386 randconfig-015-20260219 clang-20
i386 randconfig-016-20260219 clang-20
i386 randconfig-017-20260219 clang-20
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260219 clang-17
loongarch randconfig-002-20260219 clang-17
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k amiga_defconfig clang-23
m68k defconfig clang-19
m68k m5272c3_defconfig gcc-15.2.0
m68k m5475evb_defconfig gcc-15.2.0
m68k multi_defconfig clang-23
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips bigsur_defconfig gcc-15.2.0
mips fuloong2e_defconfig gcc-15.2.0
mips ip22_defconfig clang-23
mips rb532_defconfig clang-18
nios2 allmodconfig clang-23
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001-20260219 clang-17
nios2 randconfig-002-20260219 clang-17
openrisc allmodconfig clang-23
openrisc allmodconfig gcc-15.2.0
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260219 clang-23
parisc randconfig-002-20260219 clang-23
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc cm5200_defconfig clang-23
powerpc currituck_defconfig gcc-15.2.0
powerpc ep8248e_defconfig clang-23
powerpc ep88xc_defconfig clang-18
powerpc fsp2_defconfig gcc-15.2.0
powerpc iss476-smp_defconfig clang-18
powerpc katmai_defconfig gcc-15.2.0
powerpc mvme5100_defconfig gcc-15.2.0
powerpc pasemi_defconfig clang-23
powerpc ppc44x_defconfig gcc-15.2.0
powerpc randconfig-001-20260219 clang-23
powerpc randconfig-002-20260219 clang-23
powerpc64 randconfig-001-20260219 clang-23
powerpc64 randconfig-002-20260219 clang-23
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260219 clang-17
riscv randconfig-002-20260219 clang-17
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260219 clang-17
s390 randconfig-002-20260219 clang-17
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh apsh4a3a_defconfig gcc-15.2.0
sh defconfig gcc-14
sh kfr2r09-romimage_defconfig clang-23
sh lboxre2_defconfig gcc-15.2.0
sh migor_defconfig gcc-15.2.0
sh r7785rp_defconfig gcc-15.2.0
sh randconfig-001-20260219 clang-17
sh randconfig-002-20260219 clang-17
sh rsk7264_defconfig gcc-15.2.0
sh rsk7269_defconfig gcc-15.2.0
sh se7343_defconfig gcc-15.2.0
sh se7705_defconfig clang-18
sh titan_defconfig clang-18
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001-20260219 gcc-8.5.0
sparc randconfig-002-20260219 gcc-8.5.0
sparc64 alldefconfig clang-23
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001-20260219 gcc-8.5.0
sparc64 randconfig-002-20260219 gcc-8.5.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001-20260219 gcc-8.5.0
um randconfig-002-20260219 gcc-8.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260219 gcc-14
x86_64 buildonly-randconfig-002-20260219 gcc-14
x86_64 buildonly-randconfig-003-20260219 gcc-14
x86_64 buildonly-randconfig-004-20260219 gcc-14
x86_64 buildonly-randconfig-005-20260219 gcc-14
x86_64 buildonly-randconfig-006-20260219 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260219 clang-20
x86_64 randconfig-002-20260219 clang-20
x86_64 randconfig-003-20260219 clang-20
x86_64 randconfig-004-20260219 clang-20
x86_64 randconfig-005-20260219 clang-20
x86_64 randconfig-006-20260219 clang-20
x86_64 randconfig-011-20260219 gcc-14
x86_64 randconfig-012-20260219 gcc-14
x86_64 randconfig-013-20260219 gcc-14
x86_64 randconfig-014-20260219 gcc-14
x86_64 randconfig-015-20260219 gcc-14
x86_64 randconfig-016-20260219 gcc-14
x86_64 randconfig-071-20260219 clang-20
x86_64 randconfig-071-20260219 gcc-14
x86_64 randconfig-072-20260219 gcc-14
x86_64 randconfig-073-20260219 gcc-14
x86_64 randconfig-074-20260219 gcc-14
x86_64 randconfig-075-20260219 clang-20
x86_64 randconfig-075-20260219 gcc-14
x86_64 randconfig-076-20260219 clang-20
x86_64 randconfig-076-20260219 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa common_defconfig gcc-15.2.0
xtensa randconfig-001-20260219 gcc-8.5.0
xtensa randconfig-002-20260219 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] HID: Add HID_CLAIMED_INPUT guards in raw_event callbacks missing them
From: Greg Kroah-Hartman @ 2026-02-19 14:33 UTC (permalink / raw)
To: linux-input
Cc: linux-kernel, Greg Kroah-Hartman, Jiri Kosina, Benjamin Tissoires,
Bastien Nocera, stable
In commit 2ff5baa9b527 ("HID: appleir: Fix potential NULL dereference at
raw event handle"), we handle the fact that raw event callbacks
can happen even for a HID device that has not been "claimed" causing a
crash if a broken device were attempted to be connected to the system.
Fix up the remaining in-tree HID drivers that forgot to add this same
check to resolve the same issue.
Cc: Jiri Kosina <jikos@kernel.org>
Cc: Benjamin Tissoires <bentiss@kernel.org>
Cc: Bastien Nocera <hadess@hadess.net>
Cc: linux-input@vger.kernel.org
Cc: stable <stable@kernel.org>
Assisted-by: gkh_clanker_2000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
This issue was found by running a tool to compare a past kernel CVE to
try to find any potential places in the existing codebase that was
missed with the original fix.
drivers/hid/hid-cmedia.c | 2 +-
drivers/hid/hid-creative-sb0540.c | 2 +-
drivers/hid/hid-zydacron.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c
index 528d7f361215..8bf5649b0c79 100644
--- a/drivers/hid/hid-cmedia.c
+++ b/drivers/hid/hid-cmedia.c
@@ -99,7 +99,7 @@ static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report,
{
struct cmhid *cm = hid_get_drvdata(hid);
- if (len != CM6533_JD_RAWEV_LEN)
+ if (len != CM6533_JD_RAWEV_LEN || !(hid->claimed & HID_CLAIMED_INPUT))
goto out;
if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx)))
goto out;
diff --git a/drivers/hid/hid-creative-sb0540.c b/drivers/hid/hid-creative-sb0540.c
index b4c8e7a5d3e0..dfd6add353d1 100644
--- a/drivers/hid/hid-creative-sb0540.c
+++ b/drivers/hid/hid-creative-sb0540.c
@@ -153,7 +153,7 @@ static int creative_sb0540_raw_event(struct hid_device *hid,
u64 code, main_code;
int key;
- if (len != 6)
+ if (len != 6 || !(hid->claimed & HID_CLAIMED_INPUT))
return 0;
/* From daemons/hw_hiddev.c sb0540_rec() in lirc */
diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c
index 3bdb26f45592..1aae80f848f5 100644
--- a/drivers/hid/hid-zydacron.c
+++ b/drivers/hid/hid-zydacron.c
@@ -114,7 +114,7 @@ static int zc_raw_event(struct hid_device *hdev, struct hid_report *report,
unsigned key;
unsigned short index;
- if (report->id == data[0]) {
+ if (report->id == data[0] && (hdev->claimed & HID_CLAIMED_INPUT)) {
/* break keys */
for (index = 0; index < 4; index++) {
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox