Description
Unlike the callback re-init cases, this issue is about in-place UART state updates.
Here the problem is that UART reconfiguration updates shared RX state in place, while RX IRQ delivery is still possible. The IRQ handler can then observe temporary values that belong to the middle of reconfiguration, not to the final UART mode.
Common sequence:
- RX IRQ is enabled for one UART instance
- thread context enters UART mode or callback reconfiguration
- shared RX state is rewritten in place
- RX IRQ arrives in that window
- ISR uses a temporary
data_mask, callback pointer, or callback argument value
Related issues:
Scope
- RIOT source snapshot:
bbe5252d00f98658fc6493ede1a3e9f8f4102f9d (2026-06-08)
- Affected files:
cpu/stm32/periph/uart.c
cpu/gd32v/periph/uart.c
- Grouped cases:
stm32-uart-unique-1-isr-ctx-0-data-mask
stm32-uart-unique-2-isr-ctx-0-data-mask
stm32-uart-unique-3-isr-ctx-0-data-mask
stm32-uart-unique-4-isr-ctx-0-data-mask
stm32-uart-unique-5-isr-ctx-0-data-mask
gd32v-uart-unique-3-isr-ctx-0-data-mask
gd32v-uart-unique-4-isr-ctx-0-data-mask
gd32v-uart-unique-5-isr-ctx-0-rx-cb
gd32v-uart-unique-6-isr-ctx-0-arg
gd32v-uart-unique-8-isr-ctx-0-rx-cb
gd32v-uart-unique-9-isr-ctx-0-data-mask
Representative code paths
- STM32 UART:
uart_mode() -> UART_x_ISR() -> irq_handler()
- GD32V UART mode:
uart_mode() -> _uart_isr() -> _irq_handler()
- GD32V UART callback setup:
uart_init() -> _uart_isr() -> _irq_handler()
Representative source snippets
cpu/stm32/periph/uart.c rewrites IRQ-visible RX state in place:
isr_ctx[uart].rx_cb = rx_cb;
isr_ctx[uart].arg = arg;
isr_ctx[uart].data_mask = 0xFF;
uart_mode() can later update the same data_mask while RX IRQ delivery is possible:
isr_ctx[uart].data_mask = 0xFF;
...
isr_ctx[uart].data_mask = 0x3F;
...
isr_ctx[uart].data_mask = 0x7F;
The RX IRQ handler consumes that state:
isr_ctx[uart].rx_cb(isr_ctx[uart].arg,
(uint8_t)dev(uart)->RDR_REG & isr_ctx[uart].data_mask);
cpu/gd32v/periph/uart.c has the same data_mask pattern:
isr_ctx[uart].data_mask = 0xFF;
...
isr_ctx[uart].data_mask = 0x3F;
...
isr_ctx[uart].data_mask = 0x7F;
isr_ctx[uart].rx_cb(isr_ctx[uart].arg,
(uint8_t)dev(uart)->DATA & isr_ctx[uart].data_mask);
Local evidence
- A representative GD32V UART case was replayed locally with a QEMU-based harness.
- A replay log or minimized reproducer is available for the GD32V UART case.
Expected results
RX IRQ handlers should only observe final stable RX callback/configuration state for the selected UART mode.
Actual results
RX IRQ handlers may observe temporary in-progress state during UART reconfiguration.
Analysis
This group is about in-place shared-state updates, not only re-init.
UART mode or callback reconfiguration rewrites IRQ-visible RX state field by field. If RX IRQ fires in middle of that update, ISR can use a temporary data_mask, callback pointer, or callback argument value that does not match final UART mode.
Suggested Fix
- prevent RX IRQ delivery while shared RX state is being rewritten, or
- build complete new RX state first and switch to it atomically, or
- split callback/config state so ISR never sees partially updated values
Description
Unlike the callback re-init cases, this issue is about in-place UART state updates.
Here the problem is that UART reconfiguration updates shared RX state in place, while RX IRQ delivery is still possible. The IRQ handler can then observe temporary values that belong to the middle of reconfiguration, not to the final UART mode.
Common sequence:
data_mask, callback pointer, or callback argument valueRelated issues:
#22405Scope
bbe5252d00f98658fc6493ede1a3e9f8f4102f9d(2026-06-08)cpu/stm32/periph/uart.ccpu/gd32v/periph/uart.cstm32-uart-unique-1-isr-ctx-0-data-maskstm32-uart-unique-2-isr-ctx-0-data-maskstm32-uart-unique-3-isr-ctx-0-data-maskstm32-uart-unique-4-isr-ctx-0-data-maskstm32-uart-unique-5-isr-ctx-0-data-maskgd32v-uart-unique-3-isr-ctx-0-data-maskgd32v-uart-unique-4-isr-ctx-0-data-maskgd32v-uart-unique-5-isr-ctx-0-rx-cbgd32v-uart-unique-6-isr-ctx-0-arggd32v-uart-unique-8-isr-ctx-0-rx-cbgd32v-uart-unique-9-isr-ctx-0-data-maskRepresentative code paths
uart_mode()->UART_x_ISR()->irq_handler()uart_mode()->_uart_isr()->_irq_handler()uart_init()->_uart_isr()->_irq_handler()Representative source snippets
cpu/stm32/periph/uart.crewrites IRQ-visible RX state in place:uart_mode()can later update the samedata_maskwhile RX IRQ delivery is possible:The RX IRQ handler consumes that state:
cpu/gd32v/periph/uart.chas the samedata_maskpattern:Local evidence
Expected results
RX IRQ handlers should only observe final stable RX callback/configuration state for the selected UART mode.
Actual results
RX IRQ handlers may observe temporary in-progress state during UART reconfiguration.
Analysis
This group is about in-place shared-state updates, not only re-init.
UART mode or callback reconfiguration rewrites IRQ-visible RX state field by field. If RX IRQ fires in middle of that update, ISR can use a temporary
data_mask, callback pointer, or callback argument value that does not match final UART mode.Suggested Fix