1. NPU设备初始化与资源探测

NPU设备初始化与资源探测 学习卡片

任务说明

本案例要求实现一个NPU(神经网络处理单元)驱动初始化函数,完成硬件资源探测:读取寄存器基址、中断号、内存窗口信息,并将设备信息打印输出。为聚焦核心逻辑,采用平台设备(platform device)模型模拟真实驱动框架,通过设备树或平台数据提供资源描述,驱动初始化时解析并验证这些资源。

实现方案

驱动初始化流程分为三步:
1. 获取平台设备资源(寄存器基址、中断号、内存窗口);
2. 对寄存器基址和内存窗口进行地址映射与范围校验;
3. 打印全部设备资源信息。

代码基于Linux内核驱动框架编写,可在真实内核模块中编译运行,也可通过QEMU或内核模块测试环境验证。为便于独立演示,同时提供用户态模拟版本(注释标明),但核心实现以内核模块为准。

NPU驱动初始化资源探测流程 platform_driver.probe npu_probe(struct platform_device*) platform_get_resource IORESOURCE_MEM / IRQ ioremap / request_mem_region 地址映射与占用检查 校验 返回 -ENOMEM / -EBUSY probe失败 dev_info 打印资源 基址/中断/内存窗口 probe 成功返回0 remove: iounmap + release_mem_region 正常流程 失败路径 成功路径 清理路径

驱动代码实现


#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/of.h>

#define NPU_DRV_NAME "npu_dev"

struct npu_device {
    void __iomem *reg_base;    /* 寄存器基址映射 */
    void __iomem *mem_window;  /* 内存窗口映射 */
    int irq;                   /* 中断号 */
    phys_addr_t reg_start;     /* 寄存器物理起始 */
    resource_size_t reg_len;
    phys_addr_t mem_start;     /* 内存窗口物理起始 */
    resource_size_t mem_len;
};

static int npu_probe(struct platform_device *pdev)
{
    struct resource *res_mem, *res_irq, *res_memwin;
    struct npu_device *npu;
    int ret = 0;

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

    /* 1. 获取寄存器基址资源 (index 0) */
    res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res_mem) {
        dev_err(&pdev->dev, "missing register base resource\n");
        return -ENXIO;
    }
    npu->reg_start = res_mem->start;
    npu->reg_len = resource_size(res_mem);

    /* 2. 获取中断号资源 (index 0) */
    res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (!res_irq) {
        dev_err(&pdev->dev, "missing IRQ resource\n");
        return -ENXIO;
    }
    npu->irq = res_irq->start;

    /* 3. 获取内存窗口资源 (index 1) */
    res_memwin = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (!res_memwin) {
        dev_err(&pdev->dev, "missing memory window resource\n");
        return -ENXIO;
    }
    npu->mem_start = res_memwin->start;
    npu->mem_len = resource_size(res_memwin);

    /* 4. 寄存器基址映射与占用 */
    if (!request_mem_region(npu->reg_start, npu->reg_len, NPU_DRV_NAME)) {
        dev_err(&pdev->dev, "failed to request reg region\n");
        return -EBUSY;
    }
    npu->reg_base = ioremap(npu->reg_start, npu->reg_len);
    if (!npu->reg_base) {
        dev_err(&pdev->dev, "ioremap reg failed\n");
        release_mem_region(npu->reg_start, npu->reg_len);
        return -ENOMEM;
    }

    /* 5. 内存窗口映射与占用 */
    if (!request_mem_region(npu->mem_start, npu->mem_len, NPU_DRV_NAME)) {
        dev_err(&pdev->dev, "failed to request mem window\n");
        iounmap(npu->reg_base);
        release_mem_region(npu->reg_start, npu->reg_len);
        return -EBUSY;
    }
    npu->mem_window = ioremap(npu->mem_start, npu->mem_len);
    if (!npu->mem_window) {
        dev_err(&pdev->dev, "ioremap mem window failed\n");
        release_mem_region(npu->mem_start, npu->mem_len);
        iounmap(npu->reg_base);
        release_mem_region(npu->reg_start, npu->reg_len);
        return -ENOMEM;
    }

    /* 6. 打印设备资源信息 */
    dev_info(&pdev->dev, "NPU resource probe OK:\n");
    dev_info(&pdev->dev, "  reg_base   phys=0x%llx len=0x%llx virt=0x%px\n",
             (unsigned long long)npu->reg_start,
             (unsigned long long)npu->reg_len, npu->reg_base);
    dev_info(&pdev->dev, "  irq        = %d\n", npu->irq);
    dev_info(&pdev->dev, "  mem_window phys=0x%llx len=0x%llx virt=0x%px\n",
             (unsigned long long)npu->mem_start,
             (unsigned long long)npu->mem_len, npu->mem_window);

    platform_set_drvdata(pdev, npu);
    return ret;
}

static int npu_remove(struct platform_device *pdev)
{
    struct npu_device *npu = platform_get_drvdata(pdev);

    if (npu->mem_window) {
        iounmap(npu->mem_window);
        release_mem_region(npu->mem_start, npu->mem_len);
    }
    if (npu->reg_base) {
        iounmap(npu->reg_base);
        release_mem_region(npu->reg_start, npu->reg_len);
    }
    dev_info(&pdev->dev, "NPU driver removed\n");
    return 0;
}

static const struct of_device_id npu_of_match[] = {
    { .compatible = "example,npu-v1" },
    { }
};
MODULE_DEVICE_TABLE(of, npu_of_match);

static struct platform_driver npu_driver = {
    .probe  = npu_probe,
    .remove = npu_remove,
    .driver = {
        .name = NPU_DRV_NAME,
        .of_match_table = npu_of_match,
    },
};
module_platform_driver(npu_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("NPU device init and resource probe driver");

设备树节点示例


npu@10000000 {
    compatible = "example,npu-v1";
    reg = <0x0 0x10000000 0x0 0x1000>,   /* 寄存器基址 */
          <0x0 0x20000000 0x0 0x800000>; /* 内存窗口 */
    interrupts = <0 42 4>;               /* SPI 42, 高电平触发 */
};

编译与加载验证

依赖:Linux内核头文件、交叉编译工具链(若目标为ARM/SoC)、设备树编译器dtc。

编译:将驱动代码保存为npu_probe.c,编写Makefile:


obj-m := npu_probe.o
KDIR := /lib/modules/$(shell uname -r)/build
all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

执行make生成npu_probe.ko。

加载与验证步骤:


# 1. 加载模块(需root)
insmod npu_probe.ko

# 2. 观察内核日志
dmesg | tail -20

判定标准:若设备树匹配成功,dmesg中应出现类似以下输出(地址与设备树一致):


NPU resource probe OK:
  reg_base   phys=0x10000000 len=0x1000 virt=0x...
  irq        = 42
  mem_window phys=0x20000000 len=0x800000 virt=0x...

若设备树节点不存在或资源缺失,dmesg会显示对应的missing ... resource错误信息,probe返回负错误码,模块加载失败——此行为即为验证失败判定。

卸载验证:执行rmmod npu_probe,dmesg应显示NPU driver removed,且无内存区域泄漏警告。

验证要点

1. 资源获取:platform_get_resource按索引正确解析reg与interrupts属性;
2. 地址映射:ioremap成功且返回非空虚拟地址;
3. 资源占用:request_mem_region防止多个驱动同时操作同一物理地址;
4. 错误路径:任一资源缺失或映射失败时,已获取的资源被正确释放,无泄漏。