#include <asm/arcregs.h>
#include <asm/cache.h>
+/*
+ * [ NOTE 1 ]:
+ * Data cache (L1 D$ or SL$) entire invalidate operation or data cache disable
+ * operation may result in unexpected behavior and data loss even if we flush
+ * data cache right before invalidation. That may happens if we store any context
+ * on stack (like we store BLINK register on stack before function call).
+ * BLINK register is the register where return address is automatically saved
+ * when we do function call with instructions like 'bl'.
+ *
+ * There is the real example:
+ * We may hang in the next code as we store any BLINK register on stack in
+ * invalidate_dcache_all() function.
+ *
+ * void flush_dcache_all() {
+ * __dc_entire_op(OP_FLUSH);
+ * // Other code //
+ * }
+ *
+ * void invalidate_dcache_all() {
+ * __dc_entire_op(OP_INV);
+ * // Other code //
+ * }
+ *
+ * void foo(void) {
+ * flush_dcache_all();
+ * invalidate_dcache_all();
+ * }
+ *
+ * Now let's see what really happens during that code execution:
+ *
+ * foo()
+ * |->> call flush_dcache_all
+ * [return address is saved to BLINK register]
+ * [push BLINK] (save to stack) ![point 1]
+ * |->> call __dc_entire_op(OP_FLUSH)
+ * [return address is saved to BLINK register]
+ * [flush L1 D$]
+ * return [jump to BLINK]
+ * <<------
+ * [other flush_dcache_all code]
+ * [pop BLINK] (get from stack)
+ * return [jump to BLINK]
+ * <<------
+ * |->> call invalidate_dcache_all
+ * [return address is saved to BLINK register]
+ * [push BLINK] (save to stack) ![point 2]
+ * |->> call __dc_entire_op(OP_FLUSH)
+ * [return address is saved to BLINK register]
+ * [invalidate L1 D$] ![point 3]
+ * // Oops!!!
+ * // We lose return address from invalidate_dcache_all function:
+ * // we save it to stack and invalidate L1 D$ after that!
+ * return [jump to BLINK]
+ * <<------
+ * [other invalidate_dcache_all code]
+ * [pop BLINK] (get from stack)
+ * // we don't have this data in L1 dcache as we invalidated it in [point 3]
+ * // so we get it from next memory level (for example DDR memory)
+ * // but in the memory we have value which we save in [point 1], which
+ * // is return address from flush_dcache_all function (instead of
+ * // address from current invalidate_dcache_all function which we
+ * // saved in [point 2] !)
+ * return [jump to BLINK]
+ * <<------
+ * // As BLINK points to invalidate_dcache_all, we call it again and
+ * // loop forever.
+ *
+ * Fortunately we may fix that by using flush & invalidation of D$ with a single
+ * one instruction (instead of flush and invalidation instructions pair) and
+ * enabling force function inline with '__attribute__((always_inline))' gcc
+ * attribute to avoid any function call (and BLINK store) between cache flush
+ * and disable.
+ */
+
/* Bit values in IC_CTRL */
#define IC_CTRL_CACHE_DISABLE BIT(0)
#define DC_CTRL_FLUSH_STATUS BIT(8)
#define CACHE_VER_NUM_MASK 0xF
-#define OP_INV 0x1
-#define OP_FLUSH 0x2
-#define OP_INV_IC 0x3
+#define OP_INV BIT(0)
+#define OP_FLUSH BIT(1)
+#define OP_FLUSH_N_INV (OP_FLUSH | OP_INV)
/* Bit val in SLC_CONTROL */
#define SLC_CTRL_DIS 0x001
/* IOC Aperture size is equal to DDR size */
long ap_size = CONFIG_SYS_SDRAM_SIZE;
- flush_dcache_all();
- invalidate_dcache_all();
+ flush_n_invalidate_dcache_all();
if (!is_power_of_2(ap_size) || ap_size < 4096)
panic("IOC Aperture size must be power of 2 and bigger 4Kib");
IC_CTRL_CACHE_DISABLE);
}
-void invalidate_icache_all(void)
+/* IC supports only invalidation */
+static inline void __ic_entire_invalidate(void)
{
+ if (!icache_status())
+ return;
+
/* Any write to IC_IVIC register triggers invalidation of entire I$ */
- if (icache_status()) {
- write_aux_reg(ARC_AUX_IC_IVIC, 1);
- /*
- * As per ARC HS databook (see chapter 5.3.3.2)
- * it is required to add 3 NOPs after each write to IC_IVIC.
- */
- __builtin_arc_nop();
- __builtin_arc_nop();
- __builtin_arc_nop();
- read_aux_reg(ARC_AUX_IC_CTRL); /* blocks */
- }
+ write_aux_reg(ARC_AUX_IC_IVIC, 1);
+ /*
+ * As per ARC HS databook (see chapter 5.3.3.2)
+ * it is required to add 3 NOPs after each write to IC_IVIC.
+ */
+ __builtin_arc_nop();
+ __builtin_arc_nop();
+ __builtin_arc_nop();
+ read_aux_reg(ARC_AUX_IC_CTRL); /* blocks */
+}
+
+void invalidate_icache_all(void)
+{
+ __ic_entire_invalidate();
#ifdef CONFIG_ISA_ARCV2
if (slc_exists)
}
#ifndef CONFIG_SYS_DCACHE_OFF
-/*
- * Common Helper for Line Operations on {I,D}-Cache
- */
-static inline void __cache_line_loop(unsigned long paddr, unsigned long sz,
- const int cacheop)
+/* Common Helper for Line Operations on D-cache */
+static inline void __dcache_line_loop(unsigned long paddr, unsigned long sz,
+ const int cacheop)
{
unsigned int aux_cmd;
-#if (CONFIG_ARC_MMU_VER == 3)
- unsigned int aux_tag;
-#endif
int num_lines;
- if (cacheop == OP_INV_IC) {
- aux_cmd = ARC_AUX_IC_IVIL;
-#if (CONFIG_ARC_MMU_VER == 3)
- aux_tag = ARC_AUX_IC_PTAG;
-#endif
- } else {
- /* d$ cmd: INV (discard or wback-n-discard) OR FLUSH (wback) */
- aux_cmd = cacheop & OP_INV ? ARC_AUX_DC_IVDL : ARC_AUX_DC_FLDL;
-#if (CONFIG_ARC_MMU_VER == 3)
- aux_tag = ARC_AUX_DC_PTAG;
-#endif
- }
+ /* d$ cmd: INV (discard or wback-n-discard) OR FLUSH (wback) */
+ aux_cmd = cacheop & OP_INV ? ARC_AUX_DC_IVDL : ARC_AUX_DC_FLDL;
sz += paddr & ~CACHE_LINE_MASK;
paddr &= CACHE_LINE_MASK;
while (num_lines-- > 0) {
#if (CONFIG_ARC_MMU_VER == 3)
- write_aux_reg(aux_tag, paddr);
+ write_aux_reg(ARC_AUX_DC_PTAG, paddr);
#endif
write_aux_reg(aux_cmd, paddr);
paddr += l1_line_sz;
}
}
-static unsigned int __before_dc_op(const int op)
+static void __before_dc_op(const int op)
{
- unsigned int reg;
+ unsigned int ctrl;
- if (op == OP_INV) {
- /*
- * IM is set by default and implies Flush-n-inv
- * Clear it here for vanilla inv
- */
- reg = read_aux_reg(ARC_AUX_DC_CTRL);
- write_aux_reg(ARC_AUX_DC_CTRL, reg & ~DC_CTRL_INV_MODE_FLUSH);
- }
+ ctrl = read_aux_reg(ARC_AUX_DC_CTRL);
+
+ /* IM bit implies flush-n-inv, instead of vanilla inv */
+ if (op == OP_INV)
+ ctrl &= ~DC_CTRL_INV_MODE_FLUSH;
+ else
+ ctrl |= DC_CTRL_INV_MODE_FLUSH;
- return reg;
+ write_aux_reg(ARC_AUX_DC_CTRL, ctrl);
}
-static void __after_dc_op(const int op, unsigned int reg)
+static void __after_dc_op(const int op)
{
if (op & OP_FLUSH) /* flush / flush-n-inv both wait */
while (read_aux_reg(ARC_AUX_DC_CTRL) & DC_CTRL_FLUSH_STATUS);
-
- /* Switch back to default Invalidate mode */
- if (op == OP_INV)
- write_aux_reg(ARC_AUX_DC_CTRL, reg | DC_CTRL_INV_MODE_FLUSH);
}
static inline void __dc_entire_op(const int cacheop)
{
int aux;
- unsigned int ctrl_reg = __before_dc_op(cacheop);
+
+ __before_dc_op(cacheop);
if (cacheop & OP_INV) /* Inv or flush-n-inv use same cmd reg */
aux = ARC_AUX_DC_IVDC;
write_aux_reg(aux, 0x1);
- __after_dc_op(cacheop, ctrl_reg);
+ __after_dc_op(cacheop);
}
static inline void __dc_line_op(unsigned long paddr, unsigned long sz,
const int cacheop)
{
- unsigned int ctrl_reg = __before_dc_op(cacheop);
-
- __cache_line_loop(paddr, sz, cacheop);
- __after_dc_op(cacheop, ctrl_reg);
+ __before_dc_op(cacheop);
+ __dcache_line_loop(paddr, sz, cacheop);
+ __after_dc_op(cacheop);
}
#else
#define __dc_entire_op(cacheop)
flush_dcache_range(start, start + size);
}
-void invalidate_dcache_all(void)
+/*
+ * As invalidate_dcache_all() is not used in generic U-Boot code and as we
+ * don't need it in arch/arc code alone (invalidate without flush) we implement
+ * flush_n_invalidate_dcache_all (flush and invalidate in 1 operation) because
+ * it's much safer. See [ NOTE 1 ] for more details.
+ */
+void flush_n_invalidate_dcache_all(void)
{
- __dc_entire_op(OP_INV);
+ __dc_entire_op(OP_FLUSH_N_INV);
#ifdef CONFIG_ISA_ARCV2
if (slc_exists)
- __slc_entire_op(OP_INV);
+ __slc_entire_op(OP_FLUSH_N_INV);
#endif
}