[Cryptech-Commits] [sw/stm32] 01/01: Replace the RTOS with a simple cooperative tasker.
git at cryptech.is
git at cryptech.is
Thu Apr 27 21:43:01 UTC 2017
This is an automated email from the git hooks/post-receive script.
paul at psgd.org pushed a commit to branch no-rtos
in repository sw/stm32.
commit bf394f25dacac8e3e3add80ea326312cdd97ed00
Author: Paul Selkirk <paul at psgd.org>
AuthorDate: Thu Apr 27 16:53:56 2017 -0400
Replace the RTOS with a simple cooperative tasker.
There are no priorities and no preemption, so tasks run in a round-robin
fashion, and explicitly yield control.
---
Makefile | 17 +-
projects/hsm/Makefile | 9 +-
projects/hsm/hsm.c | 377 ++++++++++++++++++----------
projects/hsm/mgmt-cli.c | 22 +-
projects/hsm/{mgmt-thread.h => mgmt-task.c} | 52 +++-
projects/hsm/{mgmt-thread.h => mgmt-task.h} | 14 +-
projects/hsm/mgmt-thread.c | 100 --------
task.c | 318 +++++++++++++++++++++++
projects/hsm/mgmt-thread.h => task.h | 44 +++-
9 files changed, 666 insertions(+), 287 deletions(-)
diff --git a/Makefile b/Makefile
index 2944aa9..9f33437 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-# Copyright (c) 2015-2016, NORDUnet A/S
+# Copyright (c) 2015-2017, NORDUnet A/S
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -38,7 +38,6 @@ export LIBS_DIR = $(TOPLEVEL)/libraries
export MBED_DIR = $(LIBS_DIR)/mbed
export CMSIS_DIR = $(MBED_DIR)/targets/cmsis/TARGET_STM/TARGET_STM32F4
export BOARD_DIR = $(CMSIS_DIR)/$(BOARD)
-export RTOS_DIR = $(MBED_DIR)/rtos
export LIBHAL_SRC = $(CRYPTECH_ROOT)/sw/libhal
export LIBHAL_BLD = $(LIBS_DIR)/libhal
@@ -128,15 +127,9 @@ $(MBED_DIR)/libstmf4.a: .FORCE
board-test: $(BOARD_OBJS) $(LIBS) .FORCE
$(MAKE) -C projects/board-test
-cli-test: $(BOARD_OBJS) $(LIBS) $(LIBCLI_BLD)/libcli.a $(LIBHAL_BLD)/libhal.a $(RTOS_DIR)/librtos.a .FORCE
+cli-test: $(BOARD_OBJS) $(LIBS) $(LIBCLI_BLD)/libcli.a $(LIBHAL_BLD)/libhal.a .FORCE
$(MAKE) -C projects/cli-test
-$(RTOS_DIR)/librtos.a: .FORCE
- $(MAKE) -C $(RTOS_DIR)
-
-rtos-test: $(RTOS_OBJS) $(LIBS) $(RTOS_DIR)/librtos.a .FORCE
- $(MAKE) -C projects/rtos-test
-
$(LIBTFM_BLD)/libtfm.a: .FORCE
$(MAKE) -C $(LIBTFM_BLD) PREFIX=$(PREFIX)
@@ -149,7 +142,7 @@ $(LIBCLI_BLD)/libcli.a: .FORCE
libhal-test: $(BOARD_OBJS) $(LIBS) $(LIBHAL_BLD)/libhal.a .FORCE
$(MAKE) -C projects/libhal-test
-hsm: $(BOARD_OBJS) $(LIBS) $(LIBHAL_BLD)/libhal.a $(RTOS_DIR)/librtos.a $(LIBCLI_BLD)/libcli.a .FORCE
+hsm: $(BOARD_OBJS) $(LIBS) $(LIBHAL_BLD)/libhal.a $(LIBCLI_BLD)/libcli.a .FORCE
$(MAKE) -C projects/hsm
bootloader: $(BOARD_OBJS) $(LIBS) $(LIBHAL_BLD)/libhal.a .FORCE
@@ -158,7 +151,7 @@ bootloader: $(BOARD_OBJS) $(LIBS) $(LIBHAL_BLD)/libhal.a .FORCE
# don't automatically delete objects, to avoid a lot of unnecessary rebuilding
.SECONDARY: $(BOARD_OBJS)
-.PHONY: board-test rtos-test libhal-test cli-test hsm bootloader
+.PHONY: board-test libhal-test cli-test hsm bootloader
# We don't (and shouldn't) know enough about libraries and projects to
# know whether they need rebuilding or not, so we let their Makefiles
@@ -174,13 +167,11 @@ clean:
$(MAKE) -C $(LIBHAL_BLD) clean
$(MAKE) -C projects/board-test clean
$(MAKE) -C projects/cli-test clean
- $(MAKE) -C projects/rtos-test clean
$(MAKE) -C projects/libhal-test clean
$(MAKE) -C projects/hsm clean
$(MAKE) -C projects/bootloader clean
distclean: clean
$(MAKE) -C $(MBED_DIR) clean
- $(MAKE) -C $(RTOS_DIR) clean
$(MAKE) -C $(LIBTFM_BLD) clean
$(MAKE) -C $(LIBCLI_BLD) clean
diff --git a/projects/hsm/Makefile b/projects/hsm/Makefile
index 6f941cf..d08fa52 100644
--- a/projects/hsm/Makefile
+++ b/projects/hsm/Makefile
@@ -8,7 +8,7 @@ OBJS = mgmt-cli.o \
mgmt-keystore.c \
mgmt-masterkey.c \
mgmt-misc.c \
- mgmt-thread.c \
+ mgmt-task.c \
log.o
BOARD_OBJS = \
@@ -22,20 +22,19 @@ BOARD_OBJS = \
$(TOPLEVEL)/stm-keystore.o \
$(TOPLEVEL)/stm-sdram.o \
$(TOPLEVEL)/stm-flash.o \
- $(BOARD_DIR)/TOOLCHAIN_GCC_ARM/startup_stm32f429xx_rtos.o \
+ $(BOARD_DIR)/TOOLCHAIN_GCC_ARM/startup_stm32f429xx.o \
$(BOARD_DIR)/system_stm32f4xx.o \
$(BOARD_DIR)/stm32f4xx_hal_msp.o \
- $(BOARD_DIR)/stm32f4xx_it_rtos.o
+ $(BOARD_DIR)/stm32f4xx_it.o \
+ $(TOPLEVEL)/task.o
CFLAGS += -DNUM_RPC_TASK=4
CFLAGS += -I$(LIBHAL_SRC)
CFLAGS += -I$(LIBCLI_SRC)
-CFLAGS += -I$(RTOS_DIR)/rtos -I$(RTOS_DIR)/rtx/TARGET_CORTEX_M
LIBS += $(LIBHAL_BLD)/libhal.a $(LIBTFM_BLD)/libtfm.a
LIBS += $(LIBCLI_BLD)/libcli.a
-LIBS += $(RTOS_DIR)/librtos.a
all: $(PROJ:=.elf)
diff --git a/projects/hsm/hsm.c b/projects/hsm/hsm.c
index f71e2c2..a683b7f 100644
--- a/projects/hsm/hsm.c
+++ b/projects/hsm/hsm.c
@@ -44,13 +44,12 @@
/* Rename both CMSIS HAL_OK and libhal HAL_OK to disambiguate */
#define HAL_OK CMSIS_HAL_OK
-#include "cmsis_os.h"
-
#include "stm-init.h"
#include "stm-led.h"
#include "stm-fmc.h"
#include "stm-uart.h"
#include "stm-sdram.h"
+#include "task.h"
#include "mgmt-cli.h"
@@ -63,10 +62,9 @@
#undef HAL_OK
#ifndef NUM_RPC_TASK
-/* Just one RPC task for now. More will require active resource management
- * of at least the FPGA cores.
- */
#define NUM_RPC_TASK 1
+#elif NUM_RPC_TASK < 1 || NUM_RPC_TASK > 10
+#error invalid NUM_RPC_TASK
#endif
#ifndef TASK_STACK_SIZE
@@ -77,6 +75,21 @@
#define TASK_STACK_SIZE 200*1024
#endif
+/* Stack for the busy task. This doesn't need to be very big.
+ */
+#ifndef BUSY_STACK_SIZE
+#define BUSY_STACK_SIZE 1*1024
+#endif
+static uint8_t busy_stack[BUSY_STACK_SIZE];
+
+/* Stack for the CLI task. This needs to be big enough to accept a
+ * 4096-byte block of an FPGA or bootloader image upload.
+ */
+#ifndef CLI_STACK_SIZE
+#define CLI_STACK_SIZE 8*1024
+#endif
+static uint8_t cli_stack[CLI_STACK_SIZE];
+
#ifndef MAX_PKT_SIZE
/* An arbitrary number, more or less driven by the 4096-bit RSA
* keygen test.
@@ -84,146 +97,238 @@
#define MAX_PKT_SIZE 4096
#endif
-/* RPC buffers. For each active RPC, there will be two - input and output.
+/* RPC buffers. For each active request, there will be two - input and output.
*/
-typedef struct {
+typedef struct rpc_buffer_s {
size_t len;
uint8_t buf[MAX_PKT_SIZE];
+ struct rpc_buffer_s *next; /* for ibuf queue linking */
} rpc_buffer_t;
-/* A mail queue (memory pool + message queue) for RPC request messages.
- */
-osMailQId ibuf_queue;
-osMailQDef(ibuf_queue, NUM_RPC_TASK + 2, rpc_buffer_t);
+/* RPC input (requst) buffers */
+static rpc_buffer_t ibufs[NUM_RPC_TASK];
-#if NUM_RPC_TASK > 1
-/* A mutex to arbitrate concurrent UART transmits, from RPC responses.
- */
-osMutexId uart_mutex;
-osMutexDef(uart_mutex);
-static inline void uart_lock(void) { osMutexWait(uart_mutex, osWaitForever); }
-static inline void uart_unlock(void) { osMutexRelease(uart_mutex); }
-#else
-static inline void uart_lock(void) { }
-static inline void uart_unlock(void) { }
-#endif
+/* ibuf queue structure */
+typedef struct {
+ rpc_buffer_t *head, *tail;
+ size_t len, max; /* for reporting */
+} ibufq_t;
-#if NUM_RPC_TASK > 1
-/* A mutex to arbitrate concurrent access to the keystore.
+/* ibuf queues. These correspond roughly to task states - 'waiting' is for
+ * unallocated ibufs, while 'ready' is for requests that are ready to be
+ * processed.
*/
-osMutexId ks_mutex;
-osMutexDef(ks_mutex);
-void hal_ks_lock(void) { osMutexWait(ks_mutex, osWaitForever); }
-void hal_ks_unlock(void) { osMutexRelease(ks_mutex); }
-#endif
+static ibufq_t ibuf_waiting, ibuf_ready;
-/* A ring buffer for the UART DMA receiver. In theory, it should get at most
- * 92 characters per 1ms tick, but we're going to up-size it for safety.
- */
-#ifndef RPC_UART_RECVBUF_SIZE
-#define RPC_UART_RECVBUF_SIZE 256 /* must be a power of 2 */
-#endif
-#define RPC_UART_RECVBUF_MASK (RPC_UART_RECVBUF_SIZE - 1)
+/* Get an ibuf from a queue. */
+static rpc_buffer_t *ibuf_get(ibufq_t *q)
+{
+ hal_critical_section_start();
+ rpc_buffer_t *ibuf = q->head;
+ if (ibuf) {
+ q->head = ibuf->next;
+ if (q->head == NULL)
+ q->tail = NULL;
+ ibuf->next = NULL;
+ --q->len;
+ }
+ hal_critical_section_end();
+ return ibuf;
+}
-typedef struct {
- uint32_t ridx;
- uint8_t buf[RPC_UART_RECVBUF_SIZE];
-} uart_ringbuf_t;
+/* Put an ibuf on a queue. */
+static void ibuf_put(ibufq_t *q, rpc_buffer_t *ibuf)
+{
+ hal_critical_section_start();
+ if (q->tail)
+ q->tail->next = ibuf;
+ else
+ q->head = ibuf;
+ q->tail = ibuf;
+ ibuf->next = NULL;
+ if (++q->len > q->max)
+ q->max = q->len;
+ hal_critical_section_end();
+}
-volatile uart_ringbuf_t uart_ringbuf = {0, {0}};
+/* Get the current length of the 'ready' queue, for reporting in the CLI. */
+size_t request_queue_len(void)
+{
+ size_t n;
+
+ hal_critical_section_start();
+ n = ibuf_ready.len;
+ hal_critical_section_end();
+
+ return n;
+}
+
+/* Get the maximum length of the 'ready' queue, for reporting in the CLI. */
+size_t request_queue_max(void)
+{
+ size_t n;
-#define RINGBUF_RIDX(rb) (rb.ridx & RPC_UART_RECVBUF_MASK)
-#define RINGBUF_WIDX(rb) (sizeof(rb.buf) - __HAL_DMA_GET_COUNTER(huart_user.hdmarx))
-#define RINGBUF_COUNT(rb) ((unsigned)(RINGBUF_WIDX(rb) - RINGBUF_RIDX(rb)))
-#define RINGBUF_READ(rb, dst) {dst = rb.buf[RINGBUF_RIDX(rb)]; rb.ridx++;}
+ hal_critical_section_start();
+ n = ibuf_ready.max;
+ hal_critical_section_end();
-/* Thread entry point for the UART DMA monitor.
+ return n;
+}
+
+static void dispatch_task(void);
+static void busy_task(void);
+static tcb_t *busy_tcb;
+
+/* Select an available dispatch task. For simplicity, this doesn't try to
+ * allocate tasks in a round-robin fashion, so the lowest-numbered task
+ * will see the most action. OTOH, this lets us gauge the level of system
+ * activity in the CLI's 'task show' command.
*/
-void uart_rx_thread(void const *args)
+static tcb_t *task_next_waiting(void)
{
- /* current RPC input buffer */
- rpc_buffer_t *ibuf = NULL;
+ for (tcb_t *t = task_iterate(NULL); t; t = task_iterate(t)) {
+ if (task_get_func(t) == dispatch_task &&
+ task_get_state(t) == TASK_WAITING)
+ return t;
+ }
+ return NULL;
+}
- /* I wanted to call osThreadYield(), but the documentation is misleading,
- * and it only yields to the next ready thread of the same priority, so
- * this high-priority thread wouldn't let anything else run. osDelay(1)
- * reschedules this thread for the next tick, which is what we want.
+static uint8_t *sdram_malloc(size_t size);
+
+/* Callback for HAL_UART_Receive_DMA().
+ */
+static void RxCallback(uint8_t c)
+{
+ int complete;
+ static rpc_buffer_t *ibuf = NULL;
+
+ /* If we couldn't previously get an ibuf, a task may have freed one up
+ * in the meantime. Otherwise, allocate one from SDRAM. In normal
+ * operation, the number of ibufs will expand to the number of remote
+ * clients (which we don't know and can't predict). It would take an
+ * active attempt to DOS the system to exhaust SDRAM, and there are
+ * easier ways to attack the device (don't release hash or pkey handles).
*/
- for ( ; ; osDelay(1)) {
+ if (ibuf == NULL) {
+ ibuf = ibuf_get(&ibuf_waiting);
if (ibuf == NULL) {
- if ((ibuf = (rpc_buffer_t *)osMailAlloc(ibuf_queue, 1)) == NULL)
- /* This could happen if all dispatch threads are busy, and
- * there are NUM_RPC_TASK requests already queued. We could
- * send a "server busy" error, or we could just try again on
- * the next tick.
- */
+ ibuf = (rpc_buffer_t *)sdram_malloc(sizeof(rpc_buffer_t));
+ if (ibuf == NULL)
Error_Handler();
- ibuf->len = 0;
}
+ ibuf->len = 0;
+ }
- while (RINGBUF_COUNT(uart_ringbuf)) {
- uint8_t c;
- int complete;
-
- RINGBUF_READ(uart_ringbuf, c);
- if (hal_slip_process_char(c, ibuf->buf, &ibuf->len, sizeof(ibuf->buf), &complete) != LIBHAL_OK)
- Error_Handler();
+ /* Process this character into the ibuf. */
+ if (hal_slip_process_char(c, ibuf->buf, &ibuf->len, sizeof(ibuf->buf), &complete) != LIBHAL_OK)
+ Error_Handler();
- if (complete) {
- if (osMailPut(ibuf_queue, (void *)ibuf) != osOK)
- Error_Handler();
- ibuf = NULL;
- /* Yield, to allow one of the dispatch threads to pick up this
- * new request.
- */
- break;
- }
- }
+ if (complete) {
+ /* Add the ibuf to the request queue, and try to get another ibuf.
+ */
+ ibuf_put(&ibuf_ready, ibuf);
+ ibuf = ibuf_get(&ibuf_waiting);
+ if (ibuf != NULL)
+ ibuf->len = 0;
+ /* else all ibufs are busy, try again next time */
+
+ /* Wake a dispatch task to deal with this request, or wake the
+ * busy task to re-try scheduling a dispatch task.
+ */
+ tcb_t *t = task_next_waiting();
+ if (t)
+ task_wake(t);
+ else
+ task_wake(busy_tcb);
}
}
-osThreadDef(uart_rx_thread, osPriorityHigh, DEFAULT_STACK_SIZE);
+static uint8_t uart_rx[2]; /* current character received from UART */
+static uint32_t uart_rx_idx = 0;
+
+/* UART DMA half-complete and complete callbacks. With a 2-character DMA
+ * buffer, one or the other of these will fire on each incoming character.
+ * Under heavy load, these will sometimes fire in the wrong order, but the
+ * data are in the right order in the DMA buffer, so we have a flip-flop
+ * buffer index that doesn't depend on the order of the callbacks.
+ */
+void HAL_UART2_RxHalfCpltCallback(UART_HandleTypeDef *huart)
+{
+ RxCallback(uart_rx[uart_rx_idx]);
+ uart_rx_idx ^= 1;
+}
+
+void HAL_UART2_RxCpltCallback(UART_HandleTypeDef *huart)
+{
+ RxCallback(uart_rx[uart_rx_idx]);
+ uart_rx_idx ^= 1;
+}
+
+/* Send one character over the UART. This is called from
+ * hal_slip_send_char().
+ */
hal_error_t hal_serial_send_char(uint8_t c)
{
return (uart_send_char2(STM_UART_USER, c) == 0) ? LIBHAL_OK : HAL_ERROR_RPC_TRANSPORT;
}
-/* Thread entry point for the RPC request handler.
+/* Task entry point for the RPC request handler.
*/
-void dispatch_thread(void const *args)
+static void dispatch_task(void)
{
- rpc_buffer_t obuf_s, *obuf = &obuf_s, *ibuf;
+ rpc_buffer_t obuf_s, *obuf = &obuf_s;
while (1) {
- memset(obuf, 0, sizeof(*obuf));
- obuf->len = sizeof(obuf->buf);
-
/* Wait for a complete RPC request */
- osEvent evt = osMailGet(ibuf_queue, osWaitForever);
- if (evt.status != osEventMail)
+ task_sleep();
+
+ rpc_buffer_t *ibuf = ibuf_get(&ibuf_ready);
+ if (ibuf == NULL)
+ /* probably an error, but go back to sleep */
continue;
- ibuf = (rpc_buffer_t *)evt.value.p;
+
+ memset(obuf, 0, sizeof(*obuf));
+ obuf->len = sizeof(obuf->buf);
/* Process the request */
- hal_error_t ret = hal_rpc_server_dispatch(ibuf->buf, ibuf->len, obuf->buf, &obuf->len);
- osMailFree(ibuf_queue, (void *)ibuf);
- if (ret != LIBHAL_OK) {
- /* If hal_rpc_server_dispatch failed with an XDR error, it
- * probably means the request packet was garbage. In any case, we
- * have nothing to transmit.
- */
- continue;
- }
+ hal_error_t ret = hal_rpc_server_dispatch(ibuf->buf, ibuf->len, obuf->buf, &obuf->len);
+ ibuf_put(&ibuf_waiting, ibuf);
+ if (ret == LIBHAL_OK) {
+ /* Send the response */
+ if (hal_rpc_sendto(obuf->buf, obuf->len, NULL) != LIBHAL_OK)
+ Error_Handler();
+ }
+ /* Else hal_rpc_server_dispatch failed with an XDR error, which
+ * probably means the request packet was garbage. In any case, we
+ * have nothing to transmit.
+ */
+ }
+}
- /* Send the response */
- uart_lock();
- ret = hal_rpc_sendto(obuf->buf, obuf->len, NULL);
- uart_unlock();
- if (ret != LIBHAL_OK)
- Error_Handler();
+/* Task entry point for the task-rescheduling task.
+ */
+static void busy_task(void)
+{
+ while (1) {
+ /* Wake as many tasks as we have requests.
+ */
+ size_t n;
+ for (n = request_queue_len(); n > 0; --n) {
+ tcb_t *t;
+ if ((t = task_next_waiting()) != NULL)
+ task_wake(t);
+ else
+ break;
+ }
+ if (n == 0)
+ /* flushed the queue, our work here is done */
+ task_sleep();
+ else
+ /* more work to do, try again after some tasks have run */
+ task_yield();
}
}
-osThreadDef_t thread_def[NUM_RPC_TASK];
/* Allocate memory from SDRAM1. There is only malloc, no free, so we don't
* worry about fragmentation. */
@@ -255,8 +360,7 @@ void *hal_allocate_static_memory(const size_t size)
return sdram_malloc(size);
}
-#if NUM_RPC_TASK > 1
-/* Critical section start/end, currently used just for hal_core_alloc/_free.
+/* Critical section start/end - temporarily disable interrupts.
*/
void hal_critical_section_start(void)
{
@@ -267,12 +371,19 @@ void hal_critical_section_end(void)
{
__enable_irq();
}
-#endif
-/* The main thread. This does all the setup, and the worker threads handle
+/* A genericized public interface to task_yield(), for calling from
+ * libhal.
+ */
+void hal_task_yield(void)
+{
+ task_yield();
+}
+
+/* The main task. This does all the setup, and the worker tasks handle
* the rest.
*/
-int main()
+int main(void)
{
stm_init();
uart_set_default(STM_UART_MGMT);
@@ -282,41 +393,43 @@ int main()
fmc_init();
sdram_init();
- if ((ibuf_queue = osMailCreate(osMailQ(ibuf_queue), NULL)) == NULL)
+ if (hal_rpc_server_init() != LIBHAL_OK)
Error_Handler();
-#if NUM_RPC_TASK > 1
- if ((uart_mutex = osMutexCreate(osMutex(uart_mutex))) == NULL)
- Error_Handler();
- if ((ks_mutex = osMutexCreate(osMutex(ks_mutex))) == NULL)
- Error_Handler();
-#endif
-
- if (hal_rpc_server_init() != LIBHAL_OK)
- Error_Handler();
+ /* Initialize the ibuf queues. */
+ memset(&ibuf_waiting, 0, sizeof(ibuf_waiting));
+ memset(&ibuf_ready, 0, sizeof(ibuf_ready));
+ for (int i = 0; i < sizeof(ibufs)/sizeof(ibufs[0]); ++i)
+ ibuf_put(&ibuf_waiting, &ibufs[i]);
- /* Create the rpc dispatch worker threads. */
+ /* Create the rpc dispatch worker tasks. */
+ static char label[NUM_RPC_TASK][sizeof("dispatch0")];
for (int i = 0; i < NUM_RPC_TASK; ++i) {
- osThreadDef_t *ot = &thread_def[i];
- ot->pthread = dispatch_thread;
- ot->tpriority = osPriorityNormal;
- ot->stacksize = TASK_STACK_SIZE;
- ot->stack_pointer = (uint32_t *)(sdram_malloc(TASK_STACK_SIZE));
- if (ot->stack_pointer == NULL)
+ sprintf(label[i], "dispatch%d", i);
+ void *stack = (void *)sdram_malloc(TASK_STACK_SIZE);
+ if (stack == NULL)
Error_Handler();
- if (osThreadCreate(ot, (void *)i) == NULL)
+ if (task_add(label[i], dispatch_task, &ibufs[i], stack, TASK_STACK_SIZE) == NULL)
Error_Handler();
}
- /* Start the UART receiver. */
- if (HAL_UART_Receive_DMA(&huart_user, (uint8_t *) uart_ringbuf.buf, sizeof(uart_ringbuf.buf)) != CMSIS_HAL_OK)
+ /* Create the busy task. */
+ busy_tcb = task_add("busy", busy_task, NULL, busy_stack, sizeof(busy_stack));
+ if (busy_tcb == NULL)
Error_Handler();
- if (osThreadCreate(osThread(uart_rx_thread), NULL) == NULL)
+
+ /* Start the UART receiver. */
+ if (HAL_UART_Receive_DMA(&huart_user, uart_rx, 2) != CMSIS_HAL_OK)
Error_Handler();
- /* Launch other threads (csprng warm-up thread?)
+ /* Launch other tasks (csprng warm-up task?)
* Wait for FPGA_DONE interrupt.
*/
- return cli_main();
+ /* Create the CLI task. */
+ if (task_add("cli", (funcp_t)cli_main, NULL, cli_stack, sizeof(cli_stack)) == NULL)
+ Error_Handler();
+
+ /* Start the tasker */
+ task_yield();
}
diff --git a/projects/hsm/mgmt-cli.c b/projects/hsm/mgmt-cli.c
index 3c1a3bc..ec9bf8f 100644
--- a/projects/hsm/mgmt-cli.c
+++ b/projects/hsm/mgmt-cli.c
@@ -3,7 +3,7 @@
* ---------
* Management CLI code.
*
- * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ * Copyright (c) 2016-2017, NORDUnet A/S All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -36,11 +36,10 @@
/* Rename both CMSIS HAL_OK and libhal HAL_OK to disambiguate */
#define HAL_OK CMSIS_HAL_OK
-#include "cmsis_os.h"
-
#include "stm-init.h"
#include "stm-uart.h"
#include "stm-led.h"
+#include "task.h"
#include "mgmt-cli.h"
#include "mgmt-firmware.h"
@@ -49,7 +48,7 @@
#include "mgmt-misc.h"
#include "mgmt-keystore.h"
#include "mgmt-masterkey.h"
-#include "mgmt-thread.h"
+#include "mgmt-task.h"
#undef HAL_OK
#define HAL_OK LIBHAL_OK
@@ -58,6 +57,8 @@
#include "hal_internal.h"
#undef HAL_OK
+static tcb_t *cli_task;
+
#ifndef CLI_UART_RECVBUF_SIZE
#define CLI_UART_RECVBUF_SIZE 256
#endif
@@ -98,17 +99,12 @@ static ringbuf_t uart_ringbuf;
/* current character received from UART */
static uint8_t uart_rx;
-/* Semaphore to inform uart_cli_read that there's a new character.
- */
-osSemaphoreId uart_sem;
-osSemaphoreDef(uart_sem);
-
/* Callback for HAL_UART_Receive_DMA().
*/
void HAL_UART1_RxCpltCallback(UART_HandleTypeDef *huart)
{
ringbuf_write_char(&uart_ringbuf, uart_rx);
- osSemaphoreRelease(uart_sem);
+ task_wake(cli_task);
}
static void uart_cli_print(struct cli_def *cli __attribute__ ((unused)), const char *buf)
@@ -122,7 +118,7 @@ static ssize_t uart_cli_read(struct cli_def *cli __attribute__ ((unused)), void
{
for (int i = 0; i < count; ++i) {
while (ringbuf_read_char(&uart_ringbuf, (uint8_t *)(buf + i)) == 0)
- osSemaphoreWait(uart_sem, osWaitForever);
+ task_sleep();
}
return (ssize_t)count;
}
@@ -175,7 +171,7 @@ static int check_auth(const char *username, const char *password)
int cli_main(void)
{
- uart_sem = osSemaphoreCreate(osSemaphore(uart_sem), 0);
+ cli_task = task_get_tcb();
struct cli_def *cli;
cli = cli_init();
@@ -198,7 +194,7 @@ int cli_main(void)
configure_cli_firmware(cli);
configure_cli_bootloader(cli);
configure_cli_misc(cli);
- configure_cli_thread(cli);
+ configure_cli_task(cli);
while (1) {
control_mgmt_uart_dma_rx(DMA_RX_START);
diff --git a/projects/hsm/mgmt-thread.h b/projects/hsm/mgmt-task.c
similarity index 52%
copy from projects/hsm/mgmt-thread.h
copy to projects/hsm/mgmt-task.c
index f72695e..a1ae7e6 100644
--- a/projects/hsm/mgmt-thread.h
+++ b/projects/hsm/mgmt-task.c
@@ -1,9 +1,9 @@
/*
- * mgmt-thread.h
+ * mgmt-task.c
* -----------
- * Management CLI 'thread' functions.
+ * CLI 'task' functions.
*
- * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ * Copyright (c) 2016-2017, NORDUnet A/S All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -32,11 +32,47 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef __STM32_CLI_MGMT_THREAD_H
-#define __STM32_CLI_MGMT_THREAD_H
+/*
+ * Show the active tasks. This is mostly for debugging, and looks deeply
+ * into OS-level structures, but sometimes you just need to know...
+ */
+
+#include "mgmt-cli.h"
+#include "mgmt-task.h"
+#include "task.h"
+
+static char *task_state[] = {
+ "INIT",
+ "WAITING",
+ "READY"
+};
+
+extern size_t request_queue_len(void);
+extern size_t request_queue_max(void);
+
+static int cmd_task_show(struct cli_def *cli, const char *command, char *argv[], int argc)
+{
+ cli_print(cli, "name state stack high water");
+ cli_print(cli, "-------- -------- ----------------");
+
+ for (tcb_t *t = task_iterate(NULL); t != NULL; t = task_iterate(t)) {
+ cli_print(cli, "%-15s %-15s %d",
+ task_get_name(t),
+ task_state[task_get_state(t)],
+ task_get_stack_highwater(t));
+ }
+
+ cli_print(cli, " ");
+ cli_print(cli, "request queue current length: %d", request_queue_len());
+ cli_print(cli, "request queue maximum length: %d", request_queue_max());
-#include <libcli.h>
+ return CLI_OK;
+}
-extern void configure_cli_thread(struct cli_def *cli);
+void configure_cli_task(struct cli_def *cli)
+{
+ struct cli_command *c = cli_register_command(cli, NULL, "task", NULL, 0, 0, NULL);
-#endif /* __STM32_CLI_MGMT_THREAD_H */
+ /* task show */
+ cli_register_command(cli, c, "show", cmd_task_show, 0, 0, "Show the active tasks");
+}
diff --git a/projects/hsm/mgmt-thread.h b/projects/hsm/mgmt-task.h
similarity index 84%
copy from projects/hsm/mgmt-thread.h
copy to projects/hsm/mgmt-task.h
index f72695e..f903962 100644
--- a/projects/hsm/mgmt-thread.h
+++ b/projects/hsm/mgmt-task.h
@@ -1,9 +1,9 @@
/*
- * mgmt-thread.h
+ * mgmt-task.h
* -----------
- * Management CLI 'thread' functions.
+ * Management CLI 'task' functions.
*
- * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ * Copyright (c) 2016-2017, NORDUnet A/S All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -32,11 +32,11 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef __STM32_CLI_MGMT_THREAD_H
-#define __STM32_CLI_MGMT_THREAD_H
+#ifndef __STM32_CLI_MGMT_TASK_H
+#define __STM32_CLI_MGMT_TASK_H
#include <libcli.h>
-extern void configure_cli_thread(struct cli_def *cli);
+extern void configure_cli_task(struct cli_def *cli);
-#endif /* __STM32_CLI_MGMT_THREAD_H */
+#endif /* __STM32_CLI_MGMT_TASK_H */
diff --git a/projects/hsm/mgmt-thread.c b/projects/hsm/mgmt-thread.c
deleted file mode 100644
index 96776aa..0000000
--- a/projects/hsm/mgmt-thread.c
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * mgmt-thread.c
- * -----------
- * CLI 'thread' functions.
- *
- * Copyright (c) 2016, NORDUnet A/S All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- * - Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * - Neither the name of the NORDUnet nor the names of its contributors may
- * be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/*
- * Show the active threads. This is mostly for debugging, and looks deeply
- * into OS-level structures, but sometimes you just need to know...
- */
-
-#include "mgmt-cli.h"
-#include "mgmt-thread.h"
-
-/* rt_TypeDef.h redefines NULL (previously defined in stddef.h, via libcli.h) */
-#undef NULL
-
-#include "rt_TypeDef.h"
-#include "RTX_Conf.h"
-
-static char *task_state[] = {
- "INACTIVE",
- "READY",
- "RUNNING",
- "WAIT_DLY",
- "WAIT_ITV",
- "WAIT_OR",
- "WAIT_AND",
- "WAIT_SEM",
- "WAIT_MBX",
- "WAIT_MUT",
-};
-
-static int cmd_thread_show(struct cli_def *cli, const char *command, char *argv[], int argc)
-{
- OS_TID task_id;
- P_TCB task;
- char *name;
- extern void main(void);
- extern void dispatch_thread(void);
- extern void osTimerThread(void);
- extern void uart_rx_thread(void);
-
- for (task_id = 1; task_id <= os_maxtaskrun; ++ task_id) {
- if ((task = os_active_TCB[task_id-1]) != NULL) {
- if (task->ptask == main)
- name = "main";
- else if (task->ptask == dispatch_thread)
- name = "dispatch_thread";
- else if (task->ptask == osTimerThread)
- name = "osTimerThread";
- else if (task->ptask == uart_rx_thread)
- name = "uart_rx_thread";
- else
- name = "unknown";
-
- cli_print(cli, "%d:\tptask\t%p\t%s", task_id, task->ptask, name);
- cli_print(cli, "\tstate\t%d\t\t%s", (int)task->state, task_state[task->state]);
- cli_print(cli, "\tprio\t%d", (int)task->prio);
- cli_print(cli, "\tstack\t%p", task->stack);
- }
- }
- return CLI_OK;
-}
-
-void configure_cli_thread(struct cli_def *cli)
-{
- struct cli_command *c = cli_register_command(cli, NULL, "thread", NULL, 0, 0, NULL);
-
- /* thread show */
- cli_register_command(cli, c, "show", cmd_thread_show, 0, 0, "Show the active threads");
-}
diff --git a/task.c b/task.c
new file mode 100644
index 0000000..980ca1d
--- /dev/null
+++ b/task.c
@@ -0,0 +1,318 @@
+/*
+ * task.c
+ * ----------------
+ * Simple cooperative tasking system.
+ *
+ * Copyright (c) 2017, NORDUnet A/S All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the NORDUnet nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Dead-simple fully-cooperative tasker. There are no priorities; tasks
+ * are run in a strictly round-robin fashion. There is no preemption;
+ * tasks explicitly yield control. Tasks are created at system init time,
+ * and are expected to run an infinite loop; tasks do not return, nor are
+ * tasks deleted.
+ */
+
+#include "stm-init.h"
+#include "task.h"
+
+/* Task Control Block. The structure is private, in case we want to change
+ * it later without having to change the API. In any case, external code
+ * shouldn't poke its fingers in the internal details.
+ */
+struct task_cb {
+ struct task_cb *next;
+ task_state_t state;
+
+ char *name;
+ funcp_t func;
+ void *cookie;
+
+ void *stack_base;
+ size_t stack_len;
+ void *stack_ptr;
+};
+
+/* Number of tasks. Default is number of RPC dispatch tasks, plus CLI task. */
+#ifndef MAX_TASK
+#ifdef NUM_RPC_TASK
+#define MAX_TASK (NUM_RPC_TASK + 2)
+#else
+#define MAX_TASK 6
+#endif
+#endif
+
+static tcb_t tcbs[MAX_TASK];
+static size_t num_task = 0;
+
+/* We have a circular list of tasks. New tasks are added at the tail, and
+ * tail->next is the head.
+ */
+static tcb_t *tail = NULL;
+
+/* Currently running task */
+static tcb_t *cur_task = NULL;
+
+#define STACK_GUARD_WORD 0x55AA5A5A
+
+/* Add a task.
+ */
+tcb_t *task_add(char *name, funcp_t func, void *cookie, void *stack, size_t stack_len)
+{
+ if (num_task >= MAX_TASK)
+ return NULL;
+
+ if (name == NULL || func == NULL || stack == NULL)
+ return NULL;
+
+ tcb_t *t = &tcbs[num_task++];
+ t->state = TASK_INIT;
+
+ t->name = name;
+ t->func = func;
+ t->cookie = cookie;
+
+ t->stack_base = stack;
+ t->stack_len = stack_len;
+ t->stack_ptr = stack + stack_len;
+
+ for (uint32_t *p = (uint32_t *)t->stack_base; p < (uint32_t *)t->stack_ptr; ++p)
+ *p = STACK_GUARD_WORD;
+
+ if (tail == NULL) {
+ /* Empty list; initialize it to this task. */
+ t->next = t;
+ tail = t;
+ }
+ else {
+ /* Otherwise insert at the end of the list. */
+ t->next = tail->next;
+ tail->next = t;
+ tail = t;
+ }
+
+ return t;
+}
+
+/* Set the idle hook function pointer.
+ *
+ * This function is called repeatedly when the system is idle (there are
+ * no runnable tasks).
+ */
+static void default_idle_hook(void) { }
+static funcp_t idle_hook = default_idle_hook;
+void task_set_idle_hook(funcp_t func)
+{
+ idle_hook = (func == NULL) ? default_idle_hook : func;
+}
+
+/* Find the next runnable task.
+ */
+static tcb_t *next_task(void)
+{
+ tcb_t *t;
+
+ /* If the tasker isn't running yet, return the first task. */
+ if (cur_task == NULL)
+ return (tail == NULL) ? NULL : tail->next;
+
+ // XXX critical section?
+
+ /* find the next runnable task */
+ for (t = cur_task->next; t != cur_task; t = t->next) {
+ if (t->state != TASK_WAITING)
+ return t;
+ }
+
+ /* searched all the way back to cur_task - is it runnable? */
+ return (cur_task->state == TASK_WAITING) ? NULL : cur_task;
+}
+
+/* Check for stack overruns.
+ */
+static void check_stack(tcb_t *t)
+{
+ if (t->stack_ptr < t->stack_base ||
+ t->stack_ptr >= t->stack_base + t->stack_len ||
+ *(uint32_t *)t->stack_base != STACK_GUARD_WORD)
+ Error_Handler();
+}
+
+/* Yield control to the next runnable task.
+ */
+void task_yield(void)
+{
+ tcb_t *next;
+
+ /* Find the next runnable task. Loop if every task is waiting. */
+ while (1) {
+ next = next_task();
+ if (next == NULL)
+ idle_hook();
+ else
+ break;
+ }
+
+ /* If we decide we don't need the idle hook, the preceding loop could
+ * devolve to something like this:
+ *
+ * do {
+ * next = next_task();
+ * } while (next == NULL);
+ */
+
+ /* If there are no other runnable tasks (and cur_task is runnable),
+ * we don't need to context-switch.
+ */
+ if (next == cur_task)
+ return;
+
+ /* Save current context, if there is one. */
+ if (cur_task != NULL) {
+ __asm("push {r0-r12, lr}");
+ cur_task->stack_ptr = (void *)__get_MSP();
+
+ /* Check for stack overruns. */
+ check_stack(cur_task);
+ }
+
+ cur_task = next;
+
+ /* If task is in init state, call its entry point. */
+ if (cur_task->state == TASK_INIT) {
+ __set_MSP((uint32_t)cur_task->stack_ptr);
+ cur_task->state = TASK_READY;
+ cur_task->func();
+ /*NOTREACHED*/
+ }
+
+ /* Otherwise, restore the task's context. */
+ else {
+ __set_MSP((uint32_t)cur_task->stack_ptr);
+ __asm("pop {r0-r12, lr}");
+ return;
+ }
+}
+
+/* Put the current task to sleep (make it non-runnable).
+ */
+void task_sleep(void)
+{
+ if (cur_task != NULL)
+ cur_task->state = TASK_WAITING;
+
+ task_yield();
+}
+
+/* Wake a task (make it runnable).
+ */
+void task_wake(tcb_t *t)
+{
+ if (t != NULL)
+ t->state = TASK_READY;
+}
+
+/* Accessor functions */
+
+tcb_t *task_get_tcb(void)
+{
+ return cur_task;
+}
+
+char *task_get_name(tcb_t *t)
+{
+ if (t == NULL)
+ t = cur_task;
+
+ return t->name;
+}
+
+funcp_t task_get_func(tcb_t *t)
+{
+ if (t == NULL)
+ t = cur_task;
+
+ return t->func;
+}
+
+void *task_get_cookie(tcb_t *t)
+{
+ if (t == NULL)
+ t = cur_task;
+
+ return t->cookie;
+}
+
+task_state_t task_get_state(tcb_t *t)
+{
+ if (t == NULL)
+ t = cur_task;
+
+ return t->state;
+}
+
+void *task_get_stack(tcb_t *t)
+{
+ if (t == NULL)
+ t = cur_task;
+
+ return t->stack_ptr;
+}
+
+/* stupid linear search for first non guard word */
+size_t task_get_stack_highwater(tcb_t *t)
+{
+ if (t == NULL)
+ t = cur_task;
+
+ const uint32_t * const b = (uint32_t *)t->stack_base;
+
+ for (size_t i = 0; i < t->stack_len/4; ++i) {
+ if (b[i] != STACK_GUARD_WORD) {
+ return (t->stack_len - (i * 4));
+ }
+ }
+
+ return 0;
+}
+
+/* Iterate through tasks.
+ *
+ * Returns the next task control block, or NULL at the end of the list.
+ */
+tcb_t *task_iterate(tcb_t *t)
+{
+ if (t == NULL)
+ return (tail == NULL) ? NULL : tail->next;
+
+ if (t == tail)
+ return NULL;
+
+ return t->next;
+}
diff --git a/projects/hsm/mgmt-thread.h b/task.h
similarity index 62%
rename from projects/hsm/mgmt-thread.h
rename to task.h
index f72695e..ee0dc61 100644
--- a/projects/hsm/mgmt-thread.h
+++ b/task.h
@@ -1,9 +1,9 @@
/*
- * mgmt-thread.h
- * -----------
- * Management CLI 'thread' functions.
+ * task.c
+ * ----------------
+ * Simple cooperative tasking system.
*
- * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ * Copyright (c) 2017, NORDUnet A/S All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
@@ -32,11 +32,37 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#ifndef __STM32_CLI_MGMT_THREAD_H
-#define __STM32_CLI_MGMT_THREAD_H
+#ifndef _TASK_H_
+#define _TASK_H_
-#include <libcli.h>
+#include <sys/types.h>
-extern void configure_cli_thread(struct cli_def *cli);
+typedef enum task_state {
+ TASK_INIT,
+ TASK_WAITING,
+ TASK_READY
+} task_state_t;
-#endif /* __STM32_CLI_MGMT_THREAD_H */
+typedef struct task_cb tcb_t;
+
+typedef void (*funcp_t)(void);
+
+extern tcb_t *task_add(char *name, funcp_t func, void *cookie, void *stack, size_t stack_len);
+
+extern void task_yield(void);
+extern void task_sleep(void);
+extern void task_wake(tcb_t *t);
+
+extern tcb_t *task_get_tcb(void);
+extern char *task_get_name(tcb_t *t);
+extern funcp_t task_get_func(tcb_t *t);
+extern void *task_get_cookie(tcb_t *t);
+extern task_state_t task_get_state(tcb_t *t);
+extern void *task_get_stack(tcb_t *t);
+extern size_t task_get_stack_highwater(tcb_t *t);
+
+extern tcb_t *task_iterate(tcb_t *t);
+
+extern void task_set_idle_hook(funcp_t func);
+
+#endif /* _TASK_H_ */
More information about the Commits
mailing list