* arch/sh/mm/cache-sh4.c
*
* Copyright (C) 1999, 2000, 2002 Niibe Yutaka
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 Paul Mundt
+ * Copyright (C) 2001 - 2006 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/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 maximum number of pages we support up to when doing ranged dcache
+ * flushing. Anything exceeding this will simply flush the dcache in its
+ * entirety.
+ */
+#define MAX_DCACHE_PAGES 64 /* XXX: Tune for ways */
+
static void __flush_dcache_segment_1way(unsigned long start,
unsigned long extent);
static void __flush_dcache_segment_2way(unsigned long start,
static inline void flush_cache_4096(unsigned long start,
unsigned long phys)
{
- unsigned long flags;
-
/*
* All types of SH-4 require PC to be in P2 to operate on the I-cache.
* Some types of SH-4 require PC to be in P2 to operate on the D-cache.
*/
- if ((cpu_data->flags & CPU_HAS_P2_FLUSH_BUG)
- || start < CACHE_OC_ADDRESS_ARRAY) {
+ if ((cpu_data->flags & CPU_HAS_P2_FLUSH_BUG) ||
+ (start < CACHE_OC_ADDRESS_ARRAY)) {
+ unsigned long flags;
+
local_irq_save(flags);
__flush_cache_4096(start | SH_CACHE_ASSOC,
P1SEGADDR(phys), 0x20000000);
wmb();
}
+/* TODO: Selective icache invalidation through IC address array.. */
static inline void flush_icache_all(void)
{
unsigned long flags, ccr;
flush_icache_all();
}
+static void __flush_cache_mm(struct mm_struct *mm, unsigned long start,
+ unsigned long end)
+{
+ unsigned long d = 0, p = start & PAGE_MASK;
+ unsigned long alias_mask = cpu_data->dcache.alias_mask;
+ unsigned long n_aliases = cpu_data->dcache.n_aliases;
+ unsigned long select_bit;
+ unsigned long all_aliases_mask;
+ unsigned long addr_offset;
+ pgd_t *dir;
+ pmd_t *pmd;
+ pud_t *pud;
+ pte_t *pte;
+ int i;
+
+ dir = pgd_offset(mm, p);
+ pud = pud_offset(dir, p);
+ pmd = pmd_offset(pud, p);
+ end = PAGE_ALIGN(end);
+
+ all_aliases_mask = (1 << n_aliases) - 1;
+
+ do {
+ if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd))) {
+ p &= PMD_MASK;
+ p += PMD_SIZE;
+ pmd++;
+
+ continue;
+ }
+
+ pte = pte_offset_kernel(pmd, p);
+
+ do {
+ unsigned long phys;
+ pte_t entry = *pte;
+
+ if (!(pte_val(entry) & _PAGE_PRESENT)) {
+ pte++;
+ p += PAGE_SIZE;
+ continue;
+ }
+
+ phys = pte_val(entry) & PTE_PHYS_MASK;
+
+ if ((p ^ phys) & alias_mask) {
+ d |= 1 << ((p & alias_mask) >> PAGE_SHIFT);
+ d |= 1 << ((phys & alias_mask) >> PAGE_SHIFT);
+
+ if (d == all_aliases_mask)
+ goto loop_exit;
+ }
+
+ pte++;
+ p += PAGE_SIZE;
+ } while (p < end && ((unsigned long)pte & ~PAGE_MASK));
+ pmd++;
+ } while (p < end);
+
+loop_exit:
+ addr_offset = 0;
+ select_bit = 1;
+
+ for (i = 0; i < n_aliases; i++) {
+ if (d & select_bit) {
+ (*__flush_dcache_segment_fn)(addr_offset, PAGE_SIZE);
+ wmb();
+ }
+
+ select_bit <<= 1;
+ addr_offset += PAGE_SIZE;
+ }
+}
+
+/*
+ * Note : (RPC) since the caches are physically tagged, the only point
+ * of flush_cache_mm for SH-4 is to get rid of aliases from the
+ * D-cache. The assumption elsewhere, e.g. flush_cache_range, is that
+ * lines can stay resident so long as the virtual address they were
+ * accessed with (hence cache set) is in accord with the physical
+ * address (i.e. tag). It's no different here. So I reckon we don't
+ * need to flush the I-cache, since aliases don't matter for that. We
+ * should try that.
+ *
+ * Caller takes mm->mmap_sem.
+ */
void flush_cache_mm(struct mm_struct *mm)
{
/*
- * Note : (RPC) since the caches are physically tagged, the only point
- * of flush_cache_mm for SH-4 is to get rid of aliases from the
- * D-cache. The assumption elsewhere, e.g. flush_cache_range, is that
- * lines can stay resident so long as the virtual address they were
- * accessed with (hence cache set) is in accord with the physical
- * address (i.e. tag). It's no different here. So I reckon we don't
- * need to flush the I-cache, since aliases don't matter for that. We
- * should try that.
+ * If cache is only 4k-per-way, there are never any 'aliases'. Since
+ * the cache is physically tagged, the data can just be left in there.
+ */
+ if (cpu_data->dcache.n_aliases == 0)
+ return;
+
+ /*
+ * Don't bother groveling around the dcache for the VMA ranges
+ * if there are too many PTEs to make it worthwhile.
*/
- flush_cache_all();
+ if (mm->nr_ptes >= MAX_DCACHE_PAGES)
+ flush_dcache_all();
+ else {
+ struct vm_area_struct *vma;
+
+ /*
+ * In this case there are reasonably sized ranges to flush,
+ * iterate through the VMA list and take care of any aliases.
+ */
+ for (vma = mm->mmap; vma; vma = vma->vm_next)
+ __flush_cache_mm(mm, vma->vm_start, vma->vm_end);
+ }
+
+ /* Only touch the icache if one of the VMAs has VM_EXEC set. */
+ if (mm->exec_vm)
+ flush_icache_all();
}
/*
* ADDR: Virtual Address (U0 address)
* PFN: Physical page number
*/
-void flush_cache_page(struct vm_area_struct *vma, unsigned long address, unsigned long pfn)
+void flush_cache_page(struct vm_area_struct *vma, unsigned long address,
+ unsigned long pfn)
{
unsigned long phys = pfn << PAGE_SHIFT;
unsigned int alias_mask;
void flush_cache_range(struct vm_area_struct *vma, unsigned long start,
unsigned long end)
{
- unsigned long d = 0, p = start & PAGE_MASK;
- unsigned long alias_mask = cpu_data->dcache.alias_mask;
- unsigned long n_aliases = cpu_data->dcache.n_aliases;
- unsigned long select_bit;
- unsigned long all_aliases_mask;
- unsigned long addr_offset;
- unsigned long phys;
- pgd_t *dir;
- pmd_t *pmd;
- pud_t *pud;
- pte_t *pte;
- pte_t entry;
- int i;
-
/*
* If cache is only 4k-per-way, there are never any 'aliases'. Since
* the cache is physically tagged, the data can just be left in there.
*/
- if (n_aliases == 0)
+ if (cpu_data->dcache.n_aliases == 0)
return;
- all_aliases_mask = (1 << n_aliases) - 1;
-
/*
* Don't bother with the lookup and alias check if we have a
* wide range to cover, just blow away the dcache in its
* entirety instead. -- PFM.
*/
- if (((end - start) >> PAGE_SHIFT) >= 64) {
+ if (((end - start) >> PAGE_SHIFT) >= MAX_DCACHE_PAGES)
flush_dcache_all();
-
- if (vma->vm_flags & VM_EXEC)
- flush_icache_all();
-
- return;
- }
-
- dir = pgd_offset(vma->vm_mm, p);
- pud = pud_offset(dir, p);
- pmd = pmd_offset(pud, p);
- end = PAGE_ALIGN(end);
-
- 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) & alias_mask) {
- d |= 1 << ((p & alias_mask) >> PAGE_SHIFT);
- d |= 1 << ((phys & alias_mask) >> PAGE_SHIFT);
-
- if (d == all_aliases_mask)
- goto loop_exit;
- }
- }
-
- pte++;
- p += PAGE_SIZE;
- } while (p < end && ((unsigned long)pte & ~PAGE_MASK));
- pmd++;
- } while (p < end);
-
-loop_exit:
- for (i = 0, select_bit = 0x1, addr_offset = 0x0; i < n_aliases;
- i++, select_bit <<= 1, addr_offset += PAGE_SIZE)
- if (d & select_bit) {
- (*__flush_dcache_segment_fn)(addr_offset, PAGE_SIZE);
- wmb();
- }
+ else
+ __flush_cache_mm(vma->vm_mm, start, end);
if (vma->vm_flags & VM_EXEC) {
/*
a3 += linesz;
} while (a0 < a0e);
}
-