intel dpdk api pci设备驱动注册和初始化过程

声明:此文档只做学习交流使用,请勿用作其他商业用途

author:朝阳_tony
E-mail : [email protected]
Create Date: 2013-7-18 18:42:38  Thursday
Last Change: 2013-7-19 11:10:45  Friday

转载请注明出处:http://blog.csdn.net/linzhaolove


此文中源码可以去http://dpdk.org/dev 网页中下载;更多官方文档请访问http://dpdk.org



1、 安装dpdk提供的驱动


dpdk  提供了新的关于intel 网卡的驱动,我们在成功编译源码之后,就可以安装;

具体请看我的写的intel  dpdk  安装部署的文章,
http://blog.csdn.net/linzhaolove/article/details/9251433


我们在安装了,标准内核的uio,还有 dpdk 提供的igb_uio.ko 驱动后,(x86_64-default-linuxapp-gcc/kmod/igb_uio.ko   )

会在/dev/目录下产生几个uio的设备文件;

# ls /dev/uio*
/dev/uio0  /dev/uio1  /dev/uio2  /dev/uio3
由于我有4个intel 的网卡,因此下面产生的4个设备文件描述符;


具体igb_uio.ko  和 uio 之间的关系,如遇机会我会在后期讲解,如果你急于想了解uio,请上网自己学习,再自己看igb的 驱动源码,dpdk很多数据处理都是在用户态空间完成,因此它的驱动很简单;



2、发现pci设备

我们用dpdk 提供的实例程序l2fwd 做参考,我们去看他是怎样调用的;

打开main.c 文件;vim examples/l2fwd/main.c


首先会在main函数中调用rte_eal_init()进行dpdk的底层结构初始化在,在这个函数中会调用到rte_eal_pci_init()函数,去扫描/sys/bus/pci/devices/下的所以设备,并将有效信息保存到device_list 列表中;

struct pci_device_list device_list; /**< Global list of PCI devices. */

rte_eal_pci_init()代码的详细介绍请看我写的文章http://blog.csdn.net/linzhaolove/article/details/9358085



3、驱动注册

在l2fwd 实例code中,调用rte_igb_pmd_init()函数,去完成回调函数的初始化,底层网络数据包处理函数的管理等;我用的是千兆网卡,因此用这个函数,如果是10G的网卡那就要用到rte_ixgbe_pmd_init()这个函数,我这只讲千兆网卡,其实他们的初始化流程大致相同;


1)介绍一下rte_igb_pmd_init()函数

这个函数在dpdk/lib/librte_pmd_igb/e1000_ethdev.c文件中定义;


 rte_eth_driver_register(&rte_igb_pmd);
函数中调用dirver注册函数将 rte_igb_pmd 这个结构体传递进去,我们看一下这个结构体的赋值;

static struct eth_driver rte_igb_pmd = {
    {
        .name = "rte_igb_pmd",
        .id_table = pci_id_igb_map,
        .drv_flags = RTE_PCI_DRV_NEED_IGB_UIO,
    },
    .eth_dev_init = eth_igb_dev_init,
    .dev_private_size = sizeof(struct e1000_adapter),
};

其中的pci_id_igb_map保存的是设备厂商的id和设备id;会在后面的驱动和设备匹配时用到;

里面传递的eth_igb_dev_init()  这个函数是pci设备的初始化函数;我们稍后会讲到它;这是1000兆网卡的驱动,所以它的私有数据.dev_private_size = sizeof(struct e1000_adapter),保存的是千兆的结构体信息;


2)介绍rte_eth_driver_register()  函数

这个函数在dpdk/lib/librte_ether/rte_ethdev.c中定义;我们看一下它的源码;

    eth_drv->pci_drv.devinit = rte_eth_dev_init;
    rte_eal_pci_register(ð_drv->pci_drv);
它的源码也很简单,将rte_eth_dev_init函数的指针赋值给来的devinit,这个函数会在pci这个嗅探的时候调用,进行初始化;

而下面的rte_eal_pci_register函数,是将pci_drv这个设备驱动相关的结构体,插入到driver_list列表中,这个驱动列表是在rte_eal_pci_init()函数中,与deivce_list列表,一块初始化的;


4、设备驱动初始化加载

接下来l2fwd 中,调用rte_eal_pci_probe()函数,去完成驱动与设备之间的加载;

    TAILQ_FOREACH(dev, &device_list, next)
        pci_probe_all_drivers(dev);
通过便利设备链表去查询对应的设备;
    TAILQ_FOREACH(dr, &driver_list, next) {
        if (rte_eal_pci_probe_one_driver(dr, dev))
查看一下设备列表的中的设备与驱动列表的中的驱动,是否想匹配,在rte_eal_pci_probe_one_driver函数中去完成;


1)介绍rte_eal_pci_probe_one_driver函数

函数在dpdk/lib/librte_eal/linuxapp/eal/eal_pci.c文件中定义;

 uio_status = pci_uio_check_module(module_name);
代码开始先核查igb_uio驱动是不安装,module_name = IGB_UIO_NAME=igb_uio ; 这个驱动,就是文章开始我们安装的igb_uio.ko驱动;

所谓的核查驱动是否安装,其实就是打开/proc/modules文件,去遍历查找看是否存在;

# cat /proc/modules
rte_kni 282604 0 - Live 0xffffffffa03b3000
igb_uio 6483 0 - Live 0xffffffffa0054000

接下来在核查id_table表,看是否有于设备想匹配的vendor_id,device_id,subsystem_vendor_id,subsystem_device_id等;


而这个id_table表是在文章上面初始化rte_igb_pmd这个结构体赋值的;


2)分离系统给予的驱动

            /* unbind current driver, bind ours */
                if (pci_unbind_kernel_driver(dev) < 0)
一般系统是用于设备的驱动的,而且一般系统启动时,已经当驱动赋给了设备,而这个驱动并不是我们想用的饿,因此,当我们找到了设备,我们就要分开原来的驱动,绑定我们新安装的驱动;
pci_unbind_kernel_driver函数去完成这个任务;

其实是打开/sys/bus/pci/devices目录下相应设备的目录下的/driver/unbind   文件,将设备信息写进去;

例如我的一个设备是0000:15:00.0,就去打开设备目录的驱动目录下的unbind  文件,

/sys/bus/pci/devices/0000:15:00.0/driver/unbind
将设备的,loc->domain, loc->bus, loc->devid, loc->function , 信息写进去;对应的就是domain=0000  bus =15 ,devid=00,function=0;


3)绑定dpdk提供的驱动

if (pci_uio_bind_device(dev, module_name) < 0)
调用pci_uio_bind_device函数去绑定新的设备驱动 ;

打开/sys/bus/pci/drivers/igb_uio/new_id 文件, 将dev->id.vendor_id, dev->id.device_id 设备厂商id很设备id写入这个文件;


打开/sys/bus/pci/drivers/igb_uio/bind 文件, 将loc->domain, loc->bus, loc->devid, loc->function   这个几个参数写入;完成驱动的绑定;


4)设备资源的映射

驱动绑定好后,我们要怎样使用这些资源呢?

接下来的代码是调用pci_uio_map_resource()函数去映射设备资源;

            /* map the NIC resources */
            if (pci_uio_map_resource(dev) < 0)
介绍一下pci_uio_map_resource(),这个函数用来映射pci设备虚拟内存的资源;


首先查找设备中包含的uio目录,例如我的几个设备的对应的目录为

/sys/bus/pci/devices/0000\:15\:00.0/uio/uio0/
对应该的两个设备是也对应着两个uio的设备描述符;找到上面的目录后,然后再分别打开目录下 maps/map0/  子目录下的的size  和offset 两个文件,取出设备可以映射的的尺寸大小,和对应的映射偏移量;

/sys/bus/pci/devices/0000\:15\:00.0/uio/uio0/maps/map0/offset
/sys/bus/pci/devices/0000\:15\:00.0/uio/uio0/maps/map0/size


接下来去打开对应的设备文件描述符/dev/uioX , 打开相应的文件去映射;

    /* open and mmap /dev/uioX */
    rte_snprintf(devname, sizeof(devname), "/dev/uio%u", uio_num);
    mapaddr = pci_map_resource(dev, NULL, devname, offset, size);
这调用的pci_map_resource函数,在其内部完成资源映射的同时,还要将中断设备的文件描述符赋值给对应的中断句柄;这个中断句柄会在中断处理时用到,这一点很重要;

关于dpdk中断模块的介绍请看http://blog.csdn.net/linzhaolove/article/details/9309855

        dev->intr_handle.fd = fd;
        dev->intr_handle.type = RTE_INTR_HANDLE_UIO;
最后将映射到的uio的信息保存到一个新分配的uio_res结构体重,然后保存到uio_res_list链表中; 这个链表是在rte_eal_pci_init()中提前预留创建的;

    memcpy(&uio_res->pci_addr, &dev->addr, sizeof(uio_res->pci_addr));

    TAILQ_INSERT_TAIL(uio_res_list, uio_res, next);


5) 初始化pci驱动;

调用devinit这个函数指针去初始化驱动,这个函数指针指的是 文章第3节小2 部分,赋值给他的函数rte_eth_dev_init;

        /* call the driver devinit() function */
        return dr->devinit(dr, dev);


下面详细介绍一下 rte_eth_dev_init()

eth_dev = rte_eth_dev_allocate();
获取一个全局的rte_eth_devices[nb_ports] 地址空间,并将nb_ports目前设备端口计数加1;


    /* init user callbacks */
    TAILQ_INIT(&(eth_dev->callbacks));
初始一个中断回调函数列表;

    /* Invoke PMD device initialization function */
    diag = (*eth_drv->eth_dev_init)(eth_drv, eth_dev);
去初始化一个 PMD 模式的pci设备驱动,这调用的函数指针eth_dev_init 是在文章开始初始化的结构体中赋值的eth_igb_dev_init()函数;


下面介绍eth_igb_dev_init()函数;

函数在dpdk/lib/librte_pmd_igb/e1000_ethdev.c 文件中定义;

    eth_dev->dev_ops = ð_igb_ops;
    eth_dev->rx_pkt_burst = ð_igb_recv_pkts;
    eth_dev->tx_pkt_burst = ð_igb_xmit_pkts;
将eth_igb_ops这个结构体赋值给了dev_ops , 里面主要包含很多的函数调用指针,如eth_igb_configure,eth_igb_start,eth_igb_stop,等一个pci设备操作函数都提前初始化在eth_igb_ops 这个结构体中;这个结构体定义在dpdk/lib/librte_pmd_igb/e1000_ethdev.c 文件中;

接下来将网卡数据的接收和发送函数也赋值给了eth_dev这个结构体中eth_igb_recv_pkts(),eth_igb_xmit_pkts();

 if (e1000_setup_init_funcs(hw, TRUE) != E1000_SUCCESS) {
调用这函数去完成剩余函数指针的初始化,为后期的设备运行做准备;

if (e1000_read_mac_addr(hw) != 0)
获取实际的mac地址,


最后将中断处理函数,注册进入中断链表;

    rte_intr_callback_register(&(pci_dev->intr_handle),
        eth_igb_interrupt_handler, (void *)eth_dev);



技术水平有待提高,如果文章有错误的地方希望读者指正,相互交流,互相学习;O(∩_∩)O~

你可能感兴趣的:(intel,dpdk,intel,dpdk,学习交流)