Read the fucking source code!
--By 鲁迅A picture is worth a thousand words.
--By 高尔基说明:node
先回顾一下PCIe的架构图:数据结构
Root Complex
部分,至关于PCI的Host Bridge
部分;nwl-pcie
来进行分析;match
函数),当发现驱动与设备能进行匹配时,就会执行probe函数的操做;《Linux PCI驱动框架分析(二)》
中提到过PCI设备、PCI总线和PCI驱动的建立,PCI设备和PCI驱动挂接在PCI总线上,这个理解很直观。针对PCIe的控制器来讲,一样遵循设备、总线、驱动的匹配模型,不过这里的总线是由虚拟总线platform
总线来替代,相应的设备和驱动分别为platform_device
和platform_driver
;那么问题来了,platform_device
是在何时建立的呢?那就不得不提到Device Tree
设备树了。架构
device_node
描述的Device Tree
;device_node
节点,建立platform_device
结构,并最终注册进系统,这个也就是PCIe Host设备的建立过程;咱们看看PCIe Host的设备树内容:app
pcie: pcie@fd0e0000 { compatible = "xlnx,nwl-pcie-2.11"; status = "disabled"; #address-cells = <3>; #size-cells = <2>; #interrupt-cells = <1>; msi-controller; device_type = "pci"; interrupt-parent = <&gic>; interrupts = <0 118 4>, <0 117 4>, <0 116 4>, <0 115 4>, /* MSI_1 [63...32] */ <0 114 4>; /* MSI_0 [31...0] */ interrupt-names = "misc", "dummy", "intx", "msi1", "msi0"; msi-parent = <&pcie>; reg = <0x0 0xfd0e0000 0x0 0x1000>, <0x0 0xfd480000 0x0 0x1000>, <0x80 0x00000000 0x0 0x1000000>; reg-names = "breg", "pcireg", "cfg"; ranges = <0x02000000 0x00000000 0xe0000000 0x00000000 0xe0000000 0x00000000 0x10000000 /* non-prefetchable memory */ 0x43000000 0x00000006 0x00000000 0x00000006 0x00000000 0x00000002 0x00000000>;/* prefetchable memory */ bus-range = <0x00 0xff>; interrupt-map-mask = <0x0 0x0 0x0 0x7>; interrupt-map = <0x0 0x0 0x0 0x1 &pcie_intc 0x1>, <0x0 0x0 0x0 0x2 &pcie_intc 0x2>, <0x0 0x0 0x0 0x3 &pcie_intc 0x3>, <0x0 0x0 0x0 0x4 &pcie_intc 0x4>; pcie_intc: legacy-interrupt-controller { interrupt-controller; #address-cells = <0>; #interrupt-cells = <1>; }; };
关键字段描述以下:框架
compatible
:用于匹配PCIe Host驱动;msi-controller
:表示是一个MSI(Message Signaled Interrupt
)控制器节点,这里须要注意的是,有的SoC中断控制器使用的是GICv2版本,而GICv2并不支持MSI,因此会致使该功能的缺失;device-type
:必须是"pci"
;interrupts
:包含NWL PCIe控制器的中断号;interrupts-name
:msi1, msi0
用于MSI中断,intx
用于旧式中断,与interrupts
中的中断号对应;reg
:包含用于访问PCIe控制器操做的寄存器物理地址和大小;reg-name
:分别表示Bridge registers
,PCIe Controller registers
, Configuration space region
,与reg
中的值对应;ranges
:PCIe地址空间转换到CPU的地址空间中的范围;bus-range
:PCIe总线的起始范围;interrupt-map-mask
和interrupt-map
:标准PCI属性,用于定义PCI接口到中断号的映射;legacy-interrupt-controller
:旧式的中断控制器;compatible
字段匹配上后,会调用probe函数,也就是nwl_pcie_probe
;看一下nwl_pcie_probe
函数:dom
pci_host_bridge
结构,最终经过这个bridge
去枚举PCI总线上的全部设备;devm_pci_alloc_host_bridge
:分配并初始化一个基础的pci_hsot_bridge
结构;nwl_pcie_parse_dt
:获取DTS中的寄存器信息及中断信息,并经过irq_set_chained_handler_and_data
设置intx
中断号对应的中断处理函数,该处理函数用于中断的级联;nwl_pcie_bridge_init
:硬件的Controller一堆设置,这部分须要去查阅Spec,了解硬件工做的细节。此外,经过devm_request_irq
注册misc
中断号对应的中断处理函数,该处理函数用于控制器自身状态的处理;pci_parse_request_of_pci_ranges
:用于解析PCI总线的总线范围和总线上的地址范围,也就是CPU能看到的地址区域;nwl_pcie_init_irq_domain
和mwl_pcie_enable_msi
与中断级联相关,下个小节介绍;pci_scan_root_bus_bridge
:对总线上的设备进行扫描枚举,这个流程在Linux PCI驱动框架分析(二)
中分析过。brdige
结构体中的pci_ops
字段,用于指向PCI的读写操做函数集,当具体扫描到设备要读写配置空间时,调用的就是这个函数,由具体的Controller驱动实现;PCIe控制器,经过PCIe总线链接各类设备,所以它自己充当一个中断控制器,级联到上一层的中断控制器(好比GIC),以下图:函数
INTA#, INTB#, INTC#, INTD#
四根中断信号,PCI设备借助这四根信号使用电平触发方式提交中断请求;Message Signaled Interrupt
) Interrupt:基于消息机制的中断,也就是往一个指定地址写入特定消息,从而触发一个中断;针对两种处理方式,NWL PCIe
驱动中,实现了两个irq_chip
,也就是两种方式的中断控制器:工具
irq_domain
对应一个中断控制器(irq_chip
),irq_domain
负责将硬件中断号映射到虚拟中断号上;再来看一下nwl_pcie_enable_msi
函数:fetch
因此,稍微汇总一下,做为两种不一样的中断处理方式,套路都是同样的,都是建立irq_chip
中断控制器,为该中断控制器添加irq_domain
,具体设备的中断响应流程以下:spa
nwl_pcie_leg_handler
,nwl_pcie_msi_handler_high
,和nwl_pcie_leg_handler_low
;chained_irq_enter
进入中断级联处理;irq_find_mapping
找到具体的PCIe设备的中断号;generic_handle_irq
触发具体的PCIe设备的中断处理函数执行;chained_irq_exit
退出中断级联的处理;file_operation
操做函数集;下篇开始,继续回归到虚拟化,期待一下吧。
Documentation/devicetree/bindings/pci/xlinx-nwl-pcie.txt
欢迎关注我的公众号,不按期分享技术文章: