1/*
2 * Blackfin CPLB exception handling for when MPU in on
3 *
4 * Copyright 2008-2009 Analog Devices Inc.
5 *
6 * Licensed under the GPL-2 or later.
7 */
8
9#include <linux/module.h>
10#include <linux/mm.h>
11
12#include <asm/blackfin.h>
13#include <asm/cacheflush.h>
14#include <asm/cplb.h>
15#include <asm/cplbinit.h>
16#include <asm/mmu_context.h>
17
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 */
25
26int page_mask_nelts;
27int page_mask_order;
28unsigned long *current_rwx_mask[NR_CPUS];
29
30int nr_dcplb_miss[NR_CPUS], nr_icplb_miss[NR_CPUS];
31int nr_icplb_supv_miss[NR_CPUS], nr_dcplb_prot[NR_CPUS];
32int nr_cplb_flush[NR_CPUS];
33
34#ifdef CONFIG_EXCPT_IRQ_SYSC_L1
35#define MGR_ATTR __attribute__((l1_text))
36#else
37#define MGR_ATTR
38#endif
39
40/*
41 * Given the contents of the status register, return the index of the
42 * CPLB that caused the fault.
43 */
44static 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 */
54static 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.  */
63static int icplb_rr_index[NR_CPUS], dcplb_rr_index[NR_CPUS];
64
65/*
66 * Find an ICPLB entry to be evicted and return its index.
67 */
68MGR_ATTR static int evict_one_icplb(unsigned int cpu)
69{
70	int i;
71	for (i = first_switched_icplb; i < MAX_CPLBS; i++)
72		if ((icplb_tbl[cpu][i].data & CPLB_VALID) == 0)
73			return i;
74	i = first_switched_icplb + icplb_rr_index[cpu];
75	if (i >= MAX_CPLBS) {
76		i -= MAX_CPLBS - first_switched_icplb;
77		icplb_rr_index[cpu] -= MAX_CPLBS - first_switched_icplb;
78	}
79	icplb_rr_index[cpu]++;
80	return i;
81}
82
83MGR_ATTR static int evict_one_dcplb(unsigned int cpu)
84{
85	int i;
86	for (i = first_switched_dcplb; i < MAX_CPLBS; i++)
87		if ((dcplb_tbl[cpu][i].data & CPLB_VALID) == 0)
88			return i;
89	i = first_switched_dcplb + dcplb_rr_index[cpu];
90	if (i >= MAX_CPLBS) {
91		i -= MAX_CPLBS - first_switched_dcplb;
92		dcplb_rr_index[cpu] -= MAX_CPLBS - first_switched_dcplb;
93	}
94	dcplb_rr_index[cpu]++;
95	return i;
96}
97
98MGR_ATTR static noinline int dcplb_miss(unsigned int cpu)
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
106	nr_dcplb_miss[cpu]++;
107
108	d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB;
109#ifdef CONFIG_BFIN_EXTMEM_DCACHEABLE
110	if (bfin_addr_dcacheable(addr)) {
111		d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;
112# ifdef CONFIG_BFIN_EXTMEM_WRITETHROUGH
113		d_data |= CPLB_L1_AOW | CPLB_WT;
114# endif
115	}
116#endif
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) {
122		if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) {
123#if defined(CONFIG_ROMFS_ON_MTD) && defined(CONFIG_MTD_ROM)
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			}
133#endif
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;
138			d_data |= PAGE_SIZE_1MB;
139		} else
140			return CPLB_PROT_VIOL;
141	} else if (addr >= _ramend) {
142		d_data |= CPLB_USER_RD | CPLB_USER_WR;
143		if (reserved_mem_dcache_on)
144			d_data |= CPLB_L1_CHBL;
145	} else {
146		mask = current_rwx_mask[cpu];
147		if (mask) {
148			int page = addr >> PAGE_SHIFT;
149			int idx = page >> 5;
150			int bit = 1 << (page & 31);
151
152			if (mask[idx] & bit)
153				d_data |= CPLB_USER_RD;
154
155			mask += page_mask_nelts;
156			if (mask[idx] & bit)
157				d_data |= CPLB_USER_WR;
158		}
159	}
160	idx = evict_one_dcplb(cpu);
161
162	addr &= PAGE_MASK;
163	dcplb_tbl[cpu][idx].addr = addr;
164	dcplb_tbl[cpu][idx].data = d_data;
165
166	_disable_dcplb();
167	bfin_write32(DCPLB_DATA0 + idx * 4, d_data);
168	bfin_write32(DCPLB_ADDR0 + idx * 4, addr);
169	_enable_dcplb();
170
171	return 0;
172}
173
174MGR_ATTR static noinline int icplb_miss(unsigned int cpu)
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
181	nr_icplb_miss[cpu]++;
182
183	/* If inside the uncached DMA region, fault.  */
184	if (addr >= _ramend - DMA_UNCACHED_REGION && addr < _ramend)
185		return CPLB_PROT_VIOL;
186
187	if (status & FAULT_USERSUPV)
188		nr_icplb_supv_miss[cpu]++;
189
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++) {
196		if (icplb_tbl[cpu][idx].data & CPLB_VALID) {
197			unsigned long this_addr = icplb_tbl[cpu][idx].addr;
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;
206
207#ifdef CONFIG_BFIN_EXTMEM_ICACHEABLE
208	/*
209	 * Normal RAM, and possibly the reserved memory area, are
210	 * cacheable.
211	 */
212	if (addr < _ramend ||
213	    (addr < physical_mem_end && reserved_mem_icache_on))
214		i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND;
215#endif
216
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) {
221		if (addr >= ASYNC_BANK0_BASE && addr < ASYNC_BANK3_BASE + ASYNC_BANK3_SIZE) {
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			}
235		} else if (addr >= BOOT_ROM_START && addr < BOOT_ROM_START + BOOT_ROM_LENGTH
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;
242	} else if (addr >= _ramend) {
243		i_data |= CPLB_USER_RD;
244		if (reserved_mem_icache_on)
245			i_data |= CPLB_L1_CHBL;
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)) {
254			unsigned long *mask = current_rwx_mask[cpu];
255
256			if (mask) {
257				int page = addr >> PAGE_SHIFT;
258				int idx = page >> 5;
259				int bit = 1 << (page & 31);
260
261				mask += 2 * page_mask_nelts;
262				if (mask[idx] & bit)
263					i_data |= CPLB_USER_RD;
264			}
265		}
266	}
267	idx = evict_one_icplb(cpu);
268	addr &= PAGE_MASK;
269	icplb_tbl[cpu][idx].addr = addr;
270	icplb_tbl[cpu][idx].data = i_data;
271
272	_disable_icplb();
273	bfin_write32(ICPLB_DATA0 + idx * 4, i_data);
274	bfin_write32(ICPLB_ADDR0 + idx * 4, addr);
275	_enable_icplb();
276
277	return 0;
278}
279
280MGR_ATTR static noinline int dcplb_protection_fault(unsigned int cpu)
281{
282	int status = bfin_read_DCPLB_STATUS();
283
284	nr_dcplb_prot[cpu]++;
285
286	if (status & FAULT_RW) {
287		int idx = faulting_cplb_index(status);
288		unsigned long data = dcplb_tbl[cpu][idx].data;
289		if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) &&
290		    write_permitted(status, data)) {
291			data |= CPLB_DIRTY;
292			dcplb_tbl[cpu][idx].data = data;
293			bfin_write32(DCPLB_DATA0 + idx * 4, data);
294			return 0;
295		}
296	}
297	return CPLB_PROT_VIOL;
298}
299
300MGR_ATTR int cplb_hdr(int seqstat, struct pt_regs *regs)
301{
302	int cause = seqstat & 0x3f;
303	unsigned int cpu = raw_smp_processor_id();
304	switch (cause) {
305	case 0x23:
306		return dcplb_protection_fault(cpu);
307	case 0x2C:
308		return icplb_miss(cpu);
309	case 0x26:
310		return dcplb_miss(cpu);
311	default:
312		return 1;
313	}
314}
315
316void flush_switched_cplbs(unsigned int cpu)
317{
318	int i;
319	unsigned long flags;
320
321	nr_cplb_flush[cpu]++;
322
323	flags = hard_local_irq_save();
324	_disable_icplb();
325	for (i = first_switched_icplb; i < MAX_CPLBS; i++) {
326		icplb_tbl[cpu][i].data = 0;
327		bfin_write32(ICPLB_DATA0 + i * 4, 0);
328	}
329	_enable_icplb();
330
331	_disable_dcplb();
332	for (i = first_switched_dcplb; i < MAX_CPLBS; i++) {
333		dcplb_tbl[cpu][i].data = 0;
334		bfin_write32(DCPLB_DATA0 + i * 4, 0);
335	}
336	_enable_dcplb();
337	hard_local_irq_restore(flags);
338
339}
340
341void set_mask_dcplbs(unsigned long *masks, unsigned int cpu)
342{
343	int i;
344	unsigned long addr = (unsigned long)masks;
345	unsigned long d_data;
346	unsigned long flags;
347
348	if (!masks) {
349		current_rwx_mask[cpu] = masks;
350		return;
351	}
352
353	flags = hard_local_irq_save();
354	current_rwx_mask[cpu] = masks;
355
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
366#endif
367	}
368
369	_disable_dcplb();
370	for (i = first_mask_dcplb; i < first_switched_dcplb; i++) {
371		dcplb_tbl[cpu][i].addr = addr;
372		dcplb_tbl[cpu][i].data = d_data;
373		bfin_write32(DCPLB_DATA0 + i * 4, d_data);
374		bfin_write32(DCPLB_ADDR0 + i * 4, addr);
375		addr += PAGE_SIZE;
376	}
377	_enable_dcplb();
378	hard_local_irq_restore(flags);
379}
380