Commit | Line | Data |
---|---|---|
dbdf20db BS |
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 | } |