diff options
author | Bernd Schmidt <bernd.schmidt@analog.com> | 2008-01-27 13:39:16 +0300 |
---|---|---|
committer | Bryan Wu <bryan.wu@analog.com> | 2008-01-27 13:39:16 +0300 |
commit | b97b8a998397e8c64699559099fa9febffae2b4d (patch) | |
tree | 689188b6336cf45b4391f5bc764878e342b9ac90 /arch/blackfin/kernel/cplb-mpu | |
parent | 2047e40d724d42928c0b5994a1568c1b738efdb7 (diff) | |
download | linux-b97b8a998397e8c64699559099fa9febffae2b4d.tar.xz |
[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 @@ +# +# arch/blackfin/kernel/cplb-nompu/Makefile +# + +obj-y := cplbinit.o cacheinit.o cplbmgr.o + +obj-$(CONFIG_CPLB_INFO) += cplbinfo.o + 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 @@ +/* + * Copyright 2004-2007 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/cpu.h> + +#include <asm/cacheflush.h> +#include <asm/blackfin.h> +#include <asm/cplb.h> +#include <asm/cplbinit.h> + +#if defined(CONFIG_BFIN_ICACHE) +void bfin_icache_init(void) +{ + unsigned long ctrl; + int i; + + SSYNC(); + for (i = 0; i < MAX_CPLBS; i++) { + bfin_write32(ICPLB_ADDR0 + i * 4, icplb_tbl[i].addr); + bfin_write32(ICPLB_DATA0 + i * 4, icplb_tbl[i].data); + } + ctrl = bfin_read_IMEM_CONTROL(); + ctrl |= IMC | ENICPLB; + bfin_write_IMEM_CONTROL(ctrl); + SSYNC(); +} +#endif + +#if defined(CONFIG_BFIN_DCACHE) +void bfin_dcache_init(void) +{ + unsigned long ctrl; + int i; + + SSYNC(); + for (i = 0; i < MAX_CPLBS; i++) { + bfin_write32(DCPLB_ADDR0 + i * 4, dcplb_tbl[i].addr); + bfin_write32(DCPLB_DATA0 + i * 4, dcplb_tbl[i].data); + } + + ctrl = bfin_read_DMEM_CONTROL(); + ctrl |= DMEM_CNTR; + bfin_write_DMEM_CONTROL(ctrl); + SSYNC(); +} +#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 @@ +/* + * File: arch/blackfin/mach-common/cplbinfo.c + * Based on: + * Author: Sonic Zhang <sonic.zhang@analog.com> + * + * Created: Jan. 2005 + * Description: Display CPLB status + * + * Modified: + * Copyright 2004-2006 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/proc_fs.h> +#include <linux/uaccess.h> + +#include <asm/current.h> +#include <asm/system.h> +#include <asm/cplb.h> +#include <asm/cplbinit.h> +#include <asm/blackfin.h> + +#define CPLB_I 1 +#define CPLB_D 2 + +#define SYNC_SYS SSYNC() +#define SYNC_CORE CSYNC() + +#define CPLB_BIT_PAGESIZE 0x30000 + +static char page_size_string_table[][4] = { "1K", "4K", "1M", "4M" }; + +static char *cplb_print_entry(char *buf, struct cplb_entry *tbl, int switched) +{ + int i; + buf += sprintf(buf, "Index\tAddress\t\tData\tSize\tU/RD\tU/WR\tS/WR\tSwitch\n"); + for (i = 0; i < MAX_CPLBS; i++) { + unsigned long data = tbl[i].data; + unsigned long addr = tbl[i].addr; + if (!(data & CPLB_VALID)) + continue; + + buf += + sprintf(buf, + "%d\t0x%08lx\t%06lx\t%s\t%c\t%c\t%c\t%c\n", + i, addr, data, + page_size_string_table[(data & 0x30000) >> 16], + (data & CPLB_USER_RD) ? 'Y' : 'N', + (data & CPLB_USER_WR) ? 'Y' : 'N', + (data & CPLB_SUPV_WR) ? 'Y' : 'N', + i < switched ? 'N' : 'Y'); + } + buf += sprintf(buf, "\n"); + + return buf; +} + +int cplbinfo_proc_output(char *buf) +{ + char *p; + + p = buf; + + p += sprintf(p, "------------------ CPLB Information ------------------\n\n"); + + if (bfin_read_IMEM_CONTROL() & ENICPLB) { + p += sprintf(p, "Instruction CPLB entry:\n"); + p = cplb_print_entry(p, icplb_tbl, first_switched_icplb); + } else + p += sprintf(p, "Instruction CPLB is disabled.\n\n"); + + if (1 || bfin_read_DMEM_CONTROL() & ENDCPLB) { + p += sprintf(p, "Data CPLB entry:\n"); + p = cplb_print_entry(p, dcplb_tbl, first_switched_dcplb); + } else + p += sprintf(p, "Data CPLB is disabled.\n"); + + p += sprintf(p, "ICPLB miss: %d\nICPLB supervisor miss: %d\n", + nr_icplb_miss, nr_icplb_supv_miss); + p += sprintf(p, "DCPLB miss: %d\nDCPLB protection fault:%d\n", + nr_dcplb_miss, nr_dcplb_prot); + p += sprintf(p, "CPLB flushes: %d\n", + nr_cplb_flush); + + return p - buf; +} + +static int cplbinfo_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + + len = cplbinfo_proc_output(page); + if (len <= off + count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int __init cplbinfo_init(void) +{ + struct proc_dir_entry *entry; + + entry = create_proc_entry("cplbinfo", 0, NULL); + if (!entry) + return -ENOMEM; + + entry->read_proc = cplbinfo_read_proc; + entry->data = NULL; + + return 0; +} + +static void __exit cplbinfo_exit(void) +{ + remove_proc_entry("cplbinfo", NULL); +} + +module_init(cplbinfo_init); +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 @@ +/* + * Blackfin CPLB initialization + * + * Copyright 2004-2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/module.h> + +#include <asm/blackfin.h> +#include <asm/cplb.h> +#include <asm/cplbinit.h> + +struct cplb_entry icplb_tbl[MAX_CPLBS]; +struct cplb_entry dcplb_tbl[MAX_CPLBS]; + +int first_switched_icplb, first_switched_dcplb; +int first_mask_dcplb; + +void __init generate_cpl_tables(void) +{ + int i_d, i_i; + unsigned long addr; + unsigned long d_data, i_data; + unsigned long d_cache = 0, i_cache = 0; + +#ifdef CONFIG_BFIN_ICACHE + i_cache = CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; +#endif + +#ifdef CONFIG_BFIN_DCACHE + d_cache = CPLB_L1_CHBL; +#ifdef CONFIG_BLKFIN_WT + d_cache |= CPLB_L1_AOW | CPLB_WT; +#endif +#endif + i_d = i_i = 0; + + /* Set up the zero page. */ + dcplb_tbl[i_d].addr = 0; + dcplb_tbl[i_d++].data = SDRAM_OOPS | PAGE_SIZE_1KB; + +#if 0 + icplb_tbl[i_i].addr = 0; + icplb_tbl[i_i++].data = i_cache | CPLB_USER_RD | PAGE_SIZE_4KB; +#endif + + /* Cover kernel memory with 4M pages. */ + addr = 0; + d_data = d_cache | CPLB_SUPV_WR | CPLB_VALID | PAGE_SIZE_4MB | CPLB_DIRTY; + i_data = i_cache | CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4MB; + + for (; addr < memory_start; addr += 4 * 1024 * 1024) { + dcplb_tbl[i_d].addr = addr; + dcplb_tbl[i_d++].data = d_data; + icplb_tbl[i_i].addr = addr; + icplb_tbl[i_i++].data = i_data | (addr == 0 ? CPLB_USER_RD : 0); + } + + /* Cover L1 memory. One 4M area for code and data each is enough. */ +#if L1_DATA_A_LENGTH > 0 || L1_DATA_B_LENGTH > 0 + dcplb_tbl[i_d].addr = L1_DATA_A_START; + dcplb_tbl[i_d++].data = L1_DMEMORY | PAGE_SIZE_4MB; +#endif + icplb_tbl[i_i].addr = L1_CODE_START; + icplb_tbl[i_i++].data = L1_IMEMORY | PAGE_SIZE_4MB; + + first_mask_dcplb = i_d; + first_switched_dcplb = i_d + (1 << page_mask_order); + first_switched_icplb = i_i; + + while (i_d < MAX_CPLBS) + dcplb_tbl[i_d++].data = 0; + while (i_i < MAX_CPLBS) + icplb_tbl[i_i++].data = 0; +} 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 @@ +/* + * Blackfin CPLB exception handling. + * Copyright 2004-2007 Analog Devices Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/module.h> +#include <linux/mm.h> + +#include <asm/blackfin.h> +#include <asm/cplbinit.h> +#include <asm/mmu_context.h> + +#ifdef CONFIG_BFIN_ICACHE + +#define FAULT_RW (1 << 16) +#define FAULT_USERSUPV (1 << 17) + +int page_mask_nelts; +int page_mask_order; +unsigned long *current_rwx_mask; + +int nr_dcplb_miss, nr_icplb_miss, nr_icplb_supv_miss, nr_dcplb_prot; +int nr_cplb_flush; + +static inline void disable_dcplb(void) +{ + unsigned long ctrl; + SSYNC(); + ctrl = bfin_read_DMEM_CONTROL(); + ctrl &= ~ENDCPLB; + bfin_write_DMEM_CONTROL(ctrl); + SSYNC(); +} + +static inline void enable_dcplb(void) +{ + unsigned long ctrl; + SSYNC(); + ctrl = bfin_read_DMEM_CONTROL(); + ctrl |= ENDCPLB; + bfin_write_DMEM_CONTROL(ctrl); + SSYNC(); +} + +static inline void disable_icplb(void) +{ + unsigned long ctrl; + SSYNC(); + ctrl = bfin_read_IMEM_CONTROL(); + ctrl &= ~ENICPLB; + bfin_write_IMEM_CONTROL(ctrl); + SSYNC(); +} + +static inline void enable_icplb(void) +{ + unsigned long ctrl; + SSYNC(); + ctrl = bfin_read_IMEM_CONTROL(); + ctrl |= ENICPLB; + bfin_write_IMEM_CONTROL(ctrl); + SSYNC(); +} + +/* + * Given the contents of the status register, return the index of the + * CPLB that caused the fault. + */ +static inline int faulting_cplb_index(int status) +{ + int signbits = __builtin_bfin_norm_fr1x32(status & 0xFFFF); + return 30 - signbits; +} + +/* + * Given the contents of the status register and the DCPLB_DATA contents, + * return true if a write access should be permitted. + */ +static inline int write_permitted(int status, unsigned long data) +{ + if (status & FAULT_USERSUPV) + return !!(data & CPLB_SUPV_WR); + else + return !!(data & CPLB_USER_WR); +} + +/* Counters to implement round-robin replacement. */ +static int icplb_rr_index, dcplb_rr_index; + +/* + * Find an ICPLB entry to be evicted and return its index. + */ +static int evict_one_icplb(void) +{ + int i; + for (i = first_switched_icplb; i < MAX_CPLBS; i++) + if ((icplb_tbl[i].data & CPLB_VALID) == 0) + return i; + i = first_switched_icplb + icplb_rr_index; + if (i >= MAX_CPLBS) { + i -= MAX_CPLBS - first_switched_icplb; + icplb_rr_index -= MAX_CPLBS - first_switched_icplb; + } + icplb_rr_index++; + return i; +} + +static int evict_one_dcplb(void) +{ + int i; + for (i = first_switched_dcplb; i < MAX_CPLBS; i++) + if ((dcplb_tbl[i].data & CPLB_VALID) == 0) + return i; + i = first_switched_dcplb + dcplb_rr_index; + if (i >= MAX_CPLBS) { + i -= MAX_CPLBS - first_switched_dcplb; + dcplb_rr_index -= MAX_CPLBS - first_switched_dcplb; + } + dcplb_rr_index++; + return i; +} + +static noinline int dcplb_miss(void) +{ + unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); + int status = bfin_read_DCPLB_STATUS(); + unsigned long *mask; + int idx; + unsigned long d_data; + + nr_dcplb_miss++; + if (addr >= _ramend) + return CPLB_PROT_VIOL; + + d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; +#ifdef CONFIG_BFIN_DCACHE + d_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; +#ifdef CONFIG_BLKFIN_WT + d_data |= CPLB_L1_AOW | CPLB_WT; +#endif +#endif + mask = current_rwx_mask; + if (mask) { + int page = addr >> PAGE_SHIFT; + int offs = page >> 5; + int bit = 1 << (page & 31); + + if (mask[offs] & bit) + d_data |= CPLB_USER_RD; + + mask += page_mask_nelts; + if (mask[offs] & bit) + d_data |= CPLB_USER_WR; + } + + idx = evict_one_dcplb(); + + addr &= PAGE_MASK; + dcplb_tbl[idx].addr = addr; + dcplb_tbl[idx].data = d_data; + + disable_dcplb(); + bfin_write32(DCPLB_DATA0 + idx * 4, d_data); + bfin_write32(DCPLB_ADDR0 + idx * 4, addr); + enable_dcplb(); + + return 0; +} + +static noinline int icplb_miss(void) +{ + unsigned long addr = bfin_read_ICPLB_FAULT_ADDR(); + int status = bfin_read_ICPLB_STATUS(); + int idx; + unsigned long i_data; + + nr_icplb_miss++; + if (status & FAULT_USERSUPV) + nr_icplb_supv_miss++; + + if (addr >= _ramend) + return CPLB_PROT_VIOL; + + /* + * First, try to find a CPLB that matches this address. If we + * find one, then the fact that we're in the miss handler means + * that the instruction crosses a page boundary. + */ + for (idx = first_switched_icplb; idx < MAX_CPLBS; idx++) { + if (icplb_tbl[idx].data & CPLB_VALID) { + unsigned long this_addr = icplb_tbl[idx].addr; + if (this_addr <= addr && this_addr + PAGE_SIZE > addr) { + addr += PAGE_SIZE; + break; + } + } + } + + i_data = CPLB_VALID | CPLB_PORTPRIO | PAGE_SIZE_4KB; +#ifdef CONFIG_BFIN_ICACHE + i_data |= CPLB_L1_CHBL | ANOMALY_05000158_WORKAROUND; +#endif + + /* + * Two cases to distinguish - a supervisor access must necessarily + * be for a module page; we grant it unconditionally (could do better + * here in the future). Otherwise, check the x bitmap of the current + * process. + */ + if (!(status & FAULT_USERSUPV)) { + unsigned long *mask = current_rwx_mask; + + if (mask) { + int page = addr >> PAGE_SHIFT; + int offs = page >> 5; + int bit = 1 << (page & 31); + + mask += 2 * page_mask_nelts; + if (mask[offs] & bit) + i_data |= CPLB_USER_RD; + } + } + + idx = evict_one_icplb(); + addr &= PAGE_MASK; + icplb_tbl[idx].addr = addr; + icplb_tbl[idx].data = i_data; + + disable_icplb(); + bfin_write32(ICPLB_DATA0 + idx * 4, i_data); + bfin_write32(ICPLB_ADDR0 + idx * 4, addr); + enable_icplb(); + + return 0; +} + +static noinline int dcplb_protection_fault(void) +{ + unsigned long addr = bfin_read_DCPLB_FAULT_ADDR(); + int status = bfin_read_DCPLB_STATUS(); + + nr_dcplb_prot++; + + if (status & FAULT_RW) { + int idx = faulting_cplb_index(status); + unsigned long data = dcplb_tbl[idx].data; + if (!(data & CPLB_WT) && !(data & CPLB_DIRTY) && + write_permitted(status, data)) { + data |= CPLB_DIRTY; + dcplb_tbl[idx].data = data; + bfin_write32(DCPLB_DATA0 + idx * 4, data); + return 0; + } + } + return CPLB_PROT_VIOL; +} + +int cplb_hdr(int seqstat, struct pt_regs *regs) +{ + int cause = seqstat & 0x3f; + switch (cause) { + case 0x23: + return dcplb_protection_fault(); + case 0x2C: + return icplb_miss(); + case 0x26: + return dcplb_miss(); + default: + return 1; + panic_cplb_error(seqstat, regs); + } +} + +void flush_switched_cplbs(void) +{ + int i; + + nr_cplb_flush++; + + disable_icplb(); + for (i = first_switched_icplb; i < MAX_CPLBS; i++) { + icplb_tbl[i].data = 0; + bfin_write32(ICPLB_DATA0 + i * 4, 0); + } + enable_icplb(); + + disable_dcplb(); + for (i = first_mask_dcplb; i < MAX_CPLBS; i++) { + dcplb_tbl[i].data = 0; + bfin_write32(DCPLB_DATA0 + i * 4, 0); + } + enable_dcplb(); +} + +void set_mask_dcplbs(unsigned long *masks) +{ + int i; + unsigned long addr = (unsigned long)masks; + unsigned long d_data; + current_rwx_mask = masks; + + if (!masks) + return; + + d_data = CPLB_SUPV_WR | CPLB_VALID | CPLB_DIRTY | PAGE_SIZE_4KB; +#ifdef CONFIG_BFIN_DCACHE + d_data |= CPLB_L1_CHBL; +#ifdef CONFIG_BLKFIN_WT + d_data |= CPLB_L1_AOW | CPLB_WT; +#endif +#endif + + disable_dcplb(); + for (i = first_mask_dcplb; i < first_switched_dcplb; i++) { + dcplb_tbl[i].addr = addr; + dcplb_tbl[i].data = d_data; + bfin_write32(DCPLB_DATA0 + i * 4, d_data); + bfin_write32(DCPLB_ADDR0 + i * 4, addr); + addr += PAGE_SIZE; + } + enable_dcplb(); +} + +#endif |