summaryrefslogtreecommitdiff
path: root/components/drivers
diff options
context:
space:
mode:
authorCYFS <[email protected]>2026-11-06 09:45:46 +0800
committerGitHub <[email protected]>2026-11-06 09:45:46 +0800
commit16d725e5782896d1c20f4847cc33db504dd46a94 (patch)
tree74f72c7011878e142bb65928ab5a928f73268a0e /components/drivers
parent905e4832bfb3b29adaffef6e78e2ddc7a418817f (diff)
[utest][can] Add CAN send and receive tests (#11752)
* [utest][can] Add CAN send and receive tests * [utest][can] Fix external CAN FD receive timeout Check the no-progress timeout after duplicate or unmatched frames, and only wait on the receive semaphore when no complete frame is available. Exit immediately once all expected patterns have been received. Format the test with the current mainline configuration and clang-format 22.1.8. Validation: reproduced the original duplicate/unmatched-frame hang with a 50-tick timeout and a 1000-tick watchdog. All 22 host-stub scenarios pass, including timeout renewal, tick wraparound, empty reads, error propagation, and successful reception. The mainline formatting check also passes. No full BSP build or hardware test was performed.
Diffstat (limited to 'components/drivers')
-rw-r--r--components/drivers/can/SConscript6
-rw-r--r--components/drivers/can/utest/Kconfig200
-rw-r--r--components/drivers/can/utest/SConscript13
-rw-r--r--components/drivers/can/utest/can_send_recv_tc.c1572
4 files changed, 1791 insertions, 0 deletions
diff --git a/components/drivers/can/SConscript b/components/drivers/can/SConscript
index 28d45d016d..05ad78a494 100644
--- a/components/drivers/can/SConscript
+++ b/components/drivers/can/SConscript
@@ -1,3 +1,4 @@
+import os
from building import *
cwd = GetCurrentDir()
@@ -9,4 +10,9 @@ if not GetDepend('RT_USING_DM'):
group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_CAN'], CPPPATH = CPPPATH)
+list = os.listdir(cwd)
+for item in list:
+ if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
+ group = group + SConscript(os.path.join(item, 'SConscript'))
+
Return('group')
diff --git a/components/drivers/can/utest/Kconfig b/components/drivers/can/utest/Kconfig
new file mode 100644
index 0000000000..6ff36336c6
--- /dev/null
+++ b/components/drivers/can/utest/Kconfig
@@ -0,0 +1,200 @@
+menu "CAN Test"
+
+config RT_UTEST_CAN
+ bool "CAN Send and Receive Test"
+ default n
+ select RT_USING_CAN
+
+if RT_UTEST_CAN
+
+ choice
+ prompt "CAN Test Topology"
+ default RT_CAN_TC_USING_SELF_LOOPBACK
+
+ config RT_CAN_TC_USING_SELF_LOOPBACK
+ bool "Single CAN Self Loopback"
+ help
+ Use one CAN controller to send and receive its own frame. This
+ mode uses CAN loopback configuration and is suitable for boards
+ that support controller loopback or TX/RX self-loop wiring.
+
+ config RT_CAN_TC_USING_EXTERNAL_DEVICE
+ bool "Single CAN with External CAN Device"
+ help
+ Use one CAN controller connected through a CAN transceiver to an
+ external CAN node. The external node must follow the configured
+ transmit and receive frame agreement.
+
+ config RT_CAN_TC_USING_DUAL_DEVICE
+ bool "Two Connected CAN Devices"
+ help
+ Use two CAN controllers connected to the same CAN bus and verify
+ frame transfer in both directions.
+
+ endchoice
+
+ config RT_CAN_TC_DEVICE_NAME
+ string "Device Name for Single CAN Test"
+ default "can1"
+ depends on RT_CAN_TC_USING_SELF_LOOPBACK || RT_CAN_TC_USING_EXTERNAL_DEVICE
+
+ config RT_CAN_TC_TX_DEVICE_NAME
+ string "TX Device Name for Two CAN Test"
+ default "can1"
+ depends on RT_CAN_TC_USING_DUAL_DEVICE
+
+ config RT_CAN_TC_RX_DEVICE_NAME
+ string "RX Device Name for Two CAN Test"
+ default "can2"
+ depends on RT_CAN_TC_USING_DUAL_DEVICE
+
+ config RT_CAN_TC_EXTERNAL_USING_SEND_TEST
+ bool "Enable External Device Send Test"
+ default y
+ depends on RT_CAN_TC_USING_EXTERNAL_DEVICE
+ help
+ Send one standard data frame to the external CAN node. The external
+ node should receive standard ID RT_CAN_TC_TX_FRAME_ID with DLC 8 and
+ payload 0x10, 0x11, ..., 0x17.
+
+ config RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST
+ bool "Enable External Device Receive Test"
+ default y
+ depends on RT_CAN_TC_USING_EXTERNAL_DEVICE
+ help
+ Wait for one standard data frame from the external CAN node. The
+ external node should send standard ID RT_CAN_TC_RX_FRAME_ID with DLC
+ 8 and payload 0x20, 0x21, ..., 0x27.
+
+ config RT_CAN_TC_USING_NONBLOCKING_SEND_TEST
+ bool "Enable Nonblocking Send Test"
+ default n
+ help
+ Send one standard data frame with rt_can_msg.nonblocking set to 1.
+ Self-loopback and dual-device tests verify the frame end to end.
+ The external-device test only verifies that the frame is accepted or
+ queued because the common CAN API has no TX-complete wait interface.
+ Enable this only when the CAN driver implements sendmsg_nonblocking.
+
+ config RT_CAN_TC_TX_FRAME_ID
+ hex "External Device TX Frame ID"
+ range 0 0x7FF
+ default 0x321
+ depends on RT_CAN_TC_USING_EXTERNAL_DEVICE
+
+ config RT_CAN_TC_RX_FRAME_ID
+ hex "External Device RX Frame ID"
+ range 0 0x7FF
+ default 0x322
+ depends on RT_CAN_TC_USING_EXTERNAL_DEVICE
+
+ config RT_CAN_TC_NONBLOCKING_TX_FRAME_ID
+ hex "Nonblocking TX Frame ID"
+ range 0 0x7FF
+ default 0x323
+ depends on RT_CAN_TC_USING_NONBLOCKING_SEND_TEST
+
+ config RT_CAN_TC_BAUD_RATE
+ int "Primary Arbitration Baud Rate for CAN Test"
+ range 10000 1000000
+ default 500000
+ help
+ Select one of the standard CAN rates: 10000, 20000, 50000, 100000,
+ 125000, 250000, 500000, 800000, or 1000000 bit/s. This rate is
+ always tested. Self-loopback and dual-device tests can select
+ additional rates. The external-device topology uses only this rate
+ because the peer cannot be switched automatically.
+
+ menu "Additional Arbitration Baud Rates"
+ depends on RT_CAN_TC_USING_SELF_LOOPBACK || RT_CAN_TC_USING_DUAL_DEVICE
+
+ config RT_CAN_TC_TEST_BAUD_RATE_10K
+ bool "Test 10 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_20K
+ bool "Test 20 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_50K
+ bool "Test 50 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_100K
+ bool "Test 100 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_125K
+ bool "Test 125 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_250K
+ bool "Test 250 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_500K
+ bool "Test 500 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_800K
+ bool "Test 800 kbit/s"
+ default n
+
+ config RT_CAN_TC_TEST_BAUD_RATE_1M
+ bool "Test 1 Mbit/s"
+ default n
+
+ comment "The primary arbitration rate is always tested"
+
+ endmenu
+
+ config RT_CAN_TC_USING_CANFD
+ bool "Enable CAN FD Test"
+ depends on RT_CAN_USING_CANFD
+ default n
+ help
+ In addition to classic CAN frames, verify CAN FD frames with an
+ extended payload. This is an explicit per-device capability test;
+ enable it only for CAN FD capable controllers and drivers. CAN FD
+ frames are tested only at the primary arbitration rate.
+
+ config RT_CAN_TC_CANFD_DATA_BAUD_RATE
+ int "CAN FD Data Phase Baud Rate"
+ range 10000 10000000
+ default 2000000
+ depends on RT_CAN_TC_USING_CANFD
+ help
+ The data phase may use the same rate as the arbitration phase.
+ Select a higher rate when testing effective bit-rate switching.
+
+ config RT_CAN_TC_CANFD_DATA_LENGTH
+ int "Maximum CAN FD Payload Length"
+ range 12 64
+ default 16
+ depends on RT_CAN_TC_USING_CANFD
+ help
+ Valid CAN FD payload lengths are 12, 16, 20, 24, 32, 48, and 64
+ bytes. Other values in the numeric range are rejected at runtime.
+
+ config RT_CAN_TC_CANFD_BRS
+ bool "Enable CAN FD Bit Rate Switching"
+ default y
+ depends on RT_CAN_TC_USING_CANFD
+
+ config RT_CAN_TC_EXTERNAL_READY_DELAY_MS
+ int "External Node Ready Delay in Milliseconds"
+ range 0 60000
+ default 1000
+ depends on RT_CAN_TC_USING_EXTERNAL_DEVICE
+ help
+ Delay after the DUT is configured and prints the frame agreement.
+ This gives the external node time to start its ordered frame sequence.
+
+ config RT_CAN_TC_TIMEOUT_MS
+ int "RX Timeout in Milliseconds"
+ range 1000 60000
+ default 10000
+
+endif
+
+endmenu
diff --git a/components/drivers/can/utest/SConscript b/components/drivers/can/utest/SConscript
new file mode 100644
index 0000000000..8822a42689
--- /dev/null
+++ b/components/drivers/can/utest/SConscript
@@ -0,0 +1,13 @@
+Import('rtconfig')
+from building import *
+
+cwd = GetCurrentDir()
+src = []
+CPPPATH = [cwd, cwd + '/../../include']
+
+if GetDepend(['RT_UTEST_CAN']):
+ src += Glob('*.c')
+
+group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_CAN'], CPPPATH = CPPPATH)
+
+Return('group')
diff --git a/components/drivers/can/utest/can_send_recv_tc.c b/components/drivers/can/utest/can_send_recv_tc.c
new file mode 100644
index 0000000000..d94fa8b86e
--- /dev/null
+++ b/components/drivers/can/utest/can_send_recv_tc.c
@@ -0,0 +1,1572 @@
+/*
+ * Copyright (c) 2006-2026 RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-07-02 CYFS add CAN send and receive utest
+ * 2026-07-17 CYFS add baud-rate matrix and CAN FD coverage
+ */
+
+/**
+ * Test Case Name: CAN Send and Receive Test
+ *
+ * Test Objectives:
+ * - Verify CAN blocking send and interrupt receive paths.
+ * - Verify standard arbitration rates with self-loopback or two connected CAN
+ * devices. An external node uses one selected rate because it cannot be
+ * switched automatically.
+ * - Optionally verify CAN FD payloads, raw DLC handling, and bit-rate switching.
+ * - Optionally verify the nonblocking send acceptance path.
+ *
+ * Test Topologies:
+ * - Single CAN self-loopback.
+ * - Single CAN connected to an external CAN node.
+ * - Two CAN devices connected to the same bus.
+ *
+ * Expected Results:
+ * - Every configured phase can send and receive the expected frame without
+ * changing the device's original configuration after cleanup.
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <drivers/dev_can.h>
+#include "utest.h"
+
+#ifdef RT_UTEST_CAN
+
+#if defined(RT_CAN_TC_USING_CANFD) && !defined(RT_CAN_USING_CANFD)
+#error "RT_CAN_TC_USING_CANFD requires RT_CAN_USING_CANFD"
+#endif
+
+#define CAN_TC_FRAME_ID_BASE 0x321
+#define CAN_TC_CLASSIC_DLC CAN_MSG_8BYTES
+#define CAN_TC_FD_FRAME_ID_BASE (CAN_TC_FRAME_ID_BASE + 3)
+#define CAN_TC_REVERSE_FD_FRAME_ID_BASE (CAN_TC_FRAME_ID_BASE + 5)
+#define CAN_TC_TX_SEED 0x10
+#define CAN_TC_RX_SEED 0x20
+#define CAN_TC_EXTERNAL_TX_DRAIN_MS 50
+
+#ifndef RT_CAN_TC_TIMEOUT_MS
+#define RT_CAN_TC_TIMEOUT_MS 1000
+#endif
+
+#ifndef RT_CAN_TC_BAUD_RATE
+#define RT_CAN_TC_BAUD_RATE CAN500kBaud
+#endif
+
+#ifndef RT_CAN_TC_TX_FRAME_ID
+#define RT_CAN_TC_TX_FRAME_ID CAN_TC_FRAME_ID_BASE
+#endif
+
+#ifndef RT_CAN_TC_RX_FRAME_ID
+#define RT_CAN_TC_RX_FRAME_ID (CAN_TC_FRAME_ID_BASE + 1)
+#endif
+
+#ifndef RT_CAN_TC_NONBLOCKING_TX_FRAME_ID
+#define RT_CAN_TC_NONBLOCKING_TX_FRAME_ID (CAN_TC_FRAME_ID_BASE + 2)
+#endif
+
+#ifndef RT_CAN_TC_EXTERNAL_READY_DELAY_MS
+#define RT_CAN_TC_EXTERNAL_READY_DELAY_MS 1000
+#endif
+
+#ifdef RT_CAN_TC_USING_CANFD
+#ifndef RT_CAN_TC_CANFD_DATA_BAUD_RATE
+#define RT_CAN_TC_CANFD_DATA_BAUD_RATE 2000000
+#endif
+#ifndef RT_CAN_TC_CANFD_DATA_LENGTH
+#define RT_CAN_TC_CANFD_DATA_LENGTH 16
+#endif
+#ifdef RT_CAN_TC_CANFD_BRS
+#define CAN_TC_SELECTED_FD_BRS RT_TRUE
+#else
+#define CAN_TC_SELECTED_FD_BRS RT_FALSE
+#endif
+#endif
+
+#if defined(RT_CAN_TC_USING_DUAL_DEVICE)
+#define CAN_TC_MAX_RECEIVE_WAITS 31
+#elif defined(RT_CAN_TC_USING_SELF_LOOPBACK)
+#define CAN_TC_MAX_RECEIVE_WAITS 20
+#elif defined(RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST)
+#define CAN_TC_MAX_RECEIVE_WAITS 3
+#else
+#define CAN_TC_MAX_RECEIVE_WAITS 1
+#endif
+
+#define CAN_TC_RX_TIMEOUT_SECONDS ((RT_CAN_TC_TIMEOUT_MS + 999) / 1000)
+#ifdef RT_CAN_TC_USING_EXTERNAL_DEVICE
+#define CAN_TC_READY_DELAY_SECONDS ((RT_CAN_TC_EXTERNAL_READY_DELAY_MS + 999) / 1000)
+#else
+#define CAN_TC_READY_DELAY_SECONDS 0
+#endif
+#define CAN_TC_SUITE_TIMEOUT (10 + CAN_TC_READY_DELAY_SECONDS + \
+ CAN_TC_MAX_RECEIVE_WAITS * CAN_TC_RX_TIMEOUT_SECONDS)
+
+struct can_tc_context
+{
+ rt_device_t dev;
+ struct rt_can_device *can;
+ struct can_configure original_config;
+ rt_err_t (*original_rx_indicate)(rt_device_t dev, rt_size_t size);
+ struct rt_semaphore rx_sem;
+ rt_bool_t sem_inited;
+ rt_bool_t opened;
+ rt_bool_t configuration_touched;
+};
+
+static struct can_tc_context can_tx_context;
+static struct can_tc_context can_rx_context;
+static struct can_tc_context *can_tx_ctx;
+static struct can_tc_context *can_rx_ctx;
+
+static void can_log_status(struct can_tc_context *context)
+{
+ rt_err_t result;
+ struct rt_can_status status;
+
+ rt_memset(&status, 0, sizeof(status));
+ result = rt_device_control(context->dev, RT_CAN_CMD_GET_STATUS, &status);
+ if (result == RT_EOK)
+ {
+ LOG_E("CAN status: txerr=%u, rxerr=%u, errcode=0x%08x, rxpkg=%u, rxdrop=%u, txpkg=%u, txdrop=%u",
+ status.snderrcnt, status.rcverrcnt, status.errcode,
+ status.rcvpkg, status.dropedrcvpkg,
+ status.sndpkg, status.dropedsndpkg);
+ }
+ else
+ {
+ LOG_E("Get CAN status failed on %s, result=%d",
+ context->dev->parent.name, result);
+ }
+}
+
+static const rt_uint32_t can_tc_baud_rates[] = {
+ CAN10kBaud,
+ CAN20kBaud,
+ CAN50kBaud,
+ CAN100kBaud,
+ CAN125kBaud,
+ CAN250kBaud,
+ CAN500kBaud,
+ CAN800kBaud,
+ CAN1MBaud,
+};
+
+static const rt_uint8_t can_tc_dlc_to_length[] = {
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 12,
+ 16,
+ 20,
+ 24,
+ 32,
+ 48,
+ 64,
+};
+
+static rt_bool_t can_is_standard_baud_rate(rt_uint32_t baud_rate)
+{
+ rt_size_t i;
+
+ for (i = 0; i < sizeof(can_tc_baud_rates) / sizeof(can_tc_baud_rates[0]); i++)
+ {
+ if (can_tc_baud_rates[i] == baud_rate)
+ {
+ return RT_TRUE;
+ }
+ }
+
+ return RT_FALSE;
+}
+
+#if defined(RT_CAN_TC_USING_SELF_LOOPBACK) || defined(RT_CAN_TC_USING_DUAL_DEVICE)
+static rt_bool_t can_baud_rate_is_selected(rt_uint32_t baud_rate)
+{
+ if (baud_rate == RT_CAN_TC_BAUD_RATE)
+ {
+ return RT_TRUE;
+ }
+
+ switch (baud_rate)
+ {
+ case CAN10kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_10K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN20kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_20K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN50kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_50K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN100kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_100K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN125kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_125K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN250kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_250K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN500kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_500K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN800kBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_800K
+ return RT_TRUE;
+#endif
+ break;
+ case CAN1MBaud:
+#ifdef RT_CAN_TC_TEST_BAUD_RATE_1M
+ return RT_TRUE;
+#endif
+ break;
+ default:
+ break;
+ }
+
+ return RT_FALSE;
+}
+#endif
+
+static rt_err_t can_dlc_to_payload_length(rt_uint8_t dlc, rt_size_t *payload_length)
+{
+ if (dlc >= sizeof(can_tc_dlc_to_length) / sizeof(can_tc_dlc_to_length[0]))
+ {
+ return -RT_EINVAL;
+ }
+
+ *payload_length = can_tc_dlc_to_length[dlc];
+ return RT_EOK;
+}
+
+#ifdef RT_CAN_TC_USING_CANFD
+static rt_err_t can_payload_length_to_dlc(rt_size_t payload_length, rt_uint8_t *dlc)
+{
+ rt_size_t i;
+
+ for (i = 0; i < sizeof(can_tc_dlc_to_length) / sizeof(can_tc_dlc_to_length[0]); i++)
+ {
+ if (can_tc_dlc_to_length[i] == payload_length)
+ {
+ *dlc = (rt_uint8_t)i;
+ return RT_EOK;
+ }
+ }
+
+ return -RT_EINVAL;
+}
+#endif
+
+static struct can_tc_context *can_find_context(rt_device_t dev)
+{
+ if (can_tx_ctx != RT_NULL && can_tx_ctx->dev == dev)
+ {
+ return can_tx_ctx;
+ }
+ if (can_rx_ctx != RT_NULL && can_rx_ctx->dev == dev)
+ {
+ return can_rx_ctx;
+ }
+
+ return RT_NULL;
+}
+
+static rt_err_t can_rx_indicate(rt_device_t dev, rt_size_t size)
+{
+ struct can_tc_context *context;
+
+ context = can_find_context(dev);
+ if (context != RT_NULL && context->sem_inited && size >= sizeof(struct rt_can_msg))
+ {
+ rt_sem_release(&context->rx_sem);
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t can_fill_message(struct rt_can_msg *msg,
+ rt_uint32_t id,
+ rt_uint8_t seed,
+ rt_uint8_t dlc,
+ rt_bool_t fd_frame,
+ rt_bool_t brs)
+{
+ rt_size_t i;
+ rt_size_t payload_length;
+
+ if (can_dlc_to_payload_length(dlc, &payload_length) != RT_EOK ||
+ payload_length > sizeof(msg->data))
+ {
+ return -RT_EINVAL;
+ }
+
+ rt_memset(msg, 0, sizeof(*msg));
+ msg->id = id;
+ msg->ide = RT_CAN_STDID;
+ msg->rtr = RT_CAN_DTR;
+ msg->len = dlc;
+ msg->hdr_index = -1;
+#ifdef RT_CAN_USING_CANFD
+ msg->fd_frame = fd_frame;
+ msg->brs = brs;
+#else
+ RT_UNUSED(fd_frame);
+ RT_UNUSED(brs);
+#endif
+
+ for (i = 0; i < payload_length; i++)
+ {
+ msg->data[i] = (rt_uint8_t)(seed + i);
+ }
+
+ return RT_EOK;
+}
+
+#if defined(RT_CAN_TC_USING_SELF_LOOPBACK) || \
+ defined(RT_CAN_TC_USING_DUAL_DEVICE) || \
+ defined(RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST)
+static rt_bool_t can_message_equal(const struct rt_can_msg *expected,
+ const struct rt_can_msg *actual)
+{
+ rt_size_t payload_length;
+
+ if (expected->id != actual->id ||
+ expected->ide != actual->ide ||
+ expected->rtr != actual->rtr ||
+ expected->len != actual->len)
+ {
+ return RT_FALSE;
+ }
+
+#ifdef RT_CAN_USING_CANFD
+ if (expected->fd_frame != actual->fd_frame || expected->brs != actual->brs)
+ {
+ return RT_FALSE;
+ }
+#endif
+
+ if (can_dlc_to_payload_length((rt_uint8_t)expected->len, &payload_length) != RT_EOK)
+ {
+ return RT_FALSE;
+ }
+
+ return rt_memcmp(expected->data, actual->data, payload_length) == 0;
+}
+
+static void can_drain_receive(struct can_tc_context *context)
+{
+ rt_size_t i;
+ rt_size_t limit;
+ struct rt_can_msg msg;
+
+ limit = context->can->config.msgboxsz;
+ if (limit == 0)
+ {
+ limit = RT_CANMSG_BOX_SZ;
+ }
+
+ for (i = 0; i < limit; i++)
+ {
+ rt_ssize_t read_size;
+
+ rt_memset(&msg, 0, sizeof(msg));
+ msg.hdr_index = -1;
+ read_size = rt_device_read(context->dev, 0, &msg, sizeof(msg));
+ if (read_size != sizeof(msg))
+ {
+ break;
+ }
+ }
+
+ for (i = 0; i < limit; i++)
+ {
+ if (rt_sem_take(&context->rx_sem, 0) != RT_EOK)
+ {
+ break;
+ }
+ }
+}
+
+#if defined(RT_CAN_TC_USING_SELF_LOOPBACK) || \
+ defined(RT_CAN_TC_USING_DUAL_DEVICE) || \
+ !defined(RT_CAN_TC_USING_CANFD)
+static rt_err_t can_receive_expected(struct can_tc_context *context,
+ const struct rt_can_msg *expected)
+{
+ rt_tick_t start;
+ rt_tick_t timeout;
+
+ timeout = rt_tick_from_millisecond(RT_CAN_TC_TIMEOUT_MS);
+ if (timeout == 0)
+ {
+ timeout = 1;
+ }
+ start = rt_tick_get();
+
+ while (1)
+ {
+ rt_ssize_t read_size;
+ rt_tick_t elapsed;
+ rt_tick_t remaining;
+ struct rt_can_msg actual;
+
+ rt_memset(&actual, 0, sizeof(actual));
+ actual.hdr_index = -1;
+ read_size = rt_device_read(context->dev, 0, &actual, sizeof(actual));
+ if (read_size == sizeof(actual))
+ {
+ if (actual.id != expected->id || actual.ide != expected->ide)
+ {
+ elapsed = (rt_tick_t)(rt_tick_get() - start);
+ if (elapsed >= timeout)
+ {
+ LOG_E("CAN receive timeout on %s, expected id=0x%x",
+ context->dev->parent.name, expected->id);
+ can_log_status(context);
+ return -RT_ETIMEOUT;
+ }
+ continue;
+ }
+ if (!can_message_equal(expected, &actual))
+ {
+#ifdef RT_CAN_USING_CANFD
+ LOG_E("CAN frame mismatch on %s: id=0x%x dlc=%u fd=%u brs=%u, got dlc=%u fd=%u brs=%u",
+ context->dev->parent.name,
+ expected->id,
+ expected->len,
+ expected->fd_frame,
+ expected->brs,
+ actual.len,
+ actual.fd_frame,
+ actual.brs);
+#else
+ LOG_E("CAN frame mismatch on %s: id=0x%x dlc=%u, got dlc=%u",
+ context->dev->parent.name,
+ expected->id,
+ expected->len,
+ actual.len);
+#endif
+ return -RT_ERROR;
+ }
+
+ return RT_EOK;
+ }
+ if (read_size < 0)
+ {
+ LOG_E("CAN read failed on %s, result=%d",
+ context->dev->parent.name, (int)read_size);
+ return (rt_err_t)read_size;
+ }
+
+ elapsed = (rt_tick_t)(rt_tick_get() - start);
+ if (elapsed >= timeout)
+ {
+ LOG_E("CAN receive timeout on %s, expected id=0x%x",
+ context->dev->parent.name, expected->id);
+ can_log_status(context);
+ return -RT_ETIMEOUT;
+ }
+
+ remaining = timeout - elapsed;
+ rt_sem_take(&context->rx_sem, remaining);
+ }
+}
+#endif
+#endif
+
+#if defined(RT_CAN_TC_USING_SELF_LOOPBACK) || \
+ defined(RT_CAN_TC_USING_DUAL_DEVICE) || \
+ defined(RT_CAN_TC_EXTERNAL_USING_SEND_TEST) || \
+ defined(RT_CAN_TC_USING_NONBLOCKING_SEND_TEST)
+static rt_bool_t can_support_nonblocking_send(struct can_tc_context *context)
+{
+ return context->can->ops != RT_NULL &&
+ context->can->ops->sendmsg_nonblocking != RT_NULL;
+}
+
+static rt_err_t can_send_message(struct can_tc_context *context,
+ const struct rt_can_msg *message,
+ rt_bool_t nonblocking)
+{
+ rt_ssize_t write_size;
+ struct rt_can_msg tx_message;
+
+ if (nonblocking && !can_support_nonblocking_send(context))
+ {
+ LOG_E("CAN nonblocking send is not supported by %s", context->dev->parent.name);
+ return -RT_ENOSYS;
+ }
+
+ tx_message = *message;
+ tx_message.nonblocking = nonblocking;
+ write_size = rt_device_write(context->dev, 0, &tx_message, sizeof(tx_message));
+ if (write_size != sizeof(tx_message))
+ {
+#ifdef RT_CAN_USING_CANFD
+ LOG_E("CAN %s write failed on %s, size=%d, id=0x%x, dlc=%u, fd=%u, brs=%u",
+ nonblocking ? "nonblocking" : "blocking",
+ context->dev->parent.name,
+ (int)write_size,
+ tx_message.id,
+ tx_message.len,
+ tx_message.fd_frame,
+ tx_message.brs);
+#else
+ LOG_E("CAN %s write failed on %s, size=%d, id=0x%x, dlc=%u",
+ nonblocking ? "nonblocking" : "blocking",
+ context->dev->parent.name,
+ (int)write_size,
+ tx_message.id,
+ tx_message.len);
+#endif
+ can_log_status(context);
+ return -RT_ERROR;
+ }
+
+ return RT_EOK;
+}
+#endif
+
+#if defined(RT_CAN_TC_USING_SELF_LOOPBACK) || defined(RT_CAN_TC_USING_DUAL_DEVICE)
+static rt_err_t can_transfer_message(struct can_tc_context *tx_context,
+ struct can_tc_context *rx_context,
+ const struct rt_can_msg *message,
+ rt_bool_t nonblocking)
+{
+ rt_err_t result;
+
+ can_drain_receive(rx_context);
+ result = can_send_message(tx_context, message, nonblocking);
+ if (result != RT_EOK)
+ {
+ if (rx_context != tx_context)
+ {
+ LOG_E("CAN peer status on %s after transmit failure:",
+ rx_context->dev->parent.name);
+ can_log_status(rx_context);
+ }
+ return result;
+ }
+
+ return can_receive_expected(rx_context, message);
+}
+
+static rt_err_t can_transfer_pattern(struct can_tc_context *tx_context,
+ struct can_tc_context *rx_context,
+ rt_uint32_t id,
+ rt_uint8_t seed,
+ rt_bool_t nonblocking)
+{
+ rt_err_t result;
+ struct rt_can_msg message;
+
+ result = can_fill_message(&message, id, seed, CAN_TC_CLASSIC_DLC, RT_FALSE, RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ return can_transfer_message(tx_context, rx_context, &message, nonblocking);
+}
+#endif
+
+#if defined(RT_CAN_TC_USING_EXTERNAL_DEVICE) && \
+ (defined(RT_CAN_TC_EXTERNAL_USING_SEND_TEST) || \
+ defined(RT_CAN_TC_USING_NONBLOCKING_SEND_TEST))
+static rt_err_t can_send_pattern(struct can_tc_context *context,
+ rt_uint32_t id,
+ rt_uint8_t seed,
+ rt_bool_t nonblocking)
+{
+ rt_err_t result;
+ struct rt_can_msg message;
+
+ result = can_fill_message(&message, id, seed, CAN_TC_CLASSIC_DLC, RT_FALSE, RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ return can_send_message(context, &message, nonblocking);
+}
+
+#endif
+
+#if defined(RT_CAN_TC_USING_EXTERNAL_DEVICE) && \
+ defined(RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST) && \
+ !defined(RT_CAN_TC_USING_CANFD)
+static rt_err_t can_receive_pattern(struct can_tc_context *context,
+ rt_uint32_t id,
+ rt_uint8_t seed)
+{
+ rt_err_t result;
+ struct rt_can_msg message;
+
+ result = can_fill_message(&message, id, seed, CAN_TC_CLASSIC_DLC, RT_FALSE, RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ return can_receive_expected(context, &message);
+}
+#endif
+
+#ifdef RT_CAN_TC_USING_CANFD
+static rt_err_t can_build_fd_messages(struct rt_can_msg messages[2],
+ rt_uint32_t id,
+ rt_uint8_t seed,
+ rt_size_t *message_count)
+{
+ rt_err_t result;
+ rt_uint8_t selected_dlc;
+
+ result = can_fill_message(&messages[0], id, seed, CAN_MSG_12BYTES, RT_TRUE, RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ *message_count = 1;
+
+ result = can_payload_length_to_dlc(RT_CAN_TC_CANFD_DATA_LENGTH, &selected_dlc);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ if (selected_dlc == CAN_MSG_12BYTES && !CAN_TC_SELECTED_FD_BRS)
+ {
+ return RT_EOK;
+ }
+
+ result = can_fill_message(&messages[1],
+ id,
+ (rt_uint8_t)(seed + 0x40),
+ selected_dlc,
+ RT_TRUE,
+ CAN_TC_SELECTED_FD_BRS);
+ if (result == RT_EOK)
+ {
+ *message_count = 2;
+ }
+
+ return result;
+}
+
+#if defined(RT_CAN_TC_USING_SELF_LOOPBACK) || defined(RT_CAN_TC_USING_DUAL_DEVICE)
+static rt_err_t can_transfer_fd_patterns(struct can_tc_context *tx_context,
+ struct can_tc_context *rx_context,
+ rt_uint32_t id,
+ rt_uint8_t seed)
+{
+ rt_err_t result;
+ rt_size_t i;
+ rt_size_t message_count;
+ struct rt_can_msg messages[2];
+
+ result = can_build_fd_messages(messages, id, seed, &message_count);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ for (i = 0; i < message_count; i++)
+ {
+ result = can_transfer_message(tx_context, rx_context, &messages[i], RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+
+ return RT_EOK;
+}
+#endif
+
+#ifdef RT_CAN_TC_EXTERNAL_USING_SEND_TEST
+static rt_err_t can_send_fd_patterns(struct can_tc_context *context,
+ rt_uint32_t id,
+ rt_uint8_t seed)
+{
+ rt_err_t result;
+ rt_size_t i;
+ rt_size_t message_count;
+ struct rt_can_msg messages[2];
+
+ result = can_build_fd_messages(messages, id, seed, &message_count);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ for (i = 0; i < message_count; i++)
+ {
+ result = can_send_message(context, &messages[i], RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+
+ return RT_EOK;
+}
+#endif
+
+#ifdef RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST
+static void can_log_missing_frame(const struct rt_can_msg *message)
+{
+#ifdef RT_CAN_USING_CANFD
+ LOG_E("CAN expected frame missing: id=0x%x, dlc=%u, fd=%u, brs=%u",
+ message->id, message->len, message->fd_frame, message->brs);
+#else
+ LOG_E("CAN expected frame missing: id=0x%x, dlc=%u",
+ message->id, message->len);
+#endif
+}
+
+static void can_log_unmatched_frame(const struct rt_can_msg *message)
+{
+ LOG_W("CAN RX unmatched: id=0x%x, ide=%u, rtr=%u, dlc=%u, fd=%u, brs=%u",
+ message->id, message->ide, message->rtr,
+ message->len, message->fd_frame, message->brs);
+ LOG_W("CAN RX data[0..7]: %02x %02x %02x %02x %02x %02x %02x %02x",
+ message->data[0], message->data[1], message->data[2], message->data[3],
+ message->data[4], message->data[5], message->data[6], message->data[7]);
+ if (message->len > CAN_MSG_8BYTES)
+ {
+ LOG_W("CAN RX data[8..15]: %02x %02x %02x %02x %02x %02x %02x %02x",
+ message->data[8], message->data[9], message->data[10], message->data[11],
+ message->data[12], message->data[13], message->data[14], message->data[15]);
+ }
+}
+
+static rt_err_t can_receive_external_patterns(struct can_tc_context *context,
+ rt_uint32_t id,
+ rt_uint8_t seed)
+{
+ rt_bool_t received[3] = { RT_FALSE, RT_FALSE, RT_FALSE };
+ rt_err_t result;
+ rt_size_t i;
+ rt_size_t received_count;
+ rt_size_t fd_message_count;
+ rt_size_t expected_count;
+ rt_size_t unmatched_log_count;
+ rt_tick_t progress_tick;
+ rt_tick_t timeout;
+ struct rt_can_msg messages[3];
+
+ result = can_fill_message(&messages[0], id, seed,
+ CAN_TC_CLASSIC_DLC, RT_FALSE, RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ result = can_build_fd_messages(&messages[1], id, seed, &fd_message_count);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ expected_count = fd_message_count + 1;
+ received_count = 0;
+ unmatched_log_count = 0;
+ timeout = rt_tick_from_millisecond(RT_CAN_TC_TIMEOUT_MS);
+ if (timeout == 0)
+ {
+ timeout = 1;
+ }
+ progress_tick = rt_tick_get();
+
+ while (received_count < expected_count)
+ {
+ rt_ssize_t read_size;
+ rt_tick_t elapsed;
+ rt_tick_t remaining;
+ struct rt_can_msg actual;
+
+ rt_memset(&actual, 0, sizeof(actual));
+ actual.hdr_index = -1;
+ read_size = rt_device_read(context->dev, 0, &actual, sizeof(actual));
+ if (read_size == sizeof(actual))
+ {
+ for (i = 0; i < expected_count; i++)
+ {
+ if (!received[i] && can_message_equal(&messages[i], &actual))
+ {
+ received[i] = RT_TRUE;
+ received_count++;
+ progress_tick = rt_tick_get();
+ LOG_I("CAN RX matched: id=0x%x, dlc=%u, fd=%u, brs=%u",
+ actual.id, actual.len, actual.fd_frame, actual.brs);
+ break;
+ }
+ }
+ if (i == expected_count && unmatched_log_count < 8)
+ {
+ can_log_unmatched_frame(&actual);
+ unmatched_log_count++;
+ }
+ if (received_count == expected_count)
+ {
+ break;
+ }
+ }
+ if (read_size < 0)
+ {
+ LOG_E("CAN read failed on %s, result=%d",
+ context->dev->parent.name, (int)read_size);
+ return (rt_err_t)read_size;
+ }
+
+ /* Repeated or unrelated frames must not bypass the no-progress timeout. */
+ elapsed = (rt_tick_t)(rt_tick_get() - progress_tick);
+ if (elapsed >= timeout)
+ {
+ for (i = 0; i < expected_count; i++)
+ {
+ if (!received[i])
+ {
+ can_log_missing_frame(&messages[i]);
+ }
+ }
+ can_log_status(context);
+ return -RT_ETIMEOUT;
+ }
+
+ if (read_size != sizeof(actual))
+ {
+ remaining = timeout - elapsed;
+ rt_sem_take(&context->rx_sem, remaining);
+ }
+ }
+
+ return RT_EOK;
+}
+#endif
+#endif
+
+static rt_err_t can_close_context(struct can_tc_context *context)
+{
+ rt_err_t result;
+
+ if (context == RT_NULL || !context->opened)
+ {
+ return RT_EOK;
+ }
+
+ result = rt_device_close(context->dev);
+ context->opened = RT_FALSE;
+ return result;
+}
+
+static void can_try_start(struct can_tc_context *context, rt_bool_t enable)
+{
+ rt_err_t result;
+
+ result = rt_device_control(context->dev,
+ RT_CAN_CMD_START,
+ (void *)(rt_ubase_t)enable);
+ if (result != RT_EOK)
+ {
+ LOG_D("CAN start/stop command is not supported by %s, result=%d",
+ context->dev->parent.name, result);
+ }
+}
+
+static void can_build_configuration(struct can_tc_context *context,
+ struct can_configure *configuration,
+ rt_uint32_t baud_rate,
+ rt_uint32_t mode,
+ rt_bool_t enable_canfd)
+{
+ *configuration = context->original_config;
+ configuration->baud_rate = baud_rate;
+ if (configuration->msgboxsz == 0)
+ {
+ configuration->msgboxsz = RT_CANMSG_BOX_SZ;
+ }
+ if (configuration->sndboxnumber == 0)
+ {
+ configuration->sndboxnumber = RT_CANSND_BOX_NUM;
+ }
+ configuration->mode = mode;
+ configuration->privmode = RT_CAN_MODE_NOPRIV;
+
+#ifdef RT_CAN_USING_CANFD
+ configuration->use_bit_timing = 0;
+ configuration->enable_canfd = 0;
+#ifdef RT_CAN_TC_USING_CANFD
+ if (enable_canfd)
+ {
+ configuration->baud_rate_fd = RT_CAN_TC_CANFD_DATA_BAUD_RATE;
+ configuration->enable_canfd = 1;
+ }
+#else
+ RT_UNUSED(enable_canfd);
+#endif
+#else
+ RT_UNUSED(enable_canfd);
+#endif
+}
+
+static rt_err_t can_configure_context(struct can_tc_context *context,
+ rt_uint32_t baud_rate,
+ rt_uint32_t mode,
+ rt_bool_t enable_canfd)
+{
+ rt_err_t result;
+ struct can_configure configuration;
+ struct can_configure previous_configuration;
+
+ can_build_configuration(context, &configuration, baud_rate, mode, enable_canfd);
+ previous_configuration = context->can->config;
+
+ /* The first open lazily initializes CAN with can->config, so update the cache first. */
+ context->can->config = configuration;
+ context->configuration_touched = RT_TRUE;
+ if (!(context->dev->flag & RT_DEVICE_FLAG_ACTIVATED))
+ {
+ result = rt_device_init(context->dev);
+ }
+ else
+ {
+ result = rt_device_control(context->dev, RT_DEVICE_CTRL_CONFIG, &configuration);
+ }
+ if (result != RT_EOK)
+ {
+ context->can->config = previous_configuration;
+ LOG_E("Configure %s failed at %u bit/s, result=%d",
+ context->dev->parent.name, baud_rate, result);
+ return result;
+ }
+
+ result = rt_device_open(context->dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX);
+ if (result != RT_EOK)
+ {
+ LOG_E("Open CAN device %s failed, result=%d",
+ context->dev->parent.name, result);
+ return result;
+ }
+ context->opened = RT_TRUE;
+
+ result = rt_device_set_rx_indicate(context->dev, can_rx_indicate);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+ can_try_start(context, RT_TRUE);
+ return RT_EOK;
+}
+
+static rt_err_t can_prepare_devices(rt_uint32_t baud_rate,
+ rt_uint32_t mode,
+ rt_bool_t enable_canfd)
+{
+ rt_err_t result;
+
+ result = can_close_context(can_tx_ctx);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ if (can_rx_ctx != can_tx_ctx)
+ {
+ result = can_close_context(can_rx_ctx);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+
+ result = can_configure_context(can_tx_ctx, baud_rate, mode, enable_canfd);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ if (can_rx_ctx != can_tx_ctx)
+ {
+ result = can_configure_context(can_rx_ctx, baud_rate, mode, enable_canfd);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+
+ LOG_I("CAN phase: arbitration=%u bit/s, CAN FD=%s",
+ baud_rate, enable_canfd ? "on" : "off");
+ return RT_EOK;
+}
+
+static rt_bool_t can_phase_uses_canfd(rt_uint32_t baud_rate)
+{
+#ifdef RT_CAN_TC_USING_CANFD
+ return baud_rate == RT_CAN_TC_BAUD_RATE;
+#else
+ RT_UNUSED(baud_rate);
+ return RT_FALSE;
+#endif
+}
+
+static rt_err_t can_find_device(const char *name, rt_device_t *device)
+{
+ rt_device_t found;
+
+ found = rt_device_find(name);
+ if (found == RT_NULL)
+ {
+ LOG_E("Find CAN device %s failed", name);
+ return -RT_ERROR;
+ }
+ if (found->type != RT_Device_Class_CAN)
+ {
+ LOG_E("Device %s is not a CAN device", name);
+ return -RT_ERROR;
+ }
+ if (found->ref_count != 0)
+ {
+ LOG_E("CAN device %s is busy", name);
+ return -RT_EBUSY;
+ }
+
+ *device = found;
+ return RT_EOK;
+}
+
+static rt_err_t can_init_context(struct can_tc_context *context,
+ rt_device_t device,
+ const char *semaphore_name)
+{
+ rt_err_t result;
+
+ rt_memset(context, 0, sizeof(*context));
+ context->dev = device;
+ context->can = (struct rt_can_device *)device;
+ context->original_config = context->can->config;
+ context->original_rx_indicate = device->rx_indicate;
+
+ result = rt_sem_init(&context->rx_sem, semaphore_name, 0, RT_IPC_FLAG_FIFO);
+ if (result == RT_EOK)
+ {
+ context->sem_inited = RT_TRUE;
+ }
+
+ return result;
+}
+
+static void can_detach_context(struct can_tc_context *context)
+{
+ if (context->sem_inited)
+ {
+ rt_sem_detach(&context->rx_sem);
+ }
+ rt_memset(context, 0, sizeof(*context));
+}
+
+static rt_err_t can_validate_configuration(void)
+{
+ if (!can_is_standard_baud_rate(RT_CAN_TC_BAUD_RATE))
+ {
+ LOG_E("Unsupported CAN arbitration rate: %u", RT_CAN_TC_BAUD_RATE);
+ return -RT_EINVAL;
+ }
+
+#ifdef RT_CAN_TC_USING_EXTERNAL_DEVICE
+#if !defined(RT_CAN_TC_EXTERNAL_USING_SEND_TEST) && !defined(RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST)
+ LOG_E("External CAN test requires at least one send or receive subtest");
+ return -RT_EINVAL;
+#endif
+#endif
+
+#ifdef RT_CAN_TC_USING_CANFD
+ {
+ rt_uint8_t dlc;
+
+ if (can_payload_length_to_dlc(RT_CAN_TC_CANFD_DATA_LENGTH, &dlc) != RT_EOK ||
+ RT_CAN_TC_CANFD_DATA_LENGTH <= 8)
+ {
+ LOG_E("Unsupported CAN FD payload length: %u", RT_CAN_TC_CANFD_DATA_LENGTH);
+ return -RT_EINVAL;
+ }
+ if (CAN_TC_SELECTED_FD_BRS &&
+ RT_CAN_TC_CANFD_DATA_BAUD_RATE < RT_CAN_TC_BAUD_RATE)
+ {
+ LOG_E("CAN FD BRS data rate must not be lower than the arbitration rate");
+ return -RT_EINVAL;
+ }
+ }
+#endif
+
+ return RT_EOK;
+}
+
+#ifdef RT_CAN_TC_USING_EXTERNAL_DEVICE
+static void can_print_external_agreement(void)
+{
+ LOG_I("CAN external ready: arbitration=%u bit/s, payload[i]=seed+i",
+ RT_CAN_TC_BAUD_RATE);
+#ifdef RT_CAN_TC_EXTERNAL_USING_SEND_TEST
+ LOG_I("DUT TX[1]: classic std id=0x%x, raw DLC=8, seed=0x10",
+ RT_CAN_TC_TX_FRAME_ID);
+#ifdef RT_CAN_TC_USING_CANFD
+ LOG_I("DUT TX[2]: CAN FD std id=0x%x, raw DLC=9, BRS=0, seed=0x10",
+ RT_CAN_TC_TX_FRAME_ID);
+#if RT_CAN_TC_CANFD_DATA_LENGTH != 12 || defined(RT_CAN_TC_CANFD_BRS)
+ {
+ rt_uint8_t selected_dlc;
+
+ can_payload_length_to_dlc(RT_CAN_TC_CANFD_DATA_LENGTH, &selected_dlc);
+ LOG_I("DUT TX[3]: CAN FD std id=0x%x, raw DLC=%u, BRS=%u, seed=0x50",
+ RT_CAN_TC_TX_FRAME_ID, selected_dlc, CAN_TC_SELECTED_FD_BRS);
+ }
+#endif
+#endif
+#endif
+#ifdef RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST
+ LOG_I("Peer TX[1]: classic std id=0x%x, raw DLC=8, seed=0x20",
+ RT_CAN_TC_RX_FRAME_ID);
+#ifdef RT_CAN_TC_USING_CANFD
+ LOG_I("Peer TX[2]: CAN FD std id=0x%x, raw DLC=9, BRS=0, seed=0x20",
+ RT_CAN_TC_RX_FRAME_ID);
+#if RT_CAN_TC_CANFD_DATA_LENGTH != 12 || defined(RT_CAN_TC_CANFD_BRS)
+ {
+ rt_uint8_t selected_dlc;
+
+ can_payload_length_to_dlc(RT_CAN_TC_CANFD_DATA_LENGTH, &selected_dlc);
+ LOG_I("Peer TX[3]: CAN FD std id=0x%x, raw DLC=%u, BRS=%u, seed=0x60",
+ RT_CAN_TC_RX_FRAME_ID, selected_dlc, CAN_TC_SELECTED_FD_BRS);
+ }
+#endif
+#endif
+#endif
+#ifdef RT_CAN_TC_USING_NONBLOCKING_SEND_TEST
+ LOG_I("DUT nonblocking TX: classic std id=0x%x, raw DLC=8, seed=0x10",
+ RT_CAN_TC_NONBLOCKING_TX_FRAME_ID);
+#endif
+#ifdef RT_CAN_TC_USING_CANFD
+ LOG_I("CAN FD configured data rate=%u bit/s", RT_CAN_TC_CANFD_DATA_BAUD_RATE);
+#endif
+}
+#endif
+
+#ifdef RT_CAN_TC_USING_SELF_LOOPBACK
+static rt_err_t can_run_self_loopback(void)
+{
+ rt_err_t result;
+ rt_size_t i;
+
+ for (i = 0; i < sizeof(can_tc_baud_rates) / sizeof(can_tc_baud_rates[0]); i++)
+ {
+ rt_bool_t enable_canfd;
+ rt_uint32_t baud_rate;
+
+ baud_rate = can_tc_baud_rates[i];
+ if (!can_baud_rate_is_selected(baud_rate))
+ {
+ continue;
+ }
+ enable_canfd = can_phase_uses_canfd(baud_rate);
+
+ result = can_prepare_devices(baud_rate, RT_CAN_MODE_LOOPBACK, enable_canfd);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ result = can_transfer_pattern(can_tx_ctx,
+ can_rx_ctx,
+ CAN_TC_FRAME_ID_BASE,
+ CAN_TC_TX_SEED,
+ RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+#ifdef RT_CAN_TC_USING_NONBLOCKING_SEND_TEST
+ result = can_transfer_pattern(can_tx_ctx,
+ can_rx_ctx,
+ RT_CAN_TC_NONBLOCKING_TX_FRAME_ID,
+ CAN_TC_TX_SEED,
+ RT_TRUE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+#endif
+
+#ifdef RT_CAN_TC_USING_CANFD
+ if (enable_canfd)
+ {
+ result = can_transfer_fd_patterns(can_tx_ctx,
+ can_rx_ctx,
+ CAN_TC_FD_FRAME_ID_BASE,
+ CAN_TC_TX_SEED);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+#endif
+ }
+
+ return RT_EOK;
+}
+
+static void can_self_loopback_test(void)
+{
+ uassert_int_equal(can_run_self_loopback(), RT_EOK);
+}
+#endif
+
+#ifdef RT_CAN_TC_USING_DUAL_DEVICE
+static rt_err_t can_run_dual_device(void)
+{
+ rt_err_t result;
+ rt_size_t i;
+
+ for (i = 0; i < sizeof(can_tc_baud_rates) / sizeof(can_tc_baud_rates[0]); i++)
+ {
+ rt_bool_t enable_canfd;
+ rt_uint32_t baud_rate;
+
+ baud_rate = can_tc_baud_rates[i];
+ if (!can_baud_rate_is_selected(baud_rate))
+ {
+ continue;
+ }
+ enable_canfd = can_phase_uses_canfd(baud_rate);
+
+ result = can_prepare_devices(baud_rate, RT_CAN_MODE_NORMAL, enable_canfd);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ result = can_transfer_pattern(can_tx_ctx,
+ can_rx_ctx,
+ CAN_TC_FRAME_ID_BASE,
+ CAN_TC_TX_SEED,
+ RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ result = can_transfer_pattern(can_rx_ctx,
+ can_tx_ctx,
+ CAN_TC_FRAME_ID_BASE + 1,
+ CAN_TC_RX_SEED,
+ RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+#ifdef RT_CAN_TC_USING_NONBLOCKING_SEND_TEST
+ result = can_transfer_pattern(can_tx_ctx,
+ can_rx_ctx,
+ RT_CAN_TC_NONBLOCKING_TX_FRAME_ID,
+ CAN_TC_TX_SEED,
+ RT_TRUE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+#endif
+
+#ifdef RT_CAN_TC_USING_CANFD
+ if (enable_canfd)
+ {
+ result = can_transfer_fd_patterns(can_tx_ctx,
+ can_rx_ctx,
+ CAN_TC_FD_FRAME_ID_BASE,
+ CAN_TC_TX_SEED);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ result = can_transfer_fd_patterns(can_rx_ctx,
+ can_tx_ctx,
+ CAN_TC_REVERSE_FD_FRAME_ID_BASE,
+ CAN_TC_RX_SEED);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ }
+#endif
+ }
+
+ return RT_EOK;
+}
+
+static void can_dual_device_test(void)
+{
+ uassert_int_equal(can_run_dual_device(), RT_EOK);
+}
+#endif
+
+#ifdef RT_CAN_TC_USING_EXTERNAL_DEVICE
+static rt_err_t can_run_external_device(void)
+{
+ rt_err_t result;
+ rt_bool_t enable_canfd;
+
+ enable_canfd = can_phase_uses_canfd(RT_CAN_TC_BAUD_RATE);
+ result = can_prepare_devices(RT_CAN_TC_BAUD_RATE, RT_CAN_MODE_NORMAL, enable_canfd);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+#ifdef RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST
+ can_drain_receive(can_rx_ctx);
+#endif
+ can_print_external_agreement();
+ if (RT_CAN_TC_EXTERNAL_READY_DELAY_MS > 0)
+ {
+ rt_thread_mdelay(RT_CAN_TC_EXTERNAL_READY_DELAY_MS);
+ }
+
+#ifdef RT_CAN_TC_EXTERNAL_USING_SEND_TEST
+ result = can_send_pattern(can_tx_ctx,
+ RT_CAN_TC_TX_FRAME_ID,
+ CAN_TC_TX_SEED,
+ RT_FALSE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+#ifdef RT_CAN_TC_USING_CANFD
+ result = can_send_fd_patterns(can_tx_ctx, RT_CAN_TC_TX_FRAME_ID, CAN_TC_TX_SEED);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+#endif
+#endif
+
+#ifdef RT_CAN_TC_EXTERNAL_USING_RECEIVE_TEST
+#ifdef RT_CAN_TC_USING_CANFD
+ result = can_receive_external_patterns(can_rx_ctx,
+ RT_CAN_TC_RX_FRAME_ID,
+ CAN_TC_RX_SEED);
+#else
+ result = can_receive_pattern(can_rx_ctx, RT_CAN_TC_RX_FRAME_ID, CAN_TC_RX_SEED);
+#endif
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+#endif
+
+#ifdef RT_CAN_TC_USING_NONBLOCKING_SEND_TEST
+ result = can_send_pattern(can_tx_ctx,
+ RT_CAN_TC_NONBLOCKING_TX_FRAME_ID,
+ CAN_TC_TX_SEED,
+ RT_TRUE);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ LOG_I("External nonblocking frame was accepted or queued");
+ rt_thread_mdelay(CAN_TC_EXTERNAL_TX_DRAIN_MS);
+#endif
+
+ return RT_EOK;
+}
+
+static void can_external_device_test(void)
+{
+ uassert_int_equal(can_run_external_device(), RT_EOK);
+}
+#endif
+
+static rt_err_t utest_tc_init(void)
+{
+ rt_err_t result;
+ rt_device_t tx_device;
+ rt_device_t rx_device;
+
+ can_tx_ctx = RT_NULL;
+ can_rx_ctx = RT_NULL;
+ rt_memset(&can_tx_context, 0, sizeof(can_tx_context));
+ rt_memset(&can_rx_context, 0, sizeof(can_rx_context));
+
+ result = can_validate_configuration();
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+#ifdef RT_CAN_TC_USING_DUAL_DEVICE
+ result = can_find_device(RT_CAN_TC_TX_DEVICE_NAME, &tx_device);
+#else
+ result = can_find_device(RT_CAN_TC_DEVICE_NAME, &tx_device);
+#endif
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+
+#ifdef RT_CAN_TC_USING_DUAL_DEVICE
+ result = can_find_device(RT_CAN_TC_RX_DEVICE_NAME, &rx_device);
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ if (rx_device == tx_device)
+ {
+ LOG_E("Dual CAN test requires different TX and RX devices");
+ return -RT_EINVAL;
+ }
+#else
+ rx_device = tx_device;
+#endif
+
+ result = can_init_context(&can_tx_context, tx_device, "cantx");
+ if (result != RT_EOK)
+ {
+ return result;
+ }
+ can_tx_ctx = &can_tx_context;
+
+ if (rx_device == tx_device)
+ {
+ can_rx_ctx = can_tx_ctx;
+ }
+ else
+ {
+ result = can_init_context(&can_rx_context, rx_device, "canrx");
+ if (result != RT_EOK)
+ {
+ can_detach_context(&can_tx_context);
+ can_tx_ctx = RT_NULL;
+ return result;
+ }
+ can_rx_ctx = &can_rx_context;
+ }
+
+ return RT_EOK;
+}
+
+static void can_record_cleanup_error(rt_err_t *result, rt_err_t operation_result)
+{
+ if (*result == RT_EOK && operation_result != RT_EOK)
+ {
+ *result = operation_result;
+ }
+}
+
+static rt_err_t can_restore_context(struct can_tc_context *context)
+{
+ rt_err_t result;
+ rt_err_t operation_result;
+
+ result = RT_EOK;
+ operation_result = rt_device_set_rx_indicate(context->dev,
+ context->original_rx_indicate);
+ can_record_cleanup_error(&result, operation_result);
+
+ if (context->configuration_touched)
+ {
+ context->can->config = context->original_config;
+ if (context->dev->flag & RT_DEVICE_FLAG_ACTIVATED)
+ {
+ operation_result = rt_device_control(context->dev,
+ RT_DEVICE_CTRL_CONFIG,
+ &context->original_config);
+ can_record_cleanup_error(&result, operation_result);
+ context->can->config = context->original_config;
+ can_try_start(context, RT_FALSE);
+ }
+ }
+
+ if (context->sem_inited)
+ {
+ operation_result = rt_sem_detach(&context->rx_sem);
+ can_record_cleanup_error(&result, operation_result);
+ }
+
+ return result;
+}
+
+static rt_err_t utest_tc_cleanup(void)
+{
+ rt_err_t result;
+ rt_err_t operation_result;
+
+ result = RT_EOK;
+ if (can_tx_ctx != RT_NULL)
+ {
+ operation_result = can_restore_context(can_tx_ctx);
+ can_record_cleanup_error(&result, operation_result);
+ }
+ if (can_rx_ctx != RT_NULL && can_rx_ctx != can_tx_ctx)
+ {
+ operation_result = can_restore_context(can_rx_ctx);
+ can_record_cleanup_error(&result, operation_result);
+ }
+
+ if (can_tx_ctx != RT_NULL)
+ {
+ operation_result = can_close_context(can_tx_ctx);
+ can_record_cleanup_error(&result, operation_result);
+ }
+ if (can_rx_ctx != RT_NULL && can_rx_ctx != can_tx_ctx)
+ {
+ operation_result = can_close_context(can_rx_ctx);
+ can_record_cleanup_error(&result, operation_result);
+ }
+
+ can_tx_ctx = RT_NULL;
+ can_rx_ctx = RT_NULL;
+ rt_memset(&can_tx_context, 0, sizeof(can_tx_context));
+ rt_memset(&can_rx_context, 0, sizeof(can_rx_context));
+
+ return result;
+}
+
+static void testcase(void)
+{
+#ifdef RT_CAN_TC_USING_SELF_LOOPBACK
+ UTEST_UNIT_RUN(can_self_loopback_test);
+#endif
+#ifdef RT_CAN_TC_USING_EXTERNAL_DEVICE
+ UTEST_UNIT_RUN(can_external_device_test);
+#endif
+#ifdef RT_CAN_TC_USING_DUAL_DEVICE
+ UTEST_UNIT_RUN(can_dual_device_test);
+#endif
+}
+
+UTEST_TC_EXPORT(testcase,
+ "components.drivers.can.send_recv",
+ utest_tc_init,
+ utest_tc_cleanup,
+ CAN_TC_SUITE_TIMEOUT);
+
+#endif /* RT_UTEST_CAN */