aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/brcm2708/patches-3.10/0082-dwc_otg-prevent-OOPSes-during-device-disconnects.patch
blob: 129dc0894b4ec2261516ae2314daf1a06f4b6708 (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
135
136
137
138
139
140
141
142
143
From 2e2f1ef719a2fa55fd1d95d9536fa8cca94459a4 Mon Sep 17 00:00:00 2001
From: P33M <P33M@github.com>
Date: Thu, 18 Jul 2013 17:07:26 +0100
Subject: [PATCH 082/196] 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(-)

diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
index af9108c..a1970dc 100644
--- 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_t * 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_t * 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;
 }
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
index 0f72bd5..ad03ff1 100644
--- 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 usb_hcd *hcd,
 #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 usb_hcd *hcd,
 	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 usb_hcd *hcd,
 						    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 usb_hcd *hcd,
 						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;
 }
 
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c
index 8125307..5aed416 100644
--- 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 * qtd, dwc_otg_hcd_urb_t * urb)
  * 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 * qtd,
 			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 * qtd,
 			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;
-- 
1.9.1