Skip to content

Commit 83827e8

Browse files
committed
stm32/uart: Fix race conditions and clearing status in IRQ handler.
Prior to this commit IRQs on STM32F4 could be lost because SR is cleared by reading SR then reading DR. For example, if both RXNE and IDLE IRQs were active upon entry to the IRQ handler, then IDLE is lost because the code that handles RXNE comes first and accidentally clears SR (by reading SR then DR to get the incoming character). This commit fixes this problem by making the IRQ handler more atomic in the following operations: - get current IRQ status flags - deal with RX character - clear remaining status flags - call user handler On the STM32F4 it's very hard to get this right because the only way to clear IRQ status flags is to read SR then DR, but the read of DR may read some data which should remain in the register until the user wants to read it. And it won't work to cache the read because RTS/CTS flow control will then not work. So instead the new code disables interrupts if the DR is full and waits for the user to read it before reenabling the interrupts. Fixes issue mentioned in adafruit#4599 and adafruit#6082. Signed-off-by: Damien George <damien@micropython.org>
1 parent 07ea1af commit 83827e8

File tree

1 file changed

+53
-26
lines changed

1 file changed

+53
-26
lines changed

ports/stm32/uart.c

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@
4040

4141
#if defined(STM32F4)
4242
#define UART_RXNE_IS_SET(uart) ((uart)->SR & USART_SR_RXNE)
43-
#elif defined(STM32H7)
44-
#define UART_RXNE_IS_SET(uart) ((uart)->ISR & USART_ISR_RXNE_RXFNE)
4543
#else
44+
#if defined(STM32H7)
45+
#define USART_ISR_RXNE USART_ISR_RXNE_RXFNE
46+
#endif
4647
#define UART_RXNE_IS_SET(uart) ((uart)->ISR & USART_ISR_RXNE)
4748
#endif
49+
4850
#define UART_RXNE_IT_EN(uart) do { (uart)->CR1 |= USART_CR1_RXNEIE; } while (0)
4951
#define UART_RXNE_IT_DIS(uart) do { (uart)->CR1 &= ~USART_CR1_RXNEIE; } while (0)
5052

@@ -877,7 +879,17 @@ int uart_rx_char(pyb_uart_obj_t *self) {
877879
self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set
878880
return data;
879881
#else
880-
return self->uartx->DR & self->char_mask;
882+
int data = self->uartx->DR & self->char_mask;
883+
// Re-enable any IRQs that were disabled in uart_irq_handler because SR couldn't
884+
// be cleared there (clearing SR in uart_irq_handler required reading DR which
885+
// may have lost a character).
886+
if (self->mp_irq_trigger & UART_FLAG_RXNE) {
887+
self->uartx->CR1 |= USART_CR1_RXNEIE;
888+
}
889+
if (self->mp_irq_trigger & UART_FLAG_IDLE) {
890+
self->uartx->CR1 |= USART_CR1_IDLEIE;
891+
}
892+
return data;
881893
#endif
882894
}
883895
}
@@ -982,7 +994,10 @@ void uart_tx_strn(pyb_uart_obj_t *uart_obj, const char *str, uint len) {
982994
uart_tx_data(uart_obj, str, len, &errcode);
983995
}
984996

985-
// this IRQ handler is set up to handle RXNE interrupts only
997+
// This IRQ handler is set up to handle RXNE, IDLE and ORE interrupts only.
998+
// Notes:
999+
// - ORE (overrun error) is tied to the RXNE IRQ line.
1000+
// - On STM32F4 the IRQ flags are cleared by reading SR then DR.
9861001
void uart_irq_handler(mp_uint_t uart_id) {
9871002
// get the uart object
9881003
pyb_uart_obj_t *self = MP_STATE_PORT(pyb_uart_obj_all)[uart_id - 1];
@@ -993,16 +1008,28 @@ void uart_irq_handler(mp_uint_t uart_id) {
9931008
return;
9941009
}
9951010

996-
if (UART_RXNE_IS_SET(self->uartx)) {
1011+
// Capture IRQ status flags.
1012+
#if defined(STM32F4)
1013+
self->mp_irq_flags = self->uartx->SR;
1014+
bool rxne_is_set = self->mp_irq_flags & USART_SR_RXNE;
1015+
bool did_clear_sr = false;
1016+
#else
1017+
self->mp_irq_flags = self->uartx->ISR;
1018+
bool rxne_is_set = self->mp_irq_flags & USART_ISR_RXNE;
1019+
#endif
1020+
1021+
// Process RXNE flag, either read the character or disable the interrupt.
1022+
if (rxne_is_set) {
9971023
if (self->read_buf_len != 0) {
9981024
uint16_t next_head = (self->read_buf_head + 1) % self->read_buf_len;
9991025
if (next_head != self->read_buf_tail) {
10001026
// only read data if room in buf
10011027
#if defined(STM32F0) || defined(STM32F7) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7) || defined(STM32WB)
10021028
int data = self->uartx->RDR; // clears UART_FLAG_RXNE
1003-
self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set
10041029
#else
1030+
self->mp_irq_flags = self->uartx->SR; // resample to get any new flags since next read of DR will clear SR
10051031
int data = self->uartx->DR; // clears UART_FLAG_RXNE
1032+
did_clear_sr = true;
10061033
#endif
10071034
data &= self->char_mask;
10081035
if (self->attached_to_repl && data == mp_interrupt_char) {
@@ -1019,32 +1046,32 @@ void uart_irq_handler(mp_uint_t uart_id) {
10191046
} else { // No room: leave char in buf, disable interrupt
10201047
UART_RXNE_IT_DIS(self->uartx);
10211048
}
1049+
} else {
1050+
// No buffering, disable interrupt.
1051+
UART_RXNE_IT_DIS(self->uartx);
10221052
}
10231053
}
1024-
// If RXNE is clear but ORE set then clear the ORE flag (it's tied to RXNE IRQ)
1025-
#if defined(STM32F4)
1026-
else if (self->uartx->SR & USART_SR_ORE) {
1027-
(void)self->uartx->DR;
1028-
}
1029-
#else
1030-
else if (self->uartx->ISR & USART_ISR_ORE) {
1031-
self->uartx->ICR = USART_ICR_ORECF;
1032-
}
1033-
#endif
10341054

1035-
// Set user IRQ flags
1036-
self->mp_irq_flags = 0;
1055+
// Clear other interrupt flags that can trigger this IRQ handler.
10371056
#if defined(STM32F4)
1038-
if (self->uartx->SR & USART_SR_IDLE) {
1039-
(void)self->uartx->SR;
1040-
(void)self->uartx->DR;
1041-
self->mp_irq_flags |= UART_FLAG_IDLE;
1057+
if (did_clear_sr) {
1058+
// SR was cleared above. Re-enable IDLE if it should be enabled.
1059+
if (self->mp_irq_trigger & UART_FLAG_IDLE) {
1060+
self->uartx->CR1 |= USART_CR1_IDLEIE;
1061+
}
1062+
} else {
1063+
// On STM32F4 the only way to clear flags is to read SR then DR, but that may
1064+
// lead to a loss of data in DR. So instead the IRQs are disabled.
1065+
if (self->mp_irq_flags & USART_SR_IDLE) {
1066+
self->uartx->CR1 &= ~USART_CR1_IDLEIE;
1067+
}
1068+
if (self->mp_irq_flags & USART_SR_ORE) {
1069+
// ORE is tied to RXNE so that must be disabled.
1070+
self->uartx->CR1 &= ~USART_CR1_RXNEIE;
1071+
}
10421072
}
10431073
#else
1044-
if (self->uartx->ISR & USART_ISR_IDLE) {
1045-
self->uartx->ICR = USART_ICR_IDLECF;
1046-
self->mp_irq_flags |= UART_FLAG_IDLE;
1047-
}
1074+
self->uartx->ICR = self->mp_irq_flags & (USART_ICR_IDLECF | USART_ICR_ORECF);
10481075
#endif
10491076

10501077
// Check the flags to see if the user handler should be called

0 commit comments

Comments
 (0)