2023年8月3日发(作者:)
Linux内核SocketCAN中⽂⽂档
⾃⼰在年假中空闲之余翻译的内核中Socket CAN的⽂档,原⽂地址在:但是这篇⽂档没有涉及⼴播管理协议套接字 (SOCK_DGRAM) 的内容。另外⼀篇⽐较好的Socket CAN的英⽂⽂档是(详细介绍了⼴播管理协议套接字):Low Level CAN Framework Application Programmers Interface⾃⼰暂时没时间翻译这篇⽂章了,有空再说吧。
⾃⼰英⽂⽔平有限,希望⾼⼈指正。================================================================================
这篇⽂章主要针对can协议簇(aka socket can)这篇⽂章包含以下内容:===============1 概述--什么是Socket CAN?
2 动机--为什么使⽤socket API接⼝?
3 Socket CAN详解 3.1 接收队列 3.2 发送帧的本地回环 3.3 ⽹络安全相关 3.4 ⽹络故障监测
4 如何使⽤Socket CAN 4.1 使⽤can_filter的原始套接字 (RAW socket) 4.1.1 原始套接字选项 CAN_RAW_FILTER 4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER 4.1.3 原始套接字选项 CAN_RAW_LOOPBACK 4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS 4.2 ⼴播管理协议套接字 (SOCK_DGRAM) 4.3 ⾯向连接的传输协议 (SOCK_SEQPACKET) 4.4 ⽆连接的传输协议 (SOCK_DGRAM)
5 Socket CAN核⼼模块 5.1 模块的参数 5.2 procfs接⼝ 5.3 写⼀个⾃⼰的CAN协议模块
6 CAN⽹络驱动 6.1 常见设置 6.2 发送帧的本地回环 6.3 CAN控制器的硬件过滤 6.4 虚拟的CAN驱动 (vcan) 6.5 CAN⽹络设备驱动接⼝ 6.5.1 Netlink接⼝--设置/获取设备属性 6.5.2 设置CAN的⽐特_时序 6.5.3 启动和停⽌CAN⽹络设备 6.6 ⽀持Socket CAN的硬件
7 学习Socket CAN的相关资源
8 贡献者名单
==============现在开始===================1. 概述--什么是Socket CAN?================== socketcan⼦系统是在Linux下CAN协议(Controller Area Network)实现的⼀种实现⽅法。 CAN是⼀种在世界范围内⼴泛⽤于⾃动控制、嵌⼊式设备和汽车领域的⽹络技术。Linux下最早使⽤CAN的⽅法是基于字符设备来实现的,与之不同的是Socket CAN使⽤伯克利的socket接⼝和linux⽹络协议栈,这种⽅法使得can设备驱动可以通过⽹络接⼝来调⽤。Socket CAN的接⼝被设计的尽量接近TCP/IP的协议,让那些熟悉⽹络编程的程序员能够⽐较容易的学习和使⽤。
2. 动机--为什么使⽤socket API接⼝?=======================
在Socket CAN之前Linux中已经有了⼀些CAN的实现⽅法,那为什么还要启动Socket CAN这个项⽬呢?⼤多数已经存在的实现⽅法仅仅作为某个具体硬件的设备驱动,它们往往基于字符设备并且提供的功能很少。那些⽅案通常是由⼀个针对具体硬件的设备驱动提供的字符设备接⼝来实现原始can帧的发送和接收,并且直接和控制器硬件打交道。帧队列和ISO-TP这样的⾼层协议必须在⽤户空间来实现。就像串⼝设备接⼝⼀样,⼤多数基于字符设备的实现在同⼀时刻仅仅⽀持⼀个进程的访问。如果更换了CAN控制器,那么同时也要更换另⼀个设备驱动,并且需要⼤多数应⽤程序重新调整以适应新驱动的API。
Socket CAN被设计⽤来克服以上种种不⾜。这种新的协议族实现了⽤户空间的socket接⼝,它构建于Linux⽹络层之上,因此可以直接使⽤已有的队列功能。CAN控制器的设备驱动将⾃⼰作为⼀个⽹络设备注册进Linux的⽹络层,CAN控制器收到的CAN帧可以传输给⾼层的⽹络协议和CAN协议族,反之,发送的帧也会通过⾼层给CAN控制器。传输协议模块可以使⽤协议族提供的接⼝注册⾃⼰,所以可以动态的加载和卸载多个传输协议。事实上,CAN核⼼模块不提供任何协议,也不能在没有加载其它协议的情况下单独使⽤。同⼀时间可以在相同或者不同的协议上打开多个套接字,可以在相同或者不同的CAN ID上同时监听和发送(listen/send)。⼏个同时监听具有相同ID帧的套接字可以在匹配的帧到来后接收到相同的内容。如果⼀个应⽤程序希望使⽤⼀个特殊的协议(⽐如ISO-TP)进⾏通信,只要在打开套接字的时候选择那个协议就可以了,接下来就可以读取和写⼊应⽤数据流了,根本⽆需关⼼CAN-ID和帧的结构等信息。
使⽤字符设备也可以让⽤户空间获得类似的便利,但是这中解决⽅案在技术上不够优雅,原因如下:
*复杂的操作。使⽤Socket CAN,只需要向socket(2)函数传递协议参数并使⽤bind(2)选择CAN接⼝和CAN ID,基于字符设备实现这样的功能却要使⽤ioctl(2)来完成所有的操作。
*⽆法代码复⽤。字符设备⽆法使⽤Linux的⽹络队列代码,所以必须为CAN ⽹络重写这部分功能。
*缺乏抽象性。在⼤多数已经实现的字符设备⽅案中,硬件相关的CAN控制器设备驱动直接提供应⽤程序需要的字符设备。在Unix系统中,⽆论对于字符设备还是块设备,这都是不常见的。⽐如,你不会为某个串⼝、电脑上的某个声卡、访问磁带和硬盘的SCSI/IDE控制器直接创建⼀个字符设备。相反,你会将向应⽤程序提供统⼀字符设备/块设备接⼝的功能交给⼀个抽象层来做,你⾃⼰仅仅提供硬件相关的设备驱动接⼝。这种抽象是通过⼦系统来完成的,⽐如tty层⼦系统、声卡⼦系统和SCSI/IDE⼦系统。
实现CAN设备驱动最简单的办法就是直接提供⼀个字符设备⽽不使⽤(或不完全使⽤)抽象层,⼤多数已经存在的驱动也是这么做的。但是正确的⽅法却要增加抽象层以⽀持各种功能,如注册⼀个特定的CAN-ID,⽀持多次打开的操作和这些操作之间的CAN帧复⽤,⽀持CAN帧复杂的队列功能,还要提供注册设备驱动的API。然⽽使⽤Linux内核提供的⽹络框架将会⼤⼤简化,这就是Socket CAN要做的。
在Linux中实现CAN功能最⾃然和合适的⽅式就是使⽤内核的⽹络框架。
3. Socket CAN详解============
就像第⼆章所讲的那样,使⽤Socket CAN的主要⽬的就是为⽤户空间的应⽤程序提供基于Linux⽹络层的套接字接⼝。与⼴为⼈知的TCP/IP协议以及以太⽹不同,CAN总线没有类似以太⽹的MAC层地址,只能⽤于⼴播。CAN ID仅仅⽤来进⾏总线的仲裁。因此CAN ID在总线上必须是唯⼀的。当设计⼀个CAN-ECU(Electronic Control Unit 电⼦控制单元)⽹络的时候,CAN-ID可以映射到具体的ECU。因此CAN-ID可以当作发送源的地址来使⽤。
3.1 接收队列---------------
允许多个应⽤程序同时访问⽹络导致了新的问题出现,那就是不同的应⽤程序可能会在同⼀个CAN⽹络接⼝上对具有相同CAN-ID的帧感兴趣。Socket CAN的核⼼部分实现了Socket CAN的协议族,通过⾼效的接收队列解决了这个问题。⽐如⼀个⽤户空间的程序打开了⼀个原始CAN套接字,原始协议模块将向CAN套接字的核⼼模块申请⽤户空间需要的⼀系列CAN-ID。Socket CAN的核⼼向CAN协议模块提供预约和解约CAN-ID的接⼝--can_rx_(un)register(),⽆论这个CAN-ID是针对⼀个具体的CAN接⼝还是所有已知的CAN接⼝(参考第5章)。为了优化CPU的运⾏效率,每个设备都对应⼀个接收队列,这样⽐较容易实现各种报⽂过滤规则。3.2 发送帧的本地回环-----------------在其它种类的⽹络中,在相同或者不同⽹络节点上的应⽤程序都可以相互交换数据。 ___ ___ ___ _______ ___ | _ | | _ | | _ | | _ _ | | _ | ||A|| ||B|| ||C|| ||A| |B|| ||C|| |___| |___| |___| |_______| |___| | | | | | -----------------(1)- CAN bus -(2)---------------
请看上图的两个例⼦。为了保证应⽤程序A在两个例⼦中能够接收到同样的例⼦(例2中A和B在同⼀个CAN设备上),发送出去的CAN帧需要能够在本地回环。Linux下的⽹络设备仅仅处理物理媒介上帧的发送和接受。总线仲裁机制下,⾼优先级的帧会将低优先级的帧延后。为了正确反映实际的通信活动,回环必须在正确传输成功之后进⾏。如果CAN⽹络的硬件不⽀持回环功能,⼀种低效的⽅案是使⽤Socket CAN核⼼部分来实现软件回环。具体的情况请参考6.2⼩节。CAN⽹络的回环功能是默认开启的。由于RT-SocketCAN 的特殊需求,每个套接字的回环功能可以被独⽴关闭。CAN原始套接字的控制选项请参考4.1⼩节。*当你在同⼀个节点上运⾏CAN分析命令“candump”或者“cansniffer”的时候就会发现回环功能真的很有⽤。3.3 ⽹络安全相关--------------------CAN⽹络是⼀种现场总线,仅仅⽀持没有路由和安全控制的⼴播传输。⼤部分应⽤程序都需要要直接处理原始CAN帧,所以和其它类型的⽹络⼀样,CAN⽹络对所有的⽤户(⽽不仅仅是root⽤户)访问没有任何限制。由于当前CAN_RAW和CAN_BCM的实现仅仅⽀持对CAN接⼝的读写,所以允许所有的⽤户访问CAN并不影响其它类型⽹络的安全性。为了使能⾮root⽤户对CAN_RAW和CAN_BCM协议套接字的访问,必须在编译内核的时候选上Kconfig的CAN_RAW_USER/CAN_BCM_USER选项。
3.4 ⽹络故障监测--------------------
使⽤CAN总线的时候,可能会遇到物理和mac(media access control)层的问题。为了⽅便⽤户分析物理收发器的硬件错误、总线仲裁错误和不同的ECU( Electrical Conversion Unit,电⽓转换装置)引起的错误,对于底层(物理和mac层)的监测和记录是⾄关重要的。拥有精确时间戳的错误监测对于诊断错误是⾮常重要的。基于以上原因,CAN接⼝的驱动可以可以选择性的产⽣所谓的错误帧,这些帧以和其它的CAN帧⼀样的⽅式传递给⽤户程序。当⼀个物理层或者MAC层的错误被(CAN控制器)检测到之后,驱动创建⼀个相应的错误帧。错误帧可以被应⽤程序通过CAN的过滤机制请求得到。过滤机制允许选择需要的错误帧的类型。默认情况下,接收错误帧的功能是禁⽌的。CAN错误帧的详细格式定义在linux头⽂件中:include/linux/can/error.h。
4. 如何使⽤Socket CAN===============
就像TCP/IP协议⼀样,在使⽤CAN⽹络之前你⾸先需要打开⼀个套接字。CAN的套接字使⽤到了⼀个新的协议族,所以在调⽤socket(2)这个系统函数的时候需要将PF_CAN作为第⼀个参数。当前有两个CAN的协议可以选择,⼀个是原始套接字协议( raw socket protocol),另⼀个是⼴播管理协议BCM(broadcast manager)。你可以这样来打开⼀个套接字:
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);或者 s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
在成功创建⼀个套接字之后,你通常需要使⽤bind(2)函数将套接字绑定在某个CAN接⼝上(这和TCP/IP使⽤不同的IP地址不同,参见第3章)。在绑定 (CAN_RAW)或连接(CAN_BCM)套接字之后,你可以在套接字上使⽤read(2)/write(2),也可以使⽤send(2)/sendto(2)/sendmsg(2)和对应的recv*操作。当然也会有CAN特有的套接字选项,下⾯将会说明。
基本的CAN帧结构体和套接字地址结构体定义在include/linux/can.h:
/** 扩展格式识别符由 29 位组成。其格式包含两个部分:11 位基本 ID、18 位扩展 ID。* Controller Area Network Identifier structure** bit 0-28 : CAN识别符 (11/29 bit)* bit 29 : 错误帧标志 (0 = data frame, 1 = error frame)* bit 30 : 远程发送请求标志 (1 = rtr frame)* bit 31 :帧格式标志 (0 = standard 11 bit, 1 = extended 29 bit)*/typedef __u32 canid_t;
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ __u8 can_dlc; /* 数据长度: 0 .. 8 */ __u8 data[8] __attribute__((aligned(8)));
};
结构体的有效数据在data[]数组中,它的字节对齐是64bit的,所以⽤户可以⽐较⽅便的在data[]中传输⾃⼰定义的结构体和共⽤体。CAN总线中没有默认的字节序。在CAN_RAW套接字上调⽤read(2),返回给⽤户空间的数据是⼀个struct can_frame结构体。
就像PF_PACKET套接字⼀样,sockaddr_can结构体也有接⼝的索引,这个索引绑定了特定接⼝:
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; /* 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(_name, "can0" ); ioctl(s, SIOCGIFINDEX, &ifr);
_family = AF_CAN; _ifindex = _ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
(..)
为了将套接字和所有的CAN接⼝绑定,接⼝索引必须是0。这样套接字便可以从所有使能的CAN接⼝接收CAN帧。recvfrom(2)可以指定从哪个接⼝接收。在⼀个已经和所有CAN接⼝绑定的套接字上,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 framen"); return 1; }
/* do something with the received CAN frame */
写CAN帧也是类似的,需要⽤到write (2)函数:
nbytes = write(s, &frame, sizeof(struct can_frame));
如果套接字跟所有的CAN接⼝都绑定了(_index = 0),推荐使⽤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 */ _ifindex = _ifindex; ioctl(s, SIOCGIFNAME, &ifr); printf("Received a CAN frame from interface %s", _name);
对于绑定了所有接⼝的套接字,向某个端⼝发送数据必须指定接⼝的详细信息:
strcpy(_name, "can0"); ioctl(s, SIOCGIFINDEX, &ifr); _ifindex = _ifindex; _family = AF_CAN;
nbytes = sendto(s, &frame, sizeof(struct can_frame), 0, (struct sockaddr*)&addr, sizeof(addr));
4.1 使⽤can_filter的原始套接字 (RAW socket)----------------------------------------------------
CAN_RAW套接字的⽤法和CAN字符设备的⽤法是类似的。为了使⽤CAN套接字的新特性,在绑定原始套接字的时候将会默认开启以下特性:
- filter将会接收所有的数据- 套接字仅仅接收有效的数据帧(=> no error frames)- 发送帧的回环功能被开启(参见 3.2节)- (回环模式下)套接字不接收它⾃⼰发送的帧
这些特性的设置可以在绑定之前和之后修改。为了使⽤CAN_RAW套接字相关的选项,必须包含
4.1.1 原始套接字选项 CAN_RAW_FILTERCAN_RAW套接字的接收可以使⽤CAN_RAW_FILTER套接字选项指定的多个过滤规则(过滤器)来过滤。
过滤规则(过滤器)的定义在 include/linux/can.h中:
struct can_filter { canid_t can_id; canid_t can_mask; };
过滤规则的匹配:
/*#define CAN_INV_FILTER 0x20000000U /* to be set in can__id */#define CAN_ERR_FLAG 0x20000000U /* error frame */*/这和⼤家所熟知的CAN控制器硬件过滤⾮常相似。可以使⽤ CAN_INV_FILTER这个宏将can_filter结构体的成员can_id中的⽐特位反转。和CAN控制器的硬件过滤形成鲜明对⽐的是,⽤户可以为每⼀个打开的套接字设置多个独⽴的过滤规则(过滤器):
/* /* valid bits in CAN ID for frame formats */#define CAN_SFF_MASK 0x000007FFU /* 标准帧格式 (SFF) */#define CAN_EFF_MASK 0x1FFFFFFFU /* 扩展帧格式 (EFF) */#define CAN_ERR_MASK 0x1FFFFFFFU /* 忽略EFF, RTR, ERR标志 */ */
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套接字上禁⽤接收过滤规则,可以这样:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
在⼀些极端情况下不需要读取数据,可以把过滤规则清零(所有成员设为0),这样原始套接字就会忽略接收到的CAN帧。在这种仅仅发送数据(不读取)的应⽤中可以在内核中省略接收队列,以此减少CPU的负载(虽然只能减少⼀点点)。
4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER正如3.4节所说,CAN接⼝驱动可以选择性的产⽣错误帧,错误帧和正常帧以相同的⽅式传给应⽤程序。可能产⽣的错误被分为不同的种类,使⽤适当的错误掩码可以过滤它们。为了注册所有可能的错误情况,CAN_ERR_MASK(0x1FFFFFFFU)这个宏可以⽤来作为错误掩码。这个错误掩码定义在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));
4.1.3 原始套接字选项 CAN_RAW_LOOPBACK为了满⾜众多应⽤程序的需要,本地回环功能默认是开启的(详细情况参考3.2节)。但是在⼀些嵌⼊式应⽤场景中(⽐如只有⼀个⽤户在使⽤CAN总线),回环功能可以被关闭(各个套接字之间是独⽴的):
int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS在本地回环功能开启的情况下,所有的发送帧都会被回环到在相应CAN接⼝上注册了同样CAN-ID(和发送帧的相同)的套接字上。发送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));
(yll:下⾯三⼩节没有内容)4.2 ⼴播管理协议套接字 (SOCK_DGRAM)-----------------------------------------------
4.3 ⾯向连接的传输协议 (SOCK_SEQPACKET)----------------------------------------------------
4.4 ⽆连接的传输协议 (SOCK_DGRAM)---------------------------------------------
5. Socket CAN核⼼模块==============
CAN套接字的核⼼模块实现了PF_CAN协议族。CAN协议模块在核⼼模块运⾏后加载。CAN核⼼模块为协议模块提供了申请需要的ID的接⼝(参考3.1⼩节)。
5.1 模块的参数-------------------------
- stats_timer: 为了计算CAN套接字的统计信息(⽐如最近⼀秒的帧数和每秒最⼤的帧数),调⽤⼀个定时间隔为1秒的定时器,默认情况下这个定时器是开启的。这个定时器也可以在模块参数中传⼊stattimer=0来禁⽌。
- debug : (removed since SocketCAN SVN r546) 5.2 procfs接⼝-----------------
就像3.1节描述的那样,CAN套接字核⼼借助于⼀些带有过滤规则的队列向CAN协议模块传递接收到的CAN帧。可以在procfs中查看这些接收队列的的过滤规则和匹配规则的次数。所有的条⽬都包了设备名和协议模块标识:
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) (yll补充:function是⼀个函数指针,userdata是⼀个void *指针,所以这两个值打印出来没有太⼤意义)
在这个例⼦中,这个应⽤程序接收所有vcan0上传输的数据.
rcvlist_all - 没有过滤规则的队列 rcvlist_eff - 扩展帧(EFF)的队列 rcvlist_err - 错误帧队列 rcvlist_fil - 通过过滤规则的队列 rcvlist_inv - 未通过过滤规则的队列 rcvlist_sff - 标准帧的队列
在/proc/net/can中还有另外⼀些⽂件:
stats - CAN套接字核⼼的统计信息(接收/发送的帧数,匹配率等) reset_stats - 复位统计信息 version - 打印CAN套接字核⼼的版本和ABI的版本
5.3 写⼀个⾃⼰的CAN协议模块-----------------------------------
要在PF_CAN中增加⼀个新的协议,必须在include/linux/can.h中为新的协议增加相应的定义。包含include/linux/can.h这个⽂件便可以使⽤增加的原型和定义。内核除了提供了注册CAN协议和CAN设备的通知列表的功能,也提供了在⼀个特定CAN接⼝上注册感兴趣的CAN帧或者发送CAN帧的功能。
can_rx_register - 在⼀个特定接⼝上注册希望接收到的CAN帧的信息 (yll:这个函数的定义在内核的net/can/af_can.c中) can_rx_unregister - 注销上⾯的申请
can_send - 发送CAN帧(可以选择是否开启本地回环)
详细的信息请参考内核中的源码net/can/af_can.c、net/can/raw.c、net/can/bcm.c。
6. CAN⽹络驱动==========编写⼀个CAN⽹络设备驱动要⽐写⼀个CAN字符设备驱动要容易的多。和编写其它⽹络设备驱动类似,你只要处理以下事宜:
- TX :将套接字缓冲区的CAN帧发送到CAN控制器- RX :从CAN控制器的CAN帧读取到套接字缓冲区
Documentation/networking/中是⽹络设备驱动的例⼦。下⾯将会介绍CAN⽹络设备驱动的⼀些独有特性。
6.1 常见设置---------------
dev->type = ARPHRD_CAN; /* the netdevice hardware type */ dev->flags = IFF_NOARP; /* CAN has no arp */ dev->mtu = sizeof(struct can_frame);
结构体can_frame是PF_CAN协议族套接字缓冲区的数组载体。
6.2 发送帧的本地回环-------------------------
如3.2⼩节所述,CAN⽹络设备驱动应该⽀持类似TTY设备回显功能的本地回环功能。如果驱动⽀持这个功能,则要设备IFF_ECHO标志来防⽌PF_CAN核⼼回显发送帧(⼜称回环)。 dev->flags = (IFF_NOARP | IFF_ECHO);
6.3 CAN控制器的硬件过滤------------------------------
为了减⼩⼀些嵌⼊式系统的中断负载,⼀些CAN控制器⽀持多个CAN-ID或者多个CAN-ID区间的过滤功能。硬件过滤功能在不同的控制器之间差异很⼤,并且不能同时满⾜多个⽤户的不同过滤需求。在单⽤户应⽤中使⽤控制器的硬件过滤或许还有意义,但是在⼀个多⽤户系统中驱动层的过滤将会影响所有⽤户。PF_CAN核⼼内置的过滤规则集合允许对每个套接字独⽴的设置多个过滤规则。因此使⽤硬件过滤属于嵌⼊式系统中“⼿动调整”的范畴。从2002年开始笔者⼀直使⽤拥有四路SJA1000 CAN控制器的MPC603e @133MHz,总线负载虽然很⾼,但是到⽬前为⽌还没有什么问题。
6.4 虚拟的CAN驱动 (vcan)------------------------------
和⽹络回环设备⼀样,vcan提供⼀个虚拟的本地CAN接⼝。CAN中⼀个有效的地址包括:
- ⼀个有效的CAN标识符(CAN ID)- 这个CAN标识符将要发往的总线(⽐如 can0).
所以在⼀般的应⽤场景中往往需要多个vcan接⼝。
vcan接⼝允许在没有控制器硬件的情况下进⾏发送和接收。vcan⽹络设备的命名⼀般采⽤‘vcanX’,⽐如can1 vcan2等。当编译为单独的模块的时候,vcan驱动的模块名为。
vcan驱动从linux2.6.24开始⽀持netlink接⼝,使得创建vcan⽹络设备变的可能。可以使⽤ip(8)命令⼯具管理vcan⽹络设备的创建和移除:
- 创建⼀个vcan⽹络接⼝: $ ip link add type vcan
- 使⽤给定的名字 'vcan42'创建 ⼀个vcan⽹络接⼝: $ ip link add dev vcan42 type vcan
- 移除vcan⽹络接⼝'vcan42': $ ip link del vcan42
6.5 CAN⽹络设备驱动接⼝------------------------------
CAN⽹络设备驱动提供了进⾏安装、配置和监控CAN⽹络设备的接⼝。可以使⽤IPROUTE2⼯具集中的“ip”命令通过netlink接⼝配置CAN设备,⽐如设置波特率。本章剩余的部分将会简介如何使⽤这⼀⼯具。另外这些接⼝使⽤⼀些通⽤的数据结构并且提供了⼀系列常⽤的功能,这些功能都是CAN⽹络设备驱动需要⽤到的。请参考SJA1000或者MSCAN的驱动去了解如何使⽤它们。模块的名字是。6.5.1 Netlink接⼝--设置/获取设备属性CAN设备必须使⽤netlink来配置。在"include/linux/can/netlink.h"有对netlink消息类型的定义和简短描述。IPROUTE2⼯具集中的“ip”命令可以使⽤CAN的netlink⽀持,下⾯是使⽤⽰例:
- 设置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 ] ] [ loopback { on | off } ] [ listen-only { on | off } ] [ triple-sampling { 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:
link/can can
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
下⾯是上⾯⼀些名词的解释:
"
"state ERROR-ACTIVE"CAN控制器的当前状态:"ERROR-ACTIVE", "ERROR-WARNING", "ERROR-PASSIVE", "BUS-OFF" or "STOPPED"
"restart-ms 100"⾃动重启的延时时间。如果设为⾮零值, 在总线关闭的情况下,在设定的数量毫秒后CAN控制器被⾃动触发。这个功能默认是关闭的。
"bitrate 125000 sample_point 0.875"使⽤bits/sec作为单位显⽰位时间并显⽰0.000~0.999的采样点位置。如果内核中使能了统计位时间的功能(CONFIG_CAN_CALC_BITTIMING=y),位时间可以使⽤"bitrate"参数来设置。可选的"sample-point"也是可以配置的。默认使⽤0.000这个推荐值。
"tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1"以ns为单位显⽰时间份额(tq-time quanta)、传播段(prop-seg : propagation segment)、相位缓冲段1和2(phase-seg:phasebuffer),以tq为单位显⽰同步跳转宽度(sjw:synchronisation jump width)。这些变量允许定义与硬件⽆关的位时序,这也是Bosch CAN2.0 spec所推荐的(参考第⼋章/pdf/)。
"sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000"显⽰CAN控制器的⽐特时序常量,这⾥的例⼦以sja1000为例。时间段(tseg -time segment)1和2的最⼩和最⼤值,以tq为单位的同步跳转宽度,⽐特速率的预分频器(brp--pre-scaler)和CAN系统时钟(以HZ为单位)。这些常量可以被⽤户空间的⽐特时序统计算法所使⽤。
"re-started bus-errors arbit-lost error-warn error-pass bus-off"显⽰重启的次数、总线和仲裁丢失错误,错误主动(error-warning)、错误被动(error-passive)、和总线关闭的状态变化。接收的过载错误在 统计信息的"overrun"域下⾯列出。
6.5.2 设置CAN的⽐特时序CAN⽐特时序参数可以使⽤硬件⽆关的定义⽅法。这些参数是: "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"被设置了,CAN的这些参数将会⽣效:
$ ip link set canX type can bitrate 125000
请注意,这条命令在⼤部分使⽤标准波特率的CAN控制器上⼯作良好,但是使⽤⼀些少见的波特率值(如115000)和时钟频率值将会失败。禁⽤内核的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
6.5.3 启动和停⽌CAN⽹络设备 ⼀个CAN⽹络设备可以使⽤"ifconfig canX up/down" 或者 "ip link set canX up/down"来开启和关闭。为了避免错误的默认值,必须在启动CAN设备之前设置它的⽐特时序参数:
$ ip link set canX up type can bitrate 125000
如果总线上出现太多的错误设备可能进⼊总线关闭状态(也就是从总线脱离)。进⼊总线关闭状态之后设备不会再发送和接收信息。给"restart-ms"设置⼀个⾮零值可以开启总线关闭⾃动恢复的功能(也就是进⼊总线关闭状态后重新开启),下⾯是⼀个⽰例:
$ ip link set canX type can restart-ms 100
应⽤程序可以通过监测CAN的错误帧意识到已经进⼊总线关闭状态,并且可以使⽤以下命令重启:
$ ip link set canX type can restart
注意,重启也会⽣成⼀个CAN错误帧(参见3.4节)。
6.6 ⽀持Socket CAN的硬件------------------------------
"drivers/net/can"中的“Kconfig”⽂件中可以查看到所有⽀持的硬件列表。在CAN套接字项⽬⽹站上(参见第7章)有更多驱动何以获得,当然也有对早些时期版本内核的⽀持 。
7. 学习Socket CAN的相关资源===================
你可在BerliOS OSS项⽬⽹站的CAN套接字页⾯中发现更多资源,⽐如⽤户空间⼯具、对旧版内核的⽀持、更多的驱动、邮件列表等:
如果你有任何问题或者发现了BUG,不要迟疑,⽴马发送邮件到CAN套接字的⽤户邮件列表。但是在发送之前请⾸先搜索邮件记录中是否已经有了相同的问题。
8. 贡献者名单=========
Oliver Hartkopp (PF_CAN core, filters, drivers, bcm, SJA1000 driver) Urs Thuermann (PF_CAN core, kernel integration, socket interfaces,raw, vcan) Jan Kizka (RT-SocketCAN core, Socket-API reconciliation) Wolfgang Grandegger (RT-SocketCAN core & drivers, Raw Socket-API reviews, CAN device driver interface, MSCAN driver) RobertSchwebel (design reviews, PTXdist integration) Marc Kleine-Budde (design reviews, Kernel 2.6 cleanups, drivers) Benedikt Spranger (reviews) Thomas Gleixner (LKML reviews, coding style, posting hints) Andrey Volkov (kernel subtree structure, ioctls, MSCAN driver) Matthias Brukner (first SJA1000 CAN netdevice implementation Q2/2003)Klaus Hitschler (PEAK driver integration) Uwe Koppe (CAN netdevices with PF_PACKET approach) Michael Schulze (driver layer loopback requirement, RT CAN drivers review) Pavel Pisa (Bit-timing calculation) Sascha Hauer (SJA1000 platform driver) Sebastian Haas (SJA1000 EMS PCI driver) Markus Plessing (SJA1000 EMS PCI driver) Per Dalen (SJA1000 Kvaser PCI driver) Sam Ravnborg (reviews, coding style, kbuild help)
2023年8月3日发(作者:)
Linux内核SocketCAN中⽂⽂档
⾃⼰在年假中空闲之余翻译的内核中Socket CAN的⽂档,原⽂地址在:但是这篇⽂档没有涉及⼴播管理协议套接字 (SOCK_DGRAM) 的内容。另外⼀篇⽐较好的Socket CAN的英⽂⽂档是(详细介绍了⼴播管理协议套接字):Low Level CAN Framework Application Programmers Interface⾃⼰暂时没时间翻译这篇⽂章了,有空再说吧。
⾃⼰英⽂⽔平有限,希望⾼⼈指正。================================================================================
这篇⽂章主要针对can协议簇(aka socket can)这篇⽂章包含以下内容:===============1 概述--什么是Socket CAN?
2 动机--为什么使⽤socket API接⼝?
3 Socket CAN详解 3.1 接收队列 3.2 发送帧的本地回环 3.3 ⽹络安全相关 3.4 ⽹络故障监测
4 如何使⽤Socket CAN 4.1 使⽤can_filter的原始套接字 (RAW socket) 4.1.1 原始套接字选项 CAN_RAW_FILTER 4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER 4.1.3 原始套接字选项 CAN_RAW_LOOPBACK 4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS 4.2 ⼴播管理协议套接字 (SOCK_DGRAM) 4.3 ⾯向连接的传输协议 (SOCK_SEQPACKET) 4.4 ⽆连接的传输协议 (SOCK_DGRAM)
5 Socket CAN核⼼模块 5.1 模块的参数 5.2 procfs接⼝ 5.3 写⼀个⾃⼰的CAN协议模块
6 CAN⽹络驱动 6.1 常见设置 6.2 发送帧的本地回环 6.3 CAN控制器的硬件过滤 6.4 虚拟的CAN驱动 (vcan) 6.5 CAN⽹络设备驱动接⼝ 6.5.1 Netlink接⼝--设置/获取设备属性 6.5.2 设置CAN的⽐特_时序 6.5.3 启动和停⽌CAN⽹络设备 6.6 ⽀持Socket CAN的硬件
7 学习Socket CAN的相关资源
8 贡献者名单
==============现在开始===================1. 概述--什么是Socket CAN?================== socketcan⼦系统是在Linux下CAN协议(Controller Area Network)实现的⼀种实现⽅法。 CAN是⼀种在世界范围内⼴泛⽤于⾃动控制、嵌⼊式设备和汽车领域的⽹络技术。Linux下最早使⽤CAN的⽅法是基于字符设备来实现的,与之不同的是Socket CAN使⽤伯克利的socket接⼝和linux⽹络协议栈,这种⽅法使得can设备驱动可以通过⽹络接⼝来调⽤。Socket CAN的接⼝被设计的尽量接近TCP/IP的协议,让那些熟悉⽹络编程的程序员能够⽐较容易的学习和使⽤。
2. 动机--为什么使⽤socket API接⼝?=======================
在Socket CAN之前Linux中已经有了⼀些CAN的实现⽅法,那为什么还要启动Socket CAN这个项⽬呢?⼤多数已经存在的实现⽅法仅仅作为某个具体硬件的设备驱动,它们往往基于字符设备并且提供的功能很少。那些⽅案通常是由⼀个针对具体硬件的设备驱动提供的字符设备接⼝来实现原始can帧的发送和接收,并且直接和控制器硬件打交道。帧队列和ISO-TP这样的⾼层协议必须在⽤户空间来实现。就像串⼝设备接⼝⼀样,⼤多数基于字符设备的实现在同⼀时刻仅仅⽀持⼀个进程的访问。如果更换了CAN控制器,那么同时也要更换另⼀个设备驱动,并且需要⼤多数应⽤程序重新调整以适应新驱动的API。
Socket CAN被设计⽤来克服以上种种不⾜。这种新的协议族实现了⽤户空间的socket接⼝,它构建于Linux⽹络层之上,因此可以直接使⽤已有的队列功能。CAN控制器的设备驱动将⾃⼰作为⼀个⽹络设备注册进Linux的⽹络层,CAN控制器收到的CAN帧可以传输给⾼层的⽹络协议和CAN协议族,反之,发送的帧也会通过⾼层给CAN控制器。传输协议模块可以使⽤协议族提供的接⼝注册⾃⼰,所以可以动态的加载和卸载多个传输协议。事实上,CAN核⼼模块不提供任何协议,也不能在没有加载其它协议的情况下单独使⽤。同⼀时间可以在相同或者不同的协议上打开多个套接字,可以在相同或者不同的CAN ID上同时监听和发送(listen/send)。⼏个同时监听具有相同ID帧的套接字可以在匹配的帧到来后接收到相同的内容。如果⼀个应⽤程序希望使⽤⼀个特殊的协议(⽐如ISO-TP)进⾏通信,只要在打开套接字的时候选择那个协议就可以了,接下来就可以读取和写⼊应⽤数据流了,根本⽆需关⼼CAN-ID和帧的结构等信息。
使⽤字符设备也可以让⽤户空间获得类似的便利,但是这中解决⽅案在技术上不够优雅,原因如下:
*复杂的操作。使⽤Socket CAN,只需要向socket(2)函数传递协议参数并使⽤bind(2)选择CAN接⼝和CAN ID,基于字符设备实现这样的功能却要使⽤ioctl(2)来完成所有的操作。
*⽆法代码复⽤。字符设备⽆法使⽤Linux的⽹络队列代码,所以必须为CAN ⽹络重写这部分功能。
*缺乏抽象性。在⼤多数已经实现的字符设备⽅案中,硬件相关的CAN控制器设备驱动直接提供应⽤程序需要的字符设备。在Unix系统中,⽆论对于字符设备还是块设备,这都是不常见的。⽐如,你不会为某个串⼝、电脑上的某个声卡、访问磁带和硬盘的SCSI/IDE控制器直接创建⼀个字符设备。相反,你会将向应⽤程序提供统⼀字符设备/块设备接⼝的功能交给⼀个抽象层来做,你⾃⼰仅仅提供硬件相关的设备驱动接⼝。这种抽象是通过⼦系统来完成的,⽐如tty层⼦系统、声卡⼦系统和SCSI/IDE⼦系统。
实现CAN设备驱动最简单的办法就是直接提供⼀个字符设备⽽不使⽤(或不完全使⽤)抽象层,⼤多数已经存在的驱动也是这么做的。但是正确的⽅法却要增加抽象层以⽀持各种功能,如注册⼀个特定的CAN-ID,⽀持多次打开的操作和这些操作之间的CAN帧复⽤,⽀持CAN帧复杂的队列功能,还要提供注册设备驱动的API。然⽽使⽤Linux内核提供的⽹络框架将会⼤⼤简化,这就是Socket CAN要做的。
在Linux中实现CAN功能最⾃然和合适的⽅式就是使⽤内核的⽹络框架。
3. Socket CAN详解============
就像第⼆章所讲的那样,使⽤Socket CAN的主要⽬的就是为⽤户空间的应⽤程序提供基于Linux⽹络层的套接字接⼝。与⼴为⼈知的TCP/IP协议以及以太⽹不同,CAN总线没有类似以太⽹的MAC层地址,只能⽤于⼴播。CAN ID仅仅⽤来进⾏总线的仲裁。因此CAN ID在总线上必须是唯⼀的。当设计⼀个CAN-ECU(Electronic Control Unit 电⼦控制单元)⽹络的时候,CAN-ID可以映射到具体的ECU。因此CAN-ID可以当作发送源的地址来使⽤。
3.1 接收队列---------------
允许多个应⽤程序同时访问⽹络导致了新的问题出现,那就是不同的应⽤程序可能会在同⼀个CAN⽹络接⼝上对具有相同CAN-ID的帧感兴趣。Socket CAN的核⼼部分实现了Socket CAN的协议族,通过⾼效的接收队列解决了这个问题。⽐如⼀个⽤户空间的程序打开了⼀个原始CAN套接字,原始协议模块将向CAN套接字的核⼼模块申请⽤户空间需要的⼀系列CAN-ID。Socket CAN的核⼼向CAN协议模块提供预约和解约CAN-ID的接⼝--can_rx_(un)register(),⽆论这个CAN-ID是针对⼀个具体的CAN接⼝还是所有已知的CAN接⼝(参考第5章)。为了优化CPU的运⾏效率,每个设备都对应⼀个接收队列,这样⽐较容易实现各种报⽂过滤规则。3.2 发送帧的本地回环-----------------在其它种类的⽹络中,在相同或者不同⽹络节点上的应⽤程序都可以相互交换数据。 ___ ___ ___ _______ ___ | _ | | _ | | _ | | _ _ | | _ | ||A|| ||B|| ||C|| ||A| |B|| ||C|| |___| |___| |___| |_______| |___| | | | | | -----------------(1)- CAN bus -(2)---------------
请看上图的两个例⼦。为了保证应⽤程序A在两个例⼦中能够接收到同样的例⼦(例2中A和B在同⼀个CAN设备上),发送出去的CAN帧需要能够在本地回环。Linux下的⽹络设备仅仅处理物理媒介上帧的发送和接受。总线仲裁机制下,⾼优先级的帧会将低优先级的帧延后。为了正确反映实际的通信活动,回环必须在正确传输成功之后进⾏。如果CAN⽹络的硬件不⽀持回环功能,⼀种低效的⽅案是使⽤Socket CAN核⼼部分来实现软件回环。具体的情况请参考6.2⼩节。CAN⽹络的回环功能是默认开启的。由于RT-SocketCAN 的特殊需求,每个套接字的回环功能可以被独⽴关闭。CAN原始套接字的控制选项请参考4.1⼩节。*当你在同⼀个节点上运⾏CAN分析命令“candump”或者“cansniffer”的时候就会发现回环功能真的很有⽤。3.3 ⽹络安全相关--------------------CAN⽹络是⼀种现场总线,仅仅⽀持没有路由和安全控制的⼴播传输。⼤部分应⽤程序都需要要直接处理原始CAN帧,所以和其它类型的⽹络⼀样,CAN⽹络对所有的⽤户(⽽不仅仅是root⽤户)访问没有任何限制。由于当前CAN_RAW和CAN_BCM的实现仅仅⽀持对CAN接⼝的读写,所以允许所有的⽤户访问CAN并不影响其它类型⽹络的安全性。为了使能⾮root⽤户对CAN_RAW和CAN_BCM协议套接字的访问,必须在编译内核的时候选上Kconfig的CAN_RAW_USER/CAN_BCM_USER选项。
3.4 ⽹络故障监测--------------------
使⽤CAN总线的时候,可能会遇到物理和mac(media access control)层的问题。为了⽅便⽤户分析物理收发器的硬件错误、总线仲裁错误和不同的ECU( Electrical Conversion Unit,电⽓转换装置)引起的错误,对于底层(物理和mac层)的监测和记录是⾄关重要的。拥有精确时间戳的错误监测对于诊断错误是⾮常重要的。基于以上原因,CAN接⼝的驱动可以可以选择性的产⽣所谓的错误帧,这些帧以和其它的CAN帧⼀样的⽅式传递给⽤户程序。当⼀个物理层或者MAC层的错误被(CAN控制器)检测到之后,驱动创建⼀个相应的错误帧。错误帧可以被应⽤程序通过CAN的过滤机制请求得到。过滤机制允许选择需要的错误帧的类型。默认情况下,接收错误帧的功能是禁⽌的。CAN错误帧的详细格式定义在linux头⽂件中:include/linux/can/error.h。
4. 如何使⽤Socket CAN===============
就像TCP/IP协议⼀样,在使⽤CAN⽹络之前你⾸先需要打开⼀个套接字。CAN的套接字使⽤到了⼀个新的协议族,所以在调⽤socket(2)这个系统函数的时候需要将PF_CAN作为第⼀个参数。当前有两个CAN的协议可以选择,⼀个是原始套接字协议( raw socket protocol),另⼀个是⼴播管理协议BCM(broadcast manager)。你可以这样来打开⼀个套接字:
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);或者 s = socket(PF_CAN, SOCK_DGRAM, CAN_BCM);
在成功创建⼀个套接字之后,你通常需要使⽤bind(2)函数将套接字绑定在某个CAN接⼝上(这和TCP/IP使⽤不同的IP地址不同,参见第3章)。在绑定 (CAN_RAW)或连接(CAN_BCM)套接字之后,你可以在套接字上使⽤read(2)/write(2),也可以使⽤send(2)/sendto(2)/sendmsg(2)和对应的recv*操作。当然也会有CAN特有的套接字选项,下⾯将会说明。
基本的CAN帧结构体和套接字地址结构体定义在include/linux/can.h:
/** 扩展格式识别符由 29 位组成。其格式包含两个部分:11 位基本 ID、18 位扩展 ID。* Controller Area Network Identifier structure** bit 0-28 : CAN识别符 (11/29 bit)* bit 29 : 错误帧标志 (0 = data frame, 1 = error frame)* bit 30 : 远程发送请求标志 (1 = rtr frame)* bit 31 :帧格式标志 (0 = standard 11 bit, 1 = extended 29 bit)*/typedef __u32 canid_t;
struct can_frame {
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ __u8 can_dlc; /* 数据长度: 0 .. 8 */ __u8 data[8] __attribute__((aligned(8)));
};
结构体的有效数据在data[]数组中,它的字节对齐是64bit的,所以⽤户可以⽐较⽅便的在data[]中传输⾃⼰定义的结构体和共⽤体。CAN总线中没有默认的字节序。在CAN_RAW套接字上调⽤read(2),返回给⽤户空间的数据是⼀个struct can_frame结构体。
就像PF_PACKET套接字⼀样,sockaddr_can结构体也有接⼝的索引,这个索引绑定了特定接⼝:
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; /* 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(_name, "can0" ); ioctl(s, SIOCGIFINDEX, &ifr);
_family = AF_CAN; _ifindex = _ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
(..)
为了将套接字和所有的CAN接⼝绑定,接⼝索引必须是0。这样套接字便可以从所有使能的CAN接⼝接收CAN帧。recvfrom(2)可以指定从哪个接⼝接收。在⼀个已经和所有CAN接⼝绑定的套接字上,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 framen"); return 1; }
/* do something with the received CAN frame */
写CAN帧也是类似的,需要⽤到write (2)函数:
nbytes = write(s, &frame, sizeof(struct can_frame));
如果套接字跟所有的CAN接⼝都绑定了(_index = 0),推荐使⽤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 */ _ifindex = _ifindex; ioctl(s, SIOCGIFNAME, &ifr); printf("Received a CAN frame from interface %s", _name);
对于绑定了所有接⼝的套接字,向某个端⼝发送数据必须指定接⼝的详细信息:
strcpy(_name, "can0"); ioctl(s, SIOCGIFINDEX, &ifr); _ifindex = _ifindex; _family = AF_CAN;
nbytes = sendto(s, &frame, sizeof(struct can_frame), 0, (struct sockaddr*)&addr, sizeof(addr));
4.1 使⽤can_filter的原始套接字 (RAW socket)----------------------------------------------------
CAN_RAW套接字的⽤法和CAN字符设备的⽤法是类似的。为了使⽤CAN套接字的新特性,在绑定原始套接字的时候将会默认开启以下特性:
- filter将会接收所有的数据- 套接字仅仅接收有效的数据帧(=> no error frames)- 发送帧的回环功能被开启(参见 3.2节)- (回环模式下)套接字不接收它⾃⼰发送的帧
这些特性的设置可以在绑定之前和之后修改。为了使⽤CAN_RAW套接字相关的选项,必须包含
4.1.1 原始套接字选项 CAN_RAW_FILTERCAN_RAW套接字的接收可以使⽤CAN_RAW_FILTER套接字选项指定的多个过滤规则(过滤器)来过滤。
过滤规则(过滤器)的定义在 include/linux/can.h中:
struct can_filter { canid_t can_id; canid_t can_mask; };
过滤规则的匹配:
/*#define CAN_INV_FILTER 0x20000000U /* to be set in can__id */#define CAN_ERR_FLAG 0x20000000U /* error frame */*/这和⼤家所熟知的CAN控制器硬件过滤⾮常相似。可以使⽤ CAN_INV_FILTER这个宏将can_filter结构体的成员can_id中的⽐特位反转。和CAN控制器的硬件过滤形成鲜明对⽐的是,⽤户可以为每⼀个打开的套接字设置多个独⽴的过滤规则(过滤器):
/* /* valid bits in CAN ID for frame formats */#define CAN_SFF_MASK 0x000007FFU /* 标准帧格式 (SFF) */#define CAN_EFF_MASK 0x1FFFFFFFU /* 扩展帧格式 (EFF) */#define CAN_ERR_MASK 0x1FFFFFFFU /* 忽略EFF, RTR, ERR标志 */ */
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套接字上禁⽤接收过滤规则,可以这样:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
在⼀些极端情况下不需要读取数据,可以把过滤规则清零(所有成员设为0),这样原始套接字就会忽略接收到的CAN帧。在这种仅仅发送数据(不读取)的应⽤中可以在内核中省略接收队列,以此减少CPU的负载(虽然只能减少⼀点点)。
4.1.2 原始套接字选项 CAN_RAW_ERR_FILTER正如3.4节所说,CAN接⼝驱动可以选择性的产⽣错误帧,错误帧和正常帧以相同的⽅式传给应⽤程序。可能产⽣的错误被分为不同的种类,使⽤适当的错误掩码可以过滤它们。为了注册所有可能的错误情况,CAN_ERR_MASK(0x1FFFFFFFU)这个宏可以⽤来作为错误掩码。这个错误掩码定义在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));
4.1.3 原始套接字选项 CAN_RAW_LOOPBACK为了满⾜众多应⽤程序的需要,本地回环功能默认是开启的(详细情况参考3.2节)。但是在⼀些嵌⼊式应⽤场景中(⽐如只有⼀个⽤户在使⽤CAN总线),回环功能可以被关闭(各个套接字之间是独⽴的):
int loopback = 0; /* 0 = disabled, 1 = enabled (default) */
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
4.1.4 原始套接字选项 CAN_RAW_RECV_OWN_MSGS在本地回环功能开启的情况下,所有的发送帧都会被回环到在相应CAN接⼝上注册了同样CAN-ID(和发送帧的相同)的套接字上。发送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));
(yll:下⾯三⼩节没有内容)4.2 ⼴播管理协议套接字 (SOCK_DGRAM)-----------------------------------------------
4.3 ⾯向连接的传输协议 (SOCK_SEQPACKET)----------------------------------------------------
4.4 ⽆连接的传输协议 (SOCK_DGRAM)---------------------------------------------
5. Socket CAN核⼼模块==============
CAN套接字的核⼼模块实现了PF_CAN协议族。CAN协议模块在核⼼模块运⾏后加载。CAN核⼼模块为协议模块提供了申请需要的ID的接⼝(参考3.1⼩节)。
5.1 模块的参数-------------------------
- stats_timer: 为了计算CAN套接字的统计信息(⽐如最近⼀秒的帧数和每秒最⼤的帧数),调⽤⼀个定时间隔为1秒的定时器,默认情况下这个定时器是开启的。这个定时器也可以在模块参数中传⼊stattimer=0来禁⽌。
- debug : (removed since SocketCAN SVN r546) 5.2 procfs接⼝-----------------
就像3.1节描述的那样,CAN套接字核⼼借助于⼀些带有过滤规则的队列向CAN协议模块传递接收到的CAN帧。可以在procfs中查看这些接收队列的的过滤规则和匹配规则的次数。所有的条⽬都包了设备名和协议模块标识:
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) (yll补充:function是⼀个函数指针,userdata是⼀个void *指针,所以这两个值打印出来没有太⼤意义)
在这个例⼦中,这个应⽤程序接收所有vcan0上传输的数据.
rcvlist_all - 没有过滤规则的队列 rcvlist_eff - 扩展帧(EFF)的队列 rcvlist_err - 错误帧队列 rcvlist_fil - 通过过滤规则的队列 rcvlist_inv - 未通过过滤规则的队列 rcvlist_sff - 标准帧的队列
在/proc/net/can中还有另外⼀些⽂件:
stats - CAN套接字核⼼的统计信息(接收/发送的帧数,匹配率等) reset_stats - 复位统计信息 version - 打印CAN套接字核⼼的版本和ABI的版本
5.3 写⼀个⾃⼰的CAN协议模块-----------------------------------
要在PF_CAN中增加⼀个新的协议,必须在include/linux/can.h中为新的协议增加相应的定义。包含include/linux/can.h这个⽂件便可以使⽤增加的原型和定义。内核除了提供了注册CAN协议和CAN设备的通知列表的功能,也提供了在⼀个特定CAN接⼝上注册感兴趣的CAN帧或者发送CAN帧的功能。
can_rx_register - 在⼀个特定接⼝上注册希望接收到的CAN帧的信息 (yll:这个函数的定义在内核的net/can/af_can.c中) can_rx_unregister - 注销上⾯的申请
can_send - 发送CAN帧(可以选择是否开启本地回环)
详细的信息请参考内核中的源码net/can/af_can.c、net/can/raw.c、net/can/bcm.c。
6. CAN⽹络驱动==========编写⼀个CAN⽹络设备驱动要⽐写⼀个CAN字符设备驱动要容易的多。和编写其它⽹络设备驱动类似,你只要处理以下事宜:
- TX :将套接字缓冲区的CAN帧发送到CAN控制器- RX :从CAN控制器的CAN帧读取到套接字缓冲区
Documentation/networking/中是⽹络设备驱动的例⼦。下⾯将会介绍CAN⽹络设备驱动的⼀些独有特性。
6.1 常见设置---------------
dev->type = ARPHRD_CAN; /* the netdevice hardware type */ dev->flags = IFF_NOARP; /* CAN has no arp */ dev->mtu = sizeof(struct can_frame);
结构体can_frame是PF_CAN协议族套接字缓冲区的数组载体。
6.2 发送帧的本地回环-------------------------
如3.2⼩节所述,CAN⽹络设备驱动应该⽀持类似TTY设备回显功能的本地回环功能。如果驱动⽀持这个功能,则要设备IFF_ECHO标志来防⽌PF_CAN核⼼回显发送帧(⼜称回环)。 dev->flags = (IFF_NOARP | IFF_ECHO);
6.3 CAN控制器的硬件过滤------------------------------
为了减⼩⼀些嵌⼊式系统的中断负载,⼀些CAN控制器⽀持多个CAN-ID或者多个CAN-ID区间的过滤功能。硬件过滤功能在不同的控制器之间差异很⼤,并且不能同时满⾜多个⽤户的不同过滤需求。在单⽤户应⽤中使⽤控制器的硬件过滤或许还有意义,但是在⼀个多⽤户系统中驱动层的过滤将会影响所有⽤户。PF_CAN核⼼内置的过滤规则集合允许对每个套接字独⽴的设置多个过滤规则。因此使⽤硬件过滤属于嵌⼊式系统中“⼿动调整”的范畴。从2002年开始笔者⼀直使⽤拥有四路SJA1000 CAN控制器的MPC603e @133MHz,总线负载虽然很⾼,但是到⽬前为⽌还没有什么问题。
6.4 虚拟的CAN驱动 (vcan)------------------------------
和⽹络回环设备⼀样,vcan提供⼀个虚拟的本地CAN接⼝。CAN中⼀个有效的地址包括:
- ⼀个有效的CAN标识符(CAN ID)- 这个CAN标识符将要发往的总线(⽐如 can0).
所以在⼀般的应⽤场景中往往需要多个vcan接⼝。
vcan接⼝允许在没有控制器硬件的情况下进⾏发送和接收。vcan⽹络设备的命名⼀般采⽤‘vcanX’,⽐如can1 vcan2等。当编译为单独的模块的时候,vcan驱动的模块名为。
vcan驱动从linux2.6.24开始⽀持netlink接⼝,使得创建vcan⽹络设备变的可能。可以使⽤ip(8)命令⼯具管理vcan⽹络设备的创建和移除:
- 创建⼀个vcan⽹络接⼝: $ ip link add type vcan
- 使⽤给定的名字 'vcan42'创建 ⼀个vcan⽹络接⼝: $ ip link add dev vcan42 type vcan
- 移除vcan⽹络接⼝'vcan42': $ ip link del vcan42
6.5 CAN⽹络设备驱动接⼝------------------------------
CAN⽹络设备驱动提供了进⾏安装、配置和监控CAN⽹络设备的接⼝。可以使⽤IPROUTE2⼯具集中的“ip”命令通过netlink接⼝配置CAN设备,⽐如设置波特率。本章剩余的部分将会简介如何使⽤这⼀⼯具。另外这些接⼝使⽤⼀些通⽤的数据结构并且提供了⼀系列常⽤的功能,这些功能都是CAN⽹络设备驱动需要⽤到的。请参考SJA1000或者MSCAN的驱动去了解如何使⽤它们。模块的名字是。6.5.1 Netlink接⼝--设置/获取设备属性CAN设备必须使⽤netlink来配置。在"include/linux/can/netlink.h"有对netlink消息类型的定义和简短描述。IPROUTE2⼯具集中的“ip”命令可以使⽤CAN的netlink⽀持,下⾯是使⽤⽰例:
- 设置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 ] ] [ loopback { on | off } ] [ listen-only { on | off } ] [ triple-sampling { 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:
link/can can
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
下⾯是上⾯⼀些名词的解释:
"
"state ERROR-ACTIVE"CAN控制器的当前状态:"ERROR-ACTIVE", "ERROR-WARNING", "ERROR-PASSIVE", "BUS-OFF" or "STOPPED"
"restart-ms 100"⾃动重启的延时时间。如果设为⾮零值, 在总线关闭的情况下,在设定的数量毫秒后CAN控制器被⾃动触发。这个功能默认是关闭的。
"bitrate 125000 sample_point 0.875"使⽤bits/sec作为单位显⽰位时间并显⽰0.000~0.999的采样点位置。如果内核中使能了统计位时间的功能(CONFIG_CAN_CALC_BITTIMING=y),位时间可以使⽤"bitrate"参数来设置。可选的"sample-point"也是可以配置的。默认使⽤0.000这个推荐值。
"tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1"以ns为单位显⽰时间份额(tq-time quanta)、传播段(prop-seg : propagation segment)、相位缓冲段1和2(phase-seg:phasebuffer),以tq为单位显⽰同步跳转宽度(sjw:synchronisation jump width)。这些变量允许定义与硬件⽆关的位时序,这也是Bosch CAN2.0 spec所推荐的(参考第⼋章/pdf/)。
"sja1000: tseg1 1..16 tseg2 1..8 sjw 1..4 brp 1..64 brp-inc 1 clock 8000000"显⽰CAN控制器的⽐特时序常量,这⾥的例⼦以sja1000为例。时间段(tseg -time segment)1和2的最⼩和最⼤值,以tq为单位的同步跳转宽度,⽐特速率的预分频器(brp--pre-scaler)和CAN系统时钟(以HZ为单位)。这些常量可以被⽤户空间的⽐特时序统计算法所使⽤。
"re-started bus-errors arbit-lost error-warn error-pass bus-off"显⽰重启的次数、总线和仲裁丢失错误,错误主动(error-warning)、错误被动(error-passive)、和总线关闭的状态变化。接收的过载错误在 统计信息的"overrun"域下⾯列出。
6.5.2 设置CAN的⽐特时序CAN⽐特时序参数可以使⽤硬件⽆关的定义⽅法。这些参数是: "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"被设置了,CAN的这些参数将会⽣效:
$ ip link set canX type can bitrate 125000
请注意,这条命令在⼤部分使⽤标准波特率的CAN控制器上⼯作良好,但是使⽤⼀些少见的波特率值(如115000)和时钟频率值将会失败。禁⽤内核的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
6.5.3 启动和停⽌CAN⽹络设备 ⼀个CAN⽹络设备可以使⽤"ifconfig canX up/down" 或者 "ip link set canX up/down"来开启和关闭。为了避免错误的默认值,必须在启动CAN设备之前设置它的⽐特时序参数:
$ ip link set canX up type can bitrate 125000
如果总线上出现太多的错误设备可能进⼊总线关闭状态(也就是从总线脱离)。进⼊总线关闭状态之后设备不会再发送和接收信息。给"restart-ms"设置⼀个⾮零值可以开启总线关闭⾃动恢复的功能(也就是进⼊总线关闭状态后重新开启),下⾯是⼀个⽰例:
$ ip link set canX type can restart-ms 100
应⽤程序可以通过监测CAN的错误帧意识到已经进⼊总线关闭状态,并且可以使⽤以下命令重启:
$ ip link set canX type can restart
注意,重启也会⽣成⼀个CAN错误帧(参见3.4节)。
6.6 ⽀持Socket CAN的硬件------------------------------
"drivers/net/can"中的“Kconfig”⽂件中可以查看到所有⽀持的硬件列表。在CAN套接字项⽬⽹站上(参见第7章)有更多驱动何以获得,当然也有对早些时期版本内核的⽀持 。
7. 学习Socket CAN的相关资源===================
你可在BerliOS OSS项⽬⽹站的CAN套接字页⾯中发现更多资源,⽐如⽤户空间⼯具、对旧版内核的⽀持、更多的驱动、邮件列表等:
如果你有任何问题或者发现了BUG,不要迟疑,⽴马发送邮件到CAN套接字的⽤户邮件列表。但是在发送之前请⾸先搜索邮件记录中是否已经有了相同的问题。
8. 贡献者名单=========
Oliver Hartkopp (PF_CAN core, filters, drivers, bcm, SJA1000 driver) Urs Thuermann (PF_CAN core, kernel integration, socket interfaces,raw, vcan) Jan Kizka (RT-SocketCAN core, Socket-API reconciliation) Wolfgang Grandegger (RT-SocketCAN core & drivers, Raw Socket-API reviews, CAN device driver interface, MSCAN driver) RobertSchwebel (design reviews, PTXdist integration) Marc Kleine-Budde (design reviews, Kernel 2.6 cleanups, drivers) Benedikt Spranger (reviews) Thomas Gleixner (LKML reviews, coding style, posting hints) Andrey Volkov (kernel subtree structure, ioctls, MSCAN driver) Matthias Brukner (first SJA1000 CAN netdevice implementation Q2/2003)Klaus Hitschler (PEAK driver integration) Uwe Koppe (CAN netdevices with PF_PACKET approach) Michael Schulze (driver layer loopback requirement, RT CAN drivers review) Pavel Pisa (Bit-timing calculation) Sascha Hauer (SJA1000 platform driver) Sebastian Haas (SJA1000 EMS PCI driver) Markus Plessing (SJA1000 EMS PCI driver) Per Dalen (SJA1000 Kvaser PCI driver) Sam Ravnborg (reviews, coding style, kbuild help)
发布评论