Skip to content

Commit

Permalink
Merge pull request #15 from Amerlander/merge-upstream
Browse files Browse the repository at this point in the history
Merge upstream
  • Loading branch information
Amerlander authored Sep 28, 2019
2 parents 2febdb4 + 3ebd9e4 commit a392cf1
Show file tree
Hide file tree
Showing 6 changed files with 193 additions and 42 deletions.
5 changes: 5 additions & 0 deletions inc/core/MicroBitConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions inc/core/MicroBitFiber.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
34 changes: 34 additions & 0 deletions inc/core/MicroBitHeapAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions inc/platform/yotta_cfg_mappings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
150 changes: 113 additions & 37 deletions source/core/MicroBitFiber.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -65,6 +67,44 @@ 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.
*
Expand Down Expand Up @@ -171,6 +211,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;
}

Expand All @@ -191,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();
Expand Down Expand Up @@ -314,6 +358,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.
Expand All @@ -327,28 +394,14 @@ 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;
}
Fiber *f = handle_fob();

// Calculate and store the time we want to wake up.
f->context = system_timer_current_time() + t;
Expand Down Expand Up @@ -388,7 +441,7 @@ int fiber_wait_for_event(uint16_t id, uint16_t value)
if(ret == MICROBIT_OK)
schedule();

return ret;
return ret;
}

/**
Expand All @@ -412,28 +465,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. :-)
Expand All @@ -453,6 +495,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.
*
Expand All @@ -475,7 +523,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.
Expand Down Expand Up @@ -504,6 +552,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)
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.
Expand Down Expand Up @@ -538,7 +589,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.
Expand Down Expand Up @@ -567,6 +618,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)
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.
Expand Down Expand Up @@ -710,6 +764,20 @@ 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((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);

Expand Down Expand Up @@ -742,6 +810,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;

Expand All @@ -754,6 +828,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;
}
}

Expand Down
Loading

0 comments on commit a392cf1

Please sign in to comment.