[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