linux IRQ Management(三)- IRQ Framework_no interrupt controller found-程序员宅基地

技术标签: 中断子系统  

  • 了解irq management framework

1.Introduction

  Linux is a system on which devices notify the kernel about particular events by means of IRQs.The CPU exposes IRQ lines, shared or not, and used by connected devices, so that when a device needs the CPU it sends a request to the CPU. When the CPU gets this request it stops its actual job and saves its context, in order to serve the request issued by the device. After serving the device, its state is restored back to exactly where it stopped when the interruption occurred. There are so many IRQ lines, that another device is responsible for them to the CPU.

  Not only can devices raise interrupts, some processor operations can do that too. There are two different kinds of interrupts:

  • Synchronous interrupts called exceptions, produced by the CPU while processing instructions. These are non-maskable interrupts (NMI), and result from a critical malfunction such as hardware failure. They are always processed by the CPU.
  • Asynchronous interrupts called interrupts, are issued by other hardware devices. These are normal and maskable interrupts.

1.1.Exceptions

  It is as described are generated by the CPU when an ‘error’ occurs. Some exceptions are not really errors in most cases, such as page faults. Exceptions are a type of interrupt.

Exceptions are classified as:

  • Processor-detected exceptions: Those the CPU generates in response to an anomalous condition, and it is divided into three groups:

    • Faults: These can be corrected and the program may continue as if nothing happened.
    • Traps: Traps are reported immediately after the execution of the trapping instruction.
    • Aborts: Some severe unrecoverable error.
  • Programmed exception: These are requested by the programmer, handled like a trap.

1.2.Interrupts

  When an interrupt originates from an external device, it is referred to as a hardware interrupt. These signals are generated by external hardware to seek the attention of the processor on occurrence of a significant external event, for instance a key hit on the keyboard, a click on a mouse button, or moving the mouse trigger hardware interrupts through which the processor is notified about the availability of data to be read. Hardware interrupts occur asynchronously with respect to the processor clock (meaning they can occur at random times), and hence are also termed as asynchronous interrupts.

2.中断硬件描述

  一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器和CPU本身,SMP系统中断硬件的组成结构:
在这里插入图片描述

  • 设备:设备是发起中断的源,当设备需要请求某种服务的时候,它会发起一个硬件中断信号,通常,该信号会连接至中断控制器,由中断控制器做进一步的处理。在现代的移动设备中,发起中断的设备可以位于soc(system-on-chip)芯片的外部,也可以位于soc的内部,因为目前大多数soc都集成了大量的硬件IP,例如I2C、SPI、Display Controller等等。

  • 中断控制器:中断控制器负责收集所有中断源发起的中断,现有的中断控制器几乎都是可编程的,通过对中断控制器的编程,我们可以控制每个中断源的优先级、中断的电器类型,还可以打开和关闭某一个中断源,在smp系统中,甚至可以控制某个中断源发往哪一个CPU进行处理。对于ARM架构的soc,使用较多的中断控制器是VIC(Vector Interrupt Controller),进入多核时代以后,GIC(General Interrupt Controller)的应用也开始逐渐变多。

  • CPU:CPU是最终响应中断的部件,它通过对可编程中断控制器的编程操作,控制和管理者系统中的每个中断,当中断控制器最终判定一个中断可以被处理时,它会根据事先的设定,通知其中一个或者是某几个cpu对该中断进行处理,虽然中断控制器可以同时通知数个cpu对某一个中断进行处理,实际上,最后只会有一个cpu相应这个中断请求,但具体是哪个cpu进行响应是可能是随机的,中断控制器在硬件上对这一特性进行了保证,不过这也依赖于操作系统对中断系统的软件实现。在smp系统中,cpu之间也通过IPI(inter processor interrupt)中断进行通信。

  中断硬件系统主要有三种器件:各个外设、中断控制器和CPU。各个外设提供irq request line,在发生中断事件时,通过irq request line上的电气信号向CPU系统请求处理。由于外设的irq request line太多,CPU需要一个Interrupt controller帮忙。Interrupt Controller是连接外设中断系统和CPU系统的桥梁。根据外设irq request line的多少,Interrupt Controller可以级联。CPU的主要功能是运算,因此CPU并不处理中断优先级,那是Interrupt controller的事情。

  对于CPU而言,一般有两种中断请求:

  • X86,有可屏蔽中断和不可屏蔽中断。

  • ARM,是IRQ和FIQ,分别进入IRQ mode和FIQ mode。
    在这里插入图片描述
      系统中有若干个CPU block用来接收中断事件并进行处理,若干个Interrupt controller形成树状的结构,汇集系统中所有外设的irq request line,并将中断事件分发给某一个CPU block进行处理。

  • 中断接口:

    • 一个硬件的信号线,通过电平信号传递中断事件(ARM以及GIC组成的中断系统)。
    • x86+APIC(Advanced Programmable Interrupt Controller)组成的系统,每个x86的核有一个Local APIC,这些Local APIC们通过ICC(Interrupt Controller Communication)bus连接到IO APIC上。IO APIC收集各个外设的中断,并翻译成总线上的message,传递给某个CPU上的Local APIC。因此,上面的红色线条也是逻辑层面的中断信号,可能是实际的PCB上的铜线(或者SOC内部的铜线),也可能是一个message而已。
  • 控制接口:
    CPU和Interrupt Controller之间还需要有控制信息的交流。Interrupt Controller会开放一些寄存器让CPU访问、控制。

3.通用中断子系统软件抽象

  在通用中断子系统(generic irq)出现之前,内核使用__do_IRQ处理所有的中断,这意味着__do_IRQ中要处理各种类型的中断,这会导致软件的复杂性增加,层次不分明,而且代码的可重用性也不好。到了内核版本2.6.38,__do_IRQ这种方式已经彻底在内核的代码中消失了。通用中断子系统的原型最初出现于ARM体系中,一开始内核的开发者们把3种中断类型区分出来,它们是:

  • 电平触发中断(level type)
  • 边缘触发中断(edge type)
  • 简易的中断(simple type)

  后来又针对某些需要回应eoi(end of interrupt)的中断控制器,加入了fast eoi type,针对smp加入了per cpu type。把这些不同的中断类型抽象出来后,成为了中断子系统的流控层。要使所有的体系架构都可以重用这部分的代码,中断控制器也被进一步地封装起来,形成了中断子系统中的硬件封装层。通用中断子系统的层次结构:
在这里插入图片描述
3.1.硬件封装层

  它包含了体系架构相关的所有代码,包括中断控制器的抽象封装,arch相关的中断初始化,以及各个IRQ的相关数据结构的初始化工作,cpu的中断入口也会在arch相关的代码中实现。中断通用逻辑层通过标准的封装接口(实际上就是struct irq_chip定义的接口)访问并控制中断控制器的行为,体系相关的中断入口函数在获取IRQ编号后,通过中断通用逻辑层提供的标准函数,把中断调用传递到中断流控层中。irq_chip的部分定义:

struct irq_chip {
	const char	*name;
	unsigned int	(*irq_startup)(struct irq_data *data);
	void		(*irq_shutdown)(struct irq_data *data);
	void		(*irq_enable)(struct irq_data *data);
	void		(*irq_disable)(struct irq_data *data);
 
	void		(*irq_ack)(struct irq_data *data);
	void		(*irq_mask)(struct irq_data *data);
	void		(*irq_mask_ack)(struct irq_data *data);
	void		(*irq_unmask)(struct irq_data *data);
	void		(*irq_eoi)(struct irq_data *data);
 
	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
	int		(*irq_retrigger)(struct irq_data *data);
	int		(*irq_set_type)(struct irq_data *data, unsigned int flow_type);
	int		(*irq_set_wake)(struct irq_data *data, unsigned int on);
        ......
};

3.2.中断流控层

  中断流控是指合理并正确地处理连续发生的中断,比如一个中断在处理中,同一个中断再次到达时如何处理,何时应该屏蔽中断,何时打开中断,何时回应中断控制器等一系列的操作。该层实现了与体系和硬件无关的中断流控处理操作,它针对不同的中断电气类型(level,edge…),实现了对应的标准中断流控处理函数,在这些处理函数中,最终会把中断控制权传递到驱动程序注册中断时传入的处理函数或者是中断线程中。目前内核提供了以下几个主要的中断流控函数的实现(只列出部分):

  • handle_simple_irq();
  • handle_level_irq(); 电平中断流控处理程序
  • handle_edge_irq(); 边沿触发中断流控处理程序
  • handle_fasteoi_irq(); 需要eoi的中断处理器使用的中断流控处理程序
  • handle_percpu_irq(); 该irq只有单个cpu响应时使用的流控处理程序

3.3.中断通用逻辑层

  该层实现了对中断系统几个重要数据的管理,并提供了一系列的辅助管理函数。同时,该层还实现了中断线程的实现和管理,共享中断和嵌套中断的实现和管理,另外它还提供了一些接口函数,它们将作为硬件封装层和中断流控层以及驱动程序API层之间的桥梁,例如以下API:

  • generic_handle_irq();
  • irq_to_desc();
  • irq_set_chip();
  • irq_set_chained_handler();

3.4.驱动程序API

  该部分向驱动程序提供了一系列的API,用于向系统申请/释放中断,打开/关闭中断,设置中断类型和中断唤醒系统的特性等操作。驱动程序的开发者通常只会使用到这一层提供的这些API即可完成驱动程序的开发工作,其他的细节都由另外几个软件层较好地“隐藏”起来了,驱动程序开发者无需再关注底层的实现,其中的一些API如下:

  • enable_irq();
  • disable_irq();
  • disable_irq_nosync();
  • request_threaded_irq();
  • irq_set_affinity();

4.Interrupt controllers VS CPUs 拓扑结构

  Interrupt controller 有的是支持多个CPU core的(如GIC、APIC等),有的不支持(如S3C2410的中断控制器,X86平台的PIC等)。

  如果硬件平台中只有一个GIC的话,那么通过控制该GIC的寄存器可以将所有的外设中断,分发给连接在该interrupt controller上的CPU。

5.Interrupt controller分发中断

  一般而言,Interrupt controller可以把中断事件上报给一个CPU或者一组CPU(包括广播到所有的CPU上去)。对于外设类型的中断,当然是送到一个cpu上就OK了。如果送达了多个cpu,实际上,也应该只有一个handler实际和外设进行交互,另外一个cpu上的handler的动作应该是这样的:发现该irq number对应的中断已经被另外一个cpu处理了,直接退出handler,返回中断现场。

从用户的角度看,需求可能包括:

  • 让某个IRQ number的中断由某个特定的CPU处理
  • 让某个特定的中断由几个CPU轮流处理

6.中断子系统拓扑图
在这里插入图片描述

6.1.中断子系统相关的软件框架图
在这里插入图片描述
上面所示,中断子系统分成4个部分:

  • 硬件无关的代码,即Linux kernel通用中断处理模块。无论是哪种CPU,哪种controller,其中断处理的过程都有一些相同的内容,这些相同的内容被抽象出来,和HW无关。

  • CPU architecture相关的中断处理, 和系统使用的具体的CPU architecture相关。

  • Interrupt controller驱动代码 ,和系统使用的Interrupt controller相关。

  • 普通外设的驱动,这些驱动将使用Linux kernel通用中断处理模块的API来实现自己的驱动逻辑。

6.2.中断管理系统代码初始化

  start_kernel()函数调用trap_init()、early_irq_init()和init_IRQ()三个函数来初始化中断管理系统。

start_kernel():
asmlinkage void __init start_kernel(void)
{
   ……
   trap_init();
   ……
   early_irq_init();
   init_IRQ();
  ……

内核启动时初始化中断的入口:

6.2.1. early_irq_init:

  • 如果定义 CONFIG_SPARSE_IRQ,则所有irq_descs以radix tree的形式管理;
  • 否则所有irq_descs放在一个全局数组中,并对某些成员进行初始化。

第一种方式:使用基数树管理16个legacy中断

229 int __init early_irq_init(void)
  230 {
  231     int i, initcnt, node = first_online_node;
  232     struct irq_desc *desc;
  233
  234     init_irq_default_affinity();  
  235
  236     /* Let arch update nr_irqs and return the nr of preallocated irqs */
  237     initcnt = arch_probe_nr_irqs();      
  240
  241     if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
  242         nr_irqs = IRQ_BITMAP_BITS;
  243
  244     if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
  245         initcnt = IRQ_BITMAP_BITS;
  246
  247     if (initcnt > nr_irqs)
  248         nr_irqs = initcnt;
  249
  250     for (i = 0; i < initcnt; i++) {         //对以上的16个irq进行irq_desc的初始化
  251         desc = alloc_desc(i, node, NULL);   //分配irq_desc并对其中某些成员进行初始化
  252         set_bit(i, allocated_irqs);              //set bit in allocated_irqs
  253         irq_insert_desc(i, desc);              //插入到radix tree中
  254     }
  255     return arch_early_irq_init();     //设置以上16个legacy irq的chip_data,void类型
  256 }

重点函数:

  • alloc_desc
  • irq_insert_desc

第二种方式:使用全局数组

  260 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
  261     [0 ... NR_IRQS-1] = {
  262         .handle_irq = handle_bad_irq,
  263         .depth      = 1,
  264         .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
  265     }
  266 };

  268 int __init early_irq_init(void)
  269 {
  270     int count, i, node = first_online_node;
  271     struct irq_desc *desc;
  272
  273     init_irq_default_affinity();
  274
~ 275     printk(KERN_INFO "NR_IRQS:%d, adasda\n", NR_IRQS);
+ 276     13131
  277
  278     desc = irq_desc;
  279     count = ARRAY_SIZE(irq_desc);
  280
  281     for (i = 0; i < count; i++) { //遍历数组,对成员进行初始化
  282         desc[i].kstat_irqs = alloc_percpu(unsigned int);
  283         alloc_masks(&desc[i], GFP_KERNEL, node);
  284         raw_spin_lock_init(&desc[i].lock);
  285         lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
  286         desc_set_defaults(i, &desc[i], node, NULL);   //初始化irq_desc结构体
  287     }
  288     return arch_early_irq_init();
  289 }

重点函数:

  • desc_set_defaults

6.2.2.init_IRQ():

 arch/arm64/kernel/irq.c:
  72 void __init init_IRQ(void)
   73 {
   74     init_irq_stacks();
   75     irqchip_init();                                                                                    
   76     if (!handle_arch_irq)
   77         panic("No interrupt controller found.");
   78 }

init_irq_stacks(初始化中断栈):

  • 对于x86平台:
    x86平台上,中断栈是独立于内核栈的存在,两者并不共享,如果是多处理器架构,那么每个CPU都对应有一个中断栈。

  • 对于 ARM平台:
    中断栈和内核栈则是共享的,中断栈和内核栈共享有一个负面因素,如果中断发生嵌套,可能会造成栈溢出,从而可能会破坏到内核栈的一些重要数据。

  • 对于ARM64平台:
    中断栈的实现是独立的,并且区分两种情况,分别是vmap申请内存,还是直接静态定义,并且根据CPU个数,每个CPU对应单独的一个stack。

设备树中的中断控制器的处理入口:irqchip_init()

   22 static const struct of_device_id
   23 irqchip_of_match_end __used __section(__irqchip_of_table_end);
   24 
   25 extern struct of_device_id __irqchip_of_table[];
   26 
   27 void __init irqchip_init(void)                                                                         
   28 {
   29     of_irq_init(__irqchip_of_table);
   30     acpi_probe_device_table(irqchip);
   31 }

  分析irqchip_init之前,先看GICv3 DTS设备描述,如下所示:

gic: interrupt-controller@2c010000 {
                compatible = "arm,gic-v3";               
                #interrupt-cells = <4>;                   
                #address-cells = <2>;
                #size-cells = <2>;
                ranges;
                interrupt-controller;
                redistributor-stride = <0x0 0x40000>;   // 256kB stride
                #redistributor-regions = <2>;
                reg = <0x0 0x2c010000 0 0x10000>,       // GICD
                      <0x0 0x2d000000 0 0x800000>,      // GICR 1: CPUs 0-31
                      <0x0 0x2e000000 0 0x800000>;      // GICR 2: CPUs 32-63
                      <0x0 0x2c040000 0 0x2000>,        // GICC
                      <0x0 0x2c060000 0 0x2000>,        // GICH
                      <0x0 0x2c080000 0 0x2000>;        // GICV
                interrupts = <1 9 4>;

                gic-its@2c200000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        #msi-cells = <1>;
                        reg = <0x0 0x2c200000 0 0x20000>;
                };

                gic-its@2c400000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        #msi-cells = <1>;
                        reg = <0x0 0x2c400000 0 0x20000>;
                };
        };
  • compatible: 用于匹配GICv3驱动
  • #interrupt-cells: 这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符(-interrupts)中 cell 的个数
  • #address-cells , #size-cells, ranges:用于寻址, #address-cells表示reg中address元素的个数,#size-cells用来表示length元素的个数
  • interrupt-controller: 表示该节点是一个中断控制器
  • redistributor-stride: 一个GICR的大小
  • #redistributor-regions: GICR域个数。
  • reg :GIC的物理基地址,分别对应GICD,GICR,GICC…
  • interrupts: 分别代表中断类型,中断号,中断类型, PPI中断亲和, 保留字段。
    a为0表示SPI,1表示PPI;b表示中断号(注意SPI/PPI的中断号范围);c为1表示沿中断,4表示电平中断。
  • msi-controller: 表示节点是MSI控制器

  如上设备数的节点是由哪个驱动函数来解析的?带着疑问来看一下:

  irq chip driver声明:

IRQCHIP_DECLARE宏定义:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)

#define OF_DECLARE_2(table, name, compat, fn) \ 
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)

#define _OF_DECLARE(table, name, compat, fn, fn_type)            \ 
    static const struct of_device_id __of_table_##name        \ 
        __used __section(__##table##_of_table)            \ 
         = { .compatible = compat,                \ 
             .data = (fn == (fn_type)NULL) ? fn : fn  }

  该宏初始化一个struct of_device_id的静态常量,并放置在__irqchip_of_table section中。gic-v3使用IRQCHIP_DECLARE定义如下:

 IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);   

  它定义了一个of_device_id结构体,段属性为__irqchip_of_table,在编译内核时这些段被放在__irqchip_of_table地址处。即__irqchip_of_table起始地址处,放置了一个或多个 of_device_id,它含有compatible成员;设备树中的设备节点含有compatible属性,如果双方的compatible相同, 并且设备节点含有interrupt-controller属性,则调用of_device_id中的函数gic_of_init来处理该设备节点。所以IRQCHIP_DECLARE是用来声明设备树中的中断控制器的处理函数。

  irq chip table保存kernel支持的所有的中断控制器的ID信息(最重要的是驱动代码初始化函数和DT compatible string)。struct of_device_id定义:

struct of_device_id 
{ 
    char    name[32];------要匹配的device node的名字 
    char    type[32];-------要匹配的device node的类型 
    char    compatible[128];---匹配字符串(DT compatible string),用来匹配适合的device node 
    const void *data;--------对于GIC,这里是初始化函数指针 
};

  下面以fsl为例进一步分析:

 //arch/arm/mach-imx/gpc.c 
 IRQCHIP_DECLARE(imx_gpc, "fsl,imx6q-gpc", imx_gpc_init); 

展开宏可得:

static const struct of_device_id __of_table_imx_gpc      __used __section(__irqchip_of_table) = 
{ .compatible = "fsl,imx6q-gpc",                             
  .data = imx_gpc_init  
}

  回到函数irqchip_init中调用了of_irq_init,该函数对设备树文件中每一个中断控制器节点,调用对应的处理函数;并将加入到irqchip_of_table中的IRQ控制器和设备树进行匹配后初始化对应的控制器。

Dtsi中断控制器定义如下:

intc: interrupt-controller@00a01000 {
               compatible = "arm,cortex-a9-gic";
               #interrupt-cells = <3>;
               interrupt-controller;
               reg = <0x00a01000 0x1000>,
                     <0x00a00100 0x100>;
               interrupt-parent = <&intc>;
       };
		
	gpio1: gpio@0209c000 {
                               compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio";
                               reg = <0x0209c000 0x4000>;
                               interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,
                                            <0 67 IRQ_TYPE_LEVEL_HIGH>;
                               gpio-controller;
                               #gpio-cells = <2>;
                               interrupt-controller;
                               #interrupt-cells = <2>;
                       };
                       
    //General Power Control
	gpc: gpc@020dc000 {
                               compatible = "fsl,imx6q-gpc";
                               reg = <0x020dc000 0x4000>;
                               interrupt-controller;
                               #interrupt-cells = <3>;
                               interrupts = <0 89 IRQ_TYPE_LEVEL_HIGH>,
                                            <0 90 IRQ_TYPE_LEVEL_HIGH>;
                               interrupt-parent = <&intc>;
                               pu-supply = <&reg_pu>;
                               clocks = <&clks IMX6QDL_CLK_GPU3D_CORE>,
                                        <&clks IMX6QDL_CLK_GPU3D_SHADER>,
                                        <&clks IMX6QDL_CLK_GPU2D_CORE>,
                                        <&clks IMX6QDL_CLK_GPU2D_AXI>,
                                        <&clks IMX6QDL_CLK_OPENVG_AXI>,
                                        <&clks IMX6QDL_CLK_VPU_AXI>;
                               #power-domain-cells = <1>;
                       };
				fec: ethernet@02188000 {
                               compatible = "fsl,imx6q-fec";
                               reg = <0x02188000 0x4000>;
                               interrupts-extended =
                                       <&gpc 0 118 IRQ_TYPE_LEVEL_HIGH>,
                                       <&gpc 0 119 IRQ_TYPE_LEVEL_HIGH>;
                               clocks = <&clks IMX6QDL_CLK_ENET>,
                                        <&clks IMX6QDL_CLK_ENET>,
                                        <&clks IMX6QDL_CLK_ENET_REF>;
                               clock-names = "ipg", "ahb", "ptp";
                               stop-mode = <&gpr 0x34 27>;
                               fsl,wakeup_irq = <0>;
                               status = "disabled";
                       };

设备树interrupt控制器:

  • gic “arm,cortex-a9-gic”
  • gpio”fsl,imx6q-gpio”, “fsl,imx35-gpio”
  • gpc”fsl,imx6q-gpc”;
//drivers/of/irq.c
void __init of_irq_init(const struct of_device_id *matches)
{
	struct device_node *np, *parent = NULL;
	struct intc_desc *desc, *temp_desc;
	struct list_head intc_desc_list, intc_parent_list;

	INIT_LIST_HEAD(&intc_desc_list);
	INIT_LIST_HEAD(&intc_parent_list);

	for_each_matching_node(np, matches) {
		if (!of_find_property(np, "interrupt-controller", NULL) ||
				!of_device_is_available(np))
			continue;
		desc = kzalloc(sizeof(*desc), GFP_KERNEL);
		desc->dev = np;
		desc->interrupt_parent = of_irq_find_parent(np);
		if (desc->interrupt_parent == np)
			desc->interrupt_parent = NULL;
		list_add_tail(&desc->list, &intc_desc_list);
	}

	while (!list_empty(&intc_desc_list)) {
		list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
			const struct of_device_id *match;
			int ret;
			of_irq_init_cb_t irq_init_cb;

			if (desc->interrupt_parent != parent)
				continue;

			list_del(&desc->list);
			match = of_match_node(matches, desc->dev);
			irq_init_cb = (of_irq_init_cb_t)match->data;
			ret = irq_init_cb(desc->dev, desc->interrupt_parent);
			if (ret) {
				kfree(desc);
				continue;
			}
			list_add_tail(&desc->list, &intc_parent_list);
		}

		/* Get the next pending parent that might have children */
		desc = list_first_entry_or_null(&intc_parent_list,
						typeof(*desc), list);
		list_del(&desc->list);
		parent = desc->dev;
		kfree(desc);
	}

	list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
		list_del(&desc->list);
		kfree(desc);
	}
}

  of_irq_init函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点都已经形成了一个树状结构,每个节点代表一个设备的device node。of_irq_init是在所有的device node中寻找中断控制器节点,形成树状结构(系统可以有多个interrupt controller,之所以形成中断控制器的树状结构,是为了让系统中所有的中断控制器驱动按照一定的顺序进行初始化)。之后,从root interrupt controller节点开始,对于每一个interrupt controller的device node,扫描irq chip table,进行匹配,一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为参数传递给irq chip driver。

6.3. GIC driver初始化

gic_of_init():

 drivers/irqchip/irq-gic-v3.c:
  1273 static int __init gic_of_init(struct device_node *node, struct device_node *parent)
  1274 {
  1275     void __iomem *dist_base;
  1276     struct redist_region *rdist_regs;
  1277     u64 redist_stride;
  1278     u32 nr_redist_regions;
  1279     int err, i;
  1280 
  1281     dist_base = of_iomap(node, 0);
  1287     err = gic_validate_dist_version(dist_base);
  1292 
  1293     if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
  1294         nr_redist_regions = 1;
  1295 
  1296     rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),
  1297                  GFP_KERNEL);
  1302 
  1303     for (i = 0; i < nr_redist_regions; i++) {
  1304         struct resource res;
  1305         int ret;
  1306 
  1307         ret = of_address_to_resource(node, 1 + i, &res);
  1308         rdist_regs[i].redist_base = of_iomap(node, 1 + i);
  1314         rdist_regs[i].phys_base = res.start;
  1315     }
  1316 
  1317     if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
  1318         redist_stride = 0;
  1319 
  1320     err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
  1321                  redist_stride, &node->fwnode);
  1339 }
  1340 
  1341 IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);

重点分析gic_init_bases:

 1071 static int __init gic_init_bases(void __iomem *dist_base,
  1072                  struct redist_region *rdist_regs,
  1073                  u32 nr_redist_regions,
  1074                  u64 redist_stride,
  1075                  struct fwnode_handle *handle)
  1076 {
  1077     u32 typer;
  1078     int gic_irqs;
  1079     int err;
  1080 
  1081     if (!is_hyp_mode_available())
  1082         static_branch_disable(&supports_deactivate_key);
  1083 
  1084     if (static_branch_likely(&supports_deactivate_key))
  1085         pr_info("GIC: Using split EOI/Deactivate mode\n");
  1086 
  1087     gic_data.fwnode = handle;
  1088     gic_data.dist_base = dist_base;
  1089     gic_data.redist_regions = rdist_regs;
  1090     gic_data.nr_redist_regions = nr_redist_regions;
  1091     gic_data.redist_stride = redist_stride;
  1092 
  1097     typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
  1098     gic_data.rdists.gicd_typer = typer;
  1099     gic_irqs = GICD_TYPER_IRQS(typer);
  1100     if (gic_irqs > 1020)
  1101         gic_irqs = 1020;
  1102     gic_data.irq_nr = gic_irqs;
  1103 
  1104     gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
  1105                          &gic_data);
  1106     irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
  1107     gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
  1108     gic_data.rdists.has_vlpis = true;
  1109     gic_data.rdists.has_direct_lpi = true;
  1110 
  1116     gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
  1119                                                                  
  1126     set_handle_irq(gic_handle_irq);
  1128     gic_update_vlpi_properties();
  1129 
  1130     if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
  1131         its_init(handle, &gic_data.rdists, gic_data.domain);
  1132 
  1133     gic_smp_init();
  1134     gic_dist_init();
  1135     gic_cpu_init();
  1136     gic_cpu_pm_init();
  1138     return 0;
  1145 }
  • 确认支持SPI 中断号最大的值为多少,GICv3最多支持1020个中断(SPI+SGI+SPI).GICD_TYPER寄存器bit[4:0], 如果该字段的值为N,则最大SPI INTID为32(N + 1)-1。 例如,0x00011指定最大SPI INTID为127。

  • 向系统中注册一个irq domain的数据结构. irq_domain主要作用是将硬件中断号映射到IRQ number。

  • 用于区分MSI域。 比如一个域用作PCI/MSI, 一个域用作wired IRQS.

  • 判断GICD 是否支持rss, rss(Range Selector Support)表示SGI中断亲和性的范围 GICD_TYPER寄存器bit[26], 如果该字段为0,表示中断路由(IRI) 支持affinity 0-15的SGI,如果该字段为1, 表示支持affinity 0 - 255的SGI

  • 判断是否支持通过写GICD寄存器生成消息中断。GICD_TYPER寄存器bit[16]

  • 设定arch相关的irq handler。gic_irq_handle是内核gic中断处理的入口函数。

  • 更新vlpi相关配置。gic虚拟化相关。

  • 初始化ITS。 Interrupt Translation Service, 用来解析LPI中断。 初始化之前需要先判断GIC是否支持LPI,该功能在ARM里是可选的。

  • 该函数主要包含两个作用。

    • 1.设置核间通信函数。当一个CPU core上的软件控制行为需要传递到其他的CPU上的时候,就会调用这个callback函数(例如在某一个CPU上运行的进程调用了系统调用进行reboot)。对于GIC v3,这个callback定义为gic_raise_softirq.
    • 2.设置CPU 上下线流程中和GIC相关的状态机。
  • 初始化GICD。

  • 初始化CPU interface.

  • 初始化GIC电源管理。

函数调用流程如下:
在这里插入图片描述

  分析之前需要了解IRQ domain,请查看linux IRQ Management(二)- IRQ Domain.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41028621/article/details/89713208

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue