DPDK 16.07 驱动初始化和收发包函数学习笔记
- 文档保留了 markdown 格式。可以转为纯文本格式,方便在其他编辑器中使用语法高亮阅读代码。
- 文档行文主要是提纲式的。如果阅读过程发现缺少了方向。请立刻回到章节的开首处,查询总体的函数调用图。
- 文档只讨论了千兆(
I350 Gigabit
)和 万兆(82599ES 10-Gigabit SFI/SFP+
)的驱动做为例子。 - 分析代码的过程中,各分别绑定了一个网口千兆(
04:00.0
)和 万兆(08:00.0
)做为例子。 - 如果你的时间宝贵。请着重阅读以下重要的章节:
5.1. <#### 2.2.2.1. id_table 的定义> <== DPDK 所支持的驱动列表
5.2. <### 4.1.4. 创建 收包队列的 mbuf> <== DPDK 零拷贝的实现 - 文档的图例基本使用 UML。但也有自定义的图例。详细见 [本文档所使用的图例] 的说明。
本文档所使用的图例:
注意:
聚合和组合都表示整体和部分的关联关系。
如果整体销毁后,部分也随之销毁。会使用组合表示。
如果整体销毁后,部分可以独立存在。会使用聚合表示。
1. rte_driver
驱动 的 注册
rte_driver
驱动 是用宏函数 PMD_REGISTER_DRIVER()
,创建一个拼接函数,然后注册驱动的。
函数调用图如下:
PMD_REGISTER_DRIVER(xxx)
+-> devinitfn_xxx_drv /* 拼接生成的注册函数 */
+-> rte_eal_driver_register() /* 注册驱动 */
1.1. rte_driver
驱动 的 结构体
dpdk
的 rte_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
则表示驱动的类型:
PMD_PDEV
是物理设备驱动;PMD_VDEV
是虚拟设备驱动。
例子:
- 千兆网卡的驱动为
pmd_igb_drv
。
其中 的 回调函数init
设置 为rte_igb_pmd_init
。
static struct rte_driver pmd_igb_drv = {
.type = PMD_PDEV,
.init = rte_igb_pmd_init, /* <== 驱动的初始化 */
};
- 万兆网卡的驱动为
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__)
例子:
- 千兆网卡的注册:
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); /* <== 注册千兆驱动 */
}
- 万兆网卡的注册:
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_list
的 tail_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
初始化,其中与设备相关的部分主要函数如下:
- rte_eal_pci_init 对 所有 dpdk 托管 pci 网卡 的 进行初始化。
- rte_eal_dev_init 设备 的初始化。
- 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(ð_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(ð_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 = ð_igb_ops; /* 关联网口设备操作回调函数 */
. +-> eth_dev->rx_pkt_burst = ð_igb_recv_pkts; /* 设置默认收包回调函数 */
. +-> eth_dev->tx_pkt_burst = ð_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
设备和驱动
的初始化:
rte_eal_pci_init
函数 初始化 了 两个全局的tail queue
。
1.1.pci_driver_list
,用于组织pci 驱动
。
2.2.pci_device_list
,用于组织pci 设备
。- 最后通过
rte_eal_pci_scan
函数,来 扫描 网络设备。
链表 pci_driver_list
和 pci_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 设备
的 addr
,id
, mem_resource
。当中最重要的是 id
字段 和 driver
指针。
id
字段 记录了pci 设备
的vendor_id
和device_id
。后续用于探测驱动driver
。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 驱动
。
其中比较重要的成员变量有:
- 回调函数
devinit
和devuninit
,用于以pci 设备
的 初始化和 反初始化。 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 设备(网口)。
- 读取 ‘SYSFS_PCI_DEVICES’ ("/sys/bus/pci/devices") 目录下的 pci设备 的 pci 地址。
- 使用
parse_pci_addr_format
函数,提取 的pci 地址
信息 (domain
,bus
,devid
,function
)。 - 使用
pci_get_sysfs_path
返回的SYSFS_PCI_DEVICES
("/sys/bus/pci/devices"),拼接出 pci 设备的文件子系统路径。 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 设备
的详细信息。
- 为
pci 设备
分配内存。 - 填充
pci 设备
的pci 地址 信息
。 - 读取
pci 设备
id
信息 和max_vfs
等信息。 - 解释
pci 设备
的resource
。 - 解释
pci 设备
的内核驱动类型
。 - 将
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_id
和 device_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),