042、PCIE BAR空间类型与映射
上周调一块FPGA的PCIE板卡,系统里死活认不出设备。查了半天发现BAR配置错了,FPGA那边映射的是Memory空间,驱动里却按IO空间来访问,直接导致枚举失败。今天咱们就聊聊BAR空间那点事,这问题踩坑的人不少。
从一次枚举失败说起
当时用lspci看设备,能识别到Vendor ID和Device ID,但BAR寄存器全是0。第一反应是硬件链路有问题,但LTSSM状态又是正常的。后来打开配置空间dump一看,发现BAR0的值是0xFFFF0001——高位全1,说明这块空间需要64位地址;最低位是1,表示这是IO空间。而我们的FPGA设计里,这块区域实际是Memory映射区域,应该设置成0xFFFF0000才对。
就这一位的差别,系统在分配资源时就乱了套。IO空间和Memory空间的访问机制完全不同,弄混了自然没法正常通信。
BAR到底是个啥
简单说,BAR(Base Address Register)就是PCIE设备在系统内存或IO空间里的“门牌号”。主机通过BAR知道该往哪个地址发数据,设备也通过BAR知道主机想访问自己的哪块资源。每个PCIE设备最多可以有6个BAR,在配置空间里占着0x10到0x24的位置。
你写驱动的时候,通过pci_resource_start()拿到的那个地址,其实就是系统给BAR分配好的基地址。但在这之前,系统得先知道你这设备到底要什么类型的地址空间。
两种空间类型:IO和Memory
最低位(bit0)是类型标识位:0是Memory空间,1是IO空间。这个位是只读的,硬件设计时就得定死,软件改不了。
IO空间现在用的少了,主要是x86架构的历史遗留。它的地址是32位的,访问得用专门的IO指令(in/out)。现在新设计基本不用这个,但有些老芯片或者特定场景还得兼容。
Memory空间是主流,又分三个子类型:
- bit2~bit1=00:32位地址空间,现在也少见了
- bit2~bit1=10:64位地址空间,大内存设备必备
- bit1=1:保留位,别用
64位空间要占两个BAR寄存器,比如BAR0和BAR1合成一个64位地址。这里有个坑:如果你设计的是64位设备,BAR0的bit2~bit1必须是10,而且BAR1会被占用(即使你只用一个BAR)。写驱动时要注意,别把BAR1当独立资源用了。
预取与非预取
Memory空间还有个bit3是预取使能位。如果设备支持预读(比如读操作没有副作用,可以提前缓存),就设成1。这个位挺重要,设对了能提升性能,设错了可能出数据一致性问题。
我们之前有个DMA设备,读操作会清除缓冲区,这种就不能设预取。结果硬件工程师设成了1,导致缓存里的数据是旧的,传出去的文件总带上一帧的残留数据。调了两天才发现是这个位的问题。
系统如何分配BAR地址
上电后,系统开始枚举PCIE设备。看到BAR寄存器,先往里面写全1(0xFFFFFFFF),然后读回来。硬件会把可分配的空间位返回1,固定位返回0。比如你需要16MB内存空间,就会返回0xFF000000(高8位可分配)。
系统根据这个信息,在内存或IO空间里找一块空闲区域,把基地址写回BAR。这个过程叫资源分配,在Linux里是pci_assign_resource()干的活。
驱动里怎么用
// 获取资源res=pci_resource_start(pdev,bar_num);// 这里bar_num是0~5len=pci_resource_len(pdev,bar_num);flags=pci_resource_flags(pdev,bar_num);// 检查类型if(flags&IORESOURCE_IO){// IO空间,得用pci_iomap()port=pci_iomap(pdev,bar_num,len);// 访问要用专门的IO函数inb(port+offset);}elseif(flags&IORESOURCE_MEM){// Memory空间,推荐用pci_iomap()addr=pci_iomap(pdev,bar_num,len);// 直接指针访问就行reg=readl(addr+offset);}别直接用ioremap(),pci_iomap()会帮你处理类型判断。还有,用完记得pci_iounmap()。
几个实际踩过的坑
坑1:64位地址对齐
64位BAR的地址必须8字节对齐。我们有一次设计,BAR0设了0x0000000C(32位空间),但实际需要64位地址。系统分配时按8字节对齐,地址总是错位,导致访问异常。改成0x00000008就好了。
坑2:空间大小设置
BAR申请的空间大小必须是2的幂,而且自然对齐。比如你需要3MB空间,得申请4MB。我们有个设计要5MB,申请了8MB,但硬件只解码5MB,结果访问第6MB地址时设备没响应,系统以为访问错误直接panic了。
坑3:预取位设置
前面说的DMA设备预取问题是个典型。现在我们的规则是:除非确定读操作无副作用,否则一律不设预取。宁可性能差点,也别出数据错误。
给硬件工程师的建议
画原理图的时候就想好BAR规划。哪个BAR做什么用,用32位还是64位,要不要预取,这些在RTL设计阶段就得定下来。最好做个表格,把每个BAR的类型、大小、功能都列清楚,给驱动工程师参考。
FPGA做PCIE端点时,检查一下BAR配置寄存器的实现。有些IP核默认设置可能不适合你的场景,特别是类型位和预取位,一定要根据实际需求配置。
给驱动工程师的建议
拿到新硬件,先用lspci -vvv看看BAR分配情况。重点关注类型对不对,大小合不合理。如果BAR全是0或者显示“disabled”,大概率是硬件配置有问题。
写初始化代码时,别假设BAR类型。一定要用pci_resource_flags()判断后再操作。我们吃过亏:同一款芯片,不同版本硬件改了BAR类型,驱动没判断直接按Memory访问,新板子全报错。
最后,如果设备有多个功能(Multi-function),每个功能的BAR是独立的。别以为Function 0配置好了,Function 1就能直接用。
调试BAR问题,本质是在调试硬件和软件的约定。两边对不上,通信就失败。把BAR理解成设备的“通信地址表”,设计时仔细点,调试时耐心点,大部分问题都能解决。