两类中断控制器处理流程详解(链式和层级)

1. 下级中断控制器的类别

GIC之下的中断控制器分为两类:链式(chained)、层级(hierarchy)。

1.1 链式中断控制器(chained)

上图中,左边的"chained intc"就是链式中断控制器。

它底下的4个中断触发时,都会导致GIC的33号中断被触发。

处理中断时,需要分辨:是谁触发了GIC 33号中断?这需要读取"chained intc"中的寄存器。

 

1.2 层级中断控制器(hierarchy)

上图中,右边边的"hierarchy intc"就是层级中断控制器。

它底下的4个中断,跟GIC中的4个中断一一对应。

处理GIC 100~103号中断时,不需要读取"hierarchy intc"的寄存器来分辨是谁触发了中断。

1.3 链式中断控制器的处理流程

下图中:

  • handleA、irq_dataA由GIC驱动提供

  • handleB、irq_dataB由GPIO驱动提供

  • handleC也是GPIO驱动提供

  • 假设GPIO模块下有4个引脚,都可以产生中断,都连接到GIC的33号中断

  • GPIO就是一个链式中断控制器,它底下有4个中断

  • 对于GPIO模块中0~3这四个hwirq,分配四个irq_desc(irq_desc 是一个结构体,代表了一个中断描述符。它包含了与特定硬件中断请求(IRQ)相关的所有信息和控制数据。)

    • 可以一下子分配4个:legacy,老方法

    • 也可以用到时再分配:linear,新方法

  • 假设这4个irq_desc的序号为100~103,在GPIO domain中记录(0,100) (1,101)(2,102) (3,103)

  • 对于KEY,注册中断时就是:request_irq(102, ...)

  • 按下KEY时:

    • 程序从GIC中读取寄存器知道发生了33号中断,通过GIC irq_domain可以知道virq为17

    • 处理virq 17号中断:调用irq_desc[17].handle_irq,即handleB

      • mask/ack中断: 调用irq_desc[17].irq_data->irq_chip的函数,即irq_dataA

      • 细分中断源、处理

        • 读取GPIO寄存器,确定是GPIO里2号引脚发生中断

        • 通过GPIO irq_domain可以知道virq为102

        • 处理virq 102号中断:调用irq_desc[102].handle_irq,即handleC

          • mask/ack中断: 调用irq_desc[102].irq_data->irq_chip的函数

          • 调用irq_desc[102].action链表中用户注册的函数

          • unmask中断: 调用irq_desc[102].irq_data->irq_chip的函数

      • unmask中断: 调用irq_desc[17].irq_data->irq_chip的函数

1.4 层级中断控制器的处理流程

下图中:

  • handleA、irq_dataA由GIC驱动提供

  • irq_dataB由GPIO驱动提供,不需要handleB

  • 假设GPIO模块下有4个引脚,都可以产生中断,分别链接到GIC的100~103号中断

  • GPIO就是一个层级中断控制器

  • 对于GPIO模块中0~3这四个hwirq,分配四个irq_desc,用到时再分配

  • 假设这4个irq_desc的序号为234~237

    • 在GIC domain中记录(100,234) (101,235)(102,236) (103,237)

    • 在GPIO domain中记录(0,234) (1,235)(2,236) (3,237)

  • 对于KEY,注册中断时就是:request_irq(236, ...)

  • 按下KEY时:

    • 程序从GIC中读取寄存器知道发生了102号中断,通过GIC irq_domain可以知道virq为236

    • 处理virq 236号中断:调用irq_desc[236].handle_irq,即handleA

      • mask/ack中断:

        • 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB

          • 它会调用父级irq_dataA->irq_chip的函数

      • 调用irq_desc[236].action链表中用户注册的函数

      • unmask中断:

        • 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB

          • 它会调用父级irq_dataA->irq_chip的函数 2. 

2. 链式中断控制器驱动程序分析

2.1 中断处理流程

为方便描述,假设下级的链式中断控制器就是GPIO控制器。

沿着中断的处理流程,GIC之下的中断控制器涉及这4个重要部分:handleB、GPIO Domain、handleC、irq_chip

  • handleB:处理GIC 33号中断,handleB由GPIO驱动提供

    • 屏蔽GIC 33号中断:调用irq_dataA的irq_chip的函数,irq_dataA由GIC驱动提供

    • 细分并处理某个GPIO中断:

      • 读取GPIO寄存器得到hwirq,通过GPIO Domain转换为virq,假设是102

      • 调用irq_desc[102].handle_irq,即handleC

    • 清除GIC 33号中断:调用irq_dataA的irq_chip的函数,由GIC驱动提供

  • handleC:处理GPIO 2号中断,handleC由GPIO驱动提供

    • 屏蔽GPIO 2号中断:调用irq_dataB的irq_chip的函数,由GPIO驱动提供

    • 处理:调用actions链表中用户注册的函数

    • 清除GPIO 2号中断:调用irq_dataB的irq_chip的函数,由GPIO驱动提供

2.2  irq_domain的核心作用

怎么把handleB、GPIO Domain、handleC、irq_chip这4个结构体组织起来,irq_domain是核心。

我们从使用中断的流程来讲解。

  • 在设备树里指定使用哪个中断

   gpio_keys_100ask {
        compatible = "100ask,gpio_key";
		interrupt-parent = <&gpio5>;
		interrupts = <3 IRQ_TYPE_EDGE_BOTH>,
    };
  • 2.3内核解析、处理设备树的中断信息

    • 根据interrupt-parent找到驱动程序注册的irq_domain

    • 使用irq_domain.ops中的translate或xlate函数解析设备树,得到hwirq和type

    • 分配/找到irq_desc,得到virq

      • 把(hwirq, virq)的关系存入irq_domain

      • 把virq存入platform_device的resource中

    • 使用irq_domain.ops中的alloc或map函数进行设置

      • 可能是替换irq_desc[virq].handle_irq函数

      • 可能是替换irq_desc[virq].irq_data,里面有irq_chip

  • 用户的驱动程序注册中断

    • 从platform_device的resource中得到中断号virq

    • request_irq(virq, ..., func)

  • 发生中断、处理中断:处理流程见上面。

2.3 驱动程序编写

2.3.1 gpio_key_drv.c

 

#include <linux/module.h>      // 模块化编程支持
#include <linux/fs.h>          // 文件系统支持
#include <linux/errno.h>       // 错误号定义
#include <linux/miscdevice.h>  // 杂项设备支持
#include <linux/kernel.h>      // 标准内核函数
#include <linux/major.h>       // 设备主设备号定义
#include <linux/mutex.h>       // 互斥锁支持
#include <linux/proc_fs.h>     // 进程文件系统支持
#include <linux/seq_file.h>    // 顺序文件操作支持
#include <linux/stat.h>        // 状态定义
#include <linux/init.h>        // 模块初始化和清理宏
#include <linux/device.h>      // 设备模型支持
#include <linux/tty.h>         // 终端设备支持
#include <linux/kmod.h>        // 内核模块支持
#include <linux/gfp.h>         // 内存分配标志定义
#include <linux/gpio/consumer.h> // GPIO消费者支持
#include <linux/platform_device.h> // 平台设备支持
#include <linux/of_gpio.h>     // 设备树中GPIO支持
#include <linux/of_irq.h>       // 设备树中中断支持
#include <linux/interrupt.h>    // 中断支持
#include <linux/irq.h>          // IRQ支持
#include <linux/slab.h>         // 内存分配

struct gpio_key {            // 定义GPIO按键结构体
    char name[100];
    int irq;
    int cnt;
};

static struct gpio_key gpio_keys_100ask[100]; // 全局数组,存储按键信息

// 中断服务程序
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    struct gpio_key *gpio_key = dev_id;

    // 打印中断服务程序被调用的按键名称和计数器
    printk(KERN_INFO "gpio_key_isr %s cnt %d\n", gpio_key->name, gpio_key->cnt++);

    return IRQ_HANDLED; // 告诉内核中断已经被处理
}

// 平台设备探测函数
static int gpio_key_probe(struct platform_device *pdev)
{
    int err;
    int i = 0;
    int irq;

    printk(KERN_INFO "%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    while (1) {
        irq = platform_get_irq(pdev, i); // 获取平台设备指定索引的IRQ号
        if (irq <= 0)
            break;
        gpio_keys_100ask[i].irq = irq; // 保存IRQ号
        sprintf(gpio_keys_100ask[i].name, "100as_virtual_key%d", i); // 格式化按键名称

        // 请求IRQ
        err = devm_request_irq(&pdev->dev, gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, gpio_keys_100ask[i].name, &gpio_keys_100ask[i]);
        printk(KERN_INFO "devm_request_irq %d for %s, err = %d\n", irq, gpio_keys_100ask[i].name, err);
        i++;
    }

    return 0;
}

// 平台设备移除函数
static int gpio_key_remove(struct platform_device *pdev)
{
    return 0;
}

// 设备树匹配表
static const struct of_device_id ask100_keys[] = {
    { .compatible = "100ask,gpio_key" },
    { },
};

// 定义platform_driver
static struct platform_driver gpio_keys_driver = {
    .probe      = gpio_key_probe, // 设备探测函数
    .remove     = gpio_key_remove, // 设备移除函数
    .driver     = {
        .name   = "100ask_gpio_key", // 驱动名称
        .of_match_table = ask100_keys, // 设备树匹配表
    },
};

// 模块初始化入口
static int __init gpio_key_init(void)
{
    int err;

    printk(KERN_INFO "%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    err = platform_driver_register(&gpio_keys_driver); // 注册platform_driver

    return err;
}

// 模块退出出口
static void __exit gpio_key_exit(void)
{
    printk(KERN_INFO "%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

    platform_driver_unregister(&gpio_keys_driver); // 注销platform_driver
}

module_init(gpio_key_init); // 注册初始化函数
module_exit(gpio_key_exit); // 注册退出函数

MODULE_LICENSE("GPL"); // 模块许可证

代码解释:

  • 头文件包含:代码包括了处理文件系统、设备树、中断、GPIO等所需的头文件。
  • 全局变量:定义了一个gpio_key结构体数组gpio_keys_100ask,用于存储每个按键的信息。
  • 中断服务程序gpio_key_isr是一个中断处理函数,当按键的GPIO线状态变化时被调用。
  • 平台设备探测函数gpio_key_probe在设备被探测到时被调用,进行设备初始化,包括获取IRQ、注册中断处理函数。
  • 平台设备移除函数gpio_key_remove在设备被移除时被调用,进行资源清理。
  • 设备树匹配表:定义了一个匹配表,用于设备树匹配。
  • 驱动注册:在模块初始化时注册平台驱动,在模块退出时注销平台驱动。
  • 许可证:指定了模块的许可证为GPL。

这个模块实现了一个基于GPIO的虚拟按键驱动程序,它可以处理多个按键的中断,并在设备树中定义按键的配置。

2.3.2 virtual_int_controller

#include <linux/kernel.h>    // 标准内核函数
#include <linux/module.h>    // 模块化编程支持
#include <linux/clk.h>       // 时钟支持
#include <linux/err.h>       // 错误处理
#include <linux/init.h>      // 模块初始化和清理宏
#include <linux/interrupt.h> // 中断支持
#include <linux/io.h>        // IO操作
#include <linux/random.h>    // 随机数生成
#include <linux/irq.h>       // IRQ支持
#include <linux/irqdomain.h> // IRQ域支持
#include <linux/irqchip/chained_irq.h> // 链式IRQ支持
#include <linux/platform_device.h> // 平台设备支持
#include <linux/pm_runtime.h> // 电源管理运行时
#include <linux/slab.h>      // 内存分配
#include <linux/gpio/driver.h> // GPIO驱动支持
#include <linux/of.h>        // 设备树支持
#include <linux/of_device.h> // 设备树设备支持
#include <linux/bug.h>       // 调试支持
#include <linux/random.h>    // 随机数生成

static struct irq_domain *virtual_intc_domain; // 定义一个全局变量,指向虚拟中断域

// 定义一个简单的函数来模拟获取硬件IRQ号
static int virtual_intc_get_hwirq(void) {
    return get_random_int() & 0x3; // 返回一个随机的硬件IRQ号
}

// 定义虚拟中断控制器的中断处理函数
static void virtual_intc_irq_handler(struct irq_desc *desc) {
    // ... 处理中断 ...
}

// 定义虚拟中断控制器的中断结束函数
static void virtual_intc_irq_eoi(struct irq_data *data) {
    // ... 处理中断结束 ...
}

// 定义虚拟中断控制器的IRQ芯片操作
static struct irq_chip virtual_intc_irq_chip = {
    .name = "100ask_virtual_intc",
    // ... 其他中断处理函数 ...
};

// 定义虚拟中断控制器的IRQ映射函数
static int virtual_intc_irq_map(struct irq_domain *h, unsigned int virq, irq_hw_number_t hw) {
    // ... 设置IRQ处理函数和IRQ芯片 ...
    return 0;
}

// 定义虚拟中断控制器的IRQ域操作
static const struct irq_domain_ops virtual_intc_domain_ops = {
    .xlate = irq_domain_xlate_onetwocell,
    .map   = virtual_intc_irq_map,
};

// 定义虚拟中断控制器的探测函数
static int virtual_intc_probe(struct platform_device *pdev) {
    // ... 从设备树获取中断号,设置中断处理函数,注册IRQ域 ...
    return 0;
}

// 定义虚拟中断控制器的移除函数
static int virtual_intc_remove(struct platform_device *pdev) {
    return 0;
}

// 定义设备树匹配表
static const struct of_device_id virtual_intc_of_match[] = {
    { .compatible = "100ask,virtual_intc", },
    { },
};

// 定义虚拟中断控制器的平台驱动
static struct platform_driver virtual_intc_driver = {
    .probe      = virtual_intc_probe,
    .remove     = virtual_intc_remove,
    .driver     = {
        .name   = "100ask_virtual_intc",
        .of_match_table = of_match_ptr(virtual_intc_of_match),
    }
};

// 定义模块初始化入口函数
static int __init virtual_intc_init(void) {
    // ... 注册platform_driver ...
    return platform_driver_register(&virtual_intc_driver);
}

// 定义模块退出出口函数
static void __exit virtual_intc_exit(void) {
    // ... 注销platform_driver ...
    platform_driver_unregister(&virtual_intc_driver);
}

module_init(virtual_intc_init);
module_exit(virtual_intc_exit);

MODULE_LICENSE("GPL"); // 模块许可证

代码解释:

  • 头文件包含:代码包括了处理中断、时钟、设备树、GPIO等所需的头文件。
  • 全局变量:定义了一个指向irq_domain结构体的指针,用于表示虚拟中断域。
  • 中断处理函数virtual_intc_irq_handler是一个中断处理函数,用于处理虚拟中断。
  • IRQ芯片操作:定义了一系列函数来操作IRQ芯片,例如virtual_intc_irq_ackvirtual_intc_irq_mask等。
  • IRQ映射函数virtual_intc_irq_map函数用于映射虚拟中断号到硬件中断号,并设置相应的处理函数和IRQ芯片。
  • IRQ域操作virtual_intc_domain_ops结构体定义了虚拟中断域的操作。
  • 平台设备探测和移除函数virtual_intc_probevirtual_intc_remove分别用于初始化和清理虚拟中断控制器。
  • 设备树匹配表:定义了一个匹配表,用于设备树匹配。
  • 平台驱动virtual_intc_driver结构体定义了虚拟中断控制器的平台驱动。
  • 模块初始化和退出函数virtual_intc_initvirtual_intc_exit分别用于注册和注销平台驱动。

这个模块实现了一个虚拟中断控制器,它可以模拟中断的发生和处理,对于嵌入式系统开发中的中断测试和调试非常有用。

2.3.3 virtual_intc.dts

/ {
  // 定义一个名为 virtual_intc_100ask 的虚拟中断控制器节点
  virtual_intc: virtual_intc_100ask {
      compatible = "100ask,virtual_intc";  // 设备兼容标识

      interrupt-controller;  // 声明该节点为中断控制器
      #interrupt-cells = <2>;  // 指定中断控制器需要两个单元来指定中断

      // 指定该中断控制器的父中断控制器,这里是系统中的顶级中断控制器
      interrupt-parent = <&intc>;

      // 注释掉的中断定义,适用于不同的硬件平台
      // interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>;    // stm32mp157
      // 启用的中断定义,适用于当前配置的硬件平台
      interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;  // imx6ull
  };

  // 定义一个名为 gpio_keys_100ask 的GPIO按键节点
  gpio_keys_100ask {
      compatible = "100ask,gpio_key";  // 设备兼容标识

      // 指定GPIO按键的中断父控制器,这里它将使用上面定义的虚拟中断控制器
      interrupt-parent = <&virtual_intc>;

      // 定义GPIO按键的中断信息,包括中断号和中断类型
      // 这里定义了四个GPIO按键的中断,每个按键产生一个高电平触发的中断
      interrupts = <
          0 IRQ_TYPE_LEVEL_HIGH,  // GPIO按键0的中断信息
          1 IRQ_TYPE_LEVEL_HIGH,  // GPIO按键1的中断信息
          2 IRQ_TYPE_LEVEL_HIGH,  // GPIO按键2的中断信息
          3 IRQ_TYPE_LEVEL_HIGH   // GPIO按键3的中断信息
      >;
  };
};

在这段代码中:

  • / 表示设备树的根节点。
  • virtual_intc: virtual_intc_100ask 定义了一个名为 virtual_intc_100ask 的虚拟中断控制器设备节点。
  • compatible 属性用于指定设备的兼容字符串,这有助于内核识别设备类型。
  • interrupt-controller 关键词表明该节点是一个中断控制器。
  • #interrupt-cells 属性定义了中断控制器需要多少个参数来完全指定一个中断。
  • interrupt-parent 属性指定了中断控制器的父节点,这是中断处理的层级结构。
  • interrupts 属性定义了该中断控制器的中断源,这里使用了 GIC_SPI 来指定通用中断控制器(GIC)的一个中断源。
  • gpio_keys_100ask 定义了一个GPIO按键设备节点,它使用 virtual_intc 作为中断父控制器。
  • interrupts 属性为GPIO按键设备定义了中断信息,包括中断号和中断类型。每个按键都映射到一个高电平触发的中断。

3. 层级中断控制器驱动程序分析

3.1 层级中断处理流程

设下级的层级中断控制器就是GPIO控制器。

下图中:

  • handleA、irq_dataA由GIC驱动提供

  • irq_dataB由GPIO驱动提供,不需要handleB

 

  • 假设GPIO模块下有4个引脚,都可以产生中断,分别链接到GIC的100~103号中断

  • GPIO就是一个层级中断控制器

  • 对于GPIO模块中0~3这四个hwirq,分配四个irq_desc,用到时再分配

  • 假设这4个irq_desc的序号为234~237

    • 在GIC domain中记录(100,234) (101,235)(102,236) (103,237)

    • 在GPIO domain中记录(0,234) (1,235)(2,236) (3,237)

  • 对于KEY,注册中断时就是:request_irq(236, ...)

  • 按下KEY时:

    • 程序从GIC中读取寄存器知道发生了102号中断,通过GIC irq_domain可以知道virq为236

    • 处理virq 236号中断:调用irq_desc[236].handle_irq,即handleA

      • mask/ack中断:

        • 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB

          • 它会调用父级irq_dataA->irq_chip的函数

      • 调用irq_desc[236].action链表中用户注册的函数

      • unmask中断:

        • 调用irq_desc[236].irq_data->irq_chip的函数,即irq_dataB

          • 它会调用父级irq_dataA->irq_chip的函数

3.2 代码分析 

会涉及2个驱动程序:虚拟的中断控制器驱动程序,按键驱动程序,以及对应的设备树。

虚拟的中断控制器驱动程序中,涉及2个递归处理。

alloc的递归处理:

irq_chip的递归处理

3.3 上机实验

1. 设置交叉编译工具链

2. 编译替换设备树:修改设备树代码,编译设备树,复制到NFS目录,开发板上挂载NFS文件系统

更新设备树,重启开发板

3.编译、安装驱动程序

4. 测试运行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值