test(spm): lend/donate realm memory

Validate if memory gets delegated to a realm PAS, it can't be used
in FF-A memory lend/donate from NS to SWd.

Signed-off-by: J-Alves <joao.alves@arm.com>
Change-Id: I16c9b7193b4ae8f56b1182e3b28d13a742bfcd4e
diff --git a/tftf/tests/runtime_services/secure_service/test_ffa_memory_sharing.c b/tftf/tests/runtime_services/secure_service/test_ffa_memory_sharing.c
index cd1cfb1..87180bf 100644
--- a/tftf/tests/runtime_services/secure_service/test_ffa_memory_sharing.c
+++ b/tftf/tests/runtime_services/secure_service/test_ffa_memory_sharing.c
@@ -1,18 +1,22 @@
 /*
- * Copyright (c) 2020-2023, Arm Limited. All rights reserved.
+ * Copyright (c) 2020-2024, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
 #include "arch_features.h"
+#include "arch_helpers.h"
+#include "ffa_helpers.h"
 #include "ffa_svc.h"
 #include "stdint.h"
+#include "utils_def.h"
 #include <debug.h>
 #include "ffa_helpers.h"
 #include <sync.h>
 
 #include <cactus_test_cmds.h>
 #include <ffa_endpoints.h>
+#include <host_realm_rmi.h>
 #include <spm_common.h>
 #include <spm_test_helpers.h>
 #include <test_helpers.h>
@@ -39,6 +43,7 @@
 /* Memory section to be used for memory share operations */
 static __aligned(PAGE_SIZE) uint8_t
 	share_page[PAGE_SIZE * FRAGMENTED_SHARE_PAGE_COUNT];
+static __aligned(PAGE_SIZE) uint8_t donate_page[PAGE_SIZE];
 static __aligned(PAGE_SIZE) uint8_t consecutive_donate_page[PAGE_SIZE];
 static __aligned(PAGE_SIZE) uint8_t four_share_pages[PAGE_SIZE * 4];
 
@@ -105,7 +110,8 @@
 	if (EC_BITS(esr_elx) == EC_DABORT_CUR_EL) {
 		/* Synchronous data abort triggered by Granule protection */
 		if ((ISS_BITS(esr_elx) & ISS_DFSC_MASK) == DFSC_GPF_DABORT) {
-			VERBOSE("%s GPF Data Abort caught\n", __func__);
+			VERBOSE("%s GPF Data Abort caught to address: %llx\n",
+				__func__, (uint64_t)read_far_el2());
 			gpc_abort_triggered = true;
 			return true;
 		}
@@ -114,6 +120,15 @@
 	return false;
 }
 
+static bool get_gpc_abort_triggered(void)
+{
+	bool ret = gpc_abort_triggered;
+
+	gpc_abort_triggered = false;
+
+	return ret;
+}
+
 /**
  * Test invocation to FF-A memory sharing interfaces that should return in an
  * error.
@@ -253,7 +268,7 @@
 
 	if (check_gpc_fault) {
 		unregister_custom_sync_exception_handler();
-		if (!gpc_abort_triggered) {
+		if (!get_gpc_abort_triggered()) {
 			ERROR("No exception due to GPC for lend/donate with RME.\n");
 			return TEST_RESULT_FAIL;
 		}
@@ -292,7 +307,7 @@
 test_result_t test_mem_donate_sp(void)
 {
 	struct ffa_memory_region_constituent constituents[] = {
-		{(void *)share_page, 1, 0}
+		{(void *)donate_page, 1, 0}
 	};
 	const uint32_t constituents_count = sizeof(constituents) /
 				sizeof(struct ffa_memory_region_constituent);
@@ -963,3 +978,186 @@
 {
 	return hypervisor_retrieve_request_test_helper(FFA_MEM_LEND_SMC32, false, true);
 }
+
+/**
+ * Test helper that performs memory sharing operation, and alters the PAS
+ * of the memory, to validate that SPM intersects the operation in case the PAS
+ * is not coherent with its use. Relevant for the functioning of FFA_MEM_LEND
+ * and FFA_MEM_DONATE from NWd to an SP.
+ *
+ * In cases the memory is not in NS state, the SPMC should intersect memory
+ * management call with an appropriate FFA_ERROR.
+ */
+static test_result_t test_ffa_mem_send_realm_expect_fail(
+		uint32_t mem_func, ffa_id_t borrower,
+		struct ffa_memory_region_constituent *constituents,
+		size_t constituents_count, uint64_t delegate_addr)
+{
+	struct ffa_value ret;
+	uint32_t remaining_constituent_count;
+	uint32_t total_length;
+	uint32_t fragment_length;
+	struct mailbox_buffers mb;
+	u_register_t ret_rmm;
+	test_result_t result = TEST_RESULT_FAIL;
+	struct ffa_memory_access receiver =
+		ffa_memory_access_init_permissions_from_mem_func(borrower,
+								 mem_func);
+
+	if (get_armv9_2_feat_rme_support() == 0U) {
+		return TEST_RESULT_SKIPPED;
+	}
+
+	/***********************************************************************
+	 * Check if SPMC has ffa_version and expected FFA endpoints are deployed.
+	 **********************************************************************/
+	CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
+
+	GET_TFTF_MAILBOX(mb);
+
+	register_custom_sync_exception_handler(data_abort_handler);
+
+	/*
+	 * Delegate page to a realm. This should make memory sharing operation
+	 * fail.
+	 */
+	ret_rmm = host_rmi_granule_delegate((u_register_t)delegate_addr);
+
+	if (ret_rmm != 0UL) {
+		INFO("Delegate operation returns 0x%lx for address %llx\n",
+		     ret_rmm, delegate_addr);
+		return TEST_RESULT_FAIL;
+	}
+
+	remaining_constituent_count = ffa_memory_region_init(
+		(struct ffa_memory_region *)mb.send, MAILBOX_SIZE, SENDER,
+		&receiver, 1, constituents, constituents_count, 0,
+		FFA_MEMORY_REGION_FLAG_CLEAR,
+		FFA_MEMORY_NOT_SPECIFIED_MEM, 0, 0,
+		&total_length, &fragment_length);
+
+	if (remaining_constituent_count != 0) {
+		goto out;
+	}
+
+	switch (mem_func) {
+	case FFA_MEM_LEND_SMC32:
+		ret = ffa_mem_lend(total_length, fragment_length);
+		break;
+	case FFA_MEM_DONATE_SMC32:
+		ret = ffa_mem_donate(total_length, fragment_length);
+		break;
+	default:
+		ERROR("Not expected for func name: %x\n", mem_func);
+		return TEST_RESULT_FAIL;
+	}
+
+	if (!is_expected_ffa_error(ret, FFA_ERROR_DENIED)) {
+		goto out;
+	}
+
+	/* Undelegate to reestablish the same security state for PAS. */
+	ret_rmm = host_rmi_granule_undelegate((u_register_t)delegate_addr);
+
+	for (uint32_t i = 0; i < constituents_count; i++) {
+		uint32_t *ptr = (uint32_t *)constituents[i].address;
+
+		*ptr = 0xFFA;
+	}
+
+	if (get_gpc_abort_triggered()) {
+		ERROR("Exception due to GPC for lend/donate with RME. Not"
+		      " expected for this case.\n");
+		result = TEST_RESULT_FAIL;
+	} else {
+		result = TEST_RESULT_SUCCESS;
+	}
+out:
+	unregister_custom_sync_exception_handler();
+
+	if (ret_rmm != 0UL) {
+		INFO("Undelegate operation returns 0x%lx for address %llx\n",
+		     ret_rmm, (uint64_t)delegate_addr);
+		return TEST_RESULT_FAIL;
+	}
+
+	return result;
+}
+
+/**
+ * Memory to be shared between partitions is described in a composite, with
+ * various constituents. In an RME system, the memory must be in NS PAS in
+ * operations from NWd to an SP. In case the PAS is not following this
+ * expectation memory lend/donate should fail, and all constituents must
+ * remain in the NS PAS.
+ *
+ * This test validates that if one page in the middle of one of the constituents
+ * is not in the NS PAS the operation fails.
+ */
+test_result_t test_ffa_mem_send_sp_realm_memory(void)
+{
+	test_result_t ret;
+	uint32_t mem_func[] = {FFA_MEM_LEND_SMC32, FFA_MEM_DONATE_SMC32};
+	struct ffa_memory_region_constituent constituents[] = {
+		{(void *)four_share_pages, 4, 0},
+		{(void *)share_page, 1, 0}
+	};
+
+	const uint32_t constituents_count = sizeof(constituents) /
+				sizeof(struct ffa_memory_region_constituent);
+
+	for (unsigned j = 0; j < ARRAY_SIZE(mem_func); j++) {
+		for (unsigned int i = 0; i < 4; i++) {
+			/* Address to be delegated to Realm PAS. */
+			uint64_t realm_addr =
+				(uint64_t)&four_share_pages[i * PAGE_SIZE];
+
+			INFO("%s memory with realm addr: %llx\n",
+			     mem_func[j] == FFA_MEM_LEND_SMC32
+				? "Lend"
+				: "Donate",
+			     realm_addr);
+
+			ret = test_ffa_mem_send_realm_expect_fail(
+				mem_func[j], SP_ID(1), constituents,
+				constituents_count, realm_addr);
+
+			if (ret != TEST_RESULT_SUCCESS) {
+				break;
+			}
+		}
+	}
+
+	return ret;
+}
+
+/**
+ * Memory to be shared between partitions is described in a composite, with
+ * various constituents. In an RME system, the memory must be in NS PAS in
+ * operations from NWd to an SP. In case the PAS is not following this
+ * expectation memory lend/donate should fail, and all constituents must
+ * remain in the NS PAS.
+ *
+ * This test validates the case in which the memory lend/donate fail in
+ * case one of the constituents in the composite is not in the NS PAS.
+ */
+test_result_t test_ffa_mem_lend_sp_realm_memory_separate_constituent(void)
+{
+	test_result_t ret;
+	struct ffa_memory_region_constituent constituents[] = {
+		{(void *)four_share_pages, 4, 0},
+		{(void *)share_page, 1, 0}
+	};
+	const uint32_t constituents_count = sizeof(constituents) /
+				sizeof(struct ffa_memory_region_constituent);
+	/* Address to be delegated to Realm PAS. */
+	uint64_t realm_addr = (uint64_t)&share_page[0];
+
+	INFO("Sharing memory with realm addr: %llx\n", realm_addr);
+
+	ret = test_ffa_mem_send_realm_expect_fail(
+		FFA_MEM_LEND_SMC32, SP_ID(1), constituents,
+		constituents_count, realm_addr);
+
+	return ret;
+}
diff --git a/tftf/tests/tests-memory-access.xml b/tftf/tests/tests-memory-access.xml
index 42f21cc..4318cc9 100644
--- a/tftf/tests/tests-memory-access.xml
+++ b/tftf/tests/tests-memory-access.xml
@@ -53,6 +53,10 @@
                 function="rt_memory_cannot_be_accessed_in_rl" />
       <testcase name="Share memory to an SP from a Root region"
                 function="rt_memory_cannot_be_accessed_in_s" />
+      <testcase name="FF-A memory share fails if using realm memory"
+                function="test_ffa_mem_send_sp_realm_memory" />
+      <testcase name="FF-A memory share fail realm memory other constituent"
+                function="test_ffa_mem_lend_sp_realm_memory_separate_constituent" />
   </testsuite>
 
 </testsuites>