Skip to content

Commit b4ed18a

Browse files
dtorJiri Kosina
authored andcommitted
HID: i2c-hid: ensure various commands do not interfere with each other
i2c-hid uses 2 shared buffers: command and "raw" input buffer for sending requests to peripherals and read data from peripherals when executing variety of commands. Such commands include reading of HID registers, requesting particular power mode, getting and setting reports and so on. Because all such requests use the same 2 buffers they should not execute simultaneously. Fix this by introducing "cmd_lock" mutex and acquire it whenever we needs to access ihid->cmdbuf or idid->rawbuf. Signed-off-by: Dmitry Torokhov <[email protected]> Reviewed-by: Hans de Goede <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 6e44365 commit b4ed18a

File tree

1 file changed

+27
-15
lines changed

1 file changed

+27
-15
lines changed

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

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ struct i2c_hid {
105105

106106
wait_queue_head_t wait; /* For waiting the interrupt */
107107

108+
struct mutex cmd_lock; /* protects cmdbuf and rawbuf */
108109
struct mutex reset_lock;
109110

110111
struct i2chid_ops *ops;
@@ -220,6 +221,8 @@ static int i2c_hid_xfer(struct i2c_hid *ihid,
220221
static int i2c_hid_read_register(struct i2c_hid *ihid, __le16 reg,
221222
void *buf, size_t len)
222223
{
224+
guard(mutex)(&ihid->cmd_lock);
225+
223226
*(__le16 *)ihid->cmdbuf = reg;
224227

225228
return i2c_hid_xfer(ihid, ihid->cmdbuf, sizeof(__le16), buf, len);
@@ -252,6 +255,8 @@ static int i2c_hid_get_report(struct i2c_hid *ihid,
252255

253256
i2c_hid_dbg(ihid, "%s\n", __func__);
254257

258+
guard(mutex)(&ihid->cmd_lock);
259+
255260
/* Command register goes first */
256261
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
257262
length += sizeof(__le16);
@@ -342,6 +347,8 @@ static int i2c_hid_set_or_send_report(struct i2c_hid *ihid,
342347
if (!do_set && le16_to_cpu(ihid->hdesc.wMaxOutputLength) == 0)
343348
return -ENOSYS;
344349

350+
guard(mutex)(&ihid->cmd_lock);
351+
345352
if (do_set) {
346353
/* Command register goes first */
347354
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
@@ -384,6 +391,8 @@ static int i2c_hid_set_power_command(struct i2c_hid *ihid, int power_state)
384391
{
385392
size_t length;
386393

394+
guard(mutex)(&ihid->cmd_lock);
395+
387396
/* SET_POWER uses command register */
388397
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
389398
length = sizeof(__le16);
@@ -440,25 +449,27 @@ static int i2c_hid_start_hwreset(struct i2c_hid *ihid)
440449
if (ret)
441450
return ret;
442451

443-
/* Prepare reset command. Command register goes first. */
444-
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
445-
length += sizeof(__le16);
446-
/* Next is RESET command itself */
447-
length += i2c_hid_encode_command(ihid->cmdbuf + length,
448-
I2C_HID_OPCODE_RESET, 0, 0);
452+
scoped_guard(mutex, &ihid->cmd_lock) {
453+
/* Prepare reset command. Command register goes first. */
454+
*(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister;
455+
length += sizeof(__le16);
456+
/* Next is RESET command itself */
457+
length += i2c_hid_encode_command(ihid->cmdbuf + length,
458+
I2C_HID_OPCODE_RESET, 0, 0);
449459

450-
set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
460+
set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
451461

452-
ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
453-
if (ret) {
454-
dev_err(&ihid->client->dev,
455-
"failed to reset device: %d\n", ret);
456-
goto err_clear_reset;
457-
}
462+
ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0);
463+
if (ret) {
464+
dev_err(&ihid->client->dev,
465+
"failed to reset device: %d\n", ret);
466+
break;
467+
}
458468

459-
return 0;
469+
return 0;
470+
}
460471

461-
err_clear_reset:
472+
/* Clean up if sending reset command failed */
462473
clear_bit(I2C_HID_RESET_PENDING, &ihid->flags);
463474
i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP);
464475
return ret;
@@ -1200,6 +1211,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
12001211
ihid->is_panel_follower = drm_is_panel_follower(&client->dev);
12011212

12021213
init_waitqueue_head(&ihid->wait);
1214+
mutex_init(&ihid->cmd_lock);
12031215
mutex_init(&ihid->reset_lock);
12041216
INIT_WORK(&ihid->panel_follower_prepare_work, ihid_core_panel_prepare_work);
12051217

0 commit comments

Comments
 (0)