版权声明:本文为本文为博主原创文章,转载请注明出处。如有问题,欢迎指正。博客地址:https://www.cnblogs.com/wsg1100/

1 启动脚本

igh通过脚本来启动,可以是systemd、init.d或sysconfig。分别位于源码script目录下:

对于systemd方式,编译时由ethercat.service.in文件生成ethercat.serviceethercat.service中指定了执行文件为ethercatctl.ethercatctl文件由``ethercatctl.in`生成。init.d和sysconfig类似,都是生成一个可执行脚本,且脚本完成的工作一致,主要完成加载主站模块、网卡驱动、给主站内核模块传递参数、卸载模块等操作。

ethercat.conf共同的配置文件,配置主站使用的网卡、驱动等信息。下面看脚本start和stop所做的工作。

1.1 start

  1. 加载ec_master.ko

    模块参数:

    • main_devices :主网卡MAC地址,多个main_devices 表示创建多个主站,MAC参数个数master_count。
    • backup_devices :备用网卡MAC地址,多个backup_devices 表示创建多个主站,MAC参数个数backup_count。
    • debug_level :调试level,调试信息输出级别。
    • eoe_interfaces eoe接口,eoe_count表示eoe_interfaces 的个数。
    • eoe_autocreate 是否自动创建eoe handler。
    • pcap_size Pcap buffer size。
  2. 遍历配置文件中/etc/sysconfig/ethercat的环境变量DEVICE_MODULES,位于ethercat.conf中。

  3. 在每个DEVICE_MODULES前添加前缀ec_替换,如DEVICE_MODULES为igb的话,添加前缀后为ec_igb

  4. modinfo检查该模块是否存在。

  5. 对于非genericrtdm的驱动,需要先将网卡与当前驱动unbindunbind后的网卡才能被新驱动接管

  6. 加载该驱动。

start加载了两个内核模块,ec_master.ko和网卡驱动ec_xxx.ko,ec_master先根据内核参数(网卡MAC)来创建主站实例,此时主站处于Orphaned phase。后续加载网卡驱动ec_xxx.ko,执行网卡驱动probe,根据MAC地址将网卡与主站实例匹配,此时主站得到操作的网卡设备,进入Idle phase。详细过程见后文。

1.2 stop

卸载内核模块ec_master.ko和网卡驱动ec_xxx.ko。

  1. 遍历配置文件中的环境变量DEVICE_MODULES。
  2. 在每个DEVICE_MODULES前添加前缀‘ec_’替换。
  3. lsmod检查该模块是否被加载。
  4. 卸载模块。

后文“主站”和”master“均表示主站或主站实例对象,slave和从站表示从站或从站对象,中英混用,不刻意区分。

2 主站实例创建

start过程中执行insmod ec_master.ko,这个时候,先调用的就是 module_init 调用的初始化函数ec_init_module()。先根据参数main_devices 个数master_count,注册master_count字符设备的主次设备号device_number和名称。

if (master_count) {
if (alloc_chrdev_region(&device_number,
0, master_count, "EtherCAT")) {
EC_ERR("Failed to obtain device number(s)!\n");
...
}
} class = class_create(THIS_MODULE, "EtherCAT");

解析模块参数得到MAC地址,保存到数组macs中。

 for (i = 0; i < master_count; i++) {
ret = ec_mac_parse(macs[i][0], main_devices[i], 0); if (i < backup_count) {
ret = ec_mac_parse(macs[i][1], backup_devices[i], 1);
}
}

分配master_count个主站对象的内存,调用ec_master_init()初始化这些实例。

    if (master_count) {
if (!(masters = kmalloc(sizeof(ec_master_t) * master_count,
GFP_KERNEL))) {
...
}
} for (i = 0; i < master_count; i++) {
ret = ec_master_init(&masters[i], i, macs[i][0], macs[i][1],
device_number, class, debug_level);
...
}

2.1 Master Phases

igh中,状态机是其核心思想,一切操作基于状态机来执行,对创建的每个EtherCAT主站实例都需要经过如下阶段转换(见图2.3),主站各阶段操作如下:

Orphaned phase 此时主站实例已经分配初始化,正在等待以太网设备连接,即还没有与网卡驱动联系起来,此时无法使用总线通讯。

Idle phase 主站接受以太网设备所有请求,但没有应用请求时该模式生效。master运行master状态机(请参阅第5.3节),自动扫描总线上的从站,并从应用空间接口执行挂起操作(如SDO访问)。该阶段命令行工具能够访问总线,但无法进行过程数据交换,因为还缺少总线配置。

Operation phase 可以通过提供总线配置和交换过程数据的应用程序请求主站。

2.2 数据报与状态机

继续看master初始化代码ec_master_init前,我们先了解数据报与状态机的关系,这对后续理解很有帮助。

数据报

EtherCAT是以以太网为基础的现场总线系统,EtherCAT使用标准的IEEE802.3以太网帧,在主站一侧使用标准的以太网控制器,不需要额外的硬件。并在以太网帧头使用以太网类型0x88A4来和其他以太网帧相区别(EtherCAT数据还可以通过UDP/IP 来传输,本文已忽略),标准的IEEE802.3以太网帧中数据部分为EtherCAT的数据,标准的IEEE802.3以太网帧与EtherCAT数据帧关系如下:

EtherCAT数据位于以太网帧数据区,EtherCAT数据由EtherCAT头若干EtherCAT数据报文组成。其中EtheRCAT头中记录了EtherCAT数据报的长度、和类型,类型为1表示与从站通讯。EtherCAT数据报文内包含多个子报文,每个子报文又由子报文头、数据和WKC域组成。子报文结构含义如下。

整个EtherCAT网络形成一个环状,主站与从站之间是通过EtherCAT数据报来交互,一个EtherCAT报文从网卡TX发出后,从站ESC芯片EtherCAT报文进行交换数据,最后该报文回到主站。网上有个经典的EtherCAT动态图.

认识EtherCAT数据帧结构后,我们看IgH内是如何表示一个EtherCAT数据报文的?EtherCAT数据报文在igh中用对象ec_datagram_t表示。

typedef struct {
struct list_head queue; /**< 发送和接收时插入主站帧队列. */
struct list_head sent; /**< 已发送数据报的主站列表项. */
ec_device_index_t device_index; /**< 发送/接收数据报的设备。 */ ec_datagram_type_t type; /**< 帧类型 (APRD, BWR, etc.). */
uint8_t address[EC_ADDR_LEN]; /**< Recipient address. */
uint8_t *data; /**< 数据. */
ec_origin_t data_origin; /**< 数据保存的地方. */
size_t mem_size; /**< Datagram \a data memory size. */
size_t data_size; /**< Size of the data in \a data. */
uint8_t index; /**< Index (set by master). */
uint16_t working_counter; /**< 工作计数. */
ec_datagram_state_t state; /**数据帧状态 */
#ifdef EC_HAVE_CYCLES
cycles_t cycles_sent; /**< Time, 数据报何时发送. */
#endif
unsigned long jiffies_sent; /**< Jiffies,数据报何时发送. */
#ifdef EC_HAVE_CYCLES
cycles_t cycles_received; /**< Time, 何时被接收. */
#endif
unsigned long jiffies_received; /**< Jiffies,何时被接收. */
unsigned int skip_count; /**< 尚未收到的重新排队数. */
unsigned long stats_output_jiffies; /**< Last statistics output. */
char name[EC_DATAGRAM_NAME_SIZE]; /**< Description of the datagram. */
} ec_datagram_t;

可以看到上面子报文中各字段大都在ec_datagram_t中有表示了,不过多介绍,其它几个成员简单介绍下,

device_index表示这个数据报是属于哪个网卡设备发送接收的,IgH中一个master实例可以使用多个多个网卡设备来发送/接收EtherCAT数据帧,device_index就是表示master下的网络设备的。

data_origin表示每个master管理着多个空闲的ec_datagram_t,这些ec_datagram_t分成了三类,给不同的状态机使用,具体的后文马上会细说,data_origin就是表示这个ec_datagram_t是属于哪类的。

index表示该数据报是EtherCAT数据区的第index个子报文,在master发送一个完整的EtherCAT数据报时,组装以太网数据帧时使用。

data这个指向子报文的数据内存区,由于每个子报文交换数据不同,其大小不同,所以数据区为动态分配,mem_size表示的就是分配的大小。

data_size表示数据报操作的数据大小,比如该数据报用于读从站的某个寄存器,该值就是这个寄存器的大小。

jiffies_sent、jiffies_received、cycles_sent、cycles_received使用不同时钟方式是记录数据帧发送接收时间的,用于统计。

​ 此处缺张图。

ec_datagram_state_t表示数据报的状态,每个数据报(ec_datagram_t)也是基于状态来处理,有6种状态:

  • EC_DATAGRAM_INIT :数据报已经初始化
  • EC_DATAGRAM_QUEUED :插入发送队列准备发送
  • EC_DATAGRAM_SENT :已经发送(还存在队列中)
  • EC_DATAGRAM_RECEIVED:该数据报已接收,并从发送队列删除
  • EC_DATAGRAM_TIMED_OUT :该数据报发送后,接收超时,从发送队列删除
  • EC_DATAGRAM_ERROR :发送和接收过程中出错(从队列删除),校验错误、不匹配等。

M位在master发送时组装EtherCAT数据帧时确定,接收时也根据该位判断后面还有没有子报文。

数据报对象初始化由函数ec_datagram_init()完成:

void ec_datagram_init(ec_datagram_t *datagram /**< EtherCAT datagram. */)
{
INIT_LIST_HEAD(&datagram->queue); // mark as unqueued
datagram->device_index = EC_DEVICE_MAIN; /*默认主设备使用*/
datagram->type = EC_DATAGRAM_NONE; /*数据报类型*/
memset(datagram->address, 0x00, EC_ADDR_LEN); /*数据报地址清零*/
datagram->data = NULL;
datagram->data_origin = EC_ORIG_INTERNAL; /*默认内部数据*/
datagram->mem_size = 0;
datagram->data_size = 0;
datagram->index = 0x00;
datagram->working_counter = 0x0000;
datagram->state = EC_DATAGRAM_INIT; /*初始状态*/
#ifdef EC_HAVE_CYCLES
datagram->cycles_sent = 0;
#endif
datagram->jiffies_sent = 0;
#ifdef EC_HAVE_CYCLES
datagram->cycles_received = 0;
#endif
datagram->jiffies_received = 0;
datagram->skip_count = 0;
datagram->stats_output_jiffies = 0;
memset(datagram->name, 0x00, EC_DATAGRAM_NAME_SIZE);
}

状态机

说完IgH数据报对象,我们来看有限状态机(fsm),有限状态机(fsm):表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。说起状态机,相信大家大学时候都有用过吧,不管是单片机、FPGA,用它写按键、菜单、协议处理、控制器什么的爽的一塌糊涂。其实我们使用的计算机就是本就是基于状态机作为计算模型的,它对数字系统的设计具有十分重要的作用。另外Linux TCP协议也是由状态机实现。同样igh主站内部机制使用有限状态机来实现,IgH内状态机的基本表示如下:

struct ec_fsm_xxx {
ec_datagram_t *datagram; /**< 主站状态机使用的数据报对象 */
void (*state)(ec_fsm_master_t *); /**< 状态函数 */
....
};

每个状态机管理着某个对象的状态、功能实现的状态装换,而这些状态转换是基于EtherCAT数据报来进行的,所以IgH EtherCAT协议栈中,每个状态机内都包含有指向该状态机操作的数据报对象指针datagram和状态执行的状态函数void (*state)(ec_fsm_master_t *)

总结一句话:状态机是根据数据报的状态来执行,每个状态机都对应一个数据报对象

现在知道了状态机与数据报的关系,下面介绍IgH EtherCAT协议栈中有哪些状态机,及状态机使用的数据报对象是从哪里分配如何管理的。

master状态机

前面说到主站具有的三个阶段,当主站与网卡设备attach后进入Idle phase,处于Idle phase后,开始执行主站状态机。

主站状态机包含1个主状态机和许多子状态机,matser状态机主要目的是:

  • Bus monitoring 监控EtherCAT总线拓扑结构,如果发生改变,则重新扫描。

  • Slave conguration 监视从站的应用程序层状态。如果从站未处于其应有的状态,则从站将被(重新)配置

  • Request handling 请求(源自应用程序或外部来源)和处理,主站任务应该处理异步请求,例如:SII访问,SDO访问或类似。

主状态机ec_fsm_master_t结构如下:

struct ec_fsm_master {
ec_master_t *master; /**< master the FSM runs on */
ec_datagram_t *datagram; /**< 主站状态机使用的数据报对象 */
unsigned int retries; /**< retries on datagram timeout. */ void (*state)(ec_fsm_master_t *); /**< master state function */
ec_device_index_t dev_idx; /**< Current device index (for scanning etc.).
*/
int idle; /**< state machine is in idle phase */
unsigned long scan_jiffies; /**< beginning of slave scanning */
uint8_t link_state[EC_MAX_NUM_DEVICES]; /**< Last link state for every
device. */
unsigned int slaves_responding[EC_MAX_NUM_DEVICES]; /**<每个设备的响应从站数。*/
unsigned int rescan_required; /**< A bus rescan is required. */
ec_slave_state_t slave_states[EC_MAX_NUM_DEVICES]; /**< AL states of
responding slaves for
every device. */
ec_slave_t *slave; /**< current slave */
ec_sii_write_request_t *sii_request; /**< SII write request */
off_t sii_index; /**< index to SII write request data */
ec_sdo_request_t *sdo_request; /**< SDO request to process. */ ec_fsm_coe_t fsm_coe; /**< CoE state machine */
ec_fsm_soe_t fsm_soe; /**< SoE state machine */
ec_fsm_pdo_t fsm_pdo; /**< PDO configuration state machine. */
ec_fsm_change_t fsm_change; /**< State change state machine */
ec_fsm_slave_config_t fsm_slave_config; /**< slave state machine */
ec_fsm_slave_scan_t fsm_slave_scan; /**< slave state machine */
ec_fsm_sii_t fsm_sii; /**< SII state machine */
};

可以看到,主站状态机结构下还有很多子状态机,想象一下如果主站的所有功能通过一个状态机来完成,那么这个状态机的状态数量、各状态之间的联系会有多恐怖,复杂性级别将会提高到无法管理的水平。为此,IgH中,将EtherCAT主状态机的某些功能用子状态机完成。这有助于封装相关工作流,并且避免“状态爆炸”现象。这样当主站完成coe功能时,可以由子状态机fsm_coe去完成。具体各功能是如何通过状态机完成的,文章后面会介绍。

slave状态机

slave状态机管理着每个从站的状态,所以位于从站对象(ec_slave_t)内:

struct ec_slave
{
ec_master_t *master; /**< Master owning the slave. */
.....
ec_fsm_slave_t fsm; /**< Slave state machine. */
.....
};
struct ec_fsm_slave {
ec_slave_t *slave; /**< slave the FSM runs on */
struct list_head list; /**< Used for execution list. */
ec_dict_request_t int_dict_request; /**< Internal dictionary request. */ void (*state)(ec_fsm_slave_t *, ec_datagram_t *); /**< State function. */
ec_datagram_t *datagram; /**< Previous state datagram. */
ec_sdo_request_t *sdo_request; /**< SDO request to process. */
ec_reg_request_t *reg_request; /**< Register request to process. */
ec_foe_request_t *foe_request; /**< FoE request to process. */
off_t foe_index; /**< Index to FoE write request data. */
ec_soe_request_t *soe_request; /**< SoE request to process. */
ec_eoe_request_t *eoe_request; /**< EoE request to process. */
ec_mbg_request_t *mbg_request; /**< MBox Gateway request to process. */
ec_dict_request_t *dict_request; /**< Dictionary request to process. */ ec_fsm_coe_t fsm_coe; /**< CoE state machine. */
ec_fsm_foe_t fsm_foe; /**< FoE state machine. */
ec_fsm_soe_t fsm_soe; /**< SoE state machine. */
ec_fsm_eoe_t fsm_eoe; /**< EoE state machine. */
ec_fsm_mbg_t fsm_mbg; /**< MBox Gateway state machine. */
ec_fsm_pdo_t fsm_pdo; /**< PDO configuration state machine. */
ec_fsm_change_t fsm_change; /**< State change state machine */
ec_fsm_slave_scan_t fsm_slave_scan; /**< slave scan state machine */
ec_fsm_slave_config_t fsm_slave_config; /**< slave config state machine. */
};

slave状态机和master状态机类似,slave状态机内还包含许多子状态机。slave状态机主要目的是:

  • 主站管理从站状态
  • 主站与从站应用层(AL)通讯。比如具有EoE功能的从站,主站通过该从站下的子状态机fsm_eoe来管理主站与从站应用层的EOE通讯。

数据报对象的管理

上面简单介绍了IgH内的状态机,fsm对象内只有数据报对象的指针,那fsm工作过程中的数据报对象从哪里分配?

在master实例我们可以看到下面的数据报对象:

struct ec_master {
...
ec_datagram_t fsm_datagram; /**< Datagram used for state machines. */
...
ec_datagram_t ref_sync_datagram; /**< Datagram used for synchronizing the
reference clock to the master clock.*/
ec_datagram_t sync_datagram; /**< Datagram used for DC drift
compensation. */
ec_datagram_t sync_mon_datagram; /**< Datagram used for DC synchronisation
monitoring. */
...
ec_datagram_t ext_datagram_ring[EC_EXT_RING_SIZE];
}

这些数据报对象都是已经分配内存的(还未分配data内存),分为三类:

  • 主站状态机及子fsm使用的数据报对象:fsm_datagram

  • 外部数据报:用于slave状态机及其子状态机执行过程中动态使用

    • ext_datagram_ring[]
  • 应用数据报: 它并不固定属于某个状态机,主要通过接口提供给应用直接使用:

    • ref_sync_datagram
    • sync_datagram
    • sync64_datagram
    • sync_mon_datagram

初始化各状态机需要先为数据报数据区(data)分配内存,数据报内的数据区内存通过ec_datagram_prealloc()来分配.

int ec_datagram_prealloc(
ec_datagram_t *datagram, /**< EtherCAT datagram. */
size_t size /**< New payload size in bytes. */
)
{
if (datagram->data_origin == EC_ORIG_EXTERNAL
|| size <= datagram->mem_size)
return 0;
...... if (!(datagram->data = kmalloc(size, GFP_KERNEL))) {
......
} datagram->mem_size = size;
return 0;
}

数据区的大小为一个以太网帧中单个Ethercat数据报的最大数据大小EC_MAX_DATA_SIZE

/** Size of an EtherCAT frame header. */
#define EC_FRAME_HEADER_SIZE 2 /** Size of an EtherCAT datagram header. */
#define EC_DATAGRAM_HEADER_SIZE 10 /** Size of an EtherCAT datagram footer. */
#define EC_DATAGRAM_FOOTER_SIZE 2 /** Size of the EtherCAT address field. */
#define EC_ADDR_LEN 4 /** Resulting maximum data size of a single datagram in a frame. */
#define EC_MAX_DATA_SIZE (ETH_DATA_LEN - EC_FRAME_HEADER_SIZE \
- EC_DATAGRAM_HEADER_SIZE - EC_DATAGRAM_FOOTER_SIZE)

由于以太网帧的大小有限,因此数据报的最大大小受到限制,即以太网帧长度 1500 - ethercat头2byte- ethercat子数据报报头10字节-WKC 2字节,如图:

如果过程数据镜像的大小超过该限制,就必须发送多个帧,并且必须对映像进行分区以使用多个数据报。 Domain自动进行管理。

2.3 master状态机及数据报初始化

对状态机及数据报对象有初步认识后,我们回到ec_master.ko模块入口函数ec_init_module()主站实例初始化ec_master_init(),主要完成主站状态机初始化及数据报:

    // init state machine datagram
ec_datagram_init(&master->fsm_datagram); /*初始化数据报对象*/
snprintf(master->fsm_datagram.name, EC_DATAGRAM_NAME_SIZE, "master-fsm");
ret = ec_datagram_prealloc(&master->fsm_datagram, EC_MAX_DATA_SIZE); // create state machine object
ec_fsm_master_init(&master->fsm, master, &master->fsm_datagram); /*初始化master fsm*/

其中ec_fsm_master_init初始化master fsm和子状态机,并指定了master fsm使用的数据报对象fsm_datagram

void ec_fsm_master_init(
ec_fsm_master_t *fsm, /**< Master state machine. */
ec_master_t *master, /**< EtherCAT master. */
ec_datagram_t *datagram /**< Datagram object to use. */
)
{
fsm->master = master;
fsm->datagram = datagram;
ec_fsm_master_reset(fsm); // init sub-state-machines
ec_fsm_coe_init(&fsm->fsm_coe);
ec_fsm_soe_init(&fsm->fsm_soe);
ec_fsm_pdo_init(&fsm->fsm_pdo, &fsm->fsm_coe);
ec_fsm_change_init(&fsm->fsm_change, fsm->datagram);
ec_fsm_slave_config_init(&fsm->fsm_slave_config, fsm->datagram,
&fsm->fsm_change, &fsm->fsm_coe, &fsm->fsm_soe, &fsm->fsm_pdo);
ec_fsm_slave_scan_init(&fsm->fsm_slave_scan, fsm->datagram,
&fsm->fsm_slave_config, &fsm->fsm_pdo);
ec_fsm_sii_init(&fsm->fsm_sii, fsm->datagram);
}

初始化外部数据报队列

外部数据报队列用于从站状态机,每个状态机执行期间使用的数据报从该区域分配,下面是初始化ext_datagram_ring中每个结构:

     for (i = 0; i < EC_EXT_RING_SIZE; i++) {
ec_datagram_t *datagram = &master->ext_datagram_ring[i];
ec_datagram_init(datagram);
snprintf(datagram->name, EC_DATAGRAM_NAME_SIZE, "ext-%u", i);
}

非应用数据报队列链表,如EOE数据报会插入该队列后发送。

INIT_LIST_HEAD(&master->ext_datagram_queue);

同样初始化几个应用数据报对象,为他们分配报文数据区,就不贴代码了,需要注意的是数据报对象sync_mon_datagram,它的作用是用于同步监控,获取从站系统时间差,所以是一个BRD数据报,在此直接将数据报操作偏移地址初始化。

    ec_datagram_init(&master->sync_mon_datagram);
......
ret = ec_datagram_brd(&master->sync_mon_datagram, 0x092c, 4);
地址 名称 描述 复位值
0x092c~0x092F 0~30 系统时间差 本地系统时间副本与参考时钟系统时间值之差 0
31 符号 0:本地系统时间≥参考时钟时间
1:本地系统时间<参考时钟时间
0

另外比较重要的是将使用的网卡MAC地址放到macs[]中,在网卡驱动probe过程中根据MAC来匹配主站使用哪个网卡。

    for (dev_idx = EC_DEVICE_MAIN; dev_idx < EC_MAX_NUM_DEVICES; dev_idx++) {
master->macs[dev_idx] = NULL;
} master->macs[EC_DEVICE_MAIN] = main_mac;

2.4 初始化EtherCAT device

master协议栈主要完成EtherCAT数据报的解析和组装,然后需要再添加EtherNet报头和FCS组成一个完整的以太网帧,最后通过网卡设备发送出去。为与以太网设备驱动层解耦,igh使用ec_device_t来封装底层以太网设备,一般来说每个master只有一个ec_device_t,这个编译时配置决定:

struct ec_device
{
ec_master_t *master; /**< EtherCAT master */
struct net_device *dev; /**< 使用的网络设备 */
ec_pollfunc_t poll; /**< pointer to the device's poll function */
struct module *module; /**< pointer to the device's owning module */
uint8_t open; /**< true, if the net_device has been opened */
uint8_t link_state; /**< device link state */
struct sk_buff *tx_skb[EC_TX_RING_SIZE]; /**< transmit skb ring */
unsigned int tx_ring_index; /**< last ring entry used to transmit */
#ifdef EC_HAVE_CYCLES
cycles_t cycles_poll; /**< cycles of last poll */
#endif
#ifdef EC_DEBUG_RING
struct timeval timeval_poll;
#endif
unsigned long jiffies_poll; /**< jiffies of last poll */ // Frame statistics
u64 tx_count; /**< 发送的帧数 */
u64 last_tx_count; /**<上次统计周期发送的帧数。 */
u64 rx_count; /**< 接收的帧数 */
u64 last_rx_count; /**< 上一个统计周期收到的帧数。 */ u64 tx_bytes; /**< 发送的字节数 */
u64 last_tx_bytes; /**< 上一个统计周期发送的字节数。 */
u64 rx_bytes; /**< Number of bytes received. */
u64 last_rx_bytes; /**< Number of bytes received of last statistics cycle.
*/
u64 tx_errors; /**< Number of transmit errors. */
s32 tx_frame_rates[EC_RATE_COUNT]; /**< Transmit rates in frames/s for
different statistics cycle periods.
*/
s32 rx_frame_rates[EC_RATE_COUNT]; /**< Receive rates in frames/s for
different statistics cycle periods.
*/
s32 tx_byte_rates[EC_RATE_COUNT]; /**< Transmit rates in byte/s for
different statistics cycle periods. */
s32 rx_byte_rates[EC_RATE_COUNT]; /**< Receive rates in byte/s for
different statistics cycle periods. */ ......
};

成员*master表示改对象属于哪个master,*dev指向使用的以太网设备net_device,poll该网络设备poll函数,tx_skb[]以太网帧发送缓冲区队列,需要发送的以太网帧会先放到该队里,tx_ring_index管理tx_skb[],以及一些网络统计变量,下面初始化ec_device_t对象:

/*\master\master.c*/
for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master);
dev_idx++) {
ret = ec_device_init(&master->devices[dev_idx], master);
if (ret < 0) {
goto out_clear_devices;
}
}
/*\master\device.c*/
int ec_device_init(
ec_device_t *device, /**< EtherCAT device */
ec_master_t *master /**< master owning the device */
)
{
int ret;
unsigned int i;
struct ethhdr *eth;
.... device->master = master;
device->dev = NULL;
device->poll = NULL;
device->module = NULL;
device->open = 0;
device->link_state = 0;
for (i = 0; i < EC_TX_RING_SIZE; i++) {
device->tx_skb[i] = NULL;
}
...... ec_device_clear_stats(device);
......
for (i = 0; i < EC_TX_RING_SIZE; i++) {
if (!(device->tx_skb[i] = dev_alloc_skb(ETH_FRAME_LEN))) {
......
} // add Ethernet-II-header
skb_reserve(device->tx_skb[i], ETH_HLEN);
eth = (struct ethhdr *) skb_push(device->tx_skb[i], ETH_HLEN);
eth->h_proto = htons(0x88A4);
memset(eth->h_dest, 0xFF, ETH_ALEN);
}
.....
}

主要关注分配以太网帧发送队列内存tx_skb[],并填充Ethernet报头中的以太网类型字段为0x88A4,目标MAC地址0xFFFFFFFF FFFF,对于源MAC地址、sk_buff所属网络设备、ec_device_t对象使用的网络设备net_device,将在网卡驱动初始化与master建立联系过程中设置。

2.5 设置IDLE 线程的发送间隔:

ec_master_set_send_interval(master, 1000000 / HZ);

根据网卡速率计算:

void ec_master_set_send_interval(
ec_master_t *master, /**< EtherCAT master */
unsigned int send_interval /**< Send interval */
)
{
master->send_interval = send_interval; //发送间隔 us
master->max_queue_size =
(send_interval * 1000) / EC_BYTE_TRANSMISSION_TIME_NS;
master->max_queue_size -= master->max_queue_size / 10;
}

100Mbps网卡发送一字节数据需要的时间EC_BYTE_TRANSMISSION_TIME_NS: 1/(100 MBit/s / 8 bit/byte) = 80 ns/byte.

2.6 初始化字符设备

由于主站位于内核空间,用户空间应用与主站交互通过字符设备来交互;

创建普通字符设备,给普通linux应用和Ethercat tool使用。若使用xenomai或RTAI,则再创建实时字符设备,提供给实时应用使用。

	......
master->class_device = device_create(class, NULL,
MKDEV(MAJOR(device_number), master->index), NULL,
"EtherCAT%u", master->index);
......
#ifdef EC_RTDM
// init RTDM device
ret = ec_rtdm_dev_init(&master->rtdm_dev, master);
...
#endif

到这里明白了IgH中的状态机与数据报之间的关系,主站对象也创建好了,但是主站还没有网卡设备与之关联,主站也还没有工作,下面简单看一下ecdev_offer流程。

关于网卡驱动代码详细解析推荐这两篇文章:

Monitoring and Tuning the Linux Networking Stack: Sending Data

Monitoring and Tuning the Linux Networking Stack: Receiving Data

3 网卡

  1. 网卡probe

    static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
    {
    ......
    adapter->ecdev = ecdev_offer(netdev, ec_poll, THIS_MODULE);
    if (adapter->ecdev) { /*注册打开ec_net设备*/
    err = ecdev_open(adapter->ecdev);
    .....
    adapter->ec_watchdog_jiffies = jiffies;
    } else { /*注册普通网络设备*/
    ......
    err = register_netdev(netdev);
    ......
    }
    ......
    }
  2. 给主站提供网络设备:ecdev_offer

    根据MAC地址找到master下的ec_device_t对象

        device->dev = net_dev;
    device->poll = poll;
    device->module = module;

    上面我们只设置了ec_device_t->tx_skb[]中sk_buff的以太网类型和目的地址,现在继续填充源MAC地址为网卡的MAC地址、sk_buff所属的net_device:

        for (i = 0; i < EC_TX_RING_SIZE; i++) {
    device->tx_skb[i]->dev = net_dev;
    eth = (struct ethhdr *) (device->tx_skb[i]->data);
    memcpy(eth->h_source, net_dev->dev_addr, ETH_ALEN);
    }
  3. 调用网络设备接口打开网络设备

int ec_device_open(
ec_device_t *device /**< EtherCAT device */
)
{
int ret;
.....
ret = device->dev->open(device->dev);
if (!ret)
device->open = 1;
....
return ret;
}
  1. 当master下的所有的网络设备都open后,master从ORPHANED转到IDLE阶段
int ec_master_enter_idle_phase(
ec_master_t *master /**< EtherCAT master */
)
{
int ret;
ec_device_index_t dev_idx;
......
master->send_cb = ec_master_internal_send_cb;
master->receive_cb = ec_master_internal_receive_cb;
master->cb_data = master; master->phase = EC_IDLE; /*更新master状态*/ // reset number of responding slaves to trigger scanning
for (dev_idx = EC_DEVICE_MAIN; dev_idx < ec_master_num_devices(master);
dev_idx++) {
master->fsm.slaves_responding[dev_idx] = 0;
} ret = ec_master_nrthread_start(master, ec_master_idle_thread,
"EtherCAT-IDLE");
....
return ret;
}

其中主要设置master发送和接收回调函数,应用通过发送和接收数据时,将通过这两接口直接发送和接收。创建master idle线程ec_master_idle_thread。

4 IDLE阶段内核线程

Idle phase 主站接受以太网设备所有请求,但没有应用request_master时处于IDLE阶段。主站实例运行其状态机,自动扫描总线上的从站,并从应用空间接口执行挂起操作(如SDO访问)。此时命令行工具能够访问总线,但无法进行过程数据交换,因为还缺少总线配置。

整个过程由内核线程ec_master_idle_thread完成。

static int ec_master_idle_thread(void *priv_data)
{
ec_master_t *master = (ec_master_t *) priv_data;
int fsm_exec;
#ifdef EC_USE_HRTIMER
size_t sent_bytes;
#endif // send interval in IDLE phase
ec_master_set_send_interval(master, 250000 / HZ); while (!kthread_should_stop()) {
// receive
ecrt_master_receive(master);
......
// execute master & slave state machines
......
fsm_exec = ec_fsm_master_exec(&master->fsm); ec_master_exec_slave_fsms(master);
......
if (fsm_exec) {
ec_master_queue_datagram(master, &master->fsm_datagram);
}
// send
ecrt_master_send(master); sent_bytes = master->devices[EC_DEVICE_MAIN].tx_skb[
master->devices[EC_DEVICE_MAIN].tx_ring_index]->len;
up(&master->io_sem); if (ec_fsm_master_idle(&master->fsm)) {
ec_master_nanosleep(master->send_interval * 1000);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
} else {
ec_master_nanosleep(sent_bytes * EC_BYTE_TRANSMISSION_TIME_NS);
}
} EC_MASTER_DBG(master, 1, "Master IDLE thread exiting...\n"); return 0;
}

先简单介绍到这,敬请关注后续文章。。。。

【原创】EtherCAT主站IgH解析(一)--主站初始化、状态机与EtherCAT报文的更多相关文章

  1. 一种基于uCos-II操作系统和lwIP协议栈的IEEE-1588主站以及基于该主站的报文处理方法

    主站以及应用于电力系统的支持IEEE‐1588协议的主时钟(IEEE‐1588主站)的实现方法.该方法是在一个低成本的硬件平台上,借助uCos‐II操作系统和TCP/IP的协议栈,对以太网数据进行了分 ...

  2. 【原创】xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(一)--实时端socket创建流程

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 1.概述 上篇文章xenomai内核解析--实时IP ...

  3. 【原创】xenomai内核解析--xenomai与普通linux进程之间通讯XDDP(二)--实时与非实时关联(bind流程)

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有问题,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 1.概述 上篇文章介绍了实时端socket创建和配置 ...

  4. 【原创】xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正. 1. 引出问题 上一篇文章xenomai内核解析--双核系统调用(一)以X86处理器为例,分析了xenomai内核调用的流程, ...

  5. SpringMVC解析3-DispatcherServlet组件初始化

    在spring中,ContextLoaderListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的 ...

  6. Feign源码解析系列-核心初始化

    开始 初始化Feign客户端当然是整个过程中的核心部分,毕竟初始化完毕就等着调用了,初始化时候准备的什么,流程就走什么. 内容 从上一篇中,我们已经知道,对于扫描到的每一个有@FeignClient, ...

  7. 【原创】JAVA面试解析(有赞一面)

    本文的题目出自博客 http://www.54tianzhisheng.cn/2018/07/12/youzan/ 但是作者没有给出答案,博主斗胆来制作答案版. 引言 说在前面的话: 本文适合人群:急 ...

  8. (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决

    boost.property_tree可以用来解析xml和json文件,我主要用它来解析xml文件,它内部封装了号称最快的xml解析器rapid_xml,其解析效率还是很好的.但是在使用过程中却发现各 ...

  9. (原创)关于FFmpeg的一些有关的初始化默认值的问题

    最近手头上要做一个媒体格式分析库,能解析文件,流的视频格式,编码格式等一些重要的视频参数能进行尽量多的提取.当我们做媒体相关方面的东西,自然而然就会想到FFmpeg这个强大的开源媒体库,所以我们肯定会 ...

随机推荐

  1. Java实现发送HTTP的POST请求,返回数据以及请求状态

    /** * @param url:请求url * @param content: 请求体(参数) * @return errorStr:错误信息;status:状态码,response:返回数据 */ ...

  2. 34.vsftpd服务程序--虚拟用户模式

    1.创建用于进行FTP 认证的用户数据库文件,其中奇数行为账户名,偶数行为密码. [root@localhost ~]# cd /etc/vsftpd/ [root@localhost vsftpd] ...

  3. MacOS Big Sur开HiDPI

    我自己的环境: 开启hidpi的目的是为了让显示更加细腻,代价是缩小了显示范围. 自己在网上看了很多帖子,也尝试了几种,有些方法已经不再适合Big Sur系统了,所以本文提供一种可用的,在Big Su ...

  4. springboot基础配置-->Properties配置

    Spring Boot项目中的application.properties配置文件一共可以出现在如下4个位置: 项目根目录下的config文件夹中. 项目根目录下. classpath下的config ...

  5. springboot中的parent依赖作用详解

    最近在阅读springboot+vue全栈开发实战,松哥编写的,虽然比较简单,各种技术没有深入讲解,但是还是可以看看的,特别是我这个前端菜鸟哈哈,以后可是要学习全栈的,把书中看到的不会的地方会记录下笔 ...

  6. linux(7)top命令详细解释

    top命令 Linux top命令用于实时显示 process 的动态. top参数详解 第一行,任务队列信息 系统当前时间:13:52:56 系统开机后到现在的总运行时间:up 66 days,8m ...

  7. git的几种实用操作(合并代码与暂存复原代码)

    总述     git工具也用了很久,自己也写了几篇使用教程,今天继续给大家分享一些我工作中使用过的git操作. 1.git合并远程仓库的代码 2.git stash保存当前的修改 这两种情况大家应该都 ...

  8. 2019 ICPC Asia Nanjing Regional

    2019 ICPC Asia Nanjing Regional A - Hard Problem 计蒜客 - 42395 若 n = 10,可以先取:6,7,8,9,10.然后随便从1,2,3,4,5 ...

  9. 2019牛客暑期多校训练营(第七场)E-Find the median(思维+树状数组+离散化+二分)

    >传送门< 题意:给n个操作,每次和 (1e9范围内)即往数组里面插所有 的所有数,求每次操作后的中位数思路:区间离散化然后二分答案,因为小于中位数的数字恰好有个,这显然具有单调性.那么问 ...

  10. 2019 Multi-University Training Contest 2 I.I Love Palindrome String(回文自动机+字符串hash)

    Problem Description You are given a string S=s1s2..s|S| containing only lowercase English letters. F ...