ARC: Flush & invalidate D$ with a single command
authorEugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
Wed, 21 Mar 2018 12:58:50 +0000 (15:58 +0300)
committerAlexey Brodkin <abrodkin@synopsys.com>
Wed, 21 Mar 2018 14:06:49 +0000 (17:06 +0300)
We don't implement separate flush_dcache_all() intentionally as
entire data cache invalidation is dangerous operation even if we flush
data cache right before invalidation.

There is the real example:
We may get stuck in the following code if we store any context (like
BLINK register) on stack in invalidate_dcache_all() function.

BLINK register is the register where return address is automatically saved
when we do function call with instructions like 'bl'.

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 do flush and invalidation of D$ with a single one
instruction which automatically mitigates a situation described above.

And because invalidate_dcache_all() isn't used in common U-Boot code we
implement "flush and invalidate dcache all" instead.

Signed-off-by: Eugeniy Paltsev <Eugeniy.Paltsev@synopsys.com>
Signed-off-by: Alexey Brodkin <abrodkin@synopsys.com>
arch/arc/include/asm/cache.h
arch/arc/lib/cache.c

index d26d9fb18d0e1b6573b2d8a8c3019ba7c59565ee..382c4126c3fe6642b48ba98106e98ddf0011b8a8 100644 (file)
@@ -30,6 +30,7 @@
 #ifndef __ASSEMBLY__
 
 void cache_init(void);
+void flush_n_invalidate_dcache_all(void);
 
 #endif /* __ASSEMBLY__ */
 
index 83b77b97160b31a586a9a1b89825c665857887ff..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)
 
@@ -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");
@@ -483,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
 }