Initial import of task from xutil

trunk
alexis 2023-02-03 10:55:09 -07:00
commit a70db68857
5 changed files with 328 additions and 0 deletions

102
inc/utask.h Normal file
View File

@ -0,0 +1,102 @@
// intellectual property is bullshit bgdc
#ifndef UTASK_H
#define UTASK_H
#ifdef __cplusplus
extern "C" {
#endif
#define IN_UTASK_H
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#ifdef __riscv
# include "utask_riscv.h"
#else
# error "utask.h does not support this architecture"
#endif
#define UTASK_STACK_CANARY 0x5AD54ACC
// Extremely lightweight cooperative multitasking library. Each task runs on
// its own stack, and yields to other tasks when ready. Tasks are run
// round-robin.
//
// TODO: Allow tasks to exit
// TODO: Stack high water mark
struct utask_s;
typedef struct utask_s {
void (*func)(uintptr_t arg);
uintptr_t * stack_btm;
uintptr_t * stack_top;
char const * name; // Optional
// -- Private - user need not initialize --
uintptr_t _arg;
struct task_s * _next;
bool _started;
struct utask_arch_specific _arch;
} utask_t;
// Get the next task after this one. Useful for traversing the task list.
#define UTASK_NEXT(t) ((t)->_next)
// Pointer to the currently executing task struct
extern utask_t * current_task;
// Pointer to the task list
extern task_t * volatile task_list;
// Add a task to the queue. The queue may be modified at any time.
//
// tasks: task list. The task list should start as NULL.
// task: task to add. Function and stack pointers should be initialized.
// arg: argument passed to task.
//
// Returns the new task list pointer.
void utask_add(utask_t * task, uintptr_t arg);
// Run. Never returns.
void utask_run(void);
// Query task high water mark. Returns how many free words of stack are
// untouched.
size_t utask_stack_free_words(utask_t * task);
// Inhibit sleep. When this is called, a task_sleep_allow() must follow or the
// system will never go to sleep. May nest indefinitely. Preemption-safe, may
// be called from interrupts.
void utask_sleep_inhibit(void);
// Undo task_sleep_inhibit(). Preemption-safe, may be called from interrupts.
void utask_sleep_allow(void);
// Weak-linked stack overflow handler. Override to handle.
void utask_stack_overflow_cb(utask_t * task);
// Weak-linked sleeper. To permit the main loop to go to sleep in between
// interrupts, override this and insert whatever sleep code you want to use.
void utask_sleep_cb(void);
#ifndef utask_yield
// Yield from this task. It will be run the next time around.
#define utask_yield()
#endif
// Yield until a condition is true.
#define utask_yield_until(cond) do { while (!(cond)) utask_yield(); } while (0)
#undef IN_UTASK_H
#ifdef __cplusplus
}
#endif
#endif // !defined(UTASK_H)

72
inc/utask_riscv.h Normal file
View File

@ -0,0 +1,72 @@
// intellectual property is bullshit bgdc
#ifndef UTASK_RISCV_H
#define UTASK_RISCV_H
#ifndef IN_UTASK_H
# error "Do not use utask_riscv.h by itself. Instead, include utask.h"
#endif
// To get the fastest possible context switches, this implementation uses a
// special "micro-longjmp". Normally, setjmp/longjmp saves and restores an
// entire context. This isn't really necessary, though. By declaring all the
// normally callee-saved registers as clobbers, we can simply tell the compiler
// that we WON'T save anything, and it's forced to arrange for these values
// to be saved. Because it probably does not have the full complement of saved
// registers in flight at this moment, it is going to emit significantly less
// memory shuffling than we would have to with no knowledge of its register
// allocations.
//
// We're also floating point safe without needing to reserve space for all
// those floating-point registers even when they're unused.
struct utask_arch_specific
{
uintptr_t pc;
uintptr_t sp;
};
extern struct utask_arch_specific utask_ujb;
#define UTASK_ARCH_SPECIFIC_DEFS \
struct utask_arch_specific __attribute__((used)) utask_ujb;
void utask_riscv_start(uintptr_t arg, void * task, void * tas);
void utask_riscv_continue(void * tas);
void utask_riscv_yield(void * tas);
#define utask_start(task) do { \
(task)->_arch.sp = (uintptr_t) (task)->stack_top; \
__sync_synchronize(); \
utask_riscv_start((task)->_arg, (task), &(task)->_arch); \
asm volatile("" ::: "memory", "cc", \
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", \
"s8", "s9", "s10", "s11", \
"fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", \
"fs8", "fs9", "fs10", "fs11" \
); \
} while (0)
#define utask_continue(task) do { \
__sync_synchronize(); \
utask_riscv_continue(&(task)->_arch); \
asm volatile("" ::: "memory", "cc", \
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", \
"s8", "s9", "s10", "s11", \
"fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", \
"fs8", "fs9", "fs10", "fs11" \
); \
} while (0)
#define utask_yield() do { \
__sync_synchronize(); \
utask_riscv_yield(&current_task->_arch); \
asm volatile("" ::: "memory", "cc", \
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", \
"s8", "s9", "s10", "s11", \
"fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", \
"fs8", "fs9", "fs10", "fs11" \
); \
} while (0)
#endif // !defined(UTASK_RISCV_H)

25
meson.build Normal file
View File

@ -0,0 +1,25 @@
project(
'utask',
['c'],
default_options: ['optimization=2', 'b_staticpic=false']
)
inc = [include_directories('inc')]
src = ['src/utask.c']
if target_machine.cpu_family() == 'riscv32'
src += ['src/utask_riscv.S']
else
error('CPU family not supported: ' + target_machine.cpu_family())
endif
utask_lib = static_library(
'utask',
src,
include_directories: inc,
)
utask_dep = declare_dependency(
include_directories: inc,
link_with: utask_lib,
)

94
src/utask.c Normal file
View File

@ -0,0 +1,94 @@
// intellectual property is bullshit bgdc
#include "utask.h"
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <stdatomic.h>
utask_t * current_task;
utask_t * volatile task_list;
static atomic_uint _sleep_inh;
UTASK_ARCH_SPECIFIC_DEFS
static void _init_stack(utask_t * task);
void utask_add(utask_t * task, uintptr_t arg)
{
task->_next = task_list;
task->_started = false;
task->_arg = arg;
task_list = task;
}
void utask_run(void)
{
for (;;)
{
for (utask_t * task = task_list; task; task = task->_next)
{
current_task = task;
if (task->_started)
{
utask_continue(task);
}
else
{
task->_started = true;
_init_stack(task);
task_start(task);
}
if (*task->stack_btm != UTASK_STACK_CANARY)
utask_stack_overflow_cb(task);
}
if (!_sleep_inh)
utask_sleep_cb();
}
}
void utask_sleep_inhibit(void)
{
atomic_fetch_add(&_sleep_inh, 1);
}
void utask_sleep_allow(void)
{
atomic_fetch_sub(&_sleep_inh, 1);
}
__attribute__((weak))
void utask_stack_overflow_cb(utask_t * task)
{
(void) task;
for (;;);
}
__attribute__((weak))
void utask_sleep_cb(void)
{
}
size_t utask_stack_free_words(utask_t * task)
{
for (uintptr_t * i = task->stack_btm; i < task->stack_top; i++)
{
if (*i != UTASK_STACK_CANARY)
return i - task->stack_btm;
}
return task->stack_top - task->stack_btm;
}
static void _init_stack(utask_t * task)
{
for (uintptr_t * i = task->stack_btm; i < task->stack_top; i++)
{
*i = UTASK_STACK_CANARY;
}
}

35
src/utask_riscv.S Normal file
View File

@ -0,0 +1,35 @@
// intellectual property is bullshit bgdc
.extern utask_ujb
.global utask_riscv_start
// a0: arg
// a1: task
// a2: task->_arch
utask_riscv_start:
la t0, utask_ujb
sw ra, 0(t0)
sw sp, 4(t0)
lw sp, 4(a2)
lw t0, 0(a1)
jr t0
.global utask_riscv_continue
// a0: task->_arch
utask_riscv_continue:
la t0, utask_ujb
sw ra, 0(t0)
sw sp, 4(t0)
lw sp, 4(a0)
lw t0, 0(a0)
jr t0
.global utask_riscv_yield
// a0: task->_arch
utask_riscv_yield:
la t0, utask_ujb
sw ra, 0(a0)
sw sp, 4(a0)
lw sp, 4(t0)
lw t0, 0(t0)
jr t0