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