feat(unittests): add a unit test framework.

This patch adds all the infrastructure needed to run unit tests for
RMM, including a new variant for platform host, called `host_test`.

To build and run the tests:

cmake -DRMM_CONFIG=host_defcfg -DHOST_VARIANT=host_test \
  -DCMAKE_BUILD_TYPE=Debug -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
cmake --build ${RMM_BUILD_DIR} -- run-unittests

Signed-off-by: Javier Almansa Sobrino <javier.almansasobrino@arm.com>
Signed-off-by: Soby Mathew <soby.mathew@arm.com>
Change-Id: If16686e111d91c563f8e7281d4ee7ca2864125ae
diff --git a/.gitmodules b/.gitmodules
index b994ee1..091b707 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -8,3 +8,6 @@
 [submodule "ext/t_cose"]
 	path = ext/t_cose
 	url = https://github.com/matetothpal/t_cose.git
+[submodule "ext/cpputest"]
+	path = ext/cpputest
+	url = https://github.com/cpputest/cpputest.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 80229dc..a793590 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,7 +48,7 @@
 # and also where the project directory and version variables are set up.
 #
 
-project(RMM VERSION 0.2.0 LANGUAGES ASM C)
+project(RMM VERSION 0.2.0 LANGUAGES ASM C CXX)
 
 #
 # Set global flags.
@@ -67,6 +67,11 @@
 include("cmake/Platforms.cmake")
 
 #
+# Include the Unit Test Framework
+#
+include(UnitTestFramework)
+
+#
 # Include the common configuration options
 #
 include("cmake/CommonConfigs.cmake")
diff --git a/cmake/BuildCppUTest.cmake b/cmake/BuildCppUTest.cmake
new file mode 100644
index 0000000..df87e3c
--- /dev/null
+++ b/cmake/BuildCppUTest.cmake
@@ -0,0 +1,17 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+#
+
+#
+# Remove Werror from CXXFLAGS else CppUTest compiler checks will fail.
+# This will affect CMAKE_CXX_FLAG in the current scope and parent scope
+# is unaffected.
+#
+string(REPLACE "-Werror" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
+
+# Additional CXXFLAGS to get CppUTest to compile.
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat-pedantic ")
+
+add_subdirectory("ext/cpputest")
+
diff --git a/cmake/CommonConfigs.cmake b/cmake/CommonConfigs.cmake
index 96dc650..6f743ca 100644
--- a/cmake/CommonConfigs.cmake
+++ b/cmake/CommonConfigs.cmake
@@ -51,7 +51,7 @@
 endif()
 
 target_compile_definitions(rmm-common
-    INTERFACE "MAX_CPUS=U(${MAX_CPUS})")
+    INTERFACE "MAX_CPUS=${MAX_CPUS}U")
 
 if(NOT(GRANULE_SIZE EQUAL 4096))
     message(FATAL_ERROR "GRANULE_SIZE is not initialized correctly")
diff --git a/cmake/Modules/UnitTestFramework.cmake b/cmake/Modules/UnitTestFramework.cmake
new file mode 100644
index 0000000..758bce9
--- /dev/null
+++ b/cmake/Modules/UnitTestFramework.cmake
@@ -0,0 +1,138 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+#
+
+#[=======================================================================[.rst:
+rmm_build_unittest
+------------------
+
+.. default-domain:: unit tests
+
+.. command:: rmm_build_unittest
+
+Build a unit test group for a given target
+
+.. code:: cmake
+
+    rmm_build_unittest(NAME <name> TARGET <target> SOURCES <sources>
+                       [RUN_ISOLATED_TESTS <LIST of tests to run>]
+                       [LIBRARIES <libraries_to_link>]
+                       [ITERATIONS <iterations>])
+
+This helper function simplifies the mechanics to setup and enable an unit test.
+
+Basics
+^^^^^^
+
+Every unit test configuration has the following parameters (defined as
+strings):
+
+- ``NAME`` Name of the test. It must match the name of the CppUtest test group.
+- ``TARGET`` Target where the tests will be linked against.
+- ``SOURCES`` Source files for the tests. This is usually a single C++ file.
+- ``RUN_ISOLATED_TESTS`` Optional parameter that specifies a list of tests
+                         implemented within ``SOURCES`` to be run. When this
+                         list is specified, the binary is re-spawned for each
+                         test and only executed once (``ITERATIONS`` is
+                         ignored). Any test not included on the list will be
+                         ignored.
+                         If this parameter is not used, all the tests included
+                         in the group will be run automatically by CppUTest,
+                         the number of times specified by ``ITERATIONS``
+- ``LIBRARIES`` Optional parameter to define libraries to link against
+                the tests.
+- ``ITERATIONS`` Optional parameter that defines how many times the test will
+                 run. By default it is 1 times.
+                 This option is ignored when using ``RUN_ISOLATED_TESTS``
+
+#]=======================================================================]
+
+if(RMM_UNITTESTS)
+    include("${CMAKE_SOURCE_DIR}/cmake/BuildCppUTest.cmake")
+
+    # Clean ${IMPORT_TEST_GROUPS}, used to generate test_groups.h later.
+    SET(IMPORT_TEST_GROUPS "" CACHE INTERNAL "IMPORT_TEST_GROUP List")
+
+    # Generate an empty test_groups.h, needed if we don't have unittests
+    configure_file(${CMAKE_SOURCE_DIR}/plat/host/host_test/src/test_groups.h.in
+                   ${CMAKE_BINARY_DIR}/plat/host/host_test/src/test_groups.h
+                   @ONLY)
+
+    # Include CTest for unittests
+    include(CTest)
+
+    # Custom target to run the unit tests
+    add_custom_target(run-unittests
+        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
+        COMMAND ctest -C "$<CONFIG>"
+        DEPENDS rmm.elf rmm.map
+    )
+endif()
+
+function(rmm_build_unittest)
+    if(RMM_UNITTESTS)
+        set(_options "")
+        set(_multi_args "SOURCES;LIBRARIES;RUN_ISOLATED_TESTS")
+        set(_single_args "NAME;TARGET;ITERATIONS")
+
+        cmake_parse_arguments(
+            arg "${_options}" "${_single_args}" "${_multi_args}" ${ARGN})
+
+        if("NAME" IN_LIST arg_KEYWORDS_MISSING_VALUES OR
+            NOT DEFINED arg_NAME)
+            message(FATAL_ERROR "Missing unit test name")
+        endif()
+
+        if("TARGET" IN_LIST arg_KEYWORDS_MISSING_VALUES OR
+            NOT DEFINED arg_TARGET)
+            message(FATAL_ERROR "Missing test target")
+        endif()
+
+        if("SOURCES" IN_LIST arg_KEYWORDS_MISSING_VALUES OR
+            NOT DEFINED arg_SOURCES)
+            message(FATAL_ERROR "Missing test sources")
+        endif()
+
+        if("ITERATIONS" IN_LIST arg_KEYWORDS_MISSING_VALUES OR
+            NOT DEFINED arg_ITERATIONS)
+            set(arg_ITERATIONS "1")
+        endif()
+
+        target_sources("${arg_TARGET}"
+            PRIVATE ${arg_SOURCES})
+
+        target_link_libraries("${arg_TARGET}"
+            PRIVATE CppUTest ${arg_LIBRARIES})
+
+        # Add the test to the CMake test builder, so we can automate
+        # the test run process.
+        if("RUN_ISOLATED_TESTS" IN_LIST arg_KEYWORDS_MISSING_VALUES OR
+            NOT DEFINED arg_RUN_ISOLATED_TESTS)
+            # Run all tests at once
+            add_test(NAME "${arg_NAME}"
+                    COMMAND ${CMAKE_BINARY_DIR}/rmm.elf
+                            -g${arg_NAME}
+                            -r${arg_ITERATIONS})
+        else()
+            # Register a test for each test case, so each one on them can
+            # run on isolation.
+            foreach(TEST IN LISTS arg_RUN_ISOLATED_TESTS)
+                add_test(NAME "${arg_NAME}::${TEST}"
+                         COMMAND ${CMAKE_BINARY_DIR}/rmm.elf
+                                 -sg${arg_NAME}
+                                 -sn${TEST})
+            endforeach()
+        endif()
+
+        # Use CppUtest IMPORT_TEST_GROUP macro to explicitly include the new test
+        # group. This is needed as otherwise the linker will ignore the test code.
+        SET(IMPORT_TEST_GROUPS "${IMPORT_TEST_GROUPS} IMPORT_TEST_GROUP(${arg_NAME});"
+            CACHE INTERNAL "IMPORT_TEST_GROUP List")
+
+        # Generate the test_groups.h
+        configure_file(${CMAKE_SOURCE_DIR}/plat/host/host_test/src/test_groups.h.in
+                       ${CMAKE_BINARY_DIR}/plat/host/host_test/src/test_groups.h
+                       @ONLY)
+    endif()
+endfunction()
diff --git a/docs/getting_started/build-options.rst b/docs/getting_started/build-options.rst
index ab787a7..d41a145 100644
--- a/docs/getting_started/build-options.rst
+++ b/docs/getting_started/build-options.rst
@@ -162,6 +162,16 @@
     cmake -DRMM_CONFIG=fvp_defcfg -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
     cmake --build ${RMM_BUILD_DIR} -- checkincludes-codebase
 
+14.  Perform unit tests on development host:
+
+Build and run unit tests on host platform. It is recommended to do the Debug
+build of RMM.
+
+.. code-block:: bash
+
+    cmake -DRMM_CONFIG=host_defcfg -DHOST_VARIANT=host_test -DCMAKE_BUILD_TYPE=Debug -S ${RMM_SOURCE_DIR} -B ${RMM_BUILD_DIR}
+    cmake --build ${RMM_BUILD_DIR} -- run-unittests
+
 .. _build_options_table:
 
 ###################
@@ -198,6 +208,7 @@
    MBEDTLS_ECP_MAX_OPS		,248 -			,1000			,"Number of max operations per ECC signing iteration"
    RMM_FPU_USE_AT_REL2		,ON | OFF		,OFF(fake_host) ON(aarch64),"Enable FPU/SIMD usage in RMM."
    RMM_MAX_GRANULES		,			,0			,"Maximum number of memory granules available to the system"
+   HOST_VARIANT			,host_build | host_test	,host_build		, "Variant to build for the host platform. Only available when RMM_PLATFORM=host"
 
 
 
diff --git a/docs/readme.rst b/docs/readme.rst
index 25a7e1d..c198f72 100644
--- a/docs/readme.rst
+++ b/docs/readme.rst
@@ -64,7 +64,7 @@
 
 The TF-RMM project requires to be linked with certain other 3rd party projects
 and they are to be cloned from their repositories into ``ext`` folder before
-building. The projects are `MbedTLS`_, `t_cose`_, and `QCBOR`_.
+building. The projects are `MbedTLS`_, `t_cose`_, `QCBOR`_ and `CppUTest`_.
 
 The project also contains files which are imported from other projects
 into the source tree and may have a different license. Such files with
@@ -119,3 +119,4 @@
 .. _BSD-3-Clause License: https://tf-rmm.readthedocs.io/en/latest/about/license.html
 .. _License and Copyright for Contributions: https://tf-rmm.readthedocs.io/en/latest/process/contributing.html#license-and-copyright-for-contributions
 .. _Contributor's Guide: https://tf-rmm.readthedocs.io/en/latest/process/contributing.html
+.. _CppUTest: https://github.com/cpputest/cpputest.git
diff --git a/ext/cpputest b/ext/cpputest
new file mode 160000
index 0000000..67d2dfd
--- /dev/null
+++ b/ext/cpputest
@@ -0,0 +1 @@
+Subproject commit 67d2dfd41e13f09ff218aa08e2d35f1c32f032a1
diff --git a/lib/arch/include/fake_host/instr_helpers.h b/lib/arch/include/fake_host/instr_helpers.h
index 5113cc6..50cf362 100644
--- a/lib/arch/include/fake_host/instr_helpers.h
+++ b/lib/arch/include/fake_host/instr_helpers.h
@@ -9,6 +9,20 @@
 #include <host_harness.h>
 #include <stddef.h>
 
+#ifdef __cplusplus
+/*
+ * Disable write-strings warnings when building C++ code (used for unit
+ * testing) as ISO C++ forbits converting a string constant to char*,
+ * which is actually done by DEFINE_SYSREG_{READ, WRITE}_FUNC macros.
+ */
+
+#ifdef __clang__
+	#pragma clang diagnostic ignored "-Wwrite-strings"
+#else
+	#pragma GCC diagnostic ignored "-Wwrite-strings"
+#endif /* __clang__ */
+#endif /* __cplusplus__ */
+
 /**********************************************************************
  * Macros which create inline functions to read or write CPU system
  * registers
diff --git a/lib/arch/include/fake_host/memory.h b/lib/arch/include/fake_host/memory.h
index 3f10f44..3f182a4 100644
--- a/lib/arch/include/fake_host/memory.h
+++ b/lib/arch/include/fake_host/memory.h
@@ -14,27 +14,27 @@
 {
 	*ptr = val;
 }
-#define SCA_WRITE64(_p, _v) __sca_write64((void *)(_p), ((uint64_t)(_v)))
+#define SCA_WRITE64(_p, _v) __sca_write64((uint64_t *)(_p), ((uint64_t)(_v)))
 
 /* Single-Copy Atomic 64-bit write with RELEASE memory ordering semantics*/
 static inline void __sca_write64_release(uint64_t *ptr, uint64_t val)
 {
 	*ptr = val;
 }
-#define SCA_WRITE64_RELEASE(_p, _v) __sca_write64_release((void *)(_p), ((uint64_t)(_v)))
+#define SCA_WRITE64_RELEASE(_p, _v) __sca_write64_release((uint64_t *)(_p), ((uint64_t)(_v)))
 
 /* Single-Copy Atomic 64-bit read */
 static inline uint64_t __sca_read64(uint64_t *ptr)
 {
 	return *ptr;
 }
-#define SCA_READ64(_p) ((typeof(*(_p)))__sca_read64((void *)(_p)))
+#define SCA_READ64(_p) ((typeof(*(_p)))__sca_read64((uint64_t *)(_p)))
 
 /* Single-Copy Atomic 64-bit read with ACQUIRE memory ordering semantics */
 static inline uint64_t __sca_read64_acquire(uint64_t *ptr)
 {
 	return *ptr;
 }
-#define SCA_READ64_ACQUIRE(_p) ((typeof(*(_p)))__sca_read64_acquire((void *)(_p)))
+#define SCA_READ64_ACQUIRE(_p) ((typeof(*(_p)))__sca_read64_acquire((uint64_t *)(_p)))
 
 #endif /* MEMORY_H */
diff --git a/lib/realm/include/granule.h b/lib/realm/include/granule.h
index 34595c5..69d3b9e 100644
--- a/lib/realm/include/granule.h
+++ b/lib/realm/include/granule.h
@@ -221,7 +221,7 @@
 
 	g = find_lock_granule(addr, expected_state);
 	if (g == NULL) {
-		return status_ptr(RMI_ERROR_INPUT);
+		return (struct granule *)status_ptr(RMI_ERROR_INPUT);
 	}
 
 	/*
@@ -230,7 +230,7 @@
 	 */
 	if (granule_refcount_read_acquire(g)) {
 		granule_unlock(g);
-		return status_ptr(RMI_ERROR_IN_USE);
+		return (struct granule *)status_ptr(RMI_ERROR_IN_USE);
 	}
 
 	return g;
diff --git a/lib/realm/include/status.h b/lib/realm/include/status.h
index 51b48f1..f12335f 100644
--- a/lib/realm/include/status.h
+++ b/lib/realm/include/status.h
@@ -81,7 +81,7 @@
 static inline return_code_t make_return_code(unsigned int status,
 					     unsigned int index)
 {
-	return (return_code_t) {status, index};
+	return (return_code_t) {(status_t)status, index};
 }
 
 /*
diff --git a/plat/host/CMakeLists.txt b/plat/host/CMakeLists.txt
index 62fe84b..fc078ff 100644
--- a/plat/host/CMakeLists.txt
+++ b/plat/host/CMakeLists.txt
@@ -3,6 +3,21 @@
 # SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
 #
 
+arm_config_option(
+    NAME HOST_VARIANT
+    HELP "Select the variant to use for the host platform"
+    TYPE STRING
+    STRINGS "host_build" "host_test"
+    DEFAULT "host_build")
+
+if(HOST_VARIANT STREQUAL host_test)
+    # Disable CppUtest self tests
+    set(TESTS "OFF" CACHE STRING "Compile and run CppUTest tests")
+
+    # Disable CppUtest Extension Library
+    set(EXTENSIONS "OFF" CACHE STRING "Use the CppUTest extension library")
+endif()
+
 add_subdirectory("../common" ${RMM_BINARY_DIR}/plat/common)
 add_subdirectory("common")
-add_subdirectory("host_build")
+add_subdirectory("${HOST_VARIANT}")
diff --git a/plat/host/common/CMakeLists.txt b/plat/host/common/CMakeLists.txt
index 5bbd41a..f358fa7 100644
--- a/plat/host/common/CMakeLists.txt
+++ b/plat/host/common/CMakeLists.txt
@@ -6,8 +6,8 @@
 add_library(rmm-host-common)
 
 target_link_libraries(rmm-host-common
-    PRIVATE  rmm-plat-common
-             rmm-lib)
+    PRIVATE  rmm-lib
+             rmm-plat-common)
 
 target_sources(rmm-host-common
     PRIVATE "src/host_harness_cmn.c"
diff --git a/plat/host/common/src/host_platform_api_cmn.c b/plat/host/common/src/host_platform_api_cmn.c
index a5cf7e0..2c4fe45 100644
--- a/plat/host/common/src/host_platform_api_cmn.c
+++ b/plat/host/common/src/host_platform_api_cmn.c
@@ -60,8 +60,8 @@
 unsigned long plat_granule_addr_to_idx(unsigned long addr)
 {
 	if (!(GRANULE_ALIGNED(addr) &&
-		(addr < (host_util_get_granule_base() + HOST_MEM_SIZE))) &&
-		(addr >= host_util_get_granule_base())) {
+		(addr < (host_util_get_granule_base() + HOST_MEM_SIZE)) &&
+		(addr >= host_util_get_granule_base()))) {
 		return UINT64_MAX;
 	}
 
diff --git a/plat/host/host_build/src/host_setup.c b/plat/host/host_build/src/host_setup.c
index d1b3c98..dc8379e 100644
--- a/plat/host/host_build/src/host_setup.c
+++ b/plat/host/host_build/src/host_setup.c
@@ -96,6 +96,7 @@
 	 */
 	enable_fake_host_mmu();
 
+	/* Start RMM */
 	rmm_main();
 
 	VERBOSE("RMM: Fake Host execution completed\n");
diff --git a/plat/host/host_test/CMakeLists.txt b/plat/host/host_test/CMakeLists.txt
new file mode 100644
index 0000000..c28fb13
--- /dev/null
+++ b/plat/host/host_test/CMakeLists.txt
@@ -0,0 +1,33 @@
+#
+# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+#
+
+add_library(rmm-plat-host_test)
+
+# host_test builds unittests for RMM
+arm_config_option(
+    NAME RMM_UNITTESTS
+    HELP "Enable Unitests for the build"
+    DEFAULT "ON"
+    TYPE INTERNAL)
+
+target_link_libraries(rmm-plat-host_test
+    PRIVATE rmm-lib
+            CppUTest
+    # Needed to export host_utils.h
+    PUBLIC  rmm-host-common)
+
+# Used to locate test_groups.h
+target_include_directories(rmm-plat-host_test
+    PRIVATE "${CMAKE_BINARY_DIR}/plat/host/${HOST_VARIANT}/src")
+
+target_include_directories(rmm-plat-host_test
+    PUBLIC "include")
+
+target_sources(rmm-plat-host_test
+    PRIVATE "src/test_main.cpp"
+            "src/host_harness.c"
+            "src/test_helpers.c")
+
+add_library(rmm-platform ALIAS rmm-plat-host_test)
diff --git a/plat/host/host_test/include/test_harness.h b/plat/host/host_test/include/test_harness.h
new file mode 100644
index 0000000..db4f045
--- /dev/null
+++ b/plat/host/host_test/include/test_harness.h
@@ -0,0 +1,34 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#ifndef TEST_HARNESS_H
+#define TEST_HARNESS_H
+
+#include <buffer.h>
+
+/*
+ * Below functions are to be defined by the tests and allow them to implement
+ * specific host harness APIs as defined in host_harness.h
+ */
+
+/*
+ * Map a given granule address to a specific slot buffer
+ * Args
+ *	slot - Slot buffer type where to map to
+ *	addr - Granule address to map
+ *	ns - Flag to indicate if the granule is a non-secure one
+ * Return
+ *	The VA (or platform equivalent) where the granule was mapped to
+ */
+void *test_buffer_map(enum buffer_slot slot,
+		      unsigned long addr, bool ns);
+
+/*
+ * Unmap a given granule from its corresponding slot buffer given the
+ * mapped granule address.
+ */
+void test_buffer_unmap(void *buf);
+
+#endif /* TEST_HARNESS */
diff --git a/plat/host/host_test/include/test_helpers.h b/plat/host/host_test/include/test_helpers.h
new file mode 100644
index 0000000..1ff027d
--- /dev/null
+++ b/plat/host/host_test/include/test_helpers.h
@@ -0,0 +1,23 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+/*
+ * Helper function to fully initialize RMM.
+ *
+ * Args
+ *	secondaries - If true, support for secondary PEs is enabled.
+ */
+void test_helper_rmm_start(bool secondaries);
+
+/*
+ * Helper function to get the total number of memory granules available
+ * to the system.
+ */
+unsigned int test_helper_get_nr_granules(void);
+
+#endif
diff --git a/plat/host/host_test/src/host_harness.c b/plat/host/host_test/src/host_harness.c
new file mode 100644
index 0000000..4357c29
--- /dev/null
+++ b/plat/host/host_test/src/host_harness.c
@@ -0,0 +1,23 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#include <host_harness.h>
+#include <test_harness.h>
+
+/*
+ * Maps addr to the requested slot buffer and returns a pointer to the
+ * fake VA for the slot (the current addr), so the host can perform R/W
+ * operations on the mapped granule.
+ */
+void *host_buffer_arch_map(enum buffer_slot slot,
+			   unsigned long addr, bool ns)
+{
+	return test_buffer_map(slot, addr, ns);
+}
+
+void host_buffer_arch_unmap(void *buf)
+{
+	test_buffer_unmap(buf);
+}
diff --git a/plat/host/host_test/src/test_groups.h.in b/plat/host/host_test/src/test_groups.h.in
new file mode 100644
index 0000000..b3a548c
--- /dev/null
+++ b/plat/host/host_test/src/test_groups.h.in
@@ -0,0 +1,15 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#ifndef TEST_GROUPS_H
+#define TEST_GROUPS_H
+
+/*
+ * Add an IMPORT_TEST_GROUP for each test group, so the linker can
+ * find the tests.
+ */
+@IMPORT_TEST_GROUPS@
+
+#endif
diff --git a/plat/host/host_test/src/test_helpers.c b/plat/host/host_test/src/test_helpers.c
new file mode 100644
index 0000000..bef4759
--- /dev/null
+++ b/plat/host/host_test/src/test_helpers.c
@@ -0,0 +1,146 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#include <gic.h>
+#include <host_defs.h>
+#include <host_utils.h>
+#include <platform_api.h>
+#include <rmm_el3_ifc.h>
+#include <xlat_tables.h>
+
+/* Implemented in init.c and needed here */
+void rmm_warmboot_main(void);
+void rmm_main(void);
+
+/*
+ * Define and set the Boot Interface arguments.
+ */
+#define RMM_EL3_IFC_ABI_VERSION		(RMM_EL3_IFC_SUPPORTED_VERSION)
+#define RMM_EL3_MAX_CPUS		(MAX_CPUS)
+
+static unsigned char el3_rmm_shared_buffer[PAGE_SIZE] __aligned(PAGE_SIZE);
+
+/*
+ * Create a basic boot manifest.
+ */
+static struct rmm_core_manifest *boot_manifest =
+			(struct rmm_core_manifest *)el3_rmm_shared_buffer;
+
+/*
+ * Performs some initialization needed before RMM can be run, such as
+ * setting up callbacks for sysreg access.
+ */
+static void setup_sysreg_and_boot_manifest(void)
+{
+	/*
+	 * Initialize ID_AA64MMFR0_EL1 with a physical address
+	 * range of 18 bits
+	 */
+	(void)host_util_set_default_sysreg_cb("id_aa64mmfr0_el1",
+			INPLACE(ID_AA64MMFR0_EL1_PARANGE, 5UL));
+
+	/*
+	 * Initialize ICH_VTR_EL2 with 6 preemption bits.
+	 * (PREbits is equal number of preemption bits minus one)
+	 */
+	(void)host_util_set_default_sysreg_cb("ich_vtr_el2",
+			INPLACE(ICH_VTR_EL2_PRE_BITS, 5UL));
+
+	/* Used to hold CPU Id. Reset to CPUid 0. */
+	(void)host_util_set_default_sysreg_cb("tpidr_el2", 0UL);
+
+	(void)host_util_set_default_sysreg_cb("sctlr_el2", 0UL);
+
+	/* Initialize the boot manifest */
+	boot_manifest->version = RMM_EL3_IFC_SUPPORTED_VERSION;
+	boot_manifest->plat_data = (uintptr_t)NULL;
+}
+
+/*
+ * Function to emulate the turn on of the MMU for the fake_host architecture.
+ */
+static void enable_fake_mmu(void)
+{
+	write_sctlr_el2(SCTLR_EL2_WXN | SCTLR_EL2_M);
+}
+
+static void start_primary_pe(void)
+{
+	host_util_set_cpuid(0U);
+
+	/* Early setup the CpuId into tpidr_el2 */
+	write_tpidr_el2(0U);
+
+	plat_setup(0UL,
+		   RMM_EL3_IFC_ABI_VERSION,
+		   RMM_EL3_MAX_CPUS,
+		   (uintptr_t)&el3_rmm_shared_buffer);
+
+	/*
+	 * Enable the MMU. This is needed as some initialization code
+	 * called by rmm_main() asserts that the mmu is enabled.
+	 */
+	enable_fake_mmu();
+
+	/*
+	 * rmm_main() finishhes the warmboot path.
+	 *
+	 * Note: It is expected that the attestation init will fail.
+	 */
+	rmm_main();
+}
+
+static void start_secondary_pe(unsigned int cpuid)
+{
+	host_util_set_cpuid(cpuid);
+
+	/*
+	 * Early setup the CpuId into tpidr_el2 for each secondary.
+	 */
+	write_tpidr_el2(cpuid);
+
+	plat_warmboot_setup(0UL,
+			    RMM_EL3_IFC_ABI_VERSION,
+			    RMM_EL3_MAX_CPUS,
+			    (uintptr_t)&el3_rmm_shared_buffer);
+
+	/*
+	 * Enable the MMU. This is needed to avoid assertions during boot up
+	 * that would otherwise occur if the MMU is disabled.
+	 */
+	enable_fake_mmu();
+
+	/*
+	 * Finalize the warmboot path.
+	 * This enables the slot buffer mechanism.
+	 */
+	rmm_warmboot_main();
+}
+
+void test_helper_rmm_start(bool secondaries)
+{
+	static bool initialized;
+
+	if (initialized == false) {
+		/* Enable RMM and setup basic structures for each test. */
+		setup_sysreg_and_boot_manifest();
+
+		/* bringup primary CPU */
+		start_primary_pe();
+
+		if (secondaries) {
+			for (unsigned int i = 1U; i < RMM_EL3_MAX_CPUS; i++) {
+				start_secondary_pe(i);
+			}
+			host_util_set_cpuid(0U);
+		}
+		initialized = true;
+	}
+}
+
+unsigned int test_helper_get_nr_granules(void)
+{
+	return HOST_NR_GRANULES;
+}
diff --git a/plat/host/host_test/src/test_main.cpp b/plat/host/host_test/src/test_main.cpp
new file mode 100644
index 0000000..a1c57e8
--- /dev/null
+++ b/plat/host/host_test/src/test_main.cpp
@@ -0,0 +1,13 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ * SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
+ */
+
+#include <CppUTest/CommandLineTestRunner.h>
+#include <CppUTest/TestHarness.h>
+#include <test_groups.h>
+
+int main (int argc, char** argv)
+{
+        return CommandLineTestRunner::RunAllTests(argc, argv);
+}
diff --git a/toolchains/common.cmake b/toolchains/common.cmake
index 4d710fb..86d6fb6 100644
--- a/toolchains/common.cmake
+++ b/toolchains/common.cmake
@@ -11,7 +11,7 @@
 set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
 set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
 
-foreach(language IN ITEMS ASM C)
+foreach(language IN ITEMS ASM C CXX)
     string(APPEND CMAKE_${language}_FLAGS_INIT "-fno-common ")
     string(APPEND CMAKE_${language}_FLAGS_INIT "-fomit-frame-pointer ")
     string(APPEND CMAKE_${language}_FLAGS_INIT "-ffunction-sections ")
diff --git a/toolchains/fake_host/gnu.cmake b/toolchains/fake_host/gnu.cmake
index ca2f48f..70a1c34 100644
--- a/toolchains/fake_host/gnu.cmake
+++ b/toolchains/fake_host/gnu.cmake
@@ -12,6 +12,14 @@
     DOC "Path to gcc."
     REQUIRED)
 
+#
+# Needed to build CppUTest for unit tests
+#
+find_program(CMAKE_CXX_COMPILER
+    NAMES "g++"
+    DOC "Path to g++."
+    REQUIRED)
+
 set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
 
 string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT "-Wl,--build-id=none ")
diff --git a/toolchains/fake_host/llvm.cmake b/toolchains/fake_host/llvm.cmake
index cb0f207..ad2ee5e 100644
--- a/toolchains/fake_host/llvm.cmake
+++ b/toolchains/fake_host/llvm.cmake
@@ -14,9 +14,10 @@
 
 set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER})
 
-foreach(language IN ITEMS ASM C)
+foreach(language IN ITEMS ASM C CXX)
     string(APPEND CMAKE_${language}_FLAGS_INIT "-Wno-unknown-warning-option ")
     string(APPEND CMAKE_${language}_FLAGS_INIT "-Wno-unused-function ")
+    string(APPEND CMAKE_${language}_FLAGS_INIT "-fPIC ")
 endforeach()
 
 string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT "-Wl,--build-id=none ")