缘魁上海网站建设,网页的构成,wordpress+主题页脚,建程网app下载前言
最近在写E1000网卡的驱动#xff0c;这其中涉及到了PCI总线的相关内容。但是网上大部分关于PCI的文章都只局限在概念上的描述#xff0c;并没有给出具体的例子来解释。这其实也是情理之中的#xff0c;因为PCI总线规范就像是一个抽象的接口#xff0c;其具体怎么实现…前言
最近在写E1000网卡的驱动这其中涉及到了PCI总线的相关内容。但是网上大部分关于PCI的文章都只局限在概念上的描述并没有给出具体的例子来解释。这其实也是情理之中的因为PCI总线规范就像是一个抽象的接口其具体怎么实现是与具体的设备有关的这也是学习硬件最让人头痛的地方有时难以区分概念与实现的边界例如对于CPU是如何区分访存和MMIO这个问题两者在CPU看来都是对一个物理地址进行访问作为概念层的x86体系结构规范并没有明确规定这个该如何实现而只是在手册里面提到了这样一个概念而其具体实现方式在不同的CPU型号之间是不同的例如在Intel Xeon系列CPU中就是通过一个叫做SADSource Address Decoder的硬件来完成的。
Anyway本文旨在记录一些我在学习中遇到的问题以及这些问题的答案希望能够给到读者一些帮助。
如果需要更加深入地理解PCI总线工作原理建议配合《PCI Express体系结构导读》王齐 著使用。
PCI设备是如何读写的
PCI是总线规范其读写是通过总线事务来完成的简单来说就是按照一定的约定向总线上写入事务类型地址等参数然后再使用数据线传输数据。具体过程可以参考《PCI Express体系结构导读》 需要注意的是这里的地址其实是PCI域的地址和存储器域的地址并不等价两者要通过HOST主桥做转换但是由于在x86下存储器域地址和PCI域地址在数值上是相等的所以本文不再区分这个概念了统一使用地址这个名词这部分具体见《PCI Express体系结构导读》
如何遍历PCI总线来发现存活设备
实现思路可以通过枚举所有可能的Bus Number, Device Number 和 Function Number来探测所有的存活设备。对于一组特定的Bus, Dev和Func如果这个功能存在那么在其配置空间中的Vendor ID和Device ID就是有效值可以由此来判断。
如何访问配置空间
访问配置空间的方式与具体的体系机构有关例如在 MPC8548 处理器的 HOST 主桥中与 PCI 设备配置空间相关的寄存器由CFG_ADDR、CFG_DATA 和 INT_ACK 寄存器组成。系统软件使用 CFG_ADDR 和 CFG_DATA 寄存器访问PCI 设备的配置空间软件通过向CFG_ADDR寄存器中写入地址然后访问CFG_DATA寄存器当CFG_ADDR的EN位为1时HOST 主桥将对这个寄存器的访问转换为 PCI 配置读写总线事务并发送到 PCI 总线上。而在x86体系结构下CFG_ADDR 和 CFG_DATA是通过2个IO端口来实现的,CONFIG_ADDRESS地址是0xcf8,CONFIG_DATA地址是0xcfc也就是说可以通过IN和OUT指令对这两个端口进行读写来实现对配置空间的访问。 虽然访问CFG_ADDR的方式与具体的体系结构有关但是CFG_ADDR的格式是由PCI Spec规定好了的其具体含义如下图所示
Bus NumberDevice Number与设备被插在主板上哪个PCI插槽有关其编号方式示意图如下不完全严谨但是这个不用细究只需要知道这两个值可以唯一确定一个PCI插槽即可 Function Number表示PCI设备上的功能号一个PCI设备可以最多有8个功能但是一般的PCI设备都是单功能只是PCI规范提供了扩展的一种可能性。 Register Number的含义见下图配置空间也是PCI Spec规定了的内容这里只展示了PCI设备的配置空间PCI桥的配置空间略有差别 例如如果我想要访问某个设备某个功能的Revision ID那么Register Number就设置为0x08然后读取一个4字节的数据取其中的第一个字节即可。
BAR寄存器如何工作的
这也是初学者容易迷惑的地方这一章节将尝试回答如下问题
BAR寄存器保存的地址是什么地址有什么用BAR寄存器中的值是谁负责分配的
BAR寄存器的作用
向BAR寄存器中写入了一个地址就相当于标记了这段地址是属于这个BAR的了以后所有对这个地址的访存操作都会转发到这个设备由这个设备进行操作。 例如如果我有一个E1000网卡我把这个网卡配置空间的BAR0设置为了0xabcde000那么当我向0xabcde002的位置写入数据的时候E1000网卡就会收到这个写数据的操作并进行相应的动作。而具体访问这个地址会造成什么结果这个是和具体的设备相关的例如对于E1000网卡BAR0对应的是E1000相关寄存器即如果BAR0设置为了0xabcde000那么访问0xabcde000到0xabcdefff就等价于访问了E1000网卡的寄存器如下图所示 而具体每个地址对应到哪个寄存器每个寄存器是什么作用则需要继续查阅E1000的手册下图是部分寄存器的偏移量以及名称 注BAR寄存器有IO模式和MEM模式MEM模式就是上面所述的情况可以直接通过访存来实现而IO方式则需要通过IN和OUT指令来访问IO端口来实现这里不再赘述了
BAR寄存器的大小
接下来的问题是这里我只设置了一个Base Address我怎么知道这个区域的大小呢例如我把BAR0设置为了0xabcde000那么为什么对地址0xbbcde000的访问不会转发到这个设备来呢 这其实是通过一个规定来实现的即如果BAR空间的大小为M那么BAR寄存器中地址的低 l o g 2 M log_2M log2M位一定是0。例如BAR1对应的空间大小是 2 12 2^{12} 212字节那么BAR1的值一定是0xfffff0000xabcde000之类的不可以是0xabcde010因为需要保证这个地址的二进制位的低12位是0。 而且这种规定还是由硬件来实现的也就是说如果这个空间大小是 2 12 2^{12} 212字节那么即使我向这个BAR寄存器里面写0xfffffff最终这个寄存器里的值只会是0xfffff000硬件会自动把低12位给强制置零。事实上软件也是通过这个小trick来获取到这个BAR空间的大小的。 而且推测硬件也是通过这种方式来快速匹配总线事物的目标设备是不是自己因为如果这个BAR空间大小是 2 12 2^{12} 212字节那么只需要把地址线的高20位和BAR寄存器的高20位做比较即可得出结论 具体可参考这篇Stackoverflow Post 事实上IO模式还是MEM模式这也是由硬件定好的软件是不可能通过写寄存器来更改的
怎么就知道是访问这个设备了
现在又有一个问题我只是在E1000网卡的BAR0寄存器设置了一个值然后我使用MOV $0xabcde002,%eax指令假设虚拟地址0xabcde002对应的物理地址就是0xabcde002CPU就会去找E1000网卡了。那么CPU是怎么知道这个信息的 这涉及到总线的工作原理了读0xabcde002这个指令不是单独发给E1000网卡的而是广播给了总线上的所有设备主设备会把访存地址发送到地址线上然后每个PCI设备都拿地址线上的地址和自己的BAR寄存器匹配如果匹配上了就按照控制线上的指示进行操作并通过数据线来传递数据。具体可见这篇博客文章
此时还有一个问题CPU拿到MOV $0xabcde002,%eax指令的时候只知道要去访问物理地址0xabcde002那它为什么不去访问存储器的对应位置而是把访存请求发到了E1000设备 答案是有相应的硬件设备来进行这种路由操作不同型号的CPU对于这个功能的实现不尽相同例如参考资料【3】【4】对于Intel Xeon系列的CPU其内部有一个叫做SADSource Address Decoder的硬件这个硬件保存了对于MMIO区域的配置负责把MMIO请求转发到PCI主桥中。 注意上文提到的0xabcde002指的都是物理地址实际上在开启分页后CPU处理的都是虚拟地址虚拟地址需要通过MMU转换为物理地址 更通用地来讲对于x86架构这部分工作应该是由北桥North Bridge来完成的北桥应该负责把访问PCI设备的请求转发到PCI总线上。
MMIO究竟是怎么实现的
有了上面的铺垫还原MMIO的全过程就很简单了。 这里假设我把E1000网卡的BAR0设置为了0xabcde000MEM类型现在想要访问E1000网卡中offset为0的寄存器且已知虚拟地址0xffabe000经过MMU变换成物理地址之后是0xabcde000那么现在只需要一个movl $0xffabe000, %eax指令就可以把想要的值存储到eax中。 上述整个过程具体分解如下
0xffabe000这个地址通过MMU转换为物理地址0xabcde000CPU把读物理地址0xabcde000的请求发到北桥北桥看出这是一个MMIO区域的地址所以把请求发到PCI总线PCI总线上的E1000网卡匹配成功把数据发送到总线的数据线上数据逐层向上传递到CPU
逐级向下转发是如何实现的
PCI桥也有自己的Base和Limit寄存器可以记录这个PCI桥所管辖的地址范围所以可以实现向下转发具体见这篇文章。
BAR寄存器地址是谁分配的
通过上面的描述可以看出BAR寄存器地址分配是一个难度比较高的任务因为需要保证每个BAR寄存器的值之间一定不能有冲突否则就会出现2个PCI设备同时响应一个总线事务的混乱局面那么这个BAR寄存器值是谁分配的呢 答案是BIOS等firmware在启动时分配的而我们写操作系统时只需要读取firmware预分配好的值然后直接利用就行这个分配工作不需要操作系统来完成具体见这篇Stackoverflow Post。
参考资料
【1】《PCI Express体系结构导读》王齐 著 【2】Intel E1000 Manual 【3】Physical Address Decoding in Intel Xeon v3/v4 CPUs: A Supplemental Datasheet 【4】Intel Xeon 7500 Datasheet