[ottf] enable context switching between tasks

This commits enables FreeRTOS cooperative scheduling by:
1. invoking a context switch upon encountering a machine-mode ecall, and
2. adding several FreeRTOS wrapper functions to the OTTF API that
   simplify the FreeRTOS interface by providing functions to:
    a. create additional FreeRTOS tasks,
    b. delete the currently running task,
    c. cooperatively yield control flow between tasks, and
    d. get the name of the task that is currently executing.

Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/sw/device/lib/testing/test_framework/ottf_main.c b/sw/device/lib/testing/test_framework/ottf_main.c
index b3c8fe3..340fd16 100644
--- a/sw/device/lib/testing/test_framework/ottf_main.c
+++ b/sw/device/lib/testing/test_framework/ottf_main.c
@@ -33,12 +33,42 @@
 OT_ASSERT_MEMBER_OFFSET(ottf_test_config_t, enable_concurrency, 0);
 OT_ASSERT_MEMBER_SIZE(ottf_test_config_t, enable_concurrency, 1);
 
+// Pointer to the current FreeRTOS Task Control Block, which should be non-NULL
+// when OTTF concurrency is enabled, and test code is executed within FreeRTOS
+// tasks.
+extern void *pxCurrentTCB;
+
+// `extern` declarations to give the inline functions in the corresponding
+// header a link location.
+extern bool ottf_task_create(TaskFunction_t task_function,
+                             const char *task_name,
+                             configSTACK_DEPTH_TYPE task_stack_depth,
+                             uint32_t task_priority);
+extern void ottf_task_yield(void);
+extern void ottf_task_delete_self(void);
+extern char *ottf_task_get_self_name(void);
+
 // UART for communication with host.
 static dif_uart_t uart0;
 
 // A global random number generator testutil handle.
 rand_testutils_rng_t rand_testutils_rng_ctx;
 
+// The OTTF overrides the default machine ecall exception handler to implement
+// FreeRTOS context switching, required for supporting cooperative scheduling.
+void ottf_machine_ecall_handler(void) {
+  if (pxCurrentTCB != NULL) {
+    // If the pointer to the current TCB is not NULL, we are operating in
+    // concurrency mode. In this case, our default behavior is to assume a
+    // context switch has been requested.
+    vTaskSwitchContext();
+    return;
+  }
+  LOG_ERROR(
+      "OTTF currently only supports use of machine-mode ecall for FreeRTOS "
+      "context switching.");
+}
+
 static void init_uart(void) {
   CHECK_DIF_OK(dif_uart_init(
       mmio_region_from_addr(TOP_EARLGREY_UART0_BASE_ADDR), &uart0));
@@ -98,9 +128,9 @@
   // Run the test.
   if (kOttfTestConfig.enable_concurrency) {
     // Run `test_main()` in a FreeRTOS task, allowing other FreeRTOS tasks to
-    // be spawned, if requested in the main test task.
-    xTaskCreate(test_wrapper, "TestTask", configMINIMAL_STACK_SIZE, NULL,
-                tskIDLE_PRIORITY + 1, NULL);
+    // be spawned, if requested in the main test task. Note, we spawn the main
+    // test task at a priority level of 0.
+    ottf_task_create(test_wrapper, "test_main", kOttfFreeRtosMinStackSize, 0);
     vTaskStartScheduler();
   } else {
     // Otherwise, launch `test_main()` on bare-metal.
diff --git a/sw/device/lib/testing/test_framework/ottf_main.h b/sw/device/lib/testing/test_framework/ottf_main.h
index 448aa1f..b5abd8f 100644
--- a/sw/device/lib/testing/test_framework/ottf_main.h
+++ b/sw/device/lib/testing/test_framework/ottf_main.h
@@ -7,8 +7,8 @@
 
 #include <stdbool.h>
 
-// This private header is included here so that OTTF users can include a single
-// header in their test application (the `ottf_main.h` header).
+#include "external/freertos/include/FreeRTOS.h"
+#include "external/freertos/include/task.h"
 #include "sw/device/lib/dif/dif_uart.h"
 #include "sw/device/lib/testing/test_framework/FreeRTOSConfig.h"
 #include "sw/device/lib/testing/test_framework/ottf_test_config.h"
@@ -31,6 +31,13 @@
 extern bool test_main(void);
 
 /**
+ * OTTF Constants.
+ */
+enum {
+  kOttfFreeRtosMinStackSize = configMINIMAL_STACK_SIZE,
+};
+
+/**
  * Returns the UART that is the console device.
  */
 dif_uart_t *ottf_console(void);
@@ -45,4 +52,62 @@
  */
 extern bool manufacturer_post_test_hook(void);
 
+/**
+ * Create a FreeRTOS task.
+ *
+ * Tasks should be implemented as functions that never return. However, they may
+ * delete themselves using the `ottf_task_delete_self()` function defined below.
+ *
+ * Additionally, tasks are always run at a priority level higher than that of
+ * the FreeRTOS idle task's (which is a priority of 0).
+ *
+ * See the FreeRTOS `xTaskCreate` documentation for more details:
+ * https://www.freertos.org/a00125.html.
+ *
+ * @param task_function The name of the function that implements the task.
+ * @param task_name A task identification string used to help debugging.
+ * @param task_stack_depth The amount of memory to reserve for the task's stack.
+ * @param task_priority The numerical priority of the task.
+ * @return A boolean encoding the success of the operation.
+ */
+inline bool ottf_task_create(TaskFunction_t task_function,
+                             const char *task_name,
+                             configSTACK_DEPTH_TYPE task_stack_depth,
+                             uint32_t task_priority) {
+  return xTaskCreate(/*pvTaskCode=*/task_function, /*pcName=*/task_name,
+                     /*usStackDepth=*/task_stack_depth, /*pvParameters=*/NULL,
+                     /*uxPriority=*/tskIDLE_PRIORITY + 1 + task_priority,
+                     /*pxCreatedTask=*/NULL) == pdPASS
+             ? true
+             : false;
+}
+
+/**
+ * Yield control flow to another FreeRTOS task of equal or higher priority.
+ *
+ * Note, if there are no other tasks of equal or higher priority, then the
+ * calling task will continue executing. See the FreeRTOS `taskYIELD`
+ * documentation for more details:
+ * https://www.freertos.org/a00020.html#taskYIELD.
+ */
+inline void ottf_task_yield(void) { taskYIELD(); }
+
+/**
+ * Delete the calling FreeRTOS task.
+ *
+ * See the FreeRTOS `vTaskDelete` documentation for more details:
+ * https://www.freertos.org/a00126.html.
+ */
+inline void ottf_task_delete_self(void) { vTaskDelete(/*xTask=*/NULL); }
+
+/**
+ * Returns the name of the currently executing FreeRTOS task.
+ *
+ * See the FreeRTOS `pcTaskGetName` documentation for more details:
+ * https://www.freertos.org/a00021.html#pcTaskGetName.
+ */
+inline char *ottf_task_get_self_name(void) {
+  return pcTaskGetName(/*xTaskToQuery=*/NULL);
+}
+
 #endif  // OPENTITAN_SW_DEVICE_LIB_TESTING_TEST_FRAMEWORK_OTTF_MAIN_H_
diff --git a/sw/device/tests/example_test_from_flash.c b/sw/device/tests/example_test_from_flash.c
index 8672959..8c73b2b 100644
--- a/sw/device/tests/example_test_from_flash.c
+++ b/sw/device/tests/example_test_from_flash.c
@@ -45,6 +45,10 @@
  *
  * See `sw/device/lib/testing/test_framework/ottf_isrs.c` for implementation
  * details of the default OTTF exception handlers.
+ *
+ * Note, the `ottf_machine_ecall_handler` cannot be overridden when using the
+ * full OTTF, as it it used to implement FreeRTOS context switching. See its
+ * implementation in `sw/device/lib/testing/test_framework/ottf_main.c`.
  */
 // void ottf_exception_handler(void) {}
 // void ottf_instr_misaligned_fault_handler(void) {}
@@ -52,7 +56,6 @@
 // void ottf_illegal_instr_fault_handler(void) {}
 // void ottf_breakpoint_handler(void) {}
 // void ottf_load_store_fault_handler(void) {}
-// void ottf_machine_ecall_handler(void) {}
 // void ottf_user_ecall_handler(void) {}
 
 /**