0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux PCI驱动到底都干了些什么?(二)

Linux阅码场 来源:Linuxer 2020-04-30 15:45 次阅读

紧接着前文:Linux PCI驱动到底都干了些什么?(一)

我们在浅谈Linux PCI设备驱动(一)中(以下简称 浅谈(一) )介绍了PCI的配置寄存器组,而Linux PCI初始化就是使用了这些寄存器来进行的。后面我们会举个例子来说明Linux PCI设备驱动的主要工作内容(不是全部内容),这里只做文字性的介绍而不会涉及具体代码的分析,因为要分析代码的话,基本就是对 Linux内核源代码情景分析(下册)第八章的解读,读者若想分析代码,可以参考该书的内容,我们这里就不去深入分析这些代码了。

Linux PCI设备驱动代码必须扫描系统中所有的PCI总线,寻找系统中所有的PCI设备(包括PCI-PCI桥设备)。系统中的每条PCI总线都有个编号number,根PCI总线的编号为0。系统当前存在的所有根总线(因为可能存在不止一个Host/PCI桥,那么就可能存在多条根总线) 都通过其pci_bus结构体中的node成员链接成一个全局的根总线链表,其表头由struct list_head类型的全局变量pci_root_buses来描述,我们在/linux-2.4.18/linux/drivers/pci/pci.c的38行可以看到如下定义:

LIST_HEAD(pci_root_buses);

而根总线下面的所有下级总线则都通过其pci_bus结构体中的node成员链接到其父总线的children链表中。这样,通过这两种PCI总线链表,Linux内核就将所有的pci_bus结构体以一种倒置树的方式组织起来。

另外,每个PCI设备都由一个pci_dev结构体表示,每个pci_dev结构体都同时连入两个队列,一方面通过其成员global_list挂入一个总的pci_dev结构队列(队列头是pci_devices);同时又通过成员bus_list挂入其所在总线的pci_dev结构队列devices(队列头是pci_bus.devices,即该pci设备所在的pci总线的devices队列),并且使指针bus(指pci_dev结构体里的bus成员)指向代表着其所在总线的pci_bus结构。如果具体的设备是PCI-PCI桥,则还要使其指针subordinate指向代表着另一条PCI总线的pci_bus结构。同样我们在/linux-2.4.18/linux/drivers/pci/pci.c的39行可以看到如下定义:

LIST_HEAD(pci_devices);

对于PCI设备链表,我们可以通过图1来理解。

注:该图摘自《Linux设备驱动开发详解》 第21章 PCI设备驱动。

图1 Linux PCI设备链表

而对于我们在浅谈(一)中贴出的图1的PCI系统结构示意图,Linux内核中对应的数据结构如这里的图2所示。

图2 Linux内核PCI数据结构

Linux PCI初始化代码从PCI总线0开始扫描,它通过读取"Vendor ID"和"Device ID"来试图发现每一个插槽上的设备。如果发现了一个PCI-PCI桥,则创建一个pci_bus数据结构并且连入到由pci_root_buses指向的pci_bus和pci_dev数据结构组成的树中。PCI初始化代码通过设备类代码0x060400来判断一个PCI设备是否是PCI-PCI桥。然后,Linux核心开始构造这个桥设备另一端的PCI总线和其上的设备。如果还发现了桥设备,就以同样的步骤来进行构建。这个处理过程称之为深度优先算法。PCI-PCI桥横跨在两条总线之间,寄存器PCI_PRIMARY_BUS和PCI_SECONDARY_BUS的内容就说明了其上下两端的总线号,其中PCI_SECONDARY_BUS就是该PCI-PCI桥所连接和控制的总线,而PCI_SUBORDINATE_BUS则说明自此以下、在以此为根的子树中最大的总线号是什么。

我们可以在/linux-2.4.18/linux/include/linux/pci.h看到如下定义:

112:/*Headertype1(PCI-to-PCIbridges)*/113:#definePCI_PRIMARY_BUS0x18/*Primarybusnumber*/114:#definePCI_SECONDARY_BUS0x19/*Secondarybusnumber*/115: #define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */

由于在枚举阶段做的是深度优先扫描,所以子树中的总线号总是连续递增的。当CPU往I/O寄存器0xCF8中写入一个综合地址以后,从0号总线开始,每个PCI-PCI桥会把综合地址中的总线号与自身的总线号相比,如果相符就用逻辑设备号在本总线上寻访目标设备;否则就进一步把这个总线号与PCI_SUBORDINATE_BUS中的内容相比,如果目标总线号落在当前子树范围中,就把综合地址传递给其下的各个次层PCI-PCI桥,要不然就不予理睬。这样,最终就会找到目标设备。当然,这个过程只是在PCI设备的配置阶段需要这样做,一旦配置完成,CPU就直接通过有关的总线地址访问目标设备了。

PCI-PCI桥要想正确传递对PCI I/O,PCI Memory或PCI Configuration地址空间的读和写请求,必须知道下列信息

(1)Primary Bus Number(主总线号)

该PCI-PCI桥所处的PCI总线称为主总线。

(2)Secondary Bus Number(子总线号)

该PCI-PCI桥所连接的PCI总线称为子总线/次总线号。

(3)Subordinate Bus Number

PCI总线的下属PCI总线的总线编号最大值。有点绕,看后面的分析就明白了。

PCI I/O 和 PCI Memory 窗口

PCI桥的配置寄存器与一般的PCI设备不同。一般PCI设备可以有6个地址区间,外加一个ROM区间,代表着设备上实际存在的存储器或寄存器区间。而PCI桥,则本身并不一定有存储器或寄存器区间,但是却有三个用于地址过滤的区间。每个地址过滤区间决定了一个地址窗口,从CPU一侧发出的地址,如果落在PCI桥的某个窗口内,就可以穿过PCI桥而到达其所连接的总线上。此外,PCI桥的命令寄存器中还有”memory access enable”和”I/O access enable ”的两个控制位,当这两个控制位为0时,这些窗口就全都关上了。在未完成对PCI总线的初始化之前,还没有为PCI设备上的各个区间分配合适的总线地址时,正是因为这两个控制位为0,才不会对CPU一侧造成干扰。例如, 对于浅谈(一)的 PCI系统示意图 ,仅当读和写请求中的PCI I/O或PCI memory地址属于SCSIEthernet设备时,PCI-PCI桥才将这些总线上的请求从PCI总线0传递到PCI总线1。这种过滤机制可以避免地址在系统中没必要的繁衍。为了做到这点,每个PCI-PCI桥必须正确地被设置好它所负责的PCI I/O或PCI memory的起始地址和大小。当一个读或写请求地址落在其负责的范围之内,这个请求将被映射到次级的PCI总线上。系统中的PCI-PCI桥一旦设置完毕,如果Linux中的设备驱动程序存取的PCI I/O和PCI memory地址落在在这些窗口之内,那么这些PCI-PCI桥就是透明的。这是个很重要的特性,使得Linux PCI设备驱动程序开发者的工作容易些。

问题是配置一个PCI-PCI桥的时候,并不知道这个PCI-PCI桥的subordinate bus number。那么就不知道该PCI桥下面是否还有其他的PCI-PCI桥。即使你知道,也不清楚如何对它们赋值。解决方法是利用上述的深度扫描算法来扫描每个总线。每当发现PCI-PCI桥就对它进行赋值。当发现一个PCI-PCI桥时,可以确定它的secondary bus number。然后我们暂时先将其subordinate bus number赋值为0xFF。紧接着,开始扫描该PCI-PCI桥的downstream桥。这个过程看起来有点复杂,下面的例子将给出清晰的解释:

图3 配置PCI系统 第一步

PCI-PCI桥编号--第一步

以图3的拓扑结构为例,扫描时首先发现的桥是Bridge1。Bridge 1的downstream PCI总线号码被赋值1。自然该桥的secondary bus number也是1。其subordinate bus number暂时赋值为0xFF。上述赋值的含义是所有类型1的含有PCI总线1或更高(<255)的号码的PCI配置地址将被Bridge 1传递到PCI总线1上。如果PCI总线号是1,Bridge 1 还负责将配置地址的类型转换成类型0(对于这里说的类型0和类型1,请参考浅谈(一))。否则,就不做转换。上述动作就是开始扫描总线1时Linux PCI初始化代码所完成的对总线0的配置工作。

图4 配置PCI系统 第二步

PCI-PCI桥编号--第二步

由于Linux PCI设备驱动使用深度优先算法进行扫描,所以初始化代码开始扫描总线1。从而Bridge 2被发现。因为在Bridge 2下面发现不再有PCI-PCI桥,所以Bridge 2的subordinate bus number是2,等于它的secondary bus number。图4显示了在这个时刻总线和PCI-PCI桥的赋值情况。

图5 配置PCI系统 第三步

PCI-PCI桥编号--第三步

Linux PCI设备驱动代码从总线2的扫描中回来接着进行扫描总线1,发现Bridge 3。它的primary bus number被赋值为1,secondary bus number为3。因为总线3上还发现了PCI-PCI桥,所以Bridge 3的subordinate bus number暂时赋值0xFF。图5显示了这个时刻系统配置的状态。到目前为止,含有总线号1,2,3的类型1的PCI配置都可以正确地传送到相应的总线上。

图6 配置PCI系统 第四步

PCI-PCI桥编号--第四步

现在Linux开始扫描PCI总线3,Bridge 3的downstream。PCI总线3上有另外一个PCI-PCI桥,Bridge 4。因此Bridge 4的primary bus number的值为3,secondary bus number为4。由于Bridge 4下面没有别的桥设备,所以Bridge 4的subordinate bus number为4。然后回到PCI-PCI Bridge 3。这时就将Bridge 3的subordinate bus number从0xFF改为4,表示总线4是从Bridge 3往下走的最远的PCI-PCI桥。最后,Linux PCI设备驱动代码将4以同样的道理赋值给Bridge 1的subordinate bus number。图6反映了系统最后的状态。

注:浅谈Linux PCI设备驱动(二)暂时的整体结构就是这样了,后续可能还会有些细节上的修补和添加。在此强烈推荐想学Linux PCI设备驱动的朋友结合《Linux内核源代码情景分析下册》第八章和《Linux设备驱动开发详解》第21章 来学习。感谢您关注本文。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    30

    文章

    5020

    浏览量

    117630
  • 总线
    +关注

    关注

    10

    文章

    2695

    浏览量

    87156
  • PCI设备
    +关注

    关注

    0

    文章

    9

    浏览量

    8099

原文标题:PCI设备驱动(二)

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    通过JTAG启动Linux的方法和脚本

    存储器(QSPI Flash,eMMC 等)上的镜像,直接启动到 Linux。但当板子调试时,经常需要通过 JTAG 把 SoC 器件启动到 Linux。这篇文章将分享通过 JTAG
    的头像 发表于 12-22 10:27 577次阅读
    通过JTAG启动<b class='flag-5'>Linux</b>的方法和脚本

    Linux内核驱动与单个PCI设备的绑定和解绑定

    Linux内核2.6.13-rc3以前,驱动和设备之间的绑定和解绑只能通过insmod(modprobe)和rmmod来实现,但是这种实现方法有一个弊端,就是一旦绑定或者解绑定都是针对驱动与其
    的头像 发表于 11-17 17:11 811次阅读
    <b class='flag-5'>Linux</b>内核<b class='flag-5'>驱动</b>与单个<b class='flag-5'>PCI</b>设备的绑定和解绑定

    linux安装网卡驱动教程

    Linux系统中安装网卡驱动是一个比较基础的操作,下面我将为你详细讲解如何安装网卡驱动。 第一步,检查网卡型号和驱动支持情况:首先,你需要确定你的网卡型号,并查看该网卡型号在
    的头像 发表于 11-17 11:11 1521次阅读

    arduino驱动舵机速度能否慢一

    arduino驱动舵机速度太快,能不能慢一,就是占空比调节的指定宽度有个时间设置的函数有吗?比如0度到90度我需要转动3秒完成,但是直接驱动到90度速度太快了半秒就到90度了
    发表于 11-08 06:03

    高边驱动和低边驱动到底是什么 高边和低边驱动等效电路图讲解

    工程师在开发汽车电子项目时,会经常碰到驱动多路负载的情况,比如驱动内饰灯、驱动门窗和天窗的电机,驱动左转向灯和右转向灯的继电器。
    的头像 发表于 10-17 09:18 1.1w次阅读
    高边<b class='flag-5'>驱动</b>和低边<b class='flag-5'>驱动到底</b>是什么 高边和低边<b class='flag-5'>驱动</b>等效电路图讲解

    一文总结linux的platform驱动

    linux设备驱动中,有许多没有特定总线的外设驱动,在实际开发中,又需要使用到总线、驱动和设备模型这三个概念,故而linux提供了plat
    的头像 发表于 10-16 16:45 392次阅读
    一文总结<b class='flag-5'>linux</b>的platform<b class='flag-5'>驱动</b>

    如何添加触摸屏驱动到TouchGFX中?

    使用STM32CubeMX移植TouchGFX 一文中介绍了如何用TouchGFX点亮屏幕,但是此时屏幕还没有触摸的功能。下面将介绍如何添加触摸屏驱动到TouchGFX中
    的头像 发表于 10-09 14:41 1033次阅读

    Linux模块相关命令 Linux驱动模块的编写与挂载

    Linux模块相关命令 Linux驱动模块的编写与挂载
    发表于 10-01 12:20 183次阅读
    <b class='flag-5'>Linux</b>模块相关命令 <b class='flag-5'>Linux</b><b class='flag-5'>驱动</b>模块的编写与挂载

    Linux驱动移植 Linux系统架构优点

    系统移植 linux 驱动移植 移植是说同样的一个 linux 操作系统,我们可以跑到不同的硬件上面,我们把操作系统移植到不同的硬件上面,这个过程叫做移植。设备驱动移植步骤,如下图所示
    的头像 发表于 07-27 17:06 547次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>驱动</b>移植 <b class='flag-5'>Linux</b>系统架构优点

    讨论linux PCI驱动的slides

    PCI:32 bit 总线,33 或 66 MHz。
    发表于 06-19 14:51 405次阅读
    讨论<b class='flag-5'>linux</b> <b class='flag-5'>PCI</b><b class='flag-5'>驱动</b>的slides

    基于Linux使用spidev驱动OLED

    如果不想编写spi设备驱动,那么linux内核提供了一个通用的spidev设备驱动,提供统一的字符设备操作,那么只需要在应用层读写和控制即可。以SPI OLED为例子,使用spidev驱动
    发表于 06-16 10:36 2654次阅读
    基于<b class='flag-5'>Linux</b>使用spidev<b class='flag-5'>驱动</b>OLED

    Linux reset子系统及驱动实例

    上篇讲了Linux clock驱动,今天说说Linux的reset驱动
    发表于 05-31 16:16 604次阅读
    <b class='flag-5'>Linux</b> reset子系统及<b class='flag-5'>驱动</b>实例

    Linux clock子系统及驱动实例

    Linux驱动中,操作时钟只需要简单调用内核提供的通用接口即可,clock驱动通常是由芯片厂商开发的,在Linux启动时clock驱动就已
    发表于 05-31 16:10 458次阅读
    <b class='flag-5'>Linux</b> clock子系统及<b class='flag-5'>驱动</b>实例

    Linux之PWM驱动

    本文主要讲述了Linux的PWM驱动框架、实现方法、驱动添加方法和调试方法。
    发表于 05-25 09:19 399次阅读
    <b class='flag-5'>Linux</b>之PWM<b class='flag-5'>驱动</b>

    Linux实例:多线程和互斥锁到底该如何使用

    最近在写多进程和Linux中的各种锁的文章,总觉得只有文字讲解虽然能够知道多进程和互斥锁是什么,但是还是不知道到底该怎么用。
    发表于 05-18 14:16 256次阅读
    <b class='flag-5'>Linux</b>实例:多线程和互斥锁<b class='flag-5'>到底</b>该如何使用