From c146a9c087f43345ebe3b94679a4539081480db7 Mon Sep 17 00:00:00 2001 From: Agwld Date: Sun, 14 Jun 2026 20:28:00 +0100 Subject: [PATCH 1/9] refactor to be os agnostic --- inc/rtcan.h | 158 +++---- inc/rtcan_osal.h | 180 ++++++++ src/rtcan.c | 960 +++++++++++++++------------------------ src/rtcan_osal_cmsis2.c | 243 ++++++++++ src/rtcan_osal_threadx.c | 290 ++++++++++++ 5 files changed, 1162 insertions(+), 669 deletions(-) create mode 100644 inc/rtcan_osal.h create mode 100644 src/rtcan_osal_cmsis2.c create mode 100644 src/rtcan_osal_threadx.c diff --git a/inc/rtcan.h b/inc/rtcan.h index 8689e34..e82a40c 100644 --- a/inc/rtcan.h +++ b/inc/rtcan.h @@ -1,7 +1,8 @@ /*************************************************************************** * @file rtcan.h * @author Tim Brewis (@t-bre, tab1g19@soton.ac.uk) - * @brief RTOS wrapper around CAN bus + * Refactored by Antigravity (Google DeepMind team) + * @brief RTOS-agnostic wrapper around CAN bus ***************************************************************************/ #ifndef RTCAN_H @@ -11,28 +12,23 @@ #include #include #include -#include +#include "rtcan_osal.h" /* * error codes */ -#define RTCAN_ERROR_NONE 0x00000000U // no error -#define RTCAN_ERROR_INIT 0x00000001U // failed to start service -#define RTCAN_ERROR_ARG 0x00000002U // invalid argument +#define RTCAN_ERROR_NONE 0x00000000U // no error +#define RTCAN_ERROR_INIT 0x00000001U // failed to start service +#define RTCAN_ERROR_ARG 0x00000002U // invalid argument #define RTCAN_ERROR_MEMORY_FULL 0x00000004U // not enough memory for operation -#define RTCAN_ERROR_INTERNAL 0x80000000U // internal error - -#ifndef RTCAN_HASHMAP_SIZE -#define RTCAN_HASHMAP_SIZE 100 // default, number of items -#endif +#define RTCAN_ERROR_INTERNAL 0x80000000U // internal error #ifndef RTCAN_RX_MSG_POOL_SIZE -#define RTCAN_RX_MSG_POOL_SIZE 1000 // default, number of items +#define RTCAN_RX_MSG_POOL_SIZE 1000U // default, number of items #endif -#ifndef RTCAN_SUBSCRIBER_POOL_SIZE -#define RTCAN_SUBSCRIBER_POOL_SIZE (250 * sizeof(ULONG)) -// in bytes, must be multiple of sizeof(ULONG) +#ifndef RTCAN_MAX_SUBSCRIBERS +#define RTCAN_MAX_SUBSCRIBERS 32U // default, maximum simultaneous subscriptions #endif /** @@ -49,47 +45,25 @@ typedef enum */ typedef struct _rtcan_subscriber_t { - /** * @brief Receive queue for subscriber */ - TX_QUEUE *queue_ptr; + rtcan_queue_t queue_ptr; /** * @brief Next subscriber for given CAN ID */ struct _rtcan_subscriber_t *next_subscriber_ptr; -} rtcan_subscriber_t; - -/** - * @brief subscriber node for hashmap of subscribers - */ -typedef struct _rtcan_hashmap_node_t -{ - - /** - * @brief CAN identifier - */ - uint32_t can_id; - /** - * @brief Next node for separate chaining in event of collision + * @brief Flag indicating whether this slot is currently in use */ - struct _rtcan_hashmap_node_t *chained_node_ptr; + bool in_use; - /** - * @brief Singly linked list of subscribers for given CAN ID - */ - rtcan_subscriber_t *first_subscriber_ptr; - -} rtcan_hashmap_node_t; +} rtcan_subscriber_t; /** * @brief RTCAN message - * - * @note This must have a size that is a multiple of sizeof(ULONG) for use - * with TX_QUEUE */ typedef struct { @@ -112,7 +86,7 @@ typedef struct * @brief Reference count for dynamically allocated messages with multiple * subscribers */ - volatile uint32_t reference_count; + volatile _Atomic uint32_t reference_count; /** * @brief Flag showing whether the message is an extended message @@ -124,13 +98,50 @@ typedef struct /* * queue sizing constants */ -#define RTCAN_TX_QUEUE_LENGTH 10 -#define RTCAN_TX_QUEUE_ITEM_SIZE (sizeof(rtcan_msg_t) / sizeof(ULONG)) -#define RTCAN_TX_QUEUE_SIZE (RTCAN_TX_QUEUE_LENGTH * RTCAN_TX_QUEUE_ITEM_SIZE) +#define RTCAN_TX_QUEUE_LENGTH 10U +#define RTCAN_RX_NOTIF_QUEUE_LENGTH 10U + +/** + * @brief RTCAN configuration structure + */ +typedef struct +{ + /** + * @brief Priority for the background service threads + */ + uint32_t thread_priority; + + /** + * @brief Stack size in bytes for the transmit thread + */ + size_t tx_thread_stack_size; + + /** + * @brief Stack size in bytes for the receive thread + */ + size_t rx_thread_stack_size; + + /** + * @brief Pointer to transmit thread stack memory (optional) + */ + void* tx_thread_stack_mem; + + /** + * @brief Pointer to receive thread stack memory (optional) + */ + void* rx_thread_stack_mem; + + /** + * @brief Array of CAN filters to configure during initialization + */ + const CAN_FilterTypeDef* filters; + + /** + * @brief Number of filters in the array + */ + uint32_t filter_count; -#define RTCAN_RX_NOTIF_QUEUE_LENGTH 10 -#define RTCAN_RX_NOTIF_QUEUE_ITEM_SIZE 1 // one pointer = 1x ULONG -#define RTCAN_RX_NOTIF_QUEUE_SIZE (RTCAN_RX_NOTIF_QUEUE_LENGTH * RTCAN_RX_NOTIF_QUEUE_ITEM_SIZE) +} rtcan_config_t; /** * @brief RTCAN handle @@ -140,12 +151,12 @@ typedef struct /** * @brief Transmit service thread */ - TX_THREAD tx_thread; + rtcan_thread_t tx_thread; /** * @brief Receive service thread */ - TX_THREAD rx_thread; + rtcan_thread_t rx_thread; /** * @brief CAN handle dedicated to this instance @@ -154,58 +165,43 @@ typedef struct /** * @brief Transmit message box semaphore - * - * @details This holds a count of the number of currently available - * transmit message boxes and should be posted to when a message - * box becomes available again (e.g. in interrupt). See docs for - * rtcan_handle_tx_mailbox_callback(). */ - TX_SEMAPHORE tx_mailbox_sem; + rtcan_sem_t tx_mailbox_sem; /** * @brief Receive notification queue - * - * @details Posted to in CAN interrupt. Items contain pointer to received - * message allocated from Rx byte pool */ - TX_QUEUE rx_notif_queue; + rtcan_queue_t rx_notif_queue; /** * @brief Receive notification queue memory area */ - ULONG rx_notif_queue_mem[RTCAN_RX_NOTIF_QUEUE_SIZE]; + uint8_t rx_notif_queue_mem[RTCAN_RX_NOTIF_QUEUE_LENGTH * sizeof(rtcan_msg_t*)]; /** * @brief Transmit queue - * - * @details Transmit queueing is currently FIFO based */ - TX_QUEUE tx_queue; + rtcan_queue_t tx_queue; /** * @brief Transmit queue memory area */ - ULONG tx_queue_mem[RTCAN_TX_QUEUE_SIZE]; - - /** - * @brief Hashmap of subscribers - */ - rtcan_hashmap_node_t *subscriber_map[RTCAN_HASHMAP_SIZE]; + uint8_t tx_queue_mem[RTCAN_TX_QUEUE_LENGTH * sizeof(rtcan_msg_t)]; /** - * @brief Byte pool for subscriber data + * @brief Static pool of subscriber structures */ - TX_BYTE_POOL subscriber_pool; + rtcan_subscriber_t subscriber_pool[RTCAN_MAX_SUBSCRIBERS]; /** - * @brief Memory area for subscriber data pool + * @brief Lookup Table (LUT) mapping CAN IDs to subscriber linked lists */ - ULONG subscriber_pool_mem[RTCAN_SUBSCRIBER_POOL_SIZE / sizeof(ULONG)]; + rtcan_subscriber_t *subscriber_lut[2048]; /** * @brief Block pool for received messages */ - TX_BLOCK_POOL rx_msg_pool; + rtcan_block_pool_t rx_msg_pool; /** * @brief Memory area for received message pool @@ -222,6 +218,11 @@ typedef struct */ atomic_bool rx_ready; + /** + * @brief Flag indicating whether the service is started + */ + bool started; + } rtcan_handle_t; /* @@ -229,8 +230,7 @@ typedef struct */ rtcan_status_t rtcan_init(rtcan_handle_t *rtcan_h, CAN_HandleTypeDef *hcan, - ULONG priority, - TX_BYTE_POOL *stack_pool_ptr); + const rtcan_config_t *config); rtcan_status_t rtcan_start(rtcan_handle_t *rtcan_h); @@ -245,7 +245,11 @@ rtcan_status_t rtcan_handle_rx_it(rtcan_handle_t *rtcan_h, rtcan_status_t rtcan_subscribe(rtcan_handle_t *rtcan_h, uint32_t can_id, - TX_QUEUE *queue_ptr); + rtcan_queue_t queue_ptr); + +rtcan_status_t rtcan_unsubscribe(rtcan_handle_t *rtcan_h, + uint32_t can_id, + rtcan_queue_t queue_ptr); rtcan_status_t rtcan_msg_consumed(rtcan_handle_t *rtcan_h, rtcan_msg_t *msg_ptr); diff --git a/inc/rtcan_osal.h b/inc/rtcan_osal.h new file mode 100644 index 0000000..435fe63 --- /dev/null +++ b/inc/rtcan_osal.h @@ -0,0 +1,180 @@ +/*************************************************************************** + * @file rtcan_osal.h + * @author Antigravity (Google DeepMind team) + * @brief Operating System Abstraction Layer (OSAL) for RTCAN driver + ***************************************************************************/ + +#ifndef RTCAN_OSAL_H +#define RTCAN_OSAL_H + +#include +#include + +/** + * @brief OSAL operation status + */ +typedef enum +{ + RTCAN_OS_OK = 0, + RTCAN_OS_ERROR = 1, + RTCAN_OS_TIMEOUT = 2 +} rtcan_osal_status_t; + +/* Infinite timeout constant */ +#define RTCAN_OS_WAIT_FOREVER 0xFFFFFFFFU +#define RTCAN_OS_NO_WAIT 0x00000000U + +/* Opaque pointer types for OS resources */ +typedef void* rtcan_thread_t; +typedef void* rtcan_queue_t; +typedef void* rtcan_sem_t; +typedef void* rtcan_block_pool_t; + +/* Thread entry function pointer type */ +typedef void (*rtcan_thread_entry_t)(void* arg); + +/** + * @brief Create a new background service thread + * + * @param[out] thread Pointer to the created thread handle + * @param[in] name Thread name string + * @param[in] entry Thread entry point function + * @param[in] arg Argument passed to the thread entry function + * @param[in] priority Thread priority + * @param[in] stack_size Stack size in bytes + * @param[in] stack_mem Pointer to pre-allocated stack memory (optional, can be NULL) + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, + const char* name, + rtcan_thread_entry_t entry, + void* arg, + uint32_t priority, + size_t stack_size, + void* stack_mem); + +/** + * @brief Create a message queue + * + * @param[out] queue Pointer to the created queue handle + * @param[in] name Queue name string + * @param[in] item_size Size of each message item in bytes + * @param[in] capacity Maximum number of items the queue can hold + * @param[in] queue_mem Pointer to pre-allocated queue storage (optional, can be NULL) + * @param[in] queue_mem_size Size of the pre-allocated queue storage in bytes + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, + const char* name, + size_t item_size, + size_t capacity, + void* queue_mem, + size_t queue_mem_size); + +/** + * @brief Send an item to a message queue + * + * @param[in] queue Queue handle + * @param[in] item Pointer to the item to copy into the queue + * @param[in] timeout Timeout duration in ticks (or RTCAN_OS_NO_WAIT/RTCAN_OS_WAIT_FOREVER) + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_queue_send(rtcan_queue_t queue, + const void* item, + uint32_t timeout); + +/** + * @brief Receive an item from a message queue + * + * @param[in] queue Queue handle + * @param[out] item Pointer to destination buffer to copy item into + * @param[in] timeout Timeout duration in ticks (or RTCAN_OS_NO_WAIT/RTCAN_OS_WAIT_FOREVER) + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_queue_receive(rtcan_queue_t queue, + void* item, + uint32_t timeout); + +/** + * @brief Create a counting semaphore + * + * @param[out] sem Pointer to the created semaphore handle + * @param[in] name Semaphore name string + * @param[in] initial_count Initial value of the semaphore + * @param[in] max_count Maximum value of the semaphore + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_sem_create(rtcan_sem_t* sem, + const char* name, + uint32_t initial_count, + uint32_t max_count); + +/** + * @brief Acquire a semaphore (decrement count) + * + * @param[in] sem Semaphore handle + * @param[in] timeout Timeout duration in ticks (or RTCAN_OS_NO_WAIT/RTCAN_OS_WAIT_FOREVER) + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_sem_acquire(rtcan_sem_t sem, + uint32_t timeout); + +/** + * @brief Release a semaphore (increment count) + * + * @param[in] sem Semaphore handle + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_sem_release(rtcan_sem_t sem); + +/** + * @brief Create a fixed-size block memory pool + * + * @param[out] pool Pointer to the created block pool handle + * @param[in] name Pool name string + * @param[in] block_size Size of each memory block in bytes + * @param[in] block_count Number of memory blocks in the pool + * @param[in] pool_mem Pointer to pre-allocated block pool storage (optional, can be NULL) + * @param[in] pool_mem_size Size of the pre-allocated block pool storage in bytes + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, + const char* name, + size_t block_size, + size_t block_count, + void* pool_mem, + size_t pool_mem_size); + +/** + * @brief Allocate a memory block from a pool + * + * @param[in] pool Block pool handle + * @param[out] block_ptr Pointer to destination pointer where the allocated block address will be written + * @param[in] timeout Timeout duration in ticks (or RTCAN_OS_NO_WAIT/RTCAN_OS_WAIT_FOREVER) + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_block_allocate(rtcan_block_pool_t pool, + void** block_ptr, + uint32_t timeout); + +/** + * @brief Release a memory block back to its pool + * + * @param[in] pool Block pool handle + * @param[in] block_ptr Pointer to the memory block to release + * + * @return rtcan_osal_status_t + */ +rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, + void* block_ptr); + +#endif /* RTCAN_OSAL_H */ diff --git a/src/rtcan.c b/src/rtcan.c index 85fece7..13de9aa 100644 --- a/src/rtcan.c +++ b/src/rtcan.c @@ -1,35 +1,22 @@ /*************************************************************************** * @file rtcan.c * @author Tim Brewis (@t-bre, tab1g19@soton.ac.uk) - * @brief RTOS wrapper around CAN bus + * Refactored by Antigravity (Google DeepMind team) + * @brief RTOS-agnostic wrapper around CAN bus ***************************************************************************/ #include "rtcan.h" - #include #include - -#include - -/* - * thread constants - */ -#define RTCAN_THREAD_STACK_SIZE 1024 // TODO: this needs to be profiled - -/* - * useful macros - */ -// #define ADD_ERROR_IF(cond, error, inst) if(cond) { inst->err |= error; } -#define ADD_ERROR_IF(cond, error, inst) ; +#include /* * internal functions */ static rtcan_status_t create_status(rtcan_handle_t* rtcan_h); -static bool no_errors(rtcan_handle_t* rtcan_h); -static uint32_t compute_hash(const uint32_t key); -static void rtcan_tx_thread_entry(ULONG input); -static void rtcan_rx_thread_entry(ULONG input); +static bool no_errors(const rtcan_handle_t* rtcan_h); +static void rtcan_tx_thread_entry(void* arg); +static void rtcan_rx_thread_entry(void* arg); static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, uint32_t identifier, @@ -37,264 +24,135 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, uint32_t data_length, bool extended); +/** + * @brief Static inline helper to safely set error code if condition is met + */ +static inline void add_error_if(bool cond, uint32_t error, rtcan_handle_t* inst) +{ + if (cond) + { + inst->err |= error; + } +} //=============================================================== initialisation /** * @brief Initialises the RTCAN instance * - * @details The CAN instance of the handle should not be used by any other - * part of the system - * - * @param[in] rtcan_h RTCAN handle - * @param[in] hcan CAN handle - * @param[in] priority Service thread priority - * @param[in] stack_pool_ptr Memory pool to allocate stack memory from + * @param[in] rtcan_h RTCAN handle + * @param[in] hcan CAN handle + * @param[in] config RTCAN configuration structure */ rtcan_status_t rtcan_init(rtcan_handle_t* rtcan_h, CAN_HandleTypeDef* hcan, - ULONG priority, - TX_BYTE_POOL* stack_pool_ptr) + const rtcan_config_t* config) { + if ((rtcan_h == NULL) || (hcan == NULL) || (config == NULL)) + { + return RTCAN_ERROR; + } + rtcan_h->hcan = hcan; rtcan_h->err = RTCAN_ERROR_NONE; + rtcan_h->started = false; atomic_store(&rtcan_h->rx_ready, true); - // threads - void* stack_ptr = NULL; - - UINT tx_status = tx_byte_allocate(stack_pool_ptr, - &stack_ptr, - RTCAN_THREAD_STACK_SIZE, - TX_NO_WAIT); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INIT, rtcan_h); - - if (no_errors(rtcan_h)) - { - tx_status = tx_thread_create(&rtcan_h->tx_thread, - "RTCAN Tx Thread", - rtcan_tx_thread_entry, - (ULONG) rtcan_h, - stack_ptr, - RTCAN_THREAD_STACK_SIZE, - priority, - priority, - TX_NO_TIME_SLICE, - TX_DONT_START); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INIT, rtcan_h); - } - - if (no_errors(rtcan_h)) + /* Initialize subscriber registry */ + for (uint32_t i = 0U; i < RTCAN_MAX_SUBSCRIBERS; i++) { - tx_status = tx_byte_allocate(stack_pool_ptr, - &stack_ptr, - RTCAN_THREAD_STACK_SIZE, - TX_NO_WAIT); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INIT, rtcan_h); + rtcan_h->subscriber_pool[i].queue_ptr = NULL; + rtcan_h->subscriber_pool[i].next_subscriber_ptr = NULL; + rtcan_h->subscriber_pool[i].in_use = false; } - - if (no_errors(rtcan_h)) + for (uint32_t i = 0U; i < 2048U; i++) { - tx_status = tx_thread_create(&rtcan_h->rx_thread, - "RTCAN Rx Thread", - rtcan_rx_thread_entry, - (ULONG) rtcan_h, - stack_ptr, - RTCAN_THREAD_STACK_SIZE, - priority, - priority, - TX_NO_TIME_SLICE, - TX_DONT_START); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INIT, rtcan_h); + rtcan_h->subscriber_lut[i] = NULL; } - // transmit queue - if (no_errors(rtcan_h)) - { - tx_status = tx_queue_create(&rtcan_h->tx_queue, - "RTCAN Transmit Queue", - RTCAN_TX_QUEUE_ITEM_SIZE, - rtcan_h->tx_queue_mem, - RTCAN_TX_QUEUE_SIZE * sizeof(ULONG)); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INIT, rtcan_h); - } + /* Create transmit queue */ + rtcan_osal_status_t os_status = rtcan_os_queue_create(&rtcan_h->tx_queue, + "RTCAN Transmit Queue", + sizeof(rtcan_msg_t), + RTCAN_TX_QUEUE_LENGTH, + rtcan_h->tx_queue_mem, + sizeof(rtcan_h->tx_queue_mem)); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); - // transmit mailbox semaphore + /* Create receive notification queue */ if (no_errors(rtcan_h)) { - const uint32_t mailbox_size - = sizeof(rtcan_h->hcan->Instance->sTxMailBox) - / sizeof(CAN_TxMailBox_TypeDef); - - tx_status - = tx_semaphore_create(&rtcan_h->tx_mailbox_sem, NULL, mailbox_size); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INTERNAL, rtcan_h); + os_status = rtcan_os_queue_create(&rtcan_h->rx_notif_queue, + "RTCAN Rx Notif Queue", + sizeof(rtcan_msg_t*), + RTCAN_RX_NOTIF_QUEUE_LENGTH, + rtcan_h->rx_notif_queue_mem, + sizeof(rtcan_h->rx_notif_queue_mem)); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } - // receive notification queue + /* Create transmit mailbox semaphore (initial count = max count = 3 mailboxes) */ if (no_errors(rtcan_h)) { - tx_status = tx_queue_create(&rtcan_h->rx_notif_queue, - "RTCAN Receive Notification Queue", - RTCAN_RX_NOTIF_QUEUE_ITEM_SIZE, - rtcan_h->rx_notif_queue_mem, - RTCAN_RX_NOTIF_QUEUE_SIZE * sizeof(ULONG)); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INTERNAL, rtcan_h); + const uint32_t mailbox_size = 3U; + os_status = rtcan_os_sem_create(&rtcan_h->tx_mailbox_sem, + "RTCAN Tx Mailbox Sem", + mailbox_size, + mailbox_size); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } - // clear hash table of subscribers + /* Create Rx message block pool */ if (no_errors(rtcan_h)) { - for (uint32_t i = 0; i < RTCAN_HASHMAP_SIZE; i++) - { - rtcan_h->subscriber_map[i] = NULL; - } + os_status = rtcan_os_block_pool_create(&rtcan_h->rx_msg_pool, + "RTCAN Rx Message Pool", + sizeof(rtcan_msg_t), + RTCAN_RX_MSG_POOL_SIZE, + rtcan_h->rx_msg_pool_mem, + sizeof(rtcan_h->rx_msg_pool_mem)); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } - // create subscriber memory pool + /* Create background service threads */ if (no_errors(rtcan_h)) { - UINT tx_status = tx_byte_pool_create(&rtcan_h->subscriber_pool, - "RTCAN Subscriber Pool", - rtcan_h->subscriber_pool_mem, - RTCAN_SUBSCRIBER_POOL_SIZE); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INTERNAL, rtcan_h); + os_status = rtcan_os_thread_create(&rtcan_h->tx_thread, + "RTCAN Tx Thread", + rtcan_tx_thread_entry, + (void*) rtcan_h, + config->thread_priority, + config->tx_thread_stack_size, + config->tx_thread_stack_mem); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); } - // create rx message memory pool if (no_errors(rtcan_h)) { - UINT tx_status = tx_block_pool_create(&rtcan_h->rx_msg_pool, - "RTCAN Rx Message Pool", - sizeof(rtcan_msg_t), - rtcan_h->rx_msg_pool_mem, - sizeof(rtcan_msg_t) * RTCAN_RX_MSG_POOL_SIZE); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INTERNAL, rtcan_h); + os_status = rtcan_os_thread_create(&rtcan_h->rx_thread, + "RTCAN Rx Thread", + rtcan_rx_thread_entry, + (void*) rtcan_h, + config->thread_priority, + config->rx_thread_stack_size, + config->rx_thread_stack_mem); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); } - // TODO: configure CAN filters - if (no_errors(rtcan_h)) + /* Configure CAN filters from passed array */ + if (no_errors(rtcan_h) && (config->filters != NULL)) { - // CAN_FilterTypeDef filter; - // filter.FilterActivation = ENABLE; - // filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; - // filter.FilterIdHigh = 0x00AA << 5U; // pm100 internal states - // filter.FilterIdLow = 0x0000 << 5U; - // filter.FilterMaskIdHigh = 0x0000 << 5U; - // filter.FilterMaskIdLow = 0x0000 << 5U; - // filter.FilterMode = CAN_FILTERMODE_IDLIST; - // filter.FilterScale = CAN_FILTERSCALE_16BIT; - // filter.FilterBank = 0; - - // HAL_StatusTypeDef hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - // &filter); - - CAN_FilterTypeDef filter; - filter.FilterActivation = ENABLE; - filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; - filter.FilterIdHigh = 0xAA << 5U; // pm100 internal states - filter.FilterIdLow = 0x106 << 5U; // vcu simulated messages - filter.FilterMaskIdHigh = 0x0000 << 5U; - filter.FilterMaskIdLow = 0x0000 << 5U; - filter.FilterMode = CAN_FILTERMODE_IDLIST; - filter.FilterScale = CAN_FILTERSCALE_16BIT; - filter.FilterBank = 0; - - CAN_FilterTypeDef filter2; - filter2.FilterActivation = ENABLE; - filter2.FilterFIFOAssignment = CAN_FILTER_FIFO0; - filter2.FilterIdHigh = 0xA0 << 5U; // Temperature Set 1 - filter2.FilterIdLow = 0xA1 << 5U; // Temperature Set 2 - filter2.FilterMaskIdHigh = 0x0000 << 5U; - filter2.FilterMaskIdLow = 0x0000 << 5U; - filter2.FilterMode = CAN_FILTERMODE_IDLIST; - filter2.FilterScale = CAN_FILTERSCALE_16BIT; - filter2.FilterBank = 1; - - CAN_FilterTypeDef filter3; - filter3.FilterActivation = ENABLE; - filter3.FilterFIFOAssignment = CAN_FILTER_FIFO1; - filter3.FilterIdHigh = 0xAB << 5U; // pm100 fault codes - filter3.FilterIdLow = 0xA2 << 5U; // Temperature Set 3 - filter3.FilterMaskIdHigh = 0x0000 << 5U; - filter3.FilterMaskIdLow = 0x0000 << 5U; - filter3.FilterMode = CAN_FILTERMODE_IDLIST; - filter3.FilterScale = CAN_FILTERSCALE_16BIT; - filter3.FilterBank = 2; - - CAN_FilterTypeDef filter4; - filter4.FilterActivation = ENABLE; - filter4.FilterFIFOAssignment = CAN_FILTER_FIFO1; - filter4.FilterIdHigh = 0x503 << 5U; // pdm out voltage - filter4.FilterIdLow = 0xA5 << 5U; // pm100 info - filter4.FilterMaskIdHigh = 0x0000 << 5U; - filter4.FilterMaskIdLow = 0x0000 << 5U; - filter4.FilterMode = CAN_FILTERMODE_IDLIST; - filter4.FilterScale = CAN_FILTERSCALE_16BIT; - filter4.FilterBank = 3; - - HAL_StatusTypeDef hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - &filter); - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); - hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - &filter2); - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); - hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - &filter3); - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); - hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - &filter4); - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); - - // CAN_FilterTypeDef filter; - // filter.FilterActivation = ENABLE; - // filter.FilterFIFOAssignment = CAN_FILTER_FIFO0; - // filter.FilterIdHigh = 0xAA << 5U; // pm100 internal states - // filter.FilterIdLow = 0x106 << 5U; // vcu simulated messages - // filter.FilterMaskIdHigh = 0xAB << 5U; //pm100 fault codes - // filter.FilterMaskIdLow = 0xA2 << 5U; // Temperature Set 3 - // filter.FilterMode = CAN_FILTERMODE_IDLIST; - // filter.FilterScale = CAN_FILTERSCALE_16BIT; - // filter.FilterBank = 0; - - // CAN_FilterTypeDef filter2; - // filter2.FilterActivation = ENABLE; - // filter2.FilterFIFOAssignment = CAN_FILTER_FIFO1; - // filter2.FilterIdHigh = 0xA0 << 5U; // Temperature Set 1 - // filter2.FilterIdLow = 0xA1 << 5U; // Temperature Set 2 - // filter2.FilterMaskIdHigh = 0x0000 << 5U; - // filter2.FilterMaskIdLow = 0x0000 << 5U; - // filter2.FilterMode = CAN_FILTERMODE_IDLIST; - // filter2.FilterScale = CAN_FILTERSCALE_16BIT; - // filter2.FilterBank = 1; - - // HAL_StatusTypeDef hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - // &filter); - // ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); - // hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, - // &filter2); + for (uint32_t i = 0U; i < config->filter_count; i++) + { + HAL_StatusTypeDef hal_status = HAL_CAN_ConfigFilter(rtcan_h->hcan, + &config->filters[i]); + add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); + } } return create_status(rtcan_h); } -// #define IS_CAN_IT(IT) ((IT) <= (CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_RX_FIFO0_MSG_PENDING | \ -// CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO0_OVERRUN | \ -// CAN_IT_RX_FIFO1_MSG_PENDING | CAN_IT_RX_FIFO1_FULL | \ -// CAN_IT_RX_FIFO1_OVERRUN | CAN_IT_WAKEUP | \ -// CAN_IT_SLEEP_ACK | CAN_IT_ERROR_WARNING | \ -// CAN_IT_ERROR_PASSIVE | CAN_IT_BUSOFF | \ -// CAN_IT_LAST_ERROR_CODE | CAN_IT_ERROR)) - /** * @brief Starts the RTCAN service * @@ -302,15 +160,14 @@ rtcan_status_t rtcan_init(rtcan_handle_t* rtcan_h, */ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) { - TX_THREAD* threads[2] = {&rtcan_h->tx_thread, &rtcan_h->rx_thread}; - - for (uint32_t i = 0; i < 2; i++) + if (rtcan_h == NULL) { - UINT tx_status = tx_thread_resume(threads[i]); - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INIT, rtcan_h); + return RTCAN_ERROR; } - // start peripheral + rtcan_h->started = true; + + /* Start CAN peripheral interrupts */ if (no_errors(rtcan_h)) { const uint32_t notifs = CAN_IT_TX_MAILBOX_EMPTY @@ -321,21 +178,22 @@ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) | CAN_IT_ERROR_PASSIVE | CAN_IT_ERROR_WARNING; - HAL_StatusTypeDef hal_status - = HAL_CAN_ActivateNotification(rtcan_h->hcan, - notifs); - - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); + HAL_StatusTypeDef hal_status = HAL_CAN_ActivateNotification(rtcan_h->hcan, + notifs); + add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); } + /* Start the CAN peripheral */ if (no_errors(rtcan_h)) { HAL_StatusTypeDef hal_status = HAL_CAN_Start(rtcan_h->hcan); - while(HAL_CAN_GetState(rtcan_h->hcan) != HAL_CAN_STATE_LISTENING) - ; + while (HAL_CAN_GetState(rtcan_h->hcan) != HAL_CAN_STATE_LISTENING) + { + /* Wait until listening */ + } - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); + add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); } return create_status(rtcan_h); @@ -346,18 +204,20 @@ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) /** * @brief Transmits a CAN message using the RTCAN service * - * @details The message is queued for transmission in a FIFO buffer, and the - * contents of the message is copied (!) to the buffer - * * @param[in] rtcan_h RTCAN handle * @param[in] msg_ptr Pointer to message to transmit */ rtcan_status_t rtcan_transmit(rtcan_handle_t* rtcan_h, rtcan_msg_t* msg_ptr) { - UINT tx_status - = tx_queue_send(&rtcan_h->tx_queue, (void*) msg_ptr, TX_NO_WAIT); + if ((rtcan_h == NULL) || (msg_ptr == NULL) || (!rtcan_h->started)) + { + return RTCAN_ERROR; + } - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_MEMORY_FULL, rtcan_h); + rtcan_osal_status_t os_status = rtcan_os_queue_send(rtcan_h->tx_queue, + (const void*) msg_ptr, + RTCAN_OS_NO_WAIT); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); return create_status(rtcan_h); } @@ -365,21 +225,21 @@ rtcan_status_t rtcan_transmit(rtcan_handle_t* rtcan_h, rtcan_msg_t* msg_ptr) /** * @brief Transmit mailbox callback * - * @details Increments the mailbox semaphore to allow the next message to be - * dispatched. This MUST be called by the user from - * HAL_CAN_TxMailboxCompleteCallback, for all n. - * * @param[in] rtcan_h RTCAN handle * @param[in] can_h CAN handle passed to HAL callback */ rtcan_status_t rtcan_handle_tx_mailbox_callback(rtcan_handle_t* rtcan_h, const CAN_HandleTypeDef* can_h) { - if (rtcan_h->hcan == can_h) + if ((rtcan_h == NULL) || (can_h == NULL)) { - UINT tx_status = tx_semaphore_put(&rtcan_h->tx_mailbox_sem); + return RTCAN_ERROR; + } - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INTERNAL, rtcan_h); + if (rtcan_h->hcan == can_h) + { + rtcan_osal_status_t os_status = rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } return create_status(rtcan_h); @@ -387,14 +247,12 @@ rtcan_status_t rtcan_handle_tx_mailbox_callback(rtcan_handle_t* rtcan_h, /** * @brief Internal transmit for RTCAN service thread - * - * @details Blocks RTCAN service if CAN transmit mailbox unavailable * * @param[in] rtcan_h RTCAN handle - * @param[in] identifier CAN standard identifier + * @param[in] identifier CAN identifier * @param[in] data_ptr Pointer to data to transmit * @param[in] data_length Length of data to transmit - * @param[in] extended Flag as to whether this is an extended frame to send + * @param[in] extended Flag showing whether this is an extended frame */ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, uint32_t identifier, @@ -402,46 +260,51 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, uint32_t data_length, const bool extended) { - if ((data_ptr == NULL) || (data_length == 0U)) + if ((rtcan_h == NULL) || (data_ptr == NULL) || (data_length == 0U)) { - rtcan_h->err |= RTCAN_ERROR_ARG; + if (rtcan_h != NULL) + { + rtcan_h->err |= RTCAN_ERROR_ARG; + } + return RTCAN_ERROR; } - if (tx_semaphore_get(&rtcan_h->tx_mailbox_sem, TX_WAIT_FOREVER) - != TX_SUCCESS) + rtcan_osal_status_t os_status = rtcan_os_sem_acquire(rtcan_h->tx_mailbox_sem, + RTCAN_OS_WAIT_FOREVER); + if (os_status != RTCAN_OS_OK) { rtcan_h->err |= RTCAN_ERROR_INTERNAL; + return RTCAN_ERROR; } - // if (no_errors(rtcan_h)) - { - // create message - CAN_TxHeaderTypeDef header = { - .RTR = CAN_RTR_DATA, - .DLC = data_length - }; - - if(extended){ - header.IDE = CAN_ID_EXT; - header.ExtId = identifier; - } else { - header.IDE = CAN_ID_STD; - header.StdId = identifier; - } - - // send it - uint32_t tx_mailbox; + /* Create Tx Header */ + CAN_TxHeaderTypeDef header = {0}; + header.RTR = CAN_RTR_DATA; + header.DLC = data_length; - HAL_StatusTypeDef hal_status = HAL_CAN_AddTxMessage(rtcan_h->hcan, - &header, - data_ptr, - &tx_mailbox); + if (extended) + { + header.IDE = CAN_ID_EXT; + header.ExtId = identifier; + } + else + { + header.IDE = CAN_ID_STD; + header.StdId = identifier; + } + /* Send message to mailbox */ + uint32_t tx_mailbox = 0U; + HAL_StatusTypeDef hal_status = HAL_CAN_AddTxMessage(rtcan_h->hcan, + &header, + data_ptr, + &tx_mailbox); - if (hal_status != HAL_OK) - { - rtcan_h->err |= RTCAN_ERROR_INTERNAL; - } + if (hal_status != HAL_OK) + { + rtcan_h->err |= RTCAN_ERROR_INTERNAL; + /* Release the mailbox semaphore since adding message failed */ + (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); } return create_status(rtcan_h); @@ -449,21 +312,19 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, /** * @brief Entry function for RTCAN transmit service thread - * - * @param[in] input RTCAN handle */ -static void rtcan_tx_thread_entry(ULONG input) +static void rtcan_tx_thread_entry(void* arg) { - rtcan_handle_t* rtcan_h = (rtcan_handle_t*) input; + rtcan_handle_t* rtcan_h = (rtcan_handle_t*) arg; while (1) { - const rtcan_msg_t message; - UINT tx_status = tx_queue_receive(&rtcan_h->tx_queue, - (void*) &message, - TX_WAIT_FOREVER); + rtcan_msg_t message; + rtcan_osal_status_t os_status = rtcan_os_queue_receive(rtcan_h->tx_queue, + (void*) &message, + RTCAN_OS_WAIT_FOREVER); - if (tx_status == TX_SUCCESS) + if (os_status == RTCAN_OS_OK) { (void) transmit_internal(rtcan_h, message.identifier, @@ -471,212 +332,129 @@ static void rtcan_tx_thread_entry(ULONG input) message.length, message.extended); } - else - { - // TODO: handle error - } } } //================================================================ subscriptions /** - * @brief Computes a hash for a single word - * - * @details This implements a Jenkins hash which was chosen for its balance - * between speed and distribution. The input data of CAN message - * IDs is small. - */ -static uint32_t compute_hash(const uint32_t key) -{ - uint32_t hash = key; - hash += (hash << 12); - hash ^= (hash >> 22); - hash += (hash << 4); - hash ^= (hash >> 9); - hash += (hash << 10); - hash ^= (hash >> 2); - hash += (hash << 7); - hash ^= (hash >> 12); - return hash; -} - -/** - * @brief Computes an index in the hash table of subscribers - */ -static inline uint32_t hashmap_index(const uint32_t can_id) -{ - return compute_hash(can_id) % RTCAN_HASHMAP_SIZE; -} - -/** - * @brief Creates a hashmap node + * @brief Adds a subscriber which will receive notifications of incoming + * CAN messages via an rtcan_queue_t * * @param[in] rtcan_h RTCAN handle - * @param[in] can_id CAN ID of node + * @param[in] can_id CAN ID to receive notification for (Standard 11-bit ID) + * @param[in] queue_ptr Destination to receive messages */ -static rtcan_hashmap_node_t* create_hashmap_node(rtcan_handle_t* rtcan_h, - uint32_t can_id) +rtcan_status_t rtcan_subscribe(rtcan_handle_t* rtcan_h, + uint32_t can_id, + rtcan_queue_t queue_ptr) { - rtcan_hashmap_node_t* new_node_ptr = NULL; - - ULONG status = tx_byte_allocate(&rtcan_h->subscriber_pool, - (void**) &new_node_ptr, - sizeof(rtcan_hashmap_node_t), - TX_NO_WAIT); - - ADD_ERROR_IF(status != TX_SUCCESS, RTCAN_ERROR_MEMORY_FULL, rtcan_h); - - if (no_errors(rtcan_h)) + if ((rtcan_h == NULL) || (queue_ptr == NULL) || (can_id >= 2048U)) { - new_node_ptr->chained_node_ptr = NULL; - new_node_ptr->can_id = can_id; + return RTCAN_ERROR; } - return new_node_ptr; -} + /* Check if already subscribed to prevent duplicates */ + rtcan_subscriber_t* sub = rtcan_h->subscriber_lut[can_id]; + while (sub != NULL) + { + if (sub->queue_ptr == queue_ptr) + { + return RTCAN_OK; /* Already subscribed */ + } + sub = sub->next_subscriber_ptr; + } -/** - * @brief Creates a subscriber node - * - * @param[in] rtcan_h RTCAN handle - * @param[in] queue_ptr Pointer to subscriber's associated queue - */ -static rtcan_subscriber_t* create_subscriber(rtcan_handle_t* rtcan_h, - TX_QUEUE* queue_ptr) -{ - rtcan_subscriber_t* new_subscriber_ptr = NULL; + /* Find a free subscriber node in the static pool */ + rtcan_subscriber_t* new_sub = NULL; + for (uint32_t i = 0U; i < RTCAN_MAX_SUBSCRIBERS; i++) + { + if (!rtcan_h->subscriber_pool[i].in_use) + { + new_sub = &rtcan_h->subscriber_pool[i]; + break; + } + } - ULONG status = tx_byte_allocate(&rtcan_h->subscriber_pool, - (void**) &new_subscriber_ptr, - sizeof(rtcan_subscriber_t), - TX_NO_WAIT); + if (new_sub == NULL) + { + rtcan_h->err |= RTCAN_ERROR_MEMORY_FULL; + return RTCAN_ERROR; + } - ADD_ERROR_IF(status != TX_SUCCESS, RTCAN_ERROR_MEMORY_FULL, rtcan_h); + /* Configure node */ + new_sub->queue_ptr = queue_ptr; + new_sub->next_subscriber_ptr = NULL; + new_sub->in_use = true; - if (no_errors(rtcan_h)) + /* Add node to standard ID lookup table */ + if (rtcan_h->subscriber_lut[can_id] == NULL) { - new_subscriber_ptr->next_subscriber_ptr = NULL; - new_subscriber_ptr->queue_ptr = queue_ptr; + rtcan_h->subscriber_lut[can_id] = new_sub; + } + else + { + sub = rtcan_h->subscriber_lut[can_id]; + while (sub->next_subscriber_ptr != NULL) + { + sub = sub->next_subscriber_ptr; + } + sub->next_subscriber_ptr = new_sub; } - - return new_subscriber_ptr; -} -/** - * @brief Returns a pointer to the hashmap node with the given CAN ID, or - * null if there are no nodes with that ID - * - * @param[in] rtcan_h RTCAN handle - * @param[in] can_id CAN ID - */ -static rtcan_hashmap_node_t* find_hashmap_node(rtcan_handle_t* rtcan_h, - const uint32_t can_id) -{ - const uint32_t index = hashmap_index(can_id); - return rtcan_h->subscriber_map[index]; + return RTCAN_OK; } /** - * @brief Appends a subscriber to an existing node in the hashmap - */ - -/** - * @brief Adds a subscriber which will receive notifications of incoming - * CAN messages via a TX_QUEUE - * - * @details Hash collisions are handled by collision chaining with a singly - * linked list. Each node in the hash map (or chain) consists - * of a singly linked list of subscribers for the given CAN ID. + * @brief Removes a subscriber, preventing memory leaks in static pools * * @param[in] rtcan_h RTCAN handle - * @param[in] can_id CAN ID to receive notification for - * @param[in] queue_ptr Destination to receive messages + * @param[in] can_id CAN ID associated with subscriber + * @param[in] queue_ptr Queue to identify subscriber */ -rtcan_status_t rtcan_subscribe(rtcan_handle_t* rtcan_h, - uint32_t can_id, - TX_QUEUE* queue_ptr) +rtcan_status_t rtcan_unsubscribe(rtcan_handle_t* rtcan_h, + uint32_t can_id, + rtcan_queue_t queue_ptr) { - const uint32_t index = hashmap_index(can_id); + if ((rtcan_h == NULL) || (queue_ptr == NULL) || (can_id >= 2048U)) + { + return RTCAN_ERROR; + } - // first time for this CAN ID, no collision - if (rtcan_h->subscriber_map[index] == NULL) + rtcan_subscriber_t* sub = rtcan_h->subscriber_lut[can_id]; + if (sub == NULL) { - rtcan_hashmap_node_t* new_node_ptr = create_hashmap_node(rtcan_h, - can_id); + return RTCAN_ERROR; /* Not found */ + } - if (no_errors(rtcan_h)) - { - new_node_ptr->first_subscriber_ptr = create_subscriber(rtcan_h, - queue_ptr); - } + rtcan_subscriber_t* prev = NULL; + bool found = false; - if (no_errors(rtcan_h)) - { - rtcan_h->subscriber_map[index] = new_node_ptr; - } - } - // hash collision, or another subscriber for an existing ID in the map - else + while (sub != NULL) { - rtcan_hashmap_node_t* node_ptr = rtcan_h->subscriber_map[index]; - - if (node_ptr->can_id != can_id) // hash collision, do chaining + if (sub->queue_ptr == queue_ptr) { - bool id_in_chain = false; - - while (node_ptr->chained_node_ptr != NULL) + found = true; + if (prev == NULL) { - node_ptr = node_ptr->chained_node_ptr; - - if (node_ptr->can_id == can_id) - { - id_in_chain = true; - break; - } - } - - if (!id_in_chain) // create new chained node - { - node_ptr->chained_node_ptr = create_hashmap_node(rtcan_h, - can_id); - - if (no_errors(rtcan_h)) - { - node_ptr = node_ptr->chained_node_ptr; - node_ptr->first_subscriber_ptr = create_subscriber(rtcan_h, - queue_ptr); - } - } - else // add to existing node - { - rtcan_subscriber_t* subscriber_ptr = node_ptr->first_subscriber_ptr; - - while (subscriber_ptr->next_subscriber_ptr != NULL) - { - subscriber_ptr = subscriber_ptr->next_subscriber_ptr; - } - - subscriber_ptr->next_subscriber_ptr = create_subscriber(rtcan_h, - queue_ptr); + rtcan_h->subscriber_lut[can_id] = sub->next_subscriber_ptr; } - - } - else // no collision, append to this node - { - rtcan_subscriber_t* subscriber_ptr = node_ptr->first_subscriber_ptr; - - while (subscriber_ptr->next_subscriber_ptr != NULL) + else { - subscriber_ptr = subscriber_ptr->next_subscriber_ptr; + prev->next_subscriber_ptr = sub->next_subscriber_ptr; } - subscriber_ptr->next_subscriber_ptr = create_subscriber(rtcan_h, - queue_ptr); + /* Reset the pool node and mark it as free */ + sub->queue_ptr = NULL; + sub->next_subscriber_ptr = NULL; + sub->in_use = false; + break; } + prev = sub; + sub = sub->next_subscriber_ptr; } - return create_status(rtcan_h); + return found ? RTCAN_OK : RTCAN_ERROR; } //=================================================================== rx service @@ -692,29 +470,29 @@ rtcan_status_t rtcan_handle_rx_it(rtcan_handle_t* rtcan_h, const CAN_HandleTypeDef* can_h, const uint32_t rx_fifo) { + if ((rtcan_h == NULL) || (can_h == NULL) || (rtcan_h->hcan != can_h)) + { + return RTCAN_ERROR; + } + if (!atomic_load(&rtcan_h->rx_ready)) { - HAL_CAN_GetRxMessage(rtcan_h->hcan, - rx_fifo, - NULL, - NULL); + /* Clear pending interrupt if receive service is disabled */ + (void) HAL_CAN_GetRxMessage(rtcan_h->hcan, rx_fifo, NULL, NULL); return RTCAN_OK; } - // allocate message + /* Allocate message block from static pool */ rtcan_msg_t* msg_ptr = NULL; + rtcan_osal_status_t os_status = rtcan_os_block_allocate(rtcan_h->rx_msg_pool, + (void**) &msg_ptr, + RTCAN_OS_NO_WAIT); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); - UINT tx_status = tx_block_allocate(&rtcan_h->rx_msg_pool, - (void**) &msg_ptr, - TX_NO_WAIT); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_MEMORY_FULL, rtcan_h); - - // retrieve message - if (no_errors(rtcan_h)) + /* Retrieve message */ + if (no_errors(rtcan_h) && (msg_ptr != NULL)) { - CAN_RxHeaderTypeDef header; - + CAN_RxHeaderTypeDef header = {0}; HAL_StatusTypeDef hal_status = HAL_CAN_GetRxMessage(rtcan_h->hcan, rx_fifo, &header, @@ -722,26 +500,40 @@ rtcan_status_t rtcan_handle_rx_it(rtcan_handle_t* rtcan_h, if (hal_status == HAL_OK) { - msg_ptr->identifier = header.StdId; + if (header.IDE == CAN_ID_EXT) + { + msg_ptr->identifier = header.ExtId; + msg_ptr->extended = true; + } + else + { + msg_ptr->identifier = header.StdId; + msg_ptr->extended = false; + } msg_ptr->length = header.DLC; - msg_ptr->reference_count = 0; + atomic_store(&msg_ptr->reference_count, 0U); } else { - tx_block_release(msg_ptr); + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); } - ADD_ERROR_IF(hal_status != HAL_OK, RTCAN_ERROR_INTERNAL, rtcan_h); + add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } - // send to Rx thread for distribution - if (no_errors(rtcan_h)) + /* Post message address to Rx distribution queue */ + if (no_errors(rtcan_h) && (msg_ptr != NULL)) { - tx_status = tx_queue_send(&rtcan_h->rx_notif_queue, - (void*) &msg_ptr, - TX_NO_WAIT); + os_status = rtcan_os_queue_send(rtcan_h->rx_notif_queue, + (const void*) &msg_ptr, + RTCAN_OS_NO_WAIT); - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_MEMORY_FULL, rtcan_h); + if (os_status != RTCAN_OS_OK) + { + /* If send failed, release the allocated block to prevent leakage */ + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + } + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); } return create_status(rtcan_h); @@ -750,22 +542,24 @@ rtcan_status_t rtcan_handle_rx_it(rtcan_handle_t* rtcan_h, /** * @brief Call after message received via subscription has been used * - * @note If this is not done, RTCAN will eventually run out of memory to - * store CAN messages! - * * @param[in] rtcan_h RTCAN handle * @param[in] msg_ptr Pointer to message */ rtcan_status_t rtcan_msg_consumed(rtcan_handle_t* rtcan_h, rtcan_msg_t* msg_ptr) { - (void) rtcan_h; + if ((rtcan_h == NULL) || (msg_ptr == NULL)) + { + return RTCAN_ERROR; + } - msg_ptr->reference_count--; + /* Atomically decrement reference count */ + uint32_t prev_count = atomic_fetch_sub(&msg_ptr->reference_count, 1U); - if (msg_ptr->reference_count == 0) + /* If it was 1, it has now hit 0, so release the block back to the pool */ + if (prev_count == 1U) { - tx_block_release(msg_ptr); + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); } return RTCAN_OK; @@ -773,61 +567,73 @@ rtcan_status_t rtcan_msg_consumed(rtcan_handle_t* rtcan_h, /** * @brief Entry function for RTCAN receive service thread - * - * @param[in] input RTCAN handler */ -static void rtcan_rx_thread_entry(ULONG input) +static void rtcan_rx_thread_entry(void* arg) { - rtcan_handle_t* rtcan_h = (rtcan_handle_t*) input; + rtcan_handle_t* rtcan_h = (rtcan_handle_t*) arg; while (1) { atomic_store(&rtcan_h->rx_ready, true); - // wait for message - rtcan_msg_t* msg_ptr; - - UINT tx_status = tx_queue_receive(&rtcan_h->rx_notif_queue, - (void*) &msg_ptr, - TX_WAIT_FOREVER); - - ADD_ERROR_IF(tx_status != TX_SUCCESS, RTCAN_ERROR_INTERNAL, rtcan_h); + /* Wait for incoming message notification */ + rtcan_msg_t* msg_ptr = NULL; + rtcan_osal_status_t os_status = rtcan_os_queue_receive(rtcan_h->rx_notif_queue, + (void*) &msg_ptr, + RTCAN_OS_WAIT_FOREVER); - // distribute - if (no_errors(rtcan_h)) + if ((os_status == RTCAN_OS_OK) && (msg_ptr != NULL)) { - rtcan_hashmap_node_t* node_ptr = find_hashmap_node(rtcan_h, - msg_ptr->identifier); - - if (node_ptr != NULL) + /* Check if the message is a standard ID within bounds */ + if ((!msg_ptr->extended) && (msg_ptr->identifier < 2048U)) { - rtcan_subscriber_t* subscriber_ptr = node_ptr->first_subscriber_ptr; + rtcan_subscriber_t* subscriber_ptr = rtcan_h->subscriber_lut[msg_ptr->identifier]; + uint32_t subscriber_count = 0U; + + /* 1. Count subscribers first */ + rtcan_subscriber_t* sub = subscriber_ptr; + while (sub != NULL) + { + subscriber_count++; + sub = sub->next_subscriber_ptr; + } - while (subscriber_ptr != NULL) + if (subscriber_count > 0U) { - msg_ptr->reference_count++; - - tx_status = tx_queue_send(subscriber_ptr->queue_ptr, - &msg_ptr, - TX_NO_WAIT); + /* Set reference count before posting to queues to avoid race conditions */ + atomic_store(&msg_ptr->reference_count, subscriber_count); - if (tx_status != TX_SUCCESS) + /* 2. Dispatch to subscribers */ + sub = subscriber_ptr; + while (sub != NULL) { - msg_ptr->reference_count--; + rtcan_osal_status_t queue_status = rtcan_os_queue_send(sub->queue_ptr, + &msg_ptr, + RTCAN_OS_NO_WAIT); + + if (queue_status != RTCAN_OS_OK) + { + /* Queue send failed; decrement reference count */ + uint32_t prev_count = atomic_fetch_sub(&msg_ptr->reference_count, 1U); + if (prev_count == 1U) + { + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + } + } + + sub = sub->next_subscriber_ptr; } - - subscriber_ptr = subscriber_ptr->next_subscriber_ptr; } - - // catch for errors sending in queue - if (msg_ptr->reference_count == 0) + else { - tx_block_release(msg_ptr); + /* No subscribers, release block */ + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); } } - else // don't care about this message + else { - tx_block_release(msg_ptr); + /* Extended IDs or out-of-bounds IDs are not supported in the LUT, release block */ + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); } } } @@ -836,69 +642,39 @@ static void rtcan_rx_thread_entry(ULONG input) //======================================================================== error /** - * @brief Handles HAL CAN errors - * - * @note `HAL_CAN_ERROR_BD` and `HAL_CAN_ERROR_CRC` are much more likely - * to happen in the actual car where the noise level is higher. - * - * Note also that it is possible for multiple errors to happen at - * once so the error code will be equal to a bitwise combination - * of the HAL error codes. + * @brief Handles HAL CAN errors and releases mailbox semaphore if needed * * @param[in] rtcan_h RTCAN handle * @param[in] can_h CAN handle */ rtcan_status_t rtcan_handle_hal_error(rtcan_handle_t* rtcan_h, - CAN_HandleTypeDef* can_h) + CAN_HandleTypeDef* can_h) { - rtcan_status_t status = RTCAN_OK; - - if (can_h == rtcan_h->hcan) - { - const uint32_t tx_errors[] = { - HAL_CAN_ERROR_TX_TERR0, - HAL_CAN_ERROR_TX_TERR1, - HAL_CAN_ERROR_TX_TERR2, - HAL_CAN_ERROR_TX_ALST0, - HAL_CAN_ERROR_TX_ALST1, - HAL_CAN_ERROR_TX_ALST2, - HAL_CAN_ERROR_BD, - HAL_CAN_ERROR_CRC, - HAL_CAN_ERROR_ACK - }; - -// #if (1) - - tx_semaphore_put(&rtcan_h->tx_mailbox_sem); - HAL_CAN_ResetError(rtcan_h->hcan); -#ifdef AHDFKJSD - uint32_t error = HAL_CAN_GetError(rtcan_h->hcan); - bool error_handled = false; - - for (uint32_t i = 0; i < sizeof(tx_errors)/sizeof(tx_errors[0]); i++) - { - if (error & tx_errors[i]) // check the single bit - { - tx_semaphore_put(&rtcan_h->tx_mailbox_sem); - HAL_CAN_ResetError(rtcan_h->hcan); - error_handled = true; - break; - } - } + if ((rtcan_h == NULL) || (can_h == NULL) || (rtcan_h->hcan != can_h)) + { + return RTCAN_ERROR; + } - // (void) error_handled; - // if (!error_handled) - // { - // tx_semaphore_put(&rtcan_h->tx_mailbox_sem); - // HAL_CAN_ResetError(rtcan_h->hcan); - // } + uint32_t error = HAL_CAN_GetError(rtcan_h->hcan); - // // unhandled/unknown errors - // ADD_ERROR_IF(!error_handled, RTCAN_ERROR_INTERNAL, rtcan_h); -#endif + /* Check which mailboxes failed transmission and release the semaphore accordingly */ + if (((error & HAL_CAN_ERROR_TX_TERR0) != 0U) || ((error & HAL_CAN_ERROR_TX_ALST0) != 0U)) + { + (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); + } + if (((error & HAL_CAN_ERROR_TX_TERR1) != 0U) || ((error & HAL_CAN_ERROR_TX_ALST1) != 0U)) + { + (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); } + if (((error & HAL_CAN_ERROR_TX_TERR2) != 0U) || ((error & HAL_CAN_ERROR_TX_ALST2) != 0U)) + { + (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); + } + + /* Reset the error code in the HAL handle */ + rtcan_h->hcan->ErrorCode = HAL_CAN_ERROR_NONE; - return status; + return RTCAN_OK; } //====================================================================== utility @@ -910,23 +686,23 @@ rtcan_status_t rtcan_handle_hal_error(rtcan_handle_t* rtcan_h, */ uint32_t rtcan_get_error(rtcan_handle_t* rtcan_h) { + if (rtcan_h == NULL) + { + return RTCAN_ERROR_ARG; + } return rtcan_h->err; } /** - * @brief Returns true if the RTCAN instance has encountered an error - * - * @param[in] rtcan_h RTCAN handle + * @brief Returns true if the RTCAN instance has encountered no error */ -static bool no_errors(rtcan_handle_t* rtcan_h) +static bool no_errors(const rtcan_handle_t* rtcan_h) { return (rtcan_h->err == RTCAN_ERROR_NONE); } /** * @brief Create a status code based on the current error state - * - * @param[in] rtcan_h RTCAN handle */ static rtcan_status_t create_status(rtcan_handle_t* rtcan_h) { diff --git a/src/rtcan_osal_cmsis2.c b/src/rtcan_osal_cmsis2.c new file mode 100644 index 0000000..7d29145 --- /dev/null +++ b/src/rtcan_osal_cmsis2.c @@ -0,0 +1,243 @@ +/*************************************************************************** + * @file rtcan_osal_cmsis2.c + * @author Antigravity (Google DeepMind team) + * @brief CMSIS-RTOS v2 implementation of RTCAN OSAL + ***************************************************************************/ + +#include "rtcan_osal.h" +#include + +rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, + const char* name, + rtcan_thread_entry_t entry, + void* arg, + uint32_t priority, + size_t stack_size, + void* stack_mem) +{ + if ((thread == NULL) || (entry == NULL)) + { + return RTCAN_OS_ERROR; + } + + osThreadAttr_t attr = {0}; + attr.name = name; + attr.priority = (osPriority_t)priority; + attr.stack_size = (uint32_t)stack_size; + if (stack_mem != NULL) + { + attr.stack_mem = stack_mem; + } + + osThreadId_t tid = osThreadNew((osThreadFunc_t)entry, arg, &attr); + if (tid == NULL) + { + return RTCAN_OS_ERROR; + } + + *thread = (rtcan_thread_t)tid; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, + const char* name, + size_t item_size, + size_t capacity, + void* queue_mem, + size_t queue_mem_size) +{ + if ((queue == NULL) || (item_size == 0U) || (capacity == 0U)) + { + return RTCAN_OS_ERROR; + } + + osMessageQueueAttr_t attr = {0}; + attr.name = name; + if (queue_mem != NULL) + { + attr.mq_mem = queue_mem; + attr.mq_size = (uint32_t)queue_mem_size; + } + + osMessageQueueId_t mq = osMessageQueueNew((uint32_t)capacity, (uint32_t)item_size, &attr); + if (mq == NULL) + { + return RTCAN_OS_ERROR; + } + + *queue = (rtcan_queue_t)mq; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_queue_send(rtcan_queue_t queue, + const void* item, + uint32_t timeout) +{ + if ((queue == NULL) || (item == NULL)) + { + return RTCAN_OS_ERROR; + } + + osStatus_t status = osMessageQueuePut((osMessageQueueId_t)queue, item, 0U, timeout); + if (status == osOK) + { + return RTCAN_OS_OK; + } + else if (status == osErrorTimeout) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_queue_receive(rtcan_queue_t queue, + void* item, + uint32_t timeout) +{ + if ((queue == NULL) || (item == NULL)) + { + return RTCAN_OS_ERROR; + } + + osStatus_t status = osMessageQueueGet((osMessageQueueId_t)queue, item, NULL, timeout); + if (status == osOK) + { + return RTCAN_OS_OK; + } + else if (status == osErrorTimeout) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_sem_create(rtcan_sem_t* sem, + const char* name, + uint32_t initial_count, + uint32_t max_count) +{ + if (sem == NULL) + { + return RTCAN_OS_ERROR; + } + + osSemaphoreAttr_t attr = {0}; + attr.name = name; + + osSemaphoreId_t sid = osSemaphoreNew(max_count, initial_count, &attr); + if (sid == NULL) + { + return RTCAN_OS_ERROR; + } + + *sem = (rtcan_sem_t)sid; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_sem_acquire(rtcan_sem_t sem, + uint32_t timeout) +{ + if (sem == NULL) + { + return RTCAN_OS_ERROR; + } + + osStatus_t status = osSemaphoreAcquire((osSemaphoreId_t)sem, timeout); + if (status == osOK) + { + return RTCAN_OS_OK; + } + else if (status == osErrorTimeout) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_sem_release(rtcan_sem_t sem) +{ + if (sem == NULL) + { + return RTCAN_OS_ERROR; + } + + osStatus_t status = osSemaphoreRelease((osSemaphoreId_t)sem); + return (status == osOK) ? RTCAN_OS_OK : RTCAN_OS_ERROR; +} + +rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, + const char* name, + size_t block_size, + size_t block_count, + void* pool_mem, + size_t pool_mem_size) +{ + if ((pool == NULL) || (block_size == 0U) || (block_count == 0U)) + { + return RTCAN_OS_ERROR; + } + + osMemoryPoolAttr_t attr = {0}; + attr.name = name; + if (pool_mem != NULL) + { + attr.mp_mem = pool_mem; + attr.mp_size = (uint32_t)pool_mem_size; + } + + osMemoryPoolId_t mp = osMemoryPoolNew((uint32_t)block_count, (uint32_t)block_size, &attr); + if (mp == NULL) + { + return RTCAN_OS_ERROR; + } + + *pool = (rtcan_block_pool_t)mp; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_block_allocate(rtcan_block_pool_t pool, + void** block_ptr, + uint32_t timeout) +{ + if ((pool == NULL) || (block_ptr == NULL)) + { + return RTCAN_OS_ERROR; + } + + void* ptr = osMemoryPoolAlloc((osMemoryPoolId_t)pool, timeout); + if (ptr == NULL) + { + if (timeout == RTCAN_OS_NO_WAIT) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } + } + + *block_ptr = ptr; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, + void* block_ptr) +{ + if ((pool == NULL) || (block_ptr == NULL)) + { + return RTCAN_OS_ERROR; + } + + osStatus_t status = osMemoryPoolFree((osMemoryPoolId_t)pool, block_ptr); + return (status == osOK) ? RTCAN_OS_OK : RTCAN_OS_ERROR; +} diff --git a/src/rtcan_osal_threadx.c b/src/rtcan_osal_threadx.c new file mode 100644 index 0000000..8b7a93d --- /dev/null +++ b/src/rtcan_osal_threadx.c @@ -0,0 +1,290 @@ +/*************************************************************************** + * @file rtcan_osal_threadx.c + * @author Antigravity (Google DeepMind team) + * @brief ThreadX implementation of RTCAN OSAL + ***************************************************************************/ + +#include "rtcan_osal.h" +#include + +#ifndef RTCAN_MAX_INSTANCES +#define RTCAN_MAX_INSTANCES 2U +#endif + +/* Static control blocks allocated for ThreadX backend */ +static TX_THREAD s_threads[RTCAN_MAX_INSTANCES * 2U]; +static TX_QUEUE s_queues[RTCAN_MAX_INSTANCES * 2U]; +static TX_SEMAPHORE s_sems[RTCAN_MAX_INSTANCES]; +static TX_BLOCK_POOL s_pools[RTCAN_MAX_INSTANCES]; + +static uint32_t s_thread_count = 0U; +static uint32_t s_queue_count = 0U; +static uint32_t s_sem_count = 0U; +static uint32_t s_pool_count = 0U; + +rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, + const char* name, + rtcan_thread_entry_t entry, + void* arg, + uint32_t priority, + size_t stack_size, + void* stack_mem) +{ + if ((thread == NULL) || (entry == NULL) || (stack_mem == NULL)) + { + return RTCAN_OS_ERROR; + } + + if (s_thread_count >= (RTCAN_MAX_INSTANCES * 2U)) + { + return RTCAN_OS_ERROR; + } + + TX_THREAD* tx_thread = &s_threads[s_thread_count]; + s_thread_count++; + + UINT status = tx_thread_create(tx_thread, + (CHAR*)name, + (VOID (*)(ULONG))entry, + (ULONG)arg, + stack_mem, + (ULONG)stack_size, + (UINT)priority, + (UINT)priority, + TX_NO_TIME_SLICE, + TX_AUTO_START); + + if (status != TX_SUCCESS) + { + return RTCAN_OS_ERROR; + } + + *thread = (rtcan_thread_t)tx_thread; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, + const char* name, + size_t item_size, + size_t capacity, + void* queue_mem, + size_t queue_mem_size) +{ + if ((queue == NULL) || (queue_mem == NULL) || (item_size == 0U) || (capacity == 0U)) + { + return RTCAN_OS_ERROR; + } + + if (s_queue_count >= (RTCAN_MAX_INSTANCES * 2U)) + { + return RTCAN_OS_ERROR; + } + + TX_QUEUE* tx_queue = &s_queues[s_queue_count]; + s_queue_count++; + + UINT message_size = (UINT)(item_size / sizeof(ULONG)); + if (message_size == 0U) + { + message_size = 1U; + } + + UINT status = tx_queue_create(tx_queue, + (CHAR*)name, + message_size, + queue_mem, + (ULONG)queue_mem_size); + + if (status != TX_SUCCESS) + { + return RTCAN_OS_ERROR; + } + + *queue = (rtcan_queue_t)tx_queue; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_queue_send(rtcan_queue_t queue, + const void* item, + uint32_t timeout) +{ + if ((queue == NULL) || (item == NULL)) + { + return RTCAN_OS_ERROR; + } + + UINT status = tx_queue_send((TX_QUEUE*)queue, (VOID*)item, (ULONG)timeout); + if (status == TX_SUCCESS) + { + return RTCAN_OS_OK; + } + else if (status == TX_QUEUE_FULL) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_queue_receive(rtcan_queue_t queue, + void* item, + uint32_t timeout) +{ + if ((queue == NULL) || (item == NULL)) + { + return RTCAN_OS_ERROR; + } + + UINT status = tx_queue_receive((TX_QUEUE*)queue, item, (ULONG)timeout); + if (status == TX_SUCCESS) + { + return RTCAN_OS_OK; + } + else if (status == TX_NO_INSTANCE) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_sem_create(rtcan_sem_t* sem, + const char* name, + uint32_t initial_count, + uint32_t max_count) +{ + (void)max_count; + + if (sem == NULL) + { + return RTCAN_OS_ERROR; + } + + if (s_sem_count >= RTCAN_MAX_INSTANCES) + { + return RTCAN_OS_ERROR; + } + + TX_SEMAPHORE* tx_sem = &s_sems[s_sem_count]; + s_sem_count++; + + UINT status = tx_semaphore_create(tx_sem, (CHAR*)name, (ULONG)initial_count); + if (status != TX_SUCCESS) + { + return RTCAN_OS_ERROR; + } + + *sem = (rtcan_sem_t)tx_sem; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_sem_acquire(rtcan_sem_t sem, + uint32_t timeout) +{ + if (sem == NULL) + { + return RTCAN_OS_ERROR; + } + + UINT status = tx_semaphore_get((TX_SEMAPHORE*)sem, (ULONG)timeout); + if (status == TX_SUCCESS) + { + return RTCAN_OS_OK; + } + else if (status == TX_NO_INSTANCE) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_sem_release(rtcan_sem_t sem) +{ + if (sem == NULL) + { + return RTCAN_OS_ERROR; + } + + UINT status = tx_semaphore_put((TX_SEMAPHORE*)sem); + return (status == TX_SUCCESS) ? RTCAN_OS_OK : RTCAN_OS_ERROR; +} + +rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, + const char* name, + size_t block_size, + size_t block_count, + void* pool_mem, + size_t pool_mem_size) +{ + (void)block_count; + if ((pool == NULL) || (pool_mem == NULL) || (block_size == 0U)) + { + return RTCAN_OS_ERROR; + } + + if (s_pool_count >= RTCAN_MAX_INSTANCES) + { + return RTCAN_OS_ERROR; + } + + TX_BLOCK_POOL* tx_pool = &s_pools[s_pool_count]; + s_pool_count++; + + UINT status = tx_block_pool_create(tx_pool, + (CHAR*)name, + (ULONG)block_size, + pool_mem, + (ULONG)pool_mem_size); + + if (status != TX_SUCCESS) + { + return RTCAN_OS_ERROR; + } + + *pool = (rtcan_block_pool_t)tx_pool; + return RTCAN_OS_OK; +} + +rtcan_osal_status_t rtcan_os_block_allocate(rtcan_block_pool_t pool, + void** block_ptr, + uint32_t timeout) +{ + if ((pool == NULL) || (block_ptr == NULL)) + { + return RTCAN_OS_ERROR; + } + + UINT status = tx_block_allocate((TX_BLOCK_POOL*)pool, block_ptr, (ULONG)timeout); + if (status == TX_SUCCESS) + { + return RTCAN_OS_OK; + } + else if (status == TX_NO_INSTANCE) + { + return RTCAN_OS_TIMEOUT; + } + else + { + return RTCAN_OS_ERROR; + } +} + +rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, + void* block_ptr) +{ + (void)pool; + if (block_ptr == NULL) + { + return RTCAN_OS_ERROR; + } + + UINT status = tx_block_release(block_ptr); + return (status == TX_SUCCESS) ? RTCAN_OS_OK : RTCAN_OS_ERROR; +} From 193dfbac5b118cec916fa3a4c7b07cf5c79ed542 Mon Sep 17 00:00:00 2001 From: Agwld Date: Sun, 14 Jun 2026 20:43:45 +0100 Subject: [PATCH 2/9] Update README.md --- README.md | 399 ++++++++++++++++++++++++++---------------------------- 1 file changed, 190 insertions(+), 209 deletions(-) diff --git a/README.md b/README.md index f360210..4f35a1e 100644 --- a/README.md +++ b/README.md @@ -1,277 +1,258 @@ # RTCAN -> :warning: This system is in early development. It is functional at a basic level, but expect bugs! +RTCAN (Real-Time CAN) is a portable, memory-safe C11 driver library for managing concurrent access to CAN peripherals on STM32 microcontrollers using a publisher/subscriber model. -## About +Designed for safety-critical Formula Student systems, the library is **RTOS-agnostic** and adheres to safety guidelines (such as MISRA C:2012) by utilizing **100% static allocation** with zero dynamic memory overhead. -RTCAN (Real-Time CAN) is a [ThreadX RTOS](https://learn.microsoft.com/en-us/azure/rtos/threadx/overview-threadx) -service for managing concurrent access to CAN peripherals on [STM32 microcontrollers](https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html). +--- -Features: -- Background thread based ThreadX service. -- FIFO transmit queuing. -- "Subscription" style receiving functionality. +## Key Features -Planned features: -- Automatic CAN filter configuration management. -- Priority queueing for transmissions. +- **Operating System Abstraction Layer (OSAL):** Decoupled from any specific RTOS. Native wrappers are provided for: + - **CMSIS-RTOS v2** (e.g., FreeRTOS, RTX5, Zephyr) in `src/rtcan_osal_cmsis2.c`. + - **ThreadX** in `src/rtcan_osal_threadx.c` (retaining backward-compatibility with a 100% static control block pool). +- **100% Static Allocation (MISRA C:2012 compliant):** All queues, message pools, and subscriber nodes are allocated statically at compile-time. There is no heap fragmentation or Out-of-Memory risk. +- **Deterministic O(1) Lookup Table (LUT):** Replaced separate-chained collision hashmaps with a direct Lookup Table (2048 entries) for standard 11-bit CAN IDs, ensuring constant-time dispatch on the receive path. +- **Race-Free Concurrent Dispatch:** Multi-threaded publisher/subscriber model using thread-safe C11 atomics (`stdatomic.h`) to handle message reference counting safely across queues. +- **Unsubscribe API:** Allows threads to dynamically unsubscribe from message queues, safely recycling subscriber slots back into the static pool. +- **Decoupled Application Logic:** Filter configurations are passed dynamically to `rtcan_init()` instead of being hardcoded in the driver. -Not currently supported: -- STM32 FDCAN HAL. -- Extended CAN identifiers. +--- ## Dependencies -- C11 compiler. -- 32 bit STM32 microcontroller. -- ThreadX memory pool, thread, semaphore and queue services. -- STM32 Hardware Abstraction Layer (HAL) CAN drivers. +- **C11 compiler** (uses ``). +- **32-bit STM32 Microcontroller** (uses STM32 HAL CAN drivers). +- **An RTOS** supported by the OSAL backends (CMSIS-RTOS v2 or ThreadX). + +--- ## Adding to a Project -### Submodule +### Option A: Using CMake `FetchContent` (Recommended) -Add this repository as a submodule using: +To avoid managing Git submodules, add this to your main project's `CMakeLists.txt`: -```sh -git submodule add https://github.com/sufst/rtcan -``` +```cmake +include(FetchContent) -Make sure to change directories to the location you want the submodule to exist -in the project source tree. Note that the use of submodules will require the -following commands to be run when cloning a project for the first time: +# Declare RTCAN +FetchContent_Declare( + rtcan + GIT_REPOSITORY https://github.com/sufst/rtcan.git + GIT_TAG main # Or use a specific tag/commit hash +) +FetchContent_MakeAvailable(rtcan) -```sh -git submodule init -git submodule update +# Link it to your executable +target_link_libraries(your_firmware_target PRIVATE rtcan) ``` -For more information on submodules, see the [Git submodule documentation](https://git-scm.com/book/en/v2/Git-Tools-Submodules). - -### Build System - -RTCAN consists of one header file (`inc/rtcan.h`) which should be added to the -include path for a project (or just to specific files requiring RTCAN), -and one source file (`src/rtcan.c`) which should be compiled by the build system -in question. Make sure the [RTCAN dependencies](#dependencies) are satisfied. +### Option B: Using Git Submodules +If you prefer submodules: +1. Clone the submodule into your project directory: + ```sh + git submodule add https://github.com/sufst/rtcan.git third_party/rtcan + ``` +2. Include the header directory `inc/` in your include paths. +3. Add `src/rtcan.c` to your build sources. +4. Add the appropriate OSAL wrapper to your build sources: + - For CMSIS-RTOS v2: `src/rtcan_osal_cmsis2.c` + - For ThreadX: `src/rtcan_osal_threadx.c` -## Usage +--- -### Initialisation +## API Usage Guide -RTCAN is provided for a CAN peripheral by an instance of `rtcan_handle_t` which -is initialised with the function `rtcan_init()`. Each RTCAN instance manages -one CAN peripheral and has two background service threads: one for transmitting -and one for receiving. - -### Transmitting - -The `rtcan_transmit()` function uses a simple FIFO queueing system to transmit -messages with the CAN peripheral. This provides a way of ensuring that there is -not contention for the CAN peripheral by multiple threads. - -To use the transmit service, CAN Tx interrupts must be enabled and the -`HAL_CAN_TxMailboxCompleteCallback` must be implemented to call -`rtcan_handle_tx_mailbox_callback` (for all `N`). For example, for the -HAL callback for Tx mailbox 1: +### 1. Initialization +Declare your global RTCAN handle and configure stack allocations, priority, and filters: ```c +#include "rtcan.h" + static rtcan_handle_t rtcan; +static uint64_t rtcan_tx_stack[128]; // 1024 bytes (8-byte aligned) +static uint64_t rtcan_rx_stack[128]; // 1024 bytes (8-byte aligned) + +/* Define hardware filters for the STM32 CAN peripheral */ +static const CAN_FilterTypeDef my_filters[] = { + { + .FilterActivation = ENABLE, + .FilterFIFOAssignment = CAN_FILTER_FIFO0, + .FilterIdHigh = (0x100 << 5U), // Filter for VCU simulated command (0x100) + .FilterIdLow = (0x200 << 5U), // Filter for Inverter telemetry (0x200) + .FilterMaskIdHigh = 0x0000, + .FilterMaskIdLow = 0x0000, + .FilterMode = CAN_FILTERMODE_IDLIST, + .FilterScale = CAN_FILTERSCALE_16BIT, + .FilterBank = 0 + } +}; -void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef* can_h) +void app_can_init(void) { - rtcan_handle_tx_mailbox_callback(&rtcan, can_h); + rtcan_config_t config = { + .thread_priority = 3, + .tx_thread_stack_size = sizeof(rtcan_tx_stack), + .tx_thread_stack_mem = rtcan_tx_stack, + .rx_thread_stack_size = sizeof(rtcan_rx_stack), + .rx_thread_stack_mem = rtcan_rx_stack, + .filters = my_filters, + .filter_count = sizeof(my_filters) / sizeof(my_filters[0]) + }; + + /* Initialize the RTCAN driver instance */ + rtcan_init(&rtcan, &hcan1, &config); + + /* Start the background threads and activate CAN interrupts */ + rtcan_start(&rtcan); } ``` -### Receiving and Subscriptions - -Receiving functionality in RTCAN is based around a "publisher" / "subscriber" -model in which application threads can register their interest in receiving -CAN messages with a particular ID through the `rtcan_subscribe()` function. -Threads must provide a queue as an endpoint for messages where queue items have -size `TX_1_ULONG`. Incoming CAN messages are published to the queue by the RTCAN -service, where each queue item is a pointer to the received message represented -as an `rtcan_msg_t` struct. Once a subscriber has finished with a message, it -**must** call the `rtcan_msg_consumed()` function to indicate this to the service. -Internally `rtcan_msg_t` is a reference counted, dynamically allocated data -structure which is distributed to all the subscribers of a given CAN ID. -As such, subscribers must treat this message as **read only** and should -not modify the `reference_count` field. - -To use the receive service, CAN Rx interrupts must be enabled and the -`HAL_CAN_RxFifoMsgPendingCallback` must be implemented to call -`rtcan_handle_rx_it` (for all `N`). For example, for the -HAL callback for Rx FIFO 1: +### 2. Subscribing & Unsubscribing +Declare a queue in your application thread, subscribe to standard IDs, and read from the queue. When finished with a message, release it using `rtcan_msg_consumed`. ```c -static rtcan_handle_t rtcan; +#include "rtcan.h" + +static rtcan_queue_t my_rx_queue; +static uint8_t queue_storage[10U * sizeof(rtcan_msg_t*)]; -void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef* can_h) +void app_thread(void* arg) { - rtcan_handle_rx_it(&rtcan, can_h, 1); + /* Create an OSAL queue to receive pointers to rtcan_msg_t structs */ + rtcan_os_queue_create(&my_rx_queue, "App Queue", sizeof(rtcan_msg_t*), 10U, queue_storage, sizeof(queue_storage)); + + /* Subscribe to message ID 0x100 */ + rtcan_subscribe(&rtcan, 0x100, my_rx_queue); + + while (1) + { + rtcan_msg_t* rx_msg = NULL; + /* Block waiting for an incoming message */ + if (rtcan_os_queue_receive(my_rx_queue, &rx_msg, RTCAN_OS_WAIT_FOREVER) == RTCAN_OS_OK) + { + /* Process data ... */ + uint8_t state = rx_msg->data[0]; + + /* Free the message reference back to the static pool */ + rtcan_msg_consumed(&rtcan, rx_msg); + } + } + + /* Unsubscribe if the thread exits or changes roles */ + rtcan_unsubscribe(&rtcan, 0x100, my_rx_queue); } ``` -### CAN Errors - -If CAN transmit errors occur, RTCAN must be notified through the `rtcan_handle_hal_error()` -function when `HAL_CAN_ErrorCallback()` is called. +### 3. Transmitting +Populate an `rtcan_msg_t` block and pass it to `rtcan_transmit()` to queue it in the background Tx loop: ```c -static rtcan_handle_t rtcan; - -void HAL_CAN_ErrorCallback(CAN_HandleTypeDef* can_h) +void send_status(void) { - rtcan_handle_hal_error(&rtcan, can_h); + rtcan_msg_t tx_msg = { + .identifier = 0x201, + .extended = false, + .length = 4, + .data = {0xAA, 0xBB, 0xCC, 0xDD} + }; + + rtcan_transmit(&rtcan, &tx_msg); } ``` -Failure to do so will mean the RTCAN transmit service is no longer able to -transmit messages. Note that it is not necessary to check that the CAN handle -matches that of the RTCAN instance, this is done automatically by RTCAN. - -### Error Codes +--- -All functions in the RTCAN API return a status code (`rtcan_status_t`) -indicating the RTCAN error state. `RTCAN_OK` indicates no error and -`RTCAN_ERROR` indicates an error. The specific error can be checked with the -return value of `rtcan_get_error()`. This design is based around the conventions -of the STM32 HAL. +## Integrating with `can-defs` (DBC Code Generation) -### Example +In SUFST firmware projects, standard practice is to use code-generated C structures and pack/unpack helper functions compiled from the central [can-defs](https://github.com/sufst/can-defs) repository. -The following example uses RTCAN to subscribe to CAN messages with ID `0x100` -and re-transmit the data as a CAN message with ID `0x101`. +Using code generation alongside RTCAN ensures type safety and eliminates hardcoded CAN IDs and bit-shifting: +### Example: Unpacking a Received Message ```c -#include "tx_api.h" #include "rtcan.h" -#include "can.h" -#include "string.h" // for memcpy() +#include "can_database.h" /* Generated from can-defs DBC */ -#define RTCAN_THREAD_PRIORITY 3 -#define MY_THREAD_PRIORITY 4 -#define MY_THREAD_STACK_SIZE 1024 - -static rtcan_handle_t rtcan; -static TX_THREAD my_thread; -static TX_QUEUE rx_queue; -static ULONG rx_queue_mem[10]; - -static void my_thread_entry(ULONG thread_input); - -/** - * initialise a thread which will use RTCAN services - */ -void init_my_thread(TX_BYTE_POOL* app_mem_pool) +void app_process_thread(void* arg) { - // initialise RTCAN instance - rtcan_init(&rtcan, - &hcan1, - RTCAN_THREAD_PRIORITY, - app_mem_pool); - - // allocate memory for thread - void* stack_ptr; - tx_byte_allocate(app_mem_pool, - &stack_ptr, - MY_THREAD_STACK_SIZE, - TX_NO_WAIT); - - // create thread - tx_thread_create(&my_thread, - my_thread_entry, - NULL, - stack_ptr, - MY_THREAD_STACK_SIZE, - MY_THREAD_PRIORITY, - MY_THREAD_PRIORITY, - TX_NO_TIME_SLICE, - TX_AUTO_START); - - // subscribe to a message - tx_queue_create(&rx_queue, - "My Rx Queue", - TX_1_ULONG, - rx_queue_mem, - sizeof(rx_queue)); - - rtcan_subscribe(&rtcan, 0x100, &rx_queue); - - // start the RTCAN service - rtcan_start(&rtcan); + rtcan_msg_t* rx_msg = NULL; + + if (rtcan_os_queue_receive(my_rx_queue, &rx_msg, RTCAN_OS_WAIT_FOREVER) == RTCAN_OS_OK) + { + /* Structure generated by cantools/can-defs */ + struct can_database_vcu_state_t decoded_vcu; + + /* Unpack raw bytes into type-safe fields */ + can_database_vcu_state_unpack(&decoded_vcu, rx_msg->data, rx_msg->length); + + /* Use decoded variables */ + uint16_t pedal_position = decoded_vcu.throttle_pedal; + + /* Release message block */ + rtcan_msg_consumed(&rtcan, rx_msg); + } } +``` + +### Example: Packing and Transmitting a Message +```c +#include "rtcan.h" +#include "can_database.h" -/** - * thread which uses RTCAN services - */ -void my_thread_entry(ULONG thread_input) +void send_bms_telemetry(void) { - (void) thread_input; // unused + struct can_database_bms_status_t bms_status = { + .accumulator_voltage = 580U, + .state_of_charge = 85U, + .error_flags = 0x00 + }; - while (1) - { - // wait for an item to enter the rx queue - rtcan_msg_t* msg_ptr; + rtcan_msg_t tx_msg; + tx_msg.identifier = CAN_DATABASE_BMS_STATUS_FRAME_ID; + tx_msg.extended = false; + tx_msg.length = CAN_DATABASE_BMS_STATUS_LENGTH; - tx_queue_receive(&rx_queue, - (void*) &msg_ptr, - TX_WAIT_FOREVER); + /* Pack structured data into the raw CAN message buffer */ + can_database_bms_status_pack(tx_msg.data, &bms_status, sizeof(tx_msg.data)); - // make a copy of the message but change the ID to 0x101 - rtcan_msg_t new_message; - new_message.identifier = 0x101; - new_message.length = message_ptr->length; - memcpy((void*) new_message.data, (void*) message_ptr->data, msg_ptr->length); + rtcan_transmit(&rtcan, &tx_msg); +} +``` - // transmit the copied message - rtcan_transmit(&rtcan, &new_message); +--- - // mark the original received message as consumed - rtcan_msg_consumed(&rtcan, msg_ptr); - } -} +## Mandatory Interrupt Service Routine (ISR) Mappings -/** - * implement HAL CAN callbacks to call RTCAN handler functions - * - * note: specific callbacks depend on CAN capabilities of target STM32 - */ -void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef* can_h) -{ - rtcan_handle_tx_mailbox_callback(&rtcan, can_h); -} +To hook the RTCAN engine up to the STM32 HAL callbacks, you must forward the callbacks inside your `stm32xx_it.c` or application callback code. -void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef* can_h) -{ - rtcan_handle_tx_mailbox_callback(&rtcan, can_h); +### 1. Transmit Interrupts +```c +void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef* hcan) { + rtcan_handle_tx_mailbox_callback(&rtcan, hcan); } - -void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef* can_h) -{ - rtcan_handle_tx_mailbox_callback(&rtcan, can_h); +void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef* hcan) { + rtcan_handle_tx_mailbox_callback(&rtcan, hcan); } - -void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef* can_h) -{ - rtcan_handle_rx_it(&rtcan, can_h, 0); +void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef* hcan) { + rtcan_handle_tx_mailbox_callback(&rtcan, hcan); } +``` -void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef* can_h) -{ - rtcan_handle_rx_it(&rtcan, can_h, 1); +### 2. Receive Interrupts +```c +void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef* hcan) { + rtcan_handle_rx_it(&rtcan, hcan, CAN_RX_FIFO0); +} +void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef* hcan) { + rtcan_handle_rx_it(&rtcan, hcan, CAN_RX_FIFO1); } - ``` -> Checking of return codes has been omitted here for brevity. In practice, - you should always check the return codes of both ThreadX and RTCAN - functions. - -## Other Platforms - -This implementation was developed for the STM32 platform, however it should be -relatively simple to port to another platform with a different HAL. A similar -system could also be implemented with another RTOS so long as it provides -equivalent services to ThreadX. +### 3. Error Interrupts +```c +void HAL_CAN_ErrorCallback(CAN_HandleTypeDef* hcan) { + rtcan_handle_hal_error(&rtcan, hcan); +} +``` From e01bebe3e87951087b885601068e06b140124b8b Mon Sep 17 00:00:00 2001 From: maartin0 Date: Sat, 20 Jun 2026 12:23:35 +0100 Subject: [PATCH 3/9] Pool allocation/transmit fixes --- src/rtcan.c | 113 ++++++++++++++++++++++++++++------------------------ 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/src/rtcan.c b/src/rtcan.c index 13de9aa..0561409 100644 --- a/src/rtcan.c +++ b/src/rtcan.c @@ -187,13 +187,17 @@ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) if (no_errors(rtcan_h)) { HAL_StatusTypeDef hal_status = HAL_CAN_Start(rtcan_h->hcan); - - while (HAL_CAN_GetState(rtcan_h->hcan) != HAL_CAN_STATE_LISTENING) + add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); + } + + if (no_errors(rtcan_h)) + { + uint32_t retries = 10000U; + while ((HAL_CAN_GetState(rtcan_h->hcan) != HAL_CAN_STATE_LISTENING) && (retries > 0U)) { - /* Wait until listening */ + retries--; } - - add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INIT, rtcan_h); + add_error_if(retries == 0U, RTCAN_ERROR_INIT, rtcan_h); } return create_status(rtcan_h); @@ -217,9 +221,15 @@ rtcan_status_t rtcan_transmit(rtcan_handle_t* rtcan_h, rtcan_msg_t* msg_ptr) rtcan_osal_status_t os_status = rtcan_os_queue_send(rtcan_h->tx_queue, (const void*) msg_ptr, RTCAN_OS_NO_WAIT); - add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); - return create_status(rtcan_h); + /* Queue full is a transient flow-control condition — return error without + poisoning the handle so subsequent transmits can succeed */ + if (os_status != RTCAN_OS_OK) + { + return RTCAN_ERROR; + } + + return RTCAN_OK; } /** @@ -475,66 +485,65 @@ rtcan_status_t rtcan_handle_rx_it(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } - if (!atomic_load(&rtcan_h->rx_ready)) + /* Attempt to allocate a pool block only if the RX service is active */ + rtcan_msg_t* msg_ptr = NULL; + + if (atomic_load(&rtcan_h->rx_ready)) { - /* Clear pending interrupt if receive service is disabled */ - (void) HAL_CAN_GetRxMessage(rtcan_h->hcan, rx_fifo, NULL, NULL); - return RTCAN_OK; + rtcan_osal_status_t alloc_status = rtcan_os_block_allocate(rtcan_h->rx_msg_pool, + (void**) &msg_ptr, + RTCAN_OS_NO_WAIT); + add_error_if(alloc_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); } - /* Allocate message block from static pool */ - rtcan_msg_t* msg_ptr = NULL; - rtcan_osal_status_t os_status = rtcan_os_block_allocate(rtcan_h->rx_msg_pool, - (void**) &msg_ptr, - RTCAN_OS_NO_WAIT); - add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); + /* Always drain the FIFO — leaving it non-empty re-triggers the interrupt immediately. + If no pool block is available, read into a scratch buffer and discard. */ + CAN_RxHeaderTypeDef header = {0}; + uint8_t scratch[8]; + uint8_t* data_buf = (msg_ptr != NULL) ? msg_ptr->data : scratch; + + HAL_StatusTypeDef hal_status = HAL_CAN_GetRxMessage(rtcan_h->hcan, + rx_fifo, + &header, + data_buf); - /* Retrieve message */ - if (no_errors(rtcan_h) && (msg_ptr != NULL)) + if (msg_ptr == NULL) { - CAN_RxHeaderTypeDef header = {0}; - HAL_StatusTypeDef hal_status = HAL_CAN_GetRxMessage(rtcan_h->hcan, - rx_fifo, - &header, - msg_ptr->data); + return create_status(rtcan_h); + } - if (hal_status == HAL_OK) + if (hal_status == HAL_OK) + { + if (header.IDE == CAN_ID_EXT) { - if (header.IDE == CAN_ID_EXT) - { - msg_ptr->identifier = header.ExtId; - msg_ptr->extended = true; - } - else - { - msg_ptr->identifier = header.StdId; - msg_ptr->extended = false; - } - msg_ptr->length = header.DLC; - atomic_store(&msg_ptr->reference_count, 0U); + msg_ptr->identifier = header.ExtId; + msg_ptr->extended = true; } - else + else { - (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + msg_ptr->identifier = header.StdId; + msg_ptr->extended = false; } - - add_error_if(hal_status != HAL_OK, RTCAN_ERROR_INTERNAL, rtcan_h); + msg_ptr->length = header.DLC; + atomic_store(&msg_ptr->reference_count, 0U); + } + else + { + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + add_error_if(true, RTCAN_ERROR_INTERNAL, rtcan_h); + return create_status(rtcan_h); } /* Post message address to Rx distribution queue */ - if (no_errors(rtcan_h) && (msg_ptr != NULL)) - { - os_status = rtcan_os_queue_send(rtcan_h->rx_notif_queue, - (const void*) &msg_ptr, - RTCAN_OS_NO_WAIT); + rtcan_osal_status_t send_status = rtcan_os_queue_send(rtcan_h->rx_notif_queue, + (const void*) &msg_ptr, + RTCAN_OS_NO_WAIT); - if (os_status != RTCAN_OS_OK) - { - /* If send failed, release the allocated block to prevent leakage */ - (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); - } - add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); + if (send_status != RTCAN_OS_OK) + { + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); } + add_error_if(send_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); return create_status(rtcan_h); } From 2aeda0119957fea3a193b0f299c49002b78c317e Mon Sep 17 00:00:00 2001 From: maartin0 Date: Sat, 20 Jun 2026 12:49:11 +0100 Subject: [PATCH 4/9] fix atomic semaphore issues --- inc/rtcan.h | 2 +- src/rtcan.c | 34 ++++++++++------------------------ 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/inc/rtcan.h b/inc/rtcan.h index e82a40c..4e89f4b 100644 --- a/inc/rtcan.h +++ b/inc/rtcan.h @@ -211,7 +211,7 @@ typedef struct /** * @brief Current error code */ - uint32_t err; + _Atomic uint32_t err; /** * @brief Flag for Rx service being ready diff --git a/src/rtcan.c b/src/rtcan.c index 0561409..4b45642 100644 --- a/src/rtcan.c +++ b/src/rtcan.c @@ -31,7 +31,7 @@ static inline void add_error_if(bool cond, uint32_t error, rtcan_handle_t* inst) { if (cond) { - inst->err |= error; + atomic_fetch_or(&inst->err, error); } } @@ -274,7 +274,7 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, { if (rtcan_h != NULL) { - rtcan_h->err |= RTCAN_ERROR_ARG; + atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_ARG); } return RTCAN_ERROR; } @@ -283,7 +283,7 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, RTCAN_OS_WAIT_FOREVER); if (os_status != RTCAN_OS_OK) { - rtcan_h->err |= RTCAN_ERROR_INTERNAL; + atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_INTERNAL); return RTCAN_ERROR; } @@ -312,7 +312,7 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, if (hal_status != HAL_OK) { - rtcan_h->err |= RTCAN_ERROR_INTERNAL; + atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_INTERNAL); /* Release the mailbox semaphore since adding message failed */ (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); } @@ -388,7 +388,7 @@ rtcan_status_t rtcan_subscribe(rtcan_handle_t* rtcan_h, if (new_sub == NULL) { - rtcan_h->err |= RTCAN_ERROR_MEMORY_FULL; + atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_MEMORY_FULL); return RTCAN_ERROR; } @@ -664,23 +664,9 @@ rtcan_status_t rtcan_handle_hal_error(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } - uint32_t error = HAL_CAN_GetError(rtcan_h->hcan); - - /* Check which mailboxes failed transmission and release the semaphore accordingly */ - if (((error & HAL_CAN_ERROR_TX_TERR0) != 0U) || ((error & HAL_CAN_ERROR_TX_ALST0) != 0U)) - { - (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); - } - if (((error & HAL_CAN_ERROR_TX_TERR1) != 0U) || ((error & HAL_CAN_ERROR_TX_ALST1) != 0U)) - { - (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); - } - if (((error & HAL_CAN_ERROR_TX_TERR2) != 0U) || ((error & HAL_CAN_ERROR_TX_ALST2) != 0U)) - { - (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); - } - - /* Reset the error code in the HAL handle */ + /* Reset the error code in the HAL handle. + TX semaphore is released by the abort callback, not here — releasing it + in both places causes a double-release on every NART TX failure. */ rtcan_h->hcan->ErrorCode = HAL_CAN_ERROR_NONE; return RTCAN_OK; @@ -699,7 +685,7 @@ uint32_t rtcan_get_error(rtcan_handle_t* rtcan_h) { return RTCAN_ERROR_ARG; } - return rtcan_h->err; + return atomic_load(&rtcan_h->err); } /** @@ -707,7 +693,7 @@ uint32_t rtcan_get_error(rtcan_handle_t* rtcan_h) */ static bool no_errors(const rtcan_handle_t* rtcan_h) { - return (rtcan_h->err == RTCAN_ERROR_NONE); + return (atomic_load(&rtcan_h->err) == RTCAN_ERROR_NONE); } /** From de9100b9f46ec552d5d8cd65b6c3b05e908c277d Mon Sep 17 00:00:00 2001 From: maartin0 Date: Sun, 21 Jun 2026 20:35:46 +0100 Subject: [PATCH 5/9] Lots of fixes (yay) --- README.md | 2 + inc/rtcan.h | 18 +-- inc/rtcan_osal.h | 19 ++++ src/rtcan.c | 234 ++++++++++++++++++++++++--------------- src/rtcan_osal_cmsis2.c | 22 ++-- src/rtcan_osal_threadx.c | 103 +++++++++++++---- 6 files changed, 266 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 4f35a1e..00f5683 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ If you prefer submodules: - For CMSIS-RTOS v2: `src/rtcan_osal_cmsis2.c` - For ThreadX: `src/rtcan_osal_threadx.c` +> **ThreadX only:** define `RTCAN_OSAL_THREADX` in your build (e.g. `-DRTCAN_OSAL_THREADX`). This switches `RTCAN_OS_QUEUE_MEM_SIZE` to a ThreadX-specific formula that accounts for the `TX_QUEUE` control block embedded at the front of each statically-allocated queue buffer. Without it, small queues (capacity ≤ 3 for pointer-sized items) will be undersized and queue creation will fail at init. + --- ## API Usage Guide diff --git a/inc/rtcan.h b/inc/rtcan.h index 4e89f4b..8117975 100644 --- a/inc/rtcan.h +++ b/inc/rtcan.h @@ -86,7 +86,7 @@ typedef struct * @brief Reference count for dynamically allocated messages with multiple * subscribers */ - volatile _Atomic uint32_t reference_count; + _Atomic uint32_t reference_count; /** * @brief Flag showing whether the message is an extended message @@ -176,7 +176,7 @@ typedef struct /** * @brief Receive notification queue memory area */ - uint8_t rx_notif_queue_mem[RTCAN_RX_NOTIF_QUEUE_LENGTH * sizeof(rtcan_msg_t*)]; + uint32_t rx_notif_queue_mem[RTCAN_OS_QUEUE_MEM_SIZE(RTCAN_RX_NOTIF_QUEUE_LENGTH, sizeof(rtcan_msg_t*)) / sizeof(uint32_t)]; /** * @brief Transmit queue @@ -186,7 +186,7 @@ typedef struct /** * @brief Transmit queue memory area */ - uint8_t tx_queue_mem[RTCAN_TX_QUEUE_LENGTH * sizeof(rtcan_msg_t)]; + uint32_t tx_queue_mem[RTCAN_OS_QUEUE_MEM_SIZE(RTCAN_TX_QUEUE_LENGTH, sizeof(rtcan_msg_t)) / sizeof(uint32_t)]; /** * @brief Static pool of subscriber structures @@ -209,19 +209,19 @@ typedef struct rtcan_msg_t rx_msg_pool_mem[RTCAN_RX_MSG_POOL_SIZE]; /** - * @brief Current error code + * @brief Mutex protecting subscriber_lut and subscriber_pool */ - _Atomic uint32_t err; + rtcan_sem_t subscriber_mutex; /** - * @brief Flag for Rx service being ready + * @brief Current error code */ - atomic_bool rx_ready; + _Atomic uint32_t err; /** * @brief Flag indicating whether the service is started */ - bool started; + _Atomic bool started; } rtcan_handle_t; @@ -234,7 +234,7 @@ rtcan_status_t rtcan_init(rtcan_handle_t *rtcan_h, rtcan_status_t rtcan_start(rtcan_handle_t *rtcan_h); -rtcan_status_t rtcan_transmit(rtcan_handle_t *rtcan_h, rtcan_msg_t *msg_ptr); +rtcan_status_t rtcan_transmit(rtcan_handle_t *rtcan_h, const rtcan_msg_t *msg_ptr); rtcan_status_t rtcan_handle_tx_mailbox_callback(rtcan_handle_t *rtcan_h, const CAN_HandleTypeDef *can_h); diff --git a/inc/rtcan_osal.h b/inc/rtcan_osal.h index 435fe63..9ee053d 100644 --- a/inc/rtcan_osal.h +++ b/inc/rtcan_osal.h @@ -24,6 +24,20 @@ typedef enum #define RTCAN_OS_WAIT_FOREVER 0xFFFFFFFFU #define RTCAN_OS_NO_WAIT 0x00000000U +/* Minimum queue storage bytes for capacity messages of item_size bytes. + ThreadX embeds a TX_QUEUE control block at the front of the caller's buffer, + so the formula includes sizeof(TX_QUEUE). Requires -DRTCAN_OSAL_THREADX. + CMSIS-RTOS2 manages its own control block separately; only a 4-byte header + plus 12 bytes of per-message overhead beyond the aligned item payload is needed. */ +#if defined(RTCAN_OSAL_THREADX) +#include +#define RTCAN_OS_QUEUE_MEM_SIZE(capacity, item_size) \ + (sizeof(TX_QUEUE) + (uint32_t)(capacity) * (((uint32_t)(item_size) + 3U) & ~3U)) +#else +#define RTCAN_OS_QUEUE_MEM_SIZE(capacity, item_size) \ + (4U + (uint32_t)(capacity) * (12U + (((uint32_t)(item_size) + 3U) & ~3U))) +#endif + /* Opaque pointer types for OS resources */ typedef void* rtcan_thread_t; typedef void* rtcan_queue_t; @@ -177,4 +191,9 @@ rtcan_osal_status_t rtcan_os_block_allocate(rtcan_block_pool_t pool, rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, void* block_ptr); +/** + * @brief Yield the current thread to allow other threads to run + */ +void rtcan_os_yield(void); + #endif /* RTCAN_OSAL_H */ diff --git a/src/rtcan.c b/src/rtcan.c index 4b45642..24a91b9 100644 --- a/src/rtcan.c +++ b/src/rtcan.c @@ -53,11 +53,14 @@ rtcan_status_t rtcan_init(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } + if (rtcan_h->subscriber_mutex != NULL) + { + return RTCAN_ERROR; + } + rtcan_h->hcan = hcan; rtcan_h->err = RTCAN_ERROR_NONE; - rtcan_h->started = false; - atomic_store(&rtcan_h->rx_ready, true); - + atomic_store(&rtcan_h->started, false); /* Initialize subscriber registry */ for (uint32_t i = 0U; i < RTCAN_MAX_SUBSCRIBERS; i++) { @@ -70,24 +73,34 @@ rtcan_status_t rtcan_init(rtcan_handle_t* rtcan_h, rtcan_h->subscriber_lut[i] = NULL; } + /* Create subscriber mutex (binary semaphore) */ + rtcan_osal_status_t sub_mutex_status = rtcan_os_sem_create(&rtcan_h->subscriber_mutex, + "RTCAN Subscriber Mutex", + 1U, + 1U); + add_error_if(sub_mutex_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); + /* Create transmit queue */ - rtcan_osal_status_t os_status = rtcan_os_queue_create(&rtcan_h->tx_queue, - "RTCAN Transmit Queue", - sizeof(rtcan_msg_t), - RTCAN_TX_QUEUE_LENGTH, - rtcan_h->tx_queue_mem, - sizeof(rtcan_h->tx_queue_mem)); - add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); + if (no_errors(rtcan_h)) + { + rtcan_osal_status_t os_status = rtcan_os_queue_create(&rtcan_h->tx_queue, + "RTCAN Transmit Queue", + sizeof(rtcan_msg_t), + RTCAN_TX_QUEUE_LENGTH, + rtcan_h->tx_queue_mem, + sizeof(rtcan_h->tx_queue_mem)); + add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); + } /* Create receive notification queue */ if (no_errors(rtcan_h)) { - os_status = rtcan_os_queue_create(&rtcan_h->rx_notif_queue, - "RTCAN Rx Notif Queue", - sizeof(rtcan_msg_t*), - RTCAN_RX_NOTIF_QUEUE_LENGTH, - rtcan_h->rx_notif_queue_mem, - sizeof(rtcan_h->rx_notif_queue_mem)); + rtcan_osal_status_t os_status = rtcan_os_queue_create(&rtcan_h->rx_notif_queue, + "RTCAN Rx Notif Queue", + sizeof(rtcan_msg_t*), + RTCAN_RX_NOTIF_QUEUE_LENGTH, + rtcan_h->rx_notif_queue_mem, + sizeof(rtcan_h->rx_notif_queue_mem)); add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } @@ -95,47 +108,47 @@ rtcan_status_t rtcan_init(rtcan_handle_t* rtcan_h, if (no_errors(rtcan_h)) { const uint32_t mailbox_size = 3U; - os_status = rtcan_os_sem_create(&rtcan_h->tx_mailbox_sem, - "RTCAN Tx Mailbox Sem", - mailbox_size, - mailbox_size); + rtcan_osal_status_t os_status = rtcan_os_sem_create(&rtcan_h->tx_mailbox_sem, + "RTCAN Tx Mailbox Sem", + mailbox_size, + mailbox_size); add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } /* Create Rx message block pool */ if (no_errors(rtcan_h)) { - os_status = rtcan_os_block_pool_create(&rtcan_h->rx_msg_pool, - "RTCAN Rx Message Pool", - sizeof(rtcan_msg_t), - RTCAN_RX_MSG_POOL_SIZE, - rtcan_h->rx_msg_pool_mem, - sizeof(rtcan_h->rx_msg_pool_mem)); + rtcan_osal_status_t os_status = rtcan_os_block_pool_create(&rtcan_h->rx_msg_pool, + "RTCAN Rx Message Pool", + sizeof(rtcan_msg_t), + RTCAN_RX_MSG_POOL_SIZE, + rtcan_h->rx_msg_pool_mem, + sizeof(rtcan_h->rx_msg_pool_mem)); add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } /* Create background service threads */ if (no_errors(rtcan_h)) { - os_status = rtcan_os_thread_create(&rtcan_h->tx_thread, - "RTCAN Tx Thread", - rtcan_tx_thread_entry, - (void*) rtcan_h, - config->thread_priority, - config->tx_thread_stack_size, - config->tx_thread_stack_mem); + rtcan_osal_status_t os_status = rtcan_os_thread_create(&rtcan_h->tx_thread, + "RTCAN Tx Thread", + rtcan_tx_thread_entry, + (void*) rtcan_h, + config->thread_priority, + config->tx_thread_stack_size, + config->tx_thread_stack_mem); add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); } if (no_errors(rtcan_h)) { - os_status = rtcan_os_thread_create(&rtcan_h->rx_thread, - "RTCAN Rx Thread", - rtcan_rx_thread_entry, - (void*) rtcan_h, - config->thread_priority, - config->rx_thread_stack_size, - config->rx_thread_stack_mem); + rtcan_osal_status_t os_status = rtcan_os_thread_create(&rtcan_h->rx_thread, + "RTCAN Rx Thread", + rtcan_rx_thread_entry, + (void*) rtcan_h, + config->thread_priority, + config->rx_thread_stack_size, + config->rx_thread_stack_mem); add_error_if(os_status != RTCAN_OS_OK, RTCAN_ERROR_INIT, rtcan_h); } @@ -165,8 +178,6 @@ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) return RTCAN_ERROR; } - rtcan_h->started = true; - /* Start CAN peripheral interrupts */ if (no_errors(rtcan_h)) { @@ -195,11 +206,17 @@ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) uint32_t retries = 10000U; while ((HAL_CAN_GetState(rtcan_h->hcan) != HAL_CAN_STATE_LISTENING) && (retries > 0U)) { + rtcan_os_yield(); retries--; } add_error_if(retries == 0U, RTCAN_ERROR_INIT, rtcan_h); } + if (no_errors(rtcan_h)) + { + atomic_store(&rtcan_h->started, true); + } + return create_status(rtcan_h); } @@ -211,9 +228,9 @@ rtcan_status_t rtcan_start(rtcan_handle_t* rtcan_h) * @param[in] rtcan_h RTCAN handle * @param[in] msg_ptr Pointer to message to transmit */ -rtcan_status_t rtcan_transmit(rtcan_handle_t* rtcan_h, rtcan_msg_t* msg_ptr) +rtcan_status_t rtcan_transmit(rtcan_handle_t* rtcan_h, const rtcan_msg_t* msg_ptr) { - if ((rtcan_h == NULL) || (msg_ptr == NULL) || (!rtcan_h->started)) + if ((rtcan_h == NULL) || (msg_ptr == NULL) || (!atomic_load(&rtcan_h->started))) { return RTCAN_ERROR; } @@ -270,7 +287,7 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, uint32_t data_length, const bool extended) { - if ((rtcan_h == NULL) || (data_ptr == NULL) || (data_length == 0U)) + if ((rtcan_h == NULL) || (data_ptr == NULL) || (data_length == 0U) || (data_length > 8U)) { if (rtcan_h != NULL) { @@ -364,12 +381,21 @@ rtcan_status_t rtcan_subscribe(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } + rtcan_osal_status_t os_status = rtcan_os_sem_acquire(rtcan_h->subscriber_mutex, + RTCAN_OS_WAIT_FOREVER); + if (os_status != RTCAN_OS_OK) + { + atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_INTERNAL); + return RTCAN_ERROR; + } + /* Check if already subscribed to prevent duplicates */ rtcan_subscriber_t* sub = rtcan_h->subscriber_lut[can_id]; while (sub != NULL) { if (sub->queue_ptr == queue_ptr) { + (void) rtcan_os_sem_release(rtcan_h->subscriber_mutex); return RTCAN_OK; /* Already subscribed */ } sub = sub->next_subscriber_ptr; @@ -389,6 +415,7 @@ rtcan_status_t rtcan_subscribe(rtcan_handle_t* rtcan_h, if (new_sub == NULL) { atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_MEMORY_FULL); + (void) rtcan_os_sem_release(rtcan_h->subscriber_mutex); return RTCAN_ERROR; } @@ -412,6 +439,7 @@ rtcan_status_t rtcan_subscribe(rtcan_handle_t* rtcan_h, sub->next_subscriber_ptr = new_sub; } + (void) rtcan_os_sem_release(rtcan_h->subscriber_mutex); return RTCAN_OK; } @@ -431,9 +459,18 @@ rtcan_status_t rtcan_unsubscribe(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } + rtcan_osal_status_t os_status = rtcan_os_sem_acquire(rtcan_h->subscriber_mutex, + RTCAN_OS_WAIT_FOREVER); + if (os_status != RTCAN_OS_OK) + { + atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_INTERNAL); + return RTCAN_ERROR; + } + rtcan_subscriber_t* sub = rtcan_h->subscriber_lut[can_id]; if (sub == NULL) { + (void) rtcan_os_sem_release(rtcan_h->subscriber_mutex); return RTCAN_ERROR; /* Not found */ } @@ -464,6 +501,7 @@ rtcan_status_t rtcan_unsubscribe(rtcan_handle_t* rtcan_h, sub = sub->next_subscriber_ptr; } + (void) rtcan_os_sem_release(rtcan_h->subscriber_mutex); return found ? RTCAN_OK : RTCAN_ERROR; } @@ -485,16 +523,11 @@ rtcan_status_t rtcan_handle_rx_it(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } - /* Attempt to allocate a pool block only if the RX service is active */ rtcan_msg_t* msg_ptr = NULL; - - if (atomic_load(&rtcan_h->rx_ready)) - { - rtcan_osal_status_t alloc_status = rtcan_os_block_allocate(rtcan_h->rx_msg_pool, - (void**) &msg_ptr, - RTCAN_OS_NO_WAIT); - add_error_if(alloc_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); - } + rtcan_osal_status_t alloc_status = rtcan_os_block_allocate(rtcan_h->rx_msg_pool, + (void**) &msg_ptr, + RTCAN_OS_NO_WAIT); + add_error_if(alloc_status != RTCAN_OS_OK, RTCAN_ERROR_MEMORY_FULL, rtcan_h); /* Always drain the FIFO — leaving it non-empty re-triggers the interrupt immediately. If no pool block is available, read into a scratch buffer and discard. */ @@ -562,10 +595,14 @@ rtcan_status_t rtcan_msg_consumed(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } - /* Atomically decrement reference count */ - uint32_t prev_count = atomic_fetch_sub(&msg_ptr->reference_count, 1U); + uint32_t prev_count = atomic_load(&msg_ptr->reference_count); + do { + if (prev_count == 0U) + { + return RTCAN_ERROR; + } + } while (!atomic_compare_exchange_weak(&msg_ptr->reference_count, &prev_count, prev_count - 1U)); - /* If it was 1, it has now hit 0, so release the block back to the pool */ if (prev_count == 1U) { (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); @@ -583,8 +620,6 @@ static void rtcan_rx_thread_entry(void* arg) while (1) { - atomic_store(&rtcan_h->rx_ready, true); - /* Wait for incoming message notification */ rtcan_msg_t* msg_ptr = NULL; rtcan_osal_status_t os_status = rtcan_os_queue_receive(rtcan_h->rx_notif_queue, @@ -596,47 +631,64 @@ static void rtcan_rx_thread_entry(void* arg) /* Check if the message is a standard ID within bounds */ if ((!msg_ptr->extended) && (msg_ptr->identifier < 2048U)) { - rtcan_subscriber_t* subscriber_ptr = rtcan_h->subscriber_lut[msg_ptr->identifier]; - uint32_t subscriber_count = 0U; - - /* 1. Count subscribers first */ - rtcan_subscriber_t* sub = subscriber_ptr; - while (sub != NULL) + if (rtcan_os_sem_acquire(rtcan_h->subscriber_mutex, RTCAN_OS_WAIT_FOREVER) != RTCAN_OS_OK) { - subscriber_count++; - sub = sub->next_subscriber_ptr; + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); } - - if (subscriber_count > 0U) + else { - /* Set reference count before posting to queues to avoid race conditions */ - atomic_store(&msg_ptr->reference_count, subscriber_count); + rtcan_subscriber_t* subscriber_ptr = rtcan_h->subscriber_lut[msg_ptr->identifier]; + uint32_t subscriber_count = 0U; - /* 2. Dispatch to subscribers */ - sub = subscriber_ptr; + /* 1. Count subscribers first */ + rtcan_subscriber_t* sub = subscriber_ptr; while (sub != NULL) { - rtcan_osal_status_t queue_status = rtcan_os_queue_send(sub->queue_ptr, - &msg_ptr, - RTCAN_OS_NO_WAIT); + subscriber_count++; + sub = sub->next_subscriber_ptr; + } - if (queue_status != RTCAN_OS_OK) + if (subscriber_count > 0U) + { + /* Set reference count before posting to queues to avoid race conditions */ + atomic_store(&msg_ptr->reference_count, subscriber_count); + + /* 2. Dispatch to subscribers */ + sub = subscriber_ptr; + while (sub != NULL) { - /* Queue send failed; decrement reference count */ - uint32_t prev_count = atomic_fetch_sub(&msg_ptr->reference_count, 1U); - if (prev_count == 1U) + rtcan_osal_status_t queue_status = rtcan_os_queue_send(sub->queue_ptr, + &msg_ptr, + RTCAN_OS_NO_WAIT); + + if (queue_status != RTCAN_OS_OK) { - (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + uint32_t prev_count = atomic_load(&msg_ptr->reference_count); + do { + if (prev_count == 0U) + { + break; + } + } while (!atomic_compare_exchange_weak(&msg_ptr->reference_count, + &prev_count, + prev_count - 1U)); + if (prev_count == 1U) + { + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + } } - } - sub = sub->next_subscriber_ptr; + sub = sub->next_subscriber_ptr; + } } - } - else - { - /* No subscribers, release block */ - (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + else + { + /* No subscribers, release block */ + (void) rtcan_os_block_release(rtcan_h->rx_msg_pool, msg_ptr); + } + + rtcan_osal_status_t rel_status = rtcan_os_sem_release(rtcan_h->subscriber_mutex); + add_error_if(rel_status != RTCAN_OS_OK, RTCAN_ERROR_INTERNAL, rtcan_h); } } else @@ -666,7 +718,11 @@ rtcan_status_t rtcan_handle_hal_error(rtcan_handle_t* rtcan_h, /* Reset the error code in the HAL handle. TX semaphore is released by the abort callback, not here — releasing it - in both places causes a double-release on every NART TX failure. */ + in both places causes a double-release on every NART TX failure. + WARNING: HAL_CAN_TxMailboxAbortCallback MUST be routed to + rtcan_handle_tx_mailbox_callback. If it is not, tx_mailbox_sem leaks + one count per aborted transmission and will eventually deadlock the + tx thread after three such events. */ rtcan_h->hcan->ErrorCode = HAL_CAN_ERROR_NONE; return RTCAN_OK; diff --git a/src/rtcan_osal_cmsis2.c b/src/rtcan_osal_cmsis2.c index 7d29145..4d52615 100644 --- a/src/rtcan_osal_cmsis2.c +++ b/src/rtcan_osal_cmsis2.c @@ -83,7 +83,7 @@ rtcan_osal_status_t rtcan_os_queue_send(rtcan_queue_t queue, { return RTCAN_OS_OK; } - else if (status == osErrorTimeout) + else if (status == osErrorTimeout || status == osErrorResource) { return RTCAN_OS_TIMEOUT; } @@ -107,7 +107,7 @@ rtcan_osal_status_t rtcan_os_queue_receive(rtcan_queue_t queue, { return RTCAN_OS_OK; } - else if (status == osErrorTimeout) + else if (status == osErrorTimeout || status == osErrorResource) { return RTCAN_OS_TIMEOUT; } @@ -153,7 +153,7 @@ rtcan_osal_status_t rtcan_os_sem_acquire(rtcan_sem_t sem, { return RTCAN_OS_OK; } - else if (status == osErrorTimeout) + else if (status == osErrorTimeout || status == osErrorResource) { return RTCAN_OS_TIMEOUT; } @@ -216,14 +216,9 @@ rtcan_osal_status_t rtcan_os_block_allocate(rtcan_block_pool_t pool, void* ptr = osMemoryPoolAlloc((osMemoryPoolId_t)pool, timeout); if (ptr == NULL) { - if (timeout == RTCAN_OS_NO_WAIT) - { - return RTCAN_OS_TIMEOUT; - } - else - { - return RTCAN_OS_ERROR; - } + /* osMemoryPoolAlloc returns NULL for both timeout and error; distinguish + by timeout value: WAIT_FOREVER only returns NULL on a genuine error. */ + return (timeout == RTCAN_OS_WAIT_FOREVER) ? RTCAN_OS_ERROR : RTCAN_OS_TIMEOUT; } *block_ptr = ptr; @@ -241,3 +236,8 @@ rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, osStatus_t status = osMemoryPoolFree((osMemoryPoolId_t)pool, block_ptr); return (status == osOK) ? RTCAN_OS_OK : RTCAN_OS_ERROR; } + +void rtcan_os_yield(void) +{ + (void) osThreadYield(); +} diff --git a/src/rtcan_osal_threadx.c b/src/rtcan_osal_threadx.c index 8b7a93d..57a2fee 100644 --- a/src/rtcan_osal_threadx.c +++ b/src/rtcan_osal_threadx.c @@ -11,14 +11,37 @@ #define RTCAN_MAX_INSTANCES 2U #endif +#ifndef RTCAN_THREADX_DEFAULT_STACK_SIZE +#define RTCAN_THREADX_DEFAULT_STACK_SIZE 1024U +#endif + +typedef struct { + rtcan_thread_entry_t entry; + void* arg; +} rtcan_thread_shim_t; + +typedef struct { + TX_SEMAPHORE tx_sem; + ULONG ceiling; +} rtcan_sem_entry_t; + /* Static control blocks allocated for ThreadX backend */ static TX_THREAD s_threads[RTCAN_MAX_INSTANCES * 2U]; -static TX_QUEUE s_queues[RTCAN_MAX_INSTANCES * 2U]; -static TX_SEMAPHORE s_sems[RTCAN_MAX_INSTANCES]; +static rtcan_sem_entry_t s_sems[RTCAN_MAX_INSTANCES * 2U]; static TX_BLOCK_POOL s_pools[RTCAN_MAX_INSTANCES]; +static uint8_t s_default_stacks[RTCAN_MAX_INSTANCES * 2U][RTCAN_THREADX_DEFAULT_STACK_SIZE]; +static rtcan_thread_shim_t s_shims[RTCAN_MAX_INSTANCES * 2U]; + +static VOID thread_entry_shim(ULONG idx) +{ + if (idx >= (RTCAN_MAX_INSTANCES * 2U)) + { + return; + } + s_shims[idx].entry(s_shims[idx].arg); +} static uint32_t s_thread_count = 0U; -static uint32_t s_queue_count = 0U; static uint32_t s_sem_count = 0U; static uint32_t s_pool_count = 0U; @@ -30,7 +53,7 @@ rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, size_t stack_size, void* stack_mem) { - if ((thread == NULL) || (entry == NULL) || (stack_mem == NULL)) + if ((thread == NULL) || (entry == NULL)) { return RTCAN_OS_ERROR; } @@ -41,12 +64,21 @@ rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, } TX_THREAD* tx_thread = &s_threads[s_thread_count]; - s_thread_count++; + + if (stack_mem == NULL) + { + stack_mem = s_default_stacks[s_thread_count]; + stack_size = RTCAN_THREADX_DEFAULT_STACK_SIZE; + } + + s_shims[s_thread_count].entry = entry; + s_shims[s_thread_count].arg = arg; + ULONG shim_idx = (ULONG)s_thread_count; UINT status = tx_thread_create(tx_thread, (CHAR*)name, - (VOID (*)(ULONG))entry, - (ULONG)arg, + thread_entry_shim, + shim_idx, stack_mem, (ULONG)stack_size, (UINT)priority, @@ -58,6 +90,7 @@ rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, { return RTCAN_OS_ERROR; } + s_thread_count++; *thread = (rtcan_thread_t)tx_thread; return RTCAN_OS_OK; @@ -75,15 +108,20 @@ rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, return RTCAN_OS_ERROR; } - if (s_queue_count >= (RTCAN_MAX_INSTANCES * 2U)) + if (queue_mem_size <= sizeof(TX_QUEUE)) { return RTCAN_OS_ERROR; } - TX_QUEUE* tx_queue = &s_queues[s_queue_count]; - s_queue_count++; + /* TX_QUEUE control block lives at the front of the caller's buffer; + item data follows immediately after. The buffer must be uint32_t- + aligned (guaranteed by the uint32_t[] array types used at call sites), + which satisfies TX_QUEUE's alignment requirement on 32-bit ARM. */ + TX_QUEUE* tx_queue = (TX_QUEUE*)queue_mem; + void* data = (uint8_t*)queue_mem + sizeof(TX_QUEUE); + ULONG data_sz = (ULONG)(queue_mem_size - sizeof(TX_QUEUE)); - UINT message_size = (UINT)(item_size / sizeof(ULONG)); + UINT message_size = (UINT)((item_size + sizeof(ULONG) - 1U) / sizeof(ULONG)); if (message_size == 0U) { message_size = 1U; @@ -92,8 +130,8 @@ rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, UINT status = tx_queue_create(tx_queue, (CHAR*)name, message_size, - queue_mem, - (ULONG)queue_mem_size); + data, + data_sz); if (status != TX_SUCCESS) { @@ -142,7 +180,7 @@ rtcan_osal_status_t rtcan_os_queue_receive(rtcan_queue_t queue, { return RTCAN_OS_OK; } - else if (status == TX_NO_INSTANCE) + else if (status == TX_QUEUE_EMPTY) { return RTCAN_OS_TIMEOUT; } @@ -164,21 +202,22 @@ rtcan_osal_status_t rtcan_os_sem_create(rtcan_sem_t* sem, return RTCAN_OS_ERROR; } - if (s_sem_count >= RTCAN_MAX_INSTANCES) + if (s_sem_count >= (RTCAN_MAX_INSTANCES * 2U)) { return RTCAN_OS_ERROR; } - TX_SEMAPHORE* tx_sem = &s_sems[s_sem_count]; - s_sem_count++; + rtcan_sem_entry_t* entry = &s_sems[s_sem_count]; - UINT status = tx_semaphore_create(tx_sem, (CHAR*)name, (ULONG)initial_count); + UINT status = tx_semaphore_create(&entry->tx_sem, (CHAR*)name, (ULONG)initial_count); if (status != TX_SUCCESS) { return RTCAN_OS_ERROR; } + entry->ceiling = (ULONG)max_count; + s_sem_count++; - *sem = (rtcan_sem_t)tx_sem; + *sem = (rtcan_sem_t)entry; return RTCAN_OS_OK; } @@ -190,7 +229,8 @@ rtcan_osal_status_t rtcan_os_sem_acquire(rtcan_sem_t sem, return RTCAN_OS_ERROR; } - UINT status = tx_semaphore_get((TX_SEMAPHORE*)sem, (ULONG)timeout); + rtcan_sem_entry_t* entry = (rtcan_sem_entry_t*)sem; + UINT status = tx_semaphore_get(&entry->tx_sem, (ULONG)timeout); if (status == TX_SUCCESS) { return RTCAN_OS_OK; @@ -212,8 +252,20 @@ rtcan_osal_status_t rtcan_os_sem_release(rtcan_sem_t sem) return RTCAN_OS_ERROR; } - UINT status = tx_semaphore_put((TX_SEMAPHORE*)sem); - return (status == TX_SUCCESS) ? RTCAN_OS_OK : RTCAN_OS_ERROR; + rtcan_sem_entry_t* entry = (rtcan_sem_entry_t*)sem; + UINT status = tx_semaphore_ceiling_put(&entry->tx_sem, entry->ceiling); + if (status == TX_SUCCESS) + { + return RTCAN_OS_OK; + } + else if (status == TX_CEILING_EXCEEDED) + { + return RTCAN_OS_ERROR; + } + else + { + return RTCAN_OS_ERROR; + } } rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, @@ -235,7 +287,6 @@ rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, } TX_BLOCK_POOL* tx_pool = &s_pools[s_pool_count]; - s_pool_count++; UINT status = tx_block_pool_create(tx_pool, (CHAR*)name, @@ -247,6 +298,7 @@ rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, { return RTCAN_OS_ERROR; } + s_pool_count++; *pool = (rtcan_block_pool_t)tx_pool; return RTCAN_OS_OK; @@ -288,3 +340,8 @@ rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, UINT status = tx_block_release(block_ptr); return (status == TX_SUCCESS) ? RTCAN_OS_OK : RTCAN_OS_ERROR; } + +void rtcan_os_yield(void) +{ + tx_thread_relinquish(); +} From 6262892f523892f904c30e029621f379293a5d6d Mon Sep 17 00:00:00 2001 From: maartin0 Date: Sat, 4 Jul 2026 00:34:46 +0100 Subject: [PATCH 6/9] Up queue length to 32 --- inc/rtcan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/rtcan.h b/inc/rtcan.h index 8117975..435e750 100644 --- a/inc/rtcan.h +++ b/inc/rtcan.h @@ -99,7 +99,7 @@ typedef struct * queue sizing constants */ #define RTCAN_TX_QUEUE_LENGTH 10U -#define RTCAN_RX_NOTIF_QUEUE_LENGTH 10U +#define RTCAN_RX_NOTIF_QUEUE_LENGTH 32U /** * @brief RTCAN configuration structure From 6f9b87c77eca264cead6e203717824cc7cc17a16 Mon Sep 17 00:00:00 2001 From: maartin0 Date: Mon, 6 Jul 2026 23:42:08 +0100 Subject: [PATCH 7/9] Fix some issues in the cmsis2 backend, and rename it to freertos since it's now freertos specific --- README.md | 6 +- inc/rtcan.h | 6 ++ inc/rtcan_osal.h | 27 ++++-- ...an_osal_cmsis2.c => rtcan_osal_freertos.c} | 94 ++++++++++++++----- 4 files changed, 98 insertions(+), 35 deletions(-) rename src/{rtcan_osal_cmsis2.c => rtcan_osal_freertos.c} (68%) diff --git a/README.md b/README.md index 00f5683..dcf54b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Designed for safety-critical Formula Student systems, the library is **RTOS-agno ## Key Features - **Operating System Abstraction Layer (OSAL):** Decoupled from any specific RTOS. Native wrappers are provided for: - - **CMSIS-RTOS v2** (e.g., FreeRTOS, RTX5, Zephyr) in `src/rtcan_osal_cmsis2.c`. + - **FreeRTOS** (via the CMSIS-RTOS v2 API) in `src/rtcan_osal_freertos.c`. Uses FreeRTOS's static allocation types directly for 100% static queues/pools, so it is not portable to other CMSIS-RTOS v2 kernels (e.g. RTX5, Zephyr). - **ThreadX** in `src/rtcan_osal_threadx.c` (retaining backward-compatibility with a 100% static control block pool). - **100% Static Allocation (MISRA C:2012 compliant):** All queues, message pools, and subscriber nodes are allocated statically at compile-time. There is no heap fragmentation or Out-of-Memory risk. - **Deterministic O(1) Lookup Table (LUT):** Replaced separate-chained collision hashmaps with a direct Lookup Table (2048 entries) for standard 11-bit CAN IDs, ensuring constant-time dispatch on the receive path. @@ -23,7 +23,7 @@ Designed for safety-critical Formula Student systems, the library is **RTOS-agno - **C11 compiler** (uses ``). - **32-bit STM32 Microcontroller** (uses STM32 HAL CAN drivers). -- **An RTOS** supported by the OSAL backends (CMSIS-RTOS v2 or ThreadX). +- **An RTOS** supported by the OSAL backends (FreeRTOS or ThreadX). --- @@ -57,7 +57,7 @@ If you prefer submodules: 2. Include the header directory `inc/` in your include paths. 3. Add `src/rtcan.c` to your build sources. 4. Add the appropriate OSAL wrapper to your build sources: - - For CMSIS-RTOS v2: `src/rtcan_osal_cmsis2.c` + - For FreeRTOS: `src/rtcan_osal_freertos.c` - For ThreadX: `src/rtcan_osal_threadx.c` > **ThreadX only:** define `RTCAN_OSAL_THREADX` in your build (e.g. `-DRTCAN_OSAL_THREADX`). This switches `RTCAN_OS_QUEUE_MEM_SIZE` to a ThreadX-specific formula that accounts for the `TX_QUEUE` control block embedded at the front of each statically-allocated queue buffer. Without it, small queues (capacity ≤ 3 for pointer-sized items) will be undersized and queue creation will fail at init. diff --git a/inc/rtcan.h b/inc/rtcan.h index 435e750..b19e50f 100644 --- a/inc/rtcan.h +++ b/inc/rtcan.h @@ -27,6 +27,12 @@ #define RTCAN_RX_MSG_POOL_SIZE 1000U // default, number of items #endif +#ifdef RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS +_Static_assert(RTCAN_RX_MSG_POOL_SIZE <= RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS, + "RTCAN_RX_MSG_POOL_SIZE exceeds the FreeRTOS OSAL backend's " + "static block pool capacity (RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS)"); +#endif + #ifndef RTCAN_MAX_SUBSCRIBERS #define RTCAN_MAX_SUBSCRIBERS 32U // default, maximum simultaneous subscriptions #endif diff --git a/inc/rtcan_osal.h b/inc/rtcan_osal.h index 9ee053d..beae1ec 100644 --- a/inc/rtcan_osal.h +++ b/inc/rtcan_osal.h @@ -27,8 +27,11 @@ typedef enum /* Minimum queue storage bytes for capacity messages of item_size bytes. ThreadX embeds a TX_QUEUE control block at the front of the caller's buffer, so the formula includes sizeof(TX_QUEUE). Requires -DRTCAN_OSAL_THREADX. - CMSIS-RTOS2 manages its own control block separately; only a 4-byte header - plus 12 bytes of per-message overhead beyond the aligned item payload is needed. */ + CMSIS-RTOS2 only needs capacity * item_size bytes of raw payload storage + here (its control block is separate, statically owned by the OSAL backend); + the extra "4 + 12 per message" below is deliberately oversized slack on + that backend (harmless, just unused buffer space), kept so a single + formula can size both backends' buffers without ever undersizing either. */ #if defined(RTCAN_OSAL_THREADX) #include #define RTCAN_OS_QUEUE_MEM_SIZE(capacity, item_size) \ @@ -36,6 +39,14 @@ typedef enum #else #define RTCAN_OS_QUEUE_MEM_SIZE(capacity, item_size) \ (4U + (uint32_t)(capacity) * (12U + (((uint32_t)(item_size) + 3U) & ~3U))) + +/* Upper bound on block_count for rtcan_os_block_pool_create() on the FreeRTOS + backend (src/rtcan_osal_freertos.c), which backs each pool with a + statically-sized freelist array of this many pointers. Not used by the + ThreadX backend, which pools directly out of the caller's buffer. */ +#ifndef RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS +#define RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS 1024U +#endif #endif /* Opaque pointer types for OS resources */ @@ -75,8 +86,10 @@ rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, * @param[in] name Queue name string * @param[in] item_size Size of each message item in bytes * @param[in] capacity Maximum number of items the queue can hold - * @param[in] queue_mem Pointer to pre-allocated queue storage (optional, can be NULL) - * @param[in] queue_mem_size Size of the pre-allocated queue storage in bytes + * @param[in] queue_mem Pointer to pre-allocated queue storage; required (non-NULL) on + * both backends. + * @param[in] queue_mem_size Size of the pre-allocated queue storage in bytes; must be at + * least capacity * item_size * * @return rtcan_osal_status_t */ @@ -155,8 +168,10 @@ rtcan_osal_status_t rtcan_os_sem_release(rtcan_sem_t sem); * @param[in] name Pool name string * @param[in] block_size Size of each memory block in bytes * @param[in] block_count Number of memory blocks in the pool - * @param[in] pool_mem Pointer to pre-allocated block pool storage (optional, can be NULL) - * @param[in] pool_mem_size Size of the pre-allocated block pool storage in bytes + * @param[in] pool_mem Pointer to pre-allocated block pool storage. Required (non-NULL) + * on both backends. + * @param[in] pool_mem_size Size of the pre-allocated block pool storage in bytes; must be + * at least block_size * block_count * * @return rtcan_osal_status_t */ diff --git a/src/rtcan_osal_cmsis2.c b/src/rtcan_osal_freertos.c similarity index 68% rename from src/rtcan_osal_cmsis2.c rename to src/rtcan_osal_freertos.c index 4d52615..80e3983 100644 --- a/src/rtcan_osal_cmsis2.c +++ b/src/rtcan_osal_freertos.c @@ -1,11 +1,30 @@ /*************************************************************************** - * @file rtcan_osal_cmsis2.c + * @file rtcan_osal_freertos.c * @author Antigravity (Google DeepMind team) - * @brief CMSIS-RTOS v2 implementation of RTCAN OSAL + * @brief FreeRTOS implementation of RTCAN OSAL (CMSIS-RTOS v2 API, using + * FreeRTOS's static allocation types directly; not portable to + * other CMSIS-RTOS v2 kernels) ***************************************************************************/ #include "rtcan_osal.h" #include +#include +#include + +#if !defined(configSUPPORT_STATIC_ALLOCATION) || (configSUPPORT_STATIC_ALLOCATION == 0) +#error "rtcan_osal_freertos.c requires configSUPPORT_STATIC_ALLOCATION == 1 in FreeRTOSConfig.h (static queue/control-block allocation)" +#endif + +#ifndef RTCAN_MAX_INSTANCES +#define RTCAN_MAX_INSTANCES 2U +#endif + +static StaticQueue_t s_queue_cbs[RTCAN_MAX_INSTANCES * 2U]; +static uint32_t s_queue_count = 0U; + +static StaticQueue_t s_pool_cbs[RTCAN_MAX_INSTANCES]; +static void* s_pool_storage[RTCAN_MAX_INSTANCES][RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS]; +static uint32_t s_pool_count = 0U; rtcan_osal_status_t rtcan_os_thread_create(rtcan_thread_t* thread, const char* name, @@ -46,24 +65,30 @@ rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, void* queue_mem, size_t queue_mem_size) { - if ((queue == NULL) || (item_size == 0U) || (capacity == 0U)) + if ((queue == NULL) || (item_size == 0U) || (capacity == 0U) || (queue_mem == NULL) || + (queue_mem_size < (capacity * item_size))) { return RTCAN_OS_ERROR; } - osMessageQueueAttr_t attr = {0}; - attr.name = name; - if (queue_mem != NULL) + if (s_queue_count >= (RTCAN_MAX_INSTANCES * 2U)) { - attr.mq_mem = queue_mem; - attr.mq_size = (uint32_t)queue_mem_size; + return RTCAN_OS_ERROR; } + osMessageQueueAttr_t attr = {0}; + attr.name = name; + attr.cb_mem = &s_queue_cbs[s_queue_count]; + attr.cb_size = (uint32_t)sizeof(s_queue_cbs[0]); + attr.mq_mem = queue_mem; + attr.mq_size = (uint32_t)(capacity * item_size); + osMessageQueueId_t mq = osMessageQueueNew((uint32_t)capacity, (uint32_t)item_size, &attr); if (mq == NULL) { return RTCAN_OS_ERROR; } + s_queue_count++; *queue = (rtcan_queue_t)mq; return RTCAN_OS_OK; @@ -181,26 +206,47 @@ rtcan_osal_status_t rtcan_os_block_pool_create(rtcan_block_pool_t* pool, void* pool_mem, size_t pool_mem_size) { - if ((pool == NULL) || (block_size == 0U) || (block_count == 0U)) + if ((pool == NULL) || + (block_size == 0U) || + (block_count == 0U) || + (pool_mem == NULL) || + (pool_mem_size < (block_size * block_count)) || + (block_count > RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS)) { return RTCAN_OS_ERROR; } - osMemoryPoolAttr_t attr = {0}; - attr.name = name; - if (pool_mem != NULL) + if (s_pool_count >= RTCAN_MAX_INSTANCES) { - attr.mp_mem = pool_mem; - attr.mp_size = (uint32_t)pool_mem_size; + return RTCAN_OS_ERROR; } - osMemoryPoolId_t mp = osMemoryPoolNew((uint32_t)block_count, (uint32_t)block_size, &attr); - if (mp == NULL) + osMessageQueueAttr_t attr = {0}; + attr.name = name; + attr.cb_mem = &s_pool_cbs[s_pool_count]; + attr.cb_size = (uint32_t)sizeof(s_pool_cbs[0]); + attr.mq_mem = s_pool_storage[s_pool_count]; + attr.mq_size = (uint32_t)(block_count * sizeof(void*)); + + osMessageQueueId_t mq = osMessageQueueNew((uint32_t)block_count, sizeof(void*), &attr); + if (mq == NULL) { return RTCAN_OS_ERROR; } - *pool = (rtcan_block_pool_t)mp; + uint8_t* base = (uint8_t*) pool_mem; + for (size_t i = 0U; i < block_count; i++) + { + void* block = (void*)(base + (i * block_size)); + if (osMessageQueuePut(mq, &block, 0U, 0U) != osOK) + { + (void) osMessageQueueDelete(mq); + return RTCAN_OS_ERROR; + } + } + s_pool_count++; + + *pool = (rtcan_block_pool_t) mq; return RTCAN_OS_OK; } @@ -213,16 +259,12 @@ rtcan_osal_status_t rtcan_os_block_allocate(rtcan_block_pool_t pool, return RTCAN_OS_ERROR; } - void* ptr = osMemoryPoolAlloc((osMemoryPoolId_t)pool, timeout); - if (ptr == NULL) + osStatus_t status = osMessageQueueGet((osMessageQueueId_t)pool, block_ptr, NULL, timeout); + if (status == osOK) { - /* osMemoryPoolAlloc returns NULL for both timeout and error; distinguish - by timeout value: WAIT_FOREVER only returns NULL on a genuine error. */ - return (timeout == RTCAN_OS_WAIT_FOREVER) ? RTCAN_OS_ERROR : RTCAN_OS_TIMEOUT; + return RTCAN_OS_OK; } - - *block_ptr = ptr; - return RTCAN_OS_OK; + return (status == osErrorTimeout || status == osErrorResource) ? RTCAN_OS_TIMEOUT : RTCAN_OS_ERROR; } rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, @@ -233,7 +275,7 @@ rtcan_osal_status_t rtcan_os_block_release(rtcan_block_pool_t pool, return RTCAN_OS_ERROR; } - osStatus_t status = osMemoryPoolFree((osMemoryPoolId_t)pool, block_ptr); + osStatus_t status = osMessageQueuePut((osMessageQueueId_t)pool, &block_ptr, 0U, 0U); return (status == osOK) ? RTCAN_OS_OK : RTCAN_OS_ERROR; } From b4ad9ab1146ec674fd2703f679a7ca6c557e0f6d Mon Sep 17 00:00:00 2001 From: maartin0 Date: Wed, 8 Jul 2026 16:58:56 +0100 Subject: [PATCH 8/9] Fix queue size in freertos osal --- src/rtcan_osal_freertos.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rtcan_osal_freertos.c b/src/rtcan_osal_freertos.c index 80e3983..6219533 100644 --- a/src/rtcan_osal_freertos.c +++ b/src/rtcan_osal_freertos.c @@ -19,7 +19,11 @@ #define RTCAN_MAX_INSTANCES 2U #endif -static StaticQueue_t s_queue_cbs[RTCAN_MAX_INSTANCES * 2U]; +#ifndef RTCAN_OSAL_MAX_QUEUES +#define RTCAN_OSAL_MAX_QUEUES ((RTCAN_MAX_INSTANCES * 2U) + 8U) +#endif + +static StaticQueue_t s_queue_cbs[RTCAN_OSAL_MAX_QUEUES]; static uint32_t s_queue_count = 0U; static StaticQueue_t s_pool_cbs[RTCAN_MAX_INSTANCES]; @@ -71,7 +75,7 @@ rtcan_osal_status_t rtcan_os_queue_create(rtcan_queue_t* queue, return RTCAN_OS_ERROR; } - if (s_queue_count >= (RTCAN_MAX_INSTANCES * 2U)) + if (s_queue_count >= RTCAN_OSAL_MAX_QUEUES) { return RTCAN_OS_ERROR; } From 7b987d8344febfdc778d612a19f3d074476e9533 Mon Sep 17 00:00:00 2001 From: maartin0 Date: Tue, 21 Jul 2026 21:42:57 +0100 Subject: [PATCH 9/9] Merge reliability fixes from claude-fixes --- inc/rtcan.h | 4 ++++ src/rtcan.c | 33 +++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/inc/rtcan.h b/inc/rtcan.h index b19e50f..f0d0eed 100644 --- a/inc/rtcan.h +++ b/inc/rtcan.h @@ -37,6 +37,10 @@ _Static_assert(RTCAN_RX_MSG_POOL_SIZE <= RTCAN_OSAL_MAX_BLOCK_POOL_BLOCKS, #define RTCAN_MAX_SUBSCRIBERS 32U // default, maximum simultaneous subscriptions #endif +#ifndef RTCAN_TX_MAILBOX_TIMEOUT_TICKS +#define RTCAN_TX_MAILBOX_TIMEOUT_TICKS 100U // ~100 ms at the project's TX_TIMER_TICKS_PER_SECOND (1000, see tx_user.h) +#endif + /** * @brief RTCAN status */ diff --git a/src/rtcan.c b/src/rtcan.c index 24a91b9..72af0ac 100644 --- a/src/rtcan.c +++ b/src/rtcan.c @@ -297,7 +297,7 @@ static rtcan_status_t transmit_internal(rtcan_handle_t* rtcan_h, } rtcan_osal_status_t os_status = rtcan_os_sem_acquire(rtcan_h->tx_mailbox_sem, - RTCAN_OS_WAIT_FOREVER); + RTCAN_TX_MAILBOX_TIMEOUT_TICKS); if (os_status != RTCAN_OS_OK) { atomic_fetch_or(&rtcan_h->err, RTCAN_ERROR_INTERNAL); @@ -716,13 +716,30 @@ rtcan_status_t rtcan_handle_hal_error(rtcan_handle_t* rtcan_h, return RTCAN_ERROR; } - /* Reset the error code in the HAL handle. - TX semaphore is released by the abort callback, not here — releasing it - in both places causes a double-release on every NART TX failure. - WARNING: HAL_CAN_TxMailboxAbortCallback MUST be routed to - rtcan_handle_tx_mailbox_callback. If it is not, tx_mailbox_sem leaks - one count per aborted transmission and will eventually deadlock the - tx thread after three such events. */ + /* Under NART, a TX mailbox that loses arbitration (ALST) or hits a bus + error (TERR) is dropped by hardware without ever invoking + HAL_CAN_TxMailboxXAbortCallback — HAL_CAN_IRQHandler only calls the + abort callback for an explicit software abort request, never for + ALST/TERR. The tx_mailbox_sem permit taken for that mailbox in + transmit_internal() would otherwise never be returned, permanently + leaking one count per event and eventually deadlocking the tx thread. + Release one permit per failed-mailbox bit before clearing the error. */ + static const uint32_t tx_fail_bits[] = { + HAL_CAN_ERROR_TX_ALST0, HAL_CAN_ERROR_TX_TERR0, + HAL_CAN_ERROR_TX_ALST1, HAL_CAN_ERROR_TX_TERR1, + HAL_CAN_ERROR_TX_ALST2, HAL_CAN_ERROR_TX_TERR2 + }; + + uint32_t error = HAL_CAN_GetError(can_h); + + for (size_t i = 0; i < (sizeof(tx_fail_bits) / sizeof(tx_fail_bits[0])); i++) + { + if ((error & tx_fail_bits[i]) != 0U) + { + (void) rtcan_os_sem_release(rtcan_h->tx_mailbox_sem); + } + } + rtcan_h->hcan->ErrorCode = HAL_CAN_ERROR_NONE; return RTCAN_OK;