Support multiple ways of driving hftests, add UART
Move logic that gets the boot arguments of hftest to a separate build
target and implement the same interface for communicating over UART.
Add SerialDriver to hftest.py that drives the communication with a
device over serial port.
The UART backend for hftest depends on an implementation of
'hftest_device' interface to reboot the board when the test has
finished.
Change-Id: Idfcb2c6e9af9cd283e360993fa2580d4fea49594
diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile
index 1eb6b2d..1d4a3d5 100644
--- a/build/docker/Dockerfile
+++ b/build/docker/Dockerfile
@@ -38,5 +38,7 @@
python \
python-git `# for Linux checkpatch` \
python-ply `# for Linux checkpatch` \
+ python3 `# for all build scripts` \
+ python3-serial `# for hftest.py` \
strace `# for strace_open.sh` \
&& rm -rf /var/lib/apt/lists/*
diff --git a/inc/hf/plat/console.h b/inc/hf/plat/console.h
index 8fb4969..0584fbd 100644
--- a/inc/hf/plat/console.h
+++ b/inc/hf/plat/console.h
@@ -26,5 +26,8 @@
void plat_console_mm_init(struct mm_stage1_locked stage1_locked,
struct mpool *ppool);
-/** Puts a single character on the console. */
+/** Puts a single character on the console. This is a blocking call. */
void plat_console_putchar(char c);
+
+/** Gets a single character from the console. This is a blocking call. */
+char plat_console_getchar(void);
diff --git a/test/hftest/BUILD.gn b/test/hftest/BUILD.gn
index d65397b..db97cef 100644
--- a/test/hftest/BUILD.gn
+++ b/test/hftest/BUILD.gn
@@ -14,6 +14,11 @@
import("//build/toolchain/platform.gni")
+declare_args() {
+ hftest_ctrl = ":ctrl_fdt"
+ hftest_device = ""
+}
+
config("hftest_config") {
include_dirs = [ "//test/inc" ]
}
@@ -96,12 +101,12 @@
":mm",
":power_mgmt",
"//src:dlog",
- "//src:fdt",
"//src:memiter",
"//src/arch/${plat_arch}:entry",
"//src/arch/${plat_arch}/hftest:entry",
"//src/arch/${plat_arch}/hftest:interrupts",
"//src/arch/${plat_arch}/hftest:power_mgmt",
+ hftest_ctrl,
]
}
@@ -153,3 +158,39 @@
"//src/arch/${plat_arch}/hftest:power_mgmt",
]
}
+
+source_set("ctrl_fdt") {
+ testonly = true
+
+ public_configs = [ ":hftest_config" ]
+
+ sources = [
+ "ctrl_fdt.c",
+ ]
+
+ deps = [
+ "//src:dlog",
+ "//src:fdt",
+ "//src:memiter",
+ ]
+}
+
+source_set("ctrl_uart") {
+ testonly = true
+
+ public_configs = [ ":hftest_config" ]
+
+ sources = [
+ "ctrl_uart.c",
+ ]
+
+ deps = [
+ "//src:dlog",
+ "//src:memiter",
+ plat_console,
+ ]
+
+ if (hftest_device != "") {
+ deps += [ hftest_device ]
+ }
+}
diff --git a/test/hftest/ctrl_fdt.c b/test/hftest/ctrl_fdt.c
new file mode 100644
index 0000000..85fe1cb
--- /dev/null
+++ b/test/hftest/ctrl_fdt.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/hftest.h"
+
+bool hftest_ctrl_start(const struct fdt_header *fdt, struct memiter *cmd)
+{
+ struct fdt_node n;
+ const char *bootargs;
+ uint32_t bootargs_size;
+
+ if (!fdt_root_node(&n, fdt)) {
+ HFTEST_LOG("FDT failed validation.");
+ return false;
+ }
+
+ if (!fdt_find_child(&n, "")) {
+ HFTEST_LOG("Unable to find root node in FDT.");
+ return false;
+ }
+
+ if (!fdt_find_child(&n, "chosen")) {
+ HFTEST_LOG("Unable to find 'chosen' node in FDT.");
+ return false;
+ }
+
+ if (!fdt_read_property(&n, "bootargs", &bootargs, &bootargs_size)) {
+ HFTEST_LOG("Unable to read bootargs.");
+ return false;
+ }
+
+ /* Remove null terminator. */
+ memiter_init(cmd, bootargs, bootargs_size - 1);
+ return true;
+}
+
+void hftest_ctrl_finish(void)
+{
+ /* Nothing to do. */
+}
diff --git a/test/hftest/ctrl_uart.c b/test/hftest/ctrl_uart.c
new file mode 100644
index 0000000..da4d0d6
--- /dev/null
+++ b/test/hftest/ctrl_uart.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2020 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hf/plat/console.h"
+
+#include "test/hftest.h"
+
+/* clang-format off */
+#define CMD_GET_COMMAND_LINE "[hftest_ctrl:get_command_line]\n"
+#define CMD_FINISHED "[hftest_ctrl:finished]\n"
+/* clang-format on */
+
+static char command_line[128];
+
+static void write(const char *str)
+{
+ while (*str != '\0') {
+ plat_console_putchar(*str);
+ str++;
+ }
+}
+
+static bool read(char *buf, size_t max_len, struct memiter *str)
+{
+ char c;
+ size_t len = 0;
+
+ while (true) {
+ c = plat_console_getchar();
+ if (c == '\r' || c == '\n') {
+ memiter_init(str, buf, len);
+ return true;
+ }
+
+ if (len < max_len) {
+ buf[len++] = c;
+ } else {
+ return false;
+ }
+ }
+}
+
+bool hftest_ctrl_start(const struct fdt_header *fdt, struct memiter *cmd)
+{
+ (void)fdt;
+
+ /* Initialize the console */
+ plat_console_init();
+
+ /* Inform the host that we are ready to receive the command line. */
+ write(CMD_GET_COMMAND_LINE);
+
+ /* Read command line from the console. */
+ read(command_line, ARRAY_SIZE(command_line), cmd);
+
+ return true;
+}
+
+void hftest_ctrl_finish(void)
+{
+ /*
+ * Inform the host that this test has finished running and all
+ * subsequent logs belong to the next run.
+ */
+ write(CMD_FINISHED);
+
+ /* Reboot the device. */
+ hftest_device_reboot();
+}
diff --git a/test/hftest/hftest.py b/test/hftest/hftest.py
index cd60585..91c2782 100755
--- a/test/hftest/hftest.py
+++ b/test/hftest/hftest.py
@@ -28,6 +28,7 @@
import json
import os
import re
+import serial
import subprocess
import sys
@@ -35,6 +36,9 @@
HFTEST_LOG_FAILURE_PREFIX = "Failure:"
HFTEST_LOG_FINISHED = "FINISHED"
+HFTEST_CTRL_GET_COMMAND_LINE = "[hftest_ctrl:get_command_line]"
+HFTEST_CTRL_FINISHED = "[hftest_ctrl:finished]"
+
HF_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__))))
DTC_SCRIPT = os.path.join(HF_ROOT, "build", "image", "dtc.py")
@@ -116,7 +120,9 @@
"kernel",
"initrd",
"vm_args",
- "cpu"
+ "cpu",
+ "serial_dev",
+ "serial_baudrate",
])
@@ -337,6 +343,42 @@
return self.finish_run(run_state)
+class SerialDriver(Driver):
+ """Driver which communicates with a device over the serial port."""
+
+ def __init__(self, args):
+ Driver.__init__(self, args)
+ self.tty_file = self.args.serial_dev
+ self.baudrate = self.args.serial_baudrate
+ input("Press ENTER and then reset the device...")
+
+ def run(self, run_name, test_args, is_long_running):
+ """Communicate `test_args` to the device over the serial port."""
+ run_state = self.start_run(run_name)
+
+ with serial.Serial(self.tty_file, self.baudrate, timeout=10) as ser:
+ with open(run_state.log_path, "a") as f:
+ while True:
+ # Read one line from the serial port.
+ line = ser.readline().decode('utf-8')
+ if len(line) == 0:
+ # Timeout
+ run_state.set_ret_code(124)
+ input("Timeout. " +
+ "Press ENTER and then reset the device...")
+ break
+ # Write the line to the log file.
+ f.write(line)
+ if HFTEST_CTRL_GET_COMMAND_LINE in line:
+ # Device is waiting for `test_args`.
+ ser.write(test_args.encode('ascii'))
+ ser.write(b'\r')
+ elif HFTEST_CTRL_FINISHED in line:
+ # Device has finished running this test and will reboot.
+ break
+ return self.finish_run(run_state)
+
+
# Tuple used to return information about the results of running a set of tests.
TestRunnerResult = collections.namedtuple("TestRunnerResult", [
"tests_run",
@@ -503,7 +545,9 @@
parser.add_argument("--suite")
parser.add_argument("--test")
parser.add_argument("--vm_args")
- parser.add_argument("--fvp", action="store_true")
+ parser.add_argument("--driver", default="qemu")
+ parser.add_argument("--serial-dev", default="/dev/ttyUSB0")
+ parser.add_argument("--serial-baudrate", type=int, default=115200)
parser.add_argument("--skip-long-running-tests", action="store_true")
parser.add_argument("--cpu",
help="Selects the CPU configuration for the run environment.")
@@ -523,12 +567,17 @@
artifacts = ArtifactsManager(os.path.join(args.log, image_name))
# Create a driver for the platform we want to test on.
- driver_args = DriverArgs(artifacts, image, initrd, vm_args,
- args.cpu)
- if args.fvp:
- driver = FvpDriver(driver_args)
- else:
+ driver_args = DriverArgs(artifacts, image, initrd, vm_args, args.cpu,
+ args.serial_dev, args.serial_baudrate)
+
+ if args.driver == "qemu":
driver = QemuDriver(driver_args)
+ elif args.driver == "fvp":
+ driver = FvpDriver(driver_args)
+ elif args.driver == "serial":
+ driver = SerialDriver(driver_args)
+ else:
+ raise Exception("Unknown driver name: {}".format(args.driver))
# Create class which will drive test execution.
runner = TestRunner(artifacts, driver, image_name, args.suite, args.test,
diff --git a/test/hftest/standalone_main.c b/test/hftest/standalone_main.c
index 586b3b3..91b53b5 100644
--- a/test/hftest/standalone_main.c
+++ b/test/hftest/standalone_main.c
@@ -19,8 +19,6 @@
#include "hf/arch/vm/interrupts.h"
-#include "hf/fdt.h"
-#include "hf/memiter.h"
#include "hf/mm.h"
#include "hftest_common.h"
@@ -33,10 +31,7 @@
void kmain(const struct fdt_header *fdt)
{
- struct fdt_node n;
- const char *bootargs;
- uint32_t bootargs_size;
- struct memiter bootargs_iter;
+ struct memiter command_line;
struct memiter command;
/*
@@ -44,7 +39,7 @@
*/
if ((VM_TOOLCHAIN == 1) && !hftest_mm_init()) {
HFTEST_LOG("Memory initialization failed.");
- return;
+ goto out;
}
/*
@@ -55,55 +50,40 @@
hftest_use_list(hftest_begin, hftest_end - hftest_begin);
- if (!fdt_root_node(&n, fdt)) {
- HFTEST_LOG("FDT failed validation.");
- return;
+ if (!hftest_ctrl_start(fdt, &command_line)) {
+ HFTEST_LOG("Unable to read the command line.");
+ goto out;
}
- if (!fdt_find_child(&n, "")) {
- HFTEST_LOG("Unable to find root node in FDT.");
- return;
- }
-
- if (!fdt_find_child(&n, "chosen")) {
- HFTEST_LOG("Unable to find 'chosen' node in FDT.");
- return;
- }
-
- if (!fdt_read_property(&n, "bootargs", &bootargs, &bootargs_size)) {
- HFTEST_LOG("Unable to read bootargs.");
- return;
- }
-
- /* Remove null terminator. */
- memiter_init(&bootargs_iter, bootargs, bootargs_size - 1);
-
- if (!memiter_parse_str(&bootargs_iter, &command)) {
+ if (!memiter_parse_str(&command_line, &command)) {
HFTEST_LOG("Unable to parse command.");
- return;
+ goto out;
}
if (memiter_iseq(&command, "json")) {
hftest_json();
- return;
+ goto out;
}
if (memiter_iseq(&command, "run")) {
struct memiter suite_name;
struct memiter test_name;
- if (!memiter_parse_str(&bootargs_iter, &suite_name)) {
+ if (!memiter_parse_str(&command_line, &suite_name)) {
HFTEST_LOG("Unable to parse test suite.");
- return;
+ goto out;
}
- if (!memiter_parse_str(&bootargs_iter, &test_name)) {
+ if (!memiter_parse_str(&command_line, &test_name)) {
HFTEST_LOG("Unable to parse test.");
- return;
+ goto out;
}
hftest_run(suite_name, test_name, fdt);
- return;
+ goto out;
}
hftest_help();
+
+out:
+ hftest_ctrl_finish();
}
diff --git a/test/inc/test/hftest.h b/test/inc/test/hftest.h
index bcd22d8..1dd65aa 100644
--- a/test/inc/test/hftest.h
+++ b/test/inc/test/hftest.h
@@ -19,8 +19,11 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <stdnoreturn.h>
#include "hf/dlog.h"
+#include "hf/fdt.h"
+#include "hf/memiter.h"
/*
* Define a set up function to be run before every test in a test suite.
@@ -104,6 +107,18 @@
void hftest_mm_vcpu_init(void);
/**
+ * Inform a host that this is the start of a test run and obtain the command
+ * line arguments for it.
+ */
+bool hftest_ctrl_start(const struct fdt_header *fdt, struct memiter *cmd);
+
+/** Inform a host that this test run has finished and clean up. */
+void hftest_ctrl_finish(void);
+
+/** Reboot the device. */
+noreturn void hftest_device_reboot(void);
+
+/**
* Starts the CPU with the given ID. It will start at the provided entry point
* with the provided argument. It is a wrapper around the generic cpu_start()
* and takes care of MMU initialization.