代码时间点
文章没有选取pciutils 3.10.0的发布版本,而是选择了2023年12月30号的Rename aux fields in structs pci_access and pci_dev to backend_data 的版本,是因为这个版本修改一个重要的数据结构把aux修改成了backend_data,具体见
https://github.com/pciutils/pciutils/commit/a997ef132d943990cdb65d32a6605b6ff4bfcd79
数据结构
pciutils的lib里面有3个最重要的数据结构struct pci_methods、struct pci_access
和struct pci_dev。通过int pci_init_internal(struct pci_access *a, int skip_method)和struct pci_dev *pci_alloc_dev(struct pci_access *a)函数三个数据结构关联起来。
lib/internal.h文件
struct pci_methods
struct pci_methods是一个抽象概念,是对应的method的对于access和pci_dev的操作方法。struct pci_methods有11个函数指针,其中5个是和access相关的,6个是和pci_dev相关的。不同的access method会挂不同的函数指针。这个抽象出ops的数据结构使得pcituils能兼容多种OS,多种access method。
lib/pci.h文件
struct pci_access
unsigned int method是该access的method。在pci_init_internal赋值,范围见enum pci_access_type
int debugging是开启debug的flag。在parse_generic_option赋值,当debugging不为0的时候,struc pci_access->debug函数才会打印。
struct pci_dev *devices是access下的device的list。在pci_link_dev赋值。
struct pci_methods *methods指向access对应的method,在pci_init_internal赋值,从而指向不同的method。
void *backend_data是struct pci_access的私有数据。
struct pci_dev
struct pci_dev *next指向链表中的下一个设备。在pci_link_dev赋值。
struct pci_access *access指向该device对应的access,在pci_alloc_dev中赋值。
struct pci_method *methods指向device对应的access method,在pci_alloce_dev赋值。
void *backend_data是struct pci_dev的私有数据。
struct pci_cap
struct pci_cap *next指向下一个cap。
u16 id是cap的id,见/lib/header.h/PCI_CAP_ID_xx和/lib/header.h/PCI_EXP_CAP_ID_xx。
u16 type是cap的type,只有PCI_CAP_NORMAL和PCI_CAP_EXTENDED两种。
unsigned int addr是对应的cap在配置空间的offset。
struct pci_filter
lspci和setpci都调用pci_filter_parse_slot和pci_filter_parse_id解析输入的字符串,解析成功后放入struct pci_filter是用来保存
函数
lib/init.c文件
struct pci_access *pci_alloc(void)函数
main->pci_alloc
- 调用pci_malloc为pci_access数据结构申请一片内存。
- 调用接口(pci_init_name_list_path、pci_define_param)初始化struct pci_access的成员。
- 遍历pci_methods[PCI_ACCESS_MAX],如果pci_methods[i]不为NULL且pci_methods[i]的config函数不为NULL,则调用对应config函数。
比如sysfs的sysfs_config。ecam就是调用ecam_config
所有methods的config函数都是调用pci_define_param设置参数,并且放在struct pci_access->params里面,后续函数如detect、scan等函数会通过param找到对应的value。
调用关系如下:
main->pci_alloc->sysfs_config->pci_define_param
main->pci_alloc->ecam_config->pci_define_param
lib/filter.c文件
void pci_filter_init(struct pci_access *a, struct pci_filter *f)函数
main->pci_filter_init->pci_filter_init_v38
struct pci_filter的成员初始化成一个默认值。
lib/filter.c文件
char *pci_filter_parse_slot(struct pci_filter *f, char *str) /char *pci_filter_parse_id(struct pci_filter *f, char *str)函数
main->pci_filter_parse_slot->pci_filter_parse_slot_v38
pci_filter_parse_slot_v38根据格式解析输入的字符串char*str,并赋值给pci_filter。
main->pci_filter_parse_id->pci_filter_parse_id_v38
pci_filter_parse_id_v38根据格式解析输入的字符串char*str,并赋值给pci_filter。
lib/init.c文件
void pci_init(struct pci_access *a)函数
main->pci_init->pci_init_v35->pci_init_internal
- 为pci_acces的不同打印级别的call back函数挂上函数。
- 如果method不是PCI_ACCESS_ATUO,则使用当前的method,否则根据probe_sequence找到对应的method(主要看lib/configure根据系统配置往lib/config.h写的宏定义和probe_sequence定义的顺序,找到第一个method不为空,并且detect函数不为NULL的method)(到这里struct pci_methods、struct pci_access就建立了关系)。
- 调用methods->init函数,来初始化pci_accss,比如sysfs就是调用sysfs_init,ecam就是调用ecam_init。
static void sysfs_init(struct pci_access *a)函数
main->pci_init->pci_init_v30->pci_init_internal->sysfs_init
sysfs_init没有实质性的操作,只是把fd给赋值成-1了。
static void ecam_init(struct pci_access *a)函数
main->pci_init->pci_init_v30->pci_init_internal->ecam_init
ecam_init要复杂得多,主要实现下面功能
- 调用pci_get_param获取acpi_ecam的path(ecam_config函数中配置的/sys/firmware/acpi/table/MCFG)。
- 调用find_mcfg函数来初始化struct acpi_mcfg,其中acpi_mcfg->address,是ecam的基地址,在我的X86环境上是0xE000_0000。
- 调用pci_malloc为strcut ecam_access申请内存,并且赋值给struct access的backend_data。
- 调用get_mcfg_allocatio获取domain、bus,并调用mmap_reg做test(这一步没有实际租即用,就是test一下第2步获取的mcfg是否合法,不合法就退出了)。
lib/access.c文件
void pci_scan_bus(struct pci_access *a)函数
main->scan_devices->pci_scan_bus
pci_scan_bus回调methods->scan函数(sysfs_scan,ecam_scan)。
static void sysfs_scan_bus(struct pci_access *a)函数/stuct pci_dev *pci_alloc_dev(struct pci_access *a)/pci_link_dev函数
main->scan_devices->pci_scan_bus->sysfs_scan->pci_alloc_dev/pci_link_dev
- sysfs_scan就是read /sys/bus/pci/devices下的设备
- pci_alloc_dev为pci_dev申请内存,并且把pci_access和accees->methods的数据结构赋值给pci_dev(到这里struct pci_methods、struct pci_access和struct pci_dev就建立了关系)。如果init_dev函数不为NULL,则调用init_dev函数(pm_linux_sysfs的init_dev为NULL)。
- 读文件系统获取dom,bus,dev,func并赋值给struct pci_dev。
- 调用pci_link_dev把pci_dev加入到pci_access->devices链表中。
static void ecam_scan_bus(struct pci_access *a)函数/stuct pci_dev *pci_alloc_dev(struct pci_access *a)/pci_link_dev函数
main->scan_devices->pci_scan_bus->ecam_scan->pci_generic_scan_domain->pci_generic_scan_bus->pci_alloc_dev/pci_link_dev
- ecam_scan调用get_mcfg_allocation_count获取ecam的count
- 遍历每个domain调用pci_generic_scan_domain->pci_generic_scan_bus扫描该domian和bus下的所有设备
- pci_generic_scan_domain->pci_generic_scan_bus盗用pci_alloc_dev为struc pci_dev申请内存,并且把pci_access和accees->methods的数据结构赋值给pci_dev(到这里struct pci_methods、struct pci_access和struct pci_dev就建立了关系)。如果init_dev函数不为NULL,则调用init_dev函数(pm_ecam的init_dev为NULL)。
- 调用pci_link_dev把pci_dev加入到pci_access->devices链表中。
lspci.c文件
struct device *scan_device(struct pci_dev *p)函数
main->scan_devices-> scan_device->pci_fill_info->pci_fill_info_v38
遍历pacc->devices链表(pci_link_dev建立的list)调用scan_device,scan_device
- scan_dev首先调用pci_filter_match来看看遍历到的pci_dev是否和输入的filter匹配。
- 如果匹配则调用pci_read_block来获取PCI compatible的前面64byte的配置空间,并保存下来
- 调用pci_fill_info来获取对应的字段。
lib/filter.c文件
int pci_filter_match(struct pci_filter *f, struct pci_dev *d)函数
(1)main->scan_devices-> scan_device-> pci_filter_match->pci_filter_match_v30
->pci_filter_match_v38-> pci_fill_info_v38
(2)main->show->pci_filter_match->pci_filter_match_v30->pci_filter_match_v38-> pci_fill_info_v38
如果pci_filter->device或者pci_filter->vendor大于等于0
(main->pci_filter_parse_slot->pci_filter_parse_slot_v38解析了pci_filter->device和pci_filter->vendor)则调用pci_fill_info_v38(d, PCI_FILL_IDENT)从配置空间读取deviceid和vendorid并赋值给struct pci_dev,如果读取的struct pci_dev的deviceid和vendorid和struct pci_filter一样,则返回1。
lib/access.c文件
int pci_fill_info(struct pci_dev *d, int flags)函数
pci_fill_info_v38调用methods->fill_info函数
sysfs就是调用sysfs_fill_info
ecam就是调用pci_generic_fill_info
(1)main->scan_devices-> scan_device-> pci_filter_match->pci_filter_match_v30
->pci_filter_match_v38-> pci_fill_info_v38->sysfs_fill_info
(2)main->scan_devices-> scan_device->pci_fill_info->pci_fill_info_v38->sysfs_fill_info
- sysfs_fill_info先从sysfs中读取struct pci_dev的信息(注意某些字段只能从sysfs中获取不能从配置空间获取,如果是这种字段,sysfs_fill_info调用clear_fill把对应字段给取消,防止pci_generic_fill_info时再次调用把某些属性弄掉,这也是probe_sequence把sysfs放在最前面的原因)
- 调用pci_generic_fill_info,从配置空间读取struct pci_dev的信息。
int pci_generic_fill_info(struct pci_dev *d, int flags)函数
(1)main->scan_devices-> scan_device-> pci_filter_match->pci_filter_match_v30->
pci_filter_match_v38-> pci_fill_info_v38-> sysfs_fill_info->pci_generic_fill_info
(2)main->scan_devices-> scan_device->pci_fill_info->pci_fill_info_v38->sysfs_fill_info->pci_generic_fill_info
- 调用pci_read_byte/word/long从reg中读取对应的信息,并赋值给struct* pci_dev
- 调用pci_scan_caps读取capid和offset
lib/caps.c文件
void pci_scan_caps(struct pci_dev *d, unsigned int want_fields)函数
(1)sysfs调用关系
main->scan_devices-> scan_device->pci_fill_info->pci_fill_info_v38->sysfs_fill_info->pci_generic_fill_info->pci_scan_caps
(2)ecam调用关系:
main->scan_devices-> scan_device->pci_fill_info->pci_fill_info_v38->pci_generic_fill_info->pci_scan_caps
lspci.c文件
void show_device(struct device *d)函数
main->show->show_device->show_verbose/show_terse/show_hex_dump
main->show->show_device->show_verbose- show_terse->show_slot_name->show_slot_path
打印bus device function
main->show->show_device->show_verbose- show_terse->pci_lookup_name
main->show->show_device->show_verbose- show_terse->pci_lookup_name
打印subsystemid
main->show->show_device->show_verbose
打印pci兼容的配置空间
main->show->show_device->show_verbose->show_htype0->show_bases/show_rom/show_caps
打印兼容的BAR/expansion rom/caps
ECAM和sysfs的差异
由于OS已经把配置空间中EXPROM BAR REG给清零了,所以ECAM这种直接读配置空间的方式是没法知道EXPROM BAR的address,而sysfs是从sysfs里面获取的EXPROM BAR address和normal BAR(BAR0-5)的size,具体可以参考pci_fill_info->sysfs_fill_info(sysfs方式)和pci_fill_info->pci_generic_fill_info(ecam方式)。
怎么增加自己的access method
增加自己的access method有两种方式,
一种简单方式就是把新增的access method放在和lspci.c setpci.c同级目录,采用打补丁的方式把自己的method放进去,执行lspci和setpci时是采用-A method_name的方式指定method。
另外一种是在lib里面增加,这样就必须要修改:
lib平级的Makefile,在执行./ configure前根据make传入的特殊标记增加一个flag,lib里面的configure根据flag增加往config.h生成的宏定义、lib里面的Makefile增加对应的C文件,这种方式比较复杂,并且pcituils/lib更新后很难直接覆盖(这样你新增的method就被打包到libpci.a的静态库)。
(1)lib平级的Makefile,在执行./ configure前根据make传入的特殊标记增加一个flag
(2)lib里面的configure根据flag增加往config.h生成的宏定义,并且保证和case $sys in是二选一的关系,否则case $sys in条件分支会覆盖flag分支的东西
(3)lib里面的Makefile增加对应的C文件