042、PCIE BAR空间类型与映射
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到底是个啥简单说BARBase 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~bit10032位地址空间现在也少见了bit2~bit11064位地址空间大内存设备必备bit11保留位别用64位空间要占两个BAR寄存器比如BAR0和BAR1合成一个64位地址。这里有个坑如果你设计的是64位设备BAR0的bit2~bit1必须是10而且BAR1会被占用即使你只用一个BAR。写驱动时要注意别把BAR1当独立资源用了。预取与非预取Memory空间还有个bit3是预取使能位。如果设备支持预读比如读操作没有副作用可以提前缓存就设成1。这个位挺重要设对了能提升性能设错了可能出数据一致性问题。我们之前有个DMA设备读操作会清除缓冲区这种就不能设预取。结果硬件工程师设成了1导致缓存里的数据是旧的传出去的文件总带上一帧的残留数据。调了两天才发现是这个位的问题。系统如何分配BAR地址上电后系统开始枚举PCIE设备。看到BAR寄存器先往里面写全10xFFFFFFFF然后读回来。硬件会把可分配的空间位返回1固定位返回0。比如你需要16MB内存空间就会返回0xFF000000高8位可分配。系统根据这个信息在内存或IO空间里找一块空闲区域把基地址写回BAR。这个过程叫资源分配在Linux里是pci_assign_resource()干的活。驱动里怎么用// 获取资源respci_resource_start(pdev,bar_num);// 这里bar_num是0~5lenpci_resource_len(pdev,bar_num);flagspci_resource_flags(pdev,bar_num);// 检查类型if(flagsIORESOURCE_IO){// IO空间得用pci_iomap()portpci_iomap(pdev,bar_num,len);// 访问要用专门的IO函数inb(portoffset);}elseif(flagsIORESOURCE_MEM){// Memory空间推荐用pci_iomap()addrpci_iomap(pdev,bar_num,len);// 直接指针访问就行regreadl(addroffset);}别直接用ioremap()pci_iomap()会帮你处理类型判断。还有用完记得pci_iounmap()。几个实际踩过的坑坑164位地址对齐64位BAR的地址必须8字节对齐。我们有一次设计BAR0设了0x0000000C32位空间但实际需要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理解成设备的“通信地址表”设计时仔细点调试时耐心点大部分问题都能解决。