SocketCAN - 控制器局域网¶
概述 / 什么是 SocketCAN¶
socketcan 软件包是 Linux 中 CAN 协议(控制器局域网)的实现。CAN 是一种网络技术,广泛应用于自动化、嵌入式设备和汽车领域。虽然之前已经有其他基于字符设备的 Linux CAN 实现,但 SocketCAN 使用 Berkeley 套接字 API、Linux 网络堆栈并将 CAN 设备驱动程序实现为网络接口。CAN 套接字 API 被设计得尽可能类似于 TCP/IP 协议,以便熟悉网络编程的程序员可以轻松学习如何使用 CAN 套接字。
动机 / 为什么使用套接字 API¶
在 SocketCAN 之前已经有 Linux 的 CAN 实现,因此出现了一个问题,为什么我们要启动另一个项目。大多数现有的实现都作为某些 CAN 硬件的设备驱动程序出现,它们基于字符设备,并且提供的功能相对较少。通常,只有一个特定于硬件的设备驱动程序,它提供一个字符设备接口来直接向/从控制器硬件发送和接收原始 CAN 帧。帧的排队和诸如 ISO-TP 之类的高级传输协议必须在用户空间应用程序中实现。此外,大多数字符设备实现一次只支持一个进程打开设备,类似于串行接口。更换 CAN 控制器需要使用另一个设备驱动程序,并且通常需要调整应用程序的很大一部分以适应新驱动程序的 API。
SocketCAN 的设计是为了克服所有这些限制。已经实现了一个新的协议族,它为用户空间应用程序提供了一个套接字接口,并建立在 Linux 网络层之上,从而可以使用所有提供的排队功能。CAN 控制器硬件的设备驱动程序将其自身注册到 Linux 网络层,作为网络设备,以便来自控制器的 CAN 帧可以传递到网络层,然后传递到 CAN 协议族模块,反之亦然。此外,协议族模块还为传输协议模块注册提供了一个 API,以便可以动态加载或卸载任意数量的传输协议。事实上,can 核心模块本身不提供任何协议,如果没有加载至少一个额外的协议模块,就无法使用。可以同时打开多个套接字,在不同的或相同的协议模块上,并且它们可以监听/发送不同或相同的 CAN ID 上的帧。多个套接字监听同一接口上的具有相同 CAN ID 的帧,所有这些套接字都会收到相同的匹配 CAN 帧。希望使用特定传输协议(例如 ISO-TP)进行通信的应用程序只需在打开套接字时选择该协议,然后就可以读取和写入应用程序数据字节流,而无需处理 CAN-ID、帧等。
通过字符设备也可以提供从用户空间可见的类似功能,但这会导致一些技术上不优雅的解决方案,原因如下
复杂的使用:应用程序不是将协议参数传递给 socket(2) 并使用 bind(2) 来选择 CAN 接口和 CAN ID,而是必须使用 ioctl(2) 来完成所有这些操作。
代码重复:字符设备无法使用 Linux 网络排队代码,因此所有这些代码都必须为 CAN 网络复制。
抽象:在大多数现有的字符设备实现中,CAN 控制器的特定于硬件的设备驱动程序直接为应用程序提供字符设备。这在 Unix 系统中对于字符设备和块设备来说至少是非常不寻常的。例如,您没有用于串行接口的某个 UART、计算机中的某个声卡芯片、SCSI 或 IDE 控制器的字符设备来访问您的硬盘驱动器或磁带机设备。相反,您有抽象层,一方面为应用程序提供统一的字符或块设备接口,另一方面为特定于硬件的设备驱动程序提供接口。这些抽象由诸如 tty 层、音频子系统或上述设备的 SCSI 和 IDE 子系统等子系统提供。
实现 CAN 设备驱动程序的最简单方法是将其作为没有这种(完整)抽象层的字符设备,大多数现有驱动程序都是这样做的。然而,正确的方法是添加这样一个层,其中包含所有功能,例如注册某些 CAN ID、支持多个打开的文件描述符和在它们之间(解)复用 CAN 帧、(复杂的)CAN 帧排队,并提供一个 API 用于设备驱动程序注册。然而,这样一来,使用 Linux 内核提供的网络框架就不会更困难,甚至可能更容易,而这正是 SocketCAN 所做的。
使用 Linux 内核的网络框架只是为 Linux 实现 CAN 的自然且最适当的方式。
SocketCAN 概念¶
如 动机 / 为什么使用套接字 API 中所述,SocketCAN 的主要目标是为用户空间应用程序提供一个建立在 Linux 网络层之上的套接字接口。与常用的 TCP/IP 和以太网网络相比,CAN 总线是一种仅广播的媒介,没有像以太网那样的 MAC 层寻址。CAN 标识符 (can_id) 用于 CAN 总线上的仲裁。因此,CAN ID 在总线上必须唯一选择。在设计 CAN-ECU 网络时,CAN ID 被映射为由特定的 ECU 发送。因此,CAN-ID 可以被最好地视为一种源地址。
接收列表¶
多个应用程序的网络透明访问导致一个问题,即不同的应用程序可能对来自同一 CAN 网络接口的相同 CAN ID 感兴趣。SocketCAN 核心模块(实现了协议族 CAN)为此提供了几个高效的接收列表。例如,如果用户空间应用程序打开一个 CAN 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)---------------
为了确保在示例 (2) 中应用程序 A 接收到的信息与在示例 (1) 中接收到的信息相同,需要在相应的节点上进行某种发送 CAN 帧的本地环回。
Linux 网络设备(默认情况下)只能处理媒体相关帧的传输和接收。由于 CAN 总线上的仲裁,低优先级 CAN-ID 的传输可能会因接收高优先级 CAN 帧而延迟。为了反映节点上正确的 [1] 流量,发送数据的环回必须在成功传输后立即执行。如果 CAN 网络接口由于某些原因无法执行环回,则 SocketCAN 核心可以作为后备解决方案执行此任务。有关详细信息,请参阅 发送帧的本地环回(推荐)。
默认情况下启用环回功能以反映 CAN 应用程序的标准网络行为。由于 RT-SocketCAN 组的一些请求,可以选择为每个单独的套接字禁用环回。请参阅 带有 can_filters 的 RAW 协议套接字 (SOCK_RAW) 中的 CAN RAW 套接字的 sockopts。
网络问题通知¶
CAN 总线的使用可能会导致物理和媒体访问控制层上的几个问题。检测和记录这些较低层的问题是 CAN 用户识别物理收发器层上的硬件问题以及由不同的 ECU 引起的仲裁问题和错误帧的关键要求。检测到的错误的发生对于诊断非常重要,并且必须与确切的时间戳一起记录。因此,CAN 接口驱动程序可以生成所谓的错误消息帧,这些错误消息帧可以可选地像其他 CAN 帧一样传递给用户应用程序。每当检测到物理层或 MAC 层上的错误时(例如,由 CAN 控制器检测到),驱动程序就会创建一个相应的错误消息帧。用户应用程序可以使用常用的 CAN 过滤器机制请求错误消息帧。在此过滤器定义中,可以选择(感兴趣的)错误类型。默认情况下禁用错误消息的接收。“include/uapi/linux/can/error.h”Linux 头文件中简要描述了 CAN 错误消息帧的格式。
如何使用 SocketCAN¶
像 TCP/IP 一样,您首先需要打开一个套接字以通过 CAN 网络进行通信。由于 SocketCAN 实现了一个新的协议族,您需要将 PF_CAN 作为第一个参数传递给 socket(2) 系统调用。目前,有两种 CAN 协议可供选择,即原始套接字协议和广播管理器 (BCM)。因此,要打开一个套接字,您可以这样写
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
和
s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
分别地。成功创建套接字后,通常会使用 bind(2) 系统调用将套接字绑定到 CAN 接口(由于寻址方式不同,这与 TCP/IP 不同 - 请参阅 SocketCAN 概念)。绑定 (CAN_RAW) 或连接 (CAN_BCM) 套接字后,您可以像往常一样从套接字读取 (read(2)) 和写入 (write(2)),或者在套接字上使用 send(2)、sendto(2)、sendmsg(2) 和相应的 recv* 操作。下面还介绍了 CAN 特定的套接字选项。
经典 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 从/传递到经典 CAN 网络设备,当 len 元素为 8 时,len8_dlc 元素可以包含值 9 .. 15(对于所有大于或等于 8 的 DLC 值,实际有效负载长度为 8)。
将(线性)有效负载数据[] 对齐到 64 位边界允许用户定义自己的结构和联合,以便轻松访问 CAN 有效负载。默认情况下,CAN 总线上没有给定的字节顺序。对 CAN_RAW 套接字执行 read(2) 系统调用会将 struct can_frame 传输到用户空间。
sockaddr_can 结构具有类似于 PF_PACKET 套接字的接口索引,该索引也绑定到特定接口。
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 套接字的示例,不进行错误检查)。
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));
(..)
要将套接字绑定到所有(!)CAN 接口,接口索引必须为 0(零)。在这种情况下,套接字会接收来自每个已启用 CAN 接口的 CAN 帧。要确定原始 CAN 接口,可以使用系统调用 recvfrom(2) 代替 read(2)。要在绑定到“任何”接口的套接字上发送,需要使用 sendto(2) 来指定传出接口。
从绑定的 CAN_RAW 套接字读取 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 接口的套接字上写入 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));
在从套接字读取消息后,可以使用 ioctl(2) 调用获取准确的时间戳。
struct timeval tv;
ioctl(s, SIOCGSTAMP, &tv);
时间戳的分辨率为一微秒,并且在接收到 CAN 帧时会自动设置。
关于 CAN FD(灵活数据速率)支持的备注
通常,CAN FD 的处理方式与前面描述的示例非常相似。新的支持 CAN FD 的 CAN 控制器支持 CAN FD 帧的仲裁阶段和有效负载阶段的两种不同的比特率,以及高达 64 字节的有效负载。这种扩展的有效负载长度破坏了所有严重依赖具有固定 8 字节有效负载的 CAN 帧(struct can_frame)的内核接口 (ABI),例如 CAN_RAW 套接字。因此,例如,CAN_RAW 套接字支持一个新的套接字选项 CAN_RAW_FD_FRAMES,该选项将套接字切换到允许同时处理 CAN FD 帧和经典 CAN 帧的模式(请参阅 RAW 套接字选项 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 套接字上使用系统调用 recvmsg(2) 时,msg->msg_flags 字段可能包含以下标志。
带有 can_filters 的 RAW 协议套接字 (SOCK_RAW)¶
使用 CAN_RAW 套接字与通常已知的对 CAN 字符设备的访问方式非常相似。为了满足多用户 SocketCAN 方法提供的新可能性,在 RAW 套接字绑定时设置了一些合理的默认值。
过滤器设置为只有一个过滤器接收所有内容。
套接字仅接收有效的数据帧(=> 没有错误消息帧)。
已启用发送的 CAN 帧的环回(请参阅 发送帧的本地环回)。
套接字不会接收自身发送的帧(在环回模式下)。
这些默认设置可以在绑定套接字之前或之后更改。要使用 CAN_RAW 套接字的套接字选项的引用定义,请包含
RAW 套接字选项 CAN_RAW_FILTER¶
可以通过使用 CAN_RAW_FILTER 套接字选项定义 0 .. n 个过滤器来控制使用 CAN_RAW 套接字接收 CAN 帧。
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 控制器硬件过滤器相反,用户可以为每个打开的套接字单独设置 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 套接字上接收 CAN 帧:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
将过滤器设置为零过滤器是相当过时的,因为不读取数据会导致原始套接字丢弃接收到的 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_filter.mask 中,同时设置 CAN_EFF_FLAG 和 CAN_RTR_FLAG 位。 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 套接字选项 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 套接字选项 CAN_RAW_LOOPBACK¶
为了满足多用户的需求,默认情况下启用本地环回(有关详细信息,请参阅 发送帧的本地环回)。但是在某些嵌入式用例中(例如,当只有一个应用程序使用 CAN 总线时),可以禁用此环回功能(每个套接字单独禁用):
int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
RAW 套接字选项 CAN_RAW_RECV_OWN_MSGS¶
启用本地环回后,所有发送的 CAN 帧都会环回到已在此给定接口上为 CAN 帧的 CAN ID 注册的打开的 CAN 套接字,以满足多用户的需求。假定不希望在发送 CAN 帧的同一套接字上接收 CAN 帧,因此默认情况下禁用此行为。可以根据需要更改此默认行为。
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));
请注意,接收套接字自己的 CAN 帧与接收其他 CAN 帧一样受到相同的过滤(请参阅 RAW 套接字选项 CAN_RAW_FILTER)。
RAW 套接字选项 CAN_RAW_FD_FRAMES¶
可以使用默认情况下处于关闭状态的新套接字选项 CAN_RAW_FD_FRAMES 在 CAN_RAW 套接字中启用 CAN FD 支持。当 CAN_RAW 套接字不支持新的套接字选项时(例如,在较旧的内核上),切换 CAN_RAW_FD_FRAMES 选项会返回错误 -ENOPROTOOPT。
启用 CAN_RAW_FD_FRAMES 后,应用程序可以同时发送 CAN 帧和 CAN FD 帧。另一方面,应用程序在从套接字读取时必须处理 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 帧并已将其读取到提供的 CAN FD 结构中,则仅返回 CAN_MTU 个字节。请注意,canfd_frame.flags 数据字段未在 struct can_frame 中指定,因此它仅在 CANFD_MTU 大小的 CAN FD 帧中有效。
新 CAN 应用程序的实现提示
要构建一个支持 CAN FD 的应用程序,请使用 struct canfd_frame 作为基于 CAN_RAW 的应用程序的基本 CAN 数据结构。当应用程序在较旧的 Linux 内核上执行,并且切换 CAN_RAW_FD_FRAMES 套接字选项返回错误时:没有问题。您将获得经典 CAN 帧或 CAN FD 帧,并且可以采用相同的方式处理它们。
当向 CAN 设备发送数据时,请确保设备能够处理 CAN FD 帧,方法是检查设备的最大传输单元是否为 CANFD_MTU。CAN 设备的 MTU 可以通过例如 SIOCGIFMTU ioctl() 系统调用来获取。
RAW 套接字选项 CAN_RAW_JOIN_FILTERS¶
CAN_RAW 套接字可以设置多个 CAN 标识符特定的过滤器,这会导致 af_can.c 过滤器处理中的多个过滤器。这些过滤器彼此独立,因此在应用时会形成逻辑“或”关系的过滤器(请参阅 RAW 套接字选项 CAN_RAW_FILTER)。
此套接字选项将给定的 CAN 过滤器连接起来,使得只有匹配所有给定 CAN 过滤器的 CAN 帧才会传递到用户空间。因此,应用过滤器的语义更改为逻辑“与”。
当过滤器集是过滤器组合时,这尤其有用,其中设置了 CAN_INV_FILTER 标志,以便从传入流量中排除单个 CAN ID 或 CAN ID 范围。
广播管理器协议套接字 (SOCK_DGRAM)¶
广播管理器协议提供了一个基于命令的配置接口,用于在内核空间中过滤和发送(例如,循环)CAN 消息。
接收过滤器可用于减少频繁消息的采样;检测诸如消息内容更改、数据包长度更改之类的事件,并对接收到的消息进行超时监控。
可以在运行时创建和修改 CAN 帧或 CAN 帧序列的周期性传输任务;消息内容和两个可能的传输间隔都可以更改。
BCM 套接字不适用于使用来自 CAN_RAW 套接字的 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 套接字选项 CAN_RAW_FD_FRAMES 开头和 include/linux/can.h 头文件中定义的基本 CAN 帧结构相同。从用户空间到广播管理器的所有消息都具有此结构。
请注意,必须在创建套接字后连接 CAN_BCM 套接字,而不是绑定(不含错误检查的示例)
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));
(..)
广播管理器套接字能够同时处理任意数量的飞行传输或接收过滤器。不同的 RX/TX 作业通过每个 BCM 消息中唯一的 can_id 来区分。但是,建议使用额外的 CAN_BCM 套接字在多个 CAN 接口上进行通信。当广播管理器套接字绑定到“任何” CAN 接口(=> 接口索引设置为零)时,配置的接收过滤器将应用于任何 CAN 接口,除非使用 sendto() 系统调用来覆盖“任何” CAN 接口索引。当使用 recvfrom() 而不是 read() 来检索 BCM 套接字消息时,原始 CAN 接口将在 can_ifindex 中提供。
广播管理器操作¶
操作码定义了广播管理器要执行的操作,或详细说明了广播管理器对几个事件(包括用户请求)的响应。
传输操作(用户空间到广播管理器)
- 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(s) 不同。
- 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 的
广播管理器传输计时器¶
周期性传输配置最多可以使用两个间隔计时器。在这种情况下,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 帧数组中的索引都会增加,并在索引溢出时设置为零。
广播管理器接收过滤器计时器¶
可以在 RX_SETUP 时将计时器值 ival1 或 ival2 设置为非零值。当设置 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 核心统计信息(例如,当前/最大每秒帧数),默认情况下,此 1 秒定时器在 can.ko 模块启动时被调用。可以通过在模块命令行中使用 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 核心内部的高效过滤器集允许为每个套接字单独设置不同的多个过滤器。因此,硬件过滤器的使用属于“深度嵌入式系统上的手工调整”类别。作者在 2002 年使用四个 SJA1000 CAN 控制器在重总线负载下运行 MPC603e @133MHz,没有任何问题...
可切换终端电阻¶
CAN 总线需要在差分对上具有特定的阻抗,通常由总线上最远节点的两个 120 欧姆电阻提供。一些 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 控制器启用终端电阻支持,请在控制器的 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 网络设备。vcan 网络设备的创建和移除可以使用 ip(8) 工具进行管理。
- 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 网络设备。然后,用户可以通过 netlink 接口使用 “IPROUTE2” 工具套件中的 “ip” 程序来配置 CAN 设备,例如设置位定时参数。以下章节简要介绍了如何使用它。此外,该接口使用通用数据结构并导出一组通用函数,所有真实的 CAN 网络设备驱动程序都应使用这些函数。请查看 SJA1000 或 MSCAN 驱动程序,以了解如何使用它们。该模块的名称为 can-dev.ko。
用于设置/获取设备属性的 Netlink 接口¶
必须通过 netlink 接口配置 CAN 设备。“include/linux/can/netlink.h” 中定义并简要描述了支持的 netlink 消息类型。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
有关以上输出的更多信息
- “<三重采样>”
显示所选 CAN 控制器模式的列表:环回、仅侦听或三重采样。
- “状态错误激活”
CAN 控制器的当前状态:“错误激活”、“错误警告”、“错误被动”、“总线关闭”或“已停止”。
- “restart-ms 100”
自动重启延迟时间。如果设置为非零值,则在总线关闭的情况下,将在指定的毫秒延迟时间后自动触发 CAN 控制器的重启。默认情况下,它是关闭的。
- “比特率 125000 采样点 0.875”
显示实际比特率(位/秒)和采样点(范围为 0.000..0.999)。如果在内核中启用了比特定时参数的计算 (CONFIG_CAN_CALC_BITTIMING=y),则可以通过设置 “比特率” 参数来定义比特定时。也可以选择指定 “采样点”。默认情况下为 0.000,假设采用 CIA 推荐的采样点。
- “tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1”
以纳秒为单位显示时间量子、传播段、相位缓冲段 1 和 2 以及同步跳跃宽度(以 tq 为单位)。它们允许以 Bosch CAN 2.0 规范提出的硬件无关格式定义 CAN 比特定时(请参阅 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 时钟 8000000”
显示 CAN 控制器的比特定时常量,此处为 “sja1000”。时间段 1 和 2 的最小值和最大值、同步跳跃宽度(以 tq 为单位)、比特率预分频器和 CAN 系统时钟频率(以 Hz 为单位)。这些常量可用于用户空间中用户定义的(非标准)比特定时计算算法。
- “已重启 总线错误 仲裁丢失 错误警告 错误被动 总线关闭”
显示重启次数、总线和仲裁丢失错误以及状态更改为错误警告、错误被动和总线关闭状态的次数。RX 溢出错误在标准网络统计信息的 “溢出” 字段中列出。
设置 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,如果使用参数 “比特率” 指定了比特率,则将计算 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 网络设备¶
CAN 网络设备通常使用命令 “ifconfig canX up/down” 或 “ip link set canX up/down” 启动或停止。请注意,您必须在启动真实 CAN 设备之前为其定义正确的比特定时参数,以避免容易出错的默认设置。
$ ip link set canX up type can bitrate 125000
如果在 CAN 总线上发生过多错误,设备可能会进入 “总线关闭” 状态。然后,将不再接收或发送任何消息。可以通过将 “restart-ms” 设置为非零值来启用自动总线关闭恢复,例如:
$ ip link set canX type can restart-ms 100
或者,应用程序可以通过监视 CAN 错误消息帧来实现 “总线关闭” 状态,并在适当的时候使用命令进行重启:
$ 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 网络设备驱动程序的功能可以通过网络设备的最大传输单元 (MTU) 来区分
MTU = 16 (CAN_MTU) => sizeof(struct can_frame) => Classical CAN device
MTU = 72 (CANFD_MTU) => sizeof(struct canfd_frame) => CAN FD capable device
CAN 设备的 MTU 可以通过例如 SIOCGIFMTU ioctl() 系统调用来检索。注意:支持 CAN FD 的设备也可以处理和发送经典 CAN 帧。
配置支持 CAN FD 的 CAN 控制器时,必须设置额外的“数据”比特率。 CAN FD 帧的数据阶段的此比特率必须至少为为仲裁阶段配置的比特率。第二个比特率的指定方式与第一个比特率类似,但是“数据”比特率的比特率设置关键字以“d”开头,例如 dbitrate、dsample-point、dsjw 或 dtq 以及类似的设置。在配置过程中设置数据比特率时,可以指定控制器选项“fd on”以启用 CAN 控制器中的 CAN FD 模式。此控制器选项还将设备 MTU 切换到 72 (CANFD_MTU)。
2012 年国际 CAN 会议上提出的第一个 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 核 v3.0.1)
ISO/非 ISO CAN FD 控制器(可切换,如 PEAK PCAN-USB FD)
当前的 ISO/非 ISO 模式由 CAN 控制器驱动程序通过 netlink 公布,并通过 “ip” 工具(控制器选项 FD-NON-ISO)显示。 ISO/非 ISO 模式只能通过为可切换的 CAN FD 控制器设置“fd-non-iso {on|off}”来更改。
示例:配置 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 套接字 API 审查、CAN 设备驱动程序接口、MSCAN 驱动程序)
Robert Schwebel (设计审查、PTXdist 集成)
Marc Kleine-Budde (设计审查、内核 2.6 清理、驱动程序)
Benedikt Spranger (审查)
Thomas Gleixner (LKML 审查、编码风格、发布提示)
Andrey Volkov (内核子树结构、ioctl、MSCAN 驱动程序)
Matthias Brukner (2003 年第二季度首次 SJA1000 CAN 网络设备实现)
Klaus Hitschler (PEAK 驱动程序集成)
Uwe Koppe (采用 PF_PACKET 方法的 CAN 网络设备)
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 帮助)