aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm
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
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')
-rw-r--r--arch/arm/include/asm/mmu.h11
-rw-r--r--arch/arm/include/asm/mmu_context.h82
-rw-r--r--arch/arm/mm/context.c186
3 files changed, 93 insertions, 186 deletions
diff --git a/arch/arm/include/asm/mmu.h b/arch/arm/include/asm/mmu.h
index 14965658a923..5b53b53ab5cf 100644
--- a/arch/arm/include/asm/mmu.h
+++ b/arch/arm/include/asm/mmu.h
@@ -5,18 +5,15 @@
5 5
6typedef struct { 6typedef struct {
7#ifdef CONFIG_CPU_HAS_ASID 7#ifdef CONFIG_CPU_HAS_ASID
8 unsigned int id; 8 u64 id;
9 raw_spinlock_t id_lock;
10#endif 9#endif
11 unsigned int kvm_seq; 10 unsigned int kvm_seq;
12} mm_context_t; 11} mm_context_t;
13 12
14#ifdef CONFIG_CPU_HAS_ASID 13#ifdef CONFIG_CPU_HAS_ASID
15#define ASID(mm) ((mm)->context.id & 255) 14#define ASID_BITS 8
16 15#define ASID_MASK ((~0ULL) << ASID_BITS)
17/* init_mm.context.id_lock should be initialized. */ 16#define ASID(mm) ((mm)->context.id & ~ASID_MASK)
18#define INIT_MM_CONTEXT(name) \
19 .context.id_lock = __RAW_SPIN_LOCK_UNLOCKED(name.context.id_lock),
20#else 17#else
21#define ASID(mm) (0) 18#define ASID(mm) (0)
22#endif 19#endif
diff --git a/arch/arm/include/asm/mmu_context.h b/arch/arm/include/asm/mmu_context.h
index 0306bc642c0d..a64f61cb23d1 100644
--- a/arch/arm/include/asm/mmu_context.h
+++ b/arch/arm/include/asm/mmu_context.h
@@ -24,84 +24,8 @@ void __check_kvm_seq(struct mm_struct *mm);
24 24
25#ifdef CONFIG_CPU_HAS_ASID 25#ifdef CONFIG_CPU_HAS_ASID
26 26
27/* 27void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk);
28 * On ARMv6, we have the following structure in the Context ID: 28#define init_new_context(tsk,mm) ({ mm->context.id = 0; })
29 *
30 * 31 7 0
31 * +-------------------------+-----------+
32 * | process ID | ASID |
33 * +-------------------------+-----------+
34 * | context ID |
35 * +-------------------------------------+
36 *
37 * The ASID is used to tag entries in the CPU caches and TLBs.
38 * The context ID is used by debuggers and trace logic, and
39 * should be unique within all running processes.
40 */
41#define ASID_BITS 8
42#define ASID_MASK ((~0) << ASID_BITS)
43#define ASID_FIRST_VERSION (1 << ASID_BITS)
44
45extern unsigned int cpu_last_asid;
46
47void __init_new_context(struct task_struct *tsk, struct mm_struct *mm);
48void __new_context(struct mm_struct *mm);
49void cpu_set_reserved_ttbr0(void);
50
51static inline void switch_new_context(struct mm_struct *mm)
52{
53 unsigned long flags;
54
55 __new_context(mm);
56
57 local_irq_save(flags);
58 cpu_switch_mm(mm->pgd, mm);
59 local_irq_restore(flags);
60}
61
62static inline void check_and_switch_context(struct mm_struct *mm,
63 struct task_struct *tsk)
64{
65 if (unlikely(mm->context.kvm_seq != init_mm.context.kvm_seq))
66 __check_kvm_seq(mm);
67
68 /*
69 * Required during context switch to avoid speculative page table
70 * walking with the wrong TTBR.
71 */
72 cpu_set_reserved_ttbr0();
73
74 if (!((mm->context.id ^ cpu_last_asid) >> ASID_BITS))
75 /*
76 * The ASID is from the current generation, just switch to the
77 * new pgd. This condition is only true for calls from
78 * context_switch() and interrupts are already disabled.
79 */
80 cpu_switch_mm(mm->pgd, mm);
81 else if (irqs_disabled())
82 /*
83 * Defer the new ASID allocation until after the context
84 * switch critical region since __new_context() cannot be
85 * called with interrupts disabled (it sends IPIs).
86 */
87 set_ti_thread_flag(task_thread_info(tsk), TIF_SWITCH_MM);
88 else
89 /*
90 * That is a direct call to switch_mm() or activate_mm() with
91 * interrupts enabled and a new context.
92 */
93 switch_new_context(mm);
94}
95
96#define init_new_context(tsk,mm) (__init_new_context(tsk,mm),0)
97
98#define finish_arch_post_lock_switch \
99 finish_arch_post_lock_switch
100static inline void finish_arch_post_lock_switch(void)
101{
102 if (test_and_clear_thread_flag(TIF_SWITCH_MM))
103 switch_new_context(current->mm);
104}
105 29
106#else /* !CONFIG_CPU_HAS_ASID */ 30#else /* !CONFIG_CPU_HAS_ASID */
107 31
@@ -143,6 +67,7 @@ static inline void finish_arch_post_lock_switch(void)
143#endif /* CONFIG_CPU_HAS_ASID */ 67#endif /* CONFIG_CPU_HAS_ASID */
144 68
145#define destroy_context(mm) do { } while(0) 69#define destroy_context(mm) do { } while(0)
70#define activate_mm(prev,next) switch_mm(prev, next, NULL)
146 71
147/* 72/*
148 * This is called when "tsk" is about to enter lazy TLB mode. 73 * This is called when "tsk" is about to enter lazy TLB mode.
@@ -186,6 +111,5 @@ switch_mm(struct mm_struct *prev, struct mm_struct *next,
186} 111}
187 112
188#define deactivate_mm(tsk,mm) do { } while (0) 113#define deactivate_mm(tsk,mm) do { } while (0)
189#define activate_mm(prev,next) switch_mm(prev, next, NULL)
190 114
191#endif 115#endif
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}