aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/brcm2708/patches-3.18/0092-dwc_otg-introduce-fiq_fsm_spin-un-lock.patch
blob: 19bb50a17cffb0203cdca49e24bf042eec82e02e (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
From 424f79f35a94611f73182f19a7711174b756b052 Mon Sep 17 00:00:00 2001
From: P33M <P33M@github.com>
Date: Fri, 26 Sep 2014 11:32:09 +0100
Subject: [PATCH 092/114] dwc_otg: introduce fiq_fsm_spin(un|)lock()

SMP safety for the FIQ relies on register read-modify write cycles being
completed in the correct order. Several places in the DWC code modify
registers also touched by the FIQ. Protect these by a bare-bones lock
mechanism.

This also makes it possible to run the FIQ and IRQ handlers on different
cores.
---
 .../usb/host/dwc_common_port/dwc_common_linux.c    |  6 ---
 drivers/usb/host/dwc_otg/dwc_otg_cil.c             | 10 -----
 drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c         | 46 +++++++++++++++++++++-
 drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h         | 16 +++++++-
 drivers/usb/host/dwc_otg/dwc_otg_hcd.c             | 23 ++++++++++-
 drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c        |  9 ++++-
 6 files changed, 88 insertions(+), 22 deletions(-)

--- a/drivers/usb/host/dwc_common_port/dwc_common_linux.c
+++ b/drivers/usb/host/dwc_common_port/dwc_common_linux.c
@@ -580,13 +580,7 @@ void DWC_WRITE_REG64(uint64_t volatile *
 
 void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask)
 {
-	unsigned long flags;
-
-	local_irq_save(flags);
-	local_fiq_disable();
 	writel((readl(reg) & ~clear_mask) | set_mask, reg);
-	local_fiq_enable();
-	local_irq_restore(flags);
 }
 
 #if 0
--- a/drivers/usb/host/dwc_otg/dwc_otg_cil.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_cil.c
@@ -2244,9 +2244,7 @@ void dwc_otg_core_host_init(dwc_otg_core
  */
 void dwc_otg_hc_init(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
 {
-	uint32_t intr_enable;
 	hcintmsk_data_t hc_intr_mask;
-	gintmsk_data_t gintmsk = {.d32 = 0 };
 	hcchar_data_t hcchar;
 	hcsplt_data_t hcsplt;
 
@@ -2348,14 +2346,6 @@ void dwc_otg_hc_init(dwc_otg_core_if_t *
 	}
 	DWC_WRITE_REG32(&hc_regs->hcintmsk, hc_intr_mask.d32);
 
-	/* Enable the top level host channel interrupt. */
-	intr_enable = (1 << hc_num);
-	DWC_MODIFY_REG32(&host_if->host_global_regs->haintmsk, 0, intr_enable);
-
-	/* Make sure host channel interrupts are enabled. */
-	gintmsk.b.hcintr = 1;
-	DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, gintmsk.d32);
-
 	/*
 	 * Program the HCCHARn register with the endpoint characteristics for
 	 * the current transfer.
--- a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
@@ -75,6 +75,46 @@ void notrace _fiq_print(enum fiq_debug_l
 }
 
 /**
+ * fiq_fsm_spin_lock() - ARMv6+ bare bones spinlock
+ * Must be called with local interrupts and FIQ disabled.
+ */
+inline void fiq_fsm_spin_lock(fiq_lock_t *lock)
+{
+	unsigned long tmp;
+	uint32_t newval;
+	fiq_lock_t lockval;
+	smp_mb__before_spinlock();
+	/* Nested locking, yay. If we are on the same CPU as the fiq, then the disable
+	 * will be sufficient. If we are on a different CPU, then the lock protects us. */
+	prefetchw(&lock->slock);
+	asm volatile (
+	"1:     ldrex   %0, [%3]\n"
+	"       add     %1, %0, %4\n"
+	"       strex   %2, %1, [%3]\n"
+	"       teq     %2, #0\n"
+	"       bne     1b"
+	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
+	: "r" (&lock->slock), "I" (1 << 16)
+	: "cc");
+
+	while (lockval.tickets.next != lockval.tickets.owner) {
+		wfe();
+		lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
+	}
+	smp_mb();
+}
+
+/**
+ * fiq_fsm_spin_unlock() - ARMv6+ bare bones spinunlock
+ */
+inline void fiq_fsm_spin_unlock(fiq_lock_t *lock)
+{
+	smp_mb();
+	lock->tickets.owner++;
+	dsb_sev();
+}
+
+/**
  * fiq_fsm_restart_channel() - Poke channel enable bit for a split transaction
  * @channel: channel to re-enable
  */
@@ -1142,6 +1182,7 @@ void notrace dwc_otg_fiq_fsm(struct fiq_
 	gintsts_handled.d32 = 0;
 	haint_handled.d32 = 0;
 
+	fiq_fsm_spin_lock(&state->lock);
 	gintsts.d32 = FIQ_READ(state->dwc_regs_base + GINTSTS);
 	gintmsk.d32 = FIQ_READ(state->dwc_regs_base + GINTMSK);
 	gintsts.d32 &= gintmsk.d32;
@@ -1231,7 +1272,7 @@ void notrace dwc_otg_fiq_fsm(struct fiq_
 
 	}
 	state->fiq_done++;
-	mb();
+	fiq_fsm_spin_unlock(&state->lock);
 }
 
 
@@ -1253,6 +1294,7 @@ void notrace dwc_otg_fiq_nop(struct fiq_
 	gintmsk_data_t gintmsk;
 	hfnum_data_t hfnum;
 
+	fiq_fsm_spin_lock(&state->lock);
 	hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM);
 	gintsts.d32 = FIQ_READ(state->dwc_regs_base + GINTSTS);
 	gintmsk.d32 = FIQ_READ(state->dwc_regs_base + GINTMSK);
@@ -1290,5 +1332,5 @@ void notrace dwc_otg_fiq_nop(struct fiq_
 
 	}
 	state->fiq_done++;
-	mb();
+	fiq_fsm_spin_unlock(&state->lock);
 }
--- a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
@@ -120,7 +120,6 @@ typedef struct {
 	volatile void* intstat;
 } mphi_regs_t;
 
-
 enum fiq_debug_level {
 	FIQDBG_SCHED = (1 << 0),
 	FIQDBG_INT   = (1 << 1),
@@ -128,6 +127,16 @@ enum fiq_debug_level {
 	FIQDBG_PORTHUB = (1 << 3),
 };
 
+typedef struct {
+	union {
+		uint32_t slock;
+		struct _tickets {
+			uint16_t owner;
+			uint16_t next;
+		} tickets;
+	};
+} fiq_lock_t;
+
 struct fiq_state;
 
 extern void _fiq_print (enum fiq_debug_level dbg_lvl, volatile struct fiq_state *state, char *fmt, ...);
@@ -324,6 +333,7 @@ struct fiq_channel_state {
  * It contains top-level state information.
  */
 struct fiq_state {
+	fiq_lock_t lock;
 	mphi_regs_t mphi_regs;
 	void *dwc_regs_base;
 	dma_addr_t dma_base;
@@ -342,6 +352,10 @@ struct fiq_state {
 	struct fiq_channel_state channel[0];
 };
 
+extern void fiq_fsm_spin_lock(fiq_lock_t *lock);
+
+extern void fiq_fsm_spin_unlock(fiq_lock_t *lock);
+
 extern int fiq_fsm_too_late(struct fiq_state *st, int n);
 
 extern int fiq_fsm_tt_in_use(struct fiq_state *st, int num_channels, int n);
--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
@@ -1184,6 +1184,9 @@ static void assign_and_init_hc(dwc_otg_h
 	dwc_otg_qtd_t *qtd;
 	dwc_otg_hcd_urb_t *urb;
 	void* ptr = NULL;
+	uint32_t intr_enable;
+	unsigned long flags;
+	gintmsk_data_t gintmsk = { .d32 = 0, };
 
 	qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
 
@@ -1409,6 +1412,20 @@ static void assign_and_init_hc(dwc_otg_h
 		hc->desc_list_addr = qh->desc_list_dma;
 
 	dwc_otg_hc_init(hcd->core_if, hc);
+
+	local_irq_save(flags);
+	local_fiq_disable();
+	fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+	/* Enable the top level host channel interrupt. */
+	intr_enable = (1 << hc->hc_num);
+	DWC_MODIFY_REG32(&hcd->core_if->host_if->host_global_regs->haintmsk, 0, intr_enable);
+
+	/* Make sure host channel interrupts are enabled. */
+	gintmsk.b.hcintr = 1;
+	DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32);
+	fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+	local_fiq_enable();
+	local_irq_restore(flags);
 	hc->qh = qh;
 }
 
@@ -1659,6 +1676,7 @@ int fiq_fsm_queue_isoc_transaction(dwc_o
 	fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32);
 	hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
 	local_fiq_disable();
+	fiq_fsm_spin_lock(&hcd->fiq_state->lock);
 	DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32);
 	DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32);
 	DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32);
@@ -1676,6 +1694,7 @@ int fiq_fsm_queue_isoc_transaction(dwc_o
 	}
 	mb();
 	st->hcchar_copy.b.chen = 0;
+	fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
 	local_fiq_enable();
 	return 0;
 }
@@ -1811,7 +1830,7 @@ int fiq_fsm_queue_split_transaction(dwc_
 	DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32);
 
 	local_fiq_disable();
-	mb();
+	fiq_fsm_spin_lock(&hcd->fiq_state->lock);
 
 	if (hc->ep_type & 0x1) {
 		hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
@@ -1909,7 +1928,7 @@ int fiq_fsm_queue_split_transaction(dwc_
 		st->hcchar_copy.b.chen = 1;
 		DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
 	}
-	mb();
+	fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
 	local_fiq_enable();
 	return 0;
 }
--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
@@ -101,6 +101,7 @@ int32_t dwc_otg_hcd_handle_intr(dwc_otg_
 	if (dwc_otg_is_host_mode(core_if)) {
 		if (fiq_enable) {
 			local_fiq_disable();
+			fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
 			/* Pull in from the FIQ's disabled mask */
 			gintmsk.d32 = gintmsk.d32 | ~(dwc_otg_hcd->fiq_state->gintmsk_saved.d32);
 			dwc_otg_hcd->fiq_state->gintmsk_saved.d32 = ~0;
@@ -116,8 +117,10 @@ int32_t dwc_otg_hcd_handle_intr(dwc_otg_
 		}
 		gintsts.d32 &= gintmsk.d32;
 
-		if (fiq_enable)
+		if (fiq_enable) {
+			fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
 			local_fiq_enable();
+		}
 
 		if (!gintsts.d32) {
 			goto exit_handler_routine;
@@ -200,6 +203,7 @@ exit_handler_routine:
 		gintmsk_data_t gintmsk_new;
 		haintmsk_data_t haintmsk_new;
 		local_fiq_disable();
+		fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
 		gintmsk_new.d32 = *(volatile uint32_t *)&dwc_otg_hcd->fiq_state->gintmsk_saved.d32;
 		if(fiq_fsm_enable)
 			haintmsk_new.d32 = *(volatile uint32_t *)&dwc_otg_hcd->fiq_state->haintmsk_saved.d32;
@@ -222,6 +226,7 @@ exit_handler_routine:
 		haintmsk.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->haintmsk);
 		/* Re-enable interrupts that the FIQ masked (first time round) */
 		FIQ_WRITE(dwc_otg_hcd->fiq_state->dwc_regs_base + GINTMSK, gintmsk.d32);
+		fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
 		local_fiq_enable();
 
 		if ((jiffies / HZ) > last_time) {
@@ -633,8 +638,10 @@ int32_t dwc_otg_hcd_handle_hc_intr(dwc_o
 	{
 		/* check the mask? */
 		local_fiq_disable();
+		fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
 		haint.b2.chint |= ~(dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint);
 		dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint = ~0;
+		fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
 		local_fiq_enable();
 	}