diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /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/Makefile | 25 | ||||
-rw-r--r-- | arch/sh/mm/cache-sh2.c | 50 | ||||
-rw-r--r-- | arch/sh/mm/cache-sh3.c | 100 | ||||
-rw-r--r-- | arch/sh/mm/cache-sh4.c | 362 | ||||
-rw-r--r-- | arch/sh/mm/cache-sh7705.c | 206 | ||||
-rw-r--r-- | arch/sh/mm/clear_page.S | 295 | ||||
-rw-r--r-- | arch/sh/mm/consistent.c | 85 | ||||
-rw-r--r-- | arch/sh/mm/copy_page.S | 397 | ||||
-rw-r--r-- | arch/sh/mm/extable.c | 22 | ||||
-rw-r--r-- | arch/sh/mm/fault-nommu.c | 82 | ||||
-rw-r--r-- | arch/sh/mm/fault.c | 374 | ||||
-rw-r--r-- | arch/sh/mm/hugetlbpage.c | 264 | ||||
-rw-r--r-- | arch/sh/mm/init.c | 313 | ||||
-rw-r--r-- | arch/sh/mm/ioremap.c | 163 | ||||
-rw-r--r-- | arch/sh/mm/pg-dma.c | 97 | ||||
-rw-r--r-- | arch/sh/mm/pg-nommu.c | 36 | ||||
-rw-r--r-- | arch/sh/mm/pg-sh4.c | 122 | ||||
-rw-r--r-- | arch/sh/mm/pg-sh7705.c | 137 | ||||
-rw-r--r-- | arch/sh/mm/tlb-nommu.c | 58 | ||||
-rw-r--r-- | arch/sh/mm/tlb-sh3.c | 92 | ||||
-rw-r--r-- | arch/sh/mm/tlb-sh4.c | 96 |
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) +{ |