test(memory share): hypervisor retrieve request

Checks that the hypervisor retrieve request from section 17.4.3 of FF-A
v1.2 REL0 is supported.

Specifically, the tests:
1. Sends a FFA_MEM_SHARE/FFA_MEM_LEND/FFA_MEM_DONATE message from the
   tftf target to the SPMC.
2. Sends a hypervisor retrieve request from the tftf target to the SPMC.
3. Verify the contents of the FFA_MEM_RETRIEVE_RESP message against the
   previously sent message.
4. Invoke reclaim interface for SPMC to free memory owned by the handle.

Change-Id: If27d8d5b515c5e468977ed4f5f4ddeb86e44c5df
Signed-off-by: Karl Meakin <karl.meakin@arm.com>
diff --git a/include/runtime_services/ffa_helpers.h b/include/runtime_services/ffa_helpers.h
index 91a7a6e..90bb30f 100644
--- a/include/runtime_services/ffa_helpers.h
+++ b/include/runtime_services/ffa_helpers.h
@@ -647,6 +647,9 @@
 	enum ffa_memory_type type, enum ffa_memory_cacheability cacheability,
 	enum ffa_memory_shareability shareability);
 
+void ffa_hypervisor_retrieve_request_init(struct ffa_memory_region *region,
+					  ffa_memory_handle_t handle);
+
 uint32_t ffa_memory_region_init(
 	struct ffa_memory_region *memory_region, size_t memory_region_max_size,
 	ffa_id_t sender, ffa_id_t receiver,
diff --git a/include/runtime_services/spm_common.h b/include/runtime_services/spm_common.h
index fc7ddd7..e9c845d 100644
--- a/include/runtime_services/spm_common.h
+++ b/include/runtime_services/spm_common.h
@@ -112,6 +112,9 @@
 		     ffa_id_t sender, ffa_id_t receiver,
 		     ffa_memory_region_flags_t flags, uint32_t mem_func);
 
+bool hypervisor_retrieve_request(struct mailbox_buffers *mb, uint64_t handle,
+				 void *out, uint32_t out_size);
+
 /**
  * Helper to conduct a memory relinquish. The caller is usually the receiver,
  * after it being done with the memory shared, identified by the 'handle'.
diff --git a/tftf/tests/runtime_services/secure_service/ffa_helpers.c b/tftf/tests/runtime_services/secure_service/ffa_helpers.c
index 9447a23..e5f93f1 100644
--- a/tftf/tests/runtime_services/secure_service/ffa_helpers.c
+++ b/tftf/tests/runtime_services/secure_service/ffa_helpers.c
@@ -303,6 +303,17 @@
 	       memory_region->receiver_count * sizeof(struct ffa_memory_access);
 }
 
+/**
+ * Configure `region` for a hypervisor retrieve request - i.e. all fields except
+ * `handle` are initialized to 0.
+ */
+void ffa_hypervisor_retrieve_request_init(struct ffa_memory_region *region,
+					  ffa_memory_handle_t handle)
+{
+	memset(region, 0, sizeof(struct ffa_memory_region));
+	region->handle = handle;
+}
+
 /*
  * FFA Version ABI helper.
  * Version fields:
diff --git a/tftf/tests/runtime_services/secure_service/spm_common.c b/tftf/tests/runtime_services/secure_service/spm_common.c
index e76564a..c4323e0 100644
--- a/tftf/tests/runtime_services/secure_service/spm_common.c
+++ b/tftf/tests/runtime_services/secure_service/spm_common.c
@@ -4,6 +4,9 @@
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
+#include "assert.h"
+#include "stdint.h"
+
 #include "ffa_helpers.h"
 #include <cactus_test_cmds.h>
 #include <debug.h>
@@ -277,6 +280,79 @@
 	return true;
 }
 
+bool hypervisor_retrieve_request(struct mailbox_buffers *mb, uint64_t handle,
+				 void *out, uint32_t out_size)
+{
+	struct ffa_value ret;
+	uint32_t fragment_size;
+	uint32_t total_size;
+	struct ffa_memory_region *region_out = out;
+
+	if (out == NULL || mb == NULL) {
+		ERROR("Invalid parameters!\n");
+		return false;
+	}
+
+	ffa_hypervisor_retrieve_request_init(mb->send, handle);
+	ret = ffa_mem_retrieve_req(sizeof(struct ffa_memory_region),
+				   sizeof(struct ffa_memory_region));
+
+	if (ffa_func_id(ret) != FFA_MEM_RETRIEVE_RESP) {
+		ERROR("Couldn't retrieve the memory page. Error: %x\n",
+		      ffa_error_code(ret));
+		return false;
+	}
+
+	/*
+	 * Following total_size and fragment_size are useful to keep track
+	 * of the state of transaction. When the sum of all fragment_size of all
+	 * fragments is equal to total_size, the memory transaction has been
+	 * completed.
+	 * This is a simple test with only one segment. As such, upon
+	 * successful ffa_mem_retrieve_req, total_size must be equal to
+	 * fragment_size.
+	 */
+	total_size = ret.arg1;
+	fragment_size = ret.arg2;
+
+	if (total_size != fragment_size) {
+		ERROR("Only expect one memory segment to be sent!\n");
+		return false;
+	}
+
+	if (fragment_size > PAGE_SIZE) {
+		ERROR("Fragment should be smaller than RX buffer!\n");
+		return false;
+	}
+	if (total_size > out_size) {
+		ERROR("output buffer is not large enough to store all "
+		      "fragments (total_size=%d, max_size=%d)\n",
+		      total_size, out_size);
+		return false;
+	}
+
+	/*
+	 * Copy the received message to the out buffer. This is necessary
+	 * because `mb->recv` will be overwritten if sending a fragmented
+	 * message.
+	 */
+	memcpy(out, mb->recv, total_size);
+
+	if (region_out->receiver_count == 0) {
+		VERBOSE("copied region has no recivers\n");
+		return false;
+	}
+
+	if (region_out->receiver_count > MAX_MEM_SHARE_RECIPIENTS) {
+		VERBOSE("SPMC memory sharing operations support max of %u "
+			"receivers!\n",
+			MAX_MEM_SHARE_RECIPIENTS);
+		return false;
+	}
+
+	return true;
+}
+
 bool memory_relinquish(struct ffa_mem_relinquish *m, uint64_t handle,
 		       ffa_id_t id)
 {
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 d42492b..5b80825 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,10 +1,11 @@
 /*
- * Copyright (c) 2020-2022, Arm Limited. All rights reserved.
+ * Copyright (c) 2020-2023, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
 
 #include <debug.h>
+#include "ffa_helpers.h"
 
 #include <cactus_test_cmds.h>
 #include <ffa_endpoints.h>
@@ -459,3 +460,290 @@
 
 	return TEST_RESULT_SUCCESS;
 }
+
+/**
+ * Print `region` if LOG_LEVEL >= LOG_LEVEL_VERBOSE
+ */
+static void print_memory_region(struct ffa_memory_region *region)
+{
+	VERBOSE("region.sender = %d\n", region->sender);
+	VERBOSE("region.attributes.shareability = %d\n",
+		region->attributes.shareability);
+	VERBOSE("region.attributes.cacheability = %d\n",
+		region->attributes.cacheability);
+	VERBOSE("region.attributes.type = %d\n", region->attributes.type);
+	VERBOSE("region.attributes.security = %d\n",
+		region->attributes.security);
+	VERBOSE("region.flags = %d\n", region->flags);
+	VERBOSE("region.handle = %lld\n", region->handle);
+	VERBOSE("region.tag = %lld\n", region->tag);
+	VERBOSE("region.memory_access_desc_size = %d\n",
+		region->memory_access_desc_size);
+	VERBOSE("region.receiver_count = %d\n", region->receiver_count);
+	VERBOSE("region.receivers_offset = %d\n", region->receivers_offset);
+}
+
+/**
+ * Used by hypervisor retrieve request test: validate descriptors provided by
+ * SPMC.
+ */
+static bool verify_retrieve_response(const struct ffa_memory_region *region1,
+				     const struct ffa_memory_region *region2)
+{
+	if (region1->sender != region2->sender) {
+		ERROR("region1.sender=%d, expected %d\n", region1->sender,
+		      region2->sender);
+		return false;
+	}
+	if (region1->attributes.shareability != region2->attributes.shareability) {
+		ERROR("region1.attributes.shareability=%d, expected %d\n",
+		      region1->attributes.shareability,
+		      region2->attributes.shareability);
+		return false;
+	}
+	if (region1->attributes.cacheability != region2->attributes.cacheability) {
+		ERROR("region1.attributes.cacheability=%d, expected %d\n",
+		      region1->attributes.cacheability,
+		      region2->attributes.cacheability);
+		return false;
+	}
+	if (region1->attributes.type != region2->attributes.type) {
+		ERROR("region1.attributes.type=%d, expected %d\n",
+		      region1->attributes.type, region2->attributes.type);
+		return false;
+	}
+	if (region1->attributes.security != region2->attributes.security) {
+		ERROR("region1.attributes.security=%d, expected %d\n",
+		      region1->attributes.security, region2->attributes.security);
+		return false;
+	}
+	if (region1->flags != region2->flags) {
+		ERROR("region1->flags=%d, expected %d\n", region1->flags,
+		      region2->flags);
+		return false;
+	}
+	if (region1->handle != region2->handle) {
+		ERROR("region1.handle=%lld, expected %lld\n", region1->handle,
+		      region2->handle);
+		return false;
+	}
+	if (region1->tag != region2->tag) {
+		ERROR("region1.tag=%lld, expected %lld\n", region1->tag, region2->tag);
+		return false;
+	}
+	if (region1->memory_access_desc_size != region2->memory_access_desc_size) {
+		ERROR("region1.memory_access_desc_size=%d, expected %d\n",
+		      region1->memory_access_desc_size,
+		      region2->memory_access_desc_size);
+		return false;
+	}
+	if (region1->receiver_count != region2->receiver_count) {
+		ERROR("region1.receiver_count=%d, expected %d\n",
+		      region1->receiver_count, region2->receiver_count);
+		return false;
+	}
+	if (region1->receivers_offset != region2->receivers_offset) {
+		ERROR("region1.receivers_offset=%d, expected %d\n",
+		      region1->receivers_offset, region2->receivers_offset);
+		return false;
+	}
+	for (uint32_t i = 0; i < 3; i++) {
+		if (region1->reserved[i] != 0) {
+			ERROR("region.reserved[%d]=%d, expected 0\n", i,
+			      region1->reserved[i]);
+			return false;
+		}
+	}
+	return true;
+}
+
+/**
+ * Used by hypervisor retrieve request test: validate descriptors provided by
+ * SPMC.
+ */
+static bool
+verify_constituent(struct ffa_memory_region_constituent *constituent,
+		   void *address, uint32_t page_count)
+{
+	if (constituent->address != address) {
+		ERROR("constituent.address=%p, expected %p\n",
+		      constituent->address, address);
+		return false;
+	}
+	if (constituent->page_count != page_count) {
+		ERROR("constituent.page_count=%d, expected %d\n",
+		      constituent->page_count, page_count);
+		return false;
+	}
+	if (constituent->reserved != 0) {
+		ERROR("constituent.reserved=%d, expected 0\n",
+		      constituent->reserved);
+		return false;
+	}
+	return true;
+}
+
+/**
+ * Used by hypervisor retrieve request test: validate descriptors provided by
+ * SPMC.
+ */
+static bool verify_composite(struct ffa_composite_memory_region *composite,
+			     struct ffa_memory_region_constituent *constituent,
+			     uint32_t page_count, uint32_t constituent_count)
+{
+	if (composite->page_count != page_count) {
+		ERROR("composite.page_count=%d, expected %d\n",
+		      composite->page_count, page_count);
+		return false;
+	}
+	if (composite->constituent_count != constituent_count) {
+		ERROR("composite.constituent_count=%d, expected %d\n",
+		      composite->constituent_count, constituent_count);
+		return false;
+	}
+	if (composite->reserved_0 != 0) {
+		ERROR("composite.reserved_0=%llu, expected 0\n",
+		      composite->reserved_0);
+		return false;
+	}
+	for (uint32_t j = 0; j < composite->constituent_count; j++) {
+		if (!verify_constituent(constituent, share_page, 1)) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/**
+ * Helper for performing a hypervisor retrieve request test.
+ */
+static test_result_t hypervisor_retrieve_request_test_helper(uint32_t mem_func)
+{
+	struct ffa_memory_region_constituent sent_constituents[] = {{
+		.address = (void *)share_page,
+		.page_count = 1,
+		.reserved = 0,
+	}};
+	uint32_t sent_constituents_count = ARRAY_SIZE(sent_constituents);
+	__aligned(PAGE_SIZE) static uint8_t page[PAGE_SIZE * 2] = {0};
+	struct ffa_memory_region *hypervisor_retrieve_response =
+		(struct ffa_memory_region *)page;
+	struct ffa_memory_region expected_response;
+	struct mailbox_buffers mb;
+	ffa_memory_handle_t handle;
+	struct ffa_value ret;
+
+	uint32_t expected_flags = 0;
+
+	ffa_memory_attributes_t expected_attrs = {
+		.cacheability = FFA_MEMORY_CACHE_WRITE_BACK,
+		.shareability = FFA_MEMORY_INNER_SHAREABLE,
+		.security = FFA_MEMORY_SECURITY_NON_SECURE,
+		.type = (mem_func != FFA_MEM_SHARE_SMC32)
+				? FFA_MEMORY_NOT_SPECIFIED_MEM
+				: FFA_MEMORY_NORMAL_MEM,
+	};
+
+	CHECK_SPMC_TESTING_SETUP(1, 2, expected_sp_uuids);
+	GET_TFTF_MAILBOX(mb);
+
+	switch (mem_func) {
+	case FFA_MEM_SHARE_SMC32:
+		expected_flags = FFA_MEMORY_REGION_TRANSACTION_TYPE_SHARE;
+		break;
+	case FFA_MEM_LEND_SMC32:
+		expected_flags = FFA_MEMORY_REGION_TRANSACTION_TYPE_LEND;
+		break;
+	case FFA_MEM_DONATE_SMC32:
+		expected_flags = FFA_MEMORY_REGION_TRANSACTION_TYPE_DONATE;
+		break;
+	default:
+		ERROR("Invalid mem_func: %d\n", mem_func);
+		panic();
+	}
+
+	handle = memory_init_and_send(mb.send, MAILBOX_SIZE, SENDER, RECEIVER,
+				      sent_constituents,
+				      sent_constituents_count, mem_func, &ret);
+	if (handle == FFA_MEMORY_HANDLE_INVALID) {
+		ERROR("Memory share failed: %d\n", ffa_error_code(ret));
+		return TEST_RESULT_FAIL;
+	}
+
+	/*
+	 * Send Hypervisor Retrieve request according to section 17.4.3 of FFA
+	 * v1.2-REL0 specification.
+	 */
+	if (!hypervisor_retrieve_request(&mb, handle, page, sizeof(page))) {
+		return TEST_RESULT_FAIL;
+	}
+
+	print_memory_region(hypervisor_retrieve_response);
+
+	/*
+	 * Verify the received `FFA_MEM_RETRIEVE_RESP` aligns with
+	 * transaction description sent above.
+	 */
+	expected_response = (struct ffa_memory_region){
+		.sender = SENDER,
+		.attributes = expected_attrs,
+		.flags = expected_flags,
+		.handle = handle,
+		.tag = 0,
+		.memory_access_desc_size = sizeof(struct ffa_memory_access),
+		.receiver_count = 1,
+		.receivers_offset =
+			offsetof(struct ffa_memory_region, receivers),
+	};
+	if (!verify_retrieve_response(hypervisor_retrieve_response, &expected_response)) {
+		return TEST_RESULT_FAIL;
+	}
+
+	{
+		uint32_t i = 0;
+		struct ffa_composite_memory_region *composite =
+			ffa_memory_region_get_composite(
+				hypervisor_retrieve_response, i);
+		if (composite == NULL) {
+			ERROR("composite %d is null\n", i);
+			return TEST_RESULT_FAIL;
+		}
+
+		if (!verify_composite(composite, &composite->constituents[i],
+				      sent_constituents_count, sent_constituents_count)) {
+			return TEST_RESULT_FAIL;
+		}
+	}
+
+	/*
+	 * Reclaim for the SPMC to deallocate any data related to the handle.
+	 */
+	ret = ffa_mem_reclaim(handle, 0);
+	if (is_ffa_call_error(ret)) {
+		ERROR("Memory reclaim failed: %d\n", ffa_error_code(ret));
+		return TEST_RESULT_FAIL;
+	}
+
+	ret = ffa_rx_release();
+	if (is_ffa_call_error(ret)) {
+		ERROR("rx release failed: %d\n", ffa_error_code(ret));
+		return TEST_RESULT_FAIL;
+	}
+
+	return TEST_RESULT_SUCCESS;
+}
+
+test_result_t test_hypervisor_share_retrieve(void)
+{
+	return hypervisor_retrieve_request_test_helper(FFA_MEM_SHARE_SMC32);
+}
+
+test_result_t test_hypervisor_lend_retrieve(void)
+{
+	return hypervisor_retrieve_request_test_helper(FFA_MEM_LEND_SMC32);
+}
+
+test_result_t test_hypervisor_donate_retrieve(void)
+{
+	return hypervisor_retrieve_request_test_helper(FFA_MEM_DONATE_SMC32);
+}
diff --git a/tftf/tests/tests-spm.xml b/tftf/tests/tests-spm.xml
index e47039f..fbc1763 100644
--- a/tftf/tests/tests-spm.xml
+++ b/tftf/tests/tests-spm.xml
@@ -88,6 +88,12 @@
 
   <testsuite name="FF-A Memory Sharing"
              description="Test FF-A Memory Sharing ABIs" >
+     <testcase name="Hypervisor share + memory retrieve request"
+               function="test_hypervisor_share_retrieve" />
+     <testcase name="Hypervisor lend + memory retrieve request"
+               function="test_hypervisor_lend_retrieve" />
+     <testcase name="Hypervisor donate + memory retrieve request"
+               function="test_hypervisor_donate_retrieve" />
      <testcase name="Lend Memory to Secure World"
                function="test_mem_lend_sp" />
      <testcase name="Lend memory, clear flag set"