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/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);
+}