forking microbit-dal
Dependencies: BLE_API mbed-dev-bin nRF51822-bluetooth-mdw
Fork of microbit-dal by
Diff: inc/core/MicroBitFiber.h
- Revision:
- 1:8aa5cdb4ab67
- Child:
- 41:da05ec75cd5d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/inc/core/MicroBitFiber.h Thu Apr 07 01:33:22 2016 +0100 @@ -0,0 +1,401 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 British Broadcasting Corporation. +This software is provided by Lancaster University by arrangement with the BBC. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/** + * Functionality 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 EventModel events generated in an ISR 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 stack + */ +#ifndef MICROBIT_FIBER_H +#define MICROBIT_FIBER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitEvent.h" +#include "EventModel.h" + +// Fiber Scheduler Flags +#define MICROBIT_SCHEDULER_RUNNING 0x01 + +// Fiber Flags +#define MICROBIT_FIBER_FLAG_FOB 0x01 +#define MICROBIT_FIBER_FLAG_PARENT 0x02 +#define MICROBIT_FIBER_FLAG_CHILD 0x04 +#define MICROBIT_FIBER_FLAG_DO_NOT_PAGE 0x08 + +/** + * 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! + */ +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; + uint32_t stack_base; +}; + +/** + * Representation of a single Fiber + */ +struct Fiber +{ + Cortex_M0_TCB tcb; // Thread context when last scheduled out. + uint32_t stack_bottom; // The start address of this Fiber's stack. The stack is heap allocated, and full descending. + uint32_t stack_top; // The end address of this Fiber's stack. + uint32_t context; // Context specific information. + uint32_t flags; // Information about this fiber. + Fiber **queue; // The queue this fiber is stored on. + Fiber *next, *prev; // Position of this Fiber on the run queue. +}; + +extern Fiber *currentFiber; + + +/** + * 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. + * + * @param _messageBus An event model, used to direct the priorities of the scheduler. + */ +void scheduler_init(EventModel &_messageBus); + +/** + * Determines if the fiber scheduler is operational. + * + * @return 1 if the fber scheduler is running, 0 otherwise. + */ +int fiber_scheduler_running(); + +/** + * Exit point for all fibers. + * + * Any fiber reaching the end of its entry function will return here for recycling. + */ +void release_fiber(void); +void release_fiber(void *param); + +/** + * Launches a fiber. + * + * @param ep the entry point for the fiber. + * + * @param cp the completion routine after ep has finished execution + */ +void launch_new_fiber(void (*ep)(void), void (*cp)(void)) +#ifdef __GCC__ + __attribute__((naked)) +#endif +; + +/** + * Launches a fiber with a parameter + * + * @param ep the entry point for the fiber. + * + * @param cp the completion routine after ep has finished execution + * + * @param pm the parameter to provide to ep and cp. + */ +void launch_new_fiber_param(void (*ep)(void *), void (*cp)(void *), void *pm) +#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. + * Defaults to release_fiber. + * + * @return The new Fiber, or NULL if the operation could not be completed. + */ +Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void) = release_fiber); + + +/** + * 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 and completion_fn. + * + * @param completion_fn The function called when the thread completes execution of entry_fn. + * Defaults to release_fiber. + * + * @return The new Fiber, or NULL if the operation could not be completed. + */ +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 function 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 immediateley descheduled, and placed onto a + * wait queue until the requested amount of time has elapsed. + * + * @param t The period of time to sleep, in milliseconds. + * + * @note the fiber will not be be made runnable until after the elapsed time, but there + * are no guarantees precisely when the fiber will next be scheduled. + */ +void fiber_sleep(unsigned long t); + +/** + * The timer callback, called from interrupt context once every SYSTEM_TICK_PERIOD_MS milliseconds. + * This function 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 immediateley descheduled, and placed onto a + * wait queue until the requested event is received. + * + * @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) + * + * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. + * + * @code + * fiber_wait_for_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * @endcode + * + * @note 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. + */ +int fiber_wait_for_event(uint16_t id, uint16_t value); + +/** + * Configures the fiber context for the current fiber to block on an event ID + * and value, but does not deschedule the fiber. + * + * @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) + * + * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. + * + * @code + * fiber_wake_on_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); + * + * //perform some time critical operation. + * + * //deschedule the current fiber manually, waiting for the previously configured event. + * schedule(); + * @endcode + */ +int fiber_wake_on_event(uint16_t id, uint16_t value); + +/** + * Executes the given function asynchronously if necessary. + * + * Fibers are often used to run event handlers, however many of these event handlers are very simple functions + * that complete very quickly, bringing unecessary RAM overhead. + * + * This function takes a snapshot of the current processor context, then attempts to optimistically call the given function directly. + * We only create an additional fiber if that function performs a block operation. + * + * @param entry_fn The function to execute. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int invoke(void (*entry_fn)(void)); + +/** + * Executes the given function asynchronously if necessary, and offers the ability to provide a parameter. + * + * Fibers are often used to run event handlers, however many of these event handlers are very simple functions + * that complete very quickly, bringing unecessary RAM. overhead + * + * This function takes a snapshot of the current fiber context, then attempt to optimistically call the given function directly. + * We only create an additional fiber if that function performs a block operation. + * + * @param entry_fn The function to execute. + * + * @param param an untyped parameter passed into the entry_fn and completion_fn. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + */ +int invoke(void (*entry_fn)(void *), void *param); + +/** + * Resizes the stack allocation of the current fiber if necessary to hold the system stack. + * + * If the stack allocation is large enough to hold the current system stack, then this function does nothing. + * Otherwise, the the current allocation of the fiber is freed, and a larger block is allocated. + * + * @param f The fiber context to verify. + * + * @return The stack depth of the given fiber. + */ +inline void verify_stack_size(Fiber *f); + +/** + * Event callback. Called from an instance of MicroBitMessageBus whenever an event is raised. + * + * This function checks to determine if any fibers blocked on the wait queue need to be woken up + * and made runnable due to the event. + * + * @param evt the event that has just been raised on an instance of MicroBitMessageBus. + */ +void scheduler_event(MicroBitEvent evt); + +/** + * Determines if any fibers are waiting to be scheduled. + * + * @return The number of fibers currently on the run queue + */ +int scheduler_runqueue_empty(); + +/** + * 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); + +/** + * Set of tasks to perform when idle. + * Service any background tasks that are required, and attempt a power efficient sleep. + */ +void idle(); + +/** + * The idle task, which is called when the runtime has no fibers that require execution. + * + * This function typically calls idle(). + */ +void idle_task(); + +/** + * Adds a component to the array of idle thread components, which are processed + * when the run queue is empty. + * + * The system timer will poll isIdleCallbackNeeded on each component to determine + * if the scheduler should schedule the idle_task imminently. + * + * @param component The component to add to the array. + * + * @return MICROBIT_OK on success or MICROBIT_NO_RESOURCES if the fiber components array is full. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * // heap allocated - otherwise it will be paged out! + * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); + * + * fiber_add_idle_component(accelerometer); + * @endcode + */ +int fiber_add_idle_component(MicroBitComponent *component); + +/** + * Remove a component from the array of idle thread components + * + * @param component The component to remove from the idle component array. + * + * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previously added. + * + * @code + * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); + * + * // heap allocated - otherwise it will be paged out! + * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); + * + * fiber_add_idle_component(accelerometer); + * + * fiber_remove_idle_component(accelerometer); + * @endcode + */ +int fiber_remove_idle_component(MicroBitComponent *component); + +/** + * Determines if the processor is executing in interrupt context. + * + * @return true if any the processor is currently executing any interrupt service routine. False otherwise. + */ +inline int inInterruptContext() +{ + return (((int)__get_IPSR()) & 0x003F) > 0; +} + +/** + * Assembler Context 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); +extern "C" void save_register_context(Cortex_M0_TCB *tcb); +extern "C" void restore_register_context(Cortex_M0_TCB *tcb); + +#endif