[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>

diff --git a/arch/blackfin/Kconfig b/arch/blackfin/Kconfig
index ce521a9..6b96d7d 100644
--- a/arch/blackfin/Kconfig
+++ b/arch/blackfin/Kconfig
@@ -765,6 +765,15 @@
 	  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"
diff --git a/arch/blackfin/Makefile b/arch/blackfin/Makefile
index 2fc899c..0edc402 100644
--- a/arch/blackfin/Makefile
+++ b/arch/blackfin/Makefile
@@ -82,7 +82,11 @@
 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
index 0000000..286b693
--- /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 0000000..9eecfa4
--- /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 0000000..bd07229
--- /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 0000000..e2e2b50
--- /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 0000000..c426a22
--- /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
diff --git a/arch/blackfin/kernel/setup.c b/arch/blackfin/kernel/setup.c
index a03c2df..1a942a7 100644
--- a/arch/blackfin/kernel/setup.c
+++ b/arch/blackfin/kernel/setup.c
@@ -238,7 +238,12 @@
 	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 @@
 	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
diff --git a/arch/blackfin/mach-common/entry.S b/arch/blackfin/mach-common/entry.S
index 58f7ad6..c2e81a1 100644
--- a/arch/blackfin/mach-common/entry.S
+++ b/arch/blackfin/mach-common/entry.S
@@ -95,6 +95,9 @@
 	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 @@
 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;
diff --git a/arch/blackfin/mm/init.c b/arch/blackfin/mm/init.c
index e97ea8f..9f007ca 100644
--- a/arch/blackfin/mm/init.c
+++ b/arch/blackfin/mm/init.c
@@ -184,13 +184,15 @@
 #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
index 0000000..75c67b9
--- /dev/null
+++ b/include/asm-blackfin/cplb-mpu.h
@@ -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 */
diff --git a/include/asm-blackfin/cplb.h b/include/asm-blackfin/cplb.h
index 06828d7..654375c 100644
--- a/include/asm-blackfin/cplb.h
+++ b/include/asm-blackfin/cplb.h
@@ -65,7 +65,11 @@
 #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)
diff --git a/include/asm-blackfin/cplbinit.h b/include/asm-blackfin/cplbinit.h
index c4d0596..0eb1c1b 100644
--- a/include/asm-blackfin/cplbinit.h
+++ b/include/asm-blackfin/cplbinit.h
@@ -33,6 +33,12 @@
 #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 dpdt_swapcount_table[];
 #endif
 
+#endif /* CONFIG_MPU */
+
 extern unsigned long reserved_mem_dcache_on;
 extern unsigned long reserved_mem_icache_on;
 
diff --git a/include/asm-blackfin/mmu.h b/include/asm-blackfin/mmu.h
index 11d52f1..757e439 100644
--- a/include/asm-blackfin/mmu.h
+++ b/include/asm-blackfin/mmu.h
@@ -24,7 +24,9 @@
 	unsigned long	exec_fdpic_loadmap;
 	unsigned long	interp_fdpic_loadmap;
 #endif
-
+#ifdef CONFIG_MPU
+	unsigned long *page_rwx_mask;
+#endif
 } mm_context_t;
 
 #endif
diff --git a/include/asm-blackfin/mmu_context.h b/include/asm-blackfin/mmu_context.h
index c5c71a6..b5eb675 100644
--- a/include/asm-blackfin/mmu_context.h
+++ b/include/asm-blackfin/mmu_context.h
@@ -30,9 +30,12 @@
 #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 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 @@
 		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 @@
 
 #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 @@
 	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)
 {
-	activate_mm(prev, next);
+	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)
+{
+	flush_switched_cplbs();
+	set_mask_dcplbs(mm->context.page_rwx_mask);
+}
+#endif
+
 #endif