/* * arch/metag/mm/cache.c * * Copyright (C) 2001, 2002, 2005, 2007, 2012 Imagination Technologies. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * * Cache control code */ #include <linux/export.h> #include <linux/io.h> #include <asm/cacheflush.h> #include <asm/core_reg.h> #include <asm/global_lock.h> #include <asm/metag_isa.h> #include <asm/metag_mem.h> #include <asm/metag_regs.h> #define DEFAULT_CACHE_WAYS_LOG2 2 /* * Size of a set in the caches. Initialised for default 16K stride, adjusted * according to values passed through TBI global heap segment via LDLK (on ATP) * or config registers (on HTP/MTP) */ static int dcache_set_shift = METAG_TBI_CACHE_SIZE_BASE_LOG2 - DEFAULT_CACHE_WAYS_LOG2; static int icache_set_shift = METAG_TBI_CACHE_SIZE_BASE_LOG2 - DEFAULT_CACHE_WAYS_LOG2; /* * The number of sets in the caches. Initialised for HTP/ATP, adjusted * according to NOMMU setting in config registers */ static unsigned char dcache_sets_log2 = DEFAULT_CACHE_WAYS_LOG2; static unsigned char icache_sets_log2 = DEFAULT_CACHE_WAYS_LOG2; #ifndef CONFIG_METAG_META12 /** * metag_lnkget_probe() - Probe whether lnkget/lnkset go around the cache */ static volatile u32 lnkget_testdata[16] __initdata __aligned(64); #define LNKGET_CONSTANT 0xdeadbeef void __init metag_lnkget_probe(void) { int temp; long flags; /* * It's conceivable the user has configured a globally coherent cache * shared with non-Linux hardware threads, so use LOCK2 to prevent them * from executing and causing cache eviction during the test. */ __global_lock2(flags); /* read a value to bring it into the cache */ (void)lnkget_testdata[0]; lnkget_testdata[0] = 0; /* lnkget/lnkset it to modify it */ asm volatile( "1: LNKGETD %0, [%1]\n" " LNKSETD [%1], %2\n" " DEFR %0, TXSTAT\n" " ANDT %0, %0, #HI(0x3f000000)\n" " CMPT %0, #HI(0x02000000)\n" " BNZ 1b\n" : "=&d" (temp) : "da" (&lnkget_testdata[0]), "bd" (LNKGET_CONSTANT) : "cc"); /* re-read it to see if the cached value changed */ temp = lnkget_testdata[0]; __global_unlock2(flags); /* flush the cache line to fix any incoherency */ __builtin_dcache_flush((void *)&lnkget_testdata[0]); #if defined(CONFIG_METAG_LNKGET_AROUND_CACHE) /* if the cache is right, LNKGET_AROUND_CACHE is unnecessary */ if (temp == LNKGET_CONSTANT) pr_info("LNKGET/SET go through cache but CONFIG_METAG_LNKGET_AROUND_CACHE=y\n"); #elif defined(CONFIG_METAG_ATOMICITY_LNKGET) /* * if the cache is wrong, LNKGET_AROUND_CACHE is really necessary * because the kernel is configured to use LNKGET/SET for atomicity */ WARN(temp != LNKGET_CONSTANT, "LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n" "Expect kernel failure as it's used for atomicity primitives\n"); #elif defined(CONFIG_SMP) /* * if the cache is wrong, LNKGET_AROUND_CACHE should be used or the * gateway page won't flush and userland could break. */ WARN(temp != LNKGET_CONSTANT, "LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n" "Expect userland failure as it's used for user gateway page\n"); #else /* * if the cache is wrong, LNKGET_AROUND_CACHE is set wrong, but it * doesn't actually matter as it doesn't have any effect on !SMP && * !ATOMICITY_LNKGET. */ if (temp != LNKGET_CONSTANT) pr_warn("LNKGET/SET go around cache but CONFIG_METAG_LNKGET_AROUND_CACHE=n\n"); #endif } #endif /* !CONFIG_METAG_META12 */ /** * metag_cache_probe() - Probe L1 cache configuration. * * Probe the L1 cache configuration to aid the L1 physical cache flushing * functions. */ void __init metag_cache_probe(void) { #ifndef CONFIG_METAG_META12 int coreid = metag_in32(METAC_CORE_ID); int config = metag_in32(METAC_CORE_CONFIG2); int cfgcache = coreid & METAC_COREID_CFGCACHE_BITS; if (cfgcache == METAC_COREID_CFGCACHE_TYPE0 || cfgcache == METAC_COREID_CFGCACHE_PRIVNOMMU) { icache_sets_log2 = 1; dcache_sets_log2 = 1; } /* For normal size caches, the smallest size is 4Kb. For small caches, the smallest size is 64b */ icache_set_shift = (config & METAC_CORECFG2_ICSMALL_BIT) ? 6 : 12; icache_set_shift += (config & METAC_CORE_C2ICSZ_BITS) >> METAC_CORE_C2ICSZ_S; icache_set_shift -= icache_sets_log2; dcache_set_shift = (config & METAC_CORECFG2_DCSMALL_BIT) ? 6 : 12; dcache_set_shift += (config & METAC_CORECFG2_DCSZ_BITS) >> METAC_CORECFG2_DCSZ_S; dcache_set_shift -= dcache_sets_log2; metag_lnkget_probe(); #else /* Extract cache sizes from global heap segment */ unsigned long val, u; int width, shift, addend; PTBISEG seg; seg = __TBIFindSeg(NULL, TBID_SEG(TBID_THREAD_GLOBAL, TBID_SEGSCOPE_GLOBAL, TBID_SEGTYPE_HEAP)); if (seg != NULL) { val = seg->Data[1]; /* Work out width of I-cache size bit-field */ u = ((unsigned long) METAG_TBI_ICACHE_SIZE_BITS) >> METAG_TBI_ICACHE_SIZE_S; width = 0; while (u & 1) { width++; u >>= 1; } /* Extract sign-extended size addend value */ shift = 32 - (METAG_TBI_ICACHE_SIZE_S + width); addend = (long) ((val & METAG_TBI_ICACHE_SIZE_BITS) << shift) >> (shift + METAG_TBI_ICACHE_SIZE_S); /* Now calculate I-cache set size */ icache_set_shift = (METAG_TBI_CACHE_SIZE_BASE_LOG2 - DEFAULT_CACHE_WAYS_LOG2) + addend; /* Similarly for D-cache */ u = ((unsigned long) METAG_TBI_DCACHE_SIZE_BITS) >> METAG_TBI_DCACHE_SIZE_S; width = 0; while (u & 1) { width++; u >>= 1; } shift = 32 - (METAG_TBI_DCACHE_SIZE_S + width); addend = (long) ((val & METAG_TBI_DCACHE_SIZE_BITS) << shift) >> (shift + METAG_TBI_DCACHE_SIZE_S); dcache_set_shift = (METAG_TBI_CACHE_SIZE_BASE_LOG2 - DEFAULT_CACHE_WAYS_LOG2) + addend; } #endif } static void metag_phys_data_cache_flush(const void *start) { unsigned long flush0, flush1, flush2, flush3; int loops, step; int thread; int part, offset; int set_shift; /* Use a sequence of writes to flush the cache region requested */ thread = (__core_reg_get(TXENABLE) & TXENABLE_THREAD_BITS) >> TXENABLE_THREAD_S; /* Cache is broken into sets which lie in contiguous RAMs */ set_shift = dcache_set_shift; /* Move to the base of the physical cache flush region */ flush0 = LINSYSCFLUSH_DCACHE_LINE; step = 64; /* Get partition data for this thread */ part = metag_in32(SYSC_DCPART0 + (SYSC_xCPARTn_STRIDE * thread)); if ((int)start < 0) /* Access Global vs Local partition */ part >>= SYSC_xCPARTG_AND_S - SYSC_xCPARTL_AND_S; /* Extract offset and move SetOff */ offset = (part & SYSC_xCPARTL_OR_BITS) >> SYSC_xCPARTL_OR_S; flush0 += (offset << (set_shift - 4)); /* Shrink size */ part = (part & SYSC_xCPARTL_AND_BITS) >> SYSC_xCPARTL_AND_S; loops = ((part + 1) << (set_shift - 4)); /* Reduce loops by step of cache line size */ loops /= step; flush1 = flush0 + (1 << set_shift); flush2 = flush0 + (2 << set_shift); flush3 = flush0 + (3 << set_shift); if (dcache_sets_log2 == 1) { flush2 = flush1; flush3 = flush1 + step; flush1 = flush0 + step; step <<= 1; loops >>= 1; } /* Clear loops ways in cache */ while (loops-- != 0) { /* Clear the ways. */ #if 0 /* * GCC doesn't generate very good code for this so we * provide inline assembly instead. */ metag_out8(0, flush0); metag_out8(0, flush1); metag_out8(0, flush2); metag_out8(0, flush3); flush0 += step; flush1 += step; flush2 += step; flush3 += step; #else asm volatile ( "SETB\t[%0+%4++],%5\n" "SETB\t[%1+%4++],%5\n" "SETB\t[%2+%4++],%5\n" "SETB\t[%3+%4++],%5\n" : "+e" (flush0), "+e" (flush1), "+e" (flush2), "+e" (flush3) : "e" (step), "a" (0)); #endif } } void metag_data_cache_flush_all(const void *start) { if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_DC_ON_BIT) == 0) /* No need to flush the data cache it's not actually enabled */ return; metag_phys_data_cache_flush(start); } void metag_data_cache_flush(const void *start, int bytes) { unsigned long flush0; int loops, step; if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_DC_ON_BIT) == 0) /* No need to flush the data cache it's not actually enabled */ return; if (bytes >= 4096) { metag_phys_data_cache_flush(start); return; } /* Use linear cache flush mechanism on META IP */ flush0 = (int)start; loops = ((int)start & (DCACHE_LINE_BYTES - 1)) + bytes + (DCACHE_LINE_BYTES - 1); loops >>= DCACHE_LINE_S; #define PRIM_FLUSH(addr, offset) do { \ int __addr = ((int) (addr)) + ((offset) * 64); \ __builtin_dcache_flush((void *)(__addr)); \ } while (0) #define LOOP_INC (4*64) do { /* By default stop */ step = 0; switch (loops) { /* Drop Thru Cases! */ default: PRIM_FLUSH(flush0, 3); loops -= 4; step = 1; case 3: PRIM_FLUSH(flush0, 2); case 2: PRIM_FLUSH(flush0, 1); case 1: PRIM_FLUSH(flush0, 0); flush0 += LOOP_INC; case 0: break; } } while (step); } EXPORT_SYMBOL(metag_data_cache_flush); static void metag_phys_code_cache_flush(const void *start, int bytes) { unsigned long flush0, flush1, flush2, flush3, end_set; int loops, step; int thread; int set_shift, set_size; int part, offset; /* Use a sequence of writes to flush the cache region requested */ thread = (__core_reg_get(TXENABLE) & TXENABLE_THREAD_BITS) >> TXENABLE_THREAD_S; set_shift = icache_set_shift; /* Move to the base of the physical cache flush region */ flush0 = LINSYSCFLUSH_ICACHE_LINE; step = 64; /* Get partition code for this thread */ part = metag_in32(SYSC_ICPART0 + (SYSC_xCPARTn_STRIDE * thread)); if ((int)start < 0) /* Access Global vs Local partition */ part >>= SYSC_xCPARTG_AND_S-SYSC_xCPARTL_AND_S; /* Extract offset and move SetOff */ offset = (part & SYSC_xCPARTL_OR_BITS) >> SYSC_xCPARTL_OR_S; flush0 += (offset << (set_shift - 4)); /* Shrink size */ part = (part & SYSC_xCPARTL_AND_BITS) >> SYSC_xCPARTL_AND_S; loops = ((part + 1) << (set_shift - 4)); /* Where does the Set end? */ end_set = flush0 + loops; set_size = loops; #ifdef CONFIG_METAG_META12 if ((bytes < 4096) && (bytes < loops)) { /* Unreachable on HTP/MTP */ /* Only target the sets that could be relavent */ flush0 += (loops - step) & ((int) start); loops = (((int) start) & (step-1)) + bytes + step - 1; } #endif /* Reduce loops by step of cache line size */ loops /= step; flush1 = flush0 + (1<<set_shift); flush2 = flush0 + (2<<set_shift); flush3 = flush0 + (3<<set_shift); if (icache_sets_log2 == 1) { flush2 = flush1; flush3 = flush1 + step; flush1 = flush0 + step; #if 0 /* flush0 will stop one line early in this case * (flush1 will do the final line). * However we don't correct end_set here at the moment * because it will never wrap on HTP/MTP */ end_set -= step; #endif step <<= 1; loops >>= 1; } /* Clear loops ways in cache */ while (loops-- != 0) { #if 0 /* * GCC doesn't generate very good code for this so we * provide inline assembly instead. */ /* Clear the ways */ metag_out8(0, flush0); metag_out8(0, flush1); metag_out8(0, flush2); metag_out8(0, flush3); flush0 += step; flush1 += step; flush2 += step; flush3 += step; #else asm volatile ( "SETB\t[%0+%4++],%5\n" "SETB\t[%1+%4++],%5\n" "SETB\t[%2+%4++],%5\n" "SETB\t[%3+%4++],%5\n" : "+e" (flush0), "+e" (flush1), "+e" (flush2), "+e" (flush3) : "e" (step), "a" (0)); #endif if (flush0 == end_set) { /* Wrap within Set 0 */ flush0 -= set_size; flush1 -= set_size; flush2 -= set_size; flush3 -= set_size; } } } void metag_code_cache_flush_all(const void *start) { if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_IC_ON_BIT) == 0) /* No need to flush the code cache it's not actually enabled */ return; metag_phys_code_cache_flush(start, 4096); } EXPORT_SYMBOL(metag_code_cache_flush_all); void metag_code_cache_flush(const void *start, int bytes) { #ifndef CONFIG_METAG_META12 void *flush; int loops, step; #endif /* !CONFIG_METAG_META12 */ if ((metag_in32(SYSC_CACHE_MMU_CONFIG) & SYSC_CMMUCFG_IC_ON_BIT) == 0) /* No need to flush the code cache it's not actually enabled */ return; #ifdef CONFIG_METAG_META12 /* CACHEWD isn't available on Meta1, so always do full cache flush */ metag_phys_code_cache_flush(start, bytes); #else /* CONFIG_METAG_META12 */ /* If large size do full physical cache flush */ if (bytes >= 4096) { metag_phys_code_cache_flush(start, bytes); return; } /* Use linear cache flush mechanism on META IP */ flush = (void *)((int)start & ~(ICACHE_LINE_BYTES-1)); loops = ((int)start & (ICACHE_LINE_BYTES-1)) + bytes + (ICACHE_LINE_BYTES-1); loops >>= ICACHE_LINE_S; #define PRIM_IFLUSH(addr, offset) \ __builtin_meta2_cachewd(((addr) + ((offset) * 64)), CACHEW_ICACHE_BIT) #define LOOP_INC (4*64) do { /* By default stop */ step = 0; switch (loops) { /* Drop Thru Cases! */ default: PRIM_IFLUSH(flush, 3); loops -= 4; step = 1; case 3: PRIM_IFLUSH(flush, 2); case 2: PRIM_IFLUSH(flush, 1); case 1: PRIM_IFLUSH(flush, 0); flush += LOOP_INC; case 0: break; } } while (step); #endif /* !CONFIG_METAG_META12 */ } EXPORT_SYMBOL(metag_code_cache_flush);