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 逐项打补丁更容易维护。

Activity

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

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