Initial import of task from xutil
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