ARC: Flush & invalidate D$ with a single command
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>
diff --git a/arch/arc/lib/cache.c b/arch/arc/lib/cache.c
index 83b77b9..42207b2 100644
--- a/arch/arc/lib/cache.c
+++ b/arch/arc/lib/cache.c
@@ -12,6 +12,80 @@
#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 @@
/* 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 @@
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
}