• 嵌入式Linux应用开发-基础知识-第十八章系统对中断的处理②


    第十八章 Linux系统对中断的处理 ②

    在这里插入图片描述

    18.3 Linux中断系统中的重要数据结构

    本节内容,可以从 request_irq(include/linux/interrupt.h)函数一路分析得到。 能弄清楚下面这个图,对 Linux中断系统的掌握也基本到位了。
    在这里插入图片描述

    最核心的结构体是 irq_desc,之前为了易于理解,我们说在 Linux内核中有一个中断数组,对于每一个硬件中断,都有一个数组项,这个数组就是 irq_desc数组。

    注意:如果内核配置了 CONFIG_SPARSE_IRQ,那么它就会用基数树(radix tree)来代替 irq_desc数组。SPARSE的意思是“稀疏”,假设大小为 1000的数组中只用到 2个数组项,那不是浪费嘛?所以在中断比较“稀疏”的情况下可以用基数树来代替数组。

    18.3.1 irq_desc数组

    irq_desc结构体在 include/linux/irqdesc.h中定义,主要内容如下图:
    在这里插入图片描述

    每一个 irq_desc数组项中都有一个函数:handle_irq,还有一个 action链表。要理解它们,需要先看中断结构图:
    在这里插入图片描述

    外部设备 1、外部设备 n共享一个 GPIO中断 B,多个 GPIO中断汇聚到 GIC(通用中断控制器)的 A号中断,GIC再去中断 CPU。那么软件处理时就是反过来,先读取 GIC获得中断号 A,再细分出 GPIO中断 B,最后判断是哪一个外部芯片发生了中断。
    所以,中断的处理函数来源有三:
    ① GIC的处理函数:
    假设 irq_desc[A].handle_irq是 XXX_gpio_irq_handler(XXX指厂家),这个函数需要读取芯片的 GPIO控制器,细分发生的是哪一个 GPIO中断(假设是 B),再去调用 irq_desc[B]. handle_irq。
    注意:irq_desc[A].handle_irq细分出中断后 B,调用对应的 irq_desc[B].handle_irq。
    显然中断 A是 CPU感受到的顶层的中断,GIC中断 CPU时,CPU读取 GIC状态得到中断 A。
    ② 模块的中断处理函数:
    比如对于 GPIO模块向 GIC发出的中断 B,它的处理函数是 irq_desc[B].handle_irq。
    BSP开发人员会设置对应的处理函数,一般是 handle_level_irq或 handle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。
    注意:导致 GPIO中断 B发生的原因很多,可能是外部设备 1,可能是外部设备 n,可能只是某一个设备,也可能是多个设备。所以 irq_desc[B].handle_irq会调用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。
    ③ 外部设备提供的处理函数:
    这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”:它知道如何判断设备是否发生了中断,如何处理中断。
    对于共享中断,比如 GPIO中断 B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以 irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。
    一旦程序确定发生了 GPIO中断 B,那么就会从链表里把那些函数取出来,一一执行。
    这个链表就是 action链表。
    对于我们举的这个例子来说,irq_desc数组如下:
    在这里插入图片描述

    18.3.2 irqaction结构体

    irqaction结构体在 include/linux/interrupt.h中定义,主要内容如下图:
    在这里插入图片描述

    当调用 request_irq、request_threaded_irq注册中断处理函数时,内核就会构造一个 irqaction结构体。在里面保存 name、dev_id等,最重要的是 handler、thread_fn、thread。
    handler是中断处理的上半部函数,用来处理紧急的事情。
    thread_fn对应一个内核线程 thread,当 handler执行完毕,Linux内核会唤醒对应的内核线程。在内核线程里,会调用 thread_fn函数。
    可以提供 handler而不提供 thread_fn,就退化为一般的 request_irq函数。
    可以不提供 handler只提供 thread_fn,完全由内核线程来处理中断。
    也可以既提供 handler也提供 thread_fn,这就是中断上半部、下半部。
    里面还有一个名为 sedondary的 irqaction结构体,它的作用以后再分析。
    在 reqeust_irq时可以传入 dev_id,为何需要 dev_id?作用有 2:
    ① 中断处理函数执行时,可以使用 dev_id
    ② 卸载中断时要传入 dev_id,这样才能在 action链表中根据 dev_id找到对应项 所以在共享中断中必须提供 dev_id,非共享中断可以不提供。

    18.3.3 irq_data结构体

    irq_data结构体在 include/linux/irq.h中定义,主要内容如下图:
    在这里插入图片描述

    它就是个中转站,里面有 irq_chip指针 irq_domain指针,都是指向别的结构体。
    比较有意思的是 irq、hwirq,irq是软件中断号,hwirq是硬件中断号。比如上面我们举的例子,在 GPIO中断 B是软件中断号,可以找到 irq_desc[B]这个数组项;GPIO里的第 x号中断,这就是 hwirq。
    谁来建立 irq、hwirq之间的联系呢?由 irq_domain来建立。irq_domain会把本地的 hwirq映射为全局的 irq,什么意思?比如 GPIO控制器里有第 1号中断,UART模块里也有第 1号中断,这两个“第 1号中断”是不一样的,它们属于不同的“域”──irq_domain。

    18.3.4 irq_domain结构体

    irq_domain结构体在 include/linux/irqdomain.h中定义,主要内容如下图:
    在这里插入图片描述

    当我们后面从设备树讲起,如何在设备树中指定中断,设备树的中断如何被转换为 irq时,irq_domain将会起到极大的作为。
    这里基于入门的解度简单讲讲,在设备树中你会看到这样的属性:

    interrupt-parent = <&gpio1>; 
    interrupts = <5 IRQ_TYPE_EDGE_RISING>; 
    
    • 1
    • 2

    它表示要使用 gpio1里的第 5号中断,hwirq就是 5。
    但是我们在驱动中会使用 request_irq(irq, handler)这样的函数来注册中断,irq是什么?它是软件中断号,它应该从“gpio1的第 5号中断”转换得来。
    谁把 hwirq转换为 irq?由 gpio1的相关数据结构,就是 gpio1对应的 irq_domain结构体。
    irq_domain结构体中有一个 irq_domain_ops结构体,里面有各种操作函数,主要是:
    ① xlate
    用来解析设备树的中断属性,提取出 hwirq、type等信息。
    ② map
    把 hwirq转换为 irq。

    18.3.5 irq_chip结构体

    irq_chip结构体在 include/linux/irq.h中定义,主要内容如下图:
    在这里插入图片描述

    这个结构体跟“chip”即芯片相关,里面各成员的作用在头文件中也列得很清楚,摘录部分如下:

    * @irq_startup: start up the interrupt (defaults to ->enable if NULL) 
    * @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL) 
    * @irq_enable:  enable the interrupt (defaults to chip->unmask if NULL) 
    * @irq_disable: disable the interrupt 
    * @irq_ack:  start of a new interrupt 
    * @irq_mask:  mask an interrupt source 
    * @irq_mask_ack: ack and mask an interrupt source 
    * @irq_unmask:  unmask an interrupt source 
    * @irq_eoi:  end of interrupt 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们在 request_irq后,并不需要手工去使能中断,原因就是系统调用对应的 irq_chip里的函数帮我们使能了中断。
    我们提供的中断处理函数中,也不需要执行主芯片相关的清中断操作,也是系统帮我们调用 irq_chip中的相关函数。
    但是对于外部设备相关的清中断操作,还是需要我们自己做的。
    就像上面图里的“外部设备 1“、“外部设备 n”,外设备千变万化,内核里可没有对应的清除中断操作。

    18.4 在设备树中指定中断_在代码中获得中断

    18.4.1 设备树里中断节点的语法

    参考文档:
    内核 Documentation\devicetree\bindings\interrupt-controller\interrupts.txt

    18.4.1.1 设备树里的中断控制器

    中断的硬件框图如下:
    在这里插入图片描述

    在硬件上,“中断控制器”只有 GIC这一个,但是我们在软件上也可以把上图中的“GPIO”称为“中断控制器”。很多芯片有多个 GPIO模块,比如 GPIO1、GPIO2等等。所以软件上的“中断控制器”就有很多个:GIC、GPIO1、GPIO2等等。
    GPIO1连接到 GIC,GPIO2连接到 GIC,所以 GPIO1的父亲是 GIC,GPIO2的父亲是 GIC。
    假设 GPIO1有 32个中断源,但是它把其中的 16个汇聚起来向 GIC发出一个中断,把另外 16个汇聚起来向 GIC发出另一个中断。这就意味着 GPIO1会用到 GIC的两个中断,会涉及 GIC里的 2个 hwirq。
    这些层级关系、中断号(hwirq),都会在设备树中有所体现。

    在设备树中,中断控制器节点中必须有一个属性:interrupt-controller,表明它是“中断控制器”。 还必须有一个属性:#interrupt-cells,表明引用这个中断控制器的话需要多少个 cell。
    #interrupt-cells的值一般有如下取值:
    ① #interrupt-cells=<1>
    别的节点要使用这个中断控制器时,只需要一个 cell来表明使用“哪一个中断”。
    ② #interrupt-cells=<2>
    别的节点要使用这个中断控制器时,需要一个 cell来表明使用“哪一个中断”;
    还需要另一个 cell来描述中断,一般是表明触发类型:
    第 2个 cell的 bits[3:0] 用来表示中断触发类型(trigger type and level flags):

    1 = low-to-high edge triggered,上升沿触发 
    2 = high-to-low edge triggered,下降沿触发 
    4 = active high level-sensitive,高电平触发 
    8 = active low level-sensitive,低电平触发 
    
    • 1
    • 2
    • 3
    • 4

    示例如下:

    vic: intc@10140000 { 
     compatible = "arm,versatile-vic"; 
     interrupt-controller; 
     #interrupt-cells = <1>; 
     reg = <0x10140000 0x1000>; 
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果中断控制器有级联关系,下级的中断控制器还需要表明它的“interrupt-parent”是谁,用了interrupt-parent”中的哪一个“interrupts”,请看下一小节。

    18.4.1.2 设备树里使用中断

    一个外设,它的中断信号接到哪个“中断控制器”的哪个“中断引脚”,这个中断的触发方式是怎样的? 这 3个问题,在设备树里使用中断时,都要有所体现。
    ① interrupt-parent=<&XXXX>
    你要用哪一个中断控制器里的中断?
    ② interrupts
    你要用哪一个中断?
    Interrupts里要用几个 cell,由 interrupt-parent对应的中断控制器决定。在中断控制器里有
    “#interrupt-cells”属性,它指明了要用几个 cell来描述中断。
    比如:

    i2c@7000c000 { 
    gpioext: gpio-adnp@41 { 
     compatible = "ad,gpio-adnp"; 
    interrupt-parent = <&gpio>; interrupts = <160 1>; 
    gpio-controller; 
    #gpio-cells = <1>; 
     
     
      
     }; ...... }; 
    interrupt-controller; 
    #interrupt-cells = <2>; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ③ 新写法:interrupts-extended
    一个“interrupts-extended”属性就可以既指定“interrupt-parent”,也指定“interrupts”,比如: interrupts-extended = <&intc1 5 1>, <&intc2 1 0>;

    18.4.2 设备树里中断节点的示例

    以 xxxxxx_IMX6ULL开发板为例,在 arch/arm/boot/dts目录下可以看到 2个文件:imx6ull.dtsi、xxxxxx_imx6ull-14x14.dts,把里面有关中断的部分内容抽取出来。
    在这里插入图片描述

    从设备树反推 IMX6ULL的中断体系,如下,比之前的框图多了一个“GPC INTC”:
    在这里插入图片描述

    GPC INTC的英文是:General Power Controller, Interrupt Controller。它提供中断屏蔽、中断状态查询功能,实际上这些功能在 GIC里也实现了,个人觉得有点多余。除此之外,它还提供唤醒功能,这才是保留它的原因。

    18.4.3 在代码中获得中断

    之前我们提到过,设备树中的节点有些能被转换为内核里的 platform_device,有些不能,回顾如下: A. 根节点下含有 compatile属性的子节点,会转换为 platform_device
    B. 含有特定 compatile属性的节点的子节点,会转换为 platform_device
    如果一个节点的 compatile属性,它的值是这 4者之一: “simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”,
    那么它的子结点(需含 compatile属性)也可以转换为 platform_device。
    C. 总线 I2C、SPI节点下的子节点:不转换为 platform_device
    某个总线下到子节点,应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。

    18.4.3.1 对于 platform_device

    一个节点能被转换为 platform_device,如果它的设备树里指定了中断属性,那么可以从
    platform_device中获得“中断资源”,函数如下,可以使用下列函数获得 IORESOURCE_IRQ资源,即中断号:

    /** 
     * platform_get_resource - get a resource for a device 
     * @dev: platform device 
     * @type: resource type   // 取哪类资源?IORESOURCE_MEM、IORESOURCE_REG 
    *                      // IORESOURCE_IRQ等 
     * @num: resource index  // 这类资源中的哪一个? 
     */ 
    struct resource *platform_get_resource(struct platform_device *dev, 
               unsigned int type, unsigned int num); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    18.4.3.2 对于 I2C设备、SPI设备

    对于 I2C设备节点,I2C总线驱动在处理设备树里的 I2C子节点时,也会处理其中的中断信息。一个I2C设备会被转换为一个 i2c_client结构体,中断号会保存在 i2c_client的 irq成员里,代码如下(drivers/i2c/i2c-core.c):
    在这里插入图片描述
    对于 SPI设备节点,SPI总线驱动在处理设备树里的 SPI子节点时,也会处理其中的中断信息。一个SPI设备会被转换为一个 spi_device结构体,中断号会保存在 spi_device的 irq成员里,代码如下(drivers/spi/spi.c):
    在这里插入图片描述

    18.4.3.3 调用 of_irq_get获得中断号

    如果你的设备节点既不能转换为 platform_device,它也不是 I2C设备,不是 SPI设备,那么在驱动程序中可以自行调用 of_irq_get函数去解析设备树,得到中断号。

    18.4.3.4 对于 GPIO

    参考:drivers/input/keyboard/gpio_keys.c
    可以使用 gpio_to_irq或 gpiod_to_irq获得中断号。 举例,假设在设备树中有如下节点:

    gpio-keys { 
      compatible = "gpio-keys"; 
      pinctrl-names = "default"; 
     
     user { 
        label = "User Button"; 
        gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;     gpio-key,wakeup; 
        linux,code = <KEY_1>; 
      }; 
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    那么可以使用下面的函数获得引脚和 flag:

    button->gpio = of_get_gpio_flags(pp, 0, &flags); 
    bdata->gpiod = gpio_to_desc(button->gpio); 
    
    • 1
    • 2

    再去使用 gpiod_to_irq获得中断号:

    irq = gpiod_to_irq(bdata->gpiod); 
    
    • 1
  • 相关阅读:
    【MySQL开发手册(基础篇)】
    VEX —— Functions|Math
    Vue3之ElementPlus中Table选中数据的获取与清空方法
    MongoDB的UTCDateTime如何使用
    ubuntu编译 linphone sdk android源码下载
    Vue/ExtJS+SpringBoot打造双版本通讯录管理系统
    从链接器的角度详细分析g++报错: (.text+0x24): undefined reference to `main'
    【博客549】利用go的lazy load实现defer的延迟判断
    【dp树状数组优化】跳跃(jump)
    【web-避开客户端控件】(2.3.2)收集使用数据:拦截浏览器扩展的流量
  • 原文地址:https://blog.csdn.net/kingpower2018/article/details/133442796