Merge branch 'fix/legacy_twai_rx_non_iso_dlc_oob_v6.0' into 'release/v6.0'

fix(driver_twai): fixed legacy twai OOB issue when rx dlc larger than 8 (v6.0)

See merge request espressif/esp-idf!50750
This commit is contained in:
morris
2026-07-15 16:01:02 +08:00
11 changed files with 281 additions and 7 deletions
+2 -1
View File
@@ -822,7 +822,8 @@ esp_err_t twai_receive_v2(twai_handle_t handle, twai_message_t *message, TickTyp
message->extd = header.ide;
message->rtr = header.rtr;
//Set remaining bytes of data to 0
memset(message->data + message->data_length_code, 0, TWAI_FRAME_MAX_LEN - message->data_length_code);
uint8_t data_length = (header.dlc > TWAI_FRAME_MAX_LEN) ? TWAI_FRAME_MAX_LEN : header.dlc;
memset(message->data + data_length, 0, TWAI_FRAME_MAX_LEN - data_length);
return ESP_OK;
}
+9 -1
View File
@@ -61,6 +61,7 @@ typedef struct {
uint32_t src_freq_hz;
uint32_t timestamp_freq_hz;
uint32_t valid_fd_timing;
bool enable_scheduled_tx;
twai_event_callbacks_t cbs;
void *user_data;
#ifdef CONFIG_PM_ENABLE
@@ -608,7 +609,7 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
ESP_RETURN_ON_FALSE_ISR(!frame->header.fdf || frame->buffer_len <= TWAI_FRAME_MAX_LEN, ESP_ERR_INVALID_ARG, TAG, "fdf flag or buffer_len not supported");
#endif
ESP_RETURN_ON_FALSE_ISR((frame->header.dlc <= TWAIFD_FRAME_MAX_DLC) && \
(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN)), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld)", frame->buffer_len);
(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN)), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld, dlc %d)", frame->buffer_len, frame->header.dlc);
ESP_RETURN_ON_FALSE_ISR((!frame->header.brs) || (twai_ctx->valid_fd_timing), ESP_ERR_INVALID_ARG, TAG, "brs can't be used without config data_timing");
ESP_RETURN_ON_FALSE_ISR(!twai_ctx->hal->enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only");
ESP_RETURN_ON_FALSE_ISR(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off");
@@ -685,6 +686,11 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
ESP_RETURN_ON_FALSE(!node_config->intr_priority || (BIT(node_config->intr_priority) & ESP_INTR_FLAG_LOWMED), ESP_ERR_INVALID_ARG, TAG, "Invalid intr_priority level");
#if !SOC_TWAI_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(!node_config->flags.sleep_allow_pd, ESP_ERR_NOT_SUPPORTED, TAG, "sleep retention is not supported on this target");
#endif
#if TWAI_LL_SUPPORT(TIMESTAMP)
ESP_RETURN_ON_FALSE(!node_config->flags.enable_scheduled_tx || node_config->timestamp_resolution_hz, ESP_ERR_INVALID_ARG, TAG, "enable_scheduled_tx requires timestamp_resolution_hz");
#else
ESP_RETURN_ON_FALSE(!node_config->flags.enable_scheduled_tx, ESP_ERR_NOT_SUPPORTED, TAG, "enable_scheduled_tx is not supported on this chip");
#endif
// Allocate TWAI node from internal memory because it contains atomic variable
twai_onchip_ctx_t *node = heap_caps_calloc(1, sizeof(twai_onchip_ctx_t) + twai_hal_get_mem_requirment(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
@@ -696,6 +702,7 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
node->ctrlr_id = ctrlr_id;
node->hal = (twai_hal_context_t *)(node + 1); //hal context is place at end of driver context
node->curr_clk_src = node_config->clk_src ? node_config->clk_src : TWAI_CLK_SRC_DEFAULT;
node->enable_scheduled_tx = node_config->flags.enable_scheduled_tx;
ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(node->curr_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &node->src_freq_hz), err, TAG, "get clock source frequency failed");
// state is in bus_off before enabled
@@ -776,6 +783,7 @@ esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twa
.enable_listen_only = node_config->flags.enable_listen_only,
.enable_self_test = node_config->flags.enable_self_test,
.enable_loopback = node_config->flags.enable_loopback,
.enable_time_trigger_tx = node->enable_scheduled_tx,
};
ESP_GOTO_ON_FALSE(twai_hal_init(node->hal, &hal_config), ESP_ERR_INVALID_STATE, err, TAG, "hardware not in reset state");
node->tx_slot_num = twai_hal_get_tx_slot_num(node->hal);
@@ -26,7 +26,7 @@ typedef struct {
twai_clock_source_t clk_src; /**< Optional, clock source, remain 0 to using TWAI_CLK_SRC_DEFAULT by default */
twai_timing_basic_config_t bit_timing; /**< Timing configuration for classic twai and FD arbitration stage */
twai_timing_basic_config_t data_timing; /**< Optional, timing configuration for FD data stage */
uint32_t timestamp_resolution_hz; /**< Timebase frequency (in Hz), used for recording the timestamp of RX frame, set 0 to disable the timestamp feature */
uint32_t timestamp_resolution_hz; /**< Timebase frequency (in Hz), used for RX frame timestamps and scheduled TX trigger times, set 0 to disable the timestamp feature */
int8_t fail_retry_cnt; /**< Hardware retry limit if failed, range [-1:15], -1 for re-trans forever */
uint32_t tx_queue_depth; /**< Depth of the transmit queue */
int intr_priority; /**< Interrupt priority, [0:3] */
@@ -36,6 +36,8 @@ typedef struct {
uint32_t enable_listen_only: 1; /**< No transmissions or acknowledgements. The controller only monitors the bus without participating */
uint32_t no_receive_rtr: 1; /**< Don't receive remote frames */
uint32_t sleep_allow_pd: 1; /**< Allow power down during sleep to save power, driver will backup/restore the TWAI registers to guarantee the peripheral features. */
uint32_t enable_scheduled_tx: 1; /**< Schedule TX mode, if enabled, the tx frame will actually send until `twai_frame_t::header.trigger_time` is reached,
Feature depends on hardware support, and `timestamp_resolution_hz` must be set. */
} flags; /**< Misc configuration flags */
} twai_onchip_node_config_t;
@@ -923,3 +923,79 @@ TEST_CASE("twai rx timestamp", "[twai]")
TEST_ESP_OK(twai_node_delete(node_hdl));
}
}
TEST_CASE("twai schedule transmit", "[twai]")
{
twai_node_handle_t node_hdl;
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_TX_GPIO;
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 100000;
node_config.tx_queue_depth = 10;
node_config.flags.enable_loopback = true;
node_config.flags.enable_self_test = true;
node_config.flags.enable_scheduled_tx = true;
printf("Testing schedule feature check\n");
#if !TWAI_LL_SUPPORT(TIMESTAMP)
TEST_ESP_ERR(twai_new_node_onchip(&node_config, &node_hdl), ESP_ERR_NOT_SUPPORTED);
return;
#endif
TEST_ESP_ERR(twai_new_node_onchip(&node_config, &node_hdl), ESP_ERR_INVALID_ARG);
node_config.timestamp_resolution_hz = 1000000;
TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl));
twai_frame_t rx_frame = {};
twai_event_callbacks_t user_cbs = {};
user_cbs.on_rx_done = test_dlc_range_cb;
TEST_ESP_OK(twai_node_register_event_callbacks(node_hdl, &user_cbs, &rx_frame));
TEST_ESP_OK(twai_node_enable(node_hdl));
twai_frame_t tx_frame[6] = {};
uint64_t time_now = esp_timer_get_time();
printf("time_now = %llu\n", time_now);
printf("Testing schedule frame 1 blocks immediate frame 2\n");
tx_frame[0].header.id = 1;
tx_frame[0].header.trigger_time = time_now + 1000000;
tx_frame[1].header.id = 2;
tx_frame[1].header.trigger_time = 0; // set 0 to send immediately but will be blocked by 1st frame
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame[0], 100));
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame[1], 100));
TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, -1));
// should receive 2nd frame in same time
TEST_ASSERT_EQUAL(tx_frame[1].header.id, rx_frame.header.id);
TEST_ASSERT_INT32_WITHIN(1000000 / 100, 1000000, rx_frame.header.timestamp - time_now);
printf("\nTesting schedule time sequence\n");
time_now = esp_timer_get_time();
tx_frame[0].header.trigger_time = time_now + 1000000;
for (int i = 1; i < 6; i++) {
tx_frame[i].header.id = i;
tx_frame[i].header.trigger_time = tx_frame[i - 1].header.trigger_time + i * 1000000;
printf("Schedule frame %d after %lld s\n", i, (tx_frame[i].header.trigger_time - time_now) / 1000000);
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame[i], 0));
}
printf("\nWaiting for checking result\n");
uint64_t last_time = rx_frame.header.timestamp;
for (int i = 0; i < 5; i++) {
time_now = esp_timer_get_time();
int second_cnt = 0;
while (rx_frame.header.timestamp == last_time) {
vTaskDelay(1);
second_cnt++;
if (second_cnt % 1000 == 0) { // print time every 1 second
esp_rom_printf("%d ", second_cnt / 1000);
}
}
last_time = rx_frame.header.timestamp;
printf("\nFrame %d received after %d s\n", i, second_cnt / 1000);
TEST_ASSERT_INT32_WITHIN(1000000 / 100, second_cnt * 1000, rx_frame.header.timestamp - time_now);
}
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -116,3 +116,63 @@ TEST_CASE("twai_remote_request", "[twai_net]")
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
}
#define TEST_RANDOM_FRAME_NUM 100
TEST_CASE("twai_fd_echo_random_trans", "[twai_net]")
{
twai_node_handle_t node_hdl;
twai_onchip_node_config_t node_config = {};
node_config.io_cfg.tx = TEST_TX_GPIO;
node_config.io_cfg.rx = TEST_RX_GPIO;
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
node_config.bit_timing.bitrate = 250000;
node_config.data_timing.bitrate = 4000000;
node_config.tx_queue_depth = 3;
TEST_ESP_OK(twai_new_node_onchip(&node_config, &node_hdl));
ESP_LOGI("Test", "driver installed");
uint8_t rx_buffer[TWAIFD_FRAME_MAX_LEN] = {0};
twai_frame_t rx_frame = {};
rx_frame.buffer = rx_buffer;
rx_frame.buffer_len = sizeof(rx_buffer);
uint8_t rx_msg_cnt = 0;
void *user_data[2] = {&rx_msg_cnt, &rx_frame};
twai_event_callbacks_t user_cbs = {};
user_cbs.on_rx_done = test_listen_only_rx_cb;
TEST_ESP_OK(twai_node_register_event_callbacks(node_hdl, &user_cbs, user_data));
TEST_ESP_OK(twai_node_enable(node_hdl));
uint8_t tx_buffer[TWAIFD_FRAME_MAX_LEN] = {0};
twai_frame_t tx_frame = {};
tx_frame.buffer = tx_buffer;
for (size_t i = 0; i < TEST_RANDOM_FRAME_NUM; i++) {
ESP_LOGI("Test", "waiting random frame %d ...", (int)i);
while (rx_msg_cnt <= i) {
vTaskDelay(1);
}
const size_t rx_len = twaifd_dlc2len(rx_frame.header.dlc);
ESP_LOGI("Test", "RX: %lx [%d] fd %d, brs %d, ext %d",
rx_frame.header.id, (int)rx_len, rx_frame.header.fdf, rx_frame.header.brs, rx_frame.header.ide);
ESP_LOG_BUFFER_HEX("Data", rx_frame.buffer, rx_len);
TEST_ASSERT_LESS_OR_EQUAL(sizeof(rx_buffer), rx_len);
TEST_ASSERT_TRUE(rx_frame.header.fdf || rx_len <= 8);
// ESP only change data and send it back, PC will check the frame is correct
for (size_t j = 0; j < rx_len; j++) {
tx_buffer[j] = rx_frame.buffer[j] + 2;
}
tx_frame.header = rx_frame.header;
tx_frame.buffer_len = rx_len;
ESP_LOGI("Test", "send echo frame");
TEST_ESP_OK(twai_node_transmit(node_hdl, &tx_frame, 1000));
TEST_ESP_OK(twai_node_transmit_wait_all_done(node_hdl, 1000));
}
TEST_ESP_OK(twai_node_disable(node_hdl));
TEST_ESP_OK(twai_node_delete(node_hdl));
}
@@ -1,6 +1,7 @@
# SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import os
import random
import subprocess
import time
@@ -60,6 +61,7 @@ def esp_reset_and_wait_ready(dut: Dut) -> None:
def fixture_create_socket_can() -> Bus:
# Set up the socket CAN with the bitrate
start_command = f'sudo -n ip link set {can_env} up type can bitrate 250000'
fdstart_command = f'sudo -n ip link set {can_env} up type can bitrate 250000 dbitrate 4000000 fd on'
stop_command = f'sudo -n ip link set {can_env} down'
status_command = f'sudo -n ip -details link show {can_env}'
@@ -70,10 +72,15 @@ def fixture_create_socket_can() -> Bus:
if 'UP' in result.stdout: # Close the bus anyway if it is already up
subprocess.run(stop_command, shell=True, capture_output=True, text=True)
subprocess.run(start_command, shell=True, capture_output=True, text=True)
result = subprocess.run(fdstart_command, shell=True, capture_output=True, text=True)
fd_enabled = result.returncode == 0
if not fd_enabled:
subprocess.run(start_command, shell=True, capture_output=True, text=True)
print(f'FD mode {"Enabled" if fd_enabled else "NOT supported"} on USB CAN')
time.sleep(0.5)
bus = Bus(interface='socketcan', channel=f'{can_env}', bitrate=250000)
bus = Bus(interface='socketcan', channel=f'{can_env}', bitrate=250000, fd=fd_enabled)
bus.fd_enabled = fd_enabled
yield bus # test invoked here
bus.shutdown()
@@ -139,3 +146,63 @@ def test_driver_twai_remote_request(dut: Dut, socket_can: Bus) -> None:
dut.expect_unity_test_output(timeout=10)
finally:
esp_enter_flash_mode(dut)
# Test send 100 random frames and ESP echo them back with data + 2
# Verify the frame is received correctly
@pytest.mark.twai_adapter
@idf_parametrize('target', soc_filtered_targets('SOC_TWAI_FD_SUPPORTED == 1'), indirect=['target'])
@pytest.mark.temp_skip_ci(targets=['esp32s31', 'esp32h4'], reason='no runner')
@pytest.mark.parametrize('config', ['release'], indirect=True)
def test_driver_twai_fd_trans(dut: Dut, socket_can: Bus) -> None:
if not socket_can.fd_enabled:
pytest.fail(f'CAN interface "{can_env}" does not support CAN FD')
try:
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('"twai_fd_echo_random_trans"')
# wait the DUT to finish initialize
time.sleep(0.5)
rng = random.Random(0x20260702)
canfd_lengths = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64]
for index in range(100):
is_fd = rng.choice([False, True])
is_extended_id = rng.choice([False, True])
arbitration_id = rng.randrange(0x20000000 if is_extended_id else 0x800)
data_len = rng.choice(canfd_lengths if is_fd else list(range(9)))
message = Message(
arbitration_id=arbitration_id,
is_extended_id=is_extended_id,
is_fd=is_fd,
bitrate_switch=is_fd and rng.choice([False, True]),
data=bytes(rng.randrange(256) for _ in range(data_len)),
)
esp_to_usb_data = bytes((byte + 2) & 0xFF for byte in message.data)
print(f'USB TX {index}:', message, 'Return:', socket_can.send(message, timeout=0.2))
deadline = time.time() + 5.0
reply = None
while time.time() < deadline:
candidate = socket_can.recv(timeout=0.2)
if (
candidate is not None
and candidate.arbitration_id == message.arbitration_id
and candidate.is_extended_id == message.is_extended_id
):
reply = candidate
break
if reply is None:
raise Exception('CAN frame not received')
print(f'USB RX {index}: {reply}')
assert reply.arbitration_id == message.arbitration_id
assert reply.is_extended_id == message.is_extended_id
assert reply.is_fd == message.is_fd
assert reply.bitrate_switch == message.bitrate_switch
assert bytes(reply.data) == esp_to_usb_data
dut.expect_unity_test_output(timeout=10)
finally:
esp_enter_flash_mode(dut)
@@ -101,6 +101,7 @@ typedef struct {
bool enable_self_test;
bool enable_loopback;
bool enable_listen_only;
bool enable_time_trigger_tx;
} twai_hal_config_t;
/**
@@ -122,7 +122,7 @@ typedef struct {
};
union {
uint64_t timestamp; /**< Timestamp for received message */
uint64_t trigger_time; /**< Trigger time for transmitting message*/
uint64_t trigger_time; /**< Trigger time for transmitting message in scheduled TX mode */
};
} twai_frame_header_t;
+1
View File
@@ -27,6 +27,7 @@ bool twai_hal_init(twai_hal_context_t *hal_ctx, const twai_hal_config_t *config)
twaifd_ll_set_mode(hal_ctx->dev, config->enable_listen_only, config->enable_self_test, config->enable_loopback);
twaifd_ll_set_tx_retrans_limit(hal_ctx->dev, config->retry_cnt);
twaifd_ll_filter_drop_rtr(hal_ctx->dev, config->no_receive_rtr);
twaifd_ll_enable_time_trig_trans_mode(hal_ctx->dev, config->enable_time_trigger_tx);
twaifd_ll_enable_filter_mode(hal_ctx->dev, true); // each filter still has independent enable control
twaifd_ll_enable_fd_mode(hal_ctx->dev, true); // fd frame still controlled by `header.fdf`
twaifd_ll_enable_bus_off_tx_fail_mode(hal_ctx->dev, true); // all buffers go to "TX failed" state upon bus-off