DPDK 16.07 驱动初始化和收发包函数学习笔记

DPDK 16.07 驱动初始化和收发包函数学习笔记

  1. 文档保留了 markdown 格式。可以转为纯文本格式,方便在其他编辑器中使用语法高亮阅读代码。
  2. 文档行文主要是提纲式的。如果阅读过程发现缺少了方向。请立刻回到章节的开首处,查询总体的函数调用图。
  3. 文档只讨论了千兆(I350 Gigabit)和 万兆(82599ES 10-Gigabit SFI/SFP+)的驱动做为例子。
  4. 分析代码的过程中,各分别绑定了一个网口千兆(04:00.0)和 万兆(08:00.0)做为例子。
  5. 如果你的时间宝贵。请着重阅读以下重要的章节:
    5.1. <#### 2.2.2.1. id_table 的定义> <== DPDK 所支持的驱动列表
    5.2. <### 4.1.4. 创建 收包队列的 mbuf> <== DPDK 零拷贝的实现
  6. 文档的图例基本使用 UML。但也有自定义的图例。详细见 [本文档所使用的图例] 的说明。

本文档所使用的图例:
在这里插入图片描述

注意:

聚合和组合都表示整体和部分的关联关系。
如果整体销毁后,部分也随之销毁。会使用组合表示。
如果整体销毁后,部分可以独立存在。会使用聚合表示。

1. rte_driver 驱动 的 注册

rte_driver 驱动 是用宏函数 PMD_REGISTER_DRIVER(),创建一个拼接函数,然后注册驱动的。

函数调用图如下:

	PMD_REGISTER_DRIVER(xxx)
	+-> devinitfn_xxx_drv				/* 拼接生成的注册函数 */
		+-> rte_eal_driver_register()	/* 注册驱动 */

1.1. rte_driver 驱动 的 结构体

dpdkrte_driver 驱动 的 结构体,定义 如下。

	struct rte_driver {
   
		TAILQ_ENTRY(rte_driver) next;  /**< Next in list. */
		enum pmd_type type;	       /**< PMD Driver type */
		const char *name;          /**< Driver name. */
		rte_dev_init_t *init;      /**< Device init. function. */
		rte_dev_uninit_t *uninit;  /**< Device uninit. function. */
	};

rte_driver 结构体 类图:
在这里插入图片描述

其中最重要的是 回调函数 rte_dev_init_t *init。用于 指向不同驱动的 初始化实现函数。
另外 type 则表示驱动的类型:

  1. PMD_PDEV 是物理设备驱动;
  2. PMD_VDEV 是虚拟设备驱动。

例子:

  1. 千兆网卡的驱动为 pmd_igb_drv
    其中 的 回调函数 init 设置 为 rte_igb_pmd_init
	static struct rte_driver pmd_igb_drv = {
   
		.type = PMD_PDEV,
		.init = rte_igb_pmd_init,	/* <== 驱动的初始化 */
	};
  1. 万兆网卡的驱动为 rte_ixgbe_driver
    其中 的 回调函数 init 设置 为 rte_ixgbe_pmd_init。
	static struct rte_driver rte_ixgbe_driver = {
   
		.type = PMD_PDEV,
		.init = rte_ixgbe_pmd_init,	/* <== 驱动的初始化 */
	};

1.2. rte_driver 驱动的 注册函数

驱动 的注册,是以 PMD_REGISTER_DRIVER 宏函数 来实现的。

该宏函数 会 拼接生成一个 注册函数,该 注册函数 的属性 __attribute__() 设置为 constructor
所以可以在 main() 之前,自动的运行该注册函数。

注意:
注册函数 里面 调用 了 rte_eal_driver_register 对结构体 struct rte_driver 进行 注册。

	#define PMD_REGISTER_DRIVER(drv, nm)\
	void devinitfn_ ##drv(void);\
	void __attribute__((constructor, used)) devinitfn_ ##drv(void)\
	{\
		(drv).name = RTE_STR(nm);\
		rte_eal_driver_register(&drv);\
	} \
	DRIVER_EXPORT_NAME(nm, __COUNTER__)	

例子:

  1. 千兆网卡的注册:
	PMD_REGISTER_DRIVER(pmd_igb_drv, igb);

	/* 宏展开后,结果如下 */
	void devinitfn_pmd_igb_drv(void);
	void __attribute__((constructor, used)) devinitfn_pmd_igb_drv(void)
	{
   
		(pmd_igb_drv).name = "igb";
		rte_eal_driver_register(&pmd_igb_drv);	/* <== 注册千兆驱动 */
	}
  1. 万兆网卡的注册:
	PMD_REGISTER_DRIVER(rte_ixgbe_driver, ixgbe);

	/* 宏展开后,结果如下 */
	void devinitfn_rte_ixgbe_driver(void);
	void __attribute__((constructor, used)) devinitfn_rte_ixgbe_driver(void)
	{
   
		(rte_ixgbe_driver).name = "ixgbe";
		rte_eal_driver_register(&rte_ixgbe_driver);	/* <== 注册万兆驱动 */
	}

1.3. rte_eal_driver_register() 的作用

rte_eal_driver_register 是驱动在运行时的注册函数。
rte_eal_driver_register 将驱动 driver 放置到 名为 dev_driver_listtail_queue 链表中。

	void
	rte_eal_driver_register(struct rte_driver *driver)
	{
   
		TAILQ_INSERT_TAIL(&dev_driver_list, driver, next);
	}

dev_driver_list 是一个全局变量,登记了所有的 驱动。

	/** Double linked list of device drivers. */
	TAILQ_HEAD(rte_driver_list, rte_driver);

	/** Global list of device drivers. */
	static struct rte_driver_list dev_driver_list =
		TAILQ_HEAD_INITIALIZER(dev_driver_list);

注册了千兆和万兆网卡驱动的 rte_driver_list 链表
在这里插入图片描述

2. EAL 的初始化

环境抽象层 EAL 初始化,其中与设备相关的部分主要函数如下:

  1. rte_eal_pci_init 对 所有 dpdk 托管 pci 网卡 的 进行初始化。
  2. rte_eal_dev_init 设备 的初始化。
  3. rte_eal_pci_probe pci 设备的侦测(侦测和初始化)。

rte_eal_init() 总体函数调用图如下:

	main
	|	/* ------------------------ */
	|	/*       EAL 的初始化       */
	+-> rte_eal_init
		|
		+-> rte_eal_pci_init		/* 对 所有 dpdk 托管 pci 网口 的 进行初始化 */
		|	+-> rte_eal_pci_scan	/* 取得 网口 的 addr 信息 */
		|		+-> opendir(pci_get_sysfs_path()); /* 读取系统设备目录下的pci设备。 */
		|		+-> parse_pci_addr_format /* 提取 pci 地址 信息 */
		|		+-> pci_scan_one	/* 解释 单个 pci 设备的详细信息。 */
		|			+-> eal_parse_sysfs_value	 /* 解释 pci 设备的文件子系统的内容 */
		|			+-> pci_parse_sysfs_resource /* 取得 网口 的 resource 信息 */
		|			|	+-> pci_parse_one_sysfs_resource  /* 解释一条的 resource 信息 */
		|			+-> pci_get_kernel_driver_by_path /* 取得 内核驱动的 名字 */
		|			+-> TAILQ_INSERT_TAIL(&pci_device_list, dev, next) /* 把设备 放到 pci_device_list 链表 */
		|
		+-> rte_eal_dev_init				/* 设备 的初始化 */
		|	+-> rte_eal_vdev_init 			/* 初始化 虚拟设备。暂不深入展开。 */
		|	+-> driver->init(NULL, NULL); 	/* 触发 dev_driver_list 上 所有 driver->init() 的调用。 */
		|		.	/* 千兆: rte_igb_pmd_init */
		|		+~> rte_igb_pmd_init		
		|		.	+-> rte_eth_driver_register(&rte_igb_pmd); /* 不同的驱动会调用 ret_eth_driver_register 来注册。 */
		|		.		+-> eth_drv->pci_drv.devinit = rte_eth_dev_init; /* 设置 网口 设备 初始化 回调函数。 */
		|		.		+-> eth_drv->pci_drv.devuninit = rte_eth_dev_uninit; /* 设置 网口 设备 反初始化 回调函数。 */
		|		.		+-> rte_eal_pci_register(&eth_drv->pci_drv)	/* 注册 pci 驱动。 */
		|		.			+-> TAILQ_INSERT_TAIL(&pci_driver_list, driver, next); /* 将驱动放置到 pci_driver_list 中。 */
		|       .
		|		.	/* 万兆: rte_ixgbe_pmd_init */
		|		+~> rte_ixgbe_pmd_init		
		|			+-> rte_eth_driver_register(&rte_ixgbe_pmd); /* 不同的驱动会调用 ret_eth_driver_register 来注册。 */
		|				+-> eth_drv->pci_drv.devinit = rte_eth_dev_init; /* 设置 网口 设备 初始化 回调函数。 */
		|				+-> eth_drv->pci_drv.devuninit = rte_eth_dev_uninit; /* 设置 网口 设备 反初始化 回调函数。 */
		|				+-> rte_eal_pci_register(&eth_drv->pci_drv)	/* 注册 pci 驱动。 */
		|					+-> TAILQ_INSERT_TAIL(&pci_driver_list, driver, next); /* 将驱动放置到 pci_driver_list 中。 */
		|
		+-> rte_eal_pci_probe		/* pci 设备的侦测(侦测和初始化) */
			+-> pci_probe_all_drivers	 /* pci 设备 遍历所有 驱动,找到合适的驱动 */
				+-> rte_eal_pci_probe_one_driver /* 探测 驱动 和 设备 是否适合 */
					+->	dr->devinit(dr, dev);		/* 触发 驱动 对 设备 初始化。参见 rte_eth_driver_register */
						|	/* 无论 千兆 和 万兆 网卡。都是使用 rte_eth_dev_init。 */
						+~> rte_eth_dev_init		/* 网口 设备 初始化。 */
							+-> rte_eth_dev_create_unique_device_name
							+-> rte_eth_dev_allocate // 
							|	+-> rte_eth_dev_find_free_port	/* 从 rte_eth_devices 中,查找 空闲的 port id */
							|	+-> rte_eth_dev_data_alloc		/* 只有 当 rte_eth_dev_data[] 没有初始化的时候创建数据实体。 */
							|
							+-> eth_dev->data->dev_private = rte_zmalloc() /* 为 以太网设备的私有数据 分配空间 */
							+-> TAILQ_INIT(&(eth_dev->link_intr_cbs)) /* 初始化中断处理回调函数链表 */
							+-> (*eth_drv->eth_dev_init)(eth_dev); /* 调用 驱动 的 eth_dev_init 回调函数 初始化网口设备 */
								.	/* 千兆: rte_igb_pmd.eth_dev_init = eth_igb_dev_init */
								+~> eth_igb_dev_init()				/* 千兆以太网 设备初始化 */
								.	+-> eth_dev->dev_ops = &eth_igb_ops;			/* 关联网口设备操作回调函数 */
								.	+-> eth_dev->rx_pkt_burst = &eth_igb_recv_pkts;	/* 设置默认收包回调函数 */
								.	+-> eth_dev->tx_pkt_burst = &eth_igb_xmit_pkts; /* 设置默认发包回调函数 */
								.	+-> igb_hardware_init() 						/* 初始化 硬件 */ 
								.	+-> rte_intr_callback_register()				/* 初始化中断 */
								.
								.	/* 万兆: rte_ixgbe_pmd.eth_dev_init = eth_ixgbe_dev_init */
								+~> 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;   /* 设置默认发包回调函数 */
									+-> ixgbe_init_hw()								/* 初始化 硬件 */ 
									+-> rte_intr_callback_register()				/* 初始化中断 */ 

2.1. rte_eal_pci_init() 函数

rte_eal_pci_init 函数 用于 pci 设备和驱动的初始化:

  1. rte_eal_pci_init 函数 初始化 了 两个全局的 tail queue
    1.1. pci_driver_list,用于组织 pci 驱动
    2.2. pci_device_list,用于组织 pci 设备
  2. 最后通过 rte_eal_pci_scan 函数,来 扫描 网络设备。

链表 pci_driver_listpci_device_list 的定义:

	TAILQ_HEAD(pci_device_list, rte_pci_device); /**< PCI devices in D-linked Q. */
	TAILQ_HEAD(pci_driver_list, rte_pci_driver); /**< PCI drivers in D-linked Q. */

	extern struct pci_driver_list pci_driver_list; /**< Global list of PCI drivers. */
	extern struct pci_device_list pci_device_list; /**< Global list of PCI devices. */

2.1.1. struct rte_pci_device

struct rte_pci_device 用于表示 一个 pci 设备

其中记录了 pci 设备addrid, mem_resource。当中最重要的是 id 字段 和 driver 指针。

  1. id 字段 记录了 pci 设备vendor_iddevice_id。后续用于探测驱动 driver
  2. driver 指针指向 关联的 pci 驱动。后续需要 driver 来初始化 设备。
	/**
	 * A structure describing a PCI device.
	 */
	struct rte_pci_device {
   
		TAILQ_ENTRY(rte_pci_device) next;       /**< Next probed PCI device. */
		struct rte_pci_addr addr;               /**< PCI location. */
		struct rte_pci_id id;                   /**< <== PCI ID. */
		struct rte_pci_resource mem_resource[PCI_MAX_RESOURCE];   /**< PCI Memory Resource */
		struct rte_intr_handle intr_handle;     /**< Interrupt handle */
		struct rte_pci_driver *driver;          /**< <== Associated driver */
		uint16_t max_vfs;                       /**< sriov enable if not zero */
		int numa_node;                          /**< NUMA node connection */
		struct rte_devargs *devargs;            /**< Device user arguments */
		enum rte_kernel_driver kdrv;            /**< Kernel driver passthrough */
	};

struct rte_pci_device 类图
在这里插入图片描述

2.1.2. struct rte_pci_driver

struct rte_pci_driver 表示 pci 驱动

其中比较重要的成员变量有:

  1. 回调函数 devinitdevuninit,用于以 pci 设备的 初始化和 反初始化。
  2. id_table 指向 pci 驱动所支持的 供应商设备类型。用于 rte_eal_pci_probe_one_driver 函数。

注意:
千兆 和 万兆 网卡 id_table 的定义,请参考全局变量 pci_id_igb_map(千兆) 或者 pci_id_ixgbe_map(万兆)。

	/**
	 * A structure describing a PCI driver.
	 */
	struct rte_pci_driver {
   
		TAILQ_ENTRY(rte_pci_driver) next;       /**< Next in list. */
		const char *name;                       /**< Driver name. */
		pci_devinit_t *devinit;                 /**< <== Device init. function. */
		pci_devuninit_t *devuninit;             /**< <== Device uninit function. */
		const struct rte_pci_id *id_table;		/**< <== ID table, NULL terminated. */
		uint32_t drv_flags;                     /**< Flags contolling handling of device. */
	};

struct rte_pci_driver 类图
在这里插入图片描述

2.1.3. rte_eal_pci_scan 函数

rte_eal_pci_scan 函数用于扫描 pci 设备(网口)。

  1. 读取 ‘SYSFS_PCI_DEVICES’ ("/sys/bus/pci/devices") 目录下的 pci设备 的 pci 地址。
  2. 使用 parse_pci_addr_format 函数,提取 的 pci 地址 信息 (domain, bus, devid, function)。
  3. 使用 pci_get_sysfs_path 返回的 SYSFS_PCI_DEVICES ("/sys/bus/pci/devices"),拼接出 pci 设备的文件子系统路径。
  4. pci_scan_one 解释 单个 pci 设备的详细信息。

函数调用图如下:

	rte_eal_init
	+-> rte_eal_pci_init		/* 对 所有 dpdk 托管 pci 网口 的 进行初始化 */
		+=> rte_eal_pci_scan	/* <== 解释 单个 pci 设备的详细信息 */

以下的是部分的 rte_eal_pci_scan 函数的代码。

	int
	rte_eal_pci_scan(void)
	{
   
		struct dirent *e;
		DIR *dir;
		char dirname[PATH_MAX];
		uint16_t domain;
		uint8_t bus, devid, function;
	
		/* ------------------------------------*/
		/* 读取 SYSFS_PCI_DEVICES "/sys/bus/pci/devices" 目录下的 pci设备 的 pci 地址。 */
		dir = opendir(pci_get_sysfs_path());
		if (dir == NULL) {
   
			RTE_LOG(ERR, EAL, "%s(): opendir failed: %s\n",
				__func__, strerror(errno));
			return -1;
		}
	
		while ((e = readdir(dir)) != NULL) {
   
			if (e->d_name[0] == '.')
				continue;
	
			/* ------------------------------------*/
			/* 提取 的 pci 地址 信息 (domain, bus, devid, function) */
			if (parse_pci_addr_format(e->d_name, sizeof(e->d_name), &domain,
					&bus, &devid, &function) != 0)
				continue;
	
			/* ------------------------------------*/
			/* 拼接出 pci 设备的文件子系统路径 */
			snprintf(dirname, sizeof(dirname), "%s/%s",
					pci_get_sysfs_path(), e->d_name);
			
			/* ------------------------------------*/
			/* 解释 单个 pci 设备的详细信息。*/
			if (pci_scan_one(dirname, domain, bus, devid, function) < 0)
				goto error;
		}
		closedir(dir);
		return 0;
	
	error:
		closedir(dir);
		return -1;
	}
2.1.3.1. 读取 pci设备 文件

rte_eal_pci_scan 函数 会 查看 ‘SYSFS_PCI_DEVICES’ ("/sys/bus/pci/devices") 目录下的 pci设备 文件,绑定pci设备
文件中每一文件代表一个 pci 设备(网口)的地址。

其中 pci_get_sysfs_path 函数,对于 linux 系统,默认返回的是 SYSFS_PCI_DEVICES ("/sys/bus/pci/devices")。

例子:
通过命令行,打印出网络设备的 pci 地址

	ls /sys/bus/pci/devices
	>	0000:04:00.0
	>	0000:04:00.1
	>	0000:04:00.2
	>	0000:04:00.3
	>	0000:05:00.0
	>	0000:05:00.1
	>	0000:05:00.2
	>	0000:05:00.3
	>	0000:08:00.0
	>	0000:08:00.1
	>	0000:09:00.0
	>	0000:09:00.1
2.1.3.2. parse_pci_addr_format 函数

parse_pci_addr_format 函数,从 pci 地址提取,pci 地址 信息

函数调用图如下:

	rte_eal_init
	+-> rte_eal_pci_init		/* 对 所有 dpdk 托管 pci 网口 的 进行初始化 */
		+-> rte_eal_pci_scan	/* 解释 单个 pci 设备的详细信息 */
			+=> parse_pci_addr_format /* 提取 pci 地址 信息 */

pci 地址 信息 的组成格式为:

	<domain>:<bus>:<devid>.<function>
名称 类型 字符串表示为
domain uint16_t “0000” 4字符,有前导零,十六进制表示
bus uint8_t “00” 2字符,有前导零,十六进制表示
devid uint8_t “00” 2字符,有前导零,十六进制表示
function uint8_t “#” 无前导零,十进制表示

以 “0000:04:00.0” 为例,可解读得出:

名称 字符串值
domain “0000”
bus “04”
devid “00”
function “0”
2.1.3.3. pci 设备的文件子系统路径

为了拼接得到 pci 设备的文件子系统路径,需要先调用 pci_get_sysfs_path 函数,返回 总的 pci 设备的文件子系统路径。
最后在后边补上 pci 设备的地址。

例子:
"0000:04:00.0" 为例,则该 pci 设备的文件子系统路径 为 "/sys/bus/pci/devices/0000:04:00.0"

以下使用 ll 命令,打印出 千兆网口 /sys/bus/pci/devices/0000:04:00.0 的内容(只显示了部分)。
后续的 pci_scan_one 函数,将会解释 pci 设备的 文件子系统的详细数据。

	cd /sys/bus/pci/devices/0000:04:00.0
	ll
	>	total 0
	>	...
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 class
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 device
	>	lrwxrwxrwx 1 root root      0 Dec 12 13:34 driver -> ../../../../../../bus/pci/drivers/igb_uio
	>	-rw-r--r-- 1 root root   4096 Dec 12 13:34 max_vfs
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 numa_node
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 resource
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 subsystem_device
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 subsystem_vendor
	>	-r--r--r-- 1 root root   4096 Dec 12 13:34 vendor
	>	...

注意:

上面的例子当中 软连接 "/sys/bus/pci/devices/0000:04:00.0/driver" 所指向的文件 "../../../../../../bus/pci/drivers/igb_uio",是 内核驱动类型。
后续的 pci_scan_one 函数会解释该信息。

2.1.3.4. pci_scan_one 函数

pci_scan_one 函数,用于解释 单个 pci 设备的详细信息。

  1. pci 设备 分配内存。
  2. 填充 pci 设备pci 地址 信息
  3. 读取 pci 设备 id 信息 和 max_vfs 等信息。
  4. 解释 pci 设备resource
  5. 解释 pci 设备内核驱动类型
  6. pci 设备 插入到 pci_device_list 链表。

函数调用图如下:

	rte_eal_init
	+-> rte_eal_pci_init		/* 对 所有 dpdk 托管 pci 网口 的 进行初始化 */
		+-> rte_eal_pci_scan	/* 取得 网口 的 addr 信息 */
			+=> pci_scan_one	/* <== 解释 单个 pci 设备的详细信息。 */

以下是 pci_scan_one 的简化后的代码:

	/* Scan one pci sysfs entry, and fill the devices list from it. */
	static int
	pci_scan_one(const char *dirname, uint16_t domain, uint8_t bus,
			 uint8_t devid, uint8_t function)
	{
   
		char filename[PATH_MAX];
		unsigned long tmp;
		struct rte_pci_device *dev;
		char driver[PATH_MAX];
		int ret;

		/* ------------------------------------*/
		/* 为 pci 设备 分配内存。*/
		dev = malloc(sizeof(*dev));
		if (dev == NULL)
			return -1;

		/* ------------------------------------*/
		/* 填充 pci 设备 的 pci 地址 信息。*/
		memset(dev, 0, sizeof(*dev));
		dev->addr.domain = domain;
		dev->addr.bus = bus;
		dev->addr.devid = devid;
		dev->addr.function = function;

		/* ------------------------------------*/
		/* 读取 pci 设备 id 信息 和 max_vfs 等信息。*/
		/* get vendor id */
		snprintf(filename, sizeof(filename), "%s/vendor", dirname);
		eal_parse_sysfs_value(filename, &tmp);
		dev->id.vendor_id = (uint16_t)tmp;

		/* get device id */
		snprintf(filename, sizeof(filename), "%s/device", dirname);
		eal_parse_sysfs_value(filename, &tmp);
		dev->id.device_id = (uint16_t)tmp;

		/* get subsystem_vendor id */
		snprintf(filename, sizeof(filename), "%s/subsystem_vendor",
			 dirname);
		eal_parse_sysfs_value(filename, &tmp);
		dev->id.subsystem_vendor_id = (uint16_t)tmp;

		/* get subsystem_device id */
		snprintf(filename, sizeof(filename), "%s/subsystem_device",
			 dirname);
		eal_parse_sysfs_value(filename, &tmp);
		dev->id.subsystem_device_id = (uint16_t)tmp;

		/* get class_id */
		snprintf(filename, sizeof(filename), "%s/class",
			 dirname);
		eal_parse_sysfs_value(filename, &tmp);
		/* the least 24 bits are valid: class, subclass, program interface */
		dev->id.class_id = (uint32_t)tmp & RTE_CLASS_ANY_ID;

		/* get max_vfs */
		dev->max_vfs = 0;
		snprintf(filename, sizeof(filename), "%s/max_vfs", dirname);
		if (!access(filename, F_OK) &&
			eal_parse_sysfs_value(filename, &tmp) == 0)
			dev->max_vfs = (uint16_t)tmp;
		else {
   
			/* for non igb_uio driver, need kernel version >= 3.8 */
			snprintf(filename, sizeof(filename),
				 "%s/sriov_numvfs", dirname);
			if (!access(filename, F_OK) &&
				eal_parse_sysfs_value(filename, &tmp) == 0)
				dev->max_vfs = (uint16_t)tmp;
		}

		/* get numa node */
		snprintf(filename, sizeof(filename), "%s/numa_node",
			 dirname);
		if (access(filename, R_OK) != 0) {
   
			/* if no NUMA support, set default to 0 */
			dev->numa_node = 0;
		} else {
   
			eal_parse_sysfs_value(filename, &tmp);
			dev->numa_node = tmp;
		}

		/* ------------------------------------*/
		/* 解释 pci 设备 的 resource。*/
		/* parse resources */
		snprintf(filename, sizeof(filename), "%s/resource", dirname);
		pci_parse_sysfs_resource(filename, dev);

		/* ------------------------------------*/
		/* 解释 pci 设备 的 内核驱动类型。*/
		/* parse driver */
		snprintf(filename, sizeof(filename), "%s/driver", dirname);
		ret = pci_get_kernel_driver_by_path(filename, driver);

		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 链表。*/
		/* device is valid, add in list (sorted) */
		if (TAILQ_EMPTY(&pci_device_list)) {
   
			TAILQ_INSERT_TAIL(&pci_device_list, dev, next);
		} else {
   
			struct rte_pci_device *dev2;
			int ret;

			TAILQ_FOREACH(dev2, &pci_device_list, next) {
   
				ret = rte_eal_compare_pci_addr(&dev->addr, &dev2->addr);

				if (ret == 0) {
    /* already registered */
					dev2->kdrv = dev->kdrv;
					dev2->max_vfs = dev->max_vfs;
					memmove(dev2->mem_resource, dev->mem_resource,
						sizeof(dev->mem_resource));
					free(dev);
					
					return 0;
				} else {
   
					continue;
				}
			}
			TAILQ_INSERT_TAIL(&pci_device_list, dev, next);
		}

		return 0;
	}
2.1.3.4.1. 填充 pci 设备pci 地址 信息

struct rte_pci_device 中有属于 struct rte_pci_addr 的成员变量 addr。该成员变量 表示的是 pci 地址 信息

struct rte_pci_addr 的定义如下:

	/**
	 * A structure describing the location of a PCI device.
	 */
	struct rte_pci_addr {
   
		uint16_t domain;                /**< Device domain */
		uint8_t bus;                    /**< Device bus */
		uint8_t devid;                  /**< Device ID */
		uint8_t function;               /**< Device function. */
	};

之前 parse_pci_addr_format 函数 所解释得到的 pci 地址信息,通过传参的方式传入到 pci_scan_one 函数,最后用来填充 pci 设备的成员变量 addr

	static int
	pci_scan_one(const char *dirname, uint16_t domain, uint8_t bus,
			 uint8_t devid, uint8_t function)
	{
   
		/* ------------------------------------*/
		/* 填充 pci 设备 的 pci 地址 信息。*/
		memset(dev, 0, sizeof(*dev));
		dev->addr.domain = domain;
		dev->addr.bus = bus;
		dev->addr.devid = devid;
		dev->addr.function = function;
		/* ... */
	}
2.1.3.4.2. 读取 pci 设备 id 信息 和 max_vfs 等信息

struct rte_pci_device 中有属于 struct rte_pci_id 的成员变量 id。用于表示 pci id 信息

struct rte_pci_id 的定义:

	/**
	 * A structure describing an ID for a PCI driver. Each driver provides a
	 * table of these IDs for each device that it supports.
	 */
	struct rte_pci_id {
   
		uint32_t class_id;            /**< Class ID (class, subclass, pi) or RTE_CLASS_ANY_ID. */
		uint16_t vendor_id;           /**< <== Vendor ID or PCI_ANY_ID. */
		uint16_t device_id;           /**< <== Device ID or PCI_ANY_ID. */
		uint16_t subsystem_vendor_id; /**< Subsystem vendor ID or PCI_ANY_ID. */
		uint16_t subsystem_device_id; /**< Subsystem device ID or PCI_ANY_ID. */
	};

pci_scan_one 函数中,则通过 eal_parse_sysfs_value 函数依次解读出 pci id 信息

重点:

pci 设备中的 成员变量 id,其中的 vendor_iddevice_id 是主要用于侦察和匹配 pci 驱动的。

例子:

使用命令行,打印 pci 设备 id 信息max_vfs 等信息。

	cat /sys/bus/pci/devices/0000:04:00.0/vendor
	>	0x8086
	cat /sys/bus/pci/devices/0000:04:00.0/device
	>	0x1521
	cat /sys/bus/pci/devices/0000:04:00.0/subsystem_vendor
	>	0x1304
	cat /sys/bus/pci/devices/0000:04:00.0/subsystem_device
	>	0xffff
	cat /sys/bus/pci/devices/0000:04:00.0/class
	>	0x020000
	cat /sys/bus/pci/devices/0000:04:00.0/max_vfs
	>	0
	cat /sys/bus/pci/devices/0000:04:00.0/numa_node
	>	-1
2.1.3.4.3. 解释 pci 设备resource

struct rte_pci_device 中有属于 struct rte_pci_resource 的成员变量 mem_resource[PCI_MAX_RESOURCE]
mem_resource[PCI_MAX_RESOURCE]用于记录,后续 mmap 要用到的uio 资源的物理地址。

struct rte_pci_resource 的定义:

	/**
	 * A structure describing a PCI resource.
	 */
	struct rte_pci_resource {
   
		uint64_t phys_addr;   /**< Physical address, 0 if no resource. */
		uint64_t len;         /**< Length of the resource. */
		void *addr;           /**< Virtual address, NULL when not mapped. */
	};

pci_parse_sysfs_resource 函数读取 /sys/bus/pci/devices/xxxx:xx:xx.x/resource 的信息来填充 mem_resource[]
每一行的数据会先由 pci_parse_one_sysfs_resource 解读。
最后只有是 flags 属于 IORESOURCE_MEM 类型填充的数据,才填充到 mem_resource[]

	/** IO resource type: */
	#define IORESOURCE_IO         0x00000100	/* IO资源 */
	#define IORESOURCE_MEM        0x00000200	/* <== 内存资源 */

函数调用图如下:

	rte_eal_init
	+-> rte_eal_pci_init		/* 对 所有 dpdk 托管 pci 网口 的 进行初始化 */
		+-> rte_eal_pci_scan	/* 取得 网口 的 addr 信息 */
			+-> pci_scan_one	/* 解释 单个 pci 设备的详细信息。 */
				+=> pci_parse_sysfs_resource /* 取得 网口 的 resource 信息 */

以下是简化的 函数:

	static int
	pci_parse_sysfs_resource(const char *filename, struct rte_pci_device *dev)
	{
   
		FILE *f;
		char buf[BUFSIZ];
		int i;
		uint64_t phys_addr, end_addr, flags;

		f = fopen(filename, "r");

		for (i = 0; i<PCI_MAX_RESOURCE; i++) {
   

			fgets(buf, sizeof(buf), f);

			pci_parse_one_sysfs_resource(buf, sizeof(buf), 
  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值