mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
300 lines
7.3 KiB
C
300 lines
7.3 KiB
C
/**
|
|
* @brief
|
|
*
|
|
* // Quite OK RTOS scheduler is a very simple real-time, pre-emptive, tickless tasker
|
|
* Design goals:
|
|
* - Easily portable (limited assembly)
|
|
* - Tick-less
|
|
* - Preemptive
|
|
* - Only one inter-thread resource: mailboxes (no mutex, semaphore...)
|
|
|
|
*/
|
|
|
|
#include "ost_hal.h"
|
|
#include "debug.h"
|
|
#include "os.h"
|
|
#include <stdlib.h>
|
|
|
|
#include "pico/critical_section.h"
|
|
|
|
/**
|
|
* The fn OSAsm_ThreadSwitch, implemented in os_asm.s, is periodically called by the SchedlTimer (ISR).
|
|
* It preemptively switches to the next thread, that is, it stores the stack of the running
|
|
* thread and restores the stack of the next thread.
|
|
* It calls OS_Schedule to determine which thread is run next and update RunPt.
|
|
*/
|
|
extern void OSAsm_ThreadSwitch(void);
|
|
|
|
static critical_section_t acrit;
|
|
|
|
inline static void enable_irq()
|
|
{
|
|
critical_section_exit(&acrit);
|
|
}
|
|
|
|
inline static void disable_irq()
|
|
{
|
|
critical_section_enter_blocking(&acrit);
|
|
}
|
|
|
|
void timer_set_period(uint16_t i);
|
|
|
|
void ost_tasker_sleep_for(uint32_t ms)
|
|
{
|
|
timer_set_period(ms);
|
|
}
|
|
|
|
void ost_tasker_init()
|
|
{
|
|
ost_tasker_sleep_for(5000); // 5 seconds
|
|
}
|
|
|
|
static uint32_t counter = 0;
|
|
|
|
// void ost_tasker_timer_callback()
|
|
// {
|
|
// // debug_printf("%d\n", counter++);
|
|
|
|
// qor_switch_context();
|
|
// }
|
|
|
|
#define OS_DEBUG
|
|
|
|
/**
|
|
* TCBState indicates whether the TCB can be used by OS_ThreadCreate
|
|
* to create a new thread.
|
|
*/
|
|
typedef enum
|
|
{
|
|
TCBStateFree,
|
|
TCBStateActive
|
|
} TCBState_t;
|
|
|
|
void qor_sleep_ms(uint8_t svc, uint32_t ms)
|
|
{
|
|
|
|
__wfi;
|
|
}
|
|
|
|
/**
|
|
* Thread Control Block
|
|
*
|
|
* IMPORTANT!
|
|
* The fn OSAsm_Start and OSAsm_ThreadSwitch, implemented in os_asm.s, expect the stack pointer
|
|
* to be placed first in the struct. Don't shuffle it!
|
|
*/
|
|
typedef struct TCB
|
|
{
|
|
uint32_t *sp; /* Stack pointer, valid for threads not running */
|
|
struct TCB *next; /* Pointer to circular-linked-list of TCBs */
|
|
uint32_t sleep; /* Sleep duration in ms, zero means not sleeping */
|
|
TCBState_t status; /* TCB active or free */
|
|
Semaphore_t *blocked; /* Pointer to semaphore on which the thread is blocked, NULL if not blocked */
|
|
uint8_t priority; /* Thread priority, 0 is highest, 255 is lowest */
|
|
const char *name; /* Descriptive name to facilitate debugging */
|
|
uint32_t pc;
|
|
} TCB_t;
|
|
|
|
//==================================================================================================
|
|
// GLOBAL AND STATIC VARIABLES
|
|
//==================================================================================================
|
|
|
|
static TCB_t TCBs[MAXNUMTHREADS];
|
|
static uint32_t Stacks[MAXNUMTHREADS][STACKSIZE];
|
|
|
|
/* Pointer to the currently running thread */
|
|
TCB_t *RunPt;
|
|
|
|
static uint32_t global_stack[1000];
|
|
|
|
/* The variable ActiveTCBsCount tracks the number of TCBs in use by the OS */
|
|
static uint32_t ActiveTCBsCount;
|
|
|
|
static void OS_InitTCBsStatus(void)
|
|
{
|
|
for (uint32_t idx = 0; idx < MAXNUMTHREADS; idx++)
|
|
{
|
|
TCBs[idx].status = TCBStateFree;
|
|
}
|
|
}
|
|
|
|
void OS_Init(uint32_t scheduler_frequency_hz)
|
|
{
|
|
critical_section_init(&acrit);
|
|
OS_InitTCBsStatus();
|
|
}
|
|
|
|
void OS_ExitLoop()
|
|
{
|
|
for (;;)
|
|
;
|
|
}
|
|
|
|
extern void qor_go();
|
|
|
|
uint32_t *qor_initialize_stack(uint32_t *top_of_stack, thread_func_t task, void *args)
|
|
{
|
|
// ARM Calling convention: the folowwing registers are automatically saved onto the stack by the processor (in this ordoer on the stack)
|
|
// DDI0419C_arm_architecture_v6m_reference_manual-1.pdf B1.5.6 Exception entry behavior
|
|
top_of_stack--;
|
|
/* From the "STM32 Cortex-M4 Programming Manual" on page 23:
|
|
* attempting to execute instructions when the T bit is 0 results in a fault or lockup */
|
|
*top_of_stack = 0x01000000; /* Thumb Bit (PSR) */
|
|
top_of_stack--;
|
|
*top_of_stack = (uint32_t)task; // PC Program Counter (R15)
|
|
top_of_stack--;
|
|
*top_of_stack = (uint32_t)OS_ExitLoop; /* (LR) Link Register (Return address) R14 */
|
|
top_of_stack -= 5; // skip R12, R3, R2, R1
|
|
*top_of_stack = (uint32_t)args; // R0
|
|
top_of_stack -= 8; // R11 -> R4
|
|
return top_of_stack;
|
|
}
|
|
|
|
void qor_create_thread(thread_func_t task, uint8_t priority, const char *name)
|
|
{
|
|
assert_or_panic(ActiveTCBsCount >= 0 && ActiveTCBsCount < MAXNUMTHREADS);
|
|
disable_irq();
|
|
|
|
/* Find next available TCB */
|
|
int32_t new_tcb_idx = -1;
|
|
for (new_tcb_idx = 0; new_tcb_idx < MAXNUMTHREADS; new_tcb_idx++)
|
|
{
|
|
if (TCBs[new_tcb_idx].status == TCBStateFree)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (new_tcb_idx >= 0)
|
|
{
|
|
if (new_tcb_idx == 0)
|
|
{
|
|
RunPt = &(TCBs[0]);
|
|
}
|
|
else
|
|
{
|
|
TCBs[new_tcb_idx].next = RunPt->next;
|
|
}
|
|
|
|
TCBs[new_tcb_idx].sleep = 0;
|
|
TCBs[new_tcb_idx].status = TCBStateActive;
|
|
TCBs[new_tcb_idx].blocked = NULL;
|
|
TCBs[new_tcb_idx].priority = priority;
|
|
TCBs[new_tcb_idx].name = name;
|
|
TCBs[new_tcb_idx].sp = qor_initialize_stack(&Stacks[new_tcb_idx][STACKSIZE], task, (void *)name);
|
|
|
|
RunPt->next = &(TCBs[new_tcb_idx]);
|
|
ActiveTCBsCount++;
|
|
}
|
|
|
|
enable_irq();
|
|
}
|
|
|
|
void qor_start(void)
|
|
{
|
|
assert_or_panic(ActiveTCBsCount > 0);
|
|
|
|
/* Prevent the timer's ISR from firing before OSAsm_Start is called */
|
|
disable_irq();
|
|
|
|
qor_go();
|
|
|
|
/* This statement should not be reached */
|
|
ost_hal_panic();
|
|
}
|
|
|
|
void qor_scheduler(void)
|
|
{
|
|
TCB_t *next_pt = RunPt->next;
|
|
TCB_t *iterating_pt = next_pt;
|
|
|
|
/* Search for highest priority thread not sleeping or blocked */
|
|
uint32_t max_priority = RunPt->priority;
|
|
TCB_t *best_pt = next_pt;
|
|
do
|
|
{
|
|
if ((iterating_pt->priority > max_priority) && (iterating_pt->sleep == 0) && (iterating_pt->blocked == NULL))
|
|
{
|
|
best_pt = iterating_pt;
|
|
max_priority = best_pt->priority;
|
|
}
|
|
iterating_pt = iterating_pt->next;
|
|
} while (iterating_pt != next_pt);
|
|
|
|
RunPt = best_pt;
|
|
}
|
|
|
|
void OS_Thread_Suspend(void)
|
|
{
|
|
// SchedlTimer_ResetCounter();
|
|
}
|
|
|
|
void OS_Thread_Sleep(uint32_t sleep_duration_ms)
|
|
{
|
|
RunPt->sleep = sleep_duration_ms;
|
|
OS_Thread_Suspend();
|
|
}
|
|
|
|
void OS_DecrementTCBsSleepDuration(void)
|
|
{
|
|
for (size_t tcb_idx = 0; tcb_idx < MAXNUMTHREADS; tcb_idx++)
|
|
{
|
|
if (TCBs[tcb_idx].sleep > 0)
|
|
{
|
|
TCBs[tcb_idx].sleep -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OS_Thread_Kill(void)
|
|
{
|
|
assert_or_panic(ActiveTCBsCount > 1);
|
|
disable_irq();
|
|
|
|
TCB_t *previous_tcb = RunPt;
|
|
while (1)
|
|
{
|
|
previous_tcb = previous_tcb->next;
|
|
if (previous_tcb->next == RunPt)
|
|
break;
|
|
}
|
|
TCB_t *next_tcb = RunPt->next;
|
|
|
|
previous_tcb->next = next_tcb;
|
|
RunPt->status = TCBStateFree;
|
|
|
|
ActiveTCBsCount--;
|
|
enable_irq();
|
|
OS_Thread_Suspend();
|
|
}
|
|
|
|
void OS_Semaphore_Wait(Semaphore_t *sem)
|
|
{
|
|
disable_irq();
|
|
(*sem) = (*sem) - 1;
|
|
if ((*sem) < 0)
|
|
{
|
|
RunPt->blocked = sem; /* Reason the thread is blocked */
|
|
enable_irq();
|
|
OS_Thread_Suspend();
|
|
}
|
|
enable_irq();
|
|
}
|
|
|
|
void OS_Semaphore_Signal(Semaphore_t *sem)
|
|
{
|
|
disable_irq();
|
|
(*sem) = (*sem) + 1;
|
|
if ((*sem) <= 0)
|
|
{
|
|
/* Search for a TCB blocked on this semaphore and wake it up */
|
|
TCB_t *a_tcb = RunPt->next;
|
|
while (a_tcb->blocked != sem)
|
|
{
|
|
a_tcb = a_tcb->next;
|
|
}
|
|
a_tcb->blocked = 0;
|
|
}
|
|
enable_irq();
|
|
}
|