Cactus test for exercising SMMUv3 driver to perform stage2 translation

Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
Change-Id: I498cb63aed497ab469a38e486a7943dd634e5b36
diff --git a/include/lib/mmio.h b/include/lib/mmio.h
index e8a7df0..c788af3 100644
--- a/include/lib/mmio.h
+++ b/include/lib/mmio.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2021, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -24,16 +24,33 @@
 	*(volatile uint32_t*)addr = value;
 }
 
+static inline void mmio_write32_offset(uintptr_t addr, uint32_t byte_off,
+					uint32_t data)
+{
+	mmio_write_32((uintptr_t)((uint8_t *)addr + byte_off), data);
+}
+
 static inline uint32_t mmio_read_32(uintptr_t addr)
 {
 	return *(volatile uint32_t*)addr;
 }
 
+static inline uint32_t mmio_read32_offset(uintptr_t addr, uint32_t byte_off)
+{
+	return mmio_read_32((uintptr_t)((uint8_t *)addr + byte_off));
+}
+
 static inline void mmio_write_64(uintptr_t addr, uint64_t value)
 {
 	*(volatile uint64_t*)addr = value;
 }
 
+static inline void mmio_write64_offset(uintptr_t addr, uint32_t byte_off,
+					uint64_t data)
+{
+	mmio_write_64((uintptr_t)((uint8_t *)addr + byte_off), data);
+}
+
 static inline uint64_t mmio_read_64(uintptr_t addr)
 {
 	return *(volatile uint64_t*)addr;
diff --git a/include/runtime_services/cactus_test_cmds.h b/include/runtime_services/cactus_test_cmds.h
index d03a97f..483a7f4 100644
--- a/include/runtime_services/cactus_test_cmds.h
+++ b/include/runtime_services/cactus_test_cmds.h
@@ -299,4 +299,17 @@
 	return (enum interrupt_pin)ret.ret6;
 }
 
+/**
+ * Request to initiate DMA transaction by upstream peripheral.
+ *
+ * The command id is the hex representation of the string "SMMU"
+ */
+#define CACTUS_DMA_SMMUv3_CMD           (0x534d4d55)
+
+static inline smc_ret_values cactus_send_dma_cmd(
+	ffa_vm_id_t source, ffa_vm_id_t dest)
+{
+	return cactus_send_cmd(source, dest, CACTUS_DMA_SMMUv3_CMD, 0, 0, 0,
+			       0);
+}
 #endif
diff --git a/plat/arm/fvp/include/platform_def.h b/plat/arm/fvp/include/platform_def.h
index ed45642..3afc9b8 100644
--- a/plat/arm/fvp/include/platform_def.h
+++ b/plat/arm/fvp/include/platform_def.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2020, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2021, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -217,7 +217,11 @@
 #define MAX_XLAT_TABLES			20
 #define MAX_MMAP_REGIONS		50
 #else
+#if IMAGE_CACTUS
+#define MAX_XLAT_TABLES			6
+#else
 #define MAX_XLAT_TABLES			5
+#endif
 #define MAX_MMAP_REGIONS		16
 #endif
 
diff --git a/spm/cactus/cactus.mk b/spm/cactus/cactus.mk
index a52120f..ae66c1d 100644
--- a/spm/cactus/cactus.mk
+++ b/spm/cactus/cactus.mk
@@ -49,6 +49,7 @@
 		cactus_test_ffa.c 			\
 		cactus_test_interrupts.c		\
 		cactus_test_memory_sharing.c		\
+		cactus_tests_smmuv3.c			\
 	)
 
 # TODO: Remove dependency on TFTF files.
diff --git a/spm/cactus/cactus_main.c b/spm/cactus/cactus_main.c
index 45d2db0..ff3f618 100644
--- a/spm/cactus/cactus_main.c
+++ b/spm/cactus/cactus_main.c
@@ -93,6 +93,9 @@
 	/* PLAT_ARM_DEVICE0 area includes UART2 necessary to console */
 	MAP_REGION_FLAT(PLAT_ARM_DEVICE0_BASE, PLAT_ARM_DEVICE0_SIZE,
 			MT_DEVICE | MT_RW),
+	/* scratch memory allocated to be used for running SMMU tests */
+	MAP_REGION_FLAT(PLAT_CACTUS_MEMCPY_BASE, PLAT_CACTUS_MEMCPY_RANGE,
+			MT_MEMORY | MT_RW),
 	{0}
 };
 
diff --git a/spm/cactus/cactus_tests/cactus_tests_smmuv3.c b/spm/cactus/cactus_tests/cactus_tests_smmuv3.c
new file mode 100644
index 0000000..ce53dc6
--- /dev/null
+++ b/spm/cactus/cactus_tests/cactus_tests_smmuv3.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdint.h>
+
+#include <arch_helpers.h>
+#include "cactus.h"
+#include "cactus_message_loop.h"
+#include <cactus_platform_def.h>
+#include "cactus_test_cmds.h"
+#include "cactus_tests.h"
+#include <debug.h>
+#include <ffa_helpers.h>
+#include <mmio.h>
+#include "smmuv3_test_engine.h"
+#include <sp_helpers.h>
+#include <spm_common.h>
+
+/* Source and target address for memcopy operation */
+#define MEMCPY_SOURCE_BASE	PLAT_CACTUS_MEMCPY_BASE
+#define MEMPCY_TOTAL_SIZE	(PLAT_CACTUS_MEMCPY_RANGE / 2)
+#define MEMCPY_TARGET_BASE	(MEMCPY_SOURCE_BASE + MEMPCY_TOTAL_SIZE)
+
+/* Miscellaneous */
+#define NO_SUBSTREAMID	(0xFFFFFFFFU)
+#define TRANSFER_SIZE	(MEMPCY_TOTAL_SIZE / FRAME_COUNT)
+#define LOOP_COUNT	(5000U)
+
+static bool run_smmuv3_test(void)
+{
+	uint64_t source_addr, cpy_range, target_addr;
+	uint64_t begin_addr, end_addr, dest_addr;
+	uint32_t status;
+	unsigned int i, f, attempts;
+
+	/*
+	 * The test engine's MEMCPY command copies data from the region in
+	 * range [begin, end_incl] to the region with base address as udata.
+	 * In this test, we configure the test engine to initiate memcpy from
+	 * scratch page located at MEMCPY_SOURCE_BASE to the page located at
+	 * address MEMCPY_TARGET_BASE
+	 */
+
+	VERBOSE("CACTUS: Running SMMUv3 test\n");
+
+	source_addr = MEMCPY_SOURCE_BASE;
+	cpy_range = MEMPCY_TOTAL_SIZE;
+	target_addr = MEMCPY_TARGET_BASE;
+	uint32_t streamID_list[] = { 0U, 1U };
+
+	uint64_t data[] = {
+		ULL(0xBAADFEEDCEEBDAAF),
+		ULL(0x0123456776543210)
+	};
+
+	/* Write pre-determined content to source pages */
+	for (i = 0U; i < (cpy_range / 8U); i++) {
+		mmio_write64_offset(source_addr, i * 8, data[i%2]);
+	}
+
+	/* Clean the data caches */
+	clean_dcache_range(source_addr, cpy_range);
+
+	/*
+	 * Make sure above load, store and cache maintenance instructions
+	 * complete before we start writing to TestEngine frame configuration
+	 * fields
+	 */
+	dsbsy();
+
+	for (f = 0U; f < FRAME_COUNT; f++) {
+		attempts = 0U;
+		begin_addr = source_addr + (TRANSFER_SIZE * f);
+		end_addr = begin_addr + TRANSFER_SIZE - 1U;
+		dest_addr = target_addr + (TRANSFER_SIZE * f);
+
+		/* Initiate DMA sequence */
+		mmio_write32_offset(PRIV_BASE_FRAME + F_IDX(f), PCTRL_OFF, 0);
+		mmio_write32_offset(PRIV_BASE_FRAME + F_IDX(f), DOWNSTREAM_PORT_OFF, 0);
+		mmio_write32_offset(PRIV_BASE_FRAME + F_IDX(f), STREAM_ID_OFF, streamID_list[f%2]);
+		mmio_write32_offset(PRIV_BASE_FRAME + F_IDX(f), SUBSTREAM_ID_OFF, NO_SUBSTREAMID);
+
+		mmio_write32_offset(USR_BASE_FRAME + F_IDX(f), UCTRL_OFF, 0);
+		mmio_write32_offset(USR_BASE_FRAME + F_IDX(f), SEED_OFF, 0);
+		mmio_write64_offset(USR_BASE_FRAME + F_IDX(f), BEGIN_OFF, begin_addr);
+		mmio_write64_offset(USR_BASE_FRAME + F_IDX(f), END_CTRL_OFF, end_addr);
+
+		/* Legal values for stride: 1 and any multiples of 8 */
+		mmio_write64_offset(USR_BASE_FRAME + F_IDX(f), STRIDE_OFF, 1);
+		mmio_write64_offset(USR_BASE_FRAME + F_IDX(f), UDATA_OFF, dest_addr);
+
+		mmio_write32_offset(USR_BASE_FRAME + F_IDX(f), CMD_OFF, ENGINE_MEMCPY);
+		VERBOSE("SMMUv3TestEngine: Waiting for MEMCPY completion for frame: %u\n", f);
+
+		/*
+		 * It is guaranteed that a read of "cmd" fields after writing to it will
+		 * immediately return ENGINE_FRAME_MISCONFIGURED if the command was
+		 * invalid.
+		 */
+		if (mmio_read32_offset(USR_BASE_FRAME + F_IDX(f), CMD_OFF) == ENGINE_MIS_CFG) {
+			ERROR("SMMUv3TestEngine: Misconfigured for frame: %u\n", f);
+			return false;
+		}
+
+		/* Wait for mem copy to be complete */
+		while (attempts++ < LOOP_COUNT) {
+			status = mmio_read32_offset(USR_BASE_FRAME + F_IDX(f), CMD_OFF);
+			if (status == ENGINE_HALTED) {
+				break;
+			} else if (status == ENGINE_ERROR) {
+				ERROR("SMMUv3: Test failed\n");
+				return false;
+			}
+
+			/*
+			 * TODO: Introduce a small delay here to make sure the
+			 * CPU memory accesses do not starve the interconnect
+			 * due to continuous polling.
+			 */
+		}
+
+		if (attempts == LOOP_COUNT) {
+			ERROR("SMMUv3: Test failed\n");
+			return false;
+		}
+
+		dsbsy();
+	}
+
+	/*
+	 * Invalidate cached entries to force the CPU to fetch the data from
+	 * Main memory
+	 */
+	inv_dcache_range(source_addr, cpy_range);
+	inv_dcache_range(target_addr, cpy_range);
+
+	/* Compare source and destination memory locations for data */
+	for (i = 0U; i < (cpy_range / 8U); i++) {
+		if (mmio_read_64(source_addr + 8 * i) != mmio_read_64(target_addr + 8 * i)) {
+			ERROR("SMMUv3: Mem copy failed: %llx\n", target_addr + 8 * i);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+CACTUS_CMD_HANDLER(smmuv3_cmd, CACTUS_DMA_SMMUv3_CMD)
+{
+	smc_ret_values ffa_ret;
+	ffa_vm_id_t vm_id = ffa_dir_msg_dest(*args);
+	ffa_vm_id_t source = ffa_dir_msg_source(*args);
+
+	VERBOSE("Received request through direct message for DMA service\n");
+
+	/*
+	 * At present, the test cannot be run concurrently on multiple SPs as
+	 * there is only one SMMUv3TestEngine IP in the FVP model. Hence, run
+	 * the test only on the first SP.
+	 */
+	if (vm_id != SPM_VM_ID_FIRST) {
+		return cactus_error_resp(vm_id, source, 0);
+	}
+
+	if (run_smmuv3_test()) {
+		ffa_ret = cactus_success_resp(vm_id, source, 0);
+	} else {
+		ffa_ret = cactus_error_resp(vm_id, source, 0);
+	}
+
+	return ffa_ret;
+}
diff --git a/spm/cactus/cactus_tests/smmuv3_test_engine.h b/spm/cactus/cactus_tests/smmuv3_test_engine.h
new file mode 100644
index 0000000..32d86ac
--- /dev/null
+++ b/spm/cactus/cactus_tests/smmuv3_test_engine.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/* The test engine supports numerous frames but we only use a few */
+#define FRAME_COUNT	(2U)
+#define FRAME_SIZE	(0x80U) /* 128 bytes */
+#define F_IDX(n)	(n * FRAME_SIZE)
+
+/* Commands supported by SMMUv3TestEngine built into the AEM */
+#define ENGINE_NO_FRAME	(0U)
+#define ENGINE_HALTED	(1U)
+
+/*
+ * ENGINE_MEMCPY: Read and Write transactions
+ * ENGINE_RAND48: Only Write transactions: Source address not required
+ * ENGINE_SUM64: Only read transactions: Target address not required
+ */
+#define ENGINE_MEMCPY	(2U)
+#define ENGINE_RAND48	(3U)
+#define ENGINE_SUM64	(4U)
+#define ENGINE_ERROR	(0xFFFFFFFFU)
+#define ENGINE_MIS_CFG	(ENGINE_ERROR - 1)
+
+/*
+ * Refer to:
+ * https://developer.arm.com/documentation/100964/1111-00/Trace-components/SMMUv3TestEngine---trace
+ */
+
+/* Offset of various control fields belonging to User Frame */
+#define CMD_OFF		(0x0U)
+#define UCTRL_OFF	(0x4U)
+#define SEED_OFF	(0x24U)
+#define BEGIN_OFF	(0x28U)
+#define END_CTRL_OFF	(0x30U)
+#define STRIDE_OFF	(0x38U)
+#define UDATA_OFF	(0x40U)
+
+/* Offset of various control fields belonging to PRIV Frame */
+#define PCTRL_OFF		(0x0U)
+#define DOWNSTREAM_PORT_OFF	(0x4U)
+#define STREAM_ID_OFF		(0x8U)
+#define SUBSTREAM_ID_OFF	(0xCU)
diff --git a/spm/cactus/plat/arm/fvp/fdts/cactus.dts b/spm/cactus/plat/arm/fvp/fdts/cactus.dts
index eb569f7..1c28fde 100644
--- a/spm/cactus/plat/arm/fvp/fdts/cactus.dts
+++ b/spm/cactus/plat/arm/fvp/fdts/cactus.dts
@@ -62,6 +62,24 @@
 			pages-count = <4>;
 			attributes = <0x7>; /* read-write-execute */
 		};
+
+		/*
+		 * Scratch memory used for the purpose of testing SMMUv3 driver
+		 * through Cactus SP
+		 */
+		smmuv3-memcpy-src {
+			description = "smmuv3-memcpy-source";
+			pages-count = <4>;
+			base-address = <0x00000000 0x7400000>;
+			attributes = <0x3>; /* read-write */
+		};
+
+		smmuv3-memcpy-dst {
+			description = "smmuv3-memcpy-destination";
+			pages-count = <4>;
+			base-address = <0x00000000 0x7404000>;
+			attributes = <0x3>; /* read-write */
+		};
 	};
 
 	device-regions {
@@ -73,6 +91,20 @@
 			attributes = <0x3>; /* read-write */
 		};
 
+		smmuv3-testengine {
+			/*
+			 * SMMUv3TestEngine is a DMA IP modeled in the
+			 * Base-RevC FVP Model.
+			 * User Frame: 0x2bfe0000
+			 * Privileged Frame: 0x2bff0000
+			 */
+			base-address = <0x00000000 0x2bfe0000>;
+			pages-count = <32>; /* Two 64KB pages */
+			attributes = <0x3>; /* read-write */
+			smmu-id = <0>;
+			stream-ids = <0x0 0x1>;
+		};
+
 		test-reg {
 			/* Dummy Values */
 			base-address = <0x00000000 0x22000000>;
diff --git a/spm/cactus/plat/arm/fvp/include/cactus_platform_def.h b/spm/cactus/plat/arm/fvp/include/cactus_platform_def.h
index b4c57ef..8940c83 100644
--- a/spm/cactus/plat/arm/fvp/include/cactus_platform_def.h
+++ b/spm/cactus/plat/arm/fvp/include/cactus_platform_def.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Arm Limited. All rights reserved.
+ * Copyright (c) 2020-2021, Arm Limited. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -18,8 +18,16 @@
 #define PLAT_CACTUS_RX_BASE		ULL(0x7300000)
 #define PLAT_CACTUS_CORE_COUNT		(8U)
 
+/* Scratch memory used for SMMUv3 driver testing purposes in Cactus SP */
+#define PLAT_CACTUS_MEMCPY_BASE		ULL(0x7400000)
+#define PLAT_CACTUS_MEMCPY_RANGE	ULL(0x8000)
+
 #define CACTUS_PRIMARY_EC_COUNT		(8U)
 #define CACTUS_SECONDARY_EC_COUNT	(8U)
 #define CACTUS_TERTIARY_EC_COUNT	(1U)
 
+/* Base address of user and PRIV frames in SMMUv3TestEngine */
+#define USR_BASE_FRAME			ULL(0x2BFE0000)
+#define PRIV_BASE_FRAME			ULL(0x2BFF0000)
+
 #endif /* CACTUS_PLATFORM_DEF_H */
diff --git a/tftf/tests/runtime_services/secure_service/test_spm_smmu.c b/tftf/tests/runtime_services/secure_service/test_spm_smmu.c
new file mode 100644
index 0000000..b041a97
--- /dev/null
+++ b/tftf/tests/runtime_services/secure_service/test_spm_smmu.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <cactus_test_cmds.h>
+#include <debug.h>
+#include <ffa_endpoints.h>
+#include <smccc.h>
+#include <test_helpers.h>
+
+static const struct ffa_uuid expected_sp_uuids[] = { {PRIMARY_UUID} };
+
+/**************************************************************************
+ * Send a command to SP1 initiate DMA service with the help of a peripheral
+ * device upstream of an SMMUv3 IP
+ **************************************************************************/
+test_result_t test_smmu_spm(void)
+{
+	smc_ret_values ret;
+
+	/**********************************************************************
+	 * Check SPMC has ffa_version and expected FFA endpoints are deployed.
+	 **********************************************************************/
+	CHECK_SPMC_TESTING_SETUP(1, 0, expected_sp_uuids);
+
+	VERBOSE("Sending command to SP %x for initiating DMA transfer\n",
+			SP_ID(1));
+	ret = cactus_send_dma_cmd(HYP_ID, SP_ID(1));
+
+	if (cactus_get_response(ret) != CACTUS_SUCCESS) {
+		return TEST_RESULT_FAIL;
+	}
+
+	return TEST_RESULT_SUCCESS;
+}
+
diff --git a/tftf/tests/tests-spm.mk b/tftf/tests/tests-spm.mk
index 9364052..e62e03d 100644
--- a/tftf/tests/tests-spm.mk
+++ b/tftf/tests/tests-spm.mk
@@ -15,4 +15,5 @@
 		test_ffa_rxtx_map.c					\
 		test_ffa_version.c					\
 		test_spm_cpu_features.c					\
+		test_spm_smmu.c						\
 	)
diff --git a/tftf/tests/tests-spm.xml b/tftf/tests/tests-spm.xml
index 01ebcea..32efc16 100644
--- a/tftf/tests/tests-spm.xml
+++ b/tftf/tests/tests-spm.xml
@@ -84,4 +84,9 @@
                function="test_ffa_ns_interrupt" />
   </testsuite>
 
+  <testsuite name="SMMUv3 tests"
+             description="Initiate stage2 translation for streams from upstream peripherals" >
+     <testcase name="Check DMA command by SMMUv3TestEngine completes"
+               function="test_smmu_spm" />
+  </testsuite>
 </testsuites>