aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMika Westerberg <mika.westerberg@linux.intel.com>2015-12-21 08:26:31 -0500
committerJiri Kosina <jkosina@suse.cz>2015-12-30 17:56:31 -0500
commit9a327405014f4ef4cdad67a0686db82b9f23c62c (patch)
tree7157f0e5fafa644a7df493e246069248662b6da9
parent2078665a7e12df5d8ab4d254eac69e15959271e6 (diff)
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 <torvalds@linux-foundation.org> Reported-by: Nish Aravamudan <nish.aravamudan@gmail.com> Suggested-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.c21
1 files changed, 18 insertions, 3 deletions
diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
index 10bd8e6e4c9c..fc5ef1c25ed4 100644
--- a/drivers/hid/i2c-hid/i2c-hid.c
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -151,6 +151,7 @@ struct i2c_hid {
151 struct i2c_hid_platform_data pdata; 151 struct i2c_hid_platform_data pdata;
152 152
153 bool irq_wake_enabled; 153 bool irq_wake_enabled;
154 struct mutex reset_lock;
154}; 155};
155 156
156static int __i2c_hid_command(struct i2c_client *client, 157static int __i2c_hid_command(struct i2c_client *client,
@@ -356,9 +357,16 @@ static int i2c_hid_hwreset(struct i2c_client *client)
356 357
357 i2c_hid_dbg(ihid, "%s\n", __func__); 358 i2c_hid_dbg(ihid, "%s\n", __func__);
358 359
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
359 ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); 367 ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
360 if (ret) 368 if (ret)
361 return ret; 369 goto out_unlock;
362 370
363 i2c_hid_dbg(ihid, "resetting...\n"); 371 i2c_hid_dbg(ihid, "resetting...\n");
364 372
@@ -366,10 +374,11 @@ static int i2c_hid_hwreset(struct i2c_client *client)
366 if (ret) { 374 if (ret) {
367 dev_err(&client->dev, "failed to reset device.\n"); 375 dev_err(&client->dev, "failed to reset device.\n");
368 i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); 376 i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
369 return ret;
370 } 377 }
371 378
372 return 0; 379out_unlock:
380 mutex_unlock(&ihid->reset_lock);
381 return ret;
373} 382}
374 383
375static void i2c_hid_get_input(struct i2c_hid *ihid) 384static 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,
587 size_t count, unsigned char report_type, bool use_data) 596 size_t count, unsigned char report_type, bool use_data)
588{ 597{
589 struct i2c_client *client = hid->driver_data; 598 struct i2c_client *client = hid->driver_data;
599 struct i2c_hid *ihid = i2c_get_clientdata(client);
590 int report_id = buf[0]; 600 int report_id = buf[0];
591 int ret; 601 int ret;
592 602
593 if (report_type == HID_INPUT_REPORT) 603 if (report_type == HID_INPUT_REPORT)
594 return -EINVAL; 604 return -EINVAL;
595 605
606 mutex_lock(&ihid->reset_lock);
607
596 if (report_id) { 608 if (report_id) {
597 buf++; 609 buf++;
598 count--; 610 count--;
@@ -605,6 +617,8 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
605 if (report_id && ret >= 0) 617 if (report_id && ret >= 0)
606 ret++; /* add report_id to the number of transfered bytes */ 618 ret++; /* add report_id to the number of transfered bytes */
607 619
620 mutex_unlock(&ihid->reset_lock);
621
608 return ret; 622 return ret;
609} 623}
610 624
@@ -990,6 +1004,7 @@ static int i2c_hid_probe(struct i2c_client *client,
990 ihid->wHIDDescRegister = cpu_to_le16(hidRegister); 1004 ihid->wHIDDescRegister = cpu_to_le16(hidRegister);
991 1005
992 init_waitqueue_head(&ihid->wait); 1006 init_waitqueue_head(&ihid->wait);
1007 mutex_init(&ihid->reset_lock);
993 1008
994 /* we need to allocate the command buffer without knowing the maximum 1009 /* we need to allocate the command buffer without knowing the maximum
995 * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the 1010 * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the