3. 中断处理注册与注销
任务说明
本案例为NPU设备驱动实现标准的中断管理能力:注册中断处理函数、使能/禁用中断,以及在驱动卸载时完成中断清理。目标设备为PCIe形态的NPU(模拟为PCIe function 0),中断类型为MSI-X。完成后,驱动模块加载时自动注册并使能中断,卸载时禁用并释放中断。
中断处理实现
// npu_irq.c
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#define NPU_DRV_NAME "npu_irq_demo"
#define NPU_MSIX_VEC_COUNT 1
static struct pci_device_id npu_pci_ids[] = {
{ PCI_DEVICE(0x1a56, 0x1001) }, // 模拟NPU Vendor/Device ID
{ 0 }
};
MODULE_DEVICE_TABLE(pci, npu_pci_ids);
struct npu_device {
struct pci_dev *pdev;
int irq_vector;
bool irq_enabled;
};
static irqreturn_t npu_irq_handler(int irq, void *dev_id)
{
struct npu_device *ndev = (struct npu_device *)dev_id;
struct pci_dev *pdev = ndev->pdev;
u32 status;
// 读取中断状态寄存器(模拟BAR0偏移0x10)
pci_read_config_dword(pdev, 0x10, &status);
if (!(status & 0x1)) {
return IRQ_NONE; // 非本设备中断
}
// 清中断(写1清除,模拟寄存器)
pci_write_config_dword(pdev, 0x10, status);
// 实际驱动在此处理DMA完成、错误上报等业务
pr_info("npu_irq: interrupt handled, status=0x%x\n", status);
return IRQ_HANDLED;
}
static int npu_irq_enable(struct npu_device *ndev)
{
int ret;
ret = pci_alloc_irq_vectors(ndev->pdev, 1, NPU_MSIX_VEC_COUNT,
PCI_IRQ_MSIX);
if (ret < 0) {
dev_err(&ndev->pdev->dev, "MSI-X alloc failed: %d\n", ret);
return ret;
}
ndev->irq_vector = pci_irq_vector(ndev->pdev, 0);
ret = request_threaded_irq(ndev->irq_vector, npu_irq_handler, NULL,
IRQF_SHARED, NPU_DRV_NAME, ndev);
if (ret) {
dev_err(&ndev->pdev->dev, "request_irq failed: %d\n", ret);
pci_free_irq_vectors(ndev->pdev);
return ret;
}
ndev->irq_enabled = true;
dev_info(&ndev->pdev->dev, "IRQ %d registered and enabled\n",
ndev->irq_vector);
return 0;
}
static void npu_irq_disable(struct npu_device *ndev)
{
if (!ndev->irq_enabled)
return;
free_irq(ndev->irq_vector, ndev);
pci_free_irq_vectors(ndev->pdev);
ndev->irq_enabled = false;
dev_info(&ndev->pdev->dev, "IRQ %d disabled and released\n",
ndev->irq_vector);
}
static int npu_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct npu_device *ndev;
int ret;
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "pci_enable_device failed: %d\n", ret);
return ret;
}
ndev = devm_kzalloc(&pdev->dev, sizeof(*ndev), GFP_KERNEL);
if (!ndev)
return -ENOMEM;
ndev->pdev = pdev;
pci_set_drvdata(pdev, ndev);
ret = npu_irq_enable(ndev);
if (ret)
return ret;
return 0;
}
static void npu_remove(struct pci_dev *pdev)
{
struct npu_device *ndev = pci_get_drvdata(pdev);
npu_irq_disable(ndev);
dev_info(&pdev->dev, "npu_irq_demo removed\n");
}
static struct pci_driver npu_driver = {
.name = NPU_DRV_NAME,
.id_table = npu_pci_ids,
.probe = npu_probe,
.remove = npu_remove,
};
module_pci_driver(npu_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NPU Driver Course");
MODULE_DESCRIPTION("NPU IRQ register/enable/disable/cleanup demo");
代码行为说明
- 注册:
npu_irq_enable()先通过pci_alloc_irq_vectors()分配1个MSI-X向量,再调用request_threaded_irq()注册中断处理函数npu_irq_handler,线程化中断避免在原子上下文执行耗时操作。 - 使能:注册成功后即视为中断使能(MSI-X向量分配后硬件中断默认可送达)。处理函数中读取并清除中断状态寄存器,确保中断不会重复触发。
- 禁用与清理:
npu_irq_disable()先free_irq()注销处理函数,再pci_free_irq_vectors()释放MSI-X向量。在npu_remove()中调用,保证驱动卸载时无中断残留。
中断状态与流程
编译与加载
# Makefile
obj-m += npu_irq.o
# 编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载(需root)
sudo insmod npu_irq.ko
# 查看中断注册情况
cat /proc/interrupts | grep npu_irq
# 卸载
sudo rmmod npu_irq
验证方法与判定标准
- 加载验证:执行
insmod后,使用dmesg | tail应看到IRQ xxx registered and enabled日志,其中xxx为系统分配的MSI-X中断号。 - 中断注册确认:
cat /proc/interrupts应出现npu_irq_demo条目,且中断号与dmesg日志一致。 - 卸载清理验证:执行
rmmod后,dmesg应显示IRQ xxx disabled and released,且/proc/interrupts中对应条目消失。 - 判定标准:加载后中断成功注册并使能;卸载后中断被禁用、处理函数注销、MSI-X向量释放,无内存泄漏或中断残留警告。
说明:本案例使用PCI配置空间偏移0x10模拟中断状态寄存器,实际NPU驱动应替换为BAR映射的MMIO寄存器地址。若系统无对应PCI设备,可配合 vfio-pci 或QEMU设备模拟进行验证。