]> git.sur5r.net Git - u-boot/blobdiff - arch/arc/lib/cache.c
ARC: Flush & invalidate D$ with a single command
[u-boot] / arch / arc / lib / cache.c
index 04f1d9d59b5471f7b96ec214e70d17e93a4d5737..42207b201c58c8784c62ae731252f3d242312ded 100644 (file)
 #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)
 
@@ -21,9 +95,9 @@
 #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
@@ -256,8 +330,7 @@ void cache_init(void)
                /* 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");
@@ -315,20 +388,27 @@ void icache_disable(void)
                              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)
@@ -366,30 +446,15 @@ void dcache_disable(void)
 }
 
 #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;
@@ -398,43 +463,39 @@ static inline void __cache_line_loop(unsigned long paddr, unsigned long sz,
 
        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;
@@ -443,16 +504,15 @@ static inline void __dc_entire_op(const int cacheop)
 
        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)
@@ -496,13 +556,19 @@ void flush_cache(unsigned long start, unsigned long size)
        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
 }