第4章 地址空间类型:内存空间、IO空间、配置空间的区别与联系
做BAR调试,说白了就是跟地址空间打交道。但很多新手一上来就栽跟头——分不清这几种空间,配出的BAR要么跟别人撞车,要么设备根本不认。我刚开始搞PCIe时也踩过这个坑,直到有一次在FPGA验证平台上,一个配置空间读出来全0,查了三天才发现是地址映射搞错了。
这一章我们把内存空间、IO空间、配置空间这三兄弟拎出来,掰开揉碎了讲。它们有啥不同?实际调试中怎么区分?我建议你先把这个逻辑理清,后面分配BAR地址时才不会手忙脚乱。
4.1 内存空间
内存空间,你想想看,这名字听起来就很像我们平时用的DDR。没错,它确实是跟CPU内存地址空间最接近的那一套映射。PCIe设备可以通过BAR申请一段内存空间,CPU直接用load/store指令读写——速度最快,延迟最低。
我记得有一次调试一个NVMe SSD,它的BAR0申请了8MB内存空间。CPU写内存就像写普通数组一样简单。但要注意一点:内存空间支持预取(prefetchable)和非预取两种模式。预取意味着我可以从里面随便读,读多了也不会破坏状态。非预取则不行,读一次就可能会改变内部寄存器数值。
4.2 IO空间
IO空间是个老古董了。最早在x86架构上,为了兼容ISA设备搞出来的。地址范围只有可怜的64KB(从0x0000到0xFFFF),而且读写必须严格串行——不能乱序,不能合并,不能预取。
说白了,IO空间就是“慢但安全”。你在调试老式板卡或者Legacy设备时可能会碰到它。我几年前在一个工控项目里见过一个串口扩展卡,它的BAR类型强制设成了IO空间。当时我差点没反应过来——现在的PCIe设备谁还用IO空间啊?但客户就是不肯换。
4.3 配置空间
配置空间是最特殊的一个。它既不是CPU直读的内存,也不是IO端口。它是PCIe总线自己的“元数据区”。每个PCIe功能(Function)都有256字节的固化配置空间(如果支持Extended Configuration Space,那可以到4KB)。
为什么说它特殊?因为CPU访问配置空间不是通过普通地址,而是通过两个IO端口(CF8h/CFCh)或者通过内存映射的配置空间(MMCFG)。你想想看,BIOS或操作系统要枚举设备、读取厂商ID、写BAR寄存器……全靠这个配置空间。
所以我们在BAR调试时,最核心的战场就是配置空间。BAR寄存器位于配置空间偏移0x10~0x24的位置(每个BAR占4字节,共6个)。你写进去的值,决定了设备能应答哪个地址范围。
4.4 三者的对比
| 对比项 | 内存空间 | IO空间 | 配置空间 |
|---|---|---|---|
| 寻址范围 | 32位或64位,巨大 | 16位,最大64KB | 通过Bus/Dev/Fun + 偏移,设备内部256B |
| 访问方式 | CPU load/store 直接读写 | 专用IO指令(in/out) | CF8h/CFCh IO端口 或 MMCFG |
| 预取支持 | 支持(分prefetchable和non-prefetchable) | 不支持预取 | 概念上不适用 |
| 缓存/写合并 | 允许(需UCM/UC/WC等属性控制) | 完全不允许 | 不允许(配置读写必须串行) |
| 典型应用 | 显存、DMA缓冲区、大数据量寄存器 | 老式状态端口、Legacy设备 | 设备枚举、BAR编程、错误报告、能力列表 |
| 调试工具 | lspci -vv 看BAR地址,直接/dev/mem读写 | lspci -xxx 读配置空间,in/out指令 | setpci 读写配置空间,逻辑分析仪抓CF8/CFC |
4.5 它们是怎么联系在一起的?
上面这张表格已经说清楚了区别。但它们之间是有紧密联系的——BAR寄存器就住在配置空间里。配置空间中的BAR字段告诉系统:我这个设备想要多大一块地址空间、要什么类型(内存还是IO)。然后BIOS或操作系统根据系统整体地址布局,给这个BAR写入一个基地址(比如0xE0000000)。从此以后,CPU访问这个地址范围内的内存空间,就会命中这个设备。
嗯,这里要注意:配置空间本身也是通过IO空间(CF8h/CFCh)或内存映射(MMCFG)来访问的。实际操作中,我们调试BAR冲突时,第一步就是读配置空间,看BAR寄存器里的值是不是预期的。第二步再尝试读写内存/IO空间,看设备是否真的响应。
4.6 实际案例:错误地混用空间类型
讲一个我踩过的坑吧。有次调试一块高速数据采集卡,它需要向主机传输大量数据。我偷懒把BAR类型设成了IO空间。心想64KB够用了,IO空间还简单。结果一跑起来,吞吐量惨不忍睹——IO空间每次只能单字节/双字节读写,CPU还加了串行化屏障,速度非常慢。客户直接说“你这卡比USB 2.0还慢”。
后来我把BAR改成内存空间(prefetchable),配合DMA引擎,速度立刻翻了几十倍。从此以后我再也不用IO空间做数据传输了。所以请记住:IO空间只适合控制寄存器或者状态查询,数据搬运用内存空间。