Input: evdev - per-client waitgroups
authorKenny Levinsen <kl@kl.wtf>
Mon, 5 Oct 2020 18:15:55 +0000 (11:15 -0700)
committerDmitry Torokhov <dmitry.torokhov@gmail.com>
Wed, 7 Oct 2020 01:34:15 +0000 (18:34 -0700)
All evdev clients share a common waitgroup. On new input events, all
clients waiting on this waitgroup are woken up, even those filtering out
the events, possibly more than once per event. This leads to duplicated
and unwanted wakeups.

Split the shared waitgroup into per-client waitgroups for more
fine-grained wakeups.

Signed-off-by: Kenny Levinsen <kl@kl.wtf>
Link: https://lore.kernel.org/r/20200429184126.2155-1-kl@kl.wtf
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
drivers/input/evdev.c

index e494295..95f9069 100644 (file)
@@ -28,7 +28,6 @@
 struct evdev {
        int open;
        struct input_handle handle;
-       wait_queue_head_t wait;
        struct evdev_client __rcu *grab;
        struct list_head client_list;
        spinlock_t client_lock; /* protects client_list */
@@ -43,6 +42,7 @@ struct evdev_client {
        unsigned int tail;
        unsigned int packet_head; /* [future] position of the first element of next packet */
        spinlock_t buffer_lock; /* protects access to buffer, head and tail */
+       wait_queue_head_t wait;
        struct fasync_struct *fasync;
        struct evdev *evdev;
        struct list_head node;
@@ -245,7 +245,6 @@ static void evdev_pass_values(struct evdev_client *client,
                        const struct input_value *vals, unsigned int count,
                        ktime_t *ev_time)
 {
-       struct evdev *evdev = client->evdev;
        const struct input_value *v;
        struct input_event event;
        struct timespec64 ts;
@@ -282,7 +281,7 @@ static void evdev_pass_values(struct evdev_client *client,
        spin_unlock(&client->buffer_lock);
 
        if (wakeup)
-               wake_up_interruptible_poll(&evdev->wait,
+               wake_up_interruptible_poll(&client->wait,
                        EPOLLIN | EPOLLOUT | EPOLLRDNORM | EPOLLWRNORM);
 }
 
@@ -426,11 +425,11 @@ static void evdev_hangup(struct evdev *evdev)
        struct evdev_client *client;
 
        spin_lock(&evdev->client_lock);
-       list_for_each_entry(client, &evdev->client_list, node)
+       list_for_each_entry(client, &evdev->client_list, node) {
                kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+               wake_up_interruptible_poll(&client->wait, EPOLLHUP | EPOLLERR);
+       }
        spin_unlock(&evdev->client_lock);
-
-       wake_up_interruptible_poll(&evdev->wait, EPOLLHUP | EPOLLERR);
 }
 
 static int evdev_release(struct inode *inode, struct file *file)
@@ -479,6 +478,7 @@ static int evdev_open(struct inode *inode, struct file *file)
        if (!client)
                return -ENOMEM;
 
+       init_waitqueue_head(&client->wait);
        client->bufsize = bufsize;
        spin_lock_init(&client->buffer_lock);
        client->evdev = evdev;
@@ -595,7 +595,7 @@ static ssize_t evdev_read(struct file *file, char __user *buffer,
                        break;
 
                if (!(file->f_flags & O_NONBLOCK)) {
-                       error = wait_event_interruptible(evdev->wait,
+                       error = wait_event_interruptible(client->wait,
                                        client->packet_head != client->tail ||
                                        !evdev->exist || client->revoked);
                        if (error)
@@ -613,7 +613,7 @@ static __poll_t evdev_poll(struct file *file, poll_table *wait)
        struct evdev *evdev = client->evdev;
        __poll_t mask;
 
-       poll_wait(file, &evdev->wait, wait);
+       poll_wait(file, &client->wait, wait);
 
        if (evdev->exist && !client->revoked)
                mask = EPOLLOUT | EPOLLWRNORM;
@@ -946,7 +946,7 @@ static int evdev_revoke(struct evdev *evdev, struct evdev_client *client,
        client->revoked = true;
        evdev_ungrab(evdev, client);
        input_flush_device(&evdev->handle, file);
-       wake_up_interruptible_poll(&evdev->wait, EPOLLHUP | EPOLLERR);
+       wake_up_interruptible_poll(&client->wait, EPOLLHUP | EPOLLERR);
 
        return 0;
 }
@@ -1358,7 +1358,6 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
        INIT_LIST_HEAD(&evdev->client_list);
        spin_lock_init(&evdev->client_lock);
        mutex_init(&evdev->mutex);
-       init_waitqueue_head(&evdev->wait);
        evdev->exist = true;
 
        dev_no = minor;