说明:
- kernel版本:4.14
- arm64处理器
- 使用工具:source insight 3.5, visio
1. 概述
先回顾一下pcie的架构图:
- 本文将讲pcie host的驱动,对应为root complex部分,相当于pci的host bridge部分;
- 本文会选择xilinx的nwl-pcie来进行分析;
- 驱动的编写整体偏简单,往现有的框架上套就可以了,因此不会花太多笔墨,点到为止;
2. 流程分析
- 但凡涉及到驱动的分析,都离不开驱动模型的介绍,驱动模型的实现让具体的驱动开发变得更容易;
- 所以,还是回顾一下上篇文章提到的驱动模型:linux内核建立了一个统一的设备模型,分别采用总线、设备、驱动三者进行抽象,其中设备与驱动都挂在总线上,当有新的设备注册或者新的驱动注册时,总线会去进行匹配操作(match函数),当发现驱动与设备能进行匹配时,就会执行probe函数的操作;
- 深入分析linux pci驱动框架分析(二)中提到过pci设备、pci总线和pci驱动的创建,pci设备和pci驱动挂接在pci总线上,这个理解很直观。针对pcie的控制器来说,同样遵循设备、总线、驱动的匹配模型,不过这里的总线是由虚拟总线platform总线来替代,相应的设备和驱动分别为platform_device和platform_driver;
那么问题来了,platform_device是在什么时候创建的呢?那就不得不提到device tree设备树了。
2.1 device tree
- 设备树用于描述硬件的信息,包含节点各类属性,在dts文件中定义,最终会被编译成dtb文件加载到内存中;
- 内核会在启动过程中去解析dtb文件,解析成device_node描述的device tree;
- 根据device_node节点,创建platform_device结构,并最终注册进系统,这个也就是pcie host设备的创建过程;
我们看看pcie host的设备树内容:
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:旧式的中断控制器;
2.2 probe流程
- 系统会根据dtb文件创建对应的platform_device并进行注册;
- 当驱动与设备通过compatible字段匹配上后,会调用probe函数,也就是nwl_pcie_probe;
看一下nwl_pcie_probe函数:
- 通常probe函数都是进行一些初始化操作和注册操作:
- 初始化包括:数据结构的初始化以及设备的初始化等,设备的初始化则需要获取硬件的信息(比如寄存器基地址,长度,中断号等),这些信息都从dts而来;
- 注册操作主要是包含中断处理函数的注册,以及通常的设备文件注册等;
- 针对pci控制器的驱动,核心的流程是需要分配并初始化一个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驱动实现;
2.3 中断处理
pcie控制器,通过pcie总线连接各种设备,因此它本身充当一个中断控制器,级联到上一层的中断控制器(比如gic),如下图:
- pcie总线支持两种中断的处理方式:
- legacy interrupt:总线提供inta#, intb#, intc#, intd#四根中断信号,pci设备借助这四根信号使用电平触发方式提交中断请求;
- msi(message signaled interrupt) interrupt:基于消息机制的中断,也就是往一个指定地址写入特定消息,从而触发一个中断;
针对两种处理方式,nwl pcie驱动中,实现了两个irq_chip,也就是两种方式的中断控制器:
- irq_domain对应一个中断控制器(irq_chip),irq_domain负责将硬件中断号映射到虚拟中断号上;
- 来一张旧图吧;
再来看一下nwl_pcie_enable_msi函数:
- 在该函数中主要完成的工作就是设置级联的中断处理函数,级联的中断处理函数中最终会去调用具体的设备的中断处理函数;
所以,稍微汇总一下,作为两种不同的中断处理方式,套路都是一样的,都是创建irq_chip中断控制器,为该中断控制器添加irq_domain,具体设备的中断响应流程如下:
- 设备连接在pci总线上,触发中断时,通过pcie控制器充当的中断控制器路由到上一级控制器,最终路由到cpu;
- cpu在处理pcie控制器的中断时,调用它的中断处理函数,也就是上文中提到过的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退出中断级联的处理;
2.4 总结
- pcie控制器驱动,各家的ip实现不一样,驱动的差异可能会很大,单独分析一个驱动毕竟只是个例,应该去掌握背后的通用框架;
- 各类驱动,大体都是硬件初始化配置,资源申请注册,核心是处理与硬件的交互(一般就是中断的处理),如果需要用户来交互的,则还需要注册设备文件,实现一堆file_operation操作函数集;
发表评论