Skip to content

Commit 9a32740

Browse files
westeriJiri Kosina
authored andcommitted
HID: i2c-hid: Prevent sending reports from racing with device reset
When an i2c-hid device is resumed from system sleep the driver resets the device to be sure it is in known state. The device is expected to issue an interrupt when reset is complete. This reset might take few milliseconds to complete so if the HID driver on top (hid-rmi) starts to set up the device by sending feature reports etc. the device might not issue the reset complete interrupt anymore. Below is what happens to touchpad on Lenovo Yoga 900 during resume from system sleep: [ 24.790951] i2c_hid i2c-SYNA2B29:00: i2c_hid_hwreset [ 24.790973] i2c_hid i2c-SYNA2B29:00: i2c_hid_set_power [ 24.790982] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: cmd=22 00 00 08 [ 24.793011] i2c_hid i2c-SYNA2B29:00: resetting... [ 24.793016] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: cmd=22 00 00 01 Here i2c-hid sends reset command to the touchpad. [ 24.794012] i2c_hid i2c-SYNA2B29:00: input: 06 00 01 00 00 00 [ 24.794051] i2c_hid i2c-SYNA2B29:00: i2c_hid_set_or_send_report [ 24.794059] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: cmd=22 00 3f 03 0f 23 00 04 00 0f 01 Now hid-rmi puts the touchpad to correct mode by sending it a feature report. This makes the touchpad not to issue reset complete interrupt. [ 24.796092] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: waiting... i2c-hid starts to wait for the reset interrupt to trigger which never happens. [ 24.798304] i2c_hid i2c-SYNA2B29:00: i2c_hid_set_or_send_report [ 24.798313] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: cmd=25 00 17 00 09 01 42 00 2e 00 19 19 00 10 cc 06 74 04 0f 19 00 00 00 00 00 Yet another output report from hid-rmi driver. [ 29.795630] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: finished. [ 29.795637] i2c_hid i2c-SYNA2B29:00: failed to reset device. After 5 seconds i2c-hid driver times out. [ 29.795642] i2c_hid i2c-SYNA2B29:00: i2c_hid_set_power [ 29.795649] i2c_hid i2c-SYNA2B29:00: __i2c_hid_command: cmd=22 00 01 08 [ 29.797576] dpm_run_callback(): i2c_hid_resume+0x0/0xb0 returns -61 [ 29.797584] PM: Device i2c-SYNA2B29:00 failed to resume: error -61 After this the touchpad does not work anymore (and also resume itself gets slowed down because of the timeout). Prevent sending of feature/output reports while the device is being reset by adding a mutex which is held during that time. Reported-and-tested-by: Linus Torvalds <[email protected]> Reported-by: Nish Aravamudan <[email protected]> Suggested-by: Benjamin Tissoires <[email protected]> Reviewed-by: Benjamin Tissoires <[email protected]> Signed-off-by: Mika Westerberg <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 2078665 commit 9a32740

File tree

1 file changed

+18
-3
lines changed

1 file changed

+18
-3
lines changed

drivers/hid/i2c-hid/i2c-hid.c

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ struct i2c_hid {
151151
struct i2c_hid_platform_data pdata;
152152

153153
bool irq_wake_enabled;
154+
struct mutex reset_lock;
154155
};
155156

156157
static int __i2c_hid_command(struct i2c_client *client,
@@ -356,20 +357,28 @@ static int i2c_hid_hwreset(struct i2c_client *client)
356357

357358
i2c_hid_dbg(ihid, "%s\n", __func__);
358359

360+
/*
361+
* This prevents sending feature reports while the device is
362+
* being reset. Otherwise we may lose the reset complete
363+
* interrupt.
364+
*/
365+
mutex_lock(&ihid->reset_lock);
366+
359367
ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
360368
if (ret)
361-
return ret;
369+
goto out_unlock;
362370

363371
i2c_hid_dbg(ihid, "resetting...\n");
364372

365373
ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0);
366374
if (ret) {
367375
dev_err(&client->dev, "failed to reset device.\n");
368376
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
369-
return ret;
370377
}
371378

372-
return 0;
379+
out_unlock:
380+
mutex_unlock(&ihid->reset_lock);
381+
return ret;
373382
}
374383

375384
static void i2c_hid_get_input(struct i2c_hid *ihid)
@@ -587,12 +596,15 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
587596
size_t count, unsigned char report_type, bool use_data)
588597
{
589598
struct i2c_client *client = hid->driver_data;
599+
struct i2c_hid *ihid = i2c_get_clientdata(client);
590600
int report_id = buf[0];
591601
int ret;
592602

593603
if (report_type == HID_INPUT_REPORT)
594604
return -EINVAL;
595605

606+
mutex_lock(&ihid->reset_lock);
607+
596608
if (report_id) {
597609
buf++;
598610
count--;
@@ -605,6 +617,8 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
605617
if (report_id && ret >= 0)
606618
ret++; /* add report_id to the number of transfered bytes */
607619

620+
mutex_unlock(&ihid->reset_lock);
621+
608622
return ret;
609623
}
610624

@@ -990,6 +1004,7 @@ static int i2c_hid_probe(struct i2c_client *client,
9901004
ihid->wHIDDescRegister = cpu_to_le16(hidRegister);
9911005

9921006
init_waitqueue_head(&ihid->wait);
1007+
mutex_init(&ihid->reset_lock);
9931008

9941009
/* we need to allocate the command buffer without knowing the maximum
9951010
* size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the

0 commit comments

Comments
 (0)