summaryrefslogtreecommitdiff
path: root/target/linux/brcm2708/patches-3.10/0082-dwc_otg-prevent-OOPSes-during-device-disconnects.patch
blob: 5cfc919344947837e3b8bb3e686024230d582498 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
From dc570a70493daf0ec548ff57f1a1a9fb31caccb7 Mon Sep 17 00:00:00 2001
From: P33M <P33M@github.com>
Date: Thu, 18 Jul 2013 17:07:26 +0100
Subject: [PATCH 082/174] dwc_otg: prevent OOPSes during device disconnects

The dwc_otg_urb_enqueue function is thread-unsafe. In particular the
access of urb->hcpriv, usb_hcd_link_urb_to_ep, dwc_otg_urb->qtd and
friends does not occur within a critical section and so if a device
was unplugged during activity there was a high chance that the
usbcore hub_thread would try to disable the endpoint with partially-
formed entries in the URB queue. This would result in BUG() or null
pointer dereferences.

Fix so that access of urb->hcpriv, enqueuing to the hardware and
adding to usbcore endpoint URB lists is contained within a single
critical section.
---
 drivers/usb/host/dwc_otg/dwc_otg_hcd.c       |  3 ---
 drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c | 14 +++++---------
 drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c |  6 +-----
 3 files changed, 6 insertions(+), 17 deletions(-)

--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
@@ -464,7 +464,6 @@ int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_
 			    dwc_otg_hcd_urb_t * dwc_otg_urb, void **ep_handle,
 			    int atomic_alloc)
 {
-	dwc_irqflags_t flags;
 	int retval = 0;
 	uint8_t needs_scheduling = 0;
 	dwc_otg_transaction_type_e tr_type;
@@ -515,12 +514,10 @@ int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_
 	}
 
 	if(needs_scheduling) {
-		DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
 		tr_type = dwc_otg_hcd_select_transactions(hcd);
 		if (tr_type != DWC_OTG_TRANSACTION_NONE) {
 			dwc_otg_hcd_queue_transactions(hcd, tr_type);
 		}
-		DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
 	}
 	return retval;
 }
--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
@@ -679,9 +679,7 @@ static int dwc_otg_urb_enqueue(struct us
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
 	struct usb_host_endpoint *ep = urb->ep;
 #endif
-#if USB_URB_EP_LINKING
       	dwc_irqflags_t irqflags;
-#endif
         void **ref_ep_hcpriv = &ep->hcpriv;
 	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
 	dwc_otg_hcd_urb_t *dwc_otg_urb;
@@ -733,7 +731,6 @@ static int dwc_otg_urb_enqueue(struct us
 	if(dwc_otg_urb == NULL)
 		return -ENOMEM;
 
-	urb->hcpriv = dwc_otg_urb;
 	if (!dwc_otg_urb && urb->number_of_packets)
 		return -ENOMEM;
 
@@ -775,10 +772,10 @@ static int dwc_otg_urb_enqueue(struct us
 						    iso_frame_desc[i].length);
 	}
 
-#if USB_URB_EP_LINKING
 	DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags);
+	urb->hcpriv = dwc_otg_urb;
+#if USB_URB_EP_LINKING
 	retval = usb_hcd_link_urb_to_ep(hcd, urb);
-	DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags);
 	if (0 == retval)
 #endif
 	{
@@ -794,17 +791,16 @@ static int dwc_otg_urb_enqueue(struct us
 						urb);
 			}
 		} else {
-#if USB_URB_EP_LINKING
-			dwc_irqflags_t irqflags;
 			DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval);
-			DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags);
+#if USB_URB_EP_LINKING
 			usb_hcd_unlink_urb_from_ep(hcd, urb);
-			DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags);
 #endif
+			urb->hcpriv = NULL;
 			if (retval == -DWC_E_NO_DEVICE)
 				retval = -ENODEV;
 		}
 	}
+	DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags);
 	return retval;
 }
 
--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c
@@ -919,6 +919,7 @@ void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t
  * QH to place the QTD into.  If it does not find a QH, then it will create a
  * new QH. If the QH to which the QTD is added is not currently scheduled, it
  * is placed into the proper schedule based on its EP type.
+ * HCD lock must be held and interrupts must be disabled on entry
  *
  * @param[in] qtd The QTD to add
  * @param[in] hcd The DWC HCD structure
@@ -931,8 +932,6 @@ int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t *
 			dwc_otg_hcd_t * hcd, dwc_otg_qh_t ** qh, int atomic_alloc)
 {
 	int retval = 0;
-	dwc_irqflags_t flags;
-
 	dwc_otg_hcd_urb_t *urb = qtd->urb;
 
 	/*
@@ -946,15 +945,12 @@ int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t *
 			goto done;
 		}
 	}
-	DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
 	retval = dwc_otg_hcd_qh_add(hcd, *qh);
 	if (retval == 0) {
 		DWC_CIRCLEQ_INSERT_TAIL(&((*qh)->qtd_list), qtd,
 					qtd_list_entry);
 		qtd->qh = *qh;
 	}
-	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
-
 done:
 
 	return retval;