天天看点

【原创】Linux PCI驱动框架分析(二)

<code>Read the fucking source code!</code> --By 鲁迅

<code>A picture is worth a thousand words.</code> --By 高尔基

说明:

Kernel版本:4.14

ARM64处理器

使用工具:Source Insight 3.5, Visio

本文将分析Linux PCI子系统的框架,主要围绕Linux PCI子系统的初始化以及枚举过程分析;

如果对具体的硬件缺乏了解,建议先阅读上篇文章<code>《Linux PCI驱动框架分析(一)》</code>;

话不多说,直接开始。

【原创】Linux PCI驱动框架分析(二)

PCI体系结构的拓扑关系如图所示,而图中的不同数据结构就是用于来描述对应的模块;

Host Bridge连接CPU和PCI系统,由<code>struct pci_host_bridge</code>描述;

<code>struct pci_dev</code>描述PCI设备,以及PCI-to-PCI桥设备;

<code>struct pci_bus</code>用于描述PCI总线,<code>struct pci_slot</code>用于描述总线上的物理插槽;

来一张更详细的结构体组织图:

【原创】Linux PCI驱动框架分析(二)

总体来看,数据结构对硬件模块进行了抽象,数据结构之间也能很便捷的构建一个类似PCI子系统物理拓扑的关系图;

顶层的结构为<code>pci_host_bridge</code>,这个结构一般由Host驱动负责来初始化创建;

<code>pci_host_bridge</code>指向root bus,也就是编号为0的总线,在该总线下,可以挂接各种外设或物理slot,也可以通过PCI桥去扩展总线;

Linux PCI驱动框架,基于Linux设备驱动模型,因此有必要先简要介绍一下,实际上Linux设备驱动模型也是一个大的topic,先挖个坑,有空再来填。来张图吧:

【原创】Linux PCI驱动框架分析(二)

简单来说,Linux内核建立了一个统一的设备模型,分别采用总线、设备、驱动三者进行抽象,其中设备与驱动都挂在总线上,当有新的设备注册或者新的驱动注册时,总线会去进行匹配操作(<code>match</code>函数),当发现驱动与设备能进行匹配时,就会执行probe函数的操作;

从数据结构中可以看出,<code>bus_type</code>会维护两个链表,分别用于挂接向其注册的设备和驱动,而<code>match</code>函数就负责匹配检测;

各类驱动框架也都是基于图中的机制来实现,在这之上进行封装,比如I2C总线框架等;

设备驱动模型中,包含了很多<code>kset/kobject</code>等内容,建议去看看之前的文章<code>《linux设备模型之kset/kobj/ktype分析》</code>

好了,点到为止,感觉要跑题了,强行拉回来。

既然说到了设备驱动模型,那么首先我们要做的事情,就是先在内核里边创建一个PCI总线,用于挂接PCI设备和PCI驱动,我们的实现来到了<code>pci_driver_init()</code>函数:

【原创】Linux PCI驱动框架分析(二)

内核在PCI框架初始化时会调用<code>pci_driver_init()</code>来创建一个PCI总线结构(全局变量<code>pci_bus_type</code>),这里描述的PCI总线结构,是指驱动匹配模型中的概念,PCI的设备和驱动都会挂在该PCI总线上;

从<code>pci_bus_type</code>的函数操作接口也能看出来,<code>pci_bus_match</code>用来检查设备与驱动是否匹配,一旦匹配了就会调用<code>pci_device_probe</code>函数,下边针对这两个函数稍加介绍;

【原创】Linux PCI驱动框架分析(二)

设备或者驱动注册后,触发<code>pci_bus_match</code>函数的调用,实际会去比对<code>vendor</code>和<code>device</code>等信息,这个都是厂家固化的,在驱动中设置成<code>PCI_ANY_ID</code>就能支持所有设备;

一旦匹配成功后,就会去触发<code>pci_device_probe</code>的执行;

【原创】Linux PCI驱动框架分析(二)

实际的过程也是比较简单,无非就是进行匹配,一旦匹配上了,直接调用驱动程序的probe函数,写过驱动的同学应该就比较清楚后边的流程了;

我们还是顺着设备驱动匹配的思路继续开展;

3.2节描述的是总线的创建,那么本节中的枚举,显然就是设备的创建了;

所谓设备的创建,就是在Linux内核中维护一些数据结构来对硬件设备进行描述,而硬件的描述又跟上文中的数据结构能对应上;

枚举的入口函数:<code>pci_host_probe</code>

【原创】Linux PCI驱动框架分析(二)

设备的扫描从<code>pci_scan_root_bus_bridge</code>开始,首先需要先向系统注册一个<code>host bridge</code>,在注册的过程中需要创建一个<code>root bus</code>,也就是<code>bus 0</code>,在<code>pci_register_host_bridge</code>函数中,主要是一系列的初始化和注册工作,此外还为总线分配资源,包括地址空间等;

<code>pci_scan_child_bus</code>开始,从<code>bus 0</code>向下扫描并添加设备,这个过程由<code>pci_scan_child_bus_extend</code>来完成;

从<code>pci_scan_child_bus_extend</code>的流程可以看出,主要有两大块:

PCI设备扫描,从循环也能看出来,每条总线支持32个设备,每个设备支持8个功能,扫描完设备后将设备注册进系统,pci_scan_device的过程中会去读取PCI设备的配置空间,获取到BAR的相关信息,细节不表了;

PCI桥设备扫描,PCI桥是用于连接上一级PCI总线和下一级PCI总线的,当发现有下一级总线时,创建子结构,并再次调用<code>pci_scan_child_bus_extend</code>的函数来扫描下一级的总线,从这个过程看,就是一个递归过程。

从设备的扫描过程看,这是一个典型的DFS(<code>Depth First Search</code>)过程,熟悉数据结构与算法的同学应该清楚,这就类似典型的走迷宫的过程;

如果你对上述的流程还不清楚,再来一张图:

【原创】Linux PCI驱动框架分析(二)

图中的数字代表的就是扫描的过程,当遍历到PCI桥设备的时候,会一直穷究到底,然后再返回来;

当枚举过程结束后,系统中就已经维护了PCI设备的各类信息了,在设备驱动匹配模型中,总线和设备都已经具备了,剩下的就是写个驱动了;

暂且写这么多,细节方面不再赘述了,把握大体的框架即可,无法扼住PCI的咽喉,那就扼住它的骨架吧。

欢迎关注个人公众号,不定期分享Linux内核相关技术文章:

【原创】Linux PCI驱动框架分析(二)

作者:LoyenWang

出处:https://www.cnblogs.com/LoyenWang/

公众号:<b>LoyenWang</b>

版权:本文版权归作者和博客园共有

转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

继续阅读