往期内容
本文章相关专栏往期内容,PCI/PCIe子系统专栏:
- 嵌入式系统的内存访问和总线通信机制解析、PCI/PCIe引入
Uart子系统专栏:
- 专栏地址:Uart子系统
- Linux内核早期打印机制与RS485通信技术
– 末片,有专栏内容观看顺序interrupt子系统专栏:
- 专栏地址:interrupt子系统
- Linux 链式与层级中断控制器讲解:原理与驱动开发
– 末片,有专栏内容观看顺序pinctrl和gpio子系统专栏:
专栏地址:pinctrl和gpio子系统
编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用
– 末片,有专栏内容观看顺序
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有专栏内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有专栏内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有专栏内容观看顺序
目录
- 往期内容
- 1.访问PCI/PCIe设备的流程
- 1.1 PCI/PCIe设备的配置信息
- 1.2 主机读取设备配置信息、分配空间
- 1.3 CPU地址空间和PCI/PCIe地址空间怎么转换?
- 1.4 主机像读写内存一样访问设备
- 2.PCI设备的访问方法_非桥设备(type0)
- 2.1 硬件结构
- 2.2 PCI本地总线信号
- 2.3 访问PCI设备
- 2.3.1 PCI设备的地址空间(配置空间)
- 2.3.2 配置过程
- 配置事务的标志:
- 2.3.3 示例:配置PCI Agent设备
1.访问PCI/PCIe设备的流程
1.1 PCI/PCIe设备的配置信息
PCI/PCIe设备上有配置空间(配置寄存器),用来表明自己"需要多大的地址空间"。
注意,这是PCI/PCIe地址空间。
1.2 主机读取设备配置信息、分配空间
主机上的程序访问PCI/PCIe设备,读出配置信息。
分配地址空间:注意,分配的是PCI/PCIe地址空间。
把地址空间首地址写入设备。
1.3 CPU地址空间和PCI/PCIe地址空间怎么转换?
假设CPU发出的addr_cpu,是用来方位PCI设备的,转换关系为:
addr_pci = addr_cpu + offset
在PCI/PCIe控制器中,有某个寄存器,有来保存offset值。
1.4 主机像读写内存一样访问设备
示例代码如下:
volatile unsigned int *p = addr_cpu;
unsigned int val;
*p = val; /* 写, 硬件会把addr_cpu转换为addr_pci去写PCI/PCIe设备 */
val = *p; /* 读, 硬件会把addr_cpu转换为addr_pci去读PCI/PCIe设备 */
CPU想要读取I2C设备上的某个地址的值:
- 主芯片要发出一个 start 信号
- 然后发出一个设备地址(用来确定是往哪一个芯片写数据),方向(读/写,0 表示写,1 表示读)
- 从设备回应(用来确定这个设备是否存在),然后就可以传输数据
- 从设备发送一个字节数据给主设备,并等待回应
- 每传输一字节数据,接收方要有一个回应信号(确定数据是否接受完成),然后再传输下一个数据。
- 数据发送完之后,主芯片就会发送一个停止信号。
像发出start信号、发出设备地址,都是CPU得去设置I2C控制器的某个寄存器才能让I2Ci控制器发出
而反观PCI/PCIe,CPU只需要发出addr_cpu,经过PCI控制器进行地址空间转换addr_pci,就可以去间接访问到地址为addr_pci的PCI设备的值。
2.PCI设备的访问方法_非桥设备(type0)
[文件下载点击此处](https://www.yuque.com/yeke5166226/uf8303/coshct9wr1b80pgr?singleDoc# 《嵌入式系统的内存访问和总线通信机制解析、PCI/PCIe引入》)
2.1 硬件结构
PCI系统框图:
addr_cpu地址转换为addr_pci地址后,那么多个设备,谁来响应这个addr_pci地址(这个地址是哪个设备的)?
- 配置
a. 对于这些设备,首先得去read设备的配置空间,去知道它是哪类设备,以及要申请地址空间的大小
b. 给设备申请的地址空间进行分配,将分配的地址范围(addr_pci类型)写入PCI设备
比如上面的1号设备,要申请4k的地址空间,假设申请成功了,PCI控制器给它起始地址为0x00000000 00000000 00000000
00000000 — 0x00000000 00000000 00000000
FFFFFFFF(假设是32位的内存),那么假设addr_pci是0x00000000 00000000 00000000
00000110,这就能知道CPU发出的地址经过转换后是想要去访问1号设备
- 配置后就可以通过addr来去访问设备
而对于配置,得先去理解PCI本地总线信号
2.2 PCI本地总线信号
主要分为6类:
类别 | 信号 |
---|---|
系统引脚 | CLK:给PCI设备提供时钟 RST#:用于复位PCI设备 |
地址/数据引脚 | AD[31:00]:地址、数据复用 C/BE[3:0]:命令或者字节使能 PAR:校验引脚 |
接口控制 | FRAME#:PCI主设备驱动此信号,表示一个传输开始了、进行中 IRDY#:Initiator ready, 传输发起者就绪,一般由PCI主设备驱动此信号 TRDY#:Target ready,目标设备驱动,表示它就绪了 STOP#:目标设备驱动,表示它想停止当前传输 LOCK#:锁定总线,独占总线,有PCI桥驱动此信号 IDSEL:Initialization Device Select,配置设备时,用来选中某个PCI设备 DEVSEL#:Device Select,PCI设备驱动此信号,表示说:我就是你想访问的设备 |
仲裁引脚 | REQ#:申请使用PCI总线 GNT#:授予,表示你申请的PCI总线成功了,给你使用 |
错误通知引脚 | PERR#:奇偶校验错误 SERR#:系统错误 |
中断引脚(可选) | INTA#、INTB#、INTC#、INTD# |
- AD是传输addr还是data?
- 未配置PCI设备地址空间时又该如何去选中设备进行配置访问操作?
2.3 访问PCI设备
在硬件结构中已经提到了访问过程
- CPU发出地址addr_cpu
- PCI桥把addr_cpu转换为addr_pci
- PCI总线上所有设备都检测addr_pci地址,发现它属于某个设备的地址,该设备就负责完成此传输
步骤:
- 访问配置空间 – addr_pci
- 访问内存空间/IO空间 – data
2.3.1 PCI设备的地址空间(配置空间)
PCIe设备的配置空间(Configuration Space)是用于配置和管理PCIe设备的专用地址空间。每个PCIe设备都包含一个256字节(或扩展至4KB的配置空间),用于存储设备的各种信息,如设备标识、状态、控制寄存器、基址寄存器(BAR),以及与设备操作相关的其他配置信息。
- 作用:配置空间主要用于系统识别和配置PCIe设备,操作系统可以通过访问该配置空间来获取设备的基本信息,配置设备的工作参数,并控制设备的某些功能。
- 结构:PCIe设备的配置空间遵循PCI规范,前64字节是标准的PCI配置头,包含如设备ID、厂商ID、类代码等基本信息;其余部分包括状态寄存器、控制寄存器、基址寄存器(BAR)、扩展功能寄存器等。
围绕:怎么去配置PCI设备,使其确定自己的地址范围?
对于一个PCI设备,可以通过IDSEL来选中它,去读取它的配置寄存器进而去设置它的配置寄存器,对于一个PIC硬件设备的配置寄存器有256个字节,它分为64 byte的头标区(如上图中所示,固定不变)和192 byte 的设备关联区(标准扩展),标准扩展的寄存器组的第一个寄存器中的capabilities pointer字段保存的地址指向下组标准扩展寄存器的首寄存器。也就是说从0x100往后的配置空间是IP厂商自己设计,需要在每组扩展寄存器中的第一个寄存器里定义Next Capablity offset,该字段保存的地址将指向下一处扩展寄存器组的首寄存器。配置空间相关字段介绍:
- Device ID和Vendor lD(只读)
Vender ID代表PCI设备的生产厂商,Device ID代表这个厂商所生产的具体设备。Device |D和Vendor
|D是区分不同设备的关键,OS和UEFI在很多时候就是通过匹配他们来找到不同的设备驱动(Class
Code有时也起一定作用)。为了保证其唯一性,Vendor ID应当向PCI特别兴趣小组(PCI SIG)申请而得到。
- BAR寄存器
PCle配置空间中从地址0x10开始的6个寄存器(EP),用于存储pcie设备在PCle域的基地址、基址空间大小等属性。
这些PCI总线地址空间需要在初始化时就映射为存储器域的存储器地址空间,方便处理器访问。系统软件对PCI总线进行配置时,首先获得BAR寄存器的初始化信息,之后根据处理器系统的配置,将合理的基地址写入到响应的BAR寄存器中,这个过程在BIOS运行阶段和OS启动阶段完成。系统软件还可以使用该寄存器获得PCI设备使用的BAR空间的长度,其方法是向BAR寄存器写入0XFFFFFFFF后在读取该寄存器。
每个PCI设备在BAR中描述自己需要占用多少地址空间,BI0S或者OS通过所有设备的这些信息构建一张完整的关系图,描述系统中资源的分配情况,然后在合理的将地址空间配置给每个PCI设备。BAR在bit0来表示该设备是映射到memory还是10,bar的bit0是readonlv的,也就是说,设备寄存器是映射到memory还是I0是由设备制造商决定的,其他人无法修改。
- Revision lD和Class Code寄存器 (只读)
Revision lD记载PCI设备的版本号,可以被认为是Device ID寄存器的扩展Class
Code寄存器记载PCI设备的分类,用于系统软件识别当前PCI设备的分类。该寄存器由Base Class Code、Sub class
code和interface三个字段组成。Base Class Code将PCI设备分类为显卡、网卡、PCI杯等设备:Sub class
code对这些设备进一步细分;inteface定义为编程接囗。
- Header Type寄存器(只读)
共8bit。系统软件使用该寄存器区分不同类型的PCI配置空间。
第7位为1表示当前PCI设备为多功能设备、为0表示单功能设备。
第6-0位表示当前配置空间的类型,为0表示该设备使用PCIAgent设备的配置空间,普通PCI设备都使用该配置头;为1表示使用PCI桥的配置空间,PCI桥使用这种配置头;为2表示使用cardbus桥片的配置空间。
- cache line size
记录host处理器使用的cache line长度
- Capabilities pointer
该寄存器存放capabiltes寄存器组的基地址,该寄存器组存放域PCI设备相关的扩展配置信息。该寄存器对PCI设备可选,但PCle总线规范要求其设备必须支持Capabilities结构,该寄存器组用于实现厂商自定义的PCle设备功能。
该寄存器存放Capabilities结构链表的头指针。在一个PCle设备中,可能含有多个Capability结构,这些寄存器组成一个链表。
- interript line寄存器
记录当前PCI设备使用的中断向量号,在系统软件对该设备配置时写入。给软件使用的,PCI设备本身不使用该寄存器。软件可以写入中断相关的信息,比如在Linux系统中,可以把分配的virq(虚拟中断号)写入此寄存器。软件完全可以自己记录中断信息,没必要依赖这个寄存器。
- Interrupt pin
这个寄存器保存PCI设备使用的中断引脚。PCI总线提供了四个中断引脚:INTA#、INTB#INTC#和INTD#。InterruptPin寄存器为1时表示使用INTA#引脚向中断控制器提交中断请求,为2表示使用INTB#,为3表示使用INTC#,为4表示使用INTD#。如果PCI设备只有一个子设备时,该设备只能使用INTA#;如果有多个子设备时,可以使用INTBD#信号。如果PCI设备不使用这些中断引脚,向处理器提交中断请求时,该寄存器的值必须为0。值得注意的是,虽然在PCIe设备中并不含有INTAD#信号,但是依然可以使用该寄存器,因为PCle设备可以使用INTx中断消息,模拟PCI设备的INTA~D#信号
Interrupt Pin取值 | 含义 |
---|---|
0 | 不需要中断引脚 |
1 | 通过INTA#发出中断 |
2 | 通过INTB#发出中断 |
3 | 通过INTC#发出中断 |
4 | 通过INTD#发出中断 |
5~0xff | 保留 |
- Class Code
这是只读的寄存器,它含有3个字节,用来表明设备的功能,它分为3部分
最高字节:表示"base class",用来表示它属于内存卡、显卡等待
中间字节:表示"sub-class",再细分一下类别
最低字节:用来表示寄存器级别的编程接口"Interface"
示例如下:Base Class为01h时,表示它是一个存储设备,但是还可以继续使用sub-class、Interface细分
2.3.2 配置过程
通过DISEL来选择某一个物理设备,这个设备可能有多种功能,比如PCI设备,最多可以有8种功能,对于每一种功能,都有不同的256字节的配置寄存器(配置空间)。那么,通过IDSEL是可以选择指定的PCI设备,但是该怎么去选择其中的某一种功能,再怎么去选择其中哪一个配置寄存器去设置配置空间?
1. 通过IDSEL选中某一个PCI设备
通过某个ADn去连接设备的IDSEL引脚,PCI设备被唯一选中,用于区分系统中的不同设备。
2. Function Number 和 Register Number 的选择
在PCI设备中,配置空间的访问可以使用AD[31:0]引脚来指定Function Number和Register
Number。
Function Number:PCI设备可能有多种功能,每种功能有独立的配置空间。在AD[31:0]地址引脚中,部分比特用于指定该设备的功能号。
- 在地址格式中,AD[10:8]这三位用于指定Function Number,也就是设备的功能选择。
Register Number:配置空间中的寄存器可以通过地址来选择。在地址总线的低位部分,比如AD[7:2]这六位,用来指定配置空间中的寄存器号。
所以,AD引脚的组合决定了访问的是哪一个功能的哪一个寄存器。
关于配置地址和普通内存地址的区分:
在PCI协议中,区分是否是在访问配置空间或普通内存空间,主要通过**C/BE#**信号来完成,而不是直接根据地址本身来区分。具体来说,PCI总线使用了两个地址空间:
- 内存地址空间(Memory Space)
- 配置地址空间(Configuration Space)
通过C/BE#信号来确定当前传输的是内存访问还是配置空间访问。当要访问配置空间时,必须设置为配置事务。
配置事务的标志:
-
FRAME#信号会指示这是一个PCI事务,第一拍时的**AD[31:0]**总线携带的确实是地址信息,而后面的传输则是数据。但配置事务与内存读写事务在功能上不同,通过
C/BE#
来表明具体的操作。- **C/BE#**信号:指定了是读还是写操作(例如配置读1010,配置写1011)。
因此,当发出配置事务时,在第一拍时发送配置空间的地址,后续的时钟周期中进行的是数据传输。
3. 如何确定读写寄存器?
通过C/BE#引脚来确定是读寄存器还是写寄存器。在PCI配置事务中,以下是一些关键操作:
C/BE# 引脚用于指定操作类型:读还是写。
- 1010表示配置读操作(configuration read)。
- 1011表示配置写操作(configuration write)。
配置空间读操作:首先读取PCI设备的配置空间,比如Base Address Registers(BARs),这可以帮助系统知道设备所申请的I/O空间和内存空间的大小。
配置空间写操作:之后可以使用写操作来设置配置寄存器的值,如中断线、中断号、命令寄存器等。
4. 配置完成后的数据传输
配置完成后,系统可以通过内存地址或I/O地址来访问该PCI设备。此时,
addr_pci
表示的就是设备的I/O地址或者内存空间地址。数据传输会根据PCI设备所申请的地址空间进行。
- 通过IDSEL引脚选中某个PCI设备。
- 使用AD[31:0]引脚发送地址信息,其中高位部分指定是哪个设备的哪个功能,低位部分指定配置空间中的哪个寄存器。
-
- PCI协议通过**C/BE#**来区分配置空间访问和内存地址访问,而不是直接通过地址的差异来区分。此外,AD引脚不仅指定功能号,还要指定配置寄存器的编号。
- 使用**C/BE#**信号来指定是否进行配置空间的读写操作(配置读1010,配置写1011)。
- 配置完成后,系统根据设备的申请来分配I/O空间或内存空间,然后可以使用
addr_pci
(分配给设备的I/O或内存地址)来进行数据传输。
2.3.3 示例:配置PCI Agent设备
PCI设备可以简单地分为PCI Bridge和PCI Agent:
- PCI Bridge:桥,用来扩展PCI设备,必定有一个Root Bridge,下面还可以有其他Bridge。
- PCI Agent:真正的PCI设备(比如网卡),是PCI树的最末端
怎么配置PCI Agent设备?
-
选中:通过IDSEL来选中某个设备
-
怎么访问配置空间:发起一个type 0的配置命令
- PCI设备最多有8个功能,每个功能都有自己的配置空间(配置寄存器)
- 你要访问哪个功能?哪个寄存器?发起
-
CPU读取配置空间的BAR,得知:这个PCI设备想申请多大空间
-
CPU分配PCI地址,写到PCI设备的BAR里