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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正确使用@Autowired

目录 一、前言二、跟着官方文档,学习正确使用Autowired0、实验环境1、通过构造方法进行注入1.1 问题1:那万一没有这个CustomerPreferenceDao对象,会报错吗? 2、通过setter方法注入3、通过方法注入(这个方法可以是任意名…

为什么苹果 Mac 电脑需要使用清理软件?

尽管 Apple Mac 电脑因其卓越的性能、简洁高效的 macOS 操作系统及独特的美学设计备受全球用户青睐,但任何电子设备在长期使用后都难以避免面临系统资源日渐累积的问题。其中一个重要维护需求在于,随着使用时间的增长,Mac电脑可能会由于系统垃…

go库x/text缺陷报告CVE-2022-32149的处理方案

#问题描述 go库 golang.org/x/text ,注意这里不是go的源码, 在0.3.8版本之前存在一个缺陷(Vulnerability) 缺陷ID CVE-2022-32149 具体描述 攻击者可以通过制作一个Accept-Language报头来导致拒绝服务。 具体的原因是,在解析这个Accept-L…

数据结构__顺序表和单链表

顺序表的改进 问题: 1. 中间/头部的插入删除,时间复杂度为O(N) 2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了…

C++——位图和布隆过滤器

在C中,哈希这种思想的应用场景有很多,位图就是其中的一种。 位图 位图:位图是一种哈希思想的产物,可以通过它来对数据进行快速的查找的方法,在位图中,有2种状态来表示在或者不在,即1/0。 位图…

大数据系列 | Kafka架构分析及应用

大数据系列 | Kafka架构分析及应用 1. Kafka原理分析2. Kafka架构分析3. Kafka的应用3.1. 安装Zookeeper集群3.2. 安装Kafka集群3.3. 生产者和消费者使用3.3.1. 生产者使用3.3.1. 消费者使用 4. Kafka Controller控制器 1. Kafka原理分析 Kafka是一个高吞吐量、 持久性的分布式…

宏的使用(C语言详解)

在写一个代码生成可执行文件的过程需要经过编译和链接&#xff0c;编译又要经过三部&#xff1a;预处理&#xff0c;编译&#xff0c;汇编。 #define定义的变量和宏就是在预处理阶段会处理的。 一个简单的宏定义&#xff1a; #include<stdio.h>; #define Max(a,b) a>…

CTF之GET和POST

学过php都知道就一个简单传参&#xff0c;构造payload:?whatflag得到 flag{3121064b1e9e27280f9f709144222429} 下面是POST那题 使用firefox浏览器的插件Hackbar勾选POST传入whatflag flag{828a91acc006990d74b0cb0c2f62b8d8}

Web APIs简介 Dom

JS的组成 API API 是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于软件或硬件得以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细节 简单理解&#xff1a;API是给程序员提供的一种工具&#xff0c;以便能更轻松的实现…

【白菜基础】初识蛋白质组学

这篇文章写得很详细&#xff0c;可以仔细阅读&#xff1a;干货&#xff01;5000字基于质谱的蛋白质组详细总结|蛋白质组 1. 蛋白质组学的概念 蛋白质组&#xff08;Proteome&#xff09;&#xff1a;一个细胞或组织由整个基因组表达的全部蛋白质。 蛋白质组学&#xff08;Pr…

Longan Pi 3H简约外壳分享

Longan Pi 3H简约外壳分享 因为购买了Longan Pi 3H&#xff0c;它用的是H618&#xff0c;我记得香橙派zero2w 也是这个芯片&#xff0c;不过我很喜欢sipeed的这个风格&#xff0c;特别好看&#xff0c;而且该有的都有&#xff0c;不说废话了&#xff0c;直接上图片和文件 链接…

redis群集有三种模式

目录 redis群集有三种模式 redis群集有三种模式 分别是主从同步/复制、哨兵模式、Cluster ●主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均…

微信小程序使用icon图标

原因&#xff1a; 微信小程序使用fontawesome库使用icon图标&#xff0c;网上有很多教程&#xff0c;按照网上说法制作&#xff0c;引入到微信小程序中&#xff0c;但是验证成功&#xff0c;只能使用部分图标&#xff0c;结果不尽如人意。后面使用阿里巴巴开源iconfont来使用ic…

Git入门实战教程之创建版本库

一、Git简介 Git是一个分布式版本控制系&#xff0c;分层结构如下&#xff1a; Git分为四层&#xff1a; 1、工作目录 当前正在工作的项目的实际文件目录&#xff0c;我们执行命令git init时所在的地方&#xff0c;也就是我们执行一切文件操作的地方。 2、暂存区 暂存区是…

拾光坞N3 ARM 虚拟主机 i茅台项目

拾光坞N3 在Dcoker部署i茅台案例 OS&#xff1a;Ubuntu 22.04.1 LTS aarch64 cpu&#xff1a;RK3566 ram&#xff1a;2G 部署流程——》mysql——》java8——》redis——》nginx mysql # 依赖 apt update apt install -y net-tools apt install -y libaio* # 下载mysql wg…

分享10个免费高可用的GPT3.5和4.0网站并做功能测试【第一个】

1.介绍 网址&#xff1a;直接点&#xff1a;aicnn 或者 www.aicnn.cn 基于ChatGPT可以实现智能聊天、绘画生成、高清文本转语音、论文润色等多种功能&#xff0c;基于sd和mj实现的绘画功能&#xff0c;下面是功能测试&#xff1a; 博主从 1.GPT3.5是否完全免费/是否限制频率、…

【前沿模型解析】潜在扩散模 1 | LDM第一阶段-感知图像压缩总览

文章目录 0 开始~1 感知压缩的目的2 自回归编码器-解码器生成模型一览2.1 AE 自编码器2.2 VAE 变分自编码器2.3 VQ-VAE2.4 VQ-GAN 3 代码部分讲解总览 0 开始~ 从今天起呢&#xff0c;我们会剖析LDM&#xff08;潜在扩散模型&#xff09; 从去年开始&#xff0c;大量的生成模…

国内ChatGPT大数据模型

在中国&#xff0c;随着人工智能技术的迅猛发展&#xff0c;多个科技公司和研究机构已经开发出了与OpenAI的ChatGPT类似的大型语言模型。这些模型通常基于深度学习技术&#xff0c;尤其是Transformer架构&#xff0c;它们在大量的文本数据上进行训练&#xff0c;以理解和生成自…

Thinkphp5萤火商城B2C小程序源码

源码介绍 Thinkphp5萤火商城B2C小程序源码&#xff0c;是一款开源的电商系统&#xff0c;为中小企业提供最佳的新零售解决方案。采用稳定的MVC框架开发&#xff0c;执行效率、扩展性、稳定性值得信赖。 环境要求 Nginx/Apache/IIS PHP5.4 MySQL5.1 建议使用环境&#xff…

APP渗透总结

APP渗透测试和Web渗透测试本质上没有区别。目前APP应用主要分为Android和IOS&#xff0c;但是由于苹果的IOS操作系统不开源&#xff0c;所以一般对IOS系统进行渗透和反编译会比较困难&#xff0c;所以一般对APP系统进行渗透测试都是对Android进行测试。 目录 安装安卓模拟器抓…