diff options
author | Bernd Schmidt <bernd.schmidt@analog.com> | 2008-01-27 05:39:16 -0500 |
---|---|---|
committer | Bryan Wu <bryan.wu@analog.com> | 2008-01-27 05:39:16 -0500 |
commit | b97b8a998397e8c64699559099fa9febffae2b4d (patch) | |
tree | 689188b6336cf45b4391f5bc764878e342b9ac90 /arch/blackfin/kernel/cplb-mpu | |
parent | 2047e40d724d42928c0b5994a1568c1b738efdb7 (diff) |
[Blackfin] arch: Initial checkin of the memory protection support.
Enable it with CONFIG_MPU.
Signed-off-by: Bernd Schmidt <bernd.schmidt@analog.com>
Signed-off-by: Bryan Wu <bryan.wu@analog.com>
Diffstat (limited to 'arch/blackfin/kernel/cplb-mpu')
-rw-r--r-- | arch/blackfin/kernel/cplb-mpu/Makefile | 8 | ||||
-rw-r--r-- | arch/blackfin/kernel/cplb-mpu/cacheinit.c | 62 | ||||
-rw-r--r-- | arch/blackfin/kernel/cplb-mpu/cplbinfo.c | 144 | ||||
-rw-r--r-- | arch/blackfin/kernel/cplb-mpu/cplbinit.c | 91 | ||||
-rw-r--r-- | arch/blackfin/kernel/cplb-mpu/cplbmgr.c | 338 |
5 files changed, 643 insertions, 0 deletions
diff --git a/arch/blackfin/kernel/cplb-mpu/Makefile b/arch/blackfin/kernel/cplb-mpu/Makefile new file mode 100644 index 000000000000..286b69357f97 --- /dev/null +++ b/arch/blackfin/kernel/cplb-mpu/Makefile | |||
@@ -0,0 +1,8 @@ | |||
1 | # | ||
2 | # arch/blackfin/kernel/cplb-nompu/Makefile | ||
3 | # | ||
4 | |||
5 | obj-y := cplbinit.o cacheinit.o cplbmgr.o | ||
6 | |||
7 | obj-$(CONFIG_CPLB_INFO) += cplbinfo.o | ||
8 | |||
diff --git a/arch/blackfin/kernel/cplb-mpu/cacheinit.c b/arch/blackfin/kernel/cplb-mpu/cacheinit.c new file mode 100644 index 000000000000..9eecfa403187 --- /dev/null +++ b/arch/blackfin/kernel/cplb-mpu/cacheinit.c | |||
@@ -0,0 +1,62 @@ | |||
1 | /* | ||
2 | * Copyright 2004-2007 Analog Devices Inc. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, see the file COPYING, or write | ||
16 | * to the Free Software Foundation, Inc., | ||
17 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
18 | */ | ||
19 | |||
20 | #include <linux/cpu.h> | ||
21 | |||
22 | #include <asm/cacheflush.h> | ||
23 | #include <asm/blackfin.h> | ||
24 | #include <asm/cplb.h> | ||
25 | #include <asm/cplbinit.h> | ||
26 | |||
27 | #if defined(CONFIG_BFIN_ICACHE) | ||
28 | void bfin_icache_init(void) | ||
29 | { | ||
30 | unsigned long ctrl; | ||
31 | int i; | ||
32 | |||
33 | SSYNC(); | ||
34 | for (i = 0; i < MAX_CPLBS; i++) { | ||
35 | bfin_write32(ICPLB_ADDR0 + i * 4, icplb_tbl[i].addr); | ||
36 | bfin_write32(ICPLB_DATA0 + i * 4, icplb_tbl[i].data); | ||
37 | } | ||
38 | ctrl = bfin_read_IMEM_CONTROL(); | ||
39 | ctrl |= IMC | ENICPLB; | ||
40 | bfin_write_IMEM_CONTROL(ctrl); | ||
41 | SSYNC(); | ||
42 | } | ||
43 | #endif | ||
44 | |||
45 | #if defined(CONFIG_BFIN_DCACHE) | ||
46 | void bfin_dcache_init(void) | ||
47 | { | ||
48 | unsigned long ctrl; | ||
49 | int i; | ||
50 | |||
51 | SSYNC(); | ||
52 | for (i = 0; i < MAX_CPLBS; i++) { | ||
53 | bfin_write32(DCPLB_ADDR0 + i * 4, dcplb_tbl[i].addr); | ||
54 | bfin_write32(DCPLB_DATA0 + i * 4, dcplb_tbl[i].data); | ||
55 | } | ||
56 | |||
57 | ctrl = bfin_read_DMEM_CONTROL(); | ||
58 | ctrl |= DMEM_CNTR; | ||
59 | bfin_write_DMEM_CONTROL(ctrl); | ||
60 | SSYNC(); | ||
61 | } | ||
62 | #endif | ||
diff --git a/arch/blackfin/kernel/cplb-mpu/cplbinfo.c b/arch/blackfin/kernel/cplb-mpu/cplbinfo.c new file mode 100644 index 000000000000..bd072299f7f2 --- /dev/null +++ b/arch/blackfin/kernel/cplb-mpu/cplbinfo.c | |||
@@ -0,0 +1,144 @@ | |||
1 | /* | ||
2 | * File: arch/blackfin/mach-common/cplbinfo.c | ||
3 | * Based on: | ||
4 | * Author: Sonic Zhang <sonic.zhang@analog.com> | ||
5 | * | ||
6 | * Created: Jan. 2005 | ||
7 | * Description: Display CPLB status | ||
8 | * | ||
9 | * Modified: | ||
10 | * Copyright 2004-2006 Analog Devices Inc. | ||
11 | * | ||
12 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
13 | * | ||
14 | * This program is free software; you can redistribute it and/or modify | ||
15 | * it under the terms of the GNU General Public License as published by | ||
16 | * the Free Software Foundation; either version 2 of the License, or | ||
17 | * (at your option) any later version. | ||
18 | * | ||
19 | * This program is distributed in the hope that it will be useful, | ||
20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
22 | * GNU General Public License for more details. | ||
23 | * | ||
24 | * You should have received a copy of the GNU General Public License | ||
25 | * along with this program; if not, see the file COPYING, or write | ||
26 | * to the Free Software Foundation, Inc., | ||
27 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
28 | */ | ||
29 | |||
30 | #include <linux/module.h> | ||
31 | #include <linux/kernel.h> | ||
32 | #include <linux/init.h> | ||
33 | #include <linux/proc_fs.h> | ||
34 | #include <linux/uaccess.h> | ||
35 | |||
36 | #include <asm/current.h> | ||
37 | #include <asm/system.h> | ||
38 | #include <asm/cplb.h> | ||
39 | #include <asm/cplbinit.h> | ||
40 | #include <asm/blackfin.h> | ||
41 | |||
42 | #define CPLB_I 1 | ||
43 | #define CPLB_D 2 | ||
44 | |||
45 | #define SYNC_SYS SSYNC() | ||
46 | #define SYNC_CORE CSYNC() | ||
47 | |||
48 | #define CPLB_BIT_PAGESIZE 0x30000 | ||
49 | |||
50 | static char page_size_string_table[][4] = { "1K", "4K", "1M", "4M" }; | ||
51 | |||
52 | static char *cplb_print_entry(char *buf, struct cplb_entry *tbl, int switched) | ||
53 | { | ||
54 | int i; | ||
55 | buf += sprintf(buf, "Index\tAddress\t\tData\tSize\tU/RD\tU/WR\tS/WR\tSwitch\n"); | ||
56 | for (i = 0; i < MAX_CPLBS; i++) { | ||
57 | unsigned long data = tbl[i].data; | ||
58 | unsigned long addr = tbl[i].addr; | ||
59 | if (!(data & CPLB_VALID)) | ||
60 | continue; | ||
61 | |||
62 | buf += | ||
63 | sprintf(buf, | ||
64 | "%d\t0x%08lx\t%06lx\t%s\t%c\t%c\t%c\t%c\n", | ||
65 | i, addr, data, | ||
66 | page_size_string_table[(data & 0x30000) >> 16], | ||
67 | (data & CPLB_USER_RD) ? 'Y' : 'N', | ||
68 | (data & CPLB_USER_WR) ? 'Y' : 'N', | ||
69 | (data & CPLB_SUPV_WR) ? 'Y' : 'N', | ||
70 | i < switched ? 'N' : 'Y'); | ||
71 | } | ||
72 | buf += sprintf(buf, "\n"); | ||
73 | |||
74 | return buf; | ||
75 | } | ||
76 | |||
77 | int cplbinfo_proc_output(char *buf) | ||
78 | { | ||
79 | char *p; | ||
80 | |||
81 | p = buf; | ||
82 | |||
83 | p += sprintf(p, "------------------ CPLB Information ------------------\n\n"); | ||
84 | |||
85 | if (bfin_read_IMEM_CONTROL() & ENICPLB) { | ||
86 | p += sprintf(p, "Instruction CPLB entry:\n"); | ||
87 | p = cplb_print_entry(p, icplb_tbl, first_switched_icplb); | ||
88 | } else | ||
89 | p += sprintf(p, "Instruction CPLB is disabled.\n\n"); | ||
90 | |||
91 | if (1 || bfin_read_DMEM_CONTROL() & ENDCPLB) { | ||
92 | p += sprintf(p, "Data CPLB entry:\n"); | ||
93 | p = cplb_print_entry(p, dcplb_tbl, first_switched_dcplb); | ||
94 | } else | ||
95 | p += sprintf(p, "Data CPLB is disabled.\n"); | ||
96 | |||
97 | p += sprintf(p, "ICPLB miss: %d\nICPLB supervisor miss: %d\n", | ||
98 | nr_icplb_miss, nr_icplb_supv_miss); | ||
99 | p += sprintf(p, "DCPLB miss: %d\nDCPLB protection fault:%d\n", | ||
100 | nr_dcplb_miss, nr_dcplb_prot); | ||
101 | p += sprintf(p, "CPLB flushes: %d\n", | ||
102 | nr_cplb_flush); | ||
103 | |||
104 | return p - buf; | ||
105 | } | ||
106 | |||
107 | static int cplbinfo_read_proc(char *page, char **start, off_t off, | ||
108 | int count, int *eof, void *data) | ||
109 | { | ||
110 | int len; | ||
111 | |||
112 | len = cplbinfo_proc_output(page); | ||
113 | if (len <= off + count) | ||
114 | *eof = 1; | ||
115 | *start = page + off; | ||
116 | len -= off; | ||
117 | if (len > count) | ||
118 | len = count; | ||
119 | if (len < 0) | ||
120 | len = 0; | ||
121 | return len; | ||
122 | } | ||
123 | |||
124 | static int __init cplbinfo_init(void) | ||
125 | { | ||
126 | struct proc_dir_entry *entry; | ||
127 | |||
128 | entry = create_proc_entry("cplbinfo", 0, NULL); | ||
129 | if (!entry) | ||
130 | return -ENOMEM; | ||
131 | |||
132 | entry->read_proc = cplbinfo_read_proc; | ||
133 | entry->data = NULL; | ||
134 | |||
135 | return 0; | ||
136 | } | ||
137 | |||
138 | static void __exit cplbinfo_exit(void) | ||
139 | { | ||
140 | remove_proc_entry("cplbinfo", NULL); | ||
141 | } | ||
142 | |||
143 | module_init(cplbinfo_init); | ||
144 | module_exit(cplbinfo_exit); | ||
diff --git a/arch/blackfin/kernel/cplb-mpu/cplbinit.c b/arch/blackfin/kernel/cplb-mpu/cplbinit.c new file mode 100644 index 000000000000..e2e2b5079f5b --- /dev/null +++ b/arch/blackfin/kernel/cplb-mpu/cplbinit.c | |||
@@ -0,0 +1,91 @@ | |||
1 | /* | ||
2 | * Blackfin CPLB initialization | ||
3 | * | ||
4 | * Copyright 2004-2007 Analog Devices Inc. | ||
5 | * | ||
6 | * Bugs: Enter bugs at http://blackfin.uclinux.org/ | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | * | ||
13 | * This program is distributed in the hope that it will be useful, | ||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | * GNU General Public License for more details. | ||
17 | * | ||
18 | * You should have received a copy of the GNU General Public License | ||
19 | * along with this program; if not, see the file COPYING, or write | ||
20 | * to the Free Software Foundation, Inc., | ||
21 | * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
22 | */ | ||
23 | #include <linux/module.h> | ||
24 | |||
25 | #include <asm/blackfin.h> | ||
26 | #include <asm/cplb.h> | ||
27 | #include <asm/cplbinit.h> | ||
28 | |||
29 | struct cplb_entry icplb_tbl[MAX_CPLBS]; | ||
30 | struct cplb_entry dcplb_tbl[MAX_CPLBS]; | ||
31 | |||
32 | int first_switched_icplb, first_switched_dcplb; | ||
33 | int first_mask_dcplb; | ||
34 | |||
35 | void __init generate_cpl_tables(void) | ||
36 | { | ||
37 | int i_d, i_i; | ||
38 | unsigned long addr; | ||
39 | unsigned long d_data, i_data; | ||
40 | unsigned long d_cache = 0, i_cache = 0; | ||
41 | |||
42 | #ifdef CONFIG_BFIN_ICACHE | ||
43 | i_cache = CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; | ||
44 | #endif | ||
45 | |||
46 | #ifdef CONFIG_BFIN_DCACHE | ||
47 | d_cache = CPLB_L1_CHBL; | ||
48 | #ifdef CONFIG_BLKFIN_WT | ||
49 | d_cache |= CPLB_L1_AOW | CPLB_WT; | ||
50 | #endif | ||
51 | #endif | ||
52 | i_d = i_i = 0; | ||
53 | |||
54 | /* Set up the zero page. */ | ||
55 | dcplb_tbl[i_d].addr = 0; | ||
56 | dcplb_tbl[i_d++].data = SDRAM_OOPS | PAGE_SIZE_1KB; | ||
57 | |||
58 | #if 0 | ||
59 | icplb_tbl[i_i].addr = 0; | ||
60 | icplb_tbl[i_i++].data = i_cache | CPLB_USER_RD | PAGE_SIZE_4KB; | ||
61 | #endif | ||
62 | |||
63 | /* Cover kernel memory with 4M pages. */ | ||
64 | addr = 0; | ||
65 | d_data = d_cache | CPLB_SUPV_WR | CPLB_VALID | PAGE_SIZE_4MB | CPLB_DIRTY; | ||
66 | i_data = i_cache | CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4MB; | ||
67 | |||
68 | for (; addr < memory_start; addr += 4 * 1024 * 1024) { | ||
69 | dcplb_tbl[i_d].addr = addr; | ||
70 | dcplb_tbl[i_d++].data = d_data; | ||
71 | icplb_tbl[i_i].addr = addr; | ||
72 | icplb_tbl[i_i++].data = i_data | (addr == 0 ? CPLB_USER_RD : 0); | ||
73 | } | ||
74 | |||
75 | /* Cover L1 memory. One 4M area for code and data each is enough. */ | ||
76 | #if L1_DATA_A_LENGTH > 0 || L1_DATA_B_LENGTH > 0 | ||
77 | dcplb_tbl[i_d].addr = L1_DATA_A_START; | ||
78 | dcplb_tbl[i_d++].data = L1_DMEMORY | PAGE_SIZE_4MB; | ||
79 | #endif | ||
80 | icplb_tbl[i_i].addr = L1_CODE_START; | ||
81 | icplb_tbl[i_i++].data = L1_IMEMORY | PAGE_SIZE_4MB; | ||
82 | |||
83 | first_mask_dcplb = i_d; | ||
84 | first_switched_dcplb = i_d + (1 << page_mask_order); | ||
85 | first_switched_icplb = i_i; | ||
86 | |||
87 | while (i_d < MAX_CPLBS) | ||
88 | dcplb_tbl[i_d++].data = 0; | ||
89 | while (i_i < MAX_CPLBS) | ||
90 | icplb_tbl[i_i++].data = 0; | ||
91 | } | ||
diff --git a/arch/blackfin/kernel/cplb-mpu/cplbmgr.c b/arch/blackfin/kernel/cplb-mpu/cplbmgr.c new file mode 100644 index 000000000000..c426a22f9907 --- /dev/null +++ b/arch/blackfin/kernel/cplb-mpu/cplbmgr.c | |||
@@ -0,0 +1,338 @@ | |||
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 | #ifdef CONFIG_BFIN_ICACHE | ||
28 | |||
29 | #define FAULT_RW (1 << 16) | ||
30 | #define FAULT_USERSUPV (1 << 17) | ||
31 | |||
32 | int page_mask_nelts; | ||
33 | int page_mask_order; | ||
34 | unsigned long *current_rwx_mask; | ||
35 | |||
36 | int nr_dcplb_miss, nr_icplb_miss, nr_icplb_supv_miss, nr_dcplb_prot; | ||
37 | int nr_cplb_flush; | ||
38 | |||
39 | static inline void disable_dcplb(void) | ||
40 | { | ||
41 | unsigned long ctrl; | ||
42 | SSYNC(); | ||
43 | ctrl = bfin_read_DMEM_CONTROL(); | ||
44 | ctrl &= ~ENDCPLB; | ||
45 | bfin_write_DMEM_CONTROL(ctrl); | ||
46 | SSYNC(); | ||
47 | } | ||
48 | |||
49 | static inline void enable_dcplb(void) | ||
50 | { | ||
51 | unsigned long ctrl; | ||
52 | SSYNC(); | ||
53 | ctrl = bfin_read_DMEM_CONTROL(); | ||
54 | ctrl |= ENDCPLB; | ||
55 | bfin_write_DMEM_CONTROL(ctrl); | ||
56 | SSYNC(); | ||
57 | } | ||
58 | |||
59 | static inline void disable_icplb(void) | ||
60 | { | ||
61 | unsigned long ctrl; | ||
62 | SSYNC(); | ||
63 | ctrl = bfin_read_IMEM_CONTROL(); | ||
64 | ctrl &= ~ENICPLB; | ||
65 | bfin_write_IMEM_CONTROL(ctrl); | ||
66 | SSYNC(); | ||
67 | } | ||
68 | |||
69 | static inline void enable_icplb(void) | ||
70 | { | ||
71 | unsigned long ctrl; | ||
72 | SSYNC(); | ||
73 | ctrl = bfin_read_IMEM_CONTROL(); | ||
74 | ctrl |= ENICPLB; | ||
75 | bfin_write_IMEM_CONTROL(ctrl); | ||
76 | SSYNC(); | ||
77 | } | ||
78 | |||
79 | /* | ||
80 | * Given the contents of the status register, return the index of the | ||
81 | * CPLB that caused the fault. | ||
82 | */ | ||
83 | static inline int faulting_cplb_index(int status) | ||
84 | { | ||
85 | int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF); | ||
86 | return 30 - signbits; | ||
87 | } | ||
88 | |||
89 | /* | ||
90 | * Given the contents of the status register and the DCPLB_DATA contents, | ||
91 | * return true if a write access should be permitted. | ||
92 | */ | ||
93 | static inline int write_permitted(int status, unsigned long data) | ||
94 | { | ||
95 | if (status & FAULT_USERSUPV) | ||
96 | return !!(data & CPLB_SUPV_WR); | ||
97 | else | ||
98 | return !!(data & CPLB_USER_WR); | ||
99 | } | ||
100 | |||
101 | /* Counters to implement round-robin replacement. */ | ||
102 | static int icplb_rr_index, dcplb_rr_index; | ||
103 | |||
104 | /* | ||
105 | * Find an ICPLB entry to be evicted and return its index. | ||
106 | */ | ||
107 | static int evict_one_icplb(void) | ||
108 | { | ||
109 | int i; | ||
110 | for (i = first_switched_icplb; i < MAX_CPLBS; i++) | ||
111 | if ((icplb_tbl[i].data & CPLB_VALID) == 0) | ||
112 | return i; | ||
113 | i = first_switched_icplb + icplb_rr_index; | ||
114 | if (i >= MAX_CPLBS) { | ||
115 | i -= MAX_CPLBS - first_switched_icplb; | ||
116 | icplb_rr_index -= MAX_CPLBS - first_switched_icplb; | ||
117 | } | ||
118 | icplb_rr_index++; | ||
119 | return i; | ||
120 | } | ||
121 | |||
122 | static int evict_one_dcplb(void) | ||
123 | { | ||
124 | int i; | ||
125 | for (i = first_switched_dcplb; i < MAX_CPLBS; i++) | ||
126 | if ((dcplb_tbl[i].data & CPLB_VALID) == 0) | ||
127 | return i; | ||
128 | i = first_switched_dcplb + dcplb_rr_index; | ||
129 | if (i >= MAX_CPLBS) { | ||
130 | i -= MAX_CPLBS - first_switched_dcplb; | ||
131 | dcplb_rr_index -= MAX_CPLBS - first_switched_dcplb; | ||
132 | } | ||
133 | dcplb_rr_index++; | ||
134 | return i; | ||
135 | } | ||
136 | |||
137 | static noinline int dcplb_miss(void) | ||
138 | { | ||
139 | unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); | ||
140 | int status = bfin_read_DCPLB_STATUS(); | ||
141 | unsigned long *mask; | ||
142 | int idx; | ||
143 | unsigned long d_data; | ||
144 | |||
145 | nr_dcplb_miss++; | ||
146 | if (addr >= _ramend) | ||
147 | return CPLB_PROT_VIOL; | ||
148 | |||
149 | d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; | ||
150 | #ifdef CONFIG_BFIN_DCACHE | ||
151 | d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; | ||
152 | #ifdef CONFIG_BLKFIN_WT | ||
153 | d_data |= CPLB_L1_AOW | CPLB_WT; | ||
154 | #endif | ||
155 | #endif | ||
156 | mask = current_rwx_mask; | ||
157 | if (mask) { | ||
158 | int page = addr >> PAGE_SHIFT; | ||
159 | int offs = page >> 5; | ||
160 | int bit = 1 << (page & 31); | ||
161 | |||
162 | if (mask[offs] & bit) | ||
163 | d_data |= CPLB_USER_RD; | ||
164 | |||
165 | mask += page_mask_nelts; | ||
166 | if (mask[offs] & bit) | ||
167 | d_data |= CPLB_USER_WR; | ||
168 | } | ||
169 | |||
170 | idx = evict_one_dcplb(); | ||
171 | |||
172 | addr &= PAGE_MASK; | ||
173 | dcplb_tbl[idx].addr = addr; | ||
174 | dcplb_tbl[idx].data = d_data; | ||
175 | |||
176 | disable_dcplb(); | ||
177 | bfin_write32(DCPLB_DATA0 + idx * 4, d_data); | ||
178 | bfin_write32(DCPLB_ADDR0 + idx * 4, addr); | ||
179 | enable_dcplb(); | ||
180 | |||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | static noinline int icplb_miss(void) | ||
185 | { | ||
186 | unsigned long addr = bfin_read_ICPLB_FAULT_ADDR(); | ||
187 | int status = bfin_read_ICPLB_STATUS(); | ||
188 | int idx; | ||
189 | unsigned long i_data; | ||
190 | |||
191 | nr_icplb_miss++; | ||
192 | if (status & FAULT_USERSUPV) | ||
193 | nr_icplb_supv_miss++; | ||
194 | |||
195 | if (addr >= _ramend) | ||
196 | return CPLB_PROT_VIOL; | ||
197 | |||
198 | /* | ||
199 | * First, try to find a CPLB that matches this address. If we | ||
200 | * find one, then the fact that we're in the miss handler means | ||
201 | * that the instruction crosses a page boundary. | ||
202 | */ | ||
203 | for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) { | ||
204 | if (icplb_tbl[idx].data & CPLB_VALID) { | ||
205 | unsigned long this_addr = icplb_tbl[idx].addr; | ||
206 | if (this_addr <= addr && this_addr + PAGE_SIZE > addr) { | ||
207 | addr += PAGE_SIZE; | ||
208 | break; | ||
209 | } | ||
210 | } | ||
211 | } | ||
212 | |||
213 | i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB; | ||
214 | #ifdef CONFIG_BFIN_ICACHE | ||
215 | i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; | ||
216 | #endif | ||
217 | |||
218 | /* | ||
219 | * Two cases to distinguish - a supervisor access must necessarily | ||
220 | * be for a module page; we grant it unconditionally (could do better | ||
221 | * here in the future). Otherwise, check the x bitmap of the current | ||
222 | * process. | ||
223 | */ | ||
224 | if (!(status & FAULT_USERSUPV)) { | ||
225 | unsigned long *mask = current_rwx_mask; | ||
226 | |||
227 | if (mask) { | ||
228 | int page = addr >> PAGE_SHIFT; | ||
229 | int offs = page >> 5; | ||
230 | int bit = 1 << (page & 31); | ||
231 | |||
232 | mask += 2 * page_mask_nelts; | ||
233 | if (mask[offs] & bit) | ||
234 | i_data |= CPLB_USER_RD; | ||
235 | } | ||
236 | } | ||
237 | |||
238 | idx = evict_one_icplb(); | ||
239 | addr &= PAGE_MASK; | ||
240 | icplb_tbl[idx].addr = addr; | ||
241 | icplb_tbl[idx].data = i_data; | ||
242 | |||
243 | disable_icplb(); | ||
244 | bfin_write32(ICPLB_DATA0 + idx * 4, i_data); | ||
245 | bfin_write32(ICPLB_ADDR0 + idx * 4, addr); | ||
246 | enable_icplb(); | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static noinline int dcplb_protection_fault(void) | ||
252 | { | ||
253 | unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); | ||
254 | int status = bfin_read_DCPLB_STATUS(); | ||
255 | |||
256 | nr_dcplb_prot++; | ||
257 | |||
258 | if (status & FAULT_RW) { | ||
259 | int idx = faulting_cplb_index(status); | ||
260 | unsigned long data = dcplb_tbl[idx].data; | ||
261 | if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) && | ||
262 | write_permitted(status, data)) { | ||
263 | data |= CPLB_DIRTY; | ||
264 | dcplb_tbl[idx].data = data; | ||
265 | bfin_write32(DCPLB_DATA0 + idx * 4, data); | ||
266 | return 0; | ||
267 | } | ||
268 | } | ||
269 | return CPLB_PROT_VIOL; | ||
270 | } | ||
271 | |||
272 | int cplb_hdr(int seqstat, struct pt_regs *regs) | ||
273 | { | ||
274 | int cause = seqstat & 0x3f; | ||
275 | switch (cause) { | ||
276 | case 0x23: | ||
277 | return dcplb_protection_fault(); | ||
278 | case 0x2C: | ||
279 | return icplb_miss(); | ||
280 | case 0x26: | ||
281 | return dcplb_miss(); | ||
282 | default: | ||
283 | return 1; | ||
284 | panic_cplb_error(seqstat, regs); | ||
285 | } | ||
286 | } | ||
287 | |||
288 | void flush_switched_cplbs(void) | ||
289 | { | ||
290 | int i; | ||
291 | |||
292 | nr_cplb_flush++; | ||
293 | |||
294 | disable_icplb(); | ||
295 | for (i = first_switched_icplb; i < MAX_CPLBS; i++) { | ||
296 | icplb_tbl[i].data = 0; | ||
297 | bfin_write32(ICPLB_DATA0 + i * 4, 0); | ||
298 | } | ||
299 | enable_icplb(); | ||
300 | |||
301 | disable_dcplb(); | ||
302 | for (i = first_mask_dcplb; i < MAX_CPLBS; i++) { | ||
303 | dcplb_tbl[i].data = 0; | ||
304 | bfin_write32(DCPLB_DATA0 + i * 4, 0); | ||
305 | } | ||
306 | enable_dcplb(); | ||
307 | } | ||
308 | |||
309 | void set_mask_dcplbs(unsigned long *masks) | ||
310 | { | ||
311 | int i; | ||
312 | unsigned long addr = (unsigned long)masks; | ||
313 | unsigned long d_data; | ||
314 | current_rwx_mask = masks; | ||
315 | |||
316 | if (!masks) | ||
317 | return; | ||
318 | |||
319 | d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; | ||
320 | #ifdef CONFIG_BFIN_DCACHE | ||
321 | d_data |= CPLB_L1_CHBL; | ||
322 | #ifdef CONFIG_BLKFIN_WT | ||
323 | d_data |= CPLB_L1_AOW | CPLB_WT; | ||
324 | #endif | ||
325 | #endif | ||
326 | |||
327 | disable_dcplb(); | ||
328 | for (i = first_mask_dcplb; i < first_switched_dcplb; i++) { | ||
329 | dcplb_tbl[i].addr = addr; | ||
330 | dcplb_tbl[i].data = d_data; | ||
331 | bfin_write32(DCPLB_DATA0 + i * 4, d_data); | ||
332 | bfin_write32(DCPLB_ADDR0 + i * 4, addr); | ||
333 | addr += PAGE_SIZE; | ||
334 | } | ||
335 | enable_dcplb(); | ||
336 | } | ||
337 | |||
338 | #endif | ||