feat: add implementation of `memcpy_trapped`

Add implementation of `memcpy_trapped` which is capable of returning
error in case any of the memory accesses from source and destination
result in a data abort.

The `sync_current_exception` was refactored such that it can infer a
GPF, and in the exact str/ldr locations of the `memcpy_trapped`.

Prepare the `sync_current_exception` handler to process the GPF.
It shall be able to change the `ELR_EL2`, and return false
signaling that `ELR_EL2` shall not be restored from stack.

Signed-off-by: J-Alves <joao.alves@arm.com>
Change-Id: Id268140441a16b346b06a4891f97e69c0d1b8e6e
diff --git a/src/arch/aarch64/hypervisor/BUILD.gn b/src/arch/aarch64/hypervisor/BUILD.gn
index d3a4e93..df36f68 100644
--- a/src/arch/aarch64/hypervisor/BUILD.gn
+++ b/src/arch/aarch64/hypervisor/BUILD.gn
@@ -27,6 +27,7 @@
   sources = [
     "exceptions.S",
     "hypervisor_entry.S",
+    "memcpy_trapped.S",
     "plat_entry.S",
   ]
 
diff --git a/src/arch/aarch64/hypervisor/exceptions.S b/src/arch/aarch64/hypervisor/exceptions.S
index 57e7e20..59026f7 100644
--- a/src/arch/aarch64/hypervisor/exceptions.S
+++ b/src/arch/aarch64/hypervisor/exceptions.S
@@ -150,7 +150,7 @@
 .balign 0x800
 vector_table_el2:
 sync_cur_sp0:
-	noreturn_current_exception_sp0 el2 sync_current_exception_noreturn
+	noreturn_current_exception_sp0 el2 sync_current_exception
 
 .balign 0x80
 irq_cur_sp0:
@@ -166,7 +166,7 @@
 
 .balign 0x80
 sync_cur_spx:
-	noreturn_current_exception_spx el2 sync_current_exception_noreturn
+	current_exception_spx el2 sync_current_exception
 
 .balign 0x80
 irq_cur_spx:
@@ -214,6 +214,15 @@
 
 .balign 0x40
 
+
+/**
+ * Instantiate code path for restoring stack and returning.
+ */
+restore_from_stack_and_return el2
+
+exception_handler_return:
+	eret_with_sb
+
 /**
  * pauth_save_vcpu_and_restore_hyp_key
  *
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 47e1a96..976fd09 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -11,6 +11,7 @@
 #include "hf/arch/barriers.h"
 #include "hf/arch/gicv3.h"
 #include "hf/arch/init.h"
+#include "hf/arch/memcpy_trapped.h"
 #include "hf/arch/mmu.h"
 #include "hf/arch/plat/ffa.h"
 #include "hf/arch/plat/smc.h"
@@ -232,7 +233,12 @@
 	panic("SError from current exception level.");
 }
 
-noreturn void sync_current_exception_noreturn(uintreg_t elr, uintreg_t spsr)
+/**
+ * Returns true if ELR_EL2 is not to be restored from stack.
+ * Currently function doesn't return false, as for all other cases
+ * panics.
+ */
+bool sync_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	uintreg_t esr = read_msr(esr_el2);
 	uintreg_t ec = GET_ESR_EC(esr);
@@ -240,21 +246,53 @@
 	(void)spsr;
 
 	switch (ec) {
-	case EC_DATA_ABORT_SAME_EL:
+	case EC_DATA_ABORT_SAME_EL: {
+		uint64_t iss = GET_ESR_ISS(esr);
+		uint64_t dfsc = GET_ESR_ISS_DFSC(iss);
+		uint64_t far = read_msr(far_el2);
+
+		/* Handle Granule Protection Fault. */
+		if (is_arch_feat_rme_supported() && dfsc == DFSC_GPF) {
+			dlog_verbose(
+				"Granule Protection Fault: esr=%#x, ec=%#x, "
+				"far=%#x, elr=%#x\n",
+				esr, ec, far, elr);
+
+			/*
+			 * Change ELR_EL2 only if failed whilst either
+			 * reading or writing within 'memcpy_trapped'.
+			 */
+			if (elr == (uintptr_t)memcpy_trapped_read ||
+			    elr == (uintptr_t)memcpy_trapped_write) {
+				dlog_verbose(
+					"GPF due to data abort on %s.\n",
+					(elr == (uintptr_t)memcpy_trapped_read)
+						? "read"
+						: "write");
+
+				/*
+				 * Update the ELR_EL2 with the return
+				 * address, to return error from the
+				 * call to 'memcpy_trapped'.
+				 */
+				write_msr(ELR_EL2, memcpy_trapped_aborted);
+				return true;
+			}
+		}
+
 		if (!(esr & (1U << 10))) { /* Check FnV bit. */
 			dlog_error(
 				"Data abort: pc=%#x, esr=%#x, ec=%#x, "
 				"far=%#x\n",
-				elr, esr, ec, read_msr(far_el2));
+				elr, esr, ec, far);
+
 		} else {
 			dlog_error(
 				"Data abort: pc=%#x, esr=%#x, ec=%#x, "
 				"far=invalid\n",
 				elr, esr, ec);
 		}
-
-		break;
-
+	} break;
 	default:
 		dlog_error(
 			"Unknown current sync exception pc=%#x, esr=%#x, "
diff --git a/src/arch/aarch64/hypervisor/memcpy_trapped.S b/src/arch/aarch64/hypervisor/memcpy_trapped.S
new file mode 100644
index 0000000..df4628a
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/memcpy_trapped.S
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2024 The Hafnium Authors.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/BSD-3-Clause.
+ */
+
+.global memcpy_trapped
+.global memcpy_trapped_aborted
+.global memcpy_trapped_write
+.global memcpy_trapped_read
+
+/**
+ * This is a helper function to copy data from/to memory owned
+ * by partitions or another FF-A endpoint, whose access from the
+ * SPM might result in a data abort, due to accessing the wrong
+ * Physical Address. This function shall not be used to copy when
+ * source and destination are owned by hafnium.
+ * The function assumes the addresses are aligned to 8 bytes.
+ *
+ * - x0 contains the destination address.
+ * - x1 size of destination
+ * - x2 contains the source address.
+ * - x3 size of source.
+ *
+ * Returns:
+ * - x0: 0 if failed to copy, 1 otherwise.
+ */
+memcpy_trapped:
+	/* If source size is bigger than destination size, abort. */
+	cmp x3, x1
+	b.hi memcpy_trapped_aborted
+
+	/* Return error if destination size is 0. */
+	cbz x1, memcpy_trapped_aborted
+
+	/* Return error if destination is null. */
+	cbz x0, memcpy_trapped_aborted
+
+	/* Return error if source is null. */
+	cbz x2, memcpy_trapped_aborted
+
+	/* Check if source size is aligned to 8 bytes. */
+	and x4, x3, #(8-1)
+	cbz x4, memcpy_trapped_read
+
+	/* Align to 8 bytes if it isn't. */
+	add x3, x3, #8
+	sub x3, x3, x4
+
+	/*
+	 * The read/write from/to memory prone to cause an
+	 * exception must precisely follow the labels below.
+	 * This is so we can deterministically assert that the
+	 * exception is due to an access that we know to be
+	 * prone to be aborted.
+	 * This is enforced in the exception handler, to
+	 * determine wether the link register must be overwritten
+	 * with that of label `memcpy_trapped_aborted`, thus
+	 * returning an error to the caller of memcpy_trapped.
+	 */
+memcpy_trapped_read:
+	/* Read from the source. */
+	ldr x4, [x2], #8
+memcpy_trapped_write:
+	/* Write to destination. */
+	str x4, [x0], #8
+	sub x3, x3, #8
+	cbnz x3, memcpy_trapped_read
+
+	/* Success. */
+	mov x0, #1
+	ret
+
+/**
+ * Exit for 'memcpy_trapped' function, in case there is an error:
+ * - Argument checks in the function.
+ * - Access gets trapped due to GPF.
+ */
+memcpy_trapped_aborted:
+	mov x0, xzr
+	ret