Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/sparc-2.6
[pandora-kernel.git] / arch / blackfin / kernel / cplb-nompu / cplbmgr.c
1 /*
2  * File:         arch/blackfin/kernel/cplb-nompu-c/cplbmgr.c
3  * Based on:     arch/blackfin/kernel/cplb-mpu/cplbmgr.c
4  * Author:       Michael McTernan <mmcternan@airvana.com>
5  *
6  * Created:      01Nov2008
7  * Description:  CPLB miss handler.
8  *
9  * Modified:
10  *               Copyright 2008 Airvana Inc.
11  *               Copyright 2004-2007 Analog Devices Inc.
12  *
13  * Bugs:         Enter bugs at http://blackfin.uclinux.org/
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  */
25
26 #include <linux/kernel.h>
27 #include <asm/blackfin.h>
28 #include <asm/cplbinit.h>
29 #include <asm/cplb.h>
30 #include <asm/mmu_context.h>
31 #include <asm/traps.h>
32
33 /*
34  * WARNING
35  *
36  * This file is compiled with certain -ffixed-reg options.  We have to
37  * make sure not to call any functions here that could clobber these
38  * registers.
39  */
40
41 int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS];
42 int nr_dcplb_supv_miss[NR_CPUS], nr_icplb_supv_miss[NR_CPUS];
43 int nr_cplb_flush[NR_CPUS], nr_dcplb_prot[NR_CPUS];
44
45 #ifdef CONFIG_EXCPT_IRQ_SYSC_L1
46 #define MGR_ATTR __attribute__((l1_text))
47 #else
48 #define MGR_ATTR
49 #endif
50
51 static inline void write_dcplb_data(int cpu, int idx, unsigned long data,
52                                     unsigned long addr)
53 {
54         _disable_dcplb();
55         bfin_write32(DCPLB_DATA0 + idx * 4, data);
56         bfin_write32(DCPLB_ADDR0 + idx * 4, addr);
57         _enable_dcplb();
58
59 #ifdef CONFIG_CPLB_INFO
60         dcplb_tbl[cpu][idx].addr = addr;
61         dcplb_tbl[cpu][idx].data = data;
62 #endif
63 }
64
65 static inline void write_icplb_data(int cpu, int idx, unsigned long data,
66                                     unsigned long addr)
67 {
68         _disable_icplb();
69         bfin_write32(ICPLB_DATA0 + idx * 4, data);
70         bfin_write32(ICPLB_ADDR0 + idx * 4, addr);
71         _enable_icplb();
72
73 #ifdef CONFIG_CPLB_INFO
74         icplb_tbl[cpu][idx].addr = addr;
75         icplb_tbl[cpu][idx].data = data;
76 #endif
77 }
78
79 /* Counters to implement round-robin replacement.  */
80 static int icplb_rr_index[NR_CPUS] PDT_ATTR;
81 static int dcplb_rr_index[NR_CPUS] PDT_ATTR;
82
83 /*
84  * Find an ICPLB entry to be evicted and return its index.
85  */
86 static int evict_one_icplb(int cpu)
87 {
88         int i = first_switched_icplb + icplb_rr_index[cpu];
89         if (i >= MAX_CPLBS) {
90                 i -= MAX_CPLBS - first_switched_icplb;
91                 icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb;
92         }
93         icplb_rr_index[cpu]++;
94         return i;
95 }
96
97 static int evict_one_dcplb(int cpu)
98 {
99         int i = first_switched_dcplb + dcplb_rr_index[cpu];
100         if (i >= MAX_CPLBS) {
101                 i -= MAX_CPLBS - first_switched_dcplb;
102                 dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb;
103         }
104         dcplb_rr_index[cpu]++;
105         return i;
106 }
107
108 MGR_ATTR static int icplb_miss(int cpu)
109 {
110         unsigned long addr = bfin_read_ICPLB_FAULT_ADDR();
111         int status = bfin_read_ICPLB_STATUS();
112         int idx;
113         unsigned long i_data, base, addr1, eaddr;
114
115         nr_icplb_miss[cpu]++;
116         if (unlikely(status & FAULT_USERSUPV))
117                 nr_icplb_supv_miss[cpu]++;
118
119         base = 0;
120         idx = 0;
121         do {
122                 eaddr = icplb_bounds[idx].eaddr;
123                 if (addr < eaddr)
124                         break;
125                 base = eaddr;
126         } while (++idx < icplb_nr_bounds);
127
128         if (unlikely(idx == icplb_nr_bounds))
129                 return CPLB_NO_ADDR_MATCH;
130
131         i_data = icplb_bounds[idx].data;
132         if (unlikely(i_data == 0))
133                 return CPLB_NO_ADDR_MATCH;
134
135         addr1 = addr & ~(SIZE_4M - 1);
136         addr &= ~(SIZE_1M - 1);
137         i_data |= PAGE_SIZE_1MB;
138         if (addr1 >= base && (addr1 + SIZE_4M) <= eaddr) {
139                 /*
140                  * This works because
141                  * (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB.
142                  */
143                 i_data |= PAGE_SIZE_4MB;
144                 addr = addr1;
145         }
146
147         /* Pick entry to evict */
148         idx = evict_one_icplb(cpu);
149
150         write_icplb_data(cpu, idx, i_data, addr);
151
152         return CPLB_RELOADED;
153 }
154
155 MGR_ATTR static int dcplb_miss(int cpu)
156 {
157         unsigned long addr = bfin_read_DCPLB_FAULT_ADDR();
158         int status = bfin_read_DCPLB_STATUS();
159         int idx;
160         unsigned long d_data, base, addr1, eaddr;
161
162         nr_dcplb_miss[cpu]++;
163         if (unlikely(status & FAULT_USERSUPV))
164                 nr_dcplb_supv_miss[cpu]++;
165
166         base = 0;
167         idx = 0;
168         do {
169                 eaddr = dcplb_bounds[idx].eaddr;
170                 if (addr < eaddr)
171                         break;
172                 base = eaddr;
173         } while (++idx < dcplb_nr_bounds);
174
175         if (unlikely(idx == dcplb_nr_bounds))
176                 return CPLB_NO_ADDR_MATCH;
177
178         d_data = dcplb_bounds[idx].data;
179         if (unlikely(d_data == 0))
180                 return CPLB_NO_ADDR_MATCH;
181
182         addr1 = addr & ~(SIZE_4M - 1);
183         addr &= ~(SIZE_1M - 1);
184         d_data |= PAGE_SIZE_1MB;
185         if (addr1 >= base && (addr1 + SIZE_4M) <= eaddr) {
186                 /*
187                  * This works because
188                  * (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB.
189                  */
190                 d_data |= PAGE_SIZE_4MB;
191                 addr = addr1;
192         }
193
194         /* Pick entry to evict */
195         idx = evict_one_dcplb(cpu);
196
197         write_dcplb_data(cpu, idx, d_data, addr);
198
199         return CPLB_RELOADED;
200 }
201
202 MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs)
203 {
204         int cause = seqstat & 0x3f;
205         unsigned int cpu = raw_smp_processor_id();
206         switch (cause) {
207         case VEC_CPLB_I_M:
208                 return icplb_miss(cpu);
209         case VEC_CPLB_M:
210                 return dcplb_miss(cpu);
211         default:
212                 return CPLB_UNKNOWN_ERR;
213         }
214 }