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/runtime.h" 30 : : 31 : : // Schedules an exception on the main thread (for exceptions "thrown" by async 32 : : // sources such as interrupts and UNIX signal handlers). 33 : 8 : void MICROPY_WRAP_MP_SCHED_EXCEPTION(mp_sched_exception)(mp_obj_t exc) { 34 : 8 : MP_STATE_MAIN_THREAD(mp_pending_exception) = exc; 35 : : 36 : : #if MICROPY_ENABLE_SCHEDULER && !MICROPY_PY_THREAD 37 : : // Optimisation for the case where we have scheduler but no threading. 38 : : // Allows the VM to do a single check to exclude both pending exception 39 : : // and queued tasks. 40 : : if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { 41 : : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 42 : : } 43 : : #endif 44 : 8 : } 45 : : 46 : : #if MICROPY_KBD_EXCEPTION 47 : : // This function may be called asynchronously at any time so only do the bare minimum. 48 : 8 : void MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(mp_sched_keyboard_interrupt)(void) { 49 : 8 : MP_STATE_VM(mp_kbd_exception).traceback_data = NULL; 50 : 8 : mp_sched_exception(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))); 51 : 8 : } 52 : : #endif 53 : : 54 : : #if MICROPY_ENABLE_VM_ABORT 55 : : void MICROPY_WRAP_MP_SCHED_VM_ABORT(mp_sched_vm_abort)(void) { 56 : : MP_STATE_VM(vm_abort) = true; 57 : : } 58 : : #endif 59 : : 60 : : #if MICROPY_ENABLE_SCHEDULER 61 : : 62 : : #define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1)) 63 : : 64 : : // This is a macro so it is guaranteed to be inlined in functions like 65 : : // mp_sched_schedule that may be located in a special memory region. 66 : : #define mp_sched_full() (mp_sched_num_pending() == MICROPY_SCHEDULER_DEPTH) 67 : : 68 : 23984 : static inline bool mp_sched_empty(void) { 69 : 23984 : MP_STATIC_ASSERT(MICROPY_SCHEDULER_DEPTH <= 255); // MICROPY_SCHEDULER_DEPTH must fit in 8 bits 70 : 23984 : MP_STATIC_ASSERT((IDX_MASK(MICROPY_SCHEDULER_DEPTH) == 0)); // MICROPY_SCHEDULER_DEPTH must be a power of 2 71 : : 72 : 23984 : return mp_sched_num_pending() == 0; 73 : : } 74 : : 75 : 24012 : static inline void mp_sched_run_pending(void) { 76 : 24012 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 77 [ + + ]: 24015 : if (MP_STATE_VM(sched_state) != MP_SCHED_PENDING) { 78 : : // Something else (e.g. hard IRQ) locked the scheduler while we 79 : : // acquired the lock. 80 : 31 : MICROPY_END_ATOMIC_SECTION(atomic_state); 81 : 31 : return; 82 : : } 83 : : 84 : : // Equivalent to mp_sched_lock(), but we're already in the atomic 85 : : // section and know that we're pending. 86 : 23984 : MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; 87 : : 88 : : #if MICROPY_SCHEDULER_STATIC_NODES 89 : : // Run all pending C callbacks. 90 : : while (MP_STATE_VM(sched_head) != NULL) { 91 : : mp_sched_node_t *node = MP_STATE_VM(sched_head); 92 : : MP_STATE_VM(sched_head) = node->next; 93 : : if (MP_STATE_VM(sched_head) == NULL) { 94 : : MP_STATE_VM(sched_tail) = NULL; 95 : : } 96 : : mp_sched_callback_t callback = node->callback; 97 : : node->callback = NULL; 98 : : MICROPY_END_ATOMIC_SECTION(atomic_state); 99 : : callback(node); 100 : : atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 101 : : } 102 : : #endif 103 : : 104 : : // Run at most one pending Python callback. 105 [ + + ]: 23984 : if (!mp_sched_empty()) { 106 : 21621 : mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)]; 107 : 21621 : MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1); 108 : 21621 : --MP_STATE_VM(sched_len); 109 : 21621 : MICROPY_END_ATOMIC_SECTION(atomic_state); 110 : 21621 : mp_call_function_1_protected(item.func, item.arg); 111 : : } else { 112 : 2363 : MICROPY_END_ATOMIC_SECTION(atomic_state); 113 : : } 114 : : 115 : : // Restore MP_STATE_VM(sched_state) to idle (or pending if there are still 116 : : // tasks in the queue). 117 : 23983 : mp_sched_unlock(); 118 : : } 119 : : 120 : : // Locking the scheduler prevents tasks from executing (does not prevent new 121 : : // tasks from being added). We lock the scheduler while executing scheduled 122 : : // tasks and also in hard interrupts or GC finalisers. 123 : 4114 : void mp_sched_lock(void) { 124 : 4114 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 125 [ + + ]: 4114 : if (MP_STATE_VM(sched_state) < 0) { 126 : : // Already locked, increment lock (recursive lock). 127 : 3 : --MP_STATE_VM(sched_state); 128 : : } else { 129 : : // Pending or idle. 130 : 4111 : MP_STATE_VM(sched_state) = MP_SCHED_LOCKED; 131 : : } 132 : 4114 : MICROPY_END_ATOMIC_SECTION(atomic_state); 133 : 4114 : } 134 : : 135 : 28097 : void mp_sched_unlock(void) { 136 : 28097 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 137 [ - + ]: 28097 : assert(MP_STATE_VM(sched_state) < 0); 138 [ + + ]: 28097 : if (++MP_STATE_VM(sched_state) == 0) { 139 : : // Scheduler became unlocked. Check if there are still tasks in the 140 : : // queue and set sched_state accordingly. 141 : 28094 : if ( 142 : : #if !MICROPY_PY_THREAD 143 : : // See optimisation in mp_sched_exception. 144 : : MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL || 145 : : #endif 146 : : #if MICROPY_SCHEDULER_STATIC_NODES 147 : : MP_STATE_VM(sched_head) != NULL || 148 : : #endif 149 [ + + ]: 28094 : mp_sched_num_pending()) { 150 : 102 : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 151 : : } else { 152 : 27992 : MP_STATE_VM(sched_state) = MP_SCHED_IDLE; 153 : : } 154 : : } 155 : 28097 : MICROPY_END_ATOMIC_SECTION(atomic_state); 156 : 28097 : } 157 : : 158 : 21644 : bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) { 159 : 21644 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 160 : 21644 : bool ret; 161 [ + + ]: 21644 : if (!mp_sched_full()) { 162 [ + + ]: 21626 : if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { 163 : 21523 : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 164 : : } 165 : 21626 : uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++); 166 : 21626 : MP_STATE_VM(sched_queue)[iput].func = function; 167 : 21626 : MP_STATE_VM(sched_queue)[iput].arg = arg; 168 : 21626 : MICROPY_SCHED_HOOK_SCHEDULED; 169 : 21626 : ret = true; 170 : : } else { 171 : : // schedule queue is full 172 : : ret = false; 173 : : } 174 : 21644 : MICROPY_END_ATOMIC_SECTION(atomic_state); 175 : 21644 : return ret; 176 : : } 177 : : 178 : : #if MICROPY_SCHEDULER_STATIC_NODES 179 : : bool mp_sched_schedule_node(mp_sched_node_t *node, mp_sched_callback_t callback) { 180 : : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 181 : : bool ret; 182 : : if (node->callback == NULL) { 183 : : if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) { 184 : : MP_STATE_VM(sched_state) = MP_SCHED_PENDING; 185 : : } 186 : : node->callback = callback; 187 : : node->next = NULL; 188 : : if (MP_STATE_VM(sched_tail) == NULL) { 189 : : MP_STATE_VM(sched_head) = node; 190 : : } else { 191 : : MP_STATE_VM(sched_tail)->next = node; 192 : : } 193 : : MP_STATE_VM(sched_tail) = node; 194 : : MICROPY_SCHED_HOOK_SCHEDULED; 195 : : ret = true; 196 : : } else { 197 : : // already scheduled 198 : : ret = false; 199 : : } 200 : : MICROPY_END_ATOMIC_SECTION(atomic_state); 201 : : return ret; 202 : : } 203 : : #endif 204 : : 205 : : MP_REGISTER_ROOT_POINTER(mp_sched_item_t sched_queue[MICROPY_SCHEDULER_DEPTH]); 206 : : 207 : : #endif // MICROPY_ENABLE_SCHEDULER 208 : : 209 : : // Called periodically from the VM or from "waiting" code (e.g. sleep) to 210 : : // process background tasks and pending exceptions (e.g. KeyboardInterrupt). 211 : 35354 : void mp_handle_pending(bool raise_exc) { 212 : : // Handle pending VM abort. 213 : : #if MICROPY_ENABLE_VM_ABORT 214 : : if (MP_STATE_VM(vm_abort) && mp_thread_is_main_thread()) { 215 : : MP_STATE_VM(vm_abort) = false; 216 : : if (raise_exc && nlr_get_abort() != NULL) { 217 : : nlr_jump_abort(); 218 : : } 219 : : } 220 : : #endif 221 : : 222 : : // Handle any pending exception. 223 [ + + ]: 35354 : if (MP_STATE_THREAD(mp_pending_exception) != MP_OBJ_NULL) { 224 : 6 : mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); 225 : 6 : mp_obj_t obj = MP_STATE_THREAD(mp_pending_exception); 226 [ + - ]: 6 : if (obj != MP_OBJ_NULL) { 227 : 6 : MP_STATE_THREAD(mp_pending_exception) = MP_OBJ_NULL; 228 [ + + ]: 6 : if (raise_exc) { 229 : 4 : MICROPY_END_ATOMIC_SECTION(atomic_state); 230 : 4 : nlr_raise(obj); 231 : : } 232 : : } 233 : 2 : MICROPY_END_ATOMIC_SECTION(atomic_state); 234 : : } 235 : : 236 : : // Handle any pending callbacks. 237 : : #if MICROPY_ENABLE_SCHEDULER 238 [ + + ]: 35346 : if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) { 239 : 24012 : mp_sched_run_pending(); 240 : : } 241 : : #endif 242 : 35348 : }