PCI总线学习笔记:读写篇

前言

最近在写E1000网卡的驱动,这其中涉及到了PCI总线的相关内容。但是网上大部分关于PCI的文章都只局限在概念上的描述,并没有给出具体的例子来解释。这其实也是情理之中的,因为PCI总线规范就像是一个抽象的接口,其具体怎么实现是与具体的设备有关的,这也是学习硬件最让人头痛的地方:有时难以区分概念与实现的边界,例如,对于CPU是如何区分访存和MMIO这个问题(两者在CPU看来都是对一个物理地址进行访问),作为概念层的x86体系结构规范并没有明确规定这个该如何实现,而只是在手册里面提到了这样一个概念;而其具体实现方式在不同的CPU型号之间是不同的,例如在Intel Xeon系列CPU中就是通过一个叫做SAD(Source 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 Number,Device 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的值一定是0xfffff000,0xabcde000之类的,不可以是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对应的物理地址就是0xabcde002),CPU就会去找E1000网卡了。那么CPU是怎么知道这个信息的?
这涉及到总线的工作原理了,读0xabcde002这个指令不是单独发给E1000网卡的,而是广播给了总线上的所有设备,主设备会把访存地址发送到地址线上,然后每个PCI设备都拿地址线上的地址和自己的BAR寄存器匹配,如果匹配上了,就按照控制线上的指示进行操作,并通过数据线来传递数据。(具体可见这篇博客文章)

此时还有一个问题,CPU拿到MOV $0xabcde002,%eax指令的时候,只知道要去访问物理地址0xabcde002,那它为什么不去访问存储器的对应位置,而是把访存请求发到了E1000设备?
答案是:有相应的硬件设备来进行这种路由操作,不同型号的CPU对于这个功能的实现不尽相同,例如(参考资料【3】【4】),对于Intel Xeon系列的CPU,其内部有一个叫做SAD(Source Address Decoder)的硬件,这个硬件保存了对于MMIO区域的配置,负责把MMIO请求转发到PCI主桥中。
(注意,上文提到的0xabcde002指的都是物理地址,实际上在开启分页后,CPU处理的都是虚拟地址,虚拟地址需要通过MMU转换为物理地址)
更通用地来讲,对于x86架构,这部分工作应该是由北桥(North Bridge)来完成的,北桥应该负责把访问PCI设备的请求转发到PCI总线上。
在这里插入图片描述

MMIO究竟是怎么实现的?

有了上面的铺垫,还原MMIO的全过程就很简单了。
这里假设我把E1000网卡的BAR0设置为了0xabcde000,MEM类型;现在想要访问E1000网卡中offset为0的寄存器,且已知虚拟地址0xffabe000经过MMU变换成物理地址之后是0xabcde000,那么现在只需要一个movl $0xffabe000, %eax指令,就可以把想要的值存储到eax中。
上述整个过程具体分解如下:

  1. 0xffabe000这个地址通过MMU转换为物理地址0xabcde000
  2. CPU把读物理地址0xabcde000的请求发到北桥
  3. 北桥看出这是一个MMIO区域的地址,所以把请求发到PCI总线
  4. PCI总线上的E1000网卡匹配成功,把数据发送到总线的数据线上
  5. 数据逐层向上传递到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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/520490.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SnapGene:解码生命之链的强大工具 mac/win版

在生物科技日新月异的时代,DNA分析已成为众多科研领域不可或缺的工具。SnapGene DNA生物分析软件应运而生,以其强大的功能和直观的用户界面,赢得了众多科研人员的青睐。 SnapGene软件获取 SnapGene是一款功能全面的DNA序列分析工具&#xff…

DevC++进行调试时,控制台出来后就闪退怎么解决

工具->编译选项->代码生成->连接器,然后将调试信息改成yes就可以了 如果有帮助麻烦点个赞呗

【好书推荐-第十四期】《 互联网大厂晋升指南:从P5到P9的升级攻略》

😎 作者介绍:我是程序员洲洲,一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公众号:洲与AI。 🎈 本文专栏:本文收录…

0基础没编程经验能学嵌入式吗?

0基础没编程经验能学嵌入式吗? 可以的,即使你是0基础,没有编程经验,也完全有可能学习嵌入式系统。嵌入式系统是计算机技术与特定应用领域相结合的产物,涉及硬件和软件的知识。从零开始学习嵌入式开发,你可…

归并排序解读

在算法领域中,排序算法一直是一个核心话题。归并排序(Merge Sort)作为一种典型的分治思想应用,以其稳定、高效的特点受到了广泛的关注和应用。本文将深入探讨归并排序的原理、实现方式,以及它在实际应用中的价值。 一…

入门MyBatis

文章目录 入门MyBatisMyBatis快速入门创建user表添加数据创建模块导入坐标编写Mybatis核心配置文件编写SQL映射文件编码 使用idea编写sql代码链接数据库调出console控制台 Mapper代理开发定义与SQL映射文件同名的Mapper接口编码 MyBatis核心配置文件安装mybatisx插件配置文件完…

llama2的python视角

1 调试代码 if __name__ __main__ :config ModelArgs(dim8, n_layers2, n_heads32, n_kv_heads32, vocab_size32000, hidden_dimNone, multiple_of256, norm_eps1e-05, max_seq_len3, dropout0.0)model Transformer(config)input_tokens torch.randint(0, 32000, (1, 3)) …

【Linux】UDP编程【上】{诸多编程接口/小白入门式讲解}

文章目录 0.预备知识0.1套接字0.2TCP/UDP0.3大小端问题 1.socket 常见API1.1socket1.2各个接口1.3int bind();1.3网络头文件四件套1.4bzero1.5recvfrom1.6sendto() 2.UDP编程2.1服务器编程2.2客户端编程2.3运行测试2.3.1本机通信2.3.2popen2.3.3strcasestr2.3.4回顾C11智能指针…

靠劳保手套代加工赚钱 这几点一定要记牢

中国劳保手套代加工行业是一个与劳动保护密切相关的行业,随着中国经济的快速发展和安全生产意识的提高,劳保手套的需求呈现出稳步增长的趋势。得益于国内制造业、建筑业、矿业等劳动密集型行业的持续快速发展,中国劳保手套市场规模在过去几年…

路径规划——搜索算法详解(六):LPA*算法详解与Matlab代码

上文讲解了D*算法,D*算法为在动态环境下进行路径规划的场景提出了可行的解决方案,本文将继续介绍另外一种动态规划路径的方法——Lifelong Planning A*(LPA*)算法。 该算法可以看作是A*的增量版本,是一种在固定起始点…

P8749 [蓝桥杯 2021 省 B] 杨辉三角形

[蓝桥杯 2021 省 B] 杨辉三角形 题目描述 下面的图形是著名的杨辉三角形: 如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列: 1 , 1 , 1 , 1 , 2 , 1 , 1 , 3 , 3 , 1 , 1 , 4 , 6 , 4 , 1 , … 1,1,1,1,2,1,1,3,3,1,1,4,6,4,1, …

背包问题---

一、背包模型 有一个体积为V的背包,商店有n个物品,每个物品有一个价值v和体积w,每个物品只能被拿一次,问能够装下物品的最大价值。 这里每一种物品只有两种状态即"拿"或"不拿". 设状态dp[i][j]表示到第i个物品为止,拿的物品总体积为j的情况下的最大价…

网络网络层之(3)IPv6地址

网络网络层之(3)IPv6协议 Author: Once Day Date: 2024年4月2日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文档可参考专栏:通信网络技术_Once-Day的…

简化备案域名查询的最新API接口

随着互联网的发展,越来越多的网站和域名被注册和备案。备案域名查询是一个非常重要的功能,可以帮助用户在特定时间段内查询已备案的域名信息。现在,我将介绍一个简化备案域名查询的最新API接口,该接口可以帮助用户快速查询备案域名…

SQL 注入神器:jSQL Injection 保姆级教程

一、介绍 jSQL Injection是一种用于检测和利用SQL注入漏洞的工具,它专门针对Java应用程序进行SQL注入攻击。以下是关于jSQL Injection的一些介绍: 功能特点: 自动化扫描: jSQL Injection具有自动化扫描功能,能够检测J…

Cesium入门路上的问题解决和知识点集合

想要进行cesium事件处理,必然会涉及到Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)但是与js不同,viewer的位置要先于viewer调用代码大小写错了,就会找不到构造的东西​ 练习时这个方法无效主要是setInputAction方法的括号位置错误应…

精品推荐-2024护网HVV实战教程资料合集(共20章)

以下是资料目录,如需下载,请前往星球获取:https://t.zsxq.com/19vwYrf4t 精品推荐,2024护网HVV实战教程资料合集,压缩包内涵大量实战资料,共20章。星球内会持续更新教程包。 01-HW介绍.zip 02-HTTP&Bu…

揭秘微信如何设置自动回复

01 自动通过好友后自动回复设置 02 个人聊天的关键字自动回复设置 不仅如此,在微信私域管理系统上还可以进行批量多个号自动添加好友、批量群发、定时发朋友圈等操作,可以大幅度提升工作效率,将繁琐的任务交给系统来完成,从而节省…

【【萌新的Pytorch入门之Python的学习】】

学习记录 - 参考记录来自B站up主 -爆肝杰哥 ① NumPy 包为 Python 加上了关键的数组变量类型,弥补了 Python 的不足; ② Pandas 包在 NumPy 数组的基础上添加了与 Excel 类似的行列标签; ③ Matplotlib 库借鉴 Matlab,帮 Python 具…

论文《Structural Information Enhanced Graph Representation for Link Prediction》阅读

论文《Structural Information Enhanced Graph Representation for Link Prediction》阅读 论文概况Introduction问题一:**现有的节点标记技巧只能帮助感知路径深度,而缺乏对路径“宽度”的感知,例如节点度或路径数量**。问题二:G…