本文讲述的基于intel 总线架构的硬件架构为例来说明linux是如何扫描总线上的PCI设备。

CPU通过前端总线FS连接到北桥芯片North Bridge Chip(又称host Bridge), 北桥芯片本身也是PCI总线0上的PCI设备。北桥芯片通过DMI总线连接南桥芯片Sourth Bridge Chip。北桥芯片内置内存控制器,访问内存需要通过北桥芯片; 南桥芯片负责IO操作,以及下挂子pci总线,usb总线等。

Linux kernel扫描pci总线从pci总线0开始,即pci0。

(下面的描述基于linux4.7.1版本, 使用CONFIG_PCI_DIRECT,即由kernel自己去扫描pci总线以及设备,不使用BIOS提供的)

arch/x86/pci/legacy.c

pci_legacy_init() -> pcibios_scan_root(0) /* 参数0表示pci bus 0 */

arch/x86/pci/common.c

pcibios_scan_root() -> pci_scan_root_bus()  /* 扫描root bus, 即PCI BUS 0*/

arch/x86/pci/probe.c

pci_scan_root_bus() -> pci_scan_root_bus_msi() -> pci_scan_child_bus() -> pci_scan_slot()

pci_scan_slot() -> pci_scan_single_device()

pci_scan_single_device() -> pci_scan_device() -> pci_bus_read_dev_vendor_id()

pci_bus_read_dev_vendor_id()会去读取head_type,提取当前pci插槽设备是否是支持多funtion的,即当前插槽为多个逻辑设备domain:bus:device:function, 逻辑设备最多8个。如果是多个逻辑设备,则for循环从逻辑设备1到7开始,继续scan。

pci_bus_read_dev_vendor_id()读取VENDOR_ID,如果ID有效,则该PCI设备存在且有效。

pci_bus_read_config_dword() -> bus->ops->read() /* 这个就是pci_read(), 在common.c */

pci_read() -> raw_pci_read() -> raw_pci_ops->read()

这个raw_pci_ops->read()就是pci_conf1_read(), arch/x86/pci/direct.c

pci_conf1_read()向0Xcf8端口写入要访问的PCI地址(由总线号,逻辑设备号,以及寄存器VENDOR_ID组成)

然后由0xCFC端口读回数据。

pci_scan_single_device()在pci_scan_device()扫描到设备后,调用pci_device_add()将设备添加到bus->p->klist_devices

pci_scan_single_device() -> pci_device_add() -> device_add() -> bus_add_device()

注: 这里的dev->bus是指该设备的bus_type,即pci_dev->dev->bus, 这个pci_dev->dev->bus指向下面的pci_bus_type。

当扫描完PCI bus0总线,得到该总线上的所有PCI设备,包括桥设备,比如PCI-ISA桥,以及PCI-PCI桥等。

下一步,就是扫描pci子总线上的设备。

pci_scan_root_bus() -> pci_scan_root_bus_msi() -> pci_scan_child_bus()调用pci_scan_slot()扫描pci root bus总线上的所有设备后,如果有pci总线级联,则开始扫描子PCI总线。

pci_scan_bridge()从桥设备上读取PCI_PRIMARY_BUS寄存器获得PCI子总线的总线号secondary.

然后通过pci_add_new_bus() -> pci_alloc_child_bus()分配子pci_bus结构内存,最后调用pci_scan_child_bus()扫描pci子总线。下面的操作就和操作pci root bus没什么区别了,直到所有的pci设备都扫描到。

当kernel完成扫描后,它构建了pci bus-device tree(如下图)。这样,kernel就了解了当前系统中有多少pci总线,每个pci总线上有多少pci设备。下一步,就是给对应的设备加载正确的驱动程序了。

PCI驱动加载

pcibios_scan_root() -> pci_bus_add_devices()

pci_bus_add_devices()循环遍历当前总线上的设备以及当前总线的子总线的设备。

pcibios_scan_root() -> pci_bus_add_devices() -> pci_bus_add_device() -> device_attach() -> __device_attach()

系统第一次时,dev->driver没有加载的,即dev->driver为NULL.

__device_attach() -> bus_for_each_drv()

Bus_for_each_drv()从driver list(bus->p->klist_drivers)中寻找匹配的driver.

Driver的注册是在驱动程序中通过调用pci_register_driver() -> __pci_register_driver() -> driver_register() -> bus_add_driver()来完成的。

这里的bus之前介绍过,这里再列一下

这样就进入到了__device_attach_driver()。

bus_for_each_drv() -> __device_attach_driver()

__device_attach_driver()调用__device_attach_driver() -> drv->bus->match()间接调用驱动的match函数。如果驱动的match函数不存在,则这里默认为match(使用通用驱动程序?), 即match返回1,然后进入driver_probe_device().

driver_probe_device() -> really_probe() -> dev->bus->probe(), 即调用pci_bus_type.probe, 这里为pci_device_probe().

如果你的驱动设置了pci_driver.driver.probe(),那么你的驱动程序需要更新了。

pci_device_probe() -> __pci_device_probe() -> pci_match_device() -> pci_match_one_device()

-> pci_call_probe() -> local_pci_probe() -> pci_drv->probe()

__pci_device_probe()首先调用pci_match_device()去匹配驱动的vendor_id, device_id, sub_vendor_id, sub_device_id 是否和从pci设备上读取到的这些信息一致。

如果一致(相匹配),则通过pci_call_probe(),最终调用驱动的probe().以8139too.c为例(网卡驱动),就是调用了它的probe(),即rtl8139_init_one().

我们来看看rtl8139_init_one()做了些什么。

rtl8139_init_one():

  • rtl8139_init_board()

这个主要做一些板级初始化,比如通过pci_iomap()将PCI外设配置空间的6个BAR地址映射到cpu线性空间(IO_MEM),即将这6个BAR提供的物理地址更新到页表中。

  • 设置收包处理函数

这里通过netif_napi_add()将rtl8139_poll()设置为软中断上下文中调用一次。

rtl8139_poll()调用rtl8139_rx()从设备的rx_ring中(DMA将数据从pci设备传送到这个rx_ring的位置)取包,然后调用netif_receive_skb()进入协议栈处理。

  • register_netdev()

注册网络设备, 即给协议栈提供了TX发包接口rtl8139_start_xmit()

这样,这个pci设备就可被使用了。

总结下,kernel首先从pci总线0开始扫描总线0上的设备,如果被扫出的设备是桥设备,则扫描次级pci总线上的设备,如此来构建pci总线设备树。然后为每个pci设备查找合适的驱动程序,并调用驱动的probe来初始化板子,注册网络设备net-device(网卡).

如果pci设备是在上述程序完成后才插入pci插槽的,则设备是在kernel下一次引导之后(reboot)才会生效。

PCI设备配置空间

PCi配置空间前64个字节格式如下:

需要注意的有一下几项:

ClassCode 用于将设备分到具体的功能组,该字段分为两部分,前8个bit表示基类即大类别,后8个比特表示基类的一个子类。比如PCI_BASE_CLASS_STORAGE表示大类大容量存储器,而PCI_CLASS_STORAGE_IDE表明这个IDE控制器。

HeaderType表明头部类型。一般区分为0型头部(PCI设备)1型头部(PCI桥),注意不同头部的配置空间格式有差异。这里我们描述0型头部,即普通PCi设备的配置空间。

PCI HeaderType为一个字节的大小,最高位为0表示单功能,最高位为1表示多功能(即前面描述的逻辑设备),单功能情况下一个PCI设备就是一个逻辑设备。低7位表示头部类型。

前16个字节都是一些基本的信息就不在多说,重点看下接下来的6个BAR空间。每个BAR记录了该设备映射的一段地址空间。为了区分IO空间和IO内存,这里我们分开描述:

当BAR最后一位为0表示这是映射的IO内存,为1是表示这是IO 端口,当是IO内存的时候bit 1-2表示内存的类型,bit 2为1表示采用64位地址,为0表示采用32位地址。bit1为1表示区间大小超过1M,为0表示不超过1M.bit3表示是否支持可预取。

而相对于IO内存,当最后一位为1时表示映射的IO地址空间。IO地址空间一般不支持预取,所以这里是29位的地址。

一般情况下,6个BAR是足够使用的,大部分情况都是3-4个BAR。而这些BAR映射的空间是连续的,即这些BAR共同描述设备的地址空间范围

除了6个基本的BAR空间们还有一个额外的配置ROM区间,区间最低位表示是否使用ROM区间,高21位表示地址。中间的是保留项。其余原理和上面类似。

接下来需要注意的就是中断,因为大部分外设和系统交互就是通过中断的方式。

由配置空间中的IRQ Pin决定设备是否支持中断,1表示支持,0表示不支持。假如支持中断,IRQ Line表记录下中断号。

PCI桥配置空间

PCI桥同样是连接在PCI总线接口卡上的一个设备,只不过是一个桥设备,连接一条PCI总线。既然同属于设备,那么它同样也就有设备的配置空间,只是它的配置空间和普通设备的有些差异。PCI桥的配置空间在系统软件遍历PCI总线树的时候配置,并不需要专门的PCI驱动,故称为透明桥。PCI桥连接两条总线,和Host 桥近的称为上游总线(Primary Bus ),远的一条称为下游总线(Secondry Bus)。PCI桥的配置空间在前16个字节的格式和普通PCI设备并无区别,另外,桥还保留了普通设备的前两个BAR空间。所以从配置空间的0x18开始有了桥设备自身的配置格式。如前所述,桥设备记录了上游总线和下游总线,以及桥下最大的总线号。这里还有一个比较重要的概念就是窗口。在PCI桥的配置空间有三个窗口:IO地址区间窗口、存储器区间窗口、可预取存储器地址窗口。实际上每个窗口都是一段地址区间,就像一个门,规定了桥下设备映射的区间。从北桥出来的地址,如果在该区间内,就可以穿过该桥到达次级总线,这样依次寻找设备。反过来,从南桥出来的地址及由设备发出的,只有地址不在该区间范围内才可以穿过该桥。因为同一条总线上的设备交互不需要外部空间。就像是内网传输和公网传输一样的道理。

内核中关于桥配置空间的定义如下:

下面说说PCI设备的地址空间:

前面也简单介绍了下PCI设备的地址空间支持PIO和MMIO,即IO端口和IO内存。下面详细分析下这两种方式:

PIO

IO端口的编址是独立于系统的地址空间,其实就是一段地址区域,所有外设的地址都映射到这段区域中。就像是一个进程内部的各个变量,公用进程地址空间一样。不同外设的IO端口不同。访问IO端口需要特殊的IO指令,OUT/IN,OUT用于write操作,in用于read操作。在此基础上,操作系统实现了读写不同大小端口的函数。为什么说是不同大小呢??因为前面也说到,IO端口实际上是一段连续的区域,每个端口理论上是字节为单位即8bit,那么要想读写16位的端口只能把相邻的端口进行合并,32位的端口也是如此。

例如下面的汇编指令:

OUT 21h,al

0x21是8259A中断控制器的中断屏蔽寄存器,该指令将Intel X86处理器的al寄存器中的值写到中断寄存器的控制寄存器中,从而达到屏蔽某些中断信号的目的。类似的,在下面的汇编指令中:

IN al,20h

0x20是8259A中断控制器的正在服务寄存器,该指令将此寄存器中的状态值传递到处理器的al寄存器中。

无论是windows还是Linux都会上述指令做了封装以满足读写不同长度端口的需要。不过这种方式缺点也比较明显,一般情况CPU分配给IO端口的空间都比较小,在当前外设存储日益增大的情况下很难满足需要。另一方面,

MMIO

IO内存是直接把寄存器的地址空间直接映射到系统地址空间,系统地址空间往往会保留一段内存区用于这种MMIO的映射(当然肯定是位于系统内存区),这样系统可以直接使用普通的访存指令直接访问设备的寄存器,随着计算机内存容量的日益增大,这种方式更是显出独特的优势,在性能至上的理念下,使用MMIO可以最大限度满足日益增长的系统和外设存储的需要。所以当前其实大多数外设都是采用MMIO的方式。

还有一种方式是把IO端口空间映射到内存空间,这样依然可以通过正常的访存指令访问IO端口,但是这种方式下依然受到IO空间大小的制约,可以说并没有解决实际问题。

但是上述方案只适用于在外设和内存进行小数据量的传输时,假如进行大数据量的传输,那么IO端口这种以字节为单位的传输就不用说了,IO内存虽然进行了内存映射,但是其映射的范围大小相对于大量的数据,仍然不值一提,所以即使采用IO内存仍然是满足不了需要,会让CPU大部分时间处理繁琐的映射,极大的浪费了CPU资源。那么这种情况就引入了DMA,直接内存访问。这种方式的传输由DMA控制器控制,CPU给DMA控制器下达传输指令后就转而处理其他的事务,然后DMA控制器就开始进行数据的传输,在完成数据的传输后通过中断的方式通知CPU,这样就可以极大的解放CPU。当然本次讨论的重点不在DMA,所以对于DMA的讨论仅限于此。

那么CPU是怎么访问这些配置寄存器的呢?要知道配置寄存器指定了设备存储寄存器的映射方式以及地址区间,但是配置寄存器本身的访问就是一个问题。为每个设备预留IO端口或者IO内存都是不现实的。暂且不说x86架构下IO端口地址空间只有区区64K,就是内存,虽然现在随着科技的发展,内存空间越来越大,但是也不可能为每个设备预留空间。那么折中的方式就是为所有设备的配置寄存器使用同一个IO端口,(虽然通过同一个端口访问配置空间,但可以通过配置寄存器中的总线号、设备号、功能号来区别是哪一个逻辑设备)系统在IO地址空间预留了一段地址就是0xCF8~0xCFF一共八个字节,前四个字节做地址端口,后四个字节做数据端口。CPU访问某个设备的配置寄存器时,先向地址端口写入地址,然后从数据端口读写数据。这里的地址是一个综合地址,结构如下:

Pci地址:<domain>:<bus>:<slot>.<func>

domain: 16bits

bus: 8bits     /* 总线号*/

slot: 8bits     /* slot号*/

func: 3bits    /* 逻辑功能号 */

这里就可以解释我们总线数量和逻辑设备数量的限制了。在寻找某一个逻辑设备时,先根据总线号找到总线,然后根据设备号找到总线上的某个接口,最后在根据功能号定位某一个逻辑设备。

pci总线扫描及pci网卡驱动相关推荐

  1. PCI总线---深入理解PCI总线

    补充: PCI/PCIe基础--配置空间: http://blog.csdn.net/jiangwei0512/article/details/51603525 8.1 深入理解PCI总线 8.1.1 ...

  2. PCI总线---PCI设备扫描过程

    8.2 PCI设备扫描过程 Linux内核具备多种PCI的扫描方式,它们之间大同小异. 本节使用传统的扫描方式 执行 pci_legacy_init函数,定义在legacy.c 文件中 : stati ...

  3. 【PCI】ARM架构——PCI总线驱动、RC驱动、Host Bridge驱动、xilinx xdma ip驱动(八)

    本文以xilinx RC IP为例,讲解ARM的RC驱动(PL). IP例程参考网址:https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/188 ...

  4. Linux源码阅读——PCI总线驱动代码(三)PCI设备枚举过程

    目录 前言 1.枚举过程 1.1 acpi_pci_root_add 1.2 pci_acpi_scan_root(枚举开始) 1.3 acpi_pci_root_create 1.4 pci_sca ...

  5. linux设备驱动之PCI总线概述

    文章目录 总线概念 PCI总线 PCI总线体系结构 PCI设备寻址 PCI寻址 配置寄存器 总线概念 总线是一种传输信号的信道:总线是连接一个或多个半导体的电气连线.总线由电气接口和编程接口组成,对于 ...

  6. PCI总线特性及信号说明

    PCI:Peripheral Component Interconnect,外围设备互联总线,是一种局部总线,已成为局部总线的新标准,广泛用于当前高档微机.工作站,以及便携式微机.主要用于连接显示卡. ...

  7. PCI总线协议(一)

    刚开始接触BIOS开发,目前正在学习关于PCI总线方面的知识,以下是本人根据网上的资料所整理的PCI学习笔记,如果有什么不对的地方,感谢大神的斧正. 众所周知,PCI总线是计算机主板上不可或缺的部分, ...

  8. 什么是pci总线原理?

    [推荐阅读]一文看懂页面置换算法 深度解剖Linux内核[网卡设备驱动] 详细讲解Linux内核中伙伴系统和slab机制 PCI即Peripheral Component Interconnect,中 ...

  9. PCI总线的桥与配置(二)

    PCI桥与PCI设备的配置空间 PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,PCI ...

  10. PCI总线的含义是什么?PCI总线的主要特点是什么?

    PCI是在CPU和原来的系统总线之间插入的一级总线,具体由一个桥接电路实现对这一层的管理,并实现上下之间的接口以协调数据的传送.管理器提供了信号. 缓冲,使之能支持10种外设,并能在搞时钟频率下保存高 ...

最新文章

  1. CentOS下命令行和桌面模式的切换方法
  2. 今年,你会为5G消费吗?就一分钟,求投票
  3. linux cached 进程,关于Linux cached内存简析
  4. 技术系列课|音视频测试实战——记音视频测试那些事
  5. Ansible之Playbook详解、案例
  6. 洛谷.4172.[WC2006]水管局长(LCT Kruskal)
  7. C语言 判断两个字符串大小相等关系
  8. 用MSBuild.... DailyBuild和软件开发流程的东东
  9. 18 个惊人的 GitHub 仓库
  10. 前端常用素材网站整理
  11. 戴尔电脑硬件自检教程
  12. 申请免费领取阿里云服务器
  13. 《人性的弱点》简明总结
  14. web个人学习笔记(待完善)
  15. 华硕笔记本屏幕亮度怎么调节?屏幕亮度调节方法
  16. 乱码原因产生和解决方案
  17. 关于ModbusTCP通讯汇川PLC
  18. 交换机的工作原理及配置
  19. 济南哪个学校学计算机好,济南计算机专业哪些学校好
  20. 矩阵、坐标变换、相控阵天线拟合方向图代码

热门文章

  1. Arch-004ArchLinux搜狗输入法安装
  2. 10万套PPT模板素材,SmileTemplates全球最大PPT资源下载网站
  3. 单片机编程技巧—状态机编程
  4. 材料成型是现代制造业的重要支柱,对经济社会的发展和综合国力的提升有着十分重要的意义。
  5. CentOS 7 安装教程、硬盘分区、LVM、网络配置、软件源配置、制作
  6. [转]计算机类核心期刊投稿的一些资料汇总
  7. 真的醉了!尚学堂java马士兵全套
  8. 高一计算机教学,高一信息技术教学计划参考
  9. xtdpdgmm:动态面板数据模型一网打尽
  10. 减速器课程设计指导系统使用方法