/arch/sh/mm/cache-sh5.c
C | 621 lines | 318 code | 78 blank | 225 comment | 40 complexity | ebe06d0515011214765717fead9cf50d MD5 | raw file
Possible License(s): LGPL-2.0, AGPL-1.0, GPL-2.0
- /*
- * arch/sh/mm/cache-sh5.c
- *
- * Copyright (C) 2000, 2001 Paolo Alberelli
- * Copyright (C) 2002 Benedict Gaster
- * Copyright (C) 2003 Richard Curnow
- * Copyright (C) 2003 - 2008 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/init.h>
- #include <linux/mman.h>
- #include <linux/mm.h>
- #include <asm/tlb.h>
- #include <asm/processor.h>
- #include <asm/cache.h>
- #include <asm/pgalloc.h>
- #include <asm/uaccess.h>
- #include <asm/mmu_context.h>
- extern void __weak sh4__flush_region_init(void);
- /* Wired TLB entry for the D-cache */
- static unsigned long long dtlb_cache_slot;
- /*
- * The following group of functions deal with mapping and unmapping a
- * temporary page into a DTLB slot that has been set aside for exclusive
- * use.
- */
- static inline void
- sh64_setup_dtlb_cache_slot(unsigned long eaddr, unsigned long asid,
- unsigned long paddr)
- {
- local_irq_disable();
- sh64_setup_tlb_slot(dtlb_cache_slot, eaddr, asid, paddr);
- }
- static inline void sh64_teardown_dtlb_cache_slot(void)
- {
- sh64_teardown_tlb_slot(dtlb_cache_slot);
- local_irq_enable();
- }
- static inline void sh64_icache_inv_all(void)
- {
- unsigned long long addr, flag, data;
- unsigned long flags;
- addr = ICCR0;
- flag = ICCR0_ICI;
- data = 0;
- /* Make this a critical section for safety (probably not strictly necessary.) */
- local_irq_save(flags);
- /* Without %1 it gets unexplicably wrong */
- __asm__ __volatile__ (
- "getcfg %3, 0, %0\n\t"
- "or %0, %2, %0\n\t"
- "putcfg %3, 0, %0\n\t"
- "synci"
- : "=&r" (data)
- : "0" (data), "r" (flag), "r" (addr));
- local_irq_restore(flags);
- }
- static void sh64_icache_inv_kernel_range(unsigned long start, unsigned long end)
- {
- /* Invalidate range of addresses [start,end] from the I-cache, where
- * the addresses lie in the kernel superpage. */
- unsigned long long ullend, addr, aligned_start;
- aligned_start = (unsigned long long)(signed long long)(signed long) start;
- addr = L1_CACHE_ALIGN(aligned_start);
- ullend = (unsigned long long) (signed long long) (signed long) end;
- while (addr <= ullend) {
- __asm__ __volatile__ ("icbi %0, 0" : : "r" (addr));
- addr += L1_CACHE_BYTES;
- }
- }
- static void sh64_icache_inv_user_page(struct vm_area_struct *vma, unsigned long eaddr)
- {
- /* If we get called, we know that vma->vm_flags contains VM_EXEC.
- Also, eaddr is page-aligned. */
- unsigned int cpu = smp_processor_id();
- unsigned long long addr, end_addr;
- unsigned long flags = 0;
- unsigned long running_asid, vma_asid;
- addr = eaddr;
- end_addr = addr + PAGE_SIZE;
- /* Check whether we can use the current ASID for the I-cache
- invalidation. For example, if we're called via
- access_process_vm->flush_cache_page->here, (e.g. when reading from
- /proc), 'running_asid' will be that of the reader, not of the
- victim.
- Also, note the risk that we might get pre-empted between the ASID
- compare and blocking IRQs, and before we regain control, the
- pid->ASID mapping changes. However, the whole cache will get
- invalidated when the mapping is renewed, so the worst that can
- happen is that the loop below ends up invalidating somebody else's
- cache entries.
- */
- running_asid = get_asid();
- vma_asid = cpu_asid(cpu, vma->vm_mm);
- if (running_asid != vma_asid) {
- local_irq_save(flags);
- switch_and_save_asid(vma_asid);
- }
- while (addr < end_addr) {
- /* Worth unrolling a little */
- __asm__ __volatile__("icbi %0, 0" : : "r" (addr));
- __asm__ __volatile__("icbi %0, 32" : : "r" (addr));
- __asm__ __volatile__("icbi %0, 64" : : "r" (addr));
- __asm__ __volatile__("icbi %0, 96" : : "r" (addr));
- addr += 128;
- }
- if (running_asid != vma_asid) {
- switch_and_save_asid(running_asid);
- local_irq_restore(flags);
- }
- }
- static void sh64_icache_inv_user_page_range(struct mm_struct *mm,
- unsigned long start, unsigned long end)
- {
- /* Used for invalidating big chunks of I-cache, i.e. assume the range
- is whole pages. If 'start' or 'end' is not page aligned, the code
- is conservative and invalidates to the ends of the enclosing pages.
- This is functionally OK, just a performance loss. */
- /* See the comments below in sh64_dcache_purge_user_range() regarding
- the choice of algorithm. However, for the I-cache option (2) isn't
- available because there are no physical tags so aliases can't be
- resolved. The icbi instruction has to be used through the user
- mapping. Because icbi is cheaper than ocbp on a cache hit, it
- would be cheaper to use the selective code for a large range than is
- possible with the D-cache. Just assume 64 for now as a working
- figure.
- */
- int n_pages;
- if (!mm)
- return;
- n_pages = ((end - start) >> PAGE_SHIFT);
- if (n_pages >= 64) {
- sh64_icache_inv_all();
- } else {
- unsigned long aligned_start;
- unsigned long eaddr;
- unsigned long after_last_page_start;
- unsigned long mm_asid, current_asid;
- unsigned long flags = 0;
- mm_asid = cpu_asid(smp_processor_id(), mm);
- current_asid = get_asid();
- if (mm_asid != current_asid) {
- /* Switch ASID and run the invalidate loop under cli */
- local_irq_save(flags);
- switch_and_save_asid(mm_asid);
- }
- aligned_start = start & PAGE_MASK;
- after_last_page_start = PAGE_SIZE + ((end - 1) & PAGE_MASK);
- while (aligned_start < after_last_page_start) {
- struct vm_area_struct *vma;
- unsigned long vma_end;
- vma = find_vma(mm, aligned_start);
- if (!vma || (aligned_start <= vma->vm_end)) {
- /* Avoid getting stuck in an error condition */
- aligned_start += PAGE_SIZE;
- continue;
- }
- vma_end = vma->vm_end;
- if (vma->vm_flags & VM_EXEC) {
- /* Executable */
- eaddr = aligned_start;
- while (eaddr < vma_end) {
- sh64_icache_inv_user_page(vma, eaddr);
- eaddr += PAGE_SIZE;
- }
- }
- aligned_start = vma->vm_end; /* Skip to start of next region */
- }
- if (mm_asid != current_asid) {
- switch_and_save_asid(current_asid);
- local_irq_restore(flags);
- }
- }
- }
- static void sh64_icache_inv_current_user_range(unsigned long start, unsigned long end)
- {
- /* The icbi instruction never raises ITLBMISS. i.e. if there's not a
- cache hit on the virtual tag the instruction ends there, without a
- TLB lookup. */
- unsigned long long aligned_start;
- unsigned long long ull_end;
- unsigned long long addr;
- ull_end = end;
- /* Just invalidate over the range using the natural addresses. TLB
- miss handling will be OK (TBC). Since it's for the current process,
- either we're already in the right ASID context, or the ASIDs have
- been recycled since we were last active in which case we might just
- invalidate another processes I-cache entries : no worries, just a
- performance drop for him. */
- aligned_start = L1_CACHE_ALIGN(start);
- addr = aligned_start;
- while (addr < ull_end) {
- __asm__ __volatile__ ("icbi %0, 0" : : "r" (addr));
- __asm__ __volatile__ ("nop");
- __asm__ __volatile__ ("nop");
- addr += L1_CACHE_BYTES;
- }
- }
- /* Buffer used as the target of alloco instructions to purge data from cache
- sets by natural eviction. -- RPC */
- #define DUMMY_ALLOCO_AREA_SIZE ((L1_CACHE_BYTES << 10) + (1024 * 4))
- static unsigned char dummy_alloco_area[DUMMY_ALLOCO_AREA_SIZE] __cacheline_aligned = { 0, };
- static void inline sh64_dcache_purge_sets(int sets_to_purge_base, int n_sets)
- {
- /* Purge all ways in a particular block of sets, specified by the base
- set number and number of sets. Can handle wrap-around, if that's
- needed. */
- int dummy_buffer_base_set;
- unsigned long long eaddr, eaddr0, eaddr1;
- int j;
- int set_offset;
-