[sw/ottf] Initial prototype of OpenTitan Test Framework
This prototype of the OpenTitan Test Framework (OTTF) contains:
-Code to port FreeRTOS to run on OpenTitan hardware.
-Test setup and teardown code (similar to the prior test_main.c).
-An example Earlgrey chip-level test.
Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/sw/device/lib/meson.build b/sw/device/lib/meson.build
index f223c4e..ec154b1 100644
--- a/sw/device/lib/meson.build
+++ b/sw/device/lib/meson.build
@@ -7,7 +7,6 @@
subdir('crt')
subdir('dif')
subdir('runtime')
-subdir('testing')
# Flash controller library (sw_lib_flash_ctrl)
sw_lib_flash_ctrl = declare_dependency(
@@ -98,3 +97,5 @@
]
)
)
+
+subdir('testing')
diff --git a/sw/device/lib/testing/meson.build b/sw/device/lib/testing/meson.build
index 8472180..364e067 100644
--- a/sw/device/lib/testing/meson.build
+++ b/sw/device/lib/testing/meson.build
@@ -2,7 +2,6 @@
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
-
# hardware entropy complex (entropy_src, csrng, edn) test utilities.
sw_lib_testing_entropy_testutils = declare_dependency(
link_with: static_library(
diff --git a/sw/device/lib/testing/test_framework/FreeRTOSConfig.h b/sw/device/lib/testing/test_framework/FreeRTOSConfig.h
new file mode 100644
index 0000000..d8db2ba
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/FreeRTOSConfig.h
@@ -0,0 +1,68 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_FREERTOSCONFIG_H_
+#define OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_FREERTOSCONFIG_H_
+
+#include "sw/device/lib/arch/device.h"
+
+// These macros configure FreeRTOS. A description of each macro can be found
+// here: https://www.freertos.org/a00110.html
+
+// NOTE: the macro names below do NOT, and cannot, conform to the style
+// guide, since they are specific to FreeRTOS.
+
+// Main FreeRTOS configs.
+#define configUSE_PREEMPTION 0
+#define configUSE_IDLE_HOOK 0
+#define configUSE_TICK_HOOK 0
+#define configTICK_RATE_HZ ((TickType_t)5)
+#define configMAX_PRIORITIES 5
+#define configMINIMAL_STACK_SIZE ((unsigned short)512)
+// TODO: would be better if this was computed based on macros in the
+// autogenerated toplevel header and/or the values defined the linker script.
+// Setting this to 0x15000u for now. need a way to import this from
+#define configTOTAL_HEAP_SIZE ((size_t)0x15000u)
+#define configMAX_TASK_NAME_LEN 16
+#define configUSE_TRACE_FACILITY 0
+#define configUSE_16_BIT_TICKS 0
+#define configIDLE_SHOULD_YIELD 0
+#define configUSE_MUTEXES 0
+#define configQUEUE_REGISTRY_SIZE 0
+#define configCHECK_FOR_STACK_OVERFLOW 0
+#define configUSE_RECURSIVE_MUTEXES 0
+#define configUSE_MALLOC_FAILED_HOOK 1
+#define configUSE_APPLICATION_TASK_TAG 0
+#define configUSE_COUNTING_SEMAPHORES 0
+#define configGENERATE_RUN_TIME_STATS 0
+#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
+
+// Co-routines.
+#define configUSE_CO_ROUTINES 0
+#define configMAX_CO_ROUTINE_PRIORITIES 2
+
+// Software timers.
+#define configUSE_TIMERS 0
+
+// Task priorities. Allow these to be overridden.
+#ifndef uartPRIMARY_PRIORITY
+#define uartPRIMARY_PRIORITY (configMAX_PRIORITIES - 3)
+#endif
+
+// Set the following definitions to 1 to include the API function, or zero to
+// exclude the API function.
+#define INCLUDE_vTaskPrioritySet 1
+#define INCLUDE_uxTaskPriorityGet 1
+#define INCLUDE_vTaskDelete 1
+#define INCLUDE_vTaskCleanUpResources 1
+#define INCLUDE_vTaskSuspend 1
+#define INCLUDE_vTaskDelayUntil 1
+#define INCLUDE_vTaskDelay 1
+#define INCLUDE_eTaskGetState 1
+#define INCLUDE_xTimerPendFunctionCall 1
+#define INCLUDE_xTaskAbortDelay 1
+#define INCLUDE_xTaskGetHandle 1
+#define INCLUDE_xSemaphoreGetMutexHolder 0
+
+#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_FREERTOSCONFIG_H_
diff --git a/sw/device/lib/testing/test_framework/example_earlgrey_test.c b/sw/device/lib/testing/test_framework/example_earlgrey_test.c
new file mode 100644
index 0000000..ff9d978
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/example_earlgrey_test.c
@@ -0,0 +1,43 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <sw/device/lib/testing/test_framework/ottf.h>
+
+#include "sw/device/lib/dif/dif_rv_timer.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/vendor/freertos_freertos_kernel/include/FreeRTOS.h"
+#include "sw/vendor/freertos_freertos_kernel/include/task.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+const test_config_t kTestConfig = {
+ .can_clobber_uart = false,
+ .test_name = "ExampleTest",
+};
+
+// This example test just queries the RV Timer count and logs it over UART.
+// Currently, this test runs forever, but once test teardown logic has been
+// implemented this example will be updated.
+void test_main(void *result) {
+ mmio_region_t timer_reg =
+ mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR);
+ dif_rv_timer_t timer = {
+ .base_addr = timer_reg,
+ {.hart_count = 1, .comparator_count = 1},
+ };
+ uint64_t current_time;
+ const uint32_t kHart = (uint32_t)kTopEarlgreyPlicTargetIbex0;
+
+ while (true) {
+ CHECK(dif_rv_timer_counter_read(&timer, kHart, ¤t_time) ==
+ kDifRvTimerOk);
+ LOG_INFO("(FreeRTOS Task) Current Time: %u", (uint32_t)current_time);
+ }
+
+ *(bool *)result = true;
+
+ // Calling vTaskDelete() with NULL triggers a task to delete itself.
+ vTaskDelete(NULL);
+}
diff --git a/sw/device/lib/testing/test_framework/freertos_hooks.c b/sw/device/lib/testing/test_framework/freertos_hooks.c
new file mode 100644
index 0000000..d621ac6
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/freertos_hooks.c
@@ -0,0 +1,28 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/testing/test_framework/freertos_hooks.h"
+
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/vendor/freertos_freertos_kernel/include/FreeRTOS.h"
+
+// NOTE: the function names below do NOT, and cannot, conform to the style
+// guide, since they are specific implementations of FreeRTOS defined functions.
+
+void vApplicationMallocFailedHook(void) {
+ // TODO: communicate this event back to the host.
+ LOG_INFO("Malloc Failed.");
+ taskDISABLE_INTERRUPTS();
+ abort();
+}
+
+void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName) {
+ // TODO: communicate this event back to the host.
+ LOG_INFO("Stack Overflow.");
+ (void)pcTaskName;
+ (void)pxTask;
+ taskDISABLE_INTERRUPTS();
+ abort();
+}
diff --git a/sw/device/lib/testing/test_framework/freertos_hooks.h b/sw/device/lib/testing/test_framework/freertos_hooks.h
new file mode 100644
index 0000000..4123980
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/freertos_hooks.h
@@ -0,0 +1,29 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_FREERTOS_HOOKS_H_
+#define OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_FREERTOS_HOOKS_H_
+
+#include "sw/vendor/freertos_freertos_kernel/include/FreeRTOS.h"
+#include "sw/vendor/freertos_freertos_kernel/include/task.h"
+
+// NOTE: the function names below do NOT, and cannot, conform to the style
+// guide, since they are specific implementations of FreeRTOS defined functions.
+
+/**
+ * This is called if configUSE_MALLOC_FAILED_HOOK is set to 1 in
+ * FreeRTOSConfig.h, and a call to pvPortMalloc() fails.
+ */
+void vApplicationMallocFailedHook(void);
+
+/**
+ * This is called if a stack overflow is detected, and
+ * configCHECK_FOR_STACK_OVERFLOW is set to 1 or 2 in FreeRTOSConfig.h.
+ *
+ * @param pxTask FreeRTOS task handle.
+ * @param pcTaskName
+ */
+void vApplicationStackOverflowHook(TaskHandle_t pxTask, char *pcTaskName);
+
+#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_FREERTOS_HOOKS_H_
diff --git a/sw/device/lib/testing/test_framework/freertos_port.S b/sw/device/lib/testing/test_framework/freertos_port.S
new file mode 100644
index 0000000..a11b1f6
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/freertos_port.S
@@ -0,0 +1,168 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Ibex does not implement additional registers beyond the RV32I spec.
+#define PORT_WORD_SIZE 4
+#define PORT_CONTEXT_SIZE (30 * PORT_WORD_SIZE)
+
+.extern pxCurrentTCB
+.extern xISRStackTop
+
+// -----------------------------------------------------------------------------
+
+/**
+ * FreeRTOS, expects this function to exist and uses it to start the first task.
+ */
+.balign 8
+.global xPortStartFirstTask
+.type xPortStartFirstTask, @function
+xPortStartFirstTask:
+
+ // Load the stack pointer for the current TCB (just going to clobber sp here
+ // since we are setting it here anyway).
+ lw sp, pxCurrentTCB
+ lw sp, 0(sp)
+
+ // NOTE: for starting the FreeRTOS scheduler, the exception return address is
+ // used as the function return address. See pxPortInitialiseStack below.
+ lw ra, 0(sp)
+
+ // Restore registers initialized on task start.
+ lw t1, 3 * PORT_WORD_SIZE(sp)
+ lw t2, 4 * PORT_WORD_SIZE(sp)
+ lw s0, 5 * PORT_WORD_SIZE(sp)
+ lw s1, 6 * PORT_WORD_SIZE(sp)
+ lw a0, 7 * PORT_WORD_SIZE(sp) // task parameters (pvParameters)
+ lw a1, 8 * PORT_WORD_SIZE(sp)
+ lw a2, 9 * PORT_WORD_SIZE(sp)
+ lw a3, 10 * PORT_WORD_SIZE(sp)
+ lw a4, 11 * PORT_WORD_SIZE(sp)
+ lw a5, 12 * PORT_WORD_SIZE(sp)
+ lw a6, 13 * PORT_WORD_SIZE(sp)
+ lw a7, 14 * PORT_WORD_SIZE(sp)
+ lw s2, 15 * PORT_WORD_SIZE(sp)
+ lw s3, 16 * PORT_WORD_SIZE(sp)
+ lw s4, 17 * PORT_WORD_SIZE(sp)
+ lw s5, 18 * PORT_WORD_SIZE(sp)
+ lw s6, 19 * PORT_WORD_SIZE(sp)
+ lw s7, 20 * PORT_WORD_SIZE(sp)
+ lw s8, 21 * PORT_WORD_SIZE(sp)
+ lw s9, 22 * PORT_WORD_SIZE(sp)
+ lw s10, 23 * PORT_WORD_SIZE(sp)
+ lw s11, 24 * PORT_WORD_SIZE(sp)
+ lw t3, 25 * PORT_WORD_SIZE(sp)
+ lw t4, 26 * PORT_WORD_SIZE(sp)
+ lw t5, 27 * PORT_WORD_SIZE(sp)
+ lw t6, 28 * PORT_WORD_SIZE(sp)
+
+ // Initialize t0 to the value of MSTATUS with global interrupts enabled, which
+ // is required because this returns with ret, not eret.
+ lw t0, 29 * PORT_WORD_SIZE(sp) // Load the MSTATUS state from the stack.
+ ori t0, t0, 1<<3 // Set MIE field.
+ csrw mstatus, t0 // Ibex interrupts enabled from here!
+
+ // Restore t0 register from the stack (after using it to manipulate MSTATUS).
+ lw t0, 2 * PORT_WORD_SIZE(sp)
+
+ // Update the stack pointer (shrinking the stack).
+ addi sp, sp, PORT_CONTEXT_SIZE
+
+ ret
+
+ // Set size so this function can be disassembled.
+ .size xPortStartFirstTask, .-xPortStartFirstTask
+
+// -----------------------------------------------------------------------------
+
+/**
+ * The prototype for this function depends on configurations defined in
+ * FreeRTOSConfig.h, and is defined in:
+ * sw/vendor/freertos_freertos_kernel/include/portable.h
+ *
+ * The implementation of this assembly function assumes the prototype for this
+ * function looks like:
+ *
+ * StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack,
+ * TaskFunction_t pxCode,
+ * void *pvParameters);
+ *
+ * TODO: add some checks to verify this is the configured prototype.
+ * TODO: configure to allow use of checking for stack overflows.
+ * TODO: configure return address for first (main) task.
+ *
+ * As per the standard RISC-V ABI pxTopcOfStack is passed in in a0, pxCode in
+ * a1, and pvParameters in a2. The new top of stack is passed out in a0.
+ *
+ * The RISC-V context is saved to FreeRTOS tasks in the following stack frame,
+ * where the global and thread pointers are currently assumed to be constant,
+ * and therefore are not saved:
+ *
+ * ---Stack Bottom---
+ * ---............---
+ * Offset - Reg/Value
+ * 29 - mstatus
+ * 28 - t6 (x31)
+ * 27 - t5 (x30)
+ * 26 - t4 (x29)
+ * 25 - t3 (x28)
+ * 24 - s11 (x27)
+ * 23 - s10 (x26)
+ * 22 - s9 (x25)
+ * 21 - s8 (x24)
+ * 20 - s7 (x23)
+ * 19 - s6 (x22)
+ * 18 - s5 (x21)
+ * 17 - s4 (x20)
+ * 16 - s3 (x19)
+ * 15 - s2 (x18)
+ * 14 - a7 (x17)
+ * 13 - a6 (x16)
+ * 12 - a5 (x15)
+ * 11 - a4 (x14)
+ * 10 - a3 (x13)
+ * 9 - a2 (x12)
+ * 8 - a1 (x11)
+ * 7 - (pvParameters)
+ * 6 - s1 (x9)
+ * 5 - s0 (x8)
+ * 4 - t2 (x7)
+ * 3 - t1 (x6)
+ * 2 - t0 (x5)
+ * 1 - (return address for main task, 0 for now)
+ * 0 - (pxCode)
+ * -----Stack Top----
+ */
+.balign 8
+.global pxPortInitialiseStack
+.type pxPortInitialiseStack, @function
+pxPortInitialiseStack:
+
+ // Setup the MSTATUS register.
+ csrr t0, mstatus
+ // Ensure interrupts are disabled when the stack is restored within an ISR.
+ // Required when a task is created after the scheduler has been started,
+ // otherwise interrupts would be disabled anyway.
+ andi t0, t0, ~0x8
+ // Generate the value 0x1880, to set the MPIE and MPP bits in MSTATUS.
+ li t1, 0x188 << 4
+ or t0, t0, t1
+
+ // Setup the stack frame detailed above (a0 holds the task stack pointer).
+ addi a0, a0, -PORT_CONTEXT_SIZE
+ // Push MSTATUS onto the stack.
+ sw t0, 29(a0)
+ // Push task parameters (pvParameters that is in x12/a2, on the stack.
+ sw a2, 7(a0)
+ // Push 0 for the portTASK_RETURN_ADDRESS for now.
+ sw zero, 1(a0)
+ // Push the pointer to the task's entry point (pxCode) onto the stack. This
+ // will be loaded into either ra (in xPortStartFirstTask) or mepc (in
+ // freertosIrqExit), so that when ret/mret is called control flow will be
+ // transferred accordingly.
+ sw a1, 0(a0)
+
+ ret
+
+ // Set size so this function can be disassembled.
+ .size pxPortInitialiseStack, .-pxPortInitialiseStack
diff --git a/sw/device/lib/testing/test_framework/freertos_port.c b/sw/device/lib/testing/test_framework/freertos_port.c
new file mode 100644
index 0000000..f5f155c
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/freertos_port.c
@@ -0,0 +1,102 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/base/csr.h"
+#include "sw/device/lib/base/memory.h"
+#include "sw/device/lib/dif/dif_rv_timer.h"
+#include "sw/device/lib/handler.h"
+#include "sw/device/lib/irq.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/vendor/freertos_freertos_kernel/include/FreeRTOS.h"
+#include "sw/vendor/freertos_freertos_kernel/include/task.h"
+#include "sw/vendor/freertos_freertos_kernel/portable/GCC/RISC-V/portmacro.h"
+
+// TODO: make this toplevel agnostic.
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" // Generated.
+
+// NOTE: some of the function names below do NOT, and cannot, conform to the
+// style guide, since they are specific implementations of FreeRTOS defined
+// functions.
+
+// ----------------------------------------------------------------------------
+// Timer.
+// ----------------------------------------------------------------------------
+static dif_rv_timer_t timer;
+static const uint32_t kTimerHartId = (uint32_t)kTopEarlgreyPlicTargetIbex0;
+static const uint32_t kTimerComparatorId = 0;
+static const uint64_t kTimerDeadline =
+ 100; // Counter must reach 100 for an IRQ to be triggered.
+
+void vPortSetupTimerInterrupt(void) {
+ LOG_INFO("Configuring timer interrupt ...");
+
+ // Initialize and reset the timer.
+ mmio_region_t timer_reg =
+ mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR);
+ CHECK(dif_rv_timer_init(
+ timer_reg,
+ (dif_rv_timer_config_t){.hart_count = 1, .comparator_count = 1},
+ &timer) == kDifRvTimerOk);
+
+ // Compute and set tick parameters (i.e., step, prescale, etc.).
+ dif_rv_timer_tick_params_t tick_params;
+ CHECK(dif_rv_timer_approximate_tick_params(
+ kClockFreqPeripheralHz, configTICK_RATE_HZ * kTimerDeadline,
+ &tick_params) == kDifRvTimerApproximateTickParamsOk);
+ LOG_INFO("Tick Freq. (Hz): %u, Prescale: %u; Tick Step: %u",
+ (uint32_t)kClockFreqPeripheralHz, (uint32_t)tick_params.prescale,
+ (uint32_t)tick_params.tick_step);
+ CHECK(dif_rv_timer_set_tick_params(&timer, kTimerHartId, tick_params) ==
+ kDifRvTimerOk);
+
+ // Enable RV Timer interrupts and arm/enable the timer.
+ CHECK(dif_rv_timer_irq_enable(&timer, kTimerHartId, kTimerComparatorId,
+ kDifRvTimerEnabled) == kDifRvTimerOk);
+ CHECK(dif_rv_timer_arm(&timer, kTimerHartId, kTimerComparatorId,
+ kTimerDeadline) == kDifRvTimerOk);
+
+ CHECK(dif_rv_timer_counter_set_enabled(&timer, kTimerHartId,
+ kDifRvTimerEnabled) == kDifRvTimerOk);
+}
+
+// ----------------------------------------------------------------------------
+// Scheduler.
+// ----------------------------------------------------------------------------
+extern void xPortStartFirstTask(void);
+
+BaseType_t xPortStartScheduler(void) {
+ vPortSetupTimerInterrupt();
+ irq_timer_ctrl(true);
+ irq_external_ctrl(true);
+ irq_software_ctrl(true);
+ xPortStartFirstTask();
+
+ // Unreachable.
+ return pdFAIL;
+}
+
+void vPortEndScheduler(void) {
+ // Not implemented.
+ // TODO: trigger this to be called when from the idle task hook when all tests
+ // have completed.
+ while (true) {
+ wait_for_interrupt();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// ISRs.
+// TODO: add support for remaining ISRs.
+// ----------------------------------------------------------------------------
+void handler_irq_timer(void) {
+ LOG_INFO("Handling timer IQR ...");
+ CHECK(dif_rv_timer_irq_disable(&timer, kTimerHartId, NULL) == kDifRvTimerOk);
+ CHECK(dif_rv_timer_counter_write(&timer, kTimerHartId, 0) == kDifRvTimerOk);
+ CHECK(dif_rv_timer_irq_clear(&timer, kTimerHartId, kTimerComparatorId) ==
+ kDifRvTimerOk);
+ CHECK(dif_rv_timer_irq_enable(&timer, kTimerHartId, kTimerComparatorId,
+ kDifRvTimerEnabled) == kDifRvTimerOk);
+ LOG_INFO("Done.");
+}
diff --git a/sw/device/lib/testing/test_framework/meson.build b/sw/device/lib/testing/test_framework/meson.build
index 78e3c3b..b3b1092 100644
--- a/sw/device/lib/testing/test_framework/meson.build
+++ b/sw/device/lib/testing/test_framework/meson.build
@@ -2,6 +2,9 @@
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
+###############################################################################
+# (Current) On-device Test Framework
+###############################################################################
# Test status library.
sw_lib_testing_test_status = declare_dependency(
link_with: static_library(
@@ -62,3 +65,52 @@
],
)
)
+
+###############################################################################
+# (Future) On-device Test Framework (OTTF)
+# See #8015: https://github.com/lowRISC/opentitan/issues/8015
+###############################################################################
+# FreeRTOS kernel paths.
+freertos_root = '@0@/@1@'.format(meson.source_root(), 'sw/vendor/freertos_freertos_kernel')
+freertos_memmang_path = '@0@/@1@'.format(freertos_root, 'portable/MemMang')
+freertos_portable_path = '@0@/@1@'.format(freertos_root, 'portable/GCC/RISC-V')
+
+# OpenTitan Test Framework (OTTF) sources & includes.
+ottf_sources = [
+ 'ottf.c',
+ 'example_earlgrey_test.c',
+ 'freertos_hooks.c',
+ 'freertos_port.S',
+ 'freertos_port.c',
+ hw_ip_rv_timer_reg_h,
+ join_paths(freertos_root ,'tasks.c'),
+ join_paths(freertos_root ,'queue.c'),
+ join_paths(freertos_root ,'list.c'),
+ join_paths(freertos_memmang_path,'heap_1.c'),
+]
+ottf_incdirs = include_directories(
+ '../../../../vendor/freertos_freertos_kernel/include',
+ '../../../../vendor/freertos_freertos_kernel/portable/GCC/RISC-V')
+
+# OpenTitan Test Framework (OTTF)
+sw_lib_ottf = declare_dependency(
+ link_with: static_library(
+ 'sw_lib_ottf',
+ sources: ottf_sources,
+ include_directories: ottf_incdirs,
+ c_args: [
+ '-D__riscv_float_abi_soft',
+ ],
+ dependencies: [
+ sw_lib_mem,
+ sw_lib_runtime_hart,
+ sw_lib_runtime_log,
+ sw_lib_runtime_print,
+ sw_lib_irq,
+ sw_lib_dif_uart,
+ sw_lib_dif_rv_timer,
+ sw_lib_testing_test_status,
+ sw_lib_testing_test_coverage,
+ ],
+ )
+)
diff --git a/sw/device/lib/testing/test_framework/ottf.c b/sw/device/lib/testing/test_framework/ottf.c
new file mode 100644
index 0000000..1071b86
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/ottf.c
@@ -0,0 +1,66 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/testing/test_framework/ottf.h"
+
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_uart.h"
+#include "sw/device/lib/runtime/log.h"
+#include "sw/device/lib/runtime/print.h"
+#include "sw/device/lib/testing/check.h"
+#include "sw/device/lib/testing/test_framework/FreeRTOSConfig.h"
+#include "sw/device/lib/testing/test_framework/test_coverage.h"
+#include "sw/device/lib/testing/test_framework/test_status.h"
+#include "sw/vendor/freertos_freertos_kernel/include/FreeRTOS.h"
+#include "sw/vendor/freertos_freertos_kernel/include/queue.h"
+#include "sw/vendor/freertos_freertos_kernel/include/task.h"
+
+// TODO: make this toplevel agnostic.
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+// UART for communication with host.
+static dif_uart_t uart0;
+
+static void init_uart(void) {
+ CHECK(dif_uart_init(mmio_region_from_addr(TOP_EARLGREY_UART0_BASE_ADDR),
+ &uart0) == kDifOk,
+ "failed to init UART");
+ CHECK(dif_uart_configure(&uart0,
+ (dif_uart_config_t){
+ .baudrate = kUartBaudrate,
+ .clk_freq_hz = kClockFreqPeripheralHz,
+ .parity_enable = kDifToggleDisabled,
+ .parity = kDifUartParityEven,
+ }) == kDifUartConfigOk,
+ "failed to configure UART");
+ base_uart_stdout(&uart0);
+}
+
+int main(int argc, char **argv) {
+ test_status_set(kTestStatusInTest);
+
+ // Initialize the UART to enable logging for non-DV simulation platforms.
+ if (kDeviceType != kDeviceSimDV) {
+ init_uart();
+ }
+
+ // Run the test, which is contained within `test_main()`, as a FreeRTOS task.
+ bool result = false;
+ LOG_INFO("Starting test (%s) in a FreeRTOS task ...", kTestConfig.test_name);
+ TaskHandle_t test_task_handle = NULL;
+ xTaskCreate(test_main, kTestConfig.test_name, configMINIMAL_STACK_SIZE,
+ &result, tskIDLE_PRIORITY + 1, &test_task_handle);
+ vTaskStartScheduler();
+
+ // Must happen before any debug output.
+ if (kTestConfig.can_clobber_uart) {
+ init_uart();
+ }
+
+ test_coverage_send_buffer();
+ test_status_set(result ? kTestStatusPassed : kTestStatusFailed);
+
+ // Unreachable code.
+ return 1;
+}
diff --git a/sw/device/lib/testing/test_framework/ottf.h b/sw/device/lib/testing/test_framework/ottf.h
new file mode 100644
index 0000000..343af09
--- /dev/null
+++ b/sw/device/lib/testing/test_framework/ottf.h
@@ -0,0 +1,68 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_OTTF_H_
+#define OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_OTTF_H_
+
+#include <stdbool.h>
+
+#include "sw/device/lib/testing/test_framework/FreeRTOSConfig.h"
+
+/**
+ * @file
+ * @brief Entrypoint definitions for on-device tests
+ */
+
+/**
+ * Configuration variables for an on-device test.
+ *
+ * This type represents configuration values for an on-device test, which allow
+ * tests to configure the behavior of the OpenTitan Test Framework (OTTF).
+ *
+ * New fields can be safely added to this struct without affecting any tests;
+ * the "default" value of all fields should be zero (or NULL, or equivalent).
+ *
+ * See `kTestConfig`.
+ */
+typedef struct test_config {
+ /**
+ * Indicates that `test_main()` does something non-trivial to the UART
+ * device. Setting this to true will make `test_main()` guard against this
+ * by resetting the UART device before printing debug information.
+ */
+ bool can_clobber_uart;
+ /**
+ * A short name for the test for debugging purposes within FreeRTOS.
+ */
+ char test_name[configMAX_TASK_NAME_LEN];
+} test_config_t;
+
+/**
+ * Global test configuration.
+ *
+ * This symbol should be defined externally in a standalone SW test. For most
+ * tests, this will just look like the following:
+ *
+ * const test_config_t kTestConfig;
+ *
+ * The zero values of all of the fields will behave like sane defaults.
+ *
+ * This value needs to be provided as a global so that the initialization code
+ * that runs before `test_main()` is executed can take note of it.
+ */
+extern const test_config_t kTestConfig;
+
+/**
+ * Entry point for a SW on-device (or chip-level) test.
+ *
+ * This function should be defined externally in a standalone SW test, linked
+ * together with this library. This library provides a `main()` function that
+ * does test harness setup, initializes FreeRTOS, and starts a FreeRTOS task
+ * that executes `test_main()`.
+ *
+ * @return success or failure of the test as boolean.
+ */
+extern void test_main(void *pvParameters);
+
+#endif // OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_OTTF_H_
diff --git a/sw/device/tests/meson.build b/sw/device/tests/meson.build
index 1b1c8ae..d9260ca 100644
--- a/sw/device/tests/meson.build
+++ b/sw/device/tests/meson.build
@@ -796,3 +796,59 @@
build_by_default: true,
)
endforeach
+
+# OpenTitan (FreeRTOS) Test Framework
+foreach device_name, device_lib : sw_lib_arch_core_devices
+ ottf_elf = executable(
+ 'ottf_' + device_name,
+ name_suffix: 'elf',
+ dependencies: [
+ riscv_crt,
+ device_lib,
+ sw_lib_ottf,
+ sw_lib_irq_handlers,
+ ],
+ )
+
+ target_name = 'ottf_@0@_' + device_name
+
+ ottf_dis = custom_target(
+ target_name.format('dis'),
+ input: ottf_elf,
+ kwargs: elf_to_dis_custom_target_args,
+ )
+
+ ottf_bin = custom_target(
+ target_name.format('bin'),
+ input: ottf_elf,
+ kwargs: elf_to_bin_custom_target_args,
+ )
+
+ ottf_vmem32 = custom_target(
+ target_name.format('vmem32'),
+ input: ottf_bin,
+ kwargs: bin_to_vmem32_custom_target_args,
+ )
+
+ ottf_vmem64 = custom_target(
+ target_name.format('vmem64'),
+ input: ottf_bin,
+ kwargs: bin_to_vmem64_custom_target_args,
+ )
+
+ custom_target(
+ target_name.format('export'),
+ command: export_target_command,
+ depend_files: [export_target_depend_files,],
+ input: [
+ ottf_elf,
+ ottf_dis,
+ ottf_bin,
+ ottf_vmem32,
+ ottf_vmem64,
+ ],
+ output: target_name.format('export'),
+ build_always_stale: true,
+ build_by_default: true,
+ )
+endforeach