可锐资源网

技术资源分享平台,提供编程学习、网站建设、脚本开发教程

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

问题描述:

编写一个虚拟中断控制器,可以传递多个参数

SPI (Shared Peripheral Interrupt): 共享外设中断。这是最常见的,用于外部设备(如 UART, I2C, 以太网控制器等)产生的中断,可以路由到任何 CPU 核心。GIC_SPI 就是它的宏。
PPI (Private Peripheral Interrupt): 私有外设中断。这种中断是针对单个 CPU 核心的,通常用于每个核心私有的硬件,比如核心定时器。GIC_PPI 是它的宏。
SGI (Software-Generated Interrupt): 软件产生的中断,用于 CPU 核心之间的通信。

#define GIC_SPI 0
#define GIC_PPI 1

interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH)>;

日志

添加打印日志信息

分析步骤

第1步:
第2步:
...

代码片段

设备树

vic: virtual_irq_controller {
    compatible = "mycorp,virtual-irq-controller";
    interrupt-controller;

    #interrupt-cells = <3>;
    
    mycorp,hwirq-base = <0>; 
};

virtual_irq_client {
        compatible = "mycorp,virtual-irq-client";
        interrupt-parent = <&vic>;
        
        // === 修改点 2 ===
        // 按照新的3-cell规则提供中断信息。
        // 我们约定格式为: <group hwirq type>
        // group: 中断组 (我们自己定义的概念,比如 0 代表普通中断)
        // hwirq: 硬件中断号 (60)
        // type:  触发类型 (1, 上升沿)
        interrupts = <32 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

struct vic_chip_data {
    struct irq_domain *domain;
    u32 hwirq_base;
};

// irq_chip 回调 (vic_irq_mask, vic_irq_unmask, vic_irq_set_type) 保持不变
static void vic_irq_mask(struct irq_data *d) 
{ 
    pr_info("VIC: Masking hwirq %lu\n", d->hwirq); 
}

static void vic_irq_unmask(struct irq_data *d) 
{ 
    pr_info("VIC: Unmasking hwirq %lu\n", d->hwirq); 
}

static int vic_irq_set_type(struct irq_data *d, unsigned int type) 
{ 
    pr_info("VIC: Setting type %u for hwirq %lu\n", type, d->hwirq); 
    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,
};

// .map 回调保持不变
static int vic_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq) {
    pr_info("VIC: Mapping internal hwirq %lu to Linux irq %u\n", 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;
}


// ================================================================
// === 核心修改:实现自定义的 3-cell .xlate 回调 ===
// ================================================================
/**
 * @brief 自定义的 .xlate 回调,用于解析 3 个 cell 的中断描述。
 * @d:       指向我们的 irq_domain
 * @ctrl:    指向设备树中的中断控制器节点
 * @intspec: 指向中断描述参数数组 (e.g., [0, 60, 1])
 * @intsize: 参数的数量 (这里应该是 3)
 * @out_hwirq: [输出] 我们计算出的内部硬件中断号
 * @out_type:  [输出] 我们解析出的触发类型
 */
static int vic_domain_xlate_threecell(struct irq_domain *d, struct device_node *ctrl,
                                      const u32 *intspec, unsigned int intsize,
                                      irq_hw_number_t *out_hwirq, unsigned int *out_type)
{
    struct vic_chip_data *data = d->host_data;
    u32 group, hwirq;

    // 1. 检查参数数量是否正确
    if (intsize != 3) {
        pr_err("VIC: xlate: Expected 3 cells, but got %u\n", intsize);
        return -EINVAL;
    }

    // 2. 按照我们自己的约定解析 3 个 cell
    group = intspec[0];
    hwirq = intspec[1];
    *out_type = intspec[2]; // 第三个 cell 直接作为触发类型

    pr_info("VIC: xlate: Received spec (group=%u, hwirq=%u, type=%u)\n", group, hwirq, *out_type);
    
    // 4. 检查硬件中断号范围
    if (hwirq < data->hwirq_base || hwirq >= (data->hwirq_base + VIC_MAX_IRQS)) {
        pr_err("VIC: xlate: hwirq %u out of range\n", hwirq);
        return -EINVAL;
    }
    
    // 5. 计算最终的内部 hwirq 并输出
    // 在这个例子中,我们直接使用传入的 hwirq 作为内部 hwirq。
    *out_hwirq = group + hwirq;

    pr_info("VIC: xlate: Translated to internal hwirq %lu\n", *out_hwirq);

    return 0;
}


// === 修改点 3: 更新 irq_domain_ops 结构体 ===
static const struct irq_domain_ops vic_domain_ops = {
    .map    = vic_irq_map,
    // 使用我们自己实现的 3-cell xlate 函数!
    .xlate  = vic_domain_xlate_threecell,
};

// Sysfs 触发函数 (保持不变,但请注意它的行为)
// 注意:irq_find_mapping 的第二个参数是内部 hwirq。
// 所以 `echo 60 > trigger` 依然能工作,因为我们的 xlate 函数将外部的 60 映射到了内部的 60。
static ssize_t trigger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {
    struct vic_chip_data *data = dev_get_drvdata(dev);
    unsigned long hwirq_to_trigger;
    int linux_irq;

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

    // irq_find_mapping 使用的是内部 hwirq
    linux_irq = irq_find_mapping(data->domain, hwirq_to_trigger);
    if (linux_irq <= 0) {
        dev_err(dev, "Failed to find mapping for internal hwirq %lu. Is client loaded?\n", hwirq_to_trigger);
        return -ENXIO;
    }

    pr_info("VIC: Triggering internal hwirq %lu (Linux irq %d)\n", hwirq_to_trigger, linux_irq);

    local_irq_disable();
    generic_handle_irq(linux_irq);
    local_irq_enable();
    
    return count;
}
static DEVICE_ATTR_WO(trigger);

// probe 和 remove 函数 (保持不变)
static int vic_probe(struct platform_device *pdev) {
    struct device_node *node = pdev->dev.of_node;
    struct vic_chip_data *data;
    
    data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
    if (!data) return -ENOMEM;
    platform_set_drvdata(pdev, data);

    // 不需要读取 hwirq_base,因为我们的 xlate 函数会处理
    of_property_read_u32(node, "mycorp,hwirq-base", &data->hwirq_base);

    data->domain = irq_domain_add_linear(node, VIC_MAX_IRQS, &vic_domain_ops, data);
    if (!data->domain) {
        dev_err(&pdev->dev, "Failed to create IRQ domain\n");
        return -ENOMEM;
    }

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

    dev_info(&pdev->dev, "3-cell Virtual IRQ Controller initialized\n");
    return 0;
}

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: Controller removed\n");
    return 0;
}


// 模块 Boilerplate (保持不变)
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("Demo");
MODULE_DESCRIPTION("3-cell Virtual Interrupt Controller");

测试代码:

/*
 * @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.159\drivers\gpu\drm\test\vic_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)\n", 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\n", 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\n", irq);
        return irq;
    }

    dev_info(&pdev->dev, "Obtained Linux irq %d\n", 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\n", irq);
        return ret;
    }

    dev_info(&pdev->dev, "Successfully requested irq %d\n", 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\n");
    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_driver1.ko
virtual-irq-controller virtual_irq_controller: 3-cell Virtual IRQ Controller initialized

/ko # insmod vic_client.ko
VIC_CLIENT: irq_flags = 8
VIC: xlate: Received spec (group=32, hwirq=60, type=1)
VIC: xlate: Translated to internal hwirq 92
VIC: Mapping internal hwirq 92 to Linux irq 46
VIC: Setting type 1 for hwirq 92
virtual-irq-client virtual_irq_client: Obtained Linux irq 46
VIC: Setting type 8 for hwirq 92
VIC: Unmasking hwirq 92
virtual-irq-client virtual_irq_client: Successfully requested irq 46

可以看到硬件中断号不仅仅是在设备树中定义的硬件中断号,在次基础上加上了偏移
//主要是对应日常使用中的这种形式:interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH)>;
/ko # cat /proc/interrupts
46:          0          0       VIC  92 Level     vic-client-irq

/ko # echo 92 > /sys/devices/platform/virtual_irq_controller/trigger

VIC: Triggering internal hwirq 92 (Linux irq 46)
VIC: Masking hwirq 92
VIC_CLIENT: Interrupt received! (irq 46)
VIC: Unmasking hwirq 92

/ko # cat /proc/interrupts
46:          1          0       VIC  92 Level     vic-client-irq

图片

结论

输出结论

待查资料问题

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

参考链接

  • 官方文档
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言