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