linux内核虚拟中断控制器驱动

❓ 问题描述:

编写一个虚拟中断控制器

日志

添加打印日志信息

分析步骤

第1步:
第2步:
...

代码片段

设备树如下:

vicrtual_irq_controller {
    compatible = "mycorp,virtual-irq-controller";
    interrupt-controller;
    // 使用两个 cell: 第一个用于 HWIRQ,第二个用于中断类型
    #interrupt-cells = <2>;
    
    // 自定义属性,用于告知驱动硬件中断号的起始值
    mycorp,hwirq-base = <0>; 
};

virtual_irq_client {
    compatible = "mycorp,virtual-irq-client";
    interrupt-parent = <&vic>;
    
    // 中断描述:
    // - 60: 公共硬件中断号 (Public HWIRQ)
    // - 1:   中断类型 (IRQ_TYPE_LEVEL_HIGH)
    interrupts = <60 1>;
};

虚拟中断控制器代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of.h>
#include <linux/of_irq.h>

#define VIC_MAX_IRQS 100 // 支持32个中断

// 私有数据结构
struct vic_chip_data {
    struct irq_domain *domain;
    u32 hwirq_base;
};

// 中断控制器操作集 (irq_chip)
static void vic_irq_mask(struct irq_data *d) {
    pr_info("VIC: Masking public hwirq %lu, irq %u,domain %p
", d->hwirq, d->irq,d->domain);
}

static void vic_irq_unmask(struct irq_data *d) {
    pr_info("VIC: Unmasking public hwirq %lu, irq %u,domain %p
", d->hwirq, d->irq,d->domain);
}

static int vic_irq_set_type(struct irq_data *d, unsigned int type) {

    pr_info("VIC: Setting type %u for hwirq %lu, irq %u,domain %p
", type, d->hwirq, d->irq,d->domain);

    return 0;
}

static struct irq_chip vic_irq_chip = {
    .name       = "VIC",
    .irq_mask   = vic_irq_mask,
    .irq_unmask = vic_irq_unmask,
    .irq_set_type = vic_irq_set_type,
};

// IRQ Domain 的 .map 回调
static int vic_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) {
    // 使用 irq_domain_add_simple 后,这里的 hwirq 就是 public hwirq (e.g., 102)
    pr_info("VIC: Mapping public hwirq %lu to Linux irq %u
", hwirq, irq);

    irq_set_chip_and_handler(irq, &vic_irq_chip, handle_level_irq);
    irq_set_chip_data(irq, d->host_data);
    irq_set_probe(irq);
    return 0;
}

static int vic_domain_xlate(struct irq_domain *d, struct device_node *ctrl,
                           const u32 *intspec, unsigned int intsize,
                           irq_hw_number_t *out_hwirq, unsigned int *out_type)
{
    u32 public_hwirq = intspec[0];

    //input: intspec[0] = hwirq(硬件中断号), intspec[1] = irq_type(中断类型)
    printk("VIC: xlate: intspec[0] %u, intspec[1] %u, intspec[2] %u
", intspec[0], intspec[1]);
    
    // 核心转换,这里也可以自己进行转换,最终实际的硬件中断号,在proc/interrupts中显示的硬件中断号,在实际触发时也必须是这个
    *out_hwirq = public_hwirq;
    *out_type = intspec[1];

    pr_info("VIC: xlate: public %u -> internal %lu
", public_hwirq, *out_hwirq);

    return 0;
}

// IRQ Domain 操作集
static const struct irq_domain_ops vic_domain_ops = {
    .map    = vic_irq_map,
    // 直接使用内核提供的 twocell 翻译函数,无需自定义
    .xlate  = irq_domain_xlate_twocell,
    // .xlate  = vic_domain_xlate,
};

// Sysfs 触发函数
static ssize_t trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
    struct platform_device *pdev = to_platform_device(dev);
    struct vic_chip_data *data = platform_get_drvdata(pdev);
    unsigned long public_hwirq;
    int linux_irq;

    if (kstrtoul(buf, 0, &public_hwirq) < 0) return -EINVAL;

    // 检查范围
    if (public_hwirq < data->hwirq_base || public_hwirq >= (data->hwirq_base + VIC_MAX_IRQS)) {
        dev_err(dev, "Public hwirq %lu is out of range [%u, %u)
", 
                public_hwirq, data->hwirq_base, data->hwirq_base + VIC_MAX_IRQS);
        return -EINVAL;
    }

    // 使用 public_hwirq 直接查找映射
    linux_irq = irq_find_mapping(data->domain, public_hwirq);
    if (linux_irq <= 0) {
        dev_err(dev, "Failed to find mapping for public hwirq %lu. Is client driver loaded?
", public_hwirq);
        return -ENXIO;
    }

    pr_info("VIC: Triggering public hwirq %lu (Linux irq %d)
", public_hwirq, linux_irq);

    // 1. 禁用本地CPU中断,模拟硬件中断的自动行为
    local_irq_disable();

    generic_handle_irq(linux_irq);

    // 3. 重新使能本地CPU中断
    local_irq_enable();

    return count;
}
static DEVICE_ATTR_WO(trigger);

// 平台驱动 probe 函数
static int vic_probe(struct platform_device *pdev) {
    struct device_node *node = pdev->dev.of_node;
    struct vic_chip_data *data;
    u32 first_irq;

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

    if (of_property_read_u32(node, "mycorp,hwirq-base", &first_irq)) {
        dev_warn(&pdev->dev, "No 'mycorp,hwirq-base' found, assuming 0
");
        first_irq = 0;
    }
    data->hwirq_base = first_irq;

    // 使用 irq_domain_add_simple 创建域
 //   data->domain = irq_domain_add_simple(node, VIC_MAX_IRQS, first_irq, &vic_domain_ops, data);
   data->domain = irq_domain_add_linear(node, VIC_MAX_IRQS, &vic_domain_ops, data);

   printk("VIC: domain %p
", data->domain);
   if (!data->domain) {
        dev_err(&pdev->dev, "Failed to create simple IRQ domain
");
        return -ENOMEM;
    }

    if (device_create_file(&pdev->dev, &dev_attr_trigger)) {
        dev_err(&pdev->dev, "Failed to create sysfs 'trigger' file
");
        irq_domain_remove(data->domain);
        return -EINVAL;
    }

    dev_info(&pdev->dev, "Simple Virtual IRQ Controller initialized, range %u-%u
", 
             data->hwirq_base, data->hwirq_base + VIC_MAX_IRQS - 1);
    return 0;
}

// 平台驱动 remove 函数
static int vic_remove(struct platform_device *pdev) {
    struct vic_chip_data *data = platform_get_drvdata(pdev);
    device_remove_file(&pdev->dev, &dev_attr_trigger);
    irq_domain_remove(data->domain);
    pr_info("VIC: Virtual IRQ Controller removed
");
    return 0;
}

static const struct of_device_id vic_of_match[] = {
    { .compatible = "mycorp,virtual-irq-controller" },
    { /* sentinel */ }
};

static struct platform_driver vic_driver = {
    .driver = {
        .name = "virtual-irq-controller",
        .of_match_table = vic_of_match,
    },
    .probe  = vic_probe,
    .remove = vic_remove,
};

module_platform_driver(vic_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple Virtual Interrupt Controller using irq_domain_add_simple");

测试代码

/*
 * @Author: your name
 * @Date: 2025-10-11 10:53:21
 * @LastEditTime: 2025-10-11 18:02:03
 * @LastEditors: Please set LastEditors
 * @Description: In User Settings Edit
 * @FilePath: linux-4.4.159driversgpudrm	estvic_client.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/of.h>

// 中断服务例程 (ISR)
static irqreturn_t vic_client_isr(int irq, void *dev_id) {
    pr_info("VIC_CLIENT: Interrupt received! (irq %d)
", irq);
    return IRQ_HANDLED;
}

// 平台驱动 probe 函数
static int vic_client_probe(struct platform_device *pdev) {
    int irq;
    int ret;

    /*
    #define IRQF_TRIGGER_NONE       0x00000000
    #define IRQF_TRIGGER_RISING     0x00000001
    #define IRQF_TRIGGER_FALLING    0x00000002
    #define IRQF_TRIGGER_HIGH       0x00000004
    #define IRQF_TRIGGER_LOW        0x00000008

    */
    unsigned long irq_flags = IRQF_TRIGGER_LOW;
    
    printk("VIC_CLIENT: irq_flags = %lu
", irq_flags);
    // 从设备树获取 Linux IRQ 号
    /*
        要想使用platform_get_irq函数,在设备树中必须配置下面2个属性:
        interrupt-parent = <&vic>;
        interrupts = <60 0x4>;

        会调用到中断控制器中的.xlate回调函数函数,会根据传入的硬件中断号,生成一个Linux中断号
    */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "Failed to get IRQ from platform data: %d
", irq);
        return irq;
    }

    dev_info(&pdev->dev, "Obtained Linux irq %d
", irq);
    
    // 请求中断
    ret = devm_request_irq(&pdev->dev, irq, vic_client_isr, irq_flags, "vic-client-irq", pdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request irq %d
", irq);
        return ret;
    }

    dev_info(&pdev->dev, "Successfully requested irq %d
", irq);
    return 0;
}

// 平台驱动 remove 函数
static int vic_client_remove(struct platform_device *pdev) {
    // devm_request_irq 会自动释放,无需手动 free_irq
    pr_info("VIC_CLIENT: Client driver removed
");
    return 0;
}

static const struct of_device_id vic_client_of_match[] = {
    { .compatible = "mycorp,virtual-irq-client" },
    { /* sentinel */ }
};

static struct platform_driver vic_client_driver = {
    .driver = {
        .name = "virtual-irq-client",
        .of_match_table = vic_client_of_match,
    },
    .probe  = vic_client_probe,
    .remove = vic_client_remove,
};

module_platform_driver(vic_client_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Client driver for the Simple Virtual IRQ Controller");

日志如下:

/ko # insmod vic_driver.ko
VIC: domain bc163600
virtual-irq-controller virtual_irq_controller: Simple Virtual IRQ Controller initialized, range 0-99

/ko # insmod vic_client.ko
VIC_CLIENT: irq_flags = 8
VIC: Mapping public hwirq 60 to Linux irq 46
VIC: Setting type 1 for hwirq 60, irq 46,domain bc163600
virtual-irq-client virtual_irq_client: Obtained Linux irq 46
VIC: Setting type 8 for hwirq 60, irq 46,domain bc163600
VIC: Unmasking public hwirq 60, irq 46,domain bc163600
virtual-irq-client virtual_irq_client: Successfully requested irq 46

查看中断信息
/ko # cat /proc/interrupts
           CPU0       CPU1
 虚拟中断号       发生中断的次数   名称  硬件中断号    触发方式   名称
 46:          0          0       VIC  60            Level     vic-client-irq

模拟触发中断
/ko # echo 60 > /sys/devices/platform/virtual_irq_controller/trigger
VIC: Triggering public hwirq 60 (Linux irq 46)
VIC: Masking public hwirq 60, irq 46,domain bc163600
VIC_CLIENT: Interrupt received! (irq 46)
VIC: Unmasking public hwirq 60, irq 46,domain bc163600

可以看到下面触发了一次中断
/ko # cat /proc/interrupts
           CPU0       CPU1
 46:          1          0       VIC  60 Level     vic-client-irq

图片

✅ 结论

输出结论

待查资料问题

  • ❓ 问题 1:?
  • ❓ 问题 2:?

参考链接

  • 官方文档
© 版权声明

相关文章

1 条评论

  • 头像
    北京慈一堂预约客服刘 读者

    学到了💪

    无记录
    回复