2 * Blackfin CPLB exception handling.
3 * Copyright 2004-2007 Analog Devices Inc.
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.
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.
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
20 #include <linux/module.h>
23 #include <asm/blackfin.h>
24 #include <asm/cacheflush.h>
26 #include <asm/cplbinit.h>
27 #include <asm/mmu_context.h>
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
39 unsigned long *current_rwx_mask
[NR_CPUS
];
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
];
46 * Given the contents of the status register, return the index of the
47 * CPLB that caused the fault.
49 static inline int faulting_cplb_index(int status
)
51 int signbits
= __builtin_bfin_norm_fr1x32(status
& 0xFFFF);
56 * Given the contents of the status register and the DCPLB_DATA contents,
57 * return true if a write access should be permitted.
59 static inline int write_permitted(int status
, unsigned long data
)
61 if (status
& FAULT_USERSUPV
)
62 return !!(data
& CPLB_SUPV_WR
);
64 return !!(data
& CPLB_USER_WR
);
67 /* Counters to implement round-robin replacement. */
68 static int icplb_rr_index
[NR_CPUS
], dcplb_rr_index
[NR_CPUS
];
71 * Find an ICPLB entry to be evicted and return its index.
73 static int evict_one_icplb(unsigned int cpu
)
76 for (i
= first_switched_icplb
; i
< MAX_CPLBS
; i
++)
77 if ((icplb_tbl
[cpu
][i
].data
& CPLB_VALID
) == 0)
79 i
= first_switched_icplb
+ icplb_rr_index
[cpu
];
81 i
-= MAX_CPLBS
- first_switched_icplb
;
82 icplb_rr_index
[cpu
] -= MAX_CPLBS
- first_switched_icplb
;
84 icplb_rr_index
[cpu
]++;
88 static int evict_one_dcplb(unsigned int cpu
)
91 for (i
= first_switched_dcplb
; i
< MAX_CPLBS
; i
++)
92 if ((dcplb_tbl
[cpu
][i
].data
& CPLB_VALID
) == 0)
94 i
= first_switched_dcplb
+ dcplb_rr_index
[cpu
];
96 i
-= MAX_CPLBS
- first_switched_dcplb
;
97 dcplb_rr_index
[cpu
] -= MAX_CPLBS
- first_switched_dcplb
;
99 dcplb_rr_index
[cpu
]++;
103 static noinline
int dcplb_miss(unsigned int cpu
)
105 unsigned long addr
= bfin_read_DCPLB_FAULT_ADDR();
106 int status
= bfin_read_DCPLB_STATUS();
109 unsigned long d_data
;
111 nr_dcplb_miss
[cpu
]++;
113 d_data
= CPLB_SUPV_WR
| CPLB_VALID
| CPLB_DIRTY
| PAGE_SIZE_4KB
;
114 #ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE
115 if (bfin_addr_dcacheable(addr
)) {
116 d_data
|= CPLB_L1_CHBL
| ANOMALY_05000158_WORKAROUND
;
117 # ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH
118 d_data
|= CPLB_L1_AOW
| CPLB_WT
;
123 if (L2_LENGTH
&& addr
>= L2_START
&& addr
< L2_START
+ L2_LENGTH
) {
126 } else if (addr
>= physical_mem_end
) {
127 if (addr
>= ASYNC_BANK0_BASE
&& addr
< ASYNC_BANK3_BASE
+ ASYNC_BANK3_SIZE
128 && (status
& FAULT_USERSUPV
)) {
130 d_data
&= ~PAGE_SIZE_4KB
;
131 d_data
|= PAGE_SIZE_4MB
;
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
;
136 d_data
|= PAGE_SIZE_1MB
;
138 return CPLB_PROT_VIOL
;
139 } else if (addr
>= _ramend
) {
140 d_data
|= CPLB_USER_RD
| CPLB_USER_WR
;
142 mask
= current_rwx_mask
[cpu
];
144 int page
= addr
>> PAGE_SHIFT
;
146 int bit
= 1 << (page
& 31);
149 d_data
|= CPLB_USER_RD
;
151 mask
+= page_mask_nelts
;
153 d_data
|= CPLB_USER_WR
;
156 idx
= evict_one_dcplb(cpu
);
159 dcplb_tbl
[cpu
][idx
].addr
= addr
;
160 dcplb_tbl
[cpu
][idx
].data
= d_data
;
163 bfin_write32(DCPLB_DATA0
+ idx
* 4, d_data
);
164 bfin_write32(DCPLB_ADDR0
+ idx
* 4, addr
);
170 static noinline
int icplb_miss(unsigned int cpu
)
172 unsigned long addr
= bfin_read_ICPLB_FAULT_ADDR();
173 int status
= bfin_read_ICPLB_STATUS();
175 unsigned long i_data
;
177 nr_icplb_miss
[cpu
]++;
179 /* If inside the uncached DMA region, fault. */
180 if (addr
>= _ramend
- DMA_UNCACHED_REGION
&& addr
< _ramend
)
181 return CPLB_PROT_VIOL
;
183 if (status
& FAULT_USERSUPV
)
184 nr_icplb_supv_miss
[cpu
]++;
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.
191 for (idx
= first_switched_icplb
; idx
< MAX_CPLBS
; idx
++) {
192 if (icplb_tbl
[cpu
][idx
].data
& CPLB_VALID
) {
193 unsigned long this_addr
= icplb_tbl
[cpu
][idx
].addr
;
194 if (this_addr
<= addr
&& this_addr
+ PAGE_SIZE
> addr
) {
201 i_data
= CPLB_VALID
| CPLB_PORTPRIO
| PAGE_SIZE_4KB
;
203 #ifdef CONFIG_BFIN_EXTMEM_ICACHEABLE
205 * Normal RAM, and possibly the reserved memory area, are
208 if (addr
< _ramend
||
209 (addr
< physical_mem_end
&& reserved_mem_icache_on
))
210 i_data
|= CPLB_L1_CHBL
| ANOMALY_05000158_WORKAROUND
;
213 if (L2_LENGTH
&& addr
>= L2_START
&& addr
< L2_START
+ L2_LENGTH
) {
216 } else if (addr
>= physical_mem_end
) {
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
;
223 return CPLB_PROT_VIOL
;
224 } else if (addr
>= _ramend
) {
225 i_data
|= CPLB_USER_RD
;
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.
233 if (!(status
& FAULT_USERSUPV
)) {
234 unsigned long *mask
= current_rwx_mask
[cpu
];
237 int page
= addr
>> PAGE_SHIFT
;
239 int bit
= 1 << (page
& 31);
241 mask
+= 2 * page_mask_nelts
;
243 i_data
|= CPLB_USER_RD
;
247 idx
= evict_one_icplb(cpu
);
249 icplb_tbl
[cpu
][idx
].addr
= addr
;
250 icplb_tbl
[cpu
][idx
].data
= i_data
;
253 bfin_write32(ICPLB_DATA0
+ idx
* 4, i_data
);
254 bfin_write32(ICPLB_ADDR0
+ idx
* 4, addr
);
260 static noinline
int dcplb_protection_fault(unsigned int cpu
)
262 int status
= bfin_read_DCPLB_STATUS();
264 nr_dcplb_prot
[cpu
]++;
266 if (status
& FAULT_RW
) {
267 int idx
= faulting_cplb_index(status
);
268 unsigned long data
= dcplb_tbl
[cpu
][idx
].data
;
269 if (!(data
& CPLB_WT
) && !(data
& CPLB_DIRTY
) &&
270 write_permitted(status
, data
)) {
272 dcplb_tbl
[cpu
][idx
].data
= data
;
273 bfin_write32(DCPLB_DATA0
+ idx
* 4, data
);
277 return CPLB_PROT_VIOL
;
280 int cplb_hdr(int seqstat
, struct pt_regs
*regs
)
282 int cause
= seqstat
& 0x3f;
283 unsigned int cpu
= raw_smp_processor_id();
286 return dcplb_protection_fault(cpu
);
288 return icplb_miss(cpu
);
290 return dcplb_miss(cpu
);
296 void flush_switched_cplbs(unsigned int cpu
)
301 nr_cplb_flush
[cpu
]++;
303 local_irq_save_hw(flags
);
305 for (i
= first_switched_icplb
; i
< MAX_CPLBS
; i
++) {
306 icplb_tbl
[cpu
][i
].data
= 0;
307 bfin_write32(ICPLB_DATA0
+ i
* 4, 0);
312 for (i
= first_switched_dcplb
; i
< MAX_CPLBS
; i
++) {
313 dcplb_tbl
[cpu
][i
].data
= 0;
314 bfin_write32(DCPLB_DATA0
+ i
* 4, 0);
317 local_irq_restore_hw(flags
);
321 void set_mask_dcplbs(unsigned long *masks
, unsigned int cpu
)
324 unsigned long addr
= (unsigned long)masks
;
325 unsigned long d_data
;
329 current_rwx_mask
[cpu
] = masks
;
333 local_irq_save_hw(flags
);
334 current_rwx_mask
[cpu
] = masks
;
336 if (L2_LENGTH
&& addr
>= L2_START
&& addr
< L2_START
+ L2_LENGTH
) {
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
;
350 for (i
= first_mask_dcplb
; i
< first_switched_dcplb
; i
++) {
351 dcplb_tbl
[cpu
][i
].addr
= addr
;
352 dcplb_tbl
[cpu
][i
].data
= d_data
;
353 bfin_write32(DCPLB_DATA0
+ i
* 4, d_data
);
354 bfin_write32(DCPLB_ADDR0
+ i
* 4, addr
);
358 local_irq_restore_hw(flags
);