Hello!
I am stress-testing the nRF54L15 devkit by sending it random-sized chunks of data over UART at 921600 baud, which the devkit code receives using the asynchronous API in Zephyr 4.0.99.
The test always fails after some time, the symptom is that uart_rx_buf_rsp() fails with -EACCES when called from my UART_RX_BUF_REQUEST handler.
By logging the events, I usually see this pattern:
UART_RX_RDY
UART_RX_BUF_RELEASED
UART_RX_BUF_REQUEST
UART_RX_RDY
UART_RX_BUF_RELEASED
UART_RX_BUF_REQUEST
UART_RX_RDY
UART_RX_BUF_RELEASED
UART_RX_BUF_REQUEST
However, these are the last events before the failure:
UART_RX_RDY (len=7)
UART_RX_BUF_RELEASED
UART_RX_BUF_REQUEST
UART_RX_RDY (len=7)
UART_RX_BUF_RELEASED
UART_RX_BUF_REQUEST
UART_RX_RDY (len=64)
UART_RX_BUF_RELEASED (No REQUEST after this one)
UART_RX_RDY (len=1)
UART_RX_BUF_RELEASED
UART_RX_BUF_REQUEST (Here uart_rx_buf_rsp() fails with -EACCES)
I'm new to the driver code but this is my investigation:
The driver source is uart_nrfx_uarte.c in my case, so the implementation of uart_rx_buf_rsp() is uarte_nrfx_rx_buf_rsp(), which fails with -EACCES if (async_rx->buf == NULL).
The ISR handler seems to be uarte_nrfx_isr_async(). When receiving data, it calls endrx_isr(), which issues UART_RX_RDY and UART_RX_BUF_RELEASED and then shifts the buffers:
async_rx->buf = async_rx->next_buf;
async_rx->next_buf = NULL;
Next, UART_RX_BUF_REQUEST is typically called, and uart_rx_buf_rsp() sets next_buf.
Therefore, if endrx_isr() is called twice without issuing UART_RX_BUF_REQUEST inbetween (as in my log), both buf and next_buf will be NULL, which makes uart_rx_buf_rsp() fail on the next UART_RX_BUF_REQUEST, because buf is NULL.
Why does it happen? This comment in uarte_nrfx_isr_async() seems to explain it:
/* RXSTARTED must be handled after ENDRX because it starts the RX timeout
* and if order is swapped then ENDRX will stop this timeout.
* Skip if ENDRX is set when RXSTARTED is set. It means that
* ENDRX occurred after check for ENDRX in isr which may happen when
* UARTE interrupt got preempted. Events are not cleared
* and isr will be called again. ENDRX will be handled first.
*/
if (<RXSTARTED and not ENDRX>) {
<issue UART_RX_BUF_REQUEST>
}
Indeed, it happened when receiving a single byte at a high baud rate, so the new ENDRX may have happened during the same ISR call, which prevents UART_RX_BUF_REQUEST from being issued, and it seems the ENDRX was still pending because the next UART_RX_RDY came a few microseconds later.
(We use an Rx timeout of 16 microseconds which is in the ballpark of a single byte. We were getting framing errors before when using a longer timeout.)
So it seems the problem with postponing UART_RX_BUF_REQUEST is that we run out of buffers. Is there some consideration preventing you from always issuing the event after a read?
We are on a tight deadline to build a prototype, so I'm also looking for workarounds. One option could be to detect the missed UART_RX_BUF_REQUEST and in that case call uart_rx_buf_rsp() from UART_RX_BUF_RELEASED instead, even if it goes against the documentation.