[Blackfin] arch: Initial checkin of the memory protection support.
authorBernd Schmidt <bernd.schmidt@analog.com>
Sun, 27 Jan 2008 10:39:16 +0000 (18:39 +0800)
committerBryan Wu <bryan.wu@analog.com>
Sun, 27 Jan 2008 10:39:16 +0000 (18:39 +0800)
Enable it with CONFIG_MPU.

Signed-off-by: Bernd Schmidt <bernd.schmidt@analog.com>
Signed-off-by: Bryan Wu <bryan.wu@analog.com>
15 files changed:
arch/blackfin/Kconfig
arch/blackfin/Makefile
arch/blackfin/kernel/cplb-mpu/Makefile [new file with mode: 0644]
arch/blackfin/kernel/cplb-mpu/cacheinit.c [new file with mode: 0644]
arch/blackfin/kernel/cplb-mpu/cplbinfo.c [new file with mode: 0644]
arch/blackfin/kernel/cplb-mpu/cplbinit.c [new file with mode: 0644]
arch/blackfin/kernel/cplb-mpu/cplbmgr.c [new file with mode: 0644]
arch/blackfin/kernel/setup.c
arch/blackfin/mach-common/entry.S
arch/blackfin/mm/init.c
include/asm-blackfin/cplb-mpu.h [new file with mode: 0644]
include/asm-blackfin/cplb.h
include/asm-blackfin/cplbinit.h
include/asm-blackfin/mmu.h
include/asm-blackfin/mmu_context.h

index ce521a9..6b96d7d 100644 (file)
@@ -765,6 +765,15 @@ config L1_MAX_PIECE
          Set the max memory pieces for the L1 SRAM allocation algorithm.
          Min value is 16. Max value is 1024.
 
+
+config MPU
+       bool "Enable the memory protection unit (EXPERIMENTAL)"
+       default n
+       help
+         Use the processor's MPU to protect applications from accessing
+         memory they do not own.  This comes at a performance penalty
+         and is recommended only for debugging.
+
 comment "Asynchonous Memory Configuration"
 
 menu "EBIU_AMGCTL Global Control"
index 2fc899c..0edc402 100644 (file)
@@ -82,7 +82,11 @@ core-y   += arch/$(ARCH)/mach-$(MACHINE)/
 core-y   += arch/$(ARCH)/mach-$(MACHINE)/boards/
 endif
 
-core-y  += arch/$(ARCH)/kernel/cplb-nompu/
+ifeq ($(CONFIG_MPU),y)
+core-y += arch/$(ARCH)/kernel/cplb-mpu/
+else
+core-y += arch/$(ARCH)/kernel/cplb-nompu/
+endif
 
 libs-y   += arch/$(ARCH)/lib/
 
diff --git a/arch/blackfin/kernel/cplb-mpu/Makefile b/arch/blackfin/kernel/cplb-mpu/Makefile
new file mode 100644 (file)
index 0000000..286b693
--- /dev/null
@@ -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 (file)
index 0000000..9eecfa4
--- /dev/null
@@ -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 (file)
index 0000000..bd07229
--- /dev/null
@@ -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 (file)
index 0000000..e2e2b50
--- /dev/null
@@ -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 (file)
index 0000000..c426a22
--- /dev/null
@@ -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
index a03c2df..1a942a7 100644 (file)
@@ -238,7 +238,12 @@ void __init setup_arch(char **cmdline_p)
        memory_end = _ramend - DMA_UNCACHED_REGION;
 
        _ramstart = (unsigned long)__bss_stop;
+#ifdef CONFIG_MPU
+       /* Round up to multiple of 4MB.  */
+       memory_start = (_ramstart + 0x3fffff) & ~0x3fffff;
+#else
        memory_start = PAGE_ALIGN(_ramstart);
+#endif
 
 #if defined(CONFIG_MTD_UCLINUX)
        /* generic memory mapped MTD driver */
@@ -307,6 +312,11 @@ void __init setup_arch(char **cmdline_p)
        printk(KERN_NOTICE "Warning: limiting memory to %liMB due to hardware anomaly 05000263\n", memory_end >> 20);
 #endif                         /* ANOMALY_05000263 */
 
+#ifdef CONFIG_MPU
+       page_mask_nelts = ((_ramend >> PAGE_SHIFT) + 31) / 32;
+       page_mask_order = get_order(3 * page_mask_nelts * sizeof(long));
+#endif
+
 #if !defined(CONFIG_MTD_UCLINUX)
        memory_end -= SIZE_4K; /*In case there is no valid CPLB behind memory_end make sure we don't get to close*/
 #endif
index 58f7ad6..c2e81a1 100644 (file)
@@ -95,6 +95,9 @@ ENTRY(_ex_workaround_261)
        R6 = 0x26;      /* Data CPLB Miss */
        cc = R6 == R7;
        if cc jump _ex_dcplb_miss (BP);
+       R6 = 0x23;      /* Data CPLB Miss */
+       cc = R6 == R7;
+       if cc jump _ex_dcplb_viol (BP);
        /* Handle 0x23 Data CPLB Protection Violation
         * and Data CPLB Multiple Hits - Linux Trap Zero
         */
@@ -102,17 +105,33 @@ ENTRY(_ex_workaround_261)
 ENDPROC(_ex_workaround_261)
 
 #else
+#ifdef CONFIG_MPU
+#define _ex_dviol _ex_dcplb_viol
+#else
 #define _ex_dviol _ex_trap_c
+#endif
 #define _ex_dmiss _ex_dcplb_miss
 #define _ex_dmult _ex_trap_c
 #endif
 
+
+ENTRY(_ex_dcplb_viol)
 ENTRY(_ex_dcplb_miss)
 ENTRY(_ex_icplb_miss)
        (R7:6,P5:4) = [sp++];
        ASTAT = [sp++];
        SAVE_ALL_SYS
+#ifdef CONFIG_MPU
+       R0 = SEQSTAT;
+       R1 = SP;
+       sp += -12;
+       call _cplb_hdr;
+       sp += 12;
+       CC = R0 == 0;
+       IF !CC JUMP _handle_bad_cplb;
+#else
        call __cplb_hdr;
+#endif
        DEBUG_START_HWTRACE(p5, r7)
        RESTORE_ALL_SYS
        SP = EX_SCRATCH_REG;
index e97ea8f..9f007ca 100644 (file)
@@ -184,13 +184,15 @@ static __init void free_init_pages(const char *what, unsigned long begin, unsign
 #ifdef CONFIG_BLK_DEV_INITRD
 void __init free_initrd_mem(unsigned long start, unsigned long end)
 {
+#ifndef CONFIG_MPU
        free_init_pages("initrd memory", start, end);
+#endif
 }
 #endif
 
 void __init free_initmem(void)
 {
-#ifdef CONFIG_RAMKERNEL
+#if defined CONFIG_RAMKERNEL && !defined CONFIG_MPU
        free_init_pages("unused kernel memory",
                        (unsigned long)(&__init_begin),
                        (unsigned long)(&__init_end));
diff --git a/include/asm-blackfin/cplb-mpu.h b/include/asm-blackfin/cplb-mpu.h
new file mode 100644 (file)
index 0000000..75c67b9
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * File:         include/asm-blackfin/cplbinit.h
+ * Based on:
+ * Author:
+ *
+ * Created:
+ * Description:
+ *
+ * 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
+ */
+#ifndef __ASM_BFIN_CPLB_MPU_H
+#define __ASM_BFIN_CPLB_MPU_H
+
+struct cplb_entry {
+       unsigned long data, addr;
+};
+
+struct mem_region {
+       unsigned long start, end;
+       unsigned long dcplb_data;
+       unsigned long icplb_data;
+};
+
+extern struct cplb_entry dcplb_tbl[MAX_CPLBS];
+extern struct cplb_entry icplb_tbl[MAX_CPLBS];
+extern int first_switched_icplb;
+extern int first_mask_dcplb;
+extern int first_switched_dcplb;
+
+extern int nr_dcplb_miss, nr_icplb_miss, nr_icplb_supv_miss, nr_dcplb_prot;
+extern int nr_cplb_flush;
+
+extern int page_mask_order;
+extern int page_mask_nelts;
+
+extern unsigned long *current_rwx_mask;
+
+extern void flush_switched_cplbs(void);
+extern void set_mask_dcplbs(unsigned long *);
+
+extern void __noreturn panic_cplb_error(int seqstat, struct pt_regs *);
+
+#endif /* __ASM_BFIN_CPLB_MPU_H */
index 06828d7..654375c 100644 (file)
 #define SIZE_1M 0x00100000      /* 1M */
 #define SIZE_4M 0x00400000      /* 4M */
 
+#ifdef CONFIG_MPU
+#define MAX_CPLBS 16
+#else
 #define MAX_CPLBS (16 * 2)
+#endif
 
 #define ASYNC_MEMORY_CPLB_COVERAGE     ((ASYNC_BANK0_SIZE + ASYNC_BANK1_SIZE + \
                                 ASYNC_BANK2_SIZE + ASYNC_BANK3_SIZE) / SIZE_4M)
index c4d0596..0eb1c1b 100644 (file)
 #include <asm/blackfin.h>
 #include <asm/cplb.h>
 
+#ifdef CONFIG_MPU
+
+#include <asm/cplb-mpu.h>
+
+#else
+
 #define INITIAL_T 0x1
 #define SWITCH_T  0x2
 #define I_CPLB    0x4
@@ -79,6 +85,8 @@ extern u_long ipdt_swapcount_table[];
 extern u_long dpdt_swapcount_table[];
 #endif
 
+#endif /* CONFIG_MPU */
+
 extern unsigned long reserved_mem_dcache_on;
 extern unsigned long reserved_mem_icache_on;
 
index 11d52f1..757e439 100644 (file)
@@ -24,7 +24,9 @@ typedef struct {
        unsigned long   exec_fdpic_loadmap;
        unsigned long   interp_fdpic_loadmap;
 #endif
-
+#ifdef CONFIG_MPU
+       unsigned long *page_rwx_mask;
+#endif
 } mm_context_t;
 
 #endif
index c5c71a6..b5eb675 100644 (file)
 #ifndef __BLACKFIN_MMU_CONTEXT_H__
 #define __BLACKFIN_MMU_CONTEXT_H__
 
+#include <linux/gfp.h>
+#include <linux/sched.h>
 #include <asm/setup.h>
 #include <asm/page.h>
 #include <asm/pgalloc.h>
+#include <asm/cplbinit.h>
 
 extern void *current_l1_stack_save;
 extern int nr_l1stack_tasks;
@@ -50,6 +53,12 @@ static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
 static inline int
 init_new_context(struct task_struct *tsk, struct mm_struct *mm)
 {
+#ifdef CONFIG_MPU
+       unsigned long p = __get_free_pages(GFP_KERNEL, page_mask_order);
+       mm->context.page_rwx_mask = (unsigned long *)p;
+       memset(mm->context.page_rwx_mask, 0,
+              page_mask_nelts * 3 * sizeof(long));
+#endif
        return 0;
 }
 
@@ -73,6 +82,11 @@ static inline void destroy_context(struct mm_struct *mm)
                sram_free(tmp->addr);
                kfree(tmp);
        }
+#ifdef CONFIG_MPU
+       if (current_rwx_mask == mm->context.page_rwx_mask)
+               current_rwx_mask = NULL;
+       free_pages((unsigned long)mm->context.page_rwx_mask, page_mask_order);
+#endif
 }
 
 static inline unsigned long
@@ -106,9 +120,21 @@ activate_l1stack(struct mm_struct *mm, unsigned long sp_base)
 
 #define deactivate_mm(tsk,mm)  do { } while (0)
 
-static inline void activate_mm(struct mm_struct *prev_mm,
-                              struct mm_struct *next_mm)
+#define activate_mm(prev, next) switch_mm(prev, next, NULL)
+
+static inline void switch_mm(struct mm_struct *prev_mm, struct mm_struct *next_mm,
+                            struct task_struct *tsk)
 {
+       if (prev_mm == next_mm)
+               return;
+#ifdef CONFIG_MPU
+       if (prev_mm->context.page_rwx_mask == current_rwx_mask) {
+               flush_switched_cplbs();
+               set_mask_dcplbs(next_mm->context.page_rwx_mask);
+       }
+#endif
+
+       /* L1 stack switching.  */
        if (!next_mm->context.l1_stack_save)
                return;
        if (next_mm->context.l1_stack_save == current_l1_stack_save)
@@ -120,10 +146,36 @@ static inline void activate_mm(struct mm_struct *prev_mm,
        memcpy(l1_stack_base, current_l1_stack_save, l1_stack_len);
 }
 
-static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
-                            struct task_struct *tsk)
+#ifdef CONFIG_MPU
+static inline void protect_page(struct mm_struct *mm, unsigned long addr,
+                               unsigned long flags)
+{
+       unsigned long *mask = mm->context.page_rwx_mask;
+       unsigned long page = addr >> 12;
+       unsigned long idx = page >> 5;
+       unsigned long bit = 1 << (page & 31);
+
+       if (flags & VM_MAYREAD)
+               mask[idx] |= bit;
+       else
+               mask[idx] &= ~bit;
+       mask += page_mask_nelts;
+       if (flags & VM_MAYWRITE)
+               mask[idx] |= bit;
+       else
+               mask[idx] &= ~bit;
+       mask += page_mask_nelts;
+       if (flags & VM_MAYEXEC)
+               mask[idx] |= bit;
+       else
+               mask[idx] &= ~bit;
+}
+
+static inline void update_protections(struct mm_struct *mm)
 {
-       activate_mm(prev, next);
+       flush_switched_cplbs();
+       set_mask_dcplbs(mm->context.page_rwx_mask);
 }
+#endif
 
 #endif