diff options
author | Bernd Schmidt <bernds_cb1@t-online.de> | 2009-01-07 10:14:38 -0500 |
---|---|---|
committer | Bryan Wu <cooloney@kernel.org> | 2009-01-07 10:14:38 -0500 |
commit | dbdf20db537a5369c65330f878ad4905020a8bfa (patch) | |
tree | c7fa553755e2d75a6e98d3f32fbe41fab9f72609 /arch/blackfin/kernel/cplb-nompu/cplbmgr.c | |
parent | 6651ece9e257302ee695ee76e69a4427f7033235 (diff) |
Blackfin arch: Faster C implementation of no-MPU CPLB handler
This is a mixture ofcMichael McTernan's patch and the existing cplb-mpu code.
We ditch the old cplb-nompu implementation, which is a good example of
why a good algorithm in a HLL is preferrable to a bad algorithm written in
assembly. Rather than try to construct a table of all posible CPLBs and
search it, we just create a (smaller) table of memory regions and
their attributes. Some of the data structures are now unified for both
the mpu and nompu cases. A lot of needless complexity in cplbinit.c is
removed.
Further optimizations:
* compile cplbmgr.c with a lot of -ffixed-reg options, and omit saving
these registers on the stack when entering a CPLB exception.
* lose cli/nop/nop/sti sequences for some workarounds - these don't
* make
sense in an exception context
Additional code unification should be possible after this.
[Mike Frysinger <vapier.adi@gmail.com>:
- convert CPP if statements to C if statements
- remove redundant statements
- use a do...while loop rather than a for loop to get slightly better
optimization and to avoid gcc "may be used uninitialized" warnings ...
we know that the [id]cplb_nr_bounds variables will never be 0, so this
is OK
- the no-mpu code was the last user of MAX_MEM_SIZE and with that rewritten,
we can punt it
- add some BUG_ON() checks to make sure we dont overflow the small
cplb_bounds array
- add i/d cplb entries for the bootrom because there is functions/data in
there we want to access
- we do not need a NULL trailing entry as any time we access the bounds
arrays, we use the nr_bounds variable
]
Signed-off-by: Michael McTernan <mmcternan@airvana.com>
Signed-off-by: Mike Frysinger <vapier.adi@gmail.com>
Signed-off-by: Bernd Schmidt <bernds_cb1@t-online.de>
Signed-off-by: Bryan Wu <cooloney@kernel.org>
Diffstat (limited to 'arch/blackfin/kernel/cplb-nompu/cplbmgr.c')
-rw-r--r-- | arch/blackfin/kernel/cplb-nompu/cplbmgr.c | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/arch/blackfin/kernel/cplb-nompu/cplbmgr.c b/arch/blackfin/kernel/cplb-nompu/cplbmgr.c new file mode 100644 index 000000000000..376249ab2694 --- /dev/null +++ b/arch/blackfin/kernel/cplb-nompu/cplbmgr.c | |||
@@ -0,0 +1,283 @@ | |||
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 | |||
32 | /* | ||
33 | * WARNING | ||
34 | * | ||
35 | * This file is compiled with certain -ffixed-reg options. We have to | ||
36 | * make sure not to call any functions here that could clobber these | ||
37 | * registers. | ||
38 | */ | ||
39 | |||
40 | int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS]; | ||
41 | int nr_dcplb_supv_miss[NR_CPUS], nr_icplb_supv_miss[NR_CPUS]; | ||
42 | int nr_cplb_flush[NR_CPUS], nr_dcplb_prot[NR_CPUS]; | ||
43 | |||
44 | #ifdef CONFIG_EXCPT_IRQ_SYSC_L1 | ||
45 | #define MGR_ATTR __attribute__((l1_text)) | ||
46 | #else | ||
47 | #define MGR_ATTR | ||
48 | #endif | ||
49 | |||
50 | /* | ||
51 | * We're in an exception handler. The normal cli nop nop workaround | ||
52 | * isn't going to do very much, as the only thing that can interrupt | ||
53 | * us is an NMI, and the cli isn't going to stop that. | ||
54 | */ | ||
55 | #define NOWA_SSYNC __asm__ __volatile__ ("ssync;") | ||
56 | |||
57 | /* Anomaly handlers provide SSYNCs, so avoid extra if anomaly is present */ | ||
58 | #if ANOMALY_05000125 | ||
59 | |||
60 | #define bfin_write_DMEM_CONTROL_SSYNC(v) bfin_write_DMEM_CONTROL(v) | ||
61 | #define bfin_write_IMEM_CONTROL_SSYNC(v) bfin_write_IMEM_CONTROL(v) | ||
62 | |||
63 | #else | ||
64 | |||
65 | #define bfin_write_DMEM_CONTROL_SSYNC(v) \ | ||
66 | do { NOWA_SSYNC; bfin_write_DMEM_CONTROL(v); NOWA_SSYNC; } while (0) | ||
67 | #define bfin_write_IMEM_CONTROL_SSYNC(v) \ | ||
68 | do { NOWA_SSYNC; bfin_write_IMEM_CONTROL(v); NOWA_SSYNC; } while (0) | ||
69 | |||
70 | #endif | ||
71 | |||
72 | static inline void write_dcplb_data(int cpu, int idx, unsigned long data, | ||
73 | unsigned long addr) | ||
74 | { | ||
75 | unsigned long ctrl = bfin_read_DMEM_CONTROL(); | ||
76 | bfin_write_DMEM_CONTROL_SSYNC(ctrl & ~ENDCPLB); | ||
77 | bfin_write32(DCPLB_DATA0 + idx * 4, data); | ||
78 | bfin_write32(DCPLB_ADDR0 + idx * 4, addr); | ||
79 | bfin_write_DMEM_CONTROL_SSYNC(ctrl); | ||
80 | |||
81 | #ifdef CONFIG_CPLB_INFO | ||
82 | dcplb_tbl[cpu][idx].addr = addr; | ||
83 | dcplb_tbl[cpu][idx].data = data; | ||
84 | #endif | ||
85 | } | ||
86 | |||
87 | static inline void write_icplb_data(int cpu, int idx, unsigned long data, | ||
88 | unsigned long addr) | ||
89 | { | ||
90 | unsigned long ctrl = bfin_read_IMEM_CONTROL(); | ||
91 | |||
92 | bfin_write_IMEM_CONTROL_SSYNC(ctrl & ~ENICPLB); | ||
93 | bfin_write32(ICPLB_DATA0 + idx * 4, data); | ||
94 | bfin_write32(ICPLB_ADDR0 + idx * 4, addr); | ||
95 | bfin_write_IMEM_CONTROL_SSYNC(ctrl); | ||
96 | |||
97 | #ifdef CONFIG_CPLB_INFO | ||
98 | icplb_tbl[cpu][idx].addr = addr; | ||
99 | icplb_tbl[cpu][idx].data = data; | ||
100 | #endif | ||
101 | } | ||
102 | |||
103 | /* | ||
104 | * Given the contents of the status register, return the index of the | ||
105 | * CPLB that caused the fault. | ||
106 | */ | ||
107 | static inline int faulting_cplb_index(int status) | ||
108 | { | ||
109 | int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF); | ||
110 | return 30 - signbits; | ||
111 | } | ||
112 | |||
113 | /* | ||
114 | * Given the contents of the status register and the DCPLB_DATA contents, | ||
115 | * return true if a write access should be permitted. | ||
116 | */ | ||
117 | static inline int write_permitted(int status, unsigned long data) | ||
118 | { | ||
119 | if (status & FAULT_USERSUPV) | ||
120 | return !!(data & CPLB_SUPV_WR); | ||
121 | else | ||
122 | return !!(data & CPLB_USER_WR); | ||
123 | } | ||
124 | |||
125 | /* Counters to implement round-robin replacement. */ | ||
126 | static int icplb_rr_index[NR_CPUS] PDT_ATTR; | ||
127 | static int dcplb_rr_index[NR_CPUS] PDT_ATTR; | ||
128 | |||
129 | /* | ||
130 | * Find an ICPLB entry to be evicted and return its index. | ||
131 | */ | ||
132 | static int evict_one_icplb(int cpu) | ||
133 | { | ||
134 | int i = first_switched_icplb + icplb_rr_index[cpu]; | ||
135 | if (i >= MAX_CPLBS) { | ||
136 | i -= MAX_CPLBS - first_switched_icplb; | ||
137 | icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb; | ||
138 | } | ||
139 | icplb_rr_index[cpu]++; | ||
140 | return i; | ||
141 | } | ||
142 | |||
143 | static int evict_one_dcplb(int cpu) | ||
144 | { | ||
145 | int i = first_switched_dcplb + dcplb_rr_index[cpu]; | ||
146 | if (i >= MAX_CPLBS) { | ||
147 | i -= MAX_CPLBS - first_switched_dcplb; | ||
148 | dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb; | ||
149 | } | ||
150 | dcplb_rr_index[cpu]++; | ||
151 | return i; | ||
152 | } | ||
153 | |||
154 | MGR_ATTR static int icplb_miss(int cpu) | ||
155 | { | ||
156 | unsigned long addr = bfin_read_ICPLB_FAULT_ADDR(); | ||
157 | int status = bfin_read_ICPLB_STATUS(); | ||
158 | int idx; | ||
159 | unsigned long i_data, base, addr1, eaddr; | ||
160 | |||
161 | nr_icplb_miss[cpu]++; | ||
162 | if (unlikely(status & FAULT_USERSUPV)) | ||
163 | nr_icplb_supv_miss[cpu]++; | ||
164 | |||
165 | base = 0; | ||
166 | for (idx = 0; idx < icplb_nr_bounds; idx++) { | ||
167 | eaddr = icplb_bounds[idx].eaddr; | ||
168 | if (addr < eaddr) | ||
169 | break; | ||
170 | base = eaddr; | ||
171 | } | ||
172 | if (unlikely(idx == icplb_nr_bounds)) | ||
173 | return CPLB_NO_ADDR_MATCH; | ||
174 | |||
175 | i_data = icplb_bounds[idx].data; | ||
176 | if (unlikely(i_data == 0)) | ||
177 | return CPLB_NO_ADDR_MATCH; | ||
178 | |||
179 | addr1 = addr & ~(SIZE_4M - 1); | ||
180 | addr &= ~(SIZE_1M - 1); | ||
181 | i_data |= PAGE_SIZE_1MB; | ||
182 | if (addr1 >= base && (addr1 + SIZE_4M) <= eaddr) { | ||
183 | /* | ||
184 | * This works because | ||
185 | * (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB. | ||
186 | */ | ||
187 | i_data |= PAGE_SIZE_4MB; | ||
188 | addr = addr1; | ||
189 | } | ||
190 | |||
191 | /* Pick entry to evict */ | ||
192 | idx = evict_one_icplb(cpu); | ||
193 | |||
194 | write_icplb_data(cpu, idx, i_data, addr); | ||
195 | |||
196 | return CPLB_RELOADED; | ||
197 | } | ||
198 | |||
199 | MGR_ATTR static int dcplb_miss(int cpu) | ||
200 | { | ||
201 | unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); | ||
202 | int status = bfin_read_DCPLB_STATUS(); | ||
203 | int idx; | ||
204 | unsigned long d_data, base, addr1, eaddr; | ||
205 | |||
206 | nr_dcplb_miss[cpu]++; | ||
207 | if (unlikely(status & FAULT_USERSUPV)) | ||
208 | nr_dcplb_supv_miss[cpu]++; | ||
209 | |||
210 | base = 0; | ||
211 | for (idx = 0; idx < dcplb_nr_bounds; idx++) { | ||
212 | eaddr = dcplb_bounds[idx].eaddr; | ||
213 | if (addr < eaddr) | ||
214 | break; | ||
215 | base = eaddr; | ||
216 | } | ||
217 | if (unlikely(idx == dcplb_nr_bounds)) | ||
218 | return CPLB_NO_ADDR_MATCH; | ||
219 | |||
220 | d_data = dcplb_bounds[idx].data; | ||
221 | if (unlikely(d_data == 0)) | ||
222 | return CPLB_NO_ADDR_MATCH; | ||
223 | |||
224 | addr1 = addr & ~(SIZE_4M - 1); | ||
225 | addr &= ~(SIZE_1M - 1); | ||
226 | d_data |= PAGE_SIZE_1MB; | ||
227 | if (addr1 >= base && (addr1 + SIZE_4M) <= eaddr) { | ||
228 | /* | ||
229 | * This works because | ||
230 | * (PAGE_SIZE_4MB & PAGE_SIZE_1MB) == PAGE_SIZE_1MB. | ||
231 | */ | ||
232 | d_data |= PAGE_SIZE_4MB; | ||
233 | addr = addr1; | ||
234 | } | ||
235 | |||
236 | /* Pick entry to evict */ | ||
237 | idx = evict_one_dcplb(cpu); | ||
238 | |||
239 | write_dcplb_data(cpu, idx, d_data, addr); | ||
240 | |||
241 | return CPLB_RELOADED; | ||
242 | } | ||
243 | |||
244 | MGR_ATTR static noinline int dcplb_protection_fault(int cpu) | ||
245 | { | ||
246 | int status = bfin_read_DCPLB_STATUS(); | ||
247 | |||
248 | nr_dcplb_prot[cpu]++; | ||
249 | |||
250 | if (likely(status & FAULT_RW)) { | ||
251 | int idx = faulting_cplb_index(status); | ||
252 | unsigned long regaddr = DCPLB_DATA0 + idx * 4; | ||
253 | unsigned long data = bfin_read32(regaddr); | ||
254 | |||
255 | /* Check if fault is to dirty a clean page */ | ||
256 | if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) && | ||
257 | write_permitted(status, data)) { | ||
258 | |||
259 | dcplb_tbl[cpu][idx].data = data; | ||
260 | bfin_write32(regaddr, data); | ||
261 | return CPLB_RELOADED; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | return CPLB_PROT_VIOL; | ||
266 | } | ||
267 | |||
268 | MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs) | ||
269 | { | ||
270 | int cause = seqstat & 0x3f; | ||
271 | unsigned int cpu = smp_processor_id(); | ||
272 | switch (cause) { | ||
273 | case 0x2C: | ||
274 | return icplb_miss(cpu); | ||
275 | case 0x26: | ||
276 | return dcplb_miss(cpu); | ||
277 | default: | ||
278 | if (unlikely(cause == 0x23)) | ||
279 | return dcplb_protection_fault(cpu); | ||
280 | |||
281 | return CPLB_UNKNOWN_ERR; | ||
282 | } | ||
283 | } | ||