diff --git a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c index 97c06dadf627..b3ddc631178b 100644 --- a/bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c +++ b/bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c @@ -16,13 +16,40 @@ f4-series only. * 2025-09-20 wdfk_prog Implemented sendmsg_nonblocking op to support framework's async TX. * 2026-02-02 wdfk_prog Drain multiple RX frames per ISR with a bounded limit. + * 2026-08-30 wdfk_prog Advertise ISR-safe TX and publish error state before TX terminal events. + * 2026-08-30 wdfk_prog Add bxCAN hardware mailbox abort support. + */ + +/* + * STM32 bxCAN integration contract + * -------------------------------- + * + * This BSP stays hardware-focused; Generic CAN owns thread/ISR synchronization, + * blocking wait policy and runtime lifetime. Only the following TX requirements + * are implemented here: + * + * - sendmsg(can, frame, mailbox) performs one immediate submit to the selected + * hardware mailbox. Busy or terminal-pending mailboxes return -RT_EBUSY. + * - sendmsg_nonblocking() scans hardware mailboxes once and never sleeps, retries + * later or enqueues a software copy. + * - RQCPx is kept asserted until TX_DONE/TX_FAIL has been published through + * rt_hw_can_isr(); a completed mailbox is therefore not reused prematurely. + * - bxCAN TSR mixes request bits with write-one-to-clear completion bits. RQCPx + * and ABRQx updates use direct WRITE_REG writes instead of read-modify-write + * SET_BIT operations, which could accidentally clear another pending terminal. + * + * SET_FILTER keeps the existing BSP loop/ID conversion. The only Generic-facing + * additions are mirroring filter_cfg->actived into FilterActivation and returning + * HAL failures so the Generic shadow is committed only after hardware accepts it. + * Other RX/configuration code keeps the existing BSP structure and HAL flow; the + * framework must not push Generic scheduling/lifecycle state into this driver. */ #include "drv_can.h" #ifdef BSP_USING_CAN -#define LOG_TAG "drv_can" +#define LOG_TAG "drv_can" #include #ifndef CAN_ISR_DRAIN_LIMIT @@ -34,91 +61,84 @@ #endif /* attention !!! baud calculation example: Tclk / ((ss + bs1 + bs2) * brp) = 36 / ((1 + 8 + 3) * 3) = 1MHz*/ -#if defined (SOC_SERIES_STM32F1)/* APB1 36MHz(max) */ -static const struct stm32_baud_rate_tab can_baud_rate_tab[] = -{ - {CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 3)}, - {CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_5TQ | CAN_BS2_3TQ | 5)}, - {CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 6)}, - {CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 12)}, - {CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 24)}, - {CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 30)}, - {CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 60)}, - {CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 150)}, - {CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 300)} +#if defined(SOC_SERIES_STM32F1)/* APB1 36MHz(max) */ +static const struct stm32_baud_rate_tab can_baud_rate_tab[] = { + { CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 3) }, + { CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_5TQ | CAN_BS2_3TQ | 5) }, + { CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 6) }, + { CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 12) }, + { CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 24) }, + { CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 30) }, + { CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 60) }, + { CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 150) }, + { CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_3TQ | 300) } }; -#elif defined (SOC_SERIES_STM32F4) /* 42MHz or 45MHz */ -#if defined(STM32F405xx) || defined(STM32F415xx) || defined(STM32F407xx)|| defined(STM32F417xx) ||\ +#elif defined(SOC_SERIES_STM32F4) /* 42MHz or 45MHz */ +#if defined(STM32F405xx) || defined(STM32F415xx) || defined(STM32F407xx) || defined(STM32F417xx) || \ defined(STM32F401xC) || defined(STM32F401xE) /* 42MHz(max) */ -static const struct stm32_baud_rate_tab can_baud_rate_tab[] = -{ - {CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 3)}, - {CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_4TQ | 4)}, - {CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 6)}, - {CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 12)}, - {CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 24)}, - {CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 30)}, - {CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 60)}, - {CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 150)}, - {CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 300)} +static const struct stm32_baud_rate_tab can_baud_rate_tab[] = { + { CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 3) }, + { CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_4TQ | 4) }, + { CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 6) }, + { CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 12) }, + { CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 24) }, + { CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 30) }, + { CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 60) }, + { CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 150) }, + { CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_4TQ | 300) } }; #else /* APB1 45MHz(max) */ -static const struct stm32_baud_rate_tab can_baud_rate_tab[] = -{ +static const struct stm32_baud_rate_tab can_baud_rate_tab[] = { #ifdef BSP_USING_CAN168M - {CAN1MBaud, (CAN_SJW_1TQ | CAN_BS1_3TQ | CAN_BS2_3TQ | 6)}, + { CAN1MBaud, (CAN_SJW_1TQ | CAN_BS1_3TQ | CAN_BS2_3TQ | 6) }, #else - {CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 3)}, + { CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 3) }, #endif - {CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_5TQ | 4)}, - {CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 6)}, - {CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 12)}, - {CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 24)}, - {CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 30)}, - {CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 60)}, - {CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 150)}, - {CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 300)} + { CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_8TQ | CAN_BS2_5TQ | 4) }, + { CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 6) }, + { CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 12) }, + { CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 24) }, + { CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 30) }, + { CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 60) }, + { CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 150) }, + { CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_5TQ | 300) } }; #endif -#elif defined (SOC_SERIES_STM32F7)/* APB1 54MHz(max) */ -static const struct stm32_baud_rate_tab can_baud_rate_tab[] = -{ - {CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 3)}, - {CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_7TQ | 4)}, - {CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 6)}, - {CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 12)}, - {CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 24)}, - {CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 30)}, - {CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 60)}, - {CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 150)}, - {CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 300)} +#elif defined(SOC_SERIES_STM32F7)/* APB1 54MHz(max) */ +static const struct stm32_baud_rate_tab can_baud_rate_tab[] = { + { CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 3) }, + { CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_9TQ | CAN_BS2_7TQ | 4) }, + { CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 6) }, + { CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 12) }, + { CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 24) }, + { CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 30) }, + { CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 60) }, + { CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 150) }, + { CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_10TQ | CAN_BS2_7TQ | 300) } }; -#elif defined (SOC_SERIES_STM32L4)/* APB1 80MHz(max) */ -static const struct stm32_baud_rate_tab can_baud_rate_tab[] = -{ - {CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_5TQ | CAN_BS2_2TQ | 10)}, - {CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_14TQ | CAN_BS2_5TQ | 5)}, - {CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_7TQ | CAN_BS2_2TQ | 16)}, - {CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 20)}, - {CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 40)}, - {CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 50)}, - {CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 100)}, - {CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 250)}, - {CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 500)} +#elif defined(SOC_SERIES_STM32L4)/* APB1 80MHz(max) */ +static const struct stm32_baud_rate_tab can_baud_rate_tab[] = { + { CAN1MBaud, (CAN_SJW_2TQ | CAN_BS1_5TQ | CAN_BS2_2TQ | 10) }, + { CAN800kBaud, (CAN_SJW_2TQ | CAN_BS1_14TQ | CAN_BS2_5TQ | 5) }, + { CAN500kBaud, (CAN_SJW_2TQ | CAN_BS1_7TQ | CAN_BS2_2TQ | 16) }, + { CAN250kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 20) }, + { CAN125kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 40) }, + { CAN100kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 50) }, + { CAN50kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 100) }, + { CAN20kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 250) }, + { CAN10kBaud, (CAN_SJW_2TQ | CAN_BS1_13TQ | CAN_BS2_2TQ | 500) } }; #endif #ifdef BSP_USING_CAN1 -static struct stm32_can drv_can1 = -{ +static struct stm32_can drv_can1 = { .name = "can1", .CanHandle.Instance = CAN1, }; #endif #ifdef BSP_USING_CAN2 -static struct stm32_can drv_can2 = -{ +static struct stm32_can drv_can2 = { "can2", .CanHandle.Instance = CAN2, }; @@ -132,7 +152,9 @@ static rt_uint32_t get_can_baud_index(rt_uint32_t baud) for (index = 0; index < len; index++) { if (can_baud_rate_tab[index].baud_rate == baud) + { return index; + } } return 0; /* default baud is CAN1MBaud */ @@ -149,9 +171,9 @@ static rt_err_t _can_config(struct rt_can_device *can, struct can_configure *cfg RT_ASSERT(drv_can); drv_can->CanHandle.Init.TimeTriggeredMode = DISABLE; - drv_can->CanHandle.Init.AutoBusOff = ENABLE; + drv_can->CanHandle.Init.AutoBusOff = DISABLE; drv_can->CanHandle.Init.AutoWakeUp = DISABLE; - drv_can->CanHandle.Init.AutoRetransmission = DISABLE; + drv_can->CanHandle.Init.AutoRetransmission = ENABLE; drv_can->CanHandle.Init.ReceiveFifoLocked = DISABLE; drv_can->CanHandle.Init.TransmitFifoPriority = ENABLE; @@ -183,7 +205,10 @@ static rt_err_t _can_config(struct rt_can_device *can, struct can_configure *cfg } /* default filter config */ - HAL_CAN_ConfigFilter(&drv_can->CanHandle, &drv_can->FilterConfig); + if (HAL_CAN_ConfigFilter(&drv_can->CanHandle, &drv_can->FilterConfig) != HAL_OK) + { + return -RT_ERROR; + } return RT_EOK; } @@ -201,7 +226,7 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) switch (cmd) { case RT_DEVICE_CTRL_CLR_INT: - argval = (rt_uint32_t) arg; + argval = (rt_uint32_t)arg; if (argval == RT_DEVICE_FLAG_INT_RX) { if (CAN1 == drv_can->CanHandle.Instance) @@ -257,7 +282,7 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) } break; case RT_DEVICE_CTRL_SET_INT: - argval = (rt_uint32_t) arg; + argval = (rt_uint32_t)arg; if (argval == RT_DEVICE_FLAG_INT_RX) { __HAL_CAN_ENABLE_IT(&drv_can->CanHandle, CAN_IT_RX_FIFO0_MSG_PENDING); @@ -334,7 +359,10 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) if (RT_NULL == arg) { /* default filter config */ - HAL_CAN_ConfigFilter(&drv_can->CanHandle, &drv_can->FilterConfig); + if (HAL_CAN_ConfigFilter(&drv_can->CanHandle, &drv_can->FilterConfig) != HAL_OK) + { + return -RT_ERROR; + } } else { @@ -393,23 +421,25 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) { /* same as CAN_FxR1 */ mask_l_tail = (filter_cfg->items[i].ide << 2) | - (filter_cfg->items[i].rtr << 1); + (filter_cfg->items[i].rtr << 1); } if (filter_cfg->items[i].ide == RT_CAN_STDID) { id_h = ((filter_cfg->items[i].id << 18) >> 13) & 0xFFFF; id_l = ((filter_cfg->items[i].id << 18) | (filter_cfg->items[i].ide << 2) | - (filter_cfg->items[i].rtr << 1)) & 0xFFFF; + (filter_cfg->items[i].rtr << 1)) & + 0xFFFF; mask_h = ((filter_cfg->items[i].mask << 21) >> 16) & 0xFFFF; mask_l = ((filter_cfg->items[i].mask << 21) | mask_l_tail) & 0xFFFF; } else if (filter_cfg->items[i].ide == RT_CAN_EXTID) { id_h = (filter_cfg->items[i].id >> 13) & 0xFFFF; - id_l = ((filter_cfg->items[i].id << 3) | - (filter_cfg->items[i].ide << 2) | - (filter_cfg->items[i].rtr << 1)) & 0xFFFF; + id_l = ((filter_cfg->items[i].id << 3) | + (filter_cfg->items[i].ide << 2) | + (filter_cfg->items[i].rtr << 1)) & + 0xFFFF; mask_h = ((filter_cfg->items[i].mask << 3) >> 16) & 0xFFFF; mask_l = ((filter_cfg->items[i].mask << 3) | mask_l_tail) & 0xFFFF; } @@ -420,18 +450,23 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) drv_can->FilterConfig.FilterMode = filter_cfg->items[i].mode; drv_can->FilterConfig.FilterFIFOAssignment = filter_cfg->items[i].rxfifo;/*rxfifo = CAN_RX_FIFO0/CAN_RX_FIFO1*/ + /* Keep hardware activation aligned with the Generic HDR shadow transaction. */ + drv_can->FilterConfig.FilterActivation = filter_cfg->actived ? ENABLE : DISABLE; /* Filter conf */ - HAL_CAN_ConfigFilter(&drv_can->CanHandle, &drv_can->FilterConfig); + if (HAL_CAN_ConfigFilter(&drv_can->CanHandle, &drv_can->FilterConfig) != HAL_OK) + { + return -RT_ERROR; + } } } break; } case RT_CAN_CMD_SET_MODE: - argval = (rt_uint32_t) arg; + argval = (rt_uint32_t)arg; if (argval != RT_CAN_MODE_NORMAL && - argval != RT_CAN_MODE_LISTEN && - argval != RT_CAN_MODE_LOOPBACK && - argval != RT_CAN_MODE_LOOPBACKANLISTEN) + argval != RT_CAN_MODE_LISTEN && + argval != RT_CAN_MODE_LOOPBACK && + argval != RT_CAN_MODE_LOOPBACKANLISTEN) { return -RT_ERROR; } @@ -442,16 +477,16 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) } break; case RT_CAN_CMD_SET_BAUD: - argval = (rt_uint32_t) arg; + argval = (rt_uint32_t)arg; if (argval != CAN1MBaud && - argval != CAN800kBaud && - argval != CAN500kBaud && - argval != CAN250kBaud && - argval != CAN125kBaud && - argval != CAN100kBaud && - argval != CAN50kBaud && - argval != CAN20kBaud && - argval != CAN10kBaud) + argval != CAN800kBaud && + argval != CAN500kBaud && + argval != CAN250kBaud && + argval != CAN125kBaud && + argval != CAN100kBaud && + argval != CAN50kBaud && + argval != CAN20kBaud && + argval != CAN10kBaud) { return -RT_ERROR; } @@ -462,9 +497,9 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) } break; case RT_CAN_CMD_SET_PRIV: - argval = (rt_uint32_t) arg; + argval = (rt_uint32_t)arg; if (argval != RT_CAN_MODE_PRIV && - argval != RT_CAN_MODE_NOPRIV) + argval != RT_CAN_MODE_NOPRIV) { return -RT_ERROR; } @@ -474,6 +509,33 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) return _can_config(&drv_can->device, &drv_can->device.config); } break; + case RT_CAN_CMD_ABORT_TX: + { + rt_uint32_t tx_mailbox; + + argval = (rt_uint32_t)(rt_ubase_t)arg; + switch (argval) + { + case 0: + tx_mailbox = CAN_TSR_ABRQ0; + break; + case 1: + tx_mailbox = CAN_TSR_ABRQ1; + break; + case 2: + tx_mailbox = CAN_TSR_ABRQ2; + break; + default: + return -RT_EINVAL; + } + + /* + * TSR completion flags are W1C. Write only ABRQx so aborting one mailbox + * cannot accidentally consume another mailbox's unreported RQCPx terminal. + */ + WRITE_REG(drv_can->CanHandle.Instance->TSR, tx_mailbox); + break; + } case RT_CAN_CMD_GET_STATUS: { rt_uint32_t errtype; @@ -487,9 +549,15 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) break; } case RT_CAN_CMD_START: - argval = (rt_uint32_t) arg; - if (argval == 0) + { + argval = (rt_uint32_t)(rt_ubase_t)arg; + + if (argval == 0U) { + /* + * Final stop/deinit. + * Generic final close will clear RT_DEVICE_FLAG_ACTIVATED. + */ if (HAL_CAN_DeInit(&drv_can->CanHandle) != HAL_OK) { LOG_E("CAN deinitialization failed"); @@ -498,18 +566,48 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) } else { - rt_err_t result = _can_config(&drv_can->device, &drv_can->device.config); - if (result != RT_EOK) + /* + * Normal rt_device_open() has already called configure() + * and left the controller in READY. + * + * Keep RESET recovery for direct START(0) -> START(1) + * control usage. + */ + if (drv_can->CanHandle.State == HAL_CAN_STATE_RESET) + { + rt_err_t result; + + result = _can_config(&drv_can->device, + &drv_can->device.config); + if (result != RT_EOK) + { + return result; + } + } + + if (drv_can->CanHandle.State == HAL_CAN_STATE_LISTENING) + { + break; + } + + if (drv_can->CanHandle.State != HAL_CAN_STATE_READY) { - return result; + LOG_E("CAN start invalid state: %d", + (int)drv_can->CanHandle.State); + return -RT_ERROR; } + if (HAL_CAN_Start(&drv_can->CanHandle) != HAL_OK) { + LOG_E("CAN start failed: state=%d error=0x%08lx", + (int)drv_can->CanHandle.State, + (unsigned long)drv_can->CanHandle.ErrorCode); return -RT_ERROR; } } + } - break; + break; } return RT_EOK; @@ -519,10 +617,9 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) * @internal * @brief Low-level function to send a CAN message to a specific hardware mailbox. * - * This function is part of the **blocking** send mechanism. It is called by - * `_can_int_tx` after a hardware mailbox has already been acquired. Its role is - * to format the message according to the STM32 hardware requirements and place - * it into the specified mailbox for transmission. + * The generic framework has already reserved @p box_num before this callback. + * This implementation only programs bxCAN registers and returns immediately; it + * does not sleep or wait for transmission completion. * * @param[in] can A pointer to the CAN device structure. * @param[in] buf A pointer to the `rt_can_msg` to be sent. @@ -533,44 +630,51 @@ static rt_err_t _can_control(struct rt_can_device *can, int cmd, void *arg) static rt_ssize_t _can_sendmsg(struct rt_can_device *can, const void *buf, rt_uint32_t box_num) { CAN_HandleTypeDef *hcan; - hcan = &((struct stm32_can *) can->parent.user_data)->CanHandle; - struct rt_can_msg *pmsg = (struct rt_can_msg *) buf; - CAN_TxHeaderTypeDef txheader = {0}; + hcan = &((struct stm32_can *)can->parent.user_data)->CanHandle; + struct rt_can_msg *pmsg = (struct rt_can_msg *)buf; + CAN_TxHeaderTypeDef txheader = { 0 }; HAL_CAN_StateTypeDef state = hcan->State; /* Check the parameters */ RT_ASSERT(IS_CAN_DLC(pmsg->len)); if ((state == HAL_CAN_STATE_READY) || - (state == HAL_CAN_STATE_LISTENING)) + (state == HAL_CAN_STATE_LISTENING)) { /*check select mailbox is empty */ - uint32_t mailbox_mask; + /* + * CAN_TX_MAILBOXx selects the mailbox; CAN_TSR_TME/RQCPx are + * status bits in TSR for that mailbox and are not interchangeable. + */ uint32_t tme_flag; + uint32_t rqcp_flag; switch (1 << box_num) { case CAN_TX_MAILBOX0: - mailbox_mask = CAN_TX_MAILBOX0; tme_flag = CAN_TSR_TME0; + rqcp_flag = CAN_TSR_RQCP0; break; case CAN_TX_MAILBOX1: - mailbox_mask = CAN_TX_MAILBOX1; tme_flag = CAN_TSR_TME1; + rqcp_flag = CAN_TSR_RQCP1; break; case CAN_TX_MAILBOX2: - mailbox_mask = CAN_TX_MAILBOX2; tme_flag = CAN_TSR_TME2; + rqcp_flag = CAN_TSR_RQCP2; break; default: - RT_ASSERT(0); - return -RT_ERROR; + return -RT_EINVAL; } - if (HAL_IS_BIT_SET(hcan->Instance->TSR, tme_flag) != SET) + /* + * Keep a completed mailbox unavailable until its previous terminal has + * been published. Otherwise a stale RQCP could complete a new Generic owner. + */ + if (HAL_IS_BIT_SET(hcan->Instance->TSR, tme_flag) != SET || + HAL_IS_BIT_SET(hcan->Instance->TSR, rqcp_flag) == SET) { - RT_UNUSED(mailbox_mask); - return -RT_ERROR; + return -RT_EBUSY; } if (RT_CAN_STDID == pmsg->ide) @@ -610,14 +714,14 @@ static rt_ssize_t _can_sendmsg(struct rt_can_device *can, const void *buf, rt_ui /* Set up the data field */ WRITE_REG(hcan->Instance->sTxMailBox[box_num].TDHR, ((uint32_t)pmsg->data[7] << CAN_TDH0R_DATA7_Pos) | - ((uint32_t)pmsg->data[6] << CAN_TDH0R_DATA6_Pos) | - ((uint32_t)pmsg->data[5] << CAN_TDH0R_DATA5_Pos) | - ((uint32_t)pmsg->data[4] << CAN_TDH0R_DATA4_Pos)); + ((uint32_t)pmsg->data[6] << CAN_TDH0R_DATA6_Pos) | + ((uint32_t)pmsg->data[5] << CAN_TDH0R_DATA5_Pos) | + ((uint32_t)pmsg->data[4] << CAN_TDH0R_DATA4_Pos)); WRITE_REG(hcan->Instance->sTxMailBox[box_num].TDLR, ((uint32_t)pmsg->data[3] << CAN_TDL0R_DATA3_Pos) | - ((uint32_t)pmsg->data[2] << CAN_TDL0R_DATA2_Pos) | - ((uint32_t)pmsg->data[1] << CAN_TDL0R_DATA1_Pos) | - ((uint32_t)pmsg->data[0] << CAN_TDL0R_DATA0_Pos)); + ((uint32_t)pmsg->data[2] << CAN_TDL0R_DATA2_Pos) | + ((uint32_t)pmsg->data[1] << CAN_TDL0R_DATA1_Pos) | + ((uint32_t)pmsg->data[0] << CAN_TDL0R_DATA0_Pos)); /* Request transmission */ SET_BIT(hcan->Instance->sTxMailBox[box_num].TIR, CAN_TI0R_TXRQ); @@ -636,47 +740,36 @@ static rt_ssize_t _can_sendmsg(struct rt_can_device *can, const void *buf, rt_ui * @internal * @brief Low-level, hardware-specific non-blocking function to send a CAN message. * - * This function interacts directly with the STM32 HAL library to add a message - * to a hardware TX mailbox. It returns immediately and does not wait for the - * transmission to complete. + * This function performs one immediate hardware attempt and returns without + * waiting, retrying, or placing the frame in a software queue. * * @param[in] can A pointer to the CAN device structure. * @param[in] buf A pointer to the `rt_can_msg` to be sent. * * @return - * - `RT_EOK` if the message was successfully accepted by the hardware. - * - `-RT_EBUSY` if all hardware mailboxes are currently full. - * - `-RT_ERROR` on other HAL failures. + * - `RT_EOK` if the message was accepted by a hardware mailbox. + * - `-RT_EBUSY` if all reusable hardware mailboxes are busy. + * - Other negative errors from the selected-mailbox submit path. */ static rt_ssize_t _can_sendmsg_nonblocking(struct rt_can_device *can, const void *buf) { - CAN_HandleTypeDef *hcan = &((struct stm32_can *) can->parent.user_data)->CanHandle; - struct rt_can_msg *pmsg = (struct rt_can_msg *) buf; - CAN_TxHeaderTypeDef txheader = {0}; - uint32_t tx_mailbox; - - if ((hcan->State != HAL_CAN_STATE_READY) && (hcan->State != HAL_CAN_STATE_LISTENING)) - return -RT_ERROR; - - if (HAL_CAN_GetTxMailboxesFreeLevel(hcan) == 0) - return -RT_EBUSY; - - txheader.DLC = pmsg->len; - txheader.RTR = (pmsg->rtr == RT_CAN_RTR) ? CAN_RTR_REMOTE : CAN_RTR_DATA; - txheader.IDE = (pmsg->ide == RT_CAN_STDID) ? CAN_ID_STD : CAN_ID_EXT; - if (txheader.IDE == CAN_ID_STD) - txheader.StdId = pmsg->id; - else - txheader.ExtId = pmsg->id; + rt_uint32_t mailbox; - HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(hcan, &txheader, pmsg->data, &tx_mailbox); - if (status != HAL_OK) + for (mailbox = 0; mailbox < can->config.sndboxnumber && mailbox < 3U; mailbox++) { - LOG_W("can sendmsg nonblocking send error %d", status); - return -RT_ERROR; + rt_ssize_t result = _can_sendmsg(can, buf, mailbox); + + if (result == RT_EOK) + { + return RT_EOK; + } + if (result != -RT_EBUSY) + { + return result; + } } - return RT_EOK; + return -RT_EBUSY; } static rt_ssize_t _can_recvmsg(struct rt_can_device *can, void *buf, rt_uint32_t fifo) @@ -684,17 +777,19 @@ static rt_ssize_t _can_recvmsg(struct rt_can_device *can, void *buf, rt_uint32_t HAL_StatusTypeDef status; CAN_HandleTypeDef *hcan; struct rt_can_msg *pmsg; - CAN_RxHeaderTypeDef rxheader = {0}; + CAN_RxHeaderTypeDef rxheader = { 0 }; RT_ASSERT(can); hcan = &((struct stm32_can *)can->parent.user_data)->CanHandle; - pmsg = (struct rt_can_msg *) buf; + pmsg = (struct rt_can_msg *)buf; /* get data */ status = HAL_CAN_GetRxMessage(hcan, fifo, &rxheader, pmsg->data); if (HAL_OK != status) + { return -RT_ERROR; + } /* get id */ if (CAN_ID_STD == rxheader.IDE) { @@ -728,7 +823,7 @@ static rt_ssize_t _can_recvmsg(struct rt_can_device *can, void *buf, rt_uint32_t #ifdef CAN2 else if (hcan->Instance == CAN2) { - pmsg->hdr_index = rxheader.FilterMatchIndex; + pmsg->hdr_index = rxheader.FilterMatchIndex; } #endif @@ -736,12 +831,11 @@ static rt_ssize_t _can_recvmsg(struct rt_can_device *can, void *buf, rt_uint32_t } -static const struct rt_can_ops _can_ops = -{ - .configure = _can_config, - .control = _can_control, - .sendmsg = _can_sendmsg, - .recvmsg = _can_recvmsg, +static const struct rt_can_ops _can_ops = { + .configure = _can_config, + .control = _can_control, + .sendmsg = _can_sendmsg, + .recvmsg = _can_recvmsg, .sendmsg_nonblocking = _can_sendmsg_nonblocking, }; @@ -749,7 +843,7 @@ static void _can_rx_isr(struct rt_can_device *can, rt_uint32_t fifo) { CAN_HandleTypeDef *hcan; RT_ASSERT(can); - hcan = &((struct stm32_can *) can->parent.user_data)->CanHandle; + hcan = &((struct stm32_can *)can->parent.user_data)->CanHandle; switch (fifo) { @@ -822,48 +916,42 @@ static void _can_check_tx_complete(struct rt_can_device *can) { CAN_HandleTypeDef *hcan; RT_ASSERT(can); - hcan = &((struct stm32_can *) can->parent.user_data)->CanHandle; + hcan = &((struct stm32_can *)can->parent.user_data)->CanHandle; - if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP0)) + if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP0) && + !__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK0)) { - if (!__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK0)) - { - rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 0 << 8); - } - SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP0); + rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 0 << 8); + WRITE_REG(hcan->Instance->TSR, CAN_TSR_RQCP0); } - if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP1)) + if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP1) && + !__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK1)) { - if (!__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK1)) - { - rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 1 << 8); - } - SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP1); + rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 1 << 8); + WRITE_REG(hcan->Instance->TSR, CAN_TSR_RQCP1); } - if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP2)) + if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP2) && + !__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK2)) { - if (!__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK2)) - { - rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 2 << 8); - } - SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP2); + rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 2 << 8); + WRITE_REG(hcan->Instance->TSR, CAN_TSR_RQCP2); } if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TERR0))/*IF AutoRetransmission = ENABLE,ACK ERR handler*/ { - SET_BIT(hcan->Instance->TSR, CAN_TSR_ABRQ0);/*Abort the send request, trigger the TX interrupt,release completion quantity*/ + WRITE_REG(hcan->Instance->TSR, CAN_TSR_ABRQ0);/*Abort the send request, trigger the TX interrupt,release completion quantity*/ } if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TERR1)) { - SET_BIT(hcan->Instance->TSR, CAN_TSR_ABRQ1); + WRITE_REG(hcan->Instance->TSR, CAN_TSR_ABRQ1); } if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TERR2)) { - SET_BIT(hcan->Instance->TSR, CAN_TSR_ABRQ2); + WRITE_REG(hcan->Instance->TSR, CAN_TSR_ABRQ2); } } @@ -871,27 +959,27 @@ static void _can_sce_isr(struct rt_can_device *can) { CAN_HandleTypeDef *hcan; RT_ASSERT(can); - hcan = &((struct stm32_can *) can->parent.user_data)->CanHandle; + hcan = &((struct stm32_can *)can->parent.user_data)->CanHandle; rt_uint32_t errtype = hcan->Instance->ESR; switch ((errtype & 0x70) >> 4) { - case RT_CAN_BUS_BIT_PAD_ERR: - can->status.bitpaderrcnt++; - break; - case RT_CAN_BUS_FORMAT_ERR: - can->status.formaterrcnt++; - break; - case RT_CAN_BUS_ACK_ERR:/* attention !!! test ack err's unit is transmit unit */ - can->status.ackerrcnt++; - break; - case RT_CAN_BUS_IMPLICIT_BIT_ERR: - case RT_CAN_BUS_EXPLICIT_BIT_ERR: - can->status.biterrcnt++; - break; - case RT_CAN_BUS_CRC_ERR: - can->status.crcerrcnt++; - break; + case RT_CAN_BUS_BIT_PAD_ERR: + can->status.bitpaderrcnt++; + break; + case RT_CAN_BUS_FORMAT_ERR: + can->status.formaterrcnt++; + break; + case RT_CAN_BUS_ACK_ERR:/* attention !!! test ack err's unit is transmit unit */ + can->status.ackerrcnt++; + break; + case RT_CAN_BUS_IMPLICIT_BIT_ERR: + case RT_CAN_BUS_EXPLICIT_BIT_ERR: + can->status.biterrcnt++; + break; + case RT_CAN_BUS_CRC_ERR: + can->status.crcerrcnt++; + break; } _can_check_tx_complete(can); @@ -922,8 +1010,12 @@ static void _can_tx_isr(struct rt_can_device *can) { CAN_HandleTypeDef *hcan; RT_ASSERT(can); - hcan = &((struct stm32_can *) can->parent.user_data)->CanHandle; + hcan = &((struct stm32_can *)can->parent.user_data)->CanHandle; + /* + * Publish the terminal before clearing RQCP. _can_sendmsg() rejects a mailbox + * while RQCP is pending, so an old terminal cannot complete a new owner. + */ if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP0)) { if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_TXOK0)) @@ -934,8 +1026,8 @@ static void _can_tx_isr(struct rt_can_device *can) { rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 0 << 8); } - /* Write 0 to Clear transmission status flag RQCPx */ - SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP0); + /* Write 1 to Clear transmission status flag RQCPx */ + WRITE_REG(hcan->Instance->TSR, CAN_TSR_RQCP0); } else if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP1)) { @@ -947,8 +1039,8 @@ static void _can_tx_isr(struct rt_can_device *can) { rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 1 << 8); } - /* Write 0 to Clear transmission status flag RQCPx */ - SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP1); + /* Write 1 to Clear transmission status flag RQCPx */ + WRITE_REG(hcan->Instance->TSR, CAN_TSR_RQCP1); } else if (__HAL_CAN_GET_FLAG(hcan, CAN_FLAG_RQCP2)) { @@ -960,8 +1052,8 @@ static void _can_tx_isr(struct rt_can_device *can) { rt_hw_can_isr(can, RT_CAN_EVENT_TX_FAIL | 2 << 8); } - /* Write 0 to Clear transmission status flag RQCPx */ - SET_BIT(hcan->Instance->TSR, CAN_TSR_RQCP2); + /* Write 1 to Clear transmission status flag RQCPx */ + WRITE_REG(hcan->Instance->TSR, CAN_TSR_RQCP2); } } @@ -1058,10 +1150,10 @@ int rt_hw_can_init(void) config.maxhdr = 14; #ifdef CAN2 config.maxhdr = 28; -#endif -#endif +#endif /* CAN2 */ +#endif /* RT_CAN_USING_HDR */ /* config default filter */ - CAN_FilterTypeDef filterConf = {0}; + CAN_FilterTypeDef filterConf = { 0 }; filterConf.FilterIdHigh = 0x0000; filterConf.FilterIdLow = 0x0000; filterConf.FilterMaskIdHigh = 0x0000; diff --git a/components/drivers/can/Kconfig b/components/drivers/can/Kconfig index 3bdc212acdca..257a79f31ee9 100644 --- a/components/drivers/can/Kconfig +++ b/components/drivers/can/Kconfig @@ -10,68 +10,43 @@ if RT_USING_CAN bool "Enable CAN hardware filter support" default n help - If your CAN controller supports hardware filtering, and you want to - use the framework to configure these filters, enable this option. + Enable framework-managed hardware filter metadata and callbacks. config RT_CAN_USING_CANFD bool "Enable CAN-FD support" default n help - Enable this to support CAN with Flexible Data-Rate. This will - increase the size of the rt_can_msg structure. + Enable CAN with Flexible Data-Rate support. + + config RT_CAN_USING_STATUS_POLLING + bool "Enable periodic CAN status polling" + default y + help + Periodically query RT_CAN_CMD_GET_STATUS and dispatch the status + indication callback. TX progress does not depend on polling. config RT_CANMSG_BOX_SZ int "Software RX message box size (in messages)" default 16 help - This sets the capacity of the software buffer (FIFO) for incoming - CAN messages. It defines how many messages can be buffered by the - driver before the application must read them. + Number of CAN frames buffered by the generic RX FIFO. config RT_CANSND_BOX_NUM - int "Number of mailboxes for blocking send" + int "Number of CAN TX mailboxes" default 1 help - This sets the number of concurrent blocking send operations that - can be in flight. It is typically matched to the number of - hardware transmission mailboxes on the CAN controller. + Number of hardware TX mailboxes visible to blocking Generic CAN TX. config RT_CANSND_MSG_TIMEOUT int "Timeout for blocking send (in OS ticks)" default 100 + range 1 2147483647 help - This sets the default time a thread will wait for a hardware - mailbox to become available when sending in blocking mode. - - config RT_CAN_NB_TX_FIFO_SIZE - int "Non-blocking send buffer size (in bytes)" - default 256 - help - This defines the size of the software ring buffer used for - non-blocking and ISR-based transmissions. When the hardware - mailboxes are full, outgoing messages are temporarily stored - in this buffer. - - To calculate a suitable size, use: - (number of messages to buffer) * sizeof(struct rt_can_msg). - For standard CAN, sizeof(struct rt_can_msg) is typically 16 bytes. - The default of 256 bytes can buffer 16 standard CAN messages. - If using CAN-FD, you will need to increase this size significantly. - - config RT_CAN_MALLOC_NB_TX_BUFFER - bool "Dynamically allocate the non-blocking send buffer" - depends on RT_USING_HEAP - default n - help - If this option is enabled (y), the non-blocking send buffer will - be allocated from the system heap at runtime when the CAN device - is opened (using rt_malloc). This saves static RAM but requires - a heap to be available. - - If this option is disabled (n), the buffer will be allocated - as a static array within the rt_can_device structure. This - consumes static RAM but guarantees the memory is always available - and avoids heap fragmentation. + One hard deadline covering mailbox wait, hardware submit and the + terminal completion wait for one blocking frame. Configure this value + above the target system's worst-case mailbox contention, CAN bus + arbitration/transmission and terminal ISR scheduling latency. The + framework does not auto-extend the timeout or recover a late terminal. endif diff --git a/components/drivers/can/SConscript b/components/drivers/can/SConscript index 28d45d016d90..0d1e99aa409d 100644 --- a/components/drivers/can/SConscript +++ b/components/drivers/can/SConscript @@ -1,12 +1,17 @@ from building import * -cwd = GetCurrentDir() -src = Glob('*.c') +cwd = GetCurrentDir() CPPPATH = [cwd + '/../include'] -if not GetDepend('RT_USING_DM'): - SrcRemove(src, ['can_dm.c']) +src = [ + 'dev_can.c', + 'can_tx.c', + 'can_rx.c', +] -group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_CAN'], CPPPATH = CPPPATH) +if GetDepend('RT_USING_DM'): + src.append('can_dm.c') + +group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_CAN'], CPPPATH = CPPPATH) Return('group') diff --git a/components/drivers/can/can_internal.h b/components/drivers/can/can_internal.h new file mode 100644 index 000000000000..4bbff6eb98db --- /dev/null +++ b/components/drivers/can/can_internal.h @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-29 wdfk_prog first version + */ + +/* + * Simplified Generic CAN private contract + * --------------------------------------- + * + * This header is the private boundary between dev_can.c, can_tx.c and can_rx.c. + * The design intentionally keeps only hardware-facing state that cannot be + * derived elsewhere; there is no TX software queue, request scheduler or + * secondary lifecycle state machine. + * + * Runtime flow: + * + * open + * -> allocate/publish RX and TX runtimes + * -> enable the requested hardware IRQ sources + * + * blocking TX + * -> reserve one fixed mailbox slot + * -> BSP sendmsg(mailbox) + * -> wait for that slot completion + * -> TX_DONE/TX_FAIL retires the hardware ownership + * + * non-blocking TX + * -> one immediate BSP sendmsg_nonblocking() attempt + * -> no Generic retry or software enqueue + * -> TX_DONE/TX_FAIL decrements the shared hardware-pending count + * + * RX IRQ + * -> acquire an RX lifetime pin under rx_lock + * -> BSP recvmsg() outside rx_lock + * -> publish into the Generic RX FIFO and snapshot callback state + * -> run callback outside rx_lock + * -> release the lifetime pin + * + * management/configuration + * -> application first stops TX producers + * -> close TX admission and verify TX idle + * -> pause RX and drain already-pinned ISR/callback work + * -> apply the BSP operation + * -> restore RX/TX admission only when the resulting hardware state is safe + * + * Synchronization: + * - can->lock serializes thread-side open/close/control operations that may sleep. + * - can->tx_lock protects TX runtime publication, mailbox slots, pending and + * accepting. TX terminal ISR handling stays inside this lock domain. + * - can->rx_lock protects RX runtime/FIFO/filter publication and rx_active_isr. + * RX hardware reads and user callbacks deliberately execute outside the lock + * while rx_active_isr keeps the referenced runtime alive. + */ + +#ifndef __CAN_INTERNAL_H__ +#define __CAN_INTERNAL_H__ + +#include + +/** Blocking ownership state for one framework-visible hardware mailbox. */ +enum rt_can_tx_slot_state +{ + RT_CAN_TX_SLOT_FREE = 0, /**< No blocking caller owns this mailbox slot. */ + RT_CAN_TX_SLOT_ACTIVE, /**< Hardware accepted the blocking frame; terminal is pending. */ + RT_CAN_TX_SLOT_DONE, /**< TX_DONE arrived; the blocking waiter still owns cleanup. */ + RT_CAN_TX_SLOT_FAILED, /**< TX_FAIL arrived; the blocking waiter still owns cleanup. */ +}; + +/** Per-mailbox blocking TX waiter state. */ +struct rt_can_tx_slot +{ + struct rt_completion completion; /**< Wakes the owner on this mailbox terminal. */ + enum rt_can_tx_slot_state state; /**< Blocking ownership/terminal state. */ +}; + +/** Simplified Generic CAN TX runtime. All fields are protected by can->tx_lock. */ +struct rt_can_tx +{ + struct rt_can_tx_slot *slots; /**< Fixed slot array, one per visible mailbox. */ + struct rt_semaphore wait_sem; /**< Bounded TX-state-change notification. */ + rt_uint16_t pending; /**< Hardware-accepted frames not yet retired. */ + rt_bool_t accepting; /**< Whether normal TX admission is open. */ +}; + +/** + * @brief Allocate and publish the Generic TX runtime. + * + * Creates one blocking slot per framework-visible hardware mailbox and a bounded + * counting TX-state-change semaphore. Tokens are wake credits, not mailbox + * reservations, so waiters must rescan TX state after waking. Publication is + * serialized by can->tx_lock. + * + * @param can CAN device being opened. + * @return RT_EOK on success, or a negative allocation/configuration error. + * @note Thread context only; this function may allocate memory and initialize IPC objects. + */ +rt_err_t rt_can_tx_open(struct rt_can_device *can); + +/** + * @brief Detach and release the Generic TX runtime during final close. + * + * The caller must have stopped new TX entry and established the final teardown + * boundary before calling this function. The runtime pointer is detached under + * can->tx_lock before its storage is freed so a late TX ISR observes RT_NULL. + * + * @param can CAN device being closed. + * @note Thread context only. This function does not drain or abort hardware TX. + */ +void rt_can_tx_close(struct rt_can_device *can); + +/** + * @brief Send one or more CAN frames through the simplified TX data path. + * + * Blocking mode uses a fixed hardware-mailbox slot and waits for its terminal + * completion. Non-blocking mode performs one immediate BSP submission and never + * enters a Generic software queue. The buffer must contain whole rt_can_msg objects. + * + * @param can CAN device used for transmission. + * @param messages Frame array to transmit. + * @param size Buffer size in bytes. + * @param blocking RT_TRUE to wait for each frame terminal, RT_FALSE for one-shot TX. + * @return Accepted/completed byte count, or a negative error if no frame was accepted. + * @note Blocking mode requires thread context; ISR callers must use non-blocking mode. + */ +rt_ssize_t rt_can_tx_write_core(struct rt_can_device *can, + const struct rt_can_msg *messages, + rt_size_t size, + rt_bool_t blocking); + +/** + * @brief Retire one hardware TX terminal reported by the BSP ISR. + * + * The mailbox index is encoded in event bits [15:8]. An ACTIVE blocking slot is + * completed for its owner; otherwise the terminal retires one accepted one-shot + * non-blocking submission. pending/statistics are updated under can->tx_lock. + * + * @param can CAN device reporting the terminal. + * @param event RT_CAN_EVENT_TX_DONE or RT_CAN_EVENT_TX_FAIL plus mailbox index. + * @note ISR-safe. This function does not sleep, submit another frame or run a scheduler. + */ +void rt_can_tx_isr_core(struct rt_can_device *can, int event); + +/** + * @brief Open or close normal TX admission. + * + * This changes only the Generic accepting gate; it does not wait for already + * accepted hardware TX and does not enable/disable BSP interrupts. + * + * @param can CAN device whose admission gate is changed. + * @param accepting RT_TRUE to allow new TX, RT_FALSE to reject new TX. + * @return RT_EOK after the update; an unpublished TX runtime is treated as a no-op. + */ +rt_err_t rt_can_tx_set_accepting(struct rt_can_device *can, rt_bool_t accepting); + +/** + * @brief Establish the TX side of a controller-management quiet window. + * + * Atomically closes new TX admission and checks the complete TX-idle invariant. + * If hardware ownership is still active, the previous admission state is restored + * before -RT_EBUSY is returned. The application is responsible for stopping TX + * producers before entering management; Generic CAN does not abort them implicitly. + * + * @param can CAN device entering a management operation. + * @param was_accepting Receives the previous admission state for later restoration. + * @return RT_EOK when TX is idle, -RT_EBUSY when work is still active, or an error. + * @note Thread-side management helper; caller serializes management with can->lock. + */ +rt_err_t rt_can_tx_begin_management(struct rt_can_device *can, rt_bool_t *was_accepting); + +/** + * @brief Query the complete Generic TX-idle invariant. + * + * Idle means pending is zero and every blocking mailbox slot is FREE. + * + * @param can CAN device to inspect. + * @return RT_TRUE when no Generic TX is hardware-owned, otherwise RT_FALSE. + * @note Safe from thread context; state is sampled under can->tx_lock. + */ +rt_bool_t rt_can_tx_is_idle(struct rt_can_device *can); + +/** + * @brief Drain TX after the caller has already established device lifetime serialization. + * + * Unlike the public rt_can_tx_drain(), this helper intentionally skips the ref_count + * misuse guard so final close can drain after the device core drops the last reference. + * + * @param can CAN device whose TX ownership is drained. + * @param timeout Maximum wait in OS ticks, or RT_WAITING_FOREVER. + * @return RT_EOK when idle, -RT_ETIMEOUT on deadline expiry, or an IPC error. + * @note Thread context only; caller must serialize final-close lifetime. + */ +rt_err_t rt_can_tx_drain_core(struct rt_can_device *can, rt_tick_t timeout); + +/** + * @brief Allocate and publish the Generic software RX FIFO. + * + * The RX runtime is published under can->rx_lock before the caller enables the + * hardware RX interrupt source. + * + * @param can CAN device being opened for interrupt RX. + * @return RT_EOK on success, or a negative allocation/configuration error. + * @note Thread context only; this function allocates memory. + */ +rt_err_t rt_can_rx_open(struct rt_can_device *can); + +/** + * @brief Detach and release the Generic RX runtime. + * + * The caller must first close RX admission and wait until rx_active_isr reaches + * zero. This function refuses teardown while an ISR/callback lifetime pin remains. + * + * @param can CAN device being closed. + * @return RT_EOK on success, -RT_EBUSY while RX is pinned, or another error. + * @note Thread context only; does not disable the BSP RX interrupt by itself. + */ +rt_err_t rt_can_rx_close(struct rt_can_device *can); + +/** + * @brief Pause RX admission and wait for already-entered RX work to finish. + * + * Admission is closed under can->rx_lock before the BSP RX interrupt is disabled. + * Disabling an IRQ does not force an ISR already executing on another CPU to exit, + * so the function waits on idle_completion until every recvmsg/callback pin drains. + * + * @param can CAN device entering a management operation. + * @return RT_EOK after RX is quiescent, or a BSP/IPC error. + * @note Thread context only; this function may block. + */ +rt_err_t rt_can_rx_pause(struct rt_can_device *can); + +/** + * @brief Resume Generic RX admission and BSP RX interrupt delivery. + * + * Software admission is published before the BSP IRQ is enabled so an immediate + * interrupt cannot observe an unready Generic RX path. IRQ-enable failure rolls + * admission back to fail closed. + * + * @param can CAN device leaving a management operation. + * @return RT_EOK on success or the BSP interrupt-control error. + * @note Thread context only. + */ +rt_err_t rt_can_rx_resume(struct rt_can_device *can); + +/** + * @brief Copy buffered frames from the Generic RX FIFO to a caller buffer. + * + * FIFO and optional filter-list bookkeeping are updated while can->rx_lock is held. + * + * @param can CAN device used for reception. + * @param messages Destination frame array. + * @param size Destination size in bytes; it must contain whole rt_can_msg objects. + * @return Number of bytes copied, or a negative validation error. + */ +rt_ssize_t rt_can_rx_read_core(struct rt_can_device *can, + struct rt_can_msg *messages, + rt_ssize_t size); + +/** + * @brief Consume one hardware RX event and publish the frame into the software FIFO. + * + * The ISR first acquires an RX lifetime pin, then calls BSP recvmsg() outside + * can->rx_lock. FIFO/HDR metadata and callback state are published under the lock; + * user callbacks execute unlocked while the lifetime pin remains held. A paused + * management waiter is woken only when the final pin is released. + * + * @param can CAN device reporting RX. + * @param event RX event with hardware FIFO index encoded in bits [15:8]. + * @param overflow RT_TRUE when the BSP also reports RX FIFO overflow. + * @note ISR-safe. The callback executes in the BSP interrupt context. + */ +void rt_can_rx_isr_core(struct rt_can_device *can, int event, rt_bool_t overflow); + +#ifdef RT_CAN_USING_HDR +/** + * @brief Commit a successfully programmed explicit filter bank to Generic HDR routing state. + * + * Existing buffered frames remain in the global RX FIFO. Frames linked to filters + * that are disabled/replaced are detached only from the filter-specific lists. + * BSP-owned auto-bank requests (hdr_bank == -1) are intentionally not mapped to + * Generic per-bank routing state. + * + * @param can CAN device whose HDR routing state is updated. + * @param filter Filter configuration already accepted by the BSP. + * @return RT_EOK on success or a validation error. + * @note Caller must already hold the management quiet window with RX paused. + */ +rt_err_t rt_can_rx_commit_filter(struct rt_can_device *can, + const struct rt_can_filter_config *filter); +#endif /* RT_CAN_USING_HDR */ + +#endif /* __CAN_INTERNAL_H__ */ diff --git a/components/drivers/can/can_rx.c b/components/drivers/can/can_rx.c new file mode 100644 index 000000000000..b9676d6cf37b --- /dev/null +++ b/components/drivers/can/can_rx.c @@ -0,0 +1,605 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-29 wdfk_prog first version + */ + +/* + * Generic CAN RX lifetime and FIFO design + * --------------------------------------- + * + * Hardware RX FIFO must be drained promptly in interrupt context, so RX keeps a + * Generic software FIFO even though TX intentionally has no software queue. + * + * RX event flow: + * BSP RX IRQ -> rt_can_rx_isr_core() + * -> under rx_lock: verify admission and rx_active_isr++ + * -> outside rx_lock: BSP recvmsg() + * -> under rx_lock: allocate/reuse FIFO node, update HDR links/statistics, + * snapshot callback function/arguments + * -> outside rx_lock: invoke the user/filter callback + * -> under rx_lock: rx_active_isr-- and signal idle when the last pin drains + * + * The lifetime pin covers both recvmsg() and the callback. This matters on SMP: + * disabling the RX interrupt prevents new delivery but does not make an ISR that + * already entered on another CPU disappear. rt_can_rx_pause() therefore closes + * admission first, disables the BSP IRQ, then waits for the existing pins to drain. + * + * can->rx_lock protects FIFO/HDR ownership, runtime publication and rx_active_isr. + * It is deliberately not held while touching hardware through recvmsg() or while + * calling user code, keeping IRQ-off critical sections bounded. + * + * When the FIFO is full, the existing policy is preserved: reuse the oldest node + * so the newest received frame is retained. Disabling a HDR filter detaches its + * filter-specific links but does not delete historical frames from the global FIFO. + */ + +#include "can_internal.h" + +#ifdef RT_CAN_USING_HDR +/** + * @brief Detach one buffered frame from its filter-specific HDR list. + * + * The caller holds rx_lock. Global RX FIFO ownership is intentionally unchanged, + * so disabling/reprogramming a filter does not discard already buffered history. + * + * @param can CAN device that owns the HDR table. + * @param message Buffered frame whose HDR link is detached. + */ +static void _rx_detach_hdr_locked(struct rt_can_device *can, + struct rt_can_msg_list *message) +{ + rt_int32_t hdr; + + if (rt_list_isempty(&message->hdrlist)) + { + return; + } + + hdr = message->data.hdr_index; + rt_list_remove(&message->hdrlist); + rt_list_init(&message->hdrlist); + + if (can->hdr != RT_NULL && hdr >= 0 && hdr < (rt_int32_t)can->config.maxhdr && + can->hdr[hdr].msgs > 0) + { + can->hdr[hdr].msgs--; + } +} +#endif /* RT_CAN_USING_HDR */ + +/** + * @brief Allocate and publish the software RX FIFO. + * + * @param can CAN device to initialize for interrupt RX. + * @return RT_EOK on success or a negative allocation/configuration error. + */ +rt_err_t rt_can_rx_open(struct rt_can_device *can) +{ + struct rt_can_rx_fifo *rx_fifo; + rt_size_t alloc_size; + rt_uint32_t i; + rt_base_t level; + + RT_ASSERT(can != RT_NULL); + + if (can->config.msgboxsz == 0 || can->ops == RT_NULL || can->ops->recvmsg == RT_NULL) + { + return -RT_EINVAL; + } + + alloc_size = sizeof(*rx_fifo) + can->config.msgboxsz * sizeof(struct rt_can_msg_list); + rx_fifo = (struct rt_can_rx_fifo *)rt_calloc(1, alloc_size); + if (rx_fifo == RT_NULL) + { + return -RT_ENOMEM; + } + + rx_fifo->buffer = (struct rt_can_msg_list *)(rx_fifo + 1); + rt_completion_init(&rx_fifo->idle_completion); + rx_fifo->freenumbers = can->config.msgboxsz; + rt_list_init(&rx_fifo->freelist); + rt_list_init(&rx_fifo->uselist); + + for (i = 0; i < can->config.msgboxsz; i++) + { + rt_list_init(&rx_fifo->buffer[i].list); +#ifdef RT_CAN_USING_HDR + rt_list_init(&rx_fifo->buffer[i].hdrlist); +#endif /* RT_CAN_USING_HDR */ + rt_list_insert_before(&rx_fifo->freelist, &rx_fifo->buffer[i].list); + } + + level = rt_spin_lock_irqsave(&can->rx_lock); + if (can->can_rx != RT_NULL) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + rt_free(rx_fifo); + return -RT_EBUSY; + } + can->can_rx = rx_fifo; + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + return RT_EOK; +} + +/** + * @brief Close RX admission, disable RX IRQ delivery, and wait for pinned RX ISR work. + * + * Admission is cleared before the BSP IRQ is disabled, so no new RX handler can + * acquire a lifetime pin. If an ISR is already outside rx_lock in recvmsg() or a + * callback, its final unpin completes idle_completion and wakes this management + * waiter without polling. + */ +rt_err_t rt_can_rx_pause(struct rt_can_device *can) +{ + struct rt_can_rx_fifo *rx_fifo; + rt_err_t result = RT_EOK; + rt_err_t wait_result; + rt_bool_t was_enabled; + rt_bool_t wait_idle; + rt_base_t level; + + if (can == RT_NULL || rt_interrupt_get_nest() > 0) + { + return -RT_EINVAL; + } + + level = rt_spin_lock_irqsave(&can->rx_lock); + rx_fifo = can->can_rx; + if (rx_fifo == RT_NULL) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + return RT_EOK; + } + + was_enabled = (can->parent.open_flag & RT_DEVICE_FLAG_INT_RX) != 0; + can->parent.open_flag &= ~RT_DEVICE_FLAG_INT_RX; + wait_idle = can->rx_active_isr != 0 ? RT_TRUE : RT_FALSE; + if (wait_idle) + { + /* + * Initialize while rx_lock still excludes the final unpin. This avoids + * losing the one transition from active pins to zero between the state + * snapshot and the management thread starting to wait. + */ + rt_completion_init(&rx_fifo->idle_completion); + } + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + if (was_enabled && can->ops != RT_NULL && can->ops->control != RT_NULL) + { + result = can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, + (void *)RT_DEVICE_FLAG_INT_RX); + } + + if (wait_idle) + { + wait_result = rt_completion_wait(&rx_fifo->idle_completion, RT_WAITING_FOREVER); + if (result == RT_EOK && wait_result != RT_EOK) + { + result = wait_result; + } + } + + return result; +} + +/** + * @brief Publish RX admission and then enable BSP RX interrupt delivery. + * + * Publishing admission first lets a pending IRQ safely acquire a lifetime pin as + * soon as hardware delivery becomes visible. IRQ-enable failure rolls admission + * back to the closed state. + * + * @param can CAN device to resume. + * @return RT_EOK on success or the BSP interrupt-control error. + */ +rt_err_t rt_can_rx_resume(struct rt_can_device *can) +{ + rt_err_t result; + rt_base_t level; + + if (can == RT_NULL || rt_interrupt_get_nest() > 0) + { + return -RT_EINVAL; + } + + level = rt_spin_lock_irqsave(&can->rx_lock); + if (can->can_rx == RT_NULL) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + return RT_EOK; + } + if (can->parent.open_flag & RT_DEVICE_FLAG_INT_RX) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + return RT_EOK; + } + + /* Publish admission before IRQ enable so the first new IRQ can be pinned. */ + can->parent.open_flag |= RT_DEVICE_FLAG_INT_RX; + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + if (can->ops == RT_NULL || can->ops->control == RT_NULL) + { + result = -RT_ENOSYS; + } + else + { + result = can->ops->control(can, RT_DEVICE_CTRL_SET_INT, + (void *)RT_DEVICE_FLAG_INT_RX); + } + + if (result != RT_EOK) + { + /* Fail closed: no RX admission may remain published after enable failure. */ + level = rt_spin_lock_irqsave(&can->rx_lock); + can->parent.open_flag &= ~RT_DEVICE_FLAG_INT_RX; + rt_spin_unlock_irqrestore(&can->rx_lock, level); + } + + return result; +} + +/** + * @brief Detach and free an RX FIFO after all pinned ISR/callback work has drained. + * + * @param can CAN device whose RX runtime is released. + * @return RT_EOK on success or -RT_EBUSY if an RX lifetime pin unexpectedly remains. + */ +rt_err_t rt_can_rx_close(struct rt_can_device *can) +{ + struct rt_can_rx_fifo *rx_fifo; + rt_base_t level; +#ifdef RT_CAN_USING_HDR + rt_uint32_t i; +#endif /* RT_CAN_USING_HDR */ + + if (can == RT_NULL) + { + return -RT_EINVAL; + } + + level = rt_spin_lock_irqsave(&can->rx_lock); + RT_ASSERT(can->rx_active_isr == 0); + rx_fifo = can->can_rx; + can->can_rx = RT_NULL; + can->parent.open_flag &= ~RT_DEVICE_FLAG_INT_RX; + +#ifdef RT_CAN_USING_HDR + if (can->hdr != RT_NULL) + { + for (i = 0; i < can->config.maxhdr; i++) + { + can->hdr[i].msgs = 0; + rt_list_init(&can->hdr[i].list); + } + } +#endif /* RT_CAN_USING_HDR */ + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + if (rx_fifo != RT_NULL) + { + rt_free(rx_fifo); + } + + return RT_EOK; +} + +/** + * @brief Copy buffered CAN frames to the caller while holding RX ownership safely. + * + * @param can CAN device used for RX. + * @param messages Destination frame buffer. + * @param size Destination size in bytes. + * @return Number of bytes copied, or a negative validation/admission error. + */ +rt_ssize_t rt_can_rx_read_core(struct rt_can_device *can, + struct rt_can_msg *messages, + rt_ssize_t size) +{ + rt_ssize_t remaining; + rt_base_t level; + + if (can == RT_NULL || messages == RT_NULL || size <= 0 || + (size % (rt_ssize_t)sizeof(struct rt_can_msg)) != 0) + { + return -RT_EINVAL; + } + + remaining = size; + while (remaining >= (rt_ssize_t)sizeof(struct rt_can_msg)) + { + struct rt_can_rx_fifo *rx_fifo; + struct rt_can_msg_list *message = RT_NULL; +#ifdef RT_CAN_USING_HDR + rt_int32_t hdr; +#endif /* RT_CAN_USING_HDR */ + + level = rt_spin_lock_irqsave(&can->rx_lock); + rx_fifo = can->can_rx; + if (rx_fifo == RT_NULL || !(can->parent.open_flag & RT_DEVICE_FLAG_INT_RX)) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + return remaining == size ? -RT_EBUSY : size - remaining; + } + +#ifdef RT_CAN_USING_HDR + hdr = messages->hdr_index; + if (hdr >= 0) + { + if (can->hdr != RT_NULL && hdr < (rt_int32_t)can->config.maxhdr && + !rt_list_isempty(&can->hdr[hdr].list)) + { + message = rt_list_entry(can->hdr[hdr].list.next, + struct rt_can_msg_list, hdrlist); + rt_list_remove(&message->list); + rt_list_init(&message->list); + _rx_detach_hdr_locked(can, message); + } + } + else +#endif /* RT_CAN_USING_HDR */ + { + if (!rt_list_isempty(&rx_fifo->uselist)) + { + message = rt_list_entry(rx_fifo->uselist.next, + struct rt_can_msg_list, list); + rt_list_remove(&message->list); + rt_list_init(&message->list); +#ifdef RT_CAN_USING_HDR + _rx_detach_hdr_locked(can, message); +#endif /* RT_CAN_USING_HDR */ + } + } + + if (message == RT_NULL) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + break; + } + + rt_memcpy(messages, &message->data, sizeof(*messages)); + rt_list_insert_before(&rx_fifo->freelist, &message->list); + rx_fifo->freenumbers++; + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + messages++; + remaining -= sizeof(struct rt_can_msg); + } + + return size - remaining; +} + +/** + * @brief Receive one hardware frame while pinning RX/HDR lifetime across lock-free work. + * + * Once admission succeeds and rx_active_isr is incremented, this ISR completes the + * already-started receive even if management closes admission concurrently. Pause + * only blocks new pins, then waits for this handler (including its callback) to unpin. + */ +void rt_can_rx_isr_core(struct rt_can_device *can, int event, rt_bool_t overflow) +{ + struct rt_can_rx_fifo *rx_fifo; + struct rt_can_msg_list *message = RT_NULL; + struct rt_can_msg received = { 0 }; + rt_size_t rx_length = 0; + rt_uint32_t fifo; + rt_ssize_t result; + rt_base_t level; + rt_bool_t wake_idle = RT_FALSE; +#ifdef RT_CAN_USING_HDR + rt_int32_t hdr = -1; + rt_err_t (*hdr_ind)(rt_device_t, void *, rt_int32_t, rt_size_t) = RT_NULL; + void *hdr_args = RT_NULL; + rt_size_t hdr_length = 0; +#endif /* RT_CAN_USING_HDR */ + + RT_ASSERT(can != RT_NULL); + + level = rt_spin_lock_irqsave(&can->rx_lock); + rx_fifo = can->can_rx; + if (rx_fifo == RT_NULL || !(can->parent.open_flag & RT_DEVICE_FLAG_INT_RX)) + { + rt_spin_unlock_irqrestore(&can->rx_lock, level); + return; + } + + can->rx_active_isr++; + if (overflow) + { + can->status.dropedrcvpkg++; + } + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + /* Hardware access is intentionally outside rx_lock. */ + fifo = ((rt_uint32_t)event) >> 8; + result = can->ops->recvmsg(can, &received, fifo); + + level = rt_spin_lock_irqsave(&can->rx_lock); + if (result < 0) + { + goto release_pin_locked; + } + + can->status.rcvpkg++; + can->status.rcvchange = 1; + + if (!rt_list_isempty(&rx_fifo->freelist)) + { + message = rt_list_entry(rx_fifo->freelist.next, + struct rt_can_msg_list, list); + rt_list_remove(&message->list); + rt_list_init(&message->list); + rx_fifo->freenumbers--; + } + else if (!rt_list_isempty(&rx_fifo->uselist)) + { + /* Preserve the existing observable policy: drop oldest, retain newest. */ + message = rt_list_entry(rx_fifo->uselist.next, + struct rt_can_msg_list, list); + rt_list_remove(&message->list); + rt_list_init(&message->list); +#ifdef RT_CAN_USING_HDR + _rx_detach_hdr_locked(can, message); +#endif /* RT_CAN_USING_HDR */ + if (!overflow) + { + can->status.dropedrcvpkg++; + } + } + + if (message == RT_NULL) + { + goto release_pin_locked; + } + + rt_memcpy(&message->data, &received, sizeof(received)); + rt_list_insert_before(&rx_fifo->uselist, &message->list); + +#ifdef RT_CAN_USING_HDR + hdr = received.hdr_index; + if (can->hdr != RT_NULL && hdr >= 0 && hdr < (rt_int32_t)can->config.maxhdr && + can->hdr[hdr].connected) + { + rt_list_insert_before(&can->hdr[hdr].list, &message->hdrlist); + can->hdr[hdr].msgs++; + if (can->hdr[hdr].filter.ind != RT_NULL) + { + /* Callback data stays valid because the RX lifetime pin remains held. */ + hdr_ind = can->hdr[hdr].filter.ind; + hdr_args = can->hdr[hdr].filter.args; + hdr_length = can->hdr[hdr].msgs * sizeof(struct rt_can_msg); + } + } +#endif /* RT_CAN_USING_HDR */ + + rx_length = (can->config.msgboxsz - rx_fifo->freenumbers) * + sizeof(struct rt_can_msg); + + /* User callbacks run without rx_lock, while rx_active_isr still pins RX/HDR. */ + rt_spin_unlock_irqrestore(&can->rx_lock, level); + +#ifdef RT_CAN_USING_HDR + if (hdr_ind != RT_NULL) + { + hdr_ind(&can->parent, hdr_args, hdr, hdr_length); + } + else +#endif /* RT_CAN_USING_HDR */ + if (can->parent.rx_indicate != RT_NULL && rx_length != 0) + { + can->parent.rx_indicate(&can->parent, rx_length); + } + + level = rt_spin_lock_irqsave(&can->rx_lock); + +release_pin_locked: + RT_ASSERT(can->rx_active_isr > 0); + can->rx_active_isr--; + /* + * pause() reinitializes idle_completion while holding rx_lock before it + * starts waiting. The final pinned ISR must signal exactly when admission + * is closed and the active count reaches zero; otherwise the management + * thread could wait forever after an ISR/callback race. + */ + if (can->rx_active_isr == 0 && !(can->parent.open_flag & RT_DEVICE_FLAG_INT_RX)) + { + wake_idle = RT_TRUE; + } + rt_spin_unlock_irqrestore(&can->rx_lock, level); + + if (wake_idle) + { + rt_completion_done(&rx_fifo->idle_completion); + } +} + +#ifdef RT_CAN_USING_HDR +/** + * @brief Commit a successful BSP filter update into Generic HDR routing state. + * + * Filter-specific links from previous programming are detached, while frames stay + * on the global RX queue with their original hdr_index metadata. + * + * @param can CAN device whose HDR routing state is updated. + * @param filter Filter transaction already accepted by the BSP. + * @return RT_EOK on success or a validation/lifetime error. + */ +rt_err_t rt_can_rx_commit_filter(struct rt_can_device *can, + const struct rt_can_filter_config *filter) +{ + rt_uint32_t i; + rt_base_t level; + + if (can == RT_NULL || filter == RT_NULL || + (filter->count != 0 && filter->items == RT_NULL)) + { + return -RT_EINVAL; + } + if (can->hdr == RT_NULL) + { + return RT_EOK; + } + + level = rt_spin_lock_irqsave(&can->rx_lock); + RT_ASSERT(can->rx_active_isr == 0); + + for (i = 0; i < filter->count; i++) + { + const struct rt_can_filter_item *item = &filter->items[i]; + struct rt_can_hdr *hdr; + + /* + * hdr_bank == -1 keeps the legacy BSP auto-allocation contract. Generic + * cannot bind per-bank software routing without an explicit bank and must + * not rewrite the BSP-owned allocation result into the caller's request. + */ + if (item->hdr_bank < 0 || item->hdr_bank >= (rt_int32_t)can->config.maxhdr) + { + continue; + } + + hdr = &can->hdr[item->hdr_bank]; + + /* + * A filter bank reprogram changes the meaning of its filter-specific + * queue. Detach frames matched by the previous programming before + * committing the new routing state; the global RX history remains available. + */ + while (!rt_list_isempty(&hdr->list)) + { + struct rt_can_msg_list *message; + + message = rt_list_entry(hdr->list.next, + struct rt_can_msg_list, hdrlist); + rt_list_remove(&message->hdrlist); + rt_list_init(&message->hdrlist); + } + hdr->msgs = 0; + rt_list_init(&hdr->list); + + if (filter->actived) + { + rt_memcpy(&hdr->filter, item, sizeof(*item)); + hdr->connected = 1; + } + else + { + /* Disable only detaches filter-specific ownership; global history remains. */ + hdr->connected = 0; + rt_memset(&hdr->filter, 0, sizeof(hdr->filter)); + } + } + + rt_spin_unlock_irqrestore(&can->rx_lock, level); + return RT_EOK; +} + + +#endif /* RT_CAN_USING_HDR */ diff --git a/components/drivers/can/can_tx.c b/components/drivers/can/can_tx.c new file mode 100644 index 000000000000..e5d99048d1b1 --- /dev/null +++ b/components/drivers/can/can_tx.c @@ -0,0 +1,733 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-29 wdfk_prog first version + */ + +/* + * Simplified Generic CAN TX design + * -------------------------------- + * + * There is no Generic TX software queue or scheduler. The hardware mailboxes are + * the queueing resource and every accepted frame must later produce exactly one + * TX_DONE/TX_FAIL terminal from the BSP. + * + * Blocking frame: + * write -> scan fixed mailbox slots -> BSP sendmsg(mailbox) + * -> slot ACTIVE + pending++ + * -> wait that slot completion + * -> TX ISR marks DONE/FAILED + pending-- + * -> waiter releases the slot to FREE + * + * Non-blocking frame: + * write/ISR -> BSP sendmsg_nonblocking() once -> pending++ on RT_EOK + * -> no retry/no software enqueue -> TX terminal -> pending-- + * + * wait_sem is a bounded counting "TX state changed" notification, not a + * mailbox-count semaphore. Its token capacity matches the number of visible + * hardware mailboxes so multiple state changes are not collapsed in the + * scan-to-wait window. Blocking senders and rt_can_tx_drain() must still recheck + * state after waking because a token does not reserve a mailbox and may be stale. + * Each blocking slot has its own completion for the terminal of that specific + * hardware mailbox. + * + * can->tx_lock protects runtime publication, accepting, pending and every slot. + * BSP sendmsg()/sendmsg_nonblocking() are required to be immediate hardware-submit + * operations: they must not sleep or wait for bus transmission completion. Holding + * tx_lock across the submit therefore closes the race where a very fast TX IRQ + * could arrive before Generic ownership is published. + * + * One blocking frame uses a single hard RT_CANSND_MSG_TIMEOUT budget across both + * mailbox contention and terminal wait. This timeout is a user configuration + * contract: it must cover the worst-case mailbox wait, CAN arbitration/transmission + * latency and terminal-ISR scheduling latency for the target system. Generic CAN + * does not auto-extend the deadline or auto-abort/recover a frame whose terminal + * arrives after it. A value shorter than the real worst case is outside the + * supported contract: late-terminal correlation and continued TX bookkeeping are + * not guaranteed until the caller performs its recovery policy. + */ + +#include "can_internal.h" + +/** + * @brief Calculate the remaining portion of one hard TX deadline. + * + * The same start tick is reused for mailbox contention and terminal completion, + * so successive waits cannot each consume a fresh timeout interval. + * + * @param start Tick captured when the frame send attempt started. + * @param timeout Total deadline in ticks, or RT_WAITING_FOREVER. + * @return Remaining ticks, zero after expiry, or RT_WAITING_FOREVER. + */ +static rt_tick_t _tx_remaining(rt_tick_t start, rt_tick_t timeout) +{ + rt_tick_t elapsed; + + if (timeout == RT_WAITING_FOREVER) + { + return RT_WAITING_FOREVER; + } + + elapsed = rt_tick_get() - start; + if (elapsed >= timeout) + { + return 0; + } + + return timeout - elapsed; +} + +/** + * @brief Test the complete Generic TX idle invariant while tx_lock is held. + * + * @param can CAN device that owns the TX runtime. + * @param tx Published TX runtime. + * @return RT_TRUE only when pending is zero and every blocking mailbox slot is FREE. + */ +static rt_bool_t _tx_idle_locked(struct rt_can_device *can, struct rt_can_tx *tx) +{ + rt_uint32_t i; + + if (tx->pending != 0) + { + return RT_FALSE; + } + + for (i = 0; i < can->config.sndboxnumber; i++) + { + if (tx->slots[i].state != RT_CAN_TX_SLOT_FREE) + { + return RT_FALSE; + } + } + + return RT_TRUE; +} + + +/** + * @brief Allocate the simplified TX runtime and publish it under tx_lock. + * + * sndchange is a 32-bit public mailbox bitmap, so reject configurations that + * cannot be represented once here instead of silently truncating every TX event. + */ +rt_err_t rt_can_tx_open(struct rt_can_device *can) +{ + struct rt_can_tx *tx; + rt_size_t alloc_size; + rt_uint32_t i; + rt_base_t level; + rt_err_t result; + + RT_ASSERT(can != RT_NULL); + + if (can->config.sndboxnumber == 0 || can->ops == RT_NULL || can->ops->sendmsg == RT_NULL) + { + return -RT_EINVAL; + } + + alloc_size = sizeof(*tx) + can->config.sndboxnumber * sizeof(struct rt_can_tx_slot); + tx = (struct rt_can_tx *)rt_calloc(1, alloc_size); + if (tx == RT_NULL) + { + return -RT_ENOMEM; + } + + tx->slots = (struct rt_can_tx_slot *)(tx + 1); + for (i = 0; i < can->config.sndboxnumber; i++) + { + rt_completion_init(&tx->slots[i].completion); + tx->slots[i].state = RT_CAN_TX_SLOT_FREE; + } + + result = rt_sem_init(&tx->wait_sem, "cantx", 0, RT_IPC_FLAG_PRIO); + if (result != RT_EOK) + { + rt_free(tx); + return result; + } + /* + * Preserve up to one wake credit per visible mailbox so distinct mailbox + * transitions cannot collapse while senders move from rescan to sleep. + * A token still means only "TX state changed"; consumers must rescan state. + */ + result = rt_sem_control(&tx->wait_sem, + RT_IPC_CMD_SET_VLIMIT, + (void *)(rt_ubase_t)can->config.sndboxnumber); + if (result != RT_EOK) + { + rt_sem_detach(&tx->wait_sem); + rt_free(tx); + return result; + } + tx->accepting = RT_TRUE; + + level = rt_spin_lock_irqsave(&can->tx_lock); + if (can->can_tx != RT_NULL) + { + rt_spin_unlock_irqrestore(&can->tx_lock, level); + rt_sem_detach(&tx->wait_sem); + rt_free(tx); + return -RT_EBUSY; + } + can->can_tx = tx; + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + return RT_EOK; +} + +/** + * @brief Detach and free the TX runtime during final device teardown. + * + * Final close has stopped normal TX API entry and attempted hardware quiesce. The + * pointer is detached under tx_lock so a late terminal ISR observes RT_NULL before + * the storage is released. + */ +void rt_can_tx_close(struct rt_can_device *can) +{ + struct rt_can_tx *tx; + rt_base_t level; + + RT_ASSERT(can != RT_NULL); + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx != RT_NULL) + { + /* Final close already stopped producer/API entry and hardware IRQ delivery. */ + tx->accepting = RT_FALSE; + can->status.sndchange = 0; + can->can_tx = RT_NULL; + } + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + if (tx != RT_NULL) + { + rt_sem_detach(&tx->wait_sem); + rt_free(tx); + } +} + +/** + * @brief Open or close TX admission under the TX synchronization domain. + * + * @param can CAN device to update. + * @param accepting New admission state. + * @return RT_EOK after the state update. + */ +rt_err_t rt_can_tx_set_accepting(struct rt_can_device *can, rt_bool_t accepting) +{ + struct rt_can_tx *tx; + rt_base_t level; + + RT_ASSERT(can != RT_NULL); + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx != RT_NULL) + { + tx->accepting = accepting; + } + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + return RT_EOK; +} + +/** + * @brief Atomically close TX admission and verify that management may proceed. + * + * A sender that acquired tx_lock first either submits and makes TX non-idle, or + * finishes before this check. A sender arriving later observes accepting=false. + * + * @param can CAN device entering a management operation. + * @param was_accepting Receives the prior admission state for a later restore. + * @return RT_EOK when TX is idle, -RT_EBUSY when hardware ownership remains. + */ +rt_err_t rt_can_tx_begin_management(struct rt_can_device *can, rt_bool_t *was_accepting) +{ + struct rt_can_tx *tx; + rt_base_t level; + + if (can == RT_NULL || was_accepting == RT_NULL) + { + return -RT_EINVAL; + } + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx == RT_NULL) + { + *was_accepting = RT_FALSE; + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return RT_EOK; + } + + *was_accepting = tx->accepting; + tx->accepting = RT_FALSE; + if (!_tx_idle_locked(can, tx)) + { + tx->accepting = *was_accepting; + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return -RT_EBUSY; + } + + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return RT_EOK; +} + +/** + * @brief Query the TX idle invariant under tx_lock. + * + * @param can CAN device to inspect. + * @return RT_TRUE when no Generic TX ownership remains. + */ +rt_bool_t rt_can_tx_is_idle(struct rt_can_device *can) +{ + struct rt_can_tx *tx; + rt_bool_t idle; + rt_base_t level; + + RT_ASSERT(can != RT_NULL); + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + idle = tx == RT_NULL ? RT_TRUE : _tx_idle_locked(can, tx); + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + return idle; +} + +/** + * @brief Send one frame with a single hard deadline across mailbox wait and TX terminal wait. + * + * wait_sem is a shared TX-state-change notification for any blocking sender that + * must rescan mailboxes. The selected slot completion is different: it belongs to + * this frame only and reports that mailbox's TX_DONE/TX_FAIL terminal. Both waits + * consume the same @p timeout budget, so mailbox contention cannot extend the + * per-frame deadline. + */ +static rt_ssize_t _tx_blocking_one(struct rt_can_device *can, + const struct rt_can_msg *message, + rt_tick_t timeout) +{ + struct rt_can_tx *tx; + struct rt_can_tx_slot *slot; + rt_tick_t start; + rt_tick_t remaining; + rt_uint32_t first; + rt_uint32_t last; + rt_uint32_t mailbox; + rt_uint32_t selected = 0; + rt_err_t wait_result; + rt_ssize_t result; + rt_base_t level; + rt_bool_t submitted; + + start = rt_tick_get(); + + for (;;) + { + submitted = RT_FALSE; + result = -RT_EBUSY; + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx == RT_NULL || !tx->accepting) + { + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return -RT_EBUSY; + } + + if (can->config.privmode == RT_CAN_MODE_PRIV) + { + if (message->priv >= can->config.sndboxnumber) + { + can->status.dropedsndpkg++; + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return -RT_EINVAL; + } + first = message->priv; + last = message->priv + 1U; + } + else + { + first = 0; + last = can->config.sndboxnumber; + } + + for (mailbox = first; mailbox < last; mailbox++) + { + slot = &tx->slots[mailbox]; + if (slot->state != RT_CAN_TX_SLOT_FREE) + { + continue; + } + + /* + * Completion reset, hardware submit, ownership publication and pending++ + * stay in one TX critical section. The TX ISR takes the same lock, so a + * very fast terminal cannot retire an unpublished blocking owner. + */ + rt_completion_init(&slot->completion); + result = can->ops->sendmsg(can, message, mailbox); + if (result == RT_EOK) + { + slot->state = RT_CAN_TX_SLOT_ACTIVE; + tx->pending++; + can->status.sndchange |= (1UL << mailbox); + selected = mailbox; + submitted = RT_TRUE; + break; + } + if (result == -RT_EBUSY) + { + continue; + } + + break; + } + + if (!submitted && result != -RT_EBUSY) + { + can->status.dropedsndpkg++; + } + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + if (submitted) + { + break; + } + if (result != -RT_EBUSY) + { + return result; + } + + remaining = _tx_remaining(start, timeout); + if (remaining == 0) + { + return -RT_ETIMEOUT; + } + + /* + * A wake only means TX state changed. Another blocking/NB sender may win + * the freed mailbox, so always rescan instead of treating the wake as ownership. + */ + wait_result = rt_sem_take(&tx->wait_sem, remaining); + if (wait_result != RT_EOK) + { + return wait_result; + } + } + + remaining = _tx_remaining(start, timeout); + if (remaining == 0) + { + wait_result = -RT_ETIMEOUT; + } + else + { + /* This completion is private to the selected blocking mailbox slot. */ + wait_result = rt_completion_wait(&tx->slots[selected].completion, remaining); + } + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx == RT_NULL) + { + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return -RT_ERROR; + } + + slot = &tx->slots[selected]; + if (slot->state == RT_CAN_TX_SLOT_DONE) + { + result = RT_EOK; + } + else if (slot->state == RT_CAN_TX_SLOT_FAILED) + { + result = -RT_ERROR; + } + else if (slot->state == RT_CAN_TX_SLOT_ACTIVE) + { + /* + * The configured hard deadline expired before a terminal was published. + * Generic CAN returns that timeout and retires its bookkeeping; it does not + * extend the deadline, abort hardware, or recover a late terminal. The user + * must configure RT_CANSND_MSG_TIMEOUT above the target system's real + * worst-case mailbox/arbitration/transmission/ISR latency. Continued TX after + * violating that contract requires caller-defined recovery before bookkeeping + * can be relied upon again. + */ + if (tx->pending > 0) + { + tx->pending--; + } + result = wait_result == RT_EOK ? -RT_ERROR : wait_result; + } + else + { + result = -RT_ERROR; + } + + slot->state = RT_CAN_TX_SLOT_FREE; + can->status.sndchange &= ~(1UL << selected); + + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + /* FREE is another TX state change, not a terminal count. */ + (void)rt_sem_release(&tx->wait_sem); + return result; +} + +/** + * @brief Perform one immediate mailbox-less hardware submission. + * + * Non-blocking TX never enters a software queue and never retries in Generic CAN. + * pending is incremented only after the BSP accepts the frame while tx_lock keeps + * a very fast terminal event from being accounted before publication. + * + * @param can CAN device used for the submission. + * @param message Frame to submit. + * @return RT_EOK on hardware acceptance, -RT_EBUSY when unavailable, or another error. + */ +static rt_ssize_t _tx_nonblocking_one(struct rt_can_device *can, + const struct rt_can_msg *message) +{ + struct rt_can_tx *tx; + rt_ssize_t result; + rt_base_t level; + + if (can->config.privmode == RT_CAN_MODE_PRIV) + { + return -RT_ENOSYS; + } + if (can->ops == RT_NULL || can->ops->sendmsg_nonblocking == RT_NULL) + { + return -RT_ENOSYS; + } + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx == RT_NULL || !tx->accepting) + { + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return -RT_EBUSY; + } + + /* The same lock keeps terminal publication behind successful pending++. */ + result = can->ops->sendmsg_nonblocking(can, message); + if (result == RT_EOK) + { + tx->pending++; + } + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + return result; +} + +/** + * @brief Send a frame array through blocking or one-shot non-blocking TX. + * + * @param can CAN device used for TX. + * @param messages Array of CAN frames. + * @param size Buffer size in bytes; it must contain whole rt_can_msg objects. + * @param blocking RT_TRUE for per-frame terminal wait, RT_FALSE for one-shot submission. + * @return Accepted byte count, or a negative error when no frame was accepted. + */ +rt_ssize_t rt_can_tx_write_core(struct rt_can_device *can, + const struct rt_can_msg *messages, + rt_size_t size, + rt_bool_t blocking) +{ + rt_size_t count; + rt_size_t i; + rt_size_t accepted = 0; + rt_ssize_t result = RT_EOK; + + RT_ASSERT(can != RT_NULL); + RT_ASSERT(messages != RT_NULL); + + if (size == 0 || (size % sizeof(struct rt_can_msg)) != 0) + { + return -RT_EINVAL; + } + + count = size / sizeof(struct rt_can_msg); + for (i = 0; i < count; i++) + { + if (blocking) + { + result = _tx_blocking_one(can, &messages[i], RT_CANSND_MSG_TIMEOUT); + } + else + { + result = _tx_nonblocking_one(can, &messages[i]); + } + + if (result != RT_EOK) + { + return accepted != 0 ? (rt_ssize_t)accepted : result; + } + accepted += sizeof(struct rt_can_msg); + } + + return (rt_ssize_t)accepted; +} + +/** + * @brief Retire one hardware TX terminal event. + * + * An ACTIVE blocking slot receives DONE/FAILED and its private completion. If no + * blocking slot owns the mailbox, the terminal retires one previously accepted + * non-blocking submission. pending and terminal statistics stay in the tx_lock domain. + * + * @param can CAN device reporting the terminal. + * @param event TX_DONE/TX_FAIL with mailbox index encoded in the upper bits. + */ +void rt_can_tx_isr_core(struct rt_can_device *can, int event) +{ + struct rt_can_tx *tx; + struct rt_can_tx_slot *slot; + rt_uint32_t mailbox; + rt_uint32_t terminal; + rt_base_t level; + rt_bool_t accounted = RT_FALSE; + + RT_ASSERT(can != RT_NULL); + + mailbox = ((rt_uint32_t)event) >> 8; + terminal = (rt_uint32_t)event & 0xffU; + + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx == RT_NULL) + { + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return; + } + + if (mailbox < can->config.sndboxnumber) + { + slot = &tx->slots[mailbox]; + if (slot->state == RT_CAN_TX_SLOT_ACTIVE) + { + slot->state = terminal == RT_CAN_EVENT_TX_DONE ? RT_CAN_TX_SLOT_DONE : RT_CAN_TX_SLOT_FAILED; + rt_completion_done(&slot->completion); + accounted = RT_TRUE; + } + } + + /* A terminal with no active blocking slot belongs to a one-shot NB submit. */ + if (accounted || tx->pending > 0) + { + if (tx->pending > 0) + { + tx->pending--; + } + if (terminal == RT_CAN_EVENT_TX_DONE) + { + can->status.sndpkg++; + } + else + { + can->status.dropedsndpkg++; + } + } + + (void)rt_sem_release(&tx->wait_sem); + rt_spin_unlock_irqrestore(&can->tx_lock, level); +} + +/** + * @brief Wait until no Generic CAN TX is hardware-owned and every blocking slot is free. + * + * The caller must stop TX producers first. wait_sem is intentionally reused as a + * state-change notification; one wake does not imply idle, so the loop must recheck + * pending and all slot states until the deadline expires. + */ +rt_err_t rt_can_tx_drain_core(struct rt_can_device *can, rt_tick_t timeout) +{ + struct rt_can_tx *tx; + rt_tick_t start; + rt_tick_t remaining; + rt_err_t result; + rt_base_t level; + + RT_ASSERT(can != RT_NULL); + + start = rt_tick_get(); + for (;;) + { + level = rt_spin_lock_irqsave(&can->tx_lock); + tx = can->can_tx; + if (tx == RT_NULL || _tx_idle_locked(can, tx)) + { + rt_spin_unlock_irqrestore(&can->tx_lock, level); + return RT_EOK; + } + rt_spin_unlock_irqrestore(&can->tx_lock, level); + + remaining = _tx_remaining(start, timeout); + if (remaining == 0) + { + return -RT_ETIMEOUT; + } + + result = rt_sem_take(&tx->wait_sem, remaining); + if (result != RT_EOK) + { + return result; + } + } +} + +/** + * @brief Public TX drain entry with a defensive device-lifetime misuse check. + * + * The check intentionally does not claim to make concurrent final close safe; callers + * must still serialize drain against rt_device_close(). It only rejects calls that + * arrive after the device core has already dropped the last reference. + */ +rt_err_t rt_can_tx_drain(struct rt_can_device *can, rt_tick_t timeout) +{ + if (can == RT_NULL || rt_interrupt_get_nest() > 0) + { + return -RT_EINVAL; + } + if (can->parent.ref_count == 0) + { + return -RT_EBUSY; + } + + return rt_can_tx_drain_core(can, timeout); +} + +/** + * @brief Request hardware abort for one Generic-visible TX mailbox. + * + * The control request itself does not retire pending ownership. The BSP must later + * report TX_FAIL for that mailbox when abort completion becomes observable. + * + * @param can CAN device whose mailbox is aborted. + * @param mailbox Generic-visible hardware mailbox index. + * @return BSP control result, -RT_EINVAL for an invalid mailbox, or -RT_ENOSYS. + */ +rt_err_t rt_can_tx_abort(struct rt_can_device *can, rt_uint32_t mailbox) +{ + if (can == RT_NULL || rt_interrupt_get_nest() > 0 || + mailbox >= can->config.sndboxnumber) + { + return -RT_EINVAL; + } + if (can->ops == RT_NULL || can->ops->control == RT_NULL) + { + return -RT_ENOSYS; + } + + return can->ops->control(can, RT_CAN_CMD_ABORT_TX, + (void *)(rt_ubase_t)mailbox); +} diff --git a/components/drivers/can/dev_can.c b/components/drivers/can/dev_can.c index 04f12620f3e5..e4682e05a2c5 100644 --- a/components/drivers/can/dev_can.c +++ b/components/drivers/can/dev_can.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2025, RT-Thread Development Team + * Copyright (c) 2006-2026, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * @@ -8,1160 +8,961 @@ * 2015-05-14 aubrcool@qq.com first version * 2015-07-06 Bernard code cleanup and remove RT_CAN_USING_LED; * 2025-09-20 wdfk_prog Implemented non-blocking, ISR-safe send logic unified under rt_device_write. + * 2026-08-29 wdfk_prog Unified blocking and non-blocking TX request handling. + * 2026-08-30 wdfk_prog Stop maintenance timer before final controller stop. */ -#include -#include -#include +/* + * Generic CAN device-adapter design + * --------------------------------- + * + * This file is the RT-Thread device-framework boundary. It owns registration, + * init/open/close, read/write routing, controller management and ISR dispatch; + * can_tx.c owns TX mailbox synchronization and can_rx.c owns RX FIFO/lifetime. + * + * Normal runtime: + * register -> init/configure -> open -> allocate RX/TX runtime -> enable IRQs + * read -> can_rx.c software FIFO + * write -> blocking mailbox+completion or one-shot non-blocking hardware submit + * BSP IRQ -> rt_hw_can_isr() -> RX/TX private core + * + * Controller-management runtime: + * application stops TX producers + * -> can->lock serializes the management operation + * -> close TX admission and verify complete TX idle + * -> pause RX and drain already-entered RX ISR/callback work + * -> call BSP configure/control + * -> resume RX, then restore TX admission + * + * Generic CAN deliberately does not retain or replay BSP hardware-filter + * programming. If a controller-mutating BSP operation clears filters, the caller + * must configure them again before depending on filtered RX. A failure after RX/TX + * are quiesced is fail-closed: the function returns the hardware error without + * reopening a data path whose controller state is uncertain. Generic CAN does not + * automatically abort active TX to make a + * configuration command succeed. + * + * Final close follows the same ownership rule: stop new TX, drain accepted TX, + * pause RX, disable IRQ/controller activity, then detach/free Generic runtimes. + * can->lock is the sleeping management mutex; tx_lock/rx_lock remain the IRQ-safe + * publication/ownership boundaries used by the data paths. + */ -#define CAN_LOCK(can) rt_mutex_take(&(can->lock), RT_WAITING_FOREVER) -#define CAN_UNLOCK(can) rt_mutex_release(&(can->lock)) +#include "can_internal.h" -static rt_err_t rt_can_init(struct rt_device *dev) -{ - rt_err_t result = RT_EOK; - struct rt_can_device *can; +#define CAN_LOCK(can) rt_mutex_take(&(can)->lock, RT_WAITING_FOREVER) +#define CAN_UNLOCK(can) rt_mutex_release(&(can)->lock) - RT_ASSERT(dev != RT_NULL); - can = (struct rt_can_device *)dev; +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) +static void cantimeout(void *arg); - /* initialize rx/tx */ - can->can_rx = RT_NULL; - can->can_tx = RT_NULL; -#ifdef RT_CAN_USING_HDR - can->hdr = RT_NULL; -#endif +/** + * @brief Query whether the periodic CAN maintenance timer is active. + * @param can CAN device whose timer is inspected. + * @return RT_TRUE when the timer is active, otherwise RT_FALSE. + */ +static rt_bool_t _can_maintenance_timer_active(struct rt_can_device *can) +{ + rt_uint32_t state = RT_TIMER_FLAG_DEACTIVATED; - /* apply configuration */ - if (can->ops->configure) - result = can->ops->configure(can, &can->config); - else - result = -RT_ENOSYS; + if (rt_timer_control(&can->timer, RT_TIMER_CTRL_GET_STATE, &state) != RT_EOK) + { + return RT_FALSE; + } - return result; + return state == RT_TIMER_FLAG_ACTIVATED; } +#endif /** - * @internal - * @brief Handles reading messages from the software RX FIFO into a user buffer. - * - * This function is called by the public `rt_can_read()` API when the device - * is opened with interrupt-driven reception enabled. It safely transfers - * messages from the internal `uselist` to the user-provided buffer. - * - * @param[in] can A pointer to the CAN device. - * @param[out] data A pointer to the destination buffer for the received messages. - * @param[in] msgs The total size in bytes of the destination buffer. - * - * @return The number of bytes actually read from the FIFO. + * @brief Initialize Generic CAN runtime pointers and apply the BSP configuration. + * @param dev RT-Thread device object. + * @return RT_EOK on success or the BSP configure error. */ -rt_inline rt_ssize_t _can_int_rx(struct rt_can_device *can, struct rt_can_msg *data, rt_ssize_t msgs) +static rt_err_t rt_can_init(struct rt_device *dev) { - rt_ssize_t size; - struct rt_can_rx_fifo *rx_fifo; - RT_ASSERT(can != RT_NULL); - size = msgs; - - rx_fifo = (struct rt_can_rx_fifo *) can->can_rx; - RT_ASSERT(rx_fifo != RT_NULL); + struct rt_can_device *can; - /* read from software FIFO */ - while (msgs / sizeof(struct rt_can_msg) > 0) + if (dev == RT_NULL) { - rt_base_t level; -#ifdef RT_CAN_USING_HDR - rt_int8_t hdr; -#endif /*RT_CAN_USING_HDR*/ - struct rt_can_msg_list *listmsg = RT_NULL; - - /* disable interrupt */ - level = rt_hw_local_irq_disable(); -#ifdef RT_CAN_USING_HDR - hdr = data->hdr_index; + return -RT_EINVAL; + } - if (hdr >= 0 && can->hdr && hdr < can->config.maxhdr && !rt_list_isempty(&can->hdr[hdr].list)) - { - listmsg = rt_list_entry(can->hdr[hdr].list.next, struct rt_can_msg_list, hdrlist); - rt_list_remove(&listmsg->list); - rt_list_remove(&listmsg->hdrlist); - if (can->hdr[hdr].msgs) - { - can->hdr[hdr].msgs--; - } - listmsg->owner = RT_NULL; - } - else if (hdr == -1) -#endif /*RT_CAN_USING_HDR*/ - { - if (!rt_list_isempty(&rx_fifo->uselist)) - { - listmsg = rt_list_entry(rx_fifo->uselist.next, struct rt_can_msg_list, list); - rt_list_remove(&listmsg->list); + can = (struct rt_can_device *)dev; + can->can_rx = RT_NULL; + can->can_tx = RT_NULL; + can->rx_active_isr = 0; #ifdef RT_CAN_USING_HDR - rt_list_remove(&listmsg->hdrlist); - if (listmsg->owner != RT_NULL && listmsg->owner->msgs) - { - listmsg->owner->msgs--; - } - listmsg->owner = RT_NULL; -#endif /*RT_CAN_USING_HDR*/ - } - else - { - /* no data, enable interrupt and break out */ - rt_hw_local_irq_enable(level); - break; - } - } - - /* enable interrupt */ - rt_hw_local_irq_enable(level); - if (listmsg != RT_NULL) - { - rt_memcpy(data, &listmsg->data, sizeof(struct rt_can_msg)); - - level = rt_hw_local_irq_disable(); - rt_list_insert_before(&rx_fifo->freelist, &listmsg->list); - rx_fifo->freenumbers++; - RT_ASSERT(rx_fifo->freenumbers <= can->config.msgboxsz); - rt_hw_local_irq_enable(level); + can->hdr = RT_NULL; +#endif /* RT_CAN_USING_HDR */ - listmsg = RT_NULL; - } - else - { - break; - } - data ++; - msgs -= sizeof(struct rt_can_msg); + if (can->ops == RT_NULL || can->ops->configure == RT_NULL) + { + return -RT_ENOSYS; } - return (size - msgs); + return can->ops->configure(can, &can->config); } +#ifdef RT_CAN_USING_HDR /** - * @internal - * @brief Handles the blocking (synchronous) transmission of CAN messages. - * - * This function is the core of the blocking send mechanism. It iterates through - * a buffer of messages to be sent. For each message, it: - * 1. Acquires a hardware mailbox resource using a semaphore. - * 2. Submits the message to the low-level driver for transmission. - * 3. Blocks the calling thread by waiting on a completion object. - * 4. Is woken up by the TX complete ISR (`rt_hw_can_isr`) when the transmission is finished. - * - * @note This function will block the calling thread and must not be called from an ISR. - * - * @param[in] can A pointer to the CAN device. - * @param[in] data A pointer to the source buffer of messages to be sent. - * @param[in] msgs The total size in bytes of the source buffer. - * - * @return The number of bytes successfully sent. + * @brief Allocate the optional Generic per-bank HDR routing table. + * @param can CAN device whose HDR table is allocated. + * @return RT_EOK on success or -RT_ENOMEM. */ -rt_inline int _can_int_tx(struct rt_can_device *can, const struct rt_can_msg *data, int msgs) +static rt_err_t _can_hdr_open(struct rt_can_device *can) { - int size; - struct rt_can_tx_fifo *tx_fifo; - - RT_ASSERT(can != RT_NULL); + rt_uint32_t i; - size = msgs; - tx_fifo = (struct rt_can_tx_fifo *) can->can_tx; - RT_ASSERT(tx_fifo != RT_NULL); - - while (msgs) - { - rt_base_t level; - rt_uint32_t no; - rt_uint32_t result; - struct rt_can_sndbxinx_list *tx_tosnd = RT_NULL; - - rt_sem_take(&(tx_fifo->sem), RT_WAITING_FOREVER); - level = rt_hw_local_irq_disable(); - tx_tosnd = rt_list_entry(tx_fifo->freelist.next, struct rt_can_sndbxinx_list, list); - RT_ASSERT(tx_tosnd != RT_NULL); - rt_list_remove(&tx_tosnd->list); - rt_hw_local_irq_enable(level); - - no = ((rt_ubase_t)tx_tosnd - (rt_ubase_t)tx_fifo->buffer) / sizeof(struct rt_can_sndbxinx_list); - tx_tosnd->result = RT_CAN_SND_RESULT_WAIT; - rt_completion_init(&tx_tosnd->completion); - can->status.sndchange |= 1<ops->sendmsg(can, data, no) != RT_EOK) - { - /* send failed. */ - level = rt_hw_local_irq_disable(); - rt_list_insert_before(&tx_fifo->freelist, &tx_tosnd->list); - rt_hw_local_irq_enable(level); - rt_sem_release(&(tx_fifo->sem)); - goto err_ret; - } - - if (rt_completion_wait(&(tx_tosnd->completion), RT_CANSND_MSG_TIMEOUT) != RT_EOK) - { - level = rt_hw_local_irq_disable(); - rt_list_insert_before(&tx_fifo->freelist, &tx_tosnd->list); - can->status.sndchange &= ~ (1<sem)); - goto err_ret; - } - - level = rt_hw_local_irq_disable(); - result = tx_tosnd->result; - if (!rt_list_isempty(&tx_tosnd->list)) - { - rt_list_remove(&tx_tosnd->list); - } - rt_list_insert_before(&tx_fifo->freelist, &tx_tosnd->list); - rt_hw_local_irq_enable(level); - rt_sem_release(&(tx_fifo->sem)); + if (can->hdr != RT_NULL || can->config.maxhdr == 0) + { + return RT_EOK; + } - if (result == RT_CAN_SND_RESULT_OK) - { - level = rt_hw_local_irq_disable(); - can->status.sndpkg++; - rt_hw_local_irq_enable(level); + can->hdr = (struct rt_can_hdr *)rt_calloc(can->config.maxhdr, sizeof(*can->hdr)); + if (can->hdr == RT_NULL) + { + return -RT_ENOMEM; + } - data ++; - msgs -= sizeof(struct rt_can_msg); - if (!msgs) break; - } - else - { -err_ret: - level = rt_hw_local_irq_disable(); - can->status.dropedsndpkg++; - rt_hw_local_irq_enable(level); - break; - } + for (i = 0; i < can->config.maxhdr; i++) + { + rt_list_init(&can->hdr[i].list); } - return (size - msgs); + return RT_EOK; } +#endif /* RT_CAN_USING_HDR */ /** - * @internal - * @brief Handles blocking transmission in "private mode". - * - * This is a specialized version of `_can_int_tx` where the target hardware mailbox - * for each message is specified by the user in the `priv` field of the `rt_can_msg` - * structure, rather than being acquired dynamically from a pool. + * @brief Allocate requested Generic CAN RX/TX resources and enable BSP interrupts. * - * @param[in] can A pointer to the CAN device. - * @param[in] data A pointer to the source buffer of messages. - * @param[in] msgs The total size in bytes of the source buffer. + * Runtime pointers are published before their IRQ sources become visible. Any + * partial-open failure disables acquired IRQ sources and rolls back only resources + * created by this open attempt. * - * @return The number of bytes successfully sent. + * @param dev RT-Thread CAN device object. + * @param oflag Requested device open flags. + * @return RT_EOK on success or the first allocation/BSP control error. */ -rt_inline int _can_int_tx_priv(struct rt_can_device *can, const struct rt_can_msg *data, int msgs) +static rt_err_t rt_can_open(struct rt_device *dev, rt_uint16_t oflag) { - int size; - rt_base_t level; - rt_uint32_t no, result; - struct rt_can_tx_fifo *tx_fifo; + struct rt_can_device *can; + rt_uint16_t previous_open_flag; + rt_bool_t tx_created = RT_FALSE; + rt_bool_t rx_created = RT_FALSE; + rt_bool_t tx_irq_enabled = RT_FALSE; + rt_bool_t rx_irq_enabled = RT_FALSE; + rt_bool_t err_irq_enabled = RT_FALSE; +#ifdef RT_CAN_USING_HDR + rt_bool_t hdr_created = RT_FALSE; +#endif /* RT_CAN_USING_HDR */ + rt_err_t result; - RT_ASSERT(can != RT_NULL); + if (dev == RT_NULL) + { + return -RT_EINVAL; + } - size = msgs; - tx_fifo = (struct rt_can_tx_fifo *) can->can_tx; - RT_ASSERT(tx_fifo != RT_NULL); + can = (struct rt_can_device *)dev; + if (can->ops == RT_NULL || can->ops->control == RT_NULL) + { + return -RT_ENOSYS; + } - while (msgs) + result = CAN_LOCK(can); + if (result != RT_EOK) { - no = data->priv; - if (no >= can->config.sndboxnumber) - { - break; - } + return result; + } - level = rt_hw_local_irq_disable(); - if ((tx_fifo->buffer[no].result != RT_CAN_SND_RESULT_OK)) - { - rt_hw_local_irq_enable(level); + previous_open_flag = dev->open_flag; + if (!(previous_open_flag & RT_DEVICE_OFLAG_OPEN) && + (can->can_tx != RT_NULL || can->can_rx != RT_NULL +#ifdef RT_CAN_USING_HDR + || can->hdr != RT_NULL +#endif /* RT_CAN_USING_HDR */ + )) + { + result = -RT_EBUSY; + goto out_unlock; + } - rt_completion_wait(&(tx_fifo->buffer[no].completion), RT_WAITING_FOREVER); - continue; - } - tx_fifo->buffer[no].result = RT_CAN_SND_RESULT_WAIT; - rt_hw_local_irq_enable(level); + /* + * Start with RX/TX admission closed while runtimes are allocated. RX resume + * later publishes admission immediately before enabling its BSP IRQ so the + * first interrupt cannot race an unpublished Generic RX path. + */ + dev->open_flag = (oflag & 0xff) & + ~(RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX); - can->status.sndchange |= 1<ops->sendmsg(can, data, no) != RT_EOK) + if ((oflag & RT_DEVICE_FLAG_INT_TX) && can->can_tx == RT_NULL) + { + result = rt_can_tx_open(can); + if (result != RT_EOK) { - continue; + goto fail; } + tx_created = RT_TRUE; + } - if (rt_completion_wait(&(tx_fifo->buffer[no].completion), RT_CANSND_MSG_TIMEOUT) != RT_EOK) +#ifdef RT_CAN_USING_HDR + if (can->hdr == RT_NULL) + { + result = _can_hdr_open(can); + if (result != RT_EOK) { - can->status.sndchange &= ~ (1<hdr != RT_NULL ? RT_TRUE : RT_FALSE; + } +#endif /* RT_CAN_USING_HDR */ - result = tx_fifo->buffer[no].result; - if (result == RT_CAN_SND_RESULT_OK) + if ((oflag & RT_DEVICE_FLAG_INT_RX) && can->can_rx == RT_NULL) + { + result = rt_can_rx_open(can); + if (result != RT_EOK) { - level = rt_hw_local_irq_disable(); - can->status.sndpkg++; - rt_hw_local_irq_enable(level); - data ++; - msgs -= sizeof(struct rt_can_msg); - if (!msgs) break; + goto fail; } - else + rx_created = RT_TRUE; + + result = rt_can_rx_resume(can); + if (result != RT_EOK) { - level = rt_hw_local_irq_disable(); - can->status.dropedsndpkg++; - rt_hw_local_irq_enable(level); - break; + goto fail; } + rx_irq_enabled = RT_TRUE; } - return (size - msgs); -} - -/** - * @internal - * @brief Internal implementation of non-blocking CAN transmission. - * - * This function iterates through a buffer of CAN messages and attempts to send each one - * using a non-blocking strategy. It first tries to send directly via hardware (fast path). - * If the hardware is busy, it enqueues the message into a software ring buffer (slow path). - * This function is thread-safe and ISR-safe due to the use of critical sections for - * accessing the shared ring buffer. - * - * @param[in] can A pointer to the CAN device. - * @param[in] pmsg A pointer to the buffer of `rt_can_msg` structures. - * @param[in] size The total size of the buffer in bytes. - * - * @return The number of bytes successfully sent or enqueued for later transmission. - */ -static rt_ssize_t _can_nonblocking_tx(struct rt_can_device *can, const struct rt_can_msg *pmsg, rt_size_t size) -{ - rt_ssize_t sent_size = 0; - rt_base_t level; + if (tx_created) + { + result = can->ops->control(can, RT_DEVICE_CTRL_SET_INT, + (void *)RT_DEVICE_FLAG_INT_TX); + if (result != RT_EOK) + { + goto fail; + } + dev->open_flag |= RT_DEVICE_FLAG_INT_TX; + tx_irq_enabled = RT_TRUE; + } - if (can->ops->sendmsg_nonblocking == RT_NULL) + result = can->ops->control(can, RT_DEVICE_CTRL_SET_INT, + (void *)RT_DEVICE_CAN_INT_ERR); + if (result != RT_EOK) { - return -RT_EINVAL; + goto fail; } + err_irq_enabled = RT_TRUE; - while (sent_size < size) +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) + if (!_can_maintenance_timer_active(can)) { - if (can->ops->sendmsg_nonblocking(can, pmsg) == RT_EOK) - { - pmsg++; - sent_size += sizeof(struct rt_can_msg); - continue; - } + (void)rt_timer_start(&can->timer); + } +#endif - level = rt_hw_local_irq_disable(); - if (rt_ringbuffer_space_len(&can->nb_tx_rb) >= sizeof(struct rt_can_msg)) - { - rt_ringbuffer_put(&can->nb_tx_rb, (rt_uint8_t *)pmsg, sizeof(struct rt_can_msg)); - rt_hw_local_irq_enable(level); + CAN_UNLOCK(can); + return RT_EOK; - pmsg++; - sent_size += sizeof(struct rt_can_msg); - } - else - { - /* Buffer is full, cannot process this message or subsequent ones. */ - can->status.dropedsndpkg += (size - sent_size) / sizeof(struct rt_can_msg); - rt_hw_local_irq_enable(level); - break; - } +fail: + if (err_irq_enabled) + { + (void)can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, + (void *)RT_DEVICE_CAN_INT_ERR); } + if (tx_irq_enabled) + { + (void)can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, + (void *)RT_DEVICE_FLAG_INT_TX); + } + if (rx_irq_enabled) + { + (void)rt_can_rx_pause(can); + } + if (rx_created) + { + (void)rt_can_rx_close(can); + } +#ifdef RT_CAN_USING_HDR + if (hdr_created) + { + rt_free(can->hdr); + can->hdr = RT_NULL; + } +#endif /* RT_CAN_USING_HDR */ + if (tx_created) + { + rt_can_tx_close(can); + } + dev->open_flag = previous_open_flag; - return sent_size; +out_unlock: + CAN_UNLOCK(can); + return result; } + /** - * @internal - * @brief Opens the CAN device and initializes its resources. - * - * This function is called when `rt_device_open()` is invoked on a CAN device. - * It allocates and initializes software FIFOs for reception and transmission, - * sets up semaphores for the blocking send mechanism, configures the non-blocking - * send buffer, and starts the periodic status timer. - * - * @param[in] dev A pointer to the device to be opened. - * @param[in] oflag The open flags, e.g., `RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_INT_TX`. + * @brief Perform final CAN software teardown after the application has stopped TX producers. * - * @return `RT_EOK` on successful opening, or an error code on failure. + * Recoverable drain/abort/recovery decisions belong before rt_device_close(). The + * RT-Thread device core owns ref_count and decrements the final reference before + * entering this callback, so Generic CAN never writes ref_count to manufacture a + * retry. Non-STOP cleanup errors are collected while teardown proceeds. A BSP + * STOP failure is different: it is returned immediately after the quiet/IRQ-off + * boundary, with Generic runtimes still allocated, so recovery remains user/BSP + * policy instead of being hidden behind framework retry or state shadowing. */ -static rt_err_t rt_can_open(struct rt_device *dev, rt_uint16_t oflag) +static rt_err_t rt_can_close(struct rt_device *dev) { struct rt_can_device *can; - char tmpname[16]; - RT_ASSERT(dev != RT_NULL); - can = (struct rt_can_device *)dev; - - CAN_LOCK(can); + rt_uint16_t close_open_flag; + rt_err_t result = RT_EOK; + rt_err_t tmp_result; - /* get open flags */ - dev->open_flag = oflag & 0xff; - if (can->can_rx == RT_NULL) + if (dev == RT_NULL) { - if (oflag & RT_DEVICE_FLAG_INT_RX) - { - int i = 0; - struct rt_can_rx_fifo *rx_fifo; - - rx_fifo = (struct rt_can_rx_fifo *) rt_malloc(sizeof(struct rt_can_rx_fifo) + - can->config.msgboxsz * sizeof(struct rt_can_msg_list)); - RT_ASSERT(rx_fifo != RT_NULL); - - rx_fifo->buffer = (struct rt_can_msg_list *)(rx_fifo + 1); - rt_memset(rx_fifo->buffer, 0, can->config.msgboxsz * sizeof(struct rt_can_msg_list)); - rt_list_init(&rx_fifo->freelist); - rt_list_init(&rx_fifo->uselist); - rx_fifo->freenumbers = can->config.msgboxsz; - for (i = 0; i < can->config.msgboxsz; i++) - { - rt_list_insert_before(&rx_fifo->freelist, &rx_fifo->buffer[i].list); -#ifdef RT_CAN_USING_HDR - rt_list_init(&rx_fifo->buffer[i].hdrlist); - rx_fifo->buffer[i].owner = RT_NULL; -#endif - } - can->can_rx = rx_fifo; - - dev->open_flag |= RT_DEVICE_FLAG_INT_RX; - /* open can rx interrupt */ - can->ops->control(can, RT_DEVICE_CTRL_SET_INT, (void *)RT_DEVICE_FLAG_INT_RX); - } + return -RT_EINVAL; } - if (can->can_tx == RT_NULL) + can = (struct rt_can_device *)dev; + tmp_result = CAN_LOCK(can); + if (tmp_result != RT_EOK) { - if (oflag & RT_DEVICE_FLAG_INT_TX) - { - int i = 0; - struct rt_can_tx_fifo *tx_fifo; - - tx_fifo = (struct rt_can_tx_fifo *) rt_malloc(sizeof(struct rt_can_tx_fifo) + - can->config.sndboxnumber * sizeof(struct rt_can_sndbxinx_list)); - RT_ASSERT(tx_fifo != RT_NULL); - - tx_fifo->buffer = (struct rt_can_sndbxinx_list *)(tx_fifo + 1); - rt_memset(tx_fifo->buffer, 0, - can->config.sndboxnumber * sizeof(struct rt_can_sndbxinx_list)); - rt_list_init(&tx_fifo->freelist); - for (i = 0; i < can->config.sndboxnumber; i++) - { - rt_list_insert_before(&tx_fifo->freelist, &tx_fifo->buffer[i].list); - rt_completion_init(&(tx_fifo->buffer[i].completion)); - tx_fifo->buffer[i].result = RT_CAN_SND_RESULT_OK; - } - - rt_sprintf(tmpname, "%stl", dev->parent.name); - rt_sem_init(&(tx_fifo->sem), tmpname, can->config.sndboxnumber, RT_IPC_FLAG_FIFO); - can->can_tx = tx_fifo; - - dev->open_flag |= RT_DEVICE_FLAG_INT_TX; - /* open can tx interrupt */ - can->ops->control(can, RT_DEVICE_CTRL_SET_INT, (void *)RT_DEVICE_FLAG_INT_TX); - } + return tmp_result; } - can->ops->control(can, RT_DEVICE_CTRL_SET_INT, (void *)RT_DEVICE_CAN_INT_ERR); + close_open_flag = dev->open_flag; + (void)rt_can_tx_set_accepting(can, RT_FALSE); -#ifdef RT_CAN_USING_HDR - if (can->hdr == RT_NULL) + /* + * Recoverable finite drain/abort policy belongs before rt_device_close(). + * Final close waits for ownership retirement instead of modifying core-owned + * ref_count to manufacture a retry path. + */ + tmp_result = rt_can_tx_drain_core(can, RT_WAITING_FOREVER); + if (result == RT_EOK && tmp_result != RT_EOK) { - int i = 0; - struct rt_can_hdr *phdr; + result = tmp_result; + } - phdr = (struct rt_can_hdr *) rt_malloc(can->config.maxhdr * sizeof(struct rt_can_hdr)); - RT_ASSERT(phdr != RT_NULL); - rt_memset(phdr, 0, can->config.maxhdr * sizeof(struct rt_can_hdr)); - for (i = 0; i < can->config.maxhdr; i++) +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) + if (_can_maintenance_timer_active(can)) + { + tmp_result = rt_timer_stop(&can->timer); + if (result == RT_EOK && tmp_result != RT_EOK) { - rt_list_init(&phdr[i].list); + result = tmp_result; } - - can->hdr = phdr; } #endif -#ifdef RT_CAN_MALLOC_NB_TX_BUFFER - can->nb_tx_rb_pool = (rt_uint8_t *)rt_malloc(RT_CAN_NB_TX_FIFO_SIZE); - RT_ASSERT(can->nb_tx_rb_pool != RT_NULL); -#endif /* RT_CAN_MALLOC_NB_TX_BUFFER */ - rt_ringbuffer_init(&can->nb_tx_rb, can->nb_tx_rb_pool, RT_CAN_NB_TX_FIFO_SIZE); - - if (!can->timerinitflag) + tmp_result = rt_can_rx_pause(can); + if (result == RT_EOK && tmp_result != RT_EOK) { - can->timerinitflag = 1; - - rt_timer_start(&can->timer); + result = tmp_result; } - CAN_UNLOCK(can); - - return RT_EOK; -} - -static rt_err_t rt_can_close(struct rt_device *dev) -{ - struct rt_can_device *can; - - RT_ASSERT(dev != RT_NULL); - can = (struct rt_can_device *)dev; - - CAN_LOCK(can); - - /* this device has more reference count */ - if (dev->ref_count > 1) + if (close_open_flag & RT_DEVICE_FLAG_INT_TX) { - CAN_UNLOCK(can); - return RT_EOK; + tmp_result = can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, + (void *)RT_DEVICE_FLAG_INT_TX); + if (result == RT_EOK && tmp_result != RT_EOK) + { + result = tmp_result; + } } -#ifdef RT_CAN_MALLOC_NB_TX_BUFFER - if (can->nb_tx_rb_pool) + tmp_result = can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, + (void *)RT_DEVICE_CAN_INT_ERR); + if (result == RT_EOK && tmp_result != RT_EOK) { - rt_free(can->nb_tx_rb_pool); - can->nb_tx_rb_pool = RT_NULL; + result = tmp_result; } -#endif - if (can->timerinitflag) + /* + * STOP failure is reported directly to the caller. Generic CAN does not retry, + * roll back the quiet window, recover the controller, or keep a second + * START/STOP state shadow. The user/BSP owns the recovery decision. + */ + tmp_result = can->ops->control(can, RT_CAN_CMD_START, (void *)(rt_ubase_t)0); + if (tmp_result != RT_EOK) { - can->timerinitflag = 0; - - rt_timer_stop(&can->timer); + CAN_UNLOCK(can); + return tmp_result; } - can->status_indicate.ind = RT_NULL; - can->status_indicate.args = RT_NULL; - + tmp_result = rt_can_rx_close(can); + if (result == RT_EOK && tmp_result != RT_EOK) + { + result = tmp_result; + } #ifdef RT_CAN_USING_HDR if (can->hdr != RT_NULL) { rt_free(can->hdr); can->hdr = RT_NULL; } -#endif +#endif /* RT_CAN_USING_HDR */ + rt_can_tx_close(can); - if (dev->open_flag & RT_DEVICE_FLAG_INT_RX) - { - struct rt_can_rx_fifo *rx_fifo; +#ifdef RT_CAN_USING_STATUS_POLLING + can->status_indicate.ind = RT_NULL; + can->status_indicate.args = RT_NULL; +#endif /* RT_CAN_USING_STATUS_POLLING */ +#ifdef RT_CAN_USING_BUS_HOOK + can->bus_hook = RT_NULL; +#endif /* RT_CAN_USING_BUS_HOOK */ - /* clear can rx interrupt */ - can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, (void *)RT_DEVICE_FLAG_INT_RX); + /* + * Final close has deinitialized the BSP controller via + * RT_CAN_CMD_START(0). Mark the RT-Thread device uninitialized + * so the next rt_device_open() runs rt_can_init() again. + */ + dev->flag &= ~RT_DEVICE_FLAG_ACTIVATED; + dev->open_flag = RT_DEVICE_OFLAG_CLOSE; + CAN_UNLOCK(can); + return result; +} - rx_fifo = (struct rt_can_rx_fifo *)can->can_rx; - RT_ASSERT(rx_fifo != RT_NULL); +/** + * @brief Read frames from the interrupt-driven software RX FIFO. + * @param dev RT-Thread CAN device object. + * @param pos Unused device position. + * @param buffer Destination frame buffer. + * @param size Destination size in bytes. + * @return Number of bytes read or a negative error. + */ +static rt_ssize_t rt_can_read(struct rt_device *dev, rt_off_t pos, + void *buffer, rt_size_t size) +{ + RT_UNUSED(pos); - rt_free(rx_fifo); - dev->open_flag &= ~RT_DEVICE_FLAG_INT_RX; - can->can_rx = RT_NULL; + if (dev == RT_NULL || buffer == RT_NULL || size == 0) + { + return -RT_EINVAL; } - - if (dev->open_flag & RT_DEVICE_FLAG_INT_TX) + if (dev->ref_count == 0 || !(dev->open_flag & RT_DEVICE_FLAG_INT_RX)) { - struct rt_can_tx_fifo *tx_fifo; + return -RT_ENOSYS; + } - /* clear can tx interrupt */ - can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, (void *)RT_DEVICE_FLAG_INT_TX); + return rt_can_rx_read_core((struct rt_can_device *)dev, + (struct rt_can_msg *)buffer, (rt_ssize_t)size); +} - tx_fifo = (struct rt_can_tx_fifo *)can->can_tx; - RT_ASSERT(tx_fifo != RT_NULL); +/** + * @brief Send CAN frames using blocking or one-shot non-blocking semantics. + * @param dev RT-Thread CAN device object. + * @param pos Unused device position. + * @param buffer Source frame buffer. + * @param size Source size in bytes. + * @return Accepted byte count or a negative error. + */ +static rt_ssize_t rt_can_write(struct rt_device *dev, rt_off_t pos, + const void *buffer, rt_size_t size) +{ + const struct rt_can_msg *message; + rt_bool_t blocking; + + RT_UNUSED(pos); - rt_sem_detach(&(tx_fifo->sem)); - rt_free(tx_fifo); - dev->open_flag &= ~RT_DEVICE_FLAG_INT_TX; - can->can_tx = RT_NULL; + if (dev == RT_NULL || buffer == RT_NULL || size == 0 || + (size % sizeof(struct rt_can_msg)) != 0) + { + return -RT_EINVAL; + } + if (dev->ref_count == 0 || !(dev->open_flag & RT_DEVICE_FLAG_INT_TX)) + { + return -RT_ENOSYS; } - can->ops->control(can, RT_DEVICE_CTRL_CLR_INT, (void *)RT_DEVICE_CAN_INT_ERR); - can->ops->control(can, RT_CAN_CMD_START, RT_FALSE); - CAN_UNLOCK(can); + message = (const struct rt_can_msg *)buffer; + blocking = rt_interrupt_get_nest() == 0 && !message->nonblocking ? RT_TRUE : RT_FALSE; - return RT_EOK; + return rt_can_tx_write_core((struct rt_can_device *)dev, message, size, blocking); } -static rt_ssize_t rt_can_read(struct rt_device *dev, - rt_off_t pos, - void *buffer, - rt_size_t size) +/** + * @brief Validate a non-NULL explicit filter before the BSP receives it. + * @param can CAN device whose filter limits are checked. + * @param filter Non-NULL requested filter transaction. + * @return RT_EOK when every Generic field is valid, otherwise -RT_EINVAL. + */ +static rt_err_t _can_validate_filter(struct rt_can_device *can, + const struct rt_can_filter_config *filter) { - struct rt_can_device *can; - - RT_ASSERT(dev != RT_NULL); - if (size == 0) return -RT_EINVAL; + rt_uint32_t i; - can = (struct rt_can_device *)dev; + if (filter == RT_NULL || filter->actived > 1U || + (filter->count != 0 && filter->items == RT_NULL)) + { + return -RT_EINVAL; + } - if ((dev->open_flag & RT_DEVICE_FLAG_INT_RX) && (dev->ref_count > 0)) + for (i = 0; i < filter->count; i++) { - return _can_int_rx(can, buffer, size); + const struct rt_can_filter_item *item = &filter->items[i]; + + if (item->hdr_bank < -1 || +#ifdef RT_CAN_USING_HDR + item->hdr_bank >= (rt_int32_t)can->config.maxhdr || +#endif /* RT_CAN_USING_HDR */ + (item->ide != RT_CAN_STDID && item->ide != RT_CAN_EXTID) || + (item->rtr != RT_CAN_DTR && item->rtr != RT_CAN_RTR) || + (item->mode != RT_CAN_MODE_MASK && item->mode != RT_CAN_MODE_LIST) || + (item->rxfifo != CAN_RX_FIFO0 && item->rxfifo != CAN_RX_FIFO1) || + (item->ide == RT_CAN_STDID && item->id > 0x7FFU)) + { + return -RT_EINVAL; + } } - return -RT_ENOSYS; + return RT_EOK; } + /** - * @brief Write data to the CAN device. + * @brief Execute a controller-management command from a TX-idle/RX-paused boundary. * - * This function serves as the unified entry point for sending CAN messages. - * It intelligently routes the request to either a blocking or non-blocking - * transmission function based on: - * 1. The calling context (thread or ISR). - * 2. A user-specified flag (`nonblocking`) in the message structure. + * can->lock serializes management operations. TX admission is closed atomically + * with the idle check; RX admission is then paused and pinned ISR/callback work is + * drained before hardware state changes. A failed BSP operation or RX resume keeps + * data paths fail-closed instead of resuming on uncertain hardware state. Generic + * CAN does not retain or replay hardware filter programming across reconfiguration. * - * @param[in] dev A pointer to the device object. - * @param[in] pos This parameter is ignored for CAN devices. - * @param[in] buffer A pointer to the buffer containing one or more `rt_can_msg` structures. - * @param[in] size The total size of the buffer in bytes. Must be a multiple of `sizeof(struct rt_can_msg)`. - * - * @return The number of bytes successfully written. For non-blocking sends, this means - * the data was either sent directly or enqueued in the buffer. + * @param can CAN device to reconfigure. + * @param cmd CAN control command when use_configure is RT_FALSE. + * @param args Command/configuration arguments. + * @param use_configure Select ops->configure() instead of ops->control(). + * @return RT_EOK on success or a validation/quiesce/BSP/resume error. */ -static rt_ssize_t rt_can_write(struct rt_device *dev, - rt_off_t pos, - const void *buffer, - rt_size_t size) +static rt_err_t _can_reconfigure(struct rt_can_device *can, int cmd, void *args, + rt_bool_t use_configure) { - struct rt_can_device *can; - const struct rt_can_msg *pmsg; + rt_bool_t was_accepting = RT_FALSE; + rt_bool_t rx_was_enabled; + rt_bool_t start_enable = RT_FALSE; + rt_bool_t keep_rx_paused = RT_FALSE; + rt_err_t result; + + if (can == RT_NULL || can->ops == RT_NULL || + (use_configure && can->ops->configure == RT_NULL) || + (!use_configure && can->ops->control == RT_NULL)) + { + return -RT_ENOSYS; + } + if (rt_interrupt_get_nest() > 0) + { + return -RT_EINVAL; + } + if (use_configure && args == RT_NULL) + { + return -RT_EINVAL; + } - RT_ASSERT(dev != RT_NULL); - RT_ASSERT(buffer != RT_NULL); + if (!use_configure && cmd == RT_CAN_CMD_SET_FILTER && args != RT_NULL) + { + result = _can_validate_filter(can, (const struct rt_can_filter_config *)args); + if (result != RT_EOK) + { + return result; + } + } - if (size == 0) return -RT_EINVAL; + result = CAN_LOCK(can); + if (result != RT_EOK) + { + return result; + } - /* Ensure size is a multiple of the message size for buffer operations */ - if (size % sizeof(struct rt_can_msg) != 0) + if (use_configure) { - return -RT_EINVAL; + const struct can_configure *config = (const struct can_configure *)args; + + if ((can->can_tx != RT_NULL && config->sndboxnumber != can->config.sndboxnumber) || + (can->can_rx != RT_NULL && config->msgboxsz != can->config.msgboxsz) +#ifdef RT_CAN_USING_HDR + || (can->hdr != RT_NULL && config->maxhdr != can->config.maxhdr) +#endif /* RT_CAN_USING_HDR */ + ) + { + result = -RT_EBUSY; + goto out_unlock; + } } - can = (struct rt_can_device *)dev; - pmsg = (const struct rt_can_msg *)buffer; + /* Stage 1: close TX admission and verify no frame is hardware-owned. */ + result = rt_can_tx_begin_management(can, &was_accepting); + if (result != RT_EOK) + { + goto out_unlock; + } - if(dev->ref_count == 0) + if (!use_configure && cmd == RT_CAN_CMD_SET_PRIV) { - return -RT_ENOSYS; + rt_uint32_t privmode = (rt_uint32_t)(rt_ubase_t)args; + + if (privmode != RT_CAN_MODE_PRIV && privmode != RT_CAN_MODE_NOPRIV) + { + result = -RT_EINVAL; + (void)rt_can_tx_set_accepting(can, was_accepting); + goto out_unlock; + } } - /* - * Routing to the non-blocking send scenario: - * 1. Called from within an interrupt context. - * 2. Called from a thread, but the user explicitly set the nonblocking flag on the first message. - */ - if (rt_interrupt_get_nest() > 0 || pmsg->nonblocking) + rx_was_enabled = can->can_rx != RT_NULL && + (can->parent.open_flag & RT_DEVICE_FLAG_INT_RX); + if (rx_was_enabled) + { + /* Stage 2: stop new RX and drain ISR/callback lifetime pins. */ + result = rt_can_rx_pause(can); + if (result != RT_EOK) + { + goto out_unlock; + } + } + + if (!use_configure && cmd == RT_CAN_CMD_START) { - return _can_nonblocking_tx(can, pmsg, size); + start_enable = (rt_uint32_t)(rt_ubase_t)args != 0 ? RT_TRUE : RT_FALSE; + keep_rx_paused = start_enable ? RT_FALSE : RT_TRUE; } - if (dev->open_flag & RT_DEVICE_FLAG_INT_TX) + /* Stage 3: hardware mutation is allowed only inside the quiet window. */ + if (use_configure) + { + result = can->ops->configure(can, (struct can_configure *)args); + } + else + { + result = can->ops->control(can, cmd, args); + } + if (result != RT_EOK) { - if (can->config.privmode) + /* + * No automatic controller recovery is attempted. In particular, a + * failed STOP is returned to the caller with the quiet window kept + * closed; the user/BSP decides how to recover the controller. + */ + goto out_unlock; + } + + if (use_configure) + { + rt_memcpy(&can->config, args, sizeof(can->config)); +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) + (void)rt_timer_control(&can->timer, RT_TIMER_CTRL_SET_TIME, &can->config.ticks); +#endif + } +#ifdef RT_CAN_USING_HDR + if (!use_configure && cmd == RT_CAN_CMD_SET_FILTER && args != RT_NULL) + { + result = rt_can_rx_commit_filter(can, + (const struct rt_can_filter_config *)args); + if (result != RT_EOK) { - return _can_int_tx_priv(can, buffer, size); + goto out_unlock; } - else + } +#endif /* RT_CAN_USING_HDR */ + + if (!use_configure && cmd == RT_CAN_CMD_SET_PRIV) + { + /* Preserve the legacy BSP control contract; commit Generic policy only after BSP success. */ + can->config.privmode = (rt_uint32_t)(rt_ubase_t)args; + } + + /* Stage 4: reopen RX after the requested BSP operation is complete. */ + if (!keep_rx_paused && can->can_rx != RT_NULL && + (rx_was_enabled || (!use_configure && cmd == RT_CAN_CMD_START && start_enable))) + { + result = rt_can_rx_resume(can); + if (result != RT_EOK) { - return _can_int_tx(can, buffer, size); + /* RX enable failure keeps both data paths closed. */ + goto out_unlock; } } - return -RT_ENOSYS; + + /* Stage 5: TX admission is the last data path reopened. */ + if (!use_configure && cmd == RT_CAN_CMD_START) + { + (void)rt_can_tx_set_accepting(can, start_enable ? RT_TRUE : RT_FALSE); + } + else + { + (void)rt_can_tx_set_accepting(can, was_accepting); + } + +out_unlock: + CAN_UNLOCK(can); + return result; } -static rt_err_t rt_can_control(struct rt_device *dev, - int cmd, - void *args) +/** + * @brief Dispatch RT-Thread and CAN-specific control operations. + * @param dev RT-Thread CAN device object. + * @param cmd Control command. + * @param args Command-specific argument. + * @return RT_EOK on success or a command-specific error. + */ +static rt_err_t rt_can_control(struct rt_device *dev, int cmd, void *args) { struct rt_can_device *can; - rt_err_t res; + rt_err_t result = RT_EOK; + + if (dev == RT_NULL) + { + return -RT_EINVAL; + } - res = RT_EOK; - RT_ASSERT(dev != RT_NULL); can = (struct rt_can_device *)dev; switch (cmd) { case RT_DEVICE_CTRL_SUSPEND: - /* suspend device */ dev->flag |= RT_DEVICE_FLAG_SUSPENDED; break; case RT_DEVICE_CTRL_RESUME: - /* resume device */ dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED; break; case RT_DEVICE_CTRL_CONFIG: - /* configure device */ - res = can->ops->configure(can, (struct can_configure *)args); + result = _can_reconfigure(can, cmd, args, RT_TRUE); + break; + + case RT_CAN_CMD_SET_FILTER: + case RT_CAN_CMD_SET_BAUD: + case RT_CAN_CMD_SET_BAUD_FD: + case RT_CAN_CMD_SET_MODE: + case RT_CAN_CMD_SET_BITTIMING: + case RT_CAN_CMD_SET_CANFD: + case RT_CAN_CMD_START: + result = _can_reconfigure(can, cmd, args, RT_FALSE); break; case RT_CAN_CMD_SET_PRIV: - /* configure device */ if ((rt_uint32_t)(rt_ubase_t)args != can->config.privmode) { - int i; - rt_base_t level; - struct rt_can_tx_fifo *tx_fifo; - - res = can->ops->control(can, cmd, args); - if (res != RT_EOK) return res; - tx_fifo = (struct rt_can_tx_fifo *) can->can_tx; - if (can->config.privmode) - { - for (i = 0; i < can->config.sndboxnumber; i++) - { - level = rt_hw_local_irq_disable(); - if(rt_list_isempty(&tx_fifo->buffer[i].list)) - { - rt_sem_release(&(tx_fifo->sem)); - } - else - { - rt_list_remove(&tx_fifo->buffer[i].list); - } - rt_hw_local_irq_enable(level); - } - - } - else - { - for (i = 0; i < can->config.sndboxnumber; i++) - { - level = rt_hw_local_irq_disable(); - if (tx_fifo->buffer[i].result == RT_CAN_SND_RESULT_OK) - { - rt_list_insert_before(&tx_fifo->freelist, &tx_fifo->buffer[i].list); - } - rt_hw_local_irq_enable(level); - } - } + result = _can_reconfigure(can, cmd, args, RT_FALSE); } break; - case RT_CAN_CMD_SET_STATUS_IND: - can->status_indicate.ind = ((rt_can_status_ind_type_t)args)->ind; - can->status_indicate.args = ((rt_can_status_ind_type_t)args)->args; + case RT_CAN_CMD_ABORT_TX: + result = rt_can_tx_abort(can, (rt_uint32_t)(rt_ubase_t)args); break; -#ifdef RT_CAN_USING_HDR - case RT_CAN_CMD_SET_FILTER: - res = can->ops->control(can, cmd, args); - if (res != RT_EOK || can->hdr == RT_NULL) + case RT_CAN_CMD_ABORT_ALL: + { + rt_uint32_t mailbox; + + if (rt_interrupt_get_nest() > 0) { - return res; + result = -RT_EINVAL; + break; } - - struct rt_can_filter_config *pfilter; - struct rt_can_filter_item *pitem; - rt_uint32_t count; - rt_base_t level; - - pfilter = (struct rt_can_filter_config *)args; - RT_ASSERT(pfilter); - count = pfilter->count; - pitem = pfilter->items; - if (pfilter->actived) + result = CAN_LOCK(can); + if (result != RT_EOK) { - while (count) - { - if (pitem->hdr_bank >= can->config.maxhdr || pitem->hdr_bank < 0) - { - count--; - pitem++; - continue; - } - - level = rt_hw_local_irq_disable(); - if (!can->hdr[pitem->hdr_bank].connected) - { - rt_hw_local_irq_enable(level); - rt_memcpy(&can->hdr[pitem->hdr_bank].filter, pitem, - sizeof(struct rt_can_filter_item)); - level = rt_hw_local_irq_disable(); - can->hdr[pitem->hdr_bank].connected = 1; - can->hdr[pitem->hdr_bank].msgs = 0; - rt_list_init(&can->hdr[pitem->hdr_bank].list); - } - rt_hw_local_irq_enable(level); - - count--; - pitem++; - } + break; } - else + for (mailbox = 0; mailbox < can->config.sndboxnumber; mailbox++) { - while (count) + rt_err_t abort_result = rt_can_tx_abort(can, mailbox); + if (result == RT_EOK && abort_result != RT_EOK) { - if (pitem->hdr_bank >= can->config.maxhdr || pitem->hdr_bank < 0) - { - count--; - pitem++; - continue; - } - level = rt_hw_local_irq_disable(); - - if (can->hdr[pitem->hdr_bank].connected) - { - can->hdr[pitem->hdr_bank].connected = 0; - can->hdr[pitem->hdr_bank].msgs = 0; - if (!rt_list_isempty(&can->hdr[pitem->hdr_bank].list)) - { - rt_list_remove(can->hdr[pitem->hdr_bank].list.next); - } - rt_hw_local_irq_enable(level); - rt_memset(&can->hdr[pitem->hdr_bank].filter, 0, - sizeof(struct rt_can_filter_item)); - } - else - { - rt_hw_local_irq_enable(level); - } - count--; - pitem++; + result = abort_result; } } + CAN_UNLOCK(can); + break; + } + + case RT_CAN_CMD_SET_STATUS_IND: +#ifdef RT_CAN_USING_STATUS_POLLING + if (args == RT_NULL) + { + result = -RT_EINVAL; + break; + } + can->status_indicate.ind = ((rt_can_status_ind_type_t)args)->ind; + can->status_indicate.args = ((rt_can_status_ind_type_t)args)->args; +#else + result = -RT_ENOSYS; +#endif /* RT_CAN_USING_STATUS_POLLING */ break; -#endif /*RT_CAN_USING_HDR*/ + #ifdef RT_CAN_USING_BUS_HOOK case RT_CAN_CMD_SET_BUS_HOOK: - can->bus_hook = (rt_can_bus_hook) args; + can->bus_hook = (rt_can_bus_hook)args; break; -#endif /*RT_CAN_USING_BUS_HOOK*/ - default : - /* control device */ - if (can->ops->control != RT_NULL) - { - res = can->ops->control(can, cmd, args); - } - else - { - res = -RT_ENOSYS; - } +#endif /* RT_CAN_USING_BUS_HOOK */ + + default: + result = can->ops != RT_NULL && can->ops->control != RT_NULL ? can->ops->control(can, cmd, args) : -RT_ENOSYS; break; } - return res; + return result; } +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) /** - * @internal - * @brief Periodic timer callback for the CAN device. - * - * This function is executed periodically by a system timer. Its main purposes are: - * 1. To query the current status of the CAN controller (e.g., error counters, bus state). - * 2. To invoke a user-registered status indicator callback, if any. - * 3. To call a user-registered bus hook function for periodic tasks, if any. - * - * @param[in] arg The argument passed to the callback, which is a pointer to the `rt_can_device`. - * @return void + * @brief Run optional periodic status polling and bus hook callbacks. + * @param arg CAN device supplied when the timer was initialized. */ static void cantimeout(void *arg) { - rt_can_t can; + rt_can_t can = (rt_can_t)arg; - can = (rt_can_t)arg; - RT_ASSERT(can); - rt_device_control((rt_device_t)can, RT_CAN_CMD_GET_STATUS, (void *)&can->status); + if (can == RT_NULL || can->parent.ref_count == 0) + { + return; + } +#ifdef RT_CAN_USING_STATUS_POLLING + if (can->ops != RT_NULL && can->ops->control != RT_NULL) + { + (void)can->ops->control(can, RT_CAN_CMD_GET_STATUS, (void *)&can->status); + } if (can->status_indicate.ind != RT_NULL) { can->status_indicate.ind(can, can->status_indicate.args); } +#endif /* RT_CAN_USING_STATUS_POLLING */ #ifdef RT_CAN_USING_BUS_HOOK - if(can->bus_hook) + if (can->bus_hook != RT_NULL) { can->bus_hook(can); } -#endif /*RT_CAN_USING_BUS_HOOK*/ - if (can->timerinitflag == 1) - { - can->timerinitflag = 0xFF; - } +#endif /* RT_CAN_USING_BUS_HOOK */ } +#endif #ifdef RT_USING_DEVICE_OPS -const static struct rt_device_ops can_device_ops = -{ +static const struct rt_device_ops can_device_ops = { rt_can_init, rt_can_open, rt_can_close, rt_can_read, rt_can_write, - rt_can_control + rt_can_control, }; -#endif +#endif /* RT_USING_DEVICE_OPS */ -/* - * can register +/** + * @brief Register a CAN controller with the RT-Thread device framework. + * @param can CAN device object provided by the BSP. + * @param name Device registration name. + * @param ops Low-level CAN operation table. + * @param data BSP private data exposed as parent.user_data. + * @return Result of rt_device_register(). */ -rt_err_t rt_hw_can_register(struct rt_can_device *can, - const char *name, +rt_err_t rt_hw_can_register(struct rt_can_device *can, + const char *name, const struct rt_can_ops *ops, - void *data) + void *data) { struct rt_device *device; - RT_ASSERT(can != RT_NULL); - device = &(can->parent); + RT_ASSERT(can != RT_NULL); + RT_ASSERT(name != RT_NULL); + RT_ASSERT(ops != RT_NULL); - device->type = RT_Device_Class_CAN; + device = &can->parent; + device->type = RT_Device_Class_CAN; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; + + can->ops = ops; + can->can_rx = RT_NULL; + can->can_tx = RT_NULL; + can->rx_active_isr = 0; #ifdef RT_CAN_USING_HDR - can->hdr = RT_NULL; -#endif - can->can_rx = RT_NULL; - can->can_tx = RT_NULL; - rt_mutex_init(&(can->lock), "can", RT_IPC_FLAG_PRIO); + can->hdr = RT_NULL; +#endif /* RT_CAN_USING_HDR */ +#ifdef RT_CAN_USING_STATUS_POLLING + can->status_indicate.ind = RT_NULL; + can->status_indicate.args = RT_NULL; +#endif /* RT_CAN_USING_STATUS_POLLING */ #ifdef RT_CAN_USING_BUS_HOOK - can->bus_hook = RT_NULL; -#endif /*RT_CAN_USING_BUS_HOOK*/ + can->bus_hook = RT_NULL; +#endif /* RT_CAN_USING_BUS_HOOK */ -#ifdef RT_CAN_MALLOC_NB_TX_BUFFER - can->nb_tx_rb_pool = RT_NULL; -#endif + rt_mutex_init(&can->lock, "can", RT_IPC_FLAG_PRIO); + rt_spin_lock_init(&can->tx_lock); + rt_spin_lock_init(&can->rx_lock); + rt_memset(&can->status, 0, sizeof(can->status)); #ifdef RT_USING_DEVICE_OPS - device->ops = &can_device_ops; + device->ops = &can_device_ops; #else - device->init = rt_can_init; - device->open = rt_can_open; - device->close = rt_can_close; - device->read = rt_can_read; - device->write = rt_can_write; - device->control = rt_can_control; + device->init = rt_can_init; + device->open = rt_can_open; + device->close = rt_can_close; + device->read = rt_can_read; + device->write = rt_can_write; + device->control = rt_can_control; +#endif /* RT_USING_DEVICE_OPS */ + + device->user_data = data; + +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) + rt_timer_init(&can->timer, name, cantimeout, can, can->config.ticks, + RT_TIMER_FLAG_PERIODIC); #endif - can->ops = ops; - - can->status_indicate.ind = RT_NULL; - can->status_indicate.args = RT_NULL; - rt_memset(&can->status, 0, sizeof(can->status)); - device->user_data = data; - - can->timerinitflag = 0; - rt_timer_init(&can->timer, - name, - cantimeout, - (void *)can, - can->config.ticks, - RT_TIMER_FLAG_PERIODIC); - /* register a character device */ return rt_device_register(device, name, RT_DEVICE_FLAG_RDWR); } -/* ISR for can interrupt */ /** - * @brief The framework-level ISR handler for CAN devices. - * - * This function is called by the low-level BSP ISR and acts as the central - * dispatcher for all CAN-related interrupt events. It handles both receive - * events and transmission-complete events. - * - * @param[in] can A pointer to the CAN device structure. - * @param[in] event The interrupt event mask, indicating the cause of the interrupt. - * @return void + * @brief Dispatch a low-level CAN RX or TX terminal event into Generic CAN. + * @param can CAN device reporting the event. + * @param event Event type with optional FIFO/mailbox index in the upper bits. */ void rt_hw_can_isr(struct rt_can_device *can, int event) { - rt_bool_t is_rxof_event = RT_FALSE; + if (can == RT_NULL) + { + return; + } switch (event & 0xff) { case RT_CAN_EVENT_RXOF_IND: - { - rt_base_t level; - is_rxof_event = RT_TRUE; - level = rt_hw_local_irq_disable(); - can->status.dropedrcvpkg++; - rt_hw_local_irq_enable(level); - } - /* FALLTHROUGH: RX overflow still tries to fetch one pending frame into software FIFO. */ - case RT_CAN_EVENT_RX_IND: - { - struct rt_can_msg tmpmsg; - struct rt_can_rx_fifo *rx_fifo; - struct rt_can_msg_list *listmsg = RT_NULL; -#ifdef RT_CAN_USING_HDR - rt_int8_t hdr; -#endif - int ch = -1; - rt_base_t level; - rt_uint32_t no; - - rx_fifo = (struct rt_can_rx_fifo *)can->can_rx; - RT_ASSERT(rx_fifo != RT_NULL); - /* interrupt mode receive */ - RT_ASSERT(can->parent.open_flag & RT_DEVICE_FLAG_INT_RX); - - no = event >> 8; - ch = can->ops->recvmsg(can, &tmpmsg, no); - if (ch == -1) break; - - /* disable interrupt */ - level = rt_hw_local_irq_disable(); - can->status.rcvpkg++; - can->status.rcvchange = 1; - if (!rt_list_isempty(&rx_fifo->freelist)) - { - listmsg = rt_list_entry(rx_fifo->freelist.next, struct rt_can_msg_list, list); - rt_list_remove(&listmsg->list); -#ifdef RT_CAN_USING_HDR - rt_list_remove(&listmsg->hdrlist); - if (listmsg->owner != RT_NULL && listmsg->owner->msgs) - { - listmsg->owner->msgs--; - } - listmsg->owner = RT_NULL; -#endif /*RT_CAN_USING_HDR*/ - RT_ASSERT(rx_fifo->freenumbers > 0); - rx_fifo->freenumbers--; - } - else if (!rt_list_isempty(&rx_fifo->uselist)) - { - listmsg = rt_list_entry(rx_fifo->uselist.next, struct rt_can_msg_list, list); - if (!is_rxof_event) - { - can->status.dropedrcvpkg++; - } - rt_list_remove(&listmsg->list); -#ifdef RT_CAN_USING_HDR - rt_list_remove(&listmsg->hdrlist); - if (listmsg->owner != RT_NULL && listmsg->owner->msgs) - { - listmsg->owner->msgs--; - } - listmsg->owner = RT_NULL; -#endif - } - /* enable interrupt */ - rt_hw_local_irq_enable(level); - - if (listmsg != RT_NULL) - { - rt_memcpy(&listmsg->data, &tmpmsg, sizeof(struct rt_can_msg)); - level = rt_hw_local_irq_disable(); - rt_list_insert_before(&rx_fifo->uselist, &listmsg->list); -#ifdef RT_CAN_USING_HDR - hdr = tmpmsg.hdr_index; - if (can->hdr != RT_NULL) - { - RT_ASSERT(hdr < can->config.maxhdr && hdr >= 0); - if (can->hdr[hdr].connected) - { - rt_list_insert_before(&can->hdr[hdr].list, &listmsg->hdrlist); - listmsg->owner = &can->hdr[hdr]; - can->hdr[hdr].msgs++; - } - - } -#endif - rt_hw_local_irq_enable(level); - } - - /* invoke callback */ -#ifdef RT_CAN_USING_HDR - if (can->hdr != RT_NULL && can->hdr[hdr].connected && can->hdr[hdr].filter.ind) - { - rt_size_t rx_length; - RT_ASSERT(hdr < can->config.maxhdr && hdr >= 0); - - level = rt_hw_local_irq_disable(); - rx_length = can->hdr[hdr].msgs * sizeof(struct rt_can_msg); - rt_hw_local_irq_enable(level); - if (rx_length) - { - can->hdr[hdr].filter.ind(&can->parent, can->hdr[hdr].filter.args, hdr, rx_length); - } - } - else -#endif - { - if (can->parent.rx_indicate != RT_NULL) - { - rt_size_t rx_length; - - level = rt_hw_local_irq_disable(); - /* get rx length */ - rx_length = rt_list_len(&rx_fifo->uselist)* sizeof(struct rt_can_msg); - rt_hw_local_irq_enable(level); + rt_can_rx_isr_core(can, event, RT_TRUE); + break; - if (rx_length) - { - can->parent.rx_indicate(&can->parent, rx_length); - } - } - } + case RT_CAN_EVENT_RX_IND: + rt_can_rx_isr_core(can, event, RT_FALSE); break; - } case RT_CAN_EVENT_TX_DONE: case RT_CAN_EVENT_TX_FAIL: - { - struct rt_can_tx_fifo *tx_fifo; - rt_uint32_t no; - no = event >> 8; - tx_fifo = (struct rt_can_tx_fifo *) can->can_tx; - RT_ASSERT(tx_fifo != RT_NULL); - - if (can->status.sndchange&(1<buffer[no].result = RT_CAN_SND_RESULT_OK; - } - else - { - tx_fifo->buffer[no].result = RT_CAN_SND_RESULT_ERR; - } - rt_completion_done(&(tx_fifo->buffer[no].completion)); - } + rt_can_tx_isr_core(can, event); + break; - if (can->ops->sendmsg_nonblocking != RT_NULL) - { - while (RT_TRUE) - { - struct rt_can_msg msg_to_send; - rt_base_t level; - rt_bool_t msg_was_present = RT_FALSE; - - level = rt_hw_local_irq_disable(); - if (rt_ringbuffer_data_len(&can->nb_tx_rb) >= sizeof(struct rt_can_msg)) - { - rt_ringbuffer_get(&can->nb_tx_rb, (rt_uint8_t *)&msg_to_send, sizeof(struct rt_can_msg)); - msg_was_present = RT_TRUE; - } - rt_hw_local_irq_enable(level); - - if (!msg_was_present) - { - break; - } - - if (can->ops->sendmsg_nonblocking(can, &msg_to_send) != RT_EOK) - { - level = rt_hw_local_irq_disable(); - rt_ringbuffer_put_force(&can->nb_tx_rb, (rt_uint8_t *)&msg_to_send, sizeof(struct rt_can_msg)); - rt_hw_local_irq_enable(level); - break; - } - } - } + default: break; } - } } #ifdef RT_USING_FINSH #include + +/** + * @brief Print CAN controller status counters from the MSH shell. + * + * @param argc Number of shell arguments. + * @param argv Shell argument vector. + * @return 0 after handling the command, or -1 when the device cannot be found. + */ int cmd_canstat(int argc, void **argv) { - static const char *ErrCode[] = - { + static const char *ErrCode[] = { "No Error!", "Warning !", "Passive !", @@ -1172,13 +973,13 @@ int cmd_canstat(int argc, void **argv) { struct rt_can_status status; rt_device_t candev = rt_device_find(argv[1]); - if (!candev) + if (candev == RT_NULL) { rt_kprintf(" Can't find can device %s\n", argv[1]); return -1; } - rt_kprintf(" Found can device: %s...", argv[1]); + rt_kprintf(" Found can device: %s...", argv[1]); rt_device_control(candev, RT_CAN_CMD_GET_STATUS, &status); rt_kprintf("\n Receive...error..count: %010ld. Send.....error....count: %010ld.", status.rcverrcnt, status.snderrcnt); @@ -1200,10 +1001,7 @@ int cmd_canstat(int argc, void **argv) case 3: rt_kprintf("%s.", ErrCode[2]); break; - case 4: - case 5: - case 6: - case 7: + default: rt_kprintf("%s.", ErrCode[3]); break; } @@ -1215,10 +1013,11 @@ int cmd_canstat(int argc, void **argv) else { rt_kprintf(" Invalid Call %s\n", argv[0]); - rt_kprintf(" Please using %s cannamex .Here canname is driver name and x is candrive number.\n", argv[0]); + rt_kprintf(" Please using %s cannamex. Here canname is driver name and x is candrive number.\n", + argv[0]); } + return 0; } MSH_CMD_EXPORT_ALIAS(cmd_canstat, canstat, stat can device status); -#endif - +#endif /* RT_USING_FINSH */ diff --git a/components/drivers/include/drivers/dev_can.h b/components/drivers/include/drivers/dev_can.h index 4ec5103e7ba3..58c818b6c91e 100644 --- a/components/drivers/include/drivers/dev_can.h +++ b/components/drivers/include/drivers/dev_can.h @@ -11,22 +11,63 @@ * 2025-09-20 wdfk_prog Added non-blocking send mechanism APIs and data structures. */ +/* + * Generic CAN public/BSP contract + * ------------------------------- + * + * The framework intentionally separates policy/synchronization from hardware: + * + * application / RT-Thread device API + * | + * Generic CAN + * +---------+---------+ + * | | + * can_tx.c can_rx.c + * | | + * rt_can_ops --------------------> BSP / controller + * + * TX design: + * - Blocking TX selects a framework-visible hardware mailbox through sendmsg(). + * A busy selected mailbox returns -RT_EBUSY; Generic waits for a TX state change + * and retries within one user-configured hard timeout. The configured timeout + * must exceed the target system's worst-case mailbox/bus/terminal-ISR latency; + * Generic does not auto-extend, abort, or recover a terminal that arrives later. + * A successful in-contract submit must generate exactly one TX_DONE or TX_FAIL + * terminal for that mailbox. + * - Non-blocking TX calls sendmsg_nonblocking() once. The BSP chooses a free + * hardware mailbox immediately or returns -RT_EBUSY. Generic never queues or + * retries a non-blocking frame in software. + * - A hardware mailbox is not reusable until the previous terminal has been + * published through rt_hw_can_isr(). This prevents an old terminal from being + * mis-associated with a newer blocking owner. + * + * RX design: + * - BSP RX IRQs call rt_hw_can_isr(); Generic drains hardware through recvmsg() + * into a software FIFO. rx_active_isr pins the RX/HDR runtime while recvmsg() + * and callbacks execute outside rx_lock. + * + * Management design: + * - Applications stop TX producers before configure/filter/start/stop. Generic + * then closes TX admission, verifies TX idle, pauses RX, applies the BSP + * operation, and reopens data paths. Hardware filter programming is not retained + * or replayed by Generic CAN across controller reconfiguration. If a BSP control + * operation resets filters, the application must issue RT_CAN_CMD_SET_FILTER + * again before relying on filtered reception. + * + * Lock/lifetime boundary: + * - can->lock: sleeping thread-side management serialization. + * - tx_lock: TX runtime publication, slots, pending and admission. + * - rx_lock: RX FIFO/HDR publication and rx_active_isr lifetime pins. + * - can_rx/can_tx are typed dynamic-runtime pointers; the locks live in the + * statically registered rt_can_device so detach/free remains race-safe. + */ + #ifndef __DEV_CAN_H_ #define __DEV_CAN_H_ #include #include -#ifndef RT_CANMSG_BOX_SZ -#define RT_CANMSG_BOX_SZ 16 -#endif -#ifndef RT_CANSND_BOX_NUM -#define RT_CANSND_BOX_NUM 1 -#endif -#ifndef RT_CANSND_MSG_TIMEOUT -#define RT_CANSND_MSG_TIMEOUT 100 -#endif - enum CAN_DLC { CAN_MSG_0BYTE = 0, @@ -49,27 +90,27 @@ enum CAN_DLC enum CANBAUD { - CAN1MBaud = 1000UL * 1000,/* 1 MBit/sec */ + CAN1MBaud = 1000UL * 1000,/* 1 MBit/sec */ CAN800kBaud = 1000UL * 800, /* 800 kBit/sec */ CAN500kBaud = 1000UL * 500, /* 500 kBit/sec */ CAN250kBaud = 1000UL * 250, /* 250 kBit/sec */ CAN125kBaud = 1000UL * 125, /* 125 kBit/sec */ CAN100kBaud = 1000UL * 100, /* 100 kBit/sec */ - CAN50kBaud = 1000UL * 50, /* 50 kBit/sec */ - CAN20kBaud = 1000UL * 20, /* 20 kBit/sec */ - CAN10kBaud = 1000UL * 10 /* 10 kBit/sec */ + CAN50kBaud = 1000UL * 50, /* 50 kBit/sec */ + CAN20kBaud = 1000UL * 20, /* 20 kBit/sec */ + CAN10kBaud = 1000UL * 10 /* 10 kBit/sec */ }; -#define RT_CAN_MODE_NORMAL 0 -#define RT_CAN_MODE_LISTEN 1 -#define RT_CAN_MODE_LOOPBACK 2 -#define RT_CAN_MODE_LOOPBACKANLISTEN 3 +#define RT_CAN_MODE_NORMAL 0 +#define RT_CAN_MODE_LISTEN 1 +#define RT_CAN_MODE_LOOPBACK 2 +#define RT_CAN_MODE_LOOPBACKANLISTEN 3 -#define RT_CAN_MODE_PRIV 0x01 -#define RT_CAN_MODE_NOPRIV 0x00 +#define RT_CAN_MODE_PRIV 0x01 +#define RT_CAN_MODE_NOPRIV 0x00 -#define RT_CAN_MODE_MASK 0x00 -#define RT_CAN_MODE_LIST 0x01 +#define RT_CAN_MODE_MASK 0x00 +#define RT_CAN_MODE_LIST 0x01 /** * @defgroup group_drivers_can CAN Driver @@ -270,20 +311,20 @@ enum CANBAUD * @addtogroup group_drivers_can * @{ */ -#define CAN_RX_FIFO0 (0x00000000U) /*!< CAN receive FIFO 0 */ -#define CAN_RX_FIFO1 (0x00000001U) /*!< CAN receive FIFO 1 */ +#define CAN_RX_FIFO0 (0x00000000U) /*!< CAN receive FIFO 0 */ +#define CAN_RX_FIFO1 (0x00000001U) /*!< CAN receive FIFO 1 */ /** * @brief CAN filter item structure */ struct rt_can_filter_item { - rt_uint32_t id : 29; /**< The CAN ID to be filtered. */ - rt_uint32_t ide : 1; /**< Identifier type. 0 for Standard ID, 1 for Extended ID. */ - rt_uint32_t rtr : 1; /**< Frame type. 0 for Data Frame, 1 for Remote Frame. */ + rt_uint32_t id : 29; /**< The CAN ID to be filtered. */ + rt_uint32_t ide : 1; /**< Identifier type. 0 for Standard ID, 1 for Extended ID. */ + rt_uint32_t rtr : 1; /**< Frame type. 0 for Data Frame, 1 for Remote Frame. */ rt_uint32_t mode : 1; /**< Filter mode. 0 for Mask Mode, 1 for List Mode. */ rt_uint32_t mask; /**< The filter mask. In Mask Mode, a '1' bit means the corresponding ID bit must match. */ - rt_int32_t hdr_bank; /**< The specific hardware filter bank index to use. A value of -1 allows the driver to auto-assign. */ + rt_int32_t hdr_bank; /**< The specific hardware filter bank index to use. A value of -1 allows the driver to auto-assign. */ rt_uint32_t rxfifo; /**< The target RX FIFO for matched messages (CAN_RX_FIFO0 or CAN_RX_FIFO1). */ #ifdef RT_CAN_USING_HDR /** @@ -294,7 +335,7 @@ struct rt_can_filter_item * @param[in] size The size of the received data in bytes. * @return The operation status. */ - rt_err_t (*ind)(rt_device_t dev, void *args , rt_int32_t hdr, rt_size_t size); + rt_err_t (*ind)(rt_device_t dev, void *args, rt_int32_t hdr, rt_size_t size); void *args; /**< User arguments for the indication callback. */ #endif /*RT_CAN_USING_HDR*/ }; @@ -312,20 +353,22 @@ struct rt_can_filter_item * @param[in] ind Optional callback function (can be RT_NULL). * @param[in] args Optional arguments for the callback (can be RT_NULL). */ -#define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \ - {(id), (ide), (rtr), (mode),(mask), -1, CAN_RX_FIFO0,(ind), (args)} -#define RT_CAN_FILTER_STD_INIT(id,ind,args) \ - RT_CAN_FILTER_ITEM_INIT(id,0,0,0,0xFFFFFFFF,ind,args) -#define RT_CAN_FILTER_EXT_INIT(id,ind,args) \ - RT_CAN_FILTER_ITEM_INIT(id,1,0,0,0xFFFFFFFF,ind,args) -#define RT_CAN_STD_RMT_FILTER_INIT(id,ind,args) \ - RT_CAN_FILTER_ITEM_INIT(id,0,1,0,0xFFFFFFFF,ind,args) -#define RT_CAN_EXT_RMT_FILTER_INIT(id,ind,args) \ - RT_CAN_FILTER_ITEM_INIT(id,1,1,0,0xFFFFFFFF,ind,args) -#define RT_CAN_STD_RMT_DATA_FILTER_INIT(id,ind,args) \ - RT_CAN_FILTER_ITEM_INIT(id,0,0,1,0xFFFFFFFF,ind,args) -#define RT_CAN_EXT_RMT_DATA_FILTER_INIT(id,ind,args) \ - RT_CAN_FILTER_ITEM_INIT(id,1,0,1,0xFFFFFFFF,ind,args) +#define RT_CAN_FILTER_ITEM_INIT(id, ide, rtr, mode, mask, ind, args) \ + { \ + (id), (ide), (rtr), (mode), (mask), -1, CAN_RX_FIFO0, (ind), (args) \ + } +#define RT_CAN_FILTER_STD_INIT(id, ind, args) \ + RT_CAN_FILTER_ITEM_INIT(id, 0, 0, 0, 0xFFFFFFFF, ind, args) +#define RT_CAN_FILTER_EXT_INIT(id, ind, args) \ + RT_CAN_FILTER_ITEM_INIT(id, 1, 0, 0, 0xFFFFFFFF, ind, args) +#define RT_CAN_STD_RMT_FILTER_INIT(id, ind, args) \ + RT_CAN_FILTER_ITEM_INIT(id, 0, 1, 0, 0xFFFFFFFF, ind, args) +#define RT_CAN_EXT_RMT_FILTER_INIT(id, ind, args) \ + RT_CAN_FILTER_ITEM_INIT(id, 1, 1, 0, 0xFFFFFFFF, ind, args) +#define RT_CAN_STD_RMT_DATA_FILTER_INIT(id, ind, args) \ + RT_CAN_FILTER_ITEM_INIT(id, 0, 0, 1, 0xFFFFFFFF, ind, args) +#define RT_CAN_EXT_RMT_DATA_FILTER_INIT(id, ind, args) \ + RT_CAN_FILTER_ITEM_INIT(id, 1, 0, 1, 0xFFFFFFFF, ind, args) #else /** * @def RT_CAN_FILTER_ITEM_INIT @@ -337,26 +380,36 @@ struct rt_can_filter_item * @param[in] mode Filter mode (0 for Mask, 1 for List). * @param[in] mask The mask to be applied. */ -#define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask) \ - {(id), (ide), (rtr), (mode), (mask), -1, CAN_RX_FIFO0 } +#define RT_CAN_FILTER_ITEM_INIT(id, ide, rtr, mode, mask) \ + { \ + (id), (ide), (rtr), (mode), (mask), -1, CAN_RX_FIFO0 \ + } #define RT_CAN_FILTER_STD_INIT(id) \ - RT_CAN_FILTER_ITEM_INIT(id,0,0,0,0xFFFFFFFF) + RT_CAN_FILTER_ITEM_INIT(id, 0, 0, 0, 0xFFFFFFFF) #define RT_CAN_FILTER_EXT_INIT(id) \ - RT_CAN_FILTER_ITEM_INIT(id,1,0,0,0xFFFFFFFF) + RT_CAN_FILTER_ITEM_INIT(id, 1, 0, 0, 0xFFFFFFFF) #define RT_CAN_STD_RMT_FILTER_INIT(id) \ - RT_CAN_FILTER_ITEM_INIT(id,0,1,0,0xFFFFFFFF) + RT_CAN_FILTER_ITEM_INIT(id, 0, 1, 0, 0xFFFFFFFF) #define RT_CAN_EXT_RMT_FILTER_INIT(id) \ - RT_CAN_FILTER_ITEM_INIT(id,1,1,0,0xFFFFFFFF) + RT_CAN_FILTER_ITEM_INIT(id, 1, 1, 0, 0xFFFFFFFF) #define RT_CAN_STD_RMT_DATA_FILTER_INIT(id) \ - RT_CAN_FILTER_ITEM_INIT(id,0,0,1,0xFFFFFFFF) + RT_CAN_FILTER_ITEM_INIT(id, 0, 0, 1, 0xFFFFFFFF) #define RT_CAN_EXT_RMT_DATA_FILTER_INIT(id) \ - RT_CAN_FILTER_ITEM_INIT(id,1,0,1,0xFFFFFFFF) + RT_CAN_FILTER_ITEM_INIT(id, 1, 0, 1, 0xFFFFFFFF) #endif /** * @brief CAN hardware filter configuration structure. - * This structure is passed to the driver via `rt_device_control` with the `RT_CAN_CMD_SET_FILTER` command. + * + * This structure is passed to the BSP through `RT_CAN_CMD_SET_FILTER`. A + * `hdr_bank` value of -1 preserves the legacy contract in which the BSP chooses + * the hardware filter bank. A NULL command argument is also passed through to the + * BSP so drivers may restore their default filter configuration; Generic HDR + * routing metadata is unchanged for that NULL request. Generic CAN does not save + * or replay hardware filter configuration after controller reinitialization. + * Applications should finish controller-mutating baud/mode/bit-timing + * configuration first, then configure filters before relying on filtered reception. */ struct rt_can_filter_config { @@ -397,9 +450,9 @@ struct can_configure rt_uint32_t baud_rate; /**< The baud rate for the arbitration phase (e.g., CAN500kBaud). */ rt_uint32_t msgboxsz; /**< The size of the software receive buffer (in number of messages). */ rt_uint32_t sndboxnumber; /**< The number of hardware mailboxes used for blocking send operations. */ - rt_uint32_t mode : 8; /**< The CAN operation mode (e.g., RT_CAN_MODE_NORMAL, RT_CAN_MODE_LOOPBACK). */ - rt_uint32_t privmode : 8; /**< Private mode flag. If set, the `priv` field of `rt_can_msg` specifies the hardware mailbox. */ - rt_uint32_t reserved : 16; /**< Reserved for future use. */ + rt_uint32_t mode : 8; /**< The CAN operation mode (e.g., RT_CAN_MODE_NORMAL, RT_CAN_MODE_LOOPBACK). */ + rt_uint32_t privmode : 8; /**< Private mode flag. If set, the `priv` field of `rt_can_msg` specifies the hardware mailbox. */ + rt_uint32_t reserved : 16; /**< Reserved for future use. */ rt_uint32_t ticks; /**< The period in OS ticks for the status-checking timer. */ #ifdef RT_CAN_USING_HDR rt_uint32_t maxhdr; /**< The maximum number of hardware filters supported by the controller. */ @@ -407,9 +460,9 @@ struct can_configure #ifdef RT_CAN_USING_CANFD rt_uint32_t baud_rate_fd; /**< The baud rate for the CAN-FD data phase. */ - rt_uint32_t use_bit_timing: 8; /**< A flag to indicate that `can_timing` and `canfd_timing` should be used instead of `baud_rate`. */ - rt_uint32_t enable_canfd : 8; /**< A flag to enable CAN-FD functionality. */ - rt_uint32_t reserved1 : 16; /**< Reserved for future use. */ + rt_uint32_t use_bit_timing : 8; /**< A flag to indicate that `can_timing` and `canfd_timing` should be used instead of `baud_rate`. */ + rt_uint32_t enable_canfd : 8; /**< A flag to enable CAN-FD functionality. */ + rt_uint32_t reserved1 : 16; /**< Reserved for future use. */ /* The below fields take effect only if use_bit_timing is non-zero */ struct rt_can_bit_timing can_timing; /**< Custom bit-timing for the arbitration phase. */ @@ -417,28 +470,57 @@ struct can_configure #endif }; -#define CANDEFAULTCONFIG \ -{\ - CAN1MBaud,\ - RT_CANMSG_BOX_SZ,\ - RT_CANSND_BOX_NUM,\ - RT_CAN_MODE_NORMAL,\ -}; +#define CANDEFAULTCONFIG \ + { \ + CAN1MBaud, \ + RT_CANMSG_BOX_SZ, \ + RT_CANSND_BOX_NUM, \ + RT_CAN_MODE_NORMAL, \ + }; struct rt_can_ops; -#define RT_CAN_CMD_SET_FILTER 0x13 -#define RT_CAN_CMD_SET_BAUD 0x14 -#define RT_CAN_CMD_SET_MODE 0x15 -#define RT_CAN_CMD_SET_PRIV 0x16 -#define RT_CAN_CMD_GET_STATUS 0x17 -#define RT_CAN_CMD_SET_STATUS_IND 0x18 -#define RT_CAN_CMD_SET_BUS_HOOK 0x19 -#define RT_CAN_CMD_SET_CANFD 0x1A -#define RT_CAN_CMD_SET_BAUD_FD 0x1B -#define RT_CAN_CMD_SET_BITTIMING 0x1C -#define RT_CAN_CMD_START 0x1D - -#define RT_DEVICE_CAN_INT_ERR 0x1000 + +/** + * @brief Program CAN hardware filters through the BSP. + * + * A filter item with hdr_bank == -1 delegates hardware-bank allocation to the + * BSP, preserving the legacy RT-Thread contract. A NULL argument is passed + * directly to the BSP for its driver-defined default filter configuration and + * does not update Generic HDR routing metadata. Generic CAN does not retain or + * replay hardware programming. If baud/mode/bit-timing or another BSP control + * operation reinitializes the controller and clears its filters, issue this + * command again before depending on filtered reception. + */ +#define RT_CAN_CMD_SET_FILTER 0x13 +#define RT_CAN_CMD_SET_BAUD 0x14 +#define RT_CAN_CMD_SET_MODE 0x15 +#define RT_CAN_CMD_SET_PRIV 0x16 +#define RT_CAN_CMD_GET_STATUS 0x17 +#define RT_CAN_CMD_SET_STATUS_IND 0x18 +#define RT_CAN_CMD_SET_BUS_HOOK 0x19 +#define RT_CAN_CMD_SET_CANFD 0x1A +#define RT_CAN_CMD_SET_BAUD_FD 0x1B +#define RT_CAN_CMD_SET_BITTIMING 0x1C +#define RT_CAN_CMD_START 0x1D + +/** + * @brief Abort one hardware TX mailbox. + * + * The argument carries the mailbox index through rt_ubase_t. Successful request + * acceptance does not retire Generic ownership; the BSP must publish TX_FAIL for + * the mailbox when hardware abort completion becomes visible. + */ +#define RT_CAN_CMD_ABORT_TX 0x1F + +/** + * @brief Abort every framework-visible hardware TX mailbox. + * + * Command value 0x20 is intentionally left unused after the former TX-flush + * command was removed, so an old caller cannot silently become ABORT_ALL. + */ +#define RT_CAN_CMD_ABORT_ALL 0x21 + +#define RT_DEVICE_CAN_INT_ERR 0x1000 enum RT_CAN_STATUS_MODE { @@ -447,6 +529,7 @@ enum RT_CAN_STATUS_MODE ERRPASSIVE = 2, BUSOFF = 4, }; + enum RT_CAN_BUS_ERR { RT_CAN_BUS_NO_ERR = 0, @@ -476,7 +559,7 @@ struct rt_can_status rt_uint32_t biterrcnt; /**< Bit error (recessive/dominant) count. */ rt_uint32_t crcerrcnt; /**< CRC error count. */ rt_uint32_t rcvchange; /**< A flag indicating that the RX buffer status has changed. */ - rt_uint32_t sndchange; /**< A bitmask indicating which TX mailboxes have changed status. */ + rt_uint32_t sndchange; /**< Blocking TX mailbox ownership bit mask. */ rt_uint32_t lasterrtype; /**< The type of the last error that occurred. */ }; @@ -523,20 +606,20 @@ typedef void (*rt_can_bus_hook)(struct rt_can_device *can); */ struct rt_can_msg { - rt_uint32_t id : 29; /**< CAN ID (Standard or Extended). */ - rt_uint32_t ide : 1; /**< Identifier type: 0=Standard ID, 1=Extended ID. */ - rt_uint32_t rtr : 1; /**< Frame type: 0=Data Frame, 1=Remote Frame. */ - rt_uint32_t rsv : 1; /**< Reserved bit. */ - rt_uint32_t len : 8; /**< Data Length Code (DLC) from 0 to 8. */ - rt_uint32_t priv : 8; /**< Private data, used to specify the hardware mailbox in private mode. */ + rt_uint32_t id : 29; /**< CAN ID (Standard or Extended). */ + rt_uint32_t ide : 1; /**< Identifier type: 0=Standard ID, 1=Extended ID. */ + rt_uint32_t rtr : 1; /**< Frame type: 0=Data Frame, 1=Remote Frame. */ + rt_uint32_t rsv : 1; /**< Reserved bit. */ + rt_uint32_t len : 8; /**< Data Length Code (DLC) from 0 to 8. */ + rt_uint32_t priv : 8; /**< Private data, used to specify the hardware mailbox in private mode. */ rt_int32_t hdr_index : 8; /**< For received messages, the index of the hardware filter that matched the message. */ #ifdef RT_CAN_USING_CANFD rt_uint32_t fd_frame : 1; /**< CAN-FD frame indicator. */ - rt_uint32_t brs : 1; /**< Bit-rate switching indicator for CAN-FD. */ - rt_uint32_t rxfifo : 2; /**< The RX FIFO where the message was received. */ + rt_uint32_t brs : 1; /**< Bit-rate switching indicator for CAN-FD. */ + rt_uint32_t rxfifo : 2; /**< The RX FIFO where the message was received. */ rt_uint32_t reserved : 3; #else - rt_uint32_t rxfifo : 2; /**< The RX FIFO where the message was received. */ + rt_uint32_t rxfifo : 2; /**< The RX FIFO where the message was received. */ rt_uint32_t reserved : 5; #endif rt_uint32_t nonblocking : 1; /**< Send mode: 0=Blocking (default), 1=Non-blocking. */ @@ -548,41 +631,42 @@ struct rt_can_msg }; typedef struct rt_can_msg *rt_can_msg_t; -#ifndef RT_CAN_NB_TX_FIFO_SIZE -#define RT_CAN_NB_TX_FIFO_SIZE (RT_CANMSG_BOX_SZ * sizeof(struct rt_can_msg)) -#endif +struct rt_can_ops; +struct rt_can_rx_fifo; +struct rt_can_tx; /** - * @brief The core CAN device structure. + * @brief Generic CAN device state shared with low-level BSP drivers. + * + * can_rx and can_tx are typed pointers to dynamically allocated Generic runtimes; + * using typed pointers keeps the ownership explicit without embedding per-open + * buffers in every statically registered CAN device. */ struct rt_can_device { struct rt_device parent; /**< Inherits from the base RT-Thread device structure. */ - const struct rt_can_ops *ops; /**< A pointer to the low-level driver operations. */ struct can_configure config; /**< The current configuration of the CAN device. */ struct rt_can_status status; /**< The current status and error statistics of the CAN device. */ - - rt_uint32_t timerinitflag; /**< A flag to indicate if the status timer has been initialized. */ - struct rt_timer timer; /**< A timer for periodically checking the CAN bus status. */ - +#if defined(RT_CAN_USING_STATUS_POLLING) || defined(RT_CAN_USING_BUS_HOOK) + struct rt_timer timer; /**< Periodic maintenance timer for status polling and/or bus hook. */ +#endif /* RT_CAN_USING_STATUS_POLLING || RT_CAN_USING_BUS_HOOK */ +#ifdef RT_CAN_USING_STATUS_POLLING struct rt_can_status_ind_type status_indicate; /**< The user-registered status indication callback. */ +#endif /* RT_CAN_USING_STATUS_POLLING */ #ifdef RT_CAN_USING_HDR struct rt_can_hdr *hdr; /**< A pointer to an array of hardware filter list entries. */ -#endif +#endif /* RT_CAN_USING_HDR */ #ifdef RT_CAN_USING_BUS_HOOK rt_can_bus_hook bus_hook; /**< The user-registered periodic bus hook function. */ #endif /*RT_CAN_USING_BUS_HOOK*/ struct rt_mutex lock; /**< A mutex for thread-safe access to the device. */ - void *can_rx; /**< A pointer to the software receive FIFO structure (`rt_can_rx_fifo`). */ - void *can_tx; /**< A pointer to the software transmit FIFO structure (`rt_can_tx_fifo`). */ + struct rt_can_rx_fifo *can_rx; /**< Dynamically allocated software RX runtime. */ + struct rt_can_tx *can_tx; /**< Dynamically allocated TX mailbox runtime. */ - struct rt_ringbuffer nb_tx_rb; /**< The ring buffer for non-blocking transmissions. */ -#ifdef RT_CAN_MALLOC_NB_TX_BUFFER - rt_uint8_t *nb_tx_rb_pool; /**< A pointer to the dynamically allocated pool for the non-blocking TX ring buffer. */ -#else - rt_uint8_t nb_tx_rb_pool[RT_CAN_NB_TX_FIFO_SIZE]; /**< The statically allocated pool for the non-blocking TX ring buffer. */ -#endif /* RT_CAN_MALLOC_NB_TX_BUFFER */ + struct rt_spinlock tx_lock; /**< TX runtime publication, pending count and blocking slot ownership. */ + struct rt_spinlock rx_lock; /**< RX FIFO/filter publication plus rx_active_isr lifetime pins. */ + rt_uint32_t rx_active_isr; /**< RX ISR/callback lifetime pins, protected by rx_lock. */ }; typedef struct rt_can_device *rt_can_t; @@ -601,9 +685,8 @@ struct rt_can_msg_list { struct rt_list_node list; /**< List node to link into a free/used list. */ #ifdef RT_CAN_USING_HDR - struct rt_list_node hdrlist; /**< List node to link into a specific hardware filter's list. */ - struct rt_can_hdr *owner; /**< Pointer to the hardware filter that owns this message. */ -#endif + struct rt_list_node hdrlist; /**< List node to link into a specific hardware filter's list. */ +#endif /* RT_CAN_USING_HDR */ struct rt_can_msg data; /**< The actual CAN message data. */ }; @@ -617,39 +700,14 @@ struct rt_can_rx_fifo rt_uint32_t freenumbers; /**< The number of free nodes in the buffer pool. */ struct rt_list_node freelist; /**< The list of free message nodes. */ struct rt_list_node uselist; /**< The list of used message nodes (containing received messages). */ + struct rt_completion idle_completion; /**< Wakes the single management waiter when RX pins drain. */ }; -#define RT_CAN_SND_RESULT_OK 0 -#define RT_CAN_SND_RESULT_ERR 1 -#define RT_CAN_SND_RESULT_WAIT 2 - -#define RT_CAN_EVENT_RX_IND 0x01 /* Rx indication */ -#define RT_CAN_EVENT_TX_DONE 0x02 /* Tx complete */ -#define RT_CAN_EVENT_TX_FAIL 0x03 /* Tx fail */ -#define RT_CAN_EVENT_RX_TIMEOUT 0x05 /* Rx timeout */ -#define RT_CAN_EVENT_RXOF_IND 0x06 /* Rx overflow */ - -/** - * @internal - * @brief List node for a blocking send operation, corresponding to one hardware mailbox. - */ -struct rt_can_sndbxinx_list -{ - struct rt_list_node list; /**< List node to link into the free list. */ - struct rt_completion completion;/**< A completion object to block the sending thread. */ - rt_uint32_t result; /**< The result of the transmission (OK, ERR, WAIT). */ -}; - -/** - * @internal - * @brief Software transmit FIFO structure for blocking sends. - */ -struct rt_can_tx_fifo -{ - struct rt_can_sndbxinx_list *buffer; /**< A pointer to the buffer of sendbox nodes. */ - struct rt_semaphore sem; /**< A counting semaphore representing available hardware mailboxes. */ - struct rt_list_node freelist; /**< The list of free sendbox nodes. */ -}; +#define RT_CAN_EVENT_RX_IND 0x01 /* Rx indication */ +#define RT_CAN_EVENT_TX_DONE 0x02 /* Tx complete */ +#define RT_CAN_EVENT_TX_FAIL 0x03 /* Tx fail */ +#define RT_CAN_EVENT_RX_TIMEOUT 0x05 /* Rx timeout */ +#define RT_CAN_EVENT_RXOF_IND 0x06 /* Rx overflow */ /** * @brief The CAN device driver operations structure. @@ -675,11 +733,18 @@ struct rt_can_ops */ rt_err_t (*control)(struct rt_can_device *can, int cmd, void *arg); /** - * @brief Low-level blocking function to send a CAN message. + * @brief Perform one immediate CAN submit to a selected hardware mailbox. + * + * Generic CAN owns blocking policy and completion waiting. This BSP callback + * must only attempt the selected mailbox and return without sleeping or waiting + * for bus transmission completion. + * * @param[in] can A pointer to the CAN device structure. * @param[in] buf A pointer to the `rt_can_msg` to be sent. * @param[in] boxno The hardware mailbox number to use for transmission. - * @return The number of bytes sent on success, or a negative error code on failure. + * @return `RT_EOK` on hardware acceptance, `-RT_EBUSY` when the selected mailbox is temporarily busy, + * or another negative error code on failure. Generic CAN retries only explicit `-RT_EBUSY` results; + * other errors remain observable to the caller. */ rt_ssize_t (*sendmsg)(struct rt_can_device *can, const void *buf, rt_uint32_t boxno); /** @@ -718,10 +783,10 @@ struct rt_can_ops * * @return `RT_EOK` on successful registration, or a negative error code on failure. */ -rt_err_t rt_hw_can_register(struct rt_can_device *can, - const char *name, +rt_err_t rt_hw_can_register(struct rt_can_device *can, + const char *name, const struct rt_can_ops *ops, - void *data); + void *data); /** * @brief The framework-level ISR handler for CAN devices. @@ -735,7 +800,31 @@ rt_err_t rt_hw_can_register(struct rt_can_device *can, */ void rt_hw_can_isr(struct rt_can_device *can, int event); -/*! @}*/ +/** + * @brief Wait for all hardware-owned TX to retire. + * + * The caller must stop all TX producers before calling this management helper and + * serialize it against final device close. drain waits hardware ownership, not + * concurrent TX API-call lifetime. A close that has already dropped ref_count to + * zero is rejected defensively, but racing close after this check remains caller misuse. + * + * @param can CAN device to drain. + * @param timeout Maximum wait in OS ticks, or RT_WAITING_FOREVER. + * @return RT_EOK when idle, -RT_EBUSY after final close starts, -RT_ETIMEOUT on + * deadline expiry, or an IPC error. + */ +rt_err_t rt_can_tx_drain(struct rt_can_device *can, rt_tick_t timeout); -#endif /*__DEV_CAN_H*/ +/** + * @brief Request abort of one hardware TX mailbox. + * + * A successful control request does not retire Generic ownership; the BSP must + * later publish TX_FAIL when abort completion becomes visible. + * + * @param can CAN device whose mailbox is aborted. + * @param mailbox Generic-visible mailbox index. + * @return BSP control result, -RT_EINVAL for an invalid mailbox, or -RT_ENOSYS. + */ +rt_err_t rt_can_tx_abort(struct rt_can_device *can, rt_uint32_t mailbox); +#endif /* __DEV_CAN_H_ */