第一部分:设计理念与核心概念

1. 概述:SocketCAN 是什么

SocketCAN 是 Linux 下对控制器局域网 (Controller Area Network) 协议的原生实现,它将 CAN 设备抽象为网络接口,将 CAN 通信抽象为套接字通信,使 CAN 总线操作与传统的 TCP/IP 网络编程模型保持一致。

/**
 * @brief SocketCAN 的核心设计目标
 * 
 * - 遵循 Berkeley 套接字 API,降低学习曲线
 * - 利用 Linux 网络协议栈,复用队列、过滤等基础设施
 * - 支持多个应用程序同时访问同一 CAN 接口
 * - 支持 CAN 原始帧和多种高层传输协议 (如 ISO-TP)
 * - 提供内核空间的广播管理器和高效的接收列表
 */

2. 动机:为什么弃用字符设备而选择 Socket API

在 SocketCAN 之前,Linux 上已存在基于字符设备的 CAN 驱动,但它们存在诸多限制。内核文档列举了使用字符设备的不优雅之处,以及采用网络栈的自然性。

传统字符设备方案的缺陷:

问题 字符设备表现 SocketCAN 解决方案
使用复杂性 必须通过 ioctl() 完成所有配置(协议选择、接口绑定、过滤规则等),代码冗长且易出错 使用标准的 socket()bind()setsockopt() 语义,逻辑清晰
代码重复 无法复用内核网络队列,每个驱动都要实现帧缓冲、多路分解、流量控制等 直接借用 Linux 网络协议栈的 sk_buff 队列和流量控制机制
抽象缺失 驱动直接提供字符设备给用户空间,不符合 Unix “策略与机制分离” 的设计哲学 通过协议族 PF_CAN 提供统一接口,硬件驱动仅实现底层操作,类似 TTY 层或音频子系统
多用户支持 通常只允许一个进程打开设备(类似串口),无法实现多个应用同时监听不同 CAN-ID 多个套接字可绑定到同一接口,内核通过高效过滤列表将帧分发到各个套接字
/**
 * @brief SocketCAN 采用网络栈的必然性
 * 
 * 实现完整的 CAN 字符设备子系统需要:
 * - 注册特定 CAN-ID 的机制
 * - 多个文件描述符的支持
 * - 帧的多路分解和复用
 * - 复杂的排队机制
 * - 设备驱动的统一接口
 * 
 * 而这些恰恰是 Linux 网络栈已经提供的能力。因此,
 * 将 CAN 实现为网络协议族是最自然、最高效的选择。
 * @see net/can/af_can.c
 */

调试场景:如果迁移一个旧版字符设备 CAN 应用到 SocketCAN,常见的问题是忘记替换 ioctl 调用为 setsockopt。一个典型症状是应用能够发送帧,但无法接收,因为接收过滤没有正确设置。


3. SocketCAN 核心概念

3.1 为什么 CAN-ID 可视为源地址

CAN 总线是广播介质,没有类似以太网的 MAC 地址。CAN 标识符 (CAN-ID) 用于总线仲裁,决定哪个节点优先发送。在良好设计的 CAN 网络中,每个 CAN-ID 由特定 ECU 唯一发送。因此,CAN-ID 可被视为“源地址”。这一设计哲学贯穿 SocketCAN:套接字通过绑定 CAN-ID(或 ID 范围)来声明自己感兴趣的数据流,而不是绑定到一个目标地址。

3.2 接收列表 (Receive Lists)

多个应用程序可能对来自同一 CAN 接口的相同 CAN-ID 感兴趣。SocketCAN 核心通过高效的接收列表来解决这一需求。

/**
 * @brief 接收列表的分级设计
 * 
 * can_rx_register() / can_rx_unregister() 允许协议模块订阅 CAN-ID。
 * 为了运行时 CPU 效率,接收列表按设备和过滤复杂度分为:
 * - rcvlist_all:   未过滤的条目
 * - rcvlist_eff:   单个扩展帧 (EFF) 条目
 * - rcvlist_err:   错误消息帧掩码
 * - rcvlist_fil:   掩码/值过滤器
 * - rcvlist_inv:   反向掩码/值过滤器
 * - rcvlist_sff:   单个标准帧 (SFF) 条目
 * 
 * 这种划分使内核在接收路径上能以 O(1) 或 O(log n) 的代价
 * 找到目标套接字,而不是遍历全部订阅者。
 * @see /proc/net/can/rcvlist_*
 */

调试技巧:当怀疑某个套接字没有收到预期帧时,检查 /proc/net/can/rcvlist_* 可验证过滤规则是否正确注册。matches 计数器显示实际命中的帧数。

3.3 本地回环 (Local Loopback)

为了保证同一节点上应用间的通信行为与跨节点通信一致,SocketCAN 默认启用本地回环:当一个套接字发送 CAN 帧时,该帧会像从总线上收到一样递送给本机其他监听该 CAN-ID 的套接字。

/**
 * @brief 回环的两种实现模式
 * 
 * 1. 控制器硬件回环:CAN 控制器在发送成功后自动将帧回送到接收队列。
 *    此时设备驱动应设置 IFF_ECHO 标志,通知核心不需要软件回环。
 * 2. 软件回环:当硬件不支持时,SocketCAN 核心在成功发送后模拟回环。
 * 
 * @note 默认情况下,发送套接字自身不会收到自己发送的帧(recv_own_msgs=0)。
 *       这在多数场景下符合预期,但可通过 CAN_RAW_RECV_OWN_MSGS 选项改变。
 */
3.4 网络问题通知 (Error Frames)

CAN 总线上的物理层和 MAC 层错误(如位错误、填充错误、总线关闭)对诊断至关重要。SocketCAN 允许用户空间通过标准的 CAN 过滤机制接收错误消息帧

  • 错误帧的格式定义在 include/uapi/linux/can/error.h

  • 默认情况下,接收错误帧是禁用的,需要显式通过 CAN_RAW_ERR_FILTER 选项开启。

  • 错误类型通过位掩码分类,CAN_ERR_MASK 可启用所有错误报告。


第二部分:编程接口与数据结构

4. 如何使用 SocketCAN

4.1 套接字创建与协议选择

SocketCAN 使用协议族 PF_CAN。目前提供两种套接字类型:

/**
 * @brief 创建 CAN 套接字
 * 
 * @param PF_CAN   CAN 协议族
 * @param SOCK_RAW 原始套接字,使用 CAN_RAW 协议
 * @param CAN_RAW  协议编号,允许发送/接收原始 CAN 帧
 * 
 * @return 成功返回套接字描述符,失败返回 -1
 */
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
​
/**
 * @brief 创建广播管理器套接字
 * @param SOCK_DGRAM 数据报类型
 * @param CAN_BCM    广播管理器协议
 */
int s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
4.2 绑定到接口

由于 CAN 没有 IP 地址,套接字需通过接口索引绑定到具体的 CAN 网络接口。

/**
 * @brief 绑定 CAN 套接字到接口 "can0"
 * 
 * @note 使用 ioctl(SIOCGIFINDEX) 从接口名获取索引
 *       addr.can_ifindex = 0 表示绑定到所有接口
 *       绑定到 'any' 时,需使用 recvfrom() 获取来源接口
 */
struct sockaddr_can addr;
struct ifreq ifr;
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
4.3 帧结构

SocketCAN 定义了两种帧结构:传统 CAN (struct can_frame) 和 CAN FD (struct canfd_frame)。

/**
 * @brief 传统 CAN 帧结构 (CAN 2.0B)
 * 
 * @field can_id   32位 CAN-ID,包含 EFF/RTR/ERR 标志
 * @field len      实际负载长度 (0..8),应代替 can_dlc 使用
 * @field can_dlc  已废弃,历史上命名不当,实际存的是长度而非 DLC
 * @field len8_dlc 当 len==8 时可存储 DLC 值 9..15 (传统 CAN 控制器需要)
 * @field data     负载数据,8字节对齐便于自定义结构体映射
 */
struct can_frame {
    canid_t can_id;
    union { __u8 len; __u8 can_dlc; };
    __u8    __pad;
    __u8    __res0;
    __u8    len8_dlc;
    __u8    data[8] __attribute__((aligned(8)));
};
​
/**
 * @brief CAN FD 帧结构 (灵活数据速率)
 * 
 * @field len    负载长度 (0..64),无歧义
 * @field flags  附加标志 (如 CANFD_BRS, CANFD_ESI)
 * @field data   64字节负载,8字节对齐
 * 
 * @note struct canfd_frame 的前几个字段与 struct can_frame 兼容,
 *       使得原有代码迁移时只需扩大缓冲区。
 */
struct canfd_frame {
    canid_t can_id;
    __u8    len;
    __u8    flags;
    __u8    __res0;
    __u8    __res1;
    __u8    data[64] __attribute__((aligned(8)));
};

场景调试:当发现接收的帧长度字段异常时,检查是否错误地使用了 can_dlc 而不是 len。在读取传统 CAN 帧时,len 直接给出有效字节数;在 CAN FD 场景下,永远不要假设 8 字节上限。

4.4 CAN FD 支持

CAN FD 的 MTU 不同 (CAN_MTU=16, CANFD_MTU=72)。要启用 CAN FD,必须在 RAW 套接字上设置 CAN_RAW_FD_FRAMES 选项。

/**
 * @brief 启用 CAN FD 支持
 * 
 * 设置后,套接字可发送和接收两种帧,应用程序通过 read() 返回的
 * 字节数区分帧类型:
 * - nbytes == CANFD_MTU (72)  → 收到 CAN FD 帧
 * - nbytes == CAN_MTU  (16)  → 收到传统 CAN 帧
 * 
 * @note 旧内核不支持此选项时返回 -ENOPROTOOPT
 */
int enable = 1;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable, sizeof(enable));
​
// 接收侧处理
struct canfd_frame cfd;
nbytes = read(s, &cfd, CANFD_MTU);
if (nbytes == CANFD_MTU) {
    // CAN FD 帧, cfd.flags 有效
} else if (nbytes == CAN_MTU) {
    // 传统 CAN 帧, cfd.flags 未定义
}

设计精髓:通过保持两种帧结构的兼容性,应用程序可以始终使用 struct canfd_frame 作为缓冲区,无需重构代码即可同时支持两种帧格式。len 字段在两种结构中偏移一致,意味着通过 cfd.len 总能获得正确长度。


5. RAW 协议套接字详解

RAW 套接字是使用最广泛的 CAN 接口,对应传统的字符设备操作模型,但提供了更丰富的控制选项。

5.1 默认行为

绑定 RAW 套接字时,系统设置以下默认值:

  • 过滤器:一个“接收所有”的过滤器

  • 错误帧:不接收

  • 回环:启用

  • 自身消息:不接收自己发送的帧

5.2 CAN 过滤器 (CAN_RAW_FILTER)
/**
 * @brief CAN 过滤器结构
 * 
 * 匹配条件: (received_can_id & can_mask) == (can_id & can_mask)
 * 若 can_id 中设置 CAN_INV_FILTER 位,逻辑取反。
 * 
 * 每个 RAW 套接字可独立设置 0..n 个过滤器。
 * 设置零过滤器意味着“发送专用”,内核将移除该套接字的接收列表以节省 CPU。
 */
struct can_filter {
    canid_t can_id;
    canid_t can_mask;
};
​
/**
 * @brief 优化提示:利用单 ID 索引
 * 
 * 对于单 CAN-ID 的订阅,设置 CAN_SFF_MASK 或 CAN_EFF_MASK 时,
 * 内核利用 ID 直接索引订阅列表(SFF)或使用哈希(EFF),
 * 从而在接收路径上达到 O(1) 效率。
 * 
 * 要获得优化,必须同时设置 CAN_EFF_FLAG 和 CAN_RTR_FLAG 在 mask 中,
 * 以明确区分 SFF/EFF。
 *
 * @code
 * struct can_filter rfilter[2];
 * rfilter[0].can_id   = 0x123;
 * rfilter[0].can_mask = (CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_SFF_MASK);
 * rfilter[1].can_id   = 0x12345678 | CAN_EFF_FLAG;
 * rfilter[1].can_mask = (CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_EFF_MASK);
 * setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
 * @endcode
 */
5.3 其他 RAW 套接字选项
选项 用途 默认值
CAN_RAW_ERR_FILTER 指定接收哪些类型的错误帧 无(禁用错误帧)
CAN_RAW_LOOPBACK 启用/禁用本地回环 1 (启用)
CAN_RAW_RECV_OWN_MSGS 是否接收自己发送的帧 0 (不接收)
CAN_RAW_JOIN_FILTERS 滤波器按逻辑与 (AND) 操作 多个滤波器为逻辑或 (OR)

调试经验:如果发现发送的帧能在外部分析仪上看到,但本机其他套接字收不到,首先检查 CAN_RAW_LOOPBACK 是否被意外关闭。若需要在发送套接字上确认传输成功,可启用 CAN_RAW_RECV_OWN_MSGS,然后检查回环帧是否存在。

5.4 返回消息标志

当使用 recvmsg() 时,msg->msg_flags 可能包含:

  • MSG_DONTROUTE:帧来自本地主机

  • MSG_CONFIRM:帧是由该套接字发送的(传输确认),需要 CAN_RAW_RECV_OWN_MSGS 开启


第 6 章 广播管理器协议套接字 (BCM, SOCK_DGRAM)

广播管理器(BCM)是内核提供的一种高级 CAN 服务。与 RAW 套接字的“发送/接收原始帧”不同,BCM 支持在内核空间配置周期性发送任务内容变化检测过滤器。这样可将定时和过滤逻辑从用户空间移到内核,减少上下文切换,提高实时性。

6.1 BCM 消息格式

BCM 使用专有消息结构 struct bcm_msg_head 与内核通信。用户空间通过 write() 发送配置指令,通过 read() 接收响应和通知。

/**
 * @brief BCM 消息头结构
 *
 * @field opcode  操作码,定义要执行的操作
 * @field flags   控制标志,影响操作行为
 * @field count   发送次数(与 ival1 配合使用)
 * @field ival1   第一个间隔时间(首次或计数期间的间隔)
 * @field ival2   第二个间隔时间(count 用完后使用)
 * @field can_id  此任务的唯一 CAN 标识符
 * @field nframes 后续附加的 CAN 帧数量
 * @field frames  柔性数组成员,存放实际的 CAN 帧
 *
 * @note BCM 套接字使用 connect() 而不是 bind() 关联到 CAN 接口。
 *       绑定到 'any' 接口时,sendto() 可覆盖传出接口。
 */
struct bcm_msg_head {
    __u32 opcode;
    __u32 flags;
    __u32 count;
    struct timeval ival1, ival2;
    canid_t can_id;
    __u32 nframes;
    struct can_frame frames[0];
};

6.2 BCM 操作码

BCM 的操作分为四类,完整覆盖了任务生命周期:

类别 操作码 方向 描述
发送配置 TX_SETUP 用户→内核 创建(周期性)发送任务
TX_DELETE 用户→内核 删除发送任务(仅需 can_id)
TX_READ 用户→内核 读取发送任务属性
TX_SEND 用户→内核 发送单个 CAN 帧
发送响应 TX_STATUS 内核→用户 回复 TX_READ 请求
TX_EXPIRED 内核→用户 通知计数完成
接收配置 RX_SETUP 用户→内核 创建接收内容过滤订阅
RX_DELETE 用户→内核 删除接收订阅
RX_READ 用户→内核 读取接收订阅属性
接收响应 RX_STATUS 内核→用户 回复 RX_READ 请求
RX_TIMEOUT 内核→用户 检测到消息超时
RX_CHANGED 内核→用户 检测到 CAN 帧内容变化

6.3 BCM 标志位

flags 字段控制 BCM 操作的细节行为:

标志 作用域 描述
SETTIMER TX/RX 将 ival1/ival2/count 的值应用到定时器
STARTTIMER TX/RX 立即启动定时器(同时发送第一帧)
TX_COUNTEVT TX count 耗尽时生成 TX_EXPIRED 通知
TX_ANNOUNCE TX 数据变更时立即发送,不等定时器
TX_CP_CAN_ID TX 将头部 can_id 复制到后续帧
RX_FILTER_ID RX 仅按 can_id 过滤,不需要帧数据
RX_CHECK_DLC RX DLC 变化时触发 RX_CHANGED
RX_NO_AUTOTIMER RX 不自动启动超时监控
RX_ANNOUNCE_RESUME RX 超时后恢复接收时产生 RX_CHANGED
TX_RESET_MULTI_IDX TX 重置多帧发送索引
RX_RTR_FRAME RX 回复 RTR 请求
CAN_FD_FRAME TX/RX 指示后续帧为 CAN FD 格式

6.4 BCM 定时器模型

BCM 支持双定时器模型,用于实现灵活的多阶段周期性发送:

阶段 1 (count × ival1):
  每隔 ival1 发送一次,共 count 次
  ↓
阶段 2 (ival2):
  count 用完后,每隔 ival2 发送一次
/**
 * @brief BCM 定时器使用示例
 *
 * 创建每 100ms 发送 5 次,之后每 1s 发送一次的任务:
 *
 * struct {
 *     struct bcm_msg_head msg_head;
 *     struct can_frame frame;
 * } msg;
 *
 * msg.msg_head.opcode = TX_SETUP;
 * msg.msg_head.can_id = 0x123;
 * msg.msg_head.flags  = SETTIMER | STARTTIMER;
 * msg.msg_head.count  = 5;
 * msg.msg_head.ival1  = {0, 100000};   // 100ms
 * msg.msg_head.ival2  = {1, 0};       // 1s
 * msg.msg_head.nframes = 1;
 * // 填充 msg.frame ...
 * write(s, &msg, sizeof(msg));
 */

场景调试:如果 BCM 任务没有按预期发送,首先检查是否正确设置了 STARTTIMER 标志。只设置 SETTIMER 不会启动发送,必须配合 STARTTIMER 一起使用。

6.5 BCM 多帧序列发送

TX_SETUP 可附带最多 256 个 CAN 帧,每次发送按索引递增,到达 nframes 后回绕。

6.6 BCM 接收过滤与多路复用

BCM 支持内容变化检测和超时监控:

  • ival1:若在 ival1 时间内未再次收到匹配消息,发送 RX_TIMEOUT 通知。

  • ival2:节流速率,将接收消息速率降至 ival2 的间隔。

多路复用过滤:对于包含多路选择器的消息序列,可传递超过 1 个 CAN 帧的数组。第一个帧作为位掩码,后续帧定义不同多路选择器值和对应的匹配掩码。最多支持 257 个帧(1 个 MUX 掩码 + 256 个过滤器)。

/**
 * @brief BCM 多路复用过滤示例
 *
 * 以下配置定义了 4 个多路选择器值 (0x01, 0x02, 0x33, 0x4F)
 * 对应的匹配掩码。当收到 CAN 帧时,内核先根据首帧的 MUX 掩码
 * 提取多路选择器值,再用对应的数据掩码与上次接收的帧比较,
 * 若变化则产生 RX_CHANGED 通知。
 */
U64_DATA(&msg.frame[0]) = 0xFF00000000000000ULL;  // MUX 掩码
U64_DATA(&msg.frame[1]) = 0x01000000000000FFULL;  // MUX=0x01 的数据掩码
U64_DATA(&msg.frame[2]) = 0x0200FFFF000000FFULL;  // MUX=0x02
U64_DATA(&msg.frame[3]) = 0x330000FFFFFF0003ULL;  // MUX=0x33
U64_DATA(&msg.frame[4]) = 0x4F07FC0FF0000000ULL;  // MUX=0x4F

6.7 BCM 的 CAN FD 支持

flags 中设置 CAN_FD_FRAME 时,BCM 消息后附的是 struct canfd_frame 而非 struct can_frame。多路复用掩码仍位于第一个 64 位数据段中。


第 7 章 SocketCAN 核心模块

7.1 核心功能

SocketCAN 核心模块 (can.ko) 实现 PF_CAN 协议族,运行时加载 CAN 协议模块(如 raw、bcm)。它提供 CAN-ID 订阅/取消订阅接口 can_rx_register() / can_rx_unregister()

7.2 模块参数

/**
 * @brief can.ko 模块参数
 *
 * - stats_timer: 是否启用每秒一次的核心统计定时器。
 *   默认启用,通过 stattimer=0 禁用可减少定时器中断。
 * @note debug 参数在 SocketCAN SVN r546 后已移除
 */

7.3 procfs 接口

文件 内容
/proc/net/can/rcvlist_all 未过滤条目列表
/proc/net/can/rcvlist_eff 扩展帧 (EFF) 条目
/proc/net/can/rcvlist_err 错误消息帧掩码
/proc/net/can/rcvlist_fil 掩码/值过滤器
/proc/net/can/rcvlist_inv 反向掩码/值过滤器
/proc/net/can/rcvlist_sff 标准帧 (SFF) 条目
/proc/net/can/stats 核心统计信息 (收发帧数、匹配率等)
/proc/net/can/reset_stats 手动重置统计
/proc/net/can/version 核心和 ABI 版本 (Linux 5.10 已移除)
/**
 * @brief 检查接收列表的示例
 *
 * 通过查看 /proc/net/can/rcvlist_* 可以确认:
 * - 过滤规则是否正确注册
 * - 匹配计数器是否递增
 * - 协议模块标识是否正确
 *
 * @see /proc/net/can/rcvlist_all
 */

7.4 自定义 CAN 协议模块

实现新协议需要:

/**
 * @brief 自定义 CAN 协议模块的关键接口
 *
 * - 在 include/linux/can.h 定义新协议编号
 * - 包含 include/linux/can/core.h 获取核心接口
 * - 使用 can_rx_register() 订阅特定接口的 CAN-ID
 * - 使用 can_send() 发送 CAN 帧(可选本地回环)
 * - 注册 CAN 设备通知链以跟踪接口状态变化
 *
 * @see net/can/af_can.c  (核心实现)
 * @see net/can/raw.c      (RAW 协议模块)
 * @see net/can/bcm.c      (BCM 协议模块)
 */

第 8 章 CAN 网络设备驱动

8.1 与字符设备驱动的对比

方面 字符设备驱动 网络设备驱动
实现复杂度 高(需自行实现队列、多路分解) 低(复用内核网络栈)
TX 路径 管理自己的发送队列 struct sk_buff 获取帧写入控制器
RX 路径 管理自己的接收缓冲区 从控制器读取帧分配 sk_buff 向上传递

8.2 通用设置

/**
 * @brief CAN 网络设备初始化要点
 *
 * dev->type  = ARPHRD_CAN;    // 硬件类型
 * dev->flags = IFF_NOARP;     // CAN 没有 ARP
 * dev->mtu   = CAN_MTU;       // 16 (传统 CAN)
 * dev->mtu   = CANFD_MTU;     // 72 (CAN FD 设备)
 *
 * @note 每个 sk_buff 的载荷是 struct can_frame 或 struct canfd_frame
 */

8.3 本地回环支持

/**
 * @brief 硬件回环的标志设置
 *
 * 若 CAN 控制器在硬件层面支持发送帧的回环:
 *   dev->flags = (IFF_NOARP | IFF_ECHO);
 * 
 * 设置 IFF_ECHO 后,PF_CAN 核心不再执行软件回环,
 * 避免帧被重复递送。
 */

调试场景:如果发现应用收到重复的 CAN 帧,检查驱动是否正确设置了 IFF_ECHO。若硬件已回环但驱动未设此标志,核心会额外软件回环一次,导致重复。

8.4 控制器硬件过滤器

/**
 * @brief 硬件过滤器的使用建议
 *
 * 虽然大部分 CAN 控制器支持硬件 ID 过滤,但在多用户 SocketCAN 架构下:
 * - 硬件过滤器作用于整个接口,影响所有用户
 * - PF_CAN 核心提供高效软件过滤,支持每个套接字独立设置
 * - 硬件过滤器仅在极深入的嵌入式系统上作为手工优化手段使用
 *
 * @note 作者的 MPC603e @133MHz 运行 4 个 SJA1000 控制器,
 *       仅使用软件过滤即能承受满总线负载。
 */

第 9 章 虚拟 CAN 驱动 (vcan)

虚拟 CAN 接口允许在没有真实 CAN 硬件的情况下收发 CAN 帧,常用于测试和开发。

/**
 * @brief vcan 接口管理
 *
 * # 创建虚拟 CAN 接口
 * $ ip link add type vcan
 *
 * # 创建指定名称的接口
 * $ ip link add dev vcan42 type vcan
 *
 * # 删除接口
 * $ ip link del vcan42
 *
 * @note 虚拟接口命名约定为 vcanX (vcan0, vcan1, ...)
 *       内核模块名为 vcan.ko
 *       自 2.6.24 起支持 netlink 接口创建
 */

第 10 章 CAN FD 驱动支持

10.1 两种 CAN FD 实现

/**
 * @brief CAN FD 标准的两种实现
 *
 * 1. ISO 兼容:  ISO 11898-1:2015 标准 (默认)
 * 2. 非 ISO:    2012 年白皮书版本
 *
 * CAN FD 控制器分为三类:
 * - 固定 ISO     (大多数现代控制器)
 * - 固定非 ISO   (如 M_CAN IP core v3.0.1)
 * - 可切换       (如 PEAK PCAN-USB FD)
 *
 * @note ISO/非 ISO 模式通过 netlink 通告,ip 工具显示为 FD-NON-ISO
 */

10.2 CAN FD 配置示例

/**
 * @brief 配置 CAN FD 设备示例
 *
 * # 设置仲裁段 500kbps,数据段 4Mbps,启用 CAN FD
 * $ ip link set can0 up type can bitrate 500000 sample-point 0.75 \
 *                                dbitrate 4000000 dsample-point 0.8 fd on
 *
 * # 对可切换设备设置非 ISO 模式
 * $ ip link set can0 type can fd-non-iso on
 *
 * @note DLC 与长度的转换由驱动内部使用 can_fd_dlc2len() 完成,
 *       用户空间看到的是纯长度值
 */

第 11 章 CAN 设备配置接口

11.1 Netlink 配置

CAN 设备通过 netlink 配置,使用 ip 工具(来自 iproute2)。

/**
 * @brief CAN 设备配置选项
 *
 * 基本参数:
 *   bitrate             位速率 (1..1000000)
 *   sample-point        采样点 (0.000..0.999)
 *
 * 硬件无关位时序:
 *   tq                  时间量子 (ns)
 *   prop-seg            传播段 (1..8)
 *   phase-seg1          相位缓冲段 1 (1..8)
 *   phase-seg2          相位缓冲段 2 (1..8)
 *   sjw                 同步跳转宽度 (1..4)
 *
 * CAN FD 参数 (以 'd' 开头):
 *   dbitrate, dsample-point, dtq, dprop-seg,
 *   dphase-seg1, dphase-seg2, dsjw
 *
 * 控制器模式:
 *   loopback {on|off}      本地环回
 *   listen-only {on|off}   仅监听
 *   triple-sampling {on|off} 三次采样
 *   one-shot {on|off}      单次发送
 *   berr-reporting {on|off} 总线错误报告
 *   fd {on|off}            CAN FD 启用
 *   fd-non-iso {on|off}    非 ISO CAN FD
 */

11.2 启动和停止

/**
 * @brief CAN 设备的启动与停止
 *
 * # 启动前必须配置位时序,避免使用不安全默认值
 * $ ip link set canX up type can bitrate 125000
 *
 * # 总线关闭自动恢复
 * $ ip link set canX type can restart-ms 100
 *
 * # 手动重启
 * $ ip link set canX type can restart
 *
 * @note 总线关闭后设备不再收发。可通过监测 CAN 错误消息帧感知。
 */

第 12 章 SocketCAN 资源与致谢

SocketCAN 项目的官方资源和邮件列表在 Linux 源码树 MAINTAINERS 文件中列出,搜索 CAN NETWORK [LAYERS|DRIVERS]。

核心贡献者(部分):

  • Oliver Hartkopp:PF_CAN 核心、过滤器、多种驱动、BCM、SJA1000 驱动

  • Urs Thuermann:PF_CAN 核心、内核集成、套接字接口、RAW、vcan

  • Jan Kizka:RT-SocketCAN 核心、Socket-API 协调

  • Wolfgang Grandegger:RT-SocketCAN 核心和驱动、CAN 设备驱动接口、MSCAN 驱动

  • Marc Kleine-Budde:设计评审、Kernel 2.6 清理、多种驱动

  • Pavel Pisa:位时序计算

  • 以及众多驱动贡献者和评审者

SocketCAN 最核心的设计价值在于它正确地选择站在了巨人的肩膀上——没有重新发明轮子去实现专门的 CAN 字符设备子系统,而是将 CAN 的语义巧妙地映射到 BSD 套接字和 Linux 网络栈这个成熟的抽象上。这使得 CAN 编程不再是嵌入式领域孤立的秘术,而是融入了通用网络编程的统一范式。从高效的多路分解、广播管理器到 CAN FD 的无缝扩展,这一架构经受住了二十年的考验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐