usb: gadget: f_hid: wake up readers on disable/unbind
authorPeter Korsgaard <peter@korsgaard.com>
Tue, 18 Mar 2025 15:22:07 +0000 (16:22 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 11 Apr 2025 14:08:33 +0000 (16:08 +0200)
Similar to how it is done in the write path.

Add a disabled flag to track the function state and use it to exit the read
loops to ensure no readers get stuck when the function is disabled/unbound,
protecting against corruption when the waitq and spinlocks are reinitialized
in hidg_bind().

Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
Link: https://lore.kernel.org/r/20250318152207.330997-1-peter@korsgaard.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/gadget/function/f_hid.c

index 740311c4fa2496280fee949755492349ebe74cbb..1bc40fc0ccf77b30129c728fd2bae3c258e21763 100644 (file)
@@ -75,6 +75,7 @@ struct f_hidg {
        /* recv report */
        spinlock_t                      read_spinlock;
        wait_queue_head_t               read_queue;
+       bool                            disabled;
        /* recv report - interrupt out only (use_out_ep == 1) */
        struct list_head                completed_out_req;
        unsigned int                    qlen;
@@ -329,7 +330,7 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,
 
        spin_lock_irqsave(&hidg->read_spinlock, flags);
 
-#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req))
+#define READ_COND_INTOUT (!list_empty(&hidg->completed_out_req) || hidg->disabled)
 
        /* wait for at least one buffer to complete */
        while (!READ_COND_INTOUT) {
@@ -343,6 +344,11 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,
                spin_lock_irqsave(&hidg->read_spinlock, flags);
        }
 
+       if (hidg->disabled) {
+               spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+               return -ESHUTDOWN;
+       }
+
        /* pick the first one */
        list = list_first_entry(&hidg->completed_out_req,
                                struct f_hidg_req_list, list);
@@ -387,7 +393,7 @@ static ssize_t f_hidg_intout_read(struct file *file, char __user *buffer,
        return count;
 }
 
-#define READ_COND_SSREPORT (hidg->set_report_buf != NULL)
+#define READ_COND_SSREPORT (hidg->set_report_buf != NULL || hidg->disabled)
 
 static ssize_t f_hidg_ssreport_read(struct file *file, char __user *buffer,
                                    size_t count, loff_t *ptr)
@@ -1012,6 +1018,11 @@ static void hidg_disable(struct usb_function *f)
        }
        spin_unlock_irqrestore(&hidg->get_report_spinlock, flags);
 
+       spin_lock_irqsave(&hidg->read_spinlock, flags);
+       hidg->disabled = true;
+       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+       wake_up(&hidg->read_queue);
+
        spin_lock_irqsave(&hidg->write_spinlock, flags);
        if (!hidg->write_pending) {
                free_ep_req(hidg->in_ep, hidg->req);
@@ -1097,6 +1108,10 @@ static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
                }
        }
 
+       spin_lock_irqsave(&hidg->read_spinlock, flags);
+       hidg->disabled = false;
+       spin_unlock_irqrestore(&hidg->read_spinlock, flags);
+
        if (hidg->in_ep != NULL) {
                spin_lock_irqsave(&hidg->write_spinlock, flags);
                hidg->req = req_in;