Branch data Line data Source code
1 : : /* 2 : : * This file is part of the MicroPython project, http://micropython.org/ 3 : : * 4 : : * The MIT License (MIT) 5 : : * 6 : : * Copyright (c) 2017 Damien P. George 7 : : * 8 : : * Permission is hereby granted, free of charge, to any person obtaining a copy 9 : : * of this software and associated documentation files (the "Software"), to deal 10 : : * in the Software without restriction, including without limitation the rights 11 : : * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 : : * copies of the Software, and to permit persons to whom the Software is 13 : : * furnished to do so, subject to the following conditions: 14 : : * 15 : : * The above copyright notice and this permission notice shall be included in 16 : : * all copies or substantial portions of the Software. 17 : : * 18 : : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 : : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 : : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 : : * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 : : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 : : * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 : : * THE SOFTWARE. 25 : : */ 26 : : 27 : : #include <stdio.h> 28 : : 29 : : #include "py/mphal.h" 30 : : #include "py/runtime.h" 31 : : 32 : : // Schedules an exception on the main thread (for exceptions "thrown" by async 33 : : // sources such as interrupts and UNIX signal handlers). 34 : 8 : void MICROPY_WRAP_MP_SCHED_EXCEPTION(mp_sched_exception)(mp_obj_t exc) { 35 : 8 : MP_STATE_MAIN_THREAD(mp_pending_exception) = exc; 36 : : 37 : : #if MICROPY_ENABLE_SCHEDULER && !MICROPY_PY_THREAD 38 : : // Optimisation for the case where we have scheduler but no threading. 39 : : // Allows the VM to do a single check to exclude both pending exception 40 : : // and queued tasks. 41 : : if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { 42 : : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 43 : : } 44 : : #endif 45 : 8 : } 46 : : 47 : : #if MICROPY_KBD_EXCEPTION 48 : : // This function may be called asynchronously at any time so only do the bare minimum. 49 : 8 : void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void) { 50 : 8 : MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; 51 : 8 : mp_sched_exception(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); 52 : 8 : } 53 : : #endif 54 : : 55 : : #if MICROPY_ENABLE_VM_ABORT 56 : : void MICROPY_WRAP_MP_SCHED_VM_ABORT(mp_sched_vm_abort)(void) { 57 : : MP_STATE_VM(vm_abort) = true; 58 : : } 59 : : #endif 60 : : 61 : : #if MICROPY_ENABLE_SCHEDULER 62 : : 63 : : #define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1)) 64 : : 65 : : // This is a macro so it is guaranteed to be inlined in functions like 66 : : // mp_sched_schedule that may be located in a special memory region. 67 : : #define mp_sched_full() (mp_sched_num_pending() == MICROPY_SCHEDULER_DEPTH) 68 : : 69 : 10026 : static inline bool mp_sched_empty(void) { 70 : 10026 : MP_STATIC_ASSERT(MICROPY_SCHEDULER_DEPTH <= 255); // MICROPY_SCHEDULER_DEPTH must fit in 8 bits 71 : 10026 : MP_STATIC_ASSERT((IDX_MASK(MICROPY_SCHEDULER_DEPTH) == 0)); // MICROPY_SCHEDULER_DEPTH must be a power of 2 72 : : 73 : 10026 : return mp_sched_num_pending() == 0; 74 : : } 75 : : 76 : 10026 : static inline void mp_sched_run_pending(void) { 77 : 10026 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 78 [ - + ]: 10026 : if (MP_STATE_VM(sched_state) != MP_SCHED_PENDING) { 79 : : // Something else (e.g. hard IRQ) locked the scheduler while we 80 : : // acquired the lock. 81 : 0 : MICROPY_END_ATOMIC_SECTION(atomic_state); 82 : 0 : return; 83 : : } 84 : : 85 : : // Equivalent to mp_sched_lock(), but we're already in the atomic 86 : : // section and know that we're pending. 87 : 10026 : MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; 88 : : 89 : : #if MICROPY_SCHEDULER_STATIC_NODES 90 : : // Run all pending C callbacks. 91 : : while (MP_STATE_VM(sched_head) != NULL) { 92 : : mp_sched_node_t *node = MP_STATE_VM(sched_head); 93 : : MP_STATE_VM(sched_head) = node->next; 94 : : if (MP_STATE_VM(sched_head) == NULL) { 95 : : MP_STATE_VM(sched_tail) = NULL; 96 : : } 97 : : mp_sched_callback_t callback = node->callback; 98 : : node->callback = NULL; 99 : : MICROPY_END_ATOMIC_SECTION(atomic_state); 100 : : callback(node); 101 : : atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 102 : : } 103 : : #endif 104 : : 105 : : // Run at most one pending Python callback. 106 [ + - ]: 10026 : if (!mp_sched_empty()) { 107 : 10026 : mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)]; 108 : 10026 : MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1); 109 : 10026 : --MP_STATE_VM(sched_len); 110 : 10026 : MICROPY_END_ATOMIC_SECTION(atomic_state); 111 : 10026 : mp_call_function_1_protected(item.func, item.arg); 112 : : } else { 113 : 0 : MICROPY_END_ATOMIC_SECTION(atomic_state); 114 : : } 115 : : 116 : : // Restore MP_STATE_VM(sched_state) to idle (or pending if there are still 117 : : // tasks in the queue). 118 : 10026 : mp_sched_unlock(); 119 : : } 120 : : 121 : : // Locking the scheduler prevents tasks from executing (does not prevent new 122 : : // tasks from being added). We lock the scheduler while executing scheduled 123 : : // tasks and also in hard interrupts or GC finalisers. 124 : 4447 : void mp_sched_lock(void) { 125 : 4447 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 126 [ + + ]: 4447 : if (MP_STATE_VM(sched_state) < 0) { 127 : : // Already locked, increment lock (recursive lock). 128 : 2 : --MP_STATE_VM(sched_state); 129 : : } else { 130 : : // Pending or idle. 131 : 4445 : MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; 132 : : } 133 : 4447 : MICROPY_END_ATOMIC_SECTION(atomic_state); 134 : 4447 : } 135 : : 136 : 14473 : void mp_sched_unlock(void) { 137 : 14473 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 138 [ - + ]: 14473 : assert(MP_STATE_VM(sched_state) < 0); 139 [ + + ]: 14473 : if (++MP_STATE_VM(sched_state) == 0) { 140 : : // Scheduler became unlocked. Check if there are still tasks in the 141 : : // queue and set sched_state accordingly. 142 : 14471 : if ( 143 : : #if !MICROPY_PY_THREAD 144 : : // See optimisation in mp_sched_exception. 145 : : MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL || 146 : : #endif 147 : : #if MICROPY_SCHEDULER_STATIC_NODES 148 : : MP_STATE_VM(sched_head) != NULL || 149 : : #endif 150 [ + + ]: 14471 : mp_sched_num_pending()) { 151 : 8217 : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 152 : : } else { 153 : 6254 : MP_STATE_VM(sched_state) = MP_SCHED_IDLE; 154 : : } 155 : : } 156 : 14473 : MICROPY_END_ATOMIC_SECTION(atomic_state); 157 : 14473 : } 158 : : 159 : 16634 : bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) { 160 : 16634 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 161 : 16640 : bool ret; 162 [ + + ]: 16640 : if (!mp_sched_full()) { 163 [ + + ]: 10031 : if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { 164 : 1813 : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 165 : : } 166 : 10031 : uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++); 167 : 10031 : MP_STATE_VM(sched_queue)[iput].func = function; 168 : 10031 : MP_STATE_VM(sched_queue)[iput].arg = arg; 169 : 10031 : MICROPY_SCHED_HOOK_SCHEDULED; 170 : 10031 : ret = true; 171 : : } else { 172 : : // schedule queue is full 173 : : ret = false; 174 : : } 175 : 16640 : MICROPY_END_ATOMIC_SECTION(atomic_state); 176 : 16640 : return ret; 177 : : } 178 : : 179 : : #if MICROPY_SCHEDULER_STATIC_NODES 180 : : bool mp_sched_schedule_node(mp_sched_node_t *node, mp_sched_callback_t callback) { 181 : : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 182 : : bool ret; 183 : : if (node->callback == NULL) { 184 : : if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { 185 : : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 186 : : } 187 : : node->callback = callback; 188 : : node->next = NULL; 189 : : if (MP_STATE_VM(sched_tail) == NULL) { 190 : : MP_STATE_VM(sched_head) = node; 191 : : } else { 192 : : MP_STATE_VM(sched_tail)->next = node; 193 : : } 194 : : MP_STATE_VM(sched_tail) = node; 195 : : MICROPY_SCHED_HOOK_SCHEDULED; 196 : : ret = true; 197 : : } else { 198 : : // already scheduled 199 : : ret = false; 200 : : } 201 : : MICROPY_END_ATOMIC_SECTION(atomic_state); 202 : : return ret; 203 : : } 204 : : #endif 205 : : 206 : : MP_REGISTER_ROOT_POINTER(mp_sched_item_t sched_queue[MICROPY_SCHEDULER_DEPTH]); 207 : : 208 : : #endif // MICROPY_ENABLE_SCHEDULER 209 : : 210 : : // Called periodically from the VM or from "waiting" code (e.g. sleep) to 211 : : // process background tasks and pending exceptions (e.g. KeyboardInterrupt). 212 : 170817 : void mp_handle_pending(bool raise_exc) { 213 : : // Handle pending VM abort. 214 : : #if MICROPY_ENABLE_VM_ABORT 215 : : if (MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) { 216 : : MP_STATE_VM(vm_abort) = false; 217 : : if (raise_exc && nlr_get_abort() != NULL) { 218 : : nlr_jump_abort(); 219 : : } 220 : : } 221 : : #endif 222 : : 223 : : // Handle any pending exception. 224 [ + + ]: 170817 : if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { 225 : 6 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 226 : 6 : mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); 227 [ + - ]: 6 : if (obj != MP_OBJ_NULL) { 228 : 6 : MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; 229 [ + + ]: 6 : if (raise_exc) { 230 : 4 : MICROPY_END_ATOMIC_SECTION(atomic_state); 231 : 4 : nlr_raise(obj); 232 : : } 233 : : } 234 : 2 : MICROPY_END_ATOMIC_SECTION(atomic_state); 235 : : } 236 : : 237 : : // Handle any pending callbacks. 238 : : #if MICROPY_ENABLE_SCHEDULER 239 : 170636 : bool run_scheduler = (MP_STATE_VM(sched_state) == MP_SCHED_PENDING); 240 : : #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL 241 : : // Avoid races by running the scheduler on the main thread, only. 242 : : // (Not needed if GIL enabled, as GIL ensures thread safety here.) 243 [ + + + + ]: 170636 : run_scheduler = run_scheduler && mp_thread_is_main_thread(); 244 : : #endif 245 : 10026 : if (run_scheduler) { 246 : 10026 : mp_sched_run_pending(); 247 : : } 248 : : #endif 249 : 170744 : } 250 : : 251 : : // Handles any pending MicroPython events without waiting for an interrupt or event. 252 : 131357 : void mp_event_handle_nowait(void) { 253 : : #if defined(MICROPY_EVENT_POLL_HOOK_FAST) && !MICROPY_PREVIEW_VERSION_2 254 : : // For ports still using the old macros. 255 : : MICROPY_EVENT_POLL_HOOK_FAST 256 : : #else 257 : : // Process any port layer (non-blocking) events. 258 : 131357 : MICROPY_INTERNAL_EVENT_HOOK; 259 : 131357 : mp_handle_pending(true); 260 : : #endif 261 : 131357 : } 262 : : 263 : : // Handles any pending MicroPython events and then suspends execution until the 264 : : // next interrupt or event. 265 : 2 : void mp_event_wait_indefinite(void) { 266 : : #if defined(MICROPY_EVENT_POLL_HOOK) && !MICROPY_PREVIEW_VERSION_2 267 : : // For ports still using the old macros. 268 : : MICROPY_EVENT_POLL_HOOK 269 : : #else 270 : 2 : mp_event_handle_nowait(); 271 : 2 : MICROPY_INTERNAL_WFE(-1); 272 : : #endif 273 : 2 : } 274 : : 275 : : // Handle any pending MicroPython events and then suspends execution until the 276 : : // next interrupt or event, or until timeout_ms milliseconds have elapsed. 277 : 131317 : void mp_event_wait_ms(mp_uint_t timeout_ms) { 278 : : #if defined(MICROPY_EVENT_POLL_HOOK) && !MICROPY_PREVIEW_VERSION_2 279 : : // For ports still using the old macros. 280 : : MICROPY_EVENT_POLL_HOOK 281 : : #else 282 : 131317 : mp_event_handle_nowait(); 283 : 131311 : MICROPY_INTERNAL_WFE(timeout_ms); 284 : : #endif 285 : 131328 : }