16、重构操作系统支持:Linux对PR的支持、设备树配置、驱动开发要点

说实话,动态部分重构(PR)在FPGA上跑得再溜,如果跟操作系统配合不好,那也就是个高级玩具。我早年做过一个项目,PR功能在裸机上跑得飞起,一上Linux就各种崩溃。后来才明白——不是PR本身的问题,是操作系统根本不知道你干了什么。

今天我们就聊聊,怎么让Linux跟PR好好相处。

Linux对PR的支持现状

Linux内核从4.x版本开始,正式加入了FPGA管理器(FPGA Manager)框架。这个框架说白了,就是给内核提供了一个统一的接口,用来加载比特流、管理重构区域。

我个人的习惯是,先确认内核版本。低于4.14的版本,对PR的支持比较有限,建议升级。内核里需要开启以下配置:

CONFIG_FPGA=y
CONFIG_FPGA_MGR_XILINX_SPI=y
CONFIG_FPGA_MGR_XILINX_SELECTMAP=y
CONFIG_FPGA_BRIDGE=y
CONFIG_FPGA_REGION=y

嗯,这里要注意——FPGA_REGION这个选项特别重要。它负责把FPGA管理器、桥接器和重构区域关联起来。没有它,你加载比特流的时候,内核不知道哪些逻辑需要冻结,哪些桥需要关闭。

核心概念:Linux把PR抽象成三个层次——FPGA Manager(底层驱动)、FPGA Bridge(桥接控制)、FPGA Region(区域管理)。三者配合,才能完成一次安全的动态重构。

设备树配置——告诉内核你的PR布局

设备树(Device Tree)是Linux跟硬件沟通的桥梁。对于PR来说,设备树里要描述清楚:哪些区域是可重构的,哪些是静态的,桥接器怎么控制。

我曾经在一个项目里,因为设备树里漏配了一个桥接器,导致重构时整个芯片死机。排查了整整两天,最后发现是fpga-bridge节点没加。

下面是一个典型的PR设备树配置示例:

/ {
    fpga-full: fpga-full {
        compatible = "fpga-region";
        fpga-mgr = &fpga_manager_0;
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        /* 静态区域 */
        fpga-bridge@0 {
            compatible = "fpga-bridge";
            fpga-bridge-name = "static_region";
            reg = <0x0 0x10000>;
        };

        /* 可重构区域 */
        pr_region_0: pr-region@10000 {
            compatible = "fpga-region";
            reg = <0x10000 0x20000>;
            fpga-bridges = <&pr_bridge_0>;
            fpga-partial-fpga-config;
            pr-mgr = <&pr_manager_0>;
        };
    };
};

这里有几个关键点:

  • fpga-partial-fpga-config:这个标志告诉内核,该区域支持部分重构,不是全芯片加载。
  • fpga-bridges:指定该区域对应的桥接器。重构时,内核会自动冻结这些桥。
  • pr-mgr:指定该区域专用的PR管理器。有些设计里,不同区域可能用不同的加载接口。

我的经验:设备树里的地址范围一定要跟PR区域的边界对齐。我见过有人把区域大小写错了,结果重构时覆盖了相邻的静态逻辑,整个系统直接挂掉。建议用reg = <起始地址 大小>时,大小必须是2的幂次,且起始地址对齐。

驱动开发要点——让应用层感知PR

设备树配好了,内核能识别PR区域了。但应用层怎么知道什么时候该重构?重构完了怎么通知驱动?

这就涉及到驱动开发了。我个人建议,PR相关的驱动开发遵循以下原则:

  1. 使用FPGA Region的sysfs接口:内核会自动为每个FPGA Region创建/sys/class/fpga_region/下的节点。应用层可以通过写load文件来触发重构。
  2. 注册notifier链:当重构完成时,内核会发送通知。你的驱动可以注册一个notifier,收到通知后重新初始化硬件寄存器。
  3. DMA缓冲区管理:如果PR区域里有DMA引擎,重构后DMA描述符表可能失效。我习惯在驱动里维护一个“重构后恢复”的回调函数。

下面是一个简单的驱动框架:

#include <linux/fpga/fpga-region.h>
#include <linux/notifier.h>

static int pr_notifier_callback(struct notifier_block *nb,
                                unsigned long event, void *data)
{
    struct fpga_region *region = data;

    if (event == FPGA_REGION_POST_LOAD) {
        /* 重构完成,重新初始化硬件 */
        pr_info("PR region %s reloaded, reinit hardware\n",
                region->dev.of_node->name);
        my_hardware_reinit(region);
    }
    return NOTIFY_OK;
}

static struct notifier_block pr_notifier = {
    .notifier_call = pr_notifier_callback,
};

static int my_driver_probe(struct platform_device *pdev)
{
    /* 注册notifier */
    fpga_region_register_notifier(&pr_notifier);
    return 0;
}

注意:notifier回调函数是在原子上下文中调用的。不要在回调里做耗时操作,比如msleepkmalloc。我建议用工作队列(workqueue)把耗时的初始化操作推迟到进程上下文执行。

SVG:Linux PR支持架构图

Linux对PR的支持架构 用户空间 (User Space) 应用层通过 sysfs (/sys/class/fpga_region/) 触发重构 内核空间 (Kernel Space) FPGA Manager 底层比特流加载 FPGA Bridge 桥接器冻结/解冻 FPGA Region 区域管理与协调 Notifier 链:重构完成后通知各驱动模块 硬件层 (FPGA) 静态逻辑 + 可重构区域 (PR Region)

避坑指南——我踩过的几个坑

做PR+Linux这几年,我踩过不少坑。挑几个典型的说说:

  • 桥接器没冻结就加载比特流:我曾经在加载PR比特流之前,忘了先冻结桥接器。结果重构过程中,静态逻辑的信号直接灌进了重构区域,导致芯片内部短路。从那以后,我每次都在设备树里明确指定fpga-bridges,让内核自动处理冻结顺序。
  • DMA传输未完成就重构:有一次,PR区域里有个DMA引擎正在搬运数据,我直接触发了重构。结果DMA写到一半,目标地址变成了静态逻辑的寄存器,把整个系统搞乱了。后来我加了一个“重构前检查”机制——如果DMA还在忙,就返回-EBUSY
  • 时钟域没处理好:PR区域和静态逻辑如果跨时钟域,重构时时钟可能瞬间丢失。我建议在桥接器里加一个“时钟稳定”检测,重构完成后等时钟稳定了再释放桥。

小技巧:调试PR问题时,可以在内核启动参数里加fpga_debug=1。这样内核会打印详细的FPGA Manager日志,包括每次加载的字节数、桥接器状态变化等。我调试时基本都会开这个。

总结

Linux对PR的支持,说白了就是三个字——管、桥、区。Manager管加载,Bridge管隔离,Region管协调。设备树是它们的“户口本”,驱动是它们的“行为准则”。

我个人觉得,最难的不是写代码,而是理解整个流程的时序关系。什么时候冻结桥?什么时候加载比特流?什么时候通知驱动?顺序错了,系统就崩了。

嗯,今天就聊到这儿。记住一点——PR不是裸机上的玩具,上了Linux就要遵守Linux的规矩。把设备树配好,把notifier注册好,你的PR系统才能稳定运行。


公众号:蓝海资料掘金营,微信deep3321