From 9dedea4df2e318474d45d934e6b534da7e225cb6 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 27 Sep 2018 17:51:17 +0100 Subject: [PATCH 01/34] Introduce optional javascript semantics for event handling - new CONFIG option now allows the user to choose between ordered, serial processing of event handlers and parallel execution of events (JS semantics) OR parallel processing of event handlers and serial execution of events (legacy mciro:bit semantics) - Introduciton of a MicroBitLock primitive for mutual exclusion --- inc/core/MicroBitConfig.h | 15 +++++ inc/core/MicroBitListener.h | 4 +- inc/core/MicroBitLock.h | 66 ++++++++++++++++++++++ inc/drivers/MicroBitMessageBus.h | 4 +- inc/platform/yotta_cfg_mappings.h | 4 ++ source/core/MicroBitFiber.cpp | 81 +++++++++++++++++++++++++++ source/drivers/MicroBitMessageBus.cpp | 29 +++++++++- 7 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 inc/core/MicroBitLock.h diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index 49529596..927fa07a 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -171,6 +171,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/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/drivers/MicroBitMessageBus.h b/inc/drivers/MicroBitMessageBus.h index 4925a884..ed5c2794 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. * @@ -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. /** diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index 816ddef7..760b55ba 100644 --- a/inc/platform/yotta_cfg_mappings.h +++ b/inc/platform/yotta_cfg_mappings.h @@ -171,4 +171,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..ec8e8a3d 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -942,3 +942,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/drivers/MicroBitMessageBus.cpp b/source/drivers/MicroBitMessageBus.cpp index c2ad221e..0a55e80d 100644 --- a/source/drivers/MicroBitMessageBus.cpp +++ b/source/drivers/MicroBitMessageBus.cpp @@ -95,14 +95,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 +143,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 +270,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 +294,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; @@ -365,10 +386,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 { From 69bf72e8b57f6938e054cefad9bafa099d2ac06e Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 19:39:25 +0000 Subject: [PATCH 02/34] Update string print() method to avoid unecessary delays - First character of string is now printed on next frame update, rather than waiting for an inter-character gap - No unecessary delay after the final character is printed. These changes make the print() semantics align with the image animate() semantics. --- source/drivers/MicroBitDisplay.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index d0d7b844..72ab2a90 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; @@ -492,7 +491,7 @@ int MicroBitDisplay::printCharAsync(char c, int delay) if (delay > 0) { animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_CHARACTER; } } @@ -533,7 +532,7 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) printingChar = 0; printingText = s; animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_TEXT; } @@ -576,7 +575,7 @@ int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int de if(delay > 0) { animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_CHARACTER; } } From b1cb51901f75438ac3c5e423d10496311bc81750 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 21:53:43 +0000 Subject: [PATCH 03/34] Simplify implementation of fiber_wake_on_event() Align fork-on-block implementation of fiber_wake_on_event() with that of fiber_sleep() and MicroBitLock::wait(). This patch has no impact on external behaviour, but introduces a consistent deisgn pattern for fork-on-block handling within the scheduler. --- source/core/MicroBitFiber.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index ec8e8a3d..1914abb2 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -428,12 +428,7 @@ int fiber_wake_on_event(uint16_t id, uint16_t value) // 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(); - } } // Encode the event data in the context field. It's handy having a 32 bit core. :-) From 6112d7472cfc038d17a7fa7c753d8f08ea5bde8e Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 21:59:41 +0000 Subject: [PATCH 04/34] MicroBitMessageBus::process() updated to pass events by value Updated parameters of this internal method to pass the event being processed by value, rather than by reference. This is important for applications using the MESSAGE_BUS_CONCURRENT_EVENTS concurrency mode (JavaScript semantics) to ensure that any multiple event handlers registered for the same event maintain a persistent copy of that event on the relevant fiber stack. --- inc/drivers/MicroBitMessageBus.h | 2 +- source/drivers/MicroBitMessageBus.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/drivers/MicroBitMessageBus.h b/inc/drivers/MicroBitMessageBus.h index ed5c2794..8517b7cb 100644 --- a/inc/drivers/MicroBitMessageBus.h +++ b/inc/drivers/MicroBitMessageBus.h @@ -108,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. diff --git a/source/drivers/MicroBitMessageBus.cpp b/source/drivers/MicroBitMessageBus.cpp index 0a55e80d..f0bf46c4 100644 --- a/source/drivers/MicroBitMessageBus.cpp +++ b/source/drivers/MicroBitMessageBus.cpp @@ -358,7 +358,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; From ca43746e20363fe307bb09f0deca69a69d06e412 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 22:04:51 +0000 Subject: [PATCH 05/34] Ensure MicroBitDisplay::waitForFreeDisplay() is free of race conditions Update to the internal MicroBitDisplay::waitForFreeDisplay() method to ensure that fibers queued waiting for access to the display always re-contest for access, thereby avoiding potnetial race conditions. --- source/drivers/MicroBitDisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index 72ab2a90..d4b7c939 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -447,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); } From 31fea820d60069adbf0dd67593cceb51ce282335 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Fri, 3 May 2019 17:06:33 +0100 Subject: [PATCH 06/34] Change Fiber lists to be singly-linked - Replace doubly linked list of Fibers with singly-linked equivalent to reduce memory footprint. --- inc/core/MicroBitFiber.h | 2 +- source/core/MicroBitFiber.cpp | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index 2e042609..69ea2cc4 100644 --- a/inc/core/MicroBitFiber.h +++ b/inc/core/MicroBitFiber.h @@ -88,7 +88,7 @@ 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 *next; // Position of this Fiber on the run queue. }; extern Fiber *currentFiber; diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 484e621b..89afea9a 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -89,7 +89,6 @@ void queue_fiber(Fiber *f, Fiber **queue) if (*queue == NULL) { f->next = NULL; - f->prev = NULL; *queue = f; } else @@ -102,7 +101,6 @@ void queue_fiber(Fiber *f, Fiber **queue) last = last->next; last->next = f; - f->prev = last; f->next = NULL; } @@ -120,23 +118,30 @@ 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; - else + if (*(f->queue) == f) + { + // Remove the fiber from the head of the queue *(f->queue) = f->next; + } + else + { + Fiber *prev = *(f->queue); + + // Scan for the given fiber in its queue + while(prev->next != f) + prev = prev->next; - if(f->next) - f->next->prev = f->prev; + // Remove the fiber + prev->next = f->next; + } + // Ensure old linkage is cleared f->next = NULL; - f->prev = NULL; f->queue = NULL; __enable_irq(); - } /** From 60d423ec367910e30b9a699434704172452051c3 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Fri, 3 May 2019 17:08:13 +0100 Subject: [PATCH 07/34] Ignore .vscode metadata --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e57cb441..8a3fa7d9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ yotta_targets *.swp *~ Makefile +.vscode From 1c1426ffe4726d3e79b9e0af9228b1fb89202b04 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Fri, 3 May 2019 18:52:06 +0100 Subject: [PATCH 08/34] Introduce FiberTable stucture - Create a new FiberTable structure, that record all active fibers - Update fiber creation/deletion code to maintain the table - Add a utility function to allow read access to the table - Introduce a CONFIG option to define the initial FiberTable size --- inc/core/MicroBitConfig.h | 6 ++++ inc/core/MicroBitFiber.h | 14 ++++++++ source/core/MicroBitFiber.cpp | 60 ++++++++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index 9f5e3855..186b8b24 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -149,6 +149,12 @@ extern uint32_t __etext; #define SYSTEM_TICK_PERIOD_MS 6 #endif +// Initial FiberTable size +// Defines the initial size of the Fiber table. This will grow automatially if more Fibers are created. +#ifndef INITIAL_FIBER_TABLE_SIZE +#define INITIAL_FIBER_TABLE_SIZE 4 +#endif + // // Message Bus: // Default behaviour for event handlers, if not specified in the listen() call diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index 69ea2cc4..bf44d627 100644 --- a/inc/core/MicroBitFiber.h +++ b/inc/core/MicroBitFiber.h @@ -91,6 +91,13 @@ struct Fiber Fiber *next; // Position of this Fiber on the run queue. }; +struct FiberTable +{ + uint16_t length; // The number of live fibers in the table (n.b. the table may be non-contiguous). + uint16_t capacity; // The capacity of the FiberTable (n.b. This is not the same as the number of allocated Fibers). + + Fiber * table[0]; // List of all Fibers in the system. May be non-contiguous. Unused entries are listed as NULL. +}; extern Fiber *currentFiber; @@ -111,6 +118,13 @@ void scheduler_init(EventModel &_messageBus); */ int fiber_scheduler_running(); +/** + * Provides a list of all active fibers. + * + * @return A pointer to a FiberTable structure containing the list of currently active fibers. + */ +const FiberTable * get_fiber_table(); + /** * Exit point for all fibers. * diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 89afea9a..412d8604 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -42,7 +42,7 @@ 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. - +static FiberTable *fiberTable = NULL; // Table of all active Fibers. /* * Scheduler state. */ @@ -144,6 +144,16 @@ void dequeue_fiber(Fiber *f) __enable_irq(); } +/** + * Provides a list of all active fibers. + * + * @return A pointer to a FiberTable structure containing the list of currently active fibers. + */ +const FiberTable * get_fiber_table() +{ + return (const FiberTable *) fiberTable; +} + /** * Allocates a fiber from the fiber pool if availiable. Otherwise, allocates a new one from the heap. */ @@ -176,6 +186,38 @@ Fiber *getFiberContext() f->flags = 0; f->tcb.stack_base = CORTEX_M0_STACK_BASE; + // Add the new Fiber to the FiberTable + __disable_irq(); + if (fiberTable->length == fiberTable->capacity) + { + // Create the FiberTable. + FiberTable *newTable = (FiberTable *) realloc(fiberTable, sizeof(FiberTable) + sizeof(Fiber *) * (fiberTable->capacity + INITIAL_FIBER_TABLE_SIZE)); + + if (newTable == NULL) + { + free(f); + __enable_irq(); + return NULL; + } + + memclr(&newTable->table[newTable->capacity], sizeof(Fiber *) * INITIAL_FIBER_TABLE_SIZE); + newTable->capacity += INITIAL_FIBER_TABLE_SIZE; + + fiberTable = newTable; + } + + // Fill from the front + for (int i=0; icapacity; i++) + { + if (fiberTable->table[i] == NULL) + { + fiberTable->table[i] = f; + fiberTable->length++; + break; + } + } + __enable_irq(); + return f; } @@ -198,6 +240,12 @@ void scheduler_init(EventModel &_messageBus) // This parameter will be NULL if we're being run without a message bus. messageBus = &_messageBus; + // Create the FiberTable. + fiberTable = (FiberTable *) malloc(sizeof(FiberTable) + sizeof(Fiber *) * INITIAL_FIBER_TABLE_SIZE); + memclr(&fiberTable->table[0], sizeof(Fiber *) * INITIAL_FIBER_TABLE_SIZE); + fiberTable->capacity = INITIAL_FIBER_TABLE_SIZE; + fiberTable->length = 0; + // Create a new fiber context currentFiber = getFiberContext(); @@ -718,6 +766,16 @@ void release_fiber(void) // Add ourselves to the list of free fibers queue_fiber(currentFiber, &fiberPool); + // Remove the fiber from the FiberTable. + for (int i=0; icapacity; i++) + { + if (fiberTable->table[i] == currentFiber) + { + fiberTable->table[i] = NULL; + fiberTable->length--; + } + } + // Find something else to do! schedule(); } From 461fb33b0f69bcd930fbdf079fb9cf3eefb6766b Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 7 May 2019 15:29:23 +0100 Subject: [PATCH 09/34] Integrate optional fiber meta-data contributed in #424 - Add new CONFIG option to enable per-fiber meta data (default: disabled) - Add (void *) pointer to Fiber struct when CONFIG option enabled - Add Yotta CONFIG glue - Refactor fork-on-block handling into a unified function - Align validation semantics of both flavours of invoke() function --- inc/core/MicroBitConfig.h | 5 ++ inc/core/MicroBitFiber.h | 10 ++++ inc/platform/yotta_cfg_mappings.h | 9 +++ source/core/MicroBitFiber.cpp | 96 +++++++++++++++++-------------- 4 files changed, 76 insertions(+), 44 deletions(-) diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index 186b8b24..fe8be459 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -155,6 +155,11 @@ extern uint32_t __etext; #define INITIAL_FIBER_TABLE_SIZE 4 #endif +// Enable used_data field in Fiber structure (for thread-local data) +#ifndef MICROBIT_FIBER_USER_DATA +#define MICROBIT_FIBER_USER_DATA 0 +#endif + // // Message Bus: // Default behaviour for event handlers, if not specified in the listen() call diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index bf44d627..d44a3495 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. * @@ -89,6 +95,10 @@ struct Fiber uint32_t flags; // Information about this fiber. Fiber **queue; // The queue this fiber is stored on. Fiber *next; // Position of this Fiber on the run queue. + +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + void *user_data; // Optional pointer to user defined data block. +#endif }; struct FiberTable diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index 08ef57fc..dee7c0ab 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_INITIAL_FIBER_TABLE_SIZE + #define INITIAL_FIBER_TABLE_SIZE YOTTA_CFG_MICROBIT_DAL_INITIAL_FIBER_TABLE_SIZE +#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_HEAP_ALLOCATOR #define MICROBIT_HEAP_ALLOCATOR YOTTA_CFG_MICROBIT_DAL_HEAP_ALLOCATOR #endif diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 412d8604..dda4a67b 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -186,6 +186,10 @@ 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 FiberTable __disable_irq(); if (fiberTable->length == fiberTable->capacity) @@ -367,6 +371,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. @@ -380,28 +415,8 @@ 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()) - { - wait_ms(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; @@ -465,29 +480,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; @@ -503,6 +500,9 @@ 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; } @@ -528,7 +528,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. @@ -557,6 +557,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. @@ -591,7 +595,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. @@ -620,6 +624,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. From 07f07fba17391ce2447519a244fcf6a9f459ddbe Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 7 May 2019 15:38:02 +0100 Subject: [PATCH 10/34] Ensure Fiber stack allocation happens in the context of that Fiber --- source/core/MicroBitFiber.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index dda4a67b..e2f42ccf 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -813,6 +813,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; @@ -825,6 +831,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; } } From b71334ee4e3e9fe0d84dce0b96bb7c2ab0bf2a95 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 7 May 2019 16:58:27 +0100 Subject: [PATCH 11/34] Add user define limit to size of FiberPool --- inc/core/MicroBitConfig.h | 7 +++++++ inc/platform/yotta_cfg_mappings.h | 4 ++++ source/core/MicroBitFiber.cpp | 22 +++++++++++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index fe8be459..c6de3322 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -160,6 +160,13 @@ extern uint32_t __etext; #define MICROBIT_FIBER_USER_DATA 0 #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 diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index dee7c0ab..e19a6b5c 100644 --- a/inc/platform/yotta_cfg_mappings.h +++ b/inc/platform/yotta_cfg_mappings.h @@ -16,6 +16,10 @@ #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 diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index e2f42ccf..951479a0 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -765,15 +765,14 @@ void release_fiber(void *) */ void release_fiber(void) { + int fiberPoolSize = 0; + if (!fiber_scheduler_running()) return; // Remove ourselves form the runqueue. dequeue_fiber(currentFiber); - // Add ourselves to the list of free fibers - queue_fiber(currentFiber, &fiberPool); - // Remove the fiber from the FiberTable. for (int i=0; icapacity; i++) { @@ -784,6 +783,23 @@ void release_fiber(void) } } + // Scan the FiberPool and release memory to the heap if it is full. + for (Fiber *p = fiberPool; p; p = p->next) + fiberPoolSize++; + + while (fiberPoolSize > MICROBIT_FIBER_MAXIMUM_FIBER_POOL_SIZE) + { + // Release Fiber contexts from the head of the FiberPool. + Fiber *p = fiberPool; + fiberPool = p->next; + free((void *)p->stack_bottom); + free(p); + fiberPoolSize--; + } + + // Add ourselves to the list of free fibers + queue_fiber(currentFiber, &fiberPool); + // Find something else to do! schedule(); } From b9816a49ec8f060a4f8992ae77c5d811b8f8d86a Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 7 May 2019 17:22:08 +0100 Subject: [PATCH 12/34] Reintroduce validation test in fiber_sleep() - Replace accidentally deleted validation check in fiber_sleep(). --- source/core/MicroBitFiber.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 951479a0..3e38fd6c 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -415,6 +415,13 @@ static Fiber* handle_fob() */ void fiber_sleep(unsigned long t) { + // If the scheduler is not running, then simply perform a spin wait and exit. + if (!fiber_scheduler_running()) + { + wait_ms(t); + return; + } + // Fork a new fiber if necessary Fiber *f = handle_fob(); From 226bf9cead2faddb3b4f34acd5c46a6847ed9842 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 8 May 2019 10:53:46 +0100 Subject: [PATCH 13/34] Replace FiberTable with a list of all fibers to save memory - Remove FiberTable structure - Remove CONFIG options and mappings relating to table size - Add new list linkage to Fiber structure to maintain list of all Fibers - Replace get_fiber_table() with get_fiber_list() --- inc/core/MicroBitConfig.h | 6 --- inc/core/MicroBitFiber.h | 14 ++---- inc/platform/yotta_cfg_mappings.h | 4 -- source/core/MicroBitFiber.cpp | 80 ++++++++++++------------------- 4 files changed, 35 insertions(+), 69 deletions(-) diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index c6de3322..b9355510 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -149,12 +149,6 @@ extern uint32_t __etext; #define SYSTEM_TICK_PERIOD_MS 6 #endif -// Initial FiberTable size -// Defines the initial size of the Fiber table. This will grow automatially if more Fibers are created. -#ifndef INITIAL_FIBER_TABLE_SIZE -#define INITIAL_FIBER_TABLE_SIZE 4 -#endif - // Enable used_data field in Fiber structure (for thread-local data) #ifndef MICROBIT_FIBER_USER_DATA #define MICROBIT_FIBER_USER_DATA 0 diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index d44a3495..0ed99634 100644 --- a/inc/core/MicroBitFiber.h +++ b/inc/core/MicroBitFiber.h @@ -94,20 +94,14 @@ 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; // Position of this Fiber on the run queue. + Fiber *next; // Position of this Fiber on its queue. + Fiber *fiber_list_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 }; -struct FiberTable -{ - uint16_t length; // The number of live fibers in the table (n.b. the table may be non-contiguous). - uint16_t capacity; // The capacity of the FiberTable (n.b. This is not the same as the number of allocated Fibers). - - Fiber * table[0]; // List of all Fibers in the system. May be non-contiguous. Unused entries are listed as NULL. -}; extern Fiber *currentFiber; @@ -131,9 +125,9 @@ int fiber_scheduler_running(); /** * Provides a list of all active fibers. * - * @return A pointer to a FiberTable structure containing the list of currently active fibers. + * @return A pointer to the head of the list of all active fibers. */ -const FiberTable * get_fiber_table(); +Fiber* get_fiber_list(); /** * Exit point for all fibers. diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index e19a6b5c..7759c794 100644 --- a/inc/platform/yotta_cfg_mappings.h +++ b/inc/platform/yotta_cfg_mappings.h @@ -8,10 +8,6 @@ //DAL mappings -#ifdef YOTTA_CFG_MICROBIT_DAL_INITIAL_FIBER_TABLE_SIZE - #define INITIAL_FIBER_TABLE_SIZE YOTTA_CFG_MICROBIT_DAL_INITIAL_FIBER_TABLE_SIZE -#endif - #ifdef YOTTA_CFG_MICROBIT_DAL_FIBER_USER_DATA #define MICROBIT_FIBER_USER_DATA YOTTA_CFG_MICROBIT_DAL_FIBER_USER_DATA #endif diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 3e38fd6c..a86b4747 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -42,7 +42,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. -static FiberTable *fiberTable = NULL; // Table of all active Fibers. /* * Scheduler state. */ @@ -50,6 +49,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 @@ -147,11 +147,11 @@ void dequeue_fiber(Fiber *f) /** * Provides a list of all active fibers. * - * @return A pointer to a FiberTable structure containing the list of currently active fibers. + * @return A pointer to the head of the list of all active fibers. */ -const FiberTable * get_fiber_table() +Fiber * get_fiber_list() { - return (const FiberTable *) fiberTable; + return fiberList; } /** @@ -190,36 +190,10 @@ Fiber *getFiberContext() f->user_data = 0; #endif - // Add the new Fiber to the FiberTable + // Add the new Fiber to the list of all fibers __disable_irq(); - if (fiberTable->length == fiberTable->capacity) - { - // Create the FiberTable. - FiberTable *newTable = (FiberTable *) realloc(fiberTable, sizeof(FiberTable) + sizeof(Fiber *) * (fiberTable->capacity + INITIAL_FIBER_TABLE_SIZE)); - - if (newTable == NULL) - { - free(f); - __enable_irq(); - return NULL; - } - - memclr(&newTable->table[newTable->capacity], sizeof(Fiber *) * INITIAL_FIBER_TABLE_SIZE); - newTable->capacity += INITIAL_FIBER_TABLE_SIZE; - - fiberTable = newTable; - } - - // Fill from the front - for (int i=0; icapacity; i++) - { - if (fiberTable->table[i] == NULL) - { - fiberTable->table[i] = f; - fiberTable->length++; - break; - } - } + f->fiber_list_next = fiberList; + fiberList = f; __enable_irq(); return f; @@ -244,12 +218,6 @@ void scheduler_init(EventModel &_messageBus) // This parameter will be NULL if we're being run without a message bus. messageBus = &_messageBus; - // Create the FiberTable. - fiberTable = (FiberTable *) malloc(sizeof(FiberTable) + sizeof(Fiber *) * INITIAL_FIBER_TABLE_SIZE); - memclr(&fiberTable->table[0], sizeof(Fiber *) * INITIAL_FIBER_TABLE_SIZE); - fiberTable->capacity = INITIAL_FIBER_TABLE_SIZE; - fiberTable->length = 0; - // Create a new fiber context currentFiber = getFiberContext(); @@ -780,16 +748,6 @@ void release_fiber(void) // Remove ourselves form the runqueue. dequeue_fiber(currentFiber); - // Remove the fiber from the FiberTable. - for (int i=0; icapacity; i++) - { - if (fiberTable->table[i] == currentFiber) - { - fiberTable->table[i] = NULL; - fiberTable->length--; - } - } - // Scan the FiberPool and release memory to the heap if it is full. for (Fiber *p = fiberPool; p; p = p->next) fiberPoolSize++; @@ -807,6 +765,30 @@ void release_fiber(void) // 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->fiber_list_next; + } + else + { + Fiber *p = fiberList; + + while (p) + { + if (p->fiber_list_next == currentFiber) + { + p->fiber_list_next = currentFiber->fiber_list_next; + break; + } + + p = p->fiber_list_next; + } + } + __enable_irq(); + + // Find something else to do! schedule(); } From 92c6cc64b435bfad10370ab78099e9360972db32 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 8 May 2019 11:19:10 +0100 Subject: [PATCH 14/34] Allow overriding of the heap allocator and getting heap sizes Also port from codal-core in support of PXT GC. Also, fix compiler optimization bug in calloc(). --- inc/core/MicroBitHeapAllocator.h | 36 ++++++++++++++++++++++++++- source/core/MicroBitHeapAllocator.cpp | 31 ++++++++++++++++++----- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/inc/core/MicroBitHeapAllocator.h b/inc/core/MicroBitHeapAllocator.h index 10aa5c94..31832fcd 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(); -#endif + +/** + * 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 \ No newline at end of file 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 From 741335200d292161a69aee86c1ed92f5dd59816a Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Thu, 27 Sep 2018 17:51:17 +0100 Subject: [PATCH 15/34] Introduce optional javascript semantics for event handling - new CONFIG option now allows the user to choose between ordered, serial processing of event handlers and parallel execution of events (JS semantics) OR parallel processing of event handlers and serial execution of events (legacy mciro:bit semantics) - Introduciton of a MicroBitLock primitive for mutual exclusion --- inc/core/MicroBitConfig.h | 15 +++++ inc/core/MicroBitListener.h | 4 +- inc/core/MicroBitLock.h | 66 ++++++++++++++++++++++ inc/drivers/MicroBitMessageBus.h | 4 +- inc/platform/yotta_cfg_mappings.h | 4 ++ source/core/MicroBitFiber.cpp | 81 +++++++++++++++++++++++++++ source/drivers/MicroBitMessageBus.cpp | 29 +++++++++- 7 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 inc/core/MicroBitLock.h diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index b9355510..db917566 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -183,6 +183,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/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/drivers/MicroBitMessageBus.h b/inc/drivers/MicroBitMessageBus.h index 4925a884..ed5c2794 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. * @@ -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. /** diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index 7759c794..6864689a 100644 --- a/inc/platform/yotta_cfg_mappings.h +++ b/inc/platform/yotta_cfg_mappings.h @@ -192,4 +192,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 a86b4747..8c37e480 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -1027,3 +1027,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/drivers/MicroBitMessageBus.cpp b/source/drivers/MicroBitMessageBus.cpp index c2ad221e..0a55e80d 100644 --- a/source/drivers/MicroBitMessageBus.cpp +++ b/source/drivers/MicroBitMessageBus.cpp @@ -95,14 +95,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 +143,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 +270,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 +294,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; @@ -365,10 +386,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 { From c9c98beffe3601a24b031d90cf931b3440a15f74 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 19:39:25 +0000 Subject: [PATCH 16/34] Update string print() method to avoid unecessary delays - First character of string is now printed on next frame update, rather than waiting for an inter-character gap - No unecessary delay after the final character is printed. These changes make the print() semantics align with the image animate() semantics. --- source/drivers/MicroBitDisplay.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index d0d7b844..72ab2a90 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; @@ -492,7 +491,7 @@ int MicroBitDisplay::printCharAsync(char c, int delay) if (delay > 0) { animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_CHARACTER; } } @@ -533,7 +532,7 @@ int MicroBitDisplay::printAsync(ManagedString s, int delay) printingChar = 0; printingText = s; animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_TEXT; } @@ -576,7 +575,7 @@ int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int de if(delay > 0) { animationDelay = delay; - animationTick = 0; + animationTick = delay-1; animationMode = ANIMATION_MODE_PRINT_CHARACTER; } } From b28dba41dfa966576d94c3e302d8cf4e190bf888 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 21:59:41 +0000 Subject: [PATCH 17/34] MicroBitMessageBus::process() updated to pass events by value Updated parameters of this internal method to pass the event being processed by value, rather than by reference. This is important for applications using the MESSAGE_BUS_CONCURRENT_EVENTS concurrency mode (JavaScript semantics) to ensure that any multiple event handlers registered for the same event maintain a persistent copy of that event on the relevant fiber stack. --- inc/drivers/MicroBitMessageBus.h | 2 +- source/drivers/MicroBitMessageBus.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/drivers/MicroBitMessageBus.h b/inc/drivers/MicroBitMessageBus.h index ed5c2794..8517b7cb 100644 --- a/inc/drivers/MicroBitMessageBus.h +++ b/inc/drivers/MicroBitMessageBus.h @@ -108,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. diff --git a/source/drivers/MicroBitMessageBus.cpp b/source/drivers/MicroBitMessageBus.cpp index 0a55e80d..f0bf46c4 100644 --- a/source/drivers/MicroBitMessageBus.cpp +++ b/source/drivers/MicroBitMessageBus.cpp @@ -358,7 +358,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; From 77d679c87f3550ab01f7ee3f5de3c295d8161ab1 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 27 Feb 2019 22:04:51 +0000 Subject: [PATCH 18/34] Ensure MicroBitDisplay::waitForFreeDisplay() is free of race conditions Update to the internal MicroBitDisplay::waitForFreeDisplay() method to ensure that fibers queued waiting for access to the display always re-contest for access, thereby avoiding potnetial race conditions. --- source/drivers/MicroBitDisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index 72ab2a90..d4b7c939 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -447,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); } From 8075eab34969ae3983246e2c993e74a50f0fe243 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 24 Mar 2020 15:21:28 +0000 Subject: [PATCH 19/34] .gitignore vscode metadata --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e57cb441..fba875e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ build .yotta.json +.vscode yotta_modules yotta_targets *.swp From 25e1e10d637509225b9ae3e4c24c66bede203ebf Mon Sep 17 00:00:00 2001 From: Michal Moskal Date: Sun, 20 Jan 2019 17:58:10 +0000 Subject: [PATCH 20/34] Add Fiber->user_data and list_fiber() This is port of relevant codal changes in support of GC in the PXT runtime. Also, limit the number of fibers in pool to 3. --- inc/core/MicroBitFiber.h | 13 +++ source/core/MicroBitFiber.cpp | 144 +++++++++++++++++++++++++--------- 2 files changed, 122 insertions(+), 35 deletions(-) diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index 2e042609..f01519aa 100644 --- a/inc/core/MicroBitFiber.h +++ b/inc/core/MicroBitFiber.h @@ -89,6 +89,9 @@ struct Fiber 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. +#if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) + void *user_data; +#endif }; extern Fiber *currentFiber; @@ -364,6 +367,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/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 484e621b..7c47f0d3 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -65,6 +65,43 @@ static EventModel *messageBus = NULL; // Array of components which are iterated during idle thread execution. static MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS]; +static void get_fibers_from(Fiber ***dest, int *sum, Fiber *queue) +{ + if (queue && queue->prev) target_panic(30); + while (queue) { + if (*dest) + *(*dest)++ = queue; + (*sum)++; + queue = queue->next; + } +} + +/** + * 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) +{ + int sum = 0; + + // interrupts might move fibers between queues, but should not create new ones + __disable_irq(); + get_fibers_from(&dest, &sum, runQueue); + get_fibers_from(&dest, &sum, sleepQueue); + get_fibers_from(&dest, &sum, waitQueue); + __enable_irq(); + + // idleFiber is used to start event handlers using invoke(), + // so it may in fact have the user_data set if in FOB context + if (dest) + *dest++ = idleFiber; + sum++; + return sum; +} + /** * Utility function to add the currenty running fiber to the given queue. * @@ -171,6 +208,10 @@ 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 + return f; } @@ -314,6 +355,29 @@ void scheduler_event(MicroBitEvent evt) messageBus->ignore(evt.source, evt.value, scheduler_event); } +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 +391,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 +398,7 @@ 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; - } + Fiber *f = handle_fob(); // Calculate and store the time we want to wake up. f->context = system_timer_current_time() + t; @@ -412,28 +462,17 @@ 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(); + Fiber *f = handle_fob(); - // 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(); - } + // in case we created a new fiber, make sure to initialize its context + // in case schedule() isn't called immedietly afterwards + if (f != currentFiber) { + dequeue_fiber(f); + queue_fiber(f, &runQueue); + schedule(); } // Encode the event data in the context field. It's handy having a 32 bit core. :-) @@ -453,6 +492,12 @@ int fiber_wake_on_event(uint16_t id, uint16_t value) 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 +520,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 +549,9 @@ 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) + f->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 +586,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 +615,9 @@ 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) + f->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. @@ -710,6 +761,21 @@ void release_fiber(void) // Remove ourselves form the runqueue. dequeue_fiber(currentFiber); + // limit the number of fibers in the pool + int numFree = 0; + for (Fiber *p = fiberPool; p; p = p->next) { + if (!p->next && numFree > 3) { + p->prev->next = NULL; + free(p->tcb); + free((void *)p->stack_bottom); + memset(p, 0, sizeof(*p)); + free(p); + break; + } + numFree++; + } + + // Add ourselves to the list of free fibers queue_fiber(currentFiber, &fiberPool); @@ -742,6 +808,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 +826,8 @@ 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; + + currentFiber = prevCurrFiber; } } From 92d0ceebc714b0b2153a70ac89daa765d2df6754 Mon Sep 17 00:00:00 2001 From: Michal Moskal Date: Sun, 20 Jan 2019 17:59:10 +0000 Subject: [PATCH 21/34] Allow overriding of the heap allocator and getting heap sizes Also port from codal-core in support of PXT GC. Also, fix compiler optimization bug in calloc(). --- inc/core/MicroBitHeapAllocator.h | 34 +++++++++++++++++++++++++++ source/core/MicroBitHeapAllocator.cpp | 29 +++++++++++++++++++---- 2 files changed, 58 insertions(+), 5 deletions(-) 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/source/core/MicroBitHeapAllocator.cpp b/source/core/MicroBitHeapAllocator.cpp index c42b3daa..278dfab1 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) { From 415e5ebed28cb0f8ca32feb48a03e1beee4378d6 Mon Sep 17 00:00:00 2001 From: Michal Moskal Date: Sun, 20 Jan 2019 18:03:47 +0000 Subject: [PATCH 22/34] Compilation fixes --- source/core/MicroBitFiber.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 7c47f0d3..b3630a97 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. @@ -67,7 +69,8 @@ static MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS]; static void get_fibers_from(Fiber ***dest, int *sum, Fiber *queue) { - if (queue && queue->prev) target_panic(30); + if (queue && queue->prev) + microbit_panic(MICROBIT_HEAP_ERROR); while (queue) { if (*dest) *(*dest)++ = queue; @@ -232,7 +235,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(); @@ -438,7 +441,7 @@ int fiber_wait_for_event(uint16_t id, uint16_t value) if(ret == MICROBIT_OK) schedule(); - return ret; + return ret; } /** @@ -766,7 +769,6 @@ void release_fiber(void) for (Fiber *p = fiberPool; p; p = p->next) { if (!p->next && numFree > 3) { p->prev->next = NULL; - free(p->tcb); free((void *)p->stack_bottom); memset(p, 0, sizeof(*p)); free(p); From 15ebed2ad575a1682a985dead48cb2b8e89901d0 Mon Sep 17 00:00:00 2001 From: Michal Moskal Date: Sun, 20 Jan 2019 18:29:29 +0000 Subject: [PATCH 23/34] Add fiber_user_data to yotta mappings; fix some errors --- inc/core/MicroBitConfig.h | 5 +++++ inc/platform/yotta_cfg_mappings.h | 4 ++++ source/core/MicroBitFiber.cpp | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index 9f5e3855..ba2ff7e9 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -149,6 +149,11 @@ 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 + // // Message Bus: // Default behaviour for event handlers, if not specified in the listen() call diff --git a/inc/platform/yotta_cfg_mappings.h b/inc/platform/yotta_cfg_mappings.h index 08ef57fc..fbf565b5 100644 --- a/inc/platform/yotta_cfg_mappings.h +++ b/inc/platform/yotta_cfg_mappings.h @@ -15,6 +15,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 diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index b3630a97..5e3068c2 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -553,7 +553,7 @@ int invoke(void (*entry_fn)(void)) currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; entry_fn(); #if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) - f->user_data = 0; + currentFiber->user_data = 0; #endif currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; @@ -619,7 +619,7 @@ int invoke(void (*entry_fn)(void *), void *param) currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; entry_fn(param); #if CONFIG_ENABLED(MICROBIT_FIBER_USER_DATA) - f->user_data = 0; + currentFiber->user_data = 0; #endif currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; From d823d4b4a6ee6c3d52bb630713cd3eba3c87d2b0 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 25 Mar 2020 15:36:27 +0000 Subject: [PATCH 24/34] Rename list pointers in MicroBitFiber.h - Rename pointers in Fiber struct to be more intuitive. --- inc/core/MicroBitFiber.h | 4 ++-- source/core/MicroBitFiber.cpp | 40 +++++++++++++++++------------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/inc/core/MicroBitFiber.h b/inc/core/MicroBitFiber.h index 0ed99634..fd93b2ed 100644 --- a/inc/core/MicroBitFiber.h +++ b/inc/core/MicroBitFiber.h @@ -94,8 +94,8 @@ 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; // Position of this Fiber on its queue. - Fiber *fiber_list_next; // Position of this Fiber in the global list of fibers. + 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. diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index 8c37e480..da1cda28 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -88,7 +88,7 @@ void queue_fiber(Fiber *f, Fiber **queue) // list, it results in fairer scheduling. if (*queue == NULL) { - f->next = NULL; + f->qnext = NULL; *queue = f; } else @@ -97,11 +97,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->next = NULL; + last->qnext = f; + f->qnext = NULL; } __enable_irq(); @@ -123,22 +123,22 @@ void dequeue_fiber(Fiber *f) if (*(f->queue) == f) { // Remove the fiber from the head of the queue - *(f->queue) = f->next; + *(f->queue) = f->qnext; } else { Fiber *prev = *(f->queue); // Scan for the given fiber in its queue - while(prev->next != f) - prev = prev->next; + while(prev->qnext != f) + prev = prev->qnext; // Remove the fiber - prev->next = f->next; + prev->qnext = f->qnext; } // Ensure old linkage is cleared - f->next = NULL; + f->qnext = NULL; f->queue = NULL; __enable_irq(); @@ -192,7 +192,7 @@ Fiber *getFiberContext() // Add the new Fiber to the list of all fibers __disable_irq(); - f->fiber_list_next = fiberList; + f->next = fiberList; fiberList = f; __enable_irq(); @@ -269,7 +269,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) { @@ -305,7 +305,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; @@ -749,14 +749,14 @@ void release_fiber(void) dequeue_fiber(currentFiber); // Scan the FiberPool and release memory to the heap if it is full. - for (Fiber *p = fiberPool; p; p = p->next) + 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->next; + fiberPool = p->qnext; free((void *)p->stack_bottom); free(p); fiberPoolSize--; @@ -769,7 +769,7 @@ void release_fiber(void) __disable_irq(); if (fiberList == currentFiber) { - fiberList = fiberList->fiber_list_next; + fiberList = fiberList->next; } else { @@ -777,13 +777,13 @@ void release_fiber(void) while (p) { - if (p->fiber_list_next == currentFiber) + if (p->next == currentFiber) { - p->fiber_list_next = currentFiber->fiber_list_next; + p->next = currentFiber->next; break; } - p = p->fiber_list_next; + p = p->next; } } __enable_irq(); @@ -902,7 +902,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. From 219feb85110d98112d5f0a1fceba812ba512bc86 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 25 Mar 2020 16:28:20 +0000 Subject: [PATCH 25/34] Remove list_fibers API - Remove redundant API - superceded by get_fiber_list() --- source/core/MicroBitFiber.cpp | 38 ----------------------------------- 1 file changed, 38 deletions(-) diff --git a/source/core/MicroBitFiber.cpp b/source/core/MicroBitFiber.cpp index cbcc7ff3..cf514e4e 100644 --- a/source/core/MicroBitFiber.cpp +++ b/source/core/MicroBitFiber.cpp @@ -67,44 +67,6 @@ static EventModel *messageBus = NULL; // Array of components which are iterated during idle thread execution. static MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS]; -static void get_fibers_from(Fiber ***dest, int *sum, Fiber *queue) -{ - if (queue && queue->prev) - microbit_panic(MICROBIT_HEAP_ERROR); - while (queue) { - if (*dest) - *(*dest)++ = queue; - (*sum)++; - queue = queue->next; - } -} - -/** - * 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) -{ - int sum = 0; - - // interrupts might move fibers between queues, but should not create new ones - __disable_irq(); - get_fibers_from(&dest, &sum, runQueue); - get_fibers_from(&dest, &sum, sleepQueue); - get_fibers_from(&dest, &sum, waitQueue); - __enable_irq(); - - // idleFiber is used to start event handlers using invoke(), - // so it may in fact have the user_data set if in FOB context - if (dest) - *dest++ = idleFiber; - sum++; - return sum; -} - /** * Utility function to add the currenty running fiber to the given queue. * From 3ed3674fe17f8cf9833bd2051532697ad552e54b Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 25 Mar 2020 16:45:36 +0000 Subject: [PATCH 26/34] Add CONFIG flag to indicate use of get_fiber_list() API --- inc/core/MicroBitConfig.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/core/MicroBitConfig.h b/inc/core/MicroBitConfig.h index db917566..76bc45a6 100644 --- a/inc/core/MicroBitConfig.h +++ b/inc/core/MicroBitConfig.h @@ -154,6 +154,11 @@ extern uint32_t __etext; #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. From 76538ef6cb392f94d4402bdf26f5a0e581246520 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 25 Mar 2020 17:06:50 +0000 Subject: [PATCH 27/34] Re-enumerate component ID values to align with CODAL - Update component ID values to align with those used in CODAL - Just enables some code cleanup within MakeCode. --- inc/core/MicroBitComponent.h | 79 +++++++++++++++++------------------- 1 file changed, 38 insertions(+), 41 deletions(-) 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 From f96a39036664495c7f934d1688c744f7d51b025b Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 1 Apr 2020 14:54:51 +0100 Subject: [PATCH 28/34] Use schedule() not fiber_sleep(0) in MicroBitSerial.cpp (Fix #461) - MicroBitSerial use wake_on_event for condition synchronisation. - By contract, a fiber doing this should subsequently call schedule() and no other blocking operation --- source/drivers/MicroBitSerial.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); } /** From dd339a7f4c727172b51d1f0ac0d4fe6627fdb056 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Wed, 1 Apr 2020 16:58:07 +0100 Subject: [PATCH 29/34] Honour delay parameter in MicroBitDisplay::print(MicroBitImage) - Fix bug https://github.com/microsoft/pxt-microbit/issues/2739 --- source/drivers/MicroBitDisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index d4b7c939..4fc6188e 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -575,7 +575,7 @@ int MicroBitDisplay::printAsync(MicroBitImage i, int x, int y, int alpha, int de if(delay > 0) { animationDelay = delay; - animationTick = delay-1; + animationTick = 0; animationMode = ANIMATION_MODE_PRINT_CHARACTER; } } From 0fd4a8018c8643279021a49692a413553cc94666 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 21 Apr 2020 16:35:27 +0100 Subject: [PATCH 30/34] Correctly initialise animation timer for printChar() Fix #463 --- source/drivers/MicroBitDisplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index 4fc6188e..2a78a0a6 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -491,7 +491,7 @@ int MicroBitDisplay::printCharAsync(char c, int delay) if (delay > 0) { animationDelay = delay; - animationTick = delay-1; + animationTick = 0; animationMode = ANIMATION_MODE_PRINT_CHARACTER; } } From 3bccdd6c8ee1b63f381d1b26080d68b58b9af1aa Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Tue, 21 Apr 2020 19:03:31 +0100 Subject: [PATCH 31/34] Ensure consistent behaviour of MicroBitDisplay::print() operations - Simplify behaviour of print() and printAsync() methods. - print(char) print(ManagedString) and print(MicroBitImage) now all have same semantics: - If provided, the delay parameter is always respected. Screen is cleared after that time. - discrete print operations (character, string of length 1 and MicroBitImage) with no delay parameter are printed, and return immeditely. The screen is not cleared. - print (string of length > 1) with no delay parameter defaults to a delay of MICROBIT_DEFAULT_PRINT_SPEED milliseconds. The screen is cleared on completion. --- inc/drivers/MicroBitDisplay.h | 44 +++++++++++++++-- source/drivers/MicroBitDisplay.cpp | 78 +++++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 16 deletions(-) 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/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index 2a78a0a6..f4f919de 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -303,6 +303,7 @@ MicroBitDisplay::animationUpdate() if(animationMode == ANIMATION_MODE_PRINT_CHARACTER) { + image.print(' '); animationMode = ANIMATION_MODE_NONE; this->sendAnimationCompleteEvent(); } @@ -520,13 +521,13 @@ 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; @@ -544,6 +545,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. @@ -646,7 +674,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. @@ -656,15 +684,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 { @@ -674,6 +698,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. From 673228f89a3fbe86eedd48b8d64eaea4bc2a937c Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Fri, 24 Apr 2020 21:33:17 +0100 Subject: [PATCH 32/34] Align print() semantics with previous versions - fix regression in semantics - all print() operation of unit length now leave the screen uncleared, even when a duration is provided. --- source/drivers/MicroBitDisplay.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/source/drivers/MicroBitDisplay.cpp b/source/drivers/MicroBitDisplay.cpp index f4f919de..cc2bc916 100644 --- a/source/drivers/MicroBitDisplay.cpp +++ b/source/drivers/MicroBitDisplay.cpp @@ -303,7 +303,6 @@ MicroBitDisplay::animationUpdate() if(animationMode == ANIMATION_MODE_PRINT_CHARACTER) { - image.print(' '); animationMode = ANIMATION_MODE_NONE; this->sendAnimationCompleteEvent(); } From 2443bebacee0eee8d57d032a3d9617c87f50232e Mon Sep 17 00:00:00 2001 From: Michal Moskal Date: Mon, 8 Jun 2020 10:33:03 -0700 Subject: [PATCH 33/34] Align MICROBIT_PIN_EVENT_ON_* with codal --- inc/drivers/MicroBitPin.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From e5b67417d4dc6b01bc369d89bb1525fc7a9575c9 Mon Sep 17 00:00:00 2001 From: Joe Finney Date: Fri, 31 Jul 2020 16:39:14 +0100 Subject: [PATCH 34/34] Align allocateNotifyEvent() API with codal --- inc/core/NotifyEvents.h | 3 +++ inc/drivers/MicroBitMessageBus.h | 5 +++++ source/drivers/MicroBitMessageBus.cpp | 11 +++++++++++ 3 files changed, 19 insertions(+) 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/MicroBitMessageBus.h b/inc/drivers/MicroBitMessageBus.h index 8517b7cb..d20965da 100644 --- a/inc/drivers/MicroBitMessageBus.h +++ b/inc/drivers/MicroBitMessageBus.h @@ -180,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/source/drivers/MicroBitMessageBus.cpp b/source/drivers/MicroBitMessageBus.cpp index f0bf46c4..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. @@ -567,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. */