162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 362306a36Sopenharmony_ci * License. See the file "COPYING" in the main directory of this archive 462306a36Sopenharmony_ci * for more details. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2005-2007 Cavium Networks 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <linux/export.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/sched.h> 1162306a36Sopenharmony_ci#include <linux/smp.h> 1262306a36Sopenharmony_ci#include <linux/mm.h> 1362306a36Sopenharmony_ci#include <linux/bitops.h> 1462306a36Sopenharmony_ci#include <linux/cpu.h> 1562306a36Sopenharmony_ci#include <linux/io.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <asm/bcache.h> 1862306a36Sopenharmony_ci#include <asm/bootinfo.h> 1962306a36Sopenharmony_ci#include <asm/cacheops.h> 2062306a36Sopenharmony_ci#include <asm/cpu-features.h> 2162306a36Sopenharmony_ci#include <asm/cpu-type.h> 2262306a36Sopenharmony_ci#include <asm/page.h> 2362306a36Sopenharmony_ci#include <asm/r4kcache.h> 2462306a36Sopenharmony_ci#include <asm/traps.h> 2562306a36Sopenharmony_ci#include <asm/mmu_context.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <asm/octeon/octeon.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ciunsigned long long cache_err_dcache[NR_CPUS]; 3062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cache_err_dcache); 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* 3362306a36Sopenharmony_ci * Octeon automatically flushes the dcache on tlb changes, so 3462306a36Sopenharmony_ci * from Linux's viewpoint it acts much like a physically 3562306a36Sopenharmony_ci * tagged cache. No flushing is needed 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci */ 3862306a36Sopenharmony_cistatic void octeon_flush_data_cache_page(unsigned long addr) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci /* Nothing to do */ 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic inline void octeon_local_flush_icache(void) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci asm volatile ("synci 0($0)"); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* 4962306a36Sopenharmony_ci * Flush local I-cache for the specified range. 5062306a36Sopenharmony_ci */ 5162306a36Sopenharmony_cistatic void local_octeon_flush_icache_range(unsigned long start, 5262306a36Sopenharmony_ci unsigned long end) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci octeon_local_flush_icache(); 5562306a36Sopenharmony_ci} 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/** 5862306a36Sopenharmony_ci * octeon_flush_icache_all_cores - Flush caches as necessary for all cores 5962306a36Sopenharmony_ci * affected by a vma. If no vma is supplied, all cores are flushed. 6062306a36Sopenharmony_ci * 6162306a36Sopenharmony_ci * @vma: VMA to flush or NULL to flush all icaches. 6262306a36Sopenharmony_ci */ 6362306a36Sopenharmony_cistatic void octeon_flush_icache_all_cores(struct vm_area_struct *vma) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci extern void octeon_send_ipi_single(int cpu, unsigned int action); 6662306a36Sopenharmony_ci#ifdef CONFIG_SMP 6762306a36Sopenharmony_ci int cpu; 6862306a36Sopenharmony_ci cpumask_t mask; 6962306a36Sopenharmony_ci#endif 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci mb(); 7262306a36Sopenharmony_ci octeon_local_flush_icache(); 7362306a36Sopenharmony_ci#ifdef CONFIG_SMP 7462306a36Sopenharmony_ci preempt_disable(); 7562306a36Sopenharmony_ci cpu = smp_processor_id(); 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci /* 7862306a36Sopenharmony_ci * If we have a vma structure, we only need to worry about 7962306a36Sopenharmony_ci * cores it has been used on 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ci if (vma) 8262306a36Sopenharmony_ci mask = *mm_cpumask(vma->vm_mm); 8362306a36Sopenharmony_ci else 8462306a36Sopenharmony_ci mask = *cpu_online_mask; 8562306a36Sopenharmony_ci cpumask_clear_cpu(cpu, &mask); 8662306a36Sopenharmony_ci#ifdef CONFIG_CAVIUM_OCTEON_SOC 8762306a36Sopenharmony_ci for_each_cpu(cpu, &mask) 8862306a36Sopenharmony_ci octeon_send_ipi_single(cpu, SMP_ICACHE_FLUSH); 8962306a36Sopenharmony_ci#else 9062306a36Sopenharmony_ci smp_call_function_many(&mask, (smp_call_func_t)octeon_local_flush_icache, 9162306a36Sopenharmony_ci NULL, 1); 9262306a36Sopenharmony_ci#endif 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci preempt_enable(); 9562306a36Sopenharmony_ci#endif 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci/* 10062306a36Sopenharmony_ci * Called to flush the icache on all cores 10162306a36Sopenharmony_ci */ 10262306a36Sopenharmony_cistatic void octeon_flush_icache_all(void) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci octeon_flush_icache_all_cores(NULL); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci/** 10962306a36Sopenharmony_ci * octeon_flush_cache_mm - flush all memory associated with a memory context. 11062306a36Sopenharmony_ci * 11162306a36Sopenharmony_ci * @mm: Memory context to flush 11262306a36Sopenharmony_ci */ 11362306a36Sopenharmony_cistatic void octeon_flush_cache_mm(struct mm_struct *mm) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci /* 11662306a36Sopenharmony_ci * According to the R4K version of this file, CPUs without 11762306a36Sopenharmony_ci * dcache aliases don't need to do anything here 11862306a36Sopenharmony_ci */ 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci/* 12362306a36Sopenharmony_ci * Flush a range of kernel addresses out of the icache 12462306a36Sopenharmony_ci * 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_cistatic void octeon_flush_icache_range(unsigned long start, unsigned long end) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci octeon_flush_icache_all_cores(NULL); 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/** 13362306a36Sopenharmony_ci * octeon_flush_cache_range - Flush a range out of a vma 13462306a36Sopenharmony_ci * 13562306a36Sopenharmony_ci * @vma: VMA to flush 13662306a36Sopenharmony_ci * @start: beginning address for flush 13762306a36Sopenharmony_ci * @end: ending address for flush 13862306a36Sopenharmony_ci */ 13962306a36Sopenharmony_cistatic void octeon_flush_cache_range(struct vm_area_struct *vma, 14062306a36Sopenharmony_ci unsigned long start, unsigned long end) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci if (vma->vm_flags & VM_EXEC) 14362306a36Sopenharmony_ci octeon_flush_icache_all_cores(vma); 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci/** 14862306a36Sopenharmony_ci * octeon_flush_cache_page - Flush a specific page of a vma 14962306a36Sopenharmony_ci * 15062306a36Sopenharmony_ci * @vma: VMA to flush page for 15162306a36Sopenharmony_ci * @page: Page to flush 15262306a36Sopenharmony_ci * @pfn: Page frame number 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_cistatic void octeon_flush_cache_page(struct vm_area_struct *vma, 15562306a36Sopenharmony_ci unsigned long page, unsigned long pfn) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci if (vma->vm_flags & VM_EXEC) 15862306a36Sopenharmony_ci octeon_flush_icache_all_cores(vma); 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_cistatic void octeon_flush_kernel_vmap_range(unsigned long vaddr, int size) 16262306a36Sopenharmony_ci{ 16362306a36Sopenharmony_ci BUG(); 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci/* 16762306a36Sopenharmony_ci * Probe Octeon's caches 16862306a36Sopenharmony_ci * 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_cistatic void probe_octeon(void) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci unsigned long icache_size; 17362306a36Sopenharmony_ci unsigned long dcache_size; 17462306a36Sopenharmony_ci unsigned int config1; 17562306a36Sopenharmony_ci struct cpuinfo_mips *c = ¤t_cpu_data; 17662306a36Sopenharmony_ci int cputype = current_cpu_type(); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci config1 = read_c0_config1(); 17962306a36Sopenharmony_ci switch (cputype) { 18062306a36Sopenharmony_ci case CPU_CAVIUM_OCTEON: 18162306a36Sopenharmony_ci case CPU_CAVIUM_OCTEON_PLUS: 18262306a36Sopenharmony_ci c->icache.linesz = 2 << ((config1 >> 19) & 7); 18362306a36Sopenharmony_ci c->icache.sets = 64 << ((config1 >> 22) & 7); 18462306a36Sopenharmony_ci c->icache.ways = 1 + ((config1 >> 16) & 7); 18562306a36Sopenharmony_ci c->icache.flags |= MIPS_CACHE_VTAG; 18662306a36Sopenharmony_ci icache_size = 18762306a36Sopenharmony_ci c->icache.sets * c->icache.ways * c->icache.linesz; 18862306a36Sopenharmony_ci c->icache.waybit = ffs(icache_size / c->icache.ways) - 1; 18962306a36Sopenharmony_ci c->dcache.linesz = 128; 19062306a36Sopenharmony_ci if (cputype == CPU_CAVIUM_OCTEON_PLUS) 19162306a36Sopenharmony_ci c->dcache.sets = 2; /* CN5XXX has two Dcache sets */ 19262306a36Sopenharmony_ci else 19362306a36Sopenharmony_ci c->dcache.sets = 1; /* CN3XXX has one Dcache set */ 19462306a36Sopenharmony_ci c->dcache.ways = 64; 19562306a36Sopenharmony_ci dcache_size = 19662306a36Sopenharmony_ci c->dcache.sets * c->dcache.ways * c->dcache.linesz; 19762306a36Sopenharmony_ci c->dcache.waybit = ffs(dcache_size / c->dcache.ways) - 1; 19862306a36Sopenharmony_ci c->options |= MIPS_CPU_PREFETCH; 19962306a36Sopenharmony_ci break; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci case CPU_CAVIUM_OCTEON2: 20262306a36Sopenharmony_ci c->icache.linesz = 2 << ((config1 >> 19) & 7); 20362306a36Sopenharmony_ci c->icache.sets = 8; 20462306a36Sopenharmony_ci c->icache.ways = 37; 20562306a36Sopenharmony_ci c->icache.flags |= MIPS_CACHE_VTAG; 20662306a36Sopenharmony_ci icache_size = c->icache.sets * c->icache.ways * c->icache.linesz; 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci c->dcache.linesz = 128; 20962306a36Sopenharmony_ci c->dcache.ways = 32; 21062306a36Sopenharmony_ci c->dcache.sets = 8; 21162306a36Sopenharmony_ci dcache_size = c->dcache.sets * c->dcache.ways * c->dcache.linesz; 21262306a36Sopenharmony_ci c->options |= MIPS_CPU_PREFETCH; 21362306a36Sopenharmony_ci break; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci case CPU_CAVIUM_OCTEON3: 21662306a36Sopenharmony_ci c->icache.linesz = 128; 21762306a36Sopenharmony_ci c->icache.sets = 16; 21862306a36Sopenharmony_ci c->icache.ways = 39; 21962306a36Sopenharmony_ci c->icache.flags |= MIPS_CACHE_VTAG; 22062306a36Sopenharmony_ci icache_size = c->icache.sets * c->icache.ways * c->icache.linesz; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci c->dcache.linesz = 128; 22362306a36Sopenharmony_ci c->dcache.ways = 32; 22462306a36Sopenharmony_ci c->dcache.sets = 8; 22562306a36Sopenharmony_ci dcache_size = c->dcache.sets * c->dcache.ways * c->dcache.linesz; 22662306a36Sopenharmony_ci c->options |= MIPS_CPU_PREFETCH; 22762306a36Sopenharmony_ci break; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci default: 23062306a36Sopenharmony_ci panic("Unsupported Cavium Networks CPU type"); 23162306a36Sopenharmony_ci break; 23262306a36Sopenharmony_ci } 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* compute a couple of other cache variables */ 23562306a36Sopenharmony_ci c->icache.waysize = icache_size / c->icache.ways; 23662306a36Sopenharmony_ci c->dcache.waysize = dcache_size / c->dcache.ways; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci c->icache.sets = icache_size / (c->icache.linesz * c->icache.ways); 23962306a36Sopenharmony_ci c->dcache.sets = dcache_size / (c->dcache.linesz * c->dcache.ways); 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (smp_processor_id() == 0) { 24262306a36Sopenharmony_ci pr_info("Primary instruction cache %ldkB, %s, %d way, " 24362306a36Sopenharmony_ci "%d sets, linesize %d bytes.\n", 24462306a36Sopenharmony_ci icache_size >> 10, 24562306a36Sopenharmony_ci cpu_has_vtag_icache ? 24662306a36Sopenharmony_ci "virtually tagged" : "physically tagged", 24762306a36Sopenharmony_ci c->icache.ways, c->icache.sets, c->icache.linesz); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci pr_info("Primary data cache %ldkB, %d-way, %d sets, " 25062306a36Sopenharmony_ci "linesize %d bytes.\n", 25162306a36Sopenharmony_ci dcache_size >> 10, c->dcache.ways, 25262306a36Sopenharmony_ci c->dcache.sets, c->dcache.linesz); 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic void octeon_cache_error_setup(void) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci extern char except_vec2_octeon; 25962306a36Sopenharmony_ci set_handler(0x100, &except_vec2_octeon, 0x80); 26062306a36Sopenharmony_ci} 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci/* 26362306a36Sopenharmony_ci * Setup the Octeon cache flush routines 26462306a36Sopenharmony_ci * 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_civoid octeon_cache_init(void) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci probe_octeon(); 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci shm_align_mask = PAGE_SIZE - 1; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci flush_cache_all = octeon_flush_icache_all; 27362306a36Sopenharmony_ci __flush_cache_all = octeon_flush_icache_all; 27462306a36Sopenharmony_ci flush_cache_mm = octeon_flush_cache_mm; 27562306a36Sopenharmony_ci flush_cache_page = octeon_flush_cache_page; 27662306a36Sopenharmony_ci flush_cache_range = octeon_flush_cache_range; 27762306a36Sopenharmony_ci flush_icache_all = octeon_flush_icache_all; 27862306a36Sopenharmony_ci flush_data_cache_page = octeon_flush_data_cache_page; 27962306a36Sopenharmony_ci flush_icache_range = octeon_flush_icache_range; 28062306a36Sopenharmony_ci local_flush_icache_range = local_octeon_flush_icache_range; 28162306a36Sopenharmony_ci __flush_icache_user_range = octeon_flush_icache_range; 28262306a36Sopenharmony_ci __local_flush_icache_user_range = local_octeon_flush_icache_range; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci __flush_kernel_vmap_range = octeon_flush_kernel_vmap_range; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci build_clear_page(); 28762306a36Sopenharmony_ci build_copy_page(); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci board_cache_error_setup = octeon_cache_error_setup; 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci/* 29362306a36Sopenharmony_ci * Handle a cache error exception 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_cistatic RAW_NOTIFIER_HEAD(co_cache_error_chain); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ciint register_co_cache_error_notifier(struct notifier_block *nb) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci return raw_notifier_chain_register(&co_cache_error_chain, nb); 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(register_co_cache_error_notifier); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ciint unregister_co_cache_error_notifier(struct notifier_block *nb) 30462306a36Sopenharmony_ci{ 30562306a36Sopenharmony_ci return raw_notifier_chain_unregister(&co_cache_error_chain, nb); 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(unregister_co_cache_error_notifier); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic void co_cache_error_call_notifiers(unsigned long val) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci int rv = raw_notifier_call_chain(&co_cache_error_chain, val, NULL); 31262306a36Sopenharmony_ci if ((rv & ~NOTIFY_STOP_MASK) != NOTIFY_OK) { 31362306a36Sopenharmony_ci u64 dcache_err; 31462306a36Sopenharmony_ci unsigned long coreid = cvmx_get_core_num(); 31562306a36Sopenharmony_ci u64 icache_err = read_octeon_c0_icacheerr(); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (val) { 31862306a36Sopenharmony_ci dcache_err = cache_err_dcache[coreid]; 31962306a36Sopenharmony_ci cache_err_dcache[coreid] = 0; 32062306a36Sopenharmony_ci } else { 32162306a36Sopenharmony_ci dcache_err = read_octeon_c0_dcacheerr(); 32262306a36Sopenharmony_ci } 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci pr_err("Core%lu: Cache error exception:\n", coreid); 32562306a36Sopenharmony_ci pr_err("cp0_errorepc == %lx\n", read_c0_errorepc()); 32662306a36Sopenharmony_ci if (icache_err & 1) { 32762306a36Sopenharmony_ci pr_err("CacheErr (Icache) == %llx\n", 32862306a36Sopenharmony_ci (unsigned long long)icache_err); 32962306a36Sopenharmony_ci write_octeon_c0_icacheerr(0); 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci if (dcache_err & 1) { 33262306a36Sopenharmony_ci pr_err("CacheErr (Dcache) == %llx\n", 33362306a36Sopenharmony_ci (unsigned long long)dcache_err); 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci} 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci/* 33962306a36Sopenharmony_ci * Called when the exception is recoverable 34062306a36Sopenharmony_ci */ 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ciasmlinkage void cache_parity_error_octeon_recoverable(void) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci co_cache_error_call_notifiers(0); 34562306a36Sopenharmony_ci} 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci/* 34862306a36Sopenharmony_ci * Called when the exception is not recoverable 34962306a36Sopenharmony_ci */ 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ciasmlinkage void cache_parity_error_octeon_non_recoverable(void) 35262306a36Sopenharmony_ci{ 35362306a36Sopenharmony_ci co_cache_error_call_notifiers(1); 35462306a36Sopenharmony_ci panic("Can't handle cache error: nested exception"); 35562306a36Sopenharmony_ci} 356