Commit | Line | Data |
---|---|---|
b97b8a99 | 1 | /* |
96f1050d | 2 | * Blackfin CPLB exception handling for when MPU in on |
b97b8a99 | 3 | * |
96f1050d | 4 | * Copyright 2008-2009 Analog Devices Inc. |
b97b8a99 | 5 | * |
96f1050d | 6 | * Licensed under the GPL-2 or later. |
b97b8a99 | 7 | */ |
96f1050d | 8 | |
b97b8a99 BS |
9 | #include <linux/module.h> |
10 | #include <linux/mm.h> | |
11 | ||
12 | #include <asm/blackfin.h> | |
a92946bc | 13 | #include <asm/cacheflush.h> |
eb7bd9c4 | 14 | #include <asm/cplb.h> |
b97b8a99 BS |
15 | #include <asm/cplbinit.h> |
16 | #include <asm/mmu_context.h> | |
17 | ||
dbdf20db BS |
18 | /* |
19 | * WARNING | |
20 | * | |
21 | * This file is compiled with certain -ffixed-reg options. We have to | |
22 | * make sure not to call any functions here that could clobber these | |
23 | * registers. | |
24 | */ | |
b97b8a99 BS |
25 | |
26 | int page_mask_nelts; | |
27 | int page_mask_order; | |
b8a98989 | 28 | unsigned long *current_rwx_mask[NR_CPUS]; |
b97b8a99 | 29 | |
b8a98989 GY |
30 | int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS]; |
31 | int nr_icplb_supv_miss[NR_CPUS], nr_dcplb_prot[NR_CPUS]; | |
32 | int nr_cplb_flush[NR_CPUS]; | |
b97b8a99 | 33 | |
726e9656 BS |
34 | #ifdef CONFIG_EXCPT_IRQ_SYSC_L1 |
35 | #define MGR_ATTR __attribute__((l1_text)) | |
36 | #else | |
37 | #define MGR_ATTR | |
38 | #endif | |
39 | ||
b97b8a99 BS |
40 | /* |
41 | * Given the contents of the status register, return the index of the | |
42 | * CPLB that caused the fault. | |
43 | */ | |
44 | static inline int faulting_cplb_index(int status) | |
45 | { | |
46 | int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF); | |
47 | return 30 - signbits; | |
48 | } | |
49 | ||
50 | /* | |
51 | * Given the contents of the status register and the DCPLB_DATA contents, | |
52 | * return true if a write access should be permitted. | |
53 | */ | |
54 | static inline int write_permitted(int status, unsigned long data) | |
55 | { | |
56 | if (status & FAULT_USERSUPV) | |
57 | return !!(data & CPLB_SUPV_WR); | |
58 | else | |
59 | return !!(data & CPLB_USER_WR); | |
60 | } | |
61 | ||
62 | /* Counters to implement round-robin replacement. */ | |
b8a98989 | 63 | static int icplb_rr_index[NR_CPUS], dcplb_rr_index[NR_CPUS]; |
b97b8a99 BS |
64 | |
65 | /* | |
66 | * Find an ICPLB entry to be evicted and return its index. | |
67 | */ | |
726e9656 | 68 | MGR_ATTR static int evict_one_icplb(unsigned int cpu) |
b97b8a99 BS |
69 | { |
70 | int i; | |
71 | for (i = first_switched_icplb; i < MAX_CPLBS; i++) | |
b8a98989 | 72 | if ((icplb_tbl[cpu][i].data & CPLB_VALID) == 0) |
b97b8a99 | 73 | return i; |
b8a98989 | 74 | i = first_switched_icplb + icplb_rr_index[cpu]; |
b97b8a99 BS |
75 | if (i >= MAX_CPLBS) { |
76 | i -= MAX_CPLBS - first_switched_icplb; | |
b8a98989 | 77 | icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb; |
b97b8a99 | 78 | } |
b8a98989 | 79 | icplb_rr_index[cpu]++; |
b97b8a99 BS |
80 | return i; |
81 | } | |
82 | ||
726e9656 | 83 | MGR_ATTR static int evict_one_dcplb(unsigned int cpu) |
b97b8a99 BS |
84 | { |
85 | int i; | |
86 | for (i = first_switched_dcplb; i < MAX_CPLBS; i++) | |
b8a98989 | 87 | if ((dcplb_tbl[cpu][i].data & CPLB_VALID) == 0) |
b97b8a99 | 88 | return i; |
b8a98989 | 89 | i = first_switched_dcplb + dcplb_rr_index[cpu]; |
b97b8a99 BS |
90 | if (i >= MAX_CPLBS) { |
91 | i -= MAX_CPLBS - first_switched_dcplb; | |
b8a98989 | 92 | dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb; |
b97b8a99 | 93 | } |
b8a98989 | 94 | dcplb_rr_index[cpu]++; |
b97b8a99 BS |
95 | return i; |
96 | } | |
97 | ||
726e9656 | 98 | MGR_ATTR static noinline int dcplb_miss(unsigned int cpu) |
b97b8a99 BS |
99 | { |
100 | unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); | |
101 | int status = bfin_read_DCPLB_STATUS(); | |
102 | unsigned long *mask; | |
103 | int idx; | |
104 | unsigned long d_data; | |
105 | ||
b8a98989 | 106 | nr_dcplb_miss[cpu]++; |
b97b8a99 BS |
107 | |
108 | d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; | |
41ba653f | 109 | #ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE |
67834fa9 | 110 | if (bfin_addr_dcacheable(addr)) { |
b4bb68f7 | 111 | d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; |
41ba653f | 112 | # ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH |
b4bb68f7 | 113 | d_data |= CPLB_L1_AOW | CPLB_WT; |
41ba653f | 114 | # endif |
b97b8a99 | 115 | } |
b4bb68f7 | 116 | #endif |
41ba653f JZ |
117 | |
118 | if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) { | |
119 | addr = L2_START; | |
120 | d_data = L2_DMEMORY; | |
121 | } else if (addr >= physical_mem_end) { | |
e187837b | 122 | if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) { |
4936afc6 | 123 | #if defined(CONFIG_ROMFS_ON_MTD) && defined(CONFIG_MTD_ROM) |
e18e7dd3 BS |
124 | mask = current_rwx_mask[cpu]; |
125 | if (mask) { | |
126 | int page = (addr - (ASYNC_BANK0_BASE - _ramend)) >> PAGE_SHIFT; | |
127 | int idx = page >> 5; | |
128 | int bit = 1 << (page & 31); | |
129 | ||
130 | if (mask[idx] & bit) | |
131 | d_data |= CPLB_USER_RD; | |
132 | } | |
4936afc6 | 133 | #endif |
4e354b54 MF |
134 | } else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH |
135 | && (status & (FAULT_RW | FAULT_USERSUPV)) == FAULT_USERSUPV) { | |
136 | addr &= ~(1 * 1024 * 1024 - 1); | |
137 | d_data &= ~PAGE_SIZE_4KB; | |
4bea8b20 | 138 | d_data |= PAGE_SIZE_1MB; |
b4bb68f7 BS |
139 | } else |
140 | return CPLB_PROT_VIOL; | |
1ebc723c | 141 | } else if (addr >= _ramend) { |
5792ab2a SZ |
142 | d_data |= CPLB_USER_RD | CPLB_USER_WR; |
143 | if (reserved_mem_dcache_on) | |
144 | d_data |= CPLB_L1_CHBL; | |
b4bb68f7 | 145 | } else { |
b8a98989 | 146 | mask = current_rwx_mask[cpu]; |
b4bb68f7 BS |
147 | if (mask) { |
148 | int page = addr >> PAGE_SHIFT; | |
b8a98989 | 149 | int idx = page >> 5; |
b4bb68f7 BS |
150 | int bit = 1 << (page & 31); |
151 | ||
b8a98989 | 152 | if (mask[idx] & bit) |
b4bb68f7 | 153 | d_data |= CPLB_USER_RD; |
b97b8a99 | 154 | |
b4bb68f7 | 155 | mask += page_mask_nelts; |
b8a98989 | 156 | if (mask[idx] & bit) |
b4bb68f7 BS |
157 | d_data |= CPLB_USER_WR; |
158 | } | |
159 | } | |
b8a98989 | 160 | idx = evict_one_dcplb(cpu); |
b97b8a99 BS |
161 | |
162 | addr &= PAGE_MASK; | |
b8a98989 GY |
163 | dcplb_tbl[cpu][idx].addr = addr; |
164 | dcplb_tbl[cpu][idx].data = d_data; | |
b97b8a99 | 165 | |
eb7bd9c4 | 166 | _disable_dcplb(); |
b97b8a99 BS |
167 | bfin_write32(DCPLB_DATA0 + idx * 4, d_data); |
168 | bfin_write32(DCPLB_ADDR0 + idx * 4, addr); | |
eb7bd9c4 | 169 | _enable_dcplb(); |
b97b8a99 BS |
170 | |
171 | return 0; | |
172 | } | |
173 | ||
726e9656 | 174 | MGR_ATTR static noinline int icplb_miss(unsigned int cpu) |
b97b8a99 BS |
175 | { |
176 | unsigned long addr = bfin_read_ICPLB_FAULT_ADDR(); | |
177 | int status = bfin_read_ICPLB_STATUS(); | |
178 | int idx; | |
179 | unsigned long i_data; | |
180 | ||
b8a98989 | 181 | nr_icplb_miss[cpu]++; |
b97b8a99 | 182 | |
1ebc723c BS |
183 | /* If inside the uncached DMA region, fault. */ |
184 | if (addr >= _ramend - DMA_UNCACHED_REGION && addr < _ramend) | |
b97b8a99 BS |
185 | return CPLB_PROT_VIOL; |
186 | ||
1ebc723c | 187 | if (status & FAULT_USERSUPV) |
b8a98989 | 188 | nr_icplb_supv_miss[cpu]++; |
1ebc723c | 189 | |
b97b8a99 BS |
190 | /* |
191 | * First, try to find a CPLB that matches this address. If we | |
192 | * find one, then the fact that we're in the miss handler means | |
193 | * that the instruction crosses a page boundary. | |
194 | */ | |
195 | for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) { | |
b8a98989 GY |
196 | if (icplb_tbl[cpu][idx].data & CPLB_VALID) { |
197 | unsigned long this_addr = icplb_tbl[cpu][idx].addr; | |
b97b8a99 BS |
198 | if (this_addr <= addr && this_addr + PAGE_SIZE > addr) { |
199 | addr += PAGE_SIZE; | |
200 | break; | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
205 | i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB; | |
b97b8a99 | 206 | |
41ba653f | 207 | #ifdef CONFIG_BFIN_EXTMEM_ICACHEABLE |
b97b8a99 | 208 | /* |
1ebc723c BS |
209 | * Normal RAM, and possibly the reserved memory area, are |
210 | * cacheable. | |
b97b8a99 | 211 | */ |
1ebc723c BS |
212 | if (addr < _ramend || |
213 | (addr < physical_mem_end && reserved_mem_icache_on)) | |
214 | i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; | |
215 | #endif | |
b97b8a99 | 216 | |
41ba653f JZ |
217 | if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) { |
218 | addr = L2_START; | |
219 | i_data = L2_IMEMORY; | |
220 | } else if (addr >= physical_mem_end) { | |
e187837b | 221 | if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) { |
e18e7dd3 BS |
222 | if (!(status & FAULT_USERSUPV)) { |
223 | unsigned long *mask = current_rwx_mask[cpu]; | |
224 | ||
225 | if (mask) { | |
226 | int page = (addr - (ASYNC_BANK0_BASE - _ramend)) >> PAGE_SHIFT; | |
227 | int idx = page >> 5; | |
228 | int bit = 1 << (page & 31); | |
229 | ||
230 | mask += 2 * page_mask_nelts; | |
231 | if (mask[idx] & bit) | |
232 | i_data |= CPLB_USER_RD; | |
233 | } | |
234 | } | |
e187837b | 235 | } else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH |
4bea8b20 MF |
236 | && (status & FAULT_USERSUPV)) { |
237 | addr &= ~(1 * 1024 * 1024 - 1); | |
238 | i_data &= ~PAGE_SIZE_4KB; | |
239 | i_data |= PAGE_SIZE_1MB; | |
240 | } else | |
241 | return CPLB_PROT_VIOL; | |
1ebc723c BS |
242 | } else if (addr >= _ramend) { |
243 | i_data |= CPLB_USER_RD; | |
5792ab2a SZ |
244 | if (reserved_mem_icache_on) |
245 | i_data |= CPLB_L1_CHBL; | |
1ebc723c BS |
246 | } else { |
247 | /* | |
248 | * Two cases to distinguish - a supervisor access must | |
249 | * necessarily be for a module page; we grant it | |
250 | * unconditionally (could do better here in the future). | |
251 | * Otherwise, check the x bitmap of the current process. | |
252 | */ | |
253 | if (!(status & FAULT_USERSUPV)) { | |
b8a98989 | 254 | unsigned long *mask = current_rwx_mask[cpu]; |
1ebc723c BS |
255 | |
256 | if (mask) { | |
257 | int page = addr >> PAGE_SHIFT; | |
b8a98989 | 258 | int idx = page >> 5; |
1ebc723c BS |
259 | int bit = 1 << (page & 31); |
260 | ||
261 | mask += 2 * page_mask_nelts; | |
b8a98989 | 262 | if (mask[idx] & bit) |
1ebc723c BS |
263 | i_data |= CPLB_USER_RD; |
264 | } | |
b97b8a99 BS |
265 | } |
266 | } | |
b8a98989 | 267 | idx = evict_one_icplb(cpu); |
b97b8a99 | 268 | addr &= PAGE_MASK; |
b8a98989 GY |
269 | icplb_tbl[cpu][idx].addr = addr; |
270 | icplb_tbl[cpu][idx].data = i_data; | |
b97b8a99 | 271 | |
eb7bd9c4 | 272 | _disable_icplb(); |
b97b8a99 BS |
273 | bfin_write32(ICPLB_DATA0 + idx * 4, i_data); |
274 | bfin_write32(ICPLB_ADDR0 + idx * 4, addr); | |
eb7bd9c4 | 275 | _enable_icplb(); |
b97b8a99 BS |
276 | |
277 | return 0; | |
278 | } | |
279 | ||
726e9656 | 280 | MGR_ATTR static noinline int dcplb_protection_fault(unsigned int cpu) |
b97b8a99 | 281 | { |
b97b8a99 BS |
282 | int status = bfin_read_DCPLB_STATUS(); |
283 | ||
b8a98989 | 284 | nr_dcplb_prot[cpu]++; |
b97b8a99 BS |
285 | |
286 | if (status & FAULT_RW) { | |
287 | int idx = faulting_cplb_index(status); | |
b8a98989 | 288 | unsigned long data = dcplb_tbl[cpu][idx].data; |
b97b8a99 BS |
289 | if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) && |
290 | write_permitted(status, data)) { | |
291 | data |= CPLB_DIRTY; | |
b8a98989 | 292 | dcplb_tbl[cpu][idx].data = data; |
b97b8a99 BS |
293 | bfin_write32(DCPLB_DATA0 + idx * 4, data); |
294 | return 0; | |
295 | } | |
296 | } | |
297 | return CPLB_PROT_VIOL; | |
298 | } | |
299 | ||
726e9656 | 300 | MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs) |
b97b8a99 BS |
301 | { |
302 | int cause = seqstat & 0x3f; | |
b6dbde27 | 303 | unsigned int cpu = raw_smp_processor_id(); |
b97b8a99 BS |
304 | switch (cause) { |
305 | case 0x23: | |
b8a98989 | 306 | return dcplb_protection_fault(cpu); |
b97b8a99 | 307 | case 0x2C: |
b8a98989 | 308 | return icplb_miss(cpu); |
b97b8a99 | 309 | case 0x26: |
b8a98989 | 310 | return dcplb_miss(cpu); |
b97b8a99 | 311 | default: |
b4bb68f7 | 312 | return 1; |
b97b8a99 BS |
313 | } |
314 | } | |
315 | ||
b8a98989 | 316 | void flush_switched_cplbs(unsigned int cpu) |
b97b8a99 BS |
317 | { |
318 | int i; | |
5d2e3213 | 319 | unsigned long flags; |
b97b8a99 | 320 | |
b8a98989 | 321 | nr_cplb_flush[cpu]++; |
b97b8a99 | 322 | |
3b139cdb | 323 | flags = hard_local_irq_save(); |
eb7bd9c4 | 324 | _disable_icplb(); |
b97b8a99 | 325 | for (i = first_switched_icplb; i < MAX_CPLBS; i++) { |
b8a98989 | 326 | icplb_tbl[cpu][i].data = 0; |
b97b8a99 BS |
327 | bfin_write32(ICPLB_DATA0 + i * 4, 0); |
328 | } | |
eb7bd9c4 | 329 | _enable_icplb(); |
b97b8a99 | 330 | |
eb7bd9c4 | 331 | _disable_dcplb(); |
d56daae9 | 332 | for (i = first_switched_dcplb; i < MAX_CPLBS; i++) { |
b8a98989 | 333 | dcplb_tbl[cpu][i].data = 0; |
b97b8a99 BS |
334 | bfin_write32(DCPLB_DATA0 + i * 4, 0); |
335 | } | |
eb7bd9c4 | 336 | _enable_dcplb(); |
3b139cdb | 337 | hard_local_irq_restore(flags); |
5d2e3213 | 338 | |
b97b8a99 BS |
339 | } |
340 | ||
b8a98989 | 341 | void set_mask_dcplbs(unsigned long *masks, unsigned int cpu) |
b97b8a99 BS |
342 | { |
343 | int i; | |
344 | unsigned long addr = (unsigned long)masks; | |
345 | unsigned long d_data; | |
5d2e3213 | 346 | unsigned long flags; |
b97b8a99 | 347 | |
5d2e3213 | 348 | if (!masks) { |
b8a98989 | 349 | current_rwx_mask[cpu] = masks; |
b97b8a99 | 350 | return; |
5d2e3213 BS |
351 | } |
352 | ||
3b139cdb | 353 | flags = hard_local_irq_save(); |
b8a98989 | 354 | current_rwx_mask[cpu] = masks; |
b97b8a99 | 355 | |
41ba653f JZ |
356 | if (L2_LENGTH && addr >= L2_START && addr < L2_START + L2_LENGTH) { |
357 | addr = L2_START; | |
358 | d_data = L2_DMEMORY; | |
359 | } else { | |
360 | d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; | |
361 | #ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE | |
362 | d_data |= CPLB_L1_CHBL; | |
363 | # ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH | |
364 | d_data |= CPLB_L1_AOW | CPLB_WT; | |
365 | # endif | |
b97b8a99 | 366 | #endif |
41ba653f | 367 | } |
b97b8a99 | 368 | |
eb7bd9c4 | 369 | _disable_dcplb(); |
b97b8a99 | 370 | for (i = first_mask_dcplb; i < first_switched_dcplb; i++) { |
b8a98989 GY |
371 | dcplb_tbl[cpu][i].addr = addr; |
372 | dcplb_tbl[cpu][i].data = d_data; | |
b97b8a99 BS |
373 | bfin_write32(DCPLB_DATA0 + i * 4, d_data); |
374 | bfin_write32(DCPLB_ADDR0 + i * 4, addr); | |
375 | addr += PAGE_SIZE; | |
376 | } | |
eb7bd9c4 | 377 | _enable_dcplb(); |
3b139cdb | 378 | hard_local_irq_restore(flags); |
b97b8a99 | 379 | } |