结合linux, 介绍PCI/PCIe

文章详细解释了PCIe(PeripheralComponentInterconnectExpress)的传输速度提升过程,涉及不同版本的速率、编码方式,以及PCI设备的配置空间、TLP和DLLP协议、中断机制、驱动程序和中断管理等关键概念。
摘要由CSDN通过智能技术生成
  1. PCIe传输速度

GT/s == Giga transation per second (千兆传输/约等于Gb/s,因为还有效验码。

 PCIe是串行总线,PCIe1.0的线上比特传输速率为2.5Gb/s,物理层使用8/10编码,即8比特的数据,实际在物理线路上是需要传输10比特的,因此:

PCIe1.0 x 1的带宽=2.5Gb/s x 2(双向通道,FDD, 读写分开))/ 10bit = 0.5GB/s

        这是单条Lane的带宽,有几条Lane,那么整个带宽就0.5GB/s乘以Lane的数目。

        PCIe2.0的线上比特传输速率在PCIe1.0的基础上翻了一倍,为5Gb/s,物理层同样使用8/10编码,所以:

PCIe2.0 x 1的带宽=5Gb/s x 2(双向通道))/ 10bit = 1GB/s

        同样,有多少条Lane,带宽就是1GB/s乘以Lane的数目。

        PCIe3.0的线上比特传输速率没有在PCIe2.0的基础上翻倍,不是10Gb/s,而是8Gb/s,但物理层使用的是128/130编码进行数据传输,所以:

PCIe3.0 x 1的带宽=8Gb/s x 2(双向通道))/ 8bit = 2GB/s

        同样,有多少条Lane,带宽就是2GB/s乘以Lane的数目。

        由于采用了128/130编码,128比特的数据,只额外增加了2bit的开销,有效数据传输比率增大,虽然线上比特传输率没有翻倍,但有效数据带宽还是在PCIe2.0的基础上做到翻倍。

查看pcie 的速度(lspci -vv

1. 查看pci设备

Lspci/lspci -v 查看每个pci设备对应的驱动

网卡也是一种pci 设备,如何查询对应网口的pci设备号:

0000:00:00.0 – The domain number, bus number, device number, and function number, in that order, 称之为BDF

一般一个pci总线体系中有一个pci host bridge, 这一个pci总线系统也叫一个pci domain,
各个pci domain不可以直接相互访问。有些时候一个系统会有多个pcie root port, 这时
每个root port和下面的pci device组成各自的pci domain。我们一般只看BDF.

2. PCI 设备的配置空间

PCI 设备的配置空间初始化之后,该 PCI 设备在当前的 PCI 总线树上拥有一个独立的 PCI 总线地址空间,即 BAR 寄存器所描述的空间。在配置空间中含有该设备在 PCI 总线中使用的基地址,系统软件可以动态配置这个地址,从而保证每个 PCI 设备使用的物理地址并不相同

普通的PCI设备的配置空间(type 0)如下:

PCI 桥也是一种pci 设备(type 1),能用来扩展PCI总线能容纳的pci设备的数目

3. PCIe 的构架

类似于pci构架:

PCIe 总线采用的是一种深度优先(Depth First Search) 的拓扑算法,且 Bus0 总分配给 Root Complex. Root 中包含有集成的 Endpoint 和多个端口(Port), 每个端口内部都有一个虚拟的 PCI-to-PCI(P2P) , 并且该桥也有设备号和功能号

  PCIe传输的数据从上到下,都是以packet的形式传输的,每个packet都是有其固定的格式的。

        事务层的主要职责是创建(发送)或者解析(接收)TLP (Transaction Layer packet),流量控制,QoS,事务排序等。

        数据链路层的主要职责是创建(发送)或者解析(接收)DLLP(Data Link Layer packet)Ack/Nak协议(链路层检错和纠错),流控,电源管理等。

        物理层的主要职责是处理所有的Packet数据物理传输,发送端数据分发到各个Lane传输(stripe),接收端把各个Lane上的数据汇总起来(De-stripe),每个Lane上加扰(Scramble,目的是让01分布均匀,去除信道的电磁干扰EMI)去扰(De-scramble),以及8/10或者128/130编码解码,等等。

PCI数据是不加各种header的。

4. PCI总线枚举和BDF分配

function number是用于多function设备用于区分设备中具体访问的是哪个function的。那么对于大部分单function的设备来说,function number就固定为0;对于多个function的device或者支持虚拟function的device,function number也是由device内部来进行管理和区分的,不需要通过总线枚举来确定。

为支持ID路由,每个PCIE设备(端点和交换开关)中都应设置有贮存设备总线号和设备号的寄存器,复位时,该寄存器清0,每当设备在它的原级链路上检测到一个Type0的 config transaction 事务包时,它就从该TLP头标中的第8~9字节“捕获”总线号和设备 号作为它自己的bus和device号,并贮存入上述总线号和设备号寄存器。所以说在接收至少一个 Type0配置访问事务包以前,设备不会响应除配置周期以外的任何事务。PCIE协 议中没有定义贮存总线号和设备号信息的配置空间单元,只是规定了它必须做这件事。

两个对接的PCIE设备,主设备是RC端,从设备是EP端,那么上电复位后,EP是如何获取自身的bus number的呢?

在这样的一个PCIE结构中,上电复位后,RC端发起一个对EP的配置访问事务,EP会解析配置请求中的bus number,并保存下来,作为自身的bus number号。之后就可以相应其他TLP事务了。也就是说,上电复位后,EP必须先接收到一个配置请求,然后才能响应其他TLP事务。

5. BAR初始化

来个例子,看一下一个PCIe设备,系统软件是如何为其分配映射空间的

上电时,系统软件首先会读取PCIe设备的BAR0,得到数据:

 然后系统软件往该BAR0写入全1,得到

 BAR寄存器有些bit是只读的,是PCIe设备在出厂前就固定好的bit,写全1进去,如果值保持不变,就说明这些bit是厂家固化好的,这些固化好的bit提供了这块内部空间的一些信息:

        怎么解读?低12没变,表明该设备空间大小是4KB212次方),然后低4位表明了该存储空间的一些属性(IO映射还是内存映射,32bit地址还是64bit地址,能否预取?做过单片机的人可能知道,有些寄存器只要一读,数据就会清掉,因此,对这样的空间,是不能预读的,因为预读会改变原来的值),这些都是PCIe设备在出厂前都设置好的,提供给系统软件的信息。

        然后系统软件根据这些信息,在系统内存空间找到这样一块地方来映射这4KB的空间,把分配的基地址写入到BAR0

从而最终完成了该PCIe空间的映射。一个PCIe设备可能有若干个内部空间需要开放出来,系统软件依次读取BAR1BAR2。。。,直到BAR5,完成所有内部空间的映射

如何给pci分配系统空间呢?

cat /proc/iomem

80c1f0000-80c1f003f : mc_portal

80c200000-80c20003f : mc_portal

80c210000-80c21003f : mc_portal

2080000000-235fffffff : System RAM

  2240000000-233fffffff : reserved

  2351c00000-2351dfffff : reserved

  2351f80000-235fdfffff : reserved

  235fe10000-235fe2ffff : reserved

  235fe30000-235fe30fff : reserved

  235fe31000-235ffc0fff : reserved

  235ffc3000-235ffc3fff : reserved

  235ffc4000-235ffc7fff : reserved

  235ffc8000-235fffffff : reserved

找到最后的System RAM, 那么它后面的地址就可以分配给PCI设备了,即pci设备其实io地址0x23,6000,0000

另外系统地址和PCI地址之间也许会存在一些线性转换。

6. TLP

HostPCIe设备之间,或者PCIe设备与设备之间,数据传输都是以Packet形式进行的。事务层根据上层(软件层或者应用层)请求(Request)的类型、目的地址和其它相关属性,把这些请求打包,产生TLP,也就是Transaction Layer Packet。然后这些TLP往下,经历数据链路层,物理层,最终到达目标设备。

        根据软件层的不同请求,事务层产生四种不同的TLP请求:

  1. Memory
  2. IO
  3. Configuration
  4. Message

        前三种分别用于访问内存空间、IO空间、配置空间,这三种请求在PCI或者PCI-X时代就有了;最后的Message请求是PCIe新加的。在PCI或者PCI-X时代,像中断、错误以及电源管理相关信息,都是通过边带信号(sideband signal)进行传输的,但PCIe干掉了这些边带信号线,所有的通讯都是走带内信号,即通过Packet传输,因此,过去一些由边带信号线传输的数据,比如中断信息、错误信息等,现在就交由Message来传输了。

一个TLP,最多只能携带4KB有效数

7. PCI 中断设置

在PCIe总线中,MSI/MSI-X中断方式使用存储器写请求TLP(transaction layer packet), 即一种PCIe事务报文(和传输数据TLP类似,不过一些TLP header里面的配置不一样,具体就不深挖了),向处理器提交中断请求。PCIe设备在提交MSI/MSI-X中断请求时,都是向其MSI/MSI-X capability结构中Message Address 地址中写Message Data数据,从而组成一个MSI/MSI-X报文,向处理器提交中断请求。

MSI最多支持32个中断向量号,而MSI-X中断向量数目最大为2048。

查看pci支持的中断数量(8个):

是在配置空间配置的(下图是MSI-X的配置空间):

如何给注册PCI/PCIe中断到内核?

  1. 申请irq vector, 返回可以注册多少个irq.

int pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,

                           unsigned int max_vecs, unsigned int flags);

希望最少分配min_vecs, 不要超过max_vecs, 实际按照kernel里面irq的容量来定。

flag有如下几种:

举个例子:

               irq_count = pci_alloc_irq_vectors(pdev, 1, 32, PCI_IRQ_MSI);

  1. 为每个irq分配irq handler.
int pci_request_irq(struct pci_dev *dev, unsigned int nr, irq_handler_t handler, irq_handler_t thread_fn, void *dev_id, const char *fmt, ...)

举个例子:

For (k = 0; k < irq_num; k++){

               Ret = pci_request_irq(pdev, k, interrupt_handler, null, mqnic, “mqnic%d-%d”, id, k);

}

申请了之后, cat /proc/interrupts, 可以看到mqnic0-{0 – irq_num}的中断。

MSI 功能可以通过调用 pci_alloc_irq_vectors() 再调用request_irq(). 这会导致 PCI 支持将 CPU 向量数据编程到 PCI 设备功能寄存器中。许多架构、芯片组或 BIOS 不支持 MSI MSI-X,并且仅使用 PCI_IRQ_MSI PCI_IRQ_MSIX 标志调用 pci_alloc_irq_vectors 将失败,可以尝试指定 PCI_IRQ_LEGACY

PCIe MSI-X 中断编程 - 知乎 (zhihu.com)

7.1 如何触发PCI/PCIe中断?

PCIe设备发送一个MSI/MSI-x 消息给CPU

PCIe设备在提交MSI/MSI-X中断请求时,都是向其MSI/MSI-X capability结构中Message Address 地址中写Message Data数据,从而组成一个MSI/MSI-X报文,向处理器提交中断请求。

PCIE详解_浩雪峰的博客-CSDN博客

8. PCI 驱动

pci_register_driver() 和 pci_unregister_driver():分别用于注册和注销 PCI 驱动程序。

pci_enable_device() 和 pci_disable_device():分别用于启用和禁用 PCI 设备。

pci_request_region() 和 pci_release_region():分别用于请求和释放 PCI 区域。

在内核中预留一段空闲的内存给pci bar,没有正式分配。

pci_resource_len(pdev, barID) 和pci_resource_start(pdev, barID)

获取barID的寄存器地址和长度

pci_ioremap_bar(pdev, barID)

物理地址到虚拟地址的转换。

Dma_set_mask_and_coherent(dev, mask)

Mask可以配置成DMA_BIT_MASK(64), 表示64位地址空间可以被pci 设备全部访问

Coherent:DMA缓存一致性,使cpu和设备访问到的内容一致,而不是cache.

pci_alloc_irq_vectors() : 申请多少个irq vectors

pci_request_irq() 和 pci_free_irq():分别用于请求和释放 PCI 设备的中断。

pci_set_master() 和 pci_clear_master():分别用于设置和清除 PCI 设备的总线主控模式。

pci_enable_msi() 和 pci_disable_msi():分别用于启用和禁用 PCI 设备的 MSI 中断。

pci_enable_msix() 和 pci_disable_msix():分别用于启用和禁用 PCI 设备的 MSIX 中断。

pci_enable_sriov() 和 pci_disable_sriov():分别用于启用和禁用 PCI 设备的 SR-IOV 功能。

pci_find_capability() 和 pci_read_config_dword():分别用于查找和读取 PCI 设备的配置寄存器。

pci_set_drvdata() 和 pci_get_drvdata():分别用于设置和获取 PCI 设备的私有数据。

pci_save_state() 和 pci_restore_state():分别用于保存和恢复 PCI 设备的状态。

pci_bus_read_config_byte() 和 pci_bus_write_config_byte():分别用于读取和写入 PCI 总线的配置寄存器。

pci_find_device() 和 pci_find_slot():分别用于查找指定设备ID或槽位号的PCI设备。

pci_get_domain_bus_and_slot():用于查找指定域、总线和槽位号的PCI设备。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值