Commit 2df69484 authored by Sebastian Andrzej Siewior's avatar Sebastian Andrzej Siewior Committed by Greg Kroah-Hartman

USB: cdc-wdm: don't enable interrupts in USB-giveback

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.

Defer the error case handling to a workqueue as suggested by Oliver
Neukum. In case of an error the worker performs the read out and wakes
the user.

Fixes: c1da59da ("cdc-wdm: Clear read pipeline in case of error")
Cc: Robert Foss <robert.foss@collabora.com>
Signed-off-by: default avatarSebastian Andrzej Siewior <bigeasy@linutronix.de>
Acked-by: default avatarOliver Neukum <oneukum@suse.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 4327059a
...@@ -96,6 +96,7 @@ struct wdm_device { ...@@ -96,6 +96,7 @@ struct wdm_device {
struct mutex rlock; struct mutex rlock;
wait_queue_head_t wait; wait_queue_head_t wait;
struct work_struct rxwork; struct work_struct rxwork;
struct work_struct service_outs_intr;
int werr; int werr;
int rerr; int rerr;
int resp_count; int resp_count;
...@@ -151,9 +152,6 @@ static void wdm_out_callback(struct urb *urb) ...@@ -151,9 +152,6 @@ static void wdm_out_callback(struct urb *urb)
wake_up(&desc->wait); wake_up(&desc->wait);
} }
/* forward declaration */
static int service_outstanding_interrupt(struct wdm_device *desc);
static void wdm_in_callback(struct urb *urb) static void wdm_in_callback(struct urb *urb)
{ {
struct wdm_device *desc = urb->context; struct wdm_device *desc = urb->context;
...@@ -209,8 +207,6 @@ static void wdm_in_callback(struct urb *urb) ...@@ -209,8 +207,6 @@ static void wdm_in_callback(struct urb *urb)
} }
} }
skip_error: skip_error:
set_bit(WDM_READ, &desc->flags);
wake_up(&desc->wait);
if (desc->rerr) { if (desc->rerr) {
/* /*
...@@ -219,9 +215,11 @@ static void wdm_in_callback(struct urb *urb) ...@@ -219,9 +215,11 @@ static void wdm_in_callback(struct urb *urb)
* We should respond to further attempts from the device to send * We should respond to further attempts from the device to send
* data, so that we can get unstuck. * data, so that we can get unstuck.
*/ */
service_outstanding_interrupt(desc); schedule_work(&desc->service_outs_intr);
} else {
set_bit(WDM_READ, &desc->flags);
wake_up(&desc->wait);
} }
spin_unlock(&desc->iuspin); spin_unlock(&desc->iuspin);
} }
...@@ -758,6 +756,21 @@ static void wdm_rxwork(struct work_struct *work) ...@@ -758,6 +756,21 @@ static void wdm_rxwork(struct work_struct *work)
} }
} }
static void service_interrupt_work(struct work_struct *work)
{
struct wdm_device *desc;
desc = container_of(work, struct wdm_device, service_outs_intr);
spin_lock_irq(&desc->iuspin);
service_outstanding_interrupt(desc);
if (!desc->resp_count) {
set_bit(WDM_READ, &desc->flags);
wake_up(&desc->wait);
}
spin_unlock_irq(&desc->iuspin);
}
/* --- hotplug --- */ /* --- hotplug --- */
static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep,
...@@ -779,6 +792,7 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor ...@@ -779,6 +792,7 @@ static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor
desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber); desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber);
desc->intf = intf; desc->intf = intf;
INIT_WORK(&desc->rxwork, wdm_rxwork); INIT_WORK(&desc->rxwork, wdm_rxwork);
INIT_WORK(&desc->service_outs_intr, service_interrupt_work);
rv = -EINVAL; rv = -EINVAL;
if (!usb_endpoint_is_int_in(ep)) if (!usb_endpoint_is_int_in(ep))
...@@ -964,6 +978,7 @@ static void wdm_disconnect(struct usb_interface *intf) ...@@ -964,6 +978,7 @@ static void wdm_disconnect(struct usb_interface *intf)
mutex_lock(&desc->wlock); mutex_lock(&desc->wlock);
kill_urbs(desc); kill_urbs(desc);
cancel_work_sync(&desc->rxwork); cancel_work_sync(&desc->rxwork);
cancel_work_sync(&desc->service_outs_intr);
mutex_unlock(&desc->wlock); mutex_unlock(&desc->wlock);
mutex_unlock(&desc->rlock); mutex_unlock(&desc->rlock);
...@@ -1006,6 +1021,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) ...@@ -1006,6 +1021,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message)
/* callback submits work - order is essential */ /* callback submits work - order is essential */
kill_urbs(desc); kill_urbs(desc);
cancel_work_sync(&desc->rxwork); cancel_work_sync(&desc->rxwork);
cancel_work_sync(&desc->service_outs_intr);
} }
if (!PMSG_IS_AUTO(message)) { if (!PMSG_IS_AUTO(message)) {
mutex_unlock(&desc->wlock); mutex_unlock(&desc->wlock);
...@@ -1065,6 +1081,7 @@ static int wdm_pre_reset(struct usb_interface *intf) ...@@ -1065,6 +1081,7 @@ static int wdm_pre_reset(struct usb_interface *intf)
mutex_lock(&desc->wlock); mutex_lock(&desc->wlock);
kill_urbs(desc); kill_urbs(desc);
cancel_work_sync(&desc->rxwork); cancel_work_sync(&desc->rxwork);
cancel_work_sync(&desc->service_outs_intr);
return 0; return 0;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment