【94】pcituils解析

本文介绍了PCIUtils3.10.0版本选择Renameauxfieldsinstructspci_accessandpci_devtobackend_data的原因,着重讨论了数据结构的变化以及访问方法的关联。文章还详细解释了pciutils的核心数据结构、访问方法的实现和操作,以及如何增加自定义访问方法。
摘要由CSDN通过智能技术生成

代码时间点

    文章没有选取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_methodsstruct 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

  1. 调用pci_malloc为pci_access数据结构申请一片内存。
  2. 调用接口(pci_init_name_list_path、pci_define_param)初始化struct pci_access的成员。
  3. 遍历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

  1. 为pci_acces的不同打印级别的call back函数挂上函数。
  2. 如果method不是PCI_ACCESS_ATUO,则使用当前的method,否则根据probe_sequence找到对应的method(主要看lib/configure根据系统配置往lib/config.h写的宏定义和probe_sequence定义的顺序,找到第一个method不为空,并且detect函数不为NULL的method)(到这里struct pci_methodsstruct pci_access就建立了关系)。
  3. 调用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要复杂得多,主要实现下面功能

  1. 调用pci_get_param获取acpi_ecam的path(ecam_config函数中配置的/sys/firmware/acpi/table/MCFG)。
  2. 调用find_mcfg函数来初始化struct acpi_mcfg,其中acpi_mcfg->address,是ecam的基地址,在我的X86环境上是0xE000_0000
  3. 调用pci_malloc为strcut ecam_access申请内存,并且赋值给struct access的backend_data。
  4. 调用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

  1. sysfs_scan就是read /sys/bus/pci/devices下的设备
  2. pci_alloc_dev为pci_dev申请内存,并且把pci_access和accees->methods的数据结构赋值给pci_dev(到这里struct pci_methodsstruct pci_accessstruct pci_dev就建立了关系)。如果init_dev函数不为NULL,则调用init_dev函数(pm_linux_sysfs的init_dev为NULL)。
  3. 读文件系统获取dom,bus,dev,func并赋值给struct pci_dev。
  4. 调用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

  1. ecam_scan调用get_mcfg_allocation_count获取ecam的count
  2. 遍历每个domain调用pci_generic_scan_domain->pci_generic_scan_bus扫描该domian和bus下的所有设备
  3. pci_generic_scan_domain->pci_generic_scan_bus盗用pci_alloc_dev为struc pci_dev申请内存,并且把pci_access和accees->methods的数据结构赋值给pci_dev(到这里struct pci_methodsstruct pci_accessstruct pci_dev就建立了关系)。如果init_dev函数不为NULL,则调用init_dev函数(pm_ecam的init_dev为NULL)。
  4. 调用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

  1. scan_dev首先调用pci_filter_match来看看遍历到的pci_dev是否和输入的filter匹配。
  2. 如果匹配则调用pci_read_block来获取PCI compatible的前面64byte的配置空间,并保存下来
  3. 调用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

  1. sysfs_fill_info先从sysfs中读取struct pci_dev的信息(注意某些字段只能从sysfs中获取不能从配置空间获取,如果是这种字段,sysfs_fill_info调用clear_fill把对应字段给取消,防止pci_generic_fill_info时再次调用把某些属性弄掉,这也是probe_sequence把sysfs放在最前面的原因
  2. 调用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

  1. 调用pci_read_byte/word/long从reg中读取对应的信息,并赋值给struct* pci_dev
  2. 调用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文件

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

linjiasen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值