Skip to content

Refactor/can framework - #12053

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

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

Conversation

@wdfk-prog

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_DONE 或 TX_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 编译链接]

顺序 适配层级 芯片/控制器 难度 代码依据与最小适配重点 是否适配
01 指定首位 STM32 FDCAN(ARM Cortex-M;优先单核 STM32G4 等) 4/5 无 NB;Generic 配置 1 slot,但驱动配置 3 个 TX Buffer;核实 BufferIndexes 位图/回调索引、FIFO 模式和 START(0) 停止语义。 PASS
02 STM32 相近厂商 GD32F4/F5(先 F4 经典 CAN)(ARM 单核;F4 M4 / F5 M33) 3/5(F4) 已有旧版 NB;自动发送要返回 HAL 选中邮箱、避开 sndchange;修正忙检查与 -RT_EBUSY。F5 部分型号是 CAN FD,不能无证据复用 F4 经典邮箱逻辑。 PASS
03 bxCAN 类三邮箱 AT32 经典 CAN(ARM Cortex-M 单核) 2/5 已有 0/1/2 TX terminal;新增 BSP 自选可用邮箱和输出索引,校验已结算但未回收的 sndchange 邮箱。 PASS
04 bxCAN 类三邮箱 N32Gxx/Lxx(ARM Cortex-M 单核) 2/5 有 TSTS_TMEM0/1/2 检查及逐邮箱 TX 事件;补 NB 选箱与 busy 返回,避免软件 slot 仍被阻塞者占用。 PASS
05 bxCAN 类三邮箱 APM32 经典 CAN(ARM Cortex-M 单核) 2–3/5 有逐邮箱 TX_DONE/TX_FAIL;补 NB、自选箱/返回编号、发送忙判定,注意原 sendmsg 的错误码。 PASS
06 bxCAN 类三邮箱 ES32F369x(ARM Cortex-M 单核候选) 2–3/5 已有带邮箱编号的终端上报;先核对硬件三邮箱释放条件,再补独立非阻塞提交。 PASS
07 bxCAN 类三邮箱 ES32F0654(ARM Cortex-M 单核候选) 2–3/5 与 ES32F369x 实现接近;复用已验证的邮箱/终端适配思路,但核对具体寄存器差异。 PASS
08 bxCAN 类三邮箱 WCH CH32V(RISC-V)(单核 RISC-V;CAN 模型与 bxCAN 近似) 2–3/5 有 TSTATR_TME0/1/2、RQCP0/1/2 和逐邮箱事件;外设模型相近,因此按你的“同 CAN 设计优先”例外提前。 PASS
09 M_CAN/FDCAN 类 HC32 MCAN(ARM Cortex-M 单核) 3–4/5 已有旧版 NB 但转调 sendmsg(0);检查专用 Buffer/FIFO、Tx event 实际索引及即时返回,不能只增加 mailbox_out。 PASS
10 M_CAN/FDCAN 类 N32Hxxx FDCAN(先 N32H76x)(N32H76x 单 Cortex-M7;N32H78x 双核后置) 3–4/5 已有旧版 NB 扫描 Dedicated TX Buffer;补成功 buffer 索引及所有权过滤。对 N32H78 双核变体单独延后验证。 PASS
11 M_CAN/FDCAN 类 HPMicro MCAN(限确认的单核型号)(RISC-V;具体芯片核数待核实) 5/5 现有 sendmsg 在 TX FIFO 满时 mdelay 等待;Generic tx_lock 下不可睡眠。需区分 FIFO 物理 index/逻辑 slot、重审 START(0) 的旧终端抑制。 PASS
12 FlexCAN 同族 NXP MCX A(FlexCAN)(ARM Cortex-M 单核) 3–4/5 已有旧版 NB,会选择实际 MB;但 Generic sndboxnumber=1,不能把物理 MB 非零编号直接当 slot;需要一致映射及 ISR terminal。 PASS
13 FlexCAN 同族 NXP MCX N(先 N24)(N24 单核 Cortex-M33;N94/N54 等双核后置) 4/5 无 NB;当前仅 1 个 Generic slot,需核对 FlexCAN 物理 MB 选择、异步事件 index 和取消时序。 PASS
14 FlexCAN 同族 NXP i.MX RT(先单核型号)(ARM Cortex-M;部分型号双核后置) 4/5 无 NB;配置最多 16 个逻辑发送 slot,关注物理 MB/中断索引换算及不同系列外设差异。 PASS
15 FlexCAN 同族 Novosns NS800(暂定位置)(核心架构与核数待确认) 5/5 MB0 专用,有旧 NB,但底层 _can_sendBlocking 最多 mdelay 50 ms;START 分支为 no-op。核数未证实时不应先实施,若为多核移到队尾。 PASS
16 其他 ARM 单核 MCU HC32 经典 CAN/PTB(ARM Cortex-M 单核) 2–3/5 旧 NB 直接 sendmsg(0),PTB 单发送缓冲;可固定返回逻辑 0,但必须检查忙状态与硬件事件。 PASS
17 其他 ARM 单核 MCU HT32 CAN(ARM Cortex-M 单核) 2–3/5 无 NB,单发送缓冲/单事件模型;明确硬件忙时立即 -RT_EBUSY,成功返回逻辑 0。
18 其他 ARM 单核 MCU Synwit SWM341(ARM Cortex-M 单核) 3/5 无 NB;发送函数直接写帧寄存器且缺独立忙判定,需要先验证 TX buffer 可用与 ISR 一次性事件。
19 其他 ARM 单核 MCU Renesas RA(ARM Cortex-M 单核) 3/5 无 NB;Generic 只配置 1 slot,回调使用物理 mailbox;按实际发送路径统一逻辑索引与异步事件。
20 其他 ARM 单核 MCU AT32 CAN v2(PTB/STB)(ARM Cortex-M 单核) 4/5 不是旧款三邮箱模型;sendmsg 走 CAN_TXBUF_STB/排队,且另有等待循环;必须先建立一请求一 terminal 的逻辑 slot 映射。
21 其他 ARM 单核 MCU Nuvoton M2354(ARM Cortex-M 单核) 4/5 无 NB;CAN 消息对象中断未直接上报 TX slot,先核实消息对象编号与 Generic slot 的映射。
22 其他 ARM 单核 MCU Nuvoton M480(ARM Cortex-M 单核) 4/5 无 NB;消息对象发送与部分 TX_DONE 事件未编码邮箱编号,需统一 sendmsg(boxno) 与 ISR 对应关系。
23 其他 ARM 单核 MCU Nuvoton M460 CAN FD(ARM Cortex-M 单核) 4/5 无 NB;CAN FD 消息 RAM/发送事件及 buffer 逻辑编号须独立核对,不直接套用 M480 CAN 2.0。
24 ARM 单核 MPU Nuvoton N9H30(ARM9)(ARM9 单核 MPU) 4/5 与 NuMicro 发送对象实现同族;优先复用 M480/M2354 核对后的消息对象与 terminal 映射。
25 ARM 单核 MPU Nuvoton NUC980(ARM9)(ARM9 单核 MPU) 4/5 与 N9H30 同组,需重新确认控制器状态/缓存/IRQ 行为后适配。
26 其他架构单核 HPMicro CAN(限确认的单核型号)(RISC-V 单核型号优先) 3–4/5 无 NB;现有 TX_DONE 逻辑索引为 0,先核查硬件忙与当前发送函数是否直接等待,及 STOP 的抑制语义。
27 其他架构单核 Loongson LS1C(MIPS 单核) 3–4/5 无 NB;原 sndboxnumber=1 且终端索引 0,优先补单邮箱即时提交,另外核实停止能力。
28 多核/异构平台最后 Nuvoton MA35(CAN FD)(双 Cortex-A35 + Cortex-M4) 4/5 无 NB;发送逻辑 slot 与 Message RAM/ISR 对应需要跨处理域和运行模式核验。
29 多核/异构平台最后 Rockchip CAN FD(ARM 多核 MPU/SoC) 4/5 无 NB;当前 TX_DONE 中包含索引和无索引路径,先判明中断运行域及可用 buffer 个数。
30 多核/异构平台最后 Phytium CAN(ARM64 SoC;核数依具体型号) 4/5 无 NB;原 sndboxnumber=1 且终端多未显式编号。若目标明确为单核变体,可以重排至 ARM 单核 MPU 阶段。
31 多核/异构平台最后 FT2004(FT-2000/4)(四核 ARMv8 兼容 SoC) 5/5 无 NB;扫描的驱动内未找到显式 RT_CAN_EVENT_TX_DONE/FAIL,先补充或核实真实 terminal 事件契约。
32 多核/异构平台最后 RDK S100(6×Cortex-A78AE + 4×Cortex-R52+) 4–5/5 旧 NB 依次尝试物理箱但不返回索引;多处理域及 TX event 映射,最后实施。

当前拉取/合并请求的状态 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自查

wdfk-prog and others added 20 commits November 13, 2026 13:42
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
- Track blocking and non-blocking transmissions by hardware mailbox, preserving ownership after timeout until the terminal event.

- Prevent STM32 bxCAN non-blocking submissions from reusing mailboxes still owned by blocking senders.

- Add RT_CAN_CMD_FORCE_STOP to explicitly stop the controller and retire abandoned TX requests without automatic recovery or retries.

- Improve RX interrupt quiescing, TX/error interrupt restoration, and maintenance timer handling during reconfiguration.

- Keep forced-stop synchronization under application control. Applications must quiesce TX producers and in-flight writers before invoking FORCE_STOP.
- Expose all three dedicated FDCAN TX buffers as independent Generic CAN mailbox slots.

- Add non-blocking TX submission with mailbox selection, hardware busy checks, and software ownership protection.

- Report TX completion and cancellation events per mailbox, preserving ownership until a terminal event is received.

- Support explicit TX abort and controller stop commands with hardware status and error handling.

- Normalize CAN FD DLC encoding across supported HAL representations.

- Add typed CAN error events and update Generic CAN error counters without treating controller errors as TX failures.

- Capture protocol errors before HAL clears interrupt flags and preserve controller error-state reporting.
- Map three GD32 hardware TX mailboxes to independent Generic CAN slots.

- Update non-blocking transmission to select an available mailbox, return its index, and respect blocking TX ownership.

- Handle TX_DONE and TX_FAIL independently for each mailbox, preserving ownership until the terminal event is processed.

- Add hardware TX abort and controller stop/restart support, with explicit error handling and mailbox status checks.

- Preserve ACK-error cancellation for both automatic retransmission modes without prematurely retiring TX ownership or clearing unrelated completion flags.

- Correct CAN error register access and error status reporting.

- Document CAN0/CAN1 shared filter constraints and update the driver Change Logs.
…ment

- Support three hardware TX mailboxes with fixed-mailbox blocking transmission and BSP-selected non-blocking transmission.

- Handle TX completion events per mailbox and prevent ACK errors from prematurely releasing TX ownership.

- Add controller START/STOP support and preserve shared CAN/USBFS interrupt vectors.

- Fix AT32F425 shared IRQ dispatch by checking enabled and pending interrupt sources.
- Support three TX mailboxes and non-blocking transmission with mailbox ownership tracking.

- Report TX_DONE and TX_FAIL based on hardware completion status, without releasing mailboxes on intermediate bus errors.

- Forward CAN error events to the Generic CAN framework for unified error statistics.

- Implement TX abort and controller start/stop operations, and improve configuration error propagation.

- Preserve hardware error counters and prevent GET_STATUS from overwriting the last error type.
- Support three hardware TX mailboxes and add non-blocking transmission with mailbox ownership checks.

- Report TX_DONE and TX_FAIL from hardware completion flags, while forwarding controller errors without prematurely retiring TX ownership.

- Implement controller STOP and TX abort handling with bounded hardware-state confirmation, preserving CAN1/CAN2 shared filters.

- Clear stale TX completion flags and hardware RX FIFO entries before controller restart.
- Support three TX mailboxes and nonblocking CAN transmission.

- Add TX mailbox abort and controller start/stop operations.

- Handle TX completion and failure events per mailbox, and report protocol errors through Generic CAN.

- Enable both RX FIFO interrupts and prevent duplicate FIFO releases.

- Preserve the default filter configuration and use caller-selected filter banks without modifying input IDs or masks.

- Correct extended CAN ID encoding and update the driver for the new Generic CAN operation interfaces.
- Support three hardware TX mailboxes and nonblocking transmission.

- Implement per-mailbox TX completion handling and abort/start/stop commands.

- Fix RX FIFO release, extended CAN ID encoding, and error event reporting.

- Preserve caller-provided CAN filter IDs and masks during configuration.
- Map Generic CAN TX mailboxes to hardware TX FIFO/Queue elements and support non-blocking mailbox selection.

- Track per-mailbox TX ownership and report TX_DONE/TX_FAIL from hardware completion and abort events.

- Support controller start/stop and mailbox abort commands with bounded hardware TX drain.

- Report protocol errors as CAN bus diagnostics without prematurely releasing pending TX ownership.

- Return -RT_EBUSY for unavailable TX resources and normalize other hardware errors to -RT_ERROR.
dev_can.c calls rt_can_tx_retire_after_stop() during a forced controller
stop, but the prototype was missing from can_internal.h. Every CAN-enabled
BSP therefore failed with -Werror=implicit-function-declaration.

Add the TX-private declaration next to rt_can_tx_drain_core(); the signature
matches the definition in can_tx.c.

Help RT-Thread#11779
Register a virtual CAN controller with simulated ops and cover:
- device registration and lookup
- open/close reference counting and reopen after final close
- START(0)/START(1) TX admission and BSP error propagation
- blocking mailbox timeout
- non-blocking TX ownership retirement via a simulated terminal
- RX ISR to software FIFO to rt_device_read()

The suite needs no CAN controller, transceiver or bus, so it can run on any
BSP and under QEMU. Wire it through can/utest Kconfig and SConscript.

Help RT-Thread#11779
Related to RT-Thread#11972
@github-actions

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 added BSP: NXP Code related with NXP BSP: STM32 BSP related with ST/STM32 BSP BSP: HC32 BSP related with HC32 BSP: GD32 BSP related with GD32 BSP: AT32 BSP related with AT32 Component component: drivers/can component: drivers labels Nov 13, 2026
@github-actions github-actions Bot added the BSP: Novosns BSP related with novosns label Nov 13, 2026
@github-actions

Copy link
Copy Markdown

📌 Code Review Assignment

🏷️ Tag: bsp_hc32

Reviewers: @levizh

Changed Files (Click to expand)
  • bsp/hc32/libraries/hc32_drivers/drv_can.c
  • bsp/hc32/libraries/hc32_drivers/drv_mcan.c

🏷️ Tag: bsp_mcxa

Reviewers: @hywing

Changed Files (Click to expand)
  • bsp/nxp/mcx/mcxa/libraries/drivers/drv_can.c

🏷️ Tag: bsp_mcxn

Reviewers: @hywing

Changed Files (Click to expand)
  • bsp/nxp/mcx/mcxn/libraries/drivers/drv_can.c

🏷️ Tag: bsp_stm32

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

Changed Files (Click to expand)
  • bsp/stm32/libraries/HAL_Drivers/drivers/drv_can.c
  • bsp/stm32/libraries/HAL_Drivers/drivers/drv_fdcan.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/can/utest/Kconfig
  • components/drivers/can/utest/SConscript
  • components/drivers/can/utest/can_framework_tc.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
  • components/drivers/can/utest/Kconfig
  • components/drivers/can/utest/SConscript
  • components/drivers/can/utest/can_framework_tc.c

📊 Current Review Status (Last Updated: 2026-11-13 14:44 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.

@wdfk-prog

Copy link
Copy Markdown
Contributor Author

@github-actions

Copy link
Copy Markdown

@wdfk-prog wdfk-prog closed this Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BSP: AT32 BSP related with AT32 BSP: GD32 BSP related with GD32 BSP: HC32 BSP related with HC32 BSP: Novosns BSP related with novosns BSP: NXP Code related with NXP BSP: STM32 BSP related with ST/STM32 BSP component: drivers/can component: drivers Component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants