diff options
author | AMAN DEEP <aman.deep@samsung.com> | 2018-02-07 22:55:01 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2018-02-15 12:45:34 -0500 |
commit | 46408ea558df13b110e0866b99624384a33bdeba (patch) | |
tree | 0022901a5c3a6c9f35e8179ad1be00b313f2ec24 | |
parent | 91b119359c1c3033a6621909d3c5dbbdf201d6b4 (diff) |
usb: ohci: Proper handling of ed_rm_list to handle race condition between usb_kill_urb() and finish_unlinks()
There is a race condition between finish_unlinks->finish_urb() function
and usb_kill_urb() in ohci controller case. The finish_urb calls
spin_unlock(&ohci->lock) before usb_hcd_giveback_urb() function call,
then if during this time, usb_kill_urb is called for another endpoint,
then new ed will be added to ed_rm_list at beginning for unlink, and
ed_rm_list will point to newly added.
When finish_urb() is completed in finish_unlinks() and ed->td_list
becomes empty as in below code (in finish_unlinks() function):
if (list_empty(&ed->td_list)) {
*last = ed->ed_next;
ed->ed_next = NULL;
} else if (ohci->rh_state == OHCI_RH_RUNNING) {
*last = ed->ed_next;
ed->ed_next = NULL;
ed_schedule(ohci, ed);
}
The *last = ed->ed_next will make ed_rm_list to point to ed->ed_next
and previously added ed by usb_kill_urb will be left unreferenced by
ed_rm_list. This causes usb_kill_urb() hang forever waiting for
finish_unlink to remove added ed from ed_rm_list.
The main reason for hang in this race condtion is addition and removal
of ed from ed_rm_list in the beginning during usb_kill_urb and later
last* is modified in finish_unlinks().
As suggested by Alan Stern, the solution for proper handling of
ohci->ed_rm_list is to remove ed from the ed_rm_list before finishing
any URBs. Then at the end, we can add ed back to the list if necessary.
This properly handle the updated ohci->ed_rm_list in usb_kill_urb().
Fixes: 977dcfdc6031 ("USB: OHCI: don't lose track of EDs when a controller dies")
Acked-by: Alan Stern <stern@rowland.harvard.edu>
CC: <stable@vger.kernel.org>
Signed-off-by: Aman Deep <aman.deep@samsung.com>
Signed-off-by: Jeffy Chen <jeffy.chen@rock-chips.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/host/ohci-q.c | 17 |
1 files changed, 10 insertions, 7 deletions
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c index b2ec8c399363..4ccb85a67bb3 100644 --- a/drivers/usb/host/ohci-q.c +++ b/drivers/usb/host/ohci-q.c | |||
@@ -1019,6 +1019,8 @@ skip_ed: | |||
1019 | * have modified this list. normally it's just prepending | 1019 | * have modified this list. normally it's just prepending |
1020 | * entries (which we'd ignore), but paranoia won't hurt. | 1020 | * entries (which we'd ignore), but paranoia won't hurt. |
1021 | */ | 1021 | */ |
1022 | *last = ed->ed_next; | ||
1023 | ed->ed_next = NULL; | ||
1022 | modified = 0; | 1024 | modified = 0; |
1023 | 1025 | ||
1024 | /* unlink urbs as requested, but rescan the list after | 1026 | /* unlink urbs as requested, but rescan the list after |
@@ -1077,21 +1079,22 @@ rescan_this: | |||
1077 | goto rescan_this; | 1079 | goto rescan_this; |
1078 | 1080 | ||
1079 | /* | 1081 | /* |
1080 | * If no TDs are queued, take ED off the ed_rm_list. | 1082 | * If no TDs are queued, ED is now idle. |
1081 | * Otherwise, if the HC is running, reschedule. | 1083 | * Otherwise, if the HC is running, reschedule. |
1082 | * If not, leave it on the list for further dequeues. | 1084 | * If the HC isn't running, add ED back to the |
1085 | * start of the list for later processing. | ||
1083 | */ | 1086 | */ |
1084 | if (list_empty(&ed->td_list)) { | 1087 | if (list_empty(&ed->td_list)) { |
1085 | *last = ed->ed_next; | ||
1086 | ed->ed_next = NULL; | ||
1087 | ed->state = ED_IDLE; | 1088 | ed->state = ED_IDLE; |
1088 | list_del(&ed->in_use_list); | 1089 | list_del(&ed->in_use_list); |
1089 | } else if (ohci->rh_state == OHCI_RH_RUNNING) { | 1090 | } else if (ohci->rh_state == OHCI_RH_RUNNING) { |
1090 | *last = ed->ed_next; | ||
1091 | ed->ed_next = NULL; | ||
1092 | ed_schedule(ohci, ed); | 1091 | ed_schedule(ohci, ed); |
1093 | } else { | 1092 | } else { |
1094 | last = &ed->ed_next; | 1093 | ed->ed_next = ohci->ed_rm_list; |
1094 | ohci->ed_rm_list = ed; | ||
1095 | /* Don't loop on the same ED */ | ||
1096 | if (last == &ohci->ed_rm_list) | ||
1097 | last = &ed->ed_next; | ||
1095 | } | 1098 | } |
1096 | 1099 | ||
1097 | if (modified) | 1100 | if (modified) |