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
验证方法
- 加载检查:
insmod后执行dmesg | tail,应看到NPU HAL initialized (regs=..., irq=96),且无错误日志。 - 映射验证:
cat /proc/iomem | grep 1a100000应显示该物理地址范围已被保留。 - 中断验证:读取
/proc/interrupts,确认irq 96已注册且CPU亲和性指向当前核(例如96: 0 0 0 1 ...表示由CPU3处理)。 - 功能触发:向NPU写入任务(需额外驱动代码),观察
/proc/interrupts中irq 96计数增加,且dmesg出现interrupt handled日志。
通过标准:以上4项检查全部符合预期,且卸载模块时无异常(dmesg无错误),即可判定NPU硬件抽象层初始化成功。