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() 中调用,保证驱动卸载时无中断残留。

中断状态与流程

中断生命周期 probe() MSI-X向量分配 request_irq() 中断处理函数 free_irq() 释放MSI-X向量 remove() 硬件中断到达 中断处理流程 读状态寄存器 → 判断是否本设备中断 → 写状态寄存器清中断 → 业务处理 → 返回IRQ_HANDLED

编译与加载

# 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

验证方法与判定标准

  1. 加载验证:执行 insmod 后,使用 dmesg | tail 应看到 IRQ xxx registered and enabled 日志,其中xxx为系统分配的MSI-X中断号。
  2. 中断注册确认:cat /proc/interrupts 应出现 npu_irq_demo 条目,且中断号与dmesg日志一致。
  3. 卸载清理验证:执行 rmmod 后,dmesg应显示 IRQ xxx disabled and released,且 /proc/interrupts 中对应条目消失。
  4. 判定标准:加载后中断成功注册并使能;卸载后中断被禁用、处理函数注销、MSI-X向量释放,无内存泄漏或中断残留警告。

说明:本案例使用PCI配置空间偏移0x10模拟中断状态寄存器,实际NPU驱动应替换为BAR映射的MMIO寄存器地址。若系统无对应PCI设备,可配合 vfio-pci 或QEMU设备模拟进行验证。