222 lines
7.1 KiB
C
222 lines
7.1 KiB
C
|
/**
|
||
|
* Definitions for the MicroBit Fiber scheduler.
|
||
|
*
|
||
|
* This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes:
|
||
|
*
|
||
|
* 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks).
|
||
|
* 2) To provide ISR decoupling for Messagebus events generted in an ISR context.
|
||
|
*/
|
||
|
|
||
|
#ifndef MICROBIT_FIBER_H
|
||
|
#define MICROBIT_FIBER_H
|
||
|
|
||
|
#include "mbed.h"
|
||
|
#include "MicroBitMessageBus.h"
|
||
|
|
||
|
// Typical stack size of each fiber.
|
||
|
// A physical stack of anything less than 512 will likely hit overflow issues during ISR/mBed calls.
|
||
|
// However, as we're running a co=operative fiber scheduler, the size of the stack at the point of
|
||
|
// context switching is normally *very* small (circa 12 bytes!). Also, as we're likely to have many short lived threads
|
||
|
// being used, be actually perform a stack duplication on context switch, which keeps the RAM footprint of a fiber
|
||
|
// down to a minimum, without constraining what can be done insode a fiber context.
|
||
|
//
|
||
|
// TODO: Consider a split mode scheduler, that monitors used stack size, and maintains a dedicated, persistent
|
||
|
// stack for any long lived fibers with large stacks.
|
||
|
//
|
||
|
#define FIBER_STACK_SIZE 64
|
||
|
#define FIBER_TICK_PERIOD_MS 6
|
||
|
#define CORTEX_M0_STACK_BASE (0x20004000 - 4)
|
||
|
|
||
|
#define MICROBIT_FLAG_DATA_READ 0x01
|
||
|
|
||
|
/**
|
||
|
* Thread Context for an ARM Cortex M0 core.
|
||
|
*
|
||
|
* This is probably overkill, but the ARMCC compiler uses a lot register optimisation
|
||
|
* in its calling conventions, so better safe than sorry. :-)
|
||
|
*
|
||
|
* TODO: Check with ARM guys to see is they have suggestions to optimize this context block
|
||
|
*/
|
||
|
struct Cortex_M0_TCB
|
||
|
{
|
||
|
uint32_t R0;
|
||
|
uint32_t R1;
|
||
|
uint32_t R2;
|
||
|
uint32_t R3;
|
||
|
uint32_t R4;
|
||
|
uint32_t R5;
|
||
|
uint32_t R6;
|
||
|
uint32_t R7;
|
||
|
uint32_t R8;
|
||
|
uint32_t R9;
|
||
|
uint32_t R10;
|
||
|
uint32_t R11;
|
||
|
uint32_t R12;
|
||
|
uint32_t SP;
|
||
|
uint32_t LR;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Representation of a single Fiber
|
||
|
*/
|
||
|
|
||
|
struct Fiber
|
||
|
{
|
||
|
uint32_t stack_top, stack_bottom; // Address of this Fiber's stack. Stack is heap allocated, and full descending.
|
||
|
Cortex_M0_TCB tcb; // Thread context when last scheduled out.
|
||
|
uint32_t context; // Context specific information.
|
||
|
Fiber **queue; // The queue this fiber is stored on.
|
||
|
Fiber *next, *prev; // Position of this Fiber on the run queues.
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Initialises the Fiber scheduler.
|
||
|
* Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread.
|
||
|
*
|
||
|
* This function must be called once only from the main thread, and before any other Fiber operation.
|
||
|
*/
|
||
|
void scheduler_init();
|
||
|
|
||
|
/**
|
||
|
* Exit point for all fibers.
|
||
|
* Any fiber reaching the end of its entry function will return here for recycling.
|
||
|
*/
|
||
|
void release_fiber(void);
|
||
|
|
||
|
/**
|
||
|
* Exit point for parameterised fibers.
|
||
|
* A wrapper around release_fiber() to enable transparent operaiton.
|
||
|
*/
|
||
|
void release_fiber(void *param);
|
||
|
|
||
|
/**
|
||
|
* Launches a fiber
|
||
|
*/
|
||
|
void launch_new_fiber()
|
||
|
#ifdef __GCC__
|
||
|
__attribute__((naked))
|
||
|
#endif
|
||
|
;
|
||
|
|
||
|
/**
|
||
|
* Creates a new Fiber, and launches it.
|
||
|
*
|
||
|
* @param entry_fn The function the new Fiber will begin execution in.
|
||
|
* @param completion_fn The function called when the thread completes execution of entry_fn.
|
||
|
* @return The new Fiber.
|
||
|
*/
|
||
|
Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void) = release_fiber);
|
||
|
|
||
|
/**
|
||
|
* Launches a paramaterised fiber
|
||
|
*/
|
||
|
void launch_new_fiber_param()
|
||
|
#ifdef __GCC__
|
||
|
__attribute__((naked))
|
||
|
#endif
|
||
|
;
|
||
|
/**
|
||
|
* Creates a new parameterised Fiber, and launches it.
|
||
|
*
|
||
|
* @param entry_fn The function the new Fiber will begin execution in.
|
||
|
* @param param an untyped parameter passed into the entry_fn anf completion_fn.
|
||
|
* @param completion_fn The function called when the thread completes execution of entry_fn.
|
||
|
* @return The new Fiber.
|
||
|
*/
|
||
|
Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *) = release_fiber);
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Calls the Fiber scheduler.
|
||
|
* The calling Fiber will likely be blocked, and control given to another waiting fiber.
|
||
|
* Call this to yield control of the processor when you have nothing more to do.
|
||
|
*/
|
||
|
void schedule();
|
||
|
|
||
|
/**
|
||
|
* Blocks the calling thread for the given period of time.
|
||
|
* The calling thread will be immediatley descheduled, and placed onto a
|
||
|
* wait queue until the requested amount of time has elapsed.
|
||
|
*
|
||
|
* n.b. the fiber will not be be made runnable until after the elasped time, but there
|
||
|
* are no guarantees precisely when the fiber will next be scheduled.
|
||
|
*
|
||
|
* @param t The period of time to sleep, in milliseconds.
|
||
|
*/
|
||
|
void fiber_sleep(unsigned long t);
|
||
|
|
||
|
/**
|
||
|
* Timer callback. Called from interrupt context, once every FIBER_TICK_PERIOD_MS milliseconds.
|
||
|
* Simply checks to determine if any fibers blocked on the sleep queue need to be woken up
|
||
|
* and made runnable.
|
||
|
*/
|
||
|
void scheduler_tick();
|
||
|
|
||
|
/**
|
||
|
* Blocks the calling thread until the specified event is raised.
|
||
|
* The calling thread will be immediatley descheduled, and placed onto a
|
||
|
* wait queue until the requested event is received.
|
||
|
*
|
||
|
* n.b. the fiber will not be be made runnable until after the event is raised, but there
|
||
|
* are no guarantees precisely when the fiber will next be scheduled.
|
||
|
*
|
||
|
* @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A)
|
||
|
* @param value The VALUE of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK)
|
||
|
*/
|
||
|
void fiber_wait_for_event(uint16_t id, uint16_t value);
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Event callback. Called from the message bus whenever an event is raised.
|
||
|
* Checks to determine if any fibers blocked on the wait queue need to be woken up
|
||
|
* and made runnable due to the event.
|
||
|
*/
|
||
|
void scheduler_event(MicroBitEvent evt);
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Utility function to add the currenty running fiber to the given queue.
|
||
|
* Perform a simple add at the head, to avoid complexity,
|
||
|
* Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of
|
||
|
* brute force searching.
|
||
|
*
|
||
|
* @param f The fiber to add to the queue
|
||
|
* @param queue The run queue to add the fiber to.
|
||
|
*/
|
||
|
void queue_fiber(Fiber *f, Fiber **queue);
|
||
|
|
||
|
/**
|
||
|
* Utility function to the given fiber from whichever queue it is currently stored on.
|
||
|
* @param f the fiber to remove.
|
||
|
*/
|
||
|
void dequeue_fiber(Fiber *f);
|
||
|
|
||
|
/**
|
||
|
* IDLE task.
|
||
|
* Only scheduled for execution when the runqueue is empty.
|
||
|
* Performs a procressor sleep operation, then returns to the scheduler - most likely after a timer interrupt.
|
||
|
*/
|
||
|
void idle_task();
|
||
|
|
||
|
/**
|
||
|
* Assembler Ccontext switch routing.
|
||
|
* Defined in CortexContextSwitch.s
|
||
|
*/
|
||
|
extern "C" void swap_context(Cortex_M0_TCB *from, Cortex_M0_TCB *to, uint32_t from_stack, uint32_t to_stack);
|
||
|
extern "C" void save_context(Cortex_M0_TCB *tcb, uint32_t stack);
|
||
|
|
||
|
/**
|
||
|
* Time since power on. Measured in milliseconds.
|
||
|
* When stored as an unsigned long, this gives us approx 50 days between rollover, which is ample. :-)
|
||
|
*/
|
||
|
extern unsigned long ticks;
|
||
|
|
||
|
/**
|
||
|
* This variable is used to prioritise the systems' idle fibre to execute essential tasks.
|
||
|
*/
|
||
|
extern uint8_t fiber_flags;
|
||
|
|
||
|
#endif
|