Attempting to publish a tree
Dependencies: BLE_API mbed-dev-bin nRF51822
Fork of microbit-dal by
MicroBitFiber.cpp
00001 /* 00002 The MIT License (MIT) 00003 00004 Copyright (c) 2016 British Broadcasting Corporation. 00005 This software is provided by Lancaster University by arrangement with the BBC. 00006 00007 Permission is hereby granted, free of charge, to any person obtaining a 00008 copy of this software and associated documentation files (the "Software"), 00009 to deal in the Software without restriction, including without limitation 00010 the rights to use, copy, modify, merge, publish, distribute, sublicense, 00011 and/or sell copies of the Software, and to permit persons to whom the 00012 Software is furnished to do so, subject to the following conditions: 00013 00014 The above copyright notice and this permission notice shall be included in 00015 all copies or substantial portions of the Software. 00016 00017 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 00018 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 00019 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 00020 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 00021 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 00022 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 00023 DEALINGS IN THE SOFTWARE. 00024 */ 00025 00026 /** 00027 * Functionality definitions for the MicroBit Fiber scheduler. 00028 * 00029 * This lightweight, non-preemptive scheduler provides a simple threading mechanism for two main purposes: 00030 * 00031 * 1) To provide a clean abstraction for application languages to use when building async behaviour (callbacks). 00032 * 2) To provide ISR decoupling for EventModel events generated in an ISR context. 00033 */ 00034 #include "MicroBitConfig.h" 00035 #include "MicroBitFiber.h" 00036 #include "MicroBitSystemTimer.h" 00037 00038 /* 00039 * Statically allocated values used to create and destroy Fibers. 00040 * required to be defined here to allow persistence during context switches. 00041 */ 00042 Fiber *currentFiber = NULL; // The context in which the current fiber is executing. 00043 static Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing. 00044 static Fiber *idleFiber = NULL; // the idle task - performs a power efficient sleep, and system maintenance tasks. 00045 00046 /* 00047 * Scheduler state. 00048 */ 00049 static Fiber *runQueue = NULL; // The list of runnable fibers. 00050 static Fiber *sleepQueue = NULL; // The list of blocked fibers waiting on a fiber_sleep() operation. 00051 static Fiber *waitQueue = NULL; // The list of blocked fibers waiting on an event. 00052 static Fiber *fiberPool = NULL; // Pool of unused fibers, just waiting for a job to do. 00053 00054 /* 00055 * Scheduler wide flags 00056 */ 00057 static uint8_t fiber_flags = 0; 00058 00059 00060 /* 00061 * Fibers may perform wait/notify semantics on events. If set, these operations will be permitted on this EventModel. 00062 */ 00063 static EventModel *messageBus = NULL; 00064 00065 // Array of components which are iterated during idle thread execution, isIdleCallbackNeeded is polled during a systemTick. 00066 static MicroBitComponent* idleThreadComponents[MICROBIT_IDLE_COMPONENTS]; 00067 00068 /** 00069 * Utility function to add the currenty running fiber to the given queue. 00070 * 00071 * Perform a simple add at the head, to avoid complexity, 00072 * 00073 * Queues are normally very short, so maintaining a doubly linked, sorted list typically outweighs the cost of 00074 * brute force searching. 00075 * 00076 * @param f The fiber to add to the queue 00077 * 00078 * @param queue The run queue to add the fiber to. 00079 */ 00080 void queue_fiber(Fiber *f, Fiber **queue) 00081 { 00082 __disable_irq(); 00083 00084 // Record which queue this fiber is on. 00085 f->queue = queue; 00086 00087 // Add the fiber to the tail of the queue. Although this involves scanning the 00088 // list, it results in fairer scheduling. 00089 if (*queue == NULL) 00090 { 00091 f->next = NULL; 00092 f->prev = NULL; 00093 *queue = f; 00094 } 00095 else 00096 { 00097 // Scan to the end of the queue. 00098 // We don't maintain a tail pointer to save RAM (queues are nrmally very short). 00099 Fiber *last = *queue; 00100 00101 while (last->next != NULL) 00102 last = last->next; 00103 00104 last->next = f; 00105 f->prev = last; 00106 f->next = NULL; 00107 } 00108 00109 __enable_irq(); 00110 } 00111 00112 /** 00113 * Utility function to the given fiber from whichever queue it is currently stored on. 00114 * 00115 * @param f the fiber to remove. 00116 */ 00117 void dequeue_fiber(Fiber *f) 00118 { 00119 // If this fiber is already dequeued, nothing the there's nothing to do. 00120 if (f->queue == NULL) 00121 return; 00122 00123 // Remove this fiber fromm whichever queue it is on. 00124 __disable_irq(); 00125 00126 if (f->prev != NULL) 00127 f->prev->next = f->next; 00128 else 00129 *(f->queue) = f->next; 00130 00131 if(f->next) 00132 f->next->prev = f->prev; 00133 00134 f->next = NULL; 00135 f->prev = NULL; 00136 f->queue = NULL; 00137 00138 __enable_irq(); 00139 00140 } 00141 00142 /** 00143 * Allocates a fiber from the fiber pool if availiable. Otherwise, allocates a new one from the heap. 00144 */ 00145 Fiber *getFiberContext() 00146 { 00147 Fiber *f; 00148 00149 __disable_irq(); 00150 00151 if (fiberPool != NULL) 00152 { 00153 f = fiberPool; 00154 dequeue_fiber(f); 00155 // dequeue_fiber() exits with irqs enabled, so no need to do this again! 00156 } 00157 else 00158 { 00159 __enable_irq(); 00160 00161 f = new Fiber(); 00162 00163 if (f == NULL) 00164 return NULL; 00165 00166 f->stack_bottom = 0; 00167 f->stack_top = 0; 00168 } 00169 00170 // Ensure this fiber is in suitable state for reuse. 00171 f->flags = 0; 00172 f->tcb.stack_base = CORTEX_M0_STACK_BASE; 00173 00174 return f; 00175 } 00176 00177 00178 /** 00179 * Initialises the Fiber scheduler. 00180 * Creates a Fiber context around the calling thread, and adds it to the run queue as the current thread. 00181 * 00182 * This function must be called once only from the main thread, and before any other Fiber operation. 00183 * 00184 * @param _messageBus An event model, used to direct the priorities of the scheduler. 00185 */ 00186 void scheduler_init(EventModel &_messageBus) 00187 { 00188 // If we're already initialised, then nothing to do. 00189 if (fiber_scheduler_running()) 00190 return; 00191 00192 // Store a reference to the messageBus provided. 00193 // This parameter will be NULL if we're being run without a message bus. 00194 messageBus = &_messageBus; 00195 00196 // Create a new fiber context 00197 currentFiber = getFiberContext(); 00198 00199 // Add ourselves to the run queue. 00200 queue_fiber(currentFiber, &runQueue); 00201 00202 // Create the IDLE fiber. 00203 // Configure the fiber to directly enter the idle task. 00204 idleFiber = getFiberContext(); 00205 idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; 00206 idleFiber->tcb.LR = (uint32_t) &idle_task; 00207 00208 if (messageBus) 00209 { 00210 // Register to receive events in the NOTIFY channel - this is used to implement wait-notify semantics 00211 messageBus->listen(MICROBIT_ID_NOTIFY, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); 00212 messageBus->listen(MICROBIT_ID_NOTIFY_ONE, MICROBIT_EVT_ANY, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); 00213 } 00214 00215 // register a period callback to drive the scheduler and any other registered components. 00216 new MicroBitSystemTimerCallback(scheduler_tick); 00217 00218 fiber_flags |= MICROBIT_SCHEDULER_RUNNING; 00219 } 00220 00221 /** 00222 * Determines if the fiber scheduler is operational. 00223 * 00224 * @return 1 if the fber scheduler is running, 0 otherwise. 00225 */ 00226 int fiber_scheduler_running() 00227 { 00228 if (fiber_flags & MICROBIT_SCHEDULER_RUNNING) 00229 return 1; 00230 00231 return 0; 00232 } 00233 00234 /** 00235 * The timer callback, called from interrupt context once every SYSTEM_TICK_PERIOD_MS milliseconds. 00236 * This function checks to determine if any fibers blocked on the sleep queue need to be woken up 00237 * and made runnable. 00238 */ 00239 void scheduler_tick() 00240 { 00241 Fiber *f = sleepQueue; 00242 Fiber *t; 00243 00244 // Check the sleep queue, and wake up any fibers as necessary. 00245 while (f != NULL) 00246 { 00247 t = f->next; 00248 00249 if (system_timer_current_time() >= f->context) 00250 { 00251 // Wakey wakey! 00252 dequeue_fiber(f); 00253 queue_fiber(f,&runQueue); 00254 } 00255 00256 f = t; 00257 } 00258 } 00259 00260 /** 00261 * Event callback. Called from an instance of MicroBitMessageBus whenever an event is raised. 00262 * 00263 * This function checks to determine if any fibers blocked on the wait queue need to be woken up 00264 * and made runnable due to the event. 00265 * 00266 * @param evt the event that has just been raised on an instance of MicroBitMessageBus. 00267 */ 00268 void scheduler_event(MicroBitEvent evt) 00269 { 00270 Fiber *f = waitQueue; 00271 Fiber *t; 00272 int notifyOneComplete = 0; 00273 00274 // This should never happen. 00275 // It is however, safe to simply ignore any events provided, as if no messageBus if recorded, 00276 // no fibers are permitted to block on events. 00277 if (messageBus == NULL) 00278 return; 00279 00280 // Check the wait queue, and wake up any fibers as necessary. 00281 while (f != NULL) 00282 { 00283 t = f->next; 00284 00285 // extract the event data this fiber is blocked on. 00286 uint16_t id = f->context & 0xFFFF; 00287 uint16_t value = (f->context & 0xFFFF0000) >> 16; 00288 00289 // Special case for the NOTIFY_ONE channel... 00290 if ((evt.source == MICROBIT_ID_NOTIFY_ONE && id == MICROBIT_ID_NOTIFY) && (value == MICROBIT_EVT_ANY || value == evt.value)) 00291 { 00292 if (!notifyOneComplete) 00293 { 00294 // Wakey wakey! 00295 dequeue_fiber(f); 00296 queue_fiber(f,&runQueue); 00297 notifyOneComplete = 1; 00298 } 00299 } 00300 00301 // Normal case. 00302 else if ((id == MICROBIT_ID_ANY || id == evt.source) && (value == MICROBIT_EVT_ANY || value == evt.value)) 00303 { 00304 // Wakey wakey! 00305 dequeue_fiber(f); 00306 queue_fiber(f,&runQueue); 00307 } 00308 00309 f = t; 00310 } 00311 00312 // Unregister this event, as we've woken up all the fibers with this match. 00313 if (evt.source != MICROBIT_ID_NOTIFY && evt.source != MICROBIT_ID_NOTIFY_ONE) 00314 messageBus->ignore(evt.source, evt.value, scheduler_event); 00315 } 00316 00317 00318 /** 00319 * Blocks the calling thread for the given period of time. 00320 * The calling thread will be immediateley descheduled, and placed onto a 00321 * wait queue until the requested amount of time has elapsed. 00322 * 00323 * @param t The period of time to sleep, in milliseconds. 00324 * 00325 * @note the fiber will not be be made runnable until after the elapsed time, but there 00326 * are no guarantees precisely when the fiber will next be scheduled. 00327 */ 00328 void fiber_sleep(unsigned long t) 00329 { 00330 Fiber *f = currentFiber; 00331 00332 // If the scheduler is not running, then simply perform a spin wait and exit. 00333 if (!fiber_scheduler_running()) 00334 { 00335 wait_ms(t); 00336 return; 00337 } 00338 00339 // Sleep is a blocking call, so if we're in a fork on block context, 00340 // it's time to spawn a new fiber... 00341 if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) 00342 { 00343 // Allocate a new fiber. This will come from the fiber pool if availiable, 00344 // else a new one will be allocated on the heap. 00345 forkedFiber = getFiberContext(); 00346 00347 // If we're out of memory, there's nothing we can do. 00348 // keep running in the context of the current thread as a best effort. 00349 if (forkedFiber != NULL) 00350 f = forkedFiber; 00351 } 00352 00353 // Calculate and store the time we want to wake up. 00354 f->context = system_timer_current_time() + t; 00355 00356 // Remove fiber from the run queue 00357 dequeue_fiber(f); 00358 00359 // Add fiber to the sleep queue. We maintain strict ordering here to reduce lookup times. 00360 queue_fiber(f, &sleepQueue); 00361 00362 // Finally, enter the scheduler. 00363 schedule(); 00364 } 00365 00366 /** 00367 * Blocks the calling thread until the specified event is raised. 00368 * The calling thread will be immediateley descheduled, and placed onto a 00369 * wait queue until the requested event is received. 00370 * 00371 * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) 00372 * 00373 * @param value The value of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) 00374 * 00375 * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. 00376 * 00377 * @code 00378 * fiber_wait_for_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); 00379 * @endcode 00380 * 00381 * @note the fiber will not be be made runnable until after the event is raised, but there 00382 * are no guarantees precisely when the fiber will next be scheduled. 00383 */ 00384 int fiber_wait_for_event(uint16_t id, uint16_t value) 00385 { 00386 int ret = fiber_wake_on_event(id, value); 00387 00388 if(ret == MICROBIT_OK) 00389 schedule(); 00390 00391 return ret; 00392 } 00393 00394 /** 00395 * Configures the fiber context for the current fiber to block on an event ID 00396 * and value, but does not deschedule the fiber. 00397 * 00398 * @param id The ID field of the event to listen for (e.g. MICROBIT_ID_BUTTON_A) 00399 * 00400 * @param value The value of the event to listen for (e.g. MICROBIT_BUTTON_EVT_CLICK) 00401 * 00402 * @return MICROBIT_OK, or MICROBIT_NOT_SUPPORTED if the fiber scheduler is not running, or associated with an EventModel. 00403 * 00404 * @code 00405 * fiber_wake_on_event(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); 00406 * 00407 * //perform some time critical operation. 00408 * 00409 * //deschedule the current fiber manually, waiting for the previously configured event. 00410 * schedule(); 00411 * @endcode 00412 */ 00413 int fiber_wake_on_event(uint16_t id, uint16_t value) 00414 { 00415 Fiber *f = currentFiber; 00416 00417 if (messageBus == NULL || !fiber_scheduler_running()) 00418 return MICROBIT_NOT_SUPPORTED; 00419 00420 // Sleep is a blocking call, so if we'r ein a fork on block context, 00421 // it's time to spawn a new fiber... 00422 if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) 00423 { 00424 // Allocate a TCB from the new fiber. This will come from the tread pool if availiable, 00425 // else a new one will be allocated on the heap. 00426 forkedFiber = getFiberContext(); 00427 00428 // If we're out of memory, there's nothing we can do. 00429 // keep running in the context of the current thread as a best effort. 00430 if (forkedFiber != NULL) 00431 f = forkedFiber; 00432 } 00433 00434 // Encode the event data in the context field. It's handy having a 32 bit core. :-) 00435 f->context = value << 16 | id; 00436 00437 // Remove ourselve from the run queue 00438 dequeue_fiber(f); 00439 00440 // Add ourselves to the sleep queue. We maintain strict ordering here to reduce lookup times. 00441 queue_fiber(f, &waitQueue); 00442 00443 // Register to receive this event, so we can wake up the fiber when it happens. 00444 // Special case for teh notify channel, as we always stay registered for that. 00445 if (id != MICROBIT_ID_NOTIFY && id != MICROBIT_ID_NOTIFY_ONE) 00446 messageBus->listen(id, value, scheduler_event, MESSAGE_BUS_LISTENER_IMMEDIATE); 00447 00448 return MICROBIT_OK; 00449 } 00450 00451 /** 00452 * Executes the given function asynchronously if necessary. 00453 * 00454 * Fibers are often used to run event handlers, however many of these event handlers are very simple functions 00455 * that complete very quickly, bringing unecessary RAM overhead. 00456 * 00457 * This function takes a snapshot of the current processor context, then attempts to optimistically call the given function directly. 00458 * We only create an additional fiber if that function performs a block operation. 00459 * 00460 * @param entry_fn The function to execute. 00461 * 00462 * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. 00463 */ 00464 int invoke(void (*entry_fn)(void)) 00465 { 00466 // Validate our parameters. 00467 if (entry_fn == NULL) 00468 return MICROBIT_INVALID_PARAMETER; 00469 00470 if (!fiber_scheduler_running()) 00471 return MICROBIT_NOT_SUPPORTED; 00472 00473 if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) 00474 { 00475 // If we attempt a fork on block whilst already in fork n block context, 00476 // simply launch a fiber to deal with the request and we're done. 00477 create_fiber(entry_fn); 00478 return MICROBIT_OK; 00479 } 00480 00481 // Snapshot current context, but also update the Link Register to 00482 // refer to our calling function. 00483 save_register_context(¤tFiber->tcb); 00484 00485 // If we're here, there are two possibilities: 00486 // 1) We're about to attempt to execute the user code 00487 // 2) We've already tried to execute the code, it blocked, and we've backtracked. 00488 00489 // If we're returning from the user function and we forked another fiber then cleanup and exit. 00490 if (currentFiber->flags & MICROBIT_FIBER_FLAG_PARENT) 00491 { 00492 currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; 00493 currentFiber->flags &= ~MICROBIT_FIBER_FLAG_PARENT; 00494 return MICROBIT_OK; 00495 } 00496 00497 // Otherwise, we're here for the first time. Enter FORK ON BLOCK mode, and 00498 // execute the function directly. If the code tries to block, we detect this and 00499 // spawn a thread to deal with it. 00500 currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; 00501 entry_fn(); 00502 currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; 00503 00504 // If this is is an exiting fiber that for spawned to handle a blocking call, recycle it. 00505 // The fiber will then re-enter the scheduler, so no need for further cleanup. 00506 if (currentFiber->flags & MICROBIT_FIBER_FLAG_CHILD) 00507 release_fiber(); 00508 00509 return MICROBIT_OK; 00510 } 00511 00512 /** 00513 * Executes the given function asynchronously if necessary, and offers the ability to provide a parameter. 00514 * 00515 * Fibers are often used to run event handlers, however many of these event handlers are very simple functions 00516 * that complete very quickly, bringing unecessary RAM. overhead 00517 * 00518 * This function takes a snapshot of the current fiber context, then attempt to optimistically call the given function directly. 00519 * We only create an additional fiber if that function performs a block operation. 00520 * 00521 * @param entry_fn The function to execute. 00522 * 00523 * @param param an untyped parameter passed into the entry_fn and completion_fn. 00524 * 00525 * @return MICROBIT_OK, or MICROBIT_INVALID_PARAMETER. 00526 */ 00527 int invoke(void (*entry_fn)(void *), void *param) 00528 { 00529 // Validate our parameters. 00530 if (entry_fn == NULL) 00531 return MICROBIT_INVALID_PARAMETER; 00532 00533 if (!fiber_scheduler_running()) 00534 return MICROBIT_NOT_SUPPORTED; 00535 00536 if (currentFiber->flags & (MICROBIT_FIBER_FLAG_FOB | MICROBIT_FIBER_FLAG_PARENT | MICROBIT_FIBER_FLAG_CHILD)) 00537 { 00538 // If we attempt a fork on block whilst already in a fork on block context, 00539 // simply launch a fiber to deal with the request and we're done. 00540 create_fiber(entry_fn, param); 00541 return MICROBIT_OK; 00542 } 00543 00544 // Snapshot current context, but also update the Link Register to 00545 // refer to our calling function. 00546 save_register_context(¤tFiber->tcb); 00547 00548 // If we're here, there are two possibilities: 00549 // 1) We're about to attempt to execute the user code 00550 // 2) We've already tried to execute the code, it blocked, and we've backtracked. 00551 00552 // If we're returning from the user function and we forked another fiber then cleanup and exit. 00553 if (currentFiber->flags & MICROBIT_FIBER_FLAG_PARENT) 00554 { 00555 currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; 00556 currentFiber->flags &= ~MICROBIT_FIBER_FLAG_PARENT; 00557 return MICROBIT_OK; 00558 } 00559 00560 // Otherwise, we're here for the first time. Enter FORK ON BLOCK mode, and 00561 // execute the function directly. If the code tries to block, we detect this and 00562 // spawn a thread to deal with it. 00563 currentFiber->flags |= MICROBIT_FIBER_FLAG_FOB; 00564 entry_fn(param); 00565 currentFiber->flags &= ~MICROBIT_FIBER_FLAG_FOB; 00566 00567 // If this is is an exiting fiber that for spawned to handle a blocking call, recycle it. 00568 // The fiber will then re-enter the scheduler, so no need for further cleanup. 00569 if (currentFiber->flags & MICROBIT_FIBER_FLAG_CHILD) 00570 release_fiber(param); 00571 00572 return MICROBIT_OK; 00573 } 00574 00575 /** 00576 * Launches a fiber. 00577 * 00578 * @param ep the entry point for the fiber. 00579 * 00580 * @param cp the completion routine after ep has finished execution 00581 */ 00582 void launch_new_fiber(void (*ep)(void), void (*cp)(void)) 00583 { 00584 // Execute the thread's entrypoint 00585 ep(); 00586 00587 // Execute the thread's completion routine; 00588 cp(); 00589 00590 // If we get here, then the completion routine didn't recycle the fiber... so do it anyway. :-) 00591 release_fiber(); 00592 } 00593 00594 /** 00595 * Launches a fiber with a parameter 00596 * 00597 * @param ep the entry point for the fiber. 00598 * 00599 * @param cp the completion routine after ep has finished execution 00600 * 00601 * @param pm the parameter to provide to ep and cp. 00602 */ 00603 void launch_new_fiber_param(void (*ep)(void *), void (*cp)(void *), void *pm) 00604 { 00605 // Execute the thread's entrypoint. 00606 ep(pm); 00607 00608 // Execute the thread's completion routine. 00609 cp(pm); 00610 00611 // If we get here, then the completion routine didn't recycle the fiber... so do it anyway. :-) 00612 release_fiber(pm); 00613 } 00614 00615 Fiber *__create_fiber(uint32_t ep, uint32_t cp, uint32_t pm, int parameterised) 00616 { 00617 // Validate our parameters. 00618 if (ep == 0 || cp == 0) 00619 return NULL; 00620 00621 // Allocate a TCB from the new fiber. This will come from the fiber pool if availiable, 00622 // else a new one will be allocated on the heap. 00623 Fiber *newFiber = getFiberContext(); 00624 00625 // If we're out of memory, there's nothing we can do. 00626 if (newFiber == NULL) 00627 return NULL; 00628 00629 newFiber->tcb.R0 = (uint32_t) ep; 00630 newFiber->tcb.R1 = (uint32_t) cp; 00631 newFiber->tcb.R2 = (uint32_t) pm; 00632 00633 // Set the stack and assign the link register to refer to the appropriate entry point wrapper. 00634 newFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; 00635 newFiber->tcb.LR = parameterised ? (uint32_t) &launch_new_fiber_param : (uint32_t) &launch_new_fiber; 00636 00637 // Add new fiber to the run queue. 00638 queue_fiber(newFiber, &runQueue); 00639 00640 return newFiber; 00641 } 00642 00643 /** 00644 * Creates a new Fiber, and launches it. 00645 * 00646 * @param entry_fn The function the new Fiber will begin execution in. 00647 * 00648 * @param completion_fn The function called when the thread completes execution of entry_fn. 00649 * Defaults to release_fiber. 00650 * 00651 * @return The new Fiber, or NULL if the operation could not be completed. 00652 */ 00653 Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void)) 00654 { 00655 if (!fiber_scheduler_running()) 00656 return NULL; 00657 00658 return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, 0, 0); 00659 } 00660 00661 00662 /** 00663 * Creates a new parameterised Fiber, and launches it. 00664 * 00665 * @param entry_fn The function the new Fiber will begin execution in. 00666 * 00667 * @param param an untyped parameter passed into the entry_fn and completion_fn. 00668 * 00669 * @param completion_fn The function called when the thread completes execution of entry_fn. 00670 * Defaults to release_fiber. 00671 * 00672 * @return The new Fiber, or NULL if the operation could not be completed. 00673 */ 00674 Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *)) 00675 { 00676 if (!fiber_scheduler_running()) 00677 return NULL; 00678 00679 return __create_fiber((uint32_t) entry_fn, (uint32_t)completion_fn, (uint32_t) param, 1); 00680 } 00681 00682 /** 00683 * Exit point for all fibers. 00684 * 00685 * Any fiber reaching the end of its entry function will return here for recycling. 00686 */ 00687 void release_fiber(void *) 00688 { 00689 if (!fiber_scheduler_running()) 00690 return; 00691 00692 release_fiber(); 00693 } 00694 00695 /** 00696 * Exit point for all fibers. 00697 * 00698 * Any fiber reaching the end of its entry function will return here for recycling. 00699 */ 00700 void release_fiber(void) 00701 { 00702 if (!fiber_scheduler_running()) 00703 return; 00704 00705 // Remove ourselves form the runqueue. 00706 dequeue_fiber(currentFiber); 00707 00708 // Add ourselves to the list of free fibers 00709 queue_fiber(currentFiber, &fiberPool); 00710 00711 // Find something else to do! 00712 schedule(); 00713 } 00714 00715 /** 00716 * Resizes the stack allocation of the current fiber if necessary to hold the system stack. 00717 * 00718 * If the stack allocation is large enough to hold the current system stack, then this function does nothing. 00719 * Otherwise, the the current allocation of the fiber is freed, and a larger block is allocated. 00720 * 00721 * @param f The fiber context to verify. 00722 * 00723 * @return The stack depth of the given fiber. 00724 */ 00725 void verify_stack_size(Fiber *f) 00726 { 00727 // Ensure the stack buffer is large enough to hold the stack Reallocate if necessary. 00728 uint32_t stackDepth; 00729 uint32_t bufferSize; 00730 00731 // Calculate the stack depth. 00732 stackDepth = f->tcb.stack_base - ((uint32_t) __get_MSP()); 00733 00734 // Calculate the size of our allocated stack buffer 00735 bufferSize = f->stack_top - f->stack_bottom; 00736 00737 // If we're too small, increase our buffer size. 00738 if (bufferSize < stackDepth) 00739 { 00740 // To ease heap churn, we choose the next largest multple of 32 bytes. 00741 bufferSize = (stackDepth + 32) & 0xffffffe0; 00742 00743 // Release the old memory 00744 if (f->stack_bottom != 0) 00745 free((void *)f->stack_bottom); 00746 00747 // Allocate a new one of the appropriate size. 00748 f->stack_bottom = (uint32_t) malloc(bufferSize); 00749 00750 // Recalculate where the top of the stack is and we're done. 00751 f->stack_top = f->stack_bottom + bufferSize; 00752 } 00753 } 00754 00755 /** 00756 * Determines if any fibers are waiting to be scheduled. 00757 * 00758 * @return The number of fibers currently on the run queue 00759 */ 00760 int scheduler_runqueue_empty() 00761 { 00762 return (runQueue == NULL); 00763 } 00764 00765 /** 00766 * Calls the Fiber scheduler. 00767 * The calling Fiber will likely be blocked, and control given to another waiting fiber. 00768 * Call this function to yield control of the processor when you have nothing more to do. 00769 */ 00770 void schedule() 00771 { 00772 if (!fiber_scheduler_running()) 00773 return; 00774 00775 // First, take a reference to the currently running fiber; 00776 Fiber *oldFiber = currentFiber; 00777 00778 // First, see if we're in Fork on Block context. If so, we simply want to store the full context 00779 // of the currently running thread in a newly created fiber, and restore the context of the 00780 // currently running fiber, back to the point where it entered FOB. 00781 00782 if (currentFiber->flags & MICROBIT_FIBER_FLAG_FOB) 00783 { 00784 // Record that the fibers have a parent/child relationship 00785 currentFiber->flags |= MICROBIT_FIBER_FLAG_PARENT; 00786 forkedFiber->flags |= MICROBIT_FIBER_FLAG_CHILD; 00787 00788 // Define the stack base of the forked fiber to be align with the entry point of the parent fiber 00789 forkedFiber->tcb.stack_base = currentFiber->tcb.SP; 00790 00791 // Ensure the stack allocation of the new fiber is large enough 00792 verify_stack_size(forkedFiber); 00793 00794 // Store the full context of this fiber. 00795 save_context(&forkedFiber->tcb, forkedFiber->stack_top); 00796 00797 // We may now be either the newly created thread, or the one that created it. 00798 // if the MICROBIT_FIBER_FLAG_PARENT flag is still set, we're the old thread, so 00799 // restore the current fiber to its stored context and we're done. 00800 if (currentFiber->flags & MICROBIT_FIBER_FLAG_PARENT) 00801 restore_register_context(¤tFiber->tcb); 00802 00803 // If we're the new thread, we must have been unblocked by the scheduler, so simply return 00804 // and continue processing. 00805 return; 00806 } 00807 00808 // We're in a normal scheduling context, so perform a round robin algorithm across runnable fibers. 00809 // OK - if we've nothing to do, then run the IDLE task (power saving sleep) 00810 if (runQueue == NULL) 00811 currentFiber = idleFiber; 00812 00813 else if (currentFiber->queue == &runQueue) 00814 // If the current fiber is on the run queue, round robin. 00815 currentFiber = currentFiber->next == NULL ? runQueue : currentFiber->next; 00816 00817 else 00818 // Otherwise, just pick the head of the run queue. 00819 currentFiber = runQueue; 00820 00821 if (currentFiber == idleFiber && oldFiber->flags & MICROBIT_FIBER_FLAG_DO_NOT_PAGE) 00822 { 00823 // Run the idle task right here using the old fiber's stack. 00824 // Keep idling while the runqueue is empty, or there is data to process. 00825 00826 // Run in the context of the original fiber, to preserve state of flags... 00827 // as we are running on top of this fiber's stack. 00828 currentFiber = oldFiber; 00829 00830 do 00831 { 00832 idle(); 00833 } 00834 while (runQueue == NULL); 00835 00836 // Switch to a non-idle fiber. 00837 // If this fiber is the same as the old one then there'll be no switching at all. 00838 currentFiber = runQueue; 00839 } 00840 00841 // Swap to the context of the chosen fiber, and we're done. 00842 // Don't bother with the overhead of switching if there's only one fiber on the runqueue! 00843 if (currentFiber != oldFiber) 00844 { 00845 // Special case for the idle task, as we don't maintain a stack context (just to save memory). 00846 if (currentFiber == idleFiber) 00847 { 00848 idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04; 00849 idleFiber->tcb.LR = (uint32_t) &idle_task; 00850 } 00851 00852 if (oldFiber == idleFiber) 00853 { 00854 // Just swap in the new fiber, and discard changes to stack and register context. 00855 swap_context(NULL, ¤tFiber->tcb, 0, currentFiber->stack_top); 00856 } 00857 else 00858 { 00859 // Ensure the stack allocation of the fiber being scheduled out is large enough 00860 verify_stack_size(oldFiber); 00861 00862 // Schedule in the new fiber. 00863 swap_context(&oldFiber->tcb, ¤tFiber->tcb, oldFiber->stack_top, currentFiber->stack_top); 00864 } 00865 } 00866 } 00867 00868 /** 00869 * Adds a component to the array of idle thread components, which are processed 00870 * when the run queue is empty. 00871 * 00872 * The system timer will poll isIdleCallbackNeeded on each component to determine 00873 * if the scheduler should schedule the idle_task imminently. 00874 * 00875 * @param component The component to add to the array. 00876 * 00877 * @return MICROBIT_OK on success or MICROBIT_NO_RESOURCES if the fiber components array is full. 00878 * 00879 * @code 00880 * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); 00881 * 00882 * // heap allocated - otherwise it will be paged out! 00883 * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); 00884 * 00885 * fiber_add_idle_component(accelerometer); 00886 * @endcode 00887 */ 00888 int fiber_add_idle_component(MicroBitComponent *component) 00889 { 00890 int i = 0; 00891 00892 while(idleThreadComponents[i] != NULL && i < MICROBIT_IDLE_COMPONENTS) 00893 i++; 00894 00895 if(i == MICROBIT_IDLE_COMPONENTS) 00896 return MICROBIT_NO_RESOURCES; 00897 00898 idleThreadComponents[i] = component; 00899 00900 return MICROBIT_OK; 00901 } 00902 00903 /** 00904 * Remove a component from the array of idle thread components 00905 * 00906 * @param component The component to remove from the idle component array. 00907 * 00908 * @return MICROBIT_OK on success. MICROBIT_INVALID_PARAMETER is returned if the given component has not been previously added. 00909 * 00910 * @code 00911 * MicroBitI2C i2c(I2C_SDA0, I2C_SCL0); 00912 * 00913 * // heap allocated - otherwise it will be paged out! 00914 * MicroBitAccelerometer* accelerometer = new MicroBitAccelerometer(i2c); 00915 * 00916 * fiber_add_idle_component(accelerometer); 00917 * 00918 * fiber_remove_idle_component(accelerometer); 00919 * @endcode 00920 */ 00921 int fiber_remove_idle_component(MicroBitComponent *component) 00922 { 00923 int i = 0; 00924 00925 while(idleThreadComponents[i] != component && i < MICROBIT_IDLE_COMPONENTS) 00926 i++; 00927 00928 if(i == MICROBIT_IDLE_COMPONENTS) 00929 return MICROBIT_INVALID_PARAMETER; 00930 00931 idleThreadComponents[i] = NULL; 00932 00933 return MICROBIT_OK; 00934 } 00935 00936 /** 00937 * Set of tasks to perform when idle. 00938 * Service any background tasks that are required, and attempt a power efficient sleep. 00939 */ 00940 void idle() 00941 { 00942 // Service background tasks 00943 for(int i = 0; i < MICROBIT_IDLE_COMPONENTS; i++) 00944 if(idleThreadComponents[i] != NULL) 00945 idleThreadComponents[i]->idleTick(); 00946 00947 // If the above did create any useful work, enter power efficient sleep. 00948 if(scheduler_runqueue_empty()) 00949 __WFE(); 00950 } 00951 00952 /** 00953 * The idle task, which is called when the runtime has no fibers that require execution. 00954 * 00955 * This function typically calls idle(). 00956 */ 00957 void idle_task() 00958 { 00959 while(1) 00960 { 00961 idle(); 00962 schedule(); 00963 } 00964 }
Generated on Tue Jul 12 2022 19:58:09 by 1.7.2