refactor[CAN]: improve CAN driver lifecycle and TX handling - #11779
refactor[CAN]: improve CAN driver lifecycle and TX handling#11779wdfk-prog wants to merge 2 commits into
Conversation
|
👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread! 为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。 🛠 操作步骤 | Steps
完成后,提交将自动更新至 如有问题欢迎联系我们,再次感谢您的贡献!💐 |
📌 Code Review Assignment🏷️ Tag: bsp_stm32Reviewers: @Liang1795 @hamburger-os @wdfk-prog Changed Files (Click to expand)
🏷️ Tag: componentsReviewers: @Maihuanyi Changed Files (Click to expand)
🏷️ Tag: components_driver_canReviewers: @wdfk-prog Changed Files (Click to expand)
📊 Current Review Status (Last Updated: 2026-09-02 13:30 CST)
📝 Review Instructions
|
Simplify the Generic CAN TX path and make hardware mailbox ownership explicit. - remove Generic CAN TX software queues and list-based mailbox allocation - use hardware mailboxes as the only Generic TX queueing resource - make non-blocking TX a one-shot hardware submit with -RT_EBUSY backpressure - move buffering, retry and scheduling policy to protocol/application layers - add TX drain and hardware mailbox abort support - retire TX ownership only on TX_DONE/TX_FAIL terminal events - split TX/RX runtime management from the device adapter - improve RX ISR/callback lifetime synchronization - improve controller reconfiguration and open/close lifecycle handling - update STM32 bxCAN mailbox reuse, terminal and abort semantics
44833a0 to
edb6552
Compare
|
|
其他bsp是不是也得从新适配can? |
|
illustriousness
left a comment
There was a problem hiding this comment.
结论 / Conclusion: Request changes
当前实现改变了 Generic CAN 与所有 BSP 之间的运行时契约,但只迁移了 STM32 bxCAN,因此暂不满足“其他 BSP 无需适配”的兼容性要求。
🔴 [Concurrency/并发]: Do not call un-migrated BSP send callbacks under irqsave / 不要在关中断锁内调用尚未迁移的 BSP 回调
English: ops->sendmsg() is now called while tx_lock holds IRQs disabled, but existing BSP callbacks have not been migrated to the immediate-submit contract. HPMicro calls rt_thread_mdelay(1), NS800 waits for its TX ISR to change mbState, and HT32 busy-waits for the mailbox. These paths can sleep with interrupts disabled or deadlock because the terminal ISR cannot run. Please migrate every in-tree CAN BSP first, or redesign submission with a reserving state so the BSP callback can execute outside the IRQ-disabled section.
中文:当前在 rt_spin_lock_irqsave() 保护区内调用 BSP sendmsg(),但现有 BSP 并不都满足“立即返回、不可等待”的新契约。HPMicro 调用 rt_thread_mdelay(),NS800 等待 TX ISR 更新状态,HT32 忙等邮箱。这会造成关中断睡眠,或因 TX ISR 无法执行而死锁。必须同步迁移所有后端,或增加 RESERVING 状态,让硬件提交在锁外执行。
🟠 [Lifecycle/生命周期]: Final close now requires every BSP to implement START(0) / 最终关闭强制要求所有 BSP 实现 START(0)
English: Final close now treats RT_CAN_CMD_START(0) as mandatory and returns before Generic runtime cleanup when the BSP rejects it. Many existing backends do not implement this command; Nuvoton returns -RT_EINVAL, while Renesas and HT32 return -RT_ERROR. At this point the device core has already reduced ref_count to zero, leaving RX/TX runtime allocated and admission closed, so reopen produces a partially initialized, unusable device. Please retain the legacy optional behavior or migrate/capability-gate all BSPs and test open-close-open.
中文:rt_device_close() 进入驱动回调前已经把 ref_count 减为 0;这里若 BSP 不支持 RT_CAN_CMD_START(0),会在释放 RX/TX runtime 之前返回。现有 Nuvoton、Renesas、HT32 等后端会拒绝该命令,导致设备处于 ref_count=0、runtime 未释放、TX admission 已关闭的残缺状态,后续 reopen 也无法恢复。需要保留旧的可选行为,或为所有 BSP 完成能力适配和 open-close-open 回归。
🔴 [Ownership/所有权]: Do not release ownership before a hardware terminal / 不要在硬件 terminal 前释放 ownership
English: On timeout the blocking path decrements pending and marks the slot FREE without aborting the hardware request. Management can therefore observe TX idle and reconfigure while the old frame is still hardware-owned. Its late terminal may also be treated as a nonblocking terminal and decrement an unrelated pending frame. Keep a TIMED_OUT or ABANDONED slot until TX_DONE/TX_FAIL retires it, or abort and wait for the terminal before releasing ownership.
中文:发送超时只代表等待超时,并不代表硬件已经结束请求。当前代码却执行 pending-- 并将 slot 设为 FREE。随后 drain/reconfigure 会错误判断 TX 已空闲;旧帧仍可能发送,迟到的 terminal 还可能冲掉其他 nonblocking 请求的 pending。应保留 TIMED_OUT/ABANDONED ownership,直到真实 TX_DONE/TX_FAIL 到来。
🟠 [Compatibility/兼容性]: Existing BSPs lose status polling / 现有 BSP 默认失去状态轮询
English: Although RT_CAN_USING_STATUS_POLLING defaults to y, all 30 checked-in CAN-enabled .config and rtconfig.h files lack this new symbol. Normal SCons builds consume the checked-in rtconfig.h, so status polling is disabled and RT_CAN_CMD_SET_STATUS_IND returns -RT_ENOSYS on every existing CAN BSP. Please preserve the previous source-level default or regenerate all affected BSP configs through the supported configuration flow.
中文:仓库中 30 个启用 CAN 的 .config/rtconfig.h 都没有新宏。普通 SCons 构建不会重新计算 Kconfig 的 default y,因此现有 BSP 均不会编译状态轮询路径,并会在 RT_CAN_CMD_SET_STATUS_IND 返回 -RT_ENOSYS。需要保留原有默认行为,或通过配置工具迁移全部相关 BSP。
补充 / Additional:
- STM32 bxCAN 将
AutoBusOff从启用改为禁用、AutoRetransmission从禁用改为启用,但没有增加明确的 bus-off 恢复路径;该线上行为变化需要恢复、配置化,或单独说明并验证。 - PR 标题和 commit 不符合
[module][subsystem] Description格式;建议改为[components][can] Refactor TX ownership and controller lifecycle,并 squash 格式化 commit。 - PR 描述中的 BSP、
.config和 Action 链接仍是占位符。 git diff --check、ClangFormat 检查通过;FT2004 的 Generic CAN 与 BSP CAN 对象可编译。GitHub 当前只有 CLA 成功,没有构建检查。
|
我感觉就不要在维护一套can_V2了,这样维护起来太麻烦了。看看有没有人感兴趣一起来push这个pr吧 |
PR Message
拉取/合并请求描述:(PR description)
关联 Issue
[Discussion][CAN] RT-Thread CAN Framework 重构方案[Feature][CAN] 支持 TX Full Flush 与 Hardware Abort 能力为什么提交这份 PR (why to submit this PR)
本 PR 对 RT-Thread Generic CAN Framework 及 STM32 bxCAN 后端进行一次结构性重构。
这次修改并不是针对某一个 CAN BSP 的局部 Bug 修复,而是希望重新明确 Generic CAN Framework 的职责边界:
本次方案来源于 #11767 和 #11759 中暴露出来的一系列问题,但最终实现相比 #11767 最初讨论的
TX Request Pool + Software FIFO + Scheduler方案进一步进行了简化。当前实现不再在 Generic CAN 中维护 TX software queue,而是直接使用 CAN Controller 的 hardware mailbox 作为 Generic TX 的唯一排队资源。
这样可以显著减少 Framework 内部状态、并发关系和资源生命周期,同时让 Nonblocking、Abort、Runtime Reconfiguration 等行为具有更清晰的语义。
1. 旧 CAN TX 设计存在的问题
旧 Generic CAN Framework 中实际上存在两套不同的发送模型。
Blocking TX
Blocking TX 使用:
rt_can_tx_fifort_can_sndbxinx_liststatus.sndchange共同管理 TX mailbox。
但是 CAN Controller 的发送 mailbox 本身就是数量固定、编号固定的硬件资源。
例如 STM32 bxCAN 固定提供:
为这样一个固定、小规模、可以直接索引的资源再建立 linked-list freelist,会增加:
这些结构本身并没有提供额外的发送能力。
因此本 PR 删除 TX mailbox linked-list 管理,将其改为:
一个 software slot 直接对应一个 Framework-visible hardware mailbox。
Nonblocking TX
旧 Nonblocking TX 是另外一套完全不同的路径:
这使 Generic CAN Framework 自己承担了一层 software TX queue。
这样存在几个问题。
1. Blocking / Nonblocking 是两套发送状态机
Blocking TX 维护:
Nonblocking TX 又维护:
实际上两者最终操作的是同一组 CAN hardware mailbox,却由 Framework 维护两套完全不同的资源和状态。
随着 Abort、Runtime Bitrate Change、Bus Recovery、SMP 等能力加入,必须同时考虑两套 TX 状态,维护成本持续增加。
2. Generic CAN 隐式缓存改变了 Nonblocking 的语义
旧实现中:
返回成功并不一定表示:
也可能只是:
因此应用很难准确判断一帧当前究竟处于:
哪一个阶段。
对于需要:
的场景,这种隐式缓存会让发送状态变得模糊。
3. ISR refill 增加了顺序和并发复杂度
旧实现会在
TX_DONE/TX_FAILISR 中继续读取nb_tx_rb并重新向硬件提交。当 refill 失败时,还需要重新把 frame 放回 software queue。
这会进一步引入:
三者之间的并发关系。
Generic CAN 此时不仅是 Device Framework,同时还成为了 TX scheduler。
这超出了本 PR 希望 Generic CAN 承担的职责范围。
2. 为什么不继续实现 Generic TX Request Queue / Scheduler
#11767 最初讨论过一种更完整的统一 TX Engine:
这个方向可以统一 blocking / nonblocking,并建立完整 request ownership。
但是在进一步实现和测试后,本 PR 最终选择了更简单的设计:
原因是发送缓存本质上属于策略,而不是 CAN hardware abstraction 必须提供的机制。
不同协议对于 TX queue 的要求完全不同,例如:
Generic CAN 如果强制提供一个 FIFO,实际上等于替所有上层协议选择了一种固定的缓存策略。
同时对于本身已经有 TX queue 的协议栈,还会形成:
双重缓存。
这会增加:
因此本 PR 将职责重新划分为:
这样 Generic CAN 保持为一个较轻量、确定性的 Hardware/Device Framework。
3. 新的 TX 设计
新的 Generic CAN TX 设计遵循以下原则:
TX_DONE或TX_FAIL;-RT_EBUSY向上层提供明确 backpressure。整体流程:
flowchart TD A["Application / Protocol"] --> B["rt_device_write()"] B --> C{"Blocking?"} C -->|Yes| D["Scan fixed mailbox slots"] D --> E["ops->sendmsg(mailbox)"] E --> F["Hardware mailbox"] C -->|No| G["ops->sendmsg_nonblocking() once"] G -->|RT_EOK| F G -->|-RT_EBUSY| H["Return backpressure to caller"] F --> I["CAN Controller"] I --> J["TX_DONE / TX_FAIL IRQ"] J --> K["rt_hw_can_isr()"] K --> L["Generic TX terminal"] L --> M["pending-- / ownership retire"] M --> N["Mailbox reusable"]4. Blocking TX:删除 linked-list,直接映射 Hardware Mailbox
Blocking TX 不再建立 mailbox freelist。
现在每一个 hardware mailbox 对应一个固定 slot:
每个 slot 只维护:
Blocking send 流程:
这种设计的优势:
5. Nonblocking TX:直接发送,不再由 Generic CAN 缓存
新的 Nonblocking 定义为:
流程:
Generic CAN:
如果用户需要:
由上层自行实现。
这种设计使 Nonblocking 的返回语义非常明确:
6. 删除 Generic Nonblocking TX Ringbuffer
由于 Nonblocking 改为直接 hardware submit,本 PR 删除:
同时删除:
这一整套 ISR refill 逻辑。
因此 TX ISR 现在只负责一个职责:
即:
不再承担 scheduler 的职责。
7. Terminal 是 TX ownership 的唯一结束点
新的 Generic CAN 使用
pending跟踪已经被 hardware 接受、但还没有收到 terminal 的 frame 数量。关键原则是:
只有收到:
或者:
才能结束 hardware ownership。
因此:
Generic CAN 不通过 timeout、abort request 或 close request 直接伪造一个 hardware terminal。
8. 为什么这个设计更容易支持 Abort
#11759 最初提出 Full Flush 时,旧 Framework 需要同时处理:
新的设计已经删除 Generic TX software queue。
因此 Framework 中需要处理的 TX 状态收敛为:
这使 Abort 可以保持非常明确的硬件语义。
RT_CAN_CMD_ABORT_TX重要的是:
Generic CAN 不会在调用
RT_CAN_CMD_ABORT_TX后立即pending--。真正结束 ownership 的仍然是随后到来的
TX_FAIL。这样避免 Abort 和 ISR 并发时出现:
9. Full Flush 的设计也因此被简化
本 PR 不再增加一个负责清除 Generic software queue 的
RT_CAN_CMD_FLUSH_TX,因为 Generic CAN 已经没有 TX software queue。完整 TX silence window 应由不同层分别完成自己的职责。
例如 CANopen LSS Runtime Bitrate Change:
换句话说:
10. TX Drain
新增:
rt_can_tx_drain()用于等待:
Drain 不产生新的发送策略,也不会自动 Abort。
它只回答一个问题:
因此上层可以根据自己的业务要求选择:
Graceful
Forced
11. STM32 bxCAN Hardware Abort
STM32 bxCAN 提供 3 个 TX mailbox。
本 PR 为其实现明确的:
mailbox 映射:
非法 mailbox 返回:
同时 Abort 操作只写目标
ABRQx,避免对 bxCANTSR中其它 W1C terminal flag 进行不必要的 Read-Modify-Write。这是因为
RQCP0/RQCP1/RQCP2属于 Write-1-to-Clear 状态。Abort mailbox 0 时不应该因为寄存器 RMW 意外消费 mailbox 1/2 尚未上报给 Generic CAN 的 terminal。
12. Mailbox 在旧 Terminal 被消费前禁止复用
除了 TME(Transmit Mailbox Empty)以外,本 PR 还需要考虑
RQCPx是否仍然表示前一个 transaction 存在未消费 terminal。因此:
不等价于:
新的 frame 不能在旧
RQCPx仍 pending 时复用相同 mailbox。否则可能出现:
13. Blocking / Nonblocking 共用 Terminal Accounting
虽然 Blocking 和 Nonblocking 的提交接口不同:
但是进入 hardware 后,都统一进入:
Blocking mailbox 如果存在
ACTIVE slot,terminal 会更新该 slot 并唤醒对应 completion。如果 mailbox 没有 blocking owner,则 terminal 用于 retirement 一个已经被 hardware 接受的 Nonblocking TX。
14. TX 并发同步
新的 TX 核心使用独立
tx_lock保护:BSP submit 被定义为 immediate hardware submit,即不能:
因此可以在 TX critical section 中完成:
用于关闭快速 TX IRQ 在 ownership publish 前到来的竞态窗口。
15. Controller Management / Reconfiguration Lifecycle
本次重构不仅处理 TX,同时重新整理 CAN controller management。
旧代码中:
与 TX/RX runtime 的生命周期关系并不清晰。
新的 management transaction 使用:
flowchart TD A["Application stops TX producer"] --> B["can->lock"] B --> C["Close Generic TX admission"] C --> D{"TX completely idle?"} D -->|No| E["Return busy / caller chooses drain or abort"] D -->|Yes| F["Pause RX admission"] F --> G["Wait in-flight RX ISR/callback pins"] G --> H["BSP configure/control"] H --> I["Resume RX"] I --> J["Restore TX admission"]职责划分:
16. RX 为什么仍然保留 Software FIFO
本次只删除 TX software queue,并没有删除 RX FIFO。
这是刻意的设计差异。
RX 的生产者和消费者天然位于不同上下文:
如果 Generic CAN 不提供 RX FIFO,就可能在应用没有立即读取时直接丢失 hardware RX frame。
因此 RX buffering 属于 Device Framework 的合理职责。
而 TX 不同,发送端完全可以通过
-RT_EBUSY把 hardware backpressure 反馈给 caller,由上层决定下一步策略。所以新的原则是:
17. RX ISR / Callback Lifetime
RX 路径增加独立:
RX ISR 处理流程:
这样
recvmsg()和 user callback 不会长时间占用rx_lock。与此同时 close/reconfigure 可以先停止新的 RX admission,再等待
rx_active_isr == 0,之后才释放 RX/HDR runtime。18. 模块职责拆分
原来的
dev_can.c同时承担 device adapter、TX queue、RX queue、ISR processing、lifecycle、control。本 PR 将 Generic CAN 拆分为:
目标是让 TX correctness、RX correctness、Controller lifecycle 三个问题分别具有清晰的 ownership boundary。
19. Public API / BSP Compatibility
本次重构尽量保持现有 RT-Thread CAN 使用接口。
继续保留:
现有 BSP 的中断入口仍然通过
rt_hw_can_isr()向 Generic CAN 上报 RX/TX event。需要特别说明的一项行为变化是 Nonblocking 不再由 Generic CAN 提供 software buffering。
旧行为:
新行为:
如果应用以前依赖
nb_tx_rb进行 burst buffering,需要将该 queue 放到应用或协议层。这不是简单删除能力,而是有意进行的职责边界调整。
20. Kconfig 简化
因为移除了 Generic Nonblocking TX software queue,因此删除与其相关的配置:
不再需要用户考虑 Generic TX FIFO depth、static/dynamic TX buffer 以及 CAN/CAN-FD frame size 对 TX ringbuffer 的影响。
同时:
语义明确为 Generic CAN 可见的 Hardware TX Mailbox 数量。
对于 STM32 bxCAN,实际测试配置为:
对应 controller 的三个 TX mailbox。
21. 与 #11759 Full Flush / Hardware Abort 的关系
#11759 最初的问题本质是:
因此旧方案必须设计一个 Framework Full Flush。
本 PR 删除 Generic TX software queue 后,这个问题被重新拆分:
因此本次方案倾向于提供可组合、语义明确的基础能力:
而不是在 Framework 内部重新建立一个同时处理 software queue、protocol policy 和 hardware transaction 的大型 Flush 状态机。
测试验证
本 PR 已在真实目标板 + 实际 CAN 总线上执行功能和回归测试。
测试总线:
1. Blocking CAN / CANopenNode SDO Block 回归测试 — PASS
使用 CANopenNode 作为实际协议负载,对 blocking CAN TX/RX 路径进行回归。
SDO Block Transfer 测试长度:
同时验证:
最大数据长度稳定性:
2. Communication Reset / Reopen — PASS
验证:
结果:PASS。
3. Nonblocking TX - Paced — PASS
该测试不依赖 CANopen,在干净的 Generic CAN 环境下执行。
结果:PASS。
4. Nonblocking TX - Burst / Backpressure — PASS
连续快速提交 1000 次 Nonblocking TX:
结合 paced test:
满足:
结果:PASS。
5.
RT_CAN_CMD_ABORT_TX参数检查 — PASS6. Hardware TX Abort — PASS
Linux SocketCAN 节点持续发送高优先级:
制造实际 arbitration load。
目标 MCU 提交较低优先级 Nonblocking frame,并立即:
测试结果:
证明:
结果:PASS。
7. Abort 后 Mailbox Recovery — PASS
完成 Hardware Abort 后再次发送 CAN frame。
证明 Abort 后 old ownership 已退休、pending 已恢复、mailbox 可复用、新 TX 可以正常接受和完成。
结果:PASS。
测试总结
-RT_EBUSYBackpressureRT_CAN_CMD_ABORT_TXTX_FAILTerminalNonblocking 和 Abort 专项测试均直接针对 RT-Thread Generic CAN API 执行,不依赖 CANopen。
请提供验证的 bsp 和 config (provide the config and bsp)
BSP:
[填写实际 STM32 BSP 路径]Runtime CAN configuration:
.config:[填写本分支 GitHub Action 编译链接]当前拉取/合并请求的状态 Intent for your PR
必须选择一项 Choose one (Mandatory):
代码质量 Code Quality:
我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:
#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up