aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/common
diff options
context:
space:
mode:
authorDave Martin <dave.martin@linaro.org>2012-07-17 09:25:42 -0400
committerNicolas Pitre <nicolas.pitre@linaro.org>2013-04-24 10:37:00 -0400
commit7fe31d28e839f9565c8176ec584676a045970802 (patch)
treec3fcc8b99461e1fbff13eb08f8dbec891dc1baa4 /arch/arm/common
parent7c2b860534d02d11923dd0504b961f21508173f1 (diff)
ARM: mcpm: introduce helpers for platform coherency exit/setup
This provides helper methods to coordinate between CPUs coming down and CPUs going up, as well as documentation on the used algorithms, so that cluster teardown and setup operations are not done for a cluster simultaneously. For use in the power_down() implementation: * __mcpm_cpu_going_down(unsigned int cluster, unsigned int cpu) * __mcpm_outbound_enter_critical(unsigned int cluster) * __mcpm_outbound_leave_critical(unsigned int cluster) * __mcpm_cpu_down(unsigned int cluster, unsigned int cpu) The power_up_setup() helper should do platform-specific setup in preparation for turning the CPU on, such as invalidating local caches or entering coherency. It must be assembler for now, since it must run before the MMU can be switched on. It is passed the affinity level for which initialization should be performed. Because the mcpm_sync_struct content is looked-up and modified with the cache enabled or disabled depending on the code path, it is crucial to always ensure proper cache maintenance to update main memory right away. The sync_cache_*() helpers are used to that end. Also, in order to prevent a cached writer from interfering with an adjacent non-cached writer, we ensure each state variable is located to a separate cache line. Thanks to Nicolas Pitre and Achin Gupta for the help with this patch. Signed-off-by: Dave Martin <dave.martin@linaro.org> Signed-off-by: Nicolas Pitre <nico@linaro.org> Reviewed-by: Will Deacon <will.deacon@arm.com>
Diffstat (limited to 'arch/arm/common')
-rw-r--r--arch/arm/common/mcpm_entry.c150
-rw-r--r--arch/arm/common/mcpm_head.S106
2 files changed, 254 insertions, 2 deletions
diff --git a/arch/arm/common/mcpm_entry.c b/arch/arm/common/mcpm_entry.c
index 5d72889a58a4..370236dd1a03 100644
--- a/arch/arm/common/mcpm_entry.c
+++ b/arch/arm/common/mcpm_entry.c
@@ -16,6 +16,7 @@
16#include <asm/mcpm.h> 16#include <asm/mcpm.h>
17#include <asm/cacheflush.h> 17#include <asm/cacheflush.h>
18#include <asm/idmap.h> 18#include <asm/idmap.h>
19#include <asm/cputype.h>
19 20
20extern unsigned long mcpm_entry_vectors[MAX_NR_CLUSTERS][MAX_CPUS_PER_CLUSTER]; 21extern unsigned long mcpm_entry_vectors[MAX_NR_CLUSTERS][MAX_CPUS_PER_CLUSTER];
21 22
@@ -111,3 +112,152 @@ int mcpm_cpu_powered_up(void)
111 platform_ops->powered_up(); 112 platform_ops->powered_up();
112 return 0; 113 return 0;
113} 114}
115
116struct sync_struct mcpm_sync;
117
118/*
119 * __mcpm_cpu_going_down: Indicates that the cpu is being torn down.
120 * This must be called at the point of committing to teardown of a CPU.
121 * The CPU cache (SCTRL.C bit) is expected to still be active.
122 */
123void __mcpm_cpu_going_down(unsigned int cpu, unsigned int cluster)
124{
125 mcpm_sync.clusters[cluster].cpus[cpu].cpu = CPU_GOING_DOWN;
126 sync_cache_w(&mcpm_sync.clusters[cluster].cpus[cpu].cpu);
127}
128
129/*
130 * __mcpm_cpu_down: Indicates that cpu teardown is complete and that the
131 * cluster can be torn down without disrupting this CPU.
132 * To avoid deadlocks, this must be called before a CPU is powered down.
133 * The CPU cache (SCTRL.C bit) is expected to be off.
134 * However L2 cache might or might not be active.
135 */
136void __mcpm_cpu_down(unsigned int cpu, unsigned int cluster)
137{
138 dmb();
139 mcpm_sync.clusters[cluster].cpus[cpu].cpu = CPU_DOWN;
140 sync_cache_w(&mcpm_sync.clusters[cluster].cpus[cpu].cpu);
141 dsb_sev();
142}
143
144/*
145 * __mcpm_outbound_leave_critical: Leave the cluster teardown critical section.
146 * @state: the final state of the cluster:
147 * CLUSTER_UP: no destructive teardown was done and the cluster has been
148 * restored to the previous state (CPU cache still active); or
149 * CLUSTER_DOWN: the cluster has been torn-down, ready for power-off
150 * (CPU cache disabled, L2 cache either enabled or disabled).
151 */
152void __mcpm_outbound_leave_critical(unsigned int cluster, int state)
153{
154 dmb();
155 mcpm_sync.clusters[cluster].cluster = state;
156 sync_cache_w(&mcpm_sync.clusters[cluster].cluster);
157 dsb_sev();
158}
159
160/*
161 * __mcpm_outbound_enter_critical: Enter the cluster teardown critical section.
162 * This function should be called by the last man, after local CPU teardown
163 * is complete. CPU cache expected to be active.
164 *
165 * Returns:
166 * false: the critical section was not entered because an inbound CPU was
167 * observed, or the cluster is already being set up;
168 * true: the critical section was entered: it is now safe to tear down the
169 * cluster.
170 */
171bool __mcpm_outbound_enter_critical(unsigned int cpu, unsigned int cluster)
172{
173 unsigned int i;
174 struct mcpm_sync_struct *c = &mcpm_sync.clusters[cluster];
175
176 /* Warn inbound CPUs that the cluster is being torn down: */
177 c->cluster = CLUSTER_GOING_DOWN;
178 sync_cache_w(&c->cluster);
179
180 /* Back out if the inbound cluster is already in the critical region: */
181 sync_cache_r(&c->inbound);
182 if (c->inbound == INBOUND_COMING_UP)
183 goto abort;
184
185 /*
186 * Wait for all CPUs to get out of the GOING_DOWN state, so that local
187 * teardown is complete on each CPU before tearing down the cluster.
188 *
189 * If any CPU has been woken up again from the DOWN state, then we
190 * shouldn't be taking the cluster down at all: abort in that case.
191 */
192 sync_cache_r(&c->cpus);
193 for (i = 0; i < MAX_CPUS_PER_CLUSTER; i++) {
194 int cpustate;
195
196 if (i == cpu)
197 continue;
198
199 while (1) {
200 cpustate = c->cpus[i].cpu;
201 if (cpustate != CPU_GOING_DOWN)
202 break;
203
204 wfe();
205 sync_cache_r(&c->cpus[i].cpu);
206 }
207
208 switch (cpustate) {
209 case CPU_DOWN:
210 continue;
211
212 default:
213 goto abort;
214 }
215 }
216
217 return true;
218
219abort:
220 __mcpm_outbound_leave_critical(cluster, CLUSTER_UP);
221 return false;
222}
223
224int __mcpm_cluster_state(unsigned int cluster)
225{
226 sync_cache_r(&mcpm_sync.clusters[cluster].cluster);
227 return mcpm_sync.clusters[cluster].cluster;
228}
229
230extern unsigned long mcpm_power_up_setup_phys;
231
232int __init mcpm_sync_init(
233 void (*power_up_setup)(unsigned int affinity_level))
234{
235 unsigned int i, j, mpidr, this_cluster;
236
237 BUILD_BUG_ON(MCPM_SYNC_CLUSTER_SIZE * MAX_NR_CLUSTERS != sizeof mcpm_sync);
238 BUG_ON((unsigned long)&mcpm_sync & (__CACHE_WRITEBACK_GRANULE - 1));
239
240 /*
241 * Set initial CPU and cluster states.
242 * Only one cluster is assumed to be active at this point.
243 */
244 for (i = 0; i < MAX_NR_CLUSTERS; i++) {
245 mcpm_sync.clusters[i].cluster = CLUSTER_DOWN;
246 mcpm_sync.clusters[i].inbound = INBOUND_NOT_COMING_UP;
247 for (j = 0; j < MAX_CPUS_PER_CLUSTER; j++)
248 mcpm_sync.clusters[i].cpus[j].cpu = CPU_DOWN;
249 }
250 mpidr = read_cpuid_mpidr();
251 this_cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
252 for_each_online_cpu(i)
253 mcpm_sync.clusters[this_cluster].cpus[i].cpu = CPU_UP;
254 mcpm_sync.clusters[this_cluster].cluster = CLUSTER_UP;
255 sync_cache_w(&mcpm_sync);
256
257 if (power_up_setup) {
258 mcpm_power_up_setup_phys = virt_to_phys(power_up_setup);
259 sync_cache_w(&mcpm_power_up_setup_phys);
260 }
261
262 return 0;
263}
diff --git a/arch/arm/common/mcpm_head.S b/arch/arm/common/mcpm_head.S
index 68c9903075a9..7d729bd72674 100644
--- a/arch/arm/common/mcpm_head.S
+++ b/arch/arm/common/mcpm_head.S
@@ -7,11 +7,19 @@
7 * This program is free software; you can redistribute it and/or modify 7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as 8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation. 9 * published by the Free Software Foundation.
10 *
11 *
12 * Refer to Documentation/arm/cluster-pm-race-avoidance.txt
13 * for details of the synchronisation algorithms used here.
10 */ 14 */
11 15
12#include <linux/linkage.h> 16#include <linux/linkage.h>
13#include <asm/mcpm.h> 17#include <asm/mcpm.h>
14 18
19.if MCPM_SYNC_CLUSTER_CPUS
20.error "cpus must be the first member of struct mcpm_sync_struct"
21.endif
22
15 .macro pr_dbg string 23 .macro pr_dbg string
16#if defined(CONFIG_DEBUG_LL) && defined(DEBUG) 24#if defined(CONFIG_DEBUG_LL) && defined(DEBUG)
17 b 1901f 25 b 1901f
@@ -57,24 +65,114 @@ ENTRY(mcpm_entry_point)
572: pr_dbg "kernel mcpm_entry_point\n" 652: pr_dbg "kernel mcpm_entry_point\n"
58 66
59 /* 67 /*
60 * MMU is off so we need to get to mcpm_entry_vectors in a 68 * MMU is off so we need to get to various variables in a
61 * position independent way. 69 * position independent way.
62 */ 70 */
63 adr r5, 3f 71 adr r5, 3f
64 ldr r6, [r5] 72 ldmia r5, {r6, r7, r8}
65 add r6, r5, r6 @ r6 = mcpm_entry_vectors 73 add r6, r5, r6 @ r6 = mcpm_entry_vectors
74 ldr r7, [r5, r7] @ r7 = mcpm_power_up_setup_phys
75 add r8, r5, r8 @ r8 = mcpm_sync
76
77 mov r0, #MCPM_SYNC_CLUSTER_SIZE
78 mla r8, r0, r10, r8 @ r8 = sync cluster base
79
80 @ Signal that this CPU is coming UP:
81 mov r0, #CPU_COMING_UP
82 mov r5, #MCPM_SYNC_CPU_SIZE
83 mla r5, r9, r5, r8 @ r5 = sync cpu address
84 strb r0, [r5]
85
86 @ At this point, the cluster cannot unexpectedly enter the GOING_DOWN
87 @ state, because there is at least one active CPU (this CPU).
88
89 @ Note: the following is racy as another CPU might be testing
90 @ the same flag at the same moment. That'll be fixed later.
91 ldrb r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER]
92 cmp r0, #CLUSTER_UP @ cluster already up?
93 bne mcpm_setup @ if not, set up the cluster
94
95 @ Otherwise, skip setup:
96 b mcpm_setup_complete
97
98mcpm_setup:
99 @ Control dependency implies strb not observable before previous ldrb.
100
101 @ Signal that the cluster is being brought up:
102 mov r0, #INBOUND_COMING_UP
103 strb r0, [r8, #MCPM_SYNC_CLUSTER_INBOUND]
104 dmb
105
106 @ Any CPU trying to take the cluster into CLUSTER_GOING_DOWN from this
107 @ point onwards will observe INBOUND_COMING_UP and abort.
108
109 @ Wait for any previously-pending cluster teardown operations to abort
110 @ or complete:
111mcpm_teardown_wait:
112 ldrb r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER]
113 cmp r0, #CLUSTER_GOING_DOWN
114 bne first_man_setup
115 wfe
116 b mcpm_teardown_wait
117
118first_man_setup:
119 dmb
120
121 @ If the outbound gave up before teardown started, skip cluster setup:
122
123 cmp r0, #CLUSTER_UP
124 beq mcpm_setup_leave
125
126 @ power_up_setup is now responsible for setting up the cluster:
127
128 cmp r7, #0
129 mov r0, #1 @ second (cluster) affinity level
130 blxne r7 @ Call power_up_setup if defined
131 dmb
132
133 mov r0, #CLUSTER_UP
134 strb r0, [r8, #MCPM_SYNC_CLUSTER_CLUSTER]
135 dmb
136
137mcpm_setup_leave:
138 @ Leave the cluster setup critical section:
139
140 mov r0, #INBOUND_NOT_COMING_UP
141 strb r0, [r8, #MCPM_SYNC_CLUSTER_INBOUND]
142 dsb
143 sev
144
145mcpm_setup_complete:
146 @ If a platform-specific CPU setup hook is needed, it is
147 @ called from here.
148
149 cmp r7, #0
150 mov r0, #0 @ first (CPU) affinity level
151 blxne r7 @ Call power_up_setup if defined
152 dmb
153
154 @ Mark the CPU as up:
155
156 mov r0, #CPU_UP
157 strb r0, [r5]
158
159 @ Observability order of CPU_UP and opening of the gate does not matter.
66 160
67mcpm_entry_gated: 161mcpm_entry_gated:
68 ldr r5, [r6, r4, lsl #2] @ r5 = CPU entry vector 162 ldr r5, [r6, r4, lsl #2] @ r5 = CPU entry vector
69 cmp r5, #0 163 cmp r5, #0
70 wfeeq 164 wfeeq
71 beq mcpm_entry_gated 165 beq mcpm_entry_gated
166 dmb
167
72 pr_dbg "released\n" 168 pr_dbg "released\n"
73 bx r5 169 bx r5
74 170
75 .align 2 171 .align 2
76 172
773: .word mcpm_entry_vectors - . 1733: .word mcpm_entry_vectors - .
174 .word mcpm_power_up_setup_phys - 3b
175 .word mcpm_sync - 3b
78 176
79ENDPROC(mcpm_entry_point) 177ENDPROC(mcpm_entry_point)
80 178
@@ -84,3 +182,7 @@ ENDPROC(mcpm_entry_point)
84 .type mcpm_entry_vectors, #object 182 .type mcpm_entry_vectors, #object
85ENTRY(mcpm_entry_vectors) 183ENTRY(mcpm_entry_vectors)
86 .space 4 * MAX_NR_CLUSTERS * MAX_CPUS_PER_CLUSTER 184 .space 4 * MAX_NR_CLUSTERS * MAX_CPUS_PER_CLUSTER
185
186 .type mcpm_power_up_setup_phys, #object
187ENTRY(mcpm_power_up_setup_phys)
188 .space 4 @ set by mcpm_sync_init()