Add libfdt unit test cases

This patch incorporates the standalone libfdt unit tests written by Andre
Przywara into the unit test framework.  These tests use DTB files to
exercise the functions in libfdt and fdt_wrappers.c to ensure that they work
correctly.

Several of these DTBs cannot be supplied in this repository due to license
restrictions, they are found in the Linux kernel source tree and in the
trusted firmware a source tree.  A script has been provided to build these
DTB files and place them in the correct location to be used and can be
found in tests/lib/fdt/device_trees, it requires the two source trees to
be provided as arguments.

At some point we'd like to include DTS files in this repository to make
things easier but for now they are external dependencies.

Signed-off-by: John Powell <john.powell@arm.com>
Co-authored-by: Andre Przywara <andre.przywara@arm.com>
Change-Id: Ieccc61f84fc6ae91d6871224e8538e2d78218444
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 71bb31c..6e807c0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,3 +202,4 @@
 include(tests/bl1/test_bl1_fwu.cmake)
 include(tests/lib/libc/test_libc.cmake)
 include(tests/lib/object_pool/test_object_pool.cmake)
+include(tests/lib/fdt/test_fdt.cmake)
diff --git a/tests/lib/fdt/device_trees/build_dtb.sh b/tests/lib/fdt/device_trees/build_dtb.sh
new file mode 100755
index 0000000..30ee90b
--- /dev/null
+++ b/tests/lib/fdt/device_trees/build_dtb.sh
@@ -0,0 +1,82 @@
+#
+# Copyright (c) 2020, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+LINUX_SOURCE=$1
+TFA_SOURCE=$2
+DTS_LINUX64_JUNO_R1="$LINUX_SOURCE/arch/arm64/boot/dts/arm/juno-r1.dts"
+DTS_LINUX64_SUN50I_A64_PINE64_PLUS="$LINUX_SOURCE/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-plus.dts"
+DTS_LINUX32_ATLAS7_EVB="$LINUX_SOURCE/arch/arm/boot/dts/atlas7-evb.dts"
+DTS_LINUX32_BCM2837_RPI_3_B_PLUS="$LINUX_SOURCE/arch/arm/boot/dts/bcm2837-rpi-3-b-plus.dts"
+DTS_TEST="test.dts"
+DTS_FVP_BASE_GICV3_PSCI="$TFA_SOURCE/fdts/fvp-base-gicv3-psci.dts"
+
+process_linux_dts()
+{
+	# DTS file path in first argument
+	# DTB file in 2nd argument
+	# Use global LINUX_SOURCE
+	DTS_FILE=$1
+	DTB_FILE=$2
+
+	cpp -I $LINUX_SOURCE/include -x assembler-with-cpp -o $DTS_FILE.preprocessed $DTS_FILE
+	sed -i -e '/stdc-predef.h/d' $DTS_FILE.preprocessed -e 's/1,pci-domain/linux,pci-domain/g' $DTS_FILE.preprocessed
+	dtc -O dtb -o $DTB_FILE -i $DTS_FILE -b 0 $DTS_FILE.preprocessed
+	rm $DTS_FILE.preprocessed
+}
+
+process_tfa_dts()
+{
+	# DTS file path in first argument
+	# DTB file in 2nd argument
+	DTS_FILE=$1
+	DTB_FILE=$2
+	cpp -I $TFA_SOURCE/include -x assembler-with-cpp -o $DTS_FILE.preprocessed $DTS_FILE
+	sed -i -e '/stdc-predef.h/d' $DTS_FILE.preprocessed -e 's/1,pci-domain/linux,pci-domain/g' $DTS_FILE.preprocessed
+	dtc -O dtb -o $DTB_FILE -i $DTS_FILE -b 0 $DTS_FILE.preprocessed
+	rm $DTS_FILE.preprocessed
+}
+
+process_dts()
+{
+	# DTS file path in first argument
+	# DTB file in 2nd argument
+	DTS_FILE=$1
+	DTB_FILE=$2
+	dtc -o $DTB_FILE $DTS_FILE
+}
+
+# basic argument checks
+if [[ ( "$1" == "-h" ) || ( "$1" == "--help" ) ]]; then
+	echo "Usage: $0 [OPTION] path/to/linux/source path/to/tfa/source"
+	echo ""
+	echo "  This script will build the necessary DTB files from their"
+	echo "  sources in the Linux kernel and TFA repositories. Provide the"
+	echo "  path to the Linux source tree as the first argument to this"
+	echo "  script and the path to the TFA source tree as the second"
+	echo "  argument."
+	echo ""
+	echo "  -h, --help      print this help text"
+	exit 0
+fi
+if [[ ( -z "$1" ) || ( -z "$2" ) ]]; then
+	echo "Invalid arguments, use -h/--help for more info."
+	exit 1
+fi
+
+# create a folder for new DTB files to be placed in
+mkdir dtb
+
+# generate linux DTB files
+process_linux_dts $DTS_LINUX64_JUNO_R1 dtb/juno-r1.dtb
+process_linux_dts $DTS_LINUX64_SUN50I_A64_PINE64_PLUS dtb/sun50i-a64-pine64-plus.dtb
+process_linux_dts $DTS_LINUX32_ATLAS7_EVB dtb/atlas7-evb.dtb
+process_linux_dts $DTS_LINUX32_BCM2837_RPI_3_B_PLUS dtb/bcm2837-rpi-3-b-plus.dtb
+
+# generate TFA DTB file
+process_tfa_dts $DTS_FVP_BASE_GICV3_PSCI dtb/fvp-base-gicv3-psci.dtb
+
+# generate test DTB file
+process_dts $DTS_TEST dtb/test.dtb
diff --git a/tests/lib/fdt/device_trees/test.dts b/tests/lib/fdt/device_trees/test.dts
new file mode 100644
index 0000000..ce10e99
--- /dev/null
+++ b/tests/lib/fdt/device_trees/test.dts
@@ -0,0 +1,91 @@
+/dts-v1/;
+/{
+    #address-cells = <2>;
+    #size-cells = <2>;
+    dev1@184090000 {
+        compatible = "acme,device1";
+        reg = <0x1 0x84090000 0 0x10000>;
+        address = <0x1 0x84090000 0 0x10000>;
+    };
+    dev2@40000 {
+        compatible = "acme,device2";
+        reg = <0 0x40000 0 0x10000>;
+        address = <0 0x40000 0 0x10000>;
+    };
+    transparent-bus {
+        compatible = "simple-bus";
+        #address-cells = <2>;
+        #size-cells = <2>;
+        ranges;
+        dev3@1c00000 {
+            compatible = "acme,device3";
+            reg = <0 0x1c00000 0x0 0x40000>;
+            address = <0 0x1c00000 0x0 0x40000>;
+        };
+        dev4@901300000 {
+            compatible = "acme,device4";
+            reg = <9 0x1300000 0x0 0x40000>;
+            address = <9 0x1300000 0x0 0x40000>;
+        };
+    };
+    legacy-bus@40000000 {
+        compatible = "simple-bus";
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges = <0x0 0 0x40000000 0x10000000>;
+        dev5@0 {
+            compatible = "acme,device5";
+            reg = <0x0 0x10000>;
+            address = <0x0 0x40000000 0x0 0x10000>;
+        };
+
+        dev6@90000 {
+            compatible = "acme,device6";
+            reg = <0x90000 0x10000>;
+            address = <0x0 0x40090000 0x0 0x10000>;
+        };
+        large-bus@8000000 {
+            compatible = "simple-bus";
+            #address-cells = <2>;
+            #size-cells = <1>;
+            ranges = <0x0 0x1000000 0x8000000 0x8000000>;
+            /* outside of any mapping entry */
+            dev7@800000 {
+                compatible = "acme,device7";
+                reg = <0 0x800000 0x1000>;
+                address = <0xffffffff 0xffffffff 0x0 0x1000>;
+            };
+            dev8@1400000 {
+                compatible = "acme,device8";
+                reg = <0 0x1400000 0x1000>;
+                address = <0 0x48400000 0x0 0x1000>;
+            };
+        };
+    };
+    high-bus@c0000000 {
+        compatible = "simple-bus";
+        #address-cells = <2>;
+        #size-cells = <1>;
+        ranges = <0 0x0 0 0xc0000000 0x10000000>,
+             <1 0x0 2 0x80000000 0x10000000>,
+             <3 0x0 4 0xc0000000 0x10000000>,
+             <9 0x0 3 0x40000000 0x10000000>,
+             <5 0x0 9 0xd0000000 0x10000000>;
+        dev9@100030000 {
+            compatible = "acme,device9";
+            reg = <1 0x30000 0x10000>;
+            address = <0x2 0x80030000 0x0 0x10000>;
+        };
+        dev10@300180000 {
+            compatible = "acme,device10";
+            reg = <3 0x180000 0x10000>;
+            address = <4 0xc0180000 0 0x10000>;
+        };
+        /* not actually mapped */
+        dev11@400080000 {
+            compatible = "acme,device11";
+            reg = <4 0x80000 0x10000>;
+            address = <0xffffffff 0xffffffff 0x0 0x10000>;
+        };
+    };
+};
diff --git a/tests/lib/fdt/test_fdt.cmake b/tests/lib/fdt/test_fdt.cmake
new file mode 100644
index 0000000..098e4ae
--- /dev/null
+++ b/tests/lib/fdt/test_fdt.cmake
@@ -0,0 +1,43 @@
+#
+# Copyright (c) 2019-2020, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+include(UnitTest)
+
+unit_test_add_suite(
+	NAME fdt
+	SOURCES
+		${CMAKE_CURRENT_LIST_DIR}/test_fdt.cpp
+		${TF_A_UNIT_TESTS_PATH}/mocks/common/debug.cpp
+		${TF_A_PATH}/common/fdt_wrappers.c
+		${TF_A_PATH}/lib/libc/strlcpy.c
+		${TF_A_PATH}/lib/libfdt/fdt.c
+		${TF_A_PATH}/lib/libfdt/fdt_addresses.c
+		${TF_A_PATH}/lib/libfdt/fdt_empty_tree.c
+		${TF_A_PATH}/lib/libfdt/fdt_overlay.c
+		${TF_A_PATH}/lib/libfdt/fdt_ro.c
+		${TF_A_PATH}/lib/libfdt/fdt_rw.c
+		${TF_A_PATH}/lib/libfdt/fdt_strerror.c
+		${TF_A_PATH}/lib/libfdt/fdt_sw.c
+		${TF_A_PATH}/lib/libfdt/fdt_wip.c
+	INCLUDE_DIRECTORIES
+		${TF_A_UNIT_TESTS_PATH}/mocks/include/
+		${TF_A_UNIT_TESTS_PATH}/mocks/include/lib/libc/
+		${TF_A_PATH}/include/
+		${TF_A_PATH}/include/lib/libfdt/
+	COMPILE_DEFINITIONS
+		# Several additional DTB files from the Linux kernel and the TFA source
+		# are needed to run these tests.  They cannot be included here due to
+		# license restrictions but can be built from the sources using the
+		# script device_trees/build_dtb.sh.  The script takes the paths to these
+		# two source repositories as arguments, builds the DTB files, and places
+		# them in a folder here called dtb.
+		TFA_FVP_DTB_PATH="${CMAKE_CURRENT_LIST_DIR}/device_trees/dtb/fvp-base-gicv3-psci.dtb"
+		LINUX64_JUNO_R1_DTB="${CMAKE_CURRENT_LIST_DIR}/device_trees/dtb/juno-r1.dtb"
+		LINUX64_SUN50I_PINE64_PLUS_DTB="${CMAKE_CURRENT_LIST_DIR}/device_trees/dtb/sun50i-a64-pine64-plus.dtb"
+		LINUX32_ATLAS7_EVB_DTB="${CMAKE_CURRENT_LIST_DIR}/device_trees/dtb/atlas7-evb.dtb"
+		LINUX32_BCM2837_RPI_3_B_DTB="${CMAKE_CURRENT_LIST_DIR}/device_trees/dtb/bcm2837-rpi-3-b-plus.dtb"
+		TEST_DTB="${CMAKE_CURRENT_LIST_DIR}/device_trees/dtb/test.dtb"
+)
diff --git a/tests/lib/fdt/test_fdt.cpp b/tests/lib/fdt/test_fdt.cpp
new file mode 100644
index 0000000..c7b7e06
--- /dev/null
+++ b/tests/lib/fdt/test_fdt.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2020, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include "CppUTest/TestHarness.h"
+
+extern "C" {
+#include <common/fdt_wrappers.h>
+#include <libfdt.h>
+#include <stdio.h>
+#include <string.h>
+}
+
+#define FDT_SUCCESS (0)
+
+#define FILE_NOT_FOUND_HELP \
+"          The DTB file used in this test must be generated manually before\n" \
+"          running the test suite.  There is a script build_dtb.sh found in\n" \
+"          tests/lib/fdt/device_trees which can be used to do this."
+
+static int check_translation(const void *fdt, int node, uintptr_t *address)
+{
+	const char *prop;
+	int len, ret;
+	uintptr_t addr;
+
+	prop = (const char *)fdt_getprop(fdt, node, "compatible", &len);
+	if (!prop) {
+		return 3;
+	}
+
+	ret = fdt_get_reg_props_by_index(fdt, node, 0, &addr, NULL);
+	if (ret)
+	{
+		return 4;
+	}
+	addr = fdtw_translate_address(fdt, node, addr);
+
+	*address = addr;
+	return 0;
+}
+
+static uint64_t read_check_address (unsigned char *prop)
+{
+	uint64_t res = 0;
+	/* Address field is stored as 4 big endian uint32_t values. */
+	for (int i = 0; i < 8; i++)
+	{
+		res = res | ((uint64_t)prop[i] << (56-(i*8)));
+	}
+	return res;
+}
+
+static int run_test(const char *name, const char *dtb_path, const char *dev,
+			uintptr_t expected)
+{
+	FILE *dtb;
+	size_t size;
+	int offs;
+	int ret;
+	int len;
+	uintptr_t address;
+	unsigned char *prop;
+	void *fdt_buffer;
+
+	printf("FDT Translation Test Case: %s\n", name);
+	printf("  DTB file: %s\n", dtb_path);
+	if (dev != NULL)
+	{
+		printf("  Device: %s\n", dev);
+	}
+	else
+	{
+		printf("  Device: stdout\n");
+	}
+
+	/* Read DTB file into buffer */
+	dtb = fopen(dtb_path, "rb");
+	if (dtb == NULL)
+	{
+		printf("  [ERROR] Could not open device tree binary \"%s\"\n",
+			dtb_path);
+		printf("%s", FILE_NOT_FOUND_HELP);
+		return -1;
+	}
+
+	/* Get size and allocate buffer. */
+	fseek(dtb, 0, SEEK_END);
+	size = ftell(dtb);
+	fdt_buffer = malloc(size);
+	if (fdt_buffer == NULL)
+	{
+		printf("  [ERROR] Could not allocate file buffer.\n");
+		return -1;
+	}
+	rewind(dtb);
+
+	/* Read file into buffer and close it. */
+	if(fread(fdt_buffer, 1, size, dtb) != size)
+	{
+		printf("  [ERROR] Could not read input file.\n");
+		free(fdt_buffer);
+		return -1;
+	}
+	fclose(dtb);
+
+	ret = fdt_check_header(fdt_buffer);
+	if (ret) {
+		printf("  [ERROR] Invalid DTB header.\n");
+		free(fdt_buffer);
+		return ret;
+	}
+
+	if (dev != NULL)
+	{
+		/* If a specific device is given, look for it. */
+		offs = fdt_node_offset_by_compatible(fdt_buffer, -1, dev);
+	}
+	else
+	{
+		/* If no device specified, look for stdout node. */
+		offs = fdt_get_stdout_node_offset(fdt_buffer);
+	}
+	if (offs < 0) {
+		printf("  [ERROR] Could not find requested node.\n");
+		ret = offs;
+		free(fdt_buffer);
+		return ret;
+	}
+
+	/* Get address of device. */
+	ret = check_translation(fdt_buffer, offs, &address);
+	if (ret != 0)
+	{
+		free(fdt_buffer);
+		return ret;
+	}
+
+	/* Verify address translation. */
+	if (expected == 0)
+	{
+		/* Retrieve expected result from DTB "address" property. */
+		prop = (unsigned char *)fdt_getprop(fdt_buffer, offs, "address",
+							&len);
+		if ((prop == NULL) || (len != 16))
+		{
+			printf("  [ERROR] Expected len %d, got %d\n", 16, len);
+			free(fdt_buffer);
+			return -1;
+		}
+		expected = read_check_address(prop);
+	}
+
+	printf("  Expected: 0x%0lX\n", expected);
+	printf("  Found:    0x%0lX\n", address);
+	if (address != expected)
+	{
+		printf("  TEST FAILED\n");
+		free(fdt_buffer);
+		return -1;
+	}
+
+	printf("  TEST SUCCEEDED\n");
+	free(fdt_buffer);
+	return 0;
+}
+
+TEST_GROUP(fdt)
+{
+	/* Nothing to declare here. */
+};
+
+TEST(fdt, test_fvp)
+{
+	int result = run_test("test_fvp", TFA_FVP_DTB_PATH, "arm,sp804",
+				0x1C110000);
+	CHECK_EQUAL(result, 0);
+}
+
+TEST(fdt, test_juno_r1_a)
+{
+	/* Search for stdout with NULL compatible string. */
+	int result = run_test("test_juno_r1_a", LINUX64_JUNO_R1_DTB, NULL,
+				0x7FF80000);
+	CHECK_EQUAL(result, 0);
+}
+
+TEST(fdt, test_juno_r1_b)
+{
+	int result = run_test("test_juno_r1_b", LINUX64_JUNO_R1_DTB,
+				"arm,sp804", 0x1C110000);
+	CHECK_EQUAL(result, 0);
+}
+
+TEST(fdt, test_sun50i)
+{
+	int result = run_test("test_sun50i_pine64_plus",
+				LINUX64_SUN50I_PINE64_PLUS_DTB,
+				"allwinner,sun50i-a64-de2-rotate",
+				0x01020000);
+	CHECK_EQUAL(result, 0);
+}
+
+TEST(fdt, test_atlas)
+{
+	int result = run_test("test_atlas_7_evb", LINUX32_ATLAS7_EVB_DTB,
+				"sirf,prima2-pwm", 0x18630000);
+	CHECK_EQUAL(result, 0);
+}
+
+TEST(fdt, test_bcm2837)
+{
+	int result = run_test("test_bcm2837_rpi_3", LINUX32_BCM2837_RPI_3_B_DTB,
+				"brcm,bcm2835-txp", 0x3F004000);
+	CHECK_EQUAL(result, 0);
+}
+
+TEST(fdt, test_generic)
+{
+	int failcount = 0;
+	int i = 0;
+	char device[15] = {0};
+
+	/* Count from 1 to 11 */
+	for (int i = 1; i < 12; i++)
+	{
+		sprintf(device, "acme,device%d", i);
+		if (run_test("test_generic", TEST_DTB, device, 0) != 0)
+		{
+			failcount++;
+		}
+	}
+
+	CHECK_EQUAL(failcount, 0);
+}