searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

DPDK驱动简介

2023-06-29 06:07:29
300
0

      dpdk 用户态驱动框架是 dpdk 相对核心的功能,本文通过dpdk总线,驱动,设备的注册、加载、探测流程,简要介绍dpdk驱动的原理。LINUX的设备和驱动都是挂在总线上的,多个设备可以使用同一个驱动,设备和驱动一旦匹配之后,就会调用驱动的probe函数对设备进行初始化。DPDK运行在用户态,里面也包含着许多设备驱动。DPDK中总线、设备和驱动的模型和Linux内核是一样的,加载方式也是类似的。下面主要介绍DPDK的总线注册、驱动注册和设备扫描挂载,以及设备和驱动匹配之后的设备探测。如下图所示,简单表示了总线和设备驱动的关系:

                       

总线Bus:管理具备相同属性的device和driver(例:pci)

驱动Driver:设备程序

设备Device:设备相关信息

总线,驱动和设备之间的关联关系,如下图所示,通过不同的结构体进行管理:

             

一、总线注册

DPDK中使用一个全局链表rte_bus_list管理总线,该链表结构和声明如下:

lib\eal\include\bus_driver.h
struct rte_bus_list {
    struct rte_bus *tqh_first; /* first element */
    struct rte_bus **tqh_last; /* addr of last next element */
    TRACEBUF
}

lib\eal\common\eal_common_bus.c
static struct rte_bus_list rte_bus_list =
    TAILQ_HEAD_INITIALIZER(rte_bus_list);

       DPDK中支持pci、vdev、vmbux和auxiliary等8种总线,这些总线都是通过RTE_REGISTER_BUS(nm,bus)将对应的bus插入到rte_bus_list链表中,其中nm表示bus的名字,bus表示通用的struct rte_bus,该结构体定义如下:

struct rte_bus {
    RTE_TAILQ_ENTRY(rte_bus) next; /**< Next bus object in linked list */
    const char *name; /**< Name of the bus */
    rte_bus_scan_t scan; /**< Scan for devices attached to bus */
    rte_bus_probe_t probe; /**< Probe devices on bus */
    rte_bus_find_device_t find_device; /**< Find a device on the bus */
    rte_bus_plug_t plug; /**< Probe single device for drivers */
    rte_bus_unplug_t unplug; /**< Remove single device from driver */
    rte_bus_parse_t parse; /**< Parse a device name */
    rte_bus_devargs_parse_t devargs_parse; /**< Parse bus devargs */
    rte_dev_dma_map_t dma_map; /**< DMA map for device in the bus */
    rte_dev_dma_unmap_t dma_unmap; /**< DMA unmap for device in the bus */
    struct rte_bus_conf conf; /**< Bus configuration */
    rte_bus_get_iommu_class_t get_iommu_class; /**< Get iommu class */
    rte_dev_iterate_t dev_iterate; /**< Device iterator. */
    rte_bus_hot_unplug_handler_t hot_unplug_handler;  /**< handle hot-unplug failure on the bus */
    rte_bus_sigbus_handler_t sigbus_handler;  /**< handle sigbus error on the bus */
    rte_bus_cleanup_t cleanup; /**< Cleanup devices on bus */
};

       rte_bus是对所有总线的一个抽象,所有总线都会实现scan、probe和find_device钩子,其他的钩子函数根据需要进行实现。具体的某类总线的抽象,除了rte_bus对象之外,一般还会维护一个设备列表和驱动列表,如rte_pci_bus的结构如下:

struct rte_pci_bus {
  struct rte_bus bus;               /**< Inherit the generic class */
  RTE_TAILQ_HEAD(, rte_pci_device) device_list; /**< List of PCI devices */
  RTE_TAILQ_HEAD(, rte_pci_driver) driver_list; /**< List of PCI drivers */
};

struct rte_pci_bus rte_pci_bus = {
    .bus = {
        .scan = rte_pci_scan,
        .probe = pci_probe,
        .cleanup = pci_cleanup,
        .find_device = pci_find_device,
        .plug = pci_plug,
        .unplug = pci_unplug,
        .parse = pci_parse,
        .devargs_parse = rte_pci_devargs_parse,
        .dma_map = pci_dma_map,
        .dma_unmap = pci_dma_unmap,
        .get_iommu_class = rte_pci_get_iommu_class,
        .dev_iterate = rte_pci_dev_iterate,
        .hot_unplug_handler = pci_hot_unplug_handler,
        .sigbus_handler = pci_sigbus_handler,
    },
    .device_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.device_list),
    .driver_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.driver_list),
};
RTE_REGISTER_BUS(pci, rte_pci_bus.bus);

       各类总线的通过RTE_REGISTER_BUS()注册不是在rte_eal_init()中完成的,而是在执行DPDK主函数(main函数)前就注册好的。在执行main函数前,还有一些DPDK中的初始化,如rte_log初始化、bus总线的注册、PMD驱动的注册和设备class的注册。这些初始化的核心都是使用RTE_INIT_PRIO宏实现,它用到了附带GCC编译属性的函数定义:constructor和used属性。used比较简单,告诉GCC编译器此函数的有用性,即使函数没有被任何地方引用,也不要警告。constructor构造属性,类似C++的构造函数,它标注的函数将在主函数(main)之前执行,相关代码位于目标文件的.ctors区。RTE_INIT_PRIO被多个初始化函数调用,而且他们是有顺序的,因此引入了一个RTE_PRIO宏指定了constructor的优先级,控制着函数执行顺序。值越小优先级越高,目前定义了4个优先级:

#define RTE_INIT_PRIO(func, prio) \
        static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
#define RTE_PRIORITY_LOG 101
#define RTE_PRIORITY_BUS 110
#define RTE_PRIORITY_CLASS 120
#define RTE_PRIORITY_LAST 65535
#define RTE_PRIO(prio) RTE_PRIORITY_ ## prio

        RTE_INIT宏封装了RTE_PRIORITY_LAST优先级的函数,一般在PMD注册和PMD LOG注册时用到。RTE_REGISTER_BUS宏封装了RTE_PRIORITY_BUS优先级的函数,用于注册DPDK中的各种总线。而RTE_REGISTER_CLASS宏封装了RTE_PRIORITY_CLASS优先级的函数,用于注册设备class。RTE_PRIORITY_LOG优先级是当前最高的优先级,在进行eal_log_init中有使用RTE_INIT_PRIO(log_init, LOG),如下代码所示:

lib/librte_eal/common/eal_common_log.c RTE_INIT_PRIO(log_init, LOG)

drivers/bus/pci/pci_common.c RTE_REGISTER_BUS(pci, rte_pci_bus.bus);
drivers/bus/vdev/vdev.c RTE_REGISTER_BUS(vdev, rte_vdev_bus);
drivers/bus/dpaa/dpaa_bus.c RTE_REGISTER_BUS(FSL_DPAA_BUS_NAME, rte_dpaa_bus.bus);
drivers/bus/fslmc/fslmc_bus.c RTE_REGISTER_BUS(FSLMC_BUS_NAME, rte_fslmc_bus.bus)
drivers/bus/vmbus/vmbus_common.c RTE_REGISTER_BUS(vmbus, rte_vmbus_bus.bus);
drivers\bus\auxiliary\auxiliary_common.c RTE_REGISTER_BUS(auxiliary, auxiliary_bus.bus);
drivers\bus\ifpga\ifpga_bus.c RTE_REGISTER_BUS(IFPGA_BUS_NAME, rte_ifpga_bus);
drivers\dma\idxd\idxd_bus.c     RTE_REGISTER_BUS(dsa, dsa_bus.bus);

lib\vhost\vdpa.c RTE_REGISTER_CLASS(vdpa, rte_class_vdpa)
lib\ethdev|rte_class_eth.c RTE_REGISTER_CLASS(eth, rte_class_eth)

        以上各类初始化的优先级,可查看app编译后的map文件中搜索"ctors"关键字查看。另外,DPDK中定义了RTE_FINI相关宏,其使用了GCC的destructor和used两个属性,destructor属性表明函数在主函数(main)结束之后执行。当前暂时无任何模块使用RTE_UNREGISTER_CLASS。

#define RTE_FINI_PRIO(func, prio) \
        static void __attribute__((destructor(RTE_PRIO(prio)), used)) func(void)

#define RTE_UNREGISTER_CLASS(nm, cls) \
RTE_FINI_PRIO(classfinifn_ ##nm, CLASS) \
{ \
    rte_class_unregister(&cls); \
}

二、驱动注册

        DPDK中的设备驱动和Linux内核设备驱动也是类似的。各种总线定义了该总线上注册驱动的接口,如下图所示:

每个总线上的设备驱动,一般会在驱动主文件的末尾调用对应的钩子注册驱动,如vdev总线上的bonding和failsafe驱动,注册代码实现如下。

drivers\net\bonding\rte_eth_bond_pmd.c
struct rte_vdev_driver pmd_bond_drv = {
    .probe = bond_probe,
    .remove = bond_remove,
};
RTE_PMD_REGISTER_VDEV(net_bonding, pmd_bond_drv);

drivers\net\failsafe\failsafe.c
static struct rte_vdev_driver failsafe_drv = {
    .probe = rte_pmd_failsafe_probe,
    .remove = rte_pmd_failsafe_remove,
};
RTE_PMD_REGISTER_VDEV(net_failsafe, failsafe_drv);

        驱动的注册实现基本都是类似的,以PCI网卡驱动为例来说明,rte_pci_driver中定义的id_table,每个硬件网卡设备的vendor_id和device_id是不同的,使得一个驱动可以支持多种设备。设备驱动的注册里面也是使用RTE_INIT宏定义constructor属性函数,其执行动作也在DPDK的主函数之前就完成的,但其设置了的优先级比总线注册的优先级低。因此,驱动注册完成于主函数执行前,总线注册之后。具体的注册和结构体可以查看DPDK源码进一步理解。

 

三、设备挂载

    设备扫描挂载是在主函数中调用的rte_eal_init()中完成的。rte_eal_init()会调用rte_bus_scan(),该函数实现如下:

int
rte_bus_scan(void)
{
	int ret;
	struct rte_bus *bus = NULL;

	TAILQ_FOREACH(bus, &rte_bus_list, next) {
		ret = bus->scan();
		if (ret)
			RTE_LOG(ERR, EAL, "Scan for (%s) bus failed.\n",
				bus->name);
	}
	return 0;
}

       从该函数实现可以看出,eal层初始化时,会遍历全局总线链表,调用总线上的scan()接口扫描总线上的设备。以rte_pci_bus上的扫描过程为例,该总线的scan()接口为rte_pci_scan()。

       /sys/bus/pci/devices/目录中包含了该设备上每个pci设备的信息,该文件夹的顺序是随着BDF号(pci设备地址)的顺序从小到大依次排列的。
pci_scan_one()函数就是解析设备文件夹中的每个文件信息,从而初始化申请的rte_pci_device,并将其挂载到rte_pci_bus总线上的设备列表,每个设备文件夹的内容如下:

        rte_pci_device的结构域段就是和上图中的文件是类似的,如下图所示:

struct rte_pci_device {
	RTE_TAILQ_ENTRY(rte_pci_device) next;   /**< Next probed PCI device. */
	struct rte_device device;           /**< Inherit core device */
	struct rte_pci_addr addr;           /**< PCI location. */
	struct rte_pci_id id;               /**< PCI ID. */
	struct rte_mem_resource mem_resource[PCI_MAX_RESOURCE];
					    /**< PCI Memory Resource */
	struct rte_intr_handle *intr_handle; /**< Interrupt handle */
	struct rte_pci_driver *driver;      /**< PCI driver used in probing */
	uint16_t max_vfs;                   /**< sriov enable if not zero */
	enum rte_pci_kernel_driver kdrv;    /**< Kernel driver passthrough */
	char name[PCI_PRI_STR_SIZE+1];      /**< PCI location (ASCII) */
	struct rte_intr_handle *vfio_req_intr_handle;
				/**< Handler of VFIO request interrupt */
};

        解析过程的具体细节在此不进行展开,主要提一下以下几点:1)driver指定了当前设备的驱动,DPDK支持三种托管设备到用户态的uio驱动,即vfio_pci、igb_uio和uio_pci_generic;2) struct rte_pci_id中的信息,后面会用它和驱动的id_table进行设备和驱动的匹配;3)  resource文件,该文件保存了PCI设备的Bar0-5的地址空间,其在pci_parse_sysfs_resource函数中被解析并保存到rte_pci_device中的mem_resource[]中。综上可以看出,设备就是按照以上方式将PCI设备按照BDF号从小到大的顺序将设备挂到rte_pci_bus总线上。 

四、设备探测

        设备扫描完成后,在rte_eal_init()中的后面就调用rte_bus_probe()函数,遍历所有总线,调用总线的probe函数,代码如下:   

int
rte_bus_probe(void)
{
	int ret;
	struct rte_bus *bus, *vbus = NULL;
	TAILQ_FOREACH(bus, &rte_bus_list, next) {
		if (!strcmp(bus->name, "vdev")) {
			vbus = bus;
			continue;
		}
		ret = bus->probe();
		if (ret)
			RTE_LOG(ERR, EAL, "Bus (%s) probe failed.\n",
				bus->name);
	}
	if (vbus) {
		ret = vbus->probe();
		if (ret)
			RTE_LOG(ERR, EAL, "Bus (%s) probe failed.\n",
				vbus->name);
	}
	return 0;
}

    下面仍然以rte_pci_bus总线为例说明pci设备的探测过程。pci_probe()会按照PCI设备挂载顺序为该总线上的每个设备遍历总线上的所有驱动找到对应的驱动,它会调用pci_probe_all_drivers中就会去遍历rte_pci_bus上的所有驱动,并调用rte_pci_probe_one_driver(dr, dev)进行设备和驱动的匹配。rte_pci_probe_one_driver的匹配细节可以查看源码实现。其中rte_pci_match(dr, dev)就使用前面设备扫描过程中得到的设备pci_id信息和driver中的id_table进行匹配,若匹配则进行PCI设备Bar空间的映射,然后调用驱动的probe函数。每类设备驱动的probe函数执行也是类似的。

        对于网卡设备,上层应用看到的就是一个以太网设备(ethdev),上层应用都是通过ethdev对应的端口号,操作PCI网卡。该probe函数执行时,首先分配一个ethdev,然后调用驱动的dev_init()函数进行设备驱动中的默认初始化。驱动初始化时会将PCI设备的Bar2地址作为硬件设备的io地址,驱动通过该地址,就可在用户态直接读写设备寄存器。具体使用PCI设备的哪个Bar地址不固定,各厂商是有差异的。
        驱动的probe函数执行完,则驱动和设备就探测成功了。但设备的初始化过程并没有完全完成,上层应用还需调用dev_configure接口下发配置,根据用户的配置重新配置硬件设备,再调用dev_start()接口,整个设备才全部初始化完成。

 

0条评论
0 / 1000
w****n
6文章数
1粉丝数
w****n
6 文章 | 1 粉丝
w****n
6文章数
1粉丝数
w****n
6 文章 | 1 粉丝
原创

DPDK驱动简介

2023-06-29 06:07:29
300
0

      dpdk 用户态驱动框架是 dpdk 相对核心的功能,本文通过dpdk总线,驱动,设备的注册、加载、探测流程,简要介绍dpdk驱动的原理。LINUX的设备和驱动都是挂在总线上的,多个设备可以使用同一个驱动,设备和驱动一旦匹配之后,就会调用驱动的probe函数对设备进行初始化。DPDK运行在用户态,里面也包含着许多设备驱动。DPDK中总线、设备和驱动的模型和Linux内核是一样的,加载方式也是类似的。下面主要介绍DPDK的总线注册、驱动注册和设备扫描挂载,以及设备和驱动匹配之后的设备探测。如下图所示,简单表示了总线和设备驱动的关系:

                       

总线Bus:管理具备相同属性的device和driver(例:pci)

驱动Driver:设备程序

设备Device:设备相关信息

总线,驱动和设备之间的关联关系,如下图所示,通过不同的结构体进行管理:

             

一、总线注册

DPDK中使用一个全局链表rte_bus_list管理总线,该链表结构和声明如下:

lib\eal\include\bus_driver.h
struct rte_bus_list {
    struct rte_bus *tqh_first; /* first element */
    struct rte_bus **tqh_last; /* addr of last next element */
    TRACEBUF
}

lib\eal\common\eal_common_bus.c
static struct rte_bus_list rte_bus_list =
    TAILQ_HEAD_INITIALIZER(rte_bus_list);

       DPDK中支持pci、vdev、vmbux和auxiliary等8种总线,这些总线都是通过RTE_REGISTER_BUS(nm,bus)将对应的bus插入到rte_bus_list链表中,其中nm表示bus的名字,bus表示通用的struct rte_bus,该结构体定义如下:

struct rte_bus {
    RTE_TAILQ_ENTRY(rte_bus) next; /**< Next bus object in linked list */
    const char *name; /**< Name of the bus */
    rte_bus_scan_t scan; /**< Scan for devices attached to bus */
    rte_bus_probe_t probe; /**< Probe devices on bus */
    rte_bus_find_device_t find_device; /**< Find a device on the bus */
    rte_bus_plug_t plug; /**< Probe single device for drivers */
    rte_bus_unplug_t unplug; /**< Remove single device from driver */
    rte_bus_parse_t parse; /**< Parse a device name */
    rte_bus_devargs_parse_t devargs_parse; /**< Parse bus devargs */
    rte_dev_dma_map_t dma_map; /**< DMA map for device in the bus */
    rte_dev_dma_unmap_t dma_unmap; /**< DMA unmap for device in the bus */
    struct rte_bus_conf conf; /**< Bus configuration */
    rte_bus_get_iommu_class_t get_iommu_class; /**< Get iommu class */
    rte_dev_iterate_t dev_iterate; /**< Device iterator. */
    rte_bus_hot_unplug_handler_t hot_unplug_handler;  /**< handle hot-unplug failure on the bus */
    rte_bus_sigbus_handler_t sigbus_handler;  /**< handle sigbus error on the bus */
    rte_bus_cleanup_t cleanup; /**< Cleanup devices on bus */
};

       rte_bus是对所有总线的一个抽象,所有总线都会实现scan、probe和find_device钩子,其他的钩子函数根据需要进行实现。具体的某类总线的抽象,除了rte_bus对象之外,一般还会维护一个设备列表和驱动列表,如rte_pci_bus的结构如下:

struct rte_pci_bus {
  struct rte_bus bus;               /**< Inherit the generic class */
  RTE_TAILQ_HEAD(, rte_pci_device) device_list; /**< List of PCI devices */
  RTE_TAILQ_HEAD(, rte_pci_driver) driver_list; /**< List of PCI drivers */
};

struct rte_pci_bus rte_pci_bus = {
    .bus = {
        .scan = rte_pci_scan,
        .probe = pci_probe,
        .cleanup = pci_cleanup,
        .find_device = pci_find_device,
        .plug = pci_plug,
        .unplug = pci_unplug,
        .parse = pci_parse,
        .devargs_parse = rte_pci_devargs_parse,
        .dma_map = pci_dma_map,
        .dma_unmap = pci_dma_unmap,
        .get_iommu_class = rte_pci_get_iommu_class,
        .dev_iterate = rte_pci_dev_iterate,
        .hot_unplug_handler = pci_hot_unplug_handler,
        .sigbus_handler = pci_sigbus_handler,
    },
    .device_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.device_list),
    .driver_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.driver_list),
};
RTE_REGISTER_BUS(pci, rte_pci_bus.bus);

       各类总线的通过RTE_REGISTER_BUS()注册不是在rte_eal_init()中完成的,而是在执行DPDK主函数(main函数)前就注册好的。在执行main函数前,还有一些DPDK中的初始化,如rte_log初始化、bus总线的注册、PMD驱动的注册和设备class的注册。这些初始化的核心都是使用RTE_INIT_PRIO宏实现,它用到了附带GCC编译属性的函数定义:constructor和used属性。used比较简单,告诉GCC编译器此函数的有用性,即使函数没有被任何地方引用,也不要警告。constructor构造属性,类似C++的构造函数,它标注的函数将在主函数(main)之前执行,相关代码位于目标文件的.ctors区。RTE_INIT_PRIO被多个初始化函数调用,而且他们是有顺序的,因此引入了一个RTE_PRIO宏指定了constructor的优先级,控制着函数执行顺序。值越小优先级越高,目前定义了4个优先级:

#define RTE_INIT_PRIO(func, prio) \
        static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
#define RTE_PRIORITY_LOG 101
#define RTE_PRIORITY_BUS 110
#define RTE_PRIORITY_CLASS 120
#define RTE_PRIORITY_LAST 65535
#define RTE_PRIO(prio) RTE_PRIORITY_ ## prio

        RTE_INIT宏封装了RTE_PRIORITY_LAST优先级的函数,一般在PMD注册和PMD LOG注册时用到。RTE_REGISTER_BUS宏封装了RTE_PRIORITY_BUS优先级的函数,用于注册DPDK中的各种总线。而RTE_REGISTER_CLASS宏封装了RTE_PRIORITY_CLASS优先级的函数,用于注册设备class。RTE_PRIORITY_LOG优先级是当前最高的优先级,在进行eal_log_init中有使用RTE_INIT_PRIO(log_init, LOG),如下代码所示:

lib/librte_eal/common/eal_common_log.c RTE_INIT_PRIO(log_init, LOG)

drivers/bus/pci/pci_common.c RTE_REGISTER_BUS(pci, rte_pci_bus.bus);
drivers/bus/vdev/vdev.c RTE_REGISTER_BUS(vdev, rte_vdev_bus);
drivers/bus/dpaa/dpaa_bus.c RTE_REGISTER_BUS(FSL_DPAA_BUS_NAME, rte_dpaa_bus.bus);
drivers/bus/fslmc/fslmc_bus.c RTE_REGISTER_BUS(FSLMC_BUS_NAME, rte_fslmc_bus.bus)
drivers/bus/vmbus/vmbus_common.c RTE_REGISTER_BUS(vmbus, rte_vmbus_bus.bus);
drivers\bus\auxiliary\auxiliary_common.c RTE_REGISTER_BUS(auxiliary, auxiliary_bus.bus);
drivers\bus\ifpga\ifpga_bus.c RTE_REGISTER_BUS(IFPGA_BUS_NAME, rte_ifpga_bus);
drivers\dma\idxd\idxd_bus.c     RTE_REGISTER_BUS(dsa, dsa_bus.bus);

lib\vhost\vdpa.c RTE_REGISTER_CLASS(vdpa, rte_class_vdpa)
lib\ethdev|rte_class_eth.c RTE_REGISTER_CLASS(eth, rte_class_eth)

        以上各类初始化的优先级,可查看app编译后的map文件中搜索"ctors"关键字查看。另外,DPDK中定义了RTE_FINI相关宏,其使用了GCC的destructor和used两个属性,destructor属性表明函数在主函数(main)结束之后执行。当前暂时无任何模块使用RTE_UNREGISTER_CLASS。

#define RTE_FINI_PRIO(func, prio) \
        static void __attribute__((destructor(RTE_PRIO(prio)), used)) func(void)

#define RTE_UNREGISTER_CLASS(nm, cls) \
RTE_FINI_PRIO(classfinifn_ ##nm, CLASS) \
{ \
    rte_class_unregister(&cls); \
}

二、驱动注册

        DPDK中的设备驱动和Linux内核设备驱动也是类似的。各种总线定义了该总线上注册驱动的接口,如下图所示:

每个总线上的设备驱动,一般会在驱动主文件的末尾调用对应的钩子注册驱动,如vdev总线上的bonding和failsafe驱动,注册代码实现如下。

drivers\net\bonding\rte_eth_bond_pmd.c
struct rte_vdev_driver pmd_bond_drv = {
    .probe = bond_probe,
    .remove = bond_remove,
};
RTE_PMD_REGISTER_VDEV(net_bonding, pmd_bond_drv);

drivers\net\failsafe\failsafe.c
static struct rte_vdev_driver failsafe_drv = {
    .probe = rte_pmd_failsafe_probe,
    .remove = rte_pmd_failsafe_remove,
};
RTE_PMD_REGISTER_VDEV(net_failsafe, failsafe_drv);

        驱动的注册实现基本都是类似的,以PCI网卡驱动为例来说明,rte_pci_driver中定义的id_table,每个硬件网卡设备的vendor_id和device_id是不同的,使得一个驱动可以支持多种设备。设备驱动的注册里面也是使用RTE_INIT宏定义constructor属性函数,其执行动作也在DPDK的主函数之前就完成的,但其设置了的优先级比总线注册的优先级低。因此,驱动注册完成于主函数执行前,总线注册之后。具体的注册和结构体可以查看DPDK源码进一步理解。

 

三、设备挂载

    设备扫描挂载是在主函数中调用的rte_eal_init()中完成的。rte_eal_init()会调用rte_bus_scan(),该函数实现如下:

int
rte_bus_scan(void)
{
	int ret;
	struct rte_bus *bus = NULL;

	TAILQ_FOREACH(bus, &rte_bus_list, next) {
		ret = bus->scan();
		if (ret)
			RTE_LOG(ERR, EAL, "Scan for (%s) bus failed.\n",
				bus->name);
	}
	return 0;
}

       从该函数实现可以看出,eal层初始化时,会遍历全局总线链表,调用总线上的scan()接口扫描总线上的设备。以rte_pci_bus上的扫描过程为例,该总线的scan()接口为rte_pci_scan()。

       /sys/bus/pci/devices/目录中包含了该设备上每个pci设备的信息,该文件夹的顺序是随着BDF号(pci设备地址)的顺序从小到大依次排列的。
pci_scan_one()函数就是解析设备文件夹中的每个文件信息,从而初始化申请的rte_pci_device,并将其挂载到rte_pci_bus总线上的设备列表,每个设备文件夹的内容如下:

        rte_pci_device的结构域段就是和上图中的文件是类似的,如下图所示:

struct rte_pci_device {
	RTE_TAILQ_ENTRY(rte_pci_device) next;   /**< Next probed PCI device. */
	struct rte_device device;           /**< Inherit core device */
	struct rte_pci_addr addr;           /**< PCI location. */
	struct rte_pci_id id;               /**< PCI ID. */
	struct rte_mem_resource mem_resource[PCI_MAX_RESOURCE];
					    /**< PCI Memory Resource */
	struct rte_intr_handle *intr_handle; /**< Interrupt handle */
	struct rte_pci_driver *driver;      /**< PCI driver used in probing */
	uint16_t max_vfs;                   /**< sriov enable if not zero */
	enum rte_pci_kernel_driver kdrv;    /**< Kernel driver passthrough */
	char name[PCI_PRI_STR_SIZE+1];      /**< PCI location (ASCII) */
	struct rte_intr_handle *vfio_req_intr_handle;
				/**< Handler of VFIO request interrupt */
};

        解析过程的具体细节在此不进行展开,主要提一下以下几点:1)driver指定了当前设备的驱动,DPDK支持三种托管设备到用户态的uio驱动,即vfio_pci、igb_uio和uio_pci_generic;2) struct rte_pci_id中的信息,后面会用它和驱动的id_table进行设备和驱动的匹配;3)  resource文件,该文件保存了PCI设备的Bar0-5的地址空间,其在pci_parse_sysfs_resource函数中被解析并保存到rte_pci_device中的mem_resource[]中。综上可以看出,设备就是按照以上方式将PCI设备按照BDF号从小到大的顺序将设备挂到rte_pci_bus总线上。 

四、设备探测

        设备扫描完成后,在rte_eal_init()中的后面就调用rte_bus_probe()函数,遍历所有总线,调用总线的probe函数,代码如下:   

int
rte_bus_probe(void)
{
	int ret;
	struct rte_bus *bus, *vbus = NULL;
	TAILQ_FOREACH(bus, &rte_bus_list, next) {
		if (!strcmp(bus->name, "vdev")) {
			vbus = bus;
			continue;
		}
		ret = bus->probe();
		if (ret)
			RTE_LOG(ERR, EAL, "Bus (%s) probe failed.\n",
				bus->name);
	}
	if (vbus) {
		ret = vbus->probe();
		if (ret)
			RTE_LOG(ERR, EAL, "Bus (%s) probe failed.\n",
				vbus->name);
	}
	return 0;
}

    下面仍然以rte_pci_bus总线为例说明pci设备的探测过程。pci_probe()会按照PCI设备挂载顺序为该总线上的每个设备遍历总线上的所有驱动找到对应的驱动,它会调用pci_probe_all_drivers中就会去遍历rte_pci_bus上的所有驱动,并调用rte_pci_probe_one_driver(dr, dev)进行设备和驱动的匹配。rte_pci_probe_one_driver的匹配细节可以查看源码实现。其中rte_pci_match(dr, dev)就使用前面设备扫描过程中得到的设备pci_id信息和driver中的id_table进行匹配,若匹配则进行PCI设备Bar空间的映射,然后调用驱动的probe函数。每类设备驱动的probe函数执行也是类似的。

        对于网卡设备,上层应用看到的就是一个以太网设备(ethdev),上层应用都是通过ethdev对应的端口号,操作PCI网卡。该probe函数执行时,首先分配一个ethdev,然后调用驱动的dev_init()函数进行设备驱动中的默认初始化。驱动初始化时会将PCI设备的Bar2地址作为硬件设备的io地址,驱动通过该地址,就可在用户态直接读写设备寄存器。具体使用PCI设备的哪个Bar地址不固定,各厂商是有差异的。
        驱动的probe函数执行完,则驱动和设备就探测成功了。但设备的初始化过程并没有完全完成,上层应用还需调用dev_configure接口下发配置,根据用户的配置重新配置硬件设备,再调用dev_start()接口,整个设备才全部初始化完成。

 

文章来自个人专栏
wangh13
6 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0