【PCIe中的BAR、MMIO、MMU、mmap函数与页表】

文章目录

在 PCIe 体系结构中,基地址寄存器(Base Address Register, 简称 BAR) 是实现"即插即用"和资源映射的核心机制。
在 PCIe 体系结构中,基地址寄存器(BAR) 的核心作用是实现 内存映射 I/O(MMIO) ,即让 CPU 像访问内存(DRAM)一样访问硬件设备的寄存器或存储空间。

BAR的内存地址分配问题

注意!!PCIe 规范限制:PCIe 规定 BAR 空间大小必须是 2 的幂次方(比如 4KB, 8KB, 16KB... 128KB)。你不能跟系统说"我只要 12 字节"。

具体实例:FPGA 加法器

假设你有一个 PCIe FPGA 卡,其内部实现了一个简单的"加法器"功能:

BAR 配置: 假设FPGA所需冗余大小为 128KB,怎么分配BAR地址呢?

硬件内部寄存器定义:

偏移 0x00:输入数字 A

偏移 0x04:输入数字 B

偏移 0x08:计算结果(Sum)

第一步:算出 128KB 需要多少个"焊死的 0"在计算机里

我们要先知道 128KB 到底占了多少个二进制位(Bit):

1 KB = 2 10 字节 = 1024 字节 1\text{ KB} = 2^{10}\text{ 字节} = 1024\text{ 字节} 1 KB=210 字节=1024 字节

128 KB = 128 × 1024 = 131 , 072 字节 128\text{ KB} = 128 \times 1024 = 131,072\text{ 字节} 128 KB=128×1024=131,072 字节

我们用 2 的幂次方来表示: 128 KB = 2 7 × 2 10 = 2 17 字节 128\text{ KB} = 2^7 \times 2^{10} = 2^{17}\text{ 字节} 128 KB=27×210=217 字节。

这意味着:128KB 的空间,需要整整 17 个二进制位(Bit 0 到 Bit 16)来做内部寻址。所以,fpga工程师在设计时,会把这个 BAR 寄存器的低 17 位全部死死地焊在地上(抹成 0),只留下高 15 位(Bit 17 到 Bit 31)可以读写。

第二步:开机全 1 探测(Host 的视角)

开机时,Host 大管家(cpu侧)往这个 BAR 寄存器里盲写全 1(0xFFFFFFFF),由于低 17 位焊死,寄存器里实际存下来的二进制是这样的:

bash 复制代码
高 15 位可写 (变1)        低 17 位焊死 (强制为0)
⬇️                       ⬇️
1111 1111 1111 1110  0000 0000 0000 0000

把这一串二进制每 4 位一组,转换成十六进制读回来,Host 读到的值就是:0xFFFE0000。

Host 拿到 0xFFFE0000 后一取反再加 1:

~0xFFFE0000 + 1 = 0x00020000(十六进制的 0x20000 换算成十进制正好是 131072,即 128KB)。

系统瞬间心领神会:"懂了,这家伙要 128KB 的领地。"

第三步:系统划领地(必须严格对齐)

既然低 17 位在硬件上只能是 0,系统在给它分派物理基地址(Base Address)时,分派的地址的低 17 位也必须全是 0。

换算成十六进制,17 个二进制零意味着什么?

每 4 个二进制零可以换成 1 个十六进制的 0。

17 个零 = 4 个完整的十六进制 0,还剩下 1 个二进制零。

这导致从右往左数第 5 个十六进制数,它的二进制最低位必须是 0(也就是说,第 5 位只能是偶数:0, 2, 4, 6, 8, A, C, E)。

符合规则的合法基地址示例:

0xF0000000 (合法,末尾是 0000,第 5 位是 0)

0xF0020000 (合法,末尾是 0000,第 5 位是 2)

0xF00A0000 (合法,末尾是 0000,第 5 位是 A)

不合法的基地址示例:

0xF0010000 (不合法! 第 5 位是 1,变成二进制尾数就不是 17 个零了,写进硬件会被强行抹成 0xF0000000 导致地址错位)。

第四步:最终的占地范围

假设系统在"内存保留区"里看中了 0xF0020000 这块空地,一把写进设备的 BAR 寄存器。

那么,这个 128KB(十六进制大小为 0x00020000)的硬件设备,在 MMIO 内存大地图上的绝对占地范围就是:

起始物理地址(Base):0xF0020000

结束物理地址(End):0xF003FFFF (即 起始地址 + 128KB - 1)

从此以后,CPU 只要访问 0xF0020000 到 0xF003FFFF 之间的任何一个房号,Host 都会心领神会地打包成 PCIe 快递,精准地送到这个 128KB 的设备手里!

真实映射:地址长什么样?

结合上一题我们为 128KB 空间分派的基地址 0xF0020000,此时 CPU 想要做加法,它的驱动代码在 MMIO 的世界里看到的绝对物理地址 是这样的:

MMIO与MMU

MMU 是一个真实的硬件设备(CPU内部的独立单元),而 MMIO 并不是一个设备,它是一种"让 CPU 访问设备的通信规则"。

MMIO(内存映射 I/O) 是怎么工作的?

硬件工程师在设计主板和总线时,把一整片连续的物理地址空间(比如 32 位系统下的顶部 1GB 空间,即所谓的 PCI Hole)强行划给了外设。

当各种 PCIe 设备( 如FPGA 加法器、显卡)插上电脑时,系统通过 BAR 寄存器 给它们在这个保留区里分派具体的起始地址(如 0xF0020000)。

从此,外设内部的控制寄存器,在物理上就变成了这段内存地址里的一个个"小房间"。

CPU 想给硬件发指令,不用任何特殊手段,直接往对应的内存地址写数据就行。

在没有 MMIO 之前,CPU 想访问内存条得用一套指令,访问网卡、键盘又得用另一套专用的硬件接口和指令(即传统的 IO 端口空间)。外设一多,CPU 就会被无数的专有指令烦死。

于是,MMIO 诞生了。它的核心思想是:视硬件如内存,以地址通天下。

MMU(内存管理单元)

MMIO 规划好了硬件的物理地址,但现代操作系统(Linux/Windows)出于安全和多任务的考虑,是绝对不允许软件直接去摸物理地址的。所有程序看到的都是虚幻的虚拟地址(Virtual Address, VA)。

这时候,坐在 CPU 内部核心通道上的 MMU 就必须全天候上岗了。MMU 戴着三顶重要的帽子,行使三大核心职能:

  1. 终极翻译官:虚拟地址 → \rightarrow → 物理地址程序员在驱动里申请了一块虚拟地址(比如 0xFFFF8880...),当 CPU 试图往这个地址写数据时:MMU 瞬间拦截该请求。MMU 翻开保存在内存里的页表(Page Table)(为了加速,还会查自己随身带的高速缓存 TLB)。查到该虚拟地址对应的是 MMIO 物理地址 0xF0020000,脱掉虚拟外衣,把物理地址发射到主板总线上。
  2. 铁面安保:权限与隔离控制每个虚拟地址在页表里都带着"权限标签"(可读?可写?只能内核访问?)。如果一个普通用户态的软件(比如一个恶意木马)试图去读写 FPGA 加法器的 MMIO 地址,MMU 查表发现该地址只有内核驱动才能碰,会直接一巴掌把请求拍死,向操作系统触发一个 Page Fault(缺页异常/段错误),把流氓软件直接杀掉。
  3. 特效指挥官:Cache 策略控制(对 MMIO 至关重要!)这是 MMU 和 MMIO 产生交集最精妙的地方。CPU 读写普通内存条时,喜欢自作聪明把数据缓存到自己的 Cache 里以加速访问。但硬件寄存器的状态是实时变化的(比如加法器的结果,或者网卡刚收到的网络包),绝对不能被 CPU 缓存!当内核调用 ioremap() 把 MMIO 地址映射进虚拟世界时,MMU 会在页表里把这块区域死死标记为 Device Memory(设备内存 / Non-Cacheable 不可缓存)。只要 CPU 碰这段地址,MMU 就会严厉警告 CPU:"收起你的 Cache 缓存!必须每一铲子都实实在在去物理总线上读写!"

黄金搭档的完美合奏:数据的一生

我们把一个具体的场景穿起来:驱动程序往 FPGA 加法器(BAR基地址 0xF0020000)的输入寄存器 A(Offset 0x00)写入数字 5。

bash 复制代码
[ 1. 软件驱动层 ]  -> 写数据到虚拟地址 (例如 0xFFFF888000A10000)
         │
         ▼
[ 2. CPU内部 MMU ] -> 拦截!查页表发现是 MMIO 区域,强行关闭 Cache 缓存!
         │           -> 将虚拟地址翻译成【MMIO物理地址 0xF0020000】
         ▼
[ 3. 芯片内部总线 ] -> 物理地址 0xF0020000 欢快地流出 CPU 芯片
         │
         ▼
[ 4. PCIe Host ]   -> 拦截!发现该物理地址落在 FPGA 的 BAR 领地内
         │           -> 打包成 PCIe 专用快递 (TLP 事务层包) 发射出去
         ▼
[ 5. FPGA 硬件卡 ] -> 接收 TLP 包,内部地址解码器识别出 Offset = 0x00
                     -> 成功把数字 5 焊死在"输入数字 A"的硬件寄存器里!

MMIO、mmap、MMU 和 页表 四个概念

MMIO机制(内存映射 I/O)

指的是"把硬件寄存器伪装成内存地址来访问"的这一整套设计思想。它的对手是 PMIO(端口 I/O 机制)。

mmap(内存映射函数) ------ 属于【操作系统软件阵营】

它是操作系统提供给应用软件的一个工具(系统调用),用来向系统申请:"请帮我把某个硬件的 MMIO 物理地址,映射到我自己的虚拟世界里。"

页表(Page Table) ------ 属于【数据媒介阵营】

它是一本躺在内存条(RAM)里的对照字典。它由软件(mmap)负责填写,用来记录"哪段虚拟地址对应哪个 MMIO 物理地址",是软硬件沟通的剧本。页表的一页是4KB。

MMU(内存管理单元) ------ 属于【CPU 核心硬件阵营】

它是 CPU 内部的一个硬核执行机器。它不负责规划,只负责执行------疯狂地去读页表,把 CPU 扔出的虚拟地址瞬间翻译成总线认的物理地址。

谈谈曾经做过的RFvu3p芯片XDMA驱动

这是一款带PCIe接口的RFsoc芯片,通过PCIe接口来实现ADC/DAC控制,以及高带宽的数据收发。

我实际用到四个bar

BAR0:/dev/xdma0_user,mmap 映射寄存器读写

BAR1:xdma0_h2c_0、xdma0_h2c_1 命令 / 数据下发

BAR2:xdma0_c2h_0、xdma0_c2h_1 命令应答 / 采集数据上送

BAR3:xdma0_events_0、xdma0_events_1 中断事件读取

相关推荐
传感器与混合集成电路2 小时前
伺服数据采集控制模块系统集成实战手册:接口设计要点、上电顺序与开发环境配置全解析
fpga开发
xxLearn10 小时前
Vivado 2025.2 下载程序时提示:“ERROR : invalid command name ps7_init“
fpga开发
科恒盛远19 小时前
【无标题】
fpga开发·硬件工程·信号处理
千寻xun1 天前
一、理论篇-NVME协议学习笔记
笔记·学习·fpga开发·nvme ssd·nvme协议
nuoxin1142 天前
HR4988替代A4988-富利威
网络·人工智能·嵌入式硬件·fpga开发·dsp开发
一口一口吃成大V2 天前
vivado的bit 和 bin的区别
fpga开发
尤老师FPGA3 天前
HDMI数据的接收发送实验(十八)
fpga开发
北京青翼科技3 天前
青翼科技 JFM7K325T FPGA+FT-M6678 DSP 的全国产化信号处理平台丨FPGA开发板
fpga开发·数据采集卡·fmc子卡·fpga开发板·ad采集卡·图像处理卡·dsp信号处理
zlinear数据采集卡4 天前
从0到1硬核拆解:工业级数据采集卡的隔离设计与Modbus通信实战
arm开发·单片机·嵌入式硬件·fpga开发·开源