天天看點

linux IRQ Management(三)- IRQ Framework

  • 了解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系統中斷硬體的組成結構:

linux IRQ Management(三)- IRQ Framework
  • 裝置:裝置是發起中斷的源,當裝置需要請求某種服務的時候,它會發起一個硬體中斷信号,通常,該信号會連接配接至中斷控制器,由中斷控制器做進一步的處理。在現代的移動裝置中,發起中斷的裝置可以位于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。
    linux IRQ Management(三)- IRQ Framework
      系統中有若幹個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。把這些不同的中斷類型抽象出來後,成為了中斷子系統的流控層。要使所有的體系架構都可以重用這部分的代碼,中斷控制器也被進一步地封裝起來,形成了中斷子系統中的硬體封裝層。通用中斷子系統的層次結構:

linux IRQ Management(三)- IRQ Framework

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.中斷子系統拓撲圖

linux IRQ Management(三)- IRQ Framework

6.1.中斷子系統相關的軟體架構圖

linux IRQ Management(三)- IRQ Framework

上面所示,中斷子系統分成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: [email protected] {
                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>;

                [email protected] {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        #msi-cells = <1>;
                        reg = <0x0 0x2c200000 0 0x20000>;
                };

                [email protected] {
                        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: [email protected] {
               compatible = "arm,cortex-a9-gic";
               #interrupt-cells = <3>;
               interrupt-controller;
               reg = <0x00a01000 0x1000>,
                     <0x00a00100 0x100>;
               interrupt-parent = <&intc>;
       };
		
	gpio1: [email protected] {
                               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: [email protected] {
                               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: [email protected] {
                               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電源管理。

函數調用流程如下:

linux IRQ Management(三)- IRQ Framework

  分析之前需要了解IRQ domain,請檢視linux IRQ Management(二)- IRQ Domain.

繼續閱讀