Linux——驱动开发——PCIe驱动代码分析
linux启动过程中进入驱动加载阶段时首先通过函数platform_drc_probe()进行设备树信息提取
一、架构分析
PCI的驱动和设备匹配方式较为特殊,一般设备可以通过设备名来匹配,但是PCI比较特殊,它是通过设备的 vendor 、subvendor 、device 、subdevice 来匹配
1.1 pci数据结构
在内核中与PCI相关的结构体大概有pci_driver 、pci_bus_type 、pci_dev 、pci_bus、pci_slot、pci_host_bridge等
- pci_host_bridge
Host Bridge连接CPU和PCI系统 - pci_bus
pci_bus包括节点信息、父总线pci_bus、设备链表、设备操作函数等信息 - pci_bus_type
与pci_bus不同,该结构体是设备总线驱动模型里的总线 - pci_driver
pci设备驱动 - pci_dev
描述PCI设备,比如PCI-to-PCI桥设备等 - 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()
- 调用pcie_find_capability,进而调用__pci_bus_find_cap_start读取0x06寄存器确认是否为0x10,根据hdr返回PCI_CAPABILITY_LIST;
- 调用pci_find_next_cap_ttl查询下一个capability pointer的地址
2.5.1.2.3 pci_dev_assign_slot()
2.5.1.2.4 pci_class()
- 读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()
- 如果class code>>8为06,即为桥设备,则调用pci_cfg_space_size_ext,读0x100寄存器判断是否是pcie设备,若是,则返回4KB的配置空间大小参数
2.5.1.2.7 set_pcie_thunderbolt()
- 查询该设备的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设备,则进行如下处理:
- pci_read_irq()
- pci_read_bases()
- 如果是BRIDGE设备,则进行如下处理:
- pci_read_irq(),读中断相关寄存器
- pci_read_bases(),配置bar寄存器的值
- pci_read_bridge_windows(),配置io/memory访问属性
- set_pcie_hotplug_bridge()
- 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
- CONFIG_PCI_QUIRKS
参考链接:pci quirk介绍
更多推荐
所有评论(0)