You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
20 KiB
587 lines
20 KiB
#pragma once |
|
|
|
//#pragma GCC optimize ("O2") |
|
|
|
#include <AP_HAL/AP_HAL.h> |
|
|
|
|
|
#include "AP_HAL_F4Light_Namespace.h" |
|
#include "handler.h" |
|
#include "Config.h" |
|
|
|
#include "Semaphores.h" |
|
#include "GPIO.h" |
|
|
|
#include <delay.h> |
|
#include <systick.h> |
|
#include <boards.h> |
|
#include <timer.h> |
|
//#include <setjmp.h> |
|
|
|
#define F4Light_SCHEDULER_MAX_IO_PROCS 10 |
|
|
|
|
|
#define MAIN_PRIORITY 100 // priority for main task |
|
#define DRIVER_PRIORITY 98 // priority for drivers, speed of main will be 1/4 of this |
|
#define IO_PRIORITY 115 // main task has 100 so IO tasks will use 1/16 of CPU |
|
|
|
#define SHED_FREQ 10000 // timer's freq in Hz |
|
#define TIMER_PERIOD 100 // task timeslice period in uS |
|
|
|
|
|
#define MAIN_STACK_SIZE 4096U+1024U // measured use of stack is only 1.5K - but it grows up to 3K when using FatFs, also this includes 1K stack for ISR |
|
#define IO_STACK_SIZE 4096U // IO_tasks stack size - io_thread can do work with filesystem, stack overflows if 2K |
|
#define DEFAULT_STACK_SIZE 1024U // Default tasks stack size |
|
#define SMALL_TASK_STACK 1024U // small stack for sensors |
|
#define STACK_MAX 65536U |
|
|
|
#if 1 |
|
#define EnterCriticalSection __set_BASEPRI(SVC_INT_PRIORITY << (8 - __NVIC_PRIO_BITS)) |
|
#define LeaveCriticalSection __set_BASEPRI(0) |
|
#else |
|
#define EnterCriticalSection noInterrupts() |
|
#define LeaveCriticalSection interrupts() |
|
#endif |
|
|
|
|
|
/* |
|
* Task run-time structure (Task control block AKA TCB) |
|
*/ |
|
struct task_t { |
|
const uint8_t* sp; // Task stack pointer, should be first to access from context switcher |
|
task_t* next; // Next task (double linked list) |
|
task_t* prev; // Previous task |
|
Handler handle; // loop() in Revo_handler - to allow to change task, called via revo_call_handler |
|
const uint8_t* stack; // Task stack bottom |
|
uint8_t id; // id of task |
|
uint8_t priority; // base priority of task |
|
uint8_t curr_prio; // current priority of task, usually higher than priority |
|
bool active; // task still not ended |
|
bool f_yield; // task gives its quant voluntary (to not call it again) |
|
uint32_t ttw; // time to wait - for delays and IO |
|
uint32_t t_yield; // time when task loose control |
|
uint32_t period; // if set then task will start on time basis |
|
uint32_t time_start; // start time of task (for periodic tasks) |
|
F4Light::Semaphore *sem; // task should start after owning this semaphore |
|
F4Light::Semaphore *sem_wait; // task is waiting this semaphore |
|
uint32_t sem_time; // max time to wait semaphore |
|
uint32_t sem_start_wait; // time when waiting starts (can use t_yield but stays for clarity) |
|
#if defined(MTASK_PROF) |
|
uint32_t start; // microseconds of timeslice start |
|
uint32_t in_isr; // time in ISR when task runs |
|
uint32_t def_ttw; // default TTW - not as hard as period |
|
uint8_t sw_type; // type of task switch |
|
uint64_t time; // full time |
|
uint32_t max_time; // maximal execution time of task - to show |
|
uint32_t count; // call count to calc mean |
|
uint32_t work_time; // max time of full task |
|
uint32_t sem_max_wait; // max time of semaphore waiting |
|
uint32_t quants; // count of ticks |
|
uint32_t quants_time; // sum of quatn's times |
|
uint32_t t_paused; // time task was paused on IO |
|
uint32_t count_paused; // count task was paused on IO |
|
uint32_t max_paused; // max time task was paused on IO |
|
uint32_t max_c_paused; // count task was paused on IO |
|
uint32_t stack_free; // free stack |
|
#endif |
|
uint32_t guard; // stack guard to check TCB corruption |
|
}; |
|
|
|
extern "C" { |
|
extern unsigned _estack; // defined by link script |
|
extern uint32_t us_ticks; |
|
extern void *_sdata; |
|
extern void *_edata; |
|
extern void *_sccm; // start of CCM |
|
extern void *_eccm; // end of CCM vars |
|
|
|
void revo_call_handler(Handler hh, uint32_t arg); // universal caller for all type handlers - memberProc and Proc |
|
|
|
extern voidFuncPtr boardEmergencyHandler; // will be called on any fault or panic() before halt |
|
void PendSV_Handler(); |
|
void SVC_Handler(); |
|
void getNextTask(); |
|
|
|
void switchContext(); |
|
void __do_context_switch(); |
|
void hal_try_kill_task_or_reboot(uint8_t n); |
|
void hal_go_next_task(); |
|
void hal_stop_multitask(); |
|
|
|
extern task_t *s_running; // running task |
|
extern task_t *next_task; // task to run next |
|
|
|
extern caddr_t stack_bottom; // for SBRK check |
|
|
|
bool hal_is_armed(); |
|
|
|
// publish to low-level functions |
|
void hal_yield(uint16_t ttw); |
|
void hal_delay(uint16_t t); |
|
void hal_delay_microseconds(uint16_t t); |
|
uint32_t hal_micros(); |
|
void hal_isr_time(uint32_t t); |
|
|
|
// task management for USB MSC mode |
|
void hal_set_task_active(void * handle); |
|
void hal_context_switch_isr(); |
|
void * hal_register_task(voidFuncPtr task, uint32_t stack); |
|
void hal_set_task_priority(void * handle, uint8_t prio); |
|
|
|
void enqueue_flash_erase(uint32_t from, uint32_t to); |
|
} |
|
|
|
|
|
#define RAMEND ((size_t)&_estack) |
|
|
|
|
|
|
|
#ifdef SHED_DEBUG |
|
typedef struct RevoSchedLog { |
|
uint32_t start; |
|
uint32_t end; |
|
uint32_t ttw; |
|
uint32_t time_start; |
|
uint32_t quant; |
|
uint32_t in_isr; |
|
task_t *want_tail; |
|
uint8_t task_id; |
|
uint8_t prio; |
|
uint8_t active; |
|
uint8_t sw_type; |
|
} revo_sched_log; |
|
|
|
#define SHED_DEBUG_SIZE 512 |
|
#endif |
|
|
|
enum Revo_IO_Flags { |
|
IO_PERIODIC= 0, |
|
IO_ONCE = 1, |
|
}; |
|
|
|
typedef struct REVO_IO { |
|
Handler h; |
|
Revo_IO_Flags flags; |
|
} Revo_IO; |
|
|
|
class F4Light::Scheduler : public AP_HAL::Scheduler { |
|
public: |
|
|
|
typedef struct IO_COMPLETION { |
|
Handler handler; |
|
bool request; |
|
#ifdef SHED_PROF |
|
uint64_t time; |
|
uint32_t count; |
|
uint32_t max_time; |
|
#endif |
|
} IO_Completion; |
|
|
|
|
|
|
|
Scheduler(); |
|
void init(); |
|
inline void delay(uint16_t ms) { _delay(ms); } // uses internal static methods |
|
inline void delay_microseconds(uint16_t us) { _delay_microseconds(us); } |
|
inline void delay_microseconds_boost(uint16_t us) override { _delay_microseconds_boost(us); } |
|
|
|
inline uint32_t millis() { return AP_HAL::millis(); } |
|
inline uint32_t micros() { return _micros(); } |
|
|
|
inline void register_timer_process(AP_HAL::MemberProc proc) { _register_timer_process(proc, 1000); } |
|
inline void suspend_timer_procs(){} // nothing to do in multitask |
|
inline void resume_timer_procs() {} |
|
|
|
void register_delay_callback(AP_HAL::Proc, uint16_t min_time_ms) override; |
|
static void _register_io_process(Handler h, Revo_IO_Flags flags); |
|
void register_io_process(AP_HAL::MemberProc proc) { Revo_handler h = { .mp=proc }; _register_io_process(h.h, IO_PERIODIC); } |
|
|
|
|
|
static inline void _register_timer_process(AP_HAL::MemberProc proc, uint32_t period) { |
|
Revo_handler r = { .mp=proc }; |
|
|
|
_register_timer_task(period, r.h, NULL); |
|
} |
|
|
|
inline bool in_timerprocess() { return false; } // we don't calls anything in ISR |
|
|
|
void inline register_timer_failsafe(AP_HAL::Proc failsafe, uint32_t period_us) { _failsafe = failsafe; } |
|
|
|
void system_initialized(); |
|
|
|
static void _reboot(bool hold_in_bootloader); |
|
void reboot(bool hold_in_bootloader); |
|
|
|
inline bool in_main_thread() const override { return _in_main_thread(); } |
|
|
|
// drivers are not the best place for its own sheduler so let do it here |
|
static inline AP_HAL::Device::PeriodicHandle register_timer_task(uint32_t period_us, AP_HAL::Device::PeriodicCb proc, F4Light::Semaphore *sem) { |
|
Revo_handler r = { .pcb=proc }; |
|
return _register_timer_task(period_us, r.h, sem); |
|
} |
|
|
|
static void _delay(uint16_t ms); |
|
static void _delay_microseconds(uint16_t us); |
|
static void _delay_microseconds_boost(uint16_t us); |
|
|
|
static void _delay_us_ny(uint16_t us); // no yield delay |
|
|
|
static inline uint32_t _millis() { return systick_uptime(); } //systick_uptime returns 64-bit time |
|
static inline uint64_t _millis64() { return systick_uptime(); } |
|
|
|
static inline uint32_t _micros() { return timer_get_count32(TIMER5); } |
|
static uint64_t _micros64(); |
|
|
|
|
|
static bool adjust_timer_task(AP_HAL::Device::PeriodicHandle h, uint32_t period_us); |
|
static bool unregister_timer_task(AP_HAL::Device::PeriodicHandle h); |
|
void loop(); // to add ability to print out scheduler's stats in main thread |
|
|
|
static inline bool in_interrupt(){ return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) /* || (__get_BASEPRI()) */; } |
|
|
|
|
|
//{ this functions do a preemptive multitask and inspired by Arduino-Scheduler (Mikael Patel), and scmrtos |
|
|
|
/** |
|
* Initiate scheduler and main task with given stack size. Should |
|
* be called before start of any tasks if the main task requires a |
|
* stack size other than the default size. Returns true if |
|
* successful otherwise false. |
|
* @param[in] stackSize in bytes. |
|
* @return bool. |
|
*/ |
|
static inline bool adjust_stack(size_t stackSize) { s_top = stackSize; return true; } |
|
|
|
/** |
|
* Start a task with given function and stack size. Should be |
|
* called from main task. The functions are executed by the |
|
* task. The taskLoop function is repeatedly called. Returns |
|
* not-NULL if successful otherwise NULL (no memory for new task). |
|
* @param[in] taskLoop function to call. |
|
* @param[in] stackSize in bytes. |
|
* @return address of TCB. |
|
*/ |
|
static void * _start_task(Handler h, size_t stackSize); |
|
|
|
static inline void * start_task(voidFuncPtr taskLoop, size_t stackSize = DEFAULT_STACK_SIZE){ |
|
Revo_handler r = { .vp=taskLoop }; |
|
return _start_task(r.h, stackSize); |
|
} |
|
static inline void * start_task(AP_HAL::MemberProc proc, size_t stackSize = DEFAULT_STACK_SIZE){ |
|
Revo_handler r = { .mp=proc }; |
|
return _start_task(r.h, stackSize); |
|
} |
|
// not used - tasks are never stopped |
|
static void stop_task(void * h); |
|
|
|
|
|
// functions to alter task's properties |
|
//[ this functions called only at task start |
|
static void set_task_period(void *h, uint32_t period); // task will be auto-activated by this period |
|
|
|
static inline void set_task_priority(void *h, uint8_t prio){ // priority is a relative speed of task |
|
task_t *task = (task_t *)h; |
|
|
|
task->curr_prio= prio; |
|
task->priority = prio; |
|
} |
|
|
|
// task wants to run only with this semaphore owned |
|
static inline void set_task_semaphore(void *h, F4Light::Semaphore *sem){ // taskLoop function will be called owning this semaphore |
|
task_t *task = (task_t *)h; |
|
|
|
task->sem = sem; |
|
} |
|
//] |
|
|
|
|
|
// this functions are atomic so don't need to disable interrupts |
|
static inline void *get_current_task() { // get task handler or 0 if called from ISR |
|
if(in_interrupt()) return NULL; |
|
return s_running; |
|
} |
|
static inline void *get_current_task_isr() { // get current task handler even if called from ISR |
|
return s_running; |
|
} |
|
static inline void set_task_active(void *h) { // tasks are created in stopped state |
|
task_t * task = (task_t*)h; |
|
task->active=true; |
|
} |
|
|
|
// do context switch after return from interrupt |
|
static inline void context_switch_isr(){ timer_generate_update(TIMER14); } |
|
|
|
|
|
#if defined(MTASK_PROF) |
|
static void inline task_pause(uint32_t t) { // called from task when it starts transfer |
|
s_running->ttw=t; |
|
s_running->sem_start_wait=_micros(); |
|
s_running->count_paused++; |
|
} |
|
static void inline task_resume(void *h) { // called from IO_Complete ISR to resume task |
|
#if defined(USE_MPU) |
|
mpu_disable(); // we need access to write |
|
#endif |
|
task_t * task = (task_t*)h; |
|
task->ttw=0; |
|
task->active=true; |
|
_forced_task = task; // force it. Thus we exclude loop to select task |
|
context_switch_isr(); |
|
uint32_t dt= _micros() - task->sem_start_wait; |
|
task->t_paused += dt; |
|
} |
|
#else |
|
static void inline task_pause(uint16_t t) { s_running->ttw=t; } // called from task when it starts IO transfer |
|
static void inline task_resume(void *h) { // called from IO_Complete ISR to resume task, and will get 1st quant 100% |
|
#if defined(USE_MPU) |
|
mpu_disable(); // we need access to write |
|
#endif |
|
task_t * task = (task_t*)h; // called from ISR so don't need disabling interrupts when writes to TCB |
|
task->ttw=0; |
|
task->active=true; |
|
_forced_task = task; // force it, to not spent time for search by priority |
|
context_switch_isr(); |
|
} |
|
#endif |
|
//] |
|
|
|
/* |
|
task scheduler. Gives task ready to run with highest priority |
|
*/ |
|
static task_t *get_next_task(); |
|
|
|
/* |
|
* finish current tick and schedule new task excluding this |
|
*/ |
|
static void yield(uint16_t ttw=0); // optional time to wait |
|
|
|
/** |
|
* Return current task stack size. |
|
* @return bytes |
|
*/ |
|
static size_t task_stack(); |
|
|
|
// check from what task it called |
|
static inline bool _in_main_thread() { return s_running == &s_main; } |
|
|
|
// resume task that called delay_boost() |
|
static void resume_boost(){ |
|
if(boost_task) { |
|
task_t *task = (task_t *) boost_task; |
|
boost_task=NULL; |
|
|
|
|
|
if(task->ttw){ // task now in wait |
|
#if defined(USE_MPU) |
|
mpu_disable(); // we need access to write |
|
#endif |
|
uint32_t now = _micros(); |
|
uint32_t timeFromLast = now - task->t_yield; // time since that moment |
|
if(task->ttw<=100 || timeFromLast > task->ttw*3/2){ // gone 2/3 of time? |
|
task->ttw=0; // stop waiting |
|
task->active=true; |
|
_forced_task = task; // force it |
|
} |
|
} else { |
|
_forced_task = task; // just force it |
|
} |
|
} |
|
} |
|
|
|
static inline void plan_context_switch(){ |
|
need_switch_task = true; // require context switch |
|
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk; // PENDSVSET |
|
} |
|
|
|
static void SVC_Handler(uint32_t * svc_args); // many functions called via SVC for hardware serialization |
|
|
|
static void _try_kill_task_or_reboot(uint8_t n); // exception occures in armed state - try to kill current task |
|
static void _go_next_task(); |
|
static void _stop_multitask(); |
|
|
|
static volatile bool need_switch_task; // should be public for access from C code |
|
//} |
|
|
|
|
|
//{ IO completion routines, allows to move out time consuming parts from ISR |
|
#define MAX_IO_COMPLETION 8 |
|
|
|
typedef voidFuncPtr ioc_proc; |
|
|
|
static uint8_t register_io_completion(Handler handle); |
|
|
|
static inline uint8_t register_io_completion(ioc_proc cb) { |
|
Revo_handler r = { .vp=cb }; |
|
return register_io_completion(r.h); |
|
} |
|
static inline uint8_t register_io_completion(AP_HAL::MemberProc proc) { |
|
Revo_handler r = { .mp=proc }; |
|
return register_io_completion(r.h); |
|
} |
|
|
|
static inline void do_io_completion(uint8_t id){ // schedule selected IO completion |
|
if(id) { |
|
io_completion[id-1].request = true; |
|
timer_generate_update(TIMER13); |
|
} |
|
} |
|
|
|
static void exec_io_completion(); |
|
|
|
static volatile bool need_io_completion; |
|
//} |
|
|
|
|
|
// helpers |
|
static inline Handler get_handler(AP_HAL::MemberProc proc){ |
|
Revo_handler h = { .mp = proc }; |
|
return h.h; |
|
} |
|
static inline Handler get_handler(AP_HAL::Proc proc){ |
|
Revo_handler h = { .hp = proc }; |
|
return h.h; |
|
} |
|
|
|
static inline void setEmergencyHandler(voidFuncPtr handler) { boardEmergencyHandler = handler; } |
|
|
|
|
|
#ifdef MPU_DEBUG |
|
static inline void MPU_buffer_overflow(){ MPU_overflow_cnt++; } |
|
static inline void MPU_restarted() { MPU_restart_cnt++; } |
|
static inline void MPU_stats(uint16_t count, uint32_t time) { |
|
if(count>MPU_count) { |
|
MPU_count=count; |
|
MPU_Time=time; |
|
} |
|
} |
|
#endif |
|
|
|
static inline void arming_state_changed(bool v){ if(!v && on_disarm_handler) revo_call_handler(on_disarm_handler, 0); } |
|
static inline void register_on_disarm(Handler h){ on_disarm_handler=h; } |
|
|
|
static void start_stats_task(); // it interferes with CONNECT_COM and CONNECT_ESC so should be started last |
|
|
|
protected: |
|
|
|
//{ multitask |
|
// executor for task's handler, never called but used when task context formed |
|
static void do_task(task_t * task); |
|
|
|
// gves first deleted task or NULL - not used because tasks are never finished |
|
static task_t* get_empty_task(); |
|
/* |
|
* Initiate a task with the given functions and stack. When control |
|
* is yield to the task then the loop function is repeatedly called. |
|
* @param[in] h task handler (may be NULL) |
|
* @param[in] stack top reference. |
|
*/ |
|
static void *init_task(uint64_t h, const uint8_t* stack); |
|
|
|
static uint32_t fill_task(task_t &tp); // prepares task's TCB |
|
static void enqueue_task(task_t &tp); // add new task to run queue |
|
static void dequeue_task(task_t &tp); // remove task from run queue |
|
|
|
// plan context switch |
|
static void switch_task(); |
|
static void _switch_task(); |
|
|
|
static task_t s_main; // main task TCB |
|
static size_t s_top; // Task stack allocation top. |
|
static uint16_t task_n; // counter of tasks |
|
|
|
static task_t *_idle_task; // remember TCB of idle task |
|
static task_t *_forced_task; // task activated from ISR so should be called without prioritization |
|
static void *boost_task; // task that called delay_boost() |
|
|
|
static void check_stack(uint32_t sp); |
|
|
|
#define await(cond) while(!(cond)) yield() |
|
|
|
//} end of multitask |
|
|
|
private: |
|
static AP_HAL::Device::PeriodicHandle _register_timer_task(uint32_t period_us, Handler proc, F4Light::Semaphore *sem); |
|
|
|
static void * _delay_cb_handle; |
|
static bool _initialized; |
|
|
|
// ISR functions |
|
static void _timer_isr_event(uint32_t v /*TIM_TypeDef *tim */); |
|
static void _timer5_ovf(uint32_t v /*TIM_TypeDef *tim */ ); |
|
static void _tail_timer_event(uint32_t v /*TIM_TypeDef *tim */); |
|
static void _ioc_timer_event(uint32_t v /*TIM_TypeDef *tim */); |
|
static void _delay_timer_event(uint32_t v /*TIM_TypeDef *tim */); |
|
|
|
static void _run_timer_procs(bool called_from_isr); |
|
|
|
static uint32_t timer5_ovf_cnt; // high part of 64-bit time |
|
|
|
static AP_HAL::Proc _failsafe; // periodically called from ISR |
|
|
|
static Revo_IO _io_proc[F4Light_SCHEDULER_MAX_IO_PROCS]; // low priority tasks for IO thread |
|
static void _run_io(void); |
|
static uint8_t _num_io_proc; |
|
static bool _in_io_proc; |
|
|
|
static Handler on_disarm_handler; |
|
|
|
static void _print_stats(); |
|
|
|
static uint32_t lowest_stack; |
|
|
|
static struct IO_COMPLETION io_completion[MAX_IO_COMPLETION]; |
|
static uint8_t num_io_completion; |
|
|
|
|
|
#ifdef SHED_PROF |
|
static uint64_t shed_time; |
|
static uint64_t task_time; |
|
static bool flag_10s; |
|
|
|
static uint64_t delay_time; |
|
static uint64_t delay_int_time; |
|
static uint32_t max_loop_time; |
|
|
|
static void _set_10s_flag(); |
|
static uint64_t ioc_time; |
|
static uint64_t sleep_time; |
|
static uint32_t max_delay_err; |
|
|
|
|
|
static uint32_t tick_micros; // max exec time |
|
static uint32_t tick_count; // number of calls |
|
static uint64_t tick_fulltime; // full consumed time to calc mean |
|
|
|
#endif |
|
|
|
#ifdef MTASK_PROF |
|
static uint32_t max_wfe_time; |
|
static uint32_t tsched_count; |
|
static uint32_t tsched_sw_count; |
|
static uint32_t tsched_count_y; |
|
static uint32_t tsched_sw_count_y; |
|
static uint32_t tsched_count_t; |
|
static uint32_t tsched_sw_count_t; |
|
|
|
|
|
#ifdef SHED_DEBUG |
|
static revo_sched_log logbuf[SHED_DEBUG_SIZE]; |
|
static uint16_t sched_log_ptr; |
|
#endif |
|
#endif |
|
|
|
|
|
|
|
|
|
#ifdef MPU_DEBUG |
|
static uint32_t MPU_overflow_cnt; |
|
static uint32_t MPU_restart_cnt; |
|
static uint32_t MPU_count; |
|
static uint32_t MPU_Time; |
|
#endif |
|
|
|
}; |
|
|
|
void revo_call_handler(Handler h, uint32_t arg); |
|
|
|
|
|
|