162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * arch/xtensa/mm/cache.c 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 562306a36Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 662306a36Sopenharmony_ci * for more details. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Copyright (C) 2001-2006 Tensilica Inc. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * Chris Zankel <chris@zankel.net> 1162306a36Sopenharmony_ci * Joe Taylor 1262306a36Sopenharmony_ci * Marc Gauthier 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/init.h> 1762306a36Sopenharmony_ci#include <linux/signal.h> 1862306a36Sopenharmony_ci#include <linux/sched.h> 1962306a36Sopenharmony_ci#include <linux/kernel.h> 2062306a36Sopenharmony_ci#include <linux/errno.h> 2162306a36Sopenharmony_ci#include <linux/string.h> 2262306a36Sopenharmony_ci#include <linux/types.h> 2362306a36Sopenharmony_ci#include <linux/ptrace.h> 2462306a36Sopenharmony_ci#include <linux/memblock.h> 2562306a36Sopenharmony_ci#include <linux/swap.h> 2662306a36Sopenharmony_ci#include <linux/pagemap.h> 2762306a36Sopenharmony_ci#include <linux/pgtable.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include <asm/bootparam.h> 3062306a36Sopenharmony_ci#include <asm/mmu_context.h> 3162306a36Sopenharmony_ci#include <asm/tlb.h> 3262306a36Sopenharmony_ci#include <asm/tlbflush.h> 3362306a36Sopenharmony_ci#include <asm/page.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* 3662306a36Sopenharmony_ci * Note: 3762306a36Sopenharmony_ci * The kernel provides one architecture bit PG_arch_1 in the page flags that 3862306a36Sopenharmony_ci * can be used for cache coherency. 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * I$-D$ coherency. 4162306a36Sopenharmony_ci * 4262306a36Sopenharmony_ci * The Xtensa architecture doesn't keep the instruction cache coherent with 4362306a36Sopenharmony_ci * the data cache. We use the architecture bit to indicate if the caches 4462306a36Sopenharmony_ci * are coherent. The kernel clears this bit whenever a page is added to the 4562306a36Sopenharmony_ci * page cache. At that time, the caches might not be in sync. We, therefore, 4662306a36Sopenharmony_ci * define this flag as 'clean' if set. 4762306a36Sopenharmony_ci * 4862306a36Sopenharmony_ci * D-cache aliasing. 4962306a36Sopenharmony_ci * 5062306a36Sopenharmony_ci * With cache aliasing, we have to always flush the cache when pages are 5162306a36Sopenharmony_ci * unmapped (see tlb_start_vma(). So, we use this flag to indicate a dirty 5262306a36Sopenharmony_ci * page. 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * 5562306a36Sopenharmony_ci * 5662306a36Sopenharmony_ci */ 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#if (DCACHE_WAY_SIZE > PAGE_SIZE) 5962306a36Sopenharmony_cistatic inline void kmap_invalidate_coherent(struct page *page, 6062306a36Sopenharmony_ci unsigned long vaddr) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci if (!DCACHE_ALIAS_EQ(page_to_phys(page), vaddr)) { 6362306a36Sopenharmony_ci unsigned long kvaddr; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci if (!PageHighMem(page)) { 6662306a36Sopenharmony_ci kvaddr = (unsigned long)page_to_virt(page); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci __invalidate_dcache_page(kvaddr); 6962306a36Sopenharmony_ci } else { 7062306a36Sopenharmony_ci kvaddr = TLBTEMP_BASE_1 + 7162306a36Sopenharmony_ci (page_to_phys(page) & DCACHE_ALIAS_MASK); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci preempt_disable(); 7462306a36Sopenharmony_ci __invalidate_dcache_page_alias(kvaddr, 7562306a36Sopenharmony_ci page_to_phys(page)); 7662306a36Sopenharmony_ci preempt_enable(); 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci} 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic inline void *coherent_kvaddr(struct page *page, unsigned long base, 8262306a36Sopenharmony_ci unsigned long vaddr, unsigned long *paddr) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci *paddr = page_to_phys(page); 8562306a36Sopenharmony_ci return (void *)(base + (vaddr & DCACHE_ALIAS_MASK)); 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_civoid clear_user_highpage(struct page *page, unsigned long vaddr) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci unsigned long paddr; 9162306a36Sopenharmony_ci void *kvaddr = coherent_kvaddr(page, TLBTEMP_BASE_1, vaddr, &paddr); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci preempt_disable(); 9462306a36Sopenharmony_ci kmap_invalidate_coherent(page, vaddr); 9562306a36Sopenharmony_ci set_bit(PG_arch_1, &page->flags); 9662306a36Sopenharmony_ci clear_page_alias(kvaddr, paddr); 9762306a36Sopenharmony_ci preempt_enable(); 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ciEXPORT_SYMBOL(clear_user_highpage); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_civoid copy_user_highpage(struct page *dst, struct page *src, 10262306a36Sopenharmony_ci unsigned long vaddr, struct vm_area_struct *vma) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci unsigned long dst_paddr, src_paddr; 10562306a36Sopenharmony_ci void *dst_vaddr = coherent_kvaddr(dst, TLBTEMP_BASE_1, vaddr, 10662306a36Sopenharmony_ci &dst_paddr); 10762306a36Sopenharmony_ci void *src_vaddr = coherent_kvaddr(src, TLBTEMP_BASE_2, vaddr, 10862306a36Sopenharmony_ci &src_paddr); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci preempt_disable(); 11162306a36Sopenharmony_ci kmap_invalidate_coherent(dst, vaddr); 11262306a36Sopenharmony_ci set_bit(PG_arch_1, &dst->flags); 11362306a36Sopenharmony_ci copy_page_alias(dst_vaddr, src_vaddr, dst_paddr, src_paddr); 11462306a36Sopenharmony_ci preempt_enable(); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ciEXPORT_SYMBOL(copy_user_highpage); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci/* 11962306a36Sopenharmony_ci * Any time the kernel writes to a user page cache page, or it is about to 12062306a36Sopenharmony_ci * read from a page cache page this routine is called. 12162306a36Sopenharmony_ci * 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_civoid flush_dcache_folio(struct folio *folio) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct address_space *mapping = folio_flush_mapping(folio); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* 12962306a36Sopenharmony_ci * If we have a mapping but the page is not mapped to user-space 13062306a36Sopenharmony_ci * yet, we simply mark this page dirty and defer flushing the 13162306a36Sopenharmony_ci * caches until update_mmu(). 13262306a36Sopenharmony_ci */ 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (mapping && !mapping_mapped(mapping)) { 13562306a36Sopenharmony_ci if (!test_bit(PG_arch_1, &folio->flags)) 13662306a36Sopenharmony_ci set_bit(PG_arch_1, &folio->flags); 13762306a36Sopenharmony_ci return; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci } else { 14062306a36Sopenharmony_ci unsigned long phys = folio_pfn(folio) * PAGE_SIZE; 14162306a36Sopenharmony_ci unsigned long temp = folio_pos(folio); 14262306a36Sopenharmony_ci unsigned int i, nr = folio_nr_pages(folio); 14362306a36Sopenharmony_ci unsigned long alias = !(DCACHE_ALIAS_EQ(temp, phys)); 14462306a36Sopenharmony_ci unsigned long virt; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* 14762306a36Sopenharmony_ci * Flush the page in kernel space and user space. 14862306a36Sopenharmony_ci * Note that we can omit that step if aliasing is not 14962306a36Sopenharmony_ci * an issue, but we do have to synchronize I$ and D$ 15062306a36Sopenharmony_ci * if we have a mapping. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci if (!alias && !mapping) 15462306a36Sopenharmony_ci return; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci preempt_disable(); 15762306a36Sopenharmony_ci for (i = 0; i < nr; i++) { 15862306a36Sopenharmony_ci virt = TLBTEMP_BASE_1 + (phys & DCACHE_ALIAS_MASK); 15962306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(virt, phys); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci virt = TLBTEMP_BASE_1 + (temp & DCACHE_ALIAS_MASK); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci if (alias) 16462306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(virt, phys); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (mapping) 16762306a36Sopenharmony_ci __invalidate_icache_page_alias(virt, phys); 16862306a36Sopenharmony_ci phys += PAGE_SIZE; 16962306a36Sopenharmony_ci temp += PAGE_SIZE; 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci preempt_enable(); 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci /* There shouldn't be an entry in the cache for this page anymore. */ 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ciEXPORT_SYMBOL(flush_dcache_folio); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci/* 17962306a36Sopenharmony_ci * For now, flush the whole cache. FIXME?? 18062306a36Sopenharmony_ci */ 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_civoid local_flush_cache_range(struct vm_area_struct *vma, 18362306a36Sopenharmony_ci unsigned long start, unsigned long end) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci __flush_invalidate_dcache_all(); 18662306a36Sopenharmony_ci __invalidate_icache_all(); 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ciEXPORT_SYMBOL(local_flush_cache_range); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci/* 19162306a36Sopenharmony_ci * Remove any entry in the cache for this page. 19262306a36Sopenharmony_ci * 19362306a36Sopenharmony_ci * Note that this function is only called for user pages, so use the 19462306a36Sopenharmony_ci * alias versions of the cache flush functions. 19562306a36Sopenharmony_ci */ 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_civoid local_flush_cache_page(struct vm_area_struct *vma, unsigned long address, 19862306a36Sopenharmony_ci unsigned long pfn) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci /* Note that we have to use the 'alias' address to avoid multi-hit */ 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci unsigned long phys = page_to_phys(pfn_to_page(pfn)); 20362306a36Sopenharmony_ci unsigned long virt = TLBTEMP_BASE_1 + (address & DCACHE_ALIAS_MASK); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci preempt_disable(); 20662306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(virt, phys); 20762306a36Sopenharmony_ci __invalidate_icache_page_alias(virt, phys); 20862306a36Sopenharmony_ci preempt_enable(); 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ciEXPORT_SYMBOL(local_flush_cache_page); 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci#endif /* DCACHE_WAY_SIZE > PAGE_SIZE */ 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_civoid update_mmu_cache_range(struct vm_fault *vmf, struct vm_area_struct *vma, 21562306a36Sopenharmony_ci unsigned long addr, pte_t *ptep, unsigned int nr) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci unsigned long pfn = pte_pfn(*ptep); 21862306a36Sopenharmony_ci struct folio *folio; 21962306a36Sopenharmony_ci unsigned int i; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (!pfn_valid(pfn)) 22262306a36Sopenharmony_ci return; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci folio = page_folio(pfn_to_page(pfn)); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* Invalidate old entries in TLBs */ 22762306a36Sopenharmony_ci for (i = 0; i < nr; i++) 22862306a36Sopenharmony_ci flush_tlb_page(vma, addr + i * PAGE_SIZE); 22962306a36Sopenharmony_ci nr = folio_nr_pages(folio); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci#if (DCACHE_WAY_SIZE > PAGE_SIZE) 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci if (!folio_test_reserved(folio) && test_bit(PG_arch_1, &folio->flags)) { 23462306a36Sopenharmony_ci unsigned long phys = folio_pfn(folio) * PAGE_SIZE; 23562306a36Sopenharmony_ci unsigned long tmp; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci preempt_disable(); 23862306a36Sopenharmony_ci for (i = 0; i < nr; i++) { 23962306a36Sopenharmony_ci tmp = TLBTEMP_BASE_1 + (phys & DCACHE_ALIAS_MASK); 24062306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(tmp, phys); 24162306a36Sopenharmony_ci tmp = TLBTEMP_BASE_1 + (addr & DCACHE_ALIAS_MASK); 24262306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(tmp, phys); 24362306a36Sopenharmony_ci __invalidate_icache_page_alias(tmp, phys); 24462306a36Sopenharmony_ci phys += PAGE_SIZE; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci preempt_enable(); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci clear_bit(PG_arch_1, &folio->flags); 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci#else 25162306a36Sopenharmony_ci if (!folio_test_reserved(folio) && !test_bit(PG_arch_1, &folio->flags) 25262306a36Sopenharmony_ci && (vma->vm_flags & VM_EXEC) != 0) { 25362306a36Sopenharmony_ci for (i = 0; i < nr; i++) { 25462306a36Sopenharmony_ci void *paddr = kmap_local_folio(folio, i * PAGE_SIZE); 25562306a36Sopenharmony_ci __flush_dcache_page((unsigned long)paddr); 25662306a36Sopenharmony_ci __invalidate_icache_page((unsigned long)paddr); 25762306a36Sopenharmony_ci kunmap_local(paddr); 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci set_bit(PG_arch_1, &folio->flags); 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci#endif 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci/* 26562306a36Sopenharmony_ci * access_process_vm() has called get_user_pages(), which has done a 26662306a36Sopenharmony_ci * flush_dcache_page() on the page. 26762306a36Sopenharmony_ci */ 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci#if (DCACHE_WAY_SIZE > PAGE_SIZE) 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_civoid copy_to_user_page(struct vm_area_struct *vma, struct page *page, 27262306a36Sopenharmony_ci unsigned long vaddr, void *dst, const void *src, 27362306a36Sopenharmony_ci unsigned long len) 27462306a36Sopenharmony_ci{ 27562306a36Sopenharmony_ci unsigned long phys = page_to_phys(page); 27662306a36Sopenharmony_ci unsigned long alias = !(DCACHE_ALIAS_EQ(vaddr, phys)); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci /* Flush and invalidate user page if aliased. */ 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci if (alias) { 28162306a36Sopenharmony_ci unsigned long t = TLBTEMP_BASE_1 + (vaddr & DCACHE_ALIAS_MASK); 28262306a36Sopenharmony_ci preempt_disable(); 28362306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(t, phys); 28462306a36Sopenharmony_ci preempt_enable(); 28562306a36Sopenharmony_ci } 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci /* Copy data */ 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci memcpy(dst, src, len); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci /* 29262306a36Sopenharmony_ci * Flush and invalidate kernel page if aliased and synchronize 29362306a36Sopenharmony_ci * data and instruction caches for executable pages. 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci if (alias) { 29762306a36Sopenharmony_ci unsigned long t = TLBTEMP_BASE_1 + (vaddr & DCACHE_ALIAS_MASK); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci preempt_disable(); 30062306a36Sopenharmony_ci __flush_invalidate_dcache_range((unsigned long) dst, len); 30162306a36Sopenharmony_ci if ((vma->vm_flags & VM_EXEC) != 0) 30262306a36Sopenharmony_ci __invalidate_icache_page_alias(t, phys); 30362306a36Sopenharmony_ci preempt_enable(); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci } else if ((vma->vm_flags & VM_EXEC) != 0) { 30662306a36Sopenharmony_ci __flush_dcache_range((unsigned long)dst,len); 30762306a36Sopenharmony_ci __invalidate_icache_range((unsigned long) dst, len); 30862306a36Sopenharmony_ci } 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ciextern void copy_from_user_page(struct vm_area_struct *vma, struct page *page, 31262306a36Sopenharmony_ci unsigned long vaddr, void *dst, const void *src, 31362306a36Sopenharmony_ci unsigned long len) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci unsigned long phys = page_to_phys(page); 31662306a36Sopenharmony_ci unsigned long alias = !(DCACHE_ALIAS_EQ(vaddr, phys)); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci /* 31962306a36Sopenharmony_ci * Flush user page if aliased. 32062306a36Sopenharmony_ci * (Note: a simply flush would be sufficient) 32162306a36Sopenharmony_ci */ 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci if (alias) { 32462306a36Sopenharmony_ci unsigned long t = TLBTEMP_BASE_1 + (vaddr & DCACHE_ALIAS_MASK); 32562306a36Sopenharmony_ci preempt_disable(); 32662306a36Sopenharmony_ci __flush_invalidate_dcache_page_alias(t, phys); 32762306a36Sopenharmony_ci preempt_enable(); 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci memcpy(dst, src, len); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci#endif 334