在初学 PCIe System (一) - PCIe介绍及其配置空间有提到可以从PCIe设备的配置空间(Configuration Space)获取许多重要的资讯,那在CPU中,访问配置空间常用的两种方式如下:
Programmed Input/Output (PIO)
PIO 是由CPU主动向Device 获取资料 ,CPU 会等待Device资料传输完后才去做其他事情。使用PIO会占住CPU资源,会让系统变得低效,因此此技术已几乎被直接记忆体访问(DMA)所取代。
这边简单介绍一下PIO的作法,首先每个PCI Function的配置空间中和PCI相容的前256 Bytes会映射到一个第三个空间:配置地址空间(configuration address space)。因为PCI Function最多会有(2^8) * (2^5) * (2^3) = 65536个,每个Function都会映射256Byte的配置空间,因此配置地址空间的大小为 65536 * 256 Bytes = 16 MB。
CPU 通过主桥(Host Bridge)中的 IO 映射地址端口(Address Port )和资料端口(Data Port)进行索引来间接访问 PCI 配置空间。
地址端口(Address Port )位于 IO 地址 CF8h-CFBh,是一个大小为32 bits的暂存器,需要填入CONFIG_ADDRESS,其格式如下
Bit 31 | Bits 30-24 | Bits 23-16 | Bits 15-11 | Bits 10-8 | Bits 7-10 |
Enable Bit | Reserved | Bus Number | Device Number | Function Number | Register Offset |
Bit 31表示Enable bit,如果不设起来就不会有作用。 (1<<31) = 0x80000000,所以通常会直接设起来,直接做 | 0x80000000的动作
Bit 8-23 是上一章节提到的PCIe Device Address是由 Bus Number+Device Number+Function Number所组成的
Bit0-7这边就填想到访问的PCIe 配置空间的Offset,做多就是0x00-0xff: 256 Bytes
所以CONFIG_ADDRESS 的格式公式如下:
0x80000000 | bus << 16 | device << 11 | function << 8 | offset
CONFIG_ADDRESS 指定完需要访问的配置地址,会对资料端口映射的位置 CFCh-CFFh的CONFIG_DATA 暂存器的访问生成配置访问,将资料传入或传出 CONFIG_DATA 暂存器。
简单来说,例如我们想要读取"bus0 dev0 fun0, offset 0x00"的位置,那他的CONFIG_ADDRESS 就是0x80000000, 我们把它写入地址端口0xCF8,那该位置的资料就可以从资料端口0xCFC读到资料
这边用Linux的程式码early.c « pci « x86 « arch - kernel/git/stable/linux.git - Linux kernel stable tree为范例,读取的话动作是将想要读取的位置填到CONFIG_ADDRESS,该位置数据就会出现在CONIG_DATA
写入的话,动作差不多,就是将想要写入的位置填到CONFIG_ADDRESS,再把值填到CONIG_DATA就好
*另外这边可以发现,透过PIO只能读写配置空间中的前256Bytes,所以其余的部分的读取需要透过DMA。
Direct Memory Access (DMA)
DMA 是通知Device要做什么,Device会自己去写內存。所以DMA 可以一次获取大量资料,而且不会占住CPU资源。
所以我们可以直接透过內存去访问PCI Configuration,地址公式如下:
BaseAddress + bus << 20 | device << 15 | function << 12 | offset
这边给offset的位置大小为3 Bytes
另外BaseAddress 每个platform都不一样,需要查Spec
有了地址之后,可以直接用指标的方式取的资料,例如地址为Addr:
unsigned int data = *Addr;