[U-Boot,05/18] ARC: flush & invalidate D$ with a single command

Message ID 20180213173451.6317-6-Eugeniy.Paltsev@synopsys.com
State Superseded
Delegated to: Alexey Brodkin
Headers show
Series
  • ARC: cache subsystem improvement/refactoring
Related show

Commit Message

Eugeniy Paltsev Feb. 13, 2018, 5:34 p.m.
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 hang in the next 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>
---
 arch/arc/include/asm/cache.h |  1 +
 arch/arc/lib/cache.c         | 15 ++++++++++-----
 2 files changed, 11 insertions(+), 5 deletions(-)

Patch

diff --git a/arch/arc/include/asm/cache.h b/arch/arc/include/asm/cache.h
index d26d9fb..382c412 100644
--- a/arch/arc/include/asm/cache.h
+++ b/arch/arc/include/asm/cache.h
@@ -30,6 +30,7 @@ 
 #ifndef __ASSEMBLY__
 
 void cache_init(void);
+void flush_n_invalidate_dcache_all(void);
 
 #endif /* __ASSEMBLY__ */
 
diff --git a/arch/arc/lib/cache.c b/arch/arc/lib/cache.c
index 83b77b9..a22ffe5 100644
--- a/arch/arc/lib/cache.c
+++ b/arch/arc/lib/cache.c
@@ -256,8 +256,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 +482,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.
+ */
+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
 }