DPDK 16.07 驱动初始化和收发包函数学习笔记
I350 Gigabit
)和 万兆(82599ES 10-Gigabit SFI/SFP+
)的驱动做为例子。04:00.0
)和 万兆(08:00.0
)做为例子。注意:
聚合和组合都表示整体和部分的关联关系。
如果整体销毁后,部分也随之销毁。会使用组合表示。
如果整体销毁后,部分可以独立存在。会使用聚合表示。
rte_driver
驱动 的 注册rte_driver
驱动 是用宏函数 PMD_REGISTER_DRIVER()
,创建一个拼接函数,然后注册驱动的。
函数调用图如下:
PMD_REGISTER_DRIVER(xxx)
+-> devinitfn_xxx_drv /* 拼接生成的注册函数 */
+-> rte_eal_driver_register() /* 注册驱动 */
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_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
。 static struct rte_driver rte_ixgbe_driver = {
.type = PMD_PDEV,
.init = rte_ixgbe_pmd_init, /* <== 驱动的初始化 */
};
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); /* <== 注册万兆驱动 */
}
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
链表
EAL
的初始化环境抽象层 EAL
初始化,其中与设备相关的部分主要函数如下:
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() /* 初始化中断 */
rte_eal_pci_init()
函数rte_eal_pci_init
函数 用于 pci
设备和驱动
的初始化:
rte_eal_pci_init
函数 初始化 了 两个全局的 tail queue
。pci_driver_list
,用于组织 pci 驱动
。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. */
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_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. */
};
rte_eal_pci_scan
函数rte_eal_pci_scan 函数用于扫描 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;
}
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
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 | 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” |
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
函数会解释该信息。
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;
}
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;
/* ... */
}
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
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), &phys_addr,
&end_addr, &flags);
/* 只有是 flags 属于 IORESOURCE_MEM 类型填充的数据,才填充 */
if (flags & IORESOURCE_MEM) {
dev->mem_resource[i].phys_addr = phys_addr;
dev->mem_resource[i].len = end_addr - phys_addr + 1;
/* not mapped for now */
dev->mem_resource[i].addr = NULL;
}
}
fclose(f);
例子:
pci 设备
resource 信息
。 cat /sys/bus/pci/devices/0000:04:00.0/resource
> # i # phys_addr # end_addr # flags # resource
> 00 0x00000000f7b60000 0x00000000f7b7ffff 0x0000000000040200 # IORESOURCE_MEM <== mmap 有效
> 01 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 02 0x000000000000e060 0x000000000000e07f 0x0000000000040101 # IORESOURCE_IO
> 03 0x00000000f7b8c000 0x00000000f7b8ffff 0x0000000000040200 # IORESOURCE_MEM <== mmap 有效
> 04 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 05 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 06 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 07 0x00000000f0000000 0x00000000f001ffff 0x000000000014220c # IORESOURCE_MEM <== mmap 有效
> 08 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 09 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 10 0x00000000f0020000 0x00000000f003ffff 0x000000000014220c # IORESOURCE_MEM <== mmap 有效
> 11 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 12 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
pci 设备
resource 信息
。 cat /sys/bus/pci/devices/0000:08:00.0/resource
> # i # phys_addr # end_addr # flags # resource
> 00 0x00000000f7920000 0x00000000f793ffff 0x0000000000140204 # IORESOURCE_MEM <== mmap 有效
> 01 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 02 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 03 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 04 0x00000000f7944000 0x00000000f7947fff 0x0000000000140204 # IORESOURCE_MEM <== mmap 有效
> 05 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 06 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 07 0x00000000f0300000 0x00000000f03fffff 0x000000000014220c # IORESOURCE_MEM <== mmap 有效
> 08 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 09 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 10 0x00000000f0400000 0x00000000f04fffff 0x000000000014220c # IORESOURCE_MEM <== mmap 有效
> 11 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
> 12 0x0000000000000000 0x0000000000000000 0x0000000000000000 #
上面一条的 resource
中。数据的格式如下:
名称 | 位置 | 例子 |
---|---|---|
phys_addr | 第一列 | “0x00000000f7b60000” |
end_addr | 第二列 | “0x00000000f7b7ffff” |
flags | 第三列 | “0x0000000000040200” |
注意:
flags
属于 IORESOURCE_MEM
类型填充的数据,才填充到 mem_resource[]
。pci 设备
的 内核驱动类型
pci_get_kernel_driver_by_path
函数 用来 取得 内核驱动
的 名字。
其中使用了 readlink
函数来取得,软连接所指向的 驱动的路径。
最后取得位于末尾分段的驱动的名字。(用 basename
函数,会更容易实现)
函数调用图如下:
rte_eal_init
+-> rte_eal_pci_init /* 对 所有 dpdk 托管 pci 网口 的 进行初始化 */
+-> rte_eal_pci_scan /* 取得 网口 的 addr 信息 */
+-> pci_scan_one /* 解释 单个 pci 设备的详细信息。 */
+=> pci_get_kernel_driver_by_path /* 取得 内核驱动的 名字 */
以下是简化的 pci_get_kernel_driver_by_path
函数:
static int
pci_get_kernel_driver_by_path(const char *filename, char *dri_name)
{
int count;
char path[PATH_MAX];
char *name;
/* 取得软连接所指向的 驱动的路径 */
count = readlink(filename, path, PATH_MAX);
/* 取得位于末尾分段的驱动的名字 */
name = strrchr(path, '/');
if (name) {
strncpy(dri_name, name + 1, strlen(name + 1) + 1);
return 0;
}
return -1;
}
pci_scan_one
函数最后通过字符串比较,就可以得出 内核驱动 类型
。
以下是 pci_scan_one
函数,调用 pci_get_kernel_driver_by_path
和得出 内核驱动 类型
。
static int
pci_scan_one(const char *dirname, uint16_t domain, uint8_t bus,
uint8_t devid, uint8_t function)
{
/* ... */
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;
/* ... */
}
在 dpdk
中,内核驱动 类型
的 枚举类型,定义如下:
enum rte_kernel_driver {
RTE_KDRV_UNKNOWN = 0,
RTE_KDRV_IGB_UIO,
RTE_KDRV_VFIO,
RTE_KDRV_UIO_GENERIC,
RTE_KDRV_NIC_UIO,
RTE_KDRV_NONE,
};
例子:
使用 readlink
命令来打印出 网卡的驱动名字。
两种不同类型的网卡所打印出来的 内核驱动 类型,都为 igb_uio
。
# 千兆 铜线
# 04:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
readlink /sys/bus/pci/devices/0000:04:00.0/driver
> ../../../../../../bus/pci/drivers/igb_uio
# 万兆
# 08:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
readlink /sys/bus/pci/devices/0000:08:00.0/driver
> ../../../../../../../../bus/pci/drivers/igb_uio
pci 设备
插入到 pci_device_list
链表pci_scan_one
函数最后通过 TAILQ_INSERT_TAIL
将 pci 设备
插入到 pci_device_list
。
pci_device_list
内部元素,是按照 pci 地址
升序排列。
以下的是插入到 pci_device_list
代码片段。
static int
pci_scan_one(const char *dirname, uint16_t domain, uint8_t bus,
uint8_t devid, uint8_t function)
{
/* ... */
/* 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)
continue;
if (ret < 0) {
TAILQ_INSERT_BEFORE(dev2, dev, next);
} else { /* 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;
}
TAILQ_INSERT_TAIL(&pci_device_list, dev, next);
}
/* ... */
}
最后,pci_device_list
会组成一个链表。如下图所示:
rte_eal_dev_init
函数rte_eal_dev_init
用于 设备的初始化。其中包括:
函数调用图如下:
rte_eal_init
+=> rte_eal_dev_init /* <== 设备 的初始化 */
+-> rte_eal_vdev_init /* 虚拟设备 初始化 (暂不讨论) */
+-> driver->init(NULL, NULL); /* 物理设备的 PMD 驱动的初始化 */
rte_eal_dev_init
函数会从 dev_driver_list
中,遍历所有 struct rte_driver
,并调用各个 driver
的 init()
回调函数,来初始化 PMD 驱动
。
一旦PMD 驱动
初始化完成后,dev_driver_list
就完成任务,再也没有什么作用了。
rte_eal_dev_init
函数源码:
int
rte_eal_dev_init(void)
{
struct rte_devargs *devargs;
struct rte_driver *driver;
/*
* Note that the dev_driver_list is populated here
* from calls made to rte_eal_driver_register from constructor functions
* embedded into PMD modules via the PMD_REGISTER_DRIVER macro
*/
/* 虚拟设备 初始化 */
/* call the init function for each virtual device */
TAILQ_FOREACH(devargs, &devargs_list, next) {
if (devargs->type != RTE_DEVTYPE_VIRTUAL)
continue;
if (rte_eal_vdev_init(devargs->virt.drv_name,
devargs->args)) {
RTE_LOG(ERR, EAL, "failed to initialize %s device\n",
devargs->virt.drv_name);
return -1;
}
}
/* 物理设备的 PMD 驱动的初始化 */
/* Once the vdevs are initalized, start calling all the pdev drivers */
TAILQ_FOREACH(driver, &dev_driver_list, next) {
if (driver->type != PMD_PDEV)
continue;
/* PDEV drivers don't get passed any parameters */
driver->init(NULL, NULL);
}
return 0;
}
例子:
假设 dev_driver_list
上有 千兆网卡驱动(pmd_igb_drv
) 和 万兆网卡驱动(rte_ixgbe_driver
) 两种。
init()
回调函数为:rte_igb_pmd_init
。init()
回调函数为:rte_ixgbe_pmd_init
。rte_eal_dev_init
逐个触发 dev_driver_list
上所有驱动的 init()
回调函数
struct rte_driver
的 init()
回调函数的实现无论 千兆网卡驱动初始化 rte_igb_pmd_init()
还是 万兆网卡驱动初始化 rte_ixgbe_pmd_init()
,最后都调用了 rte_eth_driver_register()
来注册 各自的以太网 PMD 驱动
。
详细的以太网 PMD 驱动
结构,见后续的 struct eth_driver
。
rte_igb_pmd_init
函数调用图rte_igb_pmd_init
注册了 rte_igb_pmd
驱动。后续 struct eth_driver 一节中有例子说明。
rte_eal_init
+-> rte_eal_dev_init
+-> driver->init(NULL, NULL);
+=> rte_igb_pmd_init /* 千兆网卡驱动初始化 */
+-> rte_eth_driver_register(&rte_igb_pmd);
rte_ixgbe_pmd_init
函数调用图rte_ixgbe_pmd_init
注册了 rte_ixgbe_pmd
驱动。后续 struct eth_driver 一节中有例子说明。
rte_eal_init
+-> rte_eal_dev_init
+-> driver->init(NULL, NULL);
+=> rte_ixgbe_pmd_init /* 万兆网卡驱动初始化 */
+-> rte_eth_driver_register(&rte_ixgbe_pmd);
struct eth_driver
struct eth_driver
表示的是一个以太网 PMD 驱动
。
由于其中的第一个成员变量 pci_drv
为 struct rte_pci_driver
,所以可以强行转换为 struct rte_pci_driver
。
因此该结构体 为 可以认为是 struct rte_pci_driver
的子类。
所以前面所提及的 pci_driver_list
链表头,可以指向于 struct eth_driver
。
结构体当中最重要的是 eth_dev_init
和 eth_dev_uninit
,用于以太网设备的 初始化和 反初始化。
struct eth_driver {
struct rte_pci_driver pci_drv; /**< The PMD is also a PCI driver. */
eth_dev_init_t eth_dev_init; /**< <== Device init function. */
eth_dev_uninit_t eth_dev_uninit; /**< <== Device uninit function. */
unsigned int dev_private_size; /**< Size of device private data. */
};
struct eth_driver
与 struct rte_pci_driver
的关系
注意:
struct eth_driver
继承自 struct rte_pci_driver
。
例子:
以太网 PMD 驱动
: rte_igb_pmd
注意,其中的 eth_dev_init
设置为 eth_igb_dev_init
。
static struct eth_driver rte_igb_pmd = {
.pci_drv = {
.name = "rte_igb_pmd",
.id_table = pci_id_igb_map, /* <== 千兆 所支持 pci 设备的驱动 id 列表。*/
.drv_flags = RTE_PCI_DRV_NEED_MAPPING | RTE_PCI_DRV_INTR_LSC |
RTE_PCI_DRV_DETACHABLE,
},
.eth_dev_init = eth_igb_dev_init, /* <== 千兆以太网设备初始化 */
.eth_dev_uninit = eth_igb_dev_uninit,
.dev_private_size = sizeof(struct e1000_adapter),
};
以太网 PMD 驱动
: rte_ixgbe_pmd
注意,其中的 eth_dev_init
设置为 eth_ixgbe_dev_init
。
static struct eth_driver rte_ixgbe_pmd = {
.pci_drv = {
.name = "rte_ixgbe_pmd",
.id_table = pci_id_ixgbe_map, /* <== 万兆 所支持 pci 设备的驱动 id 列表。*/
.drv_flags = RTE_PCI_DRV_NEED_MAPPING | RTE_PCI_DRV_INTR_LSC |
RTE_PCI_DRV_DETACHABLE,
},
.eth_dev_init = eth_ixgbe_dev_init, /* <== 万兆以太网设备初始化 */
.eth_dev_uninit = eth_ixgbe_dev_uninit,
.dev_private_size = sizeof(struct ixgbe_adapter),
};
设置了基础信息 的 以太网 PMD 驱动
rte_igb_pmd
(千兆) 和 rte_ixgbe_pmd
(万兆)
注意:
pci_drv.id_table
指向了 所支持 pci 设备
的驱动 id 列表
,用于 pci 设备
的侦测。rte_igb_pmd
和 rte_ixgbe_pmd
都只是 设置了 eth_dev_init
和 eth_dev_uninit
。后续 在调用 rte_eth_driver_register
的时候,会进一步的设置 成员变量 pci_drv
的内容。id_table
的定义全局变量 pci_id_igb_map
(千兆) 或者 pci_id_ixgbe_map
(万兆) 分别是 千兆 和 万兆 所支持 pci 设备
的驱动 id 列表
。
例子:
id_table
:pci_id_igb_map
的代码。
/*
* The set of PCI devices this driver supports
*/
static const struct rte_pci_id pci_id_igb_map[] = {
#define RTE_PCI_DEV_ID_DECL_IGB(vend, dev) {RTE_PCI_DEVICE(vend, dev)},
#include "rte_pci_dev_ids.h"
{0},
};
千兆以太网 id_table
在 #include "rte_pci_dev_ids.h"
后,代码展开后如下:
/* 千兆 设备:
* 如果宏函数没有定义,默认是空的
* 所以不会插入任何代码
*/
#ifndef RTE_PCI_DEV_ID_DECL_IGB
#define RTE_PCI_DEV_ID_DECL_IGB(vend, dev)
#endif
/******************** Physical IGB devices from e1000_hw.h ********************/
/* 声明支持 千兆设备 若干*/
RTE_PCI_DEV_ID_DECL_IGB(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_I350_COPPER) /* 前面例子的千兆设备 */
RTE_PCI_DEV_ID_DECL_IGB(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_I350_FIBER)
RTE_PCI_DEV_ID_DECL_IGB(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_I350_SERDES)
RTE_PCI_DEV_ID_DECL_IGB(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_I350_SGMII)
RTE_PCI_DEV_ID_DECL_IGB(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_I350_DA4)
/* ... */
/*
* Undef all RTE_PCI_DEV_ID_DECL_* here.
* 设置宏函数设为未定义
*/
#undef RTE_PCI_DEV_ID_DECL_IGB
#undef RTE_PCI_DEV_ID_DECL_IXGBE
id_table
:pci_id_ixgbe_map
的代码。
/*
* The set of PCI devices this driver supports
*/
static const struct rte_pci_id pci_id_ixgbe_map[] = {
#define RTE_PCI_DEV_ID_DECL_IXGBE(vend, dev) {RTE_PCI_DEVICE(vend, dev)},
#include "rte_pci_dev_ids.h"
{ .vendor_id = 0, /* sentinel */ },
};
万兆以太网 id_table
在 #include "rte_pci_dev_ids.h"
后,代码展开后如下:
/* 万兆 设备:
* 如果宏函数没有定义,默认是空的
* 所以不会插入任何代码
*/
#ifndef RTE_PCI_DEV_ID_DECL_IXGBE
#define RTE_PCI_DEV_ID_DECL_IXGBE(vend, dev)
#endif
/****************** Physical IXGBE devices from ixgbe_type.h ******************/
/* 声明支持 万兆设备 若干*/
RTE_PCI_DEV_ID_DECL_IXGBE(PCI_VENDOR_ID_INTEL, IXGBE_DEV_ID_82599_SFP) /* 前面例子的万兆设备 */
/* ... */
/*
* Undef all RTE_PCI_DEV_ID_DECL_* here.
* 设置宏函数设为未定义
*/
#undef RTE_PCI_DEV_ID_DECL_IGB
#undef RTE_PCI_DEV_ID_DECL_IXGBE
代码分析:
"rte_pci_dev_ids.h"
开始处。如果宏函数 RTE_PCI_DEV_ID_DECL_xxx
没有定义,则默认是空的,所以在默认情况下不会插入任何代码。pci_id_igb_map[]
等,在包含头文件之前,会正确的定义 RTE_PCI_DEV_ID_DECL_IGB()
等宏函数。所以已经定义过的宏函数可以正确展开,插入需要的驱动类型。"rte_pci_dev_ids.h"
结束处。所有的宏函数 RTE_PCI_DEV_ID_DECL_xxx
设为未定义。为下一次的使用作准备。应用:
如果需要添加驱动。则需要先 在头文件中 "rte_pci_dev_ids.h"
添加 id
。那么在 pci_probe_all_drivers
函数可以找到合适的驱动。
rte_eth_driver_register
函数rte_eth_driver_register
函数 用于注册 以太网 PMD 驱动
(struct eth_driver
)。
rte_eth_driver_register
函数 会设置 eth_drv
内嵌的 pci_drv
结构体中 的 字段 devinit
和 devuninit
。当后续 调用 rte_eth_dev_init
函数,进行 pci 设备
侦测 的时候,就会 调用 devinit()
回调函数,来初始化 以太网设备
。rte_eth_driver_register
函数 内部 会调用 rte_eal_pci_register
注册 eth_drv
。函数调用图如下:
rte_eal_init
+-> rte_eal_dev_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 中。 */
源码分析如下:
注意:
无论 千兆 还是 万兆设备 其中的:
pci_drv.devinit
都设置为 rte_eth_dev_init
。pci_drv.devuninit
都设置为 rte_eth_dev_uninit
。 void
rte_eth_driver_register(struct eth_driver *eth_drv)
{
eth_drv->pci_drv.devinit = rte_eth_dev_init; /* <== devinit 都设置为 rte_eth_dev_init */
eth_drv->pci_drv.devuninit = rte_eth_dev_uninit; /* <== devuninit 都设置为 rte_eth_dev_uninit */
rte_eal_pci_register(ð_drv->pci_drv);
}
例子:
以下是 填充了 pci_drv.devinit
和 pci_drv.devuninit
字段的以太网 PMD 驱动
:
设置了 pci_drv 的 以太网 PMD 驱动 rte_igb_pmd
(千兆) 和 rte_ixgbe_pmd
(万兆)
rte_eal_pci_register
函数rte_eal_pci_register
函数 将 驱动 追加到 pci_driver_list
链表 的尾部。
pci_driver_list
链表的表头 则在 rte_eal_pci_init
函数中,就已经初始化好了。
/* register a driver */
void
rte_eal_pci_register(struct rte_pci_driver *driver)
{
TAILQ_INSERT_TAIL(&pci_driver_list, driver, next);
}
例子:
以下是 注册后的 以太网 PMD 驱动
rte_igb_pmd
(千兆) 和 rte_ixgbe_pmd
(万兆):
rte_eal_pci_probe
函数rte_eal_pci_probe
函数 用于 pci 设备
的侦测。
pci_device_list
中所有的 设备。pci_devargs_lookup
查询 pci 设备
用于数据。设置 设备 的 devargs
。pci_probe_all_drivers()
为 该设备选择合适的 驱动。函数调用图如下:
rte_eal_init
+=> rte_eal_pci_probe /* <== pci 设备的侦测(侦测和初始化) */
源码分析如下:
rte_eal_devargs_type_count(RTE_DEVTYPE_WHITELISTED_PCI)
用于 查找 "白名单"的 pci 设备
。pci 设备
,可以在命令行中 通过 -w
来指定。pci 设备
,则返回 0
。所以 probe_all
会设置为 1
。表示侦测所有的 pci 设备
。pci_devargs_lookup
函数,是会查找 命令行中是否有设置 “黑/白名单” 的设备。pci 设备
,则返回 NULL
。 /*
* Scan the content of the PCI bus, and call the devinit() function for
* all registered drivers that have a matching entry in its id_table
* for discovered devices.
*/
int
rte_eal_pci_probe(void)
{
struct rte_pci_device *dev = NULL;
struct rte_devargs *devargs;
int probe_all = 0;
int ret = 0;
/* 没有设置 "白名单",则侦测所有的 pci 设备 */
if (rte_eal_devargs_type_count(RTE_DEVTYPE_WHITELISTED_PCI) == 0)
probe_all = 1;
TAILQ_FOREACH(dev, &pci_device_list, next) {
/* set devargs in PCI structure */
devargs = pci_devargs_lookup(dev); /* 一般,devargs 为 NULL */
if (devargs != NULL)
dev->devargs = devargs;
/* probe all or only whitelisted devices */
if (probe_all) /* 一般,侦测所有的 pci 设备 */
ret = pci_probe_all_drivers(dev);
else if (devargs != NULL &&
devargs->type == RTE_DEVTYPE_WHITELISTED_PCI)
ret = pci_probe_all_drivers(dev);
if (ret < 0)
rte_exit(EXIT_FAILURE, "Requested device " PCI_PRI_FMT
" cannot be used\n", dev->addr.domain, dev->addr.bus,
dev->addr.devid, dev->addr.function);
}
return 0;
}
pci_probe_all_drivers
函数pci_probe_all_drivers
函数,会探测所有的 驱动。一旦匹配,则用来初始化设备。
函数调用图如下:
rte_eal_init
+-> rte_eal_pci_probe /* pci 设备的侦测(侦测和初始化) */
+=> pci_probe_all_drivers /* <== pci 设备 遍历所有 驱动,找到合适的驱动 */
+-> TAILQ_FOREACH(dr, &pci_driver_list, next) /* 遍历所有 驱动 */
+-> rte_eal_pci_probe_one_driver(dr, dev) /* 探测 单个驱动 dr 和 设备 dev */
注意:
pci_driver_list
链表 中的元素 虽然是 struct eth_driver
类型。但是可以强转为 struct rte_pci_device
的。
源码分析如下:
/*
* If vendor/device ID match, call the devinit() function of all
* registered driver for the given device. Return -1 if initialization
* failed, return 1 if no driver is found for this device.
*/
static int
pci_probe_all_drivers(struct rte_pci_device *dev)
{
struct rte_pci_driver *dr = NULL;
int rc = 0;
if (dev == NULL)
return -1;
TAILQ_FOREACH(dr, &pci_driver_list, next) { /* 遍历所有 驱动 */
rc = rte_eal_pci_probe_one_driver(dr, dev); /* 探测 单个驱动 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; /* 驱动和设备匹配 */
}
return 1;
}
rte_eal_pci_probe_one_driver
函数rte_eal_pci_probe_one_driver
函数 探测 单个驱动 dr
和 设备 dev
是否适合。
函数调用图如下:
rte_eal_init
+-> rte_eal_pci_probe /* pci 设备的侦测(侦测和初始化) */
+-> pci_probe_all_drivers /* pci 设备 遍历所有 驱动,找到合适的驱动 */
+=> rte_eal_pci_probe_one_driver /* <== 探测 驱动 和 设备 是否适合 */
+-> rte_eal_pci_map_device(dev); /* igb_uio 地址映射 */
+-> dev->driver = dr; /* 设备 的驱动字段。指向 探测出来的 驱动 dr。 */
+-> dr->devinit(dr, dev) /* 使用 驱动 的 devinit 来初始化设备。 */
源码分析如下:
dr
的 id_table[]
数组。dev
上的 id
字段,与 驱动 dr
某个 id_table[]
的元素匹配上。id_table[]
的元素。dev
是属于 “黑名单” 中。则不予初始化。dr->drv_flags
有 RTE_PCI_DRV_NEED_MAPPING
标志。则 设备做内存映射。dr->drv_flags
有 RTE_PCI_DRV_FORCE_UNBIND
标志 并且是 主进程
。则 设备解绑。dev
的 driver
字段指向 探测出来的 驱动 dr
。dr
的 devinit()
来初始化设备。rte_eal_pci_probe_one_driver
函数 返回值:
-1
。1
。0
。 /*
* If vendor/device ID match, call the devinit() function of the
* driver.
*/
static int
rte_eal_pci_probe_one_driver(struct rte_pci_driver *dr, struct rte_pci_device *dev)
{
int ret;
const struct rte_pci_id *id_table;
for (id_table = dr->id_table; id_table->vendor_id != 0; id_table++) {
/* 测试 驱动 dr 的 id_table 和 设备 dev 的 id 是否匹配 */
/* check if device's identifiers match the driver's ones */
if (id_table->vendor_id != dev->id.vendor_id &&
id_table->vendor_id != PCI_ANY_ID)
continue;
if (id_table->device_id != dev->id.device_id &&
id_table->device_id != PCI_ANY_ID)
continue;
if (id_table->subsystem_vendor_id != dev->id.subsystem_vendor_id &&
id_table->subsystem_vendor_id != PCI_ANY_ID)
continue;
if (id_table->subsystem_device_id != dev->id.subsystem_device_id &&
id_table->subsystem_device_id != PCI_ANY_ID)
continue;
if (id_table->class_id != dev->id.class_id &&
id_table->class_id != RTE_CLASS_ANY_ID)
continue;
struct rte_pci_addr *loc = &dev->addr;
RTE_LOG(INFO, EAL, "PCI device "PCI_PRI_FMT" on NUMA socket %i\n",
loc->domain, loc->bus, loc->devid, loc->function,
dev->numa_node);
/*如果 设备 dev 是属于"黑名单"中。则不予初始化。*/
/* no initialization when blacklisted, return without error */
if (dev->devargs != NULL &&
dev->devargs->type == RTE_DEVTYPE_BLACKLISTED_PCI) {
RTE_LOG(INFO, EAL, " Device is blacklisted, not initializing\n");
return 1;
}
RTE_LOG(INFO, EAL, " probe driver: %x:%x %s\n", dev->id.vendor_id,
dev->id.device_id, dr->name);
/* 如果 驱动 dr->drv_flags 有 RTE_PCI_DRV_NEED_MAPPING 标志。则 设备做内存映射。
* 否则如果 驱动 dr->drv_flags 有 RTE_PCI_DRV_FORCE_UNBIND 标志 并且是 主进程。则 设备解绑。
*/
if (dr->drv_flags & RTE_PCI_DRV_NEED_MAPPING) {
/* map resources for devices that use igb_uio */
ret = rte_eal_pci_map_device(dev);
if (ret != 0)
return ret;
} else if (dr->drv_flags & RTE_PCI_DRV_FORCE_UNBIND &&
rte_eal_process_type() == RTE_PROC_PRIMARY) {
/* unbind current driver */
if (pci_unbind_kernel_driver(dev) < 0)
return -1;
}
/* 设备 dev 的 driver 字段指向 探测出来的 驱动 dr */
/* reference driver structure */
dev->driver = dr;
/* 使用 驱动 dr 的 devinit() 来初始化设备。 */
/* call the driver devinit() function */
return dr->devinit(dr, dev);
}
/* return positive value if driver doesn't support this device */
return 1;
}
rte_eal_pci_probe_one_driver 图例:
注意:
无论 千兆以太网 PMD 驱动(rte_igb_pmd
) 还是 万兆以太网 PMD 驱动(rte_ixgbe_pmd
)都使用了 rte_eth_driver_register
来注册。所以其中的 pci_drv.devinit
都设置为 rte_eth_dev_init()
。
rte_eal_pci_map_device
函数rte_eal_pci_map_device
函数,用于映射pci 设备
RTE_KDRV_VFIO
(虚拟网卡),使用pci_vfio_map_resource()函数映射。RTE_KDRV_IGB_UIO
或者 RTE_KDRV_UIO_GENERIC
(物理网卡),使用pci_uio_map_resource()函数映射。函数调用图如下:
rte_eal_init
+-> rte_eal_pci_probe /* pci 设备的侦测(侦测和初始化) */
+-> pci_probe_all_drivers /* pci 设备 遍历所有 驱动,找到合适的驱动 */
+-> rte_eal_pci_probe_one_driver /* 探测 驱动 和 设备 是否适合 */
+=> rte_eal_pci_map_device(dev); /* <== igb_uio 地址映射 */
.
. /* case: [RTE_KDRV_VFIO] */
+-> pci_vfio_map_resource(dev); /* 映射 vfio 驱动(虚拟网卡) */
.
. /* case: [RTE_KDRV_IGB_UIO | RTE_KDRV_UIO_GENERIC] */
+-> pci_uio_map_resource(dev); /* 映射 uio 驱动(物理网卡) */
函数源码如下:
/* Map pci device */
int
rte_eal_pci_map_device(struct rte_pci_device *dev)
{
int ret = -1;
/* try mapping the NIC resources using VFIO if it exists */
switch (dev->kdrv) {
case RTE_KDRV_VFIO:
#ifdef VFIO_PRESENT
if (pci_vfio_is_enabled())
ret = pci_vfio_map_resource(dev);
#endif
break;
case RTE_KDRV_IGB_UIO:
case RTE_KDRV_UIO_GENERIC:
/* map resources for devices that use uio */
ret = pci_uio_map_resource(dev);
break;
default:
RTE_LOG(DEBUG, EAL,
" Not managed by a supported kernel driver, skipped\n");
ret = 1;
break;
}
return ret;
}
pci_uio_map_resource' 函数,会将
pci设备的
uio 资源` 映射到 虚拟地址。
pci_uio_alloc_resource
分配 uio 资源
,用变量 uio_res 来指向新的uio 资源
。pci_uio_map_resource_by_index
映射pci设备
资源到大页内存。 rte_eal_init
+-> rte_eal_pci_probe /* pci 设备的侦测(侦测和初始化) */
+-> pci_probe_all_drivers /* pci 设备 遍历所有 驱动,找到合适的驱动 */
+-> rte_eal_pci_probe_one_driver /* 探测 驱动 和 设备 是否适合 */
+-> rte_eal_pci_map_device(dev); /* igb_uio 地址映射 */
.
. /* case: [RTE_KDRV_VFIO] */
+-> pci_vfio_map_resource(dev); /* 映射 vfio 驱动(虚拟网卡) */
.
. /* case: [RTE_KDRV_IGB_UIO | RTE_KDRV_UIO_GENERIC] */
+=> pci_uio_map_resource(dev); /* <== 映射 uio 驱动(物理网卡) */
.
. /* case: [secondary processes] */
+-> pci_uio_map_secondary(dev);
.
. /* case: [primary process] */
+-> pci_uio_alloc_resource(); /* 分配 uio 资源 内存空间 */
| +-> dev->intr_handle.fd = open(devname, O_RDWR); /* 打开 uio 资源文件 */
| +-> dev->intr_handle.uio_cfg_fd = open(cfgname, O_RDWR); /* 打开 uio 资源配置文件 */
|
+-> pci_uio_map_resource_by_index(); /* 映射 uio 资源 */
| +-> pci_map_addr = pci_find_max_end_va(); /* 在大页内存的尾部找一个地方,作为`pci设备`资源映射出来的首地址 */
| +-> mapaddr = pci_map_resource(pci_map_addr,...); /* 映射`pci设备`资源到大页内存 */
| | +-> mmap(requested_addr, size,...,fd);
| +-> pci_map_addr = RTE_PTR_ADD(mapaddr,...); /* 初始化大页内存的`pci设备`资源 */
|
+-> TAILQ_INSERT_TAIL(uio_res_list, uio_res, next); /* 将 uio 资源追加到uio_res_list 链表 */
`pci_uio_map_resource’ 函数源码如下:
/* map the PCI resource of a PCI device in virtual memory */
int
pci_uio_map_resource(struct rte_pci_device *dev)
{
int i, map_idx = 0, ret;
uint64_t phaddr;
struct mapped_pci_resource *uio_res = NULL;
struct mapped_pci_res_list *uio_res_list =
RTE_TAILQ_CAST(rte_uio_tailq.head, mapped_pci_res_list);
dev->intr_handle.fd = -1;
dev->intr_handle.uio_cfg_fd = -1;
dev->intr_handle.type = RTE_INTR_HANDLE_UNKNOWN;
/* secondary processes - use already recorded details */
if (rte_eal_process_type() != RTE_PROC_PRIMARY)
return pci_uio_map_secondary(dev);
/* 分配 uio 资源 内存空间 */
/* allocate uio resource */
ret = pci_uio_alloc_resource(dev, &uio_res);
if (ret)
return ret;
/* Map all BARs */
for (i = 0; i != PCI_MAX_RESOURCE; i++) {
/* 跳过 phys_addr 为 0 的 资源 */
/* skip empty BAR */
phaddr = dev->mem_resource[i].phys_addr;
if (phaddr == 0)
continue;
/* 映射 uio 资源 */
ret = pci_uio_map_resource_by_index(dev, i,
uio_res, map_idx);
if (ret)
goto error;
map_idx++;
}
uio_res->nb_maps = map_idx;
TAILQ_INSERT_TAIL(uio_res_list, uio_res, next);
return 0;
error:
for (i = 0; i < map_idx; i++) {
pci_unmap_resource(uio_res->maps[i].addr,
(size_t)uio_res->maps[i].size);
rte_free(uio_res->maps[i].path);
}
pci_uio_free_resource(dev, uio_res);
return -1;
}
例子:
打印 pci设备
资源 映射地址。
完整的资源 映射地址可以参考前面的:[2.1.3.4.3. 解释 pci 设备
的 resource
]。
# `pci设备` 资源 映射地址
cat /sys/bus/pci/devices/0000:04:00.0/resource
> # 只打印有效的资源映射地址 ((flags & IORESOURCE_MEM) && (phys_addr != 0))。
> # i # phys_addr # end_addr # flags
> 00 0x00000000f7b60000 0x00000000f7b7ffff 0x0000000000040200
> 03 0x00000000f7b8c000 0x00000000f7b8ffff 0x0000000000040200
> 07 0x00000000f0000000 0x00000000f001ffff 0x000000000014220c
> 10 0x00000000f0020000 0x00000000f003ffff 0x000000000014220c
# `pci设备` 资源 映射地址
cat /sys/bus/pci/devices/0000:04:00.0/resource
> # 只打印有效的资源映射地址 ((flags & IORESOURCE_MEM) && (phys_addr != 0))。
> # i # phys_addr # end_addr # flags
> 00 0x00000000f7920000 0x00000000f793ffff 0x0000000000140204
> 04 0x00000000f7944000 0x00000000f7947fff 0x0000000000140204
> 07 0x00000000f0300000 0x00000000f03fffff 0x000000000014220c
> 10 0x00000000f0400000 0x00000000f04fffff 0x000000000014220c
####### 2.3.1.1.1.1.1. pci_uio_alloc_resource
函数
pci_uio_alloc_resource
函数,用于分配 uio 资源 内存空间。
"/dev/uio${uio_id}"
),保存 文件描述符到 dev->intr_handle.fd。"sys/class/uio/uio${uio_id}/device/config"
),保存 文件描述符到 dev->intr_handle.uio_cfg_fd。在绑定网口后,内核驱动 uio
会生成的系统文件:
uio 资源文件
:("/dev/uio${uio_id}"
)uio 设备
文件目录:("sys/class/uio/uio${uio_id}"
)uio 资源配置文件:
("sys/class/uio/uio${uio_id}/device/config"
)${uio_id}
是 uio 设备
的 id
。和 绑定的网口号 ${port_id}
一一对应。这些的 uio 的系统文件。是内核态和用户态沟通的桥梁。
函数调用图如下:
rte_eal_init
+-> rte_eal_pci_probe /* pci 设备的侦测(侦测和初始化) */
+-> pci_probe_all_drivers /* pci 设备 遍历所有 驱动,找到合适的驱动 */
+-> rte_eal_pci_probe_one_driver /* 探测 驱动 和 设备 是否适合 */
+-> rte_eal_pci_map_device(dev); /* igb_uio 地址映射 */
. /* case: [RTE_KDRV_IGB_UIO | RTE_KDRV_UIO_GENERIC] */
+-> pci_uio_map_resource(dev); /* 映射 uio 驱动(物理网卡) */
. /* case: [primary process] */
+=> pci_uio_alloc_resource(); /* <== 分配 uio 资源 内存空间 */
| +-> dev->intr_handle.fd = open(devname, O_RDWR); /* 打开 uio 资源文件 */
| +-> dev->intr_handle.uio_cfg_fd = open(cfgname, O_RDWR); /* 打开 uio 资源配置文件 */
|
+-> pci_uio_map_resource_by_index(); /* 映射 uio 资源 */
+-> TAILQ_INSERT_TAIL(uio_res_list, uio_res, next); /* 将 uio 资源追加到uio_res_list 链表 */
函数源码如下:
int
pci_uio_alloc_resource(struct rte_pci_device *dev,
struct mapped_pci_resource **uio_res)
{
char dirname[PATH_MAX];
char cfgname[PATH_MAX];
char devname[PATH_MAX]; /* contains the /dev/uioX */
int uio_num;
struct rte_pci_addr *loc;
loc = &dev->addr;
/* 查找 uio 资源的文件名称 */
/* find uio resource */
uio_num = pci_get_uio_dev(dev, dirname, sizeof(dirname), 1);
if (uio_num < 0) {
RTE_LOG(WARNING, EAL, " "PCI_PRI_FMT" not managed by UIO driver, "
"skipping\n", loc->domain, loc->bus, loc->devid, loc->function);
return 1;
}
snprintf(devname, sizeof(devname), "/dev/uio%u", uio_num);
/* 打开 uio 资源文件,保存 文件描述符到 dev->intr_handle.fd */
/* save fd if in primary process */
dev->intr_handle.fd = open(devname, O_RDWR);
if (dev->intr_handle.fd < 0) {
RTE_LOG(ERR, EAL, "Cannot open %s: %s\n",
devname, strerror(errno));
goto error;
}
/* 打开 uio 资源配置文件,保存 文件描述符到 dev->intr_handle.uio_cfg_fd */
snprintf(cfgname, sizeof(cfgname),
"/sys/class/uio/uio%u/device/config", uio_num);
dev->intr_handle.uio_cfg_fd = open(cfgname, O_RDWR);
if (dev->intr_handle.uio_cfg_fd < 0) {
RTE_LOG(ERR, EAL, "Cannot open %s: %s\n",
cfgname, strerror(errno));
goto error;
}
/* 设置 dev->intr_handle.type 的类型 */
if (dev->kdrv == RTE_KDRV_IGB_UIO)
dev->intr_handle.type = RTE_INTR_HANDLE_UIO;
else {
dev->intr_handle.type = RTE_INTR_HANDLE_UIO_INTX;
/* set bus master that is not done by uio_pci_generic */
if (pci_uio_set_bus_master(dev->intr_handle.uio_cfg_fd)) {
RTE_LOG(ERR, EAL, "Cannot set up bus mastering!\n");
goto error;
}
}
/* 分配 uio 资源 内存空间 ,并且填充成员变量 */
/* allocate the mapping details for secondary processes*/
*uio_res = rte_zmalloc("UIO_RES", sizeof(**uio_res), 0);
if (*uio_res == NULL) {
RTE_LOG(ERR, EAL,
"%s(): cannot store uio mmap details\n", __func__);
goto error;
}
snprintf((*uio_res)->path, sizeof((*uio_res)->path), "%s", devname);
memcpy(&(*uio_res)->pci_addr, &dev->addr, sizeof((*uio_res)->pci_addr));
return 0;
error:
pci_uio_free_resource(dev, *uio_res);
return -1;
}
例子:
使用命令行,打印 uio 资源。
# `uio 资源文件` (`"/dev/uio${uio_id}"`)
ll /dev/uio0
> crw------- 1 root root 247, 0 Feb 11 11:13 /dev/uio0
# `uio 设备`文件目录(`"sys/class/uio/uio${uio_id}"`)
ll /sys/class/uio/uio0
> lrwxrwxrwx 1 root root 0 Mar 4 11:33 /sys/class/uio/uio0 -> ../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:05.0/0000:04:00.0/uio/uio0
# `uio 资源配置文件`(`"sys/class/uio/uio${uio_id}/device/config"`)
ll /sys/class/uio/uio0/device/config
> -rw-r--r-- 1 root root 4096 Feb 11 08:56 /sys/class/uio/uio0/device/config
# `uio 资源文件` (`"/dev/uio${uio_id}"`)
ll /dev/uio1
> crw------- 1 root root 247, 1 Feb 11 11:13 /dev/uio1
# `uio 设备`文件目录(`"sys/class/uio/uio${uio_id}"`)
ll /sys/class/uio/uio1
> lrwxrwxrwx 1 root root 0 Feb 11 11:14 /sys/class/uio/uio1 -> ../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:09.0/0000:06:00.0/0000:07:02.0/0000:08:00.0/uio/uio1
# `uio 资源配置文件`(`"sys/class/uio/uio${uio_id}/device/config"`)
ll /sys/class/uio/uio1/device/config
> -rw-r--r-- 1 root root 4096 Feb 11 08:56 /sys/class/uio/uio1/device/config
####### 2.3.1.1.1.1.2. pci_uio_map_resource_by_index 函数
pci_uio_map_resource_by_index 函数,用于映射pci设备
资源到大页内存。
pci设备
资源路径 的名称。pci设备
资源文件 的名称分配空间。pci设备
资源 文件 ("/sys/bus/pci/devices/ p c i a d d r e s s / r e s o u r c e / r e s o u r c e {pci_address}/resource/resource pciaddress/resource/resource{resource_id}")。pci设备
资源映射出来的首地址。pci设备
资源到大页内存。pci设备
资源。在绑定网口后,内核驱动 uio
会生成 pci设备
资源文件:
资源文件
:("/sys/bus/pci/devices/${pci_address}/resource/resource${resource_id}"
)注意:
pci设备
资源的映射。是实现网卡收发包的零拷贝的关键。函数调用图如下:
rte_eal_init
+-> rte_eal_pci_probe /* pci 设备的侦测(侦测和初始化) */
+-> pci_probe_all_drivers /* pci 设备 遍历所有 驱动,找到合适的驱动 */
+-> rte_eal_pci_probe_one_driver /* 探测 驱动 和 设备 是否适合 */
+-> rte_eal_pci_map_device(dev); /* igb_uio 地址映射 */
. /* case: [RTE_KDRV_IGB_UIO | RTE_KDRV_UIO_GENERIC] */
+-> pci_uio_map_resource(dev); /* 映射 uio 驱动(物理网卡) */
. /* case: [primary process] */
+-> pci_uio_alloc_resource(); /* 分配 uio 资源 内存空间 */
|
+=> pci_uio_map_resource_by_index(); /* <== 映射 uio 资源 */
| +-> pci_map_addr = pci_find_max_end_va(); /* 在大页内存的尾部找一个地方,作为`pci设备`资源映射出来的首地址 */
| +-> mapaddr = pci_map_resource(pci_map_addr,...); /* 映射`pci设备`资源到大页内存 */
| | +-> mmap(requested_addr, size,...,fd);
| +-> pci_map_addr = RTE_PTR_ADD(mapaddr,...); /* 初始化大页内存的`pci设备`资源 */
|
+-> TAILQ_INSERT_TAIL(uio_res_list, uio_res, next); /* 将 uio 资源追加到uio_res_list 链表 */
函数源码如下:
int
pci_uio_map_resource_by_index(struct rte_pci_device *dev, int res_idx,
struct mapped_pci_resource *uio_res, int map_idx)
{
int fd;
char devname[PATH_MAX];
void *mapaddr;
struct rte_pci_addr *loc;
struct pci_map *maps;
loc = &dev->addr;
maps = uio_res->maps;
/* 取得 pci设备 资源路径 的名称 */
/* update devname for mmap */
snprintf(devname, sizeof(devname),
"%s/" PCI_PRI_FMT "/resource%d",
pci_get_sysfs_path(),
loc->domain, loc->bus, loc->devid,
loc->function, res_idx);
/* 为 pci设备 资源路径 的名称分配空间 */
/* allocate memory to keep path */
maps[map_idx].path = rte_malloc(NULL, strlen(devname) + 1, 0);
if (maps[map_idx].path == NULL) {
RTE_LOG(ERR, EAL, "Cannot allocate memory for path: %s\n",
strerror(errno));
return -1;
}
/* 打开 pci设备 资源路径 文件 */
/*
* open resource file, to mmap it
*/
fd = open(devname, O_RDWR);
if (fd < 0) {
RTE_LOG(ERR, EAL, "Cannot open %s: %s\n",
devname, strerror(errno));
goto error;
}
/* 第一次的时候,在大页内存的尾部找一个地方,
* 作为pci设备资源映射出来的首地址 */
/* try mapping somewhere close to the end of hugepages */
if (pci_map_addr == NULL)
pci_map_addr = pci_find_max_end_va();
/* 映射pci设备资源到大页内存 */
mapaddr = pci_map_resource(pci_map_addr, fd, 0,
(size_t)dev->mem_resource[res_idx].len, 0);
close(fd);
if (mapaddr == MAP_FAILED)
goto error;
/* 指向下一个映射地址 */
pci_map_addr = RTE_PTR_ADD(mapaddr,
(size_t)dev->mem_resource[res_idx].len);
/* 初始化大页内存的pci设备资源 */
maps[map_idx].phaddr = dev->mem_resource[res_idx].phys_addr;
maps[map_idx].size = dev->mem_resource[res_idx].len;
maps[map_idx].addr = mapaddr;
maps[map_idx].offset = 0;
strcpy(maps[map_idx].path, devname);
dev->mem_resource[res_idx].addr = mapaddr;
return 0;
error:
rte_free(maps[map_idx].path);
return -1;
}
例子:
通过命令行打印出`pci设备 资源路径文件。
# `pci设备` 的名称
ll /sys/bus/pci/devices/0000:04:00.0
> lrwxrwxrwx 1 root root 0 Mar 4 11:24 /sys/bus/pci/devices/0000:04:00.0 -> ../../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:05.0/0000:04:00.0
# `pci设备` 资源 映射地址
cat /sys/bus/pci/devices/0000:04:00.0/resource
> # 只打印有效的资源映射地址。
> # i # phys_addr # end_addr # flags
> 00 0x00000000f7b60000 0x00000000f7b7ffff 0x0000000000040200 <== 有对应 资源文件 resource0 ---------+
> 03 0x00000000f7b8c000 0x00000000f7b8ffff 0x0000000000040200 <== 有对应 资源文件 resource3 ---------|--+
> 07 0x00000000f0000000 0x00000000f001ffff 0x000000000014220c <== 无对应 资源文件 | |
> 10 0x00000000f0020000 0x00000000f003ffff 0x000000000014220c <== 无对应 资源文件 | |
| |
# `pci设备` 资源 文件 | |
ll /sys/bus/pci/devices/0000:04:00.0/resource | |
> -rw------- 1 root root 131072 Mar 4 11:32 resource0 # 千兆 `pci设备` 资源文件 <== 可以成功 mmap --+ |
> -rw------- 1 root root 32 Mar 4 11:32 resource2 # 千兆 `pci设备` 资源文件 |
> -rw------- 1 root root 16384 Mar 4 11:32 resource3 # 千兆 `pci设备` 资源文件 <== 可以成功 mmap -----+
# `pci设备` 的名称
ll /sys/bus/pci/devices/0000:08:00.0
> lrwxrwxrwx 1 root root 0 Mar 4 11:24 /sys/bus/pci/devices/0000:08:00.0 -> ../../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/0000:02:09.0/0000:06:00.0/0000:07:02.0/0000:08:00.0
# `pci设备` 资源 映射地址
cat /sys/bus/pci/devices/0000:04:00.0/resource
> # 只打印有效的资源映射地址。
> # i # phys_addr # end_addr # flags
> 00 0x00000000f7920000 0x00000000f793ffff 0x0000000000140204 <== 有对应 资源文件 resource0 ---------+
> 04 0x00000000f7944000 0x00000000f7947fff 0x0000000000140204 <== 有对应 资源文件 resource4 ---------|--+
> 07 0x00000000f0300000 0x00000000f03fffff 0x000000000014220c <== 无对应 资源文件 | |
> 10 0x00000000f0400000 0x00000000f04fffff 0x000000000014220c <== 无对应 资源文件 | |
| |
# `pci设备` 资源 文件 | |
ll /sys/bus/pci/devices/0000:08:00.0/resource | |
> -rw------- 1 root root 131072 Mar 4 11:32 resource0 # 万兆 `pci设备` 资源文件 <== 可以成功 mmap --+ |
> -rw------- 1 root root 16384 Mar 4 11:32 resource4 # 万兆 `pci设备` 资源文件 <== 可以成功 mmap -----+
rte_eth_dev_init
函数rte_eth_dev_init
用于 以太网设备的初始化。
函数调用图如下:
rte_eal_init
+-> 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_create_unique_device_name /* 创建 唯一的 设备名称 */
+-> eth_dev = rte_eth_dev_allocate /* 为 以太网设备 分配空间 */
+-> eth_dev->data->dev_private = rte_zmalloc() /* 为 以太网设备的私有数据 分配空间 */
+-> TAILQ_INIT(&(eth_dev->link_intr_cbs)) /* 初始化中断处理回调函数链表 */
+-> (*eth_drv->eth_dev_init)(eth_dev); /* 调用 PMD 设备的初始化 回调函数 */
源码分析如下:
static int
rte_eth_dev_init(struct rte_pci_driver *pci_drv,
struct rte_pci_device *pci_dev)
{
struct eth_driver *eth_drv;
struct rte_eth_dev *eth_dev;
char ethdev_name[RTE_ETH_NAME_MAX_LEN];
int diag;
eth_drv = (struct eth_driver *)pci_drv;
/* ------------------------------------*/
/* 创建 唯一的 设备名称 */
/* Create unique Ethernet device name using PCI address */
rte_eth_dev_create_unique_device_name(ethdev_name,
sizeof(ethdev_name), pci_dev);
/* ------------------------------------*/
/* 为 以太网设备 分配空间 */
eth_dev = rte_eth_dev_allocate(ethdev_name, RTE_ETH_DEV_PCI);
if (eth_dev == NULL)
return -ENOMEM;
/* ------------------------------------*/
/* 为 以太网设备的私有数据 分配空间 */
if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
eth_dev->data->dev_private = rte_zmalloc("ethdev private structure",
eth_drv->dev_private_size,
RTE_CACHE_LINE_SIZE);
}
eth_dev->pci_dev = pci_dev;
eth_dev->driver = eth_drv;
eth_dev->data->rx_mbuf_alloc_failed = 0;
/* ------------------------------------*/
/* 初始化中断处理回调函数链表 */
/* init user callbacks */
TAILQ_INIT(&(eth_dev->link_intr_cbs));
/* ------------------------------------*/
/* 设置默认 MTU */
/*
* Set the default MTU.
*/
eth_dev->data->mtu = ETHER_MTU;
/* ------------------------------------*/
/* 调用 PMD 设备的初始化 回调函数 */
/* Invoke PMD device initialization function */
diag = (*eth_drv->eth_dev_init)(eth_dev);
if (diag == 0)
return 0;
/* ------------------------------------*/
/* 初始化失败后的 处理 */
if (rte_eal_process_type() == RTE_PROC_PRIMARY)
rte_free(eth_dev->data->dev_private);
rte_eth_dev_release_port(eth_dev);
return diag;
}
后续的 六个小节会详细的分析 以太网设备的初始化
的过程。
struct rte_eth_dev
struct rte_eth_dev
表示一个以太网设备
。
struct rte_eth_dev {
eth_rx_burst_t rx_pkt_burst; /**< Pointer to PMD receive function. */
eth_tx_burst_t tx_pkt_burst; /**< Pointer to PMD transmit function. */
struct rte_eth_dev_data *data; /**< Pointer to device data */
const struct eth_driver *driver;/**< Driver for this device */
const struct eth_dev_ops *dev_ops; /**< Functions exported by PMD */
struct rte_pci_device *pci_dev; /**< PCI info. supplied by probing */
/** User application callbacks for NIC interrupts */
struct rte_eth_dev_cb_list link_intr_cbs;
/**
* User-supplied functions called from rx_burst to post-process
* received packets before passing them to the user
*/
struct rte_eth_rxtx_callback *post_rx_burst_cbs[RTE_MAX_QUEUES_PER_PORT];
/**
* User-supplied functions called from tx_burst to pre-process
* received packets before passing them to the driver for transmission.
*/
struct rte_eth_rxtx_callback *pre_tx_burst_cbs[RTE_MAX_QUEUES_PER_PORT];
uint8_t attached; /**< Flag indicating the port is attached */
enum rte_eth_dev_type dev_type; /**< Flag indicating the device type */
} __rte_cache_aligned;
重点:
driver
字段,指向 以太网设备 的 驱动。pci_dev
字段,指向 以太网设备 的 所属的 pci 设备。由于 以太网设备 是通过 pci 总线连接到 CPU 的,所以也是属于 pci 设备。data
字段,指向 以太网设备 的数据。dev_ops
字段,指向 以太网设备 的 操作。rte_eth_dev_init
中,通过 rte_eth_dev_create_unique_device_name
使用 pci 地址
拼接出 唯一的 设备名称。
该 唯一的 设备名称,用于后续的 rte_eth_dev_allocate
函数。
static int
rte_eth_dev_create_unique_device_name(char *name, size_t size,
struct rte_pci_device *pci_dev)
{
int ret;
ret = snprintf(name, size, "%d:%d.%d",
pci_dev->addr.bus, pci_dev->addr.devid,
pci_dev->addr.function);
if (ret < 0)
return ret;
return 0;
}
例子:
可以使用 lspci
命令来观察 网卡的 pci 地址
。
04:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01)
rte_eth_dev_create_unique_device_name
函数 返回 设备名称为: “4:0.0”
08:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
rte_eth_dev_create_unique_device_name
函数 返回 设备名称为: “8:0.0”
rte_eth_dev_allocate
遍历全局变量 rte_eth_devices[]
数组,找到 空闲的位置。来放置 以太网设备。
函数调用图如下:
rte_eal_init
+-> 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_create_unique_device_name /* 创建 唯一的 设备名称 */
+=> rte_eth_dev_allocate /* <== 为 以太网设备 分配空间 */
+-> port_id = rte_eth_dev_find_free_port() /* 遍历 rte_eth_devices 数组,找到 空闲的 port_id。 */
+-> rte_eth_dev_data_alloc /* 分配 设备数据 的内存空间 */
+-> if (rte_eth_dev_allocated(name) != NULL) /* 检测 设备 是否已经被绑定上。 */
+-> eth_dev = &rte_eth_devices[port_id]; /* eth_dev 指向可用的 rte_eth_devices[port_id] */
+-> eth_dev->data = &rte_eth_dev_data[port_id]; /* eth_dev->data 指向可用的 rte_eth_dev_data[port_id] */
+-> eth_dev->attached = DEV_ATTACHED; /* 打上 DEV_ATTACHED,表示已经绑定上设备。 */
+-> nb_ports++ /* 使用的 网口数 加一 */
源码分析如下:
rte_eth_dev_find_free_port
函数,遍历 rte_eth_devices[]
数组,找到 空闲的 port_id
。rte_eth_dev_data
初始化。则使用 rte_eth_dev_data_alloc
函数,分配 设备数据 的内存空间。rte_eth_dev_allocated
函数,检测 设备 是否已经被绑定上。如果 设备已经 绑定上,函数返回 NULL
,否则跳到第 4 步。eth_dev
和 eth_dev->data
内部的 成员变量。eth_dev
指向可用的 rte_eth_devices[port_id]
eth_dev->data
指向可用的 rte_eth_dev_data[port_id]
eth_dev->attached
赋值为 DEV_ATTACHED
。表示已经绑定上设备。 rte_eth_dev_allocate(const char *name, enum rte_eth_dev_type type)
{
uint8_t port_id;
struct rte_eth_dev *eth_dev;
/* 遍历 rte_eth_devices 数组,找到 空闲的 port_id。 */
port_id = rte_eth_dev_find_free_port();
if (port_id == RTE_MAX_ETHPORTS) {
RTE_PMD_DEBUG_TRACE("Reached maximum number of Ethernet ports\n");
return NULL;
}
/* 分配 设备数据 的内存空间 */
if (rte_eth_dev_data == NULL)
rte_eth_dev_data_alloc();
/* 检测 设备 是否已经被绑定上。*/
if (rte_eth_dev_allocated(name) != NULL) {
RTE_PMD_DEBUG_TRACE("Ethernet Device with name %s already allocated!\n",
name);
return NULL;
}
/* eth_dev 指向可用的 rte_eth_devices[port_id]
* eth_dev->data 指向可用的 rte_eth_dev_data[port_id]
*/
eth_dev = &rte_eth_devices[port_id];
eth_dev->data = &rte_eth_dev_data[port_id];
snprintf(eth_dev->data->name, sizeof(eth_dev->data->name), "%s", name);
eth_dev->data->port_id = port_id;
eth_dev->attached = DEV_ATTACHED; /* 表示已经绑定上设备 */
eth_dev->dev_type = type;
nb_ports++; /* 使用的 网口数 加一 */
return eth_dev;
}
源码分析如下:
rte_eth_dev_init
使用 rte_zmalloc
为 以太网设备
的私有数据
分配空间。eth_dev->pci_dev
指向 pci_dev
。关联 以太网设备
和 pci设备
。eth_dev->driver
指向 eth_drv
。关联 以太网设备
和 以太网驱动
。 static int
rte_eth_dev_init(struct rte_pci_driver *pci_drv,
struct rte_pci_device *pci_dev)
{
/* ... */
/* 为 以太网设备的私有数据 分配空间 */
if (rte_eal_process_type() == RTE_PROC_PRIMARY) {
eth_dev->data->dev_private = rte_zmalloc("ethdev private structure",
eth_drv->dev_private_size,
RTE_CACHE_LINE_SIZE);
}
eth_dev->pci_dev = pci_dev; /* 关联 以太网设备 和 pci设备 */
eth_dev->driver = eth_drv; /* 关联 以太网设备 和 以太网驱动 */
eth_dev->data->rx_mbuf_alloc_failed = 0;
/* ... */
}
例子:
rte_igb_pmd
在定义的时候,设置 dev_private_size
为 sizeof(struct e1000_adapter)
。
rte_zmalloc()
就为千兆以太网设备分配了 struct e1000_adapter
类型大小的内存空间。
所以最后,千兆以太网设备的 eth_dev->data->dev_private
指向的是 struct e1000_adapter
类型数据。
rte_ixgbe_pmd
在定义的时候,设置 dev_private_size
为 sizeof(struct ixgbe_adapter)
。
rte_zmalloc()
就为万兆以太网设备分配了 struct ixgbe_adapter
类型大小的内存空间。
所以最后,万兆以太网设备的 eth_dev->data->dev_private
指向的是 struct ixgbe_adapter
类型数据。
注意:
struct e1000_adapter
和 struct ixgbe_adapter
表示的是一个物理网口的私有信息。里面存储了硬件信息等。详细可以查看 相关芯片的 datasheet,这里不详细展开。
[为 以太网设备的私有数据 分配空间]
rte_eth_dev_init
函数中,初始化 设备eth_dev
上的 link_intr_cbs
中断处理回调函数链表。
static int
rte_eth_dev_init(struct rte_pci_driver *pci_drv,
struct rte_pci_device *pci_dev)
{
/* ... */
/* ------------------------------------*/
/* 初始化中断处理回调函数链表 */
/* init user callbacks */
TAILQ_INIT(&(eth_dev->link_intr_cbs));
/* ... */
}
PMD 设备
的初始化 回调函数eth_drv->eth_dev_init()
是一个回调函数。不同的驱动将会初始化各自的 以太网设备。
static int
rte_eth_dev_init(struct rte_pci_driver *pci_drv,
struct rte_pci_device *pci_dev)
{
/* ... */
/* ------------------------------------*/
/* 调用 PMD 设备的初始化 回调函数 */
/* Invoke PMD device initialization function */
diag = (*eth_drv->eth_dev_init)(eth_dev);
if (diag == 0)
return 0;
/* ... */
}
例子:
因为 rte_igb_pmd.eth_dev_init
设置为 eth_igb_dev_init
。
所以最后以 eth_igb_dev_init
来初始化 千兆以太网设备。
eth_igb_dev_init()
的函数调用图。
rte_eal_init
+-> 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 /* 网口 设备 初始化。 */
+-> (*eth_drv->eth_dev_init)(eth_dev); /* 调用 驱动 的 eth_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_ixgbe_dev_init()
的函数调用图。
rte_eal_init
+-> 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 /* 网口 设备 初始化。 */
+-> (*eth_drv->eth_dev_init)(eth_dev); /* 调用 驱动 的 eth_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() /* 初始化中断 */
重点:
千兆和万兆以太网设备的初始化函数 (eth_igb_dev_init
和 eth_ixgbe_dev_init
) 其中内部的实现。由于硬件的缘故,是大相径庭的。
但是通过上面千兆和万兆的 “函数调用图”,粗略地提取代码间的相同点。可以让我们了解代码的总体思想:
eth_dev->dev_op
。千兆设置为 eth_igb_ops
,万兆设置为 ixgbe_eth_dev_ops
。eth_dev->rx_pkt_burst
。千兆设置为 eth_igb_recv_pkts
,万兆设置为 ixgbe_recv_pkts
。eth_dev->tx_pkt_burst
。千兆设置为 eth_igb_xmit_pkts
,万兆设置为 ixgbe_xmit_pkts
。igb_hardware_init
,万兆调用函数 ixgbe_init_hw
。rte_intr_callback_register
,万兆调用函数 rte_intr_callback_register
。设备与驱动关联之后。仍然需要配置。主要的函数有:
rte_eth_dev_configure
,用户配置 以太网设备
。rte_eth_rx_queue_setup
,设置 以太网设备 的收包队列
。rte_eth_tx_queue_setup
,设置 以太网设备 的发包队列
。总体函数调用图如下:
main
| /* ------------------------ */
| /* 以太网设备的配置 */
+-> rte_eth_dev_configure /* 用户配置 以太网设备 */
| +-> memcpy(&dev->data->dev_conf, dev_conf, ...) /* 拷贝用户设置到 设备的数据结构中 */
| +-> rte_eth_dev_rx_queue_config /* 设置 收包队列 */
| | +-> dev->data->rx_queues = rte_zmalloc() /* 分配收包队列空间 */
| +-> rte_eth_dev_tx_queue_config /* 设置 发包队列 */
| | +-> dev->data->tx_queues = rte_zmalloc() /* 分配发包队列空间 */
| +-> (*dev->dev_ops->dev_configure)(dev) /* 用户设置 以太网设备 的回调函数 */
| . /* 千兆:eth_igb_ops.dev_configure = eth_igb_configure */
| +-> eth_igb_configure /* 千兆:用户配置 以太网设备 */
| . +-> igb_check_mq_mode /* 千兆:检查用户配置的收发包模式 */
| .
| . /* 万兆:ixgbe_eth_dev_ops.dev_configure = ixgbe_dev_configure */
| +-> ixgbe_dev_configure /* 万兆:用户配置 以太网设备 */
| +-> ixgbe_check_mq_mode /* 万兆:检查用户配置的收发包模式 */
|
+-> rte_eth_rx_queue_setup /* 设置 以太网设备 的收包队列 */
| +-> (*dev->dev_ops->rx_queue_setup)() /* 网口的收包队列 的初始化 */
| . /* 千兆:eth_igb_ops.rx_queue_setup = eth_igb_rx_queue_setup */
| +~> eth_igb_rx_queue_setup
| .
| . /* 万兆:ixgbe_eth_dev_ops.rx_queue_setup = ixgbe_dev_rx_queue_setup */
| +~> ixgbe_dev_rx_queue_setup
|
+-> rte_eth_tx_queue_setup /* 设置 以太网设备 的发包队列 */
+-> (*dev->dev_ops->tx_queue_setup)() /* 网口的发包队列 的初始化 */
. /* 千兆:eth_igb_ops.tx_queue_setup = eth_igb_tx_queue_setup */
+~> eth_igb_tx_queue_setup
.
. /* 万兆:ixgbe_eth_dev_ops.tx_queue_setup = eth_igb_tx_queue_setup */
+~> ixgbe_dev_tx_queue_setup
有关设备配置的结构体是 struct rte_eth_dev_data
。
struct rte_eth_dev_data
相关的类图:
注意:
由于不同的以太网设备对应的收发包队列的数据结构都有不同。
所以以下的类图中,以 “xxx_” 为首的结构体,是以斜体表示的,表示虚构的。
在后续的物件图中,将会同时以 千兆和 万兆以太网设备为例来描述。
rte_eth_dev_configure
函数rte_eth_dev_configure
用于 用户配置 以太网设备。主要的步骤有:
rte_eth_dev_rx_queue_config
,分配收包队列空间。rte_eth_dev_tx_queue_config
, 分配发包队列空间。dev_ops->dev_configure()
回调函数。函数调用图如下:
rte_eth_dev_configure /* <== 用户配置 以太网设备 */
+-> memcpy(&dev->data->dev_conf, dev_conf, ...) /* 拷贝用户设置到 设备的数据结构中 */
+-> rte_eth_dev_rx_queue_config /* 设置 收包队列 */
| +-> dev->data->rx_queues = rte_zmalloc() /* 分配收包队列空间 */
+-> rte_eth_dev_tx_queue_config /* 设置 发包队列 */
| +-> dev->data->tx_queues = rte_zmalloc() /* 分配发包队列空间 */
+-> (*dev->dev_ops->dev_configure)(dev) /* 用户设置 以太网设备 的回调函数 */
. /* 千兆:eth_igb_ops.dev_configure = eth_igb_configure */
+-> eth_igb_configure /* 千兆:用户配置 以太网设备 */
. +-> igb_check_mq_mode /* 千兆:检查用户配置的收发包模式 */
.
. /* 万兆:ixgbe_eth_dev_ops.dev_configure = ixgbe_dev_configure */
+-> ixgbe_dev_configure /* 万兆:用户配置 以太网设备 */
+-> ixgbe_check_mq_mode /* 万兆:检查用户配置的收发包模式 */
以下是 rte_eth_dev_rx_queue_config
和 rte_eth_dev_tx_queue_config
简化后的代码。
只是摘录了第一次为收发包队列分配内存的情况。
static int
rte_eth_dev_rx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues)
{
uint16_t old_nb_queues = dev->data->nb_rx_queues;
void **rxq;
unsigned i;
/* 第一次配置,为每个收包队列分配一个指针 */
if (dev->data->rx_queues == NULL && nb_queues != 0) { /* first time configuration */
dev->data->rx_queues = rte_zmalloc("ethdev->rx_queues",
sizeof(dev->data->rx_queues[0]) * nb_queues,
RTE_CACHE_LINE_SIZE);
if (dev->data->rx_queues == NULL) {
dev->data->nb_rx_queues = 0;
return -(ENOMEM);
}
}
/* ... */
}
static int
rte_eth_dev_tx_queue_config(struct rte_eth_dev *dev, uint16_t nb_queues)
{
uint16_t old_nb_queues = dev->data->nb_tx_queues;
void **txq;
unsigned i;
/* 第一次配置,为每个发包队列分配一个指针 */
if (dev->data->tx_queues == NULL && nb_queues != 0) { /* first time configuration */
dev->data->tx_queues = rte_zmalloc("ethdev->tx_queues",
sizeof(dev->data->tx_queues[0]) * nb_queues,
RTE_CACHE_LINE_SIZE);
if (dev->data->tx_queues == NULL) {
dev->data->nb_tx_queues = 0;
return -(ENOMEM);
}
}
/* ... */
}
注意:
struct rte_eth_dev_data
中,成员变量 rx_queues
和 tx_queues
都为 void **
类型。dev->data->rx_queues[0]
和 dev->data->tx_queues[0]
所以都为 void *
类型。dev->data->rx_queues
和 dev->data->tx_queues
指向的是 void *
类型的数组,数组元素个数为 nb_queues
。千兆以太网设备 & 万兆以太网设备 都会调用 rte_eth_dev_rx_queue_config
和 rte_eth_dev_tx_queue_config
分配收发包队列空间。图例如下:
rte_eth_rx_queue_setup
函数rte_eth_rx_queue_setup
用于 设置 以太网设备 的收包队列。函数最后会调用 dev->dev_ops->rx_queue_setup
的回调函数。
函数调用图如下:
rte_eth_rx_queue_setup /* <== 设置 以太网设备 的收包队列 */
+-> (*dev->dev_ops->rx_queue_setup)() /* 网口的收包队列 的初始化 */
. /* 千兆:eth_igb_ops.rx_queue_setup = eth_igb_rx_queue_setup */
+~> eth_igb_rx_queue_setup
.
. /* 万兆:ixgbe_eth_dev_ops.rx_queue_setup = ixgbe_dev_rx_queue_setup */
+~> ixgbe_dev_rx_queue_setup
例子:
因为 eth_igb_ops.rx_queue_setup
设置为 eth_igb_rx_queue_setup
。
所以使用 eth_igb_rx_queue_setup
设置 以太网设备 的收包队列。
rte_eth_rx_queue_setup /* 设置 以太网设备 的收包队列 */
+-> (*dev->dev_ops->rx_queue_setup)() /* 网口的收包队列 的初始化 */
+=> eth_igb_rx_queue_setup /* <== 千兆: 设置 以太网设备 的收包队列。 */
+-> rxq = rte_zmalloc("ethdev RX queue", sizeof(struct igb_rx_queue),...); /* 分配 收包队列 空间。 */
+-> rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,...); /* 分配 硬件收包队列 空间。 */
+-> rxq->rx_ring_phys_addr = rte_mem_phy2mch(..., rz->phys_addr); /* 关联 硬件收包队列的物理地址。 */
+-> rxq->rx_ring = (union e1000_adv_rx_desc *) rz->addr; /* 关联 硬件收包队列的虚拟地址。 */
+-> rxq->sw_ring = rte_zmalloc("rxq->sw_ring", sizeof(struct igb_rx_entry)...); /* 分配 软件收包队列 空间 */
+-> dev->data->rx_queues[queue_idx] = rxq; /* 关联 收包队列 */
ixgbe_eth_dev_ops.rx_queue_setup
设置为 ixgbe_dev_rx_queue_setup
。 ixgbe_dev_rx_queue_setup
设置 以太网设备 的收包队列。 rte_eth_rx_queue_setup /* 设置 以太网设备 的收包队列 */
+-> (*dev->dev_ops->rx_queue_setup)() /* 网口的收包队列 的初始化 */
+=> ixgbe_dev_rx_queue_setup /* <== 万兆: 设置 以太网设备 的收包队列。 */
+-> rxq = rte_zmalloc_socket("ethdev RX queue", sizeof(struct ixgbe_rx_queue),...); /* 分配 收包队列 空间。 */
+-> rz = rte_eth_dma_zone_reserve(dev, "rx_ring", queue_idx,...); /* 分配 硬件收包队列 空间。 */
+-> rxq->rx_ring_phys_addr = rte_mem_phy2mch(..., rz->phys_addr); /* 关联 硬件收包队列的物理地址。 */
+-> rxq->rx_ring = (union ixgbe_adv_rx_desc *) rz->addr; /* 关联 硬件收包队列的虚拟地址。 */
+-> rxq->sw_ring = rte_zmalloc_socket("rxq->sw_ring", sizeof(struct ixgbe_rx_entry)...);/* 分配 软件收包队列 空间 */
+-> dev->data->rx_queues[queue_idx] = rxq; /* 关联 收包队列 */
分析上面 千兆和万兆以太网设备 的 eth_igb_rx_queue_setup
和 ixgbe_dev_rx_queue_setup
函数调用图。得到主要的步骤如下:
rxq
。rx_wing
。sw_ring
。rte_eth_tx_queue_setup
函数rte_eth_tx_queue_setup
用于 设置 以太网设备 的发包队列。函数最后会调用 dev->dev_ops->tx_queue_setup
的回调函数。
函数调用图如下:
rte_eth_tx_queue_setup /* <== 设置 以太网设备 的发包队列 */
+-> (*dev->dev_ops->tx_queue_setup)() /* 网口的发包队列 的初始化 */
* /* 千兆:eth_igb_ops.tx_queue_setup = eth_igb_tx_queue_setup */
+~> eth_igb_tx_queue_setup
*
* /* 万兆:ixgbe_eth_dev_ops.tx_queue_setup = eth_igb_tx_queue_setup */
+~> ixgbe_dev_tx_queue_setup
例子:
eth_igb_ops.tx_queue_setup
设置为 eth_igb_tx_queue_setup
。eth_igb_tx_queue_setup
设置 以太网设备 的发包队列。 rte_eth_tx_queue_setup /* 设置 以太网设备 的发包队列 */
+-> (*dev->dev_ops->tx_queue_setup)() /* 网口的发包队列 的初始化 */
+=> eth_igb_tx_queue_setup /* <== 千兆: 设置 以太网设备 的发包队列。 */
+-> txq = rte_zmalloc("ethdev TX queue", sizeof(struct igb_tx_queue),...); /* 分配 发包队列 空间。 */
+-> tz = rte_eth_dma_zone_reserve(dev, "tx_ring", queue_idx,...); /* 分配 硬件发包队列 空间。 */
+-> txq->tx_ring_phys_addr = rte_mem_phy2mch(..., tz->phys_addr); /* 关联 硬件发包队列的物理地址。 */
+-> txq->tx_ring = (union e1000_adv_tx_desc *) tz->addr; /* 关联 硬件发包队列的虚拟地址。 */
+-> txq->sw_ring = rte_zmalloc("txq->sw_ring", sizeof(struct igb_tx_entry)...); /* 分配 软件发包队列 空间 */
+-> dev->tx_pkt_burst = eth_igb_xmit_pkts;
+-> dev->data->tx_queues[queue_idx] = txq; /* 关联 发包队列 */
ixgbe_eth_dev_ops.tx_queue_setup
设置为 ixgbe_dev_tx_queue_setup
。 ixgbe_dev_tx_queue_setup
设置 以太网设备 的发包队列。 rte_eth_tx_queue_setup /* 设置 以太网设备 的发包队列 */
+-> (*dev->dev_ops->tx_queue_setup)() /* 网口的发包队列 的初始化 */
+=> ixgbe_dev_tx_queue_setup /* <== 万兆: 设置 以太网设备 的发包队列。 */
+-> txq = rte_zmalloc_socket("ethdev TX queue", sizeof(struct ixgbe_tx_queue),...); /* 分配 发包队列 空间。 */
+-> tz = rte_eth_dma_zone_reserve(dev, "tx_ring", queue_idx,...); /* 分配 硬件发包队列 空间。 */
+-> txq->tx_ring_phys_addr = rte_mem_phy2mch(..., tz->phys_addr); /* 关联 硬件发包队列的物理地址。 */
+-> txq->tx_ring = (union ixgbe_adv_tx_desc *) tz->addr; /* 关联 硬件发包队列的虚拟地址。 */
+-> txq->sw_ring = rte_zmalloc_socket("txq->sw_ring", sizeof(struct ixgbe_tx_entry)...);/* 分配 软件发包队列 空间 */
+-> ixgbe_set_tx_function(dev, txq);
+-> dev->data->tx_queues[queue_idx] = txq; /* 关联 发包队列 */
分析上面 千兆和万兆以太网设备 的 eth_igb_tx_queue_setup
和 ixgbe_dev_tx_queue_setup
函数调用图。得到主要的步骤如下:
txq
。rx_ring
。sw_ring
。启动设备 的过程中,涉及到中断,硬件等等的设置。但在这里主要讨论 收发包的回调函数 的设置。
以下是 以太网设备的启动 总体的函数调用图。
main
| /* ------------------------ */
| /* 以太网设备的启动 */
+-> rte_eth_dev_start /* 以太网设备启动 */
+-> (*dev->dev_ops->dev_start)(dev)
. /* 千兆:eth_igb_ops.dev_start = eth_igb_start */
+~> eth_igb_start /* 千兆:以太网设备启动 */
. +-> eth_igb_tx_init() /* 千兆:初始化发送单元 */
. +-> eth_igb_rx_init() /* 千兆:初始化接收单元 */
. +-> igb_alloc_rx_queue_mbufs() /* <== 创建 收包队列的 mbuf ==> */
.
. /* 万兆:ixgbe_eth_dev_ops.dev_start = ixgbe_dev_start */
+~> ixgbe_dev_start /* 万兆:以太网设备启动 */
+-> ixgbe_dev_tx_init() /* 万兆:初始化发送单元 */
+-> ixgbe_dev_rx_init() /* 万兆:初始化接收单元 */
+-> ixgbe_dev_rxtx_start() /* 启动收发包单元 */
+-> ixgbe_dev_rx_queue_start() /* 启动收包单元 */
+-> ixgbe_alloc_rx_queue_mbufs() /* <== 创建 收包队列的 mbuf ==> */
rte_eth_dev_start
函数rte_eth_dev_start()
函数用于启动一个 以太网设备。
dev_ops->dev_start()
回调函数来启动。重点:
xxx_alloc_rx_queue_mbufs
函数,用于 创建 收包队列的 mbuf
。
例子:
eth_igb_start
:eth_igb_start
的函数主要流程如下:
eth_igb_tx_init
会修改 千兆以太网设备 发包的回调函数 tx_pkt_burst
。这是最后修改 的地方。eth_igb_rx_init
会修改 千兆以太网设备 收包的回调函数 rx_pkt_burst
。这是最后修改 的地方。eth_igb_rx_init
中,会调用 igb_alloc_rx_queue_mbufs
函数 用于 创建 收包队列的 mbuf
。eth_igb_start 函数调用图如下:
rte_eth_dev_start()
+-> (*dev->dev_ops->dev_start)(dev)
+=> eth_igb_start() /* <== 千兆:以太网设备启动 */
+-> rte_intr_disable() /* disable uio/vfio intr/eventfd mapping */
+-> eth_igb_dev_set_link_up() /* Power up the phy. Needed to make the link go Up */
+-> e1000_rar_set() /* Put the address into the Receive Address Array */
+-> igb_hardware_init() /* Initialize the hardware */
+-> igb_pf_host_configure() /* configure PF module if SRIOV enabled */
+-> rte_intr_cap_multiple() /* check and configure queue intr-vector mapping */
+-> rte_intr_efd_enable()
+-> eth_igb_configure_msix_intr() /* confiugre msix for rx interrupt */
+-> igb_init_manageability() /* Configure for OS presence */
+-> eth_igb_tx_init() /* 1. 初始化发送单元 */
+-> eth_igb_rx_init() /* 2. 初始化接收单元 */
| +-> igb_alloc_rx_queue_mbufs() /* <== 2.1. 创建 收包队列的 mbuf ==> */
+-> e1000_clear_hw_cntrs_base_generic()
+-> eth_igb_vlan_offload_set()
+-> e1000_setup_link()
+-> eth_igb_lsc_interrupt_setup() /* check if lsc interrupt is enabled */
+-> eth_igb_rxq_interrupt_setup() /* check if rxq interrupt is enabled */
+-> rte_intr_enable() /* enable uio/vfio intr/eventfd mapping */
+-> igb_intr_enable() /* resume enabled intr since hw reset */
ixgbe_dev_start
:ixgbe_dev_start
的函数主要流程如下:
ixgbe_dev_tx_init
会修改 万兆以太网设备 发包的回调函数 tx_pkt_burst
。这是最后修改 的地方。ixgbe_dev_rx_init
会修改 万兆以太网设备 收包的回调函数 rx_pkt_burst
。这是最后修改 的地方。ixgbe_dev_rxtx_start
函数 用于启动收发包单元。ixgbe_dev_tx_queue_start
函数 用于启动发包单元。ixgbe_dev_rx_queue_start
函数 用于启动收包单元。ixgbe_alloc_rx_queue_mbufs
函数 用于 创建 收包队列的 mbuf。ixgbe_dev_start
函数调用图如下:
rte_eth_dev_start()
+-> (*dev->dev_ops->dev_start)(dev)
+=> ixgbe_dev_start() /* <== 万兆:以太网设备启动 */
+-> rte_intr_disable() /* disable uio/vfio intr/eventfd mapping */
+-> ixgbe_stop_adapter() /* 停掉适配器,停止发送和接收单元 */
+-> ixgbe_pf_reset_hw() /* 重启适配器 */
+-> ixgbe_pf_host_configure() /* configure PF module if SRIOV enabled */
+-> ixgbe_dev_phy_intr_setup()
+-> rte_intr_cap_multiple() /* check and configure queue intr-vector mapping */
+-> rte_intr_efd_enable()
+-> ixgbe_configure_msix() /* confiugre msix for sleep until rx interrupt */
+-> ixgbe_dev_tx_init() /* 1. 初始化发送单元 */
+-> ixgbe_dev_rx_init() /* 2. 初始化接收单元 */
+-> ixgbe_dev_rxtx_start() /* 3. 启动收发包单元 */
| +-> ixgbe_dev_tx_queue_start() /* 3.1. 启动发包单元 */
| +-> ixgbe_dev_rx_queue_start() /* 3.2. 启动收包单元 */
| +-> ixgbe_alloc_rx_queue_mbufs() /* <== 3.2.1. 创建 收包队列的 mbuf ==> */
+-> hw->mac.ops.setup_sfp()
+-> ixgbe_set_phy_power() /* Turn on the copper */
+-> ixgbe_enable_tx_laser() /* Turn on the laser */
+-> ixgbe_check_link()
+-> ixgbe_setup_link()
+-> ixgbe_dev_lsc_interrupt_setup() /* check if lsc interrupt is enabled */
+-> ixgbe_dev_rxq_interrupt_setup() /* check if rxq interrupt is enabled */
+-> rte_intr_enable() /* enable uio/vfio intr/eventfd mapping */
+-> ixgbe_enable_intr() /* resume enabled intr since hw reset */
+-> ixgbe_vlan_offload_set()
+-> ixgbe_vmdq_vlan_hw_filter_enable() /* Enable vlan filtering for VMDq */
+-> ixgbe_configure_dcb() /* Configure DCB hw */
+-> ixgbe_fdir_configure()
+-> ixgbe_set_vf_rate_limit() /* Restore vf rate limit */
+-> ixgbe_restore_statistics_mapping()
rte_eth_dev_start
函数中,对 rx_pkt_burst
收包实现函数的选择在 rte_eth_dev_init
函数中,不同的驱动会触发 eth_dev_init
回调函数,为 struct rte_eth_dev
中的 rx_pkt_burst
的函数指针,设置了默认的收包实现函数。
但是到了 rte_eth_dev_start
函数中,会 对 rx_pkt_burst
进一步选择不同的 收包实现函数。
rx_pkt_burst
收包实现函数的选择千兆以太网设备,有 2 种的 rx_pkt_burst
收包实现函数。
函数名 | 描述 |
---|---|
eth_igb_recv_pkts |
千兆 默认收包函数 (报文最多由 1个 segment 组成) |
eth_igb_recv_scattered_pkts |
千兆 Rx Scattered (报文可以由 多个 segment 组成) |
千兆以太网设备,rx_pkt_burst
收包实现函数的选择,会通过 eth_igb_rx_init
函数来实现。
eth_igb_rx_init
的函数调用图如下:
rte_eth_dev_start()
+-> (*dev->dev_ops->dev_start)(dev)
+~> eth_igb_start()
+=> eth_igb_rx_init() /* <== initialize receiving unit */
eth_igb_rx_init
中,有关 rx_pkt_burst
设置的代码,简化如下:
int
eth_igb_rx_init(struct rte_eth_dev *dev)
{
/* ... */
/* default */
dev->rx_pkt_burst = eth_igb_recv_pkts;
/* scattered */
if (dev->data->dev_conf.rxmode.enable_scatter) {
dev->rx_pkt_burst = eth_igb_recv_scattered_pkts;
}
/* ... */
}
rx_pkt_burst
收包实现函数的选择万兆以太网设备,有 6 种的 rx_pkt_burst
收包实现函数。
函数名 | 描述 |
---|---|
ixgbe_recv_pkts_lro_bulk_alloc | 万兆 Rx LRO bulk allocation |
ixgbe_recv_pkts_lro_single_alloc | 万兆 Rx LRO single allocation |
ixgbe_recv_scattered_pkts_vec | 万兆 Rx Vector Scattered |
ixgbe_recv_pkts_vec | 万兆 Rx Vector enabled |
ixgbe_recv_pkts_bulk_alloc | 万兆 Rx Burst Bulk allocation |
ixgbe_recv_pkts | 万兆 默认收包函数 |
万兆以太网设备,rx_pkt_burst
收包实现函数的选择,会通过 ixgbe_set_rx_function
函数来实现。
ixgbe_set_rx_function
的函数调用图如下:
rte_eth_dev_start
+-> (*dev->dev_ops->dev_start)(dev)
+~> ixgbe_dev_start
+-> ixgbe_dev_rx_init() /* initialize receiving unit */
+=> ixgbe_set_rx_function /* <== set receiving function */
ixgbe_set_rx_function
中,有关 rx_pkt_burst
设置的代码,简化如下:
void ixgbe_set_rx_function(struct rte_eth_dev *dev)
{
/* ... */
if (dev->data->lro) {
if (adapter->rx_bulk_alloc_allowed) {
/* LRO is requested. Using a bulk allocation version */
dev->rx_pkt_burst = ixgbe_recv_pkts_lro_bulk_alloc;
} else {
/* LRO is requested. Using a single allocation version */
dev->rx_pkt_burst = ixgbe_recv_pkts_lro_single_alloc;
}
} else if (dev->data->scattered_rx) {
if (adapter->rx_vec_allowed) {
/* Using Vector Scattered Rx */
dev->rx_pkt_burst = ixgbe_recv_scattered_pkts_vec;
} else if (adapter->rx_bulk_alloc_allowed) {
/* Using a Scattered with bulk allocation */
dev->rx_pkt_burst = ixgbe_recv_pkts_lro_bulk_alloc;
} else {
/* Using Regualr (non-vector, single allocation) Scattered Rx */
dev->rx_pkt_burst = ixgbe_recv_pkts_lro_single_alloc;
}
} else if (adapter->rx_vec_allowed) {
/* Vector rx enabled */
dev->rx_pkt_burst = ixgbe_recv_pkts_vec;
} else if (adapter->rx_bulk_alloc_allowed) {
/* Rx Burst Bulk Alloc Preconditions are satisfied. Rx Burst Bulk Alloc */
dev->rx_pkt_burst = ixgbe_recv_pkts_bulk_alloc;
} else {
/* Rx Burst Bulk Alloc Preconditions are not satisfied, or Scattered Rx is requested */
dev->rx_pkt_burst = ixgbe_recv_pkts;
}
/* ... */
}
rte_eth_dev_start
函数中,tx_pkt_burst
发包实现函数的选择在 rte_eth_dev_init
函数中,不同的驱动会触发 eth_dev_init
回调函数,为 struct rte_eth_dev
中的 tx_pkt_burst
的函数指针,设置了默认的发包实现函数。
但是到了 rte_eth_dev_start
函数中,会进一步选择不同的 发包实现函数。
tx_pkt_burst
发包实现函数的选择千兆以太网设备,有 1 种的 tx_pkt_burst
发包实现函数。
函数名 | 描述 |
---|---|
eth_igb_recv_pkts |
千兆 默认发包函数 |
千兆以太网设备,tx_pkt_burst
发包实现函数的选择,在 eth_igb_dev_init
函数里设置。采用默认的 发包函数 eth_igb_recv_pkts
。
tx_pkt_burst
发包实现函数的选择万兆以太网设备,有 3 种的 tx_pkt_burst
发包实现函数。
函数名 | 描述 |
---|---|
ixgbe_xmit_pkts_vec |
万兆 Rx Vector enabled |
ixgbe_xmit_pkts_simple |
万兆 Tx simple |
ixgbe_xmit_pkts |
万兆 默认发包函数 |
万兆以太网设备,tx_pkt_burst
发包实现函数的选择,会通过 ixgbe_set_tx_function
函数来实现。
ixgbe_set_tx_function
的函数调用图如下:
rte_eth_dev_start
+-> (*dev->dev_ops->dev_start)(dev)
+~> ixgbe_dev_start
+-> ixgbe_dev_tx_init() /* initialize transmission unit */
+=> ixgbe_set_tx_function /* <== set transmission function */
ixgbe_set_tx_function
中,有关 tx_pkt_burst
设置的代码,简化如下:
ixgbe_set_tx_function(struct rte_eth_dev *dev, struct ixgbe_tx_queue *txq)
{
/* ... */
/* Use a simple Tx queue (no offloads, no multi segs) if possible */
if (((txq->txq_flags & IXGBE_SIMPLE_FLAGS) == IXGBE_SIMPLE_FLAGS)
&& (txq->tx_rs_thresh >= RTE_PMD_IXGBE_TX_MAX_BURST)) {
/* Using simple tx code path */
#ifdef RTE_IXGBE_INC_VECTOR
if (txq->tx_rs_thresh <= RTE_IXGBE_TX_MAX_FREE_BUF_SZ &&
(rte_eal_process_type() != RTE_PROC_PRIMARY ||
ixgbe_txq_vec_setup(txq) == 0)) {
/* "Vector tx enabled." */
dev->tx_pkt_burst = ixgbe_xmit_pkts_vec;
} else
#endif
dev->tx_pkt_burst = ixgbe_xmit_pkts_simple;
} else {
/* Using full-featured tx code path */
dev->tx_pkt_burst = ixgbe_xmit_pkts;
}
}
receive descriptor
)千兆和万兆以太网设备分别定义了 union e1000_adv_rx_desc
和 union ixgbe_adv_rx_desc
作为网卡的收包的描述符(receive descriptor
)。
receive descriptor
的结构是由 以太网设备 芯片所决定的。具体可以查询 芯片的 datasheet。
以 Intel® I350 为例,其中的 receive descriptor
有两种的格式,分别是 read
和 write-back
,分别定义如下:
union e1000_adv_rx_desc
和 union ixgbe_adv_rx_desc
是按照以上两种的格式来定义的。
重点:
receive descriptor
的 read.pkt_addr
指向网卡接收数据的 DMA
地址。网卡收包时,会将报文数据直接写入其所指向的地方。receive descriptor
的 read.hdr_addr
的 最低位为 DD (Descriptor Done)
标志,当接收到报文后,DD
标志会由网卡硬件置位。DD
标志,确定是否完成收包。receive descriptor
的 wb
结构体的数据,是由网卡硬件填充的。可以用于收包后 mbuf
数据的初始化。例子:
千兆以太网设备 的 receive descriptor
源码:
/* Receive Descriptor - Advanced */
union e1000_adv_rx_desc {
struct {
__le64 pkt_addr; /* Packet buffer address */
__le64 hdr_addr; /* Header buffer address */
} read;
struct {
struct {
union {
__le32 data;
struct {
__le16 pkt_info; /*RSS type, Pkt type*/
/* Split Header, header buffer len */
__le16 hdr_info;
} hs_rss;
} lo_dword;
union {
__le32 rss; /* RSS Hash */
struct {
__le16 ip_id; /* IP id */
__le16 csum; /* Packet Checksum */
} csum_ip;
} hi_dword;
} lower;
struct {
__le32 status_error; /* ext status/error */
__le16 length; /* Packet length */
__le16 vlan; /* VLAN tag */
} upper;
} wb; /* writeback */
};
万兆以太网设备 的 receive descriptor
源码:
/* Receive Descriptor - Advanced */
union ixgbe_adv_rx_desc {
struct {
__le64 pkt_addr; /* Packet buffer address */
__le64 hdr_addr; /* Header buffer address */
} read;
struct {
struct {
union {
__le32 data;
struct {
__le16 pkt_info; /* RSS, Pkt type */
__le16 hdr_info; /* Splithdr, hdrlen */
} hs_rss;
} lo_dword;
union {
__le32 rss; /* RSS Hash */
struct {
__le16 ip_id; /* IP id */
__le16 csum; /* Packet Checksum */
} csum_ip;
} hi_dword;
} lower;
struct {
__le32 status_error; /* ext status/error */
__le16 length; /* Packet length */
__le16 vlan; /* VLAN tag */
} upper;
} wb; /* writeback */
};
参考:
receive descriptor
,可以参考 Intel® I350 的 datasheet 中的 “Advanced Receive Descriptors” 一节。
mbuf
无论千兆还是万兆的设备,都需要 创建 收包队列的 mbuf
。
igb_alloc_rx_queue_mbufs
函数。ixgbe_alloc_rx_queue_mbufs
函数。函数的调用图如下:
rte_eth_dev_start /* 以太网设备启动 */
+-> (*dev->dev_ops->dev_start)(dev)
. /* 千兆:eth_igb_ops.dev_start = eth_igb_start */
+~> eth_igb_start /* 千兆:以太网设备启动 */
. +-> eth_igb_rx_init() /* 千兆:初始化接收单元 */
. +=> igb_alloc_rx_queue_mbufs() /* <== 创建 收包队列的 mbuf ==> */
.
. /* 万兆:ixgbe_eth_dev_ops.dev_start = ixgbe_dev_start */
+~> ixgbe_dev_start /* 万兆:以太网设备启动 */
+-> ixgbe_dev_rxtx_start() /* 启动收发包单元 */
+-> ixgbe_dev_rx_queue_start() /* 启动收包单元 */
+=> ixgbe_alloc_rx_queue_mbufs() /* <== 创建 收包队列的 mbuf ==> */
为了可以更好的对比不同设备的代码。将代码简化如下。
static int
igb_alloc_rx_queue_mbufs(struct igb_rx_queue *rxq)
{
struct igb_rx_entry *rxe = rxq->sw_ring;
uint64_t dma_addr;
unsigned int i;
/* Initialize software ring entries */
for (i = 0; i < rxq->nb_rx_desc; i++) {
volatile union e1000_adv_rx_desc *rxd;
struct rte_mbuf *mbuf =
rte_mbuf_raw_alloc(rxq->mb_pool);
dma_addr = rte_cpu_to_le_64(
rte_mbuf_data_dma_addr_default(mbuf));
rxd = &rxq->rx_ring[i];
rxd->read.hdr_addr = 0;
rxd->read.pkt_addr = dma_addr;
rxe[i].mbuf = mbuf;
}
return 0;
}
static int
ixgbe_alloc_rx_queue_mbufs(struct ixgbe_rx_queue *rxq)
{
struct ixgbe_rx_entry *rxe = rxq->sw_ring;
uint64_t dma_addr;
unsigned int i;
/* Initialize software ring entries */
for (i = 0; i < rxq->nb_rx_desc; i++) {
volatile union ixgbe_adv_rx_desc *rxd;
struct rte_mbuf *mbuf =
rte_mbuf_raw_alloc(rxq->mb_pool);
dma_addr = rte_cpu_to_le_64(
rte_mbuf_data_dma_addr_default(mbuf));
rxd = &rxq->rx_ring[i];
rxd->read.hdr_addr = 0;
rxd->read.pkt_addr = dma_addr;
rxe[i].mbuf = mbuf;
}
return 0;
}
简化后的代码,基本结构是相同的,除了 receive descriptor
类型有所不同。
代码分析如下:
rte_mbuf_raw_alloc
从 收包队列(rxq
)所属的 mbuf
内存池中(mb_pool
)中取出一个 mbuf
。 struct rte_mbuf *mbuf = rte_mbuf_raw_alloc(rxq->mb_pool);
rte_mbuf_data_dma_addr_default
计算得出 mbuf
的报文数据所在的物理地址(最后使用变量 dma_addr
暂存)。 /* rte_mbuf_data_dma_addr_default 的定义:
* static inline phys_addr_t
* rte_mbuf_data_dma_addr_default(const struct rte_mbuf *mb)
* {
* return mb->buf_physaddr + RTE_PKTMBUF_HEADROOM;
* }
*
* 在 x86 架构下 rte_cpu_to_le_64 的定义:
* #define rte_cpu_to_le_64(x) (x)
*/
dma_addr = rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(mbuf));
DD
(Descriptor Done) 标志。等待报文数据的接收。 rxd->read.hdr_addr = 0;
dma_addr
赋值到 rx_ring[i]
的 read.pkt_addr
字段中。网卡收包时,就会直接对 mbuf
的报文数据所在的物理地址写入数据,从而免除了从内核态到用户态之间的拷贝。 rxd->read.pkt_addr = dma_addr;
mbuf
的虚拟地址赋值到 sw_ring[i]
的 mbuf
字段中,从而记录下入列的 mbuf
。当收包完成后,就可以通过 sw_ring[]
找到 mbuf
,从而取得报文。 rxe[i].mbuf = mbuf;
sw_ring[]
和 rx_ring[]
都通过 mbuf
一一关联起来。以下为千兆的 igb_alloc_rx_queue_mbufs
函数的完成创建 收包队列的 mbuf
后的图例:
以上的以太网设备都准备好后。就可以使用 rte_eth_rx_burst
和 rte_eth_tx_burst
来收发报文了。总体的函数调用图如下所示:
main
| /* ------------------------ */
| /* 以太网设备的收包 */
+-> rte_eth_rx_burst
| +-> (*dev->rx_pkt_burst)(dev->data->rx_queues[queue_id], ...)
| . /* 千兆:rx_pkt_burst 默认收包函数为 eth_igb_recv_pkts */
| +~> eth_igb_recv_pkts
| .
| . /* 万兆:rx_pkt_burst 默认收包函数为 ixgbe_recv_pkts */
| +~> ixgbe_recv_pkts
|
| /* ------------------------ */
| /* 以太网设备的发包 */
+-> rte_eth_tx_burst
+-> (*dev->tx_pkt_burst)(dev->data->tx_queues[queue_id], ...)
. /* 千兆:tx_pkt_burst 默认发包函数为 eth_igb_xmit_pkts */
+~> eth_igb_xmit_pkts
.
. /* 万兆:tx_pkt_burst 默认发包函数为 ixgbe_xmit_pkts */
+~> ixgbe_xmit_pkts
rte_eth_rx_burst
函数rte_eth_rx_burst
函数 用于收包。在 最后会采用对应以太网设备的收包函数来实现。收包函数的主要流程如下:
DD
(Descriptor Done
) 标志,判断 是否接收到报文。接收到报文后,DD
标志会由网卡硬件置位。mbuf
,用来替换掉 已经接收到的报文 mbuf
。rx_ring
和 sw_ring
则都指向新的 mbuf
,为下一次收包做准备。receive descriptor
中的数据,初始化 已经接收到报文的 mbuf
。receive descriptor
。receive descriptor
大于 阈值。则将 RDT
设置为 最后的 receive descriptor -1
。从而避免了 RDT
等于 RDH
,造成丢包或者阻塞的情况。函数调用图如下:
注意下图只使用了默认收包函数表示。
rte_eth_rx_burst
+-> (*dev->rx_pkt_burst)(dev->data->rx_queues[queue_id],...);
. /* 千兆:rx_pkt_burst 默认收包函数为 eth_igb_recv_pkts */
+~> eth_igb_recv_pkts
.
. /* 万兆:rx_pkt_burst 默认收包函数为 ixgbe_recv_pkts */
+~> ixgbe_recv_pkts
例子:
注意,为了方便描述,代码删除了 prefetch
的代码片段和调试打印,并增加了注释。
eth_igb_recv_pkts
简化后的代码。 uint16_t
eth_igb_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
uint16_t nb_pkts)
{
struct igb_rx_queue *rxq;
volatile union e1000_adv_rx_desc *rx_ring;
volatile union e1000_adv_rx_desc *rxdp;
struct igb_rx_entry *sw_ring;
struct igb_rx_entry *rxe;
struct rte_mbuf *rxm;
struct rte_mbuf *nmb;
union e1000_adv_rx_desc rxd;
uint64_t dma_addr;
uint32_t staterr;
uint32_t hlen_type_rss;
uint16_t pkt_len;
uint16_t rx_id;
uint16_t nb_rx;
uint16_t nb_hold;
uint64_t pkt_flags;
nb_rx = 0;
nb_hold = 0;
rxq = rx_queue;
rx_id = rxq->rx_tail;
rx_ring = rxq->rx_ring;
sw_ring = rxq->sw_ring;
while (nb_rx < nb_pkts) {
/* ----------------------------- *
* 判断 是否接收到报文 *
* ----------------------------- */
/* DD (Descriptor Done) 表示 Descriptor 已经收到报文,并填充到 mbuf 中。
* 如果 Descriptor Done 没有设置,就跳出循环。
*/
rxdp = &rx_ring[rx_id];
staterr = rxdp->wb.upper.status_error;
if (! (staterr & rte_cpu_to_le_32(E1000_RXD_STAT_DD)))
break;
rxd = *rxdp;
/* ----------------------------- *
* 处理 已经接收到的报文 *
* ----------------------------- */
/* 从 内存池中取一个新的 mbuf,用 nmb 指向它
* 如果失败就用 rx_mbuf_alloc_failed 统计一下,然后跳出循环。
*/
nmb = rte_mbuf_raw_alloc(rxq->mb_pool);
if (nmb == NULL) {
rte_eth_devices[rxq->port_id].data->rx_mbuf_alloc_failed++;
break;
}
/* 用 rx_id 记录下一个 Descriptor 的下标
*/
nb_hold++;
rxe = &sw_ring[rx_id];
rx_id++;
if (rx_id == rxq->nb_rx_desc)
rx_id = 0;
/* 用 rxm 指向 已经接收到报文的 mbuf。
* rx_ring 和 sw_ring 则都指向新的 mbuf,nmb。
*/
rxm = rxe->mbuf;
rxe->mbuf = nmb;
dma_addr =
rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb));
rxdp->read.hdr_addr = 0;
rxdp->read.pkt_addr = dma_addr;
/* 初始化 已经接收到报文的 mbuf
* Initialize the returned mbuf.
* 1) setup generic mbuf fields:
* - number of segments,
* - next segment,
* - packet length,
* - RX port identifier.
* 2) integrate hardware offload data, if any:
* - RSS flag & hash,
* - IP checksum flag,
* - VLAN TCI, if any,
* - error flags.
*/
pkt_len = (uint16_t) (rte_le_to_cpu_16(rxd.wb.upper.length) -
rxq->crc_len); /* 报文长度 */
rxm->data_off = RTE_PKTMBUF_HEADROOM; /* 报文数据的偏移 */
rxm->nb_segs = 1; /* segment 个数 */
rxm->next = NULL;
rxm->pkt_len = pkt_len; /* 报文长度 */
rxm->data_len = pkt_len; /* 数据长度 */
rxm->port = rxq->port_id; /* 收包的 port_id */
rxm->hash.rss = rxd.wb.lower.hi_dword.rss; /* 硬件 rss 分流 hash key */
hlen_type_rss = rte_le_to_cpu_32(rxd.wb.lower.lo_dword.data);
/* Only valid if PKT_RX_VLAN_PKT set in pkt_flags */
rxm->vlan_tci = rte_le_to_cpu_16(rxd.wb.upper.vlan); /* vlan tag */
pkt_flags = rx_desc_hlen_type_rss_to_pkt_flags(rxq, hlen_type_rss);
pkt_flags = pkt_flags | rx_desc_status_to_pkt_flags(staterr); /* status flags */
pkt_flags = pkt_flags | rx_desc_error_to_pkt_flags(staterr); /* error flags */
rxm->ol_flags = pkt_flags; /* offload features */
rxm->packet_type = igb_rxd_pkt_info_to_pkt_type(rxd.wb.lower.
lo_dword.hs_rss.pkt_info); /* 报文类型 */
/* 将 mbuf 的地址,放置到出参的收包数组中。
*/
rx_pkts[nb_rx++] = rxm;
}
/* 收包队列尾部 指向下一个 Descriptor */
rxq->rx_tail = rx_id;
/* 更新 RDT,避免出现收包队列溢出,造成丢包或者阻塞的情况
* If the number of free RX descriptors is greater than the RX free
* threshold of the queue, advance the Receive Descriptor Tail (RDT)
* register.
* Update the RDT with the value of the last processed RX descriptor
* minus 1, to guarantee that the RDT register is never equal to the
* RDH register, which creates a "full" ring situtation from the
* hardware point of view...
*/
nb_hold = (uint16_t) (nb_hold + rxq->nb_rx_hold);
if (nb_hold > rxq->rx_free_thresh) {
rx_id = (uint16_t) ((rx_id == 0) ?
(rxq->nb_rx_desc - 1) : (rx_id - 1));
E1000_PCI_REG_WRITE(rxq->rdt_reg_addr, rx_id);
nb_hold = 0;
}
rxq->nb_rx_hold = nb_hold;
return nb_rx;
}
ixgbe_recv_pkts
简化后的代码。 uint16_t
ixgbe_recv_pkts(void *rx_queue, struct rte_mbuf **rx_pkts,
uint16_t nb_pkts)
{
struct ixgbe_rx_queue *rxq;
volatile union ixgbe_adv_rx_desc *rx_ring;
volatile union ixgbe_adv_rx_desc *rxdp;
struct ixgbe_rx_entry *sw_ring;
struct ixgbe_rx_entry *rxe;
struct rte_mbuf *rxm;
struct rte_mbuf *nmb;
union ixgbe_adv_rx_desc rxd;
uint64_t dma_addr;
uint32_t staterr;
uint32_t pkt_info;
uint16_t pkt_len;
uint16_t rx_id;
uint16_t nb_rx;
uint16_t nb_hold;
uint64_t pkt_flags;
uint64_t vlan_flags;
nb_rx = 0;
nb_hold = 0;
rxq = rx_queue;
rx_id = rxq->rx_tail;
rx_ring = rxq->rx_ring;
sw_ring = rxq->sw_ring;
vlan_flags = rxq->vlan_flags;
while (nb_rx < nb_pkts) {
/* ----------------------------- *
* 判断 是否接收到报文 *
* ----------------------------- */
/* DD (Descriptor Done) 表示 Descriptor 已经收到报文,并填充到 mbuf 中。
* 如果 Descriptor Done 没有设置,就跳出循环。
*/
rxdp = &rx_ring[rx_id];
staterr = rxdp->wb.upper.status_error;
if (!(staterr & rte_cpu_to_le_32(IXGBE_RXDADV_STAT_DD)))
break;
rxd = *rxdp;
/* ----------------------------- *
* 处理 已经接收到的报文 *
* ----------------------------- */
/* 从 内存池中取一个新的 mbuf,用 nmb 指向它
* 如果失败就用 rx_mbuf_alloc_failed 统计一下,然后跳出循环。
*/
nmb = rte_mbuf_raw_alloc(rxq->mb_pool);
if (nmb == NULL) {
rte_eth_devices[rxq->port_id].data->rx_mbuf_alloc_failed++;
break;
}
/* 用 rx_id 记录下一个 Descriptor 的下标
*/
nb_hold++;
rxe = &sw_ring[rx_id];
rx_id++;
if (rx_id == rxq->nb_rx_desc)
rx_id = 0;
/* 用 rxm 指向 已经接收到报文的 mbuf。
* rx_ring 和 sw_ring 则都指向新的 mbuf,nmb。
*/
rxm = rxe->mbuf;
rxe->mbuf = nmb;
dma_addr =
rte_cpu_to_le_64(rte_mbuf_data_dma_addr_default(nmb));
rxdp->read.hdr_addr = 0;
rxdp->read.pkt_addr = dma_addr;
/* 初始化 已经接收到报文的 mbuf
* Initialize the returned mbuf.
* 1) setup generic mbuf fields:
* - number of segments,
* - next segment,
* - packet length,
* - RX port identifier.
* 2) integrate hardware offload data, if any:
* - RSS flag & hash,
* - IP checksum flag,
* - VLAN TCI, if any,
* - error flags.
*/
pkt_len = (uint16_t) (rte_le_to_cpu_16(rxd.wb.upper.length) -
rxq->crc_len); /* 报文长度 */
rxm->data_off = RTE_PKTMBUF_HEADROOM; /* 报文数据的偏移 */
rxm->nb_segs = 1; /* segment 个数 */
rxm->next = NULL;
rxm->pkt_len = pkt_len; /* 报文长度 */
rxm->data_len = pkt_len; /* 数据长度 */
rxm->port = rxq->port_id; /* 收包的 port_id */
pkt_info = rte_le_to_cpu_32(rxd.wb.lower.lo_dword.data);
/* Only valid if PKT_RX_VLAN_PKT set in pkt_flags */
rxm->vlan_tci = rte_le_to_cpu_16(rxd.wb.upper.vlan); /* vlan tag */
pkt_flags = rx_desc_status_to_pkt_flags(staterr, vlan_flags); /* vlan flags */
pkt_flags = pkt_flags | rx_desc_error_to_pkt_flags(staterr); /* error flags */
pkt_flags = pkt_flags |
ixgbe_rxd_pkt_info_to_pkt_flags((uint16_t)pkt_info);
rxm->ol_flags = pkt_flags;
rxm->packet_type =
ixgbe_rxd_pkt_info_to_pkt_type(pkt_info,
rxq->pkt_type_mask); /* 报文类型 */
if (likely(pkt_flags & PKT_RX_RSS_HASH))
rxm->hash.rss = rte_le_to_cpu_32(
rxd.wb.lower.hi_dword.rss); /* rss hash key */
else if (pkt_flags & PKT_RX_FDIR) {
rxm->hash.fdir.hash = rte_le_to_cpu_16(
rxd.wb.lower.hi_dword.csum_ip.csum) &
IXGBE_ATR_HASH_MASK;
rxm->hash.fdir.id = rte_le_to_cpu_16(
rxd.wb.lower.hi_dword.csum_ip.ip_id);
}
/* 将 mbuf 的地址,放置到出参的收包数组中。
*/
rx_pkts[nb_rx++] = rxm;
}
/* 收包队列尾部 指向下一个 Descriptor */
rxq->rx_tail = rx_id;
/* 更新 RDT,避免出现收包队列溢出,造成丢包或者阻塞的情况
* If the number of free RX descriptors is greater than the RX free
* threshold of the queue, advance the Receive Descriptor Tail (RDT)
* register.
* Update the RDT with the value of the last processed RX descriptor
* minus 1, to guarantee that the RDT register is never equal to the
* RDH register, which creates a "full" ring situtation from the
* hardware point of view...
*/
nb_hold = (uint16_t) (nb_hold + rxq->nb_rx_hold);
if (nb_hold > rxq->rx_free_thresh) {
rx_id = (uint16_t) ((rx_id == 0) ?
(rxq->nb_rx_desc - 1) : (rx_id - 1));
IXGBE_PCI_REG_WRITE(rxq->rdt_reg_addr, rx_id);
nb_hold = 0;
}
rxq->nb_rx_hold = nb_hold;
return nb_rx;
}
参考:
有关 RDT
和 RDH
的关系以及网卡硬件收包队列,可以参考 Intel® I350 的 datasheet 中的 “Receive Descriptor Ring Structure” 一节。
rte_eth_tx_burst
函数rte_eth_rx_burst
函数 用于发包。
函数调用图如下:
注意只使用了默认发包函数表示。
rte_eth_tx_burst
+-> (*dev->tx_pkt_burst)(dev->data->tx_queues[queue_id], ...)
. /* 千兆:tx_pkt_burst 默认发包函数为 eth_igb_xmit_pkts */
+~> eth_igb_xmit_pkts
.
. /* 万兆:tx_pkt_burst 默认发包函数为 ixgbe_xmit_pkts */
+~> ixgbe_xmit_pkts
只要网卡初始化后,都可以在 gdb
中打印出 全局变量 rte_eth_devices[]
的内容。
最常用的是查看,收发包的回调函数 rx_pkt_burst
和 tx_pkt_burst
的具体调用。
例子:
" 千兆网卡
(gdb) p rte_eth_devices[0]
$32 = {
rx_pkt_burst = 0x7ffff22f0940 ,
tx_pkt_burst = 0x7ffff22f1150 ,
data = 0x7ffd685cec40,
driver = 0x7ffff25071e0 ,
dev_ops = 0x7ffff2506860 ,
pci_dev = 0x60b390,
link_intr_cbs = {
tqh_first = 0x7fffe9400fc0,
tqh_last = 0x7fffe9400fc0
},
post_rx_burst_cbs = {[0] = 0x0 },
pre_tx_burst_cbs = {[0] = 0x0 },
attached = 1 '\001',
dev_type = RTE_ETH_DEV_PCI
}
" 万兆网卡
(gdb) p rte_eth_devices[1]
$33 = {
rx_pkt_burst = 0x7ffff35be7b0 ,
tx_pkt_burst = 0x7ffff35d4ac0 ,
data = 0x7ffd685d0448,
driver = 0x7ffff37e8240 ,
dev_ops = 0x7ffff37e7ac0 ,
pci_dev = 0x60bb10,
link_intr_cbs = {
tqh_first = 0x7ffd68400880,
tqh_last = 0x7ffd68400880
},
post_rx_burst_cbs = {[0] = 0x0 },
pre_tx_burst_cbs = {[0] = 0x0 },
attached = 1 '\001',
dev_type = RTE_ETH_DEV_PCI
}
参考:
eth_igb_xmit_pkts
和 ixgbe_xmit_pkts
由于要支持多个 segment
报文。所以除了 data descriptor
还需要 context descriptor
。
详细可以参考 Intel® I350 的 datasheet 中的 “Transmit Functionality” 一节。
eth_igb_xmit_pkts
和 ixgbe_xmit_pkts
详细的代码分析这里就留给读者。