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:
@@ -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
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
Reference in New Issue
Block a user