aboutsummaryrefslogtreecommitdiff
path: root/arch/sh/mm
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /arch/sh/mm
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'arch/sh/mm')
-rw-r--r--arch/sh/mm/Makefile25
-rw-r--r--arch/sh/mm/cache-sh2.c50
-rw-r--r--arch/sh/mm/cache-sh3.c100
-rw-r--r--arch/sh/mm/cache-sh4.c362
-rw-r--r--arch/sh/mm/cache-sh7705.c206
-rw-r--r--arch/sh/mm/clear_page.S295
-rw-r--r--arch/sh/mm/consistent.c85
-rw-r--r--arch/sh/mm/copy_page.S397
-rw-r--r--arch/sh/mm/extable.c22
-rw-r--r--arch/sh/mm/fault-nommu.c82
-rw-r--r--arch/sh/mm/fault.c374
-rw-r--r--arch/sh/mm/hugetlbpage.c264
-rw-r--r--arch/sh/mm/init.c313
-rw-r--r--arch/sh/mm/ioremap.c163
-rw-r--r--arch/sh/mm/pg-dma.c97
-rw-r--r--arch/sh/mm/pg-nommu.c36
-rw-r--r--arch/sh/mm/pg-sh4.c122
-rw-r--r--arch/sh/mm/pg-sh7705.c137
-rw-r--r--arch/sh/mm/tlb-nommu.c58
-rw-r--r--arch/sh/mm/tlb-sh3.c92
-rw-r--r--arch/sh/mm/tlb-sh4.c96
21 files changed, 3376 insertions, 0 deletions
diff --git a/arch/sh/mm/Makefile b/arch/sh/mm/Makefile
new file mode 100644
index 00000000000..9489a142464
--- /dev/null
+++ b/arch/sh/mm/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for the Linux SuperH-specific parts of the memory manager.
+#
+
+obj-y := init.o extable.o consistent.o
+
+obj-$(CONFIG_CPU_SH2) += cache-sh2.o
+obj-$(CONFIG_CPU_SH3) += cache-sh3.o
+obj-$(CONFIG_CPU_SH4) += cache-sh4.o pg-sh4.o
+
+obj-$(CONFIG_DMA_PAGE_OPS) += pg-dma.o
+obj-$(CONFIG_HUGETLB_PAGE) += hugetlbpage.o
+
+mmu-y := fault-nommu.o tlb-nommu.o pg-nommu.o
+mmu-$(CONFIG_MMU) := fault.o clear_page.o copy_page.o
+
+obj-y += $(mmu-y)
+
+ifdef CONFIG_MMU
+obj-$(CONFIG_CPU_SH3) += tlb-sh3.o
+obj-$(CONFIG_CPU_SH4) += tlb-sh4.o ioremap.o
+obj-$(CONFIG_SH7705_CACHE_32KB) += pg-sh7705.o
+endif
+
+obj-$(CONFIG_SH7705_CACHE_32KB) += cache-sh7705.o
diff --git a/arch/sh/mm/cache-sh2.c b/arch/sh/mm/cache-sh2.c
new file mode 100644
index 00000000000..2689cb24ea2
--- /dev/null
+++ b/arch/sh/mm/cache-sh2.c
@@ -0,0 +1,50 @@
+/*
+ * arch/sh/mm/cache-sh2.c
+ *
+ * Copyright (C) 2002 Paul Mundt
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+#include <linux/init.h>
+#include <linux/mm.h>
+
+#include <asm/cache.h>
+#include <asm/addrspace.h>
+#include <asm/processor.h>
+#include <asm/cacheflush.h>
+#include <asm/io.h>
+
+/*
+ * Calculate the OC address and set the way bit on the SH-2.
+ *
+ * We must have already jump_to_P2()'ed prior to calling this
+ * function, since we rely on CCR manipulation to do the
+ * Right Thing(tm).
+ */
+unsigned long __get_oc_addr(unsigned long set, unsigned long way)
+{
+ unsigned long ccr;
+
+ /*
+ * On SH-2 the way bit isn't tracked in the address field
+ * if we're doing address array access .. instead, we need
+ * to manually switch out the way in the CCR.
+ */
+ ccr = ctrl_inl(CCR);
+ ccr &= ~0x00c0;
+ ccr |= way << cpu_data->dcache.way_shift;
+
+ /*
+ * Despite the number of sets being halved, we end up losing
+ * the first 2 ways to OCRAM instead of the last 2 (if we're
+ * 4-way). As a result, forcibly setting the W1 bit handily
+ * bumps us up 2 ways.
+ */
+ if (ccr & CCR_CACHE_ORA)
+ ccr |= 1 << (cpu_data->dcache.way_shift + 1);
+
+ ctrl_outl(ccr, CCR);
+
+ return CACHE_OC_ADDRESS_ARRAY | (set << cpu_data->dcache.entry_shift);
+}
+
diff --git a/arch/sh/mm/cache-sh3.c b/arch/sh/mm/cache-sh3.c
new file mode 100644
index 00000000000..838731fc608
--- /dev/null
+++ b/arch/sh/mm/cache-sh3.c
@@ -0,0 +1,100 @@
+/*
+ * arch/sh/mm/cache-sh3.c
+ *
+ * Copyright (C) 1999, 2000 Niibe Yutaka
+ * Copyright (C) 2002 Paul Mundt
+ *
+ * Released under the terms of the GNU GPL v2.0.
+ */
+
+#include <linux/init.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/threads.h>
+#include <asm/addrspace.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/processor.h>
+#include <asm/cache.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/pgalloc.h>
+#include <asm/mmu_context.h>
+#include <asm/cacheflush.h>
+
+/*
+ * Write back the dirty D-caches, but not invalidate them.
+ *
+ * Is this really worth it, or should we just alias this routine
+ * to __flush_purge_region too?
+ *
+ * START: Virtual Address (U0, P1, or P3)
+ * SIZE: Size of the region.
+ */
+
+void __flush_wback_region(void *start, int size)
+{
+ unsigned long v, j;
+ unsigned long begin, end;
+ unsigned long flags;
+
+ begin = (unsigned long)start & ~(L1_CACHE_BYTES-1);
+ end = ((unsigned long)start + size + L1_CACHE_BYTES-1)
+ & ~(L1_CACHE_BYTES-1);
+
+ for (v = begin; v < end; v+=L1_CACHE_BYTES) {
+ unsigned long addrstart = CACHE_OC_ADDRESS_ARRAY;
+ for (j = 0; j < cpu_data->dcache.ways; j++) {
+ unsigned long data, addr, p;
+
+ p = __pa(v);
+ addr = addrstart | (v & cpu_data->dcache.entry_mask);
+ local_irq_save(flags);
+ data = ctrl_inl(addr);
+
+ if ((data & CACHE_PHYSADDR_MASK) ==
+ (p & CACHE_PHYSADDR_MASK)) {
+ data &= ~SH_CACHE_UPDATED;
+ ctrl_outl(data, addr);
+ local_irq_restore(flags);
+ break;
+ }
+ local_irq_restore(flags);
+ addrstart += cpu_data->dcache.way_incr;
+ }
+ }
+}
+
+/*
+ * Write back the dirty D-caches and invalidate them.
+ *
+ * START: Virtual Address (U0, P1, or P3)
+ * SIZE: Size of the region.
+ */
+void __flush_purge_region(void *start, int size)
+{
+ unsigned long v;
+ unsigned long begin, end;
+
+ begin = (unsigned long)start & ~(L1_CACHE_BYTES-1);
+ end = ((unsigned long)start + size + L1_CACHE_BYTES-1)
+ & ~(L1_CACHE_BYTES-1);
+
+ for (v = begin; v < end; v+=L1_CACHE_BYTES) {
+ unsigned long data, addr;
+
+ data = (v & 0xfffffc00); /* _Virtual_ address, ~U, ~V */
+ addr = CACHE_OC_ADDRESS_ARRAY |
+ (v & cpu_data->dcache.entry_mask) | SH_CACHE_ASSOC;
+ ctrl_outl(data, addr);
+ }
+}
+
+/*
+ * No write back please
+ *
+ * Except I don't think there's any way to avoid the writeback. So we
+ * just alias it to __flush_purge_region(). dwmw2.
+ */
+void __flush_invalidate_region(void *start, int size)
+ __attribute__((alias("__flush_purge_region")));
diff --git a/arch/sh/mm/cache-sh4.c b/arch/sh/mm/cache-sh4.c
new file mode 100644
index 00000000000..ab833adf28c
--- /dev/null
+++ b/arch/sh/mm/cache-sh4.c
@@ -0,0 +1,362 @@
+/*
+ * arch/sh/mm/cache-sh4.c
+ *
+ * Copyright (C) 1999, 2000, 2002 Niibe Yutaka
+ * Copyright (C) 2001, 2002, 2003, 2004 Paul Mundt
+ * Copyright (C) 2003 Richard Curnow
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/threads.h>
+#include <asm/addrspace.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/processor.h>
+#include <asm/cache.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/pgalloc.h>
+#include <asm/mmu_context.h>
+#include <asm/cacheflush.h>
+
+extern void __flush_cache_4096_all(unsigned long start);
+static void __flush_cache_4096_all_ex(unsigned long start);
+extern void __flush_dcache_all(void);
+static void __flush_dcache_all_ex(void);
+
+/*
+ * SH-4 has virtually indexed and physically tagged cache.
+ */
+
+struct semaphore p3map_sem[4];
+
+void __init p3_cache_init(void)
+{
+ if (remap_area_pages(P3SEG, 0, PAGE_SIZE*4, _PAGE_CACHABLE))
+ panic("%s failed.", __FUNCTION__);
+
+ sema_init (&p3map_sem[0], 1);
+ sema_init (&p3map_sem[1], 1);
+ sema_init (&p3map_sem[2], 1);
+ sema_init (&p3map_sem[3], 1);
+}
+
+/*
+ * Write back the dirty D-caches, but not invalidate them.
+ *
+ * START: Virtual Address (U0, P1, or P3)
+ * SIZE: Size of the region.
+ */
+void __flush_wback_region(void *start, int size)
+{
+ unsigned long v;
+ unsigned long begin, end;
+
+ begin = (unsigned long)start & ~(L1_CACHE_BYTES-1);
+ end = ((unsigned long)start + size + L1_CACHE_BYTES-1)
+ & ~(L1_CACHE_BYTES-1);
+ for (v = begin; v < end; v+=L1_CACHE_BYTES) {
+ asm volatile("ocbwb %0"
+ : /* no output */
+ : "m" (__m(v)));
+ }
+}
+
+/*
+ * Write back the dirty D-caches and invalidate them.
+ *
+ * START: Virtual Address (U0, P1, or P3)
+ * SIZE: Size of the region.
+ */
+void __flush_purge_region(void *start, int size)
+{
+ unsigned long v;
+ unsigned long begin, end;
+
+ begin = (unsigned long)start & ~(L1_CACHE_BYTES-1);
+ end = ((unsigned long)start + size + L1_CACHE_BYTES-1)
+ & ~(L1_CACHE_BYTES-1);
+ for (v = begin; v < end; v+=L1_CACHE_BYTES) {
+ asm volatile("ocbp %0"
+ : /* no output */
+ : "m" (__m(v)));
+ }
+}
+
+
+/*
+ * No write back please
+ */
+void __flush_invalidate_region(void *start, int size)
+{
+ unsigned long v;
+ unsigned long begin, end;
+
+ begin = (unsigned long)start & ~(L1_CACHE_BYTES-1);
+ end = ((unsigned long)start + size + L1_CACHE_BYTES-1)
+ & ~(L1_CACHE_BYTES-1);
+ for (v = begin; v < end; v+=L1_CACHE_BYTES) {
+ asm volatile("ocbi %0"
+ : /* no output */
+ : "m" (__m(v)));
+ }
+}
+
+static void __flush_dcache_all_ex(void)
+{
+ unsigned long addr, end_addr, entry_offset;
+
+ end_addr = CACHE_OC_ADDRESS_ARRAY + (cpu_data->dcache.sets << cpu_data->dcache.entry_shift) * cpu_data->dcache.ways;
+ entry_offset = 1 << cpu_data->dcache.entry_shift;
+ for (addr = CACHE_OC_ADDRESS_ARRAY; addr < end_addr; addr += entry_offset) {
+ ctrl_outl(0, addr);
+ }
+}
+
+static void __flush_cache_4096_all_ex(unsigned long start)
+{
+ unsigned long addr, entry_offset;
+ int i;
+
+ entry_offset = 1 << cpu_data->dcache.entry_shift;
+ for (i = 0; i < cpu_data->dcache.ways; i++, start += cpu_data->dcache.way_incr) {
+ for (addr = CACHE_OC_ADDRESS_ARRAY + start;
+ addr < CACHE_OC_ADDRESS_ARRAY + 4096 + start;
+ addr += entry_offset) {
+ ctrl_outl(0, addr);
+ }
+ }
+}
+
+void flush_cache_4096_all(unsigned long start)
+{
+ if (cpu_data->dcache.ways == 1)
+ __flush_cache_4096_all(start);
+ else
+ __flush_cache_4096_all_ex(start);
+}
+
+/*
+ * Write back the range of D-cache, and purge the I-cache.
+ *
+ * Called from kernel/module.c:sys_init_module and routine for a.out format.
+ */
+void flush_icache_range(unsigned long start, unsigned long end)
+{
+ flush_cache_all();
+}
+
+/*
+ * Write back the D-cache and purge the I-cache for signal trampoline.
+ * .. which happens to be the same behavior as flush_icache_range().
+ * So, we simply flush out a line.
+ */
+void flush_cache_sigtramp(unsigned long addr)
+{
+ unsigned long v, index;
+ unsigned long flags;
+ int i;
+
+ v = addr & ~(L1_CACHE_BYTES-1);
+ asm volatile("ocbwb %0"
+ : /* no output */
+ : "m" (__m(v)));
+
+ index = CACHE_IC_ADDRESS_ARRAY | (v & cpu_data->icache.entry_mask);
+
+ local_irq_save(flags);
+ jump_to_P2();
+ for(i = 0; i < cpu_data->icache.ways; i++, index += cpu_data->icache.way_incr)
+ ctrl_outl(0, index); /* Clear out Valid-bit */
+ back_to_P1();
+ local_irq_restore(flags);
+}
+
+static inline void flush_cache_4096(unsigned long start,
+ unsigned long phys)
+{
+ unsigned long flags;
+ extern void __flush_cache_4096(unsigned long addr, unsigned long phys, unsigned long exec_offset);
+
+ /*
+ * SH7751, SH7751R, and ST40 have no restriction to handle cache.
+ * (While SH7750 must do that at P2 area.)
+ */
+ if ((cpu_data->flags & CPU_HAS_P2_FLUSH_BUG)
+ || start < CACHE_OC_ADDRESS_ARRAY) {
+ local_irq_save(flags);
+ __flush_cache_4096(start | SH_CACHE_ASSOC, P1SEGADDR(phys), 0x20000000);
+ local_irq_restore(flags);
+ } else {
+ __flush_cache_4096(start | SH_CACHE_ASSOC, P1SEGADDR(phys), 0);
+ }
+}
+
+/*
+ * Write back & invalidate the D-cache of the page.
+ * (To avoid "alias" issues)
+ */
+void flush_dcache_page(struct page *page)
+{
+ if (test_bit(PG_mapped, &page->flags)) {
+ unsigned long phys = PHYSADDR(page_address(page));
+
+ /* Loop all the D-cache */
+ flush_cache_4096(CACHE_OC_ADDRESS_ARRAY, phys);
+ flush_cache_4096(CACHE_OC_ADDRESS_ARRAY | 0x1000, phys);
+ flush_cache_4096(CACHE_OC_ADDRESS_ARRAY | 0x2000, phys);
+ flush_cache_4096(CACHE_OC_ADDRESS_ARRAY | 0x3000, phys);
+ }
+}
+
+static inline void flush_icache_all(void)
+{
+ unsigned long flags, ccr;
+
+ local_irq_save(flags);
+ jump_to_P2();
+
+ /* Flush I-cache */
+ ccr = ctrl_inl(CCR);
+ ccr |= CCR_CACHE_ICI;
+ ctrl_outl(ccr, CCR);
+
+ back_to_P1();
+ local_irq_restore(flags);
+}
+
+void flush_cache_all(void)
+{
+ if (cpu_data->dcache.ways == 1)
+ __flush_dcache_all();
+ else
+ __flush_dcache_all_ex();
+ flush_icache_all();
+}
+
+void flush_cache_mm(struct mm_struct *mm)
+{
+ /* Is there any good way? */
+ /* XXX: possibly call flush_cache_range for each vm area */
+ /*
+ * FIXME: Really, the optimal solution here would be able to flush out
+ * individual lines created by the specified context, but this isn't
+ * feasible for a number of architectures (such as MIPS, and some
+ * SPARC) .. is this possible for SuperH?
+ *
+ * In the meantime, we'll just flush all of the caches.. this
+ * seems to be the simplest way to avoid at least a few wasted
+ * cache flushes. -Lethal
+ */
+ flush_cache_all();
+}
+
+/*
+ * Write back and invalidate I/D-caches for the page.
+ *
+ * ADDR: Virtual Address (U0 address)
+ * PFN: Physical page number
+ */
+void flush_cache_page(struct vm_area_struct *vma, unsigned long address, unsigned long pfn)
+{
+ unsigned long phys = pfn << PAGE_SHIFT;
+
+ /* We only need to flush D-cache when we have alias */
+ if ((address^phys) & CACHE_ALIAS) {
+ /* Loop 4K of the D-cache */
+ flush_cache_4096(
+ CACHE_OC_ADDRESS_ARRAY | (address & CACHE_ALIAS),
+ phys);
+ /* Loop another 4K of the D-cache */
+ flush_cache_4096(
+ CACHE_OC_ADDRESS_ARRAY | (phys & CACHE_ALIAS),
+ phys);
+ }
+
+ if (vma->vm_flags & VM_EXEC)
+ /* Loop 4K (half) of the I-cache */
+ flush_cache_4096(
+ CACHE_IC_ADDRESS_ARRAY | (address & 0x1000),
+ phys);
+}
+
+/*
+ * Write back and invalidate D-caches.
+ *
+ * START, END: Virtual Address (U0 address)
+ *
+ * NOTE: We need to flush the _physical_ page entry.
+ * Flushing the cache lines for U0 only isn't enough.
+ * We need to flush for P1 too, which may contain aliases.
+ */
+void flush_cache_range(struct vm_area_struct *vma, unsigned long start,
+ unsigned long end)
+{
+ unsigned long p = start & PAGE_MASK;
+ pgd_t *dir;
+ pmd_t *pmd;
+ pte_t *pte;
+ pte_t entry;
+ unsigned long phys;
+ unsigned long d = 0;
+
+ dir = pgd_offset(vma->vm_mm, p);
+ pmd = pmd_offset(dir, p);
+
+ do {
+ if (pmd_none(*pmd) || pmd_bad(*pmd)) {
+ p &= ~((1 << PMD_SHIFT) -1);
+ p += (1 << PMD_SHIFT);
+ pmd++;
+ continue;
+ }
+ pte = pte_offset_kernel(pmd, p);
+ do {
+ entry = *pte;
+ if ((pte_val(entry) & _PAGE_PRESENT)) {
+ phys = pte_val(entry)&PTE_PHYS_MASK;
+ if ((p^phys) & CACHE_ALIAS) {
+ d |= 1 << ((p & CACHE_ALIAS)>>12);
+ d |= 1 << ((phys & CACHE_ALIAS)>>12);
+ if (d == 0x0f)
+ goto loop_exit;
+ }
+ }
+ pte++;
+ p += PAGE_SIZE;
+ } while (p < end && ((unsigned long)pte & ~PAGE_MASK));
+ pmd++;
+ } while (p < end);
+ loop_exit:
+ if (d & 1)
+ flush_cache_4096_all(0);
+ if (d & 2)
+ flush_cache_4096_all(0x1000);
+ if (d & 4)
+ flush_cache_4096_all(0x2000);
+ if (d & 8)
+ flush_cache_4096_all(0x3000);
+ if (vma->vm_flags & VM_EXEC)
+ flush_icache_all();
+}
+
+/*
+ * flush_icache_user_range
+ * @vma: VMA of the process
+ * @page: page
+ * @addr: U0 address
+ * @len: length of the range (< page size)
+ */
+void flush_icache_user_range(struct vm_area_struct *vma,
+ struct page *page, unsigned long addr, int len)
+{
+ flush_cache_page(vma, addr, page_to_pfn(page));
+}
+
diff --git a/arch/sh/mm/cache-sh7705.c b/arch/sh/mm/cache-sh7705.c
new file mode 100644
index 00000000000..ad8ed7d41e1
--- /dev/null
+++ b/arch/sh/mm/cache-sh7705.c
@@ -0,0 +1,206 @@
+/*
+ * arch/sh/mm/cache-sh7705.c
+ *
+ * Copyright (C) 1999, 2000 Niibe Yutaka
+ * Copyright (C) 2004 Alex Song
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/mman.h>
+#include <linux/mm.h>
+#include <linux/threads.h>
+#include <asm/addrspace.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/processor.h>
+#include <asm/cache.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/pgalloc.h>
+#include <asm/mmu_context.h>
+#include <asm/cacheflush.h>
+
+/* The 32KB cache on the SH7705 suffers from the same synonym problem
+ * as SH4 CPUs */
+
+#define __pte_offset(address) \
+ ((address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
+#define pte_offset(dir, address) ((pte_t *) pmd_page_kernel(*(dir)) + \
+ __pte_offset(address))
+
+static inline void cache_wback_all(void)
+{
+ unsigned long ways, waysize, addrstart;
+
+ ways = cpu_data->dcache.ways;
+ waysize = cpu_data->dcache.sets;
+ waysize <<= cpu_data->dcache.entry_shift;
+
+ addrstart = CACHE_OC_ADDRESS_ARRAY;
+
+ do {
+ unsigned long addr;
+
+ for (addr = addrstart;
+ addr < addrstart + waysize;
+ addr += cpu_data->dcache.linesz) {
+ unsigned long data;
+ int v = SH_CACHE_UPDATED | SH_CACHE_VALID;
+
+ data = ctrl_inl(addr);
+
+ if ((data & v) == v)
+ ctrl_outl(data & ~v, addr);
+
+ }
+
+ addrstart += cpu_data->dcache.way_incr;
+ } while (--ways);
+}
+
+/*
+ * Write back the range of D-cache, and purge the I-cache.
+ *
+ * Called from kernel/module.c:sys_init_module and routine for a.out format.
+ */
+void flush_icache_range(unsigned long start, unsigned long end)
+{
+ __flush_wback_region((void *)start, end - start);
+}
+
+
+/*
+ * Writeback&Invalidate the D-cache of the page
+ */
+static void __flush_dcache_page(unsigned long phys)
+{
+ unsigned long ways, waysize, addrstart;
+ unsigned long flags;
+
+ phys |= SH_CACHE_VALID;
+
+ /*
+ * Here, phys is the physical address of the page. We check all the
+ * tags in the cache for those with the same page number as this page
+ * (by masking off the lowest 2 bits of the 19-bit tag; these bits are
+ * derived from the offset within in the 4k page). Matching valid
+ * entries are invalidated.
+ *
+ * Since 2 bits of the cache index are derived from the virtual page
+ * number, knowing this would reduce the number of cache entries to be
+ * searched by a factor of 4. However this function exists to deal with
+ * potential cache aliasing, therefore the optimisation is probably not
+ * possible.
+ */
+ local_irq_save(flags);
+ jump_to_P2();
+
+ ways = cpu_data->dcache.ways;
+ waysize = cpu_data->dcache.sets;
+ waysize <<= cpu_data->dcache.entry_shift;
+
+ addrstart = CACHE_OC_ADDRESS_ARRAY;
+
+ do {
+ unsigned long addr;
+
+ for (addr = addrstart;
+ addr < addrstart + waysize;
+ addr += cpu_data->dcache.linesz) {
+ unsigned long data;
+
+ data = ctrl_inl(addr) & (0x1ffffC00 | SH_CACHE_VALID);
+ if (data == phys) {
+ data &= ~(SH_CACHE_VALID | SH_CACHE_UPDATED);
+ ctrl_outl(data, addr);
+ }
+ }
+
+ addrstart += cpu_data->dcache.way_incr;
+ } while (--ways);
+
+ back_to_P1();
+ local_irq_restore(flags);
+}
+
+
+/*
+ * Write back & invalidate the D-cache of the page.
+ * (To avoid "alias" issues)
+ */
+void flush_dcache_page(struct page *page)
+{
+ if (test_bit(PG_mapped, &page->flags))
+ __flush_dcache_page(PHYSADDR(page_address(page)));
+}
+
+void flush_cache_all(void)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ jump_to_P2();
+
+ cache_wback_all();
+ back_to_P1();
+ local_irq_restore(flags);
+}
+
+void flush_cache_mm(struct mm_struct *mm)
+{
+ /* Is there any good way? */
+ /* XXX: possibly call flush_cache_range for each vm area */
+ flush_cache_all();
+}
+
+/*
+ * Write back and invalidate D-caches.
+ *
+ * START, END: Virtual Address (U0 address)
+ *
+ * NOTE: We need to flush the _physical_ page entry.
+ * Flushing the cache lines for U0 only isn't enough.
+ * We need to flush for P1 too, which may contain aliases.
+ */
+void flush_cache_range(struct vm_area_struct *vma, unsigned long start,
+ unsigned long end)
+{
+
+ /*
+ * We could call flush_cache_page for the pages of these range,
+ * but it's not efficient (scan the caches all the time...).
+ *
+ * We can't use A-bit magic, as there's the case we don't have
+ * valid entry on TLB.
+ */
+ flush_cache_all();
+}
+
+/*
+ * Write back and invalidate I/D-caches for the page.
+ *
+ * ADDRESS: Virtual Address (U0 address)
+ */
+void flush_cache_page(struct vm_area_struct *vma, unsigned long address, unsigned long pfn)
+{
+ __flush_dcache_page(pfn << PAGE_SHIFT);
+}
+
+/*
+ * This is called when a page-cache page is about to be mapped into a
+ * user process' address space. It offers an opportunity for a
+ * port to ensure d-cache/i-cache coherency if necessary.
+ *
+ * Not entirely sure why this is necessary on SH3 with 32K cache but
+ * without it we get occasional "Memory fault" when loading a program.
+ */
+void flush_icache_page(struct vm_area_struct *vma, struct page *page)
+{
+ __flush_purge_region(page_address(page), PAGE_SIZE);
+}
+
diff --git a/arch/sh/mm/clear_page.S b/arch/sh/mm/clear_page.S
new file mode 100644
index 00000000000..ae58a61f0e6
--- /dev/null
+++ b/arch/sh/mm/clear_page.S
@@ -0,0 +1,295 @@
+/* $Id: clear_page.S,v 1.13 2003/08/25 17:03:10 lethal Exp $
+ *
+ * __clear_user_page, __clear_user, clear_page implementation of SuperH
+ *
+ * Copyright (C) 2001 Kaz Kojima
+ * Copyright (C) 2001, 2002 Niibe Yutaka
+ *
+ */
+#include <linux/config.h>
+#include <linux/linkage.h>
+
+/*
+ * clear_page_slow
+ * @to: P1 address
+ *
+ * void clear_page_slow(void *to)
+ */
+
+/*
+ * r0 --- scratch
+ * r4 --- to
+ * r5 --- to + 4096
+ */
+ENTRY(clear_page_slow)
+ mov r4,r5
+ mov.w .Llimit,r0
+ add r0,r5
+ mov #0,r0
+ !
+1:
+#if defined(CONFIG_CPU_SH3)
+ mov.l r0,@r4
+#elif defined(CONFIG_CPU_SH4)
+ movca.l r0,@r4
+ mov r4,r1
+#endif
+ add #32,r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+#if defined(CONFIG_CPU_SH4)
+ ocbwb @r1
+#endif
+ cmp/eq r5,r4
+ bf/s 1b
+ add #28,r4
+ !
+ rts
+ nop
+.Llimit: .word (4096-28)
+
+ENTRY(__clear_user)
+ !
+ mov #0, r0
+ mov #0xe0, r1 ! 0xffffffe0
+ !
+ ! r4..(r4+31)&~32 -------- not aligned [ Area 0 ]
+ ! (r4+31)&~32..(r4+r5)&~32 -------- aligned [ Area 1 ]
+ ! (r4+r5)&~32..r4+r5 -------- not aligned [ Area 2 ]
+ !
+ ! Clear area 0
+ mov r4, r2
+ !
+ tst r1, r5 ! length < 32
+ bt .Larea2 ! skip to remainder
+ !
+ add #31, r2
+ and r1, r2
+ cmp/eq r4, r2
+ bt .Larea1
+ mov r2, r3
+ sub r4, r3
+ mov r3, r7
+ mov r4, r2
+ !
+.L0: dt r3
+0: mov.b r0, @r2
+ bf/s .L0
+ add #1, r2
+ !
+ sub r7, r5
+ mov r2, r4
+.Larea1:
+ mov r4, r3
+ add r5, r3
+ and r1, r3
+ cmp/hi r2, r3
+ bf .Larea2
+ !
+ ! Clear area 1
+#if defined(CONFIG_CPU_SH4)
+1: movca.l r0, @r2
+#else
+1: mov.l r0, @r2
+#endif
+ add #4, r2
+2: mov.l r0, @r2
+ add #4, r2
+3: mov.l r0, @r2
+ add #4, r2
+4: mov.l r0, @r2
+ add #4, r2
+5: mov.l r0, @r2
+ add #4, r2
+6: mov.l r0, @r2
+ add #4, r2
+7: mov.l r0, @r2
+ add #4, r2
+8: mov.l r0, @r2
+ add #4, r2
+ cmp/hi r2, r3
+ bt/s 1b
+ nop
+ !
+ ! Clear area 2
+.Larea2:
+ mov r4, r3
+ add r5, r3
+ cmp/hs r3, r2
+ bt/s .Ldone
+ sub r2, r3
+.L2: dt r3
+9: mov.b r0, @r2
+ bf/s .L2
+ add #1, r2
+ !
+.Ldone: rts
+ mov #0, r0 ! return 0 as normal return
+
+ ! return the number of bytes remained
+.Lbad_clear_user:
+ mov r4, r0
+ add r5, r0
+ rts
+ sub r2, r0
+
+.section __ex_table,"a"
+ .align 2
+ .long 0b, .Lbad_clear_user
+ .long 1b, .Lbad_clear_user
+ .long 2b, .Lbad_clear_user
+ .long 3b, .Lbad_clear_user
+ .long 4b, .Lbad_clear_user
+ .long 5b, .Lbad_clear_user
+ .long 6b, .Lbad_clear_user
+ .long 7b, .Lbad_clear_user
+ .long 8b, .Lbad_clear_user
+ .long 9b, .Lbad_clear_user
+.previous
+
+#if defined(CONFIG_CPU_SH4)
+/*
+ * __clear_user_page
+ * @to: P3 address (with same color)
+ * @orig_to: P1 address
+ *
+ * void __clear_user_page(void *to, void *orig_to)
+ */
+
+/*
+ * r0 --- scratch
+ * r4 --- to
+ * r5 --- orig_to
+ * r6 --- to + 4096
+ */
+ENTRY(__clear_user_page)
+ mov.w .L4096,r0
+ mov r4,r6
+ add r0,r6
+ mov #0,r0
+ !
+1: ocbi @r5
+ add #32,r5
+ movca.l r0,@r4
+ mov r4,r1
+ add #32,r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ mov.l r0,@-r4
+ add #28,r4
+ cmp/eq r6,r4
+ bf/s 1b
+ ocbwb @r1
+ !
+ rts
+ nop
+.L4096: .word 4096
+
+ENTRY(__flush_cache_4096)
+ mov.l 1f,r3
+ add r6,r3
+ mov r4,r0
+ mov #64,r2
+ shll r2
+ mov #64,r6
+ jmp @r3
+ mov #96,r7
+ .align 2
+1: .long 2f
+2:
+ .rept 32
+ mov.l r5,@r0
+ mov.l r5,@(32,r0)
+ mov.l r5,@(r0,r6)
+ mov.l r5,@(r0,r7)
+ add r2,r5
+ add r2,r0
+ .endr
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ rts
+ nop
+
+ENTRY(__flush_dcache_all)
+ mov.l 2f,r0
+ mov.l 3f,r4
+ and r0,r4 ! r4 = (unsigned long)&empty_zero_page[0] & ~0xffffc000
+ stc sr,r1 ! save SR
+ mov.l 4f,r2
+ or r1,r2
+ mov #32,r3
+ shll2 r3
+1:
+ ldc r2,sr ! set BL bit
+ movca.l r0,@r4
+ ocbi @r4
+ add #32,r4
+ movca.l r0,@r4
+ ocbi @r4
+ add #32,r4
+ movca.l r0,@r4
+ ocbi @r4
+ add #32,r4
+ movca.l r0,@r4
+ ocbi @r4
+ ldc r1,sr ! restore SR
+ dt r3
+ bf/s 1b
+ add #32,r4
+
+ rts
+ nop
+ .align 2
+2: .long 0xffffc000
+3: .long empty_zero_page
+4: .long 0x10000000 ! BL bit
+
+/* __flush_cache_4096_all(unsigned long addr) */
+ENTRY(__flush_cache_4096_all)
+ mov.l 2f,r0
+ mov.l 3f,r2
+ and r0,r2
+ or r2,r4 ! r4 = addr | (unsigned long)&empty_zero_page[0] & ~0x3fff
+ stc sr,r1 ! save SR
+ mov.l 4f,r2
+ or r1,r2
+ mov #32,r3
+1:
+ ldc r2,sr ! set BL bit
+ movca.l r0,@r4
+ ocbi @r4
+ add #32,r4
+ movca.l r0,@r4
+ ocbi @r4
+ add #32,r4
+ movca.l r0,@r4
+ ocbi @r4
+ add #32,r4
+ movca.l r0,@r4
+ ocbi @r4
+ ldc r1,sr ! restore SR
+ dt r3
+ bf/s 1b
+ add #32,r4
+
+ rts
+ nop
+ .align 2
+2: .long 0xffffc000
+3: .long empty_zero_page
+4: .long 0x10000000 ! BL bit
+#endif
diff --git a/arch/sh/mm/consistent.c b/arch/sh/mm/consistent.c
new file mode 100644
index 00000000000..1f7af0c73cf
--- /dev/null
+++ b/arch/sh/mm/consistent.c
@@ -0,0 +1,85 @@
+/*
+ * arch/sh/mm/consistent.c
+ *
+ * Copyright (C) 2004 Paul Mundt
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+#include <linux/mm.h>
+#include <linux/dma-mapping.h>
+#include <asm/io.h>
+
+void *consistent_alloc(int gfp, size_t size, dma_addr_t *handle)
+{