DPDK设备驱动的匹配和初始化

摘要:
所选代码基于DPDK-17.02。数据包的驱动程序初始化在rte_eal_init()过程分为两个阶段。然后依次遍历队列中的每个PCI设备,并进行devargs_List比较以检查列表中是否有设备。如果它们在黑名单中,它们将不会被初始化。然后调用pci_probe_all_Drivers()初始化每个允许的设备。

前言:DPDK使用了UIO(用户空间I/O)的机制,跳过内核态的网卡驱动,转而使用用户态的收发包驱动,从驱动到内存和数据包,继而到数据包的处理,这是一个完整的收发包流程。这篇主要介绍设备驱动的初始化,和收发包的处理。所选代码以DPDK-17.02版本为依据。


数据包的驱动初始化是在rte_eal_init()进行的,总体上分为2个阶段进行。

  • 1.第一阶段是rte_eal_pci_init(),主要是获取系统中的设备PCI。
  • 2.第二阶段是rte_eal_pci_probe(),这个阶段做的事比较多,匹配对应的设备驱动,分配设备,并对设备进行初始化。

我们就按照这个顺序进行介绍。

  • <1>.先看rte_eal_init()这个函数,了解这一阶段的处理过程。

    在函数中,调用了rte_eal_pci_scan(),来扫描系统目录里的PCI设备。默认的扫描目录是#define SYSFS_PCI_DEVICES "/sys/bus/pci/devices",就是依次读取目录下的每一个文件名,解析PCI的地址信息,填充地址信息。

    然后调用了pci_scan_one()来进行设备信息的填充,挂接设备。先分配了一个PCI设备结构,注意:此处分配的是PCI设备结构,并不是rte_eth_dev设备。前者标识一个PCI设备,后者标识一个网卡设备。然后依次读取每个PCI设备目录下的vendor,device等文件,填充刚分配出来的PCI设备结构。接下来使用pci_get_kernel_driver_by_path()获取设备驱动的类型。比如使用82599的网卡,就会看到类型为igb_uio,设置对应的驱动类型。

    if (!ret) {
    	if (!strcmp(driver, "vfio-pci"))
    		dev->kdrv = RTE_KDRV_VFIO;
    	else if (!strcmp(driver, "igb_uio"))
    		dev->kdrv = RTE_KDRV_IGB_UIO;
    	else if (!strcmp(driver, "uio_pci_generic"))
    		dev->kdrv = RTE_KDRV_UIO_GENERIC;
    	else
    		dev->kdrv = RTE_KDRV_UNKNOWN;
    } else
    	dev->kdrv = RTE_KDRV_NONE;
    

    最后,把PCI设备挂在pci_device_list中:如果设备队列是空的,则直接挂上,如果不是空的,则按照PCI地址排序后挂接在队列中。

    这样,第一阶段的工作就做完了,主要是扫描PCI的所有设备,填充设备信息,挂接在队列中。

  • <2>.rte_eal_pci_probe()函数进入了第二阶段的初始化。
    先进行了一个设备参数类型的检查,rte_eal_devargs_type_count(),在这里又涉及到另一个变量---devargs_list,这个全局变量记录着哪些设备的PCI是在白或者黑名单里面,如果是在黑名单里,后面就不进行初始化。这个devargs_list的添加注册是在参数解析部分,-w,-b参数指定的名单。

    然后依次遍历队列中的每个PCI设备,和devargs_list比较,查看是否有设备在列表中,如果在黑名单中,就不进行初始化。

    之后调用pci_probe_all_drivers()对每个允许的设备进行初始化。

    TAILQ_FOREACH(dr, &pci_driver_list, next) {
    	rc = rte_eal_pci_probe_one_driver(dr, dev);
    	if (rc < 0)
    		/* negative value is an error */
    		return -1;
    	if (rc > 0)
    		/* positive value means driver doesn't support it */
    		continue;
    	return 0;
    }
    

    和每个注册的驱动进行比较,注册的驱动都挂接在pci_driver_list中,驱动的注册是通过下面的一段代码实现的

    #define RTE_PMD_REGISTER_PCI(nm, pci_drv) 
    RTE_INIT(pciinitfn_ ##nm); 
    static void pciinitfn_ ##nm(void) 
    {
    	(pci_drv).driver.name = RTE_STR(nm);
    	rte_eal_pci_register(&pci_drv); 
    } 
    RTE_PMD_EXPORT_NAME(nm, __COUNTER__)
    

    这里注意注册函数的类型为析构函数,gcc的补充。它是在main函数之前就执行的,所以,在main之前,驱动就已经注册好了。

    #define RTE_INIT(func) 
    static void __attribute__((constructor, used)) func(void)
    

    进一步查看的话,发现系统注册了这么几种类型的驱动:
    (1).rte_igb_pmd
    (2).rte_igbvf_pmd
    (3).rte_ixgbe_pmd
    .....

    如rte_ixgbe_pmd驱动

    static struct eth_driver rte_ixgbe_pmd = {
    .pci_drv = {
    	.id_table = pci_id_ixgbe_map,
    	.drv_flags = RTE_PCI_DRV_NEED_MAPPING | RTE_PCI_DRV_INTR_LSC,
    	.probe = rte_eth_dev_pci_probe,
    	.remove = rte_eth_dev_pci_remove,
    },
    .eth_dev_init = eth_ixgbe_dev_init,
    .eth_dev_uninit = eth_ixgbe_dev_uninit,
    .dev_private_size = sizeof(struct ixgbe_adapter),
    };
    

    其中的id_table表中就存放了各种支持的ixgbe设备的vendor号等详细信息。

    接下来,自然的,如果匹配上了,就调用对应的驱动probe函数。进入rte_eal_pci_probe_one_driver()函数进行匹配。
    当匹配成功后,对PCI资源进行映射--rte_eal_pci_map_device(),这个函数就不进行细细分析了。
    最重要的地方到了,匹配成功后,就调用了dr->probe函数,对于ixgbe驱动,就是rte_eth_dev_pci_probe()函数,我们跳进去看看这个probe函数。
    首先检查进程如果为RTE_PROC_PRIMARY类型的,那么就分配一个rte_eth_dev设备,调用rte_eth_dev_allocate(),分配可用的port_id,然后如果rte_eth_dev_data没有分 配,则一下子分配RTE_MAX_ETHPORTS个这个结构,这个结构描述了每个网卡的数据信息,并把对应port_id的rte_eth_dev_data[port_id]关联到新分配的设备上。

    设备创建好了以后,就给设备的私有数据分配空间,

    eth_dev->data->dev_private = rte_zmalloc("ethdev private structure",
    			  eth_drv->dev_private_size,
    			  RTE_CACHE_LINE_SIZE);
    

    然后填充设备的device,driver信息等。最后调用设备的初始化函数--eth_drv->eth_dev_init,这在ixgbe驱动中,是eth_ixgbe_dev_init()

    从这个初始化函数,进入最后的初始化环节。
    要知道的一点是:在这个函数中,很多的工作肯定还是填充分配的设备结构体。先填充了设备的操作函数,以及非常重要的收发包函数

    eth_dev->dev_ops = &ixgbe_eth_dev_ops;
    eth_dev->rx_pkt_burst = &ixgbe_recv_pkts;
    eth_dev->tx_pkt_burst = &ixgbe_xmit_pkts;
    eth_dev->tx_pkt_prepare = &ixgbe_prep_pkts;
    

    再检查如果不是RTE_PROC_PRIMARY进程,则只要检查一下收发函数,并不进一步设置。
    然后拷贝一下pci设备的相关信息

    rte_eth_copy_pci_info(eth_dev, pci_dev);
    eth_dev->data->dev_flags |= RTE_ETH_DEV_DETACHABLE;
    
    /* Vendor and Device ID need to be set before init of shared code */
    hw->device_id = pci_dev->id.device_id;
    hw->vendor_id = pci_dev->id.vendor_id;
    hw->hw_addr = (void *)pci_dev->mem_resource[0].addr;
    hw->allow_unsupported_sfp = 1;
    

    接下来针对对应的设备,调用ixgbe_init_shared_code()根据hw->device_id来初始化特定的设备的MAC层操作函数集,ixgbe_mac_operations,如82599设备。

    上面的操作都完成后,就可以调用ixgbe_init_hw()对硬件进行初始化了,初始化的函数在上一步MAC层函数操作集已经初始化。

    然后重置设备的硬件统计,分配MAC地址,最后初始化一下各种过滤条件。

    Done!!整个PCI驱动的匹配和初始化过程就完成了。

免责声明:文章转载自《DPDK设备驱动的匹配和初始化》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ios开发 打开第三方app模拟文件上传(一):手动文件上传下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

执行程序的内存分布总结

以下内容为各方资料汇总 所以逻辑顺序不大清晰 一般认为在c中分为这几个存储区:     1. 栈--有编译器自动分配释放         2. 堆--一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收         3. 全局区(静态区)-- 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的...

Node.js server使用

一、创建项目 #创建项目目录 cd /data mkdir webroot cd webroot #初始化git git init vim .gitignore 输入: node_modules/ 保存: :wq #初始化npm,生成package.json npm init #安装express npm install -D express #...

STM32 + UIP + ENC28J60 实现TCP 简单通讯

MCU: STM32F103C6T6 背景 上次介绍了怎么把UIP移植到STM32中来,并最后实现一个ping操作,这次在上次基础上实现MCU当TCP服务端,电脑当客户端通过TCP端链接MCU,实现通讯。 为保证程序尽量精简,程序在接受到TCP数据后,会原封不动返回给客户端(电脑), 并通过串口打印。 在使用UIP TCP功能前,需要可以让MCU获取当前时...

Red5源代码分析

原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RTMPMinaTransport的start()方法,该方法会开启rmtp的socket监听端口(默认是1935),然后使用mina(apache的io操作...

QNX 实时操作系统(Quick Unix)

Gordon Bell和Dan Dodge在1980年成立了Quantum Software Systems公司,他们根据大学时代的一些设想写出了一个能在IBM PC上运行的名叫QUNIX(Quick UNIX)的系统,直到AT&T发律师函过来才把名字改成QNX。 中文名 QNX 实时操作系统 POSⅨ 规范 系统 嵌入式系统 目录...

Java集合之ConcurrentHashMap解析

上一篇介绍了HashMap的数据结构:数组+单链表(jdk 1.8,当链表长度达到8后,链表将会被转换为红黑树结构)。日常开发中我们经常使用,随着业务规模、场景的不断复杂发展,多线程开发越来越多的进入到我们日常开发中,那么问题就来了,HashMap是线程安全的吗?答案是否定的,保证HashMap的线程安全需要我们开发中自行维护。那么有没有线程安全的集合框架...