feat(lib/granule): Clean and invalidate granules to PoE

A granule in GPT_REALM can transition between DELEGATED and other states
several times, and be accessed in different MEC contexts. When
transitioning back to DELEGATED, flush the caches to the point of
encryption in order to avoid MECID mismatches.

TF-A's clean+invalidate to PoPA when delegating/undelegating already
deals with transitions that change the granule's GPT entry. So we could
optimize this by marking the granule dirty when transitioning back to
DELEGATED, then doing the flush only if the granule is reused without
going through an undelegate operation.

Change-Id: I12e9f93f49462e55025b0d5dd16dc78f8a3b3637
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
diff --git a/lib/arch/include/arch_helpers.h b/lib/arch/include/arch_helpers.h
index 15391b7..39d89e3 100644
--- a/lib/arch/include/arch_helpers.h
+++ b/lib/arch/include/arch_helpers.h
@@ -63,6 +63,7 @@
 DEFINE_SYSOP_TYPE_PARAM_FUNC(dc, ivac)
 DEFINE_SYSOP_TYPE_PARAM_FUNC(dc, civac)
 DEFINE_SYSOP_TYPE_PARAM_FUNC(dc, cvau)
+DEFINE_SYSOP_TYPE_PARAM_FUNC(dc, cipae)
 DEFINE_SYSOP_DCZVA
 
 /*******************************************************************************
@@ -86,6 +87,7 @@
 void flush_dcache_range(uintptr_t addr, size_t size);
 void clean_dcache_range(uintptr_t addr, size_t size);
 void inv_dcache_range(uintptr_t addr, size_t size);
+void flush_dcache_range_to_poe(uintptr_t paddr, size_t size);
 
 #define is_dcache_enabled() ((read_sctlr_el2() & SCTLR_ELx_C_BIT) != 0UL)
 
diff --git a/lib/arch/src/aarch64/cache_helpers.S b/lib/arch/src/aarch64/cache_helpers.S
index 195cf61..bd7a6c4 100644
--- a/lib/arch/src/aarch64/cache_helpers.S
+++ b/lib/arch/src/aarch64/cache_helpers.S
@@ -9,6 +9,13 @@
 	.globl	flush_dcache_range
 	.globl	clean_dcache_range
 	.globl	inv_dcache_range
+	.globl	flush_dcache_range_to_poe
+
+/*
+ * sys  #4, c7, c14, #0, x0
+ * DC CIPAE, X0
+ */
+#define dc_cipae_x0    0xd50c7e00
 
 /*
  * This macro can be used for implementing various data cache operations `op`
@@ -29,6 +36,25 @@
 exit_loop_\op:
 	ret
 .endm
+
+/* op: the hexadecimal instruction opcode for the cache operation */
+.macro do_dcache_maintenance_instr op
+	/* Exit early if size is zero */
+	cbz	x1, exit_loop_\op
+	dcache_line_size x2, x3
+	add	x1, x0, x1
+	sub	x3, x2, #1
+	bic	x0, x0, x3
+loop_\op:
+	.inst	\op
+	add	x0, x0, x2
+	cmp	x0, x1
+	b.lo	loop_\op
+	dsb	sy
+exit_loop_\op:
+	ret
+.endm
+
 	/* ------------------------------------------
 	 * Clean+Invalidate from base address till
 	 * size. 'x0' = addr, 'x1' = size
@@ -55,3 +81,13 @@
 func inv_dcache_range
 	do_dcache_maintenance_by_mva ivac
 endfunc inv_dcache_range
+
+	/* ------------------------------------------
+	 * Clean and invalidate to point of
+	 * encryption from base address till size.
+	 * 'x0' = physical addr, 'x1' = size
+	 * ------------------------------------------
+	 */
+func flush_dcache_range_to_poe
+	do_dcache_maintenance_instr dc_cipae_x0
+endfunc flush_dcache_range_to_poe
diff --git a/lib/arch/src/fake_host/cache_wrappers.c b/lib/arch/src/fake_host/cache_wrappers.c
index 6de91fa..956d5ea 100644
--- a/lib/arch/src/fake_host/cache_wrappers.c
+++ b/lib/arch/src/fake_host/cache_wrappers.c
@@ -23,3 +23,8 @@
 	(void)addr;
 	(void)size;
 }
+void flush_dcache_range_to_poe(uintptr_t paddr, size_t size)
+{
+	(void)paddr;
+	(void)size;
+}
diff --git a/lib/granule/src/granule.c b/lib/granule/src/granule.c
index 3d8922f..116c93e 100644
--- a/lib/granule/src/granule.c
+++ b/lib/granule/src/granule.c
@@ -3,6 +3,7 @@
  * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
  */
 
+#include <arch_features.h>
 #include <arch_helpers.h>
 #include <assert.h>
 #include <debug.h>
@@ -268,9 +269,22 @@
 void granule_set_state(struct granule *g, unsigned char state)
 {
 	unsigned short val;
+	unsigned char prev_state = granule_get_state(g);
 
 	assert((g != NULL) && LOCKED(g));
 
+	/*
+	 * Transitionning a granule back to DELEGATED requires cleaning and
+	 * invalidating the cache to the PoE, to avoid MECID mismatch next
+	 * time the granule is used within a different MEC context.
+	 */
+	if (is_feat_mec_present() &&
+	    state == GRANULE_STATE_DELEGATED &&
+	    prev_state != GRANULE_STATE_NS &&
+	    prev_state != GRANULE_STATE_DELEGATED) {
+		flush_dcache_range_to_poe(granule_addr(g), GRANULE_SIZE);
+	}
+
 	/* NOLINTNEXTLINE(clang-analyzer-core.NullDereference) */
 	val = g->descriptor & STATE_MASK;