USB: cdc-wdm: don't enable interrupts in USB-giveback
From: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Date: 2018-06-12 16:31:32
Subsystem:
the rest, usb subsystem · Maintainers:
Linus Torvalds, Greg Kroah-Hartman
In the code path
__usb_hcd_giveback_urb()
-> wdm_in_callback()
-> service_outstanding_interrupt()
The function service_outstanding_interrupt() will unconditionally enable
interrupts during unlock and invoke usb_submit_urb() with GFP_KERNEL.
If the HCD completes in BH (like ehci does) then the context remains
atomic due local_bh_disable() and enabling interrupts does not change
this.
Add an argument to service_outstanding_interrupt() which decides
whether or not it is save to enable interrupts during unlocking and use
GFP_KERNEL or not.
Fixes: c1da59dad0eb ("cdc-wdm: Clear read pipeline in case of error")
Cc: Robert Foss <redacted>
Cc: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
drivers/usb/class/cdc-wdm.c | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c
index a0d284ef3f40..45076b9c7c5e 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c@@ -152,7 +152,7 @@ static void wdm_out_callback(struct urb *urb) } /* forward declaration */ -static int service_outstanding_interrupt(struct wdm_device *desc); +static int service_outstanding_interrupt(struct wdm_device *desc, bool en_irq); static void wdm_in_callback(struct urb *urb) {
@@ -219,7 +219,7 @@ static void wdm_in_callback(struct urb *urb) * We should respond to further attempts from the device to send * data, so that we can get unstuck. */ - service_outstanding_interrupt(desc); + service_outstanding_interrupt(desc, false); } spin_unlock(&desc->iuspin);
@@ -448,7 +448,7 @@ static ssize_t wdm_write * * Called with desc->iuspin locked */ -static int service_outstanding_interrupt(struct wdm_device *desc) +static int service_outstanding_interrupt(struct wdm_device *desc, bool en_irq) { int rv = 0;
@@ -457,9 +457,15 @@ static int service_outstanding_interrupt(struct wdm_device *desc) goto out; set_bit(WDM_RESPONDING, &desc->flags); - spin_unlock_irq(&desc->iuspin); - rv = usb_submit_urb(desc->response, GFP_KERNEL); - spin_lock_irq(&desc->iuspin); + if (en_irq) { + spin_unlock_irq(&desc->iuspin); + rv = usb_submit_urb(desc->response, GFP_KERNEL); + spin_lock_irq(&desc->iuspin); + } else { + spin_unlock(&desc->iuspin); + rv = usb_submit_urb(desc->response, GFP_ATOMIC); + spin_lock(&desc->iuspin); + } if (rv) { dev_err(&desc->intf->dev, "usb_submit_urb failed with result %d\n", rv);
@@ -544,7 +550,7 @@ static ssize_t wdm_read if (!desc->reslength) { /* zero length read */ dev_dbg(&desc->intf->dev, "zero length - clearing WDM_READ\n"); clear_bit(WDM_READ, &desc->flags); - rv = service_outstanding_interrupt(desc); + rv = service_outstanding_interrupt(desc, true); spin_unlock_irq(&desc->iuspin); if (rv < 0) goto err;
@@ -571,7 +577,7 @@ static ssize_t wdm_read /* in case we had outstanding data */ if (!desc->length) { clear_bit(WDM_READ, &desc->flags); - service_outstanding_interrupt(desc); + service_outstanding_interrupt(desc, true); } spin_unlock_irq(&desc->iuspin); rv = cntr;