Skip to content

refactor[CAN]: improve CAN driver lifecycle and TX handling - #11779

Open
wdfk-prog wants to merge 2 commits into
RT-Thread:masterfrom
wdfk-prog:refactor/can-framework
Open

refactor[CAN]: improve CAN driver lifecycle and TX handling#11779
wdfk-prog wants to merge 2 commits into
RT-Thread:masterfrom
wdfk-prog:refactor/can-framework

Conversation

@wdfk-prog

@wdfk-prog wdfk-prog commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

PR Message

拉取/合并请求描述:(PR description)

关联 Issue


为什么提交这份 PR (why to submit this PR)

本 PR 对 RT-Thread Generic CAN Framework 及 STM32 bxCAN 后端进行一次结构性重构。

这次修改并不是针对某一个 CAN BSP 的局部 Bug 修复,而是希望重新明确 Generic CAN Framework 的职责边界:

Generic CAN 负责硬件资源管理、同步、ownership、terminal 和 lifecycle;发送缓存、重试、调度和协议 QoS 策略由上层协议栈或应用自行决定。

本次方案来源于 #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

rt_device_write()
      ↓
_can_int_tx()
      ↓
TX mailbox freelist
      ↓
semaphore
      ↓
sendmsg(mailbox)
      ↓
completion
      ↓
TX_DONE / TX_FAIL

Blocking TX 使用:

  • rt_can_tx_fifo
  • rt_can_sndbxinx_list
  • linked-list freelist
  • semaphore
  • per-mailbox completion
  • status.sndchange

共同管理 TX mailbox。

但是 CAN Controller 的发送 mailbox 本身就是数量固定、编号固定的硬件资源。

例如 STM32 bxCAN 固定提供:

mailbox 0
mailbox 1
mailbox 2

为这样一个固定、小规模、可以直接索引的资源再建立 linked-list freelist,会增加:

  • list insert/remove;
  • semaphore 与 freelist 双重状态;
  • mailbox index 与 software node 之间的映射;
  • terminal 后的软件资源释放流程;
  • Abort 时对“谁拥有这个 mailbox”的判断成本。

这些结构本身并没有提供额外的发送能力。

因此本 PR 删除 TX mailbox linked-list 管理,将其改为:

hardware mailbox N
        ↕
fixed slot[N]

一个 software slot 直接对应一个 Framework-visible hardware mailbox。

Nonblocking TX

旧 Nonblocking TX 是另外一套完全不同的路径:

rt_device_write(nonblocking)
        ↓
sendmsg_nonblocking()
        ↓
hardware available?
   ┌────┴────┐
  Yes        No
   │          │
hardware   nb_tx_rb
mailbox       │
              ↓
        TX_DONE ISR
              ↓
      从 ringbuffer 取帧
              ↓
    sendmsg_nonblocking()
              ↓
      hardware mailbox

这使 Generic CAN Framework 自己承担了一层 software TX queue。

这样存在几个问题。

1. Blocking / Nonblocking 是两套发送状态机

Blocking TX 维护:

freelist
semaphore
completion
sndchange

Nonblocking TX 又维护:

sendmsg_nonblocking
nb_tx_rb
ISR refill

实际上两者最终操作的是同一组 CAN hardware mailbox,却由 Framework 维护两套完全不同的资源和状态。

随着 Abort、Runtime Bitrate Change、Bus Recovery、SMP 等能力加入,必须同时考虑两套 TX 状态,维护成本持续增加。

2. Generic CAN 隐式缓存改变了 Nonblocking 的语义

旧实现中:

rt_device_write(nonblocking)

返回成功并不一定表示:

frame 已经被 CAN controller 接受

也可能只是:

frame 被放入 Generic CAN 的 nb_tx_rb

因此应用很难准确判断一帧当前究竟处于:

Application
    ↓
Generic software queue
    ↓
Hardware mailbox
    ↓
CAN bus

哪一个阶段。

对于需要:

  • Abort;
  • Runtime bitrate switching;
  • Bus recovery;
  • Shutdown;
  • Protocol reset;
  • 严格 TX silence window;

的场景,这种隐式缓存会让发送状态变得模糊。

3. ISR refill 增加了顺序和并发复杂度

旧实现会在 TX_DONE/TX_FAIL ISR 中继续读取 nb_tx_rb 并重新向硬件提交。

当 refill 失败时,还需要重新把 frame 放回 software queue。

这会进一步引入:

Thread producer
       +
ISR consumer/refill
       +
Hardware mailbox

三者之间的并发关系。

Generic CAN 此时不仅是 Device Framework,同时还成为了 TX scheduler。

这超出了本 PR 希望 Generic CAN 承担的职责范围。


2. 为什么不继续实现 Generic TX Request Queue / Scheduler

#11767 最初讨论过一种更完整的统一 TX Engine:

Application
     ↓
TX Request Pool
     ↓
Software FIFO
     ↓
TX Scheduler
     ↓
Mailbox Ownership
     ↓
CAN Hardware

这个方向可以统一 blocking / nonblocking,并建立完整 request ownership。

但是在进一步实现和测试后,本 PR 最终选择了更简单的设计:

Generic CAN 不提供 TX software queue。

原因是发送缓存本质上属于策略,而不是 CAN hardware abstraction 必须提供的机制。

不同协议对于 TX queue 的要求完全不同,例如:

CANopen
    -> 自己已经具有 TX buffer / inhibit / priority 等机制

Motor control
    -> 可能只需要保留最新 command

Diagnostic protocol
    -> 可能需要 FIFO

Realtime control
    -> 可能宁愿直接丢弃旧帧,也不能排队增加 latency

Gateway
    -> 可能需要 priority queue / rate limit

Generic CAN 如果强制提供一个 FIFO,实际上等于替所有上层协议选择了一种固定的缓存策略。

同时对于本身已经有 TX queue 的协议栈,还会形成:

Protocol TX queue
        ↓
Generic CAN TX queue
        ↓
Hardware mailbox

双重缓存。

这会增加:

  • RAM 消耗;
  • latency;
  • queue depth 不确定性;
  • flush 范围;
  • abort 范围;
  • reset/reconfigure 的复杂度。

因此本 PR 将职责重新划分为:

┌─────────────────────────────────┐
│ Protocol / Application          │
│                                 │
│ Optional queue                  │
│ Retry policy                    │
│ Priority / QoS                  │
│ Drop / overwrite policy         │
└───────────────┬─────────────────┘
                │
                ▼
┌─────────────────────────────────┐
│ Generic CAN                     │
│                                 │
│ Hardware submit                 │
│ Mailbox ownership               │
│ Blocking wait                   │
│ TX terminal accounting          │
│ Drain / Abort                   │
│ RX lifetime                     │
│ Controller lifecycle            │
└───────────────┬─────────────────┘
                │
                ▼
┌─────────────────────────────────┐
│ BSP / CAN Controller            │
│                                 │
│ Hardware mailbox                │
│ IRQ                             │
│ Abort                           │
└─────────────────────────────────┘

这样 Generic CAN 保持为一个较轻量、确定性的 Hardware/Device Framework。


3. 新的 TX 设计

新的 Generic CAN TX 设计遵循以下原则:

  1. Generic CAN 不缓存待发送 CAN frame;
  2. Hardware mailbox 是 Generic TX 唯一的排队资源;
  3. BSP submit 必须是短路径硬件操作,不等待 CAN 总线发送完成;
  4. Blocking 与 Nonblocking 共享同一套 hardware terminal accounting;
  5. Hardware accepted frame 必须最终对应一个 TX_DONETX_FAIL
  6. Hardware mailbox 在旧 terminal 被 Framework 消费之前不能被复用;
  7. Abort request 本身不释放 ownership;
  8. ownership 只由最终 hardware terminal 结束;
  9. 上层如果需要 software queue,应自行实现;
  10. Framework 通过 -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"]
Loading

4. Blocking TX:删除 linked-list,直接映射 Hardware Mailbox

Blocking TX 不再建立 mailbox freelist。

现在每一个 hardware mailbox 对应一个固定 slot:

slot[0] <-> hardware mailbox 0
slot[1] <-> hardware mailbox 1
slot[2] <-> hardware mailbox 2

每个 slot 只维护:

state
completion

Blocking send 流程:

rt_device_write()
        ↓
scan FREE slot
        ↓
ops->sendmsg(can, msg, mailbox)
        ↓
hardware accepts
        ↓
slot = ACTIVE
pending++
        ↓
wait slot completion
        ↓
TX_DONE / TX_FAIL
        ↓
slot = DONE / FAILED
pending--
        ↓
wake blocking caller
        ↓
slot = FREE

这种设计的优势:

  • hardware mailbox 与 software ownership 一一对应;
  • 不再需要 TX freelist;
  • 不再需要 list node 分配/释放;
  • Abort 可以直接使用 mailbox index;
  • Terminal 可以直接定位 blocking owner;
  • mailbox 数量很小时固定数组比 linked-list 更直接;
  • 状态更容易 Review 和 Debug。

5. Nonblocking TX:直接发送,不再由 Generic CAN 缓存

新的 Nonblocking 定义为:

尝试一次立即 hardware submit。

流程:

rt_device_write(nonblocking = 1)
        ↓
ops->sendmsg_nonblocking()
        ↓
scan hardware mailbox once
        ↓
┌───────────────────────┐
│ 有可用 hardware slot │
└───────────┬───────────┘
            ↓
          RT_EOK
            ↓
        pending++
            ↓
         return

如果无可用 mailbox:

        -RT_EBUSY
            ↓
       立即返回 caller

Generic CAN:

不会 retry
不会 sleep
不会进入 software FIFO
不会在 TX ISR 中 refill software queue

如果用户需要:

retry
queue
priority
drop
latest-value overwrite
rate limit

由上层自行实现。

这种设计使 Nonblocking 的返回语义非常明确:

RT_EOK
    = 已经被 hardware TX resource 接受

-RT_EBUSY
    = 当前 hardware TX resource 不可用

6. 删除 Generic Nonblocking TX Ringbuffer

由于 Nonblocking 改为直接 hardware submit,本 PR 删除:

nb_tx_rb
nb_tx_rb_pool
RT_CAN_NB_TX_FIFO_SIZE
RT_CAN_MALLOC_NB_TX_BUFFER

同时删除:

TX_DONE ISR
    ↓
读取 nb_tx_rb
    ↓
重新 submit
    ↓
失败后重新塞回 ringbuffer

这一整套 ISR refill 逻辑。

因此 TX ISR 现在只负责一个职责:

处理 Hardware TX Terminal

即:

TX_DONE
或
TX_FAIL

不再承担 scheduler 的职责。


7. Terminal 是 TX ownership 的唯一结束点

新的 Generic CAN 使用 pending 跟踪已经被 hardware 接受、但还没有收到 terminal 的 frame 数量。

关键原则是:

Submit success != transaction finished

只有收到:

RT_CAN_EVENT_TX_DONE

或者:

RT_CAN_EVENT_TX_FAIL

才能结束 hardware ownership。

因此:

hardware submit
        ↓
pending++
        ↓
...
        ↓
TX_DONE / TX_FAIL
        ↓
pending--

Generic CAN 不通过 timeout、abort request 或 close request 直接伪造一个 hardware terminal。


8. 为什么这个设计更容易支持 Abort

#11759 最初提出 Full Flush 时,旧 Framework 需要同时处理:

1. Generic software TX queue
2. Hardware mailbox pending frame
3. Blocking sender

新的设计已经删除 Generic TX software queue。

因此 Framework 中需要处理的 TX 状态收敛为:

Hardware accepted frame
        +
Blocking waiter(如果存在)

这使 Abort 可以保持非常明确的硬件语义。

RT_CAN_CMD_ABORT_TX

RT_CAN_CMD_ABORT_TX(mailbox)
        ↓
BSP requests hardware abort
        ↓
CAN controller terminates mailbox
        ↓
TX_FAIL IRQ
        ↓
rt_hw_can_isr()
        ↓
Generic terminal handler
        ↓
pending--
        ↓
blocking waiter receives failure
        ↓
mailbox becomes reusable

重要的是:

ABORT_TX 请求成功不等于 TX ownership 已结束。

Generic CAN 不会在调用 RT_CAN_CMD_ABORT_TX 后立即 pending--

真正结束 ownership 的仍然是随后到来的 TX_FAIL

这样避免 Abort 和 ISR 并发时出现:

double release
double pending--
completion mismatch
mailbox reused before old terminal

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:

Disable CANopen TX producer
        ↓
clear/stop CANopen's own TX queue if required
        ↓
rt_can_tx_drain()
        │
        ├─ graceful completion
        │
        └─ timeout / policy decision
              ↓
       RT_CAN_CMD_ABORT_TX
       / RT_CAN_CMD_ABORT_ALL
              ↓
       wait TX terminal / drain
              ↓
RT_CAN_CMD_SET_BAUD
              ↓
PRE/POST timing window
              ↓
reconfigure CAN filters if required
              ↓
Enable CANopen TX

换句话说:

Protocol queue
    -> Protocol owns it

Generic hardware ownership
    -> Generic CAN owns it

Hardware abort
    -> BSP owns it

10. TX Drain

新增:

rt_can_tx_drain()

用于等待:

pending == 0
+
all blocking slots == FREE

Drain 不产生新的发送策略,也不会自动 Abort。

它只回答一个问题:

当前 Generic CAN 是否已经没有 hardware-owned TX。

因此上层可以根据自己的业务要求选择:

Graceful

stop producer
    ↓
drain
    ↓
reconfigure

Forced

stop producer
    ↓
abort hardware TX
    ↓
wait TX_FAIL terminal
    ↓
drain
    ↓
reconfigure

11. STM32 bxCAN Hardware Abort

STM32 bxCAN 提供 3 个 TX mailbox。

本 PR 为其实现明确的:

RT_CAN_CMD_ABORT_TX

mailbox 映射:

0 -> ABRQ0
1 -> ABRQ1
2 -> ABRQ2

非法 mailbox 返回:

-RT_EINVAL

同时 Abort 操作只写目标 ABRQx,避免对 bxCAN TSR 中其它 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。

因此:

Hardware 已经发送完成

不等价于:

Generic CAN 已经处理完该 mailbox 的 terminal

新的 frame 不能在旧 RQCPx 仍 pending 时复用相同 mailbox。

否则可能出现:

Frame A completed
        ↓
A terminal 尚未进入 Generic CAN
        ↓
mailbox 被 Frame B 复用
        ↓
A 的 late terminal
        ↓
错误结束 Frame B ownership

13. Blocking / Nonblocking 共用 Terminal Accounting

虽然 Blocking 和 Nonblocking 的提交接口不同:

Blocking
    -> sendmsg(mailbox)

Nonblocking
    -> sendmsg_nonblocking()

但是进入 hardware 后,都统一进入:

pending accounting
        ↓
TX_DONE / TX_FAIL
        ↓
rt_can_tx_isr_core()

Blocking mailbox 如果存在 ACTIVE slot,terminal 会更新该 slot 并唤醒对应 completion。

如果 mailbox 没有 blocking owner,则 terminal 用于 retirement 一个已经被 hardware 接受的 Nonblocking TX。


14. TX 并发同步

新的 TX 核心使用独立 tx_lock 保护:

TX runtime publication
accepting
blocking slot state
pending
hardware submit publication
terminal accounting

BSP submit 被定义为 immediate hardware submit,即不能:

sleep
等待 TX complete
take blocking semaphore/mutex

因此可以在 TX critical section 中完成:

hardware submit
+
Generic ownership publication

用于关闭快速 TX IRQ 在 ownership publish 前到来的竞态窗口。


15. Controller Management / Reconfiguration Lifecycle

本次重构不仅处理 TX,同时重新整理 CAN controller management。

旧代码中:

open
close
SET_BAUD
SET_MODE
SET_FILTER
START/STOP

与 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"]
Loading

职责划分:

can->lock
    -> thread-side management serialization

tx_lock
    -> TX ownership / terminal / admission

rx_lock
    -> RX FIFO / HDR / RX lifetime

16. RX 为什么仍然保留 Software FIFO

本次只删除 TX software queue,并没有删除 RX FIFO。

这是刻意的设计差异。

RX 的生产者和消费者天然位于不同上下文:

CAN RX IRQ
    -> producer

Application rt_device_read()
    -> consumer

如果 Generic CAN 不提供 RX FIFO,就可能在应用没有立即读取时直接丢失 hardware RX frame。

因此 RX buffering 属于 Device Framework 的合理职责。

而 TX 不同,发送端完全可以通过 -RT_EBUSY 把 hardware backpressure 反馈给 caller,由上层决定下一步策略。

所以新的原则是:

RX:
    Generic CAN retains software FIFO

TX:
    Generic CAN does not provide software FIFO

17. RX ISR / Callback Lifetime

RX 路径增加独立:

rx_lock
rx_active_isr

RX ISR 处理流程:

IRQ
 ↓
under rx_lock:
    acquire RX lifetime pin
 ↓
outside rx_lock:
    BSP recvmsg()
 ↓
under rx_lock:
    publish RX FIFO/HDR state
    snapshot callback
 ↓
outside rx_lock:
    execute callback
 ↓
under rx_lock:
    release lifetime pin

这样 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 拆分为:

dev_can.c
    -> RT-Thread device adapter
    -> open / close
    -> read / write routing
    -> control / management
    -> ISR dispatch

can_tx.c
    -> TX slots
    -> blocking wait
    -> nonblocking submit
    -> pending accounting
    -> terminal
    -> drain / abort

can_rx.c
    -> RX FIFO
    -> HDR routing
    -> RX ISR
    -> callback lifetime
    -> pause / resume

can_internal.h
    -> Generic CAN private contracts

目标是让 TX correctness、RX correctness、Controller lifecycle 三个问题分别具有清晰的 ownership boundary。


19. Public API / BSP Compatibility

本次重构尽量保持现有 RT-Thread CAN 使用接口。

继续保留:

rt_device_open()
rt_device_read()
rt_device_write()
rt_device_control()

struct rt_can_msg

rt_hw_can_isr(can, event)

现有 BSP 的中断入口仍然通过 rt_hw_can_isr() 向 Generic CAN 上报 RX/TX event。

需要特别说明的一项行为变化是 Nonblocking 不再由 Generic CAN 提供 software buffering。

旧行为:

hardware busy
    ↓
Generic queue
    ↓
write returns accepted

新行为:

hardware busy
    ↓
-RT_EBUSY

如果应用以前依赖 nb_tx_rb 进行 burst buffering,需要将该 queue 放到应用或协议层。

这不是简单删除能力,而是有意进行的职责边界调整。


20. Kconfig 简化

因为移除了 Generic Nonblocking TX software queue,因此删除与其相关的配置:

RT_CAN_NB_TX_FIFO_SIZE
RT_CAN_MALLOC_NB_TX_BUFFER

不再需要用户考虑 Generic TX FIFO depth、static/dynamic TX buffer 以及 CAN/CAN-FD frame size 对 TX ringbuffer 的影响。

同时:

RT_CANSND_BOX_NUM

语义明确为 Generic CAN 可见的 Hardware TX Mailbox 数量。

对于 STM32 bxCAN,实际测试配置为:

sndboxnumber = 3

对应 controller 的三个 TX mailbox。


21. 与 #11759 Full Flush / Hardware Abort 的关系

#11759 最初的问题本质是:

Upper-layer TX disabled
        ↓
但 Generic software queue 仍有 frame
        ↓
Hardware mailbox 仍有 frame
        ↓
Blocking sender 仍可能等待

因此旧方案必须设计一个 Framework Full Flush。

本 PR 删除 Generic TX software queue 后,这个问题被重新拆分:

Upper-layer buffered frame
    -> upper layer owns cancellation

Generic accepted hardware TX
    -> drain / terminal accounting

Hardware pending TX
    -> RT_CAN_CMD_ABORT_TX

Controller reconfiguration
    -> Generic management lifecycle

因此本次方案倾向于提供可组合、语义明确的基础能力:

TX producer gate
+
rt_can_tx_drain()
+
RT_CAN_CMD_ABORT_TX
+
controller management

而不是在 Framework 内部重新建立一个同时处理 software queue、protocol policy 和 hardware transaction 的大型 Flush 状态机。


测试验证

本 PR 已在真实目标板 + 实际 CAN 总线上执行功能和回归测试。

测试总线:

CAN bitrate: 1 Mbit/s

MCU:
    RT-Thread Generic CAN
    STM32 bxCAN
    sndboxnumber = 3

Peer:
    Linux SocketCAN
    can-utils

1. Blocking CAN / CANopenNode SDO Block 回归测试 — PASS

使用 CANopenNode 作为实际协议负载,对 blocking CAN TX/RX 路径进行回归。

SDO Block Transfer 测试长度:

32 Bytes       PASS
900 Bytes      PASS
1024 Bytes     PASS
1025 Bytes     PASS
2048 Bytes     PASS

同时验证:

Read-only object write abort + recovery        PASS
Stopped-node timeout recovery                  PASS
Client abort + recovery                        PASS
Block -> Expedited regression                  PASS
Block -> Segmented regression                  PASS

最大数据长度稳定性:

2048 Bytes × 100 round trips = PASS

2. Communication Reset / Reopen — PASS

验证:

CAN close
    ↓
controller deinit
    ↓
CAN reopen
    ↓
RX/TX runtime rebuild
    ↓
filter reconfiguration
    ↓
communication recovery

结果:PASS。

3. Nonblocking TX - Paced — PASS

该测试不依赖 CANopen,在干净的 Generic CAN 环境下执行。

accepted = 100
busy     = 0
TX_FAIL  = 0

结果:PASS。

4. Nonblocking TX - Burst / Backpressure — PASS

连续快速提交 1000 次 Nonblocking TX:

accepted  = 49
-RT_EBUSY = 951
other_err = 0

结合 paced test:

accepted_total = 149
TX_DONE        = 149
TX_FAIL        = 0

满足:

accepted_total == TX_DONE + TX_FAIL
149            == 149

结果:PASS。

5. RT_CAN_CMD_ABORT_TX 参数检查 — PASS

invalid-mailbox boundary PASS

6. Hardware TX Abort — PASS

Linux SocketCAN 节点持续发送高优先级:

CAN ID = 0x001

制造实际 arbitration load。

目标 MCU 提交较低优先级 Nonblocking frame,并立即:

RT_CAN_CMD_ABORT_TX(mailbox 0)

测试结果:

caught hardware abort at attempt=0
TX_FAIL=1

证明:

frame hardware pending
        ↓
ABRQ0
        ↓
hardware abort
        ↓
TX_FAIL terminal
        ↓
Generic pending retirement

结果:PASS。

7. Abort 后 Mailbox Recovery — PASS

完成 Hardware Abort 后再次发送 CAN frame。

recovery send PASS

证明 Abort 后 old ownership 已退休、pending 已恢复、mailbox 可复用、新 TX 可以正常接受和完成。

结果:PASS。

测试总结

测试项目 结果
Blocking CAN TX/RX PASS
SDO Block 32 / 900 / 1024 / 1025 / 2048 Bytes PASS
SDO Block 2048 Bytes × 100 PASS
SDO Abort / Timeout Recovery PASS
Block -> Expedited / Segmented Regression PASS
Communication Reset / CAN Reopen PASS
Nonblocking Paced TX 100 次 PASS
Nonblocking Burst / -RT_EBUSY Backpressure PASS
Nonblocking Terminal Accounting PASS
Invalid Abort Mailbox PASS
Hardware RT_CAN_CMD_ABORT_TX PASS
Abort TX_FAIL Terminal PASS
Abort 后 Mailbox Recovery PASS

Nonblocking 和 Abort 专项测试均直接针对 RT-Thread Generic CAN API 执行,不依赖 CANopen。


请提供验证的 bsp 和 config (provide the config and bsp)

  • BSP: [填写实际 STM32 BSP 路径]

  • Runtime CAN configuration:

bitrate       = 1000000
sndboxnumber  = 3
  • .config:
CONFIG_RT_USING_CAN=y
[补充实际 BSP CAN / CAN2 / pin / clock 等配置]
  • action:

[填写本分支 GitHub Action 编译链接]

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 代码是高质量的 Code in this PR is of high quality
  • 已经使用formatting 等源码格式化工具确保格式符合RT-Thread代码规范 This PR complies with RT-Thread code specification
  • 如果是新增bsp, 已经添加ci检查到.github/ALL_BSP_COMPILE.json 详细请参考链接BSP自查

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

👋 感谢您对 RT-Thread 的贡献!Thank you for your contribution to RT-Thread!

为确保代码符合 RT-Thread 的编码规范,请在你的仓库中执行以下步骤运行代码格式化工作流(如果格式化CI运行失败)。
To ensure your code complies with RT-Thread's coding style, please run the code formatting workflow by following the steps below (If the formatting of CI fails to run).


🛠 操作步骤 | Steps

  1. 前往 Actions 页面 | Go to the Actions page
    点击进入工作流 → | Click to open workflow →

  2. 点击 Run workflow | Click Run workflow

  • Use workflow from 保持默认分支(通常为 master
    Keep the default branch (usually master) in Use workflow from
  • branch 输入框填写 PR 分支 refactor/can-framework
    Enter PR branch refactor/can-framework in the branch field
  • 设置需排除的文件/目录(目录请以"/"结尾)
    Set files/directories to exclude (directories should end with "/")
  1. 等待工作流完成 | Wait for the workflow to complete
    格式化后的代码将作为独立提交推送至你的分支。
    The formatting changes will be pushed to your branch as a separate commit.

完成后,提交将自动更新至 refactor/can-framework 分支,关联的 Pull Request 也会同步更新。
Once completed, commits will be pushed to the refactor/can-framework branch automatically, and the related Pull Request will be updated.

如有问题欢迎联系我们,再次感谢您的贡献!💐
If you have any questions, feel free to reach out. Thanks again for your contribution!

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: bsp_stm32

Reviewers: @Liang1795 @hamburger-os @wdfk-prog

Changed Files (Click to expand)
  • bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c

🏷️ Tag: components

Reviewers: @Maihuanyi

Changed Files (Click to expand)
  • components/drivers/can/Kconfig
  • components/drivers/can/SConscript
  • components/drivers/can/can_internal.h
  • components/drivers/can/can_rx.c
  • components/drivers/can/can_tx.c
  • components/drivers/can/dev_can.c
  • components/drivers/include/drivers/dev_can.h

🏷️ Tag: components_driver_can

Reviewers: @wdfk-prog

Changed Files (Click to expand)
  • components/drivers/can/Kconfig
  • components/drivers/can/SConscript
  • components/drivers/can/can_internal.h
  • components/drivers/can/can_rx.c
  • components/drivers/can/can_tx.c
  • components/drivers/can/dev_can.c

📊 Current Review Status (Last Updated: 2026-09-02 13:30 CST)


📝 Review Instructions

  1. 维护者可以通过单击此处来刷新审查状态: 🔄 刷新状态
    Maintainers can refresh the review status by clicking here: 🔄 Refresh Status

  2. 确认审核通过后评论 LGTM/lgtm
    Comment LGTM/lgtm after confirming approval

  3. PR合并前需至少一位维护者确认
    PR must be confirmed by at least one maintainer before merging

ℹ️ 刷新CI状态操作需要具备仓库写入权限。
ℹ️ Refresh CI status operation requires repository Write permission.

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
@wdfk-prog
wdfk-prog force-pushed the refactor/can-framework branch from 44833a0 to edb6552 Compare September 2, 2026 05:30
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

@wdfk-prog

Copy link
Copy Markdown
Contributor Author

@BernardXiong @meng-plus @Rbb666 @Guozhanxin @illustriousness @gbcwbz @CYFS3

@CYFS3

CYFS3 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

其他bsp是不是也得从新适配can?

@wdfk-prog

wdfk-prog commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

其他bsp是不是也得从新适配can?

  • 不需要,可以用其他BSP验证一下,我手上并没有无法验证
  • STM32的更新是为了支持RT_CAN_CMD_ABORT_TX功能,其他改动是AI觉得可以优化的😄, 看着改动很多是进行了CI格式化
  • 特定设计做了兼容的,否则这个就应该叫CAN_V2

@illustriousness illustriousness left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

结论 / 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 成功,没有构建检查。

@wdfk-prog

Copy link
Copy Markdown
Contributor Author

结论 / Conclusion: Request changes

当前实现改变了 Generic CAN 与所有 BSP 之间的运行时契约,但只迁移了 STM32 bxCAN,因此暂不满足“其他 BSP 无需适配”的兼容性要求。

🔴 [Concurrency/并发]: Do not call un-migrated BSP send callbacks under irqsave / 不要在关中断锁内调用尚未迁移的 BSP 回调

中文:当前在 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)

中文: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 回归。

中文:发送超时只代表等待超时,并不代表硬件已经结束请求。当前代码却执行 pending-- 并将 slot 设为 FREE。随后 drain/reconfigure 会错误判断 TX 已空闲;旧帧仍可能发送,迟到的 terminal 还可能冲掉其他 nonblocking 请求的 pending。应保留 TIMED_OUT/ABANDONED ownership,直到真实 TX_DONE/TX_FAIL 到来。

🟠 [Compatibility/兼容性]: Existing BSPs lose status polling / 现有 BSP 默认失去状态轮询
中文:仓库中 30 个启用 CAN 的 .config/rtconfig.h 都没有新宏。普通 SCons 构建不会重新计算 Kconfig 的 default y,因此现有 BSP 均不会编译状态轮询路径,并会在 RT_CAN_CMD_SET_STATUS_IND 返回 -RT_ENOSYS。需要保留原有默认行为,或通过配置工具迁移全部相关 BSP。

  • 还真有BSP,在发送里面做阻塞延时啊 😄
  • 那我在修改一版,重新做成CAN_V2框架不兼容现有BSP?
  • 发送超时只代表等待超时,并不代表硬件已经结束请求。当前代码却执行 pending-- 并将 slot 设为 FREE
    • 这个问题考虑过,框架层需要处理的话,逻辑复杂了,所以设计中考虑的是超时时间交给用户设置,用户得保证这个超时时间一定是呃能够等到硬件发送完成的,如果到达超时时间反馈过来的一定是硬件发送完成或者已经知道失败了
  • 仓库中 30 个启用 CAN 的 .config/rtconfig.h 都没有新宏。这个不重要,只是默认缺少了canstata的msh命令功能,这样可以减少代码体积

@CYFS3

CYFS3 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor
  • 那我在修改一版,重新做成CAN_V2框架不兼容现有BSP?

我感觉就不要在维护一套can_V2了,这样维护起来太麻烦了。看看有没有人感兴趣一起来push这个pr吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants