* Re: [regression] synaptics-rmi4: Failed to read the Control Register Descriptor: EIO
From: David Heidelberg @ 2026-06-27 10:06 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, Linux Next Mailing List
In-Reply-To: <aj7saJXgFX--gP_8@google.com>
On 26/06/2026 23:18, Dmitry Torokhov wrote:
> Hi David,
>
> On Fri, Jun 26, 2026 at 11:09:54PM +0200, David Heidelberg wrote:
>> With next-20260626 without any additional patches, the synaptics fails on
>> OnePlus 6T with:
>>
>> ```
>> [ 16.620292] rmi4_i2c 12-0020: registering I2C-connected sensor
>> [ 16.857617] rmi4_f01 rmi4-00.fn01: found RMI device, manufacturer:
>> Synaptics, product: S3706B, fw id: 2852315
>> [ 16.867324] rmi4_f12 rmi4-00.fn12: Failed to read the Control Register
>> Descriptor: -5
>> [ 16.867338] rmi4_f12 rmi4-00.fn12: probe with driver rmi4_f12 failed with
>> error -5
>> ```
>
> Yes, at least a98518e72439 "Input: rmi4 - fix register descriptor address
> calculation" is wrong and needs to be reverted.
That did it for me, now the TS works again.
Thank you
David
^ permalink raw reply
* [PATCH] Input: rmi4 - Move sensor initialization into own function for F12
From: David Heidelberg via B4 Relay @ 2026-06-27 14:56 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input, linux-kernel, David Heidelberg
From: David Heidelberg <david@ixit.cz>
Makes rmi_f12_probe function a bit cleaner and more structured.
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/input/rmi4/rmi_f12.c | 58 ++++++++++++++++++++++++++------------------
1 file changed, 35 insertions(+), 23 deletions(-)
diff --git a/drivers/input/rmi4/rmi_f12.c b/drivers/input/rmi4/rmi_f12.c
index 88c28089de993..333477bf0663e 100644
--- a/drivers/input/rmi4/rmi_f12.c
+++ b/drivers/input/rmi4/rmi_f12.c
@@ -364,29 +364,60 @@ static int rmi_f12_config(struct rmi_function *fn)
ret = rmi_f12_write_control_regs(fn);
if (ret)
dev_warn(&fn->dev,
"Failed to write F12 control registers: %d\n", ret);
return 0;
}
+static int rmi_f12_sensor_init(struct rmi_function *fn, struct f12_data *f12)
+{
+ struct rmi_2d_sensor *sensor = &f12->sensor;
+ size_t pkt_size;
+
+ sensor->fn = fn;
+ f12->data_addr = fn->fd.data_base_addr;
+ pkt_size = rmi_register_desc_calc_size(&f12->data_reg_desc);
+ if (pkt_size > SZ_1M) {
+ dev_err(&fn->dev, "Invalid data packet size: %zu\n", pkt_size);
+ return -EINVAL;
+ }
+ sensor->pkt_size = pkt_size;
+
+ sensor->axis_align = f12->sensor_pdata.axis_align;
+
+ sensor->x_mm = f12->sensor_pdata.x_mm;
+ sensor->y_mm = f12->sensor_pdata.y_mm;
+ sensor->dribble = f12->sensor_pdata.dribble;
+
+ if (sensor->sensor_type == rmi_sensor_default)
+ sensor->sensor_type = f12->sensor_pdata.sensor_type;
+
+ rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: data packet size: %u\n", __func__,
+ sensor->pkt_size);
+ sensor->data_pkt = devm_kmalloc(&fn->dev, sensor->pkt_size, GFP_KERNEL);
+ if (!sensor->data_pkt)
+ return -ENOMEM;
+
+ return 0;
+}
+
static int rmi_f12_probe(struct rmi_function *fn)
{
struct f12_data *f12;
int ret;
struct rmi_device *rmi_dev = fn->rmi_dev;
char buf;
u16 query_addr = fn->fd.query_base_addr;
const struct rmi_register_desc_item *item;
struct rmi_2d_sensor *sensor;
struct rmi_device_platform_data *pdata = rmi_get_platform_data(rmi_dev);
struct rmi_driver_data *drvdata = dev_get_drvdata(&rmi_dev->dev);
size_t data_offset = 0;
- size_t pkt_size;
int irq_mask_size;
int i;
rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s\n", __func__);
irq_mask_size = BITS_TO_LONGS(drvdata->irq_count);
ret = rmi_read(fn->rmi_dev, query_addr, &buf);
@@ -421,39 +452,20 @@ static int rmi_f12_probe(struct rmi_function *fn)
f12->sensor_pdata = pdata->sensor_pdata;
}
ret = rmi_f12_read_register_descs(fn, f12, query_addr);
if (ret)
return ret;
sensor = &f12->sensor;
- sensor->fn = fn;
- f12->data_addr = fn->fd.data_base_addr;
- pkt_size = rmi_register_desc_calc_size(&f12->data_reg_desc);
- if (pkt_size > SZ_1M) {
- dev_err(&fn->dev, "Invalid data packet size: %zu\n", pkt_size);
- return -EINVAL;
- }
- sensor->pkt_size = pkt_size;
-
- sensor->axis_align = f12->sensor_pdata.axis_align;
- sensor->x_mm = f12->sensor_pdata.x_mm;
- sensor->y_mm = f12->sensor_pdata.y_mm;
- sensor->dribble = f12->sensor_pdata.dribble;
-
- if (sensor->sensor_type == rmi_sensor_default)
- sensor->sensor_type = f12->sensor_pdata.sensor_type;
-
- rmi_dbg(RMI_DEBUG_FN, &fn->dev, "%s: data packet size: %u\n", __func__,
- sensor->pkt_size);
- sensor->data_pkt = devm_kmalloc(&fn->dev, sensor->pkt_size, GFP_KERNEL);
- if (!sensor->data_pkt)
- return -ENOMEM;
+ ret = rmi_f12_sensor_init(fn, f12);
+ if (ret)
+ return ret;
dev_set_drvdata(&fn->dev, f12);
ret = rmi_f12_read_sensor_tuning(f12);
if (ret)
return ret;
/*
---
base-commit: 3d5670d672ae08b8c534b7beed6f57c8b44e7b43
change-id: 20260627-synaptics-rmi4-fnsplit-3698a620b73c
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply related
* [dtor-input:next] BUILD SUCCESS c8f174900926d3b58cd048ac33b4cbb3de419bfe
From: kernel test robot @ 2026-06-27 18:22 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git next
branch HEAD: c8f174900926d3b58cd048ac33b4cbb3de419bfe Input: sur40 - fix MAX_CONTACTS value based on PixelSense specification
elapsed time: 730m
configs tested: 213
configs skipped: 11
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-16.1.0
alpha allyesconfig gcc-16.1.0
alpha defconfig gcc-16.1.0
arc allmodconfig gcc-16.1.0
arc allnoconfig gcc-16.1.0
arc allyesconfig gcc-16.1.0
arc defconfig gcc-16.1.0
arc randconfig-001-20260627 gcc-8.5.0
arc randconfig-002-20260627 gcc-8.5.0
arm allnoconfig clang-23
arm allyesconfig gcc-16.1.0
arm defconfig clang-23
arm pxa910_defconfig gcc-16.1.0
arm randconfig-001-20260627 gcc-16.1.0
arm randconfig-002-20260627 clang-23
arm randconfig-003-20260627 gcc-16.1.0
arm randconfig-004-20260627 clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-16.1.0
arm64 defconfig gcc-16.1.0
arm64 randconfig-001 gcc-14.3.0
arm64 randconfig-001-20260627 gcc-10.5.0
arm64 randconfig-002 gcc-8.5.0
arm64 randconfig-002-20260627 clang-23
arm64 randconfig-003 clang-20
arm64 randconfig-003-20260627 gcc-10.5.0
arm64 randconfig-004 clang-23
arm64 randconfig-004-20260627 gcc-16.1.0
csky allmodconfig gcc-16.1.0
csky allnoconfig gcc-16.1.0
csky defconfig gcc-16.1.0
csky randconfig-001 gcc-13.4.0
csky randconfig-001-20260627 gcc-16.1.0
csky randconfig-002 gcc-14.3.0
csky randconfig-002-20260627 gcc-14.3.0
hexagon allmodconfig clang-23
hexagon allnoconfig clang-23
hexagon defconfig clang-23
hexagon randconfig-001 clang-23
hexagon randconfig-001-20260627 clang-23
hexagon randconfig-002 clang-23
hexagon randconfig-002-20260627 clang-18
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260627 clang-22
i386 buildonly-randconfig-002-20260627 clang-22
i386 buildonly-randconfig-003-20260627 clang-22
i386 buildonly-randconfig-004-20260627 clang-22
i386 buildonly-randconfig-005-20260627 gcc-13
i386 buildonly-randconfig-006-20260627 clang-22
i386 defconfig clang-22
i386 randconfig-001 clang-22
i386 randconfig-001-20260627 clang-22
i386 randconfig-002 gcc-14
i386 randconfig-002-20260627 clang-22
i386 randconfig-003 gcc-14
i386 randconfig-003-20260627 gcc-13
i386 randconfig-004 clang-22
i386 randconfig-004-20260627 clang-22
i386 randconfig-005 gcc-14
i386 randconfig-005-20260627 clang-22
i386 randconfig-006 gcc-14
i386 randconfig-006-20260627 gcc-14
i386 randconfig-007 gcc-14
i386 randconfig-007-20260627 clang-22
i386 randconfig-011-20260627 clang-22
i386 randconfig-012-20260627 clang-22
i386 randconfig-013-20260627 gcc-14
i386 randconfig-014-20260627 clang-22
i386 randconfig-015-20260627 gcc-14
i386 randconfig-016-20260627 clang-22
i386 randconfig-017-20260627 gcc-14
loongarch allmodconfig clang-19
loongarch allnoconfig clang-20
loongarch defconfig clang-23
loongarch randconfig-001 clang-23
loongarch randconfig-001-20260627 clang-23
loongarch randconfig-002 gcc-16.1.0
loongarch randconfig-002-20260627 gcc-16.1.0
m68k allmodconfig gcc-16.1.0
m68k allnoconfig gcc-16.1.0
m68k allyesconfig gcc-16.1.0
m68k defconfig gcc-16.1.0
microblaze allnoconfig gcc-16.1.0
microblaze allyesconfig gcc-16.1.0
microblaze defconfig gcc-16.1.0
mips allmodconfig gcc-16.1.0
mips allnoconfig gcc-16.1.0
mips allyesconfig gcc-16.1.0
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig gcc-11.5.0
nios2 defconfig gcc-11.5.0
nios2 randconfig-001 gcc-11.5.0
nios2 randconfig-001-20260627 gcc-11.5.0
nios2 randconfig-002 gcc-8.5.0
nios2 randconfig-002-20260627 gcc-11.5.0
openrisc allmodconfig gcc-16.1.0
openrisc allnoconfig gcc-16.1.0
openrisc defconfig gcc-16.1.0
parisc allmodconfig gcc-16.1.0
parisc allnoconfig gcc-16.1.0
parisc allyesconfig gcc-16.1.0
parisc defconfig gcc-16.1.0
parisc randconfig-001 gcc-8.5.0
parisc randconfig-001-20260627 gcc-8.5.0
parisc randconfig-002 gcc-12.5.0
parisc randconfig-002-20260627 gcc-8.5.0
parisc64 defconfig gcc-16.1.0
powerpc allmodconfig gcc-16.1.0
powerpc allnoconfig gcc-16.1.0
powerpc randconfig-001 gcc-10.5.0
powerpc randconfig-001-20260627 clang-23
powerpc randconfig-002 gcc-8.5.0
powerpc randconfig-002-20260627 clang-17
powerpc64 randconfig-001 clang-16
powerpc64 randconfig-001-20260627 clang-20
powerpc64 randconfig-002 clang-23
powerpc64 randconfig-002-20260627 clang-23
riscv allmodconfig clang-23
riscv allnoconfig gcc-16.1.0
riscv allyesconfig clang-23
riscv defconfig clang-23
riscv randconfig-001-20260627 clang-22
riscv randconfig-002-20260627 clang-23
s390 allmodconfig clang-23
s390 allnoconfig clang-23
s390 allyesconfig gcc-16.1.0
s390 defconfig clang-18
s390 randconfig-001-20260627 clang-23
s390 randconfig-002-20260627 clang-23
sh allmodconfig gcc-16.1.0
sh allnoconfig gcc-16.1.0
sh allyesconfig gcc-16.1.0
sh defconfig gcc-16.1.0
sh randconfig-001-20260627 gcc-16.1.0
sh randconfig-002-20260627 gcc-10.5.0
sh se7724_defconfig gcc-16.1.0
sparc allnoconfig gcc-16.1.0
sparc defconfig gcc-16.1.0
sparc randconfig-001 gcc-16.1.0
sparc randconfig-001-20260627 gcc-13.4.0
sparc randconfig-002 gcc-11.5.0
sparc randconfig-002-20260627 gcc-8.5.0
sparc64 allmodconfig clang-20
sparc64 defconfig clang-23
sparc64 randconfig-001 gcc-12.5.0
sparc64 randconfig-001-20260627 gcc-13.4.0
sparc64 randconfig-002 clang-20
sparc64 randconfig-002-20260627 gcc-15.2.0
um allmodconfig clang-23
um allnoconfig clang-16
um allyesconfig gcc-14
um defconfig clang-23
um i386_defconfig gcc-14
um randconfig-001 clang-23
um randconfig-001-20260627 clang-22
um randconfig-002 clang-23
um randconfig-002-20260627 clang-23
um x86_64_defconfig clang-23
x86_64 allmodconfig clang-22
x86_64 allnoconfig clang-22
x86_64 allyesconfig clang-22
x86_64 buildonly-randconfig-001 gcc-12
x86_64 buildonly-randconfig-001-20260627 gcc-13
x86_64 buildonly-randconfig-002 clang-22
x86_64 buildonly-randconfig-002-20260627 clang-22
x86_64 buildonly-randconfig-003 gcc-14
x86_64 buildonly-randconfig-003-20260627 clang-22
x86_64 buildonly-randconfig-004 gcc-14
x86_64 buildonly-randconfig-004-20260627 gcc-14
x86_64 buildonly-randconfig-005 gcc-14
x86_64 buildonly-randconfig-005-20260627 clang-22
x86_64 buildonly-randconfig-006 clang-22
x86_64 buildonly-randconfig-006-20260627 clang-22
x86_64 defconfig gcc-14
x86_64 randconfig-001 gcc-14
x86_64 randconfig-001-20260627 clang-22
x86_64 randconfig-002 gcc-14
x86_64 randconfig-002-20260627 gcc-12
x86_64 randconfig-003 clang-22
x86_64 randconfig-003-20260627 clang-22
x86_64 randconfig-004 clang-22
x86_64 randconfig-004-20260627 gcc-14
x86_64 randconfig-005 gcc-14
x86_64 randconfig-005-20260627 gcc-14
x86_64 randconfig-006 clang-22
x86_64 randconfig-006-20260627 clang-22
x86_64 randconfig-011 gcc-14
x86_64 randconfig-011-20260627 clang-22
x86_64 randconfig-012 gcc-14
x86_64 randconfig-012-20260627 clang-22
x86_64 randconfig-013 clang-22
x86_64 randconfig-013-20260627 gcc-14
x86_64 randconfig-014 gcc-14
x86_64 randconfig-014-20260627 gcc-14
x86_64 randconfig-015 gcc-14
x86_64 randconfig-015-20260627 gcc-14
x86_64 randconfig-016 clang-22
x86_64 randconfig-016-20260627 gcc-14
x86_64 randconfig-071-20260627 clang-22
x86_64 randconfig-072-20260627 clang-22
x86_64 randconfig-073-20260627 clang-22
x86_64 randconfig-074-20260627 gcc-14
x86_64 randconfig-075-20260627 gcc-14
x86_64 randconfig-076-20260627 clang-22
x86_64 rhel-9.4-rust clang-22
xtensa allnoconfig gcc-16.1.0
xtensa allyesconfig gcc-16.1.0
xtensa randconfig-001 gcc-8.5.0
xtensa randconfig-001-20260627 gcc-8.5.0
xtensa randconfig-002 gcc-13.4.0
xtensa randconfig-002-20260627 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [dtor-input:for-linus] BUILD SUCCESS d86d4f8cbb5a55a3b9b86f7b5ab8c4cdda600a3f
From: kernel test robot @ 2026-06-27 18:28 UTC (permalink / raw)
To: Dmitry Torokhov; +Cc: linux-input
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git for-linus
branch HEAD: d86d4f8cbb5a55a3b9b86f7b5ab8c4cdda600a3f Input: gscps2 - advance receive buffer write index
elapsed time: 737m
configs tested: 166
configs skipped: 12
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-16.1.0
alpha allyesconfig gcc-16.1.0
alpha defconfig gcc-16.1.0
arc allmodconfig gcc-16.1.0
arc allnoconfig gcc-16.1.0
arc allyesconfig gcc-16.1.0
arc defconfig gcc-16.1.0
arc randconfig-001-20260627 gcc-8.5.0
arc randconfig-002-20260627 gcc-8.5.0
arm allnoconfig clang-23
arm allyesconfig gcc-16.1.0
arm defconfig clang-23
arm randconfig-001-20260627 gcc-16.1.0
arm randconfig-002-20260627 clang-23
arm randconfig-003-20260627 gcc-16.1.0
arm randconfig-004-20260627 clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-16.1.0
arm64 defconfig gcc-16.1.0
arm64 randconfig-001-20260627 gcc-10.5.0
arm64 randconfig-002-20260627 clang-23
arm64 randconfig-003-20260627 gcc-10.5.0
arm64 randconfig-004-20260627 gcc-16.1.0
csky allmodconfig gcc-16.1.0
csky allnoconfig gcc-16.1.0
csky defconfig gcc-16.1.0
csky randconfig-001-20260627 gcc-16.1.0
csky randconfig-002-20260627 gcc-14.3.0
hexagon allmodconfig clang-23
hexagon allnoconfig clang-23
hexagon defconfig clang-23
hexagon randconfig-001-20260627 clang-23
hexagon randconfig-002-20260627 clang-18
i386 allmodconfig gcc-14
i386 allnoconfig gcc-14
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001-20260627 clang-22
i386 buildonly-randconfig-002-20260627 clang-22
i386 buildonly-randconfig-003-20260627 clang-22
i386 buildonly-randconfig-004-20260627 clang-22
i386 buildonly-randconfig-005-20260627 gcc-13
i386 buildonly-randconfig-006-20260627 clang-22
i386 defconfig clang-22
i386 randconfig-001-20260627 clang-22
i386 randconfig-002-20260627 clang-22
i386 randconfig-003-20260627 gcc-13
i386 randconfig-004-20260627 clang-22
i386 randconfig-005-20260627 clang-22
i386 randconfig-006-20260627 gcc-14
i386 randconfig-007-20260627 clang-22
i386 randconfig-011-20260627 clang-22
i386 randconfig-012-20260627 clang-22
i386 randconfig-013-20260627 gcc-14
i386 randconfig-014-20260627 clang-22
i386 randconfig-015-20260627 gcc-14
i386 randconfig-016-20260627 clang-22
i386 randconfig-017-20260627 gcc-14
loongarch allmodconfig clang-19
loongarch allnoconfig clang-20
loongarch defconfig clang-23
loongarch randconfig-001-20260627 clang-23
loongarch randconfig-002-20260627 gcc-16.1.0
m68k allmodconfig gcc-16.1.0
m68k allnoconfig gcc-16.1.0
m68k allyesconfig gcc-16.1.0
m68k defconfig gcc-16.1.0
microblaze allnoconfig gcc-16.1.0
microblaze allyesconfig gcc-16.1.0
microblaze defconfig gcc-16.1.0
mips allmodconfig gcc-16.1.0
mips allnoconfig gcc-16.1.0
mips allyesconfig gcc-16.1.0
nios2 allmodconfig gcc-11.5.0
nios2 allnoconfig gcc-11.5.0
nios2 defconfig gcc-11.5.0
nios2 randconfig-001-20260627 gcc-11.5.0
nios2 randconfig-002-20260627 gcc-11.5.0
openrisc allmodconfig gcc-16.1.0
openrisc allnoconfig gcc-16.1.0
openrisc defconfig gcc-16.1.0
parisc allmodconfig gcc-16.1.0
parisc allnoconfig gcc-16.1.0
parisc allyesconfig gcc-16.1.0
parisc defconfig gcc-16.1.0
parisc randconfig-001-20260627 gcc-8.5.0
parisc randconfig-002-20260627 gcc-8.5.0
parisc64 defconfig gcc-16.1.0
powerpc allmodconfig gcc-16.1.0
powerpc allnoconfig gcc-16.1.0
powerpc randconfig-001-20260627 clang-23
powerpc randconfig-002-20260627 clang-17
powerpc stx_gp3_defconfig gcc-16.1.0
powerpc64 randconfig-001-20260627 clang-20
powerpc64 randconfig-002-20260627 clang-23
riscv allnoconfig gcc-16.1.0
riscv allyesconfig clang-23
riscv defconfig clang-23
riscv randconfig-001 gcc-13.4.0
riscv randconfig-001-20260627 clang-22
riscv randconfig-002 clang-23
riscv randconfig-002-20260627 clang-23
s390 allmodconfig clang-23
s390 allnoconfig clang-23
s390 allyesconfig gcc-16.1.0
s390 defconfig clang-18
s390 randconfig-001 gcc-15.2.0
s390 randconfig-001-20260627 clang-23
s390 randconfig-002 clang-18
s390 randconfig-002-20260627 clang-23
sh allmodconfig gcc-16.1.0
sh allnoconfig gcc-16.1.0
sh allyesconfig gcc-16.1.0
sh defconfig gcc-16.1.0
sh randconfig-001 gcc-14.3.0
sh randconfig-001-20260627 gcc-16.1.0
sh randconfig-002 gcc-9.5.0
sh randconfig-002-20260627 gcc-10.5.0
sparc allnoconfig gcc-16.1.0
sparc defconfig gcc-16.1.0
sparc randconfig-001 gcc-16.1.0
sparc randconfig-001-20260627 gcc-13.4.0
sparc randconfig-002 gcc-11.5.0
sparc randconfig-002-20260627 gcc-8.5.0
sparc64 allmodconfig clang-20
sparc64 defconfig clang-23
sparc64 randconfig-001 gcc-12.5.0
sparc64 randconfig-002 clang-20
sparc64 randconfig-002-20260627 gcc-15.2.0
um allmodconfig clang-23
um allnoconfig clang-16
um allyesconfig gcc-14
um defconfig clang-23
um i386_defconfig gcc-14
um randconfig-002 clang-23
um x86_64_defconfig clang-23
x86_64 allmodconfig clang-22
x86_64 allnoconfig clang-22
x86_64 allyesconfig clang-22
x86_64 buildonly-randconfig-001-20260627 gcc-13
x86_64 buildonly-randconfig-002-20260627 clang-22
x86_64 buildonly-randconfig-003-20260627 clang-22
x86_64 buildonly-randconfig-004-20260627 gcc-14
x86_64 buildonly-randconfig-005-20260627 clang-22
x86_64 buildonly-randconfig-006-20260627 clang-22
x86_64 defconfig gcc-14
x86_64 randconfig-001-20260627 clang-22
x86_64 randconfig-002-20260627 gcc-12
x86_64 randconfig-003-20260627 clang-22
x86_64 randconfig-004-20260627 gcc-14
x86_64 randconfig-005-20260627 gcc-14
x86_64 randconfig-006-20260627 clang-22
x86_64 randconfig-011-20260627 clang-22
x86_64 randconfig-012-20260627 clang-22
x86_64 randconfig-013-20260627 gcc-14
x86_64 randconfig-014-20260627 gcc-14
x86_64 randconfig-015-20260627 gcc-14
x86_64 randconfig-016-20260627 gcc-14
x86_64 randconfig-071-20260627 clang-22
x86_64 randconfig-072-20260627 clang-22
x86_64 randconfig-073-20260627 clang-22
x86_64 randconfig-074-20260627 gcc-14
x86_64 randconfig-075-20260627 gcc-14
x86_64 randconfig-076-20260627 clang-22
x86_64 rhel-9.4-rust clang-22
xtensa allnoconfig gcc-16.1.0
xtensa randconfig-001 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH v2] HID: hyperx-headset: Add support for HyperX headset devices
From: Sofia Schneider @ 2026-06-27 20:39 UTC (permalink / raw)
To: jikos, bentiss; +Cc: linux-input, linux-kernel, Sofia Schneider
Introduce a HID driver for HyperX Cloud III Wireless headsets,
supporting battery reporting and connection status.
Tested with a HyperX Cloud III Wireless only, for lack of
other testable devices.
Signed-off-by: Sofia Schneider <sofia@schn.dev>
---
v2:
- Call hid_hw_stop() before cancelling work queues in remove() to prevent UAF.
- Safe unregistration of power supply using the spinlock in remove() to prevent race conditions.
- Cancel battery_work in the hid_hw_start() error path in probe() to prevent UAF.
- Dynamically allocate power supply names to avoid sysfs naming collisions.
v1: https://lore.kernel.org/linux-input/20260622022800.D01D51F000E9@smtp.kernel.org/T/#t
MAINTAINERS | 6 +
drivers/hid/Kconfig | 11 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-hyperx-headset.c | 387 +++++++++++++++++++++++++++++++
drivers/hid/hid-ids.h | 1 +
5 files changed, 406 insertions(+)
create mode 100644 drivers/hid/hid-hyperx-headset.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d8252026bbd4..fa49655255f6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11419,6 +11419,12 @@ F: include/uapi/linux/hid*
F: samples/hid/
F: tools/testing/selftests/hid/
+HID HYPERX HEADSET DRIVER
+M: Sofia Schneider <sofia@schn.dev>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-hyperx-headset.c
+
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 f9bcaeb66385..e9f5f1f982c9 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1215,6 +1215,17 @@ config HID_HYPERV_MOUSE
help
Select this option to enable the Hyper-V mouse driver.
+config HID_HYPERX_HEADSET
+ tristate "HyperX headset devices"
+ depends on USB_HID
+ select POWER_SUPPLY
+ help
+ Support for HyperX headset devices.
+
+ Say Y here if you would like to enable support for HyperX headset devices.
+ To compile this driver as a module, choose M here: the module will be called
+ hid-hyperx-headset.
+
config HID_SMARTJOYPLUS
tristate "SmartJoy PLUS PS2/USB adapter support"
help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 23e6e3dd0c56..9f3fd2c21837 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
+obj-$(CONFIG_HID_HYPERX_HEADSET) += hid-hyperx-headset.o
obj-$(CONFIG_HID_ICADE) += hid-icade.o
obj-$(CONFIG_HID_ITE) += hid-ite.o
obj-$(CONFIG_HID_JABRA) += hid-jabra.o
diff --git a/drivers/hid/hid-hyperx-headset.c b/drivers/hid/hid-hyperx-headset.c
new file mode 100644
index 000000000000..b77931d4dfb0
--- /dev/null
+++ b/drivers/hid/hid-hyperx-headset.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID driver for HyperX headsets
+ *
+ * Supports HyperX Cloud III Wireless headsets.
+ *
+ * Copyright (c) 2026 Sofia Schneider
+ */
+
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#include "hid-ids.h"
+
+#define HYPERX_POLL_INTERVAL_MS (2 * 60 * 1000)
+
+#define HYPERX_REPORT_ID 0x66
+#define HYPERX_PACKET_SIZE 62
+
+#define HYPERX_CMD_GET_CONNECTED 0x82
+#define HYPERX_CMD_GET_BATTERY 0x89
+#define HYPERX_CMD_GET_CHARGING 0x8A
+
+#define HYPERX_RESP_CONNECTED 0x0B
+#define HYPERX_RESP_CHARGING 0x0C
+#define HYPERX_RESP_BATTERY 0x0D
+
+#define HYPERX_PREFIX "HP, Inc "
+#define HYPERX_PREFIX_LEN strlen(HYPERX_PREFIX)
+
+struct hyperx_headset_device {
+ struct hid_device *hdev;
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+
+ spinlock_t lock;
+ u8 battery_level;
+ bool is_charging;
+ bool is_connected;
+
+ struct delayed_work poll_work;
+ struct work_struct battery_work;
+};
+
+static const enum power_supply_property hyperx_headset_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int hyperx_headset_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct hyperx_headset_device *drvdata = power_supply_get_drvdata(psy);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = drvdata->is_connected ? 1 : 0;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = drvdata->battery_level;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!drvdata->is_connected)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else if (drvdata->is_charging)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (drvdata->battery_level == 100)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = drvdata->hdev->name;
+ while (!strncmp(val->strval, HYPERX_PREFIX, HYPERX_PREFIX_LEN))
+ val->strval += HYPERX_PREFIX_LEN;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "HyperX";
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+ return ret;
+}
+
+
+
+static int hyperx_headset_send_command(struct hyperx_headset_device *drvdata,
+ u8 command)
+{
+ struct hid_device *hdev = drvdata->hdev;
+ u8 *buf;
+ int ret;
+
+ buf = kzalloc(HYPERX_PACKET_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = HYPERX_REPORT_ID;
+ buf[1] = command;
+
+ ret = hid_hw_raw_request(hdev, HYPERX_REPORT_ID, buf,
+ HYPERX_PACKET_SIZE, HID_OUTPUT_REPORT,
+ HID_REQ_SET_REPORT);
+
+ if (ret < 0)
+ hid_err(hdev, "hw_raw_request failed (command 0x%02x)\n",
+ command);
+
+ kfree(buf);
+ return ret;
+}
+
+static void hyperx_headset_poll_work(struct work_struct *work)
+{
+ struct hyperx_headset_device *drvdata = container_of(
+ work, struct hyperx_headset_device, poll_work.work);
+
+ hyperx_headset_send_command(drvdata, HYPERX_CMD_GET_CONNECTED);
+ hyperx_headset_send_command(drvdata, HYPERX_CMD_GET_BATTERY);
+ hyperx_headset_send_command(drvdata, HYPERX_CMD_GET_CHARGING);
+
+ schedule_delayed_work(&drvdata->poll_work,
+ msecs_to_jiffies(HYPERX_POLL_INTERVAL_MS));
+}
+
+static void hyperx_headset_set_wireless_status(struct hid_device *hdev,
+ bool connected)
+{
+ struct usb_interface *intf;
+
+ if (!hid_is_usb(hdev))
+ return;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
+static void hyperx_headset_battery_work(struct work_struct *work)
+{
+ struct hyperx_headset_device *drvdata =
+ container_of(work, struct hyperx_headset_device, battery_work);
+ struct power_supply_config battery_cfg = { .drv_data = drvdata };
+ unsigned long flags;
+ bool connected;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ connected = drvdata->is_connected;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ hyperx_headset_set_wireless_status(drvdata->hdev, connected);
+
+ if (connected && !drvdata->battery) {
+ struct power_supply *ps;
+
+ ps = power_supply_register(&drvdata->hdev->dev,
+ &drvdata->battery_desc,
+ &battery_cfg);
+ if (IS_ERR(ps)) {
+ hid_err(drvdata->hdev,
+ "power_supply_register failed\n");
+ return;
+ }
+
+ power_supply_powers(ps, &drvdata->hdev->dev);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ drvdata->battery = ps;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+ } else if (!connected && drvdata->battery) {
+ struct power_supply *ps;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ ps = drvdata->battery;
+ drvdata->battery = NULL;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ power_supply_unregister(ps);
+ }
+}
+
+static void
+hyperx_headset_parse_battery_event(struct hyperx_headset_device *drvdata,
+ u8 *data)
+{
+ unsigned long flags;
+ u8 state1 = data[2];
+ u8 state2 = data[3];
+ u8 level = data[4];
+
+ // Battery event is invalid if both states are 0
+ if (state1 == 0 && state2 == 0)
+ return;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ if (drvdata->battery_level != level) {
+ drvdata->battery_level = level;
+
+ if (drvdata->battery)
+ power_supply_changed(drvdata->battery);
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+}
+
+static void
+hyperx_headset_parse_charging_event(struct hyperx_headset_device *drvdata,
+ u8 *data)
+{
+ unsigned long flags;
+ bool charging = (data[2] == 1);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ if (drvdata->is_charging != charging) {
+ drvdata->is_charging = charging;
+
+ if (drvdata->battery)
+ power_supply_changed(drvdata->battery);
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+}
+
+static void
+hyperx_headset_parse_connected_event(struct hyperx_headset_device *drvdata,
+ u8 *data)
+{
+ unsigned long flags;
+ bool state_changed = false;
+ bool connected = (data[2] == 1);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ if (drvdata->is_connected != connected) {
+ drvdata->is_connected = connected;
+ state_changed = true;
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ if (state_changed)
+ schedule_work(&drvdata->battery_work);
+}
+
+static int hyperx_headset_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+ struct hyperx_headset_device *drvdata;
+
+ drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (drvdata == NULL)
+ return -ENOMEM;
+ drvdata->hdev = hdev;
+ drvdata->is_connected = false;
+ drvdata->is_charging = false;
+ drvdata->battery_level = 100;
+ spin_lock_init(&drvdata->lock);
+ hid_set_drvdata(hdev, drvdata);
+
+ drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drvdata->battery_desc.properties = hyperx_headset_battery_props;
+ drvdata->battery_desc.num_properties = ARRAY_SIZE(hyperx_headset_battery_props);
+ drvdata->battery_desc.get_property = hyperx_headset_battery_get_property;
+ drvdata->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "hyperx_headset_battery_%s",
+ strlen(hdev->uniq) ?
+ hdev->uniq : dev_name(&hdev->dev));
+ if (!drvdata->battery_desc.name)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&drvdata->poll_work, hyperx_headset_poll_work);
+ INIT_WORK(&drvdata->battery_work, hyperx_headset_battery_work);
+
+ ret = hid_parse(hdev);
+ if (ret != 0) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret != 0) {
+ hid_err(hdev, "hw_start failed\n");
+ cancel_work_sync(&drvdata->battery_work);
+ return ret;
+ }
+
+ schedule_delayed_work(&drvdata->poll_work, 0);
+
+ return 0;
+}
+
+static int hyperx_headset_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data,
+ int size)
+{
+ struct hyperx_headset_device *drvdata = hid_get_drvdata(hdev);
+
+ if (size < 5 || data[0] != HYPERX_REPORT_ID)
+ return 0;
+
+ switch (data[1]) {
+ case HYPERX_CMD_GET_CONNECTED:
+ case HYPERX_RESP_CONNECTED:
+ hyperx_headset_parse_connected_event(drvdata, data);
+ break;
+
+ case HYPERX_CMD_GET_BATTERY:
+ case HYPERX_RESP_BATTERY:
+ hyperx_headset_parse_battery_event(drvdata, data);
+ break;
+
+ case HYPERX_CMD_GET_CHARGING:
+ case HYPERX_RESP_CHARGING:
+ hyperx_headset_parse_charging_event(drvdata, data);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void hyperx_headset_remove(struct hid_device *hdev)
+{
+ struct hyperx_headset_device *drvdata = hid_get_drvdata(hdev);
+ struct power_supply *ps = NULL;
+ unsigned long flags;
+
+ hid_hw_stop(hdev);
+
+ if (drvdata) {
+ cancel_delayed_work_sync(&drvdata->poll_work);
+ cancel_work_sync(&drvdata->battery_work);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ ps = drvdata->battery;
+ drvdata->battery = NULL;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ if (ps)
+ power_supply_unregister(ps);
+ }
+}
+
+
+static const struct hid_device_id hyperx_headset_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_HP,
+ USB_DEVICE_ID_HP_HYPERX_CLOUD_III_WIRELESS) },
+ {}
+};
+MODULE_DEVICE_TABLE(hid, hyperx_headset_devices);
+
+static struct hid_driver hyperx_headset_driver = {
+ .name = "hyperx-headset",
+ .id_table = hyperx_headset_devices,
+ .probe = hyperx_headset_probe,
+ .raw_event = hyperx_headset_raw_event,
+ .remove = hyperx_headset_remove,
+};
+module_hid_driver(hyperx_headset_driver);
+
+MODULE_AUTHOR("Sofia Schneider <sofia@schn.dev>");
+MODULE_DESCRIPTION("HID driver for HyperX headsets");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..aa2c3a71315b 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -696,6 +696,7 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941 0x0941
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a
+#define USB_DEVICE_ID_HP_HYPERX_CLOUD_III_WIRELESS 0x05b7
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2] HID: hyperx-headset: Add support for HyperX headset devices
From: sashiko-bot @ 2026-06-27 20:48 UTC (permalink / raw)
To: Sofia Schneider; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260627203948.1053589-1-sofia@schn.dev>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing power supply unregister on the `hid_hw_start` failure path causes a resource leak and Use-After-Free.
--
commit 100439ac6279809f99a9f4158c6fe4a272508223
Author: Sofia Schneider <sofia@schn.dev>
HID: hyperx-headset: Add support for HyperX headset devices
Introduce a HID driver for HyperX Cloud III Wireless headsets,
supporting battery reporting and connection status.
> diff --git a/drivers/hid/hid-hyperx-headset.c b/drivers/hid/hid-hyperx-headset.c
> new file mode 100644
> index 0000000000000..b77931d4dfb08
> --- /dev/null
> +++ b/drivers/hid/hid-hyperx-headset.c
[ ... ]
> +static int hyperx_headset_probe(struct hid_device *hdev,
> + const struct hid_device_id *id)
> +{
[ ... ]
> + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> + if (ret != 0) {
> + hid_err(hdev, "hw_start failed\n");
> + cancel_work_sync(&drvdata->battery_work);
[Severity: High]
Does this error path leak the power supply if it was already registered?
If the device sends a connected event early during hid_hw_start(),
battery_work may execute concurrently. It can register the power supply
and store it in drvdata->battery. If hid_hw_start() subsequently fails
(e.g. during hid_connect), this error path cancels the work but fails
to unregister drvdata->battery.
Because drvdata is managed by devm, it will be freed when probe returns
an error. This leaves the registered power supply with a dangling pointer
to drvdata, which can lead to a use-after-free if its sysfs attributes
are accessed.
Should this path check and unregister drvdata->battery before returning?
> + return ret;
> + }
> +
> + schedule_delayed_work(&drvdata->poll_work, 0);
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260627203948.1053589-1-sofia@schn.dev?part=1
^ permalink raw reply
* [PATCH v3] HID: hyperx-headset: Add support for HyperX headset devices
From: Sofia Schneider @ 2026-06-27 21:01 UTC (permalink / raw)
To: jikos, bentiss; +Cc: linux-input, linux-kernel, Sofia Schneider
Introduce a HID driver for HyperX Cloud III Wireless headsets,
supporting battery reporting and connection status.
Tested with a HyperX Cloud III Wireless only, for lack of
other testable devices.
Signed-off-by: Sofia Schneider <sofia@schn.dev>
---
v2 -> v3:
- Unregister power supply on hid_hw_start() failure in probe() to prevent UAF/resource leak.
v1 -> v2:
- Call hid_hw_stop() before cancelling work queues in remove() to prevent UAF.
- Safe unregistration of power supply using the spinlock in remove() to prevent race conditions.
- Cancel battery_work in the hid_hw_start() error path in probe() to prevent UAF.
- Dynamically allocate power supply names to avoid sysfs naming collisions.
v1: https://lore.kernel.org/linux-input/20260622022800.D01D51F000E9@smtp.kernel.org/T/
v2: https://lore.kernel.org/linux-input/20260627204840.2DD671F000E9@smtp.kernel.org/T/
MAINTAINERS | 6 +
drivers/hid/Kconfig | 11 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-hyperx-headset.c | 399 +++++++++++++++++++++++++++++++
drivers/hid/hid-ids.h | 1 +
5 files changed, 418 insertions(+)
create mode 100644 drivers/hid/hid-hyperx-headset.c
diff --git a/MAINTAINERS b/MAINTAINERS
index d8252026bbd4..fa49655255f6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11419,6 +11419,12 @@ F: include/uapi/linux/hid*
F: samples/hid/
F: tools/testing/selftests/hid/
+HID HYPERX HEADSET DRIVER
+M: Sofia Schneider <sofia@schn.dev>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-hyperx-headset.c
+
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 f9bcaeb66385..e9f5f1f982c9 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1215,6 +1215,17 @@ config HID_HYPERV_MOUSE
help
Select this option to enable the Hyper-V mouse driver.
+config HID_HYPERX_HEADSET
+ tristate "HyperX headset devices"
+ depends on USB_HID
+ select POWER_SUPPLY
+ help
+ Support for HyperX headset devices.
+
+ Say Y here if you would like to enable support for HyperX headset devices.
+ To compile this driver as a module, choose M here: the module will be called
+ hid-hyperx-headset.
+
config HID_SMARTJOYPLUS
tristate "SmartJoy PLUS PS2/USB adapter support"
help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 23e6e3dd0c56..9f3fd2c21837 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_HID_HOLTEK) += hid-holtek-kbd.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtek-mouse.o
obj-$(CONFIG_HID_HOLTEK) += hid-holtekff.o
obj-$(CONFIG_HID_HYPERV_MOUSE) += hid-hyperv.o
+obj-$(CONFIG_HID_HYPERX_HEADSET) += hid-hyperx-headset.o
obj-$(CONFIG_HID_ICADE) += hid-icade.o
obj-$(CONFIG_HID_ITE) += hid-ite.o
obj-$(CONFIG_HID_JABRA) += hid-jabra.o
diff --git a/drivers/hid/hid-hyperx-headset.c b/drivers/hid/hid-hyperx-headset.c
new file mode 100644
index 000000000000..1564432a225c
--- /dev/null
+++ b/drivers/hid/hid-hyperx-headset.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HID driver for HyperX headsets
+ *
+ * Supports HyperX Cloud III Wireless headsets.
+ *
+ * Copyright (c) 2026 Sofia Schneider
+ */
+
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#include "hid-ids.h"
+
+#define HYPERX_POLL_INTERVAL_MS (2 * 60 * 1000)
+
+#define HYPERX_REPORT_ID 0x66
+#define HYPERX_PACKET_SIZE 62
+
+#define HYPERX_CMD_GET_CONNECTED 0x82
+#define HYPERX_CMD_GET_BATTERY 0x89
+#define HYPERX_CMD_GET_CHARGING 0x8A
+
+#define HYPERX_RESP_CONNECTED 0x0B
+#define HYPERX_RESP_CHARGING 0x0C
+#define HYPERX_RESP_BATTERY 0x0D
+
+#define HYPERX_PREFIX "HP, Inc "
+#define HYPERX_PREFIX_LEN strlen(HYPERX_PREFIX)
+
+struct hyperx_headset_device {
+ struct hid_device *hdev;
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+
+ spinlock_t lock;
+ u8 battery_level;
+ bool is_charging;
+ bool is_connected;
+
+ struct delayed_work poll_work;
+ struct work_struct battery_work;
+};
+
+static const enum power_supply_property hyperx_headset_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_SCOPE, POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int hyperx_headset_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct hyperx_headset_device *drvdata = power_supply_get_drvdata(psy);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = drvdata->is_connected ? 1 : 0;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = drvdata->battery_level;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!drvdata->is_connected)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else if (drvdata->is_charging)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (drvdata->battery_level == 100)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = drvdata->hdev->name;
+ while (!strncmp(val->strval, HYPERX_PREFIX, HYPERX_PREFIX_LEN))
+ val->strval += HYPERX_PREFIX_LEN;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "HyperX";
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+ return ret;
+}
+
+
+
+static int hyperx_headset_send_command(struct hyperx_headset_device *drvdata,
+ u8 command)
+{
+ struct hid_device *hdev = drvdata->hdev;
+ u8 *buf;
+ int ret;
+
+ buf = kzalloc(HYPERX_PACKET_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = HYPERX_REPORT_ID;
+ buf[1] = command;
+
+ ret = hid_hw_raw_request(hdev, HYPERX_REPORT_ID, buf,
+ HYPERX_PACKET_SIZE, HID_OUTPUT_REPORT,
+ HID_REQ_SET_REPORT);
+
+ if (ret < 0)
+ hid_err(hdev, "hw_raw_request failed (command 0x%02x)\n",
+ command);
+
+ kfree(buf);
+ return ret;
+}
+
+static void hyperx_headset_poll_work(struct work_struct *work)
+{
+ struct hyperx_headset_device *drvdata = container_of(
+ work, struct hyperx_headset_device, poll_work.work);
+
+ hyperx_headset_send_command(drvdata, HYPERX_CMD_GET_CONNECTED);
+ hyperx_headset_send_command(drvdata, HYPERX_CMD_GET_BATTERY);
+ hyperx_headset_send_command(drvdata, HYPERX_CMD_GET_CHARGING);
+
+ schedule_delayed_work(&drvdata->poll_work,
+ msecs_to_jiffies(HYPERX_POLL_INTERVAL_MS));
+}
+
+static void hyperx_headset_set_wireless_status(struct hid_device *hdev,
+ bool connected)
+{
+ struct usb_interface *intf;
+
+ if (!hid_is_usb(hdev))
+ return;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ usb_set_wireless_status(intf, connected ?
+ USB_WIRELESS_STATUS_CONNECTED :
+ USB_WIRELESS_STATUS_DISCONNECTED);
+}
+
+static void hyperx_headset_battery_work(struct work_struct *work)
+{
+ struct hyperx_headset_device *drvdata =
+ container_of(work, struct hyperx_headset_device, battery_work);
+ struct power_supply_config battery_cfg = { .drv_data = drvdata };
+ unsigned long flags;
+ bool connected;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ connected = drvdata->is_connected;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ hyperx_headset_set_wireless_status(drvdata->hdev, connected);
+
+ if (connected && !drvdata->battery) {
+ struct power_supply *ps;
+
+ ps = power_supply_register(&drvdata->hdev->dev,
+ &drvdata->battery_desc,
+ &battery_cfg);
+ if (IS_ERR(ps)) {
+ hid_err(drvdata->hdev,
+ "power_supply_register failed\n");
+ return;
+ }
+
+ power_supply_powers(ps, &drvdata->hdev->dev);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ drvdata->battery = ps;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+ } else if (!connected && drvdata->battery) {
+ struct power_supply *ps;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ ps = drvdata->battery;
+ drvdata->battery = NULL;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ power_supply_unregister(ps);
+ }
+}
+
+static void
+hyperx_headset_parse_battery_event(struct hyperx_headset_device *drvdata,
+ u8 *data)
+{
+ unsigned long flags;
+ u8 state1 = data[2];
+ u8 state2 = data[3];
+ u8 level = data[4];
+
+ // Battery event is invalid if both states are 0
+ if (state1 == 0 && state2 == 0)
+ return;
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ if (drvdata->battery_level != level) {
+ drvdata->battery_level = level;
+
+ if (drvdata->battery)
+ power_supply_changed(drvdata->battery);
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+}
+
+static void
+hyperx_headset_parse_charging_event(struct hyperx_headset_device *drvdata,
+ u8 *data)
+{
+ unsigned long flags;
+ bool charging = (data[2] == 1);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ if (drvdata->is_charging != charging) {
+ drvdata->is_charging = charging;
+
+ if (drvdata->battery)
+ power_supply_changed(drvdata->battery);
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+}
+
+static void
+hyperx_headset_parse_connected_event(struct hyperx_headset_device *drvdata,
+ u8 *data)
+{
+ unsigned long flags;
+ bool state_changed = false;
+ bool connected = (data[2] == 1);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+
+ if (drvdata->is_connected != connected) {
+ drvdata->is_connected = connected;
+ state_changed = true;
+ }
+
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ if (state_changed)
+ schedule_work(&drvdata->battery_work);
+}
+
+static int hyperx_headset_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+ struct hyperx_headset_device *drvdata;
+
+ drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (drvdata == NULL)
+ return -ENOMEM;
+ drvdata->hdev = hdev;
+ drvdata->is_connected = false;
+ drvdata->is_charging = false;
+ drvdata->battery_level = 100;
+ spin_lock_init(&drvdata->lock);
+ hid_set_drvdata(hdev, drvdata);
+
+ drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ drvdata->battery_desc.properties = hyperx_headset_battery_props;
+ drvdata->battery_desc.num_properties = ARRAY_SIZE(hyperx_headset_battery_props);
+ drvdata->battery_desc.get_property = hyperx_headset_battery_get_property;
+ drvdata->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+ "hyperx_headset_battery_%s",
+ strlen(hdev->uniq) ?
+ hdev->uniq : dev_name(&hdev->dev));
+ if (!drvdata->battery_desc.name)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&drvdata->poll_work, hyperx_headset_poll_work);
+ INIT_WORK(&drvdata->battery_work, hyperx_headset_battery_work);
+
+ ret = hid_parse(hdev);
+ if (ret != 0) {
+ hid_err(hdev, "parse failed\n");
+ return ret;
+ }
+
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret != 0) {
+ struct power_supply *ps;
+ unsigned long flags;
+
+ hid_err(hdev, "hw_start failed\n");
+ cancel_work_sync(&drvdata->battery_work);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ ps = drvdata->battery;
+ drvdata->battery = NULL;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ if (ps)
+ power_supply_unregister(ps);
+
+ return ret;
+ }
+
+ schedule_delayed_work(&drvdata->poll_work, 0);
+
+ return 0;
+}
+
+static int hyperx_headset_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data,
+ int size)
+{
+ struct hyperx_headset_device *drvdata = hid_get_drvdata(hdev);
+
+ if (size < 5 || data[0] != HYPERX_REPORT_ID)
+ return 0;
+
+ switch (data[1]) {
+ case HYPERX_CMD_GET_CONNECTED:
+ case HYPERX_RESP_CONNECTED:
+ hyperx_headset_parse_connected_event(drvdata, data);
+ break;
+
+ case HYPERX_CMD_GET_BATTERY:
+ case HYPERX_RESP_BATTERY:
+ hyperx_headset_parse_battery_event(drvdata, data);
+ break;
+
+ case HYPERX_CMD_GET_CHARGING:
+ case HYPERX_RESP_CHARGING:
+ hyperx_headset_parse_charging_event(drvdata, data);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void hyperx_headset_remove(struct hid_device *hdev)
+{
+ struct hyperx_headset_device *drvdata = hid_get_drvdata(hdev);
+ struct power_supply *ps = NULL;
+ unsigned long flags;
+
+ hid_hw_stop(hdev);
+
+ if (drvdata) {
+ cancel_delayed_work_sync(&drvdata->poll_work);
+ cancel_work_sync(&drvdata->battery_work);
+
+ spin_lock_irqsave(&drvdata->lock, flags);
+ ps = drvdata->battery;
+ drvdata->battery = NULL;
+ spin_unlock_irqrestore(&drvdata->lock, flags);
+
+ if (ps)
+ power_supply_unregister(ps);
+ }
+}
+
+
+static const struct hid_device_id hyperx_headset_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_HP,
+ USB_DEVICE_ID_HP_HYPERX_CLOUD_III_WIRELESS) },
+ {}
+};
+MODULE_DEVICE_TABLE(hid, hyperx_headset_devices);
+
+static struct hid_driver hyperx_headset_driver = {
+ .name = "hyperx-headset",
+ .id_table = hyperx_headset_devices,
+ .probe = hyperx_headset_probe,
+ .raw_event = hyperx_headset_raw_event,
+ .remove = hyperx_headset_remove,
+};
+module_hid_driver(hyperx_headset_driver);
+
+MODULE_AUTHOR("Sofia Schneider <sofia@schn.dev>");
+MODULE_DESCRIPTION("HID driver for HyperX headsets");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..aa2c3a71315b 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -696,6 +696,7 @@
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0941 0x0941
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_0641 0x0641
#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_1f4a 0x1f4a
+#define USB_DEVICE_ID_HP_HYPERX_CLOUD_III_WIRELESS 0x05b7
#define USB_VENDOR_ID_HUION 0x256c
#define USB_DEVICE_ID_HUION_TABLET 0x006e
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] Input: synaptics-rmi4 - bound the F54 report size to the allocated buffer
From: Bryam Vargas @ 2026-06-27 21:38 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: linux-input, linux-kernel, Guenter Roeck, Benjamin Tissoires
In-Reply-To: <aj4LoEHHCMqUnyy2@google.com>
Hi Dmitry,
Thanks for picking this up and keeping the authorship on 03.
> there are more changes needed in F54. I incorporated it in the series I
> just posted, would appreviate if you could review it.
Went through all ten. It holds together. A few things worth flagging:
Backport ordering is the one to act on. 03 leans on 02's control flow: its
report_size > max_report_size guard lands in the worker tail that 02 folds
into the single out: write. And 03's Fixes: c762cc68b6a1 (Nov 2016) postdates
02's Fixes: 3a762dbd5347 (Jul 2016), so any stable tree old enough to want 03
still carries the abort:/error: form 02 reworks; cherry-picking 03 alone
conflicts on that hunk, and 06 touches the same tail. Keeping 02 -> 03 -> 06
together for stable (or a dependency note on 03) keeps AUTOSEL from taking 03
on its own.
01 is correct but instance-complete: a device honestly reporting a large F55
num_tx still oversizes report_size until 03 lands. Maybe a line in its commit
log pointing at 03.
One aside, not for this series: F54_FIFO_OFFSET is 16-bit but report_size
reaches 2 * 255 * 255 = 130050, so past 65535 the start offset wraps and the
device reads from the wrong place. It stays in bounds (report_data + i tracks
the same product), so it's correctness, not safety; maybe a later guard.
Otherwise it's straightforward. For the series:
Reviewed-by: Bryam Vargas <hexlabsecurity@proton.me>
03 is the one I sent, so drop it there. On the memory-safety patches I ran an
in-kernel A/B under KASAN: the unpatched arm overruns the plane (07), delivers
the stale frame (06), or overruns report_data (03's class); the patched arm is
clean. So for 05, 06 and 07:
Tested-by: Bryam Vargas <hexlabsecurity@proton.me>
Thanks,
Bryam
^ permalink raw reply
* [PATCH] HID: intel-ish-hid: ignore post-init ENUM_DEVICES from firmware
From: Wahid7852 @ 2026-06-27 22:47 UTC (permalink / raw)
To: linux-input; +Cc: srinivas.pandruvada, jikos, Wahid7852
Some ISH firmware versions (observed on Tiger Lake LP, 8086:a0fc,
GUID {33AECD58-B679-4E54-9BD9-A04D34F0C226}) periodically re-send an
unsolicited HOSTIF_DM_ENUM_DEVICES response roughly every 79 seconds.
The current guard collapses two distinct cases into one condition:
if (!(response_flag) || init_done) -> bad packet + ish_hw_reset
This incorrectly treats a valid post-init firmware announcement as a
corrupted packet, triggering an ISH soft-reset and a full ISHTP
re-initialisation cycle on each occurrence (~1100 times per day on
affected hardware).
Split the check: reject messages with no response flag as before, but
simply discard valid ENUM_DEVICES messages that arrive after init is
complete.
Signed-off-by: Wahid <wahidzk0091@gmail.com>
---
drivers/hid/intel-ish-hid/ishtp-hid-client.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
index 6d64008f2..ec5e8afe7 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
@@ -113,8 +113,7 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
switch (recv_msg->hdr.command & CMD_MASK) {
case HOSTIF_DM_ENUM_DEVICES:
- if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
- client_data->init_done)) {
+ if (!(recv_msg->hdr.command & ~CMD_MASK)) {
++client_data->bad_recv_cnt;
report_bad_packet(hid_ishtp_cl, recv_msg,
cur_pos,
@@ -122,6 +121,8 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
break;
}
+ if (client_data->init_done)
+ break;
client_data->hid_dev_count = (unsigned int)*payload;
if (!client_data->hid_devices)
client_data->hid_devices = devm_kcalloc(
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] HID: intel-ish-hid: ignore post-init ENUM_DEVICES from firmware
From: sashiko-bot @ 2026-06-27 22:57 UTC (permalink / raw)
To: Wahid7852; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260627224753.35075-1-wahidzk0091@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Duplicate firmware responses during initialization (ENUM_DEVICES, GET_HID_DESCRIPTOR, GET_REPORT_DESCRIPTOR) cause heap buffer overflows.
- [High] Missing payload length validation and flawed bounds checks in HOSTIF_DM_ENUM_DEVICES parsing lead to out-of-bounds memory reads.
- [High] Unscaled pointer arithmetic and integer underflow in HOSTIF_PUBLISH_INPUT_REPORT_LIST lead to massive out-of-bounds memory accesses.
--
commit 44b4c0eea0f41216fd9e39bd67a4356eeadc37c7
Author: Wahid <wahidzk0091@gmail.com>
HID: intel-ish-hid: ignore post-init ENUM_DEVICES from firmware
This commit splits the guard condition for HOSTIF_DM_ENUM_DEVICES messages
to safely discard valid ENUM_DEVICES messages that arrive after
initialization is complete. This prevents unnecessary ISH soft-resets
caused by periodic firmware announcements.
> diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
> --- a/drivers/hid/intel-ish-hid/ishtp-hid-client.c
> +++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
> @@ -113,8 +113,7 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
>
> switch (recv_msg->hdr.command & CMD_MASK) {
> case HOSTIF_DM_ENUM_DEVICES:
> - if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
> - client_data->init_done)) {
> + if (!(recv_msg->hdr.command & ~CMD_MASK)) {
> ++client_data->bad_recv_cnt;
> report_bad_packet(hid_ishtp_cl, recv_msg,
> cur_pos,
> @@ -122,6 +121,8 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
> ish_hw_reset(ishtp_get_ishtp_device(hid_ishtp_cl));
> break;
> }
> + if (client_data->init_done)
> + break;
> client_data->hid_dev_count = (unsigned int)*payload;
[Severity: High]
This is a pre-existing issue, but does this code read out-of-bounds memory
if the payload length is zero?
If a HOSTIF_DM_ENUM_DEVICES message is received with a zero-length payload,
*payload is dereferenced here without validation.
Furthermore, in the loop that follows:
process_recv() {
...
for (i = 0; i < client_data->hid_dev_count; ++i) {
...
if (1 + sizeof(struct device_info) * i >= data_len)
break;
dev_info = (struct device_info *)(payload + 1 +
sizeof(struct device_info) * i);
if (client_data->hid_devices)
memcpy(client_data->hid_devices + i, dev_info,
sizeof(struct device_info));
...
}
The bounds check compares the relative payload offset against the absolute
buffer size (data_len) instead of the payload size. This allows the offset
to read out-of-bounds memory, which is then copied into the persistent
device information cache, leaking the contents.
> if (!client_data->hid_devices)
> client_data->hid_devices = devm_kcalloc(
[Severity: Critical]
This is a pre-existing issue, but can duplicate firmware responses cause a
heap buffer overflow here?
If multiple HOSTIF_DM_ENUM_DEVICES or GET_HID_DESCRIPTOR messages arrive
back-to-back before init_done becomes true, this allocation is incorrectly
skipped because the pointer is already non-NULL.
The memory is then copied into the existing buffer based on a potentially
larger, unvalidated hid_dev_count or payload_len:
process_recv() {
...
memcpy(client_data->hid_devices + i, dev_info,
sizeof(struct device_info));
...
memcpy(client_data->hid_descr[curr_hid_dev], payload, payload_len);
...
}
This could cause memory to be copied into the original buffer without
reallocation, causing a heap buffer overflow.
[ ... ]
[Severity: High]
This is a pre-existing issue, but is there an integer underflow and pointer
arithmetic issue when parsing HOSTIF_PUBLISH_INPUT_REPORT_LIST?
Looking further down in process_recv():
process_recv() {
...
case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
...
payload_len = report_len - sizeof(struct hostif_msg_hdr);
...
report += sizeof(*report) + payload_len;
...
}
If the firmware-controlled report_len is less than 6
(sizeof(struct hostif_msg_hdr)), payload_len underflows.
Additionally, because report is a typed pointer (struct report *), this
addition is implicitly scaled by sizeof(struct report) (8 bytes), advancing
the pointer out of bounds by a factor of 8.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260627224753.35075-1-wahidzk0091@gmail.com?part=1
^ permalink raw reply
* [PATCH] HID: steam: reject short serial number reports
From: Yousef Alhouseen @ 2026-06-28 0:41 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, Vicki Pfau, stable,
syzbot+75f3f9bff8c510602d36, Yousef Alhouseen
steam_recv_report() may return a short positive response and copies
only the bytes actually received. steam_get_serial() nevertheless reads
the full three-byte header and trusts its length without checking that
the serial payload was returned.
A malformed USB device can therefore make the driver read uninitialized
stack bytes. With a complete-looking short header, those bytes can also
be copied into steam->serial_no and printed.
Account for the stripped report ID in the return value and reject replies
that do not contain both the header and its declared payload.
Fixes: c164d6abf384 ("HID: add driver for Valve Steam Controller")
Reported-by: syzbot+75f3f9bff8c510602d36@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=75f3f9bff8c510602d36
Cc: stable@vger.kernel.org
Signed-off-by: Yousef Alhouseen <alhouseenyousef@gmail.com>
---
drivers/hid/hid-steam.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 197126d6e081..8c8bfb10e8b8 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -454,11 +454,20 @@ static int steam_get_serial(struct steam_device *steam)
ret = steam_recv_report(steam, reply, sizeof(reply));
if (ret < 0)
goto out;
+ /* hid_hw_raw_request() counts the stripped report ID byte. */
+ if (ret < 4) {
+ ret = -EIO;
+ goto out;
+ }
if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 ||
reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) {
ret = -EIO;
goto out;
}
+ if (ret - 1 < 3 + reply[1]) {
+ ret = -EIO;
+ goto out;
+ }
reply[3 + STEAM_SERIAL_LEN] = 0;
strscpy(steam->serial_no, reply + 3, reply[1]);
out:
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] HID: steam: reject short serial number reports
From: Vicki Pfau @ 2026-06-28 0:47 UTC (permalink / raw)
To: Yousef Alhouseen, Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, stable, syzbot+75f3f9bff8c510602d36
In-Reply-To: <20260628004106.26920-1-alhouseenyousef@gmail.com>
Hi Yousef,
On 6/27/26 5:41 PM, Yousef Alhouseen wrote:
> steam_recv_report() may return a short positive response and copies
> only the bytes actually received. steam_get_serial() nevertheless reads
> the full three-byte header and trusts its length without checking that
> the serial payload was returned.
>
> A malformed USB device can therefore make the driver read uninitialized
> stack bytes. With a complete-looking short header, those bytes can also
> be copied into steam->serial_no and printed.
>
> Account for the stripped report ID in the return value and reject replies
> that do not contain both the header and its declared payload.
>
> Fixes: c164d6abf384 ("HID: add driver for Valve Steam Controller")
> Reported-by: syzbot+75f3f9bff8c510602d36@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=75f3f9bff8c510602d36
> Cc: stable@vger.kernel.org
> Signed-off-by: Yousef Alhouseen <alhouseenyousef@gmail.com>
> ---
> drivers/hid/hid-steam.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
> index 197126d6e081..8c8bfb10e8b8 100644
> --- a/drivers/hid/hid-steam.c
> +++ b/drivers/hid/hid-steam.c
> @@ -454,11 +454,20 @@ static int steam_get_serial(struct steam_device *steam)
> ret = steam_recv_report(steam, reply, sizeof(reply));
> if (ret < 0)
> goto out;
> + /* hid_hw_raw_request() counts the stripped report ID byte. */
> + if (ret < 4) {
> + ret = -EIO;
> + goto out;
> + }
> if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 ||
> reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) {
> ret = -EIO;
> goto out;
> }
> + if (ret - 1 < 3 + reply[1]) {
> + ret = -EIO;
> + goto out;
> + }
> reply[3 + STEAM_SERIAL_LEN] = 0;
> strscpy(steam->serial_no, reply + 3, reply[1]);
> out:
I already have locally a patch that fixes this as part of my pending Steam Controller 2 support. However, it chooses to fix it in a different way that would affect all uses of steam_recv_report instead of per-callsite (with only one callsite fixed). I am hoping to get this patchset submitted soon, once more widescale testing is done, but if you want in the meantime I can pull out that single fix and submit it separately; it's a bit more sprawling and involves adding a new function for combined send/recv.
Vicki
^ permalink raw reply
* Re: [PATCH] HID: steam: reject short serial number reports
From: sashiko-bot @ 2026-06-28 0:54 UTC (permalink / raw)
To: Yousef Alhouseen; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260628004106.26920-1-alhouseenyousef@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] The serial number payload is systematically truncated and its buffer is improperly NUL-terminated. `strscpy(steam->serial_no, reply + 3, reply[1])` incorrectly uses the dynamic payload length (`reply[1]`) as the destination buffer size limit. Because `strscpy` guarantees NUL-termination by copying at most `size - 1` bytes, it unconditionally truncates the last byte of the serial number. Furthermore, `reply[3 + STEAM_SERIAL_LEN] = 0` hardcodes the NUL terminator at index 24 (the maximum possible payload boundary). If the device returns the maximum 22-byte payload, this overwrites the 22nd payload byte before copying. If the payload is shorter, it leaves uninitialized stack memory between the end of the payload and the NUL terminator, which would be exposed if `strscpy` used the correct buffer size.
- [High] Works and delayed works are cancelled before the underlying hardware is stopped, creating a race condition that leads to a Use-After-Free (UAF) on the `steam_device` structure.
--
commit 9b12928f5d4ac893e4176a52f291e99aaa7d63e9
Author: Yousef Alhouseen <alhouseenyousef@gmail.com>
HID: steam: reject short serial number reports
This patch modifies the steam driver to correctly validate the length of
the serial number response. It accounts for the stripped report ID in the
return value and ensures that both the header and its declared payload are
fully received before parsing.
Closes: https://syzkaller.appspot.com/bug?extid=75f3f9bff8c510602d36
> diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
> index 197126d6e0810..8c8bfb10e8b82 100644
> --- a/drivers/hid/hid-steam.c
> +++ b/drivers/hid/hid-steam.c
> @@ -454,11 +454,20 @@ static int steam_get_serial(struct steam_device *steam)
> ret = steam_recv_report(steam, reply, sizeof(reply));
> if (ret < 0)
> goto out;
> + /* hid_hw_raw_request() counts the stripped report ID byte. */
> + if (ret < 4) {
> + ret = -EIO;
> + goto out;
> + }
> if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 ||
> reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) {
> ret = -EIO;
> goto out;
> }
> + if (ret - 1 < 3 + reply[1]) {
> + ret = -EIO;
> + goto out;
> + }
> reply[3 + STEAM_SERIAL_LEN] = 0;
> strscpy(steam->serial_no, reply + 3, reply[1]);
[Severity: High]
This is a pre-existing issue, but does this logic truncate the serial number?
Because strscpy() guarantees NUL-termination by copying at most size - 1
bytes, passing reply[1] as the destination size limit will unconditionally
truncate the last byte of the serial number payload.
Additionally, setting reply[3 + STEAM_SERIAL_LEN] = 0 hardcodes the NUL
terminator at the maximum boundary.
If the device returns the maximum 22-byte payload, this overwrites the 22nd
payload byte. If the payload is shorter, could this leave uninitialized
stack memory between the end of the payload and the NUL terminator?
I also noticed another potential problem in steam_remove() which is not
touched by this patch:
> static void steam_remove(struct hid_device *hdev)
> {
[ ... ]
> cancel_delayed_work_sync(&steam->mode_switch);
> cancel_work_sync(&steam->work_connect);
> cancel_work_sync(&steam->rumble_work);
> cancel_work_sync(&steam->unregister_work);
> steam->client_hdev = NULL;
> steam->client_opened = 0;
> if (steam->quirks & STEAM_QUIRK_WIRELESS) {
> hid_info(hdev, "Steam wireless receiver disconnected");
> }
> hid_hw_close(hdev);
> hid_hw_stop(hdev);
[Severity: High]
This isn't a bug introduced by this patch, but could this cancellation
sequence cause a use-after-free on the steam struct?
Since the driver explicitly cancels works before stopping the underlying
hardware with hid_hw_close() and hid_hw_stop(), an incoming interrupt could
trigger steam_raw_event() and reschedule the work.
If the work is rescheduled right before steam_remove() returns, could the
workqueue execute on the freed steam struct after the driver core cleans
it up?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628004106.26920-1-alhouseenyousef@gmail.com?part=1
^ permalink raw reply
* [PATCH] HID: synchronize input before cleaning up a failed probe
From: Yousef Alhouseen @ 2026-06-28 0:58 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: linux-input, linux-kernel, syzbot+9eebf5f6544c5e873858,
Yousef Alhouseen
hid_device_io_start() allows reports to run concurrently with probe. If
the probe subsequently fails, __hid_device_probe() releases driver
resources and clears hdev->driver without first excluding those report
callbacks.
For example, a report may enter hidraw_report_event() while the failure
path frees the associated hidraw object, leading to a use-after-free when
the report takes the object's list lock.
Stop input before performing failed-probe cleanup. This reacquires
driver_input_lock and waits for any report callback already in progress.
Fixes: c849a6143bec ("HID: Separate struct hid_device's driver_lock into two locks.")
Reported-by: syzbot+9eebf5f6544c5e873858@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=9eebf5f6544c5e873858
Signed-off-by: Yousef Alhouseen <alhouseenyousef@gmail.com>
---
drivers/hid/hid-core.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 41a79e43c82b..469b849b7a2a 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2835,6 +2835,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
*/
if (ret) {
+ if (hdev->io_started)
+ hid_device_io_stop(hdev);
devres_release_group(&hdev->dev, hdev->devres_group_id);
hid_close_report(hdev);
hdev->driver = NULL;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] HID: synchronize input before cleaning up a failed probe
From: sashiko-bot @ 2026-06-28 1:14 UTC (permalink / raw)
To: Yousef Alhouseen; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260628005846.31248-1-alhouseenyousef@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Use-After-Free in hidraw_report_event() due to drivers calling hid_hw_stop() before hid_device_io_stop() on probe error paths.
--
commit 6c338032433c7e95cf76d26b6790e7b5f487a9b4
Author: Yousef Alhouseen <alhouseenyousef@gmail.com>
HID: synchronize input before cleaning up a failed probe
hid_device_io_start() allows reports to run concurrently with probe. If
the probe subsequently fails, __hid_device_probe() releases driver
resources and clears hdev->driver without first excluding those report
callbacks.
Closes: https://syzkaller.appspot.com/bug?extid=9eebf5f6544c5e873858
> diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
> index 41a79e43c82b3..469b849b7a2a4 100644
> --- a/drivers/hid/hid-core.c
> +++ b/drivers/hid/hid-core.c
> @@ -2835,6 +2835,8 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
> */
>
> if (ret) {
> + if (hdev->io_started)
> + hid_device_io_stop(hdev);
[Severity: High]
This is a pre-existing issue, but does this fix cover all the cases where the
use-after-free can occur?
If drivers call hid_hw_stop(hdev) in their own probe error paths before
returning an error, they might bypass the core's new hid_device_io_stop()
cleanup.
hid_hw_stop() unconditionally calls hid_disconnect(), which frees the hidraw
object. If HID_QUIRK_ALWAYS_POLL is set (or for Bluetooth devices), polling
continues, and a concurrent report could enter hidraw_report_event() with a
dangling pointer before driver_input_lock is taken.
For example, this pattern appears in nintendo_hid_probe():
drivers/hid/hid-nintendo.c:nintendo_hid_probe() {
...
err_close:
hid_hw_close(hdev);
err_stop:
hid_hw_stop(hdev);
...
}
And similarly in corsairpsu_probe():
drivers/hwmon/corsair-psu.c:corsairpsu_probe() {
...
fail_and_close:
hid_hw_close(hdev);
fail_and_stop:
hid_hw_stop(hdev);
...
}
Could these drivers also trigger the use-after-free by tearing down the
hardware and freeing hidraw while IO is still started and driver_input_lock
is unheld?
> devres_release_group(&hdev->dev, hdev->devres_group_id);
> hid_close_report(hdev);
> hdev->driver = NULL;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628005846.31248-1-alhouseenyousef@gmail.com?part=1
^ permalink raw reply
* [PATCH 0/2] Add keyboard LED support for Lenovo IdeaPad 83RR/83SR
From: Rodnei Cilto @ 2026-06-29 1:57 UTC (permalink / raw)
To: Dmitry Torokhov, Ike Panhc, Mark Pearson, Derek J. Clark,
Hans de Goede, Ilpo Järvinen
Cc: linux-input, linux-kernel, platform-driver-x86, Rodnei Cilto
This series adds support for the internal keyboard of Lenovo IdeaPad
laptops based on Wildcat Lake SoC (models 83RR worldwide and 83SR
Brazil regional variant).
The EC PS/2 emulation on these models does not fully support the AT
protocol. Sending the SETLEDS command (0xED) after initialization
causes the EC to return corrupted scancodes ('**' in i8042.debug),
rendering the keyboard non-functional.
Patch 1 adds a softleds DMI quirk to atkbd that suppresses 0xED
while keeping EV_LED capabilities visible to userspace.
Patch 2 adds physical CapsLock/NumLock LED control in ideapad-laptop
via direct EC register access (offset 0xA1) and ACPI _QDF method,
discovered via DSDT analysis.
Rodnei Cilto (2):
input: atkbd: add softleds quirk for broken EC PS/2 emulation
platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
Signed-off-by: Rodnei Cilto <rodnei.cilto@gmail.com>
---
Rodnei Cilto (2):
input: atkbd: add softleds quirk for broken EC PS/2 emulation
platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
drivers/input/keyboard/atkbd.c | 46 ++++++++-
drivers/input/serio/i8042-acpipnpio.h | 3 +
drivers/platform/x86/lenovo/ideapad-laptop.c | 143 +++++++++++++++++++++++++++
3 files changed, 191 insertions(+), 1 deletion(-)
---
base-commit: 03e2778d1f11de9260543f969e9e888a1c2bf830
change-id: 20260628-ideapad-kbd-leds-16f587e9ab97
Best regards,
--
Rodnei Cilto <rodnei.cilto@gmail.com>
^ permalink raw reply
* [PATCH 1/2] input: atkbd: add softleds quirk for broken EC PS/2 emulation
From: Rodnei Cilto @ 2026-06-29 1:57 UTC (permalink / raw)
To: Dmitry Torokhov, Ike Panhc, Mark Pearson, Derek J. Clark,
Hans de Goede, Ilpo Järvinen
Cc: linux-input, linux-kernel, platform-driver-x86, Rodnei Cilto
In-Reply-To: <20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com>
Some Lenovo IdeaPad laptops (e.g. 83RR/83SR, Wildcat Lake) implement
PS/2 keyboard emulation via the Embedded Controller (EC) but do not
fully support the AT protocol. Specifically, sending the SETLEDS
command (0xED) after initialization causes the EC to return corrupted
scancodes (reported as '**' in i8042.debug), rendering the keyboard
non-functional.
The existing SERIO_QUIRK_DUMBKBD resolves scancode corruption by
zeroing serio->write, preventing AT commands. However, LED registration
in atkbd_set_device_attrs() depends on atkbd->write being set, so
dumbkbd mode loses EV_LED capabilities entirely.
Note: serio->id.extra is __u8 (8 bits only) and cannot be used to
pass new quirk flags from i8042 to atkbd. The quirk is detected
directly in atkbd via its DMI quirk table.
Introduce atkbd_softleds: a DMI-detected mode that combines dumbkbd
behaviour (serio->write = NULL, no 0xED sent) with EV_LED registration
so that CapsLock/NumLock/ScrollLock state remains visible to userspace
via the input subsystem.
Add DMI entries for Lenovo IdeaPad 83RR (Wildcat Lake) and its Brazil
regional variant 83SR.
Signed-off-by: Rodnei Cilto <rodnei.cilto@gmail.com>
---
drivers/input/keyboard/atkbd.c | 46 ++++++++++++++++++++++++++++++++++-
drivers/input/serio/i8042-acpipnpio.h | 3 +++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
index 8cb4dc6fb165..826a21dc016a 100644
--- a/drivers/input/keyboard/atkbd.c
+++ b/drivers/input/keyboard/atkbd.c
@@ -212,6 +212,7 @@ struct atkbd {
bool softrepeat;
bool softraw;
bool scroll;
+ bool softleds; /* suppress 0xED, register EV_LED in software */
bool enabled;
/* Accessed only from interrupt */
@@ -245,6 +246,7 @@ static unsigned int (*atkbd_platform_scancode_fixup)(struct atkbd *, unsigned in
* to many commands until full reset (ATKBD_CMD_RESET_BAT) is performed.
*/
static bool atkbd_skip_deactivate;
+static bool atkbd_softleds;
static ssize_t atkbd_attr_show_helper(struct device *dev, char *buf,
ssize_t (*handler)(struct atkbd *, char *));
@@ -600,6 +602,14 @@ static int atkbd_set_leds(struct atkbd *atkbd)
struct input_dev *dev = atkbd->dev;
u8 param[2];
+ /*
+ * softleds: EC PS/2 emulation does not support AT commands
+ * after initialization. Accept LED state from userspace but
+ * never send SETLEDS (0xED) to avoid scancode corruption.
+ */
+ if (atkbd->softleds)
+ return 0;
+
param[0] = (test_bit(LED_SCROLLL, dev->led) ? 1 : 0)
| (test_bit(LED_NUML, dev->led) ? 2 : 0)
| (test_bit(LED_CAPSL, dev->led) ? 4 : 0);
@@ -1193,7 +1203,7 @@ static void atkbd_set_device_attrs(struct atkbd *atkbd)
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
BIT_MASK(EV_MSC);
- if (atkbd->write) {
+ if (atkbd->write || atkbd->softleds) {
input_dev->evbit[0] |= BIT_MASK(EV_LED);
input_dev->ledbit[0] = BIT_MASK(LED_NUML) |
BIT_MASK(LED_CAPSL) | BIT_MASK(LED_SCROLLL);
@@ -1291,6 +1301,12 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
if (atkbd->softrepeat)
atkbd->softraw = true;
+ if (atkbd_softleds) {
+ serio->write = NULL;
+ atkbd->write = false;
+ atkbd->softleds = true;
+ }
+
serio_set_drvdata(serio, atkbd);
err = serio_open(serio, drv);
@@ -1767,6 +1783,12 @@ static int __init atkbd_deactivate_fixup(const struct dmi_system_id *id)
return 1;
}
+static int __init atkbd_setup_softleds(const struct dmi_system_id *id)
+{
+ atkbd_softleds = true;
+ return 1;
+}
+
/*
* NOTE: do not add any more "force release" quirks to this table. The
* task of adjusting list of keys that should be "released" automatically
@@ -1938,6 +1960,28 @@ static const struct dmi_system_id atkbd_dmi_quirk_table[] __initconst = {
},
.callback = atkbd_deactivate_fixup,
},
+ {
+ /*
+ * Lenovo IdeaPad 83RR (Wildcat Lake) - EC PS/2 emulation
+ * returns corrupted scancodes ('**' in i8042.debug) when
+ * receiving AT SETLEDS (0xED) after keyboard initialization.
+ * Enable softleds mode: suppress 0xED to hardware while
+ * keeping CapsLock/NumLock/ScrollLock visible to userspace.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"),
+ },
+ .callback = atkbd_setup_softleds,
+ },
+ {
+ /* Lenovo IdeaPad 83SR (83RR Brazil regional variant) */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83SR"),
+ },
+ .callback = atkbd_setup_softleds,
+ },
{ }
};
diff --git a/drivers/input/serio/i8042-acpipnpio.h b/drivers/input/serio/i8042-acpipnpio.h
index 8ebdf4fb9030..d233544ebac9 100644
--- a/drivers/input/serio/i8042-acpipnpio.h
+++ b/drivers/input/serio/i8042-acpipnpio.h
@@ -79,6 +79,9 @@ static inline void i8042_write_command(int val)
#define SERIO_QUIRK_DIRECT BIT(8)
#define SERIO_QUIRK_DUMBKBD BIT(9)
#define SERIO_QUIRK_NOLOOP BIT(10)
+/* SERIO_QUIRK_DUMBKBD_LEDS handled via atkbd DMI quirk table.
+ * serio->id.extra is __u8 (8 bits only), cannot carry this flag.
+ */
#define SERIO_QUIRK_NOTIMEOUT BIT(11)
#define SERIO_QUIRK_KBDRESET BIT(12)
#define SERIO_QUIRK_DRITEK BIT(13)
--
2.51.0
^ permalink raw reply related
* [PATCH 2/2] platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
From: Rodnei Cilto @ 2026-06-29 1:57 UTC (permalink / raw)
To: Dmitry Torokhov, Ike Panhc, Mark Pearson, Derek J. Clark,
Hans de Goede, Ilpo Järvinen
Cc: linux-input, linux-kernel, platform-driver-x86, Rodnei Cilto
In-Reply-To: <20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com>
Some Lenovo IdeaPad laptops (e.g. 83RR/83SR, Wildcat Lake) have
physical CapsLock and NumLock LEDs controlled via the EC.
The EC exposes CAPL (bit 5) and NUML (bit 4) at offset 0xA1.
Writing these bits via ec_write() and evaluating _QDF via
acpi_evaluate_object() causes the firmware to sync EC state to the
GPIO lines that drive the physical LEDs.
Discovery via DSDT analysis on Lenovo IdeaPad 83RR (Wildcat Lake):
- CAPL/NUML at EC offset 0xA1 (bits 5 and 4)
- _QDF (_SB.PC00.LPCB.EC0._QDF) reads CAPL/NUML -> SGOV()
- GPIO 0x001A1087 -> CapsLock LED physical pin
- GPIO 0x001A0485 -> NumLock LED physical pin
- ec_read/ec_write exported via EXPORT_SYMBOL in drivers/acpi/ec.c
and declared in <linux/acpi.h>
Add two led_classdev entries (input::capslock, input::numlock)
guarded by DMI match (features.kbd_leds) for 83RR and its Brazil
regional variant 83SR.
Signed-off-by: Rodnei Cilto <rodnei.cilto@gmail.com>
---
drivers/platform/x86/lenovo/ideapad-laptop.c | 143 +++++++++++++++++++++++++++
1 file changed, 143 insertions(+)
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 4fbc904f1fc3..03e3f234067c 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -42,6 +42,17 @@
#include <dt-bindings/leds/common.h>
+/* EC keyboard LED control (IdeaPad EC PS/2 emulation).
+ * Validated on Lenovo IdeaPad 83RR (Wildcat Lake):
+ * EC offset 0xA1 bit4=NUML, bit5=CAPL
+ * _QDF syncs EC state to GPIO -> physical LED
+ * ec_read/ec_write declared in <linux/acpi.h>
+ */
+#define IDEAPAD_EC_KBD_LED_OFFSET 0xA1
+#define IDEAPAD_EC_KBD_LED_NUML_BIT BIT(4)
+#define IDEAPAD_EC_KBD_LED_CAPL_BIT BIT(5)
+#define IDEAPAD_ACPI_EC0_QDF_PATH "\\_SB.PC00.LPCB.EC0._QDF"
+
#define IDEAPAD_RFKILL_DEV_NUM 3
enum {
@@ -198,6 +209,7 @@ struct ideapad_private {
bool ctrl_ps2_aux_port : 1;
bool usb_charging : 1;
bool ymc_ec_trigger : 1;
+ bool kbd_leds : 1;
} features;
struct {
bool initialized;
@@ -210,6 +222,11 @@ struct ideapad_private {
struct led_classdev led;
unsigned int last_brightness;
} fn_lock;
+ struct {
+ bool initialized;
+ struct led_classdev capslock;
+ struct led_classdev numlock;
+ } kbd_leds;
};
static bool no_bt_rfkill;
@@ -1587,6 +1604,99 @@ static void ideapad_backlight_notify_brightness(struct ideapad_private *priv)
/*
* keyboard backlight
*/
+static int ideapad_kbd_led_ec_set(u8 bit, bool on)
+{
+ u8 val;
+ int err;
+
+ err = ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val);
+ if (err)
+ return err;
+ if (on)
+ val |= bit;
+ else
+ val &= ~bit;
+ err = ec_write(IDEAPAD_EC_KBD_LED_OFFSET, val);
+ if (err)
+ return err;
+ acpi_evaluate_object(NULL, IDEAPAD_ACPI_EC0_QDF_PATH, NULL, NULL);
+ return 0;
+}
+
+static void ideapad_capslock_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ ideapad_kbd_led_ec_set(IDEAPAD_EC_KBD_LED_CAPL_BIT, brightness != LED_OFF);
+}
+
+static enum led_brightness ideapad_capslock_led_get(struct led_classdev *led_cdev)
+{
+ u8 val;
+
+ if (ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val))
+ return LED_OFF;
+ return (val & IDEAPAD_EC_KBD_LED_CAPL_BIT) ? LED_ON : LED_OFF;
+}
+
+static void ideapad_numlock_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ ideapad_kbd_led_ec_set(IDEAPAD_EC_KBD_LED_NUML_BIT, brightness != LED_OFF);
+}
+
+static enum led_brightness ideapad_numlock_led_get(struct led_classdev *led_cdev)
+{
+ u8 val;
+
+ if (ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val))
+ return LED_OFF;
+ return (val & IDEAPAD_EC_KBD_LED_NUML_BIT) ? LED_ON : LED_OFF;
+}
+
+static int ideapad_kbd_leds_init(struct ideapad_private *priv)
+{
+ int err;
+
+ if (WARN_ON(priv->kbd_leds.initialized))
+ return -EEXIST;
+
+ priv->kbd_leds.capslock.name = "input::capslock";
+ priv->kbd_leds.capslock.max_brightness = 1;
+ priv->kbd_leds.capslock.brightness_set = ideapad_capslock_led_set;
+ priv->kbd_leds.capslock.brightness_get = ideapad_capslock_led_get;
+ priv->kbd_leds.capslock.flags = LED_RETAIN_AT_SHUTDOWN;
+
+ err = led_classdev_register(&priv->platform_device->dev,
+ &priv->kbd_leds.capslock);
+ if (err)
+ return err;
+
+ priv->kbd_leds.numlock.name = "input::numlock";
+ priv->kbd_leds.numlock.max_brightness = 1;
+ priv->kbd_leds.numlock.brightness_set = ideapad_numlock_led_set;
+ priv->kbd_leds.numlock.brightness_get = ideapad_numlock_led_get;
+ priv->kbd_leds.numlock.flags = LED_RETAIN_AT_SHUTDOWN;
+
+ err = led_classdev_register(&priv->platform_device->dev,
+ &priv->kbd_leds.numlock);
+ if (err) {
+ led_classdev_unregister(&priv->kbd_leds.capslock);
+ return err;
+ }
+
+ priv->kbd_leds.initialized = true;
+ return 0;
+}
+
+static void ideapad_kbd_leds_exit(struct ideapad_private *priv)
+{
+ if (!priv->kbd_leds.initialized)
+ return;
+ priv->kbd_leds.initialized = false;
+ led_classdev_unregister(&priv->kbd_leds.numlock);
+ led_classdev_unregister(&priv->kbd_leds.capslock);
+}
+
static int ideapad_kbd_bl_check_tristate(int type)
{
return (type == KBD_BL_TRISTATE) || (type == KBD_BL_TRISTATE_AUTO);
@@ -1832,6 +1942,29 @@ static void ideapad_sync_touchpad_state(struct ideapad_private *priv, bool send_
priv->r_touchpad_val = value;
}
+static const struct dmi_system_id ideapad_kbd_leds_dmi_table[] = {
+ {
+ /*
+ * Lenovo IdeaPad 83RR (Wildcat Lake) - EC PS/2 emulation
+ * controls CapsLock/NumLock LEDs via EC offset 0xA1 + _QDF.
+ * CAPL=bit5 (0x20), NUML=bit4 (0x10).
+ * _QDF drives GPIO via SGOV() to physical LED pins.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83RR"),
+ },
+ },
+ {
+ /* Lenovo IdeaPad 83SR (83RR Brazil regional variant) */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83SR"),
+ },
+ },
+ { }
+};
+
static const struct dmi_system_id ymc_ec_trigger_quirk_dmi_table[] = {
{
/* Lenovo Yoga 7 14ARB7 */
@@ -2178,6 +2311,8 @@ static int ideapad_check_features(struct ideapad_private *priv)
priv->features.touchpad_ctrl_via_ec = touchpad_ctrl_via_ec;
priv->features.ymc_ec_trigger =
ymc_ec_trigger || dmi_check_system(ymc_ec_trigger_quirk_dmi_table);
+ priv->features.kbd_leds =
+ dmi_check_system(ideapad_kbd_leds_dmi_table);
if (!read_ec_data(handle, VPCCMD_R_FAN, &val))
priv->features.fan_mode = true;
@@ -2418,6 +2553,12 @@ static int ideapad_acpi_add(struct platform_device *pdev)
dev_info(&pdev->dev, "FnLock control not available\n");
}
+ if (priv->features.kbd_leds) {
+ err = ideapad_kbd_leds_init(priv);
+ if (err)
+ dev_warn(&pdev->dev, "Could not set up kbd LEDs: %d\n", err);
+ }
+
/*
* On some models without a hw-switch (the yoga 2 13 at least)
* VPCCMD_W_RF must be explicitly set to 1 for the wifi to work.
@@ -2477,6 +2618,7 @@ static int ideapad_acpi_add(struct platform_device *pdev)
ideapad_unregister_rfkill(priv, i);
ideapad_fn_lock_led_exit(priv);
+ ideapad_kbd_leds_exit(priv);
ideapad_kbd_bl_exit(priv);
ideapad_input_exit(priv);
@@ -2506,6 +2648,7 @@ static void ideapad_acpi_remove(struct platform_device *pdev)
ideapad_unregister_rfkill(priv, i);
ideapad_fn_lock_led_exit(priv);
+ ideapad_kbd_leds_exit(priv);
ideapad_kbd_bl_exit(priv);
ideapad_input_exit(priv);
ideapad_debugfs_exit(priv);
--
2.51.0
^ permalink raw reply related
* Re: [PATCH 2/2] platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
From: sashiko-bot @ 2026-06-28 2:06 UTC (permalink / raw)
To: Rodnei Cilto; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260628-ideapad-kbd-leds-v1-2-50b421d94927@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Sleeping in atomic context due to improper LED brightness callback assignment.
- [Medium] Unsynchronized read-modify-write on the shared EC keyboard LED register.
- [Medium] Missing default triggers for CapsLock and NumLock LEDs.
--
commit 3bd8490b486c6791548a82ecc890f8f432c92886
Author: Rodnei Cilto <rodnei.cilto@gmail.com>
platform/x86: ideapad-laptop: add CapsLock/NumLock LED via EC
This commit adds support for physical CapsLock and NumLock LEDs on specific
Lenovo IdeaPad laptops (83RR/83SR). It maps the EC's offset 0xA1 bits 5 and
4 to CAPL and NUML and exposes them as led_classdev entries.
> diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
> --- a/drivers/platform/x86/lenovo/ideapad-laptop.c
> +++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
[ ... ]
> +static int ideapad_kbd_led_ec_set(u8 bit, bool on)
> +{
> + u8 val;
> + int err;
> +
> + err = ec_read(IDEAPAD_EC_KBD_LED_OFFSET, &val);
> + if (err)
> + return err;
> + if (on)
> + val |= bit;
> + else
> + val &= ~bit;
> + err = ec_write(IDEAPAD_EC_KBD_LED_OFFSET, val);
[Severity: Medium]
Is this read-modify-write sequence safe against concurrent updates?
Since CapsLock and NumLock are exposed as separate led_classdev devices, the
LED subsystem protects their state with independent locks. If both LEDs are
toggled simultaneously, could two threads concurrently enter
ideapad_kbd_led_ec_set(), read the same initial value, and write it back,
causing one modification to overwrite and lose the other?
> + if (err)
> + return err;
> + acpi_evaluate_object(NULL, IDEAPAD_ACPI_EC0_QDF_PATH, NULL, NULL);
> + return 0;
> +}
[ ... ]
> +static int ideapad_kbd_leds_init(struct ideapad_private *priv)
> +{
> + int err;
> +
> + if (WARN_ON(priv->kbd_leds.initialized))
> + return -EEXIST;
> +
> + priv->kbd_leds.capslock.name = "input::capslock";
> + priv->kbd_leds.capslock.max_brightness = 1;
> + priv->kbd_leds.capslock.brightness_set = ideapad_capslock_led_set;
[Severity: Medium]
Will this LED automatically synchronize with the system's actual keyboard lock
state? Without setting .default_trigger = "kbd-capslock", the LED subsystem
might not automatically link the keyboard input state to this LED, requiring
manual sysfs configuration.
[Severity: High]
Does this risk a scheduling while atomic panic? The LED subsystem often calls
the non-blocking brightness_set callback from atomic contexts, like softirq
for keyboard input triggers.
Since ideapad_capslock_led_set() calls ideapad_kbd_led_ec_set(), which uses
ec_read() and ec_write() that acquire mutexes and sleep, should this use
brightness_set_blocking instead to safely defer execution to a workqueue?
> + priv->kbd_leds.capslock.brightness_get = ideapad_capslock_led_get;
> + priv->kbd_leds.capslock.flags = LED_RETAIN_AT_SHUTDOWN;
> +
> + err = led_classdev_register(&priv->platform_device->dev,
> + &priv->kbd_leds.capslock);
> + if (err)
> + return err;
> +
> + priv->kbd_leds.numlock.name = "input::numlock";
> + priv->kbd_leds.numlock.max_brightness = 1;
> + priv->kbd_leds.numlock.brightness_set = ideapad_numlock_led_set;
[Severity: Medium]
Similarly, should this have .default_trigger = "kbd-numlock" set to ensure
the NumLock LED automatically tracks the physical keyboard state?
[Severity: High]
As with the CapsLock LED, does this need to use brightness_set_blocking to
avoid sleeping in atomic context when the trigger updates the LED?
> + priv->kbd_leds.numlock.brightness_get = ideapad_numlock_led_get;
> + priv->kbd_leds.numlock.flags = LED_RETAIN_AT_SHUTDOWN;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH 1/2] input: atkbd: add softleds quirk for broken EC PS/2 emulation
From: sashiko-bot @ 2026-06-28 2:09 UTC (permalink / raw)
To: Rodnei Cilto; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260628-ideapad-kbd-leds-v1-1-50b421d94927@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The `atkbd` driver mutates the parent `serio` port's `write` function pointer to `NULL`, which is a driver model violation and permanently corrupts the bus state.
- [Medium] The newly added early return `if (atkbd->softleds) return 0;` inside `atkbd_set_leds()` is completely unreachable dead code.
Pre-existing issues:
- [High] A pre-existing race condition in `atkbd_disconnect()` can lead to a Use-After-Free of the `input_dev` structure in `atkbd_event_work()`.
--
commit 449f71d6f3b7e8daf08e55bc0da16d0f59475df4
Author: Rodnei Cilto <rodnei.cilto@gmail.com>
input: atkbd: add softleds quirk for broken EC PS/2 emulation
This commit introduces the atkbd_softleds mode to handle broken EC PS/2
emulation on some Lenovo IdeaPad laptops. It combines dumbkbd behaviour with
EV_LED registration to keep LED states visible to userspace without sending
the SETLEDS command. DMI entries are added for the Lenovo IdeaPad 83RR and
83SR.
> diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c
> index 8cb4dc6fb1658..826a21dc016ab 100644
> --- a/drivers/input/keyboard/atkbd.c
> +++ b/drivers/input/keyboard/atkbd.c
> @@ -600,6 +602,14 @@ static int atkbd_set_leds(struct atkbd *atkbd)
> struct input_dev *dev = atkbd->dev;
> u8 param[2];
>
> + /*
> + * softleds: EC PS/2 emulation does not support AT commands
> + * after initialization. Accept LED state from userspace but
> + * never send SETLEDS (0xED) to avoid scancode corruption.
> + */
> + if (atkbd->softleds)
> + return 0;
> +
[Severity: Medium]
Is this early return reachable? When atkbd_softleds is active, atkbd_connect()
sets atkbd->write = false.
When an EV_LED event occurs, atkbd_event() checks atkbd->write and immediately
returns -1, dropping the event before atkbd_schedule_event_work() is called.
Does this mean atkbd_set_leds() is never executed by the workqueue in this
mode, and this check will never be triggered?
[ ... ]
> @@ -1291,6 +1301,12 @@ static int atkbd_connect(struct serio *serio, struct serio_driver *drv)
> if (atkbd->softrepeat)
> atkbd->softraw = true;
>
> + if (atkbd_softleds) {
> + serio->write = NULL;
> + atkbd->write = false;
> + atkbd->softleds = true;
> + }
> +
[Severity: High]
Does this permanently modify the parent bus provider's function pointer
(serio->write)?
If atkbd is unbound and another driver (e.g., serio_raw) binds to the same
port, or if atkbd is rebound, would the port remain crippled and unable to
transmit due to serio->write still being NULL?
[Severity: High]
This is a pre-existing issue, but can this sequence lead to a use-after-free
if a privileged user unbinds the driver while LED or repeat rate events are
being processed?
In atkbd_disconnect(), input_unregister_device() is called before
cancel_delayed_work_sync():
drivers/input/keyboard/atkbd.c:atkbd_disconnect() {
...
input_unregister_device(atkbd->dev);
/*
* Make sure we don't have a command in flight.
...
*/
cancel_delayed_work_sync(&atkbd->event_work);
...
}
If atkbd_event_work() starts executing on another CPU, it acquires
atkbd->mutex and checks atkbd->enabled. If preempted immediately after this
check, atkbd_disconnect() can proceed to set atkbd->enabled = false,
unregister and free the input device, and then block on
cancel_delayed_work_sync().
When the worker thread resumes, it calls atkbd_set_leds():
drivers/input/keyboard/atkbd.c:atkbd_event_work() {
...
if (!atkbd->enabled) {
...
} else {
if (test_and_clear_bit(ATKBD_LED_EVENT_BIT, &atkbd->event_mask))
atkbd_set_leds(atkbd);
...
}
Would atkbd_set_leds() then dereference the now-freed atkbd->dev?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260628-ideapad-kbd-leds-v1-0-50b421d94927@gmail.com?part=1
^ permalink raw reply
* [git pull] Input updates for v7.2-rc0 (part 2)
From: Dmitry Torokhov @ 2026-06-28 5:11 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linux-kernel, linux-input
Hi Linus,
Please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git tags/input-for-v7.2-rc0-2
to receive updates for the input subsystem. You will get:
- Updates to Synaptics RMI4 driver to fix potential OOB accesses in
F30 and F3A keymap handling
- A workaround in Synaptics RMI4 to tolerate buggy firmware on some
touchpads (e.g. ThinkPad T14 Gen 1) that report incomplete register
descriptor structures, preventing probe failures
- A revert of an incorrect register descriptor address calculation in
Synaptics RMI4 driver
- A fix for a regression in HP GSC PS/2 (gscps2) driver where the
receive buffer write index was not advanced, leaving keyboard and
mouse unusable.
Changelog:
---------
Bryam Vargas (2):
Input: synaptics-rmi4 - bound the F3A keymap to the GPIO count
Input: synaptics-rmi4 - bound the F30 keymap to the GPIO/LED count
Dmitry Torokhov (2):
Revert "Input: rmi4 - fix register descriptor address calculation"
Input: rmi4 - tolerate short register descriptor structure
Xu Rao (1):
Input: gscps2 - advance receive buffer write index
Diffstat:
--------
drivers/input/rmi4/rmi_driver.c | 39 ++++++++++++++++++++++++++-------------
drivers/input/rmi4/rmi_f30.c | 2 +-
drivers/input/rmi4/rmi_f3a.c | 2 +-
drivers/input/serio/gscps2.c | 1 +
4 files changed, 29 insertions(+), 15 deletions(-)
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/2] input: atkbd: add softleds quirk for broken EC PS/2 emulation
From: Dmitry Torokhov @ 2026-06-28 5:20 UTC (permalink / raw)
To: Rodnei Cilto
Cc: Ike Panhc, Mark Pearson, Derek J. Clark, Hans de Goede,
Ilpo Järvinen, linux-input, linux-kernel,
platform-driver-x86
In-Reply-To: <20260628-ideapad-kbd-leds-v1-1-50b421d94927@gmail.com>
Hi Rodnei,
On Sun, Jun 28, 2026 at 10:57:03PM -0300, Rodnei Cilto wrote:
> Some Lenovo IdeaPad laptops (e.g. 83RR/83SR, Wildcat Lake) implement
> PS/2 keyboard emulation via the Embedded Controller (EC) but do not
> fully support the AT protocol. Specifically, sending the SETLEDS
> command (0xED) after initialization causes the EC to return corrupted
> scancodes (reported as '**' in i8042.debug), rendering the keyboard
> non-functional.
"**" do not represent corrupted scancodes, they are safety measure to
not disclose your password on accident.
Use i8042.debug=1 i8042.unmask_kbd_data=1 to unveil real data.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [regression] synaptics-rmi4: Failed to read the Control Register Descriptor: EIO
From: Dmitry Torokhov @ 2026-06-28 5:43 UTC (permalink / raw)
To: David Heidelberg; +Cc: linux-input, Linux Next Mailing List
In-Reply-To: <916192e4-7d2c-4463-8576-3cfe9e36faa2@ixit.cz>
On Sat, Jun 27, 2026 at 12:06:29PM +0200, David Heidelberg wrote:
> On 26/06/2026 23:18, Dmitry Torokhov wrote:
> > Hi David,
> >
> > On Fri, Jun 26, 2026 at 11:09:54PM +0200, David Heidelberg wrote:
> > > With next-20260626 without any additional patches, the synaptics fails on
> > > OnePlus 6T with:
> > >
> > > ```
> > > [ 16.620292] rmi4_i2c 12-0020: registering I2C-connected sensor
> > > [ 16.857617] rmi4_f01 rmi4-00.fn01: found RMI device, manufacturer:
> > > Synaptics, product: S3706B, fw id: 2852315
> > > [ 16.867324] rmi4_f12 rmi4-00.fn12: Failed to read the Control Register
> > > Descriptor: -5
> > > [ 16.867338] rmi4_f12 rmi4-00.fn12: probe with driver rmi4_f12 failed with
> > > error -5
> > > ```
> >
> > Yes, at least a98518e72439 "Input: rmi4 - fix register descriptor address
> > calculation" is wrong and needs to be reverted.
>
> That did it for me, now the TS works again.
Thank you for letting me know.
--
Dmitry
^ permalink raw reply
page: | 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