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 #4599 and #6082.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George 2021-10-28 12:36:45 +11:00
parent 07ea1afe74
commit 83827e8e63
1 changed files with 53 additions and 26 deletions

View File

@ -40,11 +40,13 @@
#if defined(STM32F4)
#define UART_RXNE_IS_SET(uart) ((uart)->SR & USART_SR_RXNE)
#elif defined(STM32H7)
#define UART_RXNE_IS_SET(uart) ((uart)->ISR & USART_ISR_RXNE_RXFNE)
#else
#if defined(STM32H7)
#define USART_ISR_RXNE USART_ISR_RXNE_RXFNE
#endif
#define UART_RXNE_IS_SET(uart) ((uart)->ISR & USART_ISR_RXNE)
#endif
#define UART_RXNE_IT_EN(uart) do { (uart)->CR1 |= USART_CR1_RXNEIE; } while (0)
#define UART_RXNE_IT_DIS(uart) do { (uart)->CR1 &= ~USART_CR1_RXNEIE; } while (0)
@ -877,7 +879,17 @@ int uart_rx_char(pyb_uart_obj_t *self) {
self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set
return data;
#else
return self->uartx->DR & self->char_mask;
int data = self->uartx->DR & self->char_mask;
// Re-enable any IRQs that were disabled in uart_irq_handler because SR couldn't
// be cleared there (clearing SR in uart_irq_handler required reading DR which
// may have lost a character).
if (self->mp_irq_trigger & UART_FLAG_RXNE) {
self->uartx->CR1 |= USART_CR1_RXNEIE;
}
if (self->mp_irq_trigger & UART_FLAG_IDLE) {
self->uartx->CR1 |= USART_CR1_IDLEIE;
}
return data;
#endif
}
}
@ -982,7 +994,10 @@ void uart_tx_strn(pyb_uart_obj_t *uart_obj, const char *str, uint len) {
uart_tx_data(uart_obj, str, len, &errcode);
}
// this IRQ handler is set up to handle RXNE interrupts only
// This IRQ handler is set up to handle RXNE, IDLE and ORE interrupts only.
// Notes:
// - ORE (overrun error) is tied to the RXNE IRQ line.
// - On STM32F4 the IRQ flags are cleared by reading SR then DR.
void uart_irq_handler(mp_uint_t uart_id) {
// get the uart object
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) {
return;
}
if (UART_RXNE_IS_SET(self->uartx)) {
// Capture IRQ status flags.
#if defined(STM32F4)
self->mp_irq_flags = self->uartx->SR;
bool rxne_is_set = self->mp_irq_flags & USART_SR_RXNE;
bool did_clear_sr = false;
#else
self->mp_irq_flags = self->uartx->ISR;
bool rxne_is_set = self->mp_irq_flags & USART_ISR_RXNE;
#endif
// Process RXNE flag, either read the character or disable the interrupt.
if (rxne_is_set) {
if (self->read_buf_len != 0) {
uint16_t next_head = (self->read_buf_head + 1) % self->read_buf_len;
if (next_head != self->read_buf_tail) {
// only read data if room in buf
#if defined(STM32F0) || defined(STM32F7) || defined(STM32L0) || defined(STM32L4) || defined(STM32H7) || defined(STM32WB)
int data = self->uartx->RDR; // clears UART_FLAG_RXNE
self->uartx->ICR = USART_ICR_ORECF; // clear ORE if it was set
#else
self->mp_irq_flags = self->uartx->SR; // resample to get any new flags since next read of DR will clear SR
int data = self->uartx->DR; // clears UART_FLAG_RXNE
did_clear_sr = true;
#endif
data &= self->char_mask;
if (self->attached_to_repl && data == mp_interrupt_char) {
@ -1019,32 +1046,32 @@ void uart_irq_handler(mp_uint_t uart_id) {
} else { // No room: leave char in buf, disable interrupt
UART_RXNE_IT_DIS(self->uartx);
}
} else {
// No buffering, disable interrupt.
UART_RXNE_IT_DIS(self->uartx);
}
}
// If RXNE is clear but ORE set then clear the ORE flag (it's tied to RXNE IRQ)
#if defined(STM32F4)
else if (self->uartx->SR & USART_SR_ORE) {
(void)self->uartx->DR;
}
#else
else if (self->uartx->ISR & USART_ISR_ORE) {
self->uartx->ICR = USART_ICR_ORECF;
}
#endif
// Set user IRQ flags
self->mp_irq_flags = 0;
// Clear other interrupt flags that can trigger this IRQ handler.
#if defined(STM32F4)
if (self->uartx->SR & USART_SR_IDLE) {
(void)self->uartx->SR;
(void)self->uartx->DR;
self->mp_irq_flags |= UART_FLAG_IDLE;
if (did_clear_sr) {
// SR was cleared above. Re-enable IDLE if it should be enabled.
if (self->mp_irq_trigger & UART_FLAG_IDLE) {
self->uartx->CR1 |= USART_CR1_IDLEIE;
}
} else {
// On STM32F4 the only way to clear flags is to read SR then DR, but that may
// lead to a loss of data in DR. So instead the IRQs are disabled.
if (self->mp_irq_flags & USART_SR_IDLE) {
self->uartx->CR1 &= ~USART_CR1_IDLEIE;
}
if (self->mp_irq_flags & USART_SR_ORE) {
// ORE is tied to RXNE so that must be disabled.
self->uartx->CR1 &= ~USART_CR1_RXNEIE;
}
}
#else
if (self->uartx->ISR & USART_ISR_IDLE) {
self->uartx->ICR = USART_ICR_IDLECF;
self->mp_irq_flags |= UART_FLAG_IDLE;
}
self->uartx->ICR = self->mp_irq_flags & (USART_ICR_IDLECF | USART_ICR_ORECF);
#endif
// Check the flags to see if the user handler should be called