Merge branch 'for-3.0' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound...
[pandora-kernel.git] / arch / arm / mm / context.c
1 /*
2  *  linux/arch/arm/mm/context.c
3  *
4  *  Copyright (C) 2002-2003 Deep Blue Solutions Ltd, all rights reserved.
5  *
6  * 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
8  * published by the Free Software Foundation.
9  */
10 #include <linux/init.h>
11 #include <linux/sched.h>
12 #include <linux/mm.h>
13 #include <linux/smp.h>
14 #include <linux/percpu.h>
15
16 #include <asm/mmu_context.h>
17 #include <asm/tlbflush.h>
18
19 static DEFINE_SPINLOCK(cpu_asid_lock);
20 unsigned int cpu_last_asid = ASID_FIRST_VERSION;
21 #ifdef CONFIG_SMP
22 DEFINE_PER_CPU(struct mm_struct *, current_mm);
23 #endif
24
25 /*
26  * We fork()ed a process, and we need a new context for the child
27  * to run in.
28  */
29 void __init_new_context(struct task_struct *tsk, struct mm_struct *mm)
30 {
31         mm->context.id = 0;
32         spin_lock_init(&mm->context.id_lock);
33 }
34
35 static void flush_context(void)
36 {
37         u32 ttb;
38         /* Copy TTBR1 into TTBR0 */
39         asm volatile("mrc       p15, 0, %0, c2, c0, 1\n"
40                      "mcr       p15, 0, %0, c2, c0, 0"
41                      : "=r" (ttb));
42         isb();
43         local_flush_tlb_all();
44         if (icache_is_vivt_asid_tagged()) {
45                 __flush_icache_all();
46                 dsb();
47         }
48 }
49
50 #ifdef CONFIG_SMP
51
52 static void set_mm_context(struct mm_struct *mm, unsigned int asid)
53 {
54         unsigned long flags;
55
56         /*
57          * Locking needed for multi-threaded applications where the
58          * same mm->context.id could be set from different CPUs during
59          * the broadcast. This function is also called via IPI so the
60          * mm->context.id_lock has to be IRQ-safe.
61          */
62         spin_lock_irqsave(&mm->context.id_lock, flags);
63         if (likely((mm->context.id ^ cpu_last_asid) >> ASID_BITS)) {
64                 /*
65                  * Old version of ASID found. Set the new one and
66                  * reset mm_cpumask(mm).
67                  */
68                 mm->context.id = asid;
69                 cpumask_clear(mm_cpumask(mm));
70         }
71         spin_unlock_irqrestore(&mm->context.id_lock, flags);
72
73         /*
74          * Set the mm_cpumask(mm) bit for the current CPU.
75          */
76         cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
77 }
78
79 /*
80  * Reset the ASID on the current CPU. This function call is broadcast
81  * from the CPU handling the ASID rollover and holding cpu_asid_lock.
82  */
83 static void reset_context(void *info)
84 {
85         unsigned int asid;
86         unsigned int cpu = smp_processor_id();
87         struct mm_struct *mm = per_cpu(current_mm, cpu);
88
89         /*
90          * Check if a current_mm was set on this CPU as it might still
91          * be in the early booting stages and using the reserved ASID.
92          */
93         if (!mm)
94                 return;
95
96         smp_rmb();
97         asid = cpu_last_asid + cpu;
98
99         flush_context();
100         set_mm_context(mm, asid);
101
102         /* set the new ASID */
103         asm("mcr        p15, 0, %0, c13, c0, 1\n" : : "r" (mm->context.id));
104         isb();
105 }
106
107 #else
108
109 static inline void set_mm_context(struct mm_struct *mm, unsigned int asid)
110 {
111         mm->context.id = asid;
112         cpumask_copy(mm_cpumask(mm), cpumask_of(smp_processor_id()));
113 }
114
115 #endif
116
117 void __new_context(struct mm_struct *mm)
118 {
119         unsigned int asid;
120
121         spin_lock(&cpu_asid_lock);
122 #ifdef CONFIG_SMP
123         /*
124          * Check the ASID again, in case the change was broadcast from
125          * another CPU before we acquired the lock.
126          */
127         if (unlikely(((mm->context.id ^ cpu_last_asid) >> ASID_BITS) == 0)) {
128                 cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
129                 spin_unlock(&cpu_asid_lock);
130                 return;
131         }
132 #endif
133         /*
134          * At this point, it is guaranteed that the current mm (with
135          * an old ASID) isn't active on any other CPU since the ASIDs
136          * are changed simultaneously via IPI.
137          */
138         asid = ++cpu_last_asid;
139         if (asid == 0)
140                 asid = cpu_last_asid = ASID_FIRST_VERSION;
141
142         /*
143          * If we've used up all our ASIDs, we need
144          * to start a new version and flush the TLB.
145          */
146         if (unlikely((asid & ~ASID_MASK) == 0)) {
147                 asid = cpu_last_asid + smp_processor_id();
148                 flush_context();
149 #ifdef CONFIG_SMP
150                 smp_wmb();
151                 smp_call_function(reset_context, NULL, 1);
152 #endif
153                 cpu_last_asid += NR_CPUS - 1;
154         }
155
156         set_mm_context(mm, asid);
157         spin_unlock(&cpu_asid_lock);
158 }