• [虚拟化/云][全栈demo] 为qemu增加一个PCI的watchdog外设(五)


    目的:

    1. 了解PCI的基本知识,为完成watchdog的设备做准备。

    准备知识:

    简单的说,PCI 设备分3个空间。 配置空间,IO空间,内存地址空间。

    PCI设备厂家决定了外设是使用IO空间还是IO内存空间。 我们通过读取配置空间的bar寄存器的最低位bit0来决定是该设备使用的是IO空间还是内存地址空间。

    计算机一启动,bois或者linux会根据域,总线号、设备号和功能号,按照一定的算法,扫描PCI设备,读取设备配置空间的信息。最主要的包括厂商ID,设备ID和bar寄存器中设备的外设地址。

     

    以下的这些内容可以不看。

    具体的PCI的知识可参考: http://tldp.org/HOWTO/PCI-HOWTO.html

     PCI硬件结构(PCI系统示意图):

    PCI系统示意图

    了解的概念

    domain, bus,devcie,function(PCI设备 = "域号, 总线号, 设备号, 和功能号"的组合)

    image: http://blog.sina.com.cn/s/blog_6472c4cc0100qg5i.html

    imagehttp://blog.sina.com.cn/s/blog_6472c4cc0100qtdo.html

     

    详情:  http://blog.sina.com.cn/s/blog_6472c4cc0100qbvs.html

    PCI 寻址

    每个 PCI 外设有一个总线号, 一个设备号, 一个功能号标识. PCI 规范允许单个系统占用多达 256 个总线, 但是因为 256 个总线对许多大系统是不够的, Linux 现在支持 PCI 域. 每个 PCI 域可以占用多达 256 个总线. 每个总线占用 32 个设备, 每个设备可以是一个多功能卡(例如一个声音设备, 带有一个附加的 CD-ROM 驱动)有最多 8 个功能. 因此, 每个功能可在硬件层次被一个 16-位地址或者 key , 标识. Linux 的设备驱动编写者, 然而, 不需要处理这些二进制地址, 因为它们使用一个特定的数据结构, 称为 pci_dev, 来在设备上操作.

     $ lspci | cut -d: -f1-3

    0000:00:00.0 Host bridge

    0000:00:00.1 RAM memory

    ....

    0000:00:14.0 VGA compatible controller

    $ cat /proc/bus/pci/devices | cut -f1

    0000

    0001

    ...

    00a0

    0x00a0(0b10100000) 意思是 0000:00:14.0 当划分为域(16位), 总线(8位), 设备(5位)和功能(3位).

     

    驱动概念

     

    与USB驱动总线型的驱动类似, Linux PCI设备驱动实际包括Linux PCI设备驱动和设备本身驱动两部分。

    就是Linux PCI驱动是内核自带的,或者说内核帮你写好了!而我们需要完成的就是设备本身的驱动,比如网卡驱动等。

    这个与windows的驱动也是类似的。比如说我们需要开发一个基于USB的windows的打印机,我们只需要关心打印机的驱动即可。

    三种地址空间

    PCI I/O空间、PCI内存地址空间和PCI配置空间。

    PCI I/O空间和PCI内存地址空间由设备驱动程序(即上面提到的设备本身驱动)使用。

    PCI配置空间由Linux PCI初始化代码使用,这些代码用于配置PCI设备,比如中断号以及I/O或内存基地址。

    枚举过程

    熟悉USB开发的工程师知道USB host跟device的第一次通信都是采用0x00相同的地址,然后进行配置,分配给一个新的唯一地址。

    PCI的枚举过程类似。 http://blog.csdn.net/linuxdrivers/article/details/5849698

    对于i386结构的处理器,PCI总线的设计者在I/O地址空间保留了8个字节用于枚举过程中的配置,那就是0xCF8~0xCFF。这8个字节构成了两个32位 的寄存器,第一个是“地址寄存器”0xCF8,第二个是“数据寄存器”0xCFC。要访问某个设备中的某个配置寄存器时,CPU先往地址寄存器中写入目标 地址,然后通过数据寄存器读写数据。不过,写入地址寄存器的目标地址是一种总线号、设备号、功能号以及设备寄存器地址在内的综合地址。

    地址寄存器格式

     

    这里的总线号、设备号和功能号是对配置寄存器地址的扩充,就是上面提到的附加的其他条件。首先每个PCI总线都有个总线号,主总线的总线号为0,其余的则 由CPU在枚举阶段每当探测到一个PCI桥时便为其指定一个,依次递增。设备号一般代表着一块PCI接口卡(更确切的说是PCI总线接口芯片),通常取决 于插槽的位置。每块PCI接口卡上可以有若干个功能模块,这些功能模块共用一个PCI总线接口芯片,包括其中用于地址映射的电子线路,以降低成本。从逻辑 的角度说,每个“功能”实际上就是一个设备(看过USB设备驱动的人很眼熟吧 呵呵),所以设备号和功能号合在一起又可以称作“逻辑设备号”,而每块卡上最多可以容纳8个设备。显然,这些字段(指整个32bit)结合在一起就惟一确 定了系统中的一项PCI逻辑设备。开始时,只有0号总线可以访问,在扫描0号总线时如果发现上面某个设备是PCI桥,就为之指定一个新的总线号,例如1, 这样1号总线就可以访问了,这就是枚举阶段的任务之一。

    IO/MMIO

    IO作为CPU和外设交流的一个渠道,主要分为两种,一种是Port I/O,一种是MMIO(Memory mapping I/O)。

    I/O地址空间
    我们常说的I/O端口,它实际上的应该被称为I/O地址空间。

    对于x86架构来说,通过IN/OUT指令访问。PC架构一共有65536个8bit的I/O端口,组成64KI/O地址空间,编号从0~0xFFFF。 连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如 I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。

    MMIO

    MMIO我们可以想访问内存一样访问IO端口。
    MMIO占用CPU的物理地址空间,对它的访问可以使用CPU访问内存的指令进行。一个形象的比喻是把文件用mmap()后,可以像访问内存一样访问文 件、同样,MMIO是用访问内存一样的方式访问I/O资源,如设备上的内存。MMIO不能被cache,(有特殊情 况,如VGA)。

    Port I/O和MMIO的主要区别在于

    1)前者不占用CPU的物理地址空间,后者占有(这是对x86架构说的,一些架构,如IA64,port I/O占用物理地址空间)。

    2)前者是顺序访问。也就是说在一条I/O指令完成前,下一条指令不会执行。例如通过Port I/O对设备发起了操作,造成了设备寄存器状态变化,这个变化在下一条指令执行前生效。uncache的MMIO通过uncahce memory的特性保证顺序性。

    3)使用方式不同

    由于port I/O有独立的64KI/O地址空间,但CPU的地址线只有一套,所以必须区分地址属于物理地址空间还是I/O地址空间。早期的CPU有一个M/I针脚来 表示当前地址的类型,后来似乎改了。刚才查了一下,叫request command line,不知道是不是一个针脚。

    IBM PC架构规定了一些固定的I/O端口,ISA设备通常也有固定的I/O端口,这些可以通过ICH(南桥)的规范查到。PCI设备的I/O端口和MMIO基 地址通过设备的PCI configure space报告给操作系统,这些内容以前的帖子都很多,可以查阅一下。

    通常遇到写死在I/O指令中的I/O端口,如果不是ISA设备,一般都是架构规定死的端口号,可查阅规范。

    BAR (http://blog.sina.com.cn/s/blog_6472c4cc0100qnht.html

    PCI bar(PCI base address register)。PCI bar就是该设备可用port i/o和mmio访问的寄存器的基地址。除了寄存器,还有一种称为扩展rom,也是通过mmio访问的。而ioremap的作用就是把pci bar(pci bar是物理地址)映射到虚拟地址空间中。所以它和pci_read_config_dword是下列关系:

    pci_read_config_dword得到PCI bar ----> ioremap映射PCI bar到虚拟地址空间, 返回一个虚拟地址,以后通过该地址读写设备的寄存器。

    可以通过读取配置空间的bar寄存器{bar0: 0x10,  bar1: 0x14,  bar2: 0x18, bar3: 0x1C, bar4: 0x20,  bar5: 0x24} 来获取基地址。 如果读到的地址为是奇数,那么这个地址是个IO端口地址。 如果读到的是偶数,那么这个地址是IO内存地址。

    见这个帖子 http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=1061711&page=1

    iommu相关的PCI内容

    http://download.intel.com/technology/computing/vptech/Intel%28r%29_VT_for_Direct_IO.pdf

    这个对于我们完成watchdog的驱动没有关系,可以略过。这个对于深入虚拟化需要了解。

    IOMMU IO页表

    在没有IOMMU的情况下,设备(指32bit或64bit设备,老的16bit的不提)的DMA操作可以访问整个物理地址空间,所以理论上设备可以向操 作系统的代码段、数据段等内存区域做DMA,从而破坏整个系统。当然,通常来说不会有这样的设备。IOMMU的出现,可以实现地址空间上的隔离,使设备只 能访问规定的内存区域。

    简要说一下intel的IOMMU怎么做到这点的:
    目前PC架构最多有256PCI总线,于是IOMMU用一个称为root entry的数据结构描述PCI总线,总共256个root entry构成一张表。每条PCI总线最多允许256个设备,IOMMU用context entry描述一个PCI设备(或者是PCI桥),256个context entry构成一张表。所以就有了如图的关系。我们知道,PCI设备用 {BUS:DEV:FUNC} (当然,还有个segment,不过似乎PC架构都只有一个segment,这个暂时忽略)描述一个设备。所以对于一个特定设备,用bus号做索引 root entry表,用dev号索引context entry表可以找到描述该设备的的context entry。context entry中有一个指针指向一章I/O页表,当设备发起DMA操作时,IOMMU会根据该页表把设备的DMA地址转换成该设备可以访问内存区域的地址。
    所以只要为设备建一张I/O页表,就可以使设备只能访问规定的内存区域了。当然,也可以把该页表当成跳板,让只能寻址32bit地址空间的设备访问到64bit地址空间中去。

     

    IOMMU是对于设备发起DMA操作来说的,你可以理解成设备用于做DMA的地址是一个虚拟地址(这个虚拟地址和我们平时说的那个不一样,是指设备DMA 用的地址不是真实的物理地址,没有IOMMU的情况下用的是物理地址)。也有称这个虚拟地址为总线地址的,但我认为不恰当,容易让人误解。

    对于eeprom的访问,它又和mmio还有iommu都没有关系。eeprom是个串行设备,它不是被ioremap到虚拟地址空间访问,而是通过 PCI bar报告一个eeprom寄存器给操作系统,这个寄存器有一个bit的di(data in)、一个bit的do(data out)、一个bit的clk(时钟)。时钟在下降沿生效,写的时候通过di一个bit一个bit的写,读的时候通过do一个bit一个bit的读。

     

    SRIOV相关的知识

     http://docs.oracle.com/cd/E38902_01/html/E38873/glbzi.html

    REF:

    1.  Linux核心  第六章 PCI   

    2. Linux 设备驱动 Edition 3  第 12 章 PCI 驱动

  • 相关阅读:
    多项目共享配置文件
    C# 可选参数 命名参数
    委托初探
    未能解析引用的程序集……因为它对不在当前目标框架……
    web中的autocomplete
    web程序获取客户端MAC地址
    结合C#在MSSQL中定义和使用自定义类型
    winform中的AutoComplete自定义控件
    C#编写扩展存储过程
    eric windows下和linux的安装配置
  • 原文地址:https://www.cnblogs.com/shaohef/p/3803942.html
Copyright © 2020-2023  润新知