Commit | Line | Data |
---|---|---|
b97b8a99 BS |
1 | /* |
2 | * Blackfin CPLB exception handling. | |
3 | * Copyright 2004-2007 Analog Devices Inc. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License as published by | |
7 | * the Free Software Foundation; either version 2 of the License, or | |
8 | * (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License | |
16 | * along with this program; if not, see the file COPYING, or write | |
17 | * to the Free Software Foundation, Inc., | |
18 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
19 | */ | |
20 | #include <linux/module.h> | |
21 | #include <linux/mm.h> | |
22 | ||
23 | #include <asm/blackfin.h> | |
24 | #include <asm/cplbinit.h> | |
25 | #include <asm/mmu_context.h> | |
26 | ||
b97b8a99 BS |
27 | #define FAULT_RW (1 << 16) |
28 | #define FAULT_USERSUPV (1 << 17) | |
29 | ||
30 | int page_mask_nelts; | |
31 | int page_mask_order; | |
32 | unsigned long *current_rwx_mask; | |
33 | ||
34 | int nr_dcplb_miss, nr_icplb_miss, nr_icplb_supv_miss, nr_dcplb_prot; | |
35 | int nr_cplb_flush; | |
36 | ||
37 | static inline void disable_dcplb(void) | |
38 | { | |
39 | unsigned long ctrl; | |
40 | SSYNC(); | |
41 | ctrl = bfin_read_DMEM_CONTROL(); | |
42 | ctrl &= ~ENDCPLB; | |
43 | bfin_write_DMEM_CONTROL(ctrl); | |
44 | SSYNC(); | |
45 | } | |
46 | ||
47 | static inline void enable_dcplb(void) | |
48 | { | |
49 | unsigned long ctrl; | |
50 | SSYNC(); | |
51 | ctrl = bfin_read_DMEM_CONTROL(); | |
52 | ctrl |= ENDCPLB; | |
53 | bfin_write_DMEM_CONTROL(ctrl); | |
54 | SSYNC(); | |
55 | } | |
56 | ||
57 | static inline void disable_icplb(void) | |
58 | { | |
59 | unsigned long ctrl; | |
60 | SSYNC(); | |
61 | ctrl = bfin_read_IMEM_CONTROL(); | |
62 | ctrl &= ~ENICPLB; | |
63 | bfin_write_IMEM_CONTROL(ctrl); | |
64 | SSYNC(); | |
65 | } | |
66 | ||
67 | static inline void enable_icplb(void) | |
68 | { | |
69 | unsigned long ctrl; | |
70 | SSYNC(); | |
71 | ctrl = bfin_read_IMEM_CONTROL(); | |
72 | ctrl |= ENICPLB; | |
73 | bfin_write_IMEM_CONTROL(ctrl); | |
74 | SSYNC(); | |
75 | } | |
76 | ||
77 | /* | |
78 | * Given the contents of the status register, return the index of the | |
79 | * CPLB that caused the fault. | |
80 | */ | |
81 | static inline int faulting_cplb_index(int status) | |
82 | { | |
83 | int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF); | |
84 | return 30 - signbits; | |
85 | } | |
86 | ||
87 | /* | |
88 | * Given the contents of the status register and the DCPLB_DATA contents, | |
89 | * return true if a write access should be permitted. | |
90 | */ | |
91 | static inline int write_permitted(int status, unsigned long data) | |
92 | { | |
93 | if (status & FAULT_USERSUPV) | |
94 | return !!(data & CPLB_SUPV_WR); | |
95 | else | |
96 | return !!(data & CPLB_USER_WR); | |
97 | } | |
98 | ||
99 | /* Counters to implement round-robin replacement. */ | |
100 | static int icplb_rr_index, dcplb_rr_index; | |
101 | ||
102 | /* | |
103 | * Find an ICPLB entry to be evicted and return its index. | |
104 | */ | |
105 | static int evict_one_icplb(void) | |
106 | { | |
107 | int i; | |
108 | for (i = first_switched_icplb; i < MAX_CPLBS; i++) | |
109 | if ((icplb_tbl[i].data & CPLB_VALID) == 0) | |
110 | return i; | |
111 | i = first_switched_icplb + icplb_rr_index; | |
112 | if (i >= MAX_CPLBS) { | |
113 | i -= MAX_CPLBS - first_switched_icplb; | |
114 | icplb_rr_index -= MAX_CPLBS - first_switched_icplb; | |
115 | } | |
116 | icplb_rr_index++; | |
117 | return i; | |
118 | } | |
119 | ||
120 | static int evict_one_dcplb(void) | |
121 | { | |
122 | int i; | |
123 | for (i = first_switched_dcplb; i < MAX_CPLBS; i++) | |
124 | if ((dcplb_tbl[i].data & CPLB_VALID) == 0) | |
125 | return i; | |
126 | i = first_switched_dcplb + dcplb_rr_index; | |
127 | if (i >= MAX_CPLBS) { | |
128 | i -= MAX_CPLBS - first_switched_dcplb; | |
129 | dcplb_rr_index -= MAX_CPLBS - first_switched_dcplb; | |
130 | } | |
131 | dcplb_rr_index++; | |
132 | return i; | |
133 | } | |
134 | ||
135 | static noinline int dcplb_miss(void) | |
136 | { | |
137 | unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); | |
138 | int status = bfin_read_DCPLB_STATUS(); | |
139 | unsigned long *mask; | |
140 | int idx; | |
141 | unsigned long d_data; | |
142 | ||
143 | nr_dcplb_miss++; | |
b97b8a99 BS |
144 | |
145 | d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; | |
146 | #ifdef CONFIG_BFIN_DCACHE | |
1ebc723c BS |
147 | if (addr < _ramend - DMA_UNCACHED_REGION || |
148 | (reserved_mem_dcache_on && addr >= _ramend && | |
149 | addr < physical_mem_end)) { | |
b4bb68f7 | 150 | d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; |
dbfe44f0 | 151 | #ifdef CONFIG_BFIN_WT |
b4bb68f7 | 152 | d_data |= CPLB_L1_AOW | CPLB_WT; |
b97b8a99 | 153 | #endif |
b97b8a99 | 154 | } |
b4bb68f7 | 155 | #endif |
1ebc723c | 156 | if (addr >= physical_mem_end) { |
b4bb68f7 BS |
157 | if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE |
158 | && (status & FAULT_USERSUPV)) { | |
159 | addr &= ~0x3fffff; | |
160 | d_data &= ~PAGE_SIZE_4KB; | |
161 | d_data |= PAGE_SIZE_4MB; | |
4e354b54 MF |
162 | } else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH |
163 | && (status & (FAULT_RW | FAULT_USERSUPV)) == FAULT_USERSUPV) { | |
164 | addr &= ~(1 * 1024 * 1024 - 1); | |
165 | d_data &= ~PAGE_SIZE_4KB; | |
4bea8b20 | 166 | d_data |= PAGE_SIZE_1MB; |
b4bb68f7 BS |
167 | } else |
168 | return CPLB_PROT_VIOL; | |
1ebc723c BS |
169 | } else if (addr >= _ramend) { |
170 | d_data |= CPLB_USER_RD | CPLB_USER_WR; | |
b4bb68f7 BS |
171 | } else { |
172 | mask = current_rwx_mask; | |
173 | if (mask) { | |
174 | int page = addr >> PAGE_SHIFT; | |
175 | int offs = page >> 5; | |
176 | int bit = 1 << (page & 31); | |
177 | ||
178 | if (mask[offs] & bit) | |
179 | d_data |= CPLB_USER_RD; | |
b97b8a99 | 180 | |
b4bb68f7 BS |
181 | mask += page_mask_nelts; |
182 | if (mask[offs] & bit) | |
183 | d_data |= CPLB_USER_WR; | |
184 | } | |
185 | } | |
b97b8a99 BS |
186 | idx = evict_one_dcplb(); |
187 | ||
188 | addr &= PAGE_MASK; | |
189 | dcplb_tbl[idx].addr = addr; | |
190 | dcplb_tbl[idx].data = d_data; | |
191 | ||
192 | disable_dcplb(); | |
193 | bfin_write32(DCPLB_DATA0 + idx * 4, d_data); | |
194 | bfin_write32(DCPLB_ADDR0 + idx * 4, addr); | |
195 | enable_dcplb(); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static noinline int icplb_miss(void) | |
201 | { | |
202 | unsigned long addr = bfin_read_ICPLB_FAULT_ADDR(); | |
203 | int status = bfin_read_ICPLB_STATUS(); | |
204 | int idx; | |
205 | unsigned long i_data; | |
206 | ||
207 | nr_icplb_miss++; | |
b97b8a99 | 208 | |
1ebc723c BS |
209 | /* If inside the uncached DMA region, fault. */ |
210 | if (addr >= _ramend - DMA_UNCACHED_REGION && addr < _ramend) | |
b97b8a99 BS |
211 | return CPLB_PROT_VIOL; |
212 | ||
1ebc723c BS |
213 | if (status & FAULT_USERSUPV) |
214 | nr_icplb_supv_miss++; | |
215 | ||
b97b8a99 BS |
216 | /* |
217 | * First, try to find a CPLB that matches this address. If we | |
218 | * find one, then the fact that we're in the miss handler means | |
219 | * that the instruction crosses a page boundary. | |
220 | */ | |
221 | for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) { | |
222 | if (icplb_tbl[idx].data & CPLB_VALID) { | |
223 | unsigned long this_addr = icplb_tbl[idx].addr; | |
224 | if (this_addr <= addr && this_addr + PAGE_SIZE > addr) { | |
225 | addr += PAGE_SIZE; | |
226 | break; | |
227 | } | |
228 | } | |
229 | } | |
230 | ||
231 | i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB; | |
b97b8a99 | 232 | |
1ebc723c | 233 | #ifdef CONFIG_BFIN_ICACHE |
b97b8a99 | 234 | /* |
1ebc723c BS |
235 | * Normal RAM, and possibly the reserved memory area, are |
236 | * cacheable. | |
b97b8a99 | 237 | */ |
1ebc723c BS |
238 | if (addr < _ramend || |
239 | (addr < physical_mem_end && reserved_mem_icache_on)) | |
240 | i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; | |
241 | #endif | |
b97b8a99 | 242 | |
1ebc723c | 243 | if (addr >= physical_mem_end) { |
4bea8b20 MF |
244 | if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH |
245 | && (status & FAULT_USERSUPV)) { | |
246 | addr &= ~(1 * 1024 * 1024 - 1); | |
247 | i_data &= ~PAGE_SIZE_4KB; | |
248 | i_data |= PAGE_SIZE_1MB; | |
249 | } else | |
250 | return CPLB_PROT_VIOL; | |
1ebc723c BS |
251 | } else if (addr >= _ramend) { |
252 | i_data |= CPLB_USER_RD; | |
253 | } else { | |
254 | /* | |
255 | * Two cases to distinguish - a supervisor access must | |
256 | * necessarily be for a module page; we grant it | |
257 | * unconditionally (could do better here in the future). | |
258 | * Otherwise, check the x bitmap of the current process. | |
259 | */ | |
260 | if (!(status & FAULT_USERSUPV)) { | |
261 | unsigned long *mask = current_rwx_mask; | |
262 | ||
263 | if (mask) { | |
264 | int page = addr >> PAGE_SHIFT; | |
265 | int offs = page >> 5; | |
266 | int bit = 1 << (page & 31); | |
267 | ||
268 | mask += 2 * page_mask_nelts; | |
269 | if (mask[offs] & bit) | |
270 | i_data |= CPLB_USER_RD; | |
271 | } | |
b97b8a99 BS |
272 | } |
273 | } | |
b97b8a99 BS |
274 | idx = evict_one_icplb(); |
275 | addr &= PAGE_MASK; | |
276 | icplb_tbl[idx].addr = addr; | |
277 | icplb_tbl[idx].data = i_data; | |
278 | ||
279 | disable_icplb(); | |
280 | bfin_write32(ICPLB_DATA0 + idx * 4, i_data); | |
281 | bfin_write32(ICPLB_ADDR0 + idx * 4, addr); | |
282 | enable_icplb(); | |
283 | ||
284 | return 0; | |
285 | } | |
286 | ||
287 | static noinline int dcplb_protection_fault(void) | |
288 | { | |
b97b8a99 BS |
289 | int status = bfin_read_DCPLB_STATUS(); |
290 | ||
291 | nr_dcplb_prot++; | |
292 | ||
293 | if (status & FAULT_RW) { | |
294 | int idx = faulting_cplb_index(status); | |
295 | unsigned long data = dcplb_tbl[idx].data; | |
296 | if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) && | |
297 | write_permitted(status, data)) { | |
298 | data |= CPLB_DIRTY; | |
299 | dcplb_tbl[idx].data = data; | |
300 | bfin_write32(DCPLB_DATA0 + idx * 4, data); | |
301 | return 0; | |
302 | } | |
303 | } | |
304 | return CPLB_PROT_VIOL; | |
305 | } | |
306 | ||
307 | int cplb_hdr(int seqstat, struct pt_regs *regs) | |
308 | { | |
309 | int cause = seqstat & 0x3f; | |
310 | switch (cause) { | |
311 | case 0x23: | |
312 | return dcplb_protection_fault(); | |
313 | case 0x2C: | |
314 | return icplb_miss(); | |
315 | case 0x26: | |
316 | return dcplb_miss(); | |
317 | default: | |
b4bb68f7 | 318 | return 1; |
b97b8a99 BS |
319 | } |
320 | } | |
321 | ||
322 | void flush_switched_cplbs(void) | |
323 | { | |
324 | int i; | |
325 | ||
326 | nr_cplb_flush++; | |
327 | ||
328 | disable_icplb(); | |
329 | for (i = first_switched_icplb; i < MAX_CPLBS; i++) { | |
330 | icplb_tbl[i].data = 0; | |
331 | bfin_write32(ICPLB_DATA0 + i * 4, 0); | |
332 | } | |
333 | enable_icplb(); | |
334 | ||
335 | disable_dcplb(); | |
d56daae9 | 336 | for (i = first_switched_dcplb; i < MAX_CPLBS; i++) { |
b97b8a99 BS |
337 | dcplb_tbl[i].data = 0; |
338 | bfin_write32(DCPLB_DATA0 + i * 4, 0); | |
339 | } | |
340 | enable_dcplb(); | |
341 | } | |
342 | ||
343 | void set_mask_dcplbs(unsigned long *masks) | |
344 | { | |
345 | int i; | |
346 | unsigned long addr = (unsigned long)masks; | |
347 | unsigned long d_data; | |
348 | current_rwx_mask = masks; | |
349 | ||
350 | if (!masks) | |
351 | return; | |
352 | ||
353 | d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; | |
354 | #ifdef CONFIG_BFIN_DCACHE | |
355 | d_data |= CPLB_L1_CHBL; | |
dbfe44f0 | 356 | #ifdef CONFIG_BFIN_WT |
b97b8a99 BS |
357 | d_data |= CPLB_L1_AOW | CPLB_WT; |
358 | #endif | |
359 | #endif | |
360 | ||
361 | disable_dcplb(); | |
362 | for (i = first_mask_dcplb; i < first_switched_dcplb; i++) { | |
363 | dcplb_tbl[i].addr = addr; | |
364 | dcplb_tbl[i].data = d_data; | |
365 | bfin_write32(DCPLB_DATA0 + i * 4, d_data); | |
366 | bfin_write32(DCPLB_ADDR0 + i * 4, addr); | |
367 | addr += PAGE_SIZE; | |
368 | } | |
369 | enable_dcplb(); | |
370 | } |