Linux 内核源码can相关配置项
目录
- 一、配置项解释(kernel源码/net/can/Makefile)
- 1. CONFIG_CAN
- 2. CONFIG_PROC_FS
- 3. CONFIG_CAN_RAW
- 4. CONFIG_CAN_BCM
- 5. CONFIG_CAN_GW
- 6. CONFIG_CAN_J1939
- 7. CONFIG_CAN_ISOTP
- 8. 总结
- 二、Linux can协议栈部分功能细究
- 1. CAN Gateway
- 1.1 原理及使用场景
- 1.2 使用方法
- 1.3 为什么需要 CAN Gateway
- 2. CAN Broadcast Manager (BCM)
- 2.1 功能
- 2.2 的实现文件
- 2.3 **BCM 的使用示例**
- 2.3.1 **创建 BCM 套接字**
- 2.3.2 **配置周期性发送**
- 2.3.3 **接收 BCM 消息**
- 2.4 **BCM 的调用流程**
- 二、can应用层编程
- 1. 基本的can数据收发
- 1.1 **包含头文件**
- 1.2 **定义 CAN 接口名称**
- 1.3 **创建 CAN 套接字并设置过滤规则**
- 1.4 **接收和发送 CAN 帧**
- 1.5 **主函数**
- 1.6 **编译和运行**
- 1.7 **解释**
- 2. 使用can fd进行数据收发
- 2.1 示例程序
- 2.2 编译和运行
- 2.3 解释
- 2.4 注意事项
- 3. can设置接收所有的错误帧到app层
- 三、内核源码中can相关文档
一、配置项解释(kernel源码/net/can/Makefile)
1. CONFIG_CAN
- 含义:启用通用CAN(Controller Area Network)支持。
- 作用:CAN是一种用于实时数据交换的串行通信协议,常用于汽车、工业自动化等领域。启用这个选项后,内核将提供基本的CAN网络支持。
- 相关文件:
can.o
:包含CAN核心模块的实现。af_can.o
:实现CAN地址族(Address Family)支持。proc.o
:如果配置中启用了CONFIG_PROC_FS
,则提供与CAN相关的/proc
文件系统支持。
2. CONFIG_PROC_FS
- 含义:启用
/proc
文件系统支持。 - 作用:
/proc
文件系统是一个虚拟文件系统,提供内核状态和统计信息的接口。启用这个选项后,可以在/proc
目录下看到与CAN相关的文件和目录。 - 相关文件:
proc.o
:实现与CAN相关的/proc
文件系统支持。
3. CONFIG_CAN_RAW
- 含义:启用CAN RAW socket支持。
- 作用:CAN RAW socket允许用户空间程序直接发送和接收CAN帧,而不需要经过更高级的协议处理。
- 相关文件:
can-raw.o
:包含CAN RAW socket模块的实现。raw.o
:实现CAN RAW socket的具体功能。
4. CONFIG_CAN_BCM
- 含义:启用CAN BCM(Bridge CAN Manager)支持。
- 作用:CAN BCM提供了一种机制,可以在多个CAN接口之间桥接CAN帧,或者在多个应用之间共享CAN帧。这对于复杂的CAN网络配置非常有用。
- 相关文件:
can-bcm.o
:包含CAN BCM模块的实现。bcm.o
:实现CAN BCM的具体功能。
5. CONFIG_CAN_GW
- 含义:启用CAN GW(Gateway)支持。
- 作用:CAN GW用于在不同的CAN网络之间进行路由和数据转发。这对于多网络拓扑的CAN系统非常有用。
- 相关文件:
can-gw.o
:包含CAN GW模块的实现。gw.o
:实现CAN GW的具体功能。
6. CONFIG_CAN_J1939
- 含义:启用CAN J1939协议支持。
- 作用:J1939是一种基于CAN的高级协议,广泛用于重型车辆和工业设备的通信。启用这个选项后,内核将提供对J1939协议的支持。
- 相关文件:
j1939/
:包含J1939协议相关模块的实现。
7. CONFIG_CAN_ISOTP
- 含义:启用CAN ISOTP(ISO Transport Protocol)支持。
- 作用:ISOTP是一种用于CAN网络的传输层协议,用于传输超过8字节的数据。这对于需要传输较大数据包的应用非常有用。
- 相关文件:
can-isotp.o
:包含CAN ISOTP模块的实现。isotp.o
:实现CAN ISOTP的具体功能。
8. 总结
这些配置项主要用于控制Linux内核中CAN相关模块的编译和功能启用。根据你的硬件和软件需求,你可以选择性地启用这些选项。以下是一些常见的使用场景:
- 基本CAN支持:启用
CONFIG_CAN
和CONFIG_CAN_RAW
,用于基本的CAN通信。 - 高级CAN功能:启用
CONFIG_CAN_BCM
和CONFIG_CAN_GW
,用于复杂的CAN网络拓扑。 - 特定协议支持:启用
CONFIG_CAN_J1939
和CONFIG_CAN_ISOTP
,用于特定的工业和汽车通信协议。
二、Linux can协议栈部分功能细究
1. CAN Gateway
1.1 原理及使用场景
CAN Gateway(GW)的功能并不是指在同一个物理总线上转发消息,而是指在不同的物理总线之间转发消息。这在多总线系统中非常有用,例如:
-
多总线系统:在复杂的系统中,可能会有多个CAN总线,每个总线负责不同的功能或不同的子系统。CAN Gateway 可以将一个总线上的消息转发到另一个总线,从而实现不同总线之间的通信。
-
隔离和路由:CAN Gateway 可以用于隔离不同的CAN网络,防止一个网络中的问题影响到另一个网络。它还可以根据特定的规则路由消息,只转发特定的消息到特定的总线。
-
协议转换:CAN Gateway 不仅限于CAN总线之间的转发,还可以用于不同协议之间的转换,例如CAN到以太网、CAN到LIN等。
CAN Gateway 的具体应用场景:
假设你有一个系统,包含两个CAN总线:can0
和 can1
,即一个soc拥有两路can。这两个总线分别连接不同的子系统,但有时需要在这两个子系统之间传递消息。这时,你可以使用CAN Gateway来实现这个功能。
1.2 使用方法
可使用 cangw
工具配置 CAN Gateway,下述命令表示将 can0
接口的消息转发到 can1
接口,并启用错误帧转发。
sudo cangw -A -s can0 -d can1 -e
也可以自己利用Linux API实现该功能,示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#define CAN_INTERFACE1 "can0"
#define CAN_INTERFACE2 "can1"
void setup_can_socket(int *sock, const char *ifname) {
struct sockaddr_can addr;
struct ifreq ifr;
*sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (*sock < 0) {
perror("Socket");
exit(EXIT_FAILURE);
}
strcpy(ifr.ifr_name, ifname);
ioctl(*sock, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(*sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Bind");
close(*sock);
exit(EXIT_FAILURE);
}
}
void forward_messages(int src_sock, int dst_sock) {
struct can_frame frame;
while (1) {
if (read(src_sock, &frame, sizeof(struct can_frame)) < 0) {
perror("Read");
continue;
}
if (write(dst_sock, &frame, sizeof(struct can_frame)) < 0) {
perror("Write");
continue;
}
}
}
int main() {
int can0_sock, can1_sock;
setup_can_socket(&can0_sock, CAN_INTERFACE1);
setup_can_socket(&can1_sock, CAN_INTERFACE2);
printf("CAN Gateway started: %s <-> %s\n", CAN_INTERFACE1, CAN_INTERFACE2);
// Start forwarding messages from can0 to can1
forward_messages(can0_sock, can1_sock);
close(can0_sock);
close(can1_sock);
return 0;
}
确保 CAN 接口已经启动并配置好。你可以使用 ip 命令来配置 CAN 接口,例如:
sudo ip link set can0 up type can bitrate 500000
sudo ip link set can1 up type can bitrate 500000
然后运行编译后的程序:
./can_gateway
你可以使用 candump 和 cansend 工具来测试 CAN 网关是否正常工作。例如,发送一个 CAN 消息到 can0 接口,并检查 can1 接口是否收到该消息:
# 发送消息到 can0
cansend can0 123#DEADBEEF
# 监听 can1 接口
candump can1
如果一切正常,你应该在 can1 接口中看到 123#DEADBEEF 消息。上述代码是一个非常基础的 CAN 网关实现。你可以根据需要扩展其功能,例如:
- 添加过滤规则,只转发特定 ID 的消息。
- 支持更多的 CAN 接口。 添加日志记录和错误处理。
- 提供配置文件或命令行参数来动态配置转发规则。
1.3 为什么需要 CAN Gateway
- 物理隔离:不同的CAN总线可以物理隔离,防止一个总线上的问题影响到另一个总线。
- 消息路由:可以根据特定的规则路由消息,只转发特定的消息到特定的总线。
- 协议转换:CAN Gateway 可以用于不同协议之间的转换,例如CAN到以太网、CAN到LIN等。
2. CAN Broadcast Manager (BCM)
2.1 功能
Linux 内核中的 CAN Broadcast Manager (BCM) 是一个用于 CAN 总线的高级协议,主要用于处理周期性和非周期性的数据帧(如周期性发送、过滤、应答等),BCM 是 CAN 协议栈的一部分。
BCM 的主要功能是管理 周期性的 CAN 帧 和 非周期性的 CAN 帧:
-
周期性帧:
- BCM 可以配置为定期发送相同的 CAN 数据帧,例如周期性地发送心跳包或传感器数据。
- 用户可以配置发送频率、帧内容以及发送次数。
-
非周期性帧:
- BCM 可以处理非周期性的 CAN 帧,例如配置过滤规则,接收特定的 CAN 帧并触发回调。
- BCM 支持向接收到的 CAN 帧发送应答帧。
BCM 支持删除周期性发送任务或过滤规则。BCM 提供了一个用户空间接口,允许应用程序通过 socket API 与 BCM 进行交互。
2.2 的实现文件
BCM 的实现代码位于 Linux 内核的以下文件中:
-
net/can/bcm.c
:- 这是 BCM 的核心实现文件,包含了周期性发送、过滤、应答等功能的实现。
- 定义了 BCM 的协议处理逻辑和用户空间接口。
-
include/uapi/linux/can/bcm.h
:- 定义了 BCM 的头文件,包含 BCM 相关的结构体和常量。
- 定义了 BCM 的头文件,包含 BCM 相关的结构体和常量。
2.3 BCM 的使用示例
2.3.1 创建 BCM 套接字
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/bcm.h>
int sock = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
if (sock < 0) {
perror("socket");
return -1;
}
2.3.2 配置周期性发送
struct bcm_msg_head msg_head;
struct can_frame frames[2];
// 设置 BCM 消息头
msg_head.opcode = TX_SETUP;
msg_head.flags = 0;
msg_head.count = 0; // 无限循环发送
msg_head.ival1.tv_sec = 1; // 间隔 1 秒
msg_head.ival1.tv_usec = 0;
msg_head.can_id = 0x123;
msg_head.nframes = 1;
// 设置 CAN 帧
frames[0].can_id = 0x123;
frames[0].can_dlc = 2;
frames[0].data[0] = 0x11;
frames[0].data[1] = 0x22;
// 发送配置
write(sock, &msg_head, sizeof(msg_head));
write(sock, frames, sizeof(frames));
2.3.3 接收 BCM 消息
struct can_frame recv_frame;
recv(sock, &recv_frame, sizeof(recv_frame), 0);
2.4 BCM 的调用流程
-
创建 BCM 套接字:
- 使用
socket(PF_CAN, SOCK_DGRAM, CAN_BCM)
创建 BCM 套接字。
- 使用
-
配置 BCM 任务:
- 使用
write
向 BCM 发送配置消息,如TX_SETUP
或RX_SETUP
。
- 使用
-
启动 BCM 任务:
- BCM 根据配置执行周期性发送或过滤接收。
-
接收 BCM 消息:
- 使用
recv
接收 BCM 返回的消息,如接收到的 CAN 帧或应答帧。
- 使用
-
删除 BCM 任务:
- 使用
TX_DELETE
或RX_DELETE
删除 BCM 任务。
- 使用
通过 BCM,用户可以轻松实现复杂的 CAN 总线通信需求,例如心跳包、传感器数据采集和应答机制。
二、can应用层编程
1. 基本的can数据收发
下面是一个完整的 Linux CAN 数据收发程序,其中包括设置过滤规则的功能。这个程序将创建一个 CAN 套接字,绑定到指定的 CAN 接口,并设置过滤规则来只接收特定的 CAN 帧。
1.1 包含头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/ioctl.h>
1.2 定义 CAN 接口名称
#define CAN_IFNAME "can0"
1.3 创建 CAN 套接字并设置过滤规则
int create_can_socket_with_filter(const char *ifname, canid_t id, canid_t mask) {
int sock;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_filter rfilter[1];
// 创建 CAN 套接字
sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (sock < 0) {
perror("socket");
return -1;
}
// 获取 CAN 接口索引
strcpy(ifr.ifr_name, ifname);
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl");
close(sock);
return -1;
}
// 绑定 CAN 套接字
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return -1;
}
// 设置过滤规则
rfilter[0].can_id = id;
rfilter[0].can_mask = mask;
setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
return sock;
}
1.4 接收和发送 CAN 帧
void receive_and_send_can_frame(int sock) {
struct can_frame frame;
ssize_t nbytes;
// 从 CAN 接口读取数据
nbytes = read(sock, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("read");
return;
}
// 打印接收到的 CAN 帧
printf("Received CAN frame: ID=0x%03X DLC=%d Data=", frame.can_id, frame.can_dlc);
for (int i = 0; i < frame.can_dlc; i++) {
printf("%02X ", frame.data[i]);
}
printf("\n");
// 将接收到的 CAN 帧重新发送回同一个 CAN 接口
if (write(sock, &frame, sizeof(struct can_frame)) != sizeof(struct can_frame)) {
perror("write");
}
}
1.5 主函数
在主函数中创建 CAN 套接字,设置过滤规则,并进入循环接收和发送 CAN 帧:
int main() {
int sock;
canid_t filter_id = 0x123; // 过滤的 CAN ID
canid_t filter_mask = 0x7FF; // 过滤掩码
// 创建 CAN 套接字并设置过滤规则
sock = create_can_socket_with_filter(CAN_IFNAME, filter_id, filter_mask);
if (sock < 0) {
fprintf(stderr, "Failed to create CAN socket\n");
return -1;
}
printf("CAN socket created and filter set. Waiting for frames...\n");
while (1) {
receive_and_send_can_frame(sock);
}
close(sock);
return 0;
}
1.6 编译和运行
保存上述代码到一个文件中,例如 can_example.c
,然后使用以下命令编译和运行:
gcc -o can_example can_example.c
./can_example
1.7 解释
- 创建 CAN 套接字:使用
socket()
函数创建一个 CAN 套接字。 - 绑定 CAN 接口:使用
ioctl()
和bind()
函数将套接字绑定到指定的 CAN 接口。 - 设置过滤规则:使用
setsockopt()
函数设置 CAN 帧的过滤规则。 - 接收和发送 CAN 帧:使用
read()
和write()
函数在 CAN 接口上接收和发送 CAN 帧。
2. 使用can fd进行数据收发
2.1 示例程序
以下是一个完整的 Linux CAN FD 数据收发程序,其中包括设置过滤规则的功能。这个程序将创建一个 CAN FD 套接字,绑定到指定的 CAN 接口,并设置过滤规则来只接收特定的 CAN 帧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <sys/ioctl.h>
#define CAN_IFNAME "can0"
int create_canfd_socket_with_filter(const char *ifname, canid_t id, canid_t mask) {
int sock;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_filter rfilter[1];
int enable_canfd = 1;
// 创建 CAN FD 套接字
sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (sock < 0) {
perror("socket");
return -1;
}
// 启用 CAN FD 支持
if (setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd)) < 0) {
perror("setsockopt CAN_RAW_FD_FRAMES");
close(sock);
return -1;
}
// 获取 CAN 接口索引
strcpy(ifr.ifr_name, ifname);
if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl");
close(sock);
return -1;
}
// 绑定 CAN FD 套接字
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sock);
return -1;
}
// 设置过滤规则
rfilter[0].can_id = id;
rfilter[0].can_mask = mask;
setsockopt(sock, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
return sock;
}
void receive_and_send_canfd_frame(int sock) {
struct canfd_frame frame;
ssize_t nbytes;
// 从 CAN FD 接口读取数据
nbytes = read(sock, &frame, sizeof(struct canfd_frame));
if (nbytes < 0) {
perror("read");
return;
}
// 打印接收到的 CAN FD 帧
printf("Received CAN FD frame: ID=0x%03X DLC=%d Data=", frame.can_id, frame.len);
for (int i = 0; i < frame.len; i++) {
printf("%02X ", frame.data[i]);
}
printf("\n");
// 将接收到的 CAN FD 帧重新发送回同一个 CAN 接口
if (write(sock, &frame, sizeof(struct canfd_frame)) != sizeof(struct canfd_frame)) {
perror("write");
}
}
int main() {
int sock;
canid_t filter_id = 0x123; // 过滤的 CAN ID
canid_t filter_mask = 0x7FF; // 过滤掩码
// 创建 CAN FD 套接字并设置过滤规则
sock = create_canfd_socket_with_filter(CAN_IFNAME, filter_id, filter_mask);
if (sock < 0) {
fprintf(stderr, "Failed to create CAN FD socket\n");
return -1;
}
printf("CAN FD socket created and filter set. Waiting for frames...\n");
while (1) {
receive_and_send_canfd_frame(sock);
}
close(sock);
return 0;
}
2.2 编译和运行
gcc -o canfd_example canfd_example.c
./canfd_example
2.3 解释
-
创建 CAN FD 套接字:
- 使用
socket(PF_CAN, SOCK_RAW, CAN_RAW)
创建一个 CAN 套接字。 - 通过
setsockopt
启用 CAN FD 支持。
- 使用
-
绑定 CAN 接口:
- 使用
ioctl
获取 CAN 接口的索引。 - 使用
bind
将套接字绑定到指定的 CAN 接口。
- 使用
-
设置过滤规则:
- 使用
setsockopt
设置 CAN 帧的过滤规则。
- 使用
-
接收和发送 CAN FD 帧:
- 使用
read
从 CAN FD 接口读取数据。 - 打印接收到的 CAN FD 帧。
- 使用
write
将接收到的 CAN FD 帧重新发送回同一个 CAN 接口。
- 使用
2.4 注意事项
- CAN FD 支持:确保你的硬件和驱动程序支持 CAN FD。
- DLC 和 Length:在 CAN FD 中,
len
字段表示实际数据长度,而dlc
字段表示数据长度代码(DLC)。 - 过滤规则:过滤规则可以根据需要进行调整,以匹配特定的 CAN ID 和掩码。
3. can设置接收所有的错误帧到app层
#include <linux/can.h>
#include <linux/can/error.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#define CAN_INTERFACE "can0"
// 打印错误帧的具体类型
void print_error_type(struct can_frame *frame)
{
uint32_t error_code = frame->data[0]; // 获取错误代码的第 0 字节
printf(" - Error Code: 0x%02X\n", error_code);
if (error_code & CAN_ERR_TX_TIMEOUT)
{
printf(" - TX Timeout Error (CAN_ERR_TX_TIMEOUT)\n");
}
else if (error_code & CAN_ERR_LOSTARB)
{
printf(" - Lost Arbitration Error (CAN_ERR_LOSTARB)\n");
}
else if (error_code & CAN_ERR_CRTL)
{
printf(" - Controller problems (CAN_ERR_CRTL)\n");
}
else if (error_code & CAN_ERR_PROT)
{
printf(" - Protocol Violation (CAN_ERR_PROT)\n");
}
else if (error_code & CAN_ERR_TRX)
{
printf(" - transceiver status (CAN_ERR_TRX)\n");
}
else if (error_code & CAN_ERR_ACK)
{
printf(" - ACK Error (CAN_ERR_ACK)\n");
}
else if (error_code & CAN_ERR_BUSOFF)
{
printf(" - Bus Off (CAN_ERR_BUSOFF)\n");
}
else if (error_code & CAN_ERR_BUSERROR)
{
printf(" - Bus Error (CAN_ERR_BUSERROR)\n");
}
else if (error_code & CAN_ERR_RESTARTED)
{
printf(" - Controller Restarted (CAN_ERR_RESTARTED)\n");
}
else
{
printf(" - Unknown Error Type\n");
}
}
int main()
{
int s = -1;
struct ifreq ifr;
struct sockaddr_can addr;
struct can_frame frame;
char *ifname = CAN_INTERFACE;
// 创建一个 CAN 原始套接字
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
{
perror("Socket creation failed");
return -1;
}
// 获取接口索引
strcpy(ifr.ifr_name, ifname);
ioctl(s, SIOCGIFINDEX, &ifr);
// 设置套接字地址
addr.can_ifindex = ifr.ifr_ifindex;
addr.can_family = AF_CAN;
// 绑定套接字到指定的 CAN 接口
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("Bind failed");
return -2;
}
// 设置 CAN 过滤规则,接收所有 CAN 错误帧
uint32_t err_mask = 0xFFFFFFFF; // 接收所有错误帧
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask));
printf("Listening for CAN error frames on %s...\n", ifname);
while (1)
{
int nbytes;
struct sockaddr_can sender;
// 接收数据
nbytes = recvfrom(s, &frame, sizeof(struct can_frame), 0,
(struct sockaddr *)&sender, NULL);
if (nbytes < 0)
{
perror("recvfrom");
continue;
}
// 检查接收到的帧是否为 CAN 错误帧
if (frame.can_id & CAN_ERR_FLAG)
{
printf("Received CAN error frame: ID=0x%03X, DLC=%d\n", frame.can_id,
frame.can_dlc);
print_error_type(&frame);
}
}
// 关闭套接字
close(s);
return 0;
}
三、内核源码中can相关文档
https://linuxkernel.org.cn/doc/html/latest/networking/can.html