Skip to content

Commit ece950d

Browse files
committed
stm32/pendsv: Remove preemptive keyboard interrupt via PendSV.
Since the very beginning, the stm32 port (first called stm, then stmhal now stm32) has had a special keyboard interrupt feature which works by using PendSV to break out of any running code. This preemptive ctrl-C was added long ago in commit 01156d5. The stm32 port still uses that code, and current does this: - If ctrl-C is received on UART or USB then `mp_sched_keyboard_interrupt()` is called (like all other ports) to set a flag for the VM to see, and then the VM (or any loop calling `mp_handle_pending(true)`) will eventually handle the `KeyboardInterrupt` exception, raising it via NLR. - If another ctrl-C is received while the existing scheduled keyboard interrupt is still pending (ie the VM has not yet processed it) then a special hard NLR jump will activate, that preempts the calling code. Within the PendSV interrupt the stack is adjusted and an NLR jump is made to the most recent `nlr_push()` location. This is like a normal NLR except it is called from an interrupt context and completely annihilates the code that was interrupted by the IRQ. The reason for the preemptive interrupt was to handle ctrl-C before the VM was able to handle it. Eventually a mechanism (that's in use today by all ports) was added to the VM and runtime to be able to check for pending interrupts. Then the stm32 port was updated to use this mechanism, with a fallback to the old preemptive way if a second ctrl-C was received (without the first one being processed). This preemptive NLR jump is problematic because it can interrupt long-running instructions (eg store multiple, usually used at the end of a function to restore registers and return). If such an instruction is interrupted the CPU remembers that with some flags, and can resume the long-running instruction when the interrupt finishes. But the preemptive NLR does a long jump to different code at thread level and so the long-running interrupt is never resumed. This leads to a CPU fault. This fault has been previously reported in issues micropython#3807 and micropython#3842 (see also issue micropython#294). It's now possible to easily reproduce this problem, since commit 69c25ea. Running the test suite over and over again on any stm32 board will eventually crash the board (it can happen on a PYBv1.x, but it happens more regularly on PYBD-SF2/6). The point is, a skipped test now soft resets the board and so the board must run `boot.py` again. The test runner may then interrupt the execution of `boot.py` with the double-ctrl-C that it sends (in `tools/pyboard.py`, `enter_raw_repl()`) in order to get the board into a known good state for the next test. If the timing is right, this can trigger the preemptive PendSV in an unfortunate location and hard fault the board. The fix in this commit is to just remove the preemptive NLR jump feature. No other port has this feature and it's not needed, ctrl-C works very well on those ports. Preemptive NLR jump is a very dangerous thing (eg it may interrupt and break out of an external SPI flash operation when reading code from a filesystem) and is obviously buggy. With this commit, stm32 borads no longer hard fault when running the test suite (but it does leave an issue, the tests can still interrupt `boot.py` with a single ctrl-C; that will be fixed separately). An alternative to this commit would be to clear the CPU state for the long-running instruction as suggested in issue micropython#3842. But it's much simpler to just remove this code, which is now unnecessary and can have other problems as per issue micropython#294. Signed-off-by: Damien George <damien@micropython.org>
1 parent 44ed1c2 commit ece950d

File tree

5 files changed

+4
-56
lines changed

5 files changed

+4
-56
lines changed

ports/stm32/boards/common_isr_ram/common_isr.ld

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
flash (in main.c) along with the isr_vector above.
1515
*/
1616
. = ALIGN(4);
17-
*(.text.pendsv_kbd_intr)
17+
*(.text.mp_sched_keyboard_interrupt)
1818
*(.text.pendsv_schedule_dispatch)
1919
*(.text.storage_systick_callback)
2020
*(.text.SysTick_Handler)

ports/stm32/pendsv.c

+1-52
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,6 @@
3131
#include "pendsv.h"
3232
#include "irq.h"
3333

34-
// This variable is used to save the exception object between a ctrl-C and the
35-
// PENDSV call that actually raises the exception. It must be non-static
36-
// otherwise gcc-5 optimises it away. It can point to the heap but is not
37-
// traced by GC. This is okay because we only ever set it to
38-
// mp_kbd_exception which is in the root-pointer set.
39-
void *pendsv_object;
40-
4134
#if defined(PENDSV_DISPATCH_NUM_SLOTS)
4235
uint32_t pendsv_dispatch_active;
4336
pendsv_dispatch_t pendsv_dispatch_table[PENDSV_DISPATCH_NUM_SLOTS];
@@ -51,24 +44,6 @@ void pendsv_init(void) {
5144
NVIC_SetPriority(PendSV_IRQn, IRQ_PRI_PENDSV);
5245
}
5346

54-
// Call this function to raise a pending exception during an interrupt.
55-
// It will first try to raise the exception "softly" by setting the
56-
// mp_pending_exception variable and hoping that the VM will notice it.
57-
// If this function is called a second time (ie with the mp_pending_exception
58-
// variable already set) then it will force the exception by using the hardware
59-
// PENDSV feature. This will wait until all interrupts are finished then raise
60-
// the given exception object using nlr_jump in the context of the top-level
61-
// thread.
62-
void pendsv_kbd_intr(void) {
63-
if (MP_STATE_MAIN_THREAD(mp_pending_exception) == MP_OBJ_NULL) {
64-
mp_sched_keyboard_interrupt();
65-
} else {
66-
MP_STATE_MAIN_THREAD(mp_pending_exception) = MP_OBJ_NULL;
67-
pendsv_object = &MP_STATE_VM(mp_kbd_exception);
68-
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
69-
}
70-
}
71-
7247
#if defined(PENDSV_DISPATCH_NUM_SLOTS)
7348
void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f) {
7449
pendsv_dispatch_table[slot] = f;
@@ -90,10 +65,7 @@ void pendsv_dispatch_handler(void) {
9065
__attribute__((naked)) void PendSV_Handler(void) {
9166
// Handle a PendSV interrupt
9267
//
93-
// For the case of an asynchronous exception, re-jig the
94-
// stack so that when we return from this interrupt handler
95-
// it returns instead to nlr_jump with argument pendsv_object
96-
// note that stack has a different layout if DEBUG is enabled
68+
// Calls any pending functions in pendsv_dispatch_table.
9769
//
9870
// For the case of a thread switch, swap stacks.
9971
//
@@ -132,27 +104,6 @@ __attribute__((naked)) void PendSV_Handler(void) {
132104
".no_dispatch:\n"
133105
#endif
134106

135-
// Check if there is an active object to throw via nlr_jump
136-
"ldr r1, pendsv_object_ptr\n"
137-
"ldr r0, [r1]\n"
138-
"cmp r0, #0\n"
139-
"beq .no_obj\n"
140-
#if defined(PENDSV_DEBUG)
141-
"str r0, [sp, #8]\n" // store to r0 on stack
142-
#else
143-
"str r0, [sp, #0]\n" // store to r0 on stack
144-
#endif
145-
"mov r0, #0\n"
146-
"str r0, [r1]\n" // clear pendsv_object
147-
"ldr r0, nlr_jump_ptr\n"
148-
#if defined(PENDSV_DEBUG)
149-
"str r0, [sp, #32]\n" // store to pc on stack
150-
#else
151-
"str r0, [sp, #24]\n" // store to pc on stack
152-
#endif
153-
"bx lr\n" // return from interrupt; will return to nlr_jump
154-
".no_obj:\n" // pendsv_object==NULL
155-
156107
#if MICROPY_PY_THREAD
157108
// Do a thread context switch
158109
"push {r4-r11, lr}\n"
@@ -178,7 +129,5 @@ __attribute__((naked)) void PendSV_Handler(void) {
178129
#if defined(PENDSV_DISPATCH_NUM_SLOTS)
179130
"pendsv_dispatch_active_ptr: .word pendsv_dispatch_active\n"
180131
#endif
181-
"pendsv_object_ptr: .word pendsv_object\n"
182-
"nlr_jump_ptr: .word nlr_jump\n"
183132
);
184133
}

ports/stm32/pendsv.h

-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ enum {
5151
typedef void (*pendsv_dispatch_t)(void);
5252

5353
void pendsv_init(void);
54-
void pendsv_kbd_intr(void);
5554
void pendsv_schedule_dispatch(size_t slot, pendsv_dispatch_t f);
5655

5756
#endif // MICROPY_INCLUDED_STM32_PENDSV_H

ports/stm32/uart.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ void uart_irq_handler(mp_uint_t uart_id) {
12291229
data &= self->char_mask;
12301230
if (self->attached_to_repl && data == mp_interrupt_char) {
12311231
// Handle interrupt coming in on a UART REPL
1232-
pendsv_kbd_intr();
1232+
mp_sched_keyboard_interrupt();
12331233
} else {
12341234
if (self->char_width == CHAR_WIDTH_9BIT) {
12351235
((uint16_t *)self->read_buf)[self->read_buf_head] = data;

ports/stm32/usbd_cdc_interface.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ int8_t usbd_cdc_receive(usbd_cdc_state_t *cdc_in, size_t len) {
315315
// copy the incoming data into the circular buffer
316316
for (const uint8_t *src = cdc->rx_packet_buf, *top = cdc->rx_packet_buf + len; src < top; ++src) {
317317
if (cdc->attached_to_repl && *src == mp_interrupt_char) {
318-
pendsv_kbd_intr();
318+
mp_sched_keyboard_interrupt();
319319
} else {
320320
uint16_t next_put = (cdc->rx_buf_put + 1) & (MICROPY_HW_USB_CDC_RX_DATA_SIZE - 1);
321321
if (next_put == cdc->rx_buf_get) {

0 commit comments

Comments
 (0)