在linux系统中,网卡及对应的net_device结构体实例是由上层的网络子系统操作的,ecdev_offer()的作用是将网卡转交给ehterlab master操作。
一、预备知识net_device结构体
linux系统中,每一个网卡对应一个net_device结构体的实例,一个网卡要能够被内核识别并收发数据,一般需要经过net_device结构体的创建、初始化、注册到内核、打开设备等步骤:
其中,net_device的创建一般在网卡驱动程序的probe函数中完成。
net_device是一个很大的结构体,包含很多成员变量和函数指针,因此初始化工作分散在多处完成,与网卡硬件细节无关的通用数据域由内核的网络子系统初始化,如发送队列长度、MTU等,与网卡硬件相关的数据域由网卡的驱动程序初始化,如MAC地址、中断号等。
初始化完成后,net_device将被注册到内核中,并加入到dev_base_head开始的双向链表中,之后上层的协议(如TCP/IP)就可以通过调用net_device中的函数完成数据的收发。
然而,EtherCAT数据包不需要经过TCP/IP协议栈,因此net_device结构体不需要注册到内核中,而是由ecdev_offer注册到etherlab master中。
二、预备知识Socket Buffer
linux网络子系统中,每一个要发送和接收的数据包都对应一个Socket Buffer。
当要发送数据包时,内核的套接字层会分配一个Socket Buffer,用来存储将要发送的数据包。
当网卡收到一个数据包时,网卡的驱动程序会调用dev_alloc_skb()函数分配一个Socket Buffer,用来接收网卡中的数据包。
在IGH Etherlab中,在初始化函数ec_device_init()中提前分配了固定个数的Socket Buffer:
for (i = 0; i < EC_TX_RING_SIZE; i++) {
if (!(device->tx_skb[i] = dev_alloc_skb(ETH_FRAME_LEN))) {
EC_MASTER_ERR(master, "Error allocating device socket buffer!\n");
ret = -ENOMEM;
goto out_tx_ring;
}
三、ecdev_offer
以e1000e驱动为例,在drivers\net\ethernet\intel\e1000e\netdev.c中的e1000_probe()函数中调用ecdev_offer(),将网卡驱动对应的net_device结构体实例注册到etherlab master中:
static int __devinit e1000_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
......
adapter->ecdev = ecdev_offer(netdev, ec_poll, THIS_MODULE);
if (adapter->ecdev) { //如果网卡作为ethercat 驱动则在探测后直接打开
adapter->ec_watchdog_jiffies = jiffies;
if (ecdev_open(adapter->ecdev)) {
ecdev_withdraw(adapter->ecdev);
goto err_register;
}
} else { //如果作为通用驱动则注册到内核
strlcpy(netdev->name, "eth%d", sizeof(netdev->name));
err = register_netdev(netdev);
if (err)
goto err_register;
/* carrier off reporting is important to ethtool even BEFORE open */
netif_carrier_off(netdev);
}
......
}
ec_device_t *ecdev_offer(
struct net_device *net_dev, /**< net_device to offer */
ec_pollfunc_t poll, /**< device poll function */
struct module *module /**< pointer to the module */
)
{
......
for (dev_idx = EC_DEVICE_MAIN;
dev_idx < ec_master_num_devices(master); dev_idx++) {
if (!master->devices[dev_idx].dev
&& (ec_mac_equal(master->macs[dev_idx], net_dev->dev_addr)
|| ec_mac_is_broadcast(master->macs[dev_idx]))) {
EC_INFO("Accepting %s as %s device for master %u.\n",
str, ec_device_names[dev_idx != 0], master->index);
ec_device_attach(&master->devices[dev_idx],
net_dev, poll, module);
up(&master->device_sem);
snprintf(net_dev->name, IFNAMSIZ, "ec%c%u",
ec_device_names[dev_idx != 0][0], master->index);
return &master->devices[dev_idx]; // offer accepted
}
}
up(&master->device_sem);
EC_MASTER_DBG(master, 1, "Master declined device %s.\n", str);
}
return NULL; // offer declined
}
其中,ecdev_offer()中最重要的是调用ec_device_attach()函数:
void ec_device_attach(
ec_device_t *device, /**< EtherCAT device */
struct net_device *net_dev, /**< net_device structure */
ec_pollfunc_t poll, /**< pointer to device's poll function */
struct module *module /**< the device's module */
)
{
unsigned int i;
struct ethhdr *eth;
ec_device_detach(device); // resets fields
device->dev = net_dev;
device->poll = poll; //接收EtherCAT数据包所用的函数
device->module = module;
//初始化发送队列中的Socket Buffer, 使其指向网卡对应的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);
}
}
至此,etherlab就可以绕过linux系统中的TCP/IP协议栈,直接通过网卡驱动程序所提供的函数收发EtherCAT数据包了。