From db20b311947e23c97c96049fd9893970f863b5ed Mon Sep 17 00:00:00 2001 From: Gareth Potter Date: Wed, 11 Oct 2023 11:13:46 +0100 Subject: [PATCH] ISO-TP: new ISO-TP implementation - single public context for send and receive; no more unbinding to send a message - CAN-FD support - only supports ISO-TP fixed addressing at present - broadly async approach, but sync receive supported for familiarity/compatibility - conformance tests ported from existing implementation --- .github/workflows/build.yml | 9 +- .gitignore | 1 + CMakeLists.txt | 4 +- Kconfig | 7 +- include/thingset/can.h | 65 +- include/thingset/isotp_fast.h | 142 +++ src/CMakeLists.txt | 6 +- src/Kconfig.isotp_fast | 41 + src/can.c | 193 ++- src/isotp_fast.c | 1057 +++++++++++++++++ src/isotp_fast_internal.h | 101 ++ src/isotp_internal.h | 122 ++ tests/README.md | 11 + tests/can/CMakeLists.txt | 9 + tests/can/prj.conf | 17 + tests/can/src/main.c | 100 ++ tests/can/testcase.yaml | 7 + tests/isotp_fast/conformance/src/main.c | 757 ++++++++++++ tests/isotp_fast/conformance/src/main.h | 93 ++ .../isotp_fast/conformance/src/random_data.h | 73 ++ .../conformance_async/CMakeLists.txt | 8 + tests/isotp_fast/conformance_async/prj.conf | 9 + .../conformance_async/src/async_recv.h | 78 ++ tests/isotp_fast/conformance_async/src/main.c | 10 + .../conformance_async/testcase.yaml | 9 + .../conformance_async_fd/CMakeLists.txt | 8 + .../isotp_fast/conformance_async_fd/prj.conf | 10 + .../conformance_async_fd/src/main.c | 39 + .../conformance_async_fd/testcase.yaml | 9 + .../conformance_sync/CMakeLists.txt | 8 + tests/isotp_fast/conformance_sync/prj.conf | 9 + tests/isotp_fast/conformance_sync/src/main.c | 10 + .../conformance_sync/src/sync_recv.h | 23 + .../isotp_fast/conformance_sync/testcase.yaml | 9 + tests/thingset_isotp_fast/CMakeLists.txt | 9 + tests/thingset_isotp_fast/prj.conf | 19 + tests/thingset_isotp_fast/src/main.c | 99 ++ tests/thingset_isotp_fast/testcase.yaml | 7 + west.yml | 2 +- 39 files changed, 3172 insertions(+), 18 deletions(-) create mode 100644 include/thingset/isotp_fast.h create mode 100644 src/Kconfig.isotp_fast create mode 100644 src/isotp_fast.c create mode 100644 src/isotp_fast_internal.h create mode 100644 src/isotp_internal.h create mode 100644 tests/README.md create mode 100644 tests/can/CMakeLists.txt create mode 100644 tests/can/prj.conf create mode 100644 tests/can/src/main.c create mode 100644 tests/can/testcase.yaml create mode 100644 tests/isotp_fast/conformance/src/main.c create mode 100644 tests/isotp_fast/conformance/src/main.h create mode 100644 tests/isotp_fast/conformance/src/random_data.h create mode 100644 tests/isotp_fast/conformance_async/CMakeLists.txt create mode 100644 tests/isotp_fast/conformance_async/prj.conf create mode 100644 tests/isotp_fast/conformance_async/src/async_recv.h create mode 100644 tests/isotp_fast/conformance_async/src/main.c create mode 100644 tests/isotp_fast/conformance_async/testcase.yaml create mode 100644 tests/isotp_fast/conformance_async_fd/CMakeLists.txt create mode 100644 tests/isotp_fast/conformance_async_fd/prj.conf create mode 100644 tests/isotp_fast/conformance_async_fd/src/main.c create mode 100644 tests/isotp_fast/conformance_async_fd/testcase.yaml create mode 100644 tests/isotp_fast/conformance_sync/CMakeLists.txt create mode 100644 tests/isotp_fast/conformance_sync/prj.conf create mode 100644 tests/isotp_fast/conformance_sync/src/main.c create mode 100644 tests/isotp_fast/conformance_sync/src/sync_recv.h create mode 100644 tests/isotp_fast/conformance_sync/testcase.yaml create mode 100644 tests/thingset_isotp_fast/CMakeLists.txt create mode 100644 tests/thingset_isotp_fast/prj.conf create mode 100644 tests/thingset_isotp_fast/src/main.c create mode 100644 tests/thingset_isotp_fast/testcase.yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6fef974..671a46f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest - container: zephyrprojectrtos/ci:v0.26.2 + container: zephyrprojectrtos/ci:v0.26.5 env: CMAKE_PREFIX_PATH: /opt/toolchains steps: @@ -36,7 +36,7 @@ jobs: sudo apt install -y git make python3 python3-pip doxygen pip3 install -r docs/requirements.txt - - name: Run build tests + - name: Run sample build tests working-directory: thingset-zephyr-sdk run: | west build -p -b olimex_lora_stm32wl_devkit samples/counter -- -DOVERLAY_CONFIG=lorawan.conf @@ -51,6 +51,11 @@ jobs: west build -p -b native_posix samples/counter -- -DOVERLAY_CONFIG=native_websocket.conf west build -p -b xiao_esp32c3 samples/serial_ble_gateway + - name: Run unit tests + working-directory: thingset-zephyr-sdk + run: | + ../zephyr/scripts/twister -T ./tests --integration --inline-logs + - name: Build documentation working-directory: thingset-zephyr-sdk run: | diff --git a/.gitignore b/.gitignore index 9ffcc4e..8650111 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # folders generated by west build +twister-out* # editors .vscode/* diff --git a/CMakeLists.txt b/CMakeLists.txt index d67b306..ada1185 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ # This CMake file is picked by the Zephyr build system because it is defined # as the module CMake entry point (see zephyr/module.yml). -add_subdirectory_ifdef(CONFIG_THINGSET_SDK src) +if (CONFIG_ISOTP_FAST OR CONFIG_THINGSET_SDK) + add_subdirectory(src) +endif() zephyr_include_directories(include) diff --git a/Kconfig b/Kconfig index f9fda40..6759a30 100644 --- a/Kconfig +++ b/Kconfig @@ -1,6 +1,8 @@ # Copyright (c) The ThingSet Project Contributors # SPDX-License-Identifier: Apache-2.0 +rsource "src/Kconfig.isotp_fast" + menuconfig THINGSET_SDK bool "ThingSet SDK" @@ -69,8 +71,11 @@ config THINGSET_NODE_NAME config THINGSET_SHARED_TX_BUF_SIZE int "Shared TX buffer size" - range 256 2048 + range 256 10240 default 1024 + help + This buffer is used to create ThingSet responses for the different interfaces. It has to be + large enough to fit the largest expected response. config THINGSET_SDK_THREAD_STACK_SIZE int "Common thread stack size" diff --git a/include/thingset/can.h b/include/thingset/can.h index aa867cd..e3c7be0 100644 --- a/include/thingset/can.h +++ b/include/thingset/can.h @@ -7,6 +7,7 @@ #ifndef THINGSET_CAN_H_ #define THINGSET_CAN_H_ +#include "isotp_fast.h" #include #include @@ -138,6 +139,19 @@ extern "C" { */ typedef void (*thingset_can_report_rx_callback_t)(uint16_t data_id, const uint8_t *value, size_t value_len, uint8_t source_addr); +#ifdef CONFIG_ISOTP_FAST +typedef void (*thingset_can_response_callback_t)(uint8_t *data, size_t len, int result, + uint8_t sender_id, void *arg); + +struct thingset_can_request_response +{ + struct k_sem sem; + struct k_timer timer; + isotp_fast_msg_id sender_addr; + thingset_can_response_callback_t callback; + void *cb_arg; +}; +#endif /* CONFIG_ISOTP_FAST */ /** * ThingSet CAN context storing all information required for one instance. @@ -147,13 +161,20 @@ struct thingset_can const struct device *dev; struct k_work_delayable reporting_work; struct k_work_delayable addr_claim_work; +#ifdef CONFIG_ISOTP_FAST + struct isotp_fast_ctx ctx; +#else struct isotp_recv_ctx recv_ctx; struct isotp_send_ctx send_ctx; struct isotp_msg_id rx_addr; struct isotp_msg_id tx_addr; +#endif struct k_event events; - thingset_can_report_rx_callback_t report_rx_cb; +#ifdef CONFIG_ISOTP_FAST + struct thingset_can_request_response request_response; +#endif uint8_t rx_buffer[CONFIG_THINGSET_CAN_RX_BUF_SIZE]; + thingset_can_report_rx_callback_t report_rx_cb; int64_t next_pub_time; uint8_t node_addr; bool pub_enable; @@ -175,6 +196,25 @@ struct thingset_can int thingset_can_receive_inst(struct thingset_can *ts_can, uint8_t *rx_buf, size_t rx_buf_size, uint8_t *source_addr, k_timeout_t timeout); +#ifdef CONFIG_ISOTP_FAST +/** + * Send ThingSet message to other node + * + * @param ts_can Pointer to the thingset_can context. + * @param tx_buf Buffer containing the message. + * @param tx_len Length of the message. + * @param target_addr Target node address (8-bit value) to send the message to. + * @param rsp_callback If a response is expected, this callback will be invoked, + * either when it arrives or if a timeout or some other error occurs. + * @param callback_arg User data for the callback. + * @param rsp_timeout Timeout to wait for a response. + * + * @returns 0 for success or negative errno in case of error + */ +int thingset_can_send_inst(struct thingset_can *ts_can, uint8_t *tx_buf, size_t tx_len, + uint8_t target_addr, thingset_can_response_callback_t rsp_callback, + void *callback_arg, k_timeout_t timeout); +#else /** * Send ThingSet message to other node * @@ -206,6 +246,7 @@ int thingset_can_send_inst(struct thingset_can *ts_can, uint8_t *tx_buf, size_t * @retval -EAGAIN in case of timeout */ int thingset_can_process_inst(struct thingset_can *ts_can, k_timeout_t timeout); +#endif /* CONFIG_ISOTP_FAST */ /** * Set callback for received reports from other nodes @@ -232,6 +273,20 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can #else /* !CONFIG_THINGSET_CAN_MULTIPLE_INSTANCES */ +#ifdef CONFIG_ISOTP_FAST +/** + * Send ThingSet message to other node + * + * @param tx_buf Buffer containing the message. + * @param tx_len Length of the message. + * @param target_addr Target node address (8-bit value) to send the message to. + * + * @returns 0 for success or negative errno in case of error + */ +int thingset_can_send(uint8_t *tx_buf, size_t tx_len, uint8_t target_addr, + thingset_can_response_callback_t rsp_callback, void *callback_arg, + k_timeout_t timeout); +#else /** * Send ThingSet message to other node * @@ -242,6 +297,7 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can * @returns 0 for success or negative errno in case of error */ int thingset_can_send(uint8_t *tx_buf, size_t tx_len, uint8_t target_addr); +#endif /* CONFIG_ISOTP_FAST */ /** * Set callback for received reports from other nodes @@ -255,6 +311,13 @@ int thingset_can_send(uint8_t *tx_buf, size_t tx_len, uint8_t target_addr); */ int thingset_can_set_report_rx_callback(thingset_can_report_rx_callback_t rx_cb); +/** + * Get ThingSet CAN instance + * + * @returns Pointer to internal ThingSet CAN instance + */ +struct thingset_can *thingset_can_get_inst(); + #endif /* CONFIG_THINGSET_CAN_MULTIPLE_INSTANCES */ #ifdef __cplusplus diff --git a/include/thingset/isotp_fast.h b/include/thingset/isotp_fast.h new file mode 100644 index 0000000..6c3ba43 --- /dev/null +++ b/include/thingset/isotp_fast.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef CONFIG_ISOTP_FAST +#include + +#ifndef ISOTP_MSG_FDF +#define ISOTP_MSG_FDF BIT(3) +#endif + +/* Represents a sender or a receiver in an ISO-TP fixed addressing scheme */ +typedef uint8_t isotp_fast_node_id; +/* ISO-TP message ID, i.e. the CAN ID */ +typedef uint32_t isotp_fast_msg_id; + +/** + * Callback invoked when a message is received. + * @param buffer Pointer to a @ref net_buf. Call @ref net_buf_frags_len to + * obtain the length of the buffer, then @ref net_buf_linearize to copy the + * contents of the buffer into a local buffer. + * @param rem_len At present, this should always be zero. In future, this + * callback may be called repeatedly as a message's packets arrive to reduce + * the need to buffer an entire message in memory before it is dispatched + * to user code. + * @param sender_addr The CAN ID of the message that has been received. + * @param arg The value of @ref recv_cb_arg passed to @ref isotp_fast_bind. + */ +typedef void (*isotp_fast_recv_callback_t)(struct net_buf *buffer, int rem_len, + isotp_fast_msg_id sender_addr, void *arg); + +/** + * Callback invoked when an error occurs during message receiption. + * @param error The error code. + * @param sender_addr The CAN ID of the sender of the message, if available. + * @param arg The value of @ref recv_cb_arg passed to @ref isotp_fast_bind. + */ +typedef void (*isotp_fast_recv_error_callback_t)(int8_t error, isotp_fast_msg_id sender_addr, + void *arg); + +/** + * Callback invoked when a message has been sent. + * @param result If non-zero, an error has occurred. + * @param arg The value of @ref sent_cb_arg passed to @ref isotp_fast_send. + */ +typedef void (*isotp_fast_send_callback_t)(int result, void *arg); + +/** + * Options pertaining to the bound context. + */ +struct isotp_fast_opts +{ + uint8_t bs; /**< Block size. Number of CF PDUs before next CF is sent */ + uint8_t stmin; /**< Minimum separation time. Min time between frames */ + uint8_t flags; +}; + +/** + * General context object. + */ +struct isotp_fast_ctx +{ + /* The CAN device to which the context is bound via @ref isotp_fast_bind */ + const struct device *can_dev; + /* Identifies the CAN filter which filters incoming messages */ + int filter_id; + /* Pointer to context options described above */ + const struct isotp_fast_opts *opts; + /* Callback that is invoked when a message is received */ + isotp_fast_recv_callback_t recv_callback; + /* Pointer to user-supplied data to be passed to @ref recv_callback */ + void *recv_cb_arg; + /* Callback that is invoked when a receive error occurs */ + isotp_fast_recv_error_callback_t recv_error_callback; + /* Callback that is invoked when a message is sent */ + isotp_fast_send_callback_t sent_callback; + /* CAN ID of this node, used in both transmission and receipt of messages */ + isotp_fast_msg_id my_addr; +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + sys_slist_t wait_recv_list; +#endif +}; + +/** + * Binds the supplied ISO-TP context to the supplied CAN device. Messages + * addressed to the given address (@ref my_addr) will be delivered to user + * code by invoking the supplied callback, @ref recv_callback. + * + * @param ctx A pointer to the general ISO-TP context + * @param can_dev The CAN device to which the context should be bound + * @param my_addr The address to listen on for incoming messages or to use + * when transmitting messages + * @param opts A pointer to an options structure, @ref isotp_fast_opts + * @param recv_callback A callback that is invoked when a message is received + * @param recv_cb_arg A pointer to data to be supplied to @ref recv_callback + * @param recv_error_callback A callback that is invoked when an error occurs. + * @param sent_callback A callback that is invoked when a message is sent + * + * @returns 0 on success, otherwise an error code < 0. + */ +int isotp_fast_bind(struct isotp_fast_ctx *ctx, const struct device *can_dev, + const isotp_fast_msg_id my_addr, const struct isotp_fast_opts *opts, + isotp_fast_recv_callback_t recv_callback, void *recv_cb_arg, + isotp_fast_recv_error_callback_t recv_error_callback, + isotp_fast_send_callback_t sent_callback); + +/** + * Unbinds the supplied ISO-TP context. Removes the CAN filter if it was + * successfully set. + * + * @param ctx A pointer to the context to unbind + * + * @returns 0 on success. + */ +int isotp_fast_unbind(struct isotp_fast_ctx *ctx); + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE +int isotp_fast_recv(struct isotp_fast_ctx *ctx, struct can_filter sender, uint8_t *buf, size_t size, + k_timeout_t timeout); +#endif + +/** + * Send a message to a given recipient. If the message fits within a + * CAN frame, it will be sent synchronously. If not, it will be sent + * asynchronously. + * + * @param ctx The bound context on which the message should be sent + * @param data A pointer to the data containing the message to send + * @param len The length of the data in @ref data + * @param their_id The node ID identifying the recipient. This will be + * combined with the sending address @ref my_addr on @ref ctx to form + * the CAN ID on the message. + * @param sent_cb_arg A pointer to data to be supplied to the callback + * that will be invoked when the message is sent. + * + * @returns 0 on success. + */ +int isotp_fast_send(struct isotp_fast_ctx *ctx, const uint8_t *data, size_t len, + const isotp_fast_node_id their_id, void *sent_cb_arg); +#endif /* CONFIG_ISOTP_FAST */ \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ba5cca..b2c341e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,8 +1,10 @@ # Copyright (c) The ThingSet Project Contributors # SPDX-License-Identifier: Apache-2.0 -target_sources(app PRIVATE sdk.c) -target_sources(app PRIVATE packetizer.c) +target_sources_ifdef(CONFIG_ISOTP_FAST app PRIVATE isotp_fast.c) + +target_sources_ifdef(CONFIG_THINGSET_SDK app PRIVATE sdk.c) +target_sources_ifdef(CONFIG_THINGSET_SDK app PRIVATE packetizer.c) target_sources_ifdef(CONFIG_THINGSET_AUTH app PRIVATE auth.c) target_sources_ifdef(CONFIG_THINGSET_BLE app PRIVATE ble.c) diff --git a/src/Kconfig.isotp_fast b/src/Kconfig.isotp_fast new file mode 100644 index 0000000..b13ec91 --- /dev/null +++ b/src/Kconfig.isotp_fast @@ -0,0 +1,41 @@ +menuconfig ISOTP_FAST + bool "New ISO-TP context" + depends on CAN + depends on ISOTP + select EVENTS + +if ISOTP_FAST + +config ISOTP_FAST_RX_MAX_PACKET_COUNT + int "Max packets for ISO-TP reception" + default 8 + help + Max number of packets expected in a single ISO-TP message. + +config ISOTP_FAST_RX_BUF_COUNT + int "Max number of RX buffers" + default 4 + help + This broadly implies the max number of simultaneous receptions. + +config ISOTP_FAST_TX_BUF_COUNT + int "Max number of TX buffers" + default 4 + help + This broadly implies the max number of simultaneous transmissions. + +config ISOTP_FAST_PER_FRAME_DISPATCH + bool "Per-frame dispatch" + default false + help + Whether to invoke the receive callback on receipt of every frame + +config ISOTP_FAST_BLOCKING_RECEIVE + bool "Blocking receive" + default false + depends on !ISOTP_FAST_PER_FRAME_DISPATCH + help + Whether to make blocking receive functionality available + to ease migration from the old API. + +endif \ No newline at end of file diff --git a/src/can.c b/src/can.c index cccfae5..7ffcf1d 100644 --- a/src/can.c +++ b/src/can.c @@ -48,9 +48,19 @@ static const struct can_filter addr_claim_filter = { .flags = CAN_FILTER_DATA | CAN_FILTER_IDE, }; -static const struct isotp_fc_opts fc_opts = { +#ifdef CONFIG_ISOTP_FAST +typedef struct isotp_fast_opts isotp_opts; +#else +typedef struct isotp_fc_opts isotp_opts; +#endif +static const isotp_opts fc_opts = { .bs = 8, /* block size */ .stmin = 1, /* minimum separation time = 100 ms */ +#ifdef CONFIG_ISOTP_FAST +#ifdef CONFIG_CAN_FD_MODE + .flags = ISOTP_MSG_FDF, +#endif +#endif }; #ifdef CONFIG_THINGSET_CAN_PACKETIZED_REPORTS_RX @@ -175,7 +185,7 @@ static void thingset_can_addr_claim_rx_cb(const struct device *dev, struct can_f static void thingset_can_report_rx_cb(const struct device *dev, struct can_frame *frame, void *user_data) { - const struct thingset_can *ts_can = user_data; + struct thingset_can *ts_can = user_data; uint16_t data_id = THINGSET_CAN_DATA_ID_GET(frame->id); uint8_t source_addr = THINGSET_CAN_SOURCE_GET(frame->id); #ifdef CONFIG_THINGSET_CAN_PACKETIZED_REPORTS_RX @@ -186,19 +196,29 @@ static void thingset_can_report_rx_cb(const struct device *dev, struct can_frame (struct thingset_can_rx_context *)buffer->user_data; if (context->seq++ == frame->data[0]) { int size = can_dlc_to_bytes(frame->dlc) - 1; + if (buffer->len + size > buffer->size) { + LOG_WRN("Discarding packetised message from 0x%X for data ID 0x%X as it is too " + "large.", + source_addr, data_id); + thingset_can_free_rx_buf(buffer); + return; + } uint8_t *buf = net_buf_add(buffer, size); int pos = 0; + LOG_DBG("Reassembling %d bytes for data ID %x from node %x", size, data_id, + source_addr); bool finished = reassemble(frame->data + 1, size, buf, size, &pos, &(context->escape)); if (pos < size) { + LOG_DBG("Trimming %d bytes", size - pos); /* if we over-allocated, trim the buffer */ net_buf_remove_mem(buffer, size - pos); } if (finished) { + LOG_DBG("Finished; dispatching %d bytes for data ID %x from node %x", + buffer->len, data_id, source_addr); /* full message received */ ts_can->report_rx_cb(data_id, buffer->data, buffer->len, source_addr); - LOG_DBG("Dispatching packetised message from %x for data ID %x", source_addr, - data_id); thingset_can_free_rx_buf(buffer); } } @@ -245,13 +265,22 @@ static void thingset_can_report_tx_handler(struct k_work *work) int chunk_len; uint8_t seq = 0; uint8_t *body = frame.data + 1; + // clang-format off while ((chunk_len = packetize(sbuf->data, data_len, body, CAN_MAX_DLEN - 1, &pos_buf)) - != 0) { + != 0) + { +#ifdef CONFIG_CAN_FD_MODE + frame.flags |= CAN_FRAME_FDF; +#endif + // clang-format on frame.data[0] = seq++; - frame.dlc = chunk_len + 1; + frame.dlc = can_bytes_to_dlc(chunk_len + 1); err = can_send(ts_can->dev, &frame, K_MSEC(CONFIG_THINGSET_CAN_PACKETIZED_REPORTS_FRAME_TX_INTERVAL), thingset_can_report_tx_cb, NULL); +#ifdef CONFIG_CAN_FD_MODE + frame.flags &= ~CAN_FRAME_FDF; +#endif if (err == -EAGAIN) { LOG_DBG("Error sending CAN frame with ID %x", frame.id); break; @@ -269,10 +298,16 @@ static void thingset_can_report_tx_handler(struct k_work *work) frame.id = THINGSET_CAN_TYPE_REPORT | THINGSET_CAN_PRIO_REPORT_LOW | THINGSET_CAN_DATA_ID_SET(obj->id) | THINGSET_CAN_SOURCE_SET(ts_can->node_addr); - frame.dlc = data_len; +#ifdef CONFIG_CAN_FD_MODE + frame.flags |= CAN_FRAME_FDF; +#endif + frame.dlc = can_bytes_to_dlc(data_len); if (can_send(ts_can->dev, &frame, K_MSEC(10), thingset_can_report_tx_cb, NULL) != 0) { LOG_DBG("Error sending CAN frame with ID %x", frame.id); } +#ifdef CONFIG_CAN_FD_MODE + frame.flags &= ~CAN_FRAME_FDF; +#endif } else { k_sem_give(&sbuf->lock); @@ -289,6 +324,24 @@ static void thingset_can_report_tx_handler(struct k_work *work) thingset_sdk_reschedule_work(dwork, K_TIMEOUT_ABS_MS(ts_can->next_pub_time)); } +#ifdef CONFIG_ISOTP_FAST +void thingset_can_reset_request_response(struct thingset_can_request_response *rr) +{ + rr->callback = NULL; + rr->cb_arg = NULL; + rr->sender_addr = 0; + k_timer_stop(&rr->timer); + k_sem_give(&rr->sem); +} + +void thingset_can_request_response_timeout_handler(struct k_timer *timer) +{ + struct thingset_can_request_response *rr = + CONTAINER_OF(timer, struct thingset_can_request_response, timer); + rr->callback(NULL, 0, -ETIMEDOUT, 0, rr->cb_arg); + thingset_can_reset_request_response(rr); +} +#else int thingset_can_receive_inst(struct thingset_can *ts_can, uint8_t *rx_buffer, size_t rx_buf_size, uint8_t *source_addr, k_timeout_t timeout) { @@ -345,7 +398,90 @@ int thingset_can_receive_inst(struct thingset_can *ts_can, uint8_t *rx_buffer, s return -EIO; } } +#endif /* CONFIG_ISOTP_FAST */ + +#ifdef CONFIG_ISOTP_FAST +int thingset_can_send_inst(struct thingset_can *ts_can, uint8_t *tx_buf, size_t tx_len, + uint8_t target_addr, thingset_can_response_callback_t rsp_callback, + void *callback_arg, k_timeout_t timeout) +{ + if (!device_is_ready(ts_can->dev)) { + return -ENODEV; + } + + if (rsp_callback != NULL) { + if (k_sem_take(&ts_can->request_response.sem, timeout) != 0) { + return -ETIMEDOUT; + } + + ts_can->request_response.callback = rsp_callback; + ts_can->request_response.cb_arg = callback_arg; + k_timer_init(&ts_can->request_response.timer, thingset_can_request_response_timeout_handler, + NULL); + k_timer_start(&ts_can->request_response.timer, timeout, timeout); + ts_can->request_response.sender_addr = THINGSET_CAN_TYPE_CHANNEL | THINGSET_CAN_PRIO_CHANNEL + | THINGSET_CAN_TARGET_SET(ts_can->node_addr) + | THINGSET_CAN_SOURCE_SET(target_addr); + } + int ret = isotp_fast_send(&ts_can->ctx, tx_buf, tx_len, target_addr, ts_can); + + if (ret == ISOTP_N_OK) { + return 0; + } + else { + LOG_ERR("Error sending data to addr %d: %d", target_addr, ret); + return -EIO; + } +} +void isotp_fast_recv_callback(struct net_buf *buffer, int rem_len, isotp_fast_msg_id sender_addr, + void *arg) +{ + struct thingset_can *ts_can = arg; + + if (rem_len < 0) { + LOG_ERR("RX error %d", rem_len); + } + + if (rem_len == 0) { + size_t len = net_buf_frags_len(buffer); + net_buf_linearize(ts_can->rx_buffer, sizeof(ts_can->rx_buffer), buffer, 0, len); + if (ts_can->request_response.callback != NULL + && ts_can->request_response.sender_addr == sender_addr) + { + ts_can->request_response.callback(ts_can->rx_buffer, len, 0, + (uint8_t)(sender_addr & 0xFF), + ts_can->request_response.cb_arg); + thingset_can_reset_request_response(&ts_can->request_response); + } + else { + struct shared_buffer *sbuf = thingset_sdk_shared_buffer(); + int tx_len = + thingset_process_message(&ts, ts_can->rx_buffer, len, sbuf->data, sbuf->size); + if (tx_len > 0) { + isotp_fast_node_id target_id = (uint8_t)(sender_addr & 0xFF); + thingset_can_send_inst(ts_can, sbuf->data, tx_len, target_id, NULL, NULL, + K_NO_WAIT); + } + } + } +} + +void isotp_fast_recv_error_callback(int8_t error, isotp_fast_msg_id sender_addr, void *arg) +{ + struct thingset_can *ts_can = arg; + LOG_ERR("RX error %d", error); +} + +void isotp_fast_sent_callback(int result, void *arg) +{ + struct thingset_can *ts_can = arg; + if (ts_can->request_response.callback != NULL && result != 0) { + ts_can->request_response.callback(NULL, 0, result, 0, ts_can->request_response.cb_arg); + thingset_can_reset_request_response(&ts_can->request_response); + } +} +#else int thingset_can_send_inst(struct thingset_can *ts_can, uint8_t *tx_buf, size_t tx_len, uint8_t target_addr) { @@ -420,6 +556,7 @@ int thingset_can_process_inst(struct thingset_can *ts_can, k_timeout_t timeout) k_sem_give(&sbuf->lock); return 0; } +#endif /* CONFIG_ISOTP_FAST */ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can_dev) { @@ -439,7 +576,9 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can sys_slist_init(&rx_buf_lookup[i]); } #endif - +#ifdef CONFIG_ISOTP_FAST + k_sem_init(&ts_can->request_response.sem, 1, 1); +#endif k_work_init_delayable(&ts_can->reporting_work, thingset_can_report_tx_handler); k_work_init_delayable(&ts_can->addr_claim_work, thingset_can_addr_claim_tx_handler); @@ -452,6 +591,10 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can k_event_init(&ts_can->events); +#ifdef CONFIG_CAN_FD_MODE + can_set_mode(ts_can->dev, CAN_MODE_FD); +#endif + can_start(ts_can->dev); filter_id = @@ -517,6 +660,7 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can thingset_storage_save_queued(); #endif +#ifndef CONFIG_ISOTP_FAST ts_can->rx_addr.ide = 1; ts_can->rx_addr.use_ext_addr = 0; /* Normal ISO-TP addressing (using only CAN ID) */ ts_can->rx_addr.use_fixed_addr = 1; /* enable SAE J1939 compatible addressing */ @@ -524,6 +668,7 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can ts_can->tx_addr.ide = 1; ts_can->tx_addr.use_ext_addr = 0; ts_can->tx_addr.use_fixed_addr = 1; +#endif struct can_filter addr_discovery_filter = { .id = THINGSET_CAN_TYPE_NETWORK | THINGSET_CAN_SOURCE_SET(THINGSET_CAN_ADDR_ANONYMOUS) @@ -538,6 +683,13 @@ int thingset_can_init_inst(struct thingset_can *ts_can, const struct device *can return filter_id; } +#ifdef CONFIG_ISOTP_FAST + isotp_fast_msg_id my_addr = THINGSET_CAN_TYPE_CHANNEL | THINGSET_CAN_PRIO_CHANNEL + | THINGSET_CAN_TARGET_SET(ts_can->node_addr); + isotp_fast_bind(&ts_can->ctx, can_dev, my_addr, &fc_opts, isotp_fast_recv_callback, ts_can, + isotp_fast_recv_error_callback, isotp_fast_sent_callback); +#endif + thingset_sdk_reschedule_work(&ts_can->reporting_work, K_NO_WAIT); return 0; @@ -593,22 +745,43 @@ static struct thingset_can ts_can_single = { THINGSET_ADD_ITEM_UINT8(TS_ID_NET, TS_ID_NET_CAN_NODE_ADDR, "pCANNodeAddr", &ts_can_single.node_addr, THINGSET_ANY_RW, TS_SUBSET_NVM); +#ifdef CONFIG_ISOTP_FAST +int thingset_can_send(uint8_t *tx_buf, size_t tx_len, uint8_t target_addr, + thingset_can_response_callback_t rsp_callback, void *callback_arg, + k_timeout_t timeout) +{ + return thingset_can_send_inst(&ts_can_single, tx_buf, tx_len, target_addr, rsp_callback, + callback_arg, timeout); +} +#else int thingset_can_send(uint8_t *tx_buf, size_t tx_len, uint8_t target_addr) { return thingset_can_send_inst(&ts_can_single, tx_buf, tx_len, target_addr); } +#endif /* CONFIG_ISOTP_FAST */ int thingset_can_set_report_rx_callback(thingset_can_report_rx_callback_t rx_cb) { return thingset_can_set_report_rx_callback_inst(&ts_can_single, rx_cb); } +struct thingset_can *thingset_can_get_inst() +{ + return &ts_can_single; +} + static void thingset_can_thread() { - thingset_can_init_inst(&ts_can_single, can_dev); + int err; + + err = thingset_can_init_inst(&ts_can_single, can_dev); + if (err != 0) { + LOG_ERR("Failed to init ThingSet CAN: %d", err); + return; + } while (true) { - thingset_can_process_inst(&ts_can_single, K_FOREVER); + k_sleep(K_FOREVER); } } diff --git a/src/isotp_fast.c b/src/isotp_fast.c new file mode 100644 index 0000000..6d89f49 --- /dev/null +++ b/src/isotp_fast.c @@ -0,0 +1,1057 @@ +/* + * Copyright (c) 2019 Alexander Wachter + * Copyright (c) 2023 Enphase Energy + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "isotp_fast_internal.h" +#include +#include +#include + +LOG_MODULE_REGISTER(isotp_fast, CONFIG_ISOTP_LOG_LEVEL); + +static void receive_work_handler(struct k_work *work); +static void receive_timeout_handler(struct k_timer *timer); +static void receive_state_machine(struct isotp_fast_recv_ctx *rctx); + +/* Memory slab to hold send contexts */ +K_MEM_SLAB_DEFINE(isotp_send_ctx_slab, sizeof(struct isotp_fast_send_ctx), + CONFIG_ISOTP_FAST_TX_BUF_COUNT, 4); + +/* Memory slab to hold receive contexts */ +K_MEM_SLAB_DEFINE(isotp_recv_ctx_slab, sizeof(struct isotp_fast_recv_ctx), + CONFIG_ISOTP_FAST_RX_BUF_COUNT, 4); + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE +/* Memory slab to hold blocking receive contexts */ +K_MEM_SLAB_DEFINE(isotp_recv_await_ctx_slab, sizeof(struct isotp_fast_recv_await_ctx), + CONFIG_ISOTP_FAST_RX_BUF_COUNT, 4); +#endif + +/** + * Pool of buffers for incoming messages. The current implementation + * sizes these to match the size of a CAN frame less the 1 header byte + * that ISO-TP consumes. The important configuration options determining + * the size of the buffer are therefore CONFIG_ISOTP_FAST_RX_BUF_COUNT (i.e. broad + * number of buffers) and CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT (i.e. how big a + * message does one anticipate receiving). + */ +NET_BUF_POOL_DEFINE(isotp_rx_pool, + CONFIG_ISOTP_FAST_RX_BUF_COUNT *CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT, + CAN_MAX_DLEN - 1, sizeof(int), NULL); + +/* list of currently in-flight send contexts */ +static sys_slist_t isotp_send_ctx_list; +/* list of currently in-flight receive contexts */ +static sys_slist_t isotp_recv_ctx_list; + +static int get_send_ctx(struct isotp_fast_ctx *ctx, isotp_fast_msg_id recipient_addr, + struct isotp_fast_send_ctx **sctx) +{ + isotp_fast_node_id recipient_id = isotp_fast_get_addr_recipient(recipient_addr); + struct isotp_fast_send_ctx *context; + + SYS_SLIST_FOR_EACH_CONTAINER(&isotp_send_ctx_list, context, node) + { + if (isotp_fast_get_addr_recipient(context->recipient_addr) == recipient_id) { + LOG_DBG("Found existing send context for recipient %x", recipient_addr); + *sctx = context; + return 0; + } + } + + int err = k_mem_slab_alloc(&isotp_send_ctx_slab, (void **)&context, K_NO_WAIT); + if (err != 0) { + return ISOTP_NO_CTX_LEFT; + } + *sctx = context; + context->ctx = ctx; + context->recipient_addr = recipient_addr; + context->error = 0; + k_work_init(&context->work, receive_work_handler); + k_timer_init(&context->timer, receive_timeout_handler, NULL); + sys_slist_append(&isotp_send_ctx_list, &context->node); + LOG_DBG("Created new send context for recipient %x", recipient_addr); + + return 0; +} + +static inline void free_send_ctx(struct isotp_fast_send_ctx **ctx) +{ + LOG_DBG("Freeing send context for recipient %x", (*ctx)->recipient_addr); + k_timer_stop(&(*ctx)->timer); + sys_slist_find_and_remove(&isotp_send_ctx_list, &(*ctx)->node); + k_mem_slab_free(&isotp_send_ctx_slab, (void **)ctx); +} + +static inline void free_recv_ctx(struct isotp_fast_recv_ctx **rctx) +{ + LOG_DBG("Freeing receive context %x", (*rctx)->sender_addr); + k_timer_stop(&(*rctx)->timer); + sys_slist_find_and_remove(&isotp_recv_ctx_list, &(*rctx)->node); + net_buf_unref((*rctx)->buffer); +#ifdef ISOTP_FAST_RECEIVE_QUEUE + k_msgq_purge(&(*rctx)->recv_queue); + k_msgq_cleanup(&(*rctx)->recv_queue); +#endif + k_mem_slab_free(&isotp_recv_ctx_slab, (void **)rctx); +} + +static void free_recv_ctx_if_unowned(struct isotp_fast_recv_ctx **rctx) +{ +#ifdef ISOTP_FAST_RECEIVE_QUEUE + if ((*rctx)->pending) { + return; + } +#endif + free_recv_ctx(rctx); +} + +static int get_recv_ctx(struct isotp_fast_ctx *ctx, isotp_fast_msg_id sender_addr, + struct isotp_fast_recv_ctx **rctx) +{ + isotp_fast_node_id sender_id = isotp_fast_get_addr_sender(sender_addr); + struct isotp_fast_recv_ctx *context; + + SYS_SLIST_FOR_EACH_CONTAINER(&isotp_recv_ctx_list, context, node) + { + if (isotp_fast_get_addr_sender(context->sender_addr) == sender_id) { + LOG_DBG("Found existing receive context %x", sender_addr); + *rctx = context; + context->frag = net_buf_alloc(&isotp_rx_pool, K_NO_WAIT); + if (context->frag == NULL) { + LOG_ERR("No free buffers"); + free_recv_ctx(rctx); + return ISOTP_NO_NET_BUF_LEFT; + } +#ifndef ISOTP_FAST_RECEIVE_QUEUE + net_buf_frag_add(context->buffer, context->frag); +#endif + return 0; + } + } + + int err = k_mem_slab_alloc(&isotp_recv_ctx_slab, (void **)&context, K_NO_WAIT); + if (err != 0) { + LOG_ERR("No space for receive context - error %d.", err); + return ISOTP_NO_CTX_LEFT; + } + context->buffer = net_buf_alloc(&isotp_rx_pool, K_NO_WAIT); + if (!context->buffer) { + k_mem_slab_free(&isotp_recv_ctx_slab, (void **)&context); + LOG_ERR("No net bufs."); + return ISOTP_NO_NET_BUF_LEFT; + } + context->frag = context->buffer; + *rctx = context; + context->ctx = ctx; + context->state = ISOTP_RX_STATE_WAIT_FF_SF; + context->sender_addr = sender_addr; + context->error = 0; +#ifdef ISOTP_FAST_RECEIVE_QUEUE + k_msgq_init(&context->recv_queue, context->recv_queue_pool, sizeof(struct net_buf *), + CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT); + LOG_DBG("Queue of length %d created", k_msgq_num_free_get(&context->recv_queue)); +#endif + k_work_init(&context->work, receive_work_handler); + k_timer_init(&context->timer, receive_timeout_handler, NULL); + sys_slist_append(&isotp_recv_ctx_list, &context->node); + LOG_DBG("Created new receive context %x", sender_addr); + + return 0; +} + +static inline void receive_report_error(struct isotp_fast_recv_ctx *rctx, int8_t err) +{ + rctx->state = ISOTP_RX_STATE_ERR; + rctx->error = err; +} + +static void send_report_error(struct isotp_fast_send_ctx *sctx, int8_t err) +{ + sctx->state = ISOTP_TX_ERR; + sctx->error = err; +} + +static inline uint32_t receive_get_ff_length(uint8_t *data) +{ + uint32_t len; + uint8_t pci = data[0]; + + len = ((pci & ISOTP_PCI_FF_DL_UPPER_MASK) << 8) | data[1]; + + /* Jumbo packet (32 bit length)*/ + /* TODO: this probably isn't supported at the moment, given that max length is 4095 */ + if (!len) { + len = UNALIGNED_GET((uint32_t *)data); + len = sys_be32_to_cpu(len); + } + + return len; +} + +static inline uint32_t receive_get_sf_length(uint8_t *data, int *index) +{ + uint8_t len = data[0] & ISOTP_PCI_SF_DL_MASK; + (*index)++; + + /* Single frames > 16 bytes (CAN-FD only) */ + if (IS_ENABLED(CONFIG_CAN_FD_MODE) && !len) { + len = data[1]; + (*index)++; + } + + return len; +} + +static void receive_can_tx(const struct device *dev, int error, void *arg) +{ + struct isotp_fast_recv_ctx *rctx = (struct isotp_fast_recv_ctx *)arg; + + ARG_UNUSED(dev); + + if (error != 0) { + LOG_ERR("Error sending FC frame (%d)", error); + receive_report_error(rctx, ISOTP_N_ERROR); + k_work_submit(&rctx->work); + } +} + +static void receive_send_fc(struct isotp_fast_recv_ctx *rctx, uint8_t fs) +{ + struct can_frame frame = { + .flags = + CAN_FRAME_IDE | ((rctx->ctx->opts->flags & ISOTP_MSG_FDF) != 0 ? CAN_FRAME_FDF : 0), + .id = (rctx->sender_addr & 0xFFFF0000) | ((rctx->sender_addr & 0xFF00) >> 8) + | ((rctx->sender_addr & 0xFF) << 8) + }; + uint8_t *data = frame.data; + uint8_t payload_len; + int ret; + + __ASSERT_NO_MSG(!(fs & ISOTP_PCI_TYPE_MASK)); + + *data++ = ISOTP_PCI_TYPE_FC | fs; + *data++ = rctx->ctx->opts->bs; + *data++ = rctx->ctx->opts->stmin; + payload_len = data - frame.data; + frame.dlc = can_bytes_to_dlc(payload_len); + + ret = can_send(rctx->ctx->can_dev, &frame, K_MSEC(ISOTP_A_TIMEOUT_MS), receive_can_tx, rctx); + if (ret) { + LOG_ERR("Can't send FC, (%d)", ret); + receive_report_error(rctx, ISOTP_N_TIMEOUT_A); + receive_state_machine(rctx); + } +} + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE +static void notify_waiting_receiver(struct isotp_fast_recv_ctx *rctx) +{ + struct isotp_fast_recv_await_ctx *awaiter; + SYS_SLIST_FOR_EACH_CONTAINER(&rctx->ctx->wait_recv_list, awaiter, node) + { + if ((awaiter->sender.id & awaiter->sender.mask) + == (rctx->sender_addr & awaiter->sender.mask)) { + LOG_DBG("Matched waiting receiver %x:%x to sender %x", awaiter->sender.id, + awaiter->sender.mask, rctx->sender_addr); + awaiter->rctx = rctx; + rctx->pending = true; + if (k_sem_count_get(&awaiter->sem) == 0) { + k_sem_give(&awaiter->sem); + } + else if (rctx->error) { + /* if error state, we might already be waiting on the queue for the next + message, so purge the queue to unblock the waiter so it will see the error */ + k_msgq_purge(&rctx->recv_queue); + } + return; + } + } + + LOG_DBG("No matching receiver for sender %x", rctx->sender_addr); +} +#endif + +static void receive_state_machine(struct isotp_fast_recv_ctx *rctx) +{ +#ifdef CONFIG_ISOTP_FAST_PER_FRAME_DISPATCH + struct net_buf *frag; + while (k_msgq_get(&rctx->recv_queue, &frag, K_NO_WAIT) == 0) { + int *p_rem_len = net_buf_user_data(frag); + LOG_DBG("Remaining length %d (%d), enqueued %d", *p_rem_len, rctx->rem_len, + k_msgq_num_used_get(&rctx->recv_queue)); + rctx->ctx->recv_callback(frag, *p_rem_len, rctx->sender_addr, rctx->ctx->recv_cb_arg); + net_buf_unref(frag); + } +#endif + + switch (rctx->state) { + case ISOTP_RX_STATE_PROCESS_SF: + LOG_DBG("SM process SF of length %d", rctx->rem_len); + rctx->rem_len = 0; + rctx->state = ISOTP_RX_STATE_RECYCLE; +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + notify_waiting_receiver(rctx); +#endif + receive_state_machine(rctx); + break; + + case ISOTP_RX_STATE_PROCESS_FF: + LOG_DBG("SM process FF. Length: %d", rctx->rem_len + rctx->frag->len); + if (rctx->ctx->opts->bs == 0 + && rctx->rem_len > CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT * (CAN_MAX_DLEN - 1)) + { + LOG_ERR("Pkt length is %d but buffer has only %d bytes", rctx->rem_len, + CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT * (CAN_MAX_DLEN - 1)); + receive_report_error(rctx, ISOTP_N_BUFFER_OVERFLW); + receive_state_machine(rctx); + break; + } +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + notify_waiting_receiver(rctx); +#endif + + if (rctx->ctx->opts->bs) { + rctx->bs = rctx->ctx->opts->bs; + // ctx->ctx->recv_callback(ctx->buffer, ctx->rem_len, ctx->sender_addr, + // ctx->ctx->recv_cb_arg); LOG_INF("Dispatched chunk of length %d; remaining %d", + // ctx->buffer->len, ctx->rem_len); + } + + rctx->wft = ISOTP_WFT_FIRST; + rctx->state = ISOTP_RX_STATE_TRY_ALLOC; + __fallthrough; + case ISOTP_RX_STATE_TRY_ALLOC: + LOG_DBG("SM try to allocate"); + k_timer_stop(&rctx->timer); + // ret = receive_alloc_buffer(ctx); + // if (ret) { + // LOG_DBG("SM allocation failed. Wait for free buffer"); + // break; + // } + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + notify_waiting_receiver(rctx); +#endif + + rctx->state = ISOTP_RX_STATE_SEND_FC; + __fallthrough; + case ISOTP_RX_STATE_SEND_FC: + LOG_DBG("SM send CTS FC frame"); + receive_send_fc(rctx, ISOTP_PCI_FS_CTS); + k_timer_start(&rctx->timer, K_MSEC(ISOTP_CR_TIMEOUT_MS), K_NO_WAIT); + rctx->state = ISOTP_RX_STATE_WAIT_CF; + break; + + case ISOTP_RX_STATE_SEND_WAIT: + if (++rctx->wft < CONFIG_ISOTP_WFTMAX) { + LOG_DBG("Send wait frame number %d", rctx->wft); + receive_send_fc(rctx, ISOTP_PCI_FS_WAIT); + k_timer_start(&rctx->timer, K_MSEC(ISOTP_ALLOC_TIMEOUT_MS), K_NO_WAIT); + rctx->state = ISOTP_RX_STATE_TRY_ALLOC; + break; + } + + LOG_ERR("Sent %d wait frames. Giving up to alloc now", rctx->wft); + receive_report_error(rctx, ISOTP_N_BUFFER_OVERFLW); + __fallthrough; + case ISOTP_RX_STATE_ERR: + // LOG_DBG("SM ERR state. err nr: %d", ctx->error_nr); + k_timer_stop(&rctx->timer); + if (rctx->ctx->recv_error_callback) { + rctx->ctx->recv_error_callback(rctx->error, rctx->sender_addr, + rctx->ctx->recv_cb_arg); + } +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + notify_waiting_receiver(rctx); +#endif + if (rctx->error == ISOTP_N_BUFFER_OVERFLW) { + receive_send_fc(rctx, ISOTP_PCI_FS_OVFLW); + } + + // net_buf_unref(ctx->buffer); + // ctx->buffer = NULL; + // ctx->state = ISOTP_RX_STATE_RECYCLE; + free_recv_ctx_if_unowned(&rctx); + __fallthrough; + case ISOTP_RX_STATE_RECYCLE: +#ifndef ISOTP_FAST_RECEIVE_QUEUE + LOG_DBG("Message complete; dispatching"); + rctx->ctx->recv_callback(rctx->buffer, 0, rctx->sender_addr, rctx->ctx->recv_cb_arg); +#endif +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + notify_waiting_receiver(rctx); +#endif + rctx->state = ISOTP_RX_STATE_UNBOUND; + free_recv_ctx_if_unowned(&rctx); + break; + case ISOTP_RX_STATE_UNBOUND: + break; + + default: + break; + } +} + +static void process_ff_sf(struct isotp_fast_recv_ctx *rctx, struct can_frame *frame) +{ + int index = 0; + uint8_t payload_len; + + switch (frame->data[index] & ISOTP_PCI_TYPE_MASK) { + case ISOTP_PCI_TYPE_FF: + LOG_DBG("Got FF IRQ"); + if (frame->dlc != ISOTP_FF_DL_MIN) { + LOG_DBG("FF DLC invalid. Ignore"); + return; + } + + rctx->rem_len = receive_get_ff_length(frame->data); + rctx->state = ISOTP_RX_STATE_PROCESS_FF; + rctx->sn_expected = 1; + index += 2; + payload_len = CAN_MAX_DLEN - index; + LOG_DBG("FF total length %d, FF len %d", rctx->rem_len, payload_len); + break; + + case ISOTP_PCI_TYPE_SF: + LOG_DBG("Got SF IRQ"); + rctx->rem_len = receive_get_sf_length(frame->data, &index); + payload_len = MIN(rctx->rem_len, CAN_MAX_DLEN - index); + LOG_DBG("SF length %d", payload_len); + if (payload_len > can_dlc_to_bytes(frame->dlc)) { + LOG_DBG("SF DL does not fit. Ignore"); + return; + } + + rctx->state = ISOTP_RX_STATE_PROCESS_SF; + break; + + default: + LOG_DBG("Got unexpected frame. Ignore"); + return; + } + + LOG_DBG("Current buffer size %d; adding %d", rctx->buffer->len, payload_len); + net_buf_add_mem(rctx->frag, &frame->data[index], payload_len); + rctx->rem_len -= payload_len; +#ifdef ISOTP_FAST_RECEIVE_QUEUE + int *p_rem_len = net_buf_user_data(rctx->frag); + *p_rem_len = rctx->rem_len; + k_msgq_put(&rctx->recv_queue, &rctx->frag, K_NO_WAIT); + LOG_DBG("Enqueued item; remaining length %d, queue size %d", *p_rem_len, + k_msgq_num_used_get(&rctx->recv_queue)); +#endif +} + +static void process_cf(struct isotp_fast_recv_ctx *rctx, struct can_frame *frame) +{ + int index = 0; + uint32_t data_len; + + if ((frame->data[index] & ISOTP_PCI_TYPE_MASK) != ISOTP_PCI_TYPE_CF) { + LOG_DBG("Waiting for CF but got something else (%d)", + frame->data[index] >> ISOTP_PCI_TYPE_POS); + receive_report_error(rctx, ISOTP_N_UNEXP_PDU); + k_work_submit(&rctx->work); + return; + } + + k_timer_start(&rctx->timer, K_MSEC(ISOTP_CR_TIMEOUT_MS), K_NO_WAIT); + + if ((frame->data[index++] & ISOTP_PCI_SN_MASK) != rctx->sn_expected++) { + LOG_ERR("Sequence number mismatch"); + receive_report_error(rctx, ISOTP_N_WRONG_SN); + k_work_submit(&rctx->work); + return; + } + + LOG_DBG("Got CF irq. Appending data"); + data_len = MIN(rctx->rem_len, can_dlc_to_bytes(frame->dlc) - index); + net_buf_add_mem(rctx->frag, &frame->data[index], data_len); + rctx->rem_len -= data_len; +#ifdef ISOTP_FAST_RECEIVE_QUEUE + int *p_rem_len = net_buf_user_data(rctx->frag); + *p_rem_len = rctx->rem_len; + k_msgq_put(&rctx->recv_queue, &rctx->frag, K_NO_WAIT); /* what if this fails? */ + LOG_DBG("Enqueued item; remaining length %d, queue size %d", *p_rem_len, + k_msgq_num_used_get(&rctx->recv_queue)); +#endif + LOG_DBG("Added %d bytes; %d bytes remaining", data_len, rctx->rem_len); + + if (rctx->rem_len == 0) { + rctx->state = ISOTP_RX_STATE_RECYCLE; + k_work_submit(&rctx->work); // to dispatch complete message + return; + } + + if (rctx->ctx->opts->bs && !--rctx->bs) { + LOG_DBG("Block is complete. Allocate new buffer"); + rctx->bs = rctx->ctx->opts->bs; + // rctx->ctx->recv_callback(rctx->buffer, rctx->rem_len, rctx->sender_addr, + // rctx->ctx->recv_cb_arg); + rctx->state = ISOTP_RX_STATE_TRY_ALLOC; + } +} + +static void receive_work_handler(struct k_work *work) +{ + struct isotp_fast_recv_ctx *rctx = CONTAINER_OF(work, struct isotp_fast_recv_ctx, work); + + receive_state_machine(rctx); +} + +static void receive_timeout_handler(struct k_timer *timer) +{ + struct isotp_fast_recv_ctx *rctx = CONTAINER_OF(timer, struct isotp_fast_recv_ctx, timer); + + switch (rctx->state) { + case ISOTP_RX_STATE_WAIT_CF: + LOG_ERR("Timeout while waiting for CF"); + receive_report_error(rctx, ISOTP_N_TIMEOUT_CR); + break; + + case ISOTP_RX_STATE_TRY_ALLOC: + rctx->state = ISOTP_RX_STATE_SEND_WAIT; + break; + + default: + break; + } + + k_work_submit(&rctx->work); +} + +static void receive_can_rx(struct isotp_fast_recv_ctx *rctx, struct can_frame *frame) +{ + switch (rctx->state) { + case ISOTP_RX_STATE_WAIT_FF_SF: + process_ff_sf(rctx, frame); + break; + + case ISOTP_RX_STATE_WAIT_CF: + process_cf(rctx, frame); + /* still waiting for more CF */ + if (rctx->state == ISOTP_RX_STATE_WAIT_CF) { + return; + } + break; + + default: + LOG_DBG("Got a frame in a state where it is unexpected."); + } + + k_work_submit(&rctx->work); +} + +static inline void prepare_frame(struct can_frame *frame, struct isotp_fast_ctx *ctx, + isotp_fast_msg_id addr) +{ + frame->id = addr; + frame->flags = CAN_FRAME_IDE | ((ctx->opts->flags & ISOTP_MSG_FDF) != 0 ? CAN_FRAME_FDF : 0); +} + +static k_timeout_t stmin_to_timeout(uint8_t stmin) +{ + /* According to ISO 15765-2 stmin should be 127ms if value is corrupt */ + if (stmin > ISOTP_STMIN_MAX || (stmin > ISOTP_STMIN_MS_MAX && stmin < ISOTP_STMIN_US_BEGIN)) { + return K_MSEC(ISOTP_STMIN_MS_MAX); + } + + if (stmin >= ISOTP_STMIN_US_BEGIN) { + return K_USEC((stmin + 1 - ISOTP_STMIN_US_BEGIN) * 100U); + } + + return K_MSEC(stmin); +} + +static void send_process_fc(struct isotp_fast_send_ctx *sctx, struct can_frame *frame) +{ + uint8_t *data = frame->data; + + if ((*data & ISOTP_PCI_TYPE_MASK) != ISOTP_PCI_TYPE_FC) { + LOG_ERR("Got unexpected PDU expected FC"); + send_report_error(sctx, ISOTP_N_UNEXP_PDU); + return; + } + + switch (*data++ & ISOTP_PCI_FS_MASK) { + case ISOTP_PCI_FS_CTS: + sctx->state = ISOTP_TX_SEND_CF; + sctx->wft = 0; + sctx->backlog = 0; + k_sem_reset(&sctx->sem); + sctx->bs = *data++; + sctx->stmin = *data++; + LOG_DBG("Got CTS. BS: %d, STmin: %d", sctx->bs, sctx->stmin); + break; + + case ISOTP_PCI_FS_WAIT: + LOG_DBG("Got WAIT frame"); + k_timer_start(&sctx->timer, K_MSEC(ISOTP_BS_TIMEOUT_MS), K_NO_WAIT); + if (sctx->wft >= CONFIG_ISOTP_WFTMAX) { + LOG_WRN("Got too many wait frames"); + send_report_error(sctx, ISOTP_N_WFT_OVRN); + } + + sctx->wft++; + break; + + case ISOTP_PCI_FS_OVFLW: + LOG_ERR("Got overflow FC frame"); + send_report_error(sctx, ISOTP_N_BUFFER_OVERFLW); + break; + + default: + send_report_error(sctx, ISOTP_N_INVALID_FS); + } +} + +static void send_can_rx(struct isotp_fast_send_ctx *sctx, struct can_frame *frame) +{ + if (sctx->state == ISOTP_TX_WAIT_FC) { + k_timer_stop(&sctx->timer); + send_process_fc(sctx, frame); + } + else { + LOG_ERR("Got unexpected PDU"); + send_report_error(sctx, ISOTP_N_UNEXP_PDU); + } + + k_work_submit(&sctx->work); +} + +static void can_rx_callback(const struct device *dev, struct can_frame *frame, void *arg) +{ + struct isotp_fast_ctx *ctx = arg; + int index = 0; + isotp_fast_msg_id sender_id = + (frame->id & 0xFFFF0000) | ((frame->id & 0xFF00) >> 8) | ((frame->id & 0xFF) << 8); + if ((frame->data[index++] & ISOTP_PCI_TYPE_MASK) == ISOTP_PCI_TYPE_FC) { + LOG_DBG("Got flow control frame from %x", frame->id); + /* inbound flow control for a message we are currently transmitting */ + struct isotp_fast_send_ctx *sctx; + if (get_send_ctx(ctx, sender_id, &sctx) != 0) { + LOG_DBG("Ignoring flow control frame from %x", frame->id); + return; + } + send_can_rx(sctx, frame); + } + else { + struct isotp_fast_recv_ctx *rctx; + if (get_recv_ctx(ctx, frame->id, &rctx) != 0) { + LOG_ERR("RX buffer full"); + return; + } + receive_can_rx(rctx, frame); + } +} + +static void send_can_tx_callback(const struct device *dev, int error, void *arg) +{ + struct isotp_fast_send_ctx *sctx = arg; + + ARG_UNUSED(dev); + + sctx->backlog--; + k_sem_give(&sctx->sem); + + if (sctx->state == ISOTP_TX_WAIT_BACKLOG) { + if (sctx->backlog > 0) { + return; + } + + sctx->state = ISOTP_TX_WAIT_FIN; + } + + k_work_submit(&sctx->work); +} + +static inline int send_ff(struct isotp_fast_send_ctx *sctx) +{ + struct can_frame frame; + int index = 0; + int ret; + uint16_t len = sctx->rem_len; + + prepare_frame(&frame, sctx->ctx, sctx->recipient_addr); + + if (len > 0xFFF) { + frame.data[index++] = ISOTP_PCI_TYPE_FF; + frame.data[index++] = 0; + frame.data[index++] = (len >> 3 * 8) & 0xFF; + frame.data[index++] = (len >> 2 * 8) & 0xFF; + frame.data[index++] = (len >> 8) & 0xFF; + frame.data[index++] = len & 0xFF; + } + else { + frame.data[index++] = ISOTP_PCI_TYPE_FF | (len >> 8); + frame.data[index++] = len & 0xFF; + } + + /* According to ISO FF has sn 0 and is incremented to one + * although it's not part of the FF frame + */ + sctx->sn = 1; + uint16_t size = MIN(CAN_MAX_DLEN, len) - index; + memcpy(&frame.data[index], sctx->data, size); + sctx->rem_len -= size; + sctx->data += size; + frame.dlc = can_bytes_to_dlc(CAN_MAX_DLEN); + ret = can_send(sctx->ctx->can_dev, &frame, K_MSEC(ISOTP_A_TIMEOUT_MS), send_can_tx_callback, + sctx); + return ret; +} + +static inline int send_cf(struct isotp_fast_send_ctx *sctx) +{ + struct can_frame frame; + int index = 0; + int ret; + uint16_t len; + + prepare_frame(&frame, sctx->ctx, sctx->recipient_addr); + + /*sn wraps around at 0xF automatically because it has a 4 bit size*/ + frame.data[index++] = ISOTP_PCI_TYPE_CF | sctx->sn; + + len = MIN(sctx->rem_len, CAN_MAX_DLEN - index); + memcpy(&frame.data[index], sctx->data, len); + sctx->rem_len -= len; + sctx->data += len; + + frame.dlc = can_bytes_to_dlc(len + index); + ret = can_send(sctx->ctx->can_dev, &frame, K_MSEC(ISOTP_A_TIMEOUT_MS), send_can_tx_callback, + sctx); + if (ret == 0) { + sctx->sn++; + sctx->bs--; + sctx->backlog++; + } + + ret = ret ? ret : sctx->rem_len; + return ret; +} + +static void send_state_machine(struct isotp_fast_send_ctx *sctx) +{ + int ret; + switch (sctx->state) { + case ISOTP_TX_SEND_FF: + send_ff(sctx); + k_timer_start(&sctx->timer, K_MSEC(ISOTP_BS_TIMEOUT_MS), K_NO_WAIT); + sctx->state = ISOTP_TX_WAIT_FC; + break; + + case ISOTP_TX_SEND_CF: + k_timer_stop(&sctx->timer); + do { + ret = send_cf(sctx); + if (!ret) { + sctx->state = ISOTP_TX_WAIT_BACKLOG; + break; + } + + if (ret < 0) { + LOG_ERR("Failed to send CF"); + send_report_error(sctx, ret == -EAGAIN ? ISOTP_N_TIMEOUT_A : ISOTP_N_ERROR); + break; + } + + if (sctx->ctx->opts->bs && !sctx->bs) { + k_timer_start(&sctx->timer, K_MSEC(ISOTP_BS_TIMEOUT_MS), K_NO_WAIT); + sctx->state = ISOTP_TX_WAIT_FC; + LOG_DBG("BS reached. Wait for FC again"); + break; + } + else if (sctx->stmin) { + sctx->state = ISOTP_TX_WAIT_ST; + break; + } + + /* Ensure FIFO style transmission of CF */ + k_sem_take(&sctx->sem, K_FOREVER); + } while (ret > 0); + break; + + case ISOTP_TX_WAIT_ST: + k_timer_start(&sctx->timer, stmin_to_timeout(sctx->stmin), K_NO_WAIT); + sctx->state = ISOTP_TX_SEND_CF; + LOG_DBG("SM wait ST"); + break; + + case ISOTP_TX_ERR: + LOG_DBG("SM error"); + sctx->ctx->sent_callback(sctx->error, sctx->cb_arg); + sctx->state = ISOTP_TX_STATE_RESET; + free_send_ctx(&sctx); + break; + + /* + * We sent this synchronously in isotp_fast_send. + * case ISOTP_TX_SEND_SF: + * __fallthrough; + * */ + + case ISOTP_TX_WAIT_FIN: + LOG_DBG("SM finish"); + k_timer_stop(&sctx->timer); + + sctx->ctx->sent_callback(ISOTP_N_OK, sctx->cb_arg); + sctx->state = ISOTP_TX_STATE_RESET; + free_send_ctx(&sctx); + break; + + default: + break; + } +} + +static void send_work_handler(struct k_work *work) +{ + struct isotp_fast_send_ctx *sctx = CONTAINER_OF(work, struct isotp_fast_send_ctx, work); + + send_state_machine(sctx); +} + +static void send_timeout_handler(struct k_timer *timer) +{ + struct isotp_fast_send_ctx *sctx = CONTAINER_OF(timer, struct isotp_fast_send_ctx, timer); + + if (sctx->state != ISOTP_TX_SEND_CF) { + LOG_ERR("Timed out waiting for FC frame"); + send_report_error(sctx, ISOTP_N_TIMEOUT_BS); + } + + k_work_submit(&sctx->work); +} + +static inline void prepare_filter(struct can_filter *filter, isotp_fast_msg_id my_addr, + const struct isotp_fast_opts *opts) +{ + filter->id = my_addr; + filter->mask = ISOTP_FIXED_ADDR_RX_MASK; + filter->flags = CAN_FILTER_DATA | CAN_FILTER_IDE + | ((opts->flags & ISOTP_MSG_FDF) != 0 ? CAN_FILTER_FDF : 0); +} + +int isotp_fast_bind(struct isotp_fast_ctx *ctx, const struct device *can_dev, + const isotp_fast_msg_id my_addr, const struct isotp_fast_opts *opts, + isotp_fast_recv_callback_t recv_callback, void *recv_cb_arg, + isotp_fast_recv_error_callback_t recv_error_callback, + isotp_fast_send_callback_t sent_callback) +{ + sys_slist_init(&isotp_send_ctx_list); + sys_slist_init(&isotp_recv_ctx_list); +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + sys_slist_init(&ctx->wait_recv_list); +#endif + + ctx->can_dev = can_dev; + ctx->opts = opts; + ctx->recv_callback = recv_callback; + ctx->recv_cb_arg = recv_cb_arg; + ctx->recv_error_callback = recv_error_callback; + ctx->sent_callback = sent_callback; + ctx->my_addr = my_addr; + + struct can_filter filter; + prepare_filter(&filter, my_addr, opts); + ctx->filter_id = can_add_rx_filter(ctx->can_dev, can_rx_callback, ctx, &filter); + + LOG_INF("Successfully bound to %x:%x", filter.id, filter.mask); + + return ISOTP_N_OK; +} + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE +static void free_recv_await_ctx(struct isotp_fast_ctx *ctx, struct isotp_fast_recv_await_ctx **actx) +{ + sys_slist_find_and_remove(&ctx->wait_recv_list, &(*actx)->node); + if ((*actx)->rctx) { + free_recv_ctx(&(*actx)->rctx); + } + k_mem_slab_free(&isotp_recv_await_ctx_slab, (void **)actx); +} +#endif + +int isotp_fast_unbind(struct isotp_fast_ctx *ctx) +{ + if (ctx->filter_id >= 0 && ctx->can_dev) { + can_remove_rx_filter(ctx->can_dev, ctx->filter_id); + } + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE + struct isotp_fast_recv_await_ctx *actx; + struct isotp_fast_recv_await_ctx *next; + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&ctx->wait_recv_list, actx, next, node) + { + free_recv_await_ctx(ctx, &actx); + } +#endif + return ISOTP_N_OK; +} + +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE +int isotp_fast_recv(struct isotp_fast_ctx *ctx, struct can_filter sender, uint8_t *buf, size_t size, + k_timeout_t timeout) +{ + /* first try to find in-flight context */ + bool found = false; + struct isotp_fast_recv_await_ctx *actx; + SYS_SLIST_FOR_EACH_CONTAINER(&ctx->wait_recv_list, actx, node) + { + if (actx->sender.id == sender.id && actx->sender.mask == sender.mask) { + found = true; + break; + } + } + + int ret; + if (!found) { + /* create a new context */ + LOG_DBG("Creating new await context matching sender %x:%x", sender.id, sender.mask); + int err = k_mem_slab_alloc(&isotp_recv_await_ctx_slab, (void **)&actx, K_NO_WAIT); + if (err != 0) { + return ISOTP_NO_CTX_LEFT; + } + + actx->sender = sender; + k_sem_init(&actx->sem, 0, 1); + sys_slist_append(&ctx->wait_recv_list, &actx->node); + + /* try to find matching receive context in case there is already one pending */ + struct isotp_fast_recv_ctx *rctx; + bool wait = true; + SYS_SLIST_FOR_EACH_CONTAINER(&isotp_recv_ctx_list, rctx, node) + { + if ((sender.id & sender.mask) == (rctx->sender_addr & sender.mask) && !rctx->pending) { + LOG_DBG("Matched await context %x:%x to sender %x", sender.id, sender.mask, + rctx->sender_addr); + actx->rctx = rctx; + rctx->pending = true; + wait = false; + break; + } + } + + if (wait) { + /* completely new, so wait for something to happen */ + LOG_DBG("Waiting for message matching %x:%x", sender.id, sender.mask); + ret = k_sem_take(&actx->sem, timeout); + if (ret == -EAGAIN) { + free_recv_await_ctx(ctx, &actx); + LOG_DBG("Timed out waiting for first message"); + return ISOTP_RECV_TIMEOUT; + } + LOG_DBG("Matched; processing message"); + } + } + + if (actx->rctx->error != 0) { + LOG_DBG("Error %d occurred", actx->rctx->error); + ret = actx->rctx->error; + free_recv_await_ctx(ctx, &actx); + return ret; + } + + struct net_buf *frag; + int pos = 0; + int rem_len = 0; + while ((ret = k_msgq_get(&actx->rctx->recv_queue, &frag, timeout)) == 0) { + if (actx->rctx->error != 0) { + LOG_DBG("Error %d occurred", actx->rctx->error); + ret = actx->rctx->error; + free_recv_await_ctx(ctx, &actx); + return ret; + } + if (pos == 0) { + LOG_DBG("New messages received"); + } + rem_len = *(int *)net_buf_user_data(frag); + LOG_DBG("Remaining length %d, enqueued %d", rem_len, + k_msgq_num_used_get(&actx->rctx->recv_queue)); + int len = MIN(frag->len, size - pos); + memcpy(buf, frag->data, len); + net_buf_unref(frag); + pos += len; + buf += len; + if (size - pos < (CAN_MAX_DLEN - 1) && rem_len > (CAN_MAX_DLEN - 1)) { + /* user recv buffer full */ + LOG_DBG("Buffer full; returning"); + break; + } + if (rem_len == 0) { + break; + } + } + actx->rctx->pending = false; + if (rem_len == 0) { + free_recv_await_ctx(ctx, &actx); + } + if (ret == -EAGAIN) { + free_recv_await_ctx(ctx, &actx); + LOG_DBG("Timed out waiting on more packets"); + return ISOTP_RECV_TIMEOUT; + } + return pos; +} +#endif /* CONFIG_ISOTP_FAST_BLOCKING_RECEIVE */ + +int isotp_fast_send(struct isotp_fast_ctx *ctx, const uint8_t *data, size_t len, + const isotp_fast_node_id their_id, void *cb_arg) +{ + const isotp_fast_msg_id recipient_addr = (ctx->my_addr & 0xFFFF0000) + | (isotp_fast_get_addr_recipient(ctx->my_addr)) + | (their_id << ISOTP_FIXED_ADDR_TA_POS); + if (len <= (CAN_MAX_DLEN - ISOTP_FAST_SF_LEN_BYTE)) { + struct can_frame frame; + prepare_frame(&frame, ctx, recipient_addr); + int index = 1; +#ifdef CONFIG_CAN_FD_MODE + if (len > ISOTP_4BIT_SF_MAX_CAN_DL - 1) { + frame.data[0] = ISOTP_PCI_TYPE_SF; + frame.data[1] = (uint8_t)len; + index = 2; + } + else { + frame.data[0] = ISOTP_PCI_TYPE_SF | (uint8_t)len; + } +#else + frame.data[0] = (uint8_t)len; +#endif + frame.dlc = can_bytes_to_dlc(len + index); + memcpy(&frame.data[index], data, len); + int ret = can_send(ctx->can_dev, &frame, K_MSEC(ISOTP_A_TIMEOUT_MS), NULL, NULL); + ctx->sent_callback(ret, cb_arg); + return ISOTP_N_OK; + } + else { + if (len > ISOTP_FAST_MAX_LEN) { + return ISOTP_N_BUFFER_OVERFLW; + } + struct isotp_fast_send_ctx *context; + int ret = get_send_ctx(ctx, recipient_addr, &context); + if (ret) { + return ISOTP_NO_NET_BUF_LEFT; + } + context->ctx = ctx; + context->recipient_addr = recipient_addr; + context->data = data; + context->bs = ctx->opts->bs; + context->stmin = ctx->opts->stmin; + context->rem_len = len; + context->state = ISOTP_TX_SEND_FF; + context->cb_arg = cb_arg; + k_sem_init(&context->sem, 0, 1); + k_work_init(&context->work, send_work_handler); + k_timer_init(&context->timer, send_timeout_handler, NULL); + + k_work_submit(&context->work); + } + return ISOTP_N_OK; +} \ No newline at end of file diff --git a/src/isotp_fast_internal.h b/src/isotp_fast_internal.h new file mode 100644 index 0000000..853ed1d --- /dev/null +++ b/src/isotp_fast_internal.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "isotp_internal.h" +#include +#include + +#ifdef CONFIG_ISOTP_FAST_PER_FRAME_DISPATCH +#define ISOTP_FAST_RECEIVE_QUEUE +#endif +#ifdef CONFIG_ISOTP_FAST_BLOCKING_RECEIVE +#define ISOTP_FAST_RECEIVE_QUEUE +#endif + +#ifdef CONFIG_CAN_FD_MODE +#define ISOTP_FAST_SF_LEN_BYTE 2 +#else +#define ISOTP_FAST_SF_LEN_BYTE 1 +#endif + +#define ISOTP_FAST_MAX_LEN 4095 + +#define ISOTP_4BIT_SF_MAX_CAN_DL 8 + +/** + * Internal send context. Used to manage the transmission of a single + * message greater than 1 CAN frame in size. + */ +struct isotp_fast_send_ctx +{ + sys_snode_t node; /**< linked list node in @ref isotp_send_ctx_list */ + struct isotp_fast_ctx *ctx; /**< pointer to bound context */ + isotp_fast_msg_id recipient_addr; /**< CAN ID used on sent message frames */ + struct k_work work; + struct k_timer timer; /**< handles timeouts */ + struct k_sem sem; /**< used to ensure CF frames are sent in order */ + const uint8_t *data; /**< source message buffer */ + uint16_t rem_len : 12; /**< length of buffer; max len 4095 */ + enum isotp_tx_state state : 8; /**< current state of context */ + int8_t error; + void *cb_arg; /**< supplied to sent_callback */ + uint8_t wft; + uint8_t bs; + uint8_t sn : 4; /**< sequence number; overflows at 4 bits per spec */ + uint8_t backlog; + uint8_t stmin; +}; + +/** + * Internal receive context. Used to manage the receipt of a single + * message. + */ +struct isotp_fast_recv_ctx +{ + sys_snode_t node; /**< linked list node in @ref isotp_recv_ctx_list */ + struct isotp_fast_ctx *ctx; /**< pointer to bound context */ + isotp_fast_msg_id sender_addr; /**< CAN ID on received frames */ + struct k_work work; + struct k_timer timer; /**< handles timeouts */ + struct net_buf *buffer; /**< head node of buffer */ + struct net_buf *frag; /**< current fragment */ +#ifdef ISOTP_FAST_RECEIVE_QUEUE + struct k_msgq recv_queue; + uint8_t recv_queue_pool[sizeof(struct net_buf *) * CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT]; +#endif + uint16_t rem_len : 12; /**< remaining length of incoming message */ + enum isotp_rx_state state : 8; /**< current state of context */ + int8_t error; + uint8_t wft; + uint8_t bs; + uint8_t sn_expected : 4; +#ifdef ISOTP_FAST_RECEIVE_QUEUE + bool pending; +#endif +}; + +struct isotp_fast_recv_await_ctx +{ + sys_snode_t node; + struct can_filter sender; + struct k_sem sem; + struct isotp_fast_recv_ctx *rctx; +}; + +static inline isotp_fast_node_id isotp_fast_get_addr_sender(isotp_fast_msg_id addr) +{ + return (isotp_fast_node_id)(addr & ISOTP_FIXED_ADDR_SA_MASK); +} + +static inline isotp_fast_node_id isotp_fast_get_frame_sender(struct can_frame *frame) +{ + return (isotp_fast_node_id)(frame->id & ISOTP_FIXED_ADDR_SA_MASK); +} + +static inline isotp_fast_node_id isotp_fast_get_addr_recipient(isotp_fast_msg_id addr) +{ + return (isotp_fast_node_id)((addr & ISOTP_FIXED_ADDR_TA_MASK) >> ISOTP_FIXED_ADDR_TA_POS); +} \ No newline at end of file diff --git a/src/isotp_internal.h b/src/isotp_internal.h new file mode 100644 index 0000000..d08db0f --- /dev/null +++ b/src/isotp_internal.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Alexander Wachter + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_NET_CAN_ISOTP_INTERNAL_H_ +#define ZEPHYR_SUBSYS_NET_CAN_ISOTP_INTERNAL_H_ + +#include +#include + +/* + * Abbreviations + * BS Block Size + * CAN_DL CAN LL data size + * CF Consecutive Frame + * CTS Continue to send + * DLC Data length code + * FC Flow Control + * FF First Frame + * SF Single Frame + * FS Flow Status + * AE Adders Extension + * SN Sequence Number + * ST Separation time + * PCI Process Control Information + */ + +/* Protocol control information*/ +#define ISOTP_PCI_SF 0x00 /* Single frame*/ +#define ISOTP_PCI_FF 0x01 /* First frame */ +#define ISOTP_PCI_CF 0x02 /* Consecutive frame */ +#define ISOTP_PCI_FC 0x03 /* Flow control frame */ + +#define ISOTP_PCI_TYPE_BYTE 0 +#define ISOTP_PCI_TYPE_POS 4 +#define ISOTP_PCI_TYPE_MASK 0xF0 +#define ISOTP_PCI_TYPE_SF (ISOTP_PCI_SF << ISOTP_PCI_TYPE_POS) +#define ISOTP_PCI_TYPE_FF (ISOTP_PCI_FF << ISOTP_PCI_TYPE_POS) +#define ISOTP_PCI_TYPE_CF (ISOTP_PCI_CF << ISOTP_PCI_TYPE_POS) +#define ISOTP_PCI_TYPE_FC (ISOTP_PCI_FC << ISOTP_PCI_TYPE_POS) + +#define ISOTP_PCI_SF_DL_MASK 0x0F + +#define ISOTP_PCI_FF_DL_UPPER_BYTE 0 +#define ISOTP_PCI_FF_DL_UPPER_MASK 0x0F +#define ISOTP_PCI_FF_DL_LOWER_BYTE 1 + +#define ISOTP_PCI_FS_BYTE 0 +#define ISOTP_PCI_FS_MASK 0x0F +#define ISOTP_PCI_BS_BYTE 1 +#define ISOTP_PCI_ST_MIN_BYTE 2 + +#define ISOTP_PCI_FS_CTS 0x0 +#define ISOTP_PCI_FS_WAIT 0x1 +#define ISOTP_PCI_FS_OVFLW 0x2 + +#define ISOTP_PCI_SN_MASK 0x0F + +#ifdef CONFIG_CAN_FD_MODE +#define ISOTP_FF_DL_MIN (CANFD_MAX_DLC) +#else +#define ISOTP_FF_DL_MIN (CAN_MAX_DLC) +#endif + +#define ISOTP_STMIN_MAX 0xFA +#define ISOTP_STMIN_MS_MAX 0x7F +#define ISOTP_STMIN_US_BEGIN 0xF1 +#define ISOTP_STMIN_US_END 0xF9 + +#define ISOTP_WFT_FIRST 0xFF + +#define ISOTP_BS_TIMEOUT_MS (CONFIG_ISOTP_BS_TIMEOUT) +#define ISOTP_A_TIMEOUT_MS (CONFIG_ISOTP_A_TIMEOUT) +#define ISOTP_CR_TIMEOUT_MS (CONFIG_ISOTP_CR_TIMEOUT) + +/* Just before the sender would time out*/ +#define ISOTP_ALLOC_TIMEOUT_MS (CONFIG_ISOTP_A_TIMEOUT - 100) + +#ifdef __cplusplus +extern "C" { +#endif + +enum isotp_rx_state +{ + ISOTP_RX_STATE_WAIT_FF_SF, + ISOTP_RX_STATE_PROCESS_SF, + ISOTP_RX_STATE_PROCESS_FF, + ISOTP_RX_STATE_TRY_ALLOC, + ISOTP_RX_STATE_SEND_FC, + ISOTP_RX_STATE_WAIT_CF, + ISOTP_RX_STATE_SEND_WAIT, + ISOTP_RX_STATE_ERR, + ISOTP_RX_STATE_RECYCLE, + ISOTP_RX_STATE_UNBOUND +}; + +enum isotp_tx_state +{ + ISOTP_TX_STATE_RESET, + ISOTP_TX_SEND_SF, + ISOTP_TX_SEND_FF, + ISOTP_TX_WAIT_FC, + ISOTP_TX_SEND_CF, + ISOTP_TX_WAIT_ST, + ISOTP_TX_WAIT_BACKLOG, + ISOTP_TX_WAIT_FIN, + ISOTP_TX_ERR +}; + +struct isotp_global_ctx +{ + sys_slist_t alloc_list; + sys_slist_t ff_sf_alloc_list; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_SUBSYS_NET_CAN_ISOTP_INTERNAL_H_ */ diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..2661486 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,11 @@ +# ThingSet SDK unit tests + +## Run unit tests + +With twister: + + ../zephyr/scripts/twister -T ./tests --integration -v -n + +Manually (`tests/can` used as an example): + + west build -b native_posix tests/can -t run diff --git a/tests/can/CMakeLists.txt b/tests/can/CMakeLists.txt new file mode 100644 index 0000000..47453da --- /dev/null +++ b/tests/can/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(thingset_sdk_can_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/can/prj.conf b/tests/can/prj.conf new file mode 100644 index 0000000..0b39679 --- /dev/null +++ b/tests/can/prj.conf @@ -0,0 +1,17 @@ +# Copyright (c) The ThingSet Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_CAN=y +CONFIG_ISOTP=y +CONFIG_ENTROPY_GENERATOR=y + +CONFIG_THINGSET=y +CONFIG_THINGSET_SDK=y +CONFIG_THINGSET_CAN=y + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_SUMMARY=n + +# enable click-able absolute paths in assert messages +#CONFIG_BUILD_OUTPUT_STRIP_PATHS=n diff --git a/tests/can/src/main.c b/tests/can/src/main.c new file mode 100644 index 0000000..b827c21 --- /dev/null +++ b/tests/can/src/main.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) The ThingSet Project Contributors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#define TEST_RECEIVE_TIMEOUT K_MSEC(100) + +static struct thingset_context ts; + +static const struct device *can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); + +static struct k_sem report_rx_sem; +static struct k_sem request_tx_sem; + +static void report_rx_callback(uint16_t data_id, const uint8_t *value, size_t value_len, + uint8_t source_addr) +{ + k_sem_give(&report_rx_sem); +} + +ZTEST(thingset_can, test_receive_report_from_node) +{ + + struct can_frame report_frame = { + .id = 0x1E000002, /* node with address 0x02 */ + .flags = CAN_FRAME_IDE, + .data = { 0xF6 }, + .dlc = 1, + }; + int err; + + k_sem_reset(&report_rx_sem); + + err = can_send(can_dev, &report_frame, K_MSEC(10), NULL, NULL); + zassert_equal(err, 0, "can_send failed: %d", err); + + err = k_sem_take(&report_rx_sem, TEST_RECEIVE_TIMEOUT); + zassert_equal(err, 0, "receive timeout"); +} + +static void request_rx_cb(const struct device *dev, struct can_frame *frame, void *user_data) +{ + k_sem_give(&request_tx_sem); +} + +ZTEST(thingset_can, test_send_request_to_node) +{ + struct can_filter other_node_filter = { + .id = 0x1800CC00, + .mask = 0x1F00FF00, + .flags = CAN_FILTER_DATA | CAN_FILTER_IDE, + }; + uint8_t req_buf[] = { 0x01, 0x00 }; /* simple single-frame request via ISO-TP */ + int err; + + k_sem_reset(&request_tx_sem); + + err = can_add_rx_filter(can_dev, &request_rx_cb, NULL, &other_node_filter); + zassert_false(err < 0, "adding rx filter failed: %d", err); + + thingset_can_send(req_buf, sizeof(req_buf), 0xCC); + + err = k_sem_take(&request_tx_sem, TEST_RECEIVE_TIMEOUT); + zassert_equal(err, 0, "receive timeout"); +} + +static void *thingset_can_setup(void) +{ + int err; + + k_sem_init(&report_rx_sem, 0, 1); + k_sem_init(&request_tx_sem, 0, 1); + + thingset_init_global(&ts); + + zassert_true(device_is_ready(can_dev), "CAN device not ready"); + + (void)can_stop(can_dev); + + err = can_set_mode(can_dev, CAN_MODE_LOOPBACK); + zassert_equal(err, 0, "failed to set loopback mode (err %d)", err); + + err = can_start(can_dev); + zassert_equal(err, 0, "failed to start CAN controller (err %d)", err); + + /* wait for address claiming to finish */ + k_sleep(K_MSEC(1000)); + + thingset_can_set_report_rx_callback(report_rx_callback); + + return NULL; +} + +ZTEST_SUITE(thingset_can, NULL, thingset_can_setup, NULL, NULL, NULL); diff --git a/tests/can/testcase.yaml b/tests/can/testcase.yaml new file mode 100644 index 0000000..d7e05a2 --- /dev/null +++ b/tests/can/testcase.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +tests: + thingset_sdk.can: + integration_platforms: + - native_posix_64 + extra_args: EXTRA_CFLAGS=-Wno-error diff --git a/tests/isotp_fast/conformance/src/main.c b/tests/isotp_fast/conformance/src/main.c new file mode 100644 index 0000000..dafedf4 --- /dev/null +++ b/tests/isotp_fast/conformance/src/main.c @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2019 Alexander Wachter + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * @addtogroup t_can + * @{ + * @defgroup t_can_isotp test_can_isotp + * @brief TestPurpose: verify correctness of the iso tp implementation + * @details + * - Test Steps + * -# + * - Expected Results + * -# + * @} + */ + +void isotp_fast_sent_handler(int result, void *arg) +{ + int expected_err_nr = POINTER_TO_INT(arg); + + zassert_equal(result, expected_err_nr, "Unexpected error nr. expect: %d, got %d", + expected_err_nr, result); + k_sem_give(&send_compl_sem); +} + +static int check_data(const uint8_t *frame, const uint8_t *desired, size_t length) +{ + int ret; + + ret = memcmp(frame, desired, length); + if (ret) { + printk("desired bytes:\n"); + print_hex(desired, length); + printk("\nreceived (%zu bytes):\n", length); + print_hex(frame, length); + printk("\n"); + } + + return ret; +} + +static void send_sf(void) +{ + int ret; + + ret = isotp_fast_send(&ctx, random_data, DATA_SIZE_SF, rx_node_id, NULL); + zassert_equal(ret, 0, "Send returned %d", ret); +} + +static void get_sf(struct isotp_fast_ctx *recv_ctx, size_t data_size) +{ + int ret; + + memset(data_buf, 0, sizeof(data_buf)); + ret = blocking_recv(data_buf, sizeof(data_buf), K_MSEC(1000)); + zassert_equal(ret, data_size, "recv returned %d", ret); + + ret = check_data(data_buf, random_data, data_size); + zassert_equal(ret, 0, "Data differ"); +} + +static void get_sf_ignore(struct isotp_fast_ctx *recv_ctx) +{ + int ret; + + ret = blocking_recv(data_buf, sizeof(data_buf), K_MSEC(200)); + zassert_equal(ret, ISOTP_RECV_TIMEOUT, "recv returned %d", ret); +} + +static void send_test_data(const uint8_t *data, size_t len) +{ + int ret; + + ret = isotp_fast_send(&ctx, data, len, rx_node_id, INT_TO_POINTER(ISOTP_N_OK)); + zassert_equal(ret, 0, "Send returned %d", ret); +} + +static void receive_test_data(struct isotp_fast_ctx *recv_ctx, const uint8_t *data, size_t len, + int32_t delay) +{ + size_t remaining_len = len; + int ret, recv_len; + const uint8_t *data_ptr = data; + + do { + memset(data_buf, 0, sizeof(data_buf)); + recv_len = blocking_recv(data_buf, sizeof(data_buf), K_MSEC(1000)); + zassert_true(recv_len >= 0, "recv error: %d", recv_len); + + zassert_true(remaining_len >= recv_len, "More data than expected"); + ret = check_data(data_buf, data_ptr, recv_len); + zassert_equal(ret, 0, "Data differ"); + data_ptr += recv_len; + remaining_len -= recv_len; + + if (delay) { + k_msleep(delay); + } + } while (remaining_len); + + ret = blocking_recv(data_buf, sizeof(data_buf), K_MSEC(50)); + zassert_equal(ret, ISOTP_RECV_TIMEOUT, "Expected timeout but got %d", ret); +} + +static void send_frame_series(struct frame_desired *frames, size_t length, uint32_t id) +{ + int i, ret; + struct can_frame frame = { .flags = (id > 0x7FF) ? CAN_FRAME_IDE : 0, .id = id }; +#ifdef CONFIG_CAN_FD_MODE + frame.flags |= CAN_FRAME_FDF; +#endif + struct frame_desired *desired = frames; + + for (i = 0; i < length; i++) { + frame.dlc = can_bytes_to_dlc(desired->length); + memcpy(frame.data, desired->data, desired->length); + // printk("> [%x] [%02d] ", frame.id, desired->length); + // print_hex(frame.data, desired->length); + // printk("\n"); + ret = can_send(can_dev, &frame, K_MSEC(500), NULL, NULL); + zassert_equal(ret, 0, "Sending msg %d failed (error %d).", i, ret); + desired++; + } +} + +static void check_frame_series(struct frame_desired *frames, size_t length, struct k_msgq *msgq) +{ + int i, ret; + struct can_frame frame; + struct frame_desired *desired = frames; + + for (i = 0; i < length; i++) { + ret = k_msgq_get(msgq, &frame, K_MSEC(500)); + zassert_equal(ret, 0, "Timeout waiting for msg nr %d. ret: %d", i, ret); + // printk("RECV: "); + // print_hex(frame.data, can_dlc_to_bytes(frame.dlc)); + // printk("(%x) \n", frame.id); + /* normalise the lengths here so they are comparable */ + zassert_equal(frame.dlc, can_bytes_to_dlc(desired->length), + "DLC of frame nr %d differ. Desired: %d, Got: %d", i, + can_bytes_to_dlc(desired->length), frame.dlc); + + ret = check_data(frame.data, desired->data, desired->length); + zassert_equal(ret, 0, "Data differ"); + + desired++; + } + ret = k_msgq_get(msgq, &frame, K_MSEC(200)); + zassert_equal(ret, -EAGAIN, + "Expected timeout, but received %d; %02x %02x %02x %02x %02x %02x %02x %02x", ret, + frame.data[0], frame.data[1], frame.data[2], frame.data[3], frame.data[4], + frame.data[5], frame.data[6], frame.data[7]); +} + +static int add_rx_msgq(uint32_t id, uint32_t mask) +{ + int filter_id; + struct can_filter filter = { .flags = CAN_FILTER_DATA | ((id > 0x7FF) ? CAN_FILTER_IDE : 0), + .id = id, + .mask = mask }; +#ifdef CONFIG_CAN_FD_MODE + filter.flags |= CAN_FILTER_FDF; +#endif + + filter_id = can_add_rx_filter_msgq(can_dev, &frame_msgq, &filter); + zassert_not_equal(filter_id, -ENOSPC, "Filter full"); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + return filter_id; +} + +static void prepare_cf_frames(struct frame_desired *frames, size_t frames_cnt, const uint8_t *data, + size_t data_len) +{ + int i; + const uint8_t *data_ptr = data; + size_t remaining_length = data_len; + + for (i = 0; i < frames_cnt && remaining_length; i++) { + frames[i].data[0] = CF_PCI_BYTE_1 | ((i + 1) & 0x0F); + frames[i].length = CAN_DL; + memcpy(&des_frames[i].data[1], data_ptr, DATA_SIZE_CF); + + if (remaining_length < DATA_SIZE_CF) { + frames[i].length = remaining_length + 1; + remaining_length = 0; + } + + remaining_length -= DATA_SIZE_CF; + data_ptr += DATA_SIZE_CF; + } +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_send_sf) +{ + struct frame_desired des_frame; + +#ifdef CONFIG_CAN_FD_MODE + des_frame.data[0] = (SF_PCI_TYPE << PCI_TYPE_POS); + des_frame.data[1] = DATA_SIZE_SF; +#else + des_frame.data[0] = SF_PCI_BYTE_1; +#endif + memcpy(&des_frame.data[SF_LEN_BYTE], random_data, DATA_SIZE_SF); + des_frame.length = CAN_MAX_DLEN; + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + send_sf(); + + check_frame_series(&des_frame, 1, &frame_msgq); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_receive_sf) +{ + struct frame_desired single_frame; + +#ifdef CONFIG_CAN_FD_MODE + single_frame.data[0] = (SF_PCI_TYPE << PCI_TYPE_POS); + single_frame.data[1] = DATA_SIZE_SF; +#else + single_frame.data[0] = SF_PCI_BYTE_1; +#endif + memcpy(&single_frame.data[SF_LEN_BYTE], random_data, DATA_SIZE_SF); + single_frame.length = CAN_MAX_DLEN; + + send_frame_series(&single_frame, 1, rx_addr); + + get_sf(&ctx, DATA_SIZE_SF); + + single_frame.data[0] = SF_PCI_BYTE_LEN_8; + send_frame_series(&single_frame, 1, rx_addr); + + get_sf_ignore(&ctx); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_send_sf_fixed) +{ + int ret; + struct frame_desired des_frame; + +#ifdef CONFIG_CAN_FD_MODE + des_frame.data[0] = (SF_PCI_TYPE << PCI_TYPE_POS); + des_frame.data[1] = DATA_SIZE_SF; +#else + des_frame.data[0] = SF_PCI_BYTE_1; +#endif + memcpy(&des_frame.data[SF_LEN_BYTE], random_data, DATA_SIZE_SF); + des_frame.length = CAN_MAX_DLEN; + + /* mask to allow any priority and source address (SA) */ + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + ret = isotp_fast_send(&ctx, random_data, DATA_SIZE_SF, rx_node_id, INT_TO_POINTER(ISOTP_N_OK)); + zassert_equal(ret, 0, "Send returned %d", ret); + + check_frame_series(&des_frame, 1, &frame_msgq); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_receive_sf_fixed) +{ + struct frame_desired single_frame; + +#ifdef CONFIG_CAN_FD_MODE + single_frame.data[0] = (SF_PCI_TYPE << PCI_TYPE_POS); + single_frame.data[1] = DATA_SIZE_SF; +#else + single_frame.data[0] = SF_PCI_BYTE_1; +#endif + memcpy(&single_frame.data[SF_LEN_BYTE], random_data, DATA_SIZE_SF); + single_frame.length = CAN_MAX_DLEN; + + /* default source address */ + send_frame_series(&single_frame, 1, rx_addr); + get_sf(&ctx, DATA_SIZE_SF); + + /* different source address */ + send_frame_series(&single_frame, 1, rx_addr | 0xFF); + get_sf(&ctx, DATA_SIZE_SF); + + /* different priority */ + send_frame_series(&single_frame, 1, rx_addr | (7U << 26)); + get_sf(&ctx, DATA_SIZE_SF); + + /* different target address (should fail) */ + send_frame_series(&single_frame, 1, rx_addr | 0xFF00); + get_sf_ignore(&ctx); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_send_data) +{ + struct frame_desired fc_frame, ff_frame; + const uint8_t *data_ptr = random_data; + size_t remaining_length = DATA_SEND_LENGTH; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); + ff_frame.length = CAN_DL; + data_ptr += DATA_SIZE_FF; + remaining_length -= DATA_SIZE_FF; + + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_frame.data[1] = FC_PCI_BYTE_2(0); + fc_frame.data[2] = FC_PCI_BYTE_3(0); + fc_frame.length = DATA_SIZE_FC; + + prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length); + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + send_test_data(random_data, DATA_SEND_LENGTH); + + check_frame_series(&ff_frame, 1, &frame_msgq); + + send_frame_series(&fc_frame, 1, rx_addr); + + check_frame_series(des_frames, ARRAY_SIZE(des_frames), &frame_msgq); +} + +/* hiding this whole test to avoid compiler errors */ +#ifndef CONFIG_CAN_FD_MODE +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_send_data_blocks) +{ + const uint8_t *data_ptr = random_data; + size_t remaining_length = DATA_SEND_LENGTH; + struct frame_desired *data_frame_ptr = des_frames; + int ret; + struct can_frame dummy_frame; + struct frame_desired fc_frame, ff_frame; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); + ff_frame.length = DATA_SIZE_FF + 2; + data_ptr += DATA_SIZE_FF; + remaining_length -= DATA_SIZE_FF; + + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_frame.data[1] = FC_PCI_BYTE_2(fc_opts.bs); + fc_frame.data[2] = FC_PCI_BYTE_3(0); + fc_frame.length = DATA_SIZE_FC; + + prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length); + + remaining_length = DATA_SEND_LENGTH; + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + send_test_data(random_data, DATA_SEND_LENGTH); + + check_frame_series(&ff_frame, 1, &frame_msgq); + remaining_length -= DATA_SIZE_FF; + + send_frame_series(&fc_frame, 1, rx_addr); + + check_frame_series(data_frame_ptr, fc_opts.bs, &frame_msgq); + data_frame_ptr += fc_opts.bs; + remaining_length -= fc_opts.bs * DATA_SIZE_CF; + ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); + zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); + + fc_frame.data[1] = FC_PCI_BYTE_2(2); + send_frame_series(&fc_frame, 1, rx_addr); + + /* dynamic bs */ + check_frame_series(data_frame_ptr, 2, &frame_msgq); + data_frame_ptr += 2; + remaining_length -= 2 * DATA_SIZE_CF; + ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); + zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); + + /* get the rest */ + fc_frame.data[1] = FC_PCI_BYTE_2(0); + send_frame_series(&fc_frame, 1, rx_addr); + + check_frame_series(data_frame_ptr, DIV_ROUND_UP(remaining_length, DATA_SIZE_CF), &frame_msgq); + ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); + zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); +} +#endif /* CONFIG_CAN_FD_MODE */ + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_receive_data) +{ + const uint8_t *data_ptr = random_data; + size_t remaining_length = DATA_SEND_LENGTH; + struct frame_desired fc_frame, ff_frame; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); + ff_frame.length = CAN_DL; + data_ptr += DATA_SIZE_FF; + remaining_length -= DATA_SIZE_FF; + + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_frame.data[1] = FC_PCI_BYTE_2(fc_opts.bs); + fc_frame.data[2] = FC_PCI_BYTE_3(fc_opts.stmin); + fc_frame.length = DATA_SIZE_FC; + + prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length); + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + + send_frame_series(&ff_frame, 1, rx_addr); + + check_frame_series(&fc_frame, 1, &frame_msgq); + + send_frame_series(des_frames, ARRAY_SIZE(des_frames), rx_addr); + + receive_test_data(&ctx, random_data, DATA_SEND_LENGTH, 0); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_receive_data_blocks) +{ + const uint8_t *data_ptr = random_data; + size_t remaining_length = DATA_SEND_LENGTH; + struct frame_desired *data_frame_ptr = des_frames; + int ret; + size_t remaining_frames; + struct frame_desired fc_frame, ff_frame; + + struct can_frame dummy_frame; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], data_ptr, DATA_SIZE_FF); + ff_frame.length = DATA_SIZE_FF + 2; + data_ptr += DATA_SIZE_FF; + remaining_length -= DATA_SIZE_FF; + + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_frame.data[1] = FC_PCI_BYTE_2(fc_opts.bs); + fc_frame.data[2] = FC_PCI_BYTE_3(fc_opts.stmin); + fc_frame.length = DATA_SIZE_FC; + + prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), data_ptr, remaining_length); + + remaining_frames = DIV_ROUND_UP(remaining_length, DATA_SIZE_CF); + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + send_frame_series(&ff_frame, 1, rx_addr); + + while (remaining_frames) { + check_frame_series(&fc_frame, 1, &frame_msgq); + + if (remaining_frames >= fc_opts.bs) { + send_frame_series(data_frame_ptr, fc_opts.bs, rx_addr); + data_frame_ptr += fc_opts.bs; + remaining_frames -= fc_opts.bs; + } + else { + send_frame_series(data_frame_ptr, remaining_frames, rx_addr); + data_frame_ptr += remaining_frames; + remaining_frames = 0; + } + } + /* this used to come after the next line, which drains the queue + that had the effect of being too late to get the data from isotp_fast_recv; + even as it is, this seems to rely on everything being slow enough to + work correctly */ + receive_test_data(&ctx, random_data, DATA_SEND_LENGTH, 0); + + ret = k_msgq_get(&frame_msgq, &dummy_frame, K_MSEC(50)); + zassert_equal(ret, -EAGAIN, "Expected timeout but got %d", ret); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_send_timeouts) +{ + int ret; + uint32_t start_time, time_diff; + struct frame_desired fc_cts_frame; + + fc_cts_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_cts_frame.data[1] = FC_PCI_BYTE_2(fc_opts.bs); + fc_cts_frame.data[2] = FC_PCI_BYTE_3(0); + fc_cts_frame.length = DATA_SIZE_FC; + + /* Test timeout for first FC*/ + k_sem_reset(&send_compl_sem); + start_time = k_uptime_get_32(); + isotp_fast_send(&ctx, random_data, sizeof(random_data), rx_node_id, + INT_TO_POINTER(ISOTP_N_TIMEOUT_BS)); + ret = k_sem_take(&send_compl_sem, K_MSEC(BS_TIMEOUT_UPPER_MS)); + time_diff = k_uptime_get_32() - start_time; + zassert_equal(ret, 0, "Timeout too late"); + zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); + + /* Test timeout for consecutive FC frames */ + k_sem_reset(&send_compl_sem); + ret = isotp_fast_send(&ctx, random_data, sizeof(random_data), rx_node_id, + INT_TO_POINTER(ISOTP_N_TIMEOUT_BS)); + zassert_equal(ret, ISOTP_N_OK, "Send returned %d", ret); + + send_frame_series(&fc_cts_frame, 1, rx_addr); + + start_time = k_uptime_get_32(); + ret = k_sem_take(&send_compl_sem, K_MSEC(BS_TIMEOUT_UPPER_MS)); + zassert_equal(ret, 0, "Timeout too late"); + + time_diff = k_uptime_get_32() - start_time; + zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); + + /* Test timeout reset with WAIT frame */ + k_sem_reset(&send_compl_sem); + ret = isotp_fast_send(&ctx, random_data, sizeof(random_data), rx_node_id, + INT_TO_POINTER(ISOTP_N_TIMEOUT_BS)); + zassert_equal(ret, ISOTP_N_OK, "Send returned %d", ret); + + ret = k_sem_take(&send_compl_sem, K_MSEC(800)); + zassert_equal(ret, -EAGAIN, "Timeout too early"); + + fc_cts_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + send_frame_series(&fc_cts_frame, 1, rx_addr); + + start_time = k_uptime_get_32(); + ret = k_sem_take(&send_compl_sem, K_MSEC(BS_TIMEOUT_UPPER_MS)); + zassert_equal(ret, 0, "Timeout too late"); + time_diff = k_uptime_get_32() - start_time; + zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_receive_timeouts) +{ + int ret; + uint32_t start_time, time_diff; + struct frame_desired ff_frame; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); + ff_frame.length = DATA_SIZE_FF + 2; + + send_frame_series(&ff_frame, 1, rx_addr); + start_time = k_uptime_get_32(); + + ret = blocking_recv(data_buf, sizeof(data_buf), K_FOREVER); + zassert_equal(ret, DATA_SIZE_FF, "Expected FF data length but got %d", ret); + ret = blocking_recv(data_buf, sizeof(data_buf), K_FOREVER); + zassert_equal(ret, ISOTP_N_TIMEOUT_CR, "Expected timeout but got %d", ret); + + time_diff = k_uptime_get_32() - start_time; + zassert_true(time_diff >= BS_TIMEOUT_LOWER_MS, "Timeout too early (%dms)", time_diff); + zassert_true(time_diff <= BS_TIMEOUT_UPPER_MS, "Timeout too slow (%dms)", time_diff); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_stmin) +{ + int ret; + struct frame_desired fc_frame, ff_frame; + struct can_frame raw_frame; + uint32_t start_time, time_diff; + + if (CONFIG_SYS_CLOCK_TICKS_PER_SEC < 1000) { + /* This test requires millisecond tick resolution */ + ztest_test_skip(); + } + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SIZE_FF + DATA_SIZE_CF * 4); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SIZE_FF + DATA_SIZE_CF * 4); + memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); + ff_frame.length = DATA_SIZE_FF + 2; + + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_frame.data[1] = FC_PCI_BYTE_2(2); + fc_frame.data[2] = FC_PCI_BYTE_3(STMIN_VAL_1); + fc_frame.length = DATA_SIZE_FC; + + filter_id = add_rx_msgq(rx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + send_test_data(random_data, DATA_SIZE_FF + DATA_SIZE_CF * 4); + + check_frame_series(&ff_frame, 1, &frame_msgq); + + send_frame_series(&fc_frame, 1, tx_addr); + + ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(100)); + zassert_equal(ret, 0, "Expected to get a message. [%d]", ret); + + start_time = k_uptime_get_32(); + ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(STMIN_VAL_1 + STMIN_UPPER_TOLERANCE)); + time_diff = k_uptime_get_32() - start_time; + zassert_equal(ret, 0, "Expected to get a message within %dms. [%d]", + STMIN_VAL_1 + STMIN_UPPER_TOLERANCE, ret); + zassert_true(time_diff >= STMIN_VAL_1, "STmin too short (%dms)", time_diff); + + fc_frame.data[2] = FC_PCI_BYTE_3(STMIN_VAL_2); + send_frame_series(&fc_frame, 1, tx_addr); + + ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(100)); + zassert_equal(ret, 0, "Expected to get a message. [%d]", ret); + + start_time = k_uptime_get_32(); + ret = k_msgq_get(&frame_msgq, &raw_frame, K_MSEC(STMIN_VAL_2 + STMIN_UPPER_TOLERANCE)); + time_diff = k_uptime_get_32() - start_time; + zassert_equal(ret, 0, "Expected to get a message within %dms. [%d]", + STMIN_VAL_2 + STMIN_UPPER_TOLERANCE, ret); + zassert_true(time_diff >= STMIN_VAL_2, "STmin too short (%dms)", time_diff); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_receiver_fc_errors) +{ + int ret; + struct frame_desired ff_frame, fc_frame; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); + ff_frame.length = DATA_SIZE_FF + 2; + + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_CTS); + fc_frame.data[1] = FC_PCI_BYTE_2(fc_opts.bs); + fc_frame.data[2] = FC_PCI_BYTE_3(fc_opts.stmin); + fc_frame.length = DATA_SIZE_FC; + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + /* wrong sequence number */ + send_frame_series(&ff_frame, 1, rx_addr); + check_frame_series(&fc_frame, 1, &frame_msgq); + + /* original version of this test used data_buf, but then the receive just + blocks waiting for more frames and then times out, which is the correct + behaviour by any reasonable measure; anyway, to preserve the existing + assertion, for now, let's pass a tiny buffer that will definitely cause + isotp_fast_recv to return */ + uint8_t tiny_buf[CAN_MAX_DLEN]; + ret = blocking_recv(tiny_buf, sizeof(tiny_buf), K_MSEC(200)); + zassert_equal(ret, DATA_SIZE_FF, "Expected FF data length but got %d", ret); + + prepare_cf_frames(des_frames, ARRAY_SIZE(des_frames), random_data + DATA_SIZE_FF, + sizeof(random_data) - DATA_SIZE_FF); + /* SN should be 2 but is set to 3 for this test */ + des_frames[1].data[0] = CF_PCI_BYTE_1 | (3 & 0x0F); + send_frame_series(des_frames, ARRAY_SIZE(des_frames), rx_addr); + + ret = blocking_recv(data_buf, sizeof(data_buf), K_MSEC(200)); + zassert_equal(ret, ISOTP_N_WRONG_SN, "Expected wrong SN but got %d", ret); +} + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_sender_fc_errors) +{ + int ret, i; + struct frame_desired ff_frame, fc_frame; + + ff_frame.data[0] = FF_PCI_BYTE_1(DATA_SEND_LENGTH); + ff_frame.data[1] = FF_PCI_BYTE_2(DATA_SEND_LENGTH); + memcpy(&ff_frame.data[2], random_data, DATA_SIZE_FF); + ff_frame.length = DATA_SIZE_FF + 2; + + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + + /* invalid flow status */ + fc_frame.data[0] = FC_PCI_BYTE_1(3); + fc_frame.data[1] = FC_PCI_BYTE_2(fc_opts.bs); + fc_frame.data[2] = FC_PCI_BYTE_3(fc_opts.stmin); + fc_frame.length = DATA_SIZE_FC; + + k_sem_reset(&send_compl_sem); + ret = isotp_fast_send(&ctx, random_data, DATA_SEND_LENGTH, rx_node_id, + INT_TO_POINTER(ISOTP_N_INVALID_FS)); + zassert_equal(ret, ISOTP_N_OK, "Send returned %d", ret); + + check_frame_series(&ff_frame, 1, &frame_msgq); + send_frame_series(&fc_frame, 1, rx_addr); + ret = k_sem_take(&send_compl_sem, K_MSEC(200)); + zassert_equal(ret, 0, "Send complete callback not called"); + + /* buffer overflow */ + can_remove_rx_filter(can_dev, filter_id); + + ret = isotp_fast_send(&ctx, random_data, 5 * 1024, rx_node_id, NULL); + zassert_equal(ret, ISOTP_N_BUFFER_OVERFLW, "Expected overflow but got %d", ret); + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + + k_sem_reset(&send_compl_sem); + ret = isotp_fast_send(&ctx, random_data, DATA_SEND_LENGTH, rx_node_id, + INT_TO_POINTER(ISOTP_N_BUFFER_OVERFLW)); + + check_frame_series(&ff_frame, 1, &frame_msgq); + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_OVFLW); + send_frame_series(&fc_frame, 1, rx_addr); + ret = k_sem_take(&send_compl_sem, K_MSEC(200)); + zassert_equal(ret, 0, "Send complete callback not called"); + + /* wft overrun */ + k_sem_reset(&send_compl_sem); + ret = isotp_fast_send(&ctx, random_data, DATA_SEND_LENGTH, rx_node_id, + INT_TO_POINTER(ISOTP_N_WFT_OVRN)); + + check_frame_series(&ff_frame, 1, &frame_msgq); + fc_frame.data[0] = FC_PCI_BYTE_1(FC_PCI_WAIT); + for (i = 0; i < CONFIG_ISOTP_WFTMAX + 1; i++) { + send_frame_series(&fc_frame, 1, rx_addr); + } + + ret = k_sem_take(&send_compl_sem, K_MSEC(200)); + zassert_equal(ret, 0, "Send complete callback not called"); +} + +void *isotp_fast_conformance_setup(void) +{ + int ret; + + zassert_true(sizeof(random_data) >= sizeof(data_buf) * 2 + 10, "Test data size too small"); + + zassert_true(device_is_ready(can_dev), "CAN device not ready"); + + can_mode_t can_mode = CAN_MODE_LOOPBACK; +#ifdef CONFIG_CAN_FD_MODE + can_mode |= CAN_MODE_FD; +#endif + ret = can_set_mode(can_dev, can_mode); + zassert_equal(ret, 0, "Failed to set loopback mode [%d]", ret); + + k_sem_init(&send_compl_sem, 0, 1); + + return NULL; +} + +void isotp_fast_conformance_before(void *) +{ + int ret = can_start(can_dev); + zassert_equal(ret, 0, "Failed to start CAN controller [%d]", ret); + + filter_id = -1; + k_msgq_purge(&frame_msgq); + + isotp_fast_bind(&ctx, can_dev, rx_addr, &fc_opts, isotp_fast_recv_handler, NULL, + isotp_fast_recv_error_handler, isotp_fast_sent_handler); +} + +void isotp_fast_conformance_after(void *) +{ + isotp_fast_unbind(&ctx); + + k_msgq_purge(&frame_msgq); + if (filter_id >= 0) { + can_remove_rx_filter(can_dev, filter_id); + } + can_stop(can_dev); +} + +ZTEST_SUITE(ISOTP_FAST_CONFORMANCE_TEST_SUITE, NULL, isotp_fast_conformance_setup, + isotp_fast_conformance_before, isotp_fast_conformance_after, NULL); diff --git a/tests/isotp_fast/conformance/src/main.h b/tests/isotp_fast/conformance/src/main.h new file mode 100644 index 0000000..e97aff0 --- /dev/null +++ b/tests/isotp_fast/conformance/src/main.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019 Alexander Wachter + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "random_data.h" +#include +#include +#include +#include +#include + +#define PCI_TYPE_POS 4 +#ifdef CONFIG_CAN_FD_MODE +#define SF_LEN_BYTE 2 /* need extra byte to store length > 16 bytes */ +#else +#define SF_LEN_BYTE 1 +#endif +#define DATA_SIZE_SF (CAN_MAX_DLEN - SF_LEN_BYTE) +#define DATA_SIZE_CF (CAN_MAX_DLEN - 1) +#define DATA_SIZE_SF_EXT (CAN_MAX_DLEN - 2) +#define DATA_SIZE_FF (CAN_MAX_DLEN - 2) +#define CAN_DL CAN_MAX_DLEN +#define DATA_SEND_LENGTH 272 +#define SF_PCI_TYPE 0 +#define SF_PCI_BYTE_1 ((SF_PCI_TYPE << PCI_TYPE_POS) | DATA_SIZE_SF) +#define SF_PCI_BYTE_2_EXT ((SF_PCI_TYPE << PCI_TYPE_POS) | DATA_SIZE_SF_EXT) +#define SF_PCI_BYTE_LEN_8 ((SF_PCI_TYPE << PCI_TYPE_POS) | (DATA_SIZE_SF + 1)) +#define EXT_ADDR 5 +#define FF_PCI_TYPE 1 +#define FF_PCI_BYTE_1(dl) ((FF_PCI_TYPE << PCI_TYPE_POS) | ((dl) >> 8)) +#define FF_PCI_BYTE_2(dl) ((dl)&0xFF) +#define FC_PCI_TYPE 3 +#define FC_PCI_CTS 0 +#define FC_PCI_WAIT 1 +#define FC_PCI_OVFLW 2 +#define FC_PCI_BYTE_1(FS) ((FC_PCI_TYPE << PCI_TYPE_POS) | (FS)) +#define FC_PCI_BYTE_2(BS) (BS) +#define FC_PCI_BYTE_3(ST_MIN) (ST_MIN) +#define CF_PCI_TYPE 2 +#define CF_PCI_BYTE_1 (CF_PCI_TYPE << PCI_TYPE_POS) +#define STMIN_VAL_1 5 +#define STMIN_VAL_2 50 +#define STMIN_UPPER_TOLERANCE 5 + +#if defined(CONFIG_ISOTP_ENABLE_TX_PADDING) || defined(CONFIG_ISOTP_ENABLE_TX_PADDING) +#define DATA_SIZE_FC CAN_DL +#else +#define DATA_SIZE_FC 3 +#endif + +#define BS_TIMEOUT_UPPER_MS 1100 +#define BS_TIMEOUT_LOWER_MS 1000 + +struct frame_desired +{ + uint8_t data[CAN_MAX_DLEN]; + uint8_t length; +}; + +struct frame_desired des_frames[DIV_ROUND_UP((DATA_SEND_LENGTH - DATA_SIZE_FF), DATA_SIZE_CF)]; + +const struct isotp_fast_opts fc_opts = { + .bs = 8, + .stmin = 0, +#ifdef CONFIG_CAN_FD_MODE + .flags = ISOTP_MSG_FDF, +#else + .flags = 0 +#endif +}; + +const isotp_fast_msg_id rx_addr = 0x18DA0201; +const isotp_fast_msg_id tx_addr = 0x18DA0102; + +const isotp_fast_node_id rx_node_id = 0x01; +const isotp_fast_node_id tx_node_id = 0x02; + +const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); +struct isotp_fast_ctx ctx; +uint8_t data_buf[128]; +CAN_MSGQ_DEFINE(frame_msgq, 10); +struct k_sem send_compl_sem; +int filter_id; + +static void print_hex(const uint8_t *ptr, size_t len) +{ + while (len--) { + printk("%02x ", *ptr++); + } +} \ No newline at end of file diff --git a/tests/isotp_fast/conformance/src/random_data.h b/tests/isotp_fast/conformance/src/random_data.h new file mode 100644 index 0000000..d26625f --- /dev/null +++ b/tests/isotp_fast/conformance/src/random_data.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Alexander Wachter + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +static const uint8_t random_data[] = { + 0xdc, 0x70, 0xfa, 0x96, 0xbb, 0x71, 0x49, 0x06, 0x18, 0x75, 0x84, 0xaf, 0xe3, 0xd4, 0x60, 0x11, + 0xf8, 0xf8, 0xfa, 0xc7, 0x67, 0xae, 0xa4, 0x36, 0x08, 0xe5, 0x76, 0xa6, 0x50, 0x98, 0x2e, 0xc1, + 0x4f, 0x91, 0x90, 0x92, 0xbf, 0xfa, 0x5a, 0xce, 0x6d, 0xeb, 0x2e, 0x5c, 0x77, 0x6b, 0x90, 0xfc, + 0x50, 0xd7, 0x69, 0x04, 0x4b, 0x1d, 0xb3, 0x54, 0x55, 0xba, 0x0f, 0x75, 0xf5, 0x3b, 0x0c, 0x76, + 0xc8, 0x31, 0x7d, 0x9a, 0xb5, 0xcd, 0x4f, 0x70, 0x47, 0xa0, 0xe3, 0xe5, 0x68, 0x59, 0xfb, 0x1e, + 0x20, 0x4a, 0x9c, 0x90, 0xb6, 0xe7, 0x45, 0x83, 0x8d, 0x71, 0xd7, 0x27, 0xac, 0xef, 0xa3, 0xb9, + 0x39, 0xda, 0x30, 0xac, 0xc3, 0x3a, 0x1c, 0x7c, 0x29, 0x2f, 0xc6, 0xa0, 0xbc, 0xe1, 0x1d, 0xab, + 0x0f, 0x16, 0x30, 0xa4, 0x3c, 0x5d, 0x10, 0x45, 0x38, 0xc0, 0x82, 0x21, 0xad, 0x4c, 0xb3, 0x27, + 0xa8, 0xe8, 0x86, 0xa3, 0x2a, 0xa1, 0xfb, 0x06, 0xab, 0xa8, 0x95, 0xab, 0xcf, 0x3b, 0x9f, 0x4e, + 0xfa, 0x09, 0xf3, 0x9b, 0x2d, 0x53, 0x1f, 0x8f, 0x00, 0x36, 0x7d, 0x91, 0xd7, 0xf5, 0xc5, 0x81, + 0x62, 0x8c, 0x7e, 0xed, 0x01, 0x2a, 0x8d, 0x8c, 0x0b, 0xb8, 0x32, 0x48, 0xad, 0x75, 0xd2, 0xb9, + 0xf0, 0x8e, 0xac, 0x1e, 0xfd, 0x3a, 0xac, 0x80, 0xb5, 0x68, 0xf0, 0x84, 0xdc, 0xdf, 0xa9, 0x5a, + 0x76, 0x39, 0xee, 0x7e, 0xd5, 0x46, 0xbc, 0xa5, 0x2d, 0x6c, 0x6f, 0x21, 0x3a, 0x23, 0xe1, 0x82, + 0x68, 0x4d, 0x67, 0xf9, 0xc4, 0x9b, 0xed, 0x77, 0x9c, 0x5a, 0xc2, 0x09, 0x95, 0xd7, 0x0a, 0x21, + 0x94, 0xa7, 0xab, 0x7b, 0xa9, 0xce, 0xce, 0xc6, 0x7e, 0x55, 0xba, 0x22, 0xe5, 0x6c, 0xd5, 0x28, + 0x8e, 0x9f, 0x23, 0x5f, 0x68, 0xd3, 0xb9, 0x46, 0x3c, 0x43, 0xcb, 0xe8, 0x9f, 0x7d, 0xb2, 0x08, + 0xc8, 0xd6, 0xf8, 0xfa, 0x4e, 0x97, 0xb2, 0x6e, 0x84, 0x0f, 0x1e, 0xfa, 0x17, 0xcb, 0x63, 0xb9, + 0xdd, 0xbe, 0xf1, 0x34, 0x72, 0xbb, 0xe5, 0x13, 0xd5, 0x7e, 0x02, 0xd1, 0x06, 0x15, 0x50, 0xd5, + 0x7c, 0x91, 0xd5, 0x29, 0x72, 0x58, 0x52, 0x98, 0xb3, 0xa4, 0x4b, 0x54, 0x12, 0xbc, 0xa2, 0xd1, + 0xb5, 0x50, 0x33, 0xe2, 0x81, 0xc2, 0x1a, 0x7a, 0xf2, 0xf8, 0xe6, 0x61, 0xa3, 0x96, 0xdb, 0xea, + 0x39, 0x84, 0xbb, 0x76, 0x0e, 0xe7, 0x42, 0x6f, 0xee, 0x24, 0x16, 0x33, 0xb1, 0x30, 0x75, 0x9c, + 0x97, 0xf0, 0x34, 0x21, 0xea, 0x60, 0xcd, 0x2e, 0x22, 0x51, 0xa4, 0x0a, 0x27, 0xd3, 0x6a, 0x36, + 0xe2, 0x55, 0xb3, 0xec, 0x6d, 0x99, 0x1a, 0x26, 0xe6, 0xe0, 0xc3, 0x34, 0x98, 0x8a, 0x95, 0x06, + 0x03, 0xa7, 0xc1, 0x4d, 0x28, 0x25, 0x4d, 0xbd, 0xd3, 0x72, 0xc2, 0x5d, 0x0b, 0xf4, 0x98, 0x52, + 0x27, 0xe0, 0x56, 0xaf, 0xa8, 0x63, 0xa0, 0x7a, 0x63, 0xbb, 0xb3, 0x18, 0x64, 0xe4, 0xcb, 0x7a, + 0x3b, 0xf3, 0x66, 0x44, 0x54, 0xc7, 0xd0, 0x48, 0xb6, 0x00, 0xed, 0x1c, 0xa2, 0xec, 0x4b, 0x72, + 0x0a, 0xb0, 0x7e, 0x27, 0x25, 0x77, 0x75, 0xa0, 0x94, 0xaf, 0xb2, 0x97, 0xfc, 0x1d, 0xb2, 0xfb, + 0x4b, 0x3a, 0x09, 0x7f, 0x90, 0x05, 0xa0, 0x1d, 0x5f, 0x89, 0x95, 0xb8, 0x1c, 0x50, 0xa2, 0xfa, + 0xd9, 0xf5, 0x20, 0x44, 0x99, 0x28, 0x17, 0xa2, 0x5c, 0x5e, 0x02, 0x4d, 0xa1, 0xd1, 0xb4, 0xac, + 0x77, 0x45, 0xb8, 0x74, 0xd7, 0xe6, 0xcf, 0x32, 0xee, 0x70, 0x04, 0x05, 0x94, 0x6a, 0x48, 0x8f, + 0xcc, 0x1f, 0xf1, 0xc8, 0x92, 0xee, 0xdd, 0xd1, 0x5b, 0x1b, 0xe6, 0x4c, 0x1d, 0x10, 0xf8, 0xc7, + 0x1e, 0xc9, 0x8d, 0xbb, 0xf2, 0x30, 0x90, 0x05, 0x5c, 0x07, 0x3c, 0x3e, 0x19, 0xf2, 0xcd, 0x3b, + 0xe2, 0x4a, 0xfc, 0xc1, 0xc3, 0x60, 0x64, 0x5c, 0xd0, 0xdf, 0xf7, 0xe3, 0x0d, 0x22, 0xc3, 0x73, + 0x08, 0x29, 0x6c, 0x31, 0xa5, 0x2f, 0x99, 0x7a, 0x5c, 0x62, 0x24, 0xb9, 0xe2, 0xb2, 0x0f, 0xc2, + 0x32, 0x5d, 0x9e, 0x30, 0x9c, 0xe8, 0x31, 0xd4, 0x4c, 0x63, 0xfd, 0x08, 0x1c, 0x79, 0xbe, 0xea, + 0x04, 0xae, 0x31, 0x82, 0x10, 0xd2, 0xf9, 0x05, 0x37, 0x0c, 0x6b, 0xce, 0x1d, 0x5b, 0xdd, 0x0b, + 0xdd, 0x4d, 0x1f, 0x47, 0x08, 0xea, 0xf3, 0xf9, 0xd0, 0x5a, 0xf8, 0xf7, 0x12, 0xdf, 0x6e, 0xe8, + 0x80, 0xc5, 0x12, 0x27, 0x2d, 0x31, 0xa5, 0xa5, 0x32, 0xb0, 0x1c, 0x83, 0x0f, 0x49, 0xec, 0x95, + 0x7a, 0x99, 0x41, 0x97, 0x75, 0x37, 0x64, 0x0c, 0x47, 0x80, 0x7a, 0xe7, 0x13, 0xca, 0xf6, 0x04, + 0x18, 0x5d, 0xc3, 0xb3, 0x00, 0x6a, 0x20, 0x96, 0xfb, 0x54, 0x70, 0xa6, 0x77, 0x65, 0xdd, 0x61, + 0x5e, 0x0e, 0xd8, 0x53, 0x61, 0x57, 0x8b, 0x0e, 0xa3, 0xc0, 0x30, 0x92, 0xa9, 0xff, 0x13, 0xda, + 0xac, 0x5f, 0x38, 0x2c, 0x85, 0xba, 0x79, 0x75, 0x01, 0x65, 0x52, 0xe0, 0x8a, 0x3c, 0x1b, 0xe5, + 0x61, 0x79, 0x22, 0xb9, 0x37, 0x44, 0x29, 0x0d, 0x8a, 0xc3, 0x6c, 0x2f, 0x2c, 0x78, 0xcb, 0xab, + 0x45, 0x84, 0xb5, 0x88, 0x3a, 0x87, 0x8e, 0x1e, 0x83, 0x02, 0x15, 0x2e, 0x8d, 0x4c, 0x95, 0xbd, + 0x38, 0x83, 0xda, 0xa1, 0x9f, 0x6b, 0x41, 0xb4, 0x4a, 0x19, 0x3c, 0x6c, 0xca, 0xd2, 0xc2, 0x89, + 0x80, 0x02, 0xd9, 0x41, 0xf6, 0xdc, 0x63, 0x1d, 0x9b, 0x25, 0x25, 0x8b, 0x45, 0xc2, 0x8f, 0x92, + 0x1f, 0xf0, 0xe5, 0x0b, 0x42, 0xfc, 0x70, 0x0e, 0xf1, 0x73, 0x44, 0xaa, 0x02, 0xb6, 0x46, 0x71, + 0x1d, 0x8a, 0x2c, 0x84, 0xca, 0x48, 0x61, 0x30, 0x44, 0xff, 0x4c, 0xa2, 0x85, 0x98, 0xda, 0x64, + 0x67, 0x3d, 0xef, 0xbe, 0xe4, 0xa8, 0x14, 0x8a, 0xda, 0x63, 0xf3, 0x66, 0x39, 0x07, 0x97, 0xdd, + 0x5f, 0xea, 0x00, 0x52, 0x56, 0x1a, 0xc9, 0xbc, 0x87, 0xf3, 0x93, 0xda, 0x30, 0xaf, 0x82, 0x87, + 0xb0, 0x8e, 0xa5, 0x60, 0x39, 0xf7, 0xd0, 0x15, 0x29, 0xc9, 0x82, 0xe8, 0xe0, 0x95, 0x25, 0x51, + 0x70, 0xa3, 0x67, 0xae, 0x37, 0x62, 0x5d, 0xac, 0x73, 0x7f, 0x77, 0x8d, 0xc7, 0xa1, 0x82, 0xf3, + 0x69, 0xd9, 0x95, 0x93, 0x83, 0x95, 0x37, 0x17, 0x86, 0x13, 0x5b, 0xb2, 0x82, 0x4a, 0x5b, 0xe3, + 0x9b, 0xed, 0x16, 0x9b, 0x8e, 0x25, 0xc8, 0x64, 0xb0, 0x82, 0x16, 0x16, 0xa0, 0x79, 0x7b, 0x0a, + 0xbb, 0xd6, 0xa2, 0x27, 0x6e, 0x07, 0x5b, 0x48, 0x48, 0x49, 0xff, 0x67, 0x47, 0x4c, 0xe9, 0xea, + 0xe0, 0xf8, 0x15, 0x1f, 0x63, 0xaf, 0x41, 0xa6, 0xed, 0xd4, 0x3e, 0x68, 0x2a, 0xdf, 0x1b, 0x8f, + 0x68, 0x16, 0xe2, 0xb7, 0x3a, 0x7e, 0x57, 0x18, 0x81, 0x6b, 0xb6, 0xb3, 0x35, 0x30, 0x55, 0xf3, + 0xf9, 0xc9, 0xe6, 0x58, 0x78, 0xa6, 0xf3, 0x02, 0x3e, 0x3c, 0x35, 0x10, 0x41, 0x2c, 0x6e, 0x2c, + 0x5b, 0xb2, 0x1c, 0x66, 0xbd, 0x1f, 0x4b, 0xd9, 0x87, 0xcd, 0x54, 0x1b, 0x1e, 0x9a, 0x92, 0xdc, + 0x82, 0xae, 0x1c, 0xbc, 0x2f, 0xea, 0x9d, 0x35, 0xdc, 0x3f, 0x9b, 0x18, 0xa2, 0x94, 0x59, 0xd0, + 0xea, 0x80, 0xa6, 0xb0, 0xf7, 0x63, 0x6f, 0x4d, 0x63, 0x03, 0x14, 0x94, 0x00, 0x48, 0x7f, 0xbc, + 0xe0, 0xb3, 0x52, 0xbc, 0x09, 0xa9, 0x33, 0x51, 0x05, 0xf0, 0x2b, 0x6c, 0xad, 0x78, 0xd0, 0xe5, + 0x89, 0xc0, 0x94, 0x49, 0x62, 0xb9, 0x04, 0x71, 0x13, 0xb1, 0x2b, 0x24, 0xcb, 0x6b, 0x2b, 0x71, + 0xde, 0x58, 0x8a, 0x01, 0x4c, 0x95, 0x63, 0xa1, 0x29, 0xcb, 0x66, 0x83, 0x58, 0x5c, 0x9e, 0x04 +}; diff --git a/tests/isotp_fast/conformance_async/CMakeLists.txt b/tests/isotp_fast/conformance_async/CMakeLists.txt new file mode 100644 index 0000000..b2a3aa2 --- /dev/null +++ b/tests/isotp_fast/conformance_async/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(isotp_fast_conformance_async_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/isotp_fast/conformance_async/prj.conf b/tests/isotp_fast/conformance_async/prj.conf new file mode 100644 index 0000000..cf0cfc6 --- /dev/null +++ b/tests/isotp_fast/conformance_async/prj.conf @@ -0,0 +1,9 @@ +CONFIG_CAN=y +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ISOTP=y +CONFIG_ISOTP_FAST=y +CONFIG_ISOTP_USE_TX_BUF=y +CONFIG_ISOTP_RX_BUF_COUNT=8 +CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT=40 +CONFIG_ISOTP_FAST_PER_FRAME_DISPATCH=y \ No newline at end of file diff --git a/tests/isotp_fast/conformance_async/src/async_recv.h b/tests/isotp_fast/conformance_async/src/async_recv.h new file mode 100644 index 0000000..e57a903 --- /dev/null +++ b/tests/isotp_fast/conformance_async/src/async_recv.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../conformance/src/main.h" + +struct recv_msg +{ + uint8_t data[CAN_MAX_DLEN]; + int16_t len; + int rem_len; +}; + +K_MSGQ_DEFINE(recv_msgq, sizeof(struct recv_msg), DIV_ROUND_UP(DATA_SEND_LENGTH, DATA_SIZE_CF), 2); +int8_t recv_last_error; + +static int blocking_recv(uint8_t *buf, size_t size, k_timeout_t timeout) +{ + int ret; + struct recv_msg msg; + int rx_len = 0; + while ((ret = k_msgq_get(&recv_msgq, &msg, timeout)) == 0) { + if (recv_last_error != 0) { + ret = recv_last_error; + recv_last_error = 0; + return ret; + } + if (msg.len < 0) { + /* an error has occurred */ + // printk("Error %d occurred", msg.len); + return msg.len; + } + int cp_len = MIN(msg.len, size - rx_len); + memcpy(buf, &msg.data, cp_len); + rx_len += cp_len; + buf += cp_len; + if (msg.rem_len > (size - rx_len)) { + /* recv buffer will probably overflow on next call; hand back to user code */ + break; + } + if (msg.rem_len == 0) { + /* msg is complete */ + break; + } + } + if (recv_last_error != 0) { + ret = recv_last_error; + recv_last_error = 0; + return ret; + } + if (ret == -EAGAIN) { + return ISOTP_RECV_TIMEOUT; + } + return rx_len; +} + +void isotp_fast_recv_handler(struct net_buf *buffer, int rem_len, isotp_fast_msg_id sender_addr, + void *arg) +{ + struct recv_msg msg = { + .len = buffer->len, + .rem_len = rem_len, + }; + memcpy(&msg.data, buffer->data, MIN(sizeof(msg.data), buffer->len)); + // printk("< [%x] [%02d] ", sender_addr, buffer->len); + // print_hex(&msg.data[0], msg.len); + // printk("[%d]\n", rem_len); + k_msgq_put(&recv_msgq, &msg, K_NO_WAIT); +} + +void isotp_fast_recv_error_handler(int8_t error, isotp_fast_msg_id sender_addr, void *arg) +{ + // printk("Error %d received\n", error); + recv_last_error = error; + k_msgq_purge(&recv_msgq); +} \ No newline at end of file diff --git a/tests/isotp_fast/conformance_async/src/main.c b/tests/isotp_fast/conformance_async/src/main.c new file mode 100644 index 0000000..a51a295 --- /dev/null +++ b/tests/isotp_fast/conformance_async/src/main.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define ISOTP_FAST_CONFORMANCE_TEST_SUITE isotp_fast_conformance_async +#include "async_recv.h" + +#include "../../conformance/src/main.c" \ No newline at end of file diff --git a/tests/isotp_fast/conformance_async/testcase.yaml b/tests/isotp_fast/conformance_async/testcase.yaml new file mode 100644 index 0000000..a369e96 --- /dev/null +++ b/tests/isotp_fast/conformance_async/testcase.yaml @@ -0,0 +1,9 @@ +tests: + thingset_sdk.isotp_fast.conformance.async: + tags: + - can + - isotp + depends_on: can + integration_platforms: + - native_posix_64 + filter: dt_chosen_enabled("zephyr,canbus") and not dt_compat_enabled("kvaser,pcican") diff --git a/tests/isotp_fast/conformance_async_fd/CMakeLists.txt b/tests/isotp_fast/conformance_async_fd/CMakeLists.txt new file mode 100644 index 0000000..d039ddd --- /dev/null +++ b/tests/isotp_fast/conformance_async_fd/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(isotp_fast_conformance_async_fd_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/isotp_fast/conformance_async_fd/prj.conf b/tests/isotp_fast/conformance_async_fd/prj.conf new file mode 100644 index 0000000..0af8daf --- /dev/null +++ b/tests/isotp_fast/conformance_async_fd/prj.conf @@ -0,0 +1,10 @@ +CONFIG_CAN=y +CONFIG_CAN_FD_MODE=y +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ISOTP=y +CONFIG_ISOTP_FAST=y +CONFIG_ISOTP_USE_TX_BUF=y +CONFIG_ISOTP_RX_BUF_COUNT=8 +CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT=5 +CONFIG_ISOTP_FAST_PER_FRAME_DISPATCH=y \ No newline at end of file diff --git a/tests/isotp_fast/conformance_async_fd/src/main.c b/tests/isotp_fast/conformance_async_fd/src/main.c new file mode 100644 index 0000000..9c079c4 --- /dev/null +++ b/tests/isotp_fast/conformance_async_fd/src/main.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define ISOTP_FAST_CONFORMANCE_TEST_SUITE isotp_fast_conformance_async_fd +#include "../../conformance_async/src/async_recv.h" + +#include "../../conformance/src/main.c" + +ZTEST(ISOTP_FAST_CONFORMANCE_TEST_SUITE, test_sf_length) +{ + int ret; + struct frame_desired des_frame; + + des_frame.data[0] = (SF_PCI_TYPE << PCI_TYPE_POS) | 7; + memcpy(&des_frame.data[1], random_data, 7); + des_frame.length = 8; + + /* mask to allow any priority and source address (SA) */ + filter_id = add_rx_msgq(tx_addr, CAN_EXT_ID_MASK); + zassert_true((filter_id >= 0), "Negative filter number [%d]", filter_id); + + ret = isotp_fast_send(&ctx, random_data, 7, rx_node_id, INT_TO_POINTER(ISOTP_N_OK)); + zassert_equal(ret, 0, "Send returned %d", ret); + + check_frame_series(&des_frame, 1, &frame_msgq); + + des_frame.data[0] = (SF_PCI_TYPE << PCI_TYPE_POS); + des_frame.data[1] = 9; + memcpy(&des_frame.data[2], random_data, DATA_SIZE_SF); + des_frame.length = 9; + + ret = isotp_fast_send(&ctx, random_data, 9, rx_node_id, INT_TO_POINTER(ISOTP_N_OK)); + zassert_equal(ret, 0, "Send returned %d", ret); + + check_frame_series(&des_frame, 1, &frame_msgq); +} \ No newline at end of file diff --git a/tests/isotp_fast/conformance_async_fd/testcase.yaml b/tests/isotp_fast/conformance_async_fd/testcase.yaml new file mode 100644 index 0000000..fc9f716 --- /dev/null +++ b/tests/isotp_fast/conformance_async_fd/testcase.yaml @@ -0,0 +1,9 @@ +tests: + thingset_sdk.isotp_fast.conformance.async.fd: + tags: + - can + - isotp + depends_on: can + integration_platforms: + - native_posix_64 + filter: dt_chosen_enabled("zephyr,canbus") and not dt_compat_enabled("kvaser,pcican") diff --git a/tests/isotp_fast/conformance_sync/CMakeLists.txt b/tests/isotp_fast/conformance_sync/CMakeLists.txt new file mode 100644 index 0000000..213f6a2 --- /dev/null +++ b/tests/isotp_fast/conformance_sync/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(isotp_fast_conformance_sync_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/isotp_fast/conformance_sync/prj.conf b/tests/isotp_fast/conformance_sync/prj.conf new file mode 100644 index 0000000..20d8cb8 --- /dev/null +++ b/tests/isotp_fast/conformance_sync/prj.conf @@ -0,0 +1,9 @@ +CONFIG_CAN=y +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ISOTP=y +CONFIG_ISOTP_FAST=y +CONFIG_ISOTP_USE_TX_BUF=y +CONFIG_ISOTP_RX_BUF_COUNT=8 +CONFIG_ISOTP_FAST_RX_MAX_PACKET_COUNT=40 +CONFIG_ISOTP_FAST_BLOCKING_RECEIVE=y \ No newline at end of file diff --git a/tests/isotp_fast/conformance_sync/src/main.c b/tests/isotp_fast/conformance_sync/src/main.c new file mode 100644 index 0000000..d471602 --- /dev/null +++ b/tests/isotp_fast/conformance_sync/src/main.c @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define ISOTP_FAST_CONFORMANCE_TEST_SUITE isotp_fast_conformance_sync +#include "sync_recv.h" + +#include "../../conformance/src/main.c" \ No newline at end of file diff --git a/tests/isotp_fast/conformance_sync/src/sync_recv.h b/tests/isotp_fast/conformance_sync/src/sync_recv.h new file mode 100644 index 0000000..bd2420f --- /dev/null +++ b/tests/isotp_fast/conformance_sync/src/sync_recv.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Brill Power + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "../../conformance/src/main.h" + +static int blocking_recv(uint8_t *buf, size_t size, k_timeout_t timeout) +{ + struct can_filter sender = { + .id = 0, + .mask = 0, + }; + return isotp_fast_recv(&ctx, sender, buf, size, timeout); +} + +void isotp_fast_recv_handler(struct net_buf *buffer, int rem_len, isotp_fast_msg_id sender_addr, + void *arg) +{} + +void isotp_fast_recv_error_handler(int8_t error, isotp_fast_msg_id sender_addr, void *arg) +{} \ No newline at end of file diff --git a/tests/isotp_fast/conformance_sync/testcase.yaml b/tests/isotp_fast/conformance_sync/testcase.yaml new file mode 100644 index 0000000..798337f --- /dev/null +++ b/tests/isotp_fast/conformance_sync/testcase.yaml @@ -0,0 +1,9 @@ +tests: + thingset_sdk.isotp_fast.conformance.sync: + tags: + - can + - isotp + depends_on: can + integration_platforms: + - native_posix_64 + filter: dt_chosen_enabled("zephyr,canbus") and not dt_compat_enabled("kvaser,pcican") diff --git a/tests/thingset_isotp_fast/CMakeLists.txt b/tests/thingset_isotp_fast/CMakeLists.txt new file mode 100644 index 0000000..a1d1e65 --- /dev/null +++ b/tests/thingset_isotp_fast/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(thingset_sdk_isotp_fast_test) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/thingset_isotp_fast/prj.conf b/tests/thingset_isotp_fast/prj.conf new file mode 100644 index 0000000..5c5b399 --- /dev/null +++ b/tests/thingset_isotp_fast/prj.conf @@ -0,0 +1,19 @@ +# Copyright (c) The ThingSet Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_CAN=y +CONFIG_ISOTP=y +CONFIG_ENTROPY_GENERATOR=y + +CONFIG_THINGSET=y +CONFIG_THINGSET_SDK=y +CONFIG_THINGSET_CAN=y +CONFIG_ISOTP_FAST=y +CONFIG_ISOTP_USE_TX_BUF=y + +CONFIG_ZTEST=y +CONFIG_ZTEST_NEW_API=y +CONFIG_ZTEST_SUMMARY=n + +# enable click-able absolute paths in assert messages +#CONFIG_BUILD_OUTPUT_STRIP_PATHS=n diff --git a/tests/thingset_isotp_fast/src/main.c b/tests/thingset_isotp_fast/src/main.c new file mode 100644 index 0000000..15949e3 --- /dev/null +++ b/tests/thingset_isotp_fast/src/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) The ThingSet Project Contributors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#define TEST_RECEIVE_TIMEOUT K_MSEC(100) + +static struct thingset_context ts; + +static const struct device *can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)); + +static struct k_sem report_rx_sem; +static struct k_sem request_tx_sem; + +static void report_rx_callback(uint16_t data_id, const uint8_t *value, size_t value_len, + uint8_t source_addr) +{ + k_sem_give(&report_rx_sem); +} + +ZTEST(thingset_isotp_fast, test_receive_report_from_node) +{ + struct can_frame report_frame = { + .id = 0x1E000002, /* node with address 0x02 */ + .flags = CAN_FRAME_IDE, + .data = { 0xF6 }, + .dlc = 1, + }; + int err; + + k_sem_reset(&report_rx_sem); + + err = can_send(can_dev, &report_frame, K_MSEC(10), NULL, NULL); + zassert_equal(err, 0, "can_send failed: %d", err); + + err = k_sem_take(&report_rx_sem, TEST_RECEIVE_TIMEOUT); + zassert_equal(err, 0, "receive timeout"); +} + +static void request_rx_cb(const struct device *dev, struct can_frame *frame, void *user_data) +{ + k_sem_give(&request_tx_sem); +} + +ZTEST(thingset_isotp_fast, test_send_request_to_node) +{ + struct can_filter other_node_filter = { + .id = 0x1800CC00, + .mask = 0x1F00FF00, + .flags = CAN_FILTER_DATA | CAN_FILTER_IDE, + }; + uint8_t req_buf[] = { 0x01, 0x00 }; /* simple single-frame request via ISO-TP */ + int err; + + k_sem_reset(&request_tx_sem); + + err = can_add_rx_filter(can_dev, &request_rx_cb, NULL, &other_node_filter); + zassert_false(err < 0, "adding rx filter failed: %d", err); + + thingset_can_send(req_buf, sizeof(req_buf), 0xCC, NULL, NULL, TEST_RECEIVE_TIMEOUT); + + err = k_sem_take(&request_tx_sem, TEST_RECEIVE_TIMEOUT); + zassert_equal(err, 0, "receive timeout"); +} + +static void *thingset_isotp_fast_setup(void) +{ + int err; + + k_sem_init(&report_rx_sem, 0, 1); + k_sem_init(&request_tx_sem, 0, 1); + + thingset_init_global(&ts); + + zassert_true(device_is_ready(can_dev), "CAN device not ready"); + + (void)can_stop(can_dev); + + err = can_set_mode(can_dev, CAN_MODE_LOOPBACK); + zassert_equal(err, 0, "failed to set loopback mode (err %d)", err); + + err = can_start(can_dev); + zassert_equal(err, 0, "failed to start CAN controller (err %d)", err); + + /* wait for address claiming to finish */ + k_sleep(K_MSEC(1000)); + + thingset_can_set_report_rx_callback(report_rx_callback); + + return NULL; +} + +ZTEST_SUITE(thingset_isotp_fast, NULL, thingset_isotp_fast_setup, NULL, NULL, NULL); diff --git a/tests/thingset_isotp_fast/testcase.yaml b/tests/thingset_isotp_fast/testcase.yaml new file mode 100644 index 0000000..666392b --- /dev/null +++ b/tests/thingset_isotp_fast/testcase.yaml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +tests: + thingset_sdk.thingset_isotp_fast: + integration_platforms: + - native_posix_64 + extra_args: EXTRA_CFLAGS=-Wno-error diff --git a/west.yml b/west.yml index 28cf6e0..c8a39fb 100644 --- a/west.yml +++ b/west.yml @@ -27,6 +27,6 @@ manifest: - picolibc - name: thingset-node-c remote: thingset - revision: 04b25a4719df456c03670386ca4dde1bcca9a9e5 + revision: ef65aafb39c7df74da8c7b9b4079fb28bf4f19e3 path: modules/thingset-node-c import: true