* [PATCH] HID: i2c-hid: add quirk to send initial output reports for Acer SFA14-I31
@ 2026-10-02 18:37 ` DMS
0 siblings, 0 replies; 3+ messages in thread
From: DMS via B4 Relay @ 2026-10-02 18:37 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel, DMS
From: DMS <dms3studio@gmail.com>
The internal keyboard of the Acer Swift Air 14 (SFA14-I31, ACPI
1025174B, HID 049f:5343) does not send any input reports after probe.
It stays silent, and its GPIO interrupt never fires, until the host has
written an output report to it at least once.
The input core only forwards LED state to the device when it changes.
All LEDs start off, so no output report is written at boot and the
keyboard is unusable until the first suspend/resume cycle, where
input_dev_toggle() re-sends the LED state unconditionally. Closing and
opening the lid is the only way to get a working keyboard.
Add I2C_HID_QUIRK_INIT_OUTPUT_REPORTS, which sends every output report
once with its initial field values right after the HID device has been
added, and enable it for this keyboard.
Tested on an SFA14-I31 (BIOS V1.01) with a 7.2.8 kernel and this change
applied to i2c-hid as a module: the keyboard works from boot without a
suspend/resume cycle, and suspend/resume keeps working. Without it,
writing the LED output report by hand (i2ctransfer) or toggling the
capslock LED through sysfs has the same effect, while resetting or
re-powering the device does not. This version, rebased on hid.git
for-next, was build-tested only.
Assisted-by: LLM
Signed-off-by: DMS <dms3studio@gmail.com>
---
This was debugged and written with the help of an AI coding assistant
(Claude), working over SSH on the affected laptop. It counted keyboard
interrupts, traced i2c-hid with dynamic debug during a pm_test=devices
cycle, and replayed raw I2C transfers to isolate the LED output report
as the trigger. Rebinding i2c-hid (which resets the device), rebinding
the I2C controller and manual SET_POWER commands were ruled out. I
reviewed the change and tested it on my machine as described in the
commit message.
If you would rather handle this generically, e.g. by syncing LED state
when hid-input connects, I am happy to test that instead.
---
drivers/hid/hid-ids.h | 3 +++
drivers/hid/i2c-hid/i2c-hid-core.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index a9537b7bb..5e6096caf 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -39,6 +39,9 @@
#define USB_DEVICE_ID_ACECAD_FLAIR 0x0004
#define USB_DEVICE_ID_ACECAD_302 0x0008
+#define I2C_VENDOR_ID_ACER_KBD 0x049f
+#define I2C_PRODUCT_ID_ACER_SFA14_KBD 0x5343
+
#define USB_VENDOR_ID_ACRUX 0x1a34
#define USB_VENDOR_ID_ACTIONSTAR 0x2101
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index a8a985485..f54671092 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -52,6 +52,7 @@
#define I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND BIT(5)
#define I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME BIT(6)
#define I2C_HID_QUIRK_RE_POWER_ON BIT(7)
+#define I2C_HID_QUIRK_INIT_OUTPUT_REPORTS BIT(8)
/* Command opcodes */
#define I2C_HID_OPCODE_RESET 0x01
@@ -155,6 +156,12 @@ static const struct i2c_hid_quirks {
I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME },
{ I2C_VENDOR_ID_BLTP, I2C_PRODUCT_ID_BLTP7853,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
+ /*
+ * The Acer Swift SFA14-I31 keyboard sends no input until the host
+ * has written an output (LED) report at least once
+ */
+ { I2C_VENDOR_ID_ACER_KBD, I2C_PRODUCT_ID_ACER_SFA14_KBD,
+ I2C_HID_QUIRK_INIT_OUTPUT_REPORTS },
{ 0, 0 }
};
@@ -1079,6 +1086,32 @@ static int __i2c_hid_core_probe(struct i2c_hid *ihid)
return 0;
}
+/*
+ * The input core only writes LED state when it changes (and on resume), so
+ * a device that waits for an output report never gets one at boot. Send
+ * every output report once with its initial field values.
+ */
+static void i2c_hid_send_initial_output_reports(struct hid_device *hid)
+{
+ struct hid_report_enum *report_enum = &hid->report_enum[HID_OUTPUT_REPORT];
+ struct hid_report *report;
+ u8 *buf;
+ int ret;
+
+ list_for_each_entry(report, &report_enum->report_list, list) {
+ buf = hid_alloc_report_buf(report, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ hid_output_report(report, buf);
+ ret = i2c_hid_output_report(hid, buf, hid_report_len(report));
+ if (ret < 0)
+ hid_warn(hid, "failed to send initial output report %u: %d\n",
+ report->id, ret);
+ kfree(buf);
+ }
+}
+
static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
{
struct i2c_client *client = ihid->client;
@@ -1099,6 +1132,9 @@ static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
if (ihid->quirks & I2C_HID_QUIRK_RE_POWER_ON)
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
+ if (ihid->quirks & I2C_HID_QUIRK_INIT_OUTPUT_REPORTS)
+ i2c_hid_send_initial_output_reports(hid);
+
return ret;
}
---
base-commit: 145c2b2e9a5c0f794fb4009bcb072ab19f8ccfcd
change-id: 20261002-swift-sfa14-kbd-1afed65ef562
Best regards,
--
DMS <dms3studio@gmail.com>
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH] HID: i2c-hid: add quirk to send initial output reports for Acer SFA14-I31
@ 2026-10-02 18:37 ` DMS
0 siblings, 0 replies; 3+ messages in thread
From: DMS @ 2026-10-02 18:37 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires; +Cc: linux-input, linux-kernel, DMS
The internal keyboard of the Acer Swift Air 14 (SFA14-I31, ACPI
1025174B, HID 049f:5343) does not send any input reports after probe.
It stays silent, and its GPIO interrupt never fires, until the host has
written an output report to it at least once.
The input core only forwards LED state to the device when it changes.
All LEDs start off, so no output report is written at boot and the
keyboard is unusable until the first suspend/resume cycle, where
input_dev_toggle() re-sends the LED state unconditionally. Closing and
opening the lid is the only way to get a working keyboard.
Add I2C_HID_QUIRK_INIT_OUTPUT_REPORTS, which sends every output report
once with its initial field values right after the HID device has been
added, and enable it for this keyboard.
Tested on an SFA14-I31 (BIOS V1.01) with a 7.2.8 kernel and this change
applied to i2c-hid as a module: the keyboard works from boot without a
suspend/resume cycle, and suspend/resume keeps working. Without it,
writing the LED output report by hand (i2ctransfer) or toggling the
capslock LED through sysfs has the same effect, while resetting or
re-powering the device does not. This version, rebased on hid.git
for-next, was build-tested only.
Assisted-by: LLM
Signed-off-by: DMS <dms3studio@gmail.com>
---
This was debugged and written with the help of an AI coding assistant
(Claude), working over SSH on the affected laptop. It counted keyboard
interrupts, traced i2c-hid with dynamic debug during a pm_test=devices
cycle, and replayed raw I2C transfers to isolate the LED output report
as the trigger. Rebinding i2c-hid (which resets the device), rebinding
the I2C controller and manual SET_POWER commands were ruled out. I
reviewed the change and tested it on my machine as described in the
commit message.
If you would rather handle this generically, e.g. by syncing LED state
when hid-input connects, I am happy to test that instead.
---
drivers/hid/hid-ids.h | 3 +++
drivers/hid/i2c-hid/i2c-hid-core.c | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index a9537b7bb..5e6096caf 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -39,6 +39,9 @@
#define USB_DEVICE_ID_ACECAD_FLAIR 0x0004
#define USB_DEVICE_ID_ACECAD_302 0x0008
+#define I2C_VENDOR_ID_ACER_KBD 0x049f
+#define I2C_PRODUCT_ID_ACER_SFA14_KBD 0x5343
+
#define USB_VENDOR_ID_ACRUX 0x1a34
#define USB_VENDOR_ID_ACTIONSTAR 0x2101
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index a8a985485..f54671092 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -52,6 +52,7 @@
#define I2C_HID_QUIRK_NO_SLEEP_ON_SUSPEND BIT(5)
#define I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME BIT(6)
#define I2C_HID_QUIRK_RE_POWER_ON BIT(7)
+#define I2C_HID_QUIRK_INIT_OUTPUT_REPORTS BIT(8)
/* Command opcodes */
#define I2C_HID_OPCODE_RESET 0x01
@@ -155,6 +156,12 @@ static const struct i2c_hid_quirks {
I2C_HID_QUIRK_DELAY_WAKEUP_AFTER_RESUME },
{ I2C_VENDOR_ID_BLTP, I2C_PRODUCT_ID_BLTP7853,
I2C_HID_QUIRK_NO_IRQ_AFTER_RESET },
+ /*
+ * The Acer Swift SFA14-I31 keyboard sends no input until the host
+ * has written an output (LED) report at least once
+ */
+ { I2C_VENDOR_ID_ACER_KBD, I2C_PRODUCT_ID_ACER_SFA14_KBD,
+ I2C_HID_QUIRK_INIT_OUTPUT_REPORTS },
{ 0, 0 }
};
@@ -1079,6 +1086,32 @@ static int __i2c_hid_core_probe(struct i2c_hid *ihid)
return 0;
}
+/*
+ * The input core only writes LED state when it changes (and on resume), so
+ * a device that waits for an output report never gets one at boot. Send
+ * every output report once with its initial field values.
+ */
+static void i2c_hid_send_initial_output_reports(struct hid_device *hid)
+{
+ struct hid_report_enum *report_enum = &hid->report_enum[HID_OUTPUT_REPORT];
+ struct hid_report *report;
+ u8 *buf;
+ int ret;
+
+ list_for_each_entry(report, &report_enum->report_list, list) {
+ buf = hid_alloc_report_buf(report, GFP_KERNEL);
+ if (!buf)
+ return;
+
+ hid_output_report(report, buf);
+ ret = i2c_hid_output_report(hid, buf, hid_report_len(report));
+ if (ret < 0)
+ hid_warn(hid, "failed to send initial output report %u: %d\n",
+ report->id, ret);
+ kfree(buf);
+ }
+}
+
static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
{
struct i2c_client *client = ihid->client;
@@ -1099,6 +1132,9 @@ static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
if (ihid->quirks & I2C_HID_QUIRK_RE_POWER_ON)
ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
+ if (ihid->quirks & I2C_HID_QUIRK_INIT_OUTPUT_REPORTS)
+ i2c_hid_send_initial_output_reports(hid);
+
return ret;
}
---
base-commit: 145c2b2e9a5c0f794fb4009bcb072ab19f8ccfcd
change-id: 20261002-swift-sfa14-kbd-1afed65ef562
Best regards,
--
DMS <dms3studio@gmail.com>
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH] HID: i2c-hid: add quirk to send initial output reports for Acer SFA14-I31
2026-10-02 18:37 ` DMS
(?)
@ 2026-10-03 1:33 ` sashiko-bot
-1 siblings, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-10-03 1:33 UTC (permalink / raw)
To: DMS; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] hid: i2c-hid: silent quirk failure due to dependency on synchronous driver binding
- [High] hid: i2c-hid: unprotected report_list traversal causes data race and UAF
--
commit 8bcb0b187653aa75d332fb5ab3f4c9d75928faf8
Author: DMS <dms3studio@gmail.com>
HID: i2c-hid: add quirk to send initial output reports for Acer SFA14-I31
The internal keyboard of the Acer Swift Air 14 (SFA14-I31, ACPI
1025174B, HID 049f:5343) does not send any input reports after probe.
It stays silent, and its GPIO interrupt never fires, until the host has
written an output report to it at least once.
Add I2C_HID_QUIRK_INIT_OUTPUT_REPORTS, which sends every output report
once with its initial field values right after the HID device has been
added, and enable it for this keyboard.
> diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
> index a8a9854854f52..f546710925e64 100644
> --- a/drivers/hid/i2c-hid/i2c-hid-core.c
> +++ b/drivers/hid/i2c-hid/i2c-hid-core.c
[ ... ]
> @@ -1079,6 +1086,32 @@ static int __i2c_hid_core_probe(struct i2c_hid *ihid)
> return 0;
> }
>
> +/*
> + * The input core only writes LED state when it changes (and on resume), so
> + * a device that waits for an output report never gets one at boot. Send
> + * every output report once with its initial field values.
> + */
> +static void i2c_hid_send_initial_output_reports(struct hid_device *hid)
> +{
> + struct hid_report_enum *report_enum = &hid->report_enum[HID_OUTPUT_REPORT];
> + struct hid_report *report;
> + u8 *buf;
> + int ret;
> +
> + list_for_each_entry(report, &report_enum->report_list, list) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
Does this unsynchronized iteration risk a data race or use-after-free?
Because hid_add_device() has already exposed the device to the driver core
and userspace, the high-level driver could be binding asynchronously and
mutating the list via hid_register_report(), or unbinding and freeing the
reports concurrently via hid_close_report().
[ ... ]
> @@ -1099,6 +1132,9 @@ static int i2c_hid_core_register_hid(struct i2c_hid *ihid)
> if (ihid->quirks & I2C_HID_QUIRK_RE_POWER_ON)
> ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON);
>
> + if (ihid->quirks & I2C_HID_QUIRK_INIT_OUTPUT_REPORTS)
> + i2c_hid_send_initial_output_reports(hid);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
Will this reliably send the initial reports?
Because this executes immediately after hid_add_device() registers the
device, reports are not parsed and added to report_list yet. The list is
only populated when the high-level driver (e.g. hid-generic) probes and
calls hid_parse().
If the high-level driver binds asynchronously or is loaded as a module
after i2c-hid, i2c_hid_send_initial_output_reports() will iterate over an
empty list and silently do nothing, leaving the keyboard unresponsive.
> +
> return ret;
> }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261002-swift-sfa14-kbd-v1-1-ad312bca1076@gmail.com?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-03 1:33 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-02 18:37 [PATCH] HID: i2c-hid: add quirk to send initial output reports for Acer SFA14-I31 DMS via B4 Relay
2026-10-02 18:37 ` DMS
2026-10-03 1:33 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.