一、架构分析

PCI的驱动和设备匹配方式较为特殊,一般设备可以通过设备名来匹配,但是PCI比较特殊,它是通过设备的 vendor 、subvendor 、device 、subdevice 来匹配

1.1 pci数据结构

在内核中与PCI相关的结构体大概有pci_driver 、pci_bus_type 、pci_dev 、pci_bus、pci_slot、pci_host_bridge等

  1. pci_host_bridge
    Host Bridge连接CPU和PCI系统
  2. pci_bus
    pci_bus包括节点信息、父总线pci_bus、设备链表、设备操作函数等信息
  3. pci_bus_type
    与pci_bus不同,该结构体是设备总线驱动模型里的总线
  4. pci_driver
    pci设备驱动
  5. pci_dev
    描述PCI设备,比如PCI-to-PCI桥设备等
  6. pci_slot
    用于描述总线上的物理插槽

详细参考链接:LoyenWang博客
PCI体系结构的拓扑关系如下图所示,图中的不同数据结构用于来描述对应的模块:
在这里插入图片描述

为PCIe的mem访问空间配置 region outbound寄存器组在这里插入图片描述

  • 顶层的结构为pci_host_bridge,这个结构一般由Host驱动负责来初始化创建;
  • pci_host_bridge指向root bus,也就是编号为0的总线,在该总线下,可以挂接各种外设或物理slot,也可以通过PCI桥去扩展总线;

二、代码分析

linux启动过程中进入驱动加载阶段时首先通过函数platform_drv_probe()进行设备树信息提取,该函数会进一步调用probe(dev) —> cdns_pcie_host_probe(),即指向dev的驱动文件中的*_probe函数,例如lcadence开发的pcie控制器的驱动就指向cdns_pcie_host_probe(),从而执行该函数体里的内容。

该函数的主要任务是分配并初始化一个pci_host_bridge结构,最终通过这个bridge去枚举PCI总线上的所有设备;

2.1 devm_pci_alloc_host_bridge()

分配并初始化一个基础的pci_hsot_bridge结构;

2.2 解析设备树信息

调用pci_host_bridge_priv()
将设备树中寄存器信息及中断信息存储到rc结构体中

2.3 cdns_pcie_init_phy()

初始化pcie phy模块

2.4 cdns_pcie_host_init()

初始化pcie host controller模块

2.4.1 pci_parse_request_of_pci_ranges()

解析设备树中的总线范围和地址空间范围,包括reg、reg-names、range等信息,并且将硬件信息注册到操作系统中,

2.4.2 cdns_pcie_host_init_root_port()

初始化RC设备,配置rc bar configuration寄存器,配置成pcie桥模式

2.4.3 cdns_pcie_host_init_address_translation()

为PCIe的配置空间配置region0 outbound寄存器组
为PCIe的mem访问空间配置 region1 outbound寄存器组
为PCIe的io访问空间配置 region2 outbound寄存器组
配置inbound寄存器组

2.4 注册PCIe信息

将设备信息、地址信息、pci操作函数注册到bridge结构体中

2.5 pci_host_probe()

2.5.1 pci_scan_root_bus_bridge()

Pcie总线枚举。

  • 调用pci_register_host_bridge()注册host bridge数据结构

  • 调用pci_scan_child_bus()由bridge开始对PCI总线扫描并添加设备。该函数调用pci_scan_child_bus_extend(),即扫描host桥下面设备的主体函数,具体执行如下

2.5.1.1 pci_scan_slot()

循环扫描一个PCI slot下的所有设备,进而进行响应配置

2.5.1.2 pci_scan_single_device() —— “probe.c”

调用pci_get_slot(),即调用get_device(),增加device对象的计数器值;然后调用pci_scan_device(),扫描PCIe总线树;继而调用pci_device_add(),将扫描出来的设备添加到BUS数据结构中。
其中最重要的是==pci_scan_device()==函数,具体功能包括调用pci_bus_read_dev_vendor_id()
查询vendor id和device id(注:函数调用了pci_bus_read_config_dword()函数,在access.c中采用宏定义来实现,详细参考链接:pci_bus_read_config_dword);调用pci_alloc_dev(bus)给创建pci_dev并分配内存;调用pci_set_of_node(dev)建立pcie设备树节点;调用pci_setup_device(dev)配置和初始化设备,具体实现如下

2.5.1.2.1 pci_hdr_type()

读取配置空间的BIST, Header Type, Latency Timer and Cache Line Size Registers寄存器中header type字段(hdr),判断该设备类型。若是RC设备则该该值为1,若是EP设备该值为0

2.5.1.2.2 set_pcie_port_type()
  1. 调用pcie_find_capability,进而调用__pci_bus_find_cap_start读取0x06寄存器确认是否为0x10,根据hdr返回PCI_CAPABILITY_LIST;
  2. 调用pci_find_next_cap_ttl查询下一个capability pointer的地址
2.5.1.2.3 pci_dev_assign_slot()
2.5.1.2.4 pci_class()
  1. 读0x8寄存器,读取class code
2.5.1.2.5 early_dump_cpi_device()

若设置pci_dump_pci_device,在boot阶段将会打印devic的配置空间

2.5.1.2.6 pci_cfg_space_size()
  1. 如果class code>>8为06,即为桥设备,则调用pci_cfg_space_size_ext,读0x100寄存器判断是否是pcie设备,若是,则返回4KB的配置空间大小参数
2.5.1.2.7 set_pcie_thunderbolt()
  1. 查询该设备的capability是否是Intel thunderbolt控制器
2.5.1.2.8 pci_fixup_device()
2.5.1.2.9 配置command寄存器,测试是否INTX disable
2.5.1.2.10 根据header type值进行相应设备的处理
  • 如果是EP设备,则进行如下处理:
  1. pci_read_irq()
  2. pci_read_bases()
  • 如果是BRIDGE设备,则进行如下处理:
  1. pci_read_irq(),读中断相关寄存器
  2. pci_read_bases(),配置bar寄存器的值
  3. pci_read_bridge_windows(),配置io/memory访问属性
  4. set_pcie_hotplug_bridge()
  5. pci_find_capability()
2.5.1.3 pci_iov_bus_range()
2.5.1.4 pcibios_fixup_bus()
2.5.1.5 pci_scan_bridge_extend()

2.5.2 将PCI资源载入到iomem和ioport resource中

如果定义了CONFIG PCI且如果PCI_PROBE_ONLY在pci_flag中已设置,则运行以下函数
pci_bus_claim_resources()
若未定义CONFIG PCI且pci_flag中未设置PCI_PROBE_ONLY位,则运行以下函数
pci_bus_size_bridges()
pci_bus_assign_resources()

2.5.3 pci_bus_add_devices()

全部设备扫描完毕,注册设备

三、与PCIe相关的option

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐