SocketCAN - 控制器局域网¶
概述 / 什么是 SocketCAN¶
socketcan 包是 Linux 的 CAN 协议(控制器局域网)的实现。CAN 是一种网络技术,广泛应用于自动化、嵌入式设备和汽车领域。虽然之前有其他基于字符设备的 Linux CAN 实现,但 SocketCAN 使用 Berkeley socket API、Linux 网络堆栈,并将 CAN 设备驱动程序实现为网络接口。CAN socket API 的设计尽可能与 TCP/IP 协议相似,以便熟悉网络编程的程序员能够轻松学习如何使用 CAN socket。
动机 / 为什么使用 Socket API¶
在 SocketCAN 之前已经有 Linux 的 CAN 实现,所以出现了一个问题,为什么我们要启动另一个项目。大多数现有的实现都是作为某些 CAN 硬件的设备驱动程序提供的,它们基于字符设备,并且提供的功能相对较少。通常,只有一个硬件特定的设备驱动程序,它提供一个字符设备接口来直接从/向控制器硬件发送和接收原始 CAN 帧。帧的排队和更高级别的传输协议(如 ISO-TP)必须在用户空间应用程序中实现。此外,大多数字符设备实现一次只支持单个进程打开设备,类似于串行接口。更换 CAN 控制器需要使用另一个设备驱动程序,并且通常需要调整应用程序的大部分以适应新的驱动程序 API。
SocketCAN 的设计目的是克服所有这些限制。实现了一个新的协议族,它为用户空间应用程序提供了一个 socket 接口,并且建立在 Linux 网络层之上,从而可以使用所有提供的排队功能。CAN 控制器硬件的设备驱动程序将自己注册为 Linux 网络层中的一个网络设备,以便来自控制器的 CAN 帧可以传递到网络层,然后传递到 CAN 协议族模块,反之亦然。此外,协议族模块还为传输协议模块提供了一个 API 来注册,以便可以动态加载或卸载任意数量的传输协议。事实上,can core 模块本身不提供任何协议,如果没有加载至少一个额外的协议模块,就无法使用。可以同时打开多个 socket,在不同的或相同的协议模块上,它们可以监听/发送不同或相同的 CAN ID 的帧。多个 socket 在同一个接口上监听具有相同 CAN ID 的帧,都会收到相同的匹配 CAN 帧。希望使用特定传输协议(例如 ISO-TP)进行通信的应用程序只需在打开 socket 时选择该协议,然后就可以读取和写入应用程序数据字节流,而无需处理 CAN-ID、帧等。
用户空间可见的类似功能也可以通过字符设备提供,但这将导致一些技术上不优雅的解决方案,原因如下:
复杂的使用方式:应用程序需要使用 ioctl(2) 而不是将协议参数传递给 socket(2) 并使用 bind(2) 来选择 CAN 接口和 CAN ID 来完成所有这些操作。
代码重复:字符设备无法使用 Linux 网络排队代码,因此所有这些代码都必须为 CAN 网络重复。
抽象:在大多数现有的字符设备实现中,CAN 控制器的硬件特定设备驱动程序直接为应用程序提供字符设备。这在 Unix 系统中对于字符设备和块设备来说至少是非常不寻常的。例如,您没有用于串行接口的某个 UART、计算机中的某个声卡芯片、SCSI 或 IDE 控制器(用于访问您的硬盘或磁带流媒体设备)的字符设备。相反,您有抽象层,这些抽象层一方面为应用程序提供统一的字符或块设备接口,另一方面为硬件特定设备驱动程序提供接口。这些抽象由子系统提供,例如 tty 层、音频子系统或上述设备的 SCSI 和 IDE 子系统。
实现 CAN 设备驱动程序的最简单方法是将其作为没有这种(完整)抽象层的字符设备来实现,就像大多数现有驱动程序所做的那样。然而,正确的方法是添加这样一个层,其中包含所有功能,例如注册某些 CAN ID、支持多个打开的文件描述符以及(解)多路复用它们之间的 CAN 帧、(复杂的)CAN 帧排队,并提供一个设备驱动程序注册的 API。然而,这样一来,使用 Linux 内核提供的网络框架就不会更困难,或者可能更容易,这就是 SocketCAN 所做的。
使用 Linux 内核的网络框架只是为 Linux 实现 CAN 的自然和最合适的方式。
SocketCAN 概念¶
正如在 动机 / 为什么使用 Socket API 中描述的那样,SocketCAN 的主要目标是为用户空间应用程序提供一个建立在 Linux 网络层之上的 socket 接口。与通常已知的 TCP/IP 和以太网网络不同,CAN 总线是一个仅广播的媒介,没有像以太网这样的 MAC 层寻址。CAN 标识符 (can_id) 用于 CAN 总线上的仲裁。因此,CAN-ID 必须在总线上唯一选择。在设计 CAN-ECU 网络时,CAN-ID 被映射为由特定的 ECU 发送。因此,CAN-ID 可以最好地被视为一种源地址。
接收列表¶
多个应用程序的网络透明访问导致不同的应用程序可能对来自同一 CAN 网络接口的相同 CAN-ID 感兴趣的问题。SocketCAN 核心模块(实现协议族 CAN)为此提供了几个高效的接收列表。例如,如果一个用户空间应用程序打开一个 CAN RAW socket,则 raw 协议模块本身会从 SocketCAN 核心请求用户请求的(范围)CAN-ID。CAN-ID 的订阅和取消订阅可以针对特定的 CAN 接口或所有已知 CAN 接口完成,使用 SocketCAN 核心提供给 CAN 协议模块的 can_rx_(un)register() 函数(参见 SocketCAN 核心模块)。为了优化运行时的 CPU 使用率,接收列表被分为每个设备的几个特定列表,这些列表匹配给定用例的请求的过滤器复杂性。
发送帧的本地环回¶
正如从其他网络概念中已知的那样,数据交换应用程序可以在相同或不同的节点上运行,而无需任何更改(除了相关的寻址信息)
___ ___ ___ _______ ___
| _ | | _ | | _ | | _ _ | | _ |
||A|| ||B|| ||C|| ||A| |B|| ||C||
|___| |___| |___| |_______| |___|
| | | | |
-----------------(1)- CAN bus -(2)---------------
为了确保应用程序 A 在示例 (2) 中接收到与示例 (1) 中接收到的相同信息,需要对相应节点上的发送 CAN 帧进行某种本地环回。
Linux 网络设备(默认情况下)只能处理媒体相关帧的传输和接收。由于 CAN 总线上的仲裁,低优先级 CAN-ID 的传输可能会因高优先级 CAN 帧的接收而延迟。为了反映正确的 [1] 节点上的流量,发送数据的环回必须在成功传输后立即执行。如果 CAN 网络接口由于某种原因无法执行环回,则 SocketCAN 核心可以作为后备解决方案执行此任务。有关详细信息,请参见 发送帧的本地环回(推荐)。
默认情况下启用环回功能,以反映 CAN 应用程序的标准网络行为。由于 RT-SocketCAN 组的一些请求,可以选择为每个单独的 socket 禁用环回。有关详细信息,请参见 带有 can_filters 的 RAW 协议 Socket (SOCK_RAW) 中的 CAN RAW socket 的 sockopts。
网络问题通知¶
CAN 总线的使用可能会导致物理和媒体访问控制层上的几个问题。检测和记录这些较低层的问题是 CAN 用户识别物理收发器层上的硬件问题以及由不同 ECU 引起的仲裁问题和错误帧的重要要求。检测到的错误的发生对于诊断很重要,并且必须与确切的时间戳一起记录。因此,CAN 接口驱动程序可以生成所谓的错误消息帧,这些帧可以选择以与其他 CAN 帧相同的方式传递到用户应用程序。每当检测到物理层或 MAC 层上的错误时(例如,由 CAN 控制器),驱动程序会创建一个适当的错误消息帧。用户应用程序可以使用通用的 CAN 过滤器机制请求错误消息帧。在此过滤器定义中,可以选择(感兴趣的)错误类型。默认情况下禁用错误消息的接收。CAN 错误消息帧的格式在 Linux 头文件“include/uapi/linux/can/error.h”中简要描述。
如何使用 SocketCAN¶
与 TCP/IP 一样,您首先需要打开一个 socket 才能通过 CAN 网络进行通信。由于 SocketCAN 实现了一个新的协议族,因此您需要将 PF_CAN 作为第一个参数传递给 socket(2) 系统调用。目前,有两种 CAN 协议可供选择,即 raw socket 协议和广播管理器 (BCM)。因此,要打开一个 socket,您可以编写
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
和
s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
分别。成功创建 socket 后,您通常会使用 bind(2) 系统调用将 socket 绑定到 CAN 接口(由于不同的寻址,这与 TCP/IP 不同 - 参见 SocketCAN 概念)。绑定 (CAN_RAW) 或连接 (CAN_BCM) socket 后,您可以像往常一样从/向 socket read(2) 和 write(2),或者在 socket 上使用 send(2)、sendto(2)、sendmsg(2) 和 recv* 对等操作。下面还介绍了 CAN 特定的 socket 选项。
Classical CAN 帧结构(也称为 CAN 2.0B)、CAN FD 帧结构和 sockaddr 结构在 include/linux/can.h 中定义
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
union {
/* CAN frame payload length in byte (0 .. CAN_MAX_DLEN)
* was previously named can_dlc so we need to carry that
* name for legacy support
*/
__u8 len;
__u8 can_dlc; /* deprecated */
};
__u8 __pad; /* padding */
__u8 __res0; /* reserved / padding */
__u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */
__u8 data[8] __attribute__((aligned(8)));
};
备注:len 元素包含有效负载长度(以字节为单位),应代替 can_dlc 使用。已弃用的 can_dlc 具有误导性的名称,因为它始终包含以字节为单位的普通有效负载长度,而不是所谓的“数据长度代码”(DLC)。
要将原始 DLC 从/向 Classical CAN 网络设备传递,当 len 元素为 8 时,len8_dlc 元素可以包含值 9 .. 15(所有大于或等于 8 的 DLC 值的实际有效负载长度)。
(线性)有效负载数据 [] 与 64 位边界的对齐允许用户定义自己的结构和联合,以便轻松访问 CAN 有效负载。默认情况下,CAN 总线上没有给定的字节顺序。对 CAN_RAW socket 的 read(2) 系统调用会将 struct can_frame 传输到用户空间。
sockaddr_can 结构具有像 PF_PACKET socket 一样的接口索引,它也绑定到特定的接口
struct sockaddr_can {
sa_family_t can_family;
int can_ifindex;
union {
/* transport protocol class address info (e.g. ISOTP) */
struct { canid_t rx_id, tx_id; } tp;
/* J1939 address information */
struct {
/* 8 byte name when using dynamic addressing */
__u64 name;
/* pgn:
* 8 bit: PS in PDU2 case, else 0
* 8 bit: PF
* 1 bit: DP
* 1 bit: reserved
*/
__u32 pgn;
/* 1 byte address */
__u8 addr;
} j1939;
/* reserved for future CAN protocols address information */
} can_addr;
};
要确定接口索引,必须使用适当的 ioctl()(CAN_RAW socket 的示例,没有错误检查)
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
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));
(..)
要将 socket 绑定到所有 CAN 接口,接口索引必须为 0(零)。在这种情况下,socket 会收到来自每个启用的 CAN 接口的 CAN 帧。要确定原始 CAN 接口,可以使用系统调用 recvfrom(2) 代替 read(2)。要从绑定到“任何”接口的 socket 发送,需要 sendto(2) 来指定传出接口。
从绑定的 CAN_RAW socket 读取 CAN 帧(参见上文)包括读取 struct can_frame
struct can_frame frame;
nbytes = read(s, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("can raw socket read");
return 1;
}
/* paranoid check ... */
if (nbytes < sizeof(struct can_frame)) {
fprintf(stderr, "read: incomplete CAN frame\n");
return 1;
}
/* do something with the received CAN frame */
写入 CAN 帧可以类似地完成,使用 write(2) 系统调用
nbytes = write(s, &frame, sizeof(struct can_frame));
当 CAN 接口绑定到“任何”现有 CAN 接口时 (addr.can_ifindex = 0),如果需要有关原始 CAN 接口的信息,建议使用 recvfrom(2)
struct sockaddr_can addr;
struct ifreq ifr;
socklen_t len = sizeof(addr);
struct can_frame frame;
nbytes = recvfrom(s, &frame, sizeof(struct can_frame),
0, (struct sockaddr*)&addr, &len);
/* get interface name of the received CAN frame */
ifr.ifr_ifindex = addr.can_ifindex;
ioctl(s, SIOCGIFNAME, &ifr);
printf("Received a CAN frame from interface %s", ifr.ifr_name);
要在绑定到“任何”CAN 接口的 socket 上写入 CAN 帧,必须确定传出接口
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_ifindex = ifr.ifr_ifindex;
addr.can_family = AF_CAN;
nbytes = sendto(s, &frame, sizeof(struct can_frame),
0, (struct sockaddr*)&addr, sizeof(addr));
通过在从 socket 读取消息后使用 ioctl(2) 调用可以获得准确的时间戳
struct timeval tv;
ioctl(s, SIOCGSTAMP, &tv);
时间戳的分辨率为一微秒,并在接收到 CAN 帧时自动设置。
关于 CAN FD(灵活数据速率)支持的备注
通常,CAN FD 的处理方式与前面描述的示例非常相似。新的支持 CAN FD 的 CAN 控制器支持两种不同的比特率,分别用于仲裁阶段和 CAN FD 帧的有效负载阶段,以及最多 64 字节的有效负载。这种扩展的有效负载长度破坏了所有严重依赖于具有固定八字节有效负载的 CAN 帧 (struct can_frame) 的内核接口 (ABI),例如 CAN_RAW socket。因此,例如,CAN_RAW socket 支持一个新的 socket 选项 CAN_RAW_FD_FRAMES,该选项将 socket 切换到一种模式,该模式允许同时处理 CAN FD 帧和 Classical CAN 帧(参见 RAW Socket 选项 CAN_RAW_FD_FRAMES)。
struct canfd_frame 在 include/linux/can.h 中定义
struct canfd_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
__u8 len; /* frame payload length in byte (0 .. 64) */
__u8 flags; /* additional flags for CAN FD */
__u8 __res0; /* reserved / padding */
__u8 __res1; /* reserved / padding */
__u8 data[64] __attribute__((aligned(8)));
};
struct canfd_frame 和现有的 struct can_frame 在其结构中的相同偏移处具有 can_id、有效负载长度和有效负载数据。这允许非常相似地处理不同的结构。当 struct can_frame 的内容被复制到 struct canfd_frame 中时,所有结构元素都可以按原样使用 - 只有 data[] 变得扩展了。
当引入 struct canfd_frame 时,事实证明 struct can_frame 的数据长度代码 (DLC) 被用作长度信息,因为长度和 DLC 在 0 .. 8 的范围内具有 1:1 的映射。为了保留长度信息的易于处理,canfd_frame.len 元素包含一个从 0 .. 64 的普通长度值。因此,canfd_frame.len 和 can_frame.len 都相等并且包含长度信息,而不是 DLC。有关 CAN 和支持 CAN FD 的设备以及到总线相关数据长度代码 (DLC) 的映射的详细信息,请参见 CAN FD(灵活数据速率)驱动程序支持。
两个 CAN(FD) 帧结构的长度定义了 CAN(FD) 网络接口和 skbuff 数据长度的最大传输单元 (MTU)。在 include/linux/can.h 中指定了两个用于 CAN 特定 MTU 的定义
#define CAN_MTU (sizeof(struct can_frame)) == 16 => Classical CAN frame
#define CANFD_MTU (sizeof(struct canfd_frame)) == 72 => CAN FD frame
返回的消息标志¶
当在 RAW 或 BCM socket 上使用系统调用 recvmsg(2) 时,msg->msg_flags 字段可能包含以下标志
带有 can_filters 的 RAW 协议 Socket (SOCK_RAW)¶
使用 CAN_RAW socket 与通常已知的访问 CAN 字符设备非常相似。为了满足多用户 SocketCAN 方法提供的新可能性,在 RAW socket 绑定时设置了一些合理的默认值
过滤器设置为只有一个过滤器接收所有内容
socket 只接收有效的数据帧(=> 没有错误消息帧)
启用发送 CAN 帧的环回(参见 发送帧的本地环回)
socket 不接收自己发送的帧(在环回模式下)
这些默认设置可以在绑定 socket 之前或之后更改。要使用 CAN_RAW socket 的 socket 选项的引用定义,请包含
RAW socket 选项 CAN_RAW_FILTER¶
使用 CAN_RAW socket 接收 CAN 帧可以通过定义 0 .. n 个带有 CAN_RAW_FILTER socket 选项的过滤器来控制。
CAN 过滤器结构在 include/linux/can.h 中定义
struct can_filter {
canid_t can_id;
canid_t can_mask;
};
当
<received_can_id> & mask == can_id & mask
时,过滤器匹配,这类似于已知的 CAN 控制器硬件过滤器语义。当在 can_filter 结构的 can_id 元素中设置了 CAN_INV_FILTER 位时,可以在此语义中反转过滤器。与 CAN 控制器硬件过滤器相反,用户可以为每个打开的 socket 单独设置 0 .. n 个接收过滤器
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK;
rfilter[1].can_id = 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
要在选定的 CAN_RAW socket 上禁用 CAN 帧的接收
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
将过滤器设置为零个过滤器是相当过时的,因为不读取数据会导致 raw socket 丢弃收到的 CAN 帧。但是,有了这个“仅发送”用例,我们可以删除内核中的接收列表,以节省一点(确实是非常少!)CPU 使用率。
CAN 过滤器使用优化¶
CAN 过滤器在 CAN 帧接收时按设备过滤器列表进行处理。为了减少在遍历过滤器列表时需要执行的检查次数,当过滤器订阅专注于单个 CAN ID 时,CAN 核心提供优化的过滤器处理。
对于可能的 2048 个 SFF CAN 标识符,标识符用作索引来访问相应的订阅列表,而无需任何进一步的检查。对于 2^29 个可能的 EFF CAN 标识符,使用 10 位 XOR 折叠作为哈希函数来检索 EFF 表索引。
为了从单个 CAN 标识符的优化过滤器中受益,CAN_SFF_MASK 或 CAN_EFF_MASK 必须与设置的 CAN_EFF_FLAG 和 CAN_RTR_FLAG 位一起设置到 can_filter.mask 中。can_filter.mask 中的设置的 CAN_EFF_FLAG 位清楚地表明订阅 SFF 或 EFF CAN ID 是否重要。例如,在上面的示例中
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK;
具有 CAN ID 0x123 的 SFF 帧和具有 0xXXXXX123 的 EFF 帧都可以通过。
要仅过滤 0x123 (SFF) 和 0x12345678 (EFF) CAN 标识符,必须以这种方式定义过滤器才能从优化过滤器中受益
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));
RAW Socket 选项 CAN_RAW_ERR_FILTER¶
如 网络问题通知 中所述,CAN 接口驱动程序可以生成所谓的错误消息帧,这些帧可以选择以与其他 CAN 帧相同的方式传递到用户应用程序。可能的错误分为不同的错误类别,可以使用适当的错误掩码进行过滤。要注册每个可能的错误条件,可以使用 CAN_ERR_MASK 作为错误掩码的值。错误掩码的值在 linux/can/error.h 中定义
can_err_mask_t err_mask = ( CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF );
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER,
&err_mask, sizeof(err_mask));
RAW Socket 选项 CAN_RAW_LOOPBACK¶
为了满足多用户需求,默认情况下启用本地环回(有关详细信息,请参见 发送帧的本地环回)。但是在某些嵌入式用例中(例如,当只有一个应用程序使用 CAN 总线时),可以禁用此环回功能(每个 socket 单独禁用)
int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
RAW socket 选项 CAN_RAW_RECV_OWN_MSGS¶
当启用本地环回时,所有发送的 CAN 帧都会环回到在给定接口上注册 CAN 帧的 CAN-ID 的打开的 CAN socket,以满足多用户需求。假设接收 CAN 帧的 socket 与发送 CAN 帧的 socket 相同是不需要的,因此默认情况下禁用。可以根据需要更改此默认行为
int recv_own_msgs = 1; /* 0 = disabled (default), 1 = enabled */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
&recv_own_msgs, sizeof(recv_own_msgs));
请注意,接收 socket 自己的 CAN 帧与接收其他 CAN 帧受到相同的过滤(参见 RAW socket 选项 CAN_RAW_FILTER)。
RAW Socket 选项 CAN_RAW_FD_FRAMES¶
可以通过新的 socket 选项 CAN_RAW_FD_FRAMES 在 CAN_RAW socket 中启用 CAN FD 支持,默认情况下禁用此选项。当 CAN_RAW socket 不支持新的 socket 选项时(例如,在旧内核上),切换 CAN_RAW_FD_FRAMES 选项会返回错误 -ENOPROTOOPT。
一旦启用了 CAN_RAW_FD_FRAMES,应用程序就可以发送 CAN 帧和 CAN FD 帧。OTOH,应用程序必须在从 socket 读取时处理 CAN 帧和 CAN FD 帧
CAN_RAW_FD_FRAMES enabled: CAN_MTU and CANFD_MTU are allowed
CAN_RAW_FD_FRAMES disabled: only CAN_MTU is allowed (default)
示例
[ remember: CANFD_MTU == sizeof(struct canfd_frame) ]
struct canfd_frame cfd;
nbytes = read(s, &cfd, CANFD_MTU);
if (nbytes == CANFD_MTU) {
printf("got CAN FD frame with length %d\n", cfd.len);
/* cfd.flags contains valid data */
} else if (nbytes == CAN_MTU) {
printf("got Classical CAN frame with length %d\n", cfd.len);
/* cfd.flags is undefined */
} else {
fprintf(stderr, "read: invalid CAN(FD) frame\n");
return 1;
}
/* the content can be handled independently from the received MTU size */
printf("can_id: %X data length: %d data: ", cfd.can_id, cfd.len);
for (i = 0; i < cfd.len; i++)
printf("%02X ", cfd.data[i]);
当仅读取大小为 CANFD_MTU 的套接字返回从套接字收到的 CAN_MTU 字节时,一个 Classical CAN 帧已被读取到提供的 CAN FD 结构中。请注意,canfd_frame.flags 数据字段未在 struct can_frame 中指定,因此它仅在 CANFD_MTU 大小的 CAN FD 帧中有效。
新 CAN 应用程序的实现提示
要构建一个 CAN FD 感知应用程序,请使用 struct canfd_frame 作为基于 CAN_RAW 的应用程序的基本 CAN 数据结构。当应用程序在较旧的 Linux 内核上执行并且切换 CAN_RAW_FD_FRAMES socket 选项返回错误时:没问题。您将获得 Classical CAN 帧或 CAN FD 帧,并且可以以相同的方式处理它们。
当发送到 CAN 设备时,请确保该设备能够通过检查设备最大传输单元是否为 CANFD_MTU 来处理 CAN FD 帧。CAN 设备 MTU 可以通过例如 SIOCGIFMTU ioctl() 系统调用检索。
RAW socket 选项 CAN_RAW_JOIN_FILTERS¶
CAN_RAW socket 可以设置多个 CAN 标识符特定过滤器,这会导致 af_can.c 过滤器处理中的多个过滤器。这些过滤器彼此独立,这会导致应用时进行逻辑 OR 过滤(参见 RAW socket 选项 CAN_RAW_FILTER)。
此 socket 选项以只有匹配所有给定 CAN 过滤器的 CAN 帧才能传递到用户空间的方式连接给定的 CAN 过滤器。因此,应用于过滤器的语义更改为逻辑 AND。
当过滤器集是其中设置了 CAN_INV_FILTER 标志的过滤器的组合,以便从传入流量中排除单个 CAN ID 或 CAN ID 范围时,这尤其有用。
广播管理器协议 Socket (SOCK_DGRAM)¶
广播管理器协议提供了一个基于命令的配置接口,用于过滤和发送(例如,循环)内核空间中的 CAN 消息。
接收过滤器可用于向下采样频繁消息;检测诸如消息内容更改、数据包长度更改之类的事件,并对接收到的消息进行超时监控。
可以在运行时创建和修改 CAN 帧或一系列 CAN 帧的周期性传输任务;可以更改消息内容和两个可能的传输间隔。
CAN_BCM socket 不用于像从 CAN_RAW socket 已知的那样使用 struct can_frame 发送单个 CAN 帧。而是定义了一个特殊的 BCM 配置消息。用于与广播管理器通信的基本 BCM 配置消息和可用操作在 linux/can/bcm.h 包含中定义。BCM 消息由消息头和命令 ('opcode') 以及零个或多个 CAN 帧组成。广播管理器以相同的形式将响应发送到用户空间
struct bcm_msg_head {
__u32 opcode; /* command */
__u32 flags; /* special flags */
__u32 count; /* run 'count' times with ival1 */
struct timeval ival1, ival2; /* count and subsequent interval */
canid_t can_id; /* unique can_id for task */
__u32 nframes; /* number of can_frames following */
struct can_frame frames[0];
};
对齐的有效负载“帧”使用在 RAW Socket 选项 CAN_RAW_FD_FRAMES 开头和 include/linux/can.h 包含中定义的相同基本 CAN 帧结构。来自用户空间的发送到广播管理器的所有消息都具有此结构。
请注意,在 socket 创建后,必须连接而不是绑定 CAN_BCM socket(没有错误检查的示例)
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
connect(s, (struct sockaddr *)&addr, sizeof(addr));
(..)
广播管理器 socket 能够并发处理任意数量的飞行中传输或接收过滤器。不同的 RX/TX 作业通过每个 BCM 消息中唯一的 can_id 来区分。但是,建议使用额外的 CAN_BCM socket 在多个 CAN 接口上进行通信。当广播管理器 socket 绑定到“任何”CAN 接口时(=> 接口索引设置为零),除非使用 sendto() 系统调用来覆盖“任何”CAN 接口索引,否则配置的接收过滤器将应用于任何 CAN 接口。当使用 recvfrom() 而不是 read() 来检索 BCM socket 消息时,原始 CAN 接口在 can_ifindex 中提供。
广播管理器操作¶
opcode 定义了广播管理器要执行的操作,或详细说明了广播管理器对几个事件的响应,包括用户请求。
传输操作(用户空间到广播管理器)
- TX_SETUP
创建(循环)传输任务。
- TX_DELETE
删除(循环)传输任务,仅需要 can_id。
- TX_READ
读取 can_id 的(循环)传输任务的属性。
- TX_SEND
发送一个 CAN 帧。
传输响应(广播管理器到用户空间)
- TX_STATUS
回复 TX_READ 请求(传输任务配置)。
- TX_EXPIRED
计数器完成以初始间隔 'ival1' 发送时的通知。需要在 TX_SETUP 时设置 TX_COUNTEVT 标志。
接收操作(用户空间到广播管理器)
- RX_SETUP
创建 RX 内容过滤器订阅。
- RX_DELETE
删除 RX 内容过滤器订阅,仅需要 can_id。
- RX_READ
读取 can_id 的 RX 内容过滤器订阅的属性。
接收响应(广播管理器到用户空间)
- RX_STATUS
回复 RX_READ 请求(过滤器任务配置)。
- RX_TIMEOUT
检测到循环消息不存在(计时器 ival1 已过期)。
- RX_CHANGED
具有更新的 CAN 帧的 BCM 消息(检测到内容更改)。在收到第一条消息或收到修订后的 CAN 消息时发送。
广播管理器消息标志¶
向广播管理器发送消息时,“flags”元素可能包含以下影响行为的标志定义
- SETTIMER
设置 ival1、ival2 和 count 的值
- STARTTIMER
使用 ival1、ival2 和 count 的实际值启动计时器。启动计时器同时会导致发出 CAN 帧。
- TX_COUNTEVT
当 count 过期时,创建消息 TX_EXPIRED
- TX_ANNOUNCE
进程对数据的更改会立即发出。
- TX_CP_CAN_ID
将消息标头中的 can_id 复制到 frames 中的每个后续帧。这旨在简化用法。对于 TX 任务,消息标头中的唯一 can_id 可能与后续 struct can_frame(s) 中存储的用于传输的 can_id 不同。
- RX_FILTER_ID
仅按 can_id 过滤,无需帧(nframes=0)。
- RX_CHECK_DLC
DLC 的更改会导致 RX_CHANGED。
- RX_NO_AUTOTIMER
防止自动启动超时监视器。
- RX_ANNOUNCE_RESUME
如果在 RX_SETUP 处传递并且发生了接收超时,则在(循环)接收重新启动时将生成 RX_CHANGED 消息。
- TX_RESET_MULTI_IDX
重置多帧传输的索引。
- RX_RTR_FRAME
发送 RTR 请求的回复(放置在 op->frames[0] 中)。
- CAN_FD_FRAME
bcm_msg_head 之后的 CAN 帧是 struct canfd_frame's
广播管理器传输计时器¶
周期性传输配置最多可以使用两个间隔计时器。在这种情况下,BCM 以间隔“ival1”发送一定数量的消息(“count”),然后继续以另一个给定间隔“ival2”发送。当只需要一个计时器时,“count”设置为零,并且仅使用“ival2”。当设置了 SET_TIMER 和 START_TIMER 标志时,计时器被激活。当仅设置 SET_TIMER 时,可以在运行时更改计时器值。
广播管理器消息序列传输¶
在循环 TX 任务配置的情况下,最多可以在序列中传输 256 个 CAN 帧。CAN 帧的数量在 BCM 消息头的“nframes”元素中提供。定义的 CAN 帧数量作为数组添加到 TX_SETUP BCM 配置消息
/* create a struct to set up a sequence of four CAN frames */
struct {
struct bcm_msg_head msg_head;
struct can_frame frame[4];
} mytxmsg;
(..)
mytxmsg.msg_head.nframes = 4;
(..)
write(s, &mytxmsg, sizeof(mytxmsg));
每次传输时,CAN 帧数组中的索引都会增加,并在索引溢出时设置为零。
广播管理器接收过滤器计时器¶
计时器值 ival1 或 ival2 可以在 RX_SETUP 处设置为非零值。当设置了 SET_TIMER 标志时,计时器被启用
- ival1
当在给定时间内未再次收到接收到的消息时,发送 RX_TIMEOUT。当在 RX_SETUP 处设置 START_TIMER 时,即使没有以前的 CAN 帧接收,也会直接激活超时检测。
- ival2
将接收到的消息速率降低到 ival2 的值。当 CAN 帧内的信号是无状态的时,这对于减少应用程序的消息很有用,因为 ival2 周期内的状态更改可能会丢失。
广播管理器多路复用消息接收过滤器¶
为了过滤多路复用消息序列中的内容更改,可以在 RX_SETUP 配置消息中传递一个包含多个 CAN 帧的数组。第一个 CAN 帧的数据字节包含相关位的掩码,这些位必须与后续 CAN 帧中接收到的 CAN 帧匹配。如果其中一个后续 CAN 帧与该帧数据标记中的位相匹配,则标记要与先前接收到的内容进行比较的相关内容。最多可以将 257 个 CAN 帧(多路复用过滤器位掩码 CAN 帧加上 256 个 CAN 过滤器)作为数组添加到 TX_SETUP BCM 配置消息
/* usually used to clear CAN frame data[] - beware of endian problems! */
#define U64_DATA(p) (*(unsigned long long*)(p)->data)
struct {
struct bcm_msg_head msg_head;
struct can_frame frame[5];
} msg;
msg.msg_head.opcode = RX_SETUP;
msg.msg_head.can_id = 0x42;
msg.msg_head.flags = 0;
msg.msg_head.nframes = 5;
U64_DATA(&msg.frame[0]) = 0xFF00000000000000ULL; /* MUX mask */
U64_DATA(&msg.frame[1]) = 0x01000000000000FFULL; /* data mask (MUX 0x01) */
U64_DATA(&msg.frame[2]) = 0x0200FFFF000000FFULL; /* data mask (MUX 0x02) */
U64_DATA(&msg.frame[3]) = 0x330000FFFFFF0003ULL; /* data mask (MUX 0x33) */
U64_DATA(&msg.frame[4]) = 0x4F07FC0FF0000000ULL; /* data mask (MUX 0x4F) */
write(s, &msg, sizeof(msg));
广播管理器 CAN FD 支持¶
CAN_BCM 的编程 API 取决于 struct can_frame,它直接作为数组在 bcm_msg_head 结构之后给出。为了遵循 CAN FD 帧的这个模式,bcm_msg_head 标志中的新标志“CAN_FD_FRAME”表示 bcm_msg_head 之后连接的 CAN 帧结构被定义为 struct canfd_frame
struct {
struct bcm_msg_head msg_head;
struct canfd_frame frame[5];
} msg;
msg.msg_head.opcode = RX_SETUP;
msg.msg_head.can_id = 0x42;
msg.msg_head.flags = CAN_FD_FRAME;
msg.msg_head.nframes = 5;
(..)
当使用 CAN FD 帧进行多路复用过滤时,MUX 掩码仍然预期在 struct canfd_frame 数据部分的第一个 64 位中。
连接的传输协议 (SOCK_SEQPACKET)¶
(待编写)
未连接的传输协议 (SOCK_DGRAM)¶
(待编写)
SocketCAN 核心模块¶
SocketCAN 核心模块实现了协议族 PF_CAN。CAN 协议模块在运行时由核心模块加载。核心模块为 CAN 协议模块提供了一个接口来订阅需要的 CAN ID(参见接收列表)。
can.ko 模块参数¶
stats_timer:为了计算 SocketCAN 核心统计信息(例如,当前/最大每秒帧数),默认情况下在 can.ko 模块启动时调用此 1 秒计时器。可以使用模块命令行上的 stattimer=0 禁用此计时器。
debug:(自 SocketCAN SVN r546 以来已删除)
procfs 内容¶
如接收列表中所述,SocketCAN 核心使用多个过滤器列表将接收到的 CAN 帧传递给 CAN 协议模块。可以在相应的接收列表中检查这些接收列表、它们的过滤器和过滤器匹配计数。所有条目都包含设备和协议模块标识符
foo@bar:~$ cat /proc/net/can/rcvlist_all
receive list 'rx_all':
(vcan3: no entry)
(vcan2: no entry)
(vcan1: no entry)
device can_id can_mask function userdata matches ident
vcan0 000 00000000 f88e6370 f6c6f400 0 raw
(any: no entry)
在此示例中,应用程序请求来自 vcan0 的任何 CAN 流量
rcvlist_all - list for unfiltered entries (no filter operations)
rcvlist_eff - list for single extended frame (EFF) entries
rcvlist_err - list for error message frames masks
rcvlist_fil - list for mask/value filters
rcvlist_inv - list for mask/value filters (inverse semantic)
rcvlist_sff - list for single standard frame (SFF) entries
/proc/net/can 中的其他 procfs 文件
stats - SocketCAN core statistics (rx/tx frames, match ratios, ...)
reset_stats - manual statistic reset
version - prints SocketCAN core and ABI version (removed in Linux 5.10)
编写自己的 CAN 协议模块¶
为了在协议族 PF_CAN 中实现新协议,必须在 include/linux/can.h 中定义一个新协议。可以通过包含 include/linux/can/core.h 来访问使用 SocketCAN 核心的原型和定义。除了注册 CAN 协议和 CAN 设备通知链的函数之外,还有一些函数可以订阅 CAN 接口接收到的 CAN 帧和发送 CAN 帧
can_rx_register - subscribe CAN frames from a specific interface
can_rx_unregister - unsubscribe CAN frames from a specific interface
can_send - transmit a CAN frame (optional with local loopback)
有关详细信息,请参见 net/can/af_can.c 中的 kerneldoc 文档或 net/can/raw.c 或 net/can/bcm.c 的源代码。
CAN 网络驱动程序¶
编写 CAN 网络设备驱动程序比编写 CAN 字符设备驱动程序容易得多。与其他已知的网络设备驱动程序类似,您主要需要处理
TX:将来自套接字缓冲区的 CAN 帧放入 CAN 控制器。
RX:将来自 CAN 控制器的 CAN 帧放入套接字缓冲区。
例如,请参见网络设备、内核和您!。下面描述了编写 CAN 网络设备驱动程序的差异
常规设置¶
dev->type = ARPHRD_CAN; /* the netdevice hardware type */
dev->flags = IFF_NOARP; /* CAN has no arp */
dev->mtu = CAN_MTU; /* sizeof(struct can_frame) -> Classical CAN interface */
or alternative, when the controller supports CAN with flexible data rate:
dev->mtu = CANFD_MTU; /* sizeof(struct canfd_frame) -> CAN FD interface */
struct can_frame 或 struct canfd_frame 是协议族 PF_CAN 中每个套接字缓冲区 (skbuff) 的有效负载。
发送帧的本地环回¶
如发送帧的本地环回中所述,CAN 网络设备驱动程序应支持类似于 tty 设备本地回显的本地环回功能。在这种情况下,必须设置驱动程序标志 IFF_ECHO 以防止 PF_CAN 核心本地回显(又名环回)作为后备解决方案发送的帧
dev->flags = (IFF_NOARP | IFF_ECHO);
CAN 控制器硬件过滤器¶
为了减少深度嵌入式系统上的中断负载,一些 CAN 控制器支持过滤 CAN ID 或 CAN ID 范围。这些硬件过滤器功能因控制器而异,并且必须在多用户网络方法中识别为不可行。使用非常特定于控制器的硬件过滤器在非常专用的用例中可能是有意义的,因为驱动程序级别的过滤器会影响多用户系统中的所有用户。PF_CAN 核心内部的高效过滤器集允许为每个套接字分别设置不同的多个过滤器。因此,硬件过滤器的使用属于“深度嵌入式系统上的手工调整”类别。作者正在运行 MPC603e @133MHz,带有来自 2002 年的四个 SJA1000 CAN 控制器,在高总线负载下没有任何问题...
可切换终端电阻¶
CAN 总线需要跨差分对的特定阻抗,通常由总线最远节点上的两个 120Ohm 电阻提供。一些 CAN 控制器支持激活/停用终端电阻以提供正确的阻抗。
查询可用的电阻
$ ip -details link show can0
...
termination 120 [ 0, 120 ]
激活终端电阻
$ ip link set dev can0 type can termination 120
停用终端电阻
$ ip link set dev can0 type can termination 0
要为 can-controller 启用终端电阻支持,请在控制器的 struct can-priv 中实现
termination_const
termination_const_cnt
do_set_termination
或使用来自 Documentation/devicetree/bindings/net/can/can-controller.yaml 的设备树条目添加 gpio 控制
虚拟 CAN 驱动程序 (vcan)¶
类似于网络环回设备,vcan 提供了一个虚拟本地 CAN 接口。CAN 上的完整限定地址包括
唯一的 CAN 标识符 (CAN ID)
传输此 CAN ID 的 CAN 总线(例如,can0)
因此,在常见用例中,需要多个虚拟 CAN 接口。
虚拟 CAN 接口允许传输和接收 CAN 帧,而无需真实的 CAN 控制器硬件。虚拟 CAN 网络设备通常命名为“vcanX”,如 vcan0 vcan1 vcan2 ... 当编译为模块时,虚拟 CAN 驱动程序模块称为 vcan.ko
自 Linux 内核版本 2.6.24 以来,vcan 驱动程序支持内核 netlink 接口来创建 vcan 网络设备。可以使用 ip(8) 工具管理 vcan 网络设备的创建和删除
- Create a virtual CAN network interface:
$ ip link add type vcan
- Create a virtual CAN network interface with a specific name 'vcan42':
$ ip link add dev vcan42 type vcan
- Remove a (virtual CAN) network interface 'vcan42':
$ ip link del vcan42
CAN 网络设备驱动程序接口¶
CAN 网络设备驱动程序接口提供了一个通用接口来设置、配置和监视 CAN 网络设备。然后,用户可以使用来自“IPROUTE2”实用程序套件的程序“ip”通过 netlink 接口配置 CAN 设备,例如设置位定时参数。以下章节简要介绍了如何使用它。此外,该接口使用通用的数据结构并导出了一组通用函数,所有真实的 CAN 网络设备驱动程序都应使用这些函数。请查看 SJA1000 或 MSCAN 驱动程序以了解如何使用它们。模块名称为 can-dev.ko。
Netlink 接口用于设置/获取设备属性¶
必须通过 netlink 接口配置 CAN 设备。支持的 netlink 消息类型在“include/linux/can/netlink.h”中定义并简要描述。IPROUTE2 实用程序套件的程序“ip”的 CAN 链接支持可用,可以如下所示使用
设置 CAN 设备属性
$ ip link set can0 type can help
Usage: ip link set DEVICE type can
[ bitrate BITRATE [ sample-point SAMPLE-POINT] ] |
[ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1
phase-seg2 PHASE-SEG2 [ sjw SJW ] ]
[ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] |
[ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1
dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ]
[ loopback { on | off } ]
[ listen-only { on | off } ]
[ triple-sampling { on | off } ]
[ one-shot { on | off } ]
[ berr-reporting { on | off } ]
[ fd { on | off } ]
[ fd-non-iso { on | off } ]
[ presume-ack { on | off } ]
[ cc-len8-dlc { on | off } ]
[ restart-ms TIME-MS ]
[ restart ]
Where: BITRATE := { 1..1000000 }
SAMPLE-POINT := { 0.000..0.999 }
TQ := { NUMBER }
PROP-SEG := { 1..8 }
PHASE-SEG1 := { 1..8 }
PHASE-SEG2 := { 1..8 }
SJW := { 1..4 }
RESTART-MS := { 0 | NUMBER }
显示 CAN 设备详细信息和统计信息
$ ip -details -statistics link show can0
2: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UP qlen 10
link/can
can <TRIPLE-SAMPLING> state ERROR-ACTIVE restart-ms 100
bitrate 125000 sample_point 0.875
tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1
sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
clock 8000000
re-started bus-errors arbit-lost error-warn error-pass bus-off
41 17457 0 41 42 41
RX: bytes packets errors dropped overrun mcast
140859 17608 17457 0 0 0
TX: bytes packets errors dropped carrier collsns
861 112 0 41 0 0
有关上述输出的更多信息
- “<TRIPLE-SAMPLING>”
显示所选 CAN 控制器模式的列表:LOOPBACK、LISTEN-ONLY 或 TRIPLE-SAMPLING。
- “state ERROR-ACTIVE”
CAN 控制器的当前状态:“ERROR-ACTIVE”、“ERROR-WARNING”、“ERROR-PASSIVE”、“BUS-OFF”或“STOPPED”
- “restart-ms 100”
自动重启延迟时间。如果设置为非零值,则在总线关闭的情况下,将在指定的延迟时间(以毫秒为单位)后自动触发 CAN 控制器的重启。默认情况下,它是关闭的。
- “bitrate 125000 sample-point 0.875”
显示真实的比特率(以比特/秒为单位)和范围为 0.000..0.999 的采样点。如果在内核中启用了比特率计算(CONFIG_CAN_CALC_BITTIMING=y),则可以通过设置“bitrate”参数来定义比特率。可以选择指定“sample-point”。默认情况下,它是 0.000,假设 CIA 推荐的采样点。
- “tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1”
以 tq 单位显示时间量子(以 ns 为单位)、传播段、相位缓冲段 1 和 2 以及同步跳转宽度。它们允许以硬件无关的格式定义 CAN 比特定时,如 Bosch CAN 2.0 规范(参见http://www.semiconductors.bosch.de/pdf/can2spec.pdf的第 8 章)中提出的那样。
- “sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000”
显示 CAN 控制器的比特定时常数,此处为“sja1000”。时间段 1 和 2 的最小值和最大值、同步跳转宽度(以 tq 单位)、比特率预分频器和 CAN 系统时钟频率(以 Hz 为单位)。这些常数可用于用户定义的(非标准)用户空间比特率计算算法。
- “re-started bus-errors arbit-lost error-warn error-pass bus-off”
显示重启、总线和仲裁丢失错误以及状态更改为错误警告、错误被动和总线关闭状态的次数。RX 溢出错误列在标准网络统计信息的“overrun”字段中。
设置 CAN 比特定时¶
CAN 比特定时参数始终可以采用硬件无关的格式定义,如 Bosch CAN 2.0 规范中所述,指定参数“tq”、“prop_seg”、“phase_seg1”、“phase_seg2”和“sjw”
$ ip link set canX type can tq 125 prop-seg 6 \
phase-seg1 7 phase-seg2 2 sjw 1
如果启用了内核选项 CONFIG_CAN_CALC_BITTIMING,如果使用参数“bitrate”指定了比特率,则将计算 CIA 推荐的 CAN 比特定时参数
$ ip link set canX type can bitrate 125000
请注意,这对于大多数具有标准比特率的常见 CAN 控制器来说都可以正常工作,但对于异构比特率或 CAN 系统时钟频率可能失败。禁用 CONFIG_CAN_CALC_BITTIMING 可以节省一些空间,并允许用户空间工具单独确定和设置比特定时参数。CAN 控制器特定的比特定时常数可用于此目的。它们通过以下命令列出
$ ip -details link show can0
...
sja1000: clock 8000000 tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1
启动和停止 CAN 网络设备¶
可以使用命令“ifconfig canX up/down”或“ip link set canX up/down”像往常一样启动或停止 CAN 网络设备。请注意,在启动真实 CAN 设备之前,必须为其实际 CAN 设备定义适当的比特定时参数,以避免容易出错的默认设置
$ ip link set canX up type can bitrate 125000
如果 CAN 总线上发生过多错误,设备可能会进入“bus-off”状态。然后不再接收或发送任何消息。可以通过将“restart-ms”设置为非零值来启用自动总线关闭恢复,例如
$ ip link set canX type can restart-ms 100
或者,应用程序可以通过监视 CAN 错误消息帧来识别“bus-off”情况,并在适当时使用以下命令进行重启
$ ip link set canX type can restart
请注意,重启也会创建一个 CAN 错误消息帧(另请参见网络问题通知)。
CAN FD(灵活数据速率)驱动程序支持¶
支持 CAN FD 的 CAN 控制器支持 CAN FD 帧的仲裁阶段和有效负载阶段的两种不同的比特率。因此,必须指定第二个比特定时才能启用 CAN FD 比特率。
此外,支持 CAN FD 的 CAN 控制器支持最多 64 字节的有效负载。用户空间应用程序和 Linux 网络层内部的 can_frame.len 和 canfd_frame.len 中此长度的表示形式是从 0 .. 64 开始的纯值,而不是 CAN“数据长度代码”。数据长度代码无论如何都是经典 CAN 帧中有效负载长度的 1:1 映射。有效负载长度到总线相关 DLC 映射仅在 CAN 驱动程序内部执行,最好使用辅助函数 can_fd_dlc2len() 和 can_fd_len2dlc()。
CAN netdevice 驱动程序功能可以通过网络设备的最大传输单元 (MTU) 来区分
MTU = 16 (CAN_MTU) => sizeof(struct can_frame) => Classical CAN device
MTU = 72 (CANFD_MTU) => sizeof(struct canfd_frame) => CAN FD capable device
例如,可以使用 SIOCGIFMTU ioctl() 系统调用检索 CAN 设备 MTU。注意:支持 CAN FD 的设备也可以处理和发送经典 CAN 帧。
配置支持 CAN FD 的 CAN 控制器时,必须设置额外的“data”比特率。CAN FD 帧的数据阶段的此比特率必须至少是为仲裁阶段配置的比特率。第二个比特率与第一个比特率类似指定,但“data”比特率的比特率设置关键字以“d”开头,例如 dbitrate、dsample-point、dsjw 或 dtq 和类似设置。当在配置过程中设置了数据比特率时,可以指定控制器选项“fd on”以在 CAN 控制器中启用 CAN FD 模式。此控制器选项还会将设备 MTU 切换为 72 (CANFD_MTU)。
在国际 CAN 会议 2012 上作为白皮书提出的第一个 CAN FD 规范需要改进,以确保数据完整性。因此,今天必须区分两种 CAN FD 实现
ISO 兼容:ISO 11898-1:2015 CAN FD 实现(默认)
非 ISO 兼容:遵循 2012 年白皮书的 CAN FD 实现
最后有三种类型的 CAN FD 控制器
ISO 兼容(固定)
非 ISO 兼容(固定,如 m_can.c 中的 M_CAN IP core v3.0.1)
ISO/非 ISO CAN FD 控制器(可切换,如 PEAK PCAN-USB FD)
当前 ISO/非 ISO 模式由 CAN 控制器驱动程序通过 netlink 宣布,并由“ip”工具(控制器选项 FD-NON-ISO)显示。只能为可切换 CAN FD 控制器设置“fd-non-iso {on|off}”来更改 ISO/非 ISO 模式。
示例:配置 500 kbit/s 仲裁比特率和 4 Mbit/s 数据比特率
$ ip link set can0 up type can bitrate 500000 sample-point 0.75 \
dbitrate 4000000 dsample-point 0.8 fd on
$ ip -details link show can0
5: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 72 qdisc pfifo_fast state UNKNOWN \
mode DEFAULT group default qlen 10
link/can promiscuity 0
can <FD> state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
bitrate 500000 sample-point 0.750
tq 50 prop-seg 14 phase-seg1 15 phase-seg2 10 sjw 1
pcan_usb_pro_fd: tseg1 1..64 tseg2 1..16 sjw 1..16 brp 1..1024 \
brp-inc 1
dbitrate 4000000 dsample-point 0.800
dtq 12 dprop-seg 7 dphase-seg1 8 dphase-seg2 4 dsjw 1
pcan_usb_pro_fd: dtseg1 1..16 dtseg2 1..8 dsjw 1..4 dbrp 1..1024 \
dbrp-inc 1
clock 80000000
在此可切换 CAN FD 适配器上添加“fd-non-iso on”时的示例
can <FD,FD-NON-ISO> state ERROR-ACTIVE (berr-counter tx 0 rx 0) restart-ms 0
支持的 CAN 硬件¶
请查看“drivers/net/can”中的“Kconfig”文件以获取支持的 CAN 硬件的实际列表。在 SocketCAN 项目网站上(参见SocketCAN 资源),可能还有其他驱动程序可用,也适用于旧版本的内核。
SocketCAN 资源¶
Linux CAN / SocketCAN 项目资源(项目站点/邮件列表)在 Linux 源代码树的 MAINTAINERS 文件中引用。搜索 CAN NETWORK [LAYERS|DRIVERS]。
致谢¶
Oliver Hartkopp (PF_CAN 核心、过滤器、驱动程序、bcm、SJA1000 驱动程序)
Urs Thuermann (PF_CAN 核心、内核集成、套接字接口、raw、vcan)
Jan Kizka (RT-SocketCAN 核心、套接字 API 协调)
Wolfgang Grandegger (RT-SocketCAN 核心 & 驱动程序、Raw Socket-API 审查、CAN 设备驱动程序接口、MSCAN 驱动程序)
Robert Schwebel (设计审查、PTXdist 集成)
Marc Kleine-Budde (设计审查、Kernel 2.6 清理、驱动程序)
Benedikt Spranger (审查)
Thomas Gleixner (LKML 审查、编码风格、发布提示)
Andrey Volkov (内核子树结构、ioctls、MSCAN 驱动程序)
Matthias Brukner (第一个 SJA1000 CAN netdevice 实现 Q2/2003)
Klaus Hitschler (PEAK 驱动程序集成)
Uwe Koppe (具有 PF_PACKET 方法的 CAN netdevices)
Michael Schulze (驱动程序层环回要求、RT CAN 驱动程序审查)
Pavel Pisa (比特率计算)
Sascha Hauer (SJA1000 平台驱动程序)
Sebastian Haas (SJA1000 EMS PCI 驱动程序)
Markus Plessing (SJA1000 EMS PCI 驱动程序)
Per Dalen (SJA1000 Kvaser PCI 驱动程序)
Sam Ravnborg (审查、编码风格、kbuild 帮助)