| /* |
| * This file is part of the coreboot project. |
| * |
| * Copyright 2013 Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| * cache.c: Cache maintenance routines for ARMv7-A and ARMv7-R |
| * |
| * Reference: ARM Architecture Reference Manual, ARMv7-A and ARMv7-R edition |
| */ |
| |
| #include <stdint.h> |
| |
| #include <arch/cache.h> |
| |
| #define bitmask(high, low) ((1UL << (high)) + \ |
| ((1UL << (high)) - 1) - ((1UL << (low)) - 1)) |
| |
| /* Basic log2() implementation. Note: log2(0) is 0 for our purposes. */ |
| /* FIXME: src/include/lib.h is difficult to work with due to romcc */ |
| static unsigned long log2(unsigned long u) |
| { |
| int i = 0; |
| |
| while (u >>= 1) |
| i++; |
| |
| return i; |
| } |
| |
| void tlb_invalidate_all(void) |
| { |
| /* |
| * FIXME: ARMv7 Architecture Ref. Manual claims that the distinction |
| * instruction vs. data TLBs is deprecated in ARMv7, however this does |
| * not seem to be the case as of Cortex-A15. |
| */ |
| tlbiall(); |
| dtlbiall(); |
| itlbiall(); |
| isb(); |
| dsb(); |
| } |
| |
| void icache_invalidate_all(void) |
| { |
| /* |
| * icache can be entirely invalidated with one operation. |
| * Note: If branch predictors are architecturally-visible, ICIALLU |
| * also performs a BPIALL operation (B2-1283 in arch manual) |
| */ |
| iciallu(); |
| isb(); |
| } |
| |
| enum dcache_op { |
| OP_DCCSW, |
| OP_DCCISW, |
| OP_DCISW, |
| OP_DCCIMVAC, |
| OP_DCCMVAC, |
| OP_DCIMVAC, |
| }; |
| |
| /* |
| * Do a dcache operation on entire cache by set/way. This is done for |
| * portability because mapping of memory address to cache location is |
| * implementation defined (See note on "Requirements for operations by |
| * set/way" in arch ref. manual). |
| */ |
| static void dcache_op_set_way(enum dcache_op op) |
| { |
| uint32_t ccsidr; |
| unsigned int associativity, num_sets, linesize_bytes; |
| unsigned int set, way; |
| unsigned int level; |
| |
| level = (read_csselr() >> 1) & 0x7; |
| |
| /* |
| * dcache must be invalidated by set/way for portability since virtual |
| * memory mapping is system-defined. The number of sets and |
| * associativity is given by CCSIDR. We'll use DCISW to invalidate the |
| * dcache. |
| */ |
| ccsidr = read_ccsidr(); |
| |
| /* FIXME: rounding up required here? */ |
| num_sets = ((ccsidr & bitmask(27, 13)) >> 13) + 1; |
| associativity = ((ccsidr & bitmask(12, 3)) >> 3) + 1; |
| /* FIXME: do we need to use CTR.DminLine here? */ |
| linesize_bytes = (1 << ((ccsidr & 0x7) + 2)) * 4; |
| |
| dsb(); |
| |
| /* |
| * Set/way operations require an interesting bit packing. See section |
| * B4-35 in the ARMv7 Architecture Reference Manual: |
| * |
| * A: Log2(associativity) |
| * B: L+S |
| * L: Log2(linesize) |
| * S: Log2(num_sets) |
| * |
| * The bits are packed as follows: |
| * 31 31-A B B-1 L L-1 4 3 1 0 |
| * |---|-------------|--------|-------|-----|-| |
| * |Way| zeros | Set | zeros |level|0| |
| * |---|-------------|--------|-------|-----|-| |
| */ |
| for (way = 0; way < associativity; way++) { |
| for (set = 0; set < num_sets; set++) { |
| uint32_t val = 0; |
| val |= way << (32 - log2(associativity)); |
| val |= set << log2(linesize_bytes); |
| val |= level << 1; |
| switch(op) { |
| case OP_DCCISW: |
| dccisw(val); |
| break; |
| case OP_DCISW: |
| dcisw(val); |
| break; |
| case OP_DCCSW: |
| dccsw(val); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| isb(); |
| } |
| |
| static void dcache_foreach(enum dcache_op op) |
| { |
| uint32_t clidr; |
| int level; |
| |
| clidr = read_clidr(); |
| for (level = 0; level < 7; level++) { |
| unsigned int ctype = (clidr >> (level * 3)) & 0x7; |
| uint32_t csselr; |
| |
| switch(ctype) { |
| case 0x2: |
| case 0x3: |
| case 0x4: |
| csselr = level << 1; |
| write_csselr(csselr); |
| dcache_op_set_way(op); |
| break; |
| default: |
| /* no cache, icache only, or reserved */ |
| break; |
| } |
| } |
| } |
| |
| void dcache_clean_all(void) |
| { |
| dcache_foreach(OP_DCCSW); |
| } |
| |
| void dcache_clean_invalidate_all(void) |
| { |
| dcache_foreach(OP_DCCISW); |
| } |
| |
| void dcache_invalidate_all(void) |
| { |
| dcache_foreach(OP_DCISW); |
| } |
| |
| unsigned int dcache_line_bytes(void) |
| { |
| uint32_t ccsidr; |
| static unsigned int line_bytes = 0; |
| |
| if (line_bytes) |
| return line_bytes; |
| |
| ccsidr = read_ccsidr(); |
| /* [2:0] - Indicates (Log2(number of words in cache line)) - 2 */ |
| line_bytes = 1 << ((ccsidr & 0x7) + 2); /* words per line */ |
| line_bytes *= sizeof(unsigned int); /* bytes per line */ |
| |
| return line_bytes; |
| } |
| |
| /* |
| * Do a dcache operation by modified virtual address. This is useful for |
| * maintaining coherency in drivers which do DMA transfers and only need to |
| * perform cache maintenance on a particular memory range rather than the |
| * entire cache. |
| */ |
| static void dcache_op_mva(void const *addr, size_t len, enum dcache_op op) |
| { |
| unsigned long line, linesize; |
| |
| linesize = dcache_line_bytes(); |
| line = (uint32_t)addr & ~(linesize - 1); |
| |
| dsb(); |
| while ((void *)line < addr + len) { |
| switch(op) { |
| case OP_DCCIMVAC: |
| dccimvac(line); |
| break; |
| case OP_DCCMVAC: |
| dccmvac(line); |
| break; |
| case OP_DCIMVAC: |
| dcimvac(line); |
| break; |
| default: |
| break; |
| } |
| line += linesize; |
| } |
| isb(); |
| } |
| |
| void dcache_clean_by_mva(void const *addr, size_t len) |
| { |
| dcache_op_mva(addr, len, OP_DCCMVAC); |
| } |
| |
| void dcache_clean_invalidate_by_mva(void const *addr, size_t len) |
| { |
| dcache_op_mva(addr, len, OP_DCCIMVAC); |
| } |
| |
| void dcache_invalidate_by_mva(void const *addr, size_t len) |
| { |
| dcache_op_mva(addr, len, OP_DCIMVAC); |
| } |
| |
| void dcache_mmu_disable(void) |
| { |
| uint32_t sctlr; |
| |
| dcache_clean_invalidate_all(); |
| sctlr = read_sctlr(); |
| sctlr &= ~(SCTLR_C | SCTLR_M); |
| write_sctlr(sctlr); |
| } |
| |
| |
| void dcache_mmu_enable(void) |
| { |
| uint32_t sctlr; |
| |
| sctlr = read_sctlr(); |
| dcache_clean_invalidate_all(); |
| sctlr |= SCTLR_C | SCTLR_M; |
| write_sctlr(sctlr); |
| } |
| |
| void arm_invalidate_caches(void) |
| { |
| uint32_t clidr; |
| int level; |
| |
| /* Invalidate branch predictor */ |
| bpiall(); |
| |
| /* Iterate thru each cache identified in CLIDR and invalidate */ |
| clidr = read_clidr(); |
| for (level = 0; level < 7; level++) { |
| unsigned int ctype = (clidr >> (level * 3)) & 0x7; |
| uint32_t csselr; |
| |
| switch(ctype) { |
| case 0x0: |
| /* no cache */ |
| break; |
| case 0x1: |
| /* icache only */ |
| csselr = (level << 1) | 1; |
| write_csselr(csselr); |
| icache_invalidate_all(); |
| break; |
| case 0x2: |
| case 0x4: |
| /* dcache only or unified cache */ |
| csselr = level << 1; |
| write_csselr(csselr); |
| dcache_invalidate_all(); |
| break; |
| case 0x3: |
| /* separate icache and dcache */ |
| csselr = (level << 1) | 1; |
| write_csselr(csselr); |
| icache_invalidate_all(); |
| |
| csselr = level << 1; |
| write_csselr(csselr); |
| dcache_invalidate_all(); |
| break; |
| default: |
| /* reserved */ |
| break; |
| } |
| } |
| |
| /* Invalidate TLB */ |
| tlb_invalidate_all(); |
| } |