1. NPU设备初始化与资源探测
任务说明
本案例要求实现一个NPU(神经网络处理单元)驱动初始化函数,完成硬件资源探测:读取寄存器基址、中断号、内存窗口信息,并将设备信息打印输出。为聚焦核心逻辑,采用平台设备(platform device)模型模拟真实驱动框架,通过设备树或平台数据提供资源描述,驱动初始化时解析并验证这些资源。
实现方案
驱动初始化流程分为三步:
1. 获取平台设备资源(寄存器基址、中断号、内存窗口);
2. 对寄存器基址和内存窗口进行地址映射与范围校验;
3. 打印全部设备资源信息。
代码基于Linux内核驱动框架编写,可在真实内核模块中编译运行,也可通过QEMU或内核模块测试环境验证。为便于独立演示,同时提供用户态模拟版本(注释标明),但核心实现以内核模块为准。
驱动代码实现
#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. 错误路径:任一资源缺失或映射失败时,已获取的资源被正确释放,无泄漏。