11.DPU网络开发SDK——DPDK(十)
DPU专栏: 11.DPU网络开发SDK——DPDK(十)
接上一次内容继续分析pci总线的scan()方法。
pci_scan_one()会完成如下工作:
static int pci_scan_one(const char *dirname, const struct rte_pci_addr *addr) { char filename[PATH_MAX]; unsigned long tmp; struct rte_pci_device *dev; char driver[PATH_MAX]; int ret; dev = malloc(sizeof(*dev)); //创建 …… memset(dev, 0, sizeof(*dev)); //关联总线 dev->device.bus = &rte_pci_bus.bus; dev->addr = addr; snprintf(filename, sizeof(filename), “%s/vendor”, dirname); //设备的vendor和device id if (eal_parse_sysfs_value(filename, &tmp) id.vendor_id = (uint16_t)tmp; snprintf(filename, sizeof(filename), “%s/device”, dirname); if (eal_parse_sysfs_value(filename, &tmp) id.device_id = (uint16_t)tmp; snprintf(filename, sizeof(filename), “%s/subsystem_vendor”, dirname); //子系统的vendor和device id …… dev->id.subsystem_vendor_id = (uint16_t)tmp; snprintf(filename, sizeof(filename), “%s/subsystem_device”, dirname); …… dev->id.subsystem_device_id = (uint16_t)tmp; snprintf(filename, sizeof(filename), “%s/class”, dirname); //设备类的id …… / the least 24 bits are valid: class, subclass, program interface / dev->id.class_id = (uint32_t)tmp & RTE_CLASS_ANY_ID; dev->max_vfs = 0; snprintf(filename, sizeof(filename), “%s/max_vfs”, dirname); //最大的vf数量 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; } snprintf(filename, sizeof(filename), “%s/numa_node”, dirname); //获取到设备所属的numa节点 if (access(filename, F_OK) != -1) { …… } pci_name_set(dev); snprintf(filename, sizeof(filename), “%s/resource”, dirname); if (pci_parse_sysfs_resource(filename, dev) kdrv = RTE_PCI_KDRV_VFIO; else if (!strcmp(driver, “igb_uio”)) dev->kdrv = RTE_PCI_KDRV_IGB_UIO; else if (!strcmp(driver, “uio_pci_generic”)) dev->kdrv = RTE_PCI_KDRV_UIO_GENERIC; else dev->kdrv = RTE_PCI_KDRV_UNKNOWN; } else { dev->kdrv = RTE_PCI_KDRV_NONE; return 0; } if (TAILQ_EMPTY(&rte_pci_bus.device_list)) { //空列表直接插入 rte_pci_add_device(dev); } else { struct rte_pci_device *dev2; int ret; TAILQ_FOREACH(dev2, &rte_pci_bus.device_list, next) { ret = rte_pci_addr_cmp(&dev->addr, &dev2->addr); //比较设备名称 if (ret > 0) continue; if (ret device)) { //设备没有绑定驱动 dev2->kdrv = dev->kdrv; dev2->max_vfs = dev->max_vfs; dev2->id = dev->id; pci_name_set(dev2); memmove(dev2->mem_resource, dev->mem_resource, sizeof(dev->mem_resource)); } else { //设备绑定了驱动, if (dev2->kdrv != dev->kdrv || dev2->max_vfs != dev->max_vfs || memcmp(&dev2->id, &dev->id, sizeof(dev2->id))) RTE_LOG(ERR, EAL, “Unexpected device scan at %s!\n”, filename); else if (dev2->device.devargs != dev->device.devargs) { rte_devargs_remove(dev2->device.devargs); pci_name_set(dev2); } } free(dev); } return 0; } rte_pci_add_device(dev); } return 0;}
首先,他会创建一个rte_pci_device类型的实例,结构如下,可以认为是PCI总线设备的数据结构,他是rte_device设备的封装,从类角度上讲,rte_pci_device可以认为是rte_device的派生类。
- -
- -
- ```
struct rte_pci_device { TAILQ_ENTRY(rte_pci_device) next; /**addr, dev->name, sizeof(dev->name)); //设备名称即为设备的总线编号 devargs = pci_devargs_lookup(&dev->addr); dev->device.devargs = devargs; ……}
设置名称的过程中,还会设置设备的参数,调用pci_devargs_lookup()来实现,在分析该func前,需要对init过程中第11步的内容进行细化。这个步骤中,eal_option_device_parse()会遍历devopt_list中的每个元素,并调用rte_devargs_add()添加到devargs_list中。
int eal_option_device_parse(void) { struct device_option *devopt; …… TAILQ_FOREACH_SAFE(devopt, &devopt_list, next, tmp) { …… ret = rte_devargs_add(devopt->type, devopt->arg); …… TAILQ_REMOVE(&devopt_list, devopt, next); free(devopt); } ……}intrte_devargs_add(enum rte_devtype devtype, const char *devargs_str){ struct rte_devargs *devargs = NULL; struct rte_bus *bus = NULL; const char *dev = devargs_str; …… devargs = calloc(1, sizeof(*devargs)); if (devargs == NULL) goto fail; …… if (rte_devargs_parse(devargs, dev)) goto fail; devargs->type = devtype; bus = devargs->bus; if (devargs->type == RTE_DEVTYPE_BLOCKED) devargs->policy = RTE_DEV_BLOCKED; if (bus->conf.scan_mode == RTE_BUS_SCAN_UNDEFINED) { if (devargs->policy == RTE_DEV_ALLOWED) bus->conf.scan_mode = RTE_BUS_SCAN_ALLOWLIST; else if (devargs->policy == RTE_DEV_BLOCKED) bus->conf.scan_mode = RTE_BUS_SCAN_BLOCKLIST; } TAILQ_INSERT_TAIL(&devargs_list, devargs, next); return 0; ……}
rte_devargs_add()会创建一个rte_devargs类型的结构,并调用rte_devargs_parse()对传入的devargs字符串进行解析。传入的devargs字符串的最开始会指明总线类型,据此查找对应的rte_bus结构(通过遍历rte_bus_list结构并比较bus的名称);指明总线类型之后,devargs字符串指明了设备的名称,将设备名称存入rte_devargs->name中;名称之后,devargs字符串的内容就是额外的参数了,存入rte_devargs->args即可。调用rte_devargs_parse()之后,rte_devargs_add()如果检测到设备绑定的总线未指明scan模式,则根据设备的策略指定总线的scan模式。
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- ```
int rte_devargs_parse(struct rte_devargs *da, const char *dev) { struct rte_bus *bus = NULL; const char *devname; const size_t maxlen = sizeof(da->name); size_t i; …… do { //查找总线 devname = dev; bus = rte_bus_find(bus, bus_name_cmp, dev); if (bus == NULL) break; devname = dev + strlen(bus->name) + 1; if (rte_bus_find_by_device_name(devname) == bus) //设备名称经过解析之后,是该总线的设备名称的格式 break; } while (1); i = 0; while (devname[i] != '\0' && devname[i] != ',') { //设备名称 da->name[i] = devname[i]; i++; …… } …… da->bus = bus; if (devname[i] == ',') da->args = strdup(&devname[i + 1]); else da->args = strdup(""); …… return 0;}
回到pci_devargs_lookup(),该func根据设备名称查找devargs_list中的元素,找到设备对应的rte_devargs数据结构,将该数据结构中存入的devargs和设备名称填充到rte_pci_device->rte_device中的相应成员。
继续分析pci_scan_one()所做工作,通过sys文件系统读取完vendor和device id这些内容之后接下来分析设备的resource文件当中的内容,调用pci_parse_sysfs_resource()来实现。该过程主要是解析PCI设备的bar空间,bar空间是记录计算机在BIOS加载过程中,分配给每个设备的物理内存的信息,每个设备最多可以分配到6个连续的物理内存段,这些信息经过解析之后,会存放在rte_pci_device->mem_resource这个数组当中。解析完resource文件之后,会解析该设备绑定的驱动设备,调用pci_get_kernel_driver_by_path()来实现,获取到设备绑定的驱动程序后,将其记录在rte_pci_device->rte_device->kdrv中,目前DPDK能够识别的驱动有vfio-pci,igb_uio和uio_pci_generic。最后,调用rte_pci_add_device()将设备插入到rte_pci_bus->device_list中,插入过程需要检查是否已经有同名设备已经存在于列表当中,且插入后设备的名称是有序的;插入过程中如果设备存在,那么要么做替换处理,要么删除旧设备再插入新的。
至此PCI总线的scan方法分析完毕,PCI总线的scan方法是在DPDK的初始化过程中被调用的,通过上面的分析,可以看到scan方法主要是完成了设备的检索,并创建出相应的数据结构添加到设备列表当中。DPDK初始化过程中对每一个总线都会调用其scan方法,将各设备存储在各自的总线的列表当中。
在DPDK初始化过程的第33步调用rte_bus_probe()来处理设备探测,和rte_bus_scan()同理,每个总线的probe方法会被调用,以PCI的probe方法pci_probe()为例,该方法对于rte_pci_bus->device_list中的所有设备,依次调用pci_probe_all_drivers()方法。该func中,会依次遍历rte_pci_bus.driver_list中的PCI设备的驱动,并调用rte_pci_probe_one_driver()将驱动和设备进行匹配,建立PCI设备与PCI设备驱动之间的联系。
static int pci_probe_all_drivers(struct rte_pci_device *dev) { struct rte_pci_driver dr = NULL; int rc = 0; …… FOREACH_DRIVER_ON_PCIBUS(dr) { rc = rte_pci_probe_one_driver(dr, dev); if (rc 0) / positive value means driver doesn’t support it */ continue; return 0; } return 1;}#define FOREACH_DRIVER_ON_PCIBUS(p) \ TAILQ_FOREACH(p, &(rte_pci_bus.driver_list), next)
那么PCI设备的驱动是如何插入到rte_pci_bus.driver_list当中的呢,还是通过如下宏定义实现:
- -
- -
- -
- -
- -
- -
- -
- -
- - ```
void rte_pci_register(struct rte_pci_driver *driver) { TAILQ_INSERT_TAIL(&rte_pci_bus.driver_list, driver, next); driver->bus = &rte_pci_bus;}#define RTE_PMD_REGISTER_PCI(nm, pci_drv) \RTE_INIT(pciinitfn_ ##nm) \{\ (pci_drv).driver.name = RTE_STR(nm);\ rte_pci_register(&pci_drv); \} \RTE_PMD_EXPORT_NAME(nm, __COUNTER__)#define RTE_INIT(func) \ RTE_INIT_PRIO(func, LAST)#define RTE_INIT_PRIO(func, prio) \static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
可以看出,RTE_PMD_REGISTER_PCI这个宏定义同样是实现了一个构造函数,和总线注册时基本相同,不同之处在于其注册时的priority是LAST级别的,意思是在其他构造函数执行完之后才会执行,因为必须等到rte_bus_pci注册完毕之后,PCI设备的驱动才可以注册到rte_bus_pci.driver_list当中。
后续分析rte_pci_probe_one_driver()的实现。
** **
往期回顾: