aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mm
diff options
context:
space:
mode:
authorWill Deacon <will.deacon@arm.com>2012-06-15 09:47:31 -0400
committerWill Deacon <will.deacon@arm.com>2012-11-05 11:25:25 -0500
commitb5466f8728527a05a493cc4abe9e6f034a1bbaab (patch)
tree26c49a9c00e0e853c5fe1e4366b6af2335f52a95 /arch/arm/mm
parent3d70f8c617a436c7146ecb81df2265b4626dfe89 (diff)
ARM: mm: remove IPI broadcasting on ASID rollover
ASIDs are allocated to MMU contexts based on a rolling counter. This means that after 255 allocations we must invalidate all existing ASIDs via an expensive IPI mechanism to synchronise all of the online CPUs and ensure that all tasks execute with an ASID from the new generation. This patch changes the rollover behaviour so that we rely instead on the hardware broadcasting of the TLB invalidation to avoid the IPI calls. This works by keeping track of the active ASID on each core, which is then reserved in the case of a rollover so that currently scheduled tasks can continue to run. For cores without hardware TLB broadcasting, we keep track of pending flushes in a cpumask, so cores can flush their local TLB before scheduling a new mm. Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Tested-by: Marc Zyngier <marc.zyngier@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
Diffstat (limited to 'arch/arm/mm')
-rw-r--r--arch/arm/mm/context.c186
1 files changed, 86 insertions, 100 deletions
diff --git a/arch/arm/mm/context.c b/arch/arm/mm/context.c
index 4e07eec1270d..3172781a8e2e 100644
--- a/arch/arm/mm/context.c
+++ b/arch/arm/mm/context.c
@@ -2,6 +2,9 @@
2 * linux/arch/arm/mm/context.c 2 * linux/arch/arm/mm/context.c
3 * 3 *
4 * Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved. 4 * Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved.
5 * Copyright (C) 2012 ARM Limited
6 *
7 * Author: Will Deacon <will.deacon@arm.com>
5 * 8 *
6 * This program is free software; you can redistribute it and/or modify 9 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as 10 * it under the terms of the GNU General Public License version 2 as
@@ -14,14 +17,35 @@
14#include <linux/percpu.h> 17#include <linux/percpu.h>
15 18
16#include <asm/mmu_context.h> 19#include <asm/mmu_context.h>
20#include <asm/smp_plat.h>
17#include <asm/thread_notify.h> 21#include <asm/thread_notify.h>
18#include <asm/tlbflush.h> 22#include <asm/tlbflush.h>
19 23
24/*
25 * On ARMv6, we have the following structure in the Context ID:
26 *
27 * 31 7 0
28 * +-------------------------+-----------+
29 * | process ID | ASID |
30 * +-------------------------+-----------+
31 * | context ID |
32 * +-------------------------------------+
33 *
34 * The ASID is used to tag entries in the CPU caches and TLBs.
35 * The context ID is used by debuggers and trace logic, and
36 * should be unique within all running processes.
37 */
38#define ASID_FIRST_VERSION (1ULL << ASID_BITS)
39
20static DEFINE_RAW_SPINLOCK(cpu_asid_lock); 40static DEFINE_RAW_SPINLOCK(cpu_asid_lock);
21unsigned int cpu_last_asid = ASID_FIRST_VERSION; 41static u64 cpu_last_asid = ASID_FIRST_VERSION;
42
43static DEFINE_PER_CPU(u64, active_asids);
44static DEFINE_PER_CPU(u64, reserved_asids);
45static cpumask_t tlb_flush_pending;
22 46
23#ifdef CONFIG_ARM_LPAE 47#ifdef CONFIG_ARM_LPAE
24void cpu_set_reserved_ttbr0(void) 48static void cpu_set_reserved_ttbr0(void)
25{ 49{
26 unsigned long ttbl = __pa(swapper_pg_dir); 50 unsigned long ttbl = __pa(swapper_pg_dir);
27 unsigned long ttbh = 0; 51 unsigned long ttbh = 0;
@@ -37,7 +61,7 @@ void cpu_set_reserved_ttbr0(void)
37 isb(); 61 isb();
38} 62}
39#else 63#else
40void cpu_set_reserved_ttbr0(void) 64static void cpu_set_reserved_ttbr0(void)
41{ 65{
42 u32 ttb; 66 u32 ttb;
43 /* Copy TTBR1 into TTBR0 */ 67 /* Copy TTBR1 into TTBR0 */
@@ -84,124 +108,86 @@ static int __init contextidr_notifier_init(void)
84arch_initcall(contextidr_notifier_init); 108arch_initcall(contextidr_notifier_init);
85#endif 109#endif
86 110
87/* 111static void flush_context(unsigned int cpu)
88 * We fork()ed a process, and we need a new context for the child
89 * to run in.
90 */
91void __init_new_context(struct task_struct *tsk, struct mm_struct *mm)
92{ 112{
93 mm->context.id = 0; 113 int i;
94 raw_spin_lock_init(&mm->context.id_lock);
95}
96 114
97static void flush_context(void) 115 /* Update the list of reserved ASIDs. */
98{ 116 per_cpu(active_asids, cpu) = 0;
99 cpu_set_reserved_ttbr0(); 117 for_each_possible_cpu(i)
100 local_flush_tlb_all(); 118 per_cpu(reserved_asids, i) = per_cpu(active_asids, i);
101 if (icache_is_vivt_asid_tagged()) { 119
120 /* Queue a TLB invalidate and flush the I-cache if necessary. */
121 if (!tlb_ops_need_broadcast())
122 cpumask_set_cpu(cpu, &tlb_flush_pending);
123 else
124 cpumask_setall(&tlb_flush_pending);
125
126 if (icache_is_vivt_asid_tagged())
102 __flush_icache_all(); 127 __flush_icache_all();
103 dsb();
104 }
105} 128}
106 129
107#ifdef CONFIG_SMP 130static int is_reserved_asid(u64 asid, u64 mask)
131{
132 int cpu;
133 for_each_possible_cpu(cpu)
134 if ((per_cpu(reserved_asids, cpu) & mask) == (asid & mask))
135 return 1;
136 return 0;
137}
108 138
109static void set_mm_context(struct mm_struct *mm, unsigned int asid) 139static void new_context(struct mm_struct *mm, unsigned int cpu)
110{ 140{
111 unsigned long flags; 141 u64 asid = mm->context.id;
112 142
113 /* 143 if (asid != 0 && is_reserved_asid(asid, ULLONG_MAX)) {
114 * Locking needed for multi-threaded applications where the
115 * same mm->context.id could be set from different CPUs during
116 * the broadcast. This function is also called via IPI so the
117 * mm->context.id_lock has to be IRQ-safe.
118 */
119 raw_spin_lock_irqsave(&mm->context.id_lock, flags);
120 if (likely((mm->context.id ^ cpu_last_asid) >> ASID_BITS)) {
121 /* 144 /*
122 * Old version of ASID found. Set the new one and 145 * Our current ASID was active during a rollover, we can
123 * reset mm_cpumask(mm). 146 * continue to use it and this was just a false alarm.
124 */ 147 */
125 mm->context.id = asid; 148 asid = (cpu_last_asid & ASID_MASK) | (asid & ~ASID_MASK);
149 } else {
150 /*
151 * Allocate a free ASID. If we can't find one, take a
152 * note of the currently active ASIDs and mark the TLBs
153 * as requiring flushes.
154 */
155 do {
156 asid = ++cpu_last_asid;
157 if ((asid & ~ASID_MASK) == 0)
158 flush_context(cpu);
159 } while (is_reserved_asid(asid, ~ASID_MASK));
126 cpumask_clear(mm_cpumask(mm)); 160 cpumask_clear(mm_cpumask(mm));
127 } 161 }
128 raw_spin_unlock_irqrestore(&mm->context.id_lock, flags);
129 162
130 /* 163 mm->context.id = asid;
131 * Set the mm_cpumask(mm) bit for the current CPU.
132 */
133 cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
134} 164}
135 165
136/* 166void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk)
137 * Reset the ASID on the current CPU. This function call is broadcast
138 * from the CPU handling the ASID rollover and holding cpu_asid_lock.
139 */
140static void reset_context(void *info)
141{ 167{
142 unsigned int asid; 168 unsigned long flags;
143 unsigned int cpu = smp_processor_id(); 169 unsigned int cpu = smp_processor_id();
144 struct mm_struct *mm = current->active_mm;
145
146 smp_rmb();
147 asid = cpu_last_asid + cpu + 1;
148
149 flush_context();
150 set_mm_context(mm, asid);
151
152 /* set the new ASID */
153 cpu_switch_mm(mm->pgd, mm);
154}
155
156#else
157 170
158static inline void set_mm_context(struct mm_struct *mm, unsigned int asid) 171 if (unlikely(mm->context.kvm_seq != init_mm.context.kvm_seq))
159{ 172 __check_kvm_seq(mm);
160 mm->context.id = asid;
161 cpumask_copy(mm_cpumask(mm), cpumask_of(smp_processor_id()));
162}
163 173
164#endif
165
166void __new_context(struct mm_struct *mm)
167{
168 unsigned int asid;
169
170 raw_spin_lock(&cpu_asid_lock);
171#ifdef CONFIG_SMP
172 /* 174 /*
173 * Check the ASID again, in case the change was broadcast from 175 * Required during context switch to avoid speculative page table
174 * another CPU before we acquired the lock. 176 * walking with the wrong TTBR.
175 */ 177 */
176 if (unlikely(((mm->context.id ^ cpu_last_asid) >> ASID_BITS) == 0)) { 178 cpu_set_reserved_ttbr0();
177 cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
178 raw_spin_unlock(&cpu_asid_lock);
179 return;
180 }
181#endif
182 /*
183 * At this point, it is guaranteed that the current mm (with
184 * an old ASID) isn't active on any other CPU since the ASIDs
185 * are changed simultaneously via IPI.
186 */
187 asid = ++cpu_last_asid;
188 if (asid == 0)
189 asid = cpu_last_asid = ASID_FIRST_VERSION;
190 179
191 /* 180 raw_spin_lock_irqsave(&cpu_asid_lock, flags);
192 * If we've used up all our ASIDs, we need 181 /* Check that our ASID belongs to the current generation. */
193 * to start a new version and flush the TLB. 182 if ((mm->context.id ^ cpu_last_asid) >> ASID_BITS)
194 */ 183 new_context(mm, cpu);
195 if (unlikely((asid & ~ASID_MASK) == 0)) {
196 asid = cpu_last_asid + smp_processor_id() + 1;
197 flush_context();
198#ifdef CONFIG_SMP
199 smp_wmb();
200 smp_call_function(reset_context, NULL, 1);
201#endif
202 cpu_last_asid += NR_CPUS;
203 }
204 184
205 set_mm_context(mm, asid); 185 *this_cpu_ptr(&active_asids) = mm->context.id;
206 raw_spin_unlock(&cpu_asid_lock); 186 cpumask_set_cpu(cpu, mm_cpumask(mm));
187
188 if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
189 local_flush_tlb_all();
190 raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
191
192 cpu_switch_mm(mm->pgd, mm);
207} 193}