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