Skip to content

Commit

Permalink
HID: hidraw: Replace hidraw device table mutex with a rwsem
Browse files Browse the repository at this point in the history
Currently, the table that stores information about the connected hidraw
devices has a mutex to prevent concurrent hidraw users to manipulate the
hidraw table (e.g. delete an entry) while someone is trying to use
the table (e.g. issuing an ioctl to the device), preventing the kernel
to referencing a NULL pointer. However, since that every user that wants
to access the table for both manipulating it and reading it content,
this prevents concurrent access to the table for read-only operations
for different or the same device (e.g. two hidraw ioctls can't happen at
the same time, even if they are completely unrelated).

This proves to be a bottleneck and gives performance issues when using
multiple HID devices at same time, like VR kits where one can have two
controllers, the headset and some tracking sensors.

To improve the performance, replace the table mutex with a read-write
semaphore, enabling multiple threads to issue parallel syscalls to
multiple devices at the same time while protecting the table for
concurrent modifications.

Signed-off-by: André Almeida <andrealmeid@collabora.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Link: https://lore.kernel.org/r/20211130132957.8480-2-andrealmeid@collabora.com
  • Loading branch information
André Almeida authored and Benjamin Tissoires committed Dec 14, 2021
1 parent 03090cc commit 8590222
Showing 1 changed file with 17 additions and 17 deletions.
34 changes: 17 additions & 17 deletions drivers/hid/hidraw.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static int hidraw_major;
static struct cdev hidraw_cdev;
static struct class *hidraw_class;
static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES];
static DEFINE_MUTEX(minors_lock);
static DECLARE_RWSEM(minors_rwsem);

static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
Expand Down Expand Up @@ -107,7 +107,7 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
__u8 *buf;
int ret = 0;

lockdep_assert_held(&minors_lock);
lockdep_assert_held(&minors_rwsem);

if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
ret = -ENODEV;
Expand Down Expand Up @@ -160,9 +160,9 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
ssize_t ret;
mutex_lock(&minors_lock);
down_read(&minors_rwsem);
ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
mutex_unlock(&minors_lock);
up_read(&minors_rwsem);
return ret;
}

Expand All @@ -182,7 +182,7 @@ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t
int ret = 0, len;
unsigned char report_number;

lockdep_assert_held(&minors_lock);
lockdep_assert_held(&minors_rwsem);

if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
ret = -ENODEV;
Expand Down Expand Up @@ -272,7 +272,7 @@ static int hidraw_open(struct inode *inode, struct file *file)
goto out;
}

mutex_lock(&minors_lock);
down_read(&minors_rwsem);
if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
err = -ENODEV;
goto out_unlock;
Expand Down Expand Up @@ -301,7 +301,7 @@ static int hidraw_open(struct inode *inode, struct file *file)
spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
file->private_data = list;
out_unlock:
mutex_unlock(&minors_lock);
up_read(&minors_rwsem);
out:
if (err < 0)
kfree(list);
Expand Down Expand Up @@ -347,7 +347,7 @@ static int hidraw_release(struct inode * inode, struct file * file)
struct hidraw_list *list = file->private_data;
unsigned long flags;

mutex_lock(&minors_lock);
down_write(&minors_rwsem);

spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
list_del(&list->node);
Expand All @@ -356,7 +356,7 @@ static int hidraw_release(struct inode * inode, struct file * file)

drop_ref(hidraw_table[minor], 0);

mutex_unlock(&minors_lock);
up_write(&minors_rwsem);
return 0;
}

Expand All @@ -369,7 +369,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
struct hidraw *dev;
void __user *user_arg = (void __user*) arg;

mutex_lock(&minors_lock);
down_read(&minors_rwsem);
dev = hidraw_table[minor];
if (!dev || !dev->exist) {
ret = -ENODEV;
Expand Down Expand Up @@ -487,7 +487,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
ret = -ENOTTY;
}
out:
mutex_unlock(&minors_lock);
up_read(&minors_rwsem);
return ret;
}

Expand Down Expand Up @@ -546,7 +546,7 @@ int hidraw_connect(struct hid_device *hid)

result = -EINVAL;

mutex_lock(&minors_lock);
down_write(&minors_rwsem);

for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) {
if (hidraw_table[minor])
Expand All @@ -557,7 +557,7 @@ int hidraw_connect(struct hid_device *hid)
}

if (result) {
mutex_unlock(&minors_lock);
up_write(&minors_rwsem);
kfree(dev);
goto out;
}
Expand All @@ -567,7 +567,7 @@ int hidraw_connect(struct hid_device *hid)

if (IS_ERR(dev->dev)) {
hidraw_table[minor] = NULL;
mutex_unlock(&minors_lock);
up_write(&minors_rwsem);
result = PTR_ERR(dev->dev);
kfree(dev);
goto out;
Expand All @@ -583,7 +583,7 @@ int hidraw_connect(struct hid_device *hid)
dev->exist = 1;
hid->hidraw = dev;

mutex_unlock(&minors_lock);
up_write(&minors_rwsem);
out:
return result;

Expand All @@ -594,11 +594,11 @@ void hidraw_disconnect(struct hid_device *hid)
{
struct hidraw *hidraw = hid->hidraw;

mutex_lock(&minors_lock);
down_write(&minors_rwsem);

drop_ref(hidraw, 1);

mutex_unlock(&minors_lock);
up_write(&minors_rwsem);
}
EXPORT_SYMBOL_GPL(hidraw_disconnect);

Expand Down

0 comments on commit 8590222

Please sign in to comment.