blob: 784923e52a9a740714d37f74feca5272bc1c84fc [file] [log] [blame]
Bernd Schmidtb97b8a92008-01-27 18:39:16 +08001/*
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>
Mike Frysingera92946b2008-10-16 23:25:34 +080024#include <asm/cacheflush.h>
Bernd Schmidtb97b8a92008-01-27 18:39:16 +080025#include <asm/cplbinit.h>
26#include <asm/mmu_context.h>
27
Bernd Schmidtdbdf20d2009-01-07 23:14:38 +080028/*
29 * WARNING
30 *
31 * This file is compiled with certain -ffixed-reg options. We have to
32 * make sure not to call any functions here that could clobber these
33 * registers.
34 */
Bernd Schmidtb97b8a92008-01-27 18:39:16 +080035
36int page_mask_nelts;
37int page_mask_order;
Graf Yangb8a98982008-11-18 17:48:22 +080038unsigned long *current_rwx_mask[NR_CPUS];
Bernd Schmidtb97b8a92008-01-27 18:39:16 +080039
Graf Yangb8a98982008-11-18 17:48:22 +080040int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS];
41int nr_icplb_supv_miss[NR_CPUS], nr_dcplb_prot[NR_CPUS];
42int nr_cplb_flush[NR_CPUS];
Bernd Schmidtb97b8a92008-01-27 18:39:16 +080043
44static inline void disable_dcplb(void)
45{
46 unsigned long ctrl;
47 SSYNC();
48 ctrl = bfin_read_DMEM_CONTROL();
49 ctrl &= ~ENDCPLB;
50 bfin_write_DMEM_CONTROL(ctrl);
51 SSYNC();
52}
53
54static inline void enable_dcplb(void)
55{
56 unsigned long ctrl;
57 SSYNC();
58 ctrl = bfin_read_DMEM_CONTROL();
59 ctrl |= ENDCPLB;
60 bfin_write_DMEM_CONTROL(ctrl);
61 SSYNC();
62}
63
64static inline void disable_icplb(void)
65{
66 unsigned long ctrl;
67 SSYNC();
68 ctrl = bfin_read_IMEM_CONTROL();
69 ctrl &= ~ENICPLB;
70 bfin_write_IMEM_CONTROL(ctrl);
71 SSYNC();
72}
73
74static inline void enable_icplb(void)
75{
76 unsigned long ctrl;
77 SSYNC();
78 ctrl = bfin_read_IMEM_CONTROL();
79 ctrl |= ENICPLB;
80 bfin_write_IMEM_CONTROL(ctrl);
81 SSYNC();
82}
83
84/*
85 * Given the contents of the status register, return the index of the
86 * CPLB that caused the fault.
87 */
88static inline int faulting_cplb_index(int status)
89{
90 int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF);
91 return 30 - signbits;
92}
93
94/*
95 * Given the contents of the status register and the DCPLB_DATA contents,
96 * return true if a write access should be permitted.
97 */
98static inline int write_permitted(int status, unsigned long data)
99{
100 if (status & FAULT_USERSUPV)
101 return !!(data & CPLB_SUPV_WR);
102 else
103 return !!(data & CPLB_USER_WR);
104}
105
106/* Counters to implement round-robin replacement. */
Graf Yangb8a98982008-11-18 17:48:22 +0800107static int icplb_rr_index[NR_CPUS], dcplb_rr_index[NR_CPUS];
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800108
109/*
110 * Find an ICPLB entry to be evicted and return its index.
111 */
Graf Yangb8a98982008-11-18 17:48:22 +0800112static int evict_one_icplb(unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800113{
114 int i;
115 for (i = first_switched_icplb; i < MAX_CPLBS; i++)
Graf Yangb8a98982008-11-18 17:48:22 +0800116 if ((icplb_tbl[cpu][i].data & CPLB_VALID) == 0)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800117 return i;
Graf Yangb8a98982008-11-18 17:48:22 +0800118 i = first_switched_icplb + icplb_rr_index[cpu];
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800119 if (i >= MAX_CPLBS) {
120 i -= MAX_CPLBS - first_switched_icplb;
Graf Yangb8a98982008-11-18 17:48:22 +0800121 icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800122 }
Graf Yangb8a98982008-11-18 17:48:22 +0800123 icplb_rr_index[cpu]++;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800124 return i;
125}
126
Graf Yangb8a98982008-11-18 17:48:22 +0800127static int evict_one_dcplb(unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800128{
129 int i;
130 for (i = first_switched_dcplb; i < MAX_CPLBS; i++)
Graf Yangb8a98982008-11-18 17:48:22 +0800131 if ((dcplb_tbl[cpu][i].data & CPLB_VALID) == 0)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800132 return i;
Graf Yangb8a98982008-11-18 17:48:22 +0800133 i = first_switched_dcplb + dcplb_rr_index[cpu];
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800134 if (i >= MAX_CPLBS) {
135 i -= MAX_CPLBS - first_switched_dcplb;
Graf Yangb8a98982008-11-18 17:48:22 +0800136 dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800137 }
Graf Yangb8a98982008-11-18 17:48:22 +0800138 dcplb_rr_index[cpu]++;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800139 return i;
140}
141
Graf Yangb8a98982008-11-18 17:48:22 +0800142static noinline int dcplb_miss(unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800143{
144 unsigned long addr = bfin_read_DCPLB_FAULT_ADDR();
145 int status = bfin_read_DCPLB_STATUS();
146 unsigned long *mask;
147 int idx;
148 unsigned long d_data;
149
Graf Yangb8a98982008-11-18 17:48:22 +0800150 nr_dcplb_miss[cpu]++;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800151
152 d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;
153#ifdef CONFIG_BFIN_DCACHE
Jie Zhang67834fa2009-06-10 06:26:26 +0000154 if (bfin_addr_dcacheable(addr)) {
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800155 d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;
Bernd Schmidtdbfe44f2008-04-23 07:11:55 +0800156#ifdef CONFIG_BFIN_WT
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800157 d_data |= CPLB_L1_AOW | CPLB_WT;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800158#endif
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800159 }
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800160#endif
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800161 if (addr >= physical_mem_end) {
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800162 if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE
163 && (status & FAULT_USERSUPV)) {
164 addr &= ~0x3fffff;
165 d_data &= ~PAGE_SIZE_4KB;
166 d_data |= PAGE_SIZE_4MB;
Mike Frysinger4e354b52008-04-24 05:44:32 +0800167 } else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH
168 && (status & (FAULT_RW | FAULT_USERSUPV)) == FAULT_USERSUPV) {
169 addr &= ~(1 * 1024 * 1024 - 1);
170 d_data &= ~PAGE_SIZE_4KB;
Mike Frysinger4bea8b22008-04-24 07:23:36 +0800171 d_data |= PAGE_SIZE_1MB;
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800172 } else
173 return CPLB_PROT_VIOL;
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800174 } else if (addr >= _ramend) {
175 d_data |= CPLB_USER_RD | CPLB_USER_WR;
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800176 } else {
Graf Yangb8a98982008-11-18 17:48:22 +0800177 mask = current_rwx_mask[cpu];
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800178 if (mask) {
179 int page = addr >> PAGE_SHIFT;
Graf Yangb8a98982008-11-18 17:48:22 +0800180 int idx = page >> 5;
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800181 int bit = 1 << (page & 31);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800182
Graf Yangb8a98982008-11-18 17:48:22 +0800183 if (mask[idx] & bit)
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800184 d_data |= CPLB_USER_RD;
185
186 mask += page_mask_nelts;
Graf Yangb8a98982008-11-18 17:48:22 +0800187 if (mask[idx] & bit)
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800188 d_data |= CPLB_USER_WR;
189 }
190 }
Graf Yangb8a98982008-11-18 17:48:22 +0800191 idx = evict_one_dcplb(cpu);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800192
193 addr &= PAGE_MASK;
Graf Yangb8a98982008-11-18 17:48:22 +0800194 dcplb_tbl[cpu][idx].addr = addr;
195 dcplb_tbl[cpu][idx].data = d_data;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800196
197 disable_dcplb();
198 bfin_write32(DCPLB_DATA0 + idx * 4, d_data);
199 bfin_write32(DCPLB_ADDR0 + idx * 4, addr);
200 enable_dcplb();
201
202 return 0;
203}
204
Graf Yangb8a98982008-11-18 17:48:22 +0800205static noinline int icplb_miss(unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800206{
207 unsigned long addr = bfin_read_ICPLB_FAULT_ADDR();
208 int status = bfin_read_ICPLB_STATUS();
209 int idx;
210 unsigned long i_data;
211
Graf Yangb8a98982008-11-18 17:48:22 +0800212 nr_icplb_miss[cpu]++;
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800213
214 /* If inside the uncached DMA region, fault. */
215 if (addr >= _ramend - DMA_UNCACHED_REGION && addr < _ramend)
216 return CPLB_PROT_VIOL;
217
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800218 if (status & FAULT_USERSUPV)
Graf Yangb8a98982008-11-18 17:48:22 +0800219 nr_icplb_supv_miss[cpu]++;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800220
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800221 /*
222 * First, try to find a CPLB that matches this address. If we
223 * find one, then the fact that we're in the miss handler means
224 * that the instruction crosses a page boundary.
225 */
226 for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) {
Graf Yangb8a98982008-11-18 17:48:22 +0800227 if (icplb_tbl[cpu][idx].data & CPLB_VALID) {
228 unsigned long this_addr = icplb_tbl[cpu][idx].addr;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800229 if (this_addr <= addr && this_addr + PAGE_SIZE > addr) {
230 addr += PAGE_SIZE;
231 break;
232 }
233 }
234 }
235
236 i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB;
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800237
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800238#ifdef CONFIG_BFIN_ICACHE
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800239 /*
240 * Normal RAM, and possibly the reserved memory area, are
241 * cacheable.
242 */
243 if (addr < _ramend ||
244 (addr < physical_mem_end && reserved_mem_icache_on))
245 i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800246#endif
247
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800248 if (addr >= physical_mem_end) {
Mike Frysinger4bea8b22008-04-24 07:23:36 +0800249 if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH
250 && (status & FAULT_USERSUPV)) {
251 addr &= ~(1 * 1024 * 1024 - 1);
252 i_data &= ~PAGE_SIZE_4KB;
253 i_data |= PAGE_SIZE_1MB;
254 } else
255 return CPLB_PROT_VIOL;
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800256 } else if (addr >= _ramend) {
257 i_data |= CPLB_USER_RD;
258 } else {
259 /*
260 * Two cases to distinguish - a supervisor access must
261 * necessarily be for a module page; we grant it
262 * unconditionally (could do better here in the future).
263 * Otherwise, check the x bitmap of the current process.
264 */
265 if (!(status & FAULT_USERSUPV)) {
Graf Yangb8a98982008-11-18 17:48:22 +0800266 unsigned long *mask = current_rwx_mask[cpu];
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800267
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800268 if (mask) {
269 int page = addr >> PAGE_SHIFT;
Graf Yangb8a98982008-11-18 17:48:22 +0800270 int idx = page >> 5;
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800271 int bit = 1 << (page & 31);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800272
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800273 mask += 2 * page_mask_nelts;
Graf Yangb8a98982008-11-18 17:48:22 +0800274 if (mask[idx] & bit)
Bernd Schmidt1ebc7232008-04-24 02:58:26 +0800275 i_data |= CPLB_USER_RD;
276 }
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800277 }
278 }
Graf Yangb8a98982008-11-18 17:48:22 +0800279 idx = evict_one_icplb(cpu);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800280 addr &= PAGE_MASK;
Graf Yangb8a98982008-11-18 17:48:22 +0800281 icplb_tbl[cpu][idx].addr = addr;
282 icplb_tbl[cpu][idx].data = i_data;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800283
284 disable_icplb();
285 bfin_write32(ICPLB_DATA0 + idx * 4, i_data);
286 bfin_write32(ICPLB_ADDR0 + idx * 4, addr);
287 enable_icplb();
288
289 return 0;
290}
291
Graf Yangb8a98982008-11-18 17:48:22 +0800292static noinline int dcplb_protection_fault(unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800293{
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800294 int status = bfin_read_DCPLB_STATUS();
295
Graf Yangb8a98982008-11-18 17:48:22 +0800296 nr_dcplb_prot[cpu]++;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800297
298 if (status & FAULT_RW) {
299 int idx = faulting_cplb_index(status);
Graf Yangb8a98982008-11-18 17:48:22 +0800300 unsigned long data = dcplb_tbl[cpu][idx].data;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800301 if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) &&
302 write_permitted(status, data)) {
303 data |= CPLB_DIRTY;
Graf Yangb8a98982008-11-18 17:48:22 +0800304 dcplb_tbl[cpu][idx].data = data;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800305 bfin_write32(DCPLB_DATA0 + idx * 4, data);
306 return 0;
307 }
308 }
309 return CPLB_PROT_VIOL;
310}
311
312int cplb_hdr(int seqstat, struct pt_regs *regs)
313{
314 int cause = seqstat & 0x3f;
Graf Yangb8a98982008-11-18 17:48:22 +0800315 unsigned int cpu = smp_processor_id();
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800316 switch (cause) {
317 case 0x23:
Graf Yangb8a98982008-11-18 17:48:22 +0800318 return dcplb_protection_fault(cpu);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800319 case 0x2C:
Graf Yangb8a98982008-11-18 17:48:22 +0800320 return icplb_miss(cpu);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800321 case 0x26:
Graf Yangb8a98982008-11-18 17:48:22 +0800322 return dcplb_miss(cpu);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800323 default:
Bernd Schmidtb4bb68f2008-04-23 07:26:23 +0800324 return 1;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800325 }
326}
327
Graf Yangb8a98982008-11-18 17:48:22 +0800328void flush_switched_cplbs(unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800329{
330 int i;
Bernd Schmidt5d2e3212008-10-07 16:27:01 +0800331 unsigned long flags;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800332
Graf Yangb8a98982008-11-18 17:48:22 +0800333 nr_cplb_flush[cpu]++;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800334
Yi Li6a01f232009-01-07 23:14:39 +0800335 local_irq_save_hw(flags);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800336 disable_icplb();
337 for (i = first_switched_icplb; i < MAX_CPLBS; i++) {
Graf Yangb8a98982008-11-18 17:48:22 +0800338 icplb_tbl[cpu][i].data = 0;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800339 bfin_write32(ICPLB_DATA0 + i * 4, 0);
340 }
341 enable_icplb();
342
343 disable_dcplb();
Bernd Schmidtd56daae2008-04-24 02:56:36 +0800344 for (i = first_switched_dcplb; i < MAX_CPLBS; i++) {
Graf Yangb8a98982008-11-18 17:48:22 +0800345 dcplb_tbl[cpu][i].data = 0;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800346 bfin_write32(DCPLB_DATA0 + i * 4, 0);
347 }
348 enable_dcplb();
Yi Li6a01f232009-01-07 23:14:39 +0800349 local_irq_restore_hw(flags);
Bernd Schmidt5d2e3212008-10-07 16:27:01 +0800350
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800351}
352
Graf Yangb8a98982008-11-18 17:48:22 +0800353void set_mask_dcplbs(unsigned long *masks, unsigned int cpu)
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800354{
355 int i;
356 unsigned long addr = (unsigned long)masks;
357 unsigned long d_data;
Bernd Schmidt5d2e3212008-10-07 16:27:01 +0800358 unsigned long flags;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800359
Bernd Schmidt5d2e3212008-10-07 16:27:01 +0800360 if (!masks) {
Graf Yangb8a98982008-11-18 17:48:22 +0800361 current_rwx_mask[cpu] = masks;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800362 return;
Bernd Schmidt5d2e3212008-10-07 16:27:01 +0800363 }
364
Yi Li6a01f232009-01-07 23:14:39 +0800365 local_irq_save_hw(flags);
Graf Yangb8a98982008-11-18 17:48:22 +0800366 current_rwx_mask[cpu] = masks;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800367
368 d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;
369#ifdef CONFIG_BFIN_DCACHE
370 d_data |= CPLB_L1_CHBL;
Bernd Schmidtdbfe44f2008-04-23 07:11:55 +0800371#ifdef CONFIG_BFIN_WT
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800372 d_data |= CPLB_L1_AOW | CPLB_WT;
373#endif
374#endif
375
376 disable_dcplb();
377 for (i = first_mask_dcplb; i < first_switched_dcplb; i++) {
Graf Yangb8a98982008-11-18 17:48:22 +0800378 dcplb_tbl[cpu][i].addr = addr;
379 dcplb_tbl[cpu][i].data = d_data;
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800380 bfin_write32(DCPLB_DATA0 + i * 4, d_data);
381 bfin_write32(DCPLB_ADDR0 + i * 4, addr);
382 addr += PAGE_SIZE;
383 }
384 enable_dcplb();
Yi Li6a01f232009-01-07 23:14:39 +0800385 local_irq_restore_hw(flags);
Bernd Schmidtb97b8a92008-01-27 18:39:16 +0800386}