2. NPU硬件抽象层初始化

任务说明

本案例面向RISC-V多核处理器中的NPU(神经网络处理单元)驱动开发,目标是在Linux内核模块中实现NPU硬件抽象层(HAL)的初始化函数。该函数需要完成两项核心工作:将NPU的MMIO寄存器物理地址映射到内核虚拟地址空间,以及配置NPU完成中断(完成队列非空时触发)到当前CPU核的GIC(通用中断控制器)。

环境与依赖

  • 硬件:RISC-V 64位多核SoC,集成NPU(寄存器基址0x1A100000,中断号96)
  • 软件:Linux内核(版本≥5.10),设备树已描述NPU节点(reg与interrupts属性)
  • 编译:交叉编译工具链(riscv64-linux-gnu-gcc),内核头文件与Makefile

核心实现

以下代码实现NPU HAL初始化模块。通过platform_driver匹配设备树节点,在probe回调中完成寄存器映射与中断申请。寄存器映射使用devm_platform_ioremap_resource自动处理资源获取与映射生命周期;中断使用devm_request_irq注册处理函数,并显式设置中断亲和性到当前CPU核(多核场景下确保NPU完成中断由指定核处理)。

// npu_hal_init.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/io.h>

#define NPU_HAL_IRQ_NAME "npu_completion"

// NPU寄存器偏移(示例)
#define NPU_REG_CTRL       0x00
#define NPU_REG_STATUS     0x04
#define NPU_REG_INT_MASK   0x08

struct npu_hal_dev {
    void __iomem *regs;
    int irq;
};

// 中断处理函数:读取状态寄存器清除中断,并做必要处理
static irqreturn_t npu_hal_isr(int irq, void *dev_id)
{
    struct npu_hal_dev *npu = dev_id;
    u32 status;

    status = ioread32(npu->regs + NPU_REG_STATUS);
    // 清除中断标志(写1清零)
    iowrite32(status, npu->regs + NPU_REG_STATUS);

    // 实际驱动中此处应唤醒等待队列或调用完成回调
    pr_info("NPU HAL: interrupt handled, status=0x%x\n", status);
    return IRQ_HANDLED;
}

static int npu_hal_probe(struct platform_device *pdev)
{
    struct npu_hal_dev *npu;
    struct resource *res;
    int ret;

    npu = devm_kzalloc(&pdev->dev, sizeof(*npu), GFP_KERNEL);
    if (!npu)
        return -ENOMEM;

    // 1. 寄存器映射:获取设备树reg属性并建立MMIO映射
    npu->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(npu->regs)) {
        dev_err(&pdev->dev, "failed to map NPU registers\n");
        return PTR_ERR(npu->regs);
    }

    // 2. 获取中断号并注册中断处理函数
    npu->irq = platform_get_irq(pdev, 0);
    if (npu->irq < 0) {
        dev_err(&pdev->dev, "failed to get NPU IRQ\n");
        return npu->irq;
    }

    ret = devm_request_irq(&pdev->dev, npu->irq, npu_hal_isr,
                           0, NPU_HAL_IRQ_NAME, npu);
    if (ret) {
        dev_err(&pdev->dev, "failed to request IRQ %d\n", npu->irq);
        return ret;
    }

    // 3. 设置中断亲和性到当前CPU核(多核调度优化基础)
    ret = irq_set_affinity_hint(npu->irq, cpumask_of(smp_processor_id()));
    if (ret)
        dev_warn(&pdev->dev, "failed to set IRQ affinity\n");

    // 4. 初始化NPU控制寄存器:使能中断(清除掩码位)
    iowrite32(0x0, npu->regs + NPU_REG_INT_MASK);
    iowrite32(0x1, npu->regs + NPU_REG_CTRL);  // 使能NPU

    platform_set_drvdata(pdev, npu);
    dev_info(&pdev->dev, "NPU HAL initialized (regs=%px, irq=%d)\n",
             npu->regs, npu->irq);
    return 0;
}

static void npu_hal_remove(struct platform_device *pdev)
{
    struct npu_hal_dev *npu = platform_get_drvdata(pdev);

    // 禁用NPU并屏蔽中断
    iowrite32(0x0, npu->regs + NPU_REG_CTRL);
    iowrite32(0xFFFFFFFF, npu->regs + NPU_REG_INT_MASK);
    irq_set_affinity_hint(npu->irq, NULL);
}

static const struct of_device_id npu_hal_of_match[] = {
    { .compatible = "vendor,npu" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, npu_hal_of_match);

static struct platform_driver npu_hal_driver = {
    .probe  = npu_hal_probe,
    .remove = npu_hal_remove,
    .driver = {
        .name = "npu_hal",
        .of_match_table = npu_hal_of_match,
    },
};
module_platform_driver(npu_hal_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NPU Hardware Abstraction Layer Initialization");

设备树节点示例

// 在SoC的dtsi中添加(地址与中断号按实际硬件修改)
npu: npu@1a100000 {
    compatible = "vendor,npu";
    reg = <0x0 0x1a100000 0x0 0x1000>;
    interrupts = <0 96 4>;  // SPI, hwirq=96, level-high
    status = "okay";
};

编译与加载

# Makefile(与源码同目录)
obj-m := npu_hal_init.o
KDIR := /path/to/kernel/build
all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

# 编译
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu-

# 加载/卸载
sudo insmod npu_hal_init.ko
sudo rmmod npu_hal_init

验证方法

  1. 加载检查:insmod后执行dmesg | tail,应看到NPU HAL initialized (regs=..., irq=96),且无错误日志。
  2. 映射验证:cat /proc/iomem | grep 1a100000应显示该物理地址范围已被保留。
  3. 中断验证:读取/proc/interrupts,确认irq 96已注册且CPU亲和性指向当前核(例如96: 0 0 0 1 ...表示由CPU3处理)。
  4. 功能触发:向NPU写入任务(需额外驱动代码),观察/proc/interrupts中irq 96计数增加,且dmesg出现interrupt handled日志。

通过标准:以上4项检查全部符合预期,且卸载模块时无异常(dmesg无错误),即可判定NPU硬件抽象层初始化成功。