mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-07 01:15:14 +01:00
Add mailbox and two tasks switching example
This commit is contained in:
parent
b43e0b1ac0
commit
e552978acf
10 changed files with 660 additions and 391 deletions
|
|
@ -35,7 +35,7 @@ set(OST_SRCS
|
||||||
system/debug.c
|
system/debug.c
|
||||||
system/picture.c
|
system/picture.c
|
||||||
system/filesystem.c
|
system/filesystem.c
|
||||||
system/os.c
|
system/qor.c
|
||||||
system/qor_armv6m.s
|
system/qor_armv6m.s
|
||||||
system/audio_player.c
|
system/audio_player.c
|
||||||
system/ff/ff.c
|
system/ff/ff.c
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ add_library(
|
||||||
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_i2s.pio)
|
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_i2s.pio)
|
||||||
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/i2s.pio)
|
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/i2s.pio)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} INTERFACE pico_stdlib)
|
target_link_libraries(${PROJECT_NAME} INTERFACE pico_stdlib hardware_exception)
|
||||||
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|
||||||
target_sources(${PROJECT_NAME} INTERFACE
|
target_sources(${PROJECT_NAME} INTERFACE
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ const uint8_t SD_CARD_CS = 17;
|
||||||
|
|
||||||
const uint8_t SD_CARD_PRESENCE = 24;
|
const uint8_t SD_CARD_PRESENCE = 24;
|
||||||
|
|
||||||
#include "os.h"
|
#include "qor.h"
|
||||||
|
|
||||||
static bool sys_timer_callback(struct repeating_timer *t)
|
static bool sys_timer_callback(struct repeating_timer *t)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
#include "picture.h"
|
#include "picture.h"
|
||||||
#include "os.h"
|
#include "qor.h"
|
||||||
#include "rotary-button.h"
|
#include "rotary-button.h"
|
||||||
|
|
||||||
#define RUN_TESTS 1
|
#define RUN_TESTS 1
|
||||||
|
|
@ -64,16 +64,32 @@ void ost_hal_panic()
|
||||||
|
|
||||||
extern void qor_sleep();
|
extern void qor_sleep();
|
||||||
|
|
||||||
|
static qor_mbox_t b;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t ev;
|
||||||
|
} ost_event_t;
|
||||||
|
|
||||||
|
ost_event_t ev_queue[10];
|
||||||
|
|
||||||
|
qor_tcb_t tcb1;
|
||||||
|
qor_tcb_t tcb2;
|
||||||
|
|
||||||
void UserTask_0(void *args)
|
void UserTask_0(void *args)
|
||||||
{
|
{
|
||||||
// InstrumentTriggerPE11_Init();
|
// InstrumentTriggerPE11_Init();
|
||||||
// uint32_t count = 0;
|
// uint32_t count = 0;
|
||||||
|
|
||||||
|
qor_mbox_init(&b, (void **)&ev_queue, 10);
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
|
|
||||||
ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 1);
|
ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 1);
|
||||||
|
|
||||||
// qor_sleep();
|
// qor_sleep();
|
||||||
|
ost_event_t *e = NULL;
|
||||||
|
qor_mbox_wait(&b, (void **)&e, 3);
|
||||||
|
|
||||||
for (int i = 0; i < 65500; i++)
|
for (int i = 0; i < 65500; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -97,9 +113,17 @@ void UserTask_1(void *args)
|
||||||
{
|
{
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
ost_system_delay_ms(1000);
|
for (int i = 0; i < 65500; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 100; j++)
|
||||||
|
;
|
||||||
|
}
|
||||||
debug_printf("X\n");
|
debug_printf("X\n");
|
||||||
ost_system_delay_ms(1000);
|
for (int i = 0; i < 65500; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 100; j++)
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
@ -129,7 +153,7 @@ void UserTask_3(void)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
extern size_t __StackTop;
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
ost_system_initialize();
|
ost_system_initialize();
|
||||||
|
|
@ -144,8 +168,8 @@ int main()
|
||||||
// ost_audio_play("out2.wav");
|
// ost_audio_play("out2.wav");
|
||||||
|
|
||||||
OS_Init(THREADFREQ);
|
OS_Init(THREADFREQ);
|
||||||
qor_create_thread(UserTask_0, 1, "UserTask_0");
|
qor_create_thread(&tcb1, UserTask_0, 1, "UserTask_0");
|
||||||
// qor_create_thread(UserTask_1, 2, "UserTask_1");
|
qor_create_thread(&tcb2, UserTask_1, 2, "UserTask_1");
|
||||||
// OS_Thread_Create(UserTask_2, OS_SCHEDL_PRIO_MAIN_THREAD, "UserTask_2");
|
// OS_Thread_Create(UserTask_2, OS_SCHEDL_PRIO_MAIN_THREAD, "UserTask_2");
|
||||||
// OS_Thread_Create(OnboardUserButton_Task, OS_SCHEDL_PRIO_EVENT_THREAD, "OnboardUserButton_Task");
|
// OS_Thread_Create(OnboardUserButton_Task, OS_SCHEDL_PRIO_EVENT_THREAD, "OnboardUserButton_Task");
|
||||||
qor_start();
|
qor_start();
|
||||||
|
|
|
||||||
|
|
@ -1,299 +0,0 @@
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
#ifndef OS_H
|
|
||||||
#define OS_H
|
|
||||||
|
|
||||||
void ost_tasker_init();
|
|
||||||
|
|
||||||
extern void ost_hal_panic();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert, or panic.
|
|
||||||
*/
|
|
||||||
#define assert_or_panic(expr) ((expr) ? (void)0U : ost_hal_panic())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The module os encapsulates the core functionality of the operating system and
|
|
||||||
* exposes the functions for interacting with it.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define MAXNUMTHREADS 10 /* Maximum number of threads, allocated at compile time */
|
|
||||||
#define STACKSIZE 100 /* Number of 32-bit words in each TCB's stack */
|
|
||||||
#define THREADFREQ 1 /* Maximum time-slice, in Hz, before the scheduler is run */
|
|
||||||
|
|
||||||
#define OS_SCHEDL_PRIO_MIN 1 /* Lowest priority that can be assigned to a thread */
|
|
||||||
#define OS_SCHEDL_PRIO_MAX UINT8_MAX /* Highest priority that can be assigned to a thread */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type Semaphore_t abstracts the semaphore's counter.
|
|
||||||
* A value of type *Semaphore_t should only be updated through the fn OS_Semaphore_Wait
|
|
||||||
* and OS_Semaphore_Signal.
|
|
||||||
*/
|
|
||||||
typedef int32_t Semaphore_t;
|
|
||||||
|
|
||||||
typedef void (*thread_func_t)(void *args);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function descriptions are provided in os.c
|
|
||||||
*/
|
|
||||||
|
|
||||||
void OS_Init(uint32_t scheduler_frequency_hz);
|
|
||||||
|
|
||||||
// void OS_Thread_CreateFirst(thread_func_t task, uint8_t priority, const char *name);
|
|
||||||
|
|
||||||
void qor_create_thread(thread_func_t task, uint8_t priority, const char *name);
|
|
||||||
|
|
||||||
void qor_switch_context();
|
|
||||||
|
|
||||||
void qor_start(void);
|
|
||||||
|
|
||||||
void OS_Thread_Suspend(void);
|
|
||||||
|
|
||||||
void OS_Thread_Sleep(uint32_t sleep_duration_ms);
|
|
||||||
|
|
||||||
void OS_DecrementTCBsSleepDuration(void);
|
|
||||||
|
|
||||||
void OS_Thread_Kill(void);
|
|
||||||
|
|
||||||
void OS_Semaphore_Wait(Semaphore_t *sem);
|
|
||||||
|
|
||||||
void OS_Semaphore_Signal(Semaphore_t *sem);
|
|
||||||
|
|
||||||
#endif // OST_TASKER_H
|
|
||||||
451
software/system/qor.c
Normal file
451
software/system/qor.c
Normal file
|
|
@ -0,0 +1,451 @@
|
||||||
|
/**
|
||||||
|
* @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 "qor.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "pico/critical_section.h"
|
||||||
|
#include "hardware/exception.h"
|
||||||
|
|
||||||
|
// ===========================================================================================================
|
||||||
|
// ARM GENERIC
|
||||||
|
// ===========================================================================================================
|
||||||
|
inline static void enable_irq()
|
||||||
|
{
|
||||||
|
__asm volatile("cpsie i");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void disable_irq()
|
||||||
|
{
|
||||||
|
__asm volatile("cpsid i");
|
||||||
|
}
|
||||||
|
|
||||||
|
void qor_sleep_ms(uint8_t svc, uint32_t ms)
|
||||||
|
{
|
||||||
|
|
||||||
|
__wfi;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __set_PRIMASK(uint32_t priMask)
|
||||||
|
{
|
||||||
|
__asm volatile("MSR primask, %0"
|
||||||
|
:
|
||||||
|
: "r"(priMask)
|
||||||
|
: "memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t __get_PRIMASK(void)
|
||||||
|
{
|
||||||
|
uint32_t result;
|
||||||
|
|
||||||
|
__asm volatile("MRS %0, primask"
|
||||||
|
: "=r"(result));
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t qor_enter_critical(void)
|
||||||
|
{
|
||||||
|
uint32_t primask = __get_PRIMASK();
|
||||||
|
disable_irq();
|
||||||
|
return primask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qor_exit_critical(uint32_t status)
|
||||||
|
{
|
||||||
|
__set_PRIMASK(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((naked)) void PendSV_Handler()
|
||||||
|
{
|
||||||
|
// __asm("bkpt 1");
|
||||||
|
qor_switch_context();
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((optimize("O0"))) static void qor_svc_call(void)
|
||||||
|
{
|
||||||
|
volatile uint32_t *icsr = (void *)0xE000ED04;
|
||||||
|
// Pend a PendSV exception using by writing 1 to PENDSVSET at bit 28
|
||||||
|
*icsr = 0x1 << 28;
|
||||||
|
// flush pipeline to ensure exception takes effect before we
|
||||||
|
// return from this routine
|
||||||
|
__asm("isb");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const bool qor_inside_interrupt(void)
|
||||||
|
{
|
||||||
|
uint32_t ulCurrentInterrupt;
|
||||||
|
bool xReturn;
|
||||||
|
|
||||||
|
/* Obtain the number of the currently executing interrupt. */
|
||||||
|
__asm volatile("mrs %0, ipsr"
|
||||||
|
: "=r"(ulCurrentInterrupt)::"memory");
|
||||||
|
|
||||||
|
if (ulCurrentInterrupt == 0)
|
||||||
|
{
|
||||||
|
xReturn = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
xReturn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================================================
|
||||||
|
// GLOBAL AND STATIC VARIABLES
|
||||||
|
// ===========================================================================================================
|
||||||
|
|
||||||
|
static uint32_t Stacks[MAXNUMTHREADS][STACKSIZE];
|
||||||
|
|
||||||
|
/* Pointer to the currently running thread */
|
||||||
|
qor_tcb_t *RunPt = NULL;
|
||||||
|
static qor_tcb_t *TcbHead = NULL;
|
||||||
|
|
||||||
|
/* The variable ActiveTCBsCount tracks the number of TCBs in use by the OS */
|
||||||
|
static uint32_t ActiveTCBsCount = 0;
|
||||||
|
|
||||||
|
// ===========================================================================================================
|
||||||
|
// Quite Ok RTOS private and public functions
|
||||||
|
// ===========================================================================================================
|
||||||
|
void OS_Init(uint32_t scheduler_frequency_hz)
|
||||||
|
{
|
||||||
|
exception_set_exclusive_handler(PENDSV_EXCEPTION, PendSV_Handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(qor_tcb_t *tcb, thread_func_t task, uint8_t priority, const char *name)
|
||||||
|
{
|
||||||
|
assert_or_panic(ActiveTCBsCount >= 0 && ActiveTCBsCount < MAXNUMTHREADS);
|
||||||
|
disable_irq();
|
||||||
|
|
||||||
|
tcb->state = qor_tcb_state_active;
|
||||||
|
tcb->wait_time = 0;
|
||||||
|
tcb->state = qor_tcb_state_active;
|
||||||
|
tcb->priority = priority;
|
||||||
|
tcb->name = name;
|
||||||
|
tcb->next = NULL;
|
||||||
|
tcb->mbox = NULL;
|
||||||
|
tcb->message = NULL;
|
||||||
|
tcb->wait_next = NULL;
|
||||||
|
tcb->sp = qor_initialize_stack(&Stacks[ActiveTCBsCount][STACKSIZE], task, (void *)name);
|
||||||
|
|
||||||
|
if (TcbHead == NULL)
|
||||||
|
{
|
||||||
|
TcbHead = tcb;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Go to the end of the queue
|
||||||
|
qor_tcb_t *t = TcbHead;
|
||||||
|
while (t->next != NULL)
|
||||||
|
{
|
||||||
|
t = t->next;
|
||||||
|
}
|
||||||
|
// Add TCB to the end of the queue
|
||||||
|
t->next = tcb;
|
||||||
|
}
|
||||||
|
ActiveTCBsCount++;
|
||||||
|
|
||||||
|
enable_irq();
|
||||||
|
}
|
||||||
|
|
||||||
|
void qor_start(void)
|
||||||
|
{
|
||||||
|
assert_or_panic(ActiveTCBsCount > 0);
|
||||||
|
|
||||||
|
// FIXME: use the scheduler to find the best first thread to start
|
||||||
|
RunPt = TcbHead;
|
||||||
|
/* 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)
|
||||||
|
{
|
||||||
|
qor_tcb_t *t = TcbHead;
|
||||||
|
|
||||||
|
/* Search for highest priority thread not sleeping or blocked */
|
||||||
|
uint32_t max_priority = RunPt->priority;
|
||||||
|
qor_tcb_t *best_pt = RunPt;
|
||||||
|
while (t != NULL)
|
||||||
|
{
|
||||||
|
if ((t->priority > max_priority) &&
|
||||||
|
(t->wait_time == 0) &&
|
||||||
|
(t->state == qor_tcb_state_active))
|
||||||
|
{
|
||||||
|
best_pt = t;
|
||||||
|
max_priority = t->priority;
|
||||||
|
}
|
||||||
|
t = t->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
RunPt = best_pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OS_Thread_Suspend(void)
|
||||||
|
{
|
||||||
|
// SchedlTimer_ResetCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void qor_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();
|
||||||
|
|
||||||
|
qor_tcb_t *previous_tcb = RunPt;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
previous_tcb = previous_tcb->next;
|
||||||
|
if (previous_tcb->next == RunPt)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
qor_tcb_t *next_tcb = RunPt->next;
|
||||||
|
|
||||||
|
previous_tcb->next = next_tcb;
|
||||||
|
RunPt->state = qor_tcb_state_free;
|
||||||
|
|
||||||
|
ActiveTCBsCount--;
|
||||||
|
enable_irq();
|
||||||
|
OS_Thread_Suspend();
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================================================
|
||||||
|
// MAILBOX IMPLEMENTATION
|
||||||
|
// ===========================================================================================================
|
||||||
|
|
||||||
|
#define qor_mbox_tSendNormal 0x0
|
||||||
|
#define qor_mbox_tSendFront 0x1
|
||||||
|
|
||||||
|
void qor_mbox_init(qor_mbox_t *mbox, void **msgBuffer, uint32_t maxCount)
|
||||||
|
{
|
||||||
|
mbox->msgBuffer = msgBuffer;
|
||||||
|
mbox->maxCount = maxCount;
|
||||||
|
mbox->read = 0;
|
||||||
|
mbox->read = 0;
|
||||||
|
mbox->head = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t qor_mbox_wait(qor_mbox_t *mbox, void **msg, uint32_t waitTicks)
|
||||||
|
{
|
||||||
|
uint32_t status = qor_enter_critical();
|
||||||
|
|
||||||
|
if (mbox->count == 0)
|
||||||
|
{
|
||||||
|
if (waitTicks > 0)
|
||||||
|
{
|
||||||
|
// No any data, block on that resource
|
||||||
|
RunPt->mbox = mbox;
|
||||||
|
RunPt->state = qor_tcb_state_wait_mbox;
|
||||||
|
RunPt->wait_time = waitTicks;
|
||||||
|
qor_exit_critical(status);
|
||||||
|
qor_svc_call(); // call scheduler
|
||||||
|
|
||||||
|
status = qor_enter_critical();
|
||||||
|
if ((RunPt->wait_time >= 0) && (RunPt->message != NULL))
|
||||||
|
{
|
||||||
|
*msg = RunPt->message;
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return QOR_MBOX_OK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return QOR_MBOX_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return QOR_MBOX_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((RunPt->message != NULL))
|
||||||
|
{
|
||||||
|
*msg = RunPt->message;
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return QOR_MBOX_OK;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return QOR_MBOX_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
uint32_t qor_mbox_get(qor_mbox_t *mbox, void **msg)
|
||||||
|
{
|
||||||
|
uint32_t status = qor_enter_critical();
|
||||||
|
if (mbox->count > 0)
|
||||||
|
{
|
||||||
|
--mbox->count;
|
||||||
|
*msg = mbox->msgBuffer[mbox->read++];
|
||||||
|
if (mbox->read >= mbox->maxCount)
|
||||||
|
{
|
||||||
|
mbox->read = 0;
|
||||||
|
}
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return tErrorNoError;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return tErrorResourceUnavaliable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
uint32_t qor_mbox_notify(qor_mbox_t *mbox, void *msg, uint32_t notifyOption)
|
||||||
|
{
|
||||||
|
uint32_t status = qor_enter_critical();
|
||||||
|
if (tEventWaitCount(&mbox->event) > 0)
|
||||||
|
{
|
||||||
|
tTask *task = tEventWakeUp(&mbox->event, (void *)msg, tErrorNoError);
|
||||||
|
if (task->prio < currentTask->prio)
|
||||||
|
{
|
||||||
|
tTaskSched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mbox->count >= mbox->maxCount)
|
||||||
|
{
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return tErrorResourceFull;
|
||||||
|
}
|
||||||
|
if (notifyOption & qor_mbox_tSendFront)
|
||||||
|
{
|
||||||
|
if (mbox->read <= 0)
|
||||||
|
{
|
||||||
|
mbox->read = mbox->maxCount - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
--mbox->read;
|
||||||
|
}
|
||||||
|
mbox->msgBuffer[mbox->read] = msg;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mbox->msgBuffer[mbox->write++] = msg;
|
||||||
|
if (mbox->write >= mbox->maxCount)
|
||||||
|
{
|
||||||
|
mbox->write = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mbox->count++;
|
||||||
|
}
|
||||||
|
qor_exit_critical(status);
|
||||||
|
return tErrorNoError;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
void qor_mbox_flush(qor_mbox_t *mbox)
|
||||||
|
{
|
||||||
|
uint32_t status = qor_enter_critical();
|
||||||
|
/*
|
||||||
|
if (tEventWaitCount(&mbox->event) == 0)
|
||||||
|
{
|
||||||
|
mbox->read = 0;
|
||||||
|
mbox->write = 0;
|
||||||
|
mbox->count = 0;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
qor_exit_critical(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t mbox_destroy(qor_mbox_t *mbox)
|
||||||
|
{
|
||||||
|
uint32_t status = qor_enter_critical();
|
||||||
|
/*
|
||||||
|
uint32_t count = tEventRemoveAll(&mbox->event, (void *)0, tErrorDel);
|
||||||
|
qor_exit_critical(status);
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
tTaskSched();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return 0; // count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void qor_mbox_get_stats(qor_mbox_t *mbox, mbox_stats_t *info)
|
||||||
|
{
|
||||||
|
uint32_t status = qor_enter_critical();
|
||||||
|
|
||||||
|
info->count = mbox->count;
|
||||||
|
info->maxCount = mbox->maxCount;
|
||||||
|
|
||||||
|
info->taskCount = 0;
|
||||||
|
qor_tcb_t *head = mbox->head;
|
||||||
|
while (head != NULL)
|
||||||
|
{
|
||||||
|
info->taskCount++;
|
||||||
|
head = head->wait_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
qor_exit_critical(status);
|
||||||
|
}
|
||||||
117
software/system/qor.h
Normal file
117
software/system/qor.h
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
#ifndef QOR_H
|
||||||
|
#define QOR_H
|
||||||
|
|
||||||
|
extern void ost_hal_panic();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert, or panic.
|
||||||
|
*/
|
||||||
|
#define assert_or_panic(expr) ((expr) ? (void)0U : ost_hal_panic())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module os encapsulates the core functionality of the operating system and
|
||||||
|
* exposes the functions for interacting with it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MAXNUMTHREADS 10 /* Maximum number of threads, allocated at compile time */
|
||||||
|
#define STACKSIZE 100 /* Number of 32-bit words in each TCB's stack */
|
||||||
|
#define THREADFREQ 1 /* Maximum time-slice, in Hz, before the scheduler is run */
|
||||||
|
|
||||||
|
#define OS_SCHEDL_PRIO_MIN 1 /* Lowest priority that can be assigned to a thread */
|
||||||
|
#define OS_SCHEDL_PRIO_MAX UINT8_MAX /* Highest priority that can be assigned to a thread */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type Semaphore_t abstracts the semaphore's counter.
|
||||||
|
* A value of type *Semaphore_t should only be updated through the fn OS_Semaphore_Wait
|
||||||
|
* and OS_Semaphore_Signal.
|
||||||
|
*/
|
||||||
|
typedef int32_t Semaphore_t;
|
||||||
|
|
||||||
|
typedef void (*thread_func_t)(void *args);
|
||||||
|
|
||||||
|
// ===========================================================================================================
|
||||||
|
// THREADS API
|
||||||
|
// ===========================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RTOS Task state
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
qor_tcb_state_active,
|
||||||
|
qor_tcb_state_wait_mbox,
|
||||||
|
qor_tcb_state_sleep
|
||||||
|
} qor_tcb_state_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread Control Block
|
||||||
|
*
|
||||||
|
* IMPORTANT! keep the stack pointer on top, it is required by the task switch assembly
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct qor_mbox_t qor_mbox_t;
|
||||||
|
typedef struct TCB
|
||||||
|
{
|
||||||
|
uint32_t *sp; /* Stack pointer, valid for threads not running */
|
||||||
|
struct TCB *next; /* Pointer to circular-linked-list of TCBs */
|
||||||
|
struct TCB *wait_next; // Next TCB in waiting list
|
||||||
|
qor_tcb_state_t state; /* TCB active or free */
|
||||||
|
uint32_t wait_time; //!< Timeout for mbox maiting or sleep
|
||||||
|
qor_mbox_t *mbox; /* Pointer to mailbox on which the thread is blocked, NULL if not blocked */
|
||||||
|
void *message; //<! Actually the message transmitted
|
||||||
|
uint8_t priority; /* Thread priority, 0 is highest, 255 is lowest */
|
||||||
|
const char *name; /* Descriptive name to facilitate debugging */
|
||||||
|
|
||||||
|
} qor_tcb_t;
|
||||||
|
|
||||||
|
void OS_Init(uint32_t scheduler_frequency_hz);
|
||||||
|
|
||||||
|
// void OS_Thread_CreateFirst(thread_func_t task, uint8_t priority, const char *name);
|
||||||
|
|
||||||
|
void qor_create_thread(qor_tcb_t *tcb, thread_func_t task, uint8_t priority, const char *name);
|
||||||
|
|
||||||
|
void qor_switch_context();
|
||||||
|
|
||||||
|
void qor_start(void);
|
||||||
|
|
||||||
|
void OS_Thread_Suspend(void);
|
||||||
|
|
||||||
|
void qor_sleep(uint32_t sleep_duration_ms);
|
||||||
|
|
||||||
|
void OS_Thread_Kill(void);
|
||||||
|
|
||||||
|
void OS_Semaphore_Wait(Semaphore_t *sem);
|
||||||
|
|
||||||
|
void OS_Semaphore_Signal(Semaphore_t *sem);
|
||||||
|
|
||||||
|
// ===========================================================================================================
|
||||||
|
// MAILBOX API
|
||||||
|
// ===========================================================================================================
|
||||||
|
struct qor_mbox_t
|
||||||
|
{
|
||||||
|
qor_tcb_t *head;
|
||||||
|
uint32_t count;
|
||||||
|
uint32_t read;
|
||||||
|
uint32_t write;
|
||||||
|
uint32_t maxCount;
|
||||||
|
void **msgBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t count;
|
||||||
|
uint32_t maxCount;
|
||||||
|
uint32_t taskCount;
|
||||||
|
} mbox_stats_t;
|
||||||
|
|
||||||
|
#define QOR_MBOX_OK 1
|
||||||
|
#define QOR_MBOX_ERROR 2
|
||||||
|
|
||||||
|
void qor_mbox_init(qor_mbox_t *mbox, void **msgBuffer, uint32_t maxCount);
|
||||||
|
uint32_t qor_mbox_wait(qor_mbox_t *mbox, void **msg, uint32_t waitTicks);
|
||||||
|
|
||||||
|
#endif // QOR_H
|
||||||
|
|
@ -6,8 +6,6 @@
|
||||||
.thumb_func
|
.thumb_func
|
||||||
|
|
||||||
@ The .global directive gives the symbols external linkage.
|
@ The .global directive gives the symbols external linkage.
|
||||||
@ For clarity, the fn OSAsm_ThreadSwitch is exported as TIM2_IRQHandler, so that the vector table
|
|
||||||
@ in startup.s doesn't need to be modified.
|
|
||||||
.global qor_go
|
.global qor_go
|
||||||
.global qor_switch_context
|
.global qor_switch_context
|
||||||
.global qor_sleep
|
.global qor_sleep
|
||||||
|
|
@ -86,27 +84,13 @@ qor_go:
|
||||||
bx r3 @ /* Finally, jump to the user defined task code. */
|
bx r3 @ /* Finally, jump to the user defined task code. */
|
||||||
|
|
||||||
|
|
||||||
@ .section .text.qor_sleep
|
|
||||||
@ .type qor_sleep, %function
|
|
||||||
|
|
||||||
@ qor_sleep:
|
|
||||||
@ mov r1, r0 @ copy sleep value in second argument
|
|
||||||
@ movs r0, #1 @ sleep is SVC 1
|
|
||||||
@ mov r2, lr @ remember where we are
|
|
||||||
@ bl qor_svc_call
|
|
||||||
@ wfi
|
|
||||||
@ bx lr
|
|
||||||
|
|
||||||
@ EnterCriticalSection:
|
|
||||||
@ MRS r0, PRIMASK /* Save interrupt state. */
|
|
||||||
@ CPSID i /* Turn off interrupts. */
|
|
||||||
@ BX lr /* Return. */
|
|
||||||
|
|
||||||
@ ExitCriticalSection:
|
|
||||||
@ MSR PRIMASK, r0 /* Restore interrupt states. */
|
|
||||||
@ BX lr /* Return. */
|
|
||||||
|
|
||||||
|
|
||||||
|
@ 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.
|
||||||
|
|
||||||
|
|
||||||
@ EXC_RETURN value to return to Thread mode, while restoring state from PSP.
|
@ EXC_RETURN value to return to Thread mode, while restoring state from PSP.
|
||||||
.equ EXC_RETURN, 0xfffffffd
|
.equ EXC_RETURN, 0xfffffffd
|
||||||
|
|
|
||||||
54
software/system/qor_armv7m.s
Normal file
54
software/system/qor_armv7m.s
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
.syntax unified @ See https://sourceware.org/binutils/docs/as/ARM_002dInstruction_002dSet.html
|
||||||
|
.cpu cortex-m4
|
||||||
|
.fpu softvfp
|
||||||
|
.thumb
|
||||||
|
|
||||||
|
@ The .global directive gives the symbols external linkage.
|
||||||
|
@ For clarity, the fn OSAsm_ThreadSwitch is exported as TIM2_IRQHandler, so that the vector table
|
||||||
|
@ in startup.s doesn't need to be modified.
|
||||||
|
.global OSAsm_Start
|
||||||
|
.set TIM2_IRQHandler, OSAsm_ThreadSwitch
|
||||||
|
.global TIM2_IRQHandler
|
||||||
|
|
||||||
|
.extern RunPt
|
||||||
|
.extern SchedlTimer_ClearITFlag
|
||||||
|
.extern OS_Scheduler
|
||||||
|
|
||||||
|
.section .text.OSAsm_Start
|
||||||
|
.type OSAsm_Start, %function
|
||||||
|
OSAsm_Start:
|
||||||
|
CPSID I @ disable interrupts
|
||||||
|
LDR R0, =RunPt @ R0 = &RunPt; // TCB_t** R0 = &RunPt
|
||||||
|
LDR R1, [R0] @ R1 = *R0; // TCB_t* R1 = RunPt
|
||||||
|
LDR SP, [R1] @ SP = *R1; // uint32_t SP = *(RunPt.sp)
|
||||||
|
@ now we switched to the thread's stack, which we populated before
|
||||||
|
POP {R4-R11} @ pop regs R4-R11
|
||||||
|
POP {R0-R3} @ pop regs R0-R3
|
||||||
|
POP {R12} @ pop reg R12
|
||||||
|
POP {LR} @ discard LR
|
||||||
|
POP {LR} @ pop PC to the link register (start location)
|
||||||
|
POP {R1} @ discard PSR
|
||||||
|
CPSIE I @ enable interrupts
|
||||||
|
BX LR @ start first thread
|
||||||
|
|
||||||
|
.section .text.OSAsm_ThreadSwitch
|
||||||
|
.type OSAsm_ThreadSwitch, %function
|
||||||
|
OSAsm_ThreadSwitch:
|
||||||
|
@ save R0-R3,R12,LR,PC,PSR
|
||||||
|
CPSID I @ prevent interrupt during context-switch
|
||||||
|
PUSH {R4-R11} @ save remaining regs R4-R11
|
||||||
|
LDR R0, =RunPt @ R0 = &RunPt; // TCB_t** R0 = &RunPt
|
||||||
|
LDR R1, [R0] @ R1 = *R0; // TCB_t* R1 = RunPt
|
||||||
|
STR SP, [R1] @ *R1 = SP; // *(RunPt.sp) = SP
|
||||||
|
|
||||||
|
PUSH {R0, LR} @ push R0 and LR, so that fn calls don't loose them
|
||||||
|
BL SchedlTimer_ClearITFlag @ clear SchedlTimer interrupt flag
|
||||||
|
BL OS_Scheduler @ call OS_Scheduler, RunPt is updated
|
||||||
|
POP {R0, LR} @ restore R0 and LR
|
||||||
|
|
||||||
|
LDR R1, [R0] @ R1 = *R0; // TCB_t* R1 = RunPt
|
||||||
|
LDR SP, [R1] @ SP = *R1; // uint32_t SP = *(RunPt.sp)
|
||||||
|
@ now we switched to the new thread's stack
|
||||||
|
POP {R4-R11} @ restore regs R4-R11
|
||||||
|
CPSIE I @ tasks run with interrupts enabled
|
||||||
|
BX LR @ restore R0-R3,R12,LR,PC,PSR
|
||||||
Loading…
Reference in a new issue