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