Skip to content

[Discussion][CAN] RT-Thread CAN Framework 重构方案 #11767

Description

@wdfk-prog

https://club.rt-thread.org/ask/question/e1d2904dcdff1591.html

[Discussion][CAN] RT-Thread CAN Framework 重构方案:统一 TX Engine、Mailbox Ownership 与 Lifecycle

摘要:统一 CAN TX/RX 核心、发送事务与邮箱所有权,解决乱序、超时、Flush、关闭重配置和中断并发问题,并保持旧 BSP ISR 接口兼容。

这是一份面向 RT-Thread CAN Framework 的重构讨论方案。目标不是针对某一个 BSP 修补问题,而是把近几年 blocking/non-blocking TX、发送顺序、timeout、full flush、bus-off/recovery、CAN FD 和多厂商 BSP 暴露出来的问题收敛到一套统一的 Framework contract 中。

本文先给出一套完整方案用于讨论。已经确认的总体方向直接作为 proposal,不再逐项讨论“要不要做”;希望社区重点评估的是兼容边界、接口细节、资源成本以及分阶段落地方式。

1. 当前实现中需要解决的结构性问题

当前 components/drivers/can/dev_can.c 中 TX 实际存在两套路径:

  • blocking TX:_can_int_tx() / _can_int_tx_priv() 使用 sendmsg()、TX mailbox freelist、semaphore、completion 和 status.sndchange
  • non-blocking TX:_can_nonblocking_tx() 使用 sendmsg_nonblocking(),硬件忙时进入 nb_tx_rb
  • RT_CAN_EVENT_TX_DONE / TX_FAIL 中断处理里还会直接从 nb_tx_rb 取下一帧并再次调用 sendmsg_nonblocking()
  • 当 ISR refill 失败时,当前实现使用 rt_ringbuffer_put_force() 放回消息,这会改变 FIFO 顺序,并且在空间不足时具备覆盖旧数据的语义。

这导致 blocking/non-blocking 不只是 API 行为不同,而是 Framework 内部维护了不同的发送资源、排队方式和完成方式。随着 runtime bitrate reconfiguration、TX flush/abort、CAN FD 和 SMP 等需求增加,继续在现有结构上增加状态位和分支会越来越难维护。

相关讨论和问题包括:

当前主线代码:

2. 重构目标

目标 Framework 收敛为以下原则:

  1. blocking / non-blocking 共用一个 TX Engine;
  2. 所有 TX 先成为 Framework 管理的 TX Request;
  3. 使用预分配 TX Request Descriptor Pool,不在 ISR 中动态分配;
  4. software TX queue 保持严格 FIFO;
  5. Framework 显式维护 mailbox -> request ownership;
  6. BSP 统一通过 sendmsg(can, msg, mailbox) 提交硬件发送;
  7. 启用 non-blocking TX 的 driver,其 sendmsg() 必须 ISR-safe;
  8. 不引入 deferred scheduler、CAN worker thread 或 workqueue;
  9. blocking wait timeout 不等价于硬件 transaction 已结束;
  10. TX terminal event exactly-once;
  11. Lifecycle State 与 CAN Bus State 分离;
  12. TX/RX 使用独立 irq-safe spinlock;
  13. RX 保留现有 node pool 思路,只重构 synchronization/ownership/lifecycle;
  14. rt_hw_can_isr(can, event) 和现有 BSP ISR 调用方式第一阶段保持兼容;
  15. 第一阶段保持 rt_device_read/write/controlstruct rt_can_msg 兼容;
  16. struct rt_can_core 直接嵌入 struct rt_can_device,不增加二次动态分配;
  17. 不增加 RT_CAN_USING_RX / RT_CAN_USING_TX 这种基础裁剪项,CAN 默认同时具备 RX/TX;
  18. Kconfig 只裁剪 blocking、non-blocking、callback、trace 和 request pool 等真正影响 RAM/Flash 的功能。

3. 目标 Framework 结构

flowchart TD
    APP["Application / ISR"] --> API["rt_device_write / rt_can_send_async"]
    API --> TXQ["TX Request Pool + FIFO Queue"]
    TXQ --> SCH["TX Scheduler"]
    SCH --> OWN["TX mailbox ownership"]
    OWN --> SEND["ops->sendmsg(can, msg, mailbox)"]
    SEND --> HW["CAN Controller"]
    HW --> ISR["existing rt_hw_can_isr"]
    ISR --> TERM["TX terminal handler"]
    TERM --> SCH
    TERM --> WAIT["blocking completion"]
    TERM --> CB["async callback"]

    HW --> RXISR["RX event"]
    RXISR --> RXCORE["RX node pool + RX spinlock"]
    RXCORE --> READ["rt_device_read"]

    LC["Lifecycle Core"] --> TXQ
    LC --> RXCORE
    BUS["Bus State"] --> LC
Loading

Scheduler 不是独立线程,也不是后台任务。它只是一个“尝试把 pending TX Request 推进到硬件 mailbox”的短函数。

它主要由三个事件触发:

1. 新 TX Request 入队;
2. TX_DONE / TX_ERROR / TX_ABORTED 释放 mailbox;
3. controller recover / reconfigure 完成后重新允许 TX。

本方案不使用 deferred scheduling。

4. 统一 blocking / non-blocking TX Engine

blocking 与 non-blocking 的区别只保留在“调用者是否等待 terminal result”。

统一数据流:

CAN frame
   ↓
TX Request
   ↓
FIFO pending queue
   ↓
TX Scheduler
   ↓
TX mailbox
   ↓
ops->sendmsg()
   ↓
CAN hardware
   ↓
TX terminal event

blocking:

enqueue request
→ schedule
→ wait completion
→ terminal result
→ return

non-blocking:

enqueue request
→ schedule
→ accepted 后立即返回
→ terminal event 后 callback / auto retire

因此 BSP 不再区分“blocking send function”和“non-blocking send function”。blocking 是 Framework 的等待策略,不是 BSP 的发送方式。

5. sendmsg() 作为唯一 BSP TX submit 接口

现有很多 BSP 的 sendmsg(can, msg, box_num) 本质已经是“把一帧写入指定发送资源并立即返回”,它本身并不负责 blocking。

目标 contract 建议明确为:

/**
 * Submit one CAN frame to the specified hardware TX mailbox.
 *
 * The function only performs hardware submission. It must not wait for
 * transmission completion. Blocking/non-blocking semantics are owned by
 * the CAN framework.
 *
 * When RT_CAN_USING_TX_NONBLOCKING is enabled for the driver, this callback
 * must be safe to call from ISR context.
 *
 * @return
 * - RT_EOK: frame accepted by the selected mailbox;
 * - -RT_EBUSY: selected mailbox/resource is temporarily unavailable;
 * - other error: controller/parameter failure.
 */
rt_ssize_t (*sendmsg)(struct rt_can_device *can,
                      const void *buf,
                      rt_uint32_t mailbox);

这里的 ISR-safe 至少意味着:

- 不 sleep;
- 不 take blocking mutex/semaphore;
- 不等待 TX complete;
- 不依赖只能在线程上下文运行的服务;
- 执行时间应保持为硬件 submit 所需的短路径。

为什么 non-blocking driver 必须满足 ISR-safe sendmsg()

当前 Framework 本来就在 TX_DONE ISR 中调用 sendmsg_nonblocking() refill software queue。

新 Framework 不再保留第二套 non-blocking hardware submit path,因此:

TX_DONE
   ↓
release mailbox
   ↓
TX Scheduler
   ↓
ops->sendmsg(next_request, mailbox)

会直接发生在 ISR context。

如果这里不允许调用 sendmsg(),又禁止 deferred/workqueue,那么 pending non-blocking queue 就可能在第一帧完成后停止推进。

因此本方案把能力关系定义得更直接:

一个 driver 如果支持新的 non-blocking TX,就必须保证统一的 sendmsg() 是 ISR-safe 的。

不再增加单独的 TX_SEND_ISR_SAFE flag,也不在 Framework 中维护两套 scheduler。

sendmsg_nonblocking() 如何处理

第一阶段建议仍保留 struct rt_can_ops 中现有 sendmsg_nonblocking 成员,使旧 BSP 的静态 initializer 和源码继续编译。

但是新的 TX Core 不再依赖该接口。

Target TX path:
    sendmsg(can, msg, mailbox)

Legacy member:
    sendmsg_nonblocking(...)
    → retained temporarily for source compatibility
    → not part of the new TX Engine contract

对于希望继续支持 non-blocking 的旧 BSP,需要把现有 sendmsg_nonblocking() 中的 ISR-safe 硬件 submit 能力合并到 sendmsg(can, msg, mailbox) 中。

这一迁移只涉及 BSP 的发送实现;现有 BSP ISR 调用 rt_hw_can_isr() 的代码不需要修改。

6. TX Request Descriptor Pool

软件队列不再只保存裸 rt_can_msg,而是保存完整发送事务。

enum rt_can_tx_req_state
{
    RT_CAN_TX_REQ_FREE = 0,
    RT_CAN_TX_REQ_QUEUED,
    RT_CAN_TX_REQ_SUBMITTING,
    RT_CAN_TX_REQ_HW_PENDING,
    RT_CAN_TX_REQ_ABORTING,

    RT_CAN_TX_REQ_DONE,
    RT_CAN_TX_REQ_ERROR,
    RT_CAN_TX_REQ_ABORTED,
    RT_CAN_TX_REQ_CANCELLED,
};

建议 descriptor:

typedef void (*rt_can_tx_done_cb)(struct rt_can_device *can,
                                  const struct rt_can_msg *msg,
                                  rt_err_t result,
                                  void *arg);

struct rt_can_tx_request
{
    rt_list_t node;

    struct rt_can_msg msg;

    enum rt_can_tx_req_state state;
    rt_err_t result;

    /* -1 means no hardware mailbox is currently owned. */
    rt_int16_t mailbox;

#ifdef RT_CAN_USING_TX_BLOCKING
    rt_bool_t waiter_attached;
    struct rt_completion completion;
#endif

#ifdef RT_CAN_USING_TX_CALLBACK
    rt_can_tx_done_cb callback;
    void *callback_arg;
#endif

#ifdef RT_CAN_USING_TX_TRACE
    rt_uint32_t sequence;
#endif
};

这里不再增加通用 flags 字段。发送生命周期由 state 表示,其它需要独立语义的内容使用明确字段,避免形成 state + flags 两套状态源。

request_countsequence

request_count 是 TX Request Pool 的容量:

rt_uint16_t request_count;

例如 request_count = 16 表示最多同时存在 16 个尚未 retire 的 TX Request。

sequence 只是可选 trace/debug 编号,不参与 ownership、FIFO 或 terminal correctness。

如果启用 trace,rt_uint32_t sequence 允许自然回绕:

0xFFFFFFFE
0xFFFFFFFF
0x00000000
0x00000001

不需要为溢出建立额外状态,因为真正的 request identity 和 mailbox ownership 由 descriptor 指针维护。

7. TX Core 与 Mailbox Ownership

不为只有一个成员的 mailbox 再增加独立结构体。

建议:

struct rt_can_tx_core
{
    struct rt_spinlock lock;

    rt_list_t free_list;
    rt_list_t pending_list;

    struct rt_can_tx_request *requests;
    rt_uint16_t request_count;

    /*
     * Array size uses existing can->config.sndboxnumber.
     * mailbox_owner[n] == RT_NULL means mailbox n has no framework owner.
     */
    struct rt_can_tx_request **mailbox_owner;

    /* Protected by tx.lock; prevents scheduler re-entry. */
    rt_bool_t scheduling;
};

继续复用现有:

can->config.sndboxnumber

作为 mailbox 数量,不再增加重复的 mailbox_count

Mailbox 是什么

这里的 mailbox 沿用 RT-Thread 当前概念。

对于 STM32 bxCAN:

mailbox 0 = sTxMailBox[0]
mailbox 1 = sTxMailBox[1]
mailbox 2 = sTxMailBox[2]

其它 controller 可能叫 TX Buffer / TX FIFO Element,但 Generic Framework 仍把“可以独立产生 TX terminal event 的发送资源编号”统一称为 mailbox。

关键 ownership 不变量

1. 一个 mailbox 同一时刻最多只有一个 owner;
2. HW_PENDING / ABORTING request 必须能反查到自己的 mailbox;
3. mailbox_owner[n] 未清空前,对应 request descriptor 不得复用;
4. stale / duplicate TX IRQ 不得修改新的 request;
5. status.sndchange 不再作为 ownership correctness 的依据。

8. TX Scheduler

Scheduler 是 TX Engine 的中心推进函数,但不是线程。

基本流程:

flowchart TD
    KICK["TX schedule kick"] --> LOCK["tx.lock"]
    LOCK --> CHECK["check lifecycle / scheduling"]
    CHECK --> HEAD["take pending queue HEAD"]
    HEAD --> BOX["select available mailbox"]
    BOX --> RESERVE["mailbox_owner = request; state = SUBMITTING"]
    RESERVE --> UNLOCK["tx.unlock"]
    UNLOCK --> SEND["ops->sendmsg()"]
    SEND --> RET{"return"}
    RET -- "RT_EOK" --> PEND["HW_PENDING"]
    RET -- "-RT_EBUSY" --> RESTORE["restore exact queue HEAD"]
    RET -- "error" --> ERR["terminal ERROR"]
Loading

Driver/HAL 调用必须位于 tx.lock 外。

也就是说采用:

reserve
→ unlock
→ driver submit
→ lock
→ commit / rollback

而不是:

spinlock
→ HAL/driver
→ unlock

-RT_EBUSY 的处理

如果指定 mailbox 临时不可用:

Request = QUEUED
mailbox_owner = NULL
request 恢复到 pending queue HEAD

不再执行:

pop
→ send fail
→ put_force 到 tail

这样不会改变原始 FIFO 顺序,也不会覆盖旧 request。

9. Wire Transmission Order

本方案把目标定义为:

对同一个 rt_can_device,Framework 接受的 TX Request 应保持应用提交顺序,并在 Generic fallback 下保证本节点的 wire transmission order。

例如:

Application: A → B → C
Wire order:  A → B → C

其它 CAN 节点仍然可以通过总线仲裁插入 A/B/C 之间,这不属于本节点 Framework 的顺序问题。

多 mailbox Controller

如果同时 preload:

A → mailbox 0
B → mailbox 1
C → mailbox 2

某些 controller 可能按照 CAN ID priority 或自己的 mailbox policy 决定发送顺序。

Generic Framework 因此默认采用严格模式:

如果不能证明多个 mailbox 可以保持 submit order,
同一时刻最多允许一个 TX Request 进入 hardware pending。

如果某个 controller 明确支持 ordered multi-mailbox,可以通过 capability 允许多 mailbox preload,例如:

#define RT_CAN_CAP_TX_ORDERED_MAILBOX    (1UL << 0)

这样 strict order 是 Generic correctness baseline,多 mailbox 是 driver capability optimization。

10. Blocking timeout 不等于 TX 已结束

这是新 TX ownership 最重要的语义之一。

Request A
   ↓
HW_PENDING
   ↓
blocking thread waits
   ↓
wait timeout

timeout 只说明:

调用线程不再继续等待。

不能说明:

hardware 已经停止发送 Request A。

因此 timeout 后:

- mailbox ownership 不释放;
- descriptor 不复用;
- request 仍等待 TX_DONE / TX_ERROR / TX_ABORTED;
- waiter_attached = false;
- terminal event 到达后 Framework 自动 retire。

这可以避免旧 transaction 的 late IRQ 修改已经复用给新 transaction 的 slot/request。

11. 统一 TX terminal event

所有 TX transaction 最终只允许进入一次 terminal 状态:

enum rt_can_tx_result
{
    RT_CAN_TX_RESULT_OK = 0,
    RT_CAN_TX_RESULT_ERROR,
    RT_CAN_TX_RESULT_ABORTED,
    RT_CAN_TX_RESULT_CANCELLED,
};

所有来源最终汇入同一处理函数:

TX_DONE
TX_FAIL
hardware abort complete
full flush cancel
close cancel

例如:

static void _can_tx_complete(struct rt_can_device *can,
                             struct rt_can_tx_request *req,
                             enum rt_can_tx_result result);

规则:

non-terminal → terminal

只能成功一次。

Abort 和 TX_DONE 发生竞争时,第一个 terminal event 完成 request;后续重复/过期 event 只能记录 diagnostics,不能再次 completion、callback 或 free。

12. Async completion callback 第一阶段直接开放

建议不再只做内部预留,第一阶段直接提供 async completion API:

#ifdef RT_CAN_USING_TX_CALLBACK
rt_err_t rt_can_send_async(struct rt_can_device *can,
                           const struct rt_can_msg *msg,
                           rt_can_tx_done_cb callback,
                           void *arg);
#endif

默认 callback context 与 terminal event context 一致。

如果 TX_DONE 来自 ISR,则 callback 也在 ISR context 调用,但必须在 tx.lock 已释放以后。

因此文档 contract 必须明确 callback 不允许:

sleep
blocking mutex/semaphore
长时间运算
blocking I/O

需要线程上下文的应用可以在 callback 中自行投递 message/event;CAN Core 本身不引入 workqueue。

13. Lifecycle State

建议公共生命周期:

enum rt_can_lifecycle_state
{
    RT_CAN_LC_STOPPED = 0,
    RT_CAN_LC_RUNNING,
    RT_CAN_LC_QUIESCING,
    RT_CAN_LC_QUIESCED,
    RT_CAN_LC_RECONFIGURING,
    RT_CAN_LC_RECOVERING,
    RT_CAN_LC_CLOSING,
};

语义:

STOPPED
    controller/framework 未运行;

RUNNING
    正常允许 TX/RX;

QUIESCING
    已禁止新的 TX Request,正在处理旧 request;

QUIESCED
    TX 已完全静默,可安全进行 reconfigure/stop;

RECONFIGURING
    正在修改 baud/mode/CAN FD 等 controller 配置;

RECOVERING
    正在执行 controller/bus recovery;

CLOSING
    正在停止中断、终止 transaction 并释放 runtime resource。

不增加 admission_open / submit_refcnt

Lifecycle admission 与 TX enqueue 都在 tx.lock 下完成。

TX enqueue:

tx.lock
→ lifecycle == RUNNING ?
→ allocate request
→ enqueue
→ tx.unlock

进入 quiesce:

lifecycle_lock
→ tx.lock
→ RUNNING → QUIESCING
→ tx.unlock

因此一旦状态进入 QUIESCING,新的 TX enqueue 就无法越过状态切换。

不需要额外维护:

admission_open
submit_refcnt

QUIESCED 的严格条件

只有以下条件同时满足时进入:

pending_list empty
没有 SUBMITTING request
所有 mailbox_owner == NULL
没有 ABORTING request
scheduler 已退出

不增加 inflight_count,因为 inflight 可以直接由 mailbox ownership 推导,避免维护重复状态。

14. Quiesce / Drain / Full Flush / Abort

建议把这些语义分开。

enum rt_can_quiesce_policy
{
    RT_CAN_QUIESCE_DRAIN = 0,
    RT_CAN_QUIESCE_DROP_QUEUED,
    RT_CAN_QUIESCE_ABORT_ALL,
};

DRAIN

禁止新 TX
+ queued request 全部正常发送
+ hardware pending 正常完成
→ QUIESCED

DROP_QUEUED

禁止新 TX
+ software queued request → CANCELLED
+ hardware pending 正常完成
→ QUIESCED

ABORT_ALL / Full TX Flush

禁止新 TX
+ software queued request → CANCELLED
+ HW_PENDING → ABORTING
+ driver hardware abort
+ 等所有 mailbox_owner 清空
→ QUIESCED

典型 runtime bitrate reconfiguration:

RUNNING
→ QUIESCING
→ ABORT_ALL
→ QUIESCED
→ RECONFIGURING
→ set bitrate
→ RUNNING

Hardware abort 仍然是可选 driver capability,例如:

#define RT_CAN_CAP_TX_ABORT    (1UL << 1)

abort(mailbox) 调用成功只表示 abort request 已提交,不等价于 transaction 已 terminal;最终仍由 TX_ABORTED / TX_DONE / TX_ERROR 中的一个终态事件结束 request。

15. Bus State 与 Lifecycle 分离

Lifecycle 描述 Framework/controller 操作阶段;Bus State 描述 CAN 协议错误状态。

建议:

enum rt_can_bus_state
{
    RT_CAN_BUS_UNKNOWN = 0,
    RT_CAN_BUS_ERROR_ACTIVE,
    RT_CAN_BUS_ERROR_WARNING,
    RT_CAN_BUS_ERROR_PASSIVE,
    RT_CAN_BUS_OFF,
};

合法组合例如:

Lifecycle = RUNNING
Bus State = ERROR_PASSIVE

或:

Lifecycle = RECOVERING
Bus State = BUS_OFF

BUS_OFF 不等于 STOPPED,否则 runtime recovery、queue policy 和 controller lifecycle 会继续混在一起。

第一阶段不新增复杂的 rt_can_bus_status_corert_can_statistics。现有 rt_can_status 继续承担 observability;新的 transaction correctness 不再依赖 sndchange/rcvchange 等状态字段。

16. RX 重构

RX 也需要重构,但不复制 TX Request state machine。

TX 的复杂性来自:

queue
mailbox ownership
waiter
timeout
abort
terminal
ordering

RX 是单向:

hardware frame
→ recvmsg
→ software RX node
→ RX queue
→ rt_device_read

因此 RX 保留现有 node pool / freelist / uselist 思路,主要收敛四件事。

16.1 RX 独立 spinlock

struct rt_can_rx_core
{
    struct rt_spinlock lock;
    struct rt_can_rx_fifo *fifo;
};

rx.lock 保护:

freelist
uselist / pending list
HDR list membership
node owner
hdr->msgs
RX queue counters

不再散落使用大范围 rt_hw_local_irq_disable() 保护复合状态。

16.2 Driver recvmsg() 仍在 lock 外

当前 recvmsg() 已经由 rt_hw_can_isr() 在 ISR context 调用,因此新 RX Core 保持这一调用上下文。

流程:

RX IRQ
→ existing rt_hw_can_isr()
→ lifecycle fast check
→ ops->recvmsg()
→ rx.lock
→ allocate/update RX node ownership
→ enqueue
→ rx.unlock
→ rx_indicate/filter callback

HAL/driver callback 和用户 callback 都不在 rx.lock 内执行。

16.3 RX overflow policy

建议 pool 满时:

drop current/new incoming frame
+ increment drop counter

不通过覆盖 software queue 中已经入队的旧 frame 来获取空间。

这样 RX software FIFO 的 ownership 和顺序更明确。

16.4 Lifecycle 与 RX close/reconfigure

close/reconfigure 需要保证:

controller RX IRQ/receive path 停止
→ rx.lock
→ detach runtime RX state
→ rx.unlock
→ free resource

不能先 free RX pool,再允许 ISR 继续访问旧指针。

17. TX/RX 公共 Core

TX/RX 不共用同一个 spinlock。

struct rt_can_core
{
    struct rt_mutex lifecycle_lock;

    rt_atomic_t lifecycle_state;
    rt_atomic_t bus_state;

    rt_uint32_t capabilities;

    struct rt_can_tx_core tx;
    struct rt_can_rx_core rx;
};

然后直接嵌入:

struct rt_can_device
{
    struct rt_device parent;

    const struct rt_can_ops *ops;
    struct can_configure config;

    /* Existing compatibility fields as needed. */

    struct rt_can_core core;
};

不使用:

struct rt_can_core *core;

理由是 core 与 CAN device 生命周期一致,直接嵌入更适合 MCU,也避免额外 malloc 和 ownership。

公共部分只包含:

Lifecycle
Bus State
Capabilities
configuration/open/close/recover transaction

TX/RX 各自维护自己的 queue、ownership 和 spinlock。

锁顺序固定为:

lifecycle_lock
    ↓
tx.lock / rx.lock

ISR 永远不会获取 lifecycle_lock

18. 现有 BSP ISR 必须零修改兼容

第一阶段保留:

void rt_hw_can_isr(struct rt_can_device *can, int event);

保留现有 event 编码和 BSP 调用方式。

例如 BSP 现在:

rt_hw_can_isr(can,
              RT_CAN_EVENT_TX_DONE |
              (mailbox << 8));

不需要修改。

新的 rt_hw_can_isr() 内部改成 compatibility dispatcher:

void rt_hw_can_isr(struct rt_can_device *can, int event)
{
    rt_uint32_t type = event & 0xff;
    rt_uint32_t index = event >> 8;

    switch (type)
    {
    case RT_CAN_EVENT_TX_DONE:
        _can_tx_mailbox_event(can, index, RT_CAN_TX_RESULT_OK);
        break;

    case RT_CAN_EVENT_TX_FAIL:
        _can_tx_mailbox_event(can, index, RT_CAN_TX_RESULT_ERROR);
        break;

    case RT_CAN_EVENT_RX_IND:
        _can_rx_event(can, index);
        break;

    default:
        /* existing compatibility events */
        break;
    }
}

这样新 Core 可以获得 typed internal event,但 BSP 不需要一次性迁移。

19. Kconfig 裁剪

不增加:

RT_CAN_USING_RX
RT_CAN_USING_TX

CAN Framework 默认同时具备 TX/RX。

只对真正影响代码和 RAM 的功能做裁剪,例如:

config RT_CAN_USING_TX_BLOCKING
    bool "Enable CAN blocking TX"

config RT_CAN_USING_TX_NONBLOCKING
    bool "Enable CAN non-blocking TX"
    help
        Drivers providing non-blocking TX must implement sendmsg()
        as an ISR-safe hardware submission callback.

config RT_CAN_USING_TX_CALLBACK
    bool "Enable CAN TX completion callback"
    depends on RT_CAN_USING_TX_NONBLOCKING

config RT_CAN_USING_TX_TRACE
    bool "Enable CAN TX request trace"

config RT_CAN_TX_REQUEST_COUNT
    int "CAN TX request pool size"
    range 1 256

条件编译目标:

blocking disabled
→ remove completion / waiter path

callback disabled
→ remove callback + callback_arg

trace disabled
→ remove sequence + trace code

Request Pool RAM 可以按:

sizeof(rt_can_tx_request) × RT_CAN_TX_REQUEST_COUNT

直接评估。

20. Public API 兼容

第一阶段继续保留:

rt_device_read()
rt_device_write()
rt_device_control()
struct rt_can_msg

包括现有:

msg.nonblocking

作为 compatibility policy 输入。

内部立即转换为统一 Request:

rt_device_write
→ compatibility adapter
→ allocate TX Request
→ unified TX Engine

同时新增 async completion API,但不要求旧应用迁移。

21. 现有字段到新 Core 的迁移关系

当前实现 新 Framework
_can_int_tx blocking 独立路径 Unified TX Engine
_can_nonblocking_tx 独立路径 Unified TX Engine
rt_can_tx_fifo mailbox freelist TX Request Pool + mailbox ownership
tx_fifo->sem Request/ownership 生命周期,不再作为 correctness 主体
mailbox slot completion per-request completion
status.sndchange 不再用于 ownership
nb_tx_rb 删除,改为 TX Request FIFO
sendmsg_nonblocking() 第一阶段保留成员兼容,new core 不依赖
sendmsg(can,msg,box) 唯一 BSP TX submit primitive
TX ISR 中 put_force 删除,失败恢复 queue HEAD
raw local IRQ critical sections TX/RX 独立 spin_lock_irqsave
RX freelist/uselist 保留,但统一由 rx.lock 保护
rt_hw_can_isr() 完整保留外部 BSP contract,内部适配新 Core

22. 新 Framework 解决的问题对应关系

痛点 方案
blocking/non-blocking 两套发送逻辑 Unified TX Engine
后来的 frame 绕过 software backlog 所有 TX 统一先进入 Request FIFO
put_force 导致 reorder/overwrite reserve-submit-commit + restore queue HEAD
mailbox ownership 模糊 mailbox_owner[n] -> request
timeout 后 slot/request 过早复用 timeout != terminal
late IRQ 污染新 request explicit mailbox ownership
duplicate/abort race exactly-once terminal handler
full flush 无统一语义 QUIESCING + CANCEL + ABORT + QUIESCED
runtime bitrate change race Lifecycle transaction
close 与 active TX/RX race Lifecycle + TX/RX lock domain
non-blocking completion 无 identity TX Request + callback
IRQ-off 范围过大 short TX/RX spinlock critical sections
SMP local IRQ disable 不足 irq-safe spinlock
bus-off 与 stop 混淆 Bus State 与 Lifecycle 分离
RX list/HDR compound state RX ownership under one lock
大量 BSP 迁移成本 keep rt_hw_can_isr() / public device API

23. 分阶段落地建议

这份 Issue 讨论的是整体目标架构,不建议一个 PR 一次全部完成。

建议后续按以下层次拆分:

PR 1:Core synchronization foundation

- TX/RX spinlock domain
- lifecycle 基础状态
- 保持现有 public behavior
- 增加 core-level host/stub tests

PR 2:Unified TX Request + Scheduler

- Request Pool
- FIFO pending queue
- mailbox_owner
- reserve/submit/commit
- blocking/non-blocking unified engine
- remove nb_tx_rb correctness dependency

PR 3:Terminal / timeout correctness

- exactly-once terminal
- timeout waiter detach
- stale/duplicate IRQ handling
- callback API

PR 4:Strict TX order

- generic strict wire order
- ordered multi-mailbox capability
- resolve #11270 class ordering problems

PR 5:Quiesce / Full Flush / Abort

- QUIESCING / QUIESCED
- DRAIN / DROP_QUEUED / ABORT_ALL
- hardware abort capability
- support runtime bitrate reconfiguration

PR 6:Lifecycle / Bus-off / Recovery

- close safety
- reconfigure transaction
- Bus State
- controller recovery

PR 7:RX ownership cleanup and further capability normalization

- RX spinlock migration
- RX overflow policy
- filter/HDR ownership cleanup
- CAN FD / filter capability follow-up

实际拆分可根据 maintainer review 调整,但建议始终保持每个 PR 的 contract 清晰、可独立验证和可回滚。

24. 希望社区重点评估的细节

总体架构按上述 proposal 讨论,希望重点得到以下反馈:

  1. sendmsg() 统一为 hardware submit primitive,并要求支持 non-blocking 的 driver 保证 ISR-safe,这个 BSP contract 是否合适;
  2. sendmsg_nonblocking() 作为过渡成员保留多久比较合适;
  3. Generic strict wire-order 默认串行 hardware pending,对不同 controller 的吞吐影响是否可接受;
  4. ordered multi-mailbox capability 的抽象是否足够通用;
  5. Request Pool 的默认容量和配置入口应该放在 global Kconfig 还是 can_configure
  6. async completion callback 默认 ISR context 是否符合 RT-Thread driver API 习惯;
  7. privmode 在统一 scheduler 下继续使用指定 mailbox 时,是否需要保留现有并行语义;
  8. RX pool 满时采用 drop-new 而不是覆盖已排队旧 frame,兼容性是否需要额外考虑;
  9. Lifecycle 状态与现有 RT_CAN_CMD_START/STOP/CONFIG control command 的映射方式;
  10. 哪些现有 BSP 最适合作为第一批迁移和回归验证对象。

25. 当前建议冻结的架构基线

如果这套方向获得认可,后续实现建议以以下约束作为基线:

1. One Unified TX Engine.
2. Preallocated TX Request Descriptor Pool.
3. FIFO software pending queue.
4. Explicit mailbox -> request ownership.
5. One BSP TX submit API: sendmsg(can, msg, mailbox).
6. Non-blocking capable drivers require ISR-safe sendmsg().
7. No deferred scheduler / worker thread.
8. TX scheduler is kicked by enqueue and terminal events.
9. Generic strict wire-order guarantee.
10. Timeout is not a hardware terminal event.
11. Exactly-once terminal completion.
12. Lifecycle State and Bus State are separate.
13. Full flush is implemented through quiesce + cancel + hardware abort.
14. TX/RX have independent irq-safe spinlocks.
15. RX keeps the existing node-pool model but fixes ownership/synchronization.
16. Existing rt_hw_can_isr() call sites remain unchanged.
17. Existing rt_device API remains compatible in the first stage.
18. No RT_CAN_USING_RX / RT_CAN_USING_TX Kconfig split.
19. Optional blocking/nonblocking/callback/trace features can be compiled out.
20. status/statistics are observability only, not transaction ownership state.

这份 proposal 的核心目标是让 CAN Framework 从“多个发送路径 + 隐式 mailbox 状态 + IRQ critical sections”演进为:

explicit TX transaction ownership
+ ordered scheduler
+ explicit lifecycle
+ clear driver contract
+ independent TX/RX synchronization domains

如果整体 contract 可以达成共识,再进入具体结构体字段、error code、Kconfig 命名、BSP migration 和 PR implementation,会比继续围绕单个 bug 逐项打补丁更容易维护。

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions