diff options
author | Sebastian Andrzej Siewior <bigeasy@linutronix.de> | 2018-06-14 12:36:46 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-06-28 06:36:07 -0400 |
commit | 2df6948428542c5a22fbf9c7c36c66ccc9363c7d (patch) | |
tree | bcdf8346983bf0da88cc971d3285ccd4a1df43a2 | |
parent | 4327059a14bb6d9578bfb82a6b7c59b3bbf78335 (diff) |
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: c1da59dad0eb ("cdc-wdm: Clear read pipeline in case of error")
Cc: Robert Foss <robert.foss@collabora.com>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Acked-by: Oliver Neukum <oneukum@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 31 |
1 files changed, 24 insertions, 7 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index a0d284ef3f40..203bbd378858 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c | |||
@@ -96,6 +96,7 @@ struct wdm_device { | |||
96 | struct mutex rlock; | 96 | struct mutex rlock; |
97 | wait_queue_head_t wait; | 97 | wait_queue_head_t wait; |
98 | struct work_struct rxwork; | 98 | struct work_struct rxwork; |
99 | struct work_struct service_outs_intr; | ||
99 | int werr; | 100 | int werr; |
100 | int rerr; | 101 | int rerr; |
101 | int resp_count; | 102 | int resp_count; |
@@ -151,9 +152,6 @@ static void wdm_out_callback(struct urb *urb) | |||
151 | wake_up(&desc->wait); | 152 | wake_up(&desc->wait); |
152 | } | 153 | } |
153 | 154 | ||
154 | /* forward declaration */ | ||
155 | static int service_outstanding_interrupt(struct wdm_device *desc); | ||
156 | |||
157 | static void wdm_in_callback(struct urb *urb) | 155 | static void wdm_in_callback(struct urb *urb) |
158 | { | 156 | { |
159 | struct wdm_device *desc = urb->context; | 157 | struct wdm_device *desc = urb->context; |
@@ -209,8 +207,6 @@ static void wdm_in_callback(struct urb *urb) | |||
209 | } | 207 | } |
210 | } | 208 | } |
211 | skip_error: | 209 | skip_error: |
212 | set_bit(WDM_READ, &desc->flags); | ||
213 | wake_up(&desc->wait); | ||
214 | 210 | ||
215 | if (desc->rerr) { | 211 | if (desc->rerr) { |
216 | /* | 212 | /* |
@@ -219,9 +215,11 @@ skip_error: | |||
219 | * We should respond to further attempts from the device to send | 215 | * We should respond to further attempts from the device to send |
220 | * data, so that we can get unstuck. | 216 | * data, so that we can get unstuck. |
221 | */ | 217 | */ |
222 | service_outstanding_interrupt(desc); | 218 | schedule_work(&desc->service_outs_intr); |
219 | } else { | ||
220 | set_bit(WDM_READ, &desc->flags); | ||
221 | wake_up(&desc->wait); | ||
223 | } | 222 | } |
224 | |||
225 | spin_unlock(&desc->iuspin); | 223 | spin_unlock(&desc->iuspin); |
226 | } | 224 | } |
227 | 225 | ||
@@ -758,6 +756,21 @@ static void wdm_rxwork(struct work_struct *work) | |||
758 | } | 756 | } |
759 | } | 757 | } |
760 | 758 | ||
759 | static void service_interrupt_work(struct work_struct *work) | ||
760 | { | ||
761 | struct wdm_device *desc; | ||
762 | |||
763 | desc = container_of(work, struct wdm_device, service_outs_intr); | ||
764 | |||
765 | spin_lock_irq(&desc->iuspin); | ||
766 | service_outstanding_interrupt(desc); | ||
767 | if (!desc->resp_count) { | ||
768 | set_bit(WDM_READ, &desc->flags); | ||
769 | wake_up(&desc->wait); | ||
770 | } | ||
771 | spin_unlock_irq(&desc->iuspin); | ||
772 | } | ||
773 | |||
761 | /* --- hotplug --- */ | 774 | /* --- hotplug --- */ |
762 | 775 | ||
763 | static int wdm_create(struct usb_interface *intf, struct usb_endpoint_descriptor *ep, | 776 | 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 | desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber); | 792 | desc->inum = cpu_to_le16((u16)intf->cur_altsetting->desc.bInterfaceNumber); |
780 | desc->intf = intf; | 793 | desc->intf = intf; |
781 | INIT_WORK(&desc->rxwork, wdm_rxwork); | 794 | INIT_WORK(&desc->rxwork, wdm_rxwork); |
795 | INIT_WORK(&desc->service_outs_intr, service_interrupt_work); | ||
782 | 796 | ||
783 | rv = -EINVAL; | 797 | rv = -EINVAL; |
784 | if (!usb_endpoint_is_int_in(ep)) | 798 | if (!usb_endpoint_is_int_in(ep)) |
@@ -964,6 +978,7 @@ static void wdm_disconnect(struct usb_interface *intf) | |||
964 | mutex_lock(&desc->wlock); | 978 | mutex_lock(&desc->wlock); |
965 | kill_urbs(desc); | 979 | kill_urbs(desc); |
966 | cancel_work_sync(&desc->rxwork); | 980 | cancel_work_sync(&desc->rxwork); |
981 | cancel_work_sync(&desc->service_outs_intr); | ||
967 | mutex_unlock(&desc->wlock); | 982 | mutex_unlock(&desc->wlock); |
968 | mutex_unlock(&desc->rlock); | 983 | mutex_unlock(&desc->rlock); |
969 | 984 | ||
@@ -1006,6 +1021,7 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) | |||
1006 | /* callback submits work - order is essential */ | 1021 | /* callback submits work - order is essential */ |
1007 | kill_urbs(desc); | 1022 | kill_urbs(desc); |
1008 | cancel_work_sync(&desc->rxwork); | 1023 | cancel_work_sync(&desc->rxwork); |
1024 | cancel_work_sync(&desc->service_outs_intr); | ||
1009 | } | 1025 | } |
1010 | if (!PMSG_IS_AUTO(message)) { | 1026 | if (!PMSG_IS_AUTO(message)) { |
1011 | mutex_unlock(&desc->wlock); | 1027 | mutex_unlock(&desc->wlock); |
@@ -1065,6 +1081,7 @@ static int wdm_pre_reset(struct usb_interface *intf) | |||
1065 | mutex_lock(&desc->wlock); | 1081 | mutex_lock(&desc->wlock); |
1066 | kill_urbs(desc); | 1082 | kill_urbs(desc); |
1067 | cancel_work_sync(&desc->rxwork); | 1083 | cancel_work_sync(&desc->rxwork); |
1084 | cancel_work_sync(&desc->service_outs_intr); | ||
1068 | return 0; | 1085 | return 0; |
1069 | } | 1086 | } |
1070 | 1087 | ||