刚接触PCIE总线时,我被各种术语绕得头晕——BAR、ATU、TLP、RC、EP...这感觉就像第一次走进汽车修理厂,老师傅满嘴都是"节气门"、"ECU"、"正时链条"之类的黑话。但当我真正在嵌入式项目中用上PCIE后,发现它其实就像一条高速公路系统:BAR是收费站,ATU是导航系统,TLP就是跑在路上的车辆。
PCIE总线的精髓在于地址空间映射。想象你的SoC是个大商场,外设是入驻的店铺。BAR(Base Address Register)就像店铺门牌号,告诉顾客"本店位于A区3楼";ATU(Address Translation Unit)则是商场导览图,把顾客要找的"优衣库"转换成具体的"A3-205"坐标。在嵌入式开发中,我们经常需要把EP(Endpoint)设备的内存或寄存器空间映射到RC(Root Complex)主机的地址空间,这个过程就像给新店铺办理入驻手续。
提示:用
lspci -vvv命令就像打开商场的监控系统,能查看所有店铺的详细位置信息和经营状态。
第一次配置BAR时,我犯了个低级错误——以为它就是个普通寄存器。实际上,BAR更像是个智能合同:
以DWC_pcie控制器为例,它的6个BAR寄存器就像商场6层楼,每层可以:
c复制// 典型的BAR初始化代码示例
void configure_bar(void __iomem *base, int bar_num, u32 size, u32 flags)
{
u32 mask = ~(size - 1);
writel(0xFFFFFFFF, base + PCI_BASE_ADDRESS_0 + bar_num*4); // 探测大小
u32 val = readl(base + PCI_BASE_ADDRESS_0 + bar_num*4);
val &= mask; // 保留有效位
val |= flags; // 设置属性标志
writel(val, base + PCI_BASE_ADDRESS_0 + bar_num*4);
}
这个问题困扰了我整整两周。后来在调试网卡驱动时终于明白:
在ARM平台上,我遇到个典型问题:某设备的寄存器映射到Memory空间却用IO方式访问,结果性能差得像用吸管喝珍珠奶茶。后来用ioremap改成MMIO方式,吞吐量直接翻了8倍。
ATU就像个专业翻译官,把RC说的"我要访问0x80000000"转换成EP能听懂的"请操作你的0x1000寄存器"。在Xilinx Zynq平台上配置ATU时,我踩过这样的坑:
c复制// 错误的ATU配置(地址未对齐)
xlnx_pcie_set_atu(dev, 0, 0x80000001, 0x1000, XLNX_PCIE_ATU_TYPE_MEM);
// 正确的配置(地址必须4K对齐)
xlnx_pcie_set_atu(dev, 0, 0x80000000, 0x1000, XLNX_PCIE_ATU_TYPE_MEM);
这个错误导致DMA传输时不时丢数据,就像快递员总是送错楼层。后来用lspci -xxx查看配置空间,才发现ATU的target地址最低12位必须为0。
在RK3588平台上,我发现个有趣现象:当ATU配置为BAR匹配模式时,就像给导航系统设置了电子围栏。主机访问特定BAR范围时,ATU自动触发地址转换。这相当于商场VIP通道——只有持金卡(BAR范围内地址)的顾客才能享受专车接送(ATU转换)服务。
lspci -vvv是我的瑞士军刀。有次客户报障说设备时通时断,我用以下命令发现了端倪:
bash复制# 查看链路状态
lspci -vvv | grep -A10 "LnkSta:"
输出显示链路宽度在x1和x4之间跳动,就像车道时宽时窄。最终发现是PCB阻抗不匹配导致链路训练不稳定。
遇到一个EP设备无法识别的问题,用lspci -xxx导出配置空间后,发现Vendor ID被错误地配置为0x0000:
code复制00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
这就像店铺招牌挂错了,商场管理系统根本不认。通过JTAG重写FPGA的IP核配置后问题解决。
最近在STM32MP157上实现EP功能,需要完成以下步骤:
dts复制pcie_ep: pcie-ep@0x10000000 {
compatible = "st,stm32mp157-pcie-ep";
reg = <0x10000000 0x1000>;
interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_pcie>;
resets = <&rst_pcie>;
status = "okay";
};
c复制// 分配256KB内存空间
ep->bar[0].phys_addr = 0x30000000;
ep->bar[0].size = 0x40000;
ep->bar[0].flags = PCI_BASE_ADDRESS_MEM_TYPE_32;
c复制dw_pcie_prog_ep_atu(pcie, 0, PCIE_ATU_TYPE_MEM,
0x80000000, // CPU地址
ep->bar[0].phys_addr, // EP地址
ep->bar[0].size);
这个项目最坑的是忘记在FPGA端设置MSI-X中断,导致数据传输完成事件无法通知主机。后来用逻辑分析仪抓包,才发现TLP包里压根没有MSI-X消息。
在瑞芯微RK3568上做视频采集卡时,发现DMA性能只有理论值的30%。通过perf工具分析,发现三个瓶颈:
bash复制# 设置最大TLP大小
echo 256 > /sys/bus/pci/devices/0000:01:00.0/max_payload_size
c复制// 正确的不可预取内存设置
bar.flags |= PCI_BASE_ADDRESS_MEM_PREFETCH;
根据我的踩坑经验,整理了这个快速排查表:
| 现象 | 可能原因 | 排查工具 | 解决方法 |
|---|---|---|---|
| 设备未识别 | 电源/时钟未开启 | 示波器 | 检查硬件原理图 |
| 链路速率低 | 阻抗不匹配 | lspci -vvv | 调整PCB走线 |
| DMA数据错误 | ATU配置错误 | lspci -xxx | 核对地址映射 |
| 中断不触发 | MSI未使能 | cat /proc/interrupts | 配置MSI-X |
| 性能低下 | TLP大小不足 | perf工具 | 调整max_payload_size |
上周就遇到个典型案例:客户说他们的AI加速卡在ARM平台性能只有x86平台的一半。最后发现是PCIe控制器默认开启了ECRC校验,而FPGA端没启用这个功能。通过以下命令关闭后性能恢复正常:
bash复制# 禁用ECRC校验
setpci -s 00:01.0 CAP_EXP+0x08.W=0x0000