commit
a70db68857
@ -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)
|
@ -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(¤t_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)
|
@ -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,
|
||||
)
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
Loading…
Reference in new issue