* FAILED: Patch "Input: synaptics_i2c - guard polling restart in resume" failed to apply to 5.15-stable tree
From: Sasha Levin @ 2026-03-01 1:51 UTC (permalink / raw)
To: stable, ii4gsp; +Cc: Dmitry Torokhov, linux-input
The patch below does not apply to the 5.15-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From 870c2e7cd881d7a10abb91f2b38135622d9f9f65 Mon Sep 17 00:00:00 2001
From: Minseong Kim <ii4gsp@gmail.com>
Date: Wed, 21 Jan 2026 10:02:02 -0800
Subject: [PATCH] Input: synaptics_i2c - guard polling restart in resume
synaptics_i2c_resume() restarts delayed work unconditionally, even when
the input device is not opened. Guard the polling restart by taking the
input device mutex and checking input_device_enabled() before re-queuing
the delayed work.
Fixes: eef3e4cab72ea ("Input: add driver for Synaptics I2C touchpad")
Signed-off-by: Minseong Kim <ii4gsp@gmail.com>
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260121063738.799967-1-ii4gsp@gmail.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/mouse/synaptics_i2c.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c
index c8ddfff2605ff..29da66af36d74 100644
--- a/drivers/input/mouse/synaptics_i2c.c
+++ b/drivers/input/mouse/synaptics_i2c.c
@@ -615,13 +615,16 @@ static int synaptics_i2c_resume(struct device *dev)
int ret;
struct i2c_client *client = to_i2c_client(dev);
struct synaptics_i2c *touch = i2c_get_clientdata(client);
+ struct input_dev *input = touch->input;
ret = synaptics_i2c_reset_config(client);
if (ret)
return ret;
- mod_delayed_work(system_dfl_wq, &touch->dwork,
- msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
+ guard(mutex)(&input->mutex);
+ if (input_device_enabled(input))
+ mod_delayed_work(system_dfl_wq, &touch->dwork,
+ msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
return 0;
}
--
2.51.0
^ permalink raw reply related
* FAILED: Patch "HID: magicmouse: Do not crash on missing msc->input" failed to apply to 5.10-stable tree
From: Sasha Levin @ 2026-03-01 1:59 UTC (permalink / raw)
To: stable, gnoack; +Cc: Jiri Kosina, linux-input
The patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From 17abd396548035fbd6179ee1a431bd75d49676a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack@google.com>
Date: Fri, 9 Jan 2026 11:57:14 +0100
Subject: [PATCH] HID: magicmouse: Do not crash on missing msc->input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fake USB devices can send their own report descriptors for which the
input_mapping() hook does not get called. In this case, msc->input stays NULL,
leading to a crash at a later time.
Detect this condition in the input_configured() hook and reject the device.
This is not supposed to happen with actual magic mouse devices, but can be
provoked by imposing as a magic mouse USB device.
Cc: stable@vger.kernel.org
Signed-off-by: Günther Noack <gnoack@google.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-magicmouse.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 7d4a25c6de0eb..91f621ceb924b 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -725,6 +725,11 @@ static int magicmouse_input_configured(struct hid_device *hdev,
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
int ret;
+ if (!msc->input) {
+ hid_err(hdev, "magicmouse setup input failed (no input)");
+ return -EINVAL;
+ }
+
ret = magicmouse_setup_input(msc->input, hdev);
if (ret) {
hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
--
2.51.0
^ permalink raw reply related
* FAILED: Patch "HID: prodikeys: Check presence of pm->input_ep82" failed to apply to 5.10-stable tree
From: Sasha Levin @ 2026-03-01 1:59 UTC (permalink / raw)
To: stable, gnoack; +Cc: Jiri Kosina, linux-input
The patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From cee8337e1bad168136aecfe6416ecd7d3aa7529a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack@google.com>
Date: Fri, 9 Jan 2026 11:58:08 +0100
Subject: [PATCH] HID: prodikeys: Check presence of pm->input_ep82
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fake USB devices can send their own report descriptors for which the
input_mapping() hook does not get called. In this case, pm->input_ep82 stays
NULL, which leads to a crash later.
This does not happen with the real device, but can be provoked by imposing as
one.
Cc: stable@vger.kernel.org
Signed-off-by: Günther Noack <gnoack@google.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-prodikeys.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c
index 74bddb2c3e82e..6e413df38358a 100644
--- a/drivers/hid/hid-prodikeys.c
+++ b/drivers/hid/hid-prodikeys.c
@@ -378,6 +378,10 @@ static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data)
bit_mask = (bit_mask << 8) | data[2];
bit_mask = (bit_mask << 8) | data[3];
+ /* robustness in case input_mapping hook does not get called */
+ if (!pm->input_ep82)
+ return 0;
+
/* break keys */
for (bit_index = 0; bit_index < 24; bit_index++) {
if (!((0x01 << bit_index) & bit_mask)) {
--
2.51.0
^ permalink raw reply related
* FAILED: Patch "HID: logitech-hidpp: Check maxfield in hidpp_get_report_length()" failed to apply to 5.10-stable tree
From: Sasha Levin @ 2026-03-01 1:59 UTC (permalink / raw)
To: stable, gnoack; +Cc: Jiri Kosina, linux-input
The patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From 1547d41f9f19d691c2c9ce4c29f746297baef9e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?G=C3=BCnther=20Noack?= <gnoack@google.com>
Date: Fri, 9 Jan 2026 13:25:58 +0100
Subject: [PATCH] HID: logitech-hidpp: Check maxfield in
hidpp_get_report_length()
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Do not crash when a report has no fields.
Fake USB gadgets can send their own HID report descriptors and can define report
structures without valid fields. This can be used to crash the kernel over USB.
Cc: stable@vger.kernel.org
Signed-off-by: Günther Noack <gnoack@google.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-logitech-hidpp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index e871f1729d4b3..d0a38eff9cfa8 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4314,7 +4314,7 @@ static int hidpp_get_report_length(struct hid_device *hdev, int id)
re = &(hdev->report_enum[HID_OUTPUT_REPORT]);
report = re->report_id_hash[id];
- if (!report)
+ if (!report || !report->maxfield)
return 0;
return report->field[0]->report_count + 1;
--
2.51.0
^ permalink raw reply related
* FAILED: Patch "HID: hid-pl: handle probe errors" failed to apply to 5.10-stable tree
From: Sasha Levin @ 2026-03-01 1:59 UTC (permalink / raw)
To: stable, oneukum; +Cc: Jiri Kosina, linux-input
The patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From 3756a272d2cf356d2203da8474d173257f5f8521 Mon Sep 17 00:00:00 2001
From: Oliver Neukum <oneukum@suse.com>
Date: Wed, 19 Nov 2025 10:09:57 +0100
Subject: [PATCH] HID: hid-pl: handle probe errors
Errors in init must be reported back or we'll
follow a NULL pointer the first time FF is used.
Fixes: 20eb127906709 ("hid: force feedback driver for PantherLord USB/PS2 2in1 Adapter")
Cc: stable@vger.kernel.org
Signed-off-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-pl.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c
index 3c8827081deae..dc11d5322fc0f 100644
--- a/drivers/hid/hid-pl.c
+++ b/drivers/hid/hid-pl.c
@@ -194,9 +194,14 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto err;
}
- plff_init(hdev);
+ ret = plff_init(hdev);
+ if (ret)
+ goto stop;
return 0;
+
+stop:
+ hid_hw_stop(hdev);
err:
return ret;
}
--
2.51.0
^ permalink raw reply related
* FAILED: Patch "Input: synaptics_i2c - guard polling restart in resume" failed to apply to 5.10-stable tree
From: Sasha Levin @ 2026-03-01 2:01 UTC (permalink / raw)
To: stable, ii4gsp; +Cc: Dmitry Torokhov, linux-input
The patch below does not apply to the 5.10-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From 870c2e7cd881d7a10abb91f2b38135622d9f9f65 Mon Sep 17 00:00:00 2001
From: Minseong Kim <ii4gsp@gmail.com>
Date: Wed, 21 Jan 2026 10:02:02 -0800
Subject: [PATCH] Input: synaptics_i2c - guard polling restart in resume
synaptics_i2c_resume() restarts delayed work unconditionally, even when
the input device is not opened. Guard the polling restart by taking the
input device mutex and checking input_device_enabled() before re-queuing
the delayed work.
Fixes: eef3e4cab72ea ("Input: add driver for Synaptics I2C touchpad")
Signed-off-by: Minseong Kim <ii4gsp@gmail.com>
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260121063738.799967-1-ii4gsp@gmail.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/input/mouse/synaptics_i2c.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/input/mouse/synaptics_i2c.c b/drivers/input/mouse/synaptics_i2c.c
index c8ddfff2605ff..29da66af36d74 100644
--- a/drivers/input/mouse/synaptics_i2c.c
+++ b/drivers/input/mouse/synaptics_i2c.c
@@ -615,13 +615,16 @@ static int synaptics_i2c_resume(struct device *dev)
int ret;
struct i2c_client *client = to_i2c_client(dev);
struct synaptics_i2c *touch = i2c_get_clientdata(client);
+ struct input_dev *input = touch->input;
ret = synaptics_i2c_reset_config(client);
if (ret)
return ret;
- mod_delayed_work(system_dfl_wq, &touch->dwork,
- msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
+ guard(mutex)(&input->mutex);
+ if (input_device_enabled(input))
+ mod_delayed_work(system_dfl_wq, &touch->dwork,
+ msecs_to_jiffies(NO_DATA_SLEEP_MSECS));
return 0;
}
--
2.51.0
^ permalink raw reply related
* Re: Subject: [PATCH] Input: atkbd - validate scancode in firmware keymap entries
From: Dmitry Torokhov @ 2026-03-01 2:26 UTC (permalink / raw)
To: Ariel Silver; +Cc: linux-input, linux-kernel
In-Reply-To: <aZkYbKNxGohck2sq@google.com>
On Fri, Feb 20, 2026 at 06:33:55PM -0800, Dmitry Torokhov wrote:
> Hi Ariel,
>
> On Fri, Feb 20, 2026 at 10:44:28AM +0200, Ariel Silver wrote:
> > The SCANCODE() macro extracts a 16-bit value (0..65535) from firmware
> > device property data, but atkbd_get_keymap_from_fwnode() uses it
> > directly to index atkbd->keycode[], which only has ATKBD_KEYMAP_SIZE
> > (512) elements. A firmware-supplied scancode >= 512 causes a heap
> > out-of-bounds write that can corrupt adjacent struct atkbd fields and
> > neighboring slab objects.
> >
> > Add a bounds check that rejects the entire firmware keymap if any entry
> > contains an out-of-range scancode, consistent with the validation
> > performed by matrix_keypad_parse_keymap() in drivers/input/matrix-keymap.c
> > for the same "linux,keymap" property format. When rejected, the driver
> > falls back to the default keycode table.
> >
> > Fixes: 9d17ad2369dc ("Input: atkbd - receive and use physcode->keycode
> > mapping from FW")
> > Reported-by: Ariel Silver <arielsilver77@gmail.com>
> > Signed-off-by: Ariel Silver <arielsilver77@gmail.com>
> > Cc: stable@vger.kernel.org
>
> Was it observed on real hardware or this is theoretical? I do not think
> this needs to go to stable, but otherwise I will apply it.
Queued for the next release, thank you.
--
Dmitry
^ permalink raw reply
* [PATCH] HID: input: use __free(kfree) to clean up temporary buffers
From: Dmitry Torokhov @ 2026-03-01 5:05 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel
The __free() cleanup automatically releases given resource when leaving
the scope, so use it to make the code less cluttered with error
handling.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/hid/hid-input.c | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 2633fcd8f910..9b2d7795c621 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -432,23 +432,18 @@ static int hidinput_scale_battery_capacity(struct hid_device *dev,
static int hidinput_query_battery_capacity(struct hid_device *dev)
{
- u8 *buf;
int ret;
- buf = kmalloc(4, GFP_KERNEL);
+ u8 *buf __free(kfree) = kmalloc(4, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4,
dev->battery_report_type, HID_REQ_GET_REPORT);
- if (ret < 2) {
- kfree(buf);
+ if (ret < 2)
return -ENODATA;
- }
- ret = hidinput_scale_battery_capacity(dev, buf[1]);
- kfree(buf);
- return ret;
+ return hidinput_scale_battery_capacity(dev, buf[1]);
}
static int hidinput_get_battery_property(struct power_supply *psy,
@@ -1832,7 +1827,6 @@ static void hidinput_led_worker(struct work_struct *work)
struct hid_report *report;
int ret;
u32 len;
- __u8 *buf;
field = hidinput_get_led_field(hid);
if (!field)
@@ -1859,7 +1853,7 @@ static void hidinput_led_worker(struct work_struct *work)
/* fall back to generic raw-output-report */
len = hid_report_len(report);
- buf = hid_alloc_report_buf(report, GFP_KERNEL);
+ u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL);
if (!buf)
return;
@@ -1869,7 +1863,6 @@ static void hidinput_led_worker(struct work_struct *work)
if (ret == -ENOSYS)
hid_hw_raw_request(hid, report->id, buf, len, HID_OUTPUT_REPORT,
HID_REQ_SET_REPORT);
- kfree(buf);
}
static int hidinput_input_event(struct input_dev *dev, unsigned int type,
--
2.53.0.473.g4a7958ca14-goog
--
Dmitry
^ permalink raw reply related
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers
From: Rahul Rameshbabu @ 2026-03-01 6:48 UTC (permalink / raw)
To: Gary Guo
Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg,
alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin,
bjorn3_gh, boqun.feng, dakr, db48x, jikos, ojeda, peter.hutterer,
tmgross
In-Reply-To: <DGPUMHQSACGL.1ITBM2W573WD1@garyguo.net>
On Fri, 27 Feb, 2026 15:27:59 +0000 "Gary Guo" <gary@garyguo.net> wrote:
> On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote:
>> These abstractions enable the development of HID drivers in Rust by binding
>> with the HID core C API. They provide Rust types that map to the
>> equivalents in C. In this initial draft, only hid_device and hid_device_id
>> are provided direct Rust type equivalents. hid_driver is specially wrapped
>> with a custom Driver type. The module_hid_driver! macro provides analogous
>> functionality to its C equivalent. Only the .report_fixup callback is
>> binded to Rust so far.
>>
>> Future work for these abstractions would include more bindings for common
>> HID-related types, such as hid_field, hid_report_enum, and hid_report as
>> well as more bus callbacks. Providing Rust equivalents to useful core HID
>> functions will also be necessary for HID driver development in Rust.
>>
>> Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com>
>> ---
>>
>> Notes:
>> Changelog:
>>
>> v5->v6:
>> * Converted From<u16> for Group to TryFrom<u16> to properly handle
>> error case
>> * Renamed into method for Group to into_u16 to not conflate with the
>> From trait
>> * Refactored new upstream changes to RegistrationOps
>> * Implemented DriverLayout trait for hid::Adapter<T>
>> v4->v5:
>> * Add rust/ to drivers/hid/Makefile
>> * Implement RawDeviceIdIndex trait
>> v3->v4:
>> * Removed specifying tree in MAINTAINERS file since that is up for
>> debate
>> * Minor rebase cleanup
>> * Moved driver logic under drivers/hid/rust
>> v2->v3:
>> * Implemented AlwaysRefCounted trait using embedded struct device's
>> reference counts instead of the separate reference counter in struct
>> hid_device
>> * Used &raw mut as appropriate
>> * Binded include/linux/device.h for get_device and put_device
>> * Cleaned up various comment related formatting
>> * Minified dev_err! format string
>> * Updated Group enum to be repr(u16)
>> * Implemented From<u16> trait for Group
>> * Added TODO comment when const_trait_impl stabilizes
>> * Made group getter functions return a Group variant instead of a raw
>> number
>> * Made sure example code builds
>> v1->v2:
>> * Binded drivers/hid/hid-ids.h for use in Rust drivers
>> * Remove pre-emptive referencing of a C HID driver instance before
>> it is fully initialized in the driver registration path
>> * Moved static getters to generic Device trait implementation, so
>> they can be used by all device::DeviceContext
>> * Use core macros for supporting DeviceContext transitions
>> * Implemented the AlwaysRefCounted and AsRef traits
>> * Make use for dev_err! as appropriate
>> RFC->v1:
>> * Use Danilo's core infrastructure
>> * Account for HID device groups
>> * Remove probe and remove callbacks
>> * Implement report_fixup support
>> * Properly comment code including SAFETY comments
>>
>> MAINTAINERS | 8 +
>> drivers/hid/Kconfig | 2 +
>> drivers/hid/Makefile | 2 +
>> drivers/hid/rust/Kconfig | 12 +
>> rust/bindings/bindings_helper.h | 3 +
>> rust/kernel/hid.rs | 530 ++++++++++++++++++++++++++++++++
>> rust/kernel/lib.rs | 2 +
>> 7 files changed, 559 insertions(+)
>> create mode 100644 drivers/hid/rust/Kconfig
>> create mode 100644 rust/kernel/hid.rs
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index b8d8a5c41597..1fee14024fa2 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -11319,6 +11319,14 @@ F: include/uapi/linux/hid*
>> F: samples/hid/
>> F: tools/testing/selftests/hid/
>>
>> +HID CORE LAYER [RUST]
>> +M: Rahul Rameshbabu <sergeantsagara@protonmail.com>
>> +R: Benjamin Tissoires <bentiss@kernel.org>
>> +L: linux-input@vger.kernel.org
>> +S: Maintained
>> +F: drivers/hid/rust/*.rs
>> +F: rust/kernel/hid.rs
>> +
>> HID LOGITECH DRIVERS
>> R: Filipe Laíns <lains@riseup.net>
>> L: linux-input@vger.kernel.org
>> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
>> index c1d9f7c6a5f2..750c2d49a806 100644
>> --- a/drivers/hid/Kconfig
>> +++ b/drivers/hid/Kconfig
>> @@ -1439,6 +1439,8 @@ endmenu
>>
>> source "drivers/hid/bpf/Kconfig"
>>
>> +source "drivers/hid/rust/Kconfig"
>> +
>> source "drivers/hid/i2c-hid/Kconfig"
>>
>> source "drivers/hid/intel-ish-hid/Kconfig"
>> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
>> index e01838239ae6..b78ab84c47b4 100644
>> --- a/drivers/hid/Makefile
>> +++ b/drivers/hid/Makefile
>> @@ -8,6 +8,8 @@ hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
>>
>> obj-$(CONFIG_HID_BPF) += bpf/
>>
>> +obj-$(CONFIG_RUST_HID_ABSTRACTIONS) += rust/
>> +
>> obj-$(CONFIG_HID) += hid.o
>> obj-$(CONFIG_UHID) += uhid.o
>>
>> diff --git a/drivers/hid/rust/Kconfig b/drivers/hid/rust/Kconfig
>> new file mode 100644
>> index 000000000000..d3247651829e
>> --- /dev/null
>> +++ b/drivers/hid/rust/Kconfig
>> @@ -0,0 +1,12 @@
>> +# SPDX-License-Identifier: GPL-2.0-only
>> +menu "Rust HID support"
>> +
>> +config RUST_HID_ABSTRACTIONS
>> + bool "Rust HID abstractions support"
>> + depends on RUST
>> + depends on HID=y
>> + help
>> + Adds support needed for HID drivers written in Rust. It provides a
>> + wrapper around the C hid core.
>> +
>> +endmenu # Rust HID support
>> diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
>> index 083cc44aa952..200e58af27a3 100644
>> --- a/rust/bindings/bindings_helper.h
>> +++ b/rust/bindings/bindings_helper.h
>> @@ -48,6 +48,7 @@
>> #include <linux/cpumask.h>
>> #include <linux/cred.h>
>> #include <linux/debugfs.h>
>> +#include <linux/device.h>
>> #include <linux/device/faux.h>
>> #include <linux/dma-direction.h>
>> #include <linux/dma-mapping.h>
>> @@ -60,6 +61,8 @@
>> #include <linux/i2c.h>
>> #include <linux/interrupt.h>
>> #include <linux/io-pgtable.h>
>> +#include <linux/hid.h>
>> +#include "../../drivers/hid/hid-ids.h"
>> #include <linux/ioport.h>
>> #include <linux/jiffies.h>
>> #include <linux/jump_label.h>
>> diff --git a/rust/kernel/hid.rs b/rust/kernel/hid.rs
>> new file mode 100644
>> index 000000000000..b9db542d923a
>> --- /dev/null
>> +++ b/rust/kernel/hid.rs
>> @@ -0,0 +1,530 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +// Copyright (C) 2025 Rahul Rameshbabu <sergeantsagara@protonmail.com>
>> +
>> +//! Abstractions for the HID interface.
>> +//!
>> +//! C header: [`include/linux/hid.h`](srctree/include/linux/hid.h)
>> +
>> +use crate::{
>> + device,
>> + device_id::{
>> + RawDeviceId,
>> + RawDeviceIdIndex, //
>> + },
>> + driver,
>> + error::*,
>> + prelude::*,
>> + types::Opaque, //
>> +};
>> +use core::{
>> + marker::PhantomData,
>> + ptr::{
>> + addr_of_mut,
>> + NonNull, //
>> + } //
>> +};
>> +
>> +/// Indicates the item is static read-only.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_CONSTANT: u8 = bindings::HID_MAIN_ITEM_CONSTANT as u8;
>
> These can be bitflags with `impl_flags!`?
>
>> +
>> +/// Indicates the item represents data from a physical control.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_VARIABLE: u8 = bindings::HID_MAIN_ITEM_VARIABLE as u8;
>> +
>> +/// Indicates the item should be treated as a relative change from the previous
>> +/// report.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_RELATIVE: u8 = bindings::HID_MAIN_ITEM_RELATIVE as u8;
>> +
>> +/// Indicates the item should wrap around when reaching the extreme high or
>> +/// extreme low values.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_WRAP: u8 = bindings::HID_MAIN_ITEM_WRAP as u8;
>> +
>> +/// Indicates the item should wrap around when reaching the extreme high or
>> +/// extreme low values.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_NONLINEAR: u8 = bindings::HID_MAIN_ITEM_NONLINEAR as u8;
>> +
>> +/// Indicates whether the control has a preferred state it will physically
>> +/// return to without user intervention.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_NO_PREFERRED: u8 = bindings::HID_MAIN_ITEM_NO_PREFERRED as u8;
>> +
>> +/// Indicates whether the control has a physical state where it will not send
>> +/// any reports.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_NULL_STATE: u8 = bindings::HID_MAIN_ITEM_NULL_STATE as u8;
>> +
>> +/// Indicates whether the control requires host system logic to change state.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_VOLATILE: u8 = bindings::HID_MAIN_ITEM_VOLATILE as u8;
>> +
>> +/// Indicates whether the item is fixed size or a variable buffer of bytes.
>> +///
>> +/// Refer to [Device Class Definition for HID 1.11]
>> +/// Section 6.2.2.5 Input, Output, and Feature Items.
>> +///
>> +/// [Device Class Definition for HID 1.11]: https://www.usb.org/sites/default/files/hid1_11.pdf
>> +pub const MAIN_ITEM_BUFFERED_BYTE: u8 = bindings::HID_MAIN_ITEM_BUFFERED_BYTE as u8;
>> +
>> +/// HID device groups are intended to help categories HID devices based on a set
>> +/// of common quirks and logic that they will require to function correctly.
>> +#[repr(u16)]
>> +pub enum Group {
>> + /// Used to match a device against any group when probing.
>> + Any = bindings::HID_GROUP_ANY as u16,
>> +
>> + /// Indicates a generic device that should need no custom logic from the
>> + /// core HID stack.
>> + Generic = bindings::HID_GROUP_GENERIC as u16,
>> +
>> + /// Maps multitouch devices to hid-multitouch instead of hid-generic.
>> + Multitouch = bindings::HID_GROUP_MULTITOUCH as u16,
>> +
>> + /// Used for autodetecing and mapping of HID sensor hubs to
>> + /// hid-sensor-hub.
>> + SensorHub = bindings::HID_GROUP_SENSOR_HUB as u16,
>> +
>> + /// Used for autodetecing and mapping Win 8 multitouch devices to set the
>> + /// needed quirks.
>> + MultitouchWin8 = bindings::HID_GROUP_MULTITOUCH_WIN_8 as u16,
>> +
>> + // Vendor-specific device groups.
>> + /// Used to distinguish Synpatics touchscreens from other products. The
>> + /// touchscreens will be handled by hid-multitouch instead, while everything
>> + /// else will be managed by hid-rmi.
>> + RMI = bindings::HID_GROUP_RMI as u16,
>> +
>> + /// Used for hid-core handling to automatically identify Wacom devices and
>> + /// have them probed by hid-wacom.
>> + Wacom = bindings::HID_GROUP_WACOM as u16,
>> +
>> + /// Used by logitech-djreceiver and logitech-djdevice to autodetect if
>> + /// devices paied to the DJ receivers are DJ devices and handle them with
>> + /// the device driver.
>> + LogitechDJDevice = bindings::HID_GROUP_LOGITECH_DJ_DEVICE as u16,
>> +
>> + /// Since the Valve Steam Controller only has vendor-specific usages,
>> + /// prevent hid-generic from parsing its reports since there would be
>> + /// nothing hid-generic could do for the device.
>> + Steam = bindings::HID_GROUP_STEAM as u16,
>> +
>> + /// Used to differentiate 27 Mhz frequency Logitech DJ devices from other
>> + /// Logitech DJ devices.
>> + Logitech27MHzDevice = bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE as u16,
>> +
>> + /// Used for autodetecting and mapping Vivaldi devices to hid-vivaldi.
>> + Vivaldi = bindings::HID_GROUP_VIVALDI as u16,
>> +}
>> +
>> +// TODO: use `const_trait_impl` once stabilized:
>> +//
>> +// ```
>> +// impl const From<Group> for u16 {
>> +// /// [`Group`] variants are represented by [`u16`] values.
>> +// fn from(value: Group) -> Self {
>> +// value as Self
>> +// }
>> +// }
>> +// ```
>> +impl Group {
>> + /// Internal function used to convert [`Group`] variants into [`u16`].
>> + const fn into_u16(self) -> u16 {
>
> This and many functions in the abstraction can be `#[inline]`.
>
>> + self as u16
>> + }
>> +}
>> +
>> +impl TryFrom<u16> for Group {
>> + type Error = &'static str;
>> +
>> + /// [`u16`] values can be safely converted to [`Group`] variants.
>> + fn try_from(value: u16) -> Result<Group, Self::Error> {
>> + match value.into() {
>> + bindings::HID_GROUP_GENERIC => Ok(Group::Generic),
>> + bindings::HID_GROUP_MULTITOUCH => Ok(Group::Multitouch),
>> + bindings::HID_GROUP_SENSOR_HUB => Ok(Group::SensorHub),
>> + bindings::HID_GROUP_MULTITOUCH_WIN_8 => Ok(Group::MultitouchWin8),
>> + bindings::HID_GROUP_RMI => Ok(Group::RMI),
>> + bindings::HID_GROUP_WACOM => Ok(Group::Wacom),
>> + bindings::HID_GROUP_LOGITECH_DJ_DEVICE => Ok(Group::LogitechDJDevice),
>> + bindings::HID_GROUP_STEAM => Ok(Group::Steam),
>> + bindings::HID_GROUP_LOGITECH_27MHZ_DEVICE => Ok(Group::Logitech27MHzDevice),
>> + bindings::HID_GROUP_VIVALDI => Ok(Group::Vivaldi),
>> + _ => Err("Unknown HID group encountered!"),
>
> Please define a custom error type or just use a kernel error code.
>
>> + }
>> + }
>> +}
>> +
>> +/// The HID device representation.
>> +///
>> +/// This structure represents the Rust abstraction for a C `struct hid_device`.
>> +/// The implementation abstracts the usage of an already existing C `struct
>> +/// hid_device` within Rust code that we get passed from the C side.
>> +///
>> +/// # Invariants
>> +///
>> +/// A [`Device`] instance represents a valid `struct hid_device` created by the
>> +/// C portion of the kernel.
>> +#[repr(transparent)]
>> +pub struct Device<Ctx: device::DeviceContext = device::Normal>(
>> + Opaque<bindings::hid_device>,
>> + PhantomData<Ctx>,
>> +);
>> +
>> +impl<Ctx: device::DeviceContext> Device<Ctx> {
>> + fn as_raw(&self) -> *mut bindings::hid_device {
>> + self.0.get()
>> + }
>> +
>> + /// Returns the HID transport bus ID.
>> + pub fn bus(&self) -> u16 {
>> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
>> + unsafe { *self.as_raw() }.bus
>> + }
>> +
>> + /// Returns the HID report group.
>> + pub fn group(&self) -> Result<Group, &'static str> {
>> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
>> + unsafe { *self.as_raw() }.group.try_into()
>> + }
>> +
>> + /// Returns the HID vendor ID.
>> + pub fn vendor(&self) -> u32 {
>> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
>> + unsafe { *self.as_raw() }.vendor
>> + }
>> +
>> + /// Returns the HID product ID.
>> + pub fn product(&self) -> u32 {
>> + // SAFETY: `self.as_raw` is a valid pointer to a `struct hid_device`
>> + unsafe { *self.as_raw() }.product
>> + }
>> +}
>> +
>> +// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
>> +// argument.
>> +kernel::impl_device_context_deref!(unsafe { Device });
>> +kernel::impl_device_context_into_aref!(Device);
>> +
>> +// SAFETY: Instances of `Device` are always reference-counted.
>> +unsafe impl crate::types::AlwaysRefCounted for Device {
>> + fn inc_ref(&self) {
>> + // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
>> + unsafe { bindings::get_device(&raw mut (*self.as_raw()).dev) };
>> + }
>> +
>> + unsafe fn dec_ref(obj: NonNull<Self>) {
>> + // SAFETY: The safety requirements guarantee that the refcount is non-zero.
>> + unsafe { bindings::put_device(&raw mut (*obj.cast::<bindings::hid_device>().as_ptr()).dev) }
>> + }
>> +}
>> +
>> +impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
>> + fn as_ref(&self) -> &device::Device<Ctx> {
>> + // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a pointer to a valid
>> + // `struct hid_device`.
>> + let dev = unsafe { addr_of_mut!((*self.as_raw()).dev) };
>> +
>> + // SAFETY: `dev` points to a valid `struct device`.
>> + unsafe { device::Device::from_raw(dev) }
>> + }
>> +}
>> +
>> +/// Abstraction for the HID device ID structure `struct hid_device_id`.
>> +#[repr(transparent)]
>> +#[derive(Clone, Copy)]
>> +pub struct DeviceId(bindings::hid_device_id);
>> +
>> +impl DeviceId {
>> + /// Equivalent to C's `HID_USB_DEVICE` macro.
>> + ///
>> + /// Create a new `hid::DeviceId` from a group, vendor ID, and device ID
>> + /// number.
>
> The main description should be the first sentence, and C equivalent sentence
> follows.
>
>> + pub const fn new_usb(group: Group, vendor: u32, product: u32) -> Self {
>> + Self(bindings::hid_device_id {
>> + bus: 0x3, // BUS_USB
>> + group: group.into_u16(),
>> + vendor,
>> + product,
>> + driver_data: 0,
>> + })
>> + }
>> +
>> + /// Returns the HID transport bus ID.
>> + pub fn bus(&self) -> u16 {
>> + self.0.bus
>> + }
>> +
>> + /// Returns the HID report group.
>> + pub fn group(&self) -> Result<Group, &'static str> {
>> + self.0.group.try_into()
>> + }
>> +
>> + /// Returns the HID vendor ID.
>> + pub fn vendor(&self) -> u32 {
>> + self.0.vendor
>> + }
>> +
>> + /// Returns the HID product ID.
>> + pub fn product(&self) -> u32 {
>> + self.0.product
>> + }
>> +}
>> +
>> +// SAFETY:
>> +// * `DeviceId` is a `#[repr(transparent)` wrapper of `hid_device_id` and does not add
>> +// additional invariants, so it's safe to transmute to `RawType`.
>> +// * `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
>> +unsafe impl RawDeviceId for DeviceId {
>> + type RawType = bindings::hid_device_id;
>> +}
>> +
>> +// SAFETY: `DRIVER_DATA_OFFSET` is the offset to the `driver_data` field.
>> +unsafe impl RawDeviceIdIndex for DeviceId {
>> + const DRIVER_DATA_OFFSET: usize = core::mem::offset_of!(bindings::hid_device_id, driver_data);
>> +
>> + fn index(&self) -> usize {
>> + self.0.driver_data
>> + }
>> +}
>> +
>> +/// [`IdTable`] type for HID.
>> +pub type IdTable<T> = &'static dyn kernel::device_id::IdTable<DeviceId, T>;
>> +
>> +/// Create a HID [`IdTable`] with its alias for modpost.
>> +#[macro_export]
>> +macro_rules! hid_device_table {
>> + ($table_name:ident, $module_table_name:ident, $id_info_type: ty, $table_data: expr) => {
>> + const $table_name: $crate::device_id::IdArray<
>> + $crate::hid::DeviceId,
>> + $id_info_type,
>> + { $table_data.len() },
>> + > = $crate::device_id::IdArray::new($table_data);
>> +
>> + $crate::module_device_table!("hid", $module_table_name, $table_name);
>> + };
>> +}
>> +
>> +/// The HID driver trait.
>> +///
>> +/// # Examples
>> +///
>> +/// ```
>> +/// use kernel::{bindings, device, hid};
>> +///
>> +/// struct MyDriver;
>> +///
>> +/// kernel::hid_device_table!(
>> +/// HID_TABLE,
>> +/// MODULE_HID_TABLE,
>> +/// <MyDriver as hid::Driver>::IdInfo,
>> +/// [(
>> +/// hid::DeviceId::new_usb(
>> +/// hid::Group::Steam,
>> +/// bindings::USB_VENDOR_ID_VALVE,
>> +/// bindings::USB_DEVICE_ID_STEAM_DECK,
>> +/// ),
>> +/// (),
>> +/// )]
>> +/// );
>> +///
>> +/// #[vtable]
>> +/// impl hid::Driver for MyDriver {
>> +/// type IdInfo = ();
>> +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE;
>> +///
>> +/// /// This function is optional to implement.
>> +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] {
>> +/// // Perform some report descriptor fixup.
>> +/// rdesc
>> +/// }
>> +/// }
>> +/// ```
>> +/// Drivers must implement this trait in order to get a HID driver registered.
>> +/// Please refer to the `Adapter` documentation for an example.
>> +#[vtable]
>> +pub trait Driver: Send {
>> + /// The type holding information about each device id supported by the driver.
>> + // TODO: Use `associated_type_defaults` once stabilized:
>> + //
>> + // ```
>> + // type IdInfo: 'static = ();
>> + // ```
>> + type IdInfo: 'static;
>> +
>> + /// The table of device ids supported by the driver.
>> + const ID_TABLE: IdTable<Self::IdInfo>;
>> +
>> + /// Called before report descriptor parsing. Can be used to mutate the
>> + /// report descriptor before the core HID logic processes the descriptor.
>> + /// Useful for problematic report descriptors that prevent HID devices from
>> + /// functioning correctly.
>> + ///
>> + /// Optional to implement.
>> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] {
>
> I think this can just use a single lifetime?
>
I think the problem is when a driver decides to replace the _rdesc with
a static lifetime report descriptor declared in the device driver. My
example driver does not do this, but this is another common pattern in C
HID drivers. This is why two separate lifetimes were required.
>> + build_error!(VTABLE_DEFAULT_ERROR)
>> + }
>> +}
>> +
>> +/// An adapter for the registration of HID drivers.
>> +pub struct Adapter<T: Driver>(T);
>> +
>> +// SAFETY:
>> +// - `bindings::hid_driver` is a C type declared as `repr(C)`.
>> +// - `T` is the type of the driver's device private data.
>> +// - `struct hid_driver` embeds a `struct device_driver`.
>> +// - `DEVICE_DRIVER_OFFSET` is the correct byte offset to the embedded `struct device_driver`.
>> +unsafe impl<T: Driver + 'static> driver::DriverLayout for Adapter<T> {
>> + type DriverType = bindings::hid_driver;
>> + type DriverData = T;
>> + const DEVICE_DRIVER_OFFSET: usize = core::mem::offset_of!(Self::DriverType, driver);
>> +}
>> +
>> +// SAFETY: A call to `unregister` for a given instance of `DriverType` is guaranteed to be valid if
>> +// a preceding call to `register` has been successful.
>> +unsafe impl<T: Driver + 'static> driver::RegistrationOps for Adapter<T> {
>> + unsafe fn register(
>> + hdrv: &Opaque<Self::DriverType>,
>> + name: &'static CStr,
>> + module: &'static ThisModule,
>> + ) -> Result {
>> + // SAFETY: It's safe to set the fields of `struct hid_driver` on initialization.
>> + unsafe {
>> + (*hdrv.get()).name = name.as_char_ptr();
>> + (*hdrv.get()).id_table = T::ID_TABLE.as_ptr();
>> + (*hdrv.get()).report_fixup = if T::HAS_REPORT_FIXUP {
>> + Some(Self::report_fixup_callback)
>> + } else {
>> + None
>> + };
>> + }
>> +
>> + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType`
>> + to_result(unsafe {
>> + bindings::__hid_register_driver(hdrv.get(), module.0, name.as_char_ptr())
>> + })
>> + }
>> +
>> + unsafe fn unregister(hdrv: &Opaque<Self::DriverType>) {
>> + // SAFETY: `hdrv` is guaranteed to be a valid `DriverType`
>> + unsafe { bindings::hid_unregister_driver(hdrv.get()) }
>> + }
>> +}
>> +
>> +impl<T: Driver + 'static> Adapter<T> {
>> + extern "C" fn report_fixup_callback(
>> + hdev: *mut bindings::hid_device,
>> + buf: *mut u8,
>> + size: *mut kernel::ffi::c_uint,
>> + ) -> *const u8 {
>> + // SAFETY: The HID subsystem only ever calls the report_fixup callback
>> + // with a valid pointer to a `struct hid_device`.
>> + //
>> + // INVARIANT: `hdev` is valid for the duration of
>> + // `report_fixup_callback()`.
>> + let hdev = unsafe { &*hdev.cast::<Device<device::Core>>() };
>> +
>> + // SAFETY: The HID subsystem only ever calls the report_fixup callback
>> + // with a valid pointer to a `kernel::ffi::c_uint`.
>> + //
>> + // INVARIANT: `size` is valid for the duration of
>> + // `report_fixup_callback()`.
>> + let buf_len: usize = match unsafe { *size }.try_into() {
>
> This can just be a cast. In all kernel envs `usize` is at least as large as `u32`.
>
>> + Ok(len) => len,
>> + Err(e) => {
>> + dev_err!(
>> + hdev.as_ref(),
>> + "Cannot fix report description due to {:?}!\n",
>> + e
>> + );
>> +
>> + return buf;
>> + }
>> + };
>> +
>> + // Build a mutable Rust slice from `buf` and `size`.
>> + //
>> + // SAFETY: The HID subsystem only ever calls the `report_fixup callback`
>> + // with a valid pointer to a `u8` buffer.
>> + //
>> + // INVARIANT: `buf` is valid for the duration of
>> + // `report_fixup_callback()`.
>> + let rdesc_slice = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) };
>> + let rdesc_slice = T::report_fixup(hdev, rdesc_slice);
>> +
>> + match rdesc_slice.len().try_into() {
>
> I'm somewhat inclined to just `BUG()` (i.e. `.expect()`) in this case. A driver
> that hits this error condition would need to leak >= 4G of memory to satisfy the
> lifetime bound.
>
>> + // SAFETY: The HID subsystem only ever calls the report_fixup
>> + // callback with a valid pointer to a `kernel::ffi::c_uint`.
>> + //
>> + // INVARIANT: `size` is valid for the duration of
>> + // `report_fixup_callback()`.
>> + Ok(len) => unsafe { *size = len },
>> + Err(e) => {
>> + dev_err!(
>> + hdev.as_ref(),
>
> as_ref() shouldn't be needed now.
>
> Best,
> Gary
>
Thanks for the feedback. Aside from my concern regarding the two
separate lifetimes, I think all the feedback you provided made sense to
me. Applying towards my v7.
Thanks,
Rahul Rameshbabu
>> + "Fixed report description will not be used due to {:?}!\n",
>> + e
>> + );
>> +
>> + return buf;
>> + }
>> + }
>> +
>> + rdesc_slice.as_ptr()
>> + }
>> +}
>> +
>> +/// Declares a kernel module that exposes a single HID driver.
>> +///
>> +/// # Examples
>> +///
>> +/// ```ignore
>> +/// kernel::module_hid_driver! {
>> +/// type: MyDriver,
>> +/// name: "Module name",
>> +/// authors: ["Author name"],
>> +/// description: "Description",
>> +/// license: "GPL",
>> +/// }
>> +/// ```
>> +#[macro_export]
>> +macro_rules! module_hid_driver {
>> + ($($f:tt)*) => {
>> + $crate::module_driver!(<T>, $crate::hid::Adapter<T>, { $($f)* });
>> + };
>> +}
>> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
>> index 3da92f18f4ee..e2dcacd9369e 100644
>> --- a/rust/kernel/lib.rs
>> +++ b/rust/kernel/lib.rs
>> @@ -102,6 +102,8 @@
>> pub mod id_pool;
>> #[doc(hidden)]
>> pub mod impl_flags;
>> +#[cfg(CONFIG_RUST_HID_ABSTRACTIONS)]
>> +pub mod hid;
>> pub mod init;
>> pub mod io;
>> pub mod ioctl;
^ permalink raw reply
* Re: [PATCH v2] Bluetooth: HIDP: cap report descriptor size in HID setup
From: Bastien Nocera @ 2026-03-01 9:19 UTC (permalink / raw)
To: Eric-Terminal, marcel, johan.hedberg, luiz.dentz
Cc: linux-bluetooth, linux-kernel, linux-input
In-Reply-To: <20260228172657.53040-1-ericterminal@gmail.com>
On Sun, 2026-03-01 at 01:26 +0800, Eric-Terminal wrote:
> From: Yufan Chen <ericterminal@gmail.com>
>
> hidp_setup_hid() duplicates the report descriptor from userspace
> based on
> req->rd_size. Large values can trigger oversized copies.
>
> Do not reject the connection when rd_size exceeds
> HID_MAX_DESCRIPTOR_SIZE. Instead, cap rd_size in hidp_setup_hid()
> and use the capped value for memdup_user() and session->rd_size.
>
> This keeps compatibility with existing userspace behavior while
> bounding memory usage in the HID setup path.
Cross-sending this to linux-input@ for review, they would know the best
way to deal with oversized HID descriptors.
>
> Signed-off-by: Yufan Chen <ericterminal@gmail.com>
> ---
> net/bluetooth/hidp/core.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
> index 6fe815241..31aeffa39 100644
> --- a/net/bluetooth/hidp/core.c
> +++ b/net/bluetooth/hidp/core.c
> @@ -755,13 +755,16 @@ static int hidp_setup_hid(struct hidp_session
> *session,
> const struct hidp_connadd_req *req)
> {
> struct hid_device *hid;
> + unsigned int rd_size;
> int err;
>
> - session->rd_data = memdup_user(req->rd_data, req->rd_size);
> + rd_size = min_t(unsigned int, req->rd_size,
> HID_MAX_DESCRIPTOR_SIZE);
> +
> + session->rd_data = memdup_user(req->rd_data, rd_size);
> if (IS_ERR(session->rd_data))
> return PTR_ERR(session->rd_data);
>
> - session->rd_size = req->rd_size;
> + session->rd_size = rd_size;
>
> hid = hid_allocate_device();
> if (IS_ERR(hid)) {
^ permalink raw reply
* Re: [PATCH 1/2] iio: hid-sensor-gyro-3d: move iio_device_register() to end of probe()
From: Jonathan Cameron @ 2026-03-01 11:44 UTC (permalink / raw)
To: Bhargav Joshi
Cc: jikos, srinivas.pandruvada, dlechner, nuno.sa, andy, linux-iio,
linux-kernel, linux-input
In-Reply-To: <20260228191400.19244-2-rougueprince47@gmail.com>
On Sun, 1 Mar 2026 00:43:59 +0530
Bhargav Joshi <rougueprince47@gmail.com> wrote:
> Currently, calling iio_device_register() before
> sensor_hub_register_callback() may create a race condition where the
> device is exposed to userspace before callbacks are wired.
We needs some more here on 'why' this is a problem.
Whilst this is an unusual arrangement, I couldn't immediately find
anything that was actually broken. It's possible data will turn up
after we tear down the callbacks and before the userspace interfaces
are removed, but I think that just results in dropping data. Given it's
in a race anyway I don't think we care about that. The callbacks
don't seem to be involved in device configuration which can go on whether
or not the callbacks are registered. Maybe there is something that
won't get acknowledged if they aren't in place in time?
For a fix like this we'd normally want to see a clear flow that
leads to a bug.
Also a fixes tag so we know how far to backport.
>
> Move iio_device_register() to the end of the probe() function to prevent
> race condition.
>
> Consequently, update the error handling path in probe() and in remove()
> ensuring that iio_device_unregister() is called first to cut off
> userspace access before the hardware callbacks are removed.
>
> Signed-off-by: Bhargav Joshi <rougueprince47@gmail.com>
> ---
> drivers/iio/gyro/hid-sensor-gyro-3d.c | 20 ++++++++++----------
> 1 file changed, 10 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> index c43990c518f7..8e3628cd8529 100644
> --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
> +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> @@ -333,12 +333,6 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
> return ret;
> }
>
> - ret = iio_device_register(indio_dev);
> - if (ret) {
> - dev_err(&pdev->dev, "device register failed\n");
> - goto error_remove_trigger;
> - }
> -
> gyro_state->callbacks.send_event = gyro_3d_proc_event;
> gyro_state->callbacks.capture_sample = gyro_3d_capture_sample;
> gyro_state->callbacks.pdev = pdev;
> @@ -346,13 +340,19 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
> &gyro_state->callbacks);
> if (ret < 0) {
> dev_err(&pdev->dev, "callback reg failed\n");
> - goto error_iio_unreg;
> + goto error_remove_trigger;
> + }
> +
> + ret = iio_device_register(indio_dev);
> + if (ret) {
> + dev_err(&pdev->dev, "device register failed\n");
> + goto error_remove_callback;
> }
>
> return ret;
>
> -error_iio_unreg:
> - iio_device_unregister(indio_dev);
> +error_remove_callback:
> + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
> error_remove_trigger:
> hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes);
> return ret;
> @@ -365,8 +365,8 @@ static void hid_gyro_3d_remove(struct platform_device *pdev)
> struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> struct gyro_3d_state *gyro_state = iio_priv(indio_dev);
>
> - sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
> iio_device_unregister(indio_dev);
> + sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_GYRO_3D);
> hid_sensor_remove_trigger(indio_dev, &gyro_state->common_attributes);
> }
>
^ permalink raw reply
* Re: [PATCH 2/2] iio: hid-sensor-gyro-3d: fix typo in array name
From: Jonathan Cameron @ 2026-03-01 11:46 UTC (permalink / raw)
To: Bhargav Joshi
Cc: jikos, srinivas.pandruvada, dlechner, nuno.sa, andy, linux-iio,
linux-kernel, linux-input
In-Reply-To: <20260228191400.19244-3-rougueprince47@gmail.com>
On Sun, 1 Mar 2026 00:44:00 +0530
Bhargav Joshi <rougueprince47@gmail.com> wrote:
> The array 'gryo_3d_sensitivity_addresses' has a clear spelling mistake
> in its prefix. Rename it to 'gyro_3d_sensitivity_addresses' to correctly
> match the naming convention.
>
> Signed-off-by: Bhargav Joshi <rougueprince47@gmail.com>
Hi Bhargav,
This stands on it's own so I've picked it up.
Applied to the togreg branch of iio.git and pushed out as testing for
all the normal reasons.
Thanks,
Jonathan
> ---
> drivers/iio/gyro/hid-sensor-gyro-3d.c | 6 +++---
> 1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/iio/gyro/hid-sensor-gyro-3d.c b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> index 8e3628cd8529..4f88fe478d84 100644
> --- a/drivers/iio/gyro/hid-sensor-gyro-3d.c
> +++ b/drivers/iio/gyro/hid-sensor-gyro-3d.c
> @@ -42,7 +42,7 @@ static const u32 gyro_3d_addresses[GYRO_3D_CHANNEL_MAX] = {
> HID_USAGE_SENSOR_ANGL_VELOCITY_Z_AXIS
> };
>
> -static const u32 gryo_3d_sensitivity_addresses[] = {
> +static const u32 gyro_3d_sensitivity_addresses[] = {
> HID_USAGE_SENSOR_DATA_ANGL_VELOCITY,
> };
>
> @@ -297,8 +297,8 @@ static int hid_gyro_3d_probe(struct platform_device *pdev)
> ret = hid_sensor_parse_common_attributes(hsdev,
> HID_USAGE_SENSOR_GYRO_3D,
> &gyro_state->common_attributes,
> - gryo_3d_sensitivity_addresses,
> - ARRAY_SIZE(gryo_3d_sensitivity_addresses));
> + gyro_3d_sensitivity_addresses,
> + ARRAY_SIZE(gyro_3d_sensitivity_addresses));
> if (ret) {
> dev_err(&pdev->dev, "failed to setup common attributes\n");
> return ret;
^ permalink raw reply
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers
From: Alice Ryhl @ 2026-03-01 12:30 UTC (permalink / raw)
To: Rahul Rameshbabu
Cc: Gary Guo, linux-input, linux-kernel, rust-for-linux, a.hindborg,
alex.gaynor, benjamin.tissoires, benno.lossin, bjorn3_gh,
boqun.feng, dakr, db48x, jikos, ojeda, peter.hutterer, tmgross
In-Reply-To: <87o6l8kpud.fsf@protonmail.com>
On Sun, Mar 01, 2026 at 06:48:20AM +0000, Rahul Rameshbabu wrote:
> On Fri, 27 Feb, 2026 15:27:59 +0000 "Gary Guo" <gary@garyguo.net> wrote:
> > On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote:
> >> +/// The HID driver trait.
> >> +///
> >> +/// # Examples
> >> +///
> >> +/// ```
> >> +/// use kernel::{bindings, device, hid};
> >> +///
> >> +/// struct MyDriver;
> >> +///
> >> +/// kernel::hid_device_table!(
> >> +/// HID_TABLE,
> >> +/// MODULE_HID_TABLE,
> >> +/// <MyDriver as hid::Driver>::IdInfo,
> >> +/// [(
> >> +/// hid::DeviceId::new_usb(
> >> +/// hid::Group::Steam,
> >> +/// bindings::USB_VENDOR_ID_VALVE,
> >> +/// bindings::USB_DEVICE_ID_STEAM_DECK,
> >> +/// ),
> >> +/// (),
> >> +/// )]
> >> +/// );
> >> +///
> >> +/// #[vtable]
> >> +/// impl hid::Driver for MyDriver {
> >> +/// type IdInfo = ();
> >> +/// const ID_TABLE: hid::IdTable<Self::IdInfo> = &HID_TABLE;
> >> +///
> >> +/// /// This function is optional to implement.
> >> +/// fn report_fixup<'a, 'b: 'a>(_hdev: &hid::Device<device::Core>, rdesc: &'b mut [u8]) -> &'a [u8] {
> >> +/// // Perform some report descriptor fixup.
> >> +/// rdesc
> >> +/// }
> >> +/// }
> >> +/// ```
> >> +/// Drivers must implement this trait in order to get a HID driver registered.
> >> +/// Please refer to the `Adapter` documentation for an example.
> >> +#[vtable]
> >> +pub trait Driver: Send {
> >> + /// The type holding information about each device id supported by the driver.
> >> + // TODO: Use `associated_type_defaults` once stabilized:
> >> + //
> >> + // ```
> >> + // type IdInfo: 'static = ();
> >> + // ```
> >> + type IdInfo: 'static;
> >> +
> >> + /// The table of device ids supported by the driver.
> >> + const ID_TABLE: IdTable<Self::IdInfo>;
> >> +
> >> + /// Called before report descriptor parsing. Can be used to mutate the
> >> + /// report descriptor before the core HID logic processes the descriptor.
> >> + /// Useful for problematic report descriptors that prevent HID devices from
> >> + /// functioning correctly.
> >> + ///
> >> + /// Optional to implement.
> >> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] {
> >
> > I think this can just use a single lifetime?
> >
>
> I think the problem is when a driver decides to replace the _rdesc with
> a static lifetime report descriptor declared in the device driver. My
> example driver does not do this, but this is another common pattern in C
> HID drivers. This is why two separate lifetimes were required.
I'm not entirely sure what you mean, but adding a lifetime here does not
change what this function is allowed to do at all.
Alice
^ permalink raw reply
* Re: [PATCH v6 1/2] rust: core abstractions for HID drivers
From: Gary Guo @ 2026-03-01 13:17 UTC (permalink / raw)
To: Rahul Rameshbabu, Gary Guo
Cc: linux-input, linux-kernel, rust-for-linux, a.hindborg,
alex.gaynor, aliceryhl, benjamin.tissoires, benno.lossin,
bjorn3_gh, boqun.feng, dakr, db48x, jikos, ojeda, peter.hutterer,
tmgross
In-Reply-To: <87o6l8kpud.fsf@protonmail.com>
On Sun Mar 1, 2026 at 6:48 AM GMT, Rahul Rameshbabu wrote:
> On Fri, 27 Feb, 2026 15:27:59 +0000 "Gary Guo" <gary@garyguo.net> wrote:
>> On Sun Feb 22, 2026 at 9:56 PM GMT, Rahul Rameshbabu wrote:
>>> These abstractions enable the development of HID drivers in Rust by binding
>>> with the HID core C API. They provide Rust types that map to the
>>> equivalents in C. In this initial draft, only hid_device and hid_device_id
>>> are provided direct Rust type equivalents. hid_driver is specially wrapped
>>> with a custom Driver type. The module_hid_driver! macro provides analogous
>>> functionality to its C equivalent. Only the .report_fixup callback is
>>> binded to Rust so far.
>>>
>>> Future work for these abstractions would include more bindings for common
>>> HID-related types, such as hid_field, hid_report_enum, and hid_report as
>>> well as more bus callbacks. Providing Rust equivalents to useful core HID
>>> functions will also be necessary for HID driver development in Rust.
>>>
>>> Signed-off-by: Rahul Rameshbabu <sergeantsagara@protonmail.com>
[snip]
>>> +pub trait Driver: Send {
>>> + /// The type holding information about each device id supported by the driver.
>>> + // TODO: Use `associated_type_defaults` once stabilized:
>>> + //
>>> + // ```
>>> + // type IdInfo: 'static = ();
>>> + // ```
>>> + type IdInfo: 'static;
>>> +
>>> + /// The table of device ids supported by the driver.
>>> + const ID_TABLE: IdTable<Self::IdInfo>;
>>> +
>>> + /// Called before report descriptor parsing. Can be used to mutate the
>>> + /// report descriptor before the core HID logic processes the descriptor.
>>> + /// Useful for problematic report descriptors that prevent HID devices from
>>> + /// functioning correctly.
>>> + ///
>>> + /// Optional to implement.
>>> + fn report_fixup<'a, 'b: 'a>(_hdev: &Device<device::Core>, _rdesc: &'b mut [u8]) -> &'a [u8] {
>>
>> I think this can just use a single lifetime?
>>
>
> I think the problem is when a driver decides to replace the _rdesc with
> a static lifetime report descriptor declared in the device driver. My
> example driver does not do this, but this is another common pattern in C
> HID drivers. This is why two separate lifetimes were required.
>
Lifetime on references are covariant, so it is okay to convert `&'static [u8]`
to `&'b mut [u8]`.
Best,
Gary
^ permalink raw reply
* [PATCH 01/10] Input: stmfts - Use dev struct directly
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Makes the code better readable and noticably shorter.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 4b166b0a9a5a6..27adb139fa0ce 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -620,6 +620,7 @@ static int stmfts_enable_led(struct stmfts_data *sdata)
static int stmfts_probe(struct i2c_client *client)
{
+ struct device *dev = &client->dev;
int err;
struct stmfts_data *sdata;
@@ -628,7 +629,7 @@ static int stmfts_probe(struct i2c_client *client)
I2C_FUNC_SMBUS_I2C_BLOCK))
return -ENODEV;
- sdata = devm_kzalloc(&client->dev, sizeof(*sdata), GFP_KERNEL);
+ sdata = devm_kzalloc(dev, sizeof(*sdata), GFP_KERNEL);
if (!sdata)
return -ENOMEM;
@@ -640,13 +641,13 @@ static int stmfts_probe(struct i2c_client *client)
sdata->regulators[STMFTS_REGULATOR_VDD].supply = "vdd";
sdata->regulators[STMFTS_REGULATOR_AVDD].supply = "avdd";
- err = devm_regulator_bulk_get(&client->dev,
+ err = devm_regulator_bulk_get(dev,
ARRAY_SIZE(sdata->regulators),
sdata->regulators);
if (err)
return err;
- sdata->input = devm_input_allocate_device(&client->dev);
+ sdata->input = devm_input_allocate_device(dev);
if (!sdata->input)
return -ENOMEM;
@@ -665,7 +666,7 @@ static int stmfts_probe(struct i2c_client *client)
input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
- sdata->use_key = device_property_read_bool(&client->dev,
+ sdata->use_key = device_property_read_bool(dev,
"touch-key-connected");
if (sdata->use_key) {
input_set_capability(sdata->input, EV_KEY, KEY_MENU);
@@ -686,20 +687,20 @@ static int stmfts_probe(struct i2c_client *client)
* interrupts. To be on the safe side it's better to not enable
* the interrupts during their request.
*/
- err = devm_request_threaded_irq(&client->dev, client->irq,
+ err = devm_request_threaded_irq(dev, client->irq,
NULL, stmfts_irq_handler,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
"stmfts_irq", sdata);
if (err)
return err;
- dev_dbg(&client->dev, "initializing ST-Microelectronics FTS...\n");
+ dev_dbg(dev, "initializing ST-Microelectronics FTS...\n");
err = stmfts_power_on(sdata);
if (err)
return err;
- err = devm_add_action_or_reset(&client->dev, stmfts_power_off, sdata);
+ err = devm_add_action_or_reset(dev, stmfts_power_off, sdata);
if (err)
return err;
@@ -716,13 +717,13 @@ static int stmfts_probe(struct i2c_client *client)
* without LEDs. The ledvdd regulator pointer will be
* used as a flag.
*/
- dev_warn(&client->dev, "unable to use touchkey leds\n");
+ dev_warn(dev, "unable to use touchkey leds\n");
sdata->ledvdd = NULL;
}
}
- pm_runtime_enable(&client->dev);
- device_enable_async_suspend(&client->dev);
+ pm_runtime_enable(dev);
+ device_enable_async_suspend(dev);
return 0;
}
--
2.51.0
^ permalink raw reply related
* [PATCH 00/10] Input: support for STM FTS5
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
Used on various phones. Minimal basic support.
Includes device-tree with possibility to enable touchscreen on Pixel 3.
Sending as RFC, as this is first, seemingly clean version which works.
What is missing:
- firmware loading
- switching between AP and SLPI mode (to wake up phone by touch)
- anything above basic touch
Signed-off-by: David Heidelberg <david@ixit.cz>
---
David Heidelberg (6):
Input: stmfts - Use dev struct directly
Input: stmfts - Switch to devm_regulator_bulk_get_const
Input: stmfts - abstract reading information from the firmware
Input: stmfts - disable regulators when power on fails
dt-bindings: input: touchscreen: st,stmfts: Introduce reset GPIO
dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
Petr Hodina (4):
Input: stmfts - use client to make future code cleaner
Input: stmfts - add optional reset GPIO support
Input: stmfts - support FTS5
arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support
.../bindings/input/touchscreen/st,stmfts.yaml | 21 +-
.../arm64/boot/dts/qcom/sdm845-google-blueline.dts | 21 +-
drivers/input/touchscreen/stmfts.c | 595 +++++++++++++++++++--
3 files changed, 577 insertions(+), 60 deletions(-)
---
base-commit: c6870e543e8d55d725cccfe972fbfb5798daa1af
change-id: 20260214-stmfts5-b47311fbd732
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply
* [PATCH 02/10] Input: stmfts - Switch to devm_regulator_bulk_get_const
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Switch to devm_regulator_bulk_get_const() to stop setting the supplies
list in probe(), and move the regulator_bulk_data struct in static const.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 27adb139fa0ce..7c0f94ba49464 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -69,9 +69,9 @@
#define STMFTS_MAX_FINGERS 10
#define STMFTS_DEV_NAME "stmfts"
-enum stmfts_regulators {
- STMFTS_REGULATOR_VDD,
- STMFTS_REGULATOR_AVDD,
+static const struct regulator_bulk_data stmfts_supplies[] = {
+ { .supply = "vdd" },
+ { .supply = "avdd" },
};
struct stmfts_data {
@@ -82,7 +82,7 @@ struct stmfts_data {
struct touchscreen_properties prop;
- struct regulator_bulk_data regulators[2];
+ struct regulator_bulk_data *supplies;
/*
* Presence of ledvdd will be used also to check
@@ -524,8 +524,8 @@ static int stmfts_power_on(struct stmfts_data *sdata)
int err;
u8 reg[8];
- err = regulator_bulk_enable(ARRAY_SIZE(sdata->regulators),
- sdata->regulators);
+ err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
if (err)
return err;
@@ -590,8 +590,8 @@ static void stmfts_power_off(void *data)
struct stmfts_data *sdata = data;
disable_irq(sdata->client->irq);
- regulator_bulk_disable(ARRAY_SIZE(sdata->regulators),
- sdata->regulators);
+ regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
}
static int stmfts_enable_led(struct stmfts_data *sdata)
@@ -639,11 +639,10 @@ static int stmfts_probe(struct i2c_client *client)
mutex_init(&sdata->mutex);
init_completion(&sdata->cmd_done);
- sdata->regulators[STMFTS_REGULATOR_VDD].supply = "vdd";
- sdata->regulators[STMFTS_REGULATOR_AVDD].supply = "avdd";
- err = devm_regulator_bulk_get(dev,
- ARRAY_SIZE(sdata->regulators),
- sdata->regulators);
+ err = devm_regulator_bulk_get_const(dev,
+ ARRAY_SIZE(stmfts_supplies),
+ stmfts_supplies,
+ &sdata->supplies);
if (err)
return err;
--
2.51.0
^ permalink raw reply related
* [PATCH 04/10] Input: stmfts - disable regulators when power on fails
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: David Heidelberg <david@ixit.cz>
We must power off regulators after failing at power on phase.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index db2dd0bb59fcc..f4e5f1b3ce796 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -558,7 +558,7 @@ static int stmfts_power_on(struct stmfts_data *sdata)
err = stmfts_read_system_info(sdata);
if (err)
- return err;
+ goto power_off;
enable_irq(sdata->client->irq);
@@ -566,11 +566,11 @@ static int stmfts_power_on(struct stmfts_data *sdata)
err = stmfts_command(sdata, STMFTS_SYSTEM_RESET);
if (err)
- return err;
+ goto power_off;
err = stmfts_command(sdata, STMFTS_SLEEP_OUT);
if (err)
- return err;
+ goto power_off;
/* optional tuning */
err = stmfts_command(sdata, STMFTS_MS_CX_TUNING);
@@ -586,7 +586,7 @@ static int stmfts_power_on(struct stmfts_data *sdata)
err = stmfts_command(sdata, STMFTS_FULL_FORCE_CALIBRATION);
if (err)
- return err;
+ goto power_off;
/*
* At this point no one is using the touchscreen
@@ -595,6 +595,11 @@ static int stmfts_power_on(struct stmfts_data *sdata)
(void) i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
return 0;
+
+power_off:
+ regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ return err;
}
static void stmfts_power_off(void *data)
--
2.51.0
^ permalink raw reply related
* [PATCH 03/10] Input: stmfts - abstract reading information from the firmware
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Improves readability and makes splitting power on function in following
commit easier.
---
drivers/input/touchscreen/stmfts.c | 36 ++++++++++++++++++++++++------------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 7c0f94ba49464..db2dd0bb59fcc 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -519,22 +519,11 @@ static struct attribute *stmfts_sysfs_attrs[] = {
};
ATTRIBUTE_GROUPS(stmfts_sysfs);
-static int stmfts_power_on(struct stmfts_data *sdata)
+static int stmfts_read_system_info(struct stmfts_data *sdata)
{
int err;
u8 reg[8];
- err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
- sdata->supplies);
- if (err)
- return err;
-
- /*
- * The datasheet does not specify the power on time, but considering
- * that the reset time is < 10ms, I sleep 20ms to be sure
- */
- msleep(20);
-
err = i2c_smbus_read_i2c_block_data(sdata->client, STMFTS_READ_INFO,
sizeof(reg), reg);
if (err < 0)
@@ -548,6 +537,29 @@ static int stmfts_power_on(struct stmfts_data *sdata)
sdata->config_id = reg[4];
sdata->config_ver = reg[5];
+ return 0;
+}
+
+static int stmfts_power_on(struct stmfts_data *sdata)
+{
+ int err;
+
+ err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ if (err)
+ return err;
+
+ /*
+ * The datasheet does not specify the power on time, but considering
+ * that the reset time is < 10ms, I sleep 20ms to be sure
+ */
+ msleep(20);
+
+
+ err = stmfts_read_system_info(sdata);
+ if (err)
+ return err;
+
enable_irq(sdata->client->irq);
msleep(50);
--
2.51.0
^ permalink raw reply related
* [PATCH 05/10] Input: stmfts - use client to make future code cleaner
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Make code cleaner, compiler will optimize it away anyway.
Preparation for FTM5 support, where more steps are needed.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index f4e5f1b3ce796..9dedccbb183ed 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -764,9 +764,10 @@ static int stmfts_runtime_suspend(struct device *dev)
static int stmfts_runtime_resume(struct device *dev)
{
struct stmfts_data *sdata = dev_get_drvdata(dev);
+ struct i2c_client *client = sdata->client;
int ret;
- ret = i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_OUT);
+ ret = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
if (ret)
dev_err(dev, "failed to resume device: %d\n", ret);
--
2.51.0
^ permalink raw reply related
* [PATCH 06/10] dt-bindings: input: touchscreen: st,stmfts: Introduce reset GPIO
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: David Heidelberg <david@ixit.cz>
FTS may have associated reset GPIO, document it.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
index 12256ae7df90d..64c4f24ea3dd0 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
@@ -40,6 +40,10 @@ properties:
vdd-supply:
description: Power supply
+ reset-gpios:
+ description: Reset GPIO (active-low)
+ maxItems: 1
+
required:
- compatible
- reg
--
2.51.0
^ permalink raw reply related
* [PATCH 07/10] Input: stmfts - add optional reset GPIO support
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Add support for an optional "reset-gpios" property. If present, the
driver drives the reset line high at probe time and releases it during
power-on, after the regulators have been enabled.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index 9dedccbb183ed..a4d8e81aba275 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -77,6 +77,7 @@ static const struct regulator_bulk_data stmfts_supplies[] = {
struct stmfts_data {
struct i2c_client *client;
struct input_dev *input;
+ struct gpio_desc *reset_gpio;
struct led_classdev led_cdev;
struct mutex mutex;
@@ -540,6 +541,15 @@ static int stmfts_read_system_info(struct stmfts_data *sdata)
return 0;
}
+static void stmfts_reset(struct stmfts_data *sdata)
+{
+ gpiod_set_value_cansleep(sdata->reset_gpio, 1);
+ msleep(20);
+
+ gpiod_set_value_cansleep(sdata->reset_gpio, 0);
+ msleep(50);
+}
+
static int stmfts_power_on(struct stmfts_data *sdata)
{
int err;
@@ -549,6 +559,9 @@ static int stmfts_power_on(struct stmfts_data *sdata)
if (err)
return err;
+ if (sdata->reset_gpio)
+ stmfts_reset(sdata);
+
/*
* The datasheet does not specify the power on time, but considering
* that the reset time is < 10ms, I sleep 20ms to be sure
@@ -607,6 +620,10 @@ static void stmfts_power_off(void *data)
struct stmfts_data *sdata = data;
disable_irq(sdata->client->irq);
+
+ if (sdata->reset_gpio)
+ gpiod_set_value_cansleep(sdata->reset_gpio, 1);
+
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
sdata->supplies);
}
@@ -663,6 +680,12 @@ static int stmfts_probe(struct i2c_client *client)
if (err)
return err;
+ sdata->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sdata->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(sdata->reset_gpio),
+ "Failed to get GPIO 'reset'\n");
+
sdata->input = devm_input_allocate_device(dev);
if (!sdata->input)
return -ENOMEM;
--
2.51.0
^ permalink raw reply related
* [PATCH 08/10] dt-bindings: input: touchscreen: st,stmfts: Introduce STM FTS5
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: David Heidelberg <david@ixit.cz>
Introduce more recent STM FTS5 touchscreen support.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
.../bindings/input/touchscreen/st,stmfts.yaml | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
index 64c4f24ea3dd0..329d89977bdbc 100644
--- a/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
+++ b/Documentation/devicetree/bindings/input/touchscreen/st,stmfts.yaml
@@ -19,7 +19,9 @@ allOf:
properties:
compatible:
- const: st,stmfts
+ enum:
+ - st,stmfts
+ - st,stmfts5
reg:
maxItems: 1
@@ -53,6 +55,19 @@ required:
unevaluatedProperties: false
+allOf:
+ - if:
+ properties:
+ compatible:
+ const: st,stmfts5
+ then:
+ properties:
+ switch-gpio:
+ description: Switch between SLPI and AP mode.
+
+ required:
+ - switch-gpio
+
examples:
- |
#include <dt-bindings/interrupt-controller/irq.h>
--
2.51.0
^ permalink raw reply related
* [PATCH 09/10] Input: stmfts - support FTS5
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Introduce basic FTS5 support.
FTS support SLPI and AP mode, introduce switch GPIO to switch between
those two. Currently we can handle only full power AP mode, so we just
switch to it.
Useful for devices like Pixel 3 (blueline).
Nitpick: changed GPL v2 to GPL in module license.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/touchscreen/stmfts.c | 484 +++++++++++++++++++++++++++++++++++--
1 file changed, 461 insertions(+), 23 deletions(-)
diff --git a/drivers/input/touchscreen/stmfts.c b/drivers/input/touchscreen/stmfts.c
index a4d8e81aba275..5f0f2d59300e4 100644
--- a/drivers/input/touchscreen/stmfts.c
+++ b/drivers/input/touchscreen/stmfts.c
@@ -1,8 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
-// STMicroelectronics FTS Touchscreen device driver
-//
-// Copyright (c) 2017 Samsung Electronics Co., Ltd.
-// Copyright (c) 2017 Andi Shyti <andi@etezian.org>
+/* STMicroelectronics FTS Touchscreen device driver
+ *
+ * Supports version FTS4, FTS5.
+ *
+ * Copyright 2017 Samsung Electronics Co., Ltd.
+ * Copyright 2017 Andi Shyti <andi@etezian.org>
+ * Copyright David Heidelberg <david@ixit.cz>
+ * Copyright Petr Hodina <petr.hodina@protonmail.com>
+ */
#include <linux/delay.h>
#include <linux/i2c.h>
@@ -12,6 +17,7 @@
#include <linux/irq.h>
#include <linux/leds.h>
#include <linux/module.h>
+#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
@@ -34,6 +40,7 @@
#define STMFTS_FULL_FORCE_CALIBRATION 0xa2
#define STMFTS_MS_CX_TUNING 0xa3
#define STMFTS_SS_CX_TUNING 0xa4
+#define STMFTS5_SET_SCAN_MODE 0xa0
/* events */
#define STMFTS_EV_NO_EVENT 0x00
@@ -51,12 +58,32 @@
#define STMFTS_EV_STATUS 0x16
#define STMFTS_EV_DEBUG 0xdb
+/* events FTS5 */
+#define STMFTS5_EV_CONTROLLER_READY 0x03
+/* FTM5 event IDs (full byte, not masked) */
+#define STMFTS5_EV_MULTI_TOUCH_ENTER 0x13
+#define STMFTS5_EV_MULTI_TOUCH_MOTION 0x23
+#define STMFTS5_EV_MULTI_TOUCH_LEAVE 0x33
+#define STMFTS5_EV_STATUS_UPDATE 0x43
+#define STMFTS5_EV_USER_REPORT 0x53
+#define STMFTS5_EV_DEBUG 0xe3
+#define STMFTS5_EV_ERROR 0xf3
+
/* multi touch related event masks */
#define STMFTS_MASK_EVENT_ID 0x0f
#define STMFTS_MASK_TOUCH_ID 0xf0
#define STMFTS_MASK_LEFT_EVENT 0x0f
#define STMFTS_MASK_X_MSB 0x0f
#define STMFTS_MASK_Y_LSB 0xf0
+#define STMFTS5_MASK_TOUCH_TYPE 0x0f
+
+/* touch type classifications */
+#define STMFTS_TOUCH_TYPE_INVALID 0x00
+#define STMFTS_TOUCH_TYPE_FINGER 0x01
+#define STMFTS_TOUCH_TYPE_GLOVE 0x02
+#define STMFTS_TOUCH_TYPE_STYLUS 0x03
+#define STMFTS_TOUCH_TYPE_PALM 0x04
+#define STMFTS_TOUCH_TYPE_HOVER 0x05
/* key related event masks */
#define STMFTS_MASK_KEY_NO_TOUCH 0x00
@@ -77,7 +104,9 @@ static const struct regulator_bulk_data stmfts_supplies[] = {
struct stmfts_data {
struct i2c_client *client;
struct input_dev *input;
+ struct gpio_desc *irq_gpio;
struct gpio_desc *reset_gpio;
+ struct gpio_desc *switch_gpio;
struct led_classdev led_cdev;
struct mutex mutex;
@@ -101,9 +130,14 @@ struct stmfts_data {
struct completion cmd_done;
+ unsigned long touch_id;
+ unsigned long stylus_id;
+
+ bool is_fts5;
bool use_key;
bool led_status;
bool hover_enabled;
+ bool stylus_enabled;
bool running;
};
@@ -169,6 +203,7 @@ static int stmfts_read_events(struct stmfts_data *sdata)
return ret == ARRAY_SIZE(msgs) ? 0 : -EIO;
}
+/* FTS4 event handling functions */
static void stmfts_report_contact_event(struct stmfts_data *sdata,
const u8 event[])
{
@@ -204,6 +239,157 @@ static void stmfts_report_contact_release(struct stmfts_data *sdata,
input_sync(sdata->input);
}
+/* FTS5 event handling functions */
+static void stmfts5_report_contact_event(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ u8 area;
+ u8 maj;
+ u8 min;
+ /* FTM5 event format:
+ * event[0] = event ID (0x13/0x23)
+ * event[1] = touch type (low 4 bits) | touch ID (high 4 bits)
+ * event[2] = X LSB
+ * event[3] = X MSB (low 4 bits) | Y MSB (high 4 bits)
+ * event[4] = Y LSB
+ * event[5] = pressure
+ * event[6] = major (low 4 bits) | minor (high 4 bits)
+ * event[7] = minor (high 2 bits)
+ */
+ u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
+ u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE;
+ int x, y, distance;
+ unsigned int tool = MT_TOOL_FINGER;
+ bool touch_condition = true;
+
+ /* Parse coordinates with better precision */
+ x = (((int)event[3] & STMFTS_MASK_X_MSB) << 8) | event[2];
+ y = ((int)event[4] << 4) | ((event[3] & STMFTS_MASK_Y_LSB) >> 4);
+
+ /* Parse pressure - ensure non-zero for active touch */
+ area = event[5];
+ if (area <= 0 && touch_type != STMFTS_TOUCH_TYPE_HOVER) {
+ /* Should not happen for contact events. Set minimum pressure
+ * to prevent touch from being dropped
+ */
+ dev_warn_once(&sdata->client->dev,
+ "zero pressure on contact event, slot %d\n", touch_id);
+ area = 1;
+ }
+
+ /* Parse touch area with improved bit extraction */
+ maj = (((event[0] & 0x0C) << 2) | ((event[6] & 0xF0) >> 4));
+ min = (((event[7] & 0xC0) >> 2) | (event[6] & 0x0F));
+
+ /* Distance is 0 for touching, max for hovering */
+ distance = 0;
+
+ /* Classify touch type and set appropriate tool and parameters */
+ switch (touch_type) {
+ case STMFTS_TOUCH_TYPE_STYLUS:
+ if (sdata->stylus_enabled) {
+ tool = MT_TOOL_PEN;
+ __set_bit(touch_id, &sdata->stylus_id);
+ __clear_bit(touch_id, &sdata->touch_id);
+ break;
+ }
+ fallthrough; /* Report as finger if stylus not enabled */
+
+ case STMFTS_TOUCH_TYPE_FINGER:
+ case STMFTS_TOUCH_TYPE_GLOVE:
+ tool = MT_TOOL_FINGER;
+ __set_bit(touch_id, &sdata->touch_id);
+ __clear_bit(touch_id, &sdata->stylus_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_PALM:
+ /* Palm touch - report but can be filtered by userspace */
+ tool = MT_TOOL_PALM;
+ __set_bit(touch_id, &sdata->touch_id);
+ __clear_bit(touch_id, &sdata->stylus_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_HOVER:
+ tool = MT_TOOL_FINGER;
+ touch_condition = false;
+ area = 0;
+ distance = 255;
+ __set_bit(touch_id, &sdata->touch_id);
+ __clear_bit(touch_id, &sdata->stylus_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_INVALID:
+ default:
+ dev_warn(&sdata->client->dev,
+ "invalid touch type %d for slot %d\n",
+ touch_type, touch_id);
+ return;
+ }
+
+ /* Boundary check - some devices report max value, adjust */
+ if (x >= sdata->prop.max_x)
+ x = sdata->prop.max_x - 1;
+ if (y >= sdata->prop.max_y)
+ y = sdata->prop.max_y - 1;
+
+ input_mt_slot(sdata->input, touch_id);
+ input_report_key(sdata->input, BTN_TOUCH, touch_condition);
+ input_mt_report_slot_state(sdata->input, tool, true);
+
+ input_report_abs(sdata->input, ABS_MT_POSITION_X, x);
+ input_report_abs(sdata->input, ABS_MT_POSITION_Y, y);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MAJOR, maj);
+ input_report_abs(sdata->input, ABS_MT_TOUCH_MINOR, min);
+ input_report_abs(sdata->input, ABS_MT_PRESSURE, area);
+ input_report_abs(sdata->input, ABS_MT_DISTANCE, distance);
+
+ input_sync(sdata->input);
+}
+
+static void stmfts5_report_contact_release(struct stmfts_data *sdata,
+ const u8 event[])
+{
+ /* FTM5 format: touch ID is in high 4 bits of event[1] */
+ u8 touch_id = (event[1] & STMFTS_MASK_TOUCH_ID) >> 4;
+ u8 touch_type = event[1] & STMFTS5_MASK_TOUCH_TYPE;
+ unsigned int tool = MT_TOOL_FINGER;
+
+ /* Determine tool type based on touch classification */
+ switch (touch_type) {
+ case STMFTS_TOUCH_TYPE_STYLUS:
+ if (sdata->stylus_enabled) {
+ tool = MT_TOOL_PEN;
+ __clear_bit(touch_id, &sdata->stylus_id);
+ } else {
+ __clear_bit(touch_id, &sdata->touch_id);
+ }
+ break;
+
+ case STMFTS_TOUCH_TYPE_PALM:
+ tool = MT_TOOL_PALM;
+ __clear_bit(touch_id, &sdata->touch_id);
+ break;
+
+ case STMFTS_TOUCH_TYPE_FINGER:
+ case STMFTS_TOUCH_TYPE_GLOVE:
+ case STMFTS_TOUCH_TYPE_HOVER:
+ default:
+ tool = MT_TOOL_FINGER;
+ __clear_bit(touch_id, &sdata->touch_id);
+ break;
+ }
+
+ input_mt_slot(sdata->input, touch_id);
+ input_report_abs(sdata->input, ABS_MT_PRESSURE, 0);
+ input_mt_report_slot_state(sdata->input, tool, false);
+
+ /* Report BTN_TOUCH only if no touches remain */
+ if (!sdata->touch_id && !sdata->stylus_id)
+ input_report_key(sdata->input, BTN_TOUCH, 0);
+
+ input_sync(sdata->input);
+}
+
static void stmfts_report_hover_event(struct stmfts_data *sdata,
const u8 event[])
{
@@ -251,7 +437,6 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
switch (event[0]) {
-
case STMFTS_EV_CONTROLLER_READY:
case STMFTS_EV_SLEEP_OUT_CONTROLLER_READY:
case STMFTS_EV_STATUS:
@@ -264,7 +449,6 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
}
switch (event[0] & STMFTS_MASK_EVENT_ID) {
-
case STMFTS_EV_MULTI_TOUCH_ENTER:
case STMFTS_EV_MULTI_TOUCH_MOTION:
stmfts_report_contact_event(sdata, event);
@@ -298,6 +482,45 @@ static void stmfts_parse_events(struct stmfts_data *sdata)
}
}
+static void stmfts5_parse_events(struct stmfts_data *sdata)
+{
+ for (int i = 0; i < STMFTS_STACK_DEPTH; i++) {
+ u8 *event = &sdata->data[i * STMFTS_EVENT_SIZE];
+
+ switch (event[0]) {
+ case STMFTS5_EV_CONTROLLER_READY:
+ complete(&sdata->cmd_done);
+ fallthrough;
+
+ case STMFTS_EV_NO_EVENT:
+ case STMFTS5_EV_STATUS_UPDATE:
+ case STMFTS5_EV_USER_REPORT:
+ case STMFTS5_EV_DEBUG:
+ return;
+
+ case STMFTS5_EV_MULTI_TOUCH_ENTER:
+ case STMFTS5_EV_MULTI_TOUCH_MOTION:
+ stmfts5_report_contact_event(sdata, event);
+ break;
+
+ case STMFTS5_EV_MULTI_TOUCH_LEAVE:
+ stmfts5_report_contact_release(sdata, event);
+ break;
+
+ case STMFTS5_EV_ERROR:
+ dev_warn(&sdata->client->dev,
+ "error code: 0x%x%x%x%x%x%x",
+ event[6], event[5], event[4],
+ event[3], event[2], event[1]);
+ break;
+
+ default:
+ dev_err(&sdata->client->dev,
+ "unknown FTS5 event %#02x\n", event[0]);
+ }
+ }
+}
+
static irqreturn_t stmfts_irq_handler(int irq, void *dev)
{
struct stmfts_data *sdata = dev;
@@ -306,11 +529,15 @@ static irqreturn_t stmfts_irq_handler(int irq, void *dev)
mutex_lock(&sdata->mutex);
err = stmfts_read_events(sdata);
- if (unlikely(err))
+ if (unlikely(err)) {
dev_err(&sdata->client->dev,
"failed to read events: %d\n", err);
- else
- stmfts_parse_events(sdata);
+ } else {
+ if (sdata->is_fts5)
+ stmfts5_parse_events(sdata);
+ else
+ stmfts_parse_events(sdata);
+ }
mutex_unlock(&sdata->mutex);
return IRQ_HANDLED;
@@ -333,6 +560,25 @@ static int stmfts_command(struct stmfts_data *sdata, const u8 cmd)
return 0;
}
+static int stmfts5_set_scan_mode(struct stmfts_data *sdata, const u8 val)
+{
+ int err;
+
+ u8 scan_mode_cmd[3] = { STMFTS5_SET_SCAN_MODE, 0x00, val };
+ struct i2c_msg msg = {
+ .addr = sdata->client->addr,
+ .len = sizeof(scan_mode_cmd),
+ .buf = scan_mode_cmd,
+ };
+
+ err = i2c_transfer(sdata->client->adapter, &msg, 1);
+ if (err != 1)
+ return err < 0 ? err : -EIO;
+
+ return 0;
+
+}
+
static int stmfts_input_open(struct input_dev *dev)
{
struct stmfts_data *sdata = input_get_drvdata(dev);
@@ -372,6 +618,28 @@ static int stmfts_input_open(struct input_dev *dev)
return 0;
}
+static int stmfts5_input_open(struct input_dev *dev)
+{
+ struct stmfts_data *sdata = input_get_drvdata(dev);
+ int err;
+
+ err = pm_runtime_resume_and_get(&sdata->client->dev);
+ if (err)
+ return err;
+
+ mutex_lock(&sdata->mutex);
+ sdata->running = true;
+ mutex_unlock(&sdata->mutex);
+
+ err = stmfts5_set_scan_mode(sdata, 0xff);
+ if (err) {
+ pm_runtime_put_sync(&sdata->client->dev);
+ return err;
+ }
+
+ return 0;
+}
+
static void stmfts_input_close(struct input_dev *dev)
{
struct stmfts_data *sdata = input_get_drvdata(dev);
@@ -406,6 +674,23 @@ static void stmfts_input_close(struct input_dev *dev)
pm_runtime_put_sync(&sdata->client->dev);
}
+static void stmfts5_input_close(struct input_dev *dev)
+{
+ struct stmfts_data *sdata = input_get_drvdata(dev);
+ int err;
+
+ err = stmfts5_set_scan_mode(sdata, 0x00);
+ if (err)
+ dev_warn(&sdata->client->dev,
+ "failed to disable touchscreen: %d\n", err);
+
+ mutex_lock(&sdata->mutex);
+ sdata->running = false;
+ mutex_unlock(&sdata->mutex);
+
+ pm_runtime_put_sync(&sdata->client->dev);
+}
+
static ssize_t stmfts_sysfs_chip_id(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -485,7 +770,7 @@ static ssize_t stmfts_sysfs_hover_enable_write(struct device *dev,
if (value && sdata->hover_enabled)
goto out;
- if (sdata->running)
+ if (sdata->running && !sdata->is_fts5)
err = i2c_smbus_write_byte(sdata->client,
value ? STMFTS_SS_HOVER_SENSE_ON :
STMFTS_SS_HOVER_SENSE_OFF);
@@ -615,6 +900,41 @@ static int stmfts_power_on(struct stmfts_data *sdata)
return err;
}
+static int stmfts5_power_on(struct stmfts_data *sdata)
+{
+ int err, ret;
+ u8 event[STMFTS_EVENT_SIZE];
+
+ err = regulator_bulk_enable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ if (err)
+ return err;
+
+ /* Power stabilization delay */
+ msleep(20);
+
+ if (sdata->reset_gpio)
+ stmfts_reset(sdata);
+
+ /* Verify I2C communication */
+ ret = i2c_smbus_read_i2c_block_data(sdata->client,
+ STMFTS_READ_ALL_EVENT,
+ sizeof(event), event);
+ if (ret < 0) {
+ err = ret;
+ goto power_off;
+ }
+
+ enable_irq(sdata->client->irq);
+
+ return 0;
+
+power_off:
+ regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
+ sdata->supplies);
+ return err;
+}
+
static void stmfts_power_off(void *data)
{
struct stmfts_data *sdata = data;
@@ -624,6 +944,11 @@ static void stmfts_power_off(void *data)
if (sdata->reset_gpio)
gpiod_set_value_cansleep(sdata->reset_gpio, 1);
+ if (sdata->is_fts5) {
+ i2c_smbus_write_byte(sdata->client, STMFTS_SLEEP_IN);
+ msleep(20);
+ }
+
regulator_bulk_disable(ARRAY_SIZE(stmfts_supplies),
sdata->supplies);
}
@@ -657,6 +982,7 @@ static int stmfts_probe(struct i2c_client *client)
struct device *dev = &client->dev;
int err;
struct stmfts_data *sdata;
+ const struct of_device_id *match;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C |
I2C_FUNC_SMBUS_BYTE_DATA |
@@ -673,6 +999,12 @@ static int stmfts_probe(struct i2c_client *client)
mutex_init(&sdata->mutex);
init_completion(&sdata->cmd_done);
+ match = of_match_device(dev->driver->of_match_table, dev);
+ if (match && of_device_is_compatible(dev->of_node, "st,stmfts5"))
+ sdata->is_fts5 = true;
+ else
+ sdata->is_fts5 = false;
+
err = devm_regulator_bulk_get_const(dev,
ARRAY_SIZE(stmfts_supplies),
stmfts_supplies,
@@ -686,34 +1018,90 @@ static int stmfts_probe(struct i2c_client *client)
return dev_err_probe(dev, PTR_ERR(sdata->reset_gpio),
"Failed to get GPIO 'reset'\n");
+ if (sdata->is_fts5) {
+ sdata->irq_gpio = devm_gpiod_get_optional(dev, "irq",
+ GPIOD_IN);
+ if (IS_ERR(sdata->irq_gpio))
+ return dev_err_probe(dev, PTR_ERR(sdata->irq_gpio),
+ "Failed to get GPIO 'irq'\n");
+
+ sdata->switch_gpio = devm_gpiod_get_optional(&client->dev, "switch",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(sdata->switch_gpio))
+ return dev_err_probe(dev, PTR_ERR(sdata->switch_gpio),
+ "Failed to get GPIO 'switch'\n");
+
+ }
+
sdata->input = devm_input_allocate_device(dev);
if (!sdata->input)
return -ENOMEM;
sdata->input->name = STMFTS_DEV_NAME;
sdata->input->id.bustype = BUS_I2C;
- sdata->input->open = stmfts_input_open;
- sdata->input->close = stmfts_input_close;
+ if (sdata->is_fts5) {
+ sdata->input->open = stmfts5_input_open;
+ sdata->input->close = stmfts5_input_close;
+ } else {
+ sdata->input->open = stmfts_input_open;
+ sdata->input->close = stmfts_input_close;
+ }
+
+ /* FTS5-specific input properties */
+ if (sdata->is_fts5) {
+ /* Mark as direct input device for calibration support */
+ __set_bit(INPUT_PROP_DIRECT, sdata->input->propbit);
+
+ /* Set up basic touch capabilities */
+ input_set_capability(sdata->input, EV_KEY, BTN_TOUCH);
+ }
input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_X);
input_set_capability(sdata->input, EV_ABS, ABS_MT_POSITION_Y);
touchscreen_parse_properties(sdata->input, true, &sdata->prop);
+ /* Set resolution for accurate calibration (FTS5) */
+ if (sdata->is_fts5 && !input_abs_get_res(sdata->input, ABS_MT_POSITION_X)) {
+ input_abs_set_res(sdata->input, ABS_MT_POSITION_X, 10);
+ input_abs_set_res(sdata->input, ABS_MT_POSITION_Y, 10);
+ }
+
+ /* Enhanced MT parameters */
input_set_abs_params(sdata->input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
input_set_abs_params(sdata->input, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
- input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
input_set_abs_params(sdata->input, ABS_MT_PRESSURE, 0, 255, 0, 0);
- input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
+
+ if (sdata->is_fts5) {
+ input_set_abs_params(sdata->input, ABS_MT_DISTANCE, 0, 255, 0, 0);
+
+ /* Enable stylus support if requested */
+ sdata->stylus_enabled = device_property_read_bool(dev,
+ "stylus-enabled");
+ } else {
+ input_set_abs_params(sdata->input, ABS_MT_ORIENTATION, 0, 255, 0, 0);
+ input_set_abs_params(sdata->input, ABS_DISTANCE, 0, 255, 0, 0);
+ }
sdata->use_key = device_property_read_bool(dev,
"touch-key-connected");
- if (sdata->use_key) {
+ if (sdata->use_key && !sdata->is_fts5) {
input_set_capability(sdata->input, EV_KEY, KEY_MENU);
input_set_capability(sdata->input, EV_KEY, KEY_BACK);
}
- err = input_mt_init_slots(sdata->input,
- STMFTS_MAX_FINGERS, INPUT_MT_DIRECT);
+ /* Initialize touch tracking bitmaps (FTS5) */
+ if (sdata->is_fts5) {
+ sdata->touch_id = 0;
+ sdata->stylus_id = 0;
+
+ /* Initialize MT slots with support for pen tool type */
+ err = input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
+ INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
+ } else {
+ err = input_mt_init_slots(sdata->input, STMFTS_MAX_FINGERS,
+ INPUT_MT_DIRECT);
+ }
+
if (err)
return err;
@@ -733,9 +1121,14 @@ static int stmfts_probe(struct i2c_client *client)
if (err)
return err;
- dev_dbg(dev, "initializing ST-Microelectronics FTS...\n");
+ dev_dbg(dev, "initializing ST-Microelectronics FTS%s...\n",
+ sdata->is_fts5 ? "5" : "");
+
- err = stmfts_power_on(sdata);
+ if (sdata->is_fts5)
+ err = stmfts5_power_on(sdata);
+ else
+ err = stmfts_power_on(sdata);
if (err)
return err;
@@ -747,7 +1140,7 @@ static int stmfts_probe(struct i2c_client *client)
if (err)
return err;
- if (sdata->use_key) {
+ if (sdata->use_key && !sdata->is_fts5) {
err = stmfts_enable_led(sdata);
if (err) {
/*
@@ -791,8 +1184,47 @@ static int stmfts_runtime_resume(struct device *dev)
int ret;
ret = i2c_smbus_write_byte(client, STMFTS_SLEEP_OUT);
- if (ret)
+ if (ret) {
dev_err(dev, "failed to resume device: %d\n", ret);
+ return ret;
+ }
+
+ if (sdata->is_fts5) {
+ msleep(20);
+
+ /* Perform capacitance tuning after wakeup */
+ ret = i2c_smbus_write_byte(client, STMFTS_MS_CX_TUNING);
+ if (ret)
+ dev_warn(dev, "MS_CX_TUNING failed: %d\n", ret);
+ msleep(20);
+
+ ret = i2c_smbus_write_byte(client, STMFTS_SS_CX_TUNING);
+ if (ret)
+ dev_warn(dev, "SS_CX_TUNING failed: %d\n", ret);
+ msleep(20);
+
+ /* Force calibration */
+ ret = i2c_smbus_write_byte(client, STMFTS_FULL_FORCE_CALIBRATION);
+ if (ret)
+ dev_warn(dev, "FORCE_CALIBRATION failed: %d\n", ret);
+ msleep(50);
+
+ /* Enable controller interrupts */
+ u8 int_enable_cmd[4] = {0xB6, 0x00, 0x2C, 0x01};
+ struct i2c_msg msg = {
+ .addr = client->addr,
+ .len = 4,
+ .buf = int_enable_cmd,
+ };
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret != 1)
+ return ret < 0 ? ret : -EIO;
+
+ msleep(20);
+
+ return 0;
+ }
return ret;
}
@@ -810,7 +1242,10 @@ static int stmfts_resume(struct device *dev)
{
struct stmfts_data *sdata = dev_get_drvdata(dev);
- return stmfts_power_on(sdata);
+ if (sdata->is_fts5)
+ return stmfts5_power_on(sdata);
+ else
+ return stmfts_power_on(sdata);
}
static const struct dev_pm_ops stmfts_pm_ops = {
@@ -821,6 +1256,7 @@ static const struct dev_pm_ops stmfts_pm_ops = {
#ifdef CONFIG_OF
static const struct of_device_id stmfts_of_match[] = {
{ .compatible = "st,stmfts", },
+ { .compatible = "st,stmfts5", },
{ },
};
MODULE_DEVICE_TABLE(of, stmfts_of_match);
@@ -848,5 +1284,7 @@ static struct i2c_driver stmfts_driver = {
module_i2c_driver(stmfts_driver);
MODULE_AUTHOR("Andi Shyti <andi.shyti@samsung.com>");
+MODULE_AUTHOR("David Heidelberg <david@ixit.cz>");
+MODULE_AUTHOR("Petr Hodina <petr.hodina@protonmail.com>");
MODULE_DESCRIPTION("STMicroelectronics FTS Touch Screen");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related
* [PATCH 10/10] arm64: dts: qcom: sdm845-google: Add STM FTS touchscreen support
From: David Heidelberg via B4 Relay @ 2026-03-01 17:51 UTC (permalink / raw)
To: Dmitry Torokhov, Maxime Coquelin, Alexandre Torgue, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Henrik Rydberg,
Bjorn Andersson, Konrad Dybcio
Cc: Petr Hodina, linux-input, linux-stm32, linux-arm-kernel,
linux-kernel, Krzysztof Kozlowski, devicetree, linux-arm-msm,
phone-devel, David Heidelberg
In-Reply-To: <20260301-stmfts5-v1-0-22c458b9ac68@ixit.cz>
From: Petr Hodina <petr.hodina@protonmail.com>
Basic touchscreen connected to second i2c bus.
Signed-off-by: Petr Hodina <petr.hodina@protonmail.com>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts b/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
index fa89be500fb85..2501104b06e1b 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
+++ b/arch/arm64/boot/dts/qcom/sdm845-google-blueline.dts
@@ -26,7 +26,26 @@ &i2c2 {
status = "okay";
- /* ST,FTS @ 49 */
+ touchscreen@49 {
+ compatible = "st,stmfts5";
+ reg = <0x49>;
+
+ pinctrl-0 = <&touchscreen_pins &touchscreen_reset>;
+ pinctrl-names = "default";
+
+ interrupt-parent = <&tlmm>;
+ interrupts = <125 IRQ_TYPE_LEVEL_LOW>;
+
+ irq-gpios = <&tlmm 125 GPIO_ACTIVE_HIGH>;
+ switch-gpios = <&tlmm 136 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&tlmm 99 GPIO_ACTIVE_LOW>;
+
+ avdd-supply = <&vreg_l14a_1p8>;
+ vdd-supply = <&vreg_l19a_3p3>;
+
+ touchscreen-size-x = <1079>;
+ touchscreen-size-y = <2159>;
+ };
};
&mdss_dsi0 {
--
2.51.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