RXTO not generated by Bluetooth LE UART service application. SDK v3.3.0

Dear Everyone,

We're using the PAN B611 evaluation board (ENW89861AXKF) which features a PAN B611-1C BluetoothRegistered Low Energy (LE) module which is based on the Nordic Semiconductor nRF54L15 single-chip controller.

We have followed the instructions from:
https://pideu.panasonic.de/development-hub/panb611/software/getting_started/

We have imported and built Bluetooth LE UART service. SDK version is v3.3.0

This seem to be the source code imported by nrfconnect sdk:
github.com/.../main.c

The service seems to work, however one specific thing doesn't which may be required in our future application.

We have USB-UART to connect board with a PC. Using the Tera Term we can write messages and once we insert '\r' or \n' we see message in correct characteristic. However we can't reliably get event UART_RX_DISABLED.

static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
	ARG_UNUSED(dev);

	static size_t aborted_len;
	struct uart_data_t *buf;
	static uint8_t *aborted_buf;
	static bool disable_req;

	switch (evt->type) {
	case UART_TX_DONE:
		LOG_DBG("UART_TX_DONE");
		if ((evt->data.tx.len == 0) ||
		    (!evt->data.tx.buf)) {
			return;
		}

		if (aborted_buf) {
			buf = CONTAINER_OF(aborted_buf, struct uart_data_t,
					   data[0]);
			aborted_buf = NULL;
			aborted_len = 0;
		} else {
			buf = CONTAINER_OF(evt->data.tx.buf, struct uart_data_t,
					   data[0]);
		}

		k_free(buf);

		buf = k_fifo_get(&fifo_uart_tx_data, K_NO_WAIT);
		if (!buf) {
			return;
		}

		if (uart_tx(uart, buf->data, buf->len, SYS_FOREVER_MS)) {
			LOG_WRN("Failed to send data over UART");
		}

		break;

	case UART_RX_RDY:
		LOG_DBG("UART_RX_RDY");
		buf = CONTAINER_OF(evt->data.rx.buf, struct uart_data_t, data[0]);
		buf->len += evt->data.rx.len;

		if (disable_req) {
			return;
		}

		if ((evt->data.rx.buf[buf->len - 1] == '\n') ||
		    (evt->data.rx.buf[buf->len - 1] == '\r')) {
			disable_req = true;
			uart_rx_disable(uart);
		}

		break;

	case UART_RX_DISABLED:
		LOG_DBG("UART_RX_DISABLED");
		disable_req = false;

		buf = k_malloc(sizeof(*buf));
		if (buf) {
			buf->len = 0;
		} else {
			LOG_WRN("Not able to allocate UART receive buffer");
			k_work_reschedule(&uart_work, UART_WAIT_FOR_BUF_DELAY);
			return;
		}

		uart_rx_enable(uart, buf->data, sizeof(buf->data),
			       UART_WAIT_FOR_RX);

		break;

	case UART_RX_BUF_REQUEST:
		LOG_DBG("UART_RX_BUF_REQUEST");
		buf = k_malloc(sizeof(*buf));
		if (buf) {
			buf->len = 0;
			uart_rx_buf_rsp(uart, buf->data, sizeof(buf->data));
		} else {
			LOG_WRN("Not able to allocate UART receive buffer");
		}

		break;

	case UART_RX_BUF_RELEASED:
		LOG_DBG("UART_RX_BUF_RELEASED");
		buf = CONTAINER_OF(evt->data.rx_buf.buf, struct uart_data_t,
				   data[0]);

		if (buf->len > 0) {
			k_fifo_put(&fifo_uart_rx_data, buf);
		} else {
			k_free(buf);
		}

		break;

	case UART_TX_ABORTED:
		LOG_DBG("UART_TX_ABORTED");
		if (!aborted_buf) {
			aborted_buf = (uint8_t *)evt->data.tx.buf;
		}

		aborted_len += evt->data.tx.len;
		buf = CONTAINER_OF((void *)aborted_buf, struct uart_data_t,
				   data);

		uart_tx(uart, &buf->data[aborted_len],
			buf->len - aborted_len, SYS_FOREVER_MS);

		break;

	default:
		break;
	}
}

Please see the two paths below:

a) without any breakpoints in firmware the app enters RX_RDY once, then it sets disable_req and then it never enters UART_RX_DISABLED. The bridge works because of implementation of UART_RX_BUF_RELEASED as we keep feeding released frames to queue

b) If we place brakepoint in RX_RDY, UART_RX_BUF_REQUEST and UART_RX_DISABLED then we properly enter UART_RX_DISABLED.

This leads us to believe there is a race condition somewhere. The issue seems to be fixed by delegating uart_rx_disable calto separate zephyr work executed after we leave ISR.

Similarly the issue can be fixed by adding an if (disable_req){return} in UART_RX_BUF_REQUEST which further leads me to believe that the internal state machine is disrupted by calling 
uart_rx_buf_rsp after uart_rx_disable.

Has anyone encountered similar issue before? Do you think that one of our workarounds is safe way to go?

Best Regards and thanks in advance for any future help!

Jakub

  • Hi Jakub,

    Your diagnosis seems correct. This is a real race in the sample, not in your code. Calling uart_rx_disable() from UART_RX_RDY while still answering UART_RX_BUF_REQUEST with a fresh buffer races the driver's state machine: the new buffer rescues the receive operation that was about to stop. On older UARTE blocks the timing usually hid this; on the nRF54L15 the ordering is tighter, so it surfaces.

    I would go with the if (disable_req) return; guard in UART_RX_BUF_REQUEST. Place it at the very top, before the k_malloc. Not responding to the buffer request is the documented Zephyr way to end reception: the driver completes the current buffer, fires UART_RX_BUF_RELEASED (your handler already pushes it to the FIFO), then fires UART_RX_DISABLED cleanly. Putting the guard before the allocation avoids leaking a buffer on the stop path.

    Deferring uart_rx_disable() to a work item also works, but it does not fix the root cause. You still need a disable_req guard in UART_RX_RDY, and you add a context switch for no real benefit. The early-return in UART_RX_BUF_REQUEST is the cleaner change.

     I will raise this internally , thanks for brining this to light.

  • Hello Suheel,

    Thank you very much for the response! We'll use the option you suggested.

    Best Regards,
    Jakub

Related