diff --git a/.gitignore b/.gitignore index e57cb441..f0f4fe1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ build .yotta.json +.vscode yotta_modules yotta_targets *.swp *~ Makefile +.vscode diff --git a/inc/core/MicroBitComponent.h b/inc/core/MicroBitComponent.h index 62661e0c..9d7aadd7 100644 --- a/inc/core/MicroBitComponent.h +++ b/inc/core/MicroBitComponent.h @@ -31,47 +31,44 @@ DEALINGS IN THE SOFTWARE. // Enumeration of core components. #define MICROBIT_ID_BUTTON_A 1 #define MICROBIT_ID_BUTTON_B 2 -#define MICROBIT_ID_BUTTON_RESET 3 -#define MICROBIT_ID_ACCELEROMETER 4 -#define MICROBIT_ID_COMPASS 5 -#define MICROBIT_ID_DISPLAY 6 - -//EDGE connector events -#define MICROBIT_IO_PINS 20 - -#define MICROBIT_ID_IO_P0 7 //P0 is the left most pad (ANALOG/DIGITAL) -#define MICROBIT_ID_IO_P1 8 //P1 is the middle pad (ANALOG/DIGITAL) -#define MICROBIT_ID_IO_P2 9 //P2 is the right most pad (ANALOG/DIGITAL) -#define MICROBIT_ID_IO_P3 10 //COL1 (ANALOG/DIGITAL) -#define MICROBIT_ID_IO_P4 11 //BTN_A -#define MICROBIT_ID_IO_P5 12 //COL2 (ANALOG/DIGITAL) -#define MICROBIT_ID_IO_P6 13 //ROW2 -#define MICROBIT_ID_IO_P7 14 //ROW1 -#define MICROBIT_ID_IO_P8 15 //PIN 18 -#define MICROBIT_ID_IO_P9 16 //ROW3 -#define MICROBIT_ID_IO_P10 17 //COL3 (ANALOG/DIGITAL) -#define MICROBIT_ID_IO_P11 18 //BTN_B -#define MICROBIT_ID_IO_P12 19 //PIN 20 -#define MICROBIT_ID_IO_P13 20 //SCK -#define MICROBIT_ID_IO_P14 21 //MISO -#define MICROBIT_ID_IO_P15 22 //MOSI -#define MICROBIT_ID_IO_P16 23 //PIN 16 -#define MICROBIT_ID_IO_P19 24 //SCL -#define MICROBIT_ID_IO_P20 25 //SDA - -#define MICROBIT_ID_BUTTON_AB 26 // Button A+B multibutton -#define MICROBIT_ID_GESTURE 27 // Gesture events - -#define MICROBIT_ID_THERMOMETER 28 -#define MICROBIT_ID_RADIO 29 -#define MICROBIT_ID_RADIO_DATA_READY 30 -#define MICROBIT_ID_MULTIBUTTON_ATTACH 31 -#define MICROBIT_ID_SERIAL 32 - -#define MICROBIT_ID_IO_INT1 33 //INT1 -#define MICROBIT_ID_IO_INT2 34 //INT2 -#define MICROBIT_ID_IO_INT3 35 //INT3 -#define MICROBIT_ID_PARTIAL_FLASHING 36 +#define MICROBIT_ID_BUTTON_AB 3 // Button A+B multibutton +#define MICROBIT_ID_BUTTON_RESET 4 +#define MICROBIT_ID_ACCELEROMETER 5 +#define MICROBIT_ID_COMPASS 6 +#define MICROBIT_ID_DISPLAY 7 +#define MICROBIT_ID_THERMOMETER 8 +#define MICROBIT_ID_RADIO 9 +#define MICROBIT_ID_RADIO_DATA_READY 10 +#define MICROBIT_ID_MULTIBUTTON_ATTACH 11 +#define MICROBIT_ID_SERIAL 12 +#define MICROBIT_ID_GESTURE 13 // Gesture events + +#define MICROBIT_ID_IO_P0 100 //P0 is the left most pad (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P1 101 //P1 is the middle pad (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P2 102 //P2 is the right most pad (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P3 103 //COL1 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P4 104 //BTN_A +#define MICROBIT_ID_IO_P5 105 //COL2 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P6 106 //ROW2 +#define MICROBIT_ID_IO_P7 107 //ROW1 +#define MICROBIT_ID_IO_P8 108 //PIN 18 +#define MICROBIT_ID_IO_P9 109 //ROW3 +#define MICROBIT_ID_IO_P10 110 //COL3 (ANALOG/DIGITAL) +#define MICROBIT_ID_IO_P11 111 //BTN_B +#define MICROBIT_ID_IO_P12 112 //PIN 20 +#define MICROBIT_ID_IO_P13 113 //SCK +#define MICROBIT_ID_IO_P14 114 //MISO +#define MICROBIT_ID_IO_P15 115 //MOSI +#define MICROBIT_ID_IO_P16 116 //PIN 16 +#define MICROBIT_ID_IO_P19 119 //SCL +#define MICROBIT_ID_IO_P20 120 //SDA + +#define MICROBIT_ID_IO_INT1 130 //INT1 +#define MICROBIT_ID_IO_INT2 131 //INT2 +#define MICROBIT_ID_IO_INT3 132 //INT3 + +// System Softwarre components +#define MICROBIT_ID_PARTIAL_FLASHING 200 #define MICROBIT_ID_MESSAGE_BUS_LISTENER 1021 // Message bus indication that a handler for a given ID has been registered. #define MICROBIT_ID_NOTIFY_ONE 1022 // Notfication channel, for general purpose synchronisation diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index 9f5e3855..76bc45a6 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -149,6 +149,23 @@ extern uint32_t __etext; #define SYSTEM_TICK_PERIOD_MS 6 #endif +// Enable used_data field in Fiber structure (for thread-local data) +#ifndef MICROBIT_FIBER_USER_DATA +#define MICROBIT_FIBER_USER_DATA 0 +#endif + +// Indicate get_fiber_list() API is supported +#ifndef MICROBIT_GET_FIBER_LIST_SUPPORTED +#define MICROBIT_GET_FIBER_LIST_SUPPORTED 1 +#endif + +// Maximum size of the FiberPool +// Defines the size that the pool of unused Fiber contexts is permitted to grow to. After this point, memory +// from unused Fiber contexts will be restored to the Heap Allocator. +#ifndef MICROBIT_FIBER_MAXIMUM_FIBER_POOL_SIZE +#define MICROBIT_FIBER_MAXIMUM_FIBER_POOL_SIZE 3 +#endif + // // Message Bus: // Default behaviour for event handlers, if not specified in the listen() call @@ -171,6 +188,21 @@ extern uint32_t __etext; #define MESSAGE_BUS_LISTENER_MAX_QUEUE_DEPTH 10 #endif +// +// Define MESSAGE_BUS concurrency behaviour. +// Set to MESSAGE_BUS_CONCURRENT_LISTENERS to fire event handler +// concurrently when a given event is raised, and process events sequentially as they arrive (default micro:bit semantics). +// Set to MESSAGE_BUS_CONCURRENT_EVENTS to to fire event handlers sequentially for any given event, while still allowing +// concurrent processing of events. +// +// +// Permissable values are: +// 0: MESSAGE_BUS_CONCURRENT_LISTENERS +// 1: MESSAGE_BUS_CONCURRENT_EVENTS +// +#ifndef MESSAGE_BUS_CONCURRENCY_MODE +#define MESSAGE_BUS_CONCURRENCY_MODE MESSAGE_BUS_CONCURRENT_LISTENERS +#endif // // Core micro:bit services // diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index 2e042609..8404910b 100644 --- a/inc/core/MicroBitFiber.h +++ b/inc/core/MicroBitFiber.h @@ -51,6 +51,12 @@ DEALINGS IN THE SOFTWARE. #define MICROBIT_FIBER_FLAG_CHILD 0x04 #define MICROBIT_FIBER_FLAG_DO_NOT_PAGE 0x08 +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) +#define HAS_THREAD_USER_DATA (currentFiber->user_data != NULL) +#else +#define HAS_THREAD_USER_DATA false +#endif + /** * Thread Context for an ARM Cortex M0 core. * @@ -88,7 +94,12 @@ struct Fiber 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. + Fiber *qnext; // Position of this Fiber on its queue. + Fiber *next; // Position of this Fiber in the global list of fibers. + +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + void *user_data; // Optional pointer to user defined data block. +#endif }; extern Fiber *currentFiber; @@ -111,6 +122,13 @@ void scheduler_init(EventModel &_messageBus); */ int fiber_scheduler_running(); +/** + * Provides a list of all active fibers. + * + * @return A pointer to the head of the list of all active fibers. + */ +Fiber* get_fiber_list(); + /** * Exit point for all fibers. * @@ -364,6 +382,16 @@ inline int inInterruptContext() return (((int)__get_IPSR()) & 0x003F) > 0; } +/** + * Return all current fibers. + * + * @param dest If non-null, it points to an array of pointers to fibers to store results in. + * + * @return the number of fibers (potentially) stored + */ +int list_fibers(Fiber **dest); + + /** * Assembler Context switch routing. * Defined in CortexContextSwitch.s. diff --git a/inc/core/MicroBitHeapAllocator.h b/inc/core/MicroBitHeapAllocator.h index 10aa5c94..43886f77 100644 --- a/inc/core/MicroBitHeapAllocator.h +++ b/inc/core/MicroBitHeapAllocator.h @@ -85,4 +85,38 @@ struct HeapDefinition int microbit_create_heap(uint32_t start, uint32_t end); void microbit_heap_print(); + +/** + * Returns the size of a given heap. + * + * @param heap_index index between 0 and MICROBIT_MAXIMUM_HEAPS-1 + * + * @return the size of heap in bytes, or zero if no such heap exists. + */ +uint32_t microbit_heap_size(uint8_t heap_index); + +/** + * Attempt to allocate a given amount of memory from any of our configured heap areas. + * + * @param size The amount of memory, in bytes, to allocate. + * + * @return A pointer to the allocated memory, or NULL if insufficient memory is available. + */ +extern "C" void* microbit_alloc(size_t size); + +/** + * Release a given area of memory from the heap. + * + * @param mem The memory area to release. + */ +extern "C" void microbit_free(void *mem); + +/** + * Copy existing contents of ptr to a new memory block of given size. + * + * @param ptr The existing memory block (can be NULL) + * @param size The size of new block (can be smaller or larger than the old one) + */ +extern "C" void* microbit_realloc(void* ptr, size_t size); + #endif diff --git a/inc/core/MicroBitListener.h b/inc/core/MicroBitListener.h index 692ea767..a3f7802b 100644 --- a/inc/core/MicroBitListener.h +++ b/inc/core/MicroBitListener.h @@ -28,9 +28,9 @@ DEALINGS IN THE SOFTWARE. #include "mbed.h" #include "MicroBitConfig.h" +#include "MicroBitLock.h" #include "MicroBitEvent.h" #include "MemberFunctionCallback.h" -#include "MicroBitConfig.h" // MicroBitListener flags... #define MESSAGE_BUS_LISTENER_PARAMETERISED 0x0001 @@ -67,7 +67,7 @@ struct MicroBitListener MicroBitEvent evt; MicroBitEventQueueItem *evt_queue; - + MicroBitLock lock; MicroBitListener *next; /** diff --git a/inc/core/MicroBitLock.h b/inc/core/MicroBitLock.h new file mode 100644 index 00000000..0d66a960 --- /dev/null +++ b/inc/core/MicroBitLock.h @@ -0,0 +1,66 @@ +/* +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. +*/ + +/** + * A simple lock, mostly used for mutual exclusion. + */ + +#ifndef MICROBIT_LOCK_H +#define MICROBIT_LOCK_H + +#include "MicroBitConfig.h" + +class Fiber; + +class MicroBitLock +{ + private: + bool locked; + Fiber *queue; + + public: + + /** + * Create a new lock that can be used for mutual exclusion and condition synchronisation. + */ + MicroBitLock(); + + /** + * Block the calling fiber until the lock is available + **/ + void wait(); + + /** + * Release the lock, and signal to one waiting fiber to continue + */ + void notify(); + + /** + * Release the lock, and signal to all waiting fibers to continue + */ + void notifyAll(); +}; + +#endif diff --git a/inc/core/NotifyEvents.h b/inc/core/NotifyEvents.h index 02e8b3b4..e67bea13 100644 --- a/inc/core/NotifyEvents.h +++ b/inc/core/NotifyEvents.h @@ -34,4 +34,7 @@ DEALINGS IN THE SOFTWARE. #define MICROBIT_SERIAL_EVT_TX_EMPTY 2 #define MICROBIT_UART_S_EVT_TX_EMPTY 3 +// Any values after 1024 are available for application use +#define MICROBIT_NOTIFY_USER_EVENT_BASE 1024 + #endif diff --git a/inc/drivers/MicroBitDisplay.h b/inc/drivers/MicroBitDisplay.h index cd6c6282..d1e7d390 100644 --- a/inc/drivers/MicroBitDisplay.h +++ b/inc/drivers/MicroBitDisplay.h @@ -288,7 +288,6 @@ class MicroBitDisplay : public MicroBitComponent * @param s The string to display. * * @param delay The time to delay between characters, in milliseconds. Must be > 0. - * Defaults to: MICROBIT_DEFAULT_PRINT_SPEED. * * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. * @@ -296,7 +295,26 @@ class MicroBitDisplay : public MicroBitComponent * display.printAsync("abc123",400); * @endcode */ - int printAsync(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + int printAsync(ManagedString s, int delay); + + /** + * Prints the given ManagedString to the display, one character at a time. + * Returns immediately, and executes the animation asynchronously. + * + * If the string is greater than one charcter in length, the screen + * will be cleared after MICROBIT_DEFAULT_PRINT_SPEED milliseconds. + * Otherwise, that character will be left on the screen indefinitely. + * + * @param s The string to display. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync("abc123"); + * @endcode + */ + int printAsync(ManagedString s); + /** * Prints the given image to the display, if the display is not in use. @@ -352,7 +370,27 @@ class MicroBitDisplay : public MicroBitComponent * display.print("abc123",400); * @endcode */ - int print(ManagedString s, int delay = MICROBIT_DEFAULT_PRINT_SPEED); + int print(ManagedString s, int delay); + + /** + * Prints the given string to the display, one character at a time. + * + * Blocks the calling thread until all the text has been displayed. + * + * If the string is greater than one charcter in length, the screen + * will be cleared after MICROBIT_DEFAULT_PRINT_SPEED milliseconds. + * Otherwise, that character will be left on the screen indefinitely. + * + * @param s The string to display. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.print("abc123"); + * @endcode + */ + int print(ManagedString s); + /** * Prints the given image to the display. diff --git a/inc/drivers/MicroBitMessageBus.h b/inc/drivers/MicroBitMessageBus.h index 4925a884..d20965da 100644 --- a/inc/drivers/MicroBitMessageBus.h +++ b/inc/drivers/MicroBitMessageBus.h @@ -33,6 +33,9 @@ DEALINGS IN THE SOFTWARE. #include "MicroBitListener.h" #include "EventModel.h" +#define MESSAGE_BUS_CONCURRENT_LISTENERS 0 +#define MESSAGE_BUS_CONCURRENT_EVENTS 1 + /** * Class definition for the MicroBitMessageBus. * @@ -105,7 +108,7 @@ class MicroBitMessageBus : public EventModel, public MicroBitComponent * @note It is recommended that all external code uses the send() function instead of this function, * or the constructors provided by MicrobitEvent. */ - int process(MicroBitEvent &evt, bool urgent = false); + int process(MicroBitEvent evt, bool urgent = false); /** * Returns the microBitListener with the given position in our list. @@ -144,7 +147,6 @@ class MicroBitMessageBus : public EventModel, public MicroBitComponent MicroBitListener *listeners; // Chain of active listeners. MicroBitEventQueueItem *evt_queue_head; // Head of queued events to be processed. MicroBitEventQueueItem *evt_queue_tail; // Tail of queued events to be processed. - uint16_t nonce_val; // The last nonce issued. uint16_t queueLength; // The number of events currently waiting to be processed. /** @@ -178,4 +180,9 @@ class MicroBitMessageBus : public EventModel, public MicroBitComponent virtual void idleTick(); }; +/** + * Allocate a NOTIFY event code dynamicaly, for generally purpose condition synchronisation. + */ +uint16_t allocateNotifyEvent(); + #endif diff --git a/inc/drivers/MicroBitPin.h b/inc/drivers/MicroBitPin.h index 3c7a7311..7e99219d 100644 --- a/inc/drivers/MicroBitPin.h +++ b/inc/drivers/MicroBitPin.h @@ -66,9 +66,9 @@ DEALINGS IN THE SOFTWARE. #define MICROBIT_PIN_DEFAULT_SERVO_CENTER 1500 #define MICROBIT_PIN_EVENT_NONE 0 -#define MICROBIT_PIN_EVENT_ON_EDGE 1 -#define MICROBIT_PIN_EVENT_ON_PULSE 2 -#define MICROBIT_PIN_EVENT_ON_TOUCH 3 +#define MICROBIT_PIN_EVENT_ON_EDGE 2 +#define MICROBIT_PIN_EVENT_ON_PULSE 3 +#define MICROBIT_PIN_EVENT_ON_TOUCH 4 #define MICROBIT_PIN_EVT_RISE 2 #define MICROBIT_PIN_EVT_FALL 3 diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index 08ef57fc..b9db7579 100644 --- a/inc/platform/yotta_cfg_mappings.h +++ b/inc/platform/yotta_cfg_mappings.h @@ -7,6 +7,15 @@ */ //DAL mappings + +#ifdef YOTTA_CFG_MICROBIT_DAL_FIBER_USER_DATA + #define MICROBIT_FIBER_USER_DATA YOTTA_CFG_MICROBIT_DAL_FIBER_USER_DATA +#endif + +#ifdef YOTTA_CFG_MICROBIT_DAL_MAXIMUM_FIBER_POOL_SIZE + #define MICROBIT_FIBER_MAXIMUM_FIBER_POOL_SIZE YOTTA_CFG_MICROBIT_DAL_MAXIMUM_FIBER_POOL_SIZE +#endif + #ifdef YOTTA_CFG_MICROBIT_DAL_HEAP_ALLOCATOR #define MICROBIT_HEAP_ALLOCATOR YOTTA_CFG_MICROBIT_DAL_HEAP_ALLOCATOR #endif @@ -15,6 +24,10 @@ #define MICROBIT_NESTED_HEAP_SIZE YOTTA_CFG_MICROBIT_DAL_NESTED_HEAP_PROPORTION #endif +#ifdef YOTTA_CFG_MICROBIT_DAL_FIBER_USER_DATA + #define MICROBIT_FIBER_USER_DATA YOTTA_CFG_MICROBIT_DAL_FIBER_USER_DATA +#endif + #ifdef YOTTA_CFG_MICROBIT_DAL_REUSE_SD #define MICROBIT_HEAP_REUSE_SD YOTTA_CFG_MICROBIT_DAL_REUSE_SD #endif @@ -183,4 +196,8 @@ #define MICROBIT_BLE_DEVICE_INFORMATION_SERVICE YOTTA_CFG_MICROBIT_DAL_BLUETOOTH_DEVICE_INFO_SERVICE #endif +#ifdef YOTTA_CFG_MICROBIT_DAL_MESSAGE_BUS_CONCURRENCY_MODE + #define MESSAGE_BUS_CONCURRENCY_MODE YOTTA_CFG_MICROBIT_DAL_MESSAGE_BUS_CONCURRENCY_MODE +#endif + #endif diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 484e621b..cf514e4e 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -34,6 +34,8 @@ DEALINGS IN THE SOFTWARE. #include "MicroBitConfig.h" #include "MicroBitFiber.h" #include "MicroBitSystemTimer.h" +#include "ErrorNo.h" +#include "MicroBitDevice.h" /* * Statically allocated values used to create and destroy Fibers. @@ -42,7 +44,6 @@ DEALINGS IN THE SOFTWARE. Fiber *currentFiber = NULL; // The context in which the current fiber is executing. static Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing. static Fiber *idleFiber = NULL; // the idle task - performs a power efficient sleep, and system maintenance tasks. - /* * Scheduler state. */ @@ -50,6 +51,7 @@ static Fiber *runQueue = NULL; // The list of runnable fiber static Fiber *sleepQueue = NULL; // The list of blocked fibers waiting on a fiber_sleep() operation. static Fiber *waitQueue = NULL; // The list of blocked fibers waiting on an event. static Fiber *fiberPool = NULL; // Pool of unused fibers, just waiting for a job to do. +static Fiber *fiberList = NULL; // List of all active Fibers (excludes those in the fiberPool) /* * Scheduler wide flags @@ -88,8 +90,7 @@ void queue_fiber(Fiber *f, Fiber **queue) // list, it results in fairer scheduling. if (*queue == NULL) { - f->next = NULL; - f->prev = NULL; + f->qnext = NULL; *queue = f; } else @@ -98,12 +99,11 @@ void queue_fiber(Fiber *f, Fiber **queue) // We don't maintain a tail pointer to save RAM (queues are nrmally very short). Fiber *last = *queue; - while (last->next != NULL) - last = last->next; + while (last->qnext != NULL) + last = last->qnext; - last->next = f; - f->prev = last; - f->next = NULL; + last->qnext = f; + f->qnext = NULL; } __enable_irq(); @@ -120,23 +120,40 @@ void dequeue_fiber(Fiber *f) if (f->queue == NULL) return; - // Remove this fiber fromm whichever queue it is on. __disable_irq(); - if (f->prev != NULL) - f->prev->next = f->next; + if (*(f->queue) == f) + { + // Remove the fiber from the head of the queue + *(f->queue) = f->qnext; + } else - *(f->queue) = f->next; + { + Fiber *prev = *(f->queue); - if(f->next) - f->next->prev = f->prev; + // Scan for the given fiber in its queue + while(prev->qnext != f) + prev = prev->qnext; - f->next = NULL; - f->prev = NULL; + // Remove the fiber + prev->qnext = f->qnext; + } + + // Ensure old linkage is cleared + f->qnext = NULL; f->queue = NULL; __enable_irq(); +} +/** + * Provides a list of all active fibers. + * + * @return A pointer to the head of the list of all active fibers. + */ +Fiber * get_fiber_list() +{ + return fiberList; } /** @@ -171,6 +188,16 @@ Fiber *getFiberContext() f->flags = 0; f->tcb.stack_base = CORTEX_M0_STACK_BASE; +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + f->user_data = 0; +#endif + + // Add the new Fiber to the list of all fibers + __disable_irq(); + f->next = fiberList; + fiberList = f; + __enable_irq(); + return f; } @@ -191,7 +218,7 @@ void scheduler_init(EventModel &_messageBus) // Store a reference to the messageBus provided. // This parameter will be NULL if we're being run without a message bus. - messageBus = &_messageBus; + messageBus = &_messageBus; // Create a new fiber context currentFiber = getFiberContext(); @@ -244,7 +271,7 @@ void scheduler_tick() // Check the sleep queue, and wake up any fibers as necessary. while (f != NULL) { - t = f->next; + t = f->qnext; if (system_timer_current_time() >= f->context) { @@ -280,7 +307,7 @@ void scheduler_event(MicroBitEvent evt) // Check the wait queue, and wake up any fibers as necessary. while (f != NULL) { - t = f->next; + t = f->qnext; // extract the event data this fiber is blocked on. uint16_t id = f->context & 0xFFFF; @@ -314,6 +341,37 @@ void scheduler_event(MicroBitEvent evt) messageBus->ignore(evt.source, evt.value, scheduler_event); } +/** + * Internal utility function to perform a fork operation on the current fiber, and return + * the current fibers context to the point at which it was checkpointed. + * + * This function is called whenever a fiber requests a "Fork on Block" behaviour and a + * blocking call to the scheduler is requested. + */ +static Fiber* handle_fob() +{ + Fiber *f = currentFiber; + + // This is a blocking call, so if we're in a fork on block context, + // it's time to spawn a new fiber... + if (f->flags & MICROBIT_FIBER_FLAG_FOB) + { + // Allocate a TCB from the new fiber. This will come from the tread pool if availiable, + // else a new one will be allocated on the heap. + forkedFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + // keep running in the context of the current thread as a best effort. + if (forkedFiber != NULL) { +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + forkedFiber->user_data = f->user_data; + f->user_data = NULL; +#endif + f = forkedFiber; + } + } + return f; +} /** * Blocks the calling thread for the given period of time. @@ -327,8 +385,6 @@ void scheduler_event(MicroBitEvent evt) */ void fiber_sleep(unsigned long t) { - Fiber *f = currentFiber; - // If the scheduler is not running, then simply perform a spin wait and exit. if (!fiber_scheduler_running()) { @@ -336,19 +392,8 @@ void fiber_sleep(unsigned long t) return; } - // Sleep is a blocking call, so if we're in a fork on block context, - // it's time to spawn a new fiber... - if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) - { - // Allocate a new fiber. This will come from the fiber pool if availiable, - // else a new one will be allocated on the heap. - forkedFiber = getFiberContext(); - - // If we're out of memory, there's nothing we can do. - // keep running in the context of the current thread as a best effort. - if (forkedFiber != NULL) - f = forkedFiber; - } + // Fork a new fiber if necessary + Fiber *f = handle_fob(); // Calculate and store the time we want to wake up. f->context = system_timer_current_time() + t; @@ -388,7 +433,7 @@ int fiber_wait_for_event(uint16_t id, uint16_t value) if(ret == MICROBIT_OK) schedule(); - return ret; + return ret; } /** @@ -412,29 +457,11 @@ int fiber_wait_for_event(uint16_t id, uint16_t value) */ int fiber_wake_on_event(uint16_t id, uint16_t value) { - Fiber *f = currentFiber; - if (messageBus == NULL || !fiber_scheduler_running()) return MICROBIT_NOT_SUPPORTED; - // Sleep is a blocking call, so if we're in a fork on block context, - // it's time to spawn a new fiber... - if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) - { - // Allocate a TCB from the new fiber. This will come from the tread pool if availiable, - // else a new one will be allocated on the heap. - forkedFiber = getFiberContext(); - - // If we're out of memory, there's nothing we can do. - // keep running in the context of the current thread as a best effort. - if (forkedFiber != NULL) - { - f = forkedFiber; - dequeue_fiber(f); - queue_fiber(f, &runQueue); - schedule(); - } - } + // Fork a new fiber if necessary + Fiber *f = handle_fob(); // Encode the event data in the context field. It's handy having a 32 bit core. :-) f->context = value << 16 | id; @@ -450,9 +477,18 @@ int fiber_wake_on_event(uint16_t id, uint16_t value) if (id != MICROBIT_ID_NOTIFY && id != MICROBIT_ID_NOTIFY_ONE) messageBus->listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); + // NOTE: We intentionally don't re-enter the scheduler here, such that this function + // can be used to create atomic wait events. if using this function, the calling thread MUST + // call schedule() as its next call to the scheduler. return MICROBIT_OK; } +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) +#define HAS_THREAD_USER_DATA (currentFiber->user_data != NULL) +#else +#define HAS_THREAD_USER_DATA false +#endif + /** * Executes the given function asynchronously if necessary. * @@ -475,7 +511,7 @@ int invoke(void (*entry_fn)(void)) if (!fiber_scheduler_running()) return MICROBIT_NOT_SUPPORTED; - if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) + if (currentFiber->flags & (MICROBIT_FIBER_FLAG_FOB | MICROBIT_FIBER_FLAG_PARENT | MICROBIT_FIBER_FLAG_CHILD) || HAS_THREAD_USER_DATA) { // If we attempt a fork on block whilst already in fork n block context, // simply launch a fiber to deal with the request and we're done. @@ -504,6 +540,10 @@ int invoke(void (*entry_fn)(void)) // spawn a thread to deal with it. currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; entry_fn(); + +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + currentFiber->user_data = 0; +#endif currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; // If this is is an exiting fiber that for spawned to handle a blocking call, recycle it. @@ -538,7 +578,7 @@ int invoke(void (*entry_fn)(void *), void *param) if (!fiber_scheduler_running()) return MICROBIT_NOT_SUPPORTED; - if (currentFiber->flags & (MICROBIT_FIBER_FLAG_FOB | MICROBIT_FIBER_FLAG_PARENT | MICROBIT_FIBER_FLAG_CHILD)) + if (currentFiber->flags & (MICROBIT_FIBER_FLAG_FOB | MICROBIT_FIBER_FLAG_PARENT | MICROBIT_FIBER_FLAG_CHILD) || HAS_THREAD_USER_DATA) { // If we attempt a fork on block whilst already in a fork on block context, // simply launch a fiber to deal with the request and we're done. @@ -567,6 +607,10 @@ int invoke(void (*entry_fn)(void *), void *param) // spawn a thread to deal with it. currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; entry_fn(param); + +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + currentFiber->user_data = 0; +#endif currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; // If this is is an exiting fiber that for spawned to handle a blocking call, recycle it. @@ -704,15 +748,55 @@ void release_fiber(void *) */ void release_fiber(void) { + int fiberPoolSize = 0; + if (!fiber_scheduler_running()) return; // Remove ourselves form the runqueue. dequeue_fiber(currentFiber); + // Scan the FiberPool and release memory to the heap if it is full. + for (Fiber *p = fiberPool; p; p = p->qnext) + fiberPoolSize++; + + while (fiberPoolSize > MICROBIT_FIBER_MAXIMUM_FIBER_POOL_SIZE) + { + // Release Fiber contexts from the head of the FiberPool. + Fiber *p = fiberPool; + fiberPool = p->qnext; + free((void *)p->stack_bottom); + free(p); + fiberPoolSize--; + } + // Add ourselves to the list of free fibers queue_fiber(currentFiber, &fiberPool); + // Remove the fiber from the list of active fibers + __disable_irq(); + if (fiberList == currentFiber) + { + fiberList = fiberList->next; + } + else + { + Fiber *p = fiberList; + + while (p) + { + if (p->next == currentFiber) + { + p->next = currentFiber->next; + break; + } + + p = p->next; + } + } + __enable_irq(); + + // Find something else to do! schedule(); } @@ -742,6 +826,12 @@ void verify_stack_size(Fiber *f) // If we're too small, increase our buffer size. if (bufferSize < stackDepth) { + // We are only here when the current stack is the stack of fiber [f]. + // Make sure the contents of [currentFiber] variable reflects that, otherwise + // an external memory allocator might get confused when scanning fiber stacks. + Fiber *prevCurrFiber = currentFiber; + currentFiber = f; + // To ease heap churn, we choose the next largest multple of 32 bytes. bufferSize = (stackDepth + 32) & 0xffffffe0; @@ -754,6 +844,9 @@ void verify_stack_size(Fiber *f) // Recalculate where the top of the stack is and we're done. f->stack_top = f->stack_bottom + bufferSize; + + // Restore Fiber context + currentFiber = prevCurrFiber; } } @@ -817,7 +910,7 @@ void schedule() else if (currentFiber->queue == &runQueue) // If the current fiber is on the run queue, round robin. - currentFiber = currentFiber->next == NULL ? runQueue : currentFiber->next; + currentFiber = currentFiber->qnext == NULL ? runQueue : currentFiber->qnext; else // Otherwise, just pick the head of the run queue. @@ -942,3 +1035,84 @@ void idle_task() schedule(); } } + +/** + * Create a new lock that can be used for mutual exclusion and condition synchronisation. + */ +MicroBitLock::MicroBitLock() +{ + queue = NULL; + locked = false; +} + +/** + * Block the calling fiber until the lock is available + **/ +void MicroBitLock::wait() +{ + Fiber *f = currentFiber; + + // If the scheduler is not running, then simply exit, as we're running monothreaded. + if (!fiber_scheduler_running()) + return; + + if (locked) + { + // wait() is a blocking call, so if we're in a fork on block context, + // it's time to spawn a new fiber... + if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) + { + // Allocate a new fiber. This will come from the fiber pool if availiable, + // else a new one will be allocated on the heap. + forkedFiber = getFiberContext(); + + // If we're out of memory, there's nothing we can do. + // keep running in the context of the current thread as a best effort. + if (forkedFiber != NULL) + f = forkedFiber; + } + + // Remove fiber from the run queue + dequeue_fiber(f); + + // Add fiber to the sleep queue. We maintain strict ordering here to reduce lookup times. + queue_fiber(f, &queue); + + // Finally, enter the scheduler. + schedule(); + } + + locked = true; +} + +/** + * Release the lock, and signal to one waiting fiber to continue + */ +void MicroBitLock::notify() +{ + Fiber *f = queue; + + if (f) + { + dequeue_fiber(f); + queue_fiber(f, &runQueue); + } + locked = false; +} + +/** + * Release the lock, and signal to all waiting fibers to continue + */ +void MicroBitLock::notifyAll() +{ + Fiber *f = queue; + + while (f) + { + dequeue_fiber(f); + queue_fiber(f, &runQueue); + f = queue; + } + + locked = false; +} diff --git a/source/core/MicroBitHeapAllocator.cpp b/source/core/MicroBitHeapAllocator.cpp index c42b3daa..133a106d 100644 --- a/source/core/MicroBitHeapAllocator.cpp +++ b/source/core/MicroBitHeapAllocator.cpp @@ -173,6 +173,14 @@ int microbit_create_heap(uint32_t start, uint32_t end) return MICROBIT_OK; } +uint32_t device_heap_size(uint8_t heap_index) +{ + if (heap_index >= heap_count) + return 0; + HeapDefinition *h = &heap[heap_index]; + return (uint8_t*)h->heap_end - (uint8_t*)h->heap_start; +} + /** * Attempt to allocate a given amount of memory from a given heap area. * @@ -271,11 +279,14 @@ void *microbit_malloc(size_t size, HeapDefinition &heap) * * @return A pointer to the allocated memory, or NULL if insufficient memory is available. */ -void *malloc(size_t size) +void *microbit_alloc(size_t size) { static uint8_t initialised = 0; void *p; + if (size == 0) + return NULL; + if (!initialised) { heap_count = 0; @@ -319,7 +330,7 @@ void *malloc(size_t size) * * @param mem The memory area to release. */ -void free(void *mem) +void microbit_free(void *mem) { uint32_t *memory = (uint32_t *)mem; uint32_t *cb = memory-1; @@ -355,13 +366,17 @@ void* calloc (size_t num, size_t size) { void *mem = malloc(num*size); - if (mem) - memclr(mem, num*size); + if (mem) { + // without this write, GCC will happily optimize malloc() above into calloc() + // and remove the memset + ((uint32_t*)mem)[0] = 1; + memset(mem, 0, num*size); + } return mem; } -void* realloc (void* ptr, size_t size) +void* microbit_realloc (void* ptr, size_t size) { void *mem = malloc(size); @@ -380,6 +395,10 @@ void* realloc (void* ptr, size_t size) return mem; } +void *malloc(size_t sz) __attribute__ ((weak, alias ("microbit_alloc"))); +void free(void *mem) __attribute__ ((weak, alias ("microbit_free"))); +void* realloc (void* ptr, size_t size) __attribute__ ((weak, alias ("microbit_realloc"))); + // make sure the libc allocator is not pulled in void *_malloc_r(struct _reent *, size_t len) { @@ -408,4 +427,4 @@ int microbit_create_heap(uint32_t start, uint32_t end) return MICROBIT_OK; } -#endif +#endif \ No newline at end of file diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index d0d7b844..cc2bc916 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -354,8 +354,7 @@ void MicroBitDisplay::updateScrollText() void MicroBitDisplay::updatePrintText() { image.print(printingChar < printingText.length() ? printingText.charAt(printingChar) : ' ',0,0); - - if (printingChar > printingText.length()) + if (printingChar >= printingText.length()) { animationMode = ANIMATION_MODE_NONE; @@ -448,7 +447,7 @@ void MicroBitDisplay::stopAnimation() void MicroBitDisplay::waitForFreeDisplay() { // If there's an ongoing animation, wait for our turn to display. - if (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) + while (animationMode != ANIMATION_MODE_NONE && animationMode != ANIMATION_MODE_STOPPED) fiber_wait_for_event(MICROBIT_ID_NOTIFY, MICROBIT_DISPLAY_EVT_FREE); } @@ -521,19 +520,19 @@ int MicroBitDisplay::printCharAsync(char c, int delay) */ int MicroBitDisplay::printAsync(ManagedString s, int delay) { - if (s.length() == 1) - return printCharAsync(s.charAt(0)); - //sanitise this value - if (delay <= 0 ) + if (delay < 0 || (delay == 0 && s.length() > 1)) return MICROBIT_INVALID_PARAMETER; + if (s.length() == 1) + return printCharAsync(s.charAt(0), delay); + if (animationMode == ANIMATION_MODE_NONE || animationMode == ANIMATION_MODE_STOPPED) { printingChar = 0; printingText = s; animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_TEXT; } @@ -545,6 +544,33 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) return MICROBIT_OK; } +/** + * Prints the given ManagedString to the display, one character at a time. + * Returns immediately, and executes the animation asynchronously. + * + * If the string is greater than one charcter in length, the screen + * will be cleared after MICROBIT_DEFAULT_PRINT_SPEED milliseconds. + * Otherwise, that character will be left on the screen indefinitely. + * + * @param s The string to display. + * + * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.printAsync("abc123"); + * @endcode + */ +int MicroBitDisplay::printAsync(ManagedString s) +{ + int delay = MICROBIT_DEFAULT_PRINT_SPEED; + + if(s.length() == 1) + delay = 0; + + return printAsync(s, delay); +} + + /** * Prints the given image to the display, if the display is not in use. * Returns immediately, and executes the animation asynchronously. @@ -647,7 +673,7 @@ int MicroBitDisplay::printChar(char c, int delay) int MicroBitDisplay::print(ManagedString s, int delay) { //sanitise this value - if(delay <= 0 ) + if(delay < 0 || (delay == 0 && s.length() > 1)) return MICROBIT_INVALID_PARAMETER; // If there's an ongoing animation, wait for our turn to display. @@ -657,15 +683,11 @@ int MicroBitDisplay::print(ManagedString s, int delay) // If someone called stopAnimation(), then we simply skip... if (animationMode == ANIMATION_MODE_NONE) { + int ret = this->printAsync(s, delay); if (s.length() == 1) - { - return printCharAsync(s.charAt(0)); - } - else - { - this->printAsync(s, delay); - fiberWait(); - } + return ret; + + fiberWait(); } else { @@ -675,6 +697,34 @@ int MicroBitDisplay::print(ManagedString s, int delay) return MICROBIT_OK; } +/** + * Prints the given string to the display, one character at a time. + * + * Blocks the calling thread until all the text has been displayed. + * + * If the string is greater than one charcter in length, the screen + * will be cleared after MICROBIT_DEFAULT_PRINT_SPEED milliseconds. + * Otherwise, that character will be left on the screen indefinitely. + * + * @param s The string to display. + * + * @return MICROBIT_OK, MICROBIT_CANCELLED or MICROBIT_INVALID_PARAMETER. + * + * @code + * display.print("abc123"); + * @endcode + */ +int MicroBitDisplay::print(ManagedString s) +{ + int delay = MICROBIT_DEFAULT_PRINT_SPEED; + + if(s.length() == 1) + delay = 0; + + return print(s, delay); +} + + /** * Prints the given image to the display. * Blocks the calling thread until all the image has been displayed. diff --git a/source/drivers/MicroBitMessageBus.cpp b/source/drivers/MicroBitMessageBus.cpp index c2ad221e..4ed6c65c 100644 --- a/source/drivers/MicroBitMessageBus.cpp +++ b/source/drivers/MicroBitMessageBus.cpp @@ -51,6 +51,9 @@ DEALINGS IN THE SOFTWARE. #include "MicroBitMessageBus.h" #include "MicroBitFiber.h" #include "ErrorNo.h" +#include "NotifyEvents.h" + +static uint16_t userNotifyId = MICROBIT_NOTIFY_USER_EVENT_BASE; /** * Default constructor. @@ -95,14 +98,19 @@ void async_callback(void *param) // Queue this event up for later, if that's how we've been configured. if (listener->flags & MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY) { +#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_LISTENERS) listener->queue(listener->evt); return; +#endif } } // Determine the calling convention for the callback, and invoke... // C++ is really bad at this! Especially as the ARM compiler is yet to support C++ 11 :-/ +#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS) + listener->lock.wait(); +#endif // Record that we have a fiber going into this listener... listener->flags |= MESSAGE_BUS_LISTENER_BUSY; @@ -138,6 +146,10 @@ void async_callback(void *param) // The fiber of exiting... clear our state. listener->flags &= ~MESSAGE_BUS_LISTENER_BUSY; + +#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS) + listener->lock.notify(); +#endif } /** @@ -261,6 +273,13 @@ int MicroBitMessageBus::deleteMarkedListeners() return removed; } +MicroBitEvent last_event; +void process_sequentially(void *param) +{ + MicroBitMessageBus *m = (MicroBitMessageBus *)param; + m->process(last_event); +} + /** * Periodic callback from MicroBit. * @@ -278,7 +297,12 @@ void MicroBitMessageBus::idleTick() while (item) { // send the event to all standard event listeners. +#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_EVENTS) + last_event = item->evt; + invoke(process_sequentially,this); +#else this->process(item->evt); +#endif // Free the queue item. delete item; @@ -337,7 +361,7 @@ int MicroBitMessageBus::send(MicroBitEvent evt) * @note It is recommended that all external code uses the send() function instead of this function, * or the constructors provided by MicrobitEvent. */ -int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) +int MicroBitMessageBus::process(MicroBitEvent evt, bool urgent) { MicroBitListener *l; int complete = 1; @@ -365,10 +389,12 @@ int MicroBitMessageBus::process(MicroBitEvent &evt, bool urgent) // Otherwise, we invoke it in a 'fork on block' context, that will automatically create a fiber // should the event handler attempt a blocking operation, but doesn't have the overhead // of creating a fiber needlessly. (cool huh?) - if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING || !fiber_scheduler_running()) - async_callback(l); - else +#if (MESSAGE_BUS_CONCURRENCY_MODE == MESSAGE_BUS_CONCURRENT_LISTENERS) + if (!(l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING) && fiber_scheduler_running()) invoke(async_callback, l); + else +#endif + async_callback(l); } else { @@ -544,6 +570,14 @@ MicroBitListener* MicroBitMessageBus::elementAt(int n) return l; } +/** + * Allocate a NOTIFY event code dynamicaly, for generally purpose condition synchronisation. + */ +uint16_t allocateNotifyEvent() +{ + return userNotifyId++; +} + /** * Destructor for MicroBitMessageBus, where we deregister this instance from the array of fiber components. */ diff --git a/source/drivers/MicroBitSerial.cpp b/source/drivers/MicroBitSerial.cpp index 7fad4e00..198caba4 100644 --- a/source/drivers/MicroBitSerial.cpp +++ b/source/drivers/MicroBitSerial.cpp @@ -295,7 +295,7 @@ void MicroBitSerial::send(MicroBitSerialMode mode) while(txBufferedSize() > 0); if(mode == SYNC_SLEEP) - fiber_sleep(0); + schedule(); } /**