fix(uart): correct uart_get_tx_buffer_free_size calculation

Modified a bit on the TX ring buffer push size logic

 Closes https://github.com/espressif/esp-idf/issues/15859
This commit is contained in:
Song Ruo Jing
2025-10-17 16:09:26 +08:00
parent fb20e147d5
commit 9629b2fb6a
3 changed files with 158 additions and 30 deletions
@@ -605,7 +605,9 @@ esp_err_t uart_flush_input(uart_port_t uart_num);
esp_err_t uart_get_buffered_data_len(uart_port_t uart_num, size_t* size);
/**
* @brief UART get TX ring buffer free space size
* @brief UART get TX ring buffer free space size for the next data to be enqueued
*
* It returns the tight conservative bound for NOSPLIT ring buffer overall enqueueable payload across up to two chunks.
*
* @param uart_num UART port number, the max port number is (UART_NUM_MAX -1).
* @param size Pointer of size_t to accept the free space size
+97 -21
View File
@@ -143,7 +143,6 @@ typedef struct {
bool coll_det_flg; /*!< UART collision detection flag */
bool rx_always_timeout_flg; /*!< UART always detect rx timeout flag */
int rx_buffered_len; /*!< UART cached data length */
int rx_buf_size; /*!< RX ring buffer size */
bool rx_buffer_full_flg; /*!< RX ring buffer full flag. */
uint8_t *rx_data_buf; /*!< Data buffer to stash FIFO data*/
uint8_t rx_stash_len; /*!< stashed data length.(When using flow control, after reading out FIFO data, if we fail to push to buffer, we can just stash them.) */
@@ -153,8 +152,8 @@ typedef struct {
bool tx_waiting_fifo; /*!< this flag indicates that some task is waiting for FIFO empty interrupt, used to send all data without any data buffer*/
uint8_t *tx_ptr; /*!< TX data pointer to push to FIFO in TX buffer mode*/
uart_tx_data_t *tx_head; /*!< TX data pointer to head of the current buffer in TX ring buffer*/
uint32_t tx_len_tot; /*!< Total length of current item in ring buffer*/
uint32_t tx_len_cur;
uint32_t trans_total_remaining_len; /*!< Remaining data length of the current processing transaction in TX ring buffer*/
uint32_t trans_chunk_remaining_len; /*!< Remaining data length of the current processing chunk of the transaction in TX ring buffer*/
uint8_t tx_brk_flg; /*!< Flag to indicate to send a break signal in the end of the item sending procedure */
uint8_t tx_brk_len; /*!< TX break signal cycle length/number */
uint8_t tx_waiting_brk; /*!< Flag to indicate that TX FIFO is ready to send break signal after FIFO is empty, do not push data into TX FIFO right now.*/
@@ -1161,15 +1160,15 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param)
//That would cause a watch_dog reset because empty interrupt happens so often.
//Although this is a loop in ISR, this loop will execute at most 128 turns.
while (tx_fifo_rem) {
if (p_uart->tx_len_tot == 0 || p_uart->tx_ptr == NULL || p_uart->tx_len_cur == 0) {
if (p_uart->trans_total_remaining_len == 0 || p_uart->tx_ptr == NULL || p_uart->trans_chunk_remaining_len == 0) {
size_t size;
p_uart->tx_head = (uart_tx_data_t *) xRingbufferReceiveFromISR(p_uart->tx_ring_buf, &size);
if (p_uart->tx_head) {
//The first item is the data description
//Get the first item to get the data information
if (p_uart->tx_len_tot == 0) {
if (p_uart->trans_total_remaining_len == 0) {
p_uart->tx_ptr = NULL;
p_uart->tx_len_tot = p_uart->tx_head->tx_data.size;
p_uart->trans_total_remaining_len = p_uart->tx_head->tx_data.size;
if (p_uart->tx_head->type == UART_DATA_BREAK) {
p_uart->tx_brk_flg = 1;
p_uart->tx_brk_len = p_uart->tx_head->tx_data.brk_len;
@@ -1181,22 +1180,22 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param)
//Update the TX item pointer, we will need this to return item to buffer.
p_uart->tx_ptr = (uint8_t *)p_uart->tx_head;
en_tx_flg = true;
p_uart->tx_len_cur = size;
p_uart->trans_chunk_remaining_len = size;
}
} else {
//Can not get data from ring buffer, return;
break;
}
}
if (p_uart->tx_len_tot > 0 && p_uart->tx_ptr && p_uart->tx_len_cur > 0) {
if (p_uart->trans_total_remaining_len > 0 && p_uart->tx_ptr && p_uart->trans_chunk_remaining_len > 0) {
// To fill the TX FIFO.
uint32_t send_len = uart_enable_tx_write_fifo(uart_num, (const uint8_t *) p_uart->tx_ptr,
MIN(p_uart->tx_len_cur, tx_fifo_rem));
MIN(p_uart->trans_chunk_remaining_len, tx_fifo_rem));
p_uart->tx_ptr += send_len;
p_uart->tx_len_tot -= send_len;
p_uart->tx_len_cur -= send_len;
p_uart->trans_total_remaining_len -= send_len;
p_uart->trans_chunk_remaining_len -= send_len;
tx_fifo_rem -= send_len;
if (p_uart->tx_len_cur == 0) {
if (p_uart->trans_chunk_remaining_len == 0) {
//Return item to ring buffer.
vRingbufferReturnItemFromISR(p_uart->tx_ring_buf, p_uart->tx_head, &HPTaskAwoken);
need_yield |= (HPTaskAwoken == pdTRUE);
@@ -1204,7 +1203,7 @@ static void UART_ISR_ATTR uart_rx_intr_handler_default(void *param)
p_uart->tx_ptr = NULL;
//Sending item done, now we need to send break if there is a record.
//Set TX break signal after FIFO is empty
if (p_uart->tx_len_tot == 0 && p_uart->tx_brk_flg == 1) {
if (p_uart->trans_total_remaining_len == 0 && p_uart->tx_brk_flg == 1) {
uart_hal_clr_intsts_mask(&(uart_context[uart_num].hal), UART_INTR_TX_BRK_DONE);
UART_ENTER_CRITICAL_ISR(&(uart_context[uart_num].spinlock));
uart_hal_tx_break(&(uart_context[uart_num].hal), p_uart->tx_brk_len);
@@ -1531,6 +1530,8 @@ int uart_tx_chars(uart_port_t uart_num, const char *buffer, uint32_t len)
return tx_len;
}
// Per transaction in the ring buffer:
// A data description item, followed by one or more data chunk items
static int uart_tx_all(uart_port_t uart_num, const char *src, size_t size, bool brk_en, int brk_len)
{
if (size == 0) {
@@ -1545,7 +1546,6 @@ static int uart_tx_all(uart_port_t uart_num, const char *src, size_t size, bool
#endif
p_uart_obj[uart_num]->coll_det_flg = false;
if (p_uart_obj[uart_num]->tx_buf_size > 0) {
size_t max_size = xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf);
int offset = 0;
uart_tx_data_t evt;
evt.tx_data.size = size;
@@ -1557,11 +1557,14 @@ static int uart_tx_all(uart_port_t uart_num, const char *src, size_t size, bool
}
xRingbufferSend(p_uart_obj[uart_num]->tx_ring_buf, (void *) &evt, sizeof(uart_tx_data_t), portMAX_DELAY);
while (size > 0) {
size_t send_size = size > max_size / 2 ? max_size / 2 : size;
xRingbufferSend(p_uart_obj[uart_num]->tx_ring_buf, (void *)(src + offset), send_size, portMAX_DELAY);
size -= send_size;
offset += send_size;
uart_enable_tx_intr(uart_num, 1, UART_THRESHOLD_NUM(uart_num, UART_EMPTY_THRESH_DEFAULT));
size_t free_size = xRingbufferGetCurFreeSize(p_uart_obj[uart_num]->tx_ring_buf);
size_t send_size = MIN(size, free_size);
if (send_size > 0) {
xRingbufferSend(p_uart_obj[uart_num]->tx_ring_buf, (void *)(src + offset), send_size, portMAX_DELAY);
size -= send_size;
offset += send_size;
uart_enable_tx_intr(uart_num, 1, UART_THRESHOLD_NUM(uart_num, UART_EMPTY_THRESH_DEFAULT));
}
}
} else {
while (size) {
@@ -1685,7 +1688,79 @@ esp_err_t uart_get_tx_buffer_free_size(uart_port_t uart_num, size_t *size)
ESP_RETURN_ON_FALSE((uart_num < UART_NUM_MAX), ESP_ERR_INVALID_ARG, UART_TAG, "uart_num error");
ESP_RETURN_ON_FALSE((p_uart_obj[uart_num]), ESP_ERR_INVALID_ARG, UART_TAG, "uart driver error");
ESP_RETURN_ON_FALSE((size != NULL), ESP_ERR_INVALID_ARG, UART_TAG, "arg pointer is NULL");
*size = p_uart_obj[uart_num]->tx_buf_size - p_uart_obj[uart_num]->tx_len_tot;
// If tx buffer is disabled or ring buffer is full, overall enqueueable payload is 0
if (p_uart_obj[uart_num]->tx_buf_size == 0 || xRingbufferGetCurFreeSize(p_uart_obj[uart_num]->tx_ring_buf) == 0) {
*size = 0;
return ESP_OK;
}
// Tight conservative bound for NOSPLIT ring buffer overall enqueueable payload across up to two segments
const size_t RINGBUF_ITEM_HDR_SIZE = 8; // per public ringbuf API docs
// Per-item cap in current state and basis to infer minimal buffer size
size_t max_item = xRingbufferGetMaxItemSize(p_uart_obj[uart_num]->tx_ring_buf);
// Get current ring buffer pointer offsets and items waiting to detect empty
UBaseType_t off_free = 0;
UBaseType_t off_acq = 0;
UBaseType_t items_waiting = 0;
vRingbufferGetInfo(p_uart_obj[uart_num]->tx_ring_buf, &off_free, NULL, NULL, &off_acq, &items_waiting);
// Minimal possible total buffer size for NOSPLIT: see ringbuf initialization logic
// xMaxItemSize = ALIGN4(xSize/2) - header => xSize_min = 2 * (xMaxItemSize + header - up_to_3_alignment)
size_t buf_size_min = 2 * (max_item + RINGBUF_ITEM_HDR_SIZE - 3);
buf_size_min &= ~((size_t)3); // align down to 4 bytes
size_t total_payload = 0;
if (off_acq == off_free && items_waiting == 0) {
// Empty buffer: conservatively treat as a single large contiguous segment
total_payload = p_uart_obj[uart_num]->tx_buf_size - RINGBUF_ITEM_HDR_SIZE;
} else if (off_acq <= off_free) {
// Single contiguous free segment
size_t seg = (size_t)off_free - (size_t)off_acq;
if (seg > RINGBUF_ITEM_HDR_SIZE) {
size_t usable = seg - RINGBUF_ITEM_HDR_SIZE;
usable &= ~((size_t)3);
if (usable > max_item) {
usable = max_item;
}
total_payload = usable;
}
} else {
// Free space wraps: two segments [acq..tail) and [head..free)
size_t seg1 = buf_size_min - (size_t)off_acq;
size_t seg2 = (size_t)off_free; // from head (offset 0) to free
size_t payload1 = 0;
if (seg1 > RINGBUF_ITEM_HDR_SIZE) {
size_t usable1 = seg1 - RINGBUF_ITEM_HDR_SIZE;
usable1 &= ~((size_t)3);
if (usable1 > max_item) {
usable1 = max_item;
}
payload1 = usable1;
}
size_t payload2 = 0;
if (seg2 > RINGBUF_ITEM_HDR_SIZE) {
size_t usable2 = seg2 - RINGBUF_ITEM_HDR_SIZE;
usable2 &= ~((size_t)3);
if (usable2 > max_item) {
usable2 = max_item;
}
payload2 = usable2;
}
total_payload = payload1 + payload2;
}
// Subtract the cost of the transaction's data description item (header + aligned struct)
size_t desc_cost = RINGBUF_ITEM_HDR_SIZE + (((sizeof(uart_tx_data_t)) + 3) & ~((size_t)3));
if (total_payload > desc_cost) {
total_payload -= desc_cost;
} else {
total_payload = 0;
}
*size = total_payload;
return ESP_OK;
}
@@ -1861,7 +1936,8 @@ esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_b
p_uart_obj[uart_num]->event_queue_size = event_queue_size;
p_uart_obj[uart_num]->tx_ptr = NULL;
p_uart_obj[uart_num]->tx_head = NULL;
p_uart_obj[uart_num]->tx_len_tot = 0;
p_uart_obj[uart_num]->trans_total_remaining_len = 0;
p_uart_obj[uart_num]->trans_chunk_remaining_len = 0;
p_uart_obj[uart_num]->tx_brk_flg = 0;
p_uart_obj[uart_num]->tx_brk_len = 0;
p_uart_obj[uart_num]->tx_waiting_brk = 0;
@@ -419,23 +419,73 @@ TEST_CASE("uart tx with ringbuffer test", "[uart]")
rd_data[i] = 0;
}
size_t tx_buffer_free_space;
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_EQUAL_INT(2048, tx_buffer_free_space); // full tx buffer space is free
uart_write_bytes(uart_num, (const char *)wr_data, 1024);
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_LESS_THAN(2048, tx_buffer_free_space); // tx transmit in progress: tx buffer has content
TEST_ASSERT_GREATER_OR_EQUAL(1024, tx_buffer_free_space);
uart_wait_tx_done(uart_num, portMAX_DELAY);
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_EQUAL_INT(2048, tx_buffer_free_space); // tx done: tx buffer back to empty
uart_read_bytes(uart_num, rd_data, 1024, pdMS_TO_TICKS(1000));
TEST_ASSERT_EQUAL_HEX8_ARRAY(wr_data, rd_data, 1024);
TEST_ESP_OK(uart_driver_delete(uart_num));
free(rd_data);
free(wr_data);
}
TEST_CASE("uart tx ring buffer free space test", "[uart]")
{
uart_port_param_t port_param = {};
TEST_ASSERT(port_select(&port_param));
// This is a test on the driver API, no need to test for both HP/LP uart port, call port_select() to be compatible with pytest
// Let's only test on HP UART
if (port_param.port_num < SOC_UART_HP_NUM) {
uart_port_t uart_num = port_param.port_num;
uint8_t *rd_data = (uint8_t *)malloc(1024);
TEST_ASSERT_NOT_NULL(rd_data);
uint8_t *wr_data = (uint8_t *)malloc(2048);
TEST_ASSERT_NOT_NULL(wr_data);
uart_config_t uart_config = {
.baud_rate = 2000000,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS,
.rx_flow_ctrl_thresh = port_param.rx_flow_ctrl_thresh,
.source_clk = port_param.default_src_clk,
};
uart_wait_tx_idle_polling(uart_num);
TEST_ESP_OK(uart_param_config(uart_num, &uart_config));
TEST_ESP_OK(uart_driver_install(uart_num, 256, 1024 * 2, 20, NULL, 0));
// Let CTS be high, so that transmission is blocked
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, uart_periph_signal[uart_num].pins[SOC_UART_CTS_PIN_IDX].signal, false);
// When nothing pushed to the TX ring buffer, the free space should be the full capacity
size_t tx_buffer_free_space;
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_EQUAL_INT(2020, tx_buffer_free_space); // no-split ring buffer: 2048 - 20 (data description item) - 8 (header)
// Push 1024 bytes to the TX ring buffer
uart_write_bytes(uart_num, (const char *)wr_data, 1024); // two chunks
vTaskDelay(pdMS_TO_TICKS(500));
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_LESS_THAN(2020, tx_buffer_free_space); // tx buffer has content
TEST_ASSERT_GREATER_OR_EQUAL(952, tx_buffer_free_space);
// Fill the remaining space in the TX ring buffer
uart_write_bytes(uart_num, (const char *)wr_data, tx_buffer_free_space);
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_EQUAL_INT(0, tx_buffer_free_space); // tx buffer is full
// Let CTS be low, so that transmission is unblocked
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, uart_periph_signal[uart_num].pins[SOC_UART_CTS_PIN_IDX].signal, false);
uart_wait_tx_done(uart_num, portMAX_DELAY);
uart_get_tx_buffer_free_size(uart_num, &tx_buffer_free_space);
TEST_ASSERT_EQUAL_INT(2020, tx_buffer_free_space); // tx buffer is back to full capacity
TEST_ESP_OK(uart_driver_delete(uart_num));
free(rd_data);
free(wr_data);
}
}
TEST_CASE("uart int state restored after flush", "[uart]")
{
uart_port_param_t port_param = {};