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#if MICROPY_KBD_EXCEPTION
32// This function may be called asynchronously at any time so only do the bare minimum.
33void MICROPY_WRAP_MP_KEYBOARD_INTERRUPT(mp_keyboard_interrupt)(void) {
34 MP_STATE_VM(mp_kbd_exception).traceback_data = NULL;
35 MP_STATE_VM(mp_pending_exception) = MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception));
36 #if MICROPY_ENABLE_SCHEDULER
37 if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
38 MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
39 }
40 #endif
41}
42#endif
43
44#if MICROPY_ENABLE_SCHEDULER
45
46#define IDX_MASK(i) ((i) & (MICROPY_SCHEDULER_DEPTH - 1))
47
48// This is a macro so it is guaranteed to be inlined in functions like
49// mp_sched_schedule that may be located in a special memory region.
50#define mp_sched_full() (mp_sched_num_pending() == MICROPY_SCHEDULER_DEPTH)
51
52static inline bool mp_sched_empty(void) {
53 MP_STATIC_ASSERT(MICROPY_SCHEDULER_DEPTH <= 255); // MICROPY_SCHEDULER_DEPTH must fit in 8 bits
54 MP_STATIC_ASSERT((IDX_MASK(MICROPY_SCHEDULER_DEPTH) == 0)); // MICROPY_SCHEDULER_DEPTH must be a power of 2
55
56 return mp_sched_num_pending() == 0;
57}
58
59// A variant of this is inlined in the VM at the pending exception check
60void mp_handle_pending(bool raise_exc) {
61 if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
62 mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
63 // Re-check state is still pending now that we're in the atomic section.
64 if (MP_STATE_VM(sched_state) == MP_SCHED_PENDING) {
65 mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
66 if (obj != MP_OBJ_NULL) {
67 MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
68 if (!mp_sched_num_pending()) {
69 MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
70 }
71 if (raise_exc) {
72 MICROPY_END_ATOMIC_SECTION(atomic_state);
73 nlr_raise(obj);
74 }
75 }
76 mp_handle_pending_tail(atomic_state);
77 } else {
78 MICROPY_END_ATOMIC_SECTION(atomic_state);
79 }
80 }
81}
82
83// This function should only be called by mp_handle_pending,
84// or by the VM's inlined version of that function.
85void mp_handle_pending_tail(mp_uint_t atomic_state) {
86 MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;
87 if (!mp_sched_empty()) {
88 mp_sched_item_t item = MP_STATE_VM(sched_queue)[MP_STATE_VM(sched_idx)];
89 MP_STATE_VM(sched_idx) = IDX_MASK(MP_STATE_VM(sched_idx) + 1);
90 --MP_STATE_VM(sched_len);
91 MICROPY_END_ATOMIC_SECTION(atomic_state);
92 mp_call_function_1_protected(item.func, item.arg);
93 } else {
94 MICROPY_END_ATOMIC_SECTION(atomic_state);
95 }
96 mp_sched_unlock();
97}
98
99void mp_sched_lock(void) {
100 mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
101 if (MP_STATE_VM(sched_state) < 0) {
102 --MP_STATE_VM(sched_state);
103 } else {
104 MP_STATE_VM(sched_state) = MP_SCHED_LOCKED;
105 }
106 MICROPY_END_ATOMIC_SECTION(atomic_state);
107}
108
109void mp_sched_unlock(void) {
110 mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
111 assert(MP_STATE_VM(sched_state) < 0);
112 if (++MP_STATE_VM(sched_state) == 0) {
113 // vm became unlocked
114 if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL || mp_sched_num_pending()) {
115 MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
116 } else {
117 MP_STATE_VM(sched_state) = MP_SCHED_IDLE;
118 }
119 }
120 MICROPY_END_ATOMIC_SECTION(atomic_state);
121}
122
123bool MICROPY_WRAP_MP_SCHED_SCHEDULE(mp_sched_schedule)(mp_obj_t function, mp_obj_t arg) {
124 mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
125 bool ret;
126 if (!mp_sched_full()) {
127 if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
128 MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
129 }
130 uint8_t iput = IDX_MASK(MP_STATE_VM(sched_idx) + MP_STATE_VM(sched_len)++);
131 MP_STATE_VM(sched_queue)[iput].func = function;
132 MP_STATE_VM(sched_queue)[iput].arg = arg;
133 ret = true;
134 } else {
135 // schedule queue is full
136 ret = false;
137 }
138 MICROPY_END_ATOMIC_SECTION(atomic_state);
139 return ret;
140}
141
142#else // MICROPY_ENABLE_SCHEDULER
143
144// A variant of this is inlined in the VM at the pending exception check
145void mp_handle_pending(bool raise_exc) {
146 if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) {
147 mp_obj_t obj = MP_STATE_VM(mp_pending_exception);
148 MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
149 if (raise_exc) {
150 nlr_raise(obj);
151 }
152 }
153}
154
155#endif // MICROPY_ENABLE_SCHEDULER
156