4. RK3568 PCIe EP模式驱动开发

好,咱们今天聊聊EP模式。说实话,很多做驱动的朋友一开始都习惯把RK3568当RC(Root Complex)用,接个Wi-Fi卡、SSD什么的。但真正有意思的玩法,是把RK3568配置成Endpoint,让它作为一个智能外设去跟主机通信。

我在一个工业数据采集项目里就这么干过。RK3568作为EP,挂在x86主机的PCIE总线上,主机通过DMA直接读取采集数据,延迟比网卡低一个数量级。嗯,今天我就把这块的配置要点和踩过的坑都摊开来讲。

核心认知: EP模式下的RK3568,本质上是一个“可编程PCIe设备”。你需要自己管理BAR空间、处理配置请求、生成中断。这不是开箱即用的,但灵活性极高。

4.1 EP模式与RC模式的核心差异

先看一张对比表,帮你快速建立概念。说白了,RC是“总线主人”,EP是“总线仆人”。

对比维度 RC模式(默认) EP模式(本章目标)
总线角色 枚举并管理下游设备 被上游RC枚举和配置
配置空间 由固件/BIOS初始化 由RC写入,驱动需响应
BAR空间 由驱动分配(作为主机) 由RC分配,驱动需映射
中断生成 接收并处理中断 主动发送INTx或MSI
典型场景 接NVMe、Wi-Fi、GPU 数据采集卡、AI加速器

你想想看,EP模式下的驱动,更像是在写一个“PCIe设备固件”。你要主动告诉RC:“我有多少内存窗口”、“我能产生什么中断”。

4.2 硬件配置:让RK3568进入EP模式

第一步不是写代码,是改设备树。RK3568的PCIe控制器在硬件上同时支持RC和EP,但同一时刻只能选一种。我习惯在dts里明确指定模式。

// rk3568-ep.dtsi 关键片段
&pcie2x1 {
    status = "okay";
    rockchip,pcie-mode = "ep";  // 关键:声明为EP模式
    num-lanes = <1>;
    phys = <&phy_pcie>;
    phy-names = "pcie-phy";
    // EP模式下不需要复位GPIO,由RC控制
    // ep-gpios 这里不配
};
我曾经踩过的坑: 第一次配EP模式时,我忘了改 rockchip,pcie-mode,结果内核启动时直接去枚举下游设备,总线挂死。如果你发现RC侧根本看不到设备,先检查dts里这个属性有没有配成"ep"。

硬件上还需要注意:EP端的REFCLK必须由RC提供,或者使用独立的时钟源。我在项目中遇到过时钟抖动导致链路训练失败的问题,后来在原理图上加了AC耦合电容才稳定。

4.3 BAR空间配置:你的内存窗口

BAR(Base Address Register)是EP和RC交换数据的核心。RC通过写配置空间来分配BAR的基地址,EP驱动则需要告诉RC:“我需要多大的窗口”。

我个人习惯在驱动里用pci_epc_set_bar()来配置。来看一个实际例子:

// EP驱动中配置BAR0:4MB内存窗口
static int rk3568_ep_bar_init(struct pci_epc *epc)
{
    struct pci_epf_bar bar;
    int ret;

    memset(&bar, 0, sizeof(bar));
    bar.phys_addr = 0;          // 由RC分配
    bar.size = SZ_4M;           // 4MB窗口
    bar.barno = BAR_0;
    bar.flags = PCI_BASE_ADDRESS_MEM_TYPE_32;

    ret = pci_epc_set_bar(epc, 0, &bar);
    if (ret) {
        dev_err(&epc->dev, "Failed to set BAR0\n");
        return ret;
    }

    // 映射BAR到CPU地址空间,用于本地访问
    epf->bar_phys[0] = pci_epc_get_bar_phys(epc, 0, BAR_0);
    epf->bar_virt[0] = ioremap(epf->bar_phys[0], SZ_4M);

    return 0;
}

这里有几个要点:

  • BAR大小必须是2的幂,且不能小于4KB。我见过有人配3MB,结果RC直接报错。
  • BAR0通常用于控制寄存器,BAR1/BAR2用于数据缓冲区。这是约定俗成的做法。
  • 物理地址由RC分配,EP驱动只能通过pci_epc_get_bar_phys()拿到分配后的地址。
小技巧: 调试BAR配置时,可以在RC侧用 lspci -vvv -s 01:00.0 查看BAR的基地址和大小。如果显示 [disabled],说明EP侧没有正确调用 pci_epc_set_bar()。

4.4 中断生成:INTx与MSI

EP需要主动通知RC。RK3568支持两种中断方式:INTx(传统中断线)和MSI(消息信号中断)。

我个人的建议是:能用MSI就别用INTx。MSI不需要物理中断线,性能更好,而且可以支持多个中断向量。但有些老旧的RC只支持INTx,这时候就得兼容。

4.4.1 MSI中断生成

// 发送MSI中断
static void rk3568_ep_raise_msi(struct pci_epf *epf, u8 interrupt_num)
{
    struct pci_epc *epc = epf->epc;
    int ret;

    // interrupt_num 从1开始,对应MSI的多个向量
    ret = pci_epc_raise_irq(epc, 0, PCI_EPC_IRQ_MSI, interrupt_num);
    if (ret)
        dev_err(&epf->dev, "Failed to raise MSI %d\n", interrupt_num);
}

这里要注意:MSI的向量号从1开始,不是0。我第一次写的时候传了0,结果RC侧死活收不到中断。查了半天spec才发现这个细节。

4.4.2 INTx中断生成

// 发送INTx中断(传统方式)
static void rk3568_ep_raise_intx(struct pci_epf *epf)
{
    struct pci_epc *epc = epf->epc;

    // INTx只有A/B/C/D四个,通常用INTA
    pci_epc_raise_irq(epc, 0, PCI_EPC_IRQ_INTX, 0);
}
我曾经踩过的坑: 在RC侧,INTx是电平触发,需要EP主动拉低再拉高。如果只拉低不拉高,RC的中断处理函数会一直触发,导致CPU软锁。记得在中断处理完成后,调用 pci_epc_raise_irq() 关闭中断。

4.5 数据交互:RC与EP的通信机制

数据交互的核心是:RC通过BAR访问EP的内存,EP通过中断通知RC数据就绪。我画了一张流程图,帮你理清整个交互逻辑。

RK3568 EP模式数据交互流程 RC侧(x86主机) EP侧(RK3568) 1. 枚举设备 2. 配置BAR空间 配置写请求 3. 写入数据到BAR 4. 发送MSI中断 MSI中断消息 5. 读取BAR数据 DMA读取(可选) 核心:EP写数据 → 发中断 → RC读数据。DMA可加速大块传输。

实际代码中,数据交互通常通过DMA来提升性能。RK3568内部集成了DW DMA控制器,可以配合PCIE EP实现内存到内存的搬运。

// EP侧:准备数据并通知RC
static irqreturn_t rk3568_ep_irq_handler(int irq, void *data)
{
    struct rk3568_ep_dev *ep = data;
    u32 *buf = ep->bar_virt[1];  // BAR1作为数据缓冲区

    // 模拟采集数据
    buf[0] = readl(ep->adc_base);  // 读取ADC值
    buf[1] = readl(ep->gpio_status);

    // 通知RC数据就绪
    rk3568_ep_raise_msi(ep->epf, 1);

    return IRQ_HANDLED;
}
性能优化建议: 如果数据量超过4KB,建议使用DMA。RK3568的PCIE控制器内部集成了DMA引擎,可以通过 dmaengine_prep_slave_sg() 来配置。我在项目中用DMA搬运1MB数据,延迟从200μs降到了15μs。

4.6 避坑指南与调试技巧

最后,我把这些年积累的调试经验整理成清单,你遇到问题时可以直接对照排查。

问题现象 可能原因 排查方法
RC侧看不到设备 dts模式配错、链路训练失败 检查 rockchip,pcie-mode;用示波器看REFCLK
BAR空间读回全F BAR未正确映射、地址冲突 RC侧 lspci -vvv 查看BAR状态
MSI中断收不到 向量号从1开始、MSI使能未配 检查RC侧 /proc/interrupts
INTx中断风暴 中断未正确清除 确认EP侧在ISR中调用清除函数
数据传输CRC错误 时钟不稳定、PCB走线过长 降低PCIE速率到Gen1测试

嗯,EP模式开发确实比RC模式多了一些弯弯绕绕。但一旦你掌握了BAR配置和中断生成的套路,就会发现RK3568作为EP的潜力非常大——它不再只是一个应用处理器,而是一个可以跟任何x86/ARM主机无缝对接的智能外设。

我个人觉得,调试EP模式最有效的工具就是RC侧的lspci和setpci。配合EP侧的内核打印,大部分问题都能定位。如果遇到链路训练问题,别犹豫,直接上示波器看PCIE差分信号——软件解决不了物理层的问题。