文献来源:ethercat_doc.pdf (etherlab.org)
https://docs.etherlab.org/ethercat/1.6/pdf/ethercat_doc.pdf
网络驱动程序基础
EtherCAT 依赖于以太网硬件,并且主设备需要一个物理的以太网设备才能与总线进行通信。因此,有必要了解 Linux 如何处理网络设备及其驱动程序。
网络驱动程序的任务:网络设备驱动程序通常负责 OSI 模型的下两层,即物理层和数据链路层。网络设备本身直接处理物理层的问题:它代表用于连接介质以及以物理层的方式发送和接收数据的硬件协议描述。网络设备驱动程序负责从内核的网络栈获取数据,并将其转发给负责进行物理传输的硬件。如果数据由硬件接收,驱动程序会收到通知(通常通过中断的方式),然后必须从硬件内存中读取数据并将其转发到网络栈。网络设备驱动程序还需要处理一些其他任务,包括队列控制、统计信息以及与设备相关的功能。
net_device结构体:
驱动程序为每个设备注册一个 net_device 结构,以便与网络栈进行通信并创建“网络接口”。对于以太网驱动程序而言,该接口将以“ethX”的形式呈现,其中 X 是由内核在注册时分配的数字。net_device 结构通过多个回调函数接收事件(来自用户空间或网络栈),在注册之前必须设置这些回调函数。并非每个回调函数都是必需的,但为了正常运行,以下这些回调函数在任何情况下都是必须的:
open() 这个函数在需要启动网络通信时会被调用,例如在用户空间执行“ip link set ethX up”命令之后。此时驱动程序需要启用帧接收功能。
stop() 此回调函数的作用是“关闭”设备,即使硬件停止接收帧数据。
hard_start_xmit() 这个函数会在每次需要传输的帧中被调用。 网络栈将该帧以指向 sk_buff 结构的指针形式传递出去(“套接字缓冲区”,见下文),在发送完成后必须对该结构进行释放。
get_stats() 这个调用必须返回指向设备的 net_device_stats 结构的指针,该结构必须永久性地填入帧统计信息。这意味着,每当接收到帧、发送帧或发生错误时,此结构中的相应计数器都必须增加。
实际的注册操作通过调用 register_netdev() 来完成,而注销操作则通过 unregister_netdev() 来实现。

netif接口:(注:专用网卡驱动需要屏蔽netif接口,只有通用网卡驱动会与linux网络栈通讯)
网络接口与网络栈之间的所有其他通信都是通过netif_*()调用来完成的。例如,在成功打开设备后,网络栈需要被通知,此时它可以将帧传递给网络接口。这是通过调用netif_start_queue()来实现的。在此之后,网络栈可以调用hard_start_xmit()回调函数。此外,网络驱动程序通常会管理一个帧传输队列。如果该队列被填满,网络栈必须被告知暂时停止传递更多帧。这可以通过调用netif_stop_queue()来实现。如果已经发送了一些帧,并且又有足够的空间可以排队新的帧,可以通过netif_wake_queue()来通知这一情况。另一个重要的调用是netif_receive_skb()1:它将刚刚由设备接收的帧传递给网络栈。帧数据必须包含在所谓的“套接字缓冲区”中(见下文)。
套接字缓冲区(Socket Buffers):
套接字缓冲区是整个网络栈的基本数据类型。它们充当网络数据的容器,并能够快速添加数据头和尾部,或者再次去除它们。因此,一个套接字缓冲区由一个分配的缓冲区和几个指针组成,这些指针分别标记缓冲区的起始位置(头部)、数据的起始位置(数据)、数据的结束位置(尾部)和缓冲区的结束位置(结束)。此外,一个套接字缓冲区还包含网络头部信息以及(在接收到数据的情况下)指向接收到数据所在的网络设备的指针。存在一些函数可以创建套接字缓冲区dev_alloc_skb())、从前端添加数据(skb_push())或从后端添加数据(skb_put())、从前端移除数据(skb_pull()) 或从后端移除数据(skb_trim()),或者删除缓冲区(kfree_skb())。套接字缓冲区从一层传递到另一层,并由最后使用它的层进行释放。在发送数据的情况下,释放工作必须由网络驱动程序完成。
专用EtherCAT设备驱动程序
在将以太网硬件与具有EtherCAT功能的原生以太网驱动程序配合使用时,有一些要求需要遵守:
专用硬件:为了实现高性能和实时性,EtherCAT主机需要直接且独占地访问以太网硬件。这意味着网络设备不能像通常那样连接到内核的网络栈中,因为内核会试图将其当作普通的以太网设备来使用。
无中断操作(使用轮询):EtherCAT帧在逻辑以太网环中传输,然后被送回主设备。通信具有高度的确定性:一个帧被发送出去,会在固定的时间后再次被接收,因此无需通知驱动程序帧接收情况:如果主机期望接收到的帧已经被接收,那么它可以向硬件查询接收到的帧。 
图1 在有中断和无中断情况下进行循环帧传输与接收的两种工作流程
在右侧的工作流程“中断操作”中,首先会处理上一个周期的数据,并用新的数据包构建一个新的帧,然后将其发送出去。目前这一循环工作已经完成。之后,当硬件再次接收到该帧时,会触发中断并执行中断服务例程(ISR)。ISR会从硬件中获取帧数据,并启动帧分解过程:数据包将被处理,以便在下一个周期中能够进行处理。
在“无中断操作”这一正确的工作流程中,并未启用硬件中断。相反,硬件将由主程序通过执行中断服务程序(ISR)来进行轮询。如果在此期间已接收到帧,那么该帧将被分解。此时的情况与右侧工作流程的开始阶段相同:接收到的数据将被处理,然后会重新组装一个新帧并发送出去。在该周期的其余部分无需进行任何操作。无中断操作是理想的,因为硬件中断不利于提升驱动程序的实时性能:其不确定性的发生会加剧抖动现象。此外,如果使用实时扩展(如RTAI),则还需要付出额外的努力来对中断进行优先级排序。
以太网设备与EtherCAT设备:
另一个问题在于Linux处理相同类型设备的方式。例如,一个PCI驱动程序会在PCI 总线上扫描可处理的设备,然后它会将自身注册为所找到的所有设备的负责驱动程序。问题在于,未经修改的驱动程序无法被指示忽略某个设备,因为该设备之后将用于 EtherCAT。必须有一种方法来处理相同类型的不同设备,其中有一个设备被预留用于EtherCAT,而另一个则被视为普通的以太网设备。(因为该网络驱动不只是应用于EtherCAT,也需要适用于普通以太网设备)
出于所有这些原因,作者认为唯一可接受的解决方案是修改标准以太网驱动程序,使其保持正常功能,并能够将一个或多个设备视为支持EtherCAT的专用网卡驱动。
以下是该解决方案的诸多优点:
· 无需告知标准驱动程序忽略某些设备。
· 一款适用于EtherCAT设备和非EtherCAT设备的网络驱动程序。
· 无需从零开始开发网络驱动程序并遭遇各种问题,因为这些问题之前已经有开发者解决了。
所选的方法存在以下缺点:
· 修改后的驱动程序变得更加复杂了,因为它需要同时处理以太网控制器和非以太网控制器设备。
· 驱动程序代码中存在许多额外的案例差异。
· 标准驱动程序中的更改和错误修复必须移植到EtherCAT 系统中。
修复原生网络驱动程序
本节将介绍如何通过原生方法使标准以太网驱动程序具备EtherCAT 功能。遗憾的是,并没有统一的标准流程来使以太网驱动程序能够与 EtherCAT主机一起使用,但有一些常见的技术可供参考。
1.首先有一个简单的规则:对于所有EtherCAT设备,都应避免调用netif*()函数。如前所述,EtherCAT设备与网络栈没有关联,因此不能调用其接口函数或中断函数。所以,在硬件层面注册中断处理程序和启用中断的操作也应避免。
2.另一件重要的事情是,EtherCAT设备的运行应当遵循特定的操作规范。
3.主进程不会为每次发送操作都分配一个新的套接字缓冲区:相反,在主进程初始化时会预先分配一个固定的缓冲区。每次发送操作时,该缓冲区都会填充一个EtherCAT帧,并传递给hard_start_xmit()回调函数。为此,必须确保网络驱动程序不会像通常那样释放该套接字缓冲区。
一个以太网驱动程序通常会管理多个以太网设备,每个设备都由一个网设备结构体来描述,该结构体中有一个私有数据字段用于附加与驱动程序相关的数据。为了区分普通的以太网设备和由EtherCAT主机使用的设备,驱动程序所使用的私有数据结构可以通过添加一个指针来扩展,如果该设备被主机使用,则该指针指向由ecdev offer()函数返回的ec_device_t对象,否则该指针为零。
(RealTek RTL-8139快速以太网驱动程序是一款“简单的”以太网驱动程序,可以将其作为编写新驱动程序的范例。通过在文件devices/8139too-2.6.24-ethercat.c 中搜索字符串“ecdev”来实现这一操作。)
EtherCAT可以将本地网卡驱动做修改,以适配成可以运行IGH主站网卡驱动。
改动要点如下:
获取需要修改的专用网卡驱动代码,找出以下几个关键流程和节点:
1)poll的注册地方。
ecat主站需要网卡驱动为igh驱动提供网卡设备,因此需要在网卡驱动程序中添加ec_poll()函数并调用ecdev_offer()函数
2)所有产生中断的地方。因为ethercat是禁用中断的
在网卡驱动程序中所有产生中断的地方做ecdev设备存在判断,若该网卡被igh驱动使用,则屏蔽网卡中断。
3)与skb相关的地方。比如说gro_receive或者skb_receive的地方。
ecat主站驱动发送不释放skb_buff,因此减少buff申请和释放
4)rx函数和start_xmit函数。
不能通过中断接收数据
(在ethercat/devices目录下,*-ethercat.c就是基于*-orig.c修改后的代码,比如r8169-3.10-ethercat.c就是基于r8169-3.10-orig.c修改后的代码,所以比较这两个文件,就知道修改了啥。)
通用 EtherCAT 设备驱动程序
由于存在能够使完整版 Linux 内核实现实时运行的方法,因此无需使用具备 EtherCAT 功能的本地以太网设备驱动程序,而是可以使用 Linux 网络栈来运行。图 2 展示了“通用以太网驱动模块”,它通过网络栈与本地以太网设备相连。该内核模块名为 ec_generic,可以在主模块之后加载,就像使用原生具备 EtherCAT 功能的以太网驱动程序一样。

图2 网络驱动架构
通用设备驱动程序会扫描网络栈,查找由以太网设备驱动程序注册的接口。它将所有可能的设备提供给 EtherCAT 主机。如果主站接受某个设备,通用驱动程序就会创建一个数据包套接字(参见 man 7 packet),将套接字类型设置为 SOCK_RAW,并将其绑定到该设备上。此后,设备接口的所有功能都将在此套接字上运行。
以下是该解决方案的诸多优点:
· 任何由 Linux 以太网驱动程序支持的以太网硬件都可以用于 EtherCAT 通信。
· 无需对实际的以太网驱动程序进行任何修改。
这种通用方法存在以下缺点:
· 该性能比原生方法稍差一些,因为帧数据需要经过网络栈的底层进行传输。
· 由于通用驱动程序使用的是动态内存分配等技术,而这些技术可能会导致系统在实时环境中出现冻结现象,所以无法将像 RTAI 这样的内核级实时扩展技术与该通用驱动程序配合使用。
激活设备:
若要通过套接字发送和接收帧,与该套接字相连的以太网设备必须先被激活,否则所有帧都将被拒绝。激活操作必须在主模块加载之前完成,并且可以通过多种方式实现:
· 临时设置,使用命令“ip link set dev ethXup”(或者使用较旧的“ifconfig ethX up”)即可。
· 根据发行版的不同进行配置,例如在 openSUSE 和其他发行版中使用 ifcfg 文件(/etc/sysconfig/network/ifcfg-ethX)。如果希望 EtherCAT 主站在系统启动时启动,这是更好的选择。由于只需激活以太网设备而不需要分配 IP 地址等,使用 STARTMODE=auto 作为配置就足够了。

igh代码实例:
1. igh通用网卡代码分析
1)poll函数
· 检测网卡状态并将该状态发送给EtherCAT驱动
· 从网卡设备套接字接收数据 (每次调用最多处理10个数据包)
· 将从网卡接受到的数据发送给EtherCAT驱动

EtherCAT通过ec_device_attach函数注册到EtherCAT驱动
![]()
EtherCAT驱动通过ec_device_poll调用网卡poll函数

在ecrt_master_receive函数中调用
2)创建net_device_ops结构体

网卡发送函数

在ec_master_send_datagrams函数调用网卡发送函数

2. 专业网卡驱动实例
1)poll函数
· 每两秒调用igb_has_link函数检测igb网卡驱动链接状态,并通过ecdev_set_link发送给EtherCAT驱动
· igb_clean_tx_irq:
· igb_clean_rx_irq:

2)创建net_device_ops结构体

ndo_start_xmit函数

IGH EtherCAT网卡驱动解析
1万+

被折叠的 条评论
为什么被折叠?



