❓ 问题描述:
编写一个虚拟中断控制器
日志
添加打印日志信息
分析步骤
第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:?
参考链接
- 官方文档
© 版权声明
文章版权归作者所有,未经允许请勿转载。



学到了💪