天天看點

Linux中斷 - GIC代碼分析

Linux中斷 - GIC代碼分析

https://www.cnblogs.com/alantu2018/p/8447477.html

一、前言

GIC(Generic Interrupt Controller)是ARM公司提供的一個通用的中斷控制器,其architecture specification目前有四個版本,V1~V4(V2最多支援8個ARM core,V3/V4支援更多的ARM core,主要用于ARM64伺服器系統結構)。目前在ARM官方網站隻能下載下傳到Version 2的GIC architecture specification,是以,本文主要描述符合V2規範的GIC硬體及其驅動。

具體GIC硬體的實作形态有兩種,一種是在ARM vensor研發自己的SOC的時候,會向ARM公司購買GIC的IP,這些IP包括的型号有:PL390,GIC-400,GIC-500。其中GIC-500最多支援128個 cpu core,它要求ARM core必須是ARMV8指令集的(例如Cortex-A57),符合GIC architecture specification version 3。另外一種形态是ARM vensor直接購買ARM公司的Cortex A9或者A15的IP,Cortex A9或者A15中會包括了GIC的實作,當然,這些實作也是符合GIC V2的規格。

本文在進行硬體描述的時候主要是以GIC-400為目标,當然,也會順便提及一些Cortex A9或者A15上的GIC實作。

本文主要分析了linux kernel中GIC中斷控制器的驅動代碼(位于drivers/irqchip/irq-gic.c和irq-gic-common.c)。 irq-gic-common.c中是GIC V2和V3的通用代碼,而irq-gic.c是V2 specific的代碼,irq-gic-v3.c是V3 specific的代碼,不在本文的描述範圍。本文主要分成三個部分:第二章描述了GIC V2的硬體;第三章描述了GIC V2的初始化過程;第四章描述了底層的硬體call back函數。

注:具體的linux kernel的版本是linux-3.17-rc3。

二、GIC-V2的硬體描述

1、GIC-V2的輸入和輸出信号

(1)GIC-V2的輸入和輸出信号示意圖

要想了解一個building block(無論軟體還是硬體),我們都可以先把它當成黑盒子,隻是研究其input,output。GIC-V2的輸入和輸出信号的示意圖如下(注:我們以GIC-400為例,同時省略了clock,config等信号):

Linux中斷 - GIC代碼分析

(2)輸入信号

上圖中左邊就是來自外設的interrupt source輸入信号。分成兩種類型,分别是PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。其實從名字就可以看出來兩種類型中斷信号的特點,PPI中斷信号是CPU私有的,每個CPU都有其特定的PPI信号線。而SPI是所有CPU之間共享的。通過寄存器GICD_TYPER可以配置SPI的個數(最多480個)。GIC-400支援多少個SPI中斷,其輸入信号線就有多少個SPI interrupt request signal。同樣的,通過寄存器GICD_TYPER也可以配置CPU interface的個數(最多8個),GIC-400支援多少個CPU interface,其輸入信号線就提供多少組PPI中斷信号線。一組PPI中斷信号線包括6個實際的signal:

(a)nLEGACYIRQ信号線。對應interrupt ID 31,在bypass mode下(這裡的bypass是指bypass GIC functionality,直接連接配接到某個processor上),nLEGACYIRQ可以直接連到對應CPU的nIRQCPU信号線上。在這樣的設定下,該CPU不參與其他屬于該CPU的PPI以及SPI中斷的響應,而是特别為這一根中斷線服務。

(b)nCNTPNSIRQ信号線。來自Non-secure physical timer的中斷事件,對應interrupt ID 30。

(c)nCNTPSIRQ信号線。來自secure physical timer的中斷事件,對應interrupt ID 29。

(d)nLEGACYFIQ信号線。對應interrupt ID 28。概念同nLEGACYIRQ信号線,不再描述。

(e)nCNTVIRQ信号線。對應interrupt ID 27。Virtual Timer Event,和虛拟化相關,這裡不與描述。

(f)nCNTHPIRQ信号線。對應interrupt ID 26。Hypervisor Timer Event,和虛拟化相關,這裡不與描述。

對于Cortex A15的GIC實作,其PPI中斷信号線除了上面的6個,還有一個叫做Virtual Maintenance Interrupt,對應interrupt ID 25。

對于Cortex A9的GIC實作,其PPI中斷信号線包括5根:

(a)nLEGACYIRQ信号線和nLEGACYFIQ信号線。對應interrupt ID 31和interrupt ID 28。這部分和上面一緻。

(b)由于Cortext A9的每個處理器都有自己的Private timer和watch dog timer,這兩個HW block分别使用了ID 29和ID 30

(c)Cortext A9内嵌一個global timer為系統内的所有processor共享,對應interrupt ID 27

關于private timer和global timer的描述,請參考時間子系統的相關文檔。

關于一系列和虛拟化相關的中斷,請參考虛拟化的系列文檔。

(3)輸出信号

所謂輸出信号,其實就是GIC和各個CPU直接的接口,這些接口包括:

(a)觸發CPU中斷的信号。nIRQCPU和nFIQCPU信号線,熟悉ARM CPU的工程師對這兩個信号線應該不陌生,主要用來觸發ARM cpu進入IRQ mode和FIQ mode。

(b)Wake up信号。nFIQOUT和nIRQOUT信号線,去ARM CPU的電源管理子產品,用來喚醒CPU的

(c)AXI slave interface signals。AXI(Advanced eXtensible Interface)是一種總線協定,屬于AMBA規範的一部分。通過這些信号線,ARM CPU可以和GIC硬體block進行通信(例如寄存器通路)。

(4)中斷号的配置設定

GIC-V2支援的中斷類型有下面幾種:

(a)外設中斷(Peripheral interrupt)。有實際實體interrupt request signal的那些中斷,上面已經介紹過了。

(b)軟體觸發的中斷(SGI,Software-generated interrupt)。軟體可以通過寫GICD_SGIR寄存器來觸發一個中斷事件,這樣的中斷,可以用于processor之間的通信。

(c)虛拟中斷(Virtual interrupt)和Maintenance interrupt。這兩種中斷和本文無關,不再贅述。

為了辨別這些interrupt source,我們必須要對它們進行編碼,具體的ID配置設定情況如下:

(a)ID0~ID31是用于分發到一個特定的process的interrupt。辨別這些interrupt不能僅僅依靠ID,因為各個interrupt source都用同樣的ID0~ID31來辨別,是以識别這些interrupt需要interrupt ID + CPU interface number。ID0~ID15用于SGI,ID16~ID31用于PPI。PPI類型的中斷會送到其私有的process上,和其他的process無關。SGI是通過寫GICD_SGIR寄存器而觸發的中斷。Distributor通過processor source ID、中斷ID和target processor ID來唯一識别一個SGI。

(b)ID32~ID1019用于SPI。 這是GIC規範的最大size,實際上GIC-400最大支援480個SPI,Cortex-A15和A9上的GIC最多支援224個SPI。

2、GIC-V2的内部邏輯

(1)GIC的block diagram

GIC的block diagram如下圖所示:

Linux中斷 - GIC代碼分析

GIC可以清晰的劃分成兩個block,一個block是Distributor(上圖的左邊的block),一個是CPU interface。CPU interface有兩種,一種就是和普通processor接口,另外一種是和虛拟機接口的。Virtual CPU interface在本文中不會較長的描述。

(2)Distributor 概述

Distributor的主要的作用是檢測各個interrupt source的狀态,控制各個interrupt source的行為,分發各個interrupt source産生的中斷事件分發到指定的一個或者多個CPU interface上。雖然Distributor可以管理多個interrupt source,但是它總是把優先級最高的那個interrupt請求送往CPU interface。Distributor對中斷的控制包括:

(1)中斷enable或者disable的控制。Distributor對中斷的控制分成兩個級别。一個是全局中斷的控制(GIC_DIST_CTRL)。一旦disable了全局的中斷,那麼任何的interrupt source産生的interrupt event都不會被傳遞到CPU interface。另外一個級别是對針對各個interrupt source進行控制(GIC_DIST_ENABLE_CLEAR),disable某一個interrupt source會導緻該interrupt event不會分發到CPU interface,但不影響其他interrupt source産生interrupt event的分發。

(2)控制将目前優先級最高的中斷事件分發到一個或者一組CPU interface。當一個中斷事件分發到多個CPU interface的時候,GIC的内部邏輯應該保證隻assert 一個CPU。

(3)優先級控制。

(4)interrupt屬性設定。例如是level-sensitive還是edge-triggered

(5)interrupt group的設定

Distributor可以管理若幹個interrupt source,這些interrupt source用ID來辨別,我們稱之interrupt ID。

(3)CPU interface

CPU interface這個block主要用于和process進行接口。該block的主要功能包括:

(a)enable或者disable CPU interface向連接配接的CPU assert中斷事件。對于ARM,CPU interface block和CPU之間的中斷信号線是nIRQCPU和nFIQCPU。如果disable了中斷,那麼即便是Distributor分發了一個中斷事件到CPU interface,但是也不會assert指定的nIRQ或者nFIQ通知processor。

(b)ackowledging中斷。processor會向CPU interface block應答中斷(應答目前優先級最高的那個中斷),中斷一旦被應答,Distributor就會把該中斷的狀态從pending狀态修改成active或者pending and active(這是和該interrupt source的信号有關,例如如果是電平中斷并且保持了該asserted電平,那麼就是pending and active)。processor ack了中斷之後,CPU interface就會deassert nIRQCPU和nFIQCPU信号線。

(c)中斷處理完畢的通知。當interrupt handler處理完了一個中斷的時候,會向寫CPU interface的寄存器進而通知GIC CPU已經處理完該中斷。做這個動作一方面是通知Distributor将中斷狀态修改為deactive,另外一方面,CPU interface會priority drop,進而允許其他的pending的interrupt向CPU送出。

(d)設定priority mask。通過priority mask,可以mask掉一些優先級比較低的中斷,這些中斷不會通知到CPU。

(e)設定preemption的政策

(f)在多個中斷事件同時到來的時候,選擇一個優先級最高的通知processor

(4)執行個體

我們用一個實際的例子來描述GIC和CPU接口上的互動過程,具體過程如下:

Linux中斷 - GIC代碼分析

(注:圖檔太長,是以豎着放,看的時候有點費勁,就當活動一下脖子吧)

首先給出前提條件:

(a)N和M用來辨別兩個外設中斷,N的優先級大于M

(b)兩個中斷都是SPI類型,level trigger,active-high

(c)兩個中斷被配置為去同一個CPU

(d)都被配置成group 0,通過FIQ觸發中斷

下面的表格按照時間軸來描述互動過程:

時間 互動動作的描述
T0時刻 Distributor檢測到M這個interrupt source的有效觸發電平
T2時刻 Distributor将M這個interrupt source的狀态設定為pending
T17時刻 大約15個clock之後,CPU interface拉低nFIQCPU信号線,向CPU報告M外設的中斷請求。這時候,CPU interface的ack寄存器(GICC_IAR)的内容會修改成M interrupt source對應的ID
T42時刻 Distributor檢測到N這個優先級更高的interrupt source的觸發事件
T43時刻 Distributor将N這個interrupt source的狀态設定為pending。同時,由于N的優先級更高,是以Distributor會标記目前優先級最高的中斷
T58時刻 大約15個clock之後,CPU interface拉低nFIQCPU信号線,向CPU報告N外設的中斷請求。當然,由于T17時刻已經assert CPU了,是以實際的電平信号仍然保持asserted。這時候,CPU interface的ack寄存器(GICC_IAR)的内容會被更新成N interrupt source的ID
T61時刻

軟體通過讀取ack寄存器的内容,擷取了目前優先級最高的,并且狀态是pending的interrupt ID(也就是N interrupt source對應的ID),通過讀該寄存器,CPU也就ack了該interrupt source N。這時候,Distributor将N這個interrupt source的狀态設定為pending and active(因為是電平觸發,隻要外部仍然有asserted的電平信号,那麼一定就是pending的,而該中斷是正在被CPU處理的中斷,是以狀态是pending and active)

注意:T61辨別CPU開始服務該中斷

T64時刻 3個clock之後,由于CPU已經ack了中斷,是以GIC中CPU interface子產品 deassert nFIQCPU信号線,解除發向該CPU的中斷請求
T126時刻 由于中斷服務程式操作了N外設的控制寄存器(ack外設的中斷),是以N外設deassert了其interrupt request signal
T128時刻 Distributor解除N外設的pending狀态,是以N這個interrupt source的狀态設定為active
T131時刻

軟體操作End of Interrupt寄存器(向GICC_EOIR寄存器寫入N對應的interrupt ID),辨別中斷處理結束。Distributor将N這個interrupt source的狀态修改為idle

注意:T61~T131是CPU服務N外設中斷的的時間區域,這個期間,如果有高優先級的中斷pending,會發生中斷的搶占(硬體意義的),這時候CPU interface會向CPU assert 新的中斷。

T146時刻 大約15個clock之後,Distributor向CPU interface報告目前pending且優先級最高的interrupt source,也就是M了。漫長的pending之後,M終于迎來了春天。CPU interface拉低nFIQCPU信号線,向CPU報告M外設的中斷請求。這時候,CPU interface的ack寄存器(GICC_IAR)的内容會修改成M interrupt source對應的ID
T211時刻 CPU ack M中斷(通過讀GICC_IAR寄存器),開始處理低優先級的中斷。

三、GIC-V2 irq chip driver的初始化過程

在linux-3.17-rc3\drivers\irqchip目錄下儲存在各種不同的中斷控制器的驅動代碼,這個版本的核心支援了GICV3。irq-gic-common.c是通用的GIC的驅動代碼,可以被各個版本的GIC使用。irq-gic.c是用于V2版本的GIC controller,而irq-gic-v3.c是用于V3版本的GIC controller。

1、GIC的device node和GIC irq chip driver的比對過程

(1)irq chip driver中的聲明

在linux-3.17-rc3\drivers\irqchip目錄下的irqchip.h檔案中定義了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中。irq-gic.c檔案中使用IRQCHIP_DECLARE來定義了若幹個靜态的struct of_device_id常量,如下:

IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);

IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);

IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);

IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);

IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);

IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

相容GIC-V2的GIC實作有很多,不過其初始化函數都是一個。在linux kernel編譯的時候,你可以配置多個irq chip進入核心,編譯系統會把所有的IRQCHIP_DECLARE宏定義的資料放入到一個特殊的section中(section name是__irqchip_of_table),我們稱這個特殊的section叫做irq chip table。這個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,這裡是初始化函數指針

};

這個資料結構主要被用來進行Device node和driver子產品進行比對用的。從該資料結構的定義可以看出,在比對過程中,device name、device type和DT compatible string都是考慮的因素。更細節的内容請參考__of_device_is_compatible函數。

(2)device node

不同的GIC-V2的實作總會有一些不同,這些資訊可以通過Device tree的機制來傳遞。Device node中定義了各種屬性,其中就包括了memory資源,IRQ描述等資訊,這些資訊需要在初始化的時候傳遞給具體的驅動,是以需要一個Device node和driver子產品的比對過程。在Device Tree子產品中會包括系統中所有的device node,如果我們的系統使用了GIC-400,那麼系統的device node資料庫中會有一個node是GIC-400的,一個示例性的GIC-400的device node(我們以瑞芯微的RK3288處理器為例)定義如下:

gic: [email protected] {

    compatible = "arm,gic-400";

    interrupt-controller;

    #interrupt-cells = <3>;

    #address-cells = <0>;

    reg = <0xffc01000 0x1000="">,----Distributor address range

          <0xffc02000 0x1000="">,-----CPU interface address range

          <0xffc04000 0x2000="">,-----Virtual interface control block

          <0xffc06000 0x2000="">;-----Virtual CPU interfaces

    interrupts = <gic_ppi 0xf04="" 9="">;

};

(3)device node和irq chip driver的比對

在machine driver初始化的時候會調用irqchip_init函數進行irq chip driver的初始化。在driver/irqchip/irqchip.c檔案中定義了irqchip_init函數,如下:

void __init irqchip_init(void)

{

    of_irq_init(__irqchip_begin);

}

__irqchip_begin就是核心irq chip table的首位址,這個table也就儲存了kernel支援的所有的中斷控制器的ID資訊(用于和device node的比對)。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。。具體的比對過程的代碼屬于Device Tree子產品的内容,更詳細的資訊可以參考Device Tree代碼分析文檔。

2、GIC driver初始化代碼分析

(1)gic_of_init的代碼如下:

int __init gic_of_init(struct device_node *node, struct device_node *parent)

{

    void __iomem *cpu_base;

    void __iomem *dist_base;

    u32 percpu_offset;

    int irq;

    dist_base = of_iomap(node, 0);----------------映射GIC Distributor的寄存器位址空間

    cpu_base = of_iomap(node, 1);----------------映射GIC CPU interface的寄存器位址空間

    if (of_property_read_u32(node, "cpu-offset", &percpu_offset))--------處理cpu-offset屬性。

        percpu_offset = 0;

    gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);))-----主處理過程,後面詳述

    if (!gic_cnt)

        gic_init_physaddr(node); -----對于不支援big.LITTLE switcher(CONFIG_BL_SWITCHER)的系統,該函數為空。

    if (parent) {--------處理interrupt級聯

        irq = irq_of_parse_and_map(node, 0); ---解析second GIC的interrupts屬性,并進行mapping,傳回IRQ number

        gic_cascade_irq(gic_cnt, irq);

    }

    gic_cnt++;

    return 0;

}

我們首先看看這個函數的參數,node參數代表需要初始化的那個interrupt controller的device node,parent參數指向其parent。在映射GIC-400的memory map I/O space的時候,我們隻是映射了Distributor和CPU interface的寄存器位址空間,和虛拟化處理相關的寄存器沒有映射,是以這個版本的GIC driver應該是不支援虛拟化的(不知道後續版本是否支援,在一個嵌入式平台上支援虛拟化有實際意義嗎?最先支援虛拟化的應該是ARM64+GICV3/4這樣的平台)。

要了解cpu-offset屬性,首先要了解什麼是banked register。所謂banked register就是在一個位址上提供多個寄存器副本。比如說系統中有四個CPU,這些CPU通路某個寄存器的時候位址是一樣的,但是對于banked register,實際上,不同的CPU通路的是不同的寄存器,雖然它們的位址是一樣的。如果GIC沒有banked register,那麼需要提供根據CPU index給出一系列位址偏移,而位址偏移=cpu-offset * cpu-nr。

interrupt controller可以級聯。對于root GIC,其傳入的parent是NULL,是以不會執行級聯部分的代碼。對于second GIC,它是作為其parent(root GIC)的一個普通的irq source,是以,也需要注冊該IRQ的handler。由此可見,非root的GIC的初始化分成了兩個部分:一部分是作為一個interrupt controller,執行和root GIC一樣的初始化代碼。另外一方面,GIC又作為一個普通的interrupt generating device,需要象一個普通的裝置驅動一樣,注冊其中斷handler。了解irq_of_parse_and_map需要irq domain的知識,請參考linux kernel的中斷子系統之(二):irq domain介紹。

(2)gic_init_bases的代碼如下:

void __init gic_init_bases(unsigned int gic_nr, int irq_start,

               void __iomem *dist_base, void __iomem *cpu_base,

               u32 percpu_offset, struct device_node *node)

{

    irq_hw_number_t hwirq_base;

    struct gic_chip_data *gic;

    int gic_irqs, irq_base, i;

    gic = &gic_data[gic_nr]; 

    gic->dist_base.common_base = dist_base; ----省略了non banked的情況

    gic->cpu_base.common_base = cpu_base; 

    gic_set_base_accessor(gic, gic_get_common_base);

    for (i = 0; i < NR_GIC_CPU_IF; i++) ---後面會具體描述gic_cpu_map的含義

        gic_cpu_map[i] = 0xff;

    if (gic_nr == 0 && (irq_start & 31) > 0) { --------------------(a)

        hwirq_base = 16;

        if (irq_start != -1)

            irq_start = (irq_start & ~31) + 16;

    } else {

        hwirq_base = 32;

    }

    gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; ----(b)

    gic_irqs = (gic_irqs + 1) * 32;

    if (gic_irqs > 1020)

        gic_irqs = 1020;

    gic->gic_irqs = gic_irqs;

    gic_irqs -= hwirq_base;----------------------------(c)

    if (of_property_read_u32(node, "arm,routable-irqs",----------------(d)

                 &nr_routable_irqs)) {

        irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,  numa_node_id()); -------(e)

        if (IS_ERR_VALUE(irq_base)) {

            WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",

                 irq_start);

            irq_base = irq_start;

        }

        gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, -------(f)

                    hwirq_base, &gic_irq_domain_ops, gic);

    } else {

        gic->domain = irq_domain_add_linear(node, nr_routable_irqs, --------(f)

                            &gic_irq_domain_ops,

                            gic);

    }

    if (gic_nr == 0) { ---隻對root GIC操作,因為設定callback、注冊Notifier隻需要一次就OK了

#ifdef CONFIG_SMP

        set_smp_cross_call(gic_raise_softirq);------------------(g)

        register_cpu_notifier(&gic_cpu_notifier);------------------(h)

#endif

        set_handle_irq(gic_handle_irq); ---這個函數名字也不好,實際上是設定arch相關的irq handler

    }

    gic_chip.flags |= gic_arch_extn.flags;

    gic_dist_init(gic);---------具體的硬體初始代碼,參考下節的描述

    gic_cpu_init(gic);

    gic_pm_init(gic);

}

(a)gic_nr辨別GIC number,等于0就是root GIC。hwirq的意思就是GIC上的HW interrupt ID,并不是GIC上的每個interrupt ID都有map到linux IRQ framework中的一個IRQ number,對于SGI,是屬于軟體中斷,用于CPU之間通信,沒有必要進行HW interrupt ID到IRQ number的mapping。變量hwirq_base表示該GIC上要進行map的base ID,hwirq_base = 16也就意味着忽略掉16個SGI。對于系統中其他的GIC,其PPI也沒有必要mapping,是以hwirq_base = 32。

在本場景中,irq_start = -1,表示不指定IRQ number。有些場景會指定IRQ number,這時候,需要對IRQ number進行一個對齊的操作。

(b)變量gic_irqs儲存了該GIC支援的最大的中斷數目。該資訊是從GIC_DIST_CTR寄存器(這是V1版本的寄存器名字,V2中是GICD_TYPER,Interrupt Controller Type Register,)的低五位ITLinesNumber擷取的。如果ITLinesNumber等于N,那麼最大支援的中斷數目是32(N+1)。此外,GIC規範規定最大的中斷數目不能超過1020,1020-1023是有特别使用者的interrupt ID。

(c)減去不需要map(不需要配置設定IRQ)的那些interrupt ID,OK,這時候gic_irqs的數值終于和它的名字一緻了。gic_irqs從字面上看不就是該GIC需要配置設定的IRQ number的數目嗎?

(d)of_property_read_u32函數把arm,routable-irqs的屬性值讀出到nr_routable_irqs變量中,如果正确傳回0。在有些SOC的設計中,外設的中斷請求信号線不是直接接到GIC,而是通過crossbar/multiplexer這個的HW block連接配接到GIC上。arm,routable-irqs這個屬性用來定義那些不直接連接配接到GIC的中斷請求數目。

(e)對于那些直接連接配接到GIC的情況,我們需要通過調用irq_alloc_descs配置設定中斷描述符。如果irq_start大于0,那麼說明是指定IRQ number的配置設定,對于我們這個場景,irq_start等于-1,是以不指定IRQ 号。如果不指定IRQ number的,就需要搜尋,第二個參數16就是起始搜尋的IRQ number。gic_irqs指明要配置設定的irq number的數目。如果沒有正确的配置設定到中斷描述符,程式會認為可能是之前已經準備好了。

(f)這段代碼主要是向系統中注冊一個irq domain的資料結構。為何需要struct irq_domain這樣一個資料結構呢?從linux kernel的角度來看,任何外部的裝置的中斷都是一個異步事件,kernel都需要識别這個事件。在核心中,用IRQ number來辨別某一個裝置的某個interrupt request。有了IRQ number就可以定位到該中斷的描述符(struct irq_desc)。但是,對于中斷控制器而言,它不并知道IRQ number,它隻是知道HW interrupt number(中斷控制器會為其支援的interrupt source進行編碼,這個編碼被稱為Hardware interrupt number )。不同的軟體子產品用不同的ID來識别interrupt source,這樣就需要映射了。如何将Hardware interrupt number 映射到IRQ number呢?這需要一個translation object,核心定義為struct irq_domain。

每個interrupt controller都會形成一個irq domain,負責解析其下遊的interrut source。如果interrupt controller有級聯的情況,那麼一個非root interrupt controller的中斷控制器也是其parent irq domain的一個普通的interrupt source。struct irq_domain定義如下:

struct irq_domain {

……

    const struct irq_domain_ops *ops;

    void *host_data;

……

};

這個資料結構是屬于linux kernel通用中斷子系統的一部分,我們這裡隻是描述相關的資料成員。host_data成員是底層interrupt controller的私有資料,linux kernel通用中斷子系統不應該修改它。對于GIC而言,host_data成員指向一個struct gic_chip_data的資料結構,定義如下:

struct gic_chip_data {

    union gic_base dist_base;------------------GIC Distributor的基位址空間

    union gic_base cpu_base;------------------GIC CPU interface的基位址空間

#ifdef CONFIG_CPU_PM--------------------GIC 電源管理相關的成員

    u32 saved_spi_enable[DIV_ROUND_UP(1020, 32)];

    u32 saved_spi_conf[DIV_ROUND_UP(1020, 16)];

    u32 saved_spi_target[DIV_ROUND_UP(1020, 4)];

    u32 __percpu *saved_ppi_enable;

    u32 __percpu *saved_ppi_conf;

#endif

    struct irq_domain *domain;-----------------該GIC對應的irq domain資料結構

    unsigned int gic_irqs;-------------------GIC支援的IRQ的數目

#ifdef CONFIG_GIC_NON_BANKED

    void __iomem *(*get_base)(union gic_base *);

#endif

};

對于GIC支援的IRQ的數目,這裡還要贅述幾句。實際上并非GIC支援多少個HW interrupt ID,其就支援多少個IRQ。對于SGI,其處理比較特别,并不歸入IRQ number中。是以,對于GIC而言,其SGI(從0到15的那些HW interrupt ID)不需要irq domain進行映射處理,也就是說SGI沒有對應的IRQ number。如果系統越來越複雜,一個GIC不能支援所有的interrupt source(目前GIC支援1020個中斷源,這個數目已經非常的大了),那麼系統還需要引入secondary GIC,這個GIC主要負責擴充外設相關的interrupt source,也就是說,secondary GIC的SGI和PPI都變得備援了(這些功能,primary GIC已經提供了)。這些資訊可以協助了解代碼中的hwirq_base的設定。

在注冊GIC的irq domain的時候還有一個重要的資料結構gic_irq_domain_ops,其類型是struct irq_domain_ops ,對于GIC,其irq domain的操作函數是gic_irq_domain_ops,定義如下:

static const struct irq_domain_ops gic_irq_domain_ops = {

    .map = gic_irq_domain_map,

    .unmap = gic_irq_domain_unmap,

    .xlate = gic_irq_domain_xlate,

};

irq domain的概念是一個通用中斷子系統的概念,在具體的irq chip driver這個層次,我們需要一些解析GIC binding,建立IRQ number和HW interrupt ID的mapping的callback函數,更具體的解析參考後文的描述。

漫長的準備過程結束後,具體的注冊比較簡單,調用irq_domain_add_legacy或者irq_domain_add_linear進行注冊就OK了。關于這兩個接口請參考linux kernel的中斷子系統之(二):irq domain介紹。

(g) 一個函數名字是否起的好足可以看出工程師的功力。set_smp_cross_call這個函數看名字也知道它的含義,就是設定一個多個CPU直接通信的callback函數。當一個CPU core上的軟體控制行為需要傳遞到其他的CPU上的時候(例如在某一個CPU上運作的程序調用了系統調用進行reboot),就會調用這個callback函數。對于GIC,這個callback定義為gic_raise_softirq。這個函數名字起的不好,直覺上以為是和softirq相關,實際上其實是觸發了IPI中斷。

(h)在multi processor環境下,當processor狀态發送變化的時候(例如online,offline),需要把這些事件通知到GIC。而GIC driver在收到來自CPU的事件後會對cpu interface進行相應的設定。

3、GIC硬體初始化

(1)Distributor初始化,代碼如下:

static void __init gic_dist_init(struct gic_chip_data *gic)

{

    unsigned int i;

    u32 cpumask;

    unsigned int gic_irqs = gic->gic_irqs;---------擷取該GIC支援的IRQ的數目

    void __iomem *base = gic_data_dist_base(gic); ----擷取該GIC對應的Distributor基位址

    writel_relaxed(0, base + GIC_DIST_CTRL); -----------(a)

    cpumask = gic_get_cpumask(gic);---------------(b)

    cpumask |= cpumask << 8;

    cpumask |= cpumask << 16;------------------(c)

    for (i = 32; i < gic_irqs; i += 4)

        writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4); --(d)

    gic_dist_config(base, gic_irqs, NULL); ---------------(e)

    writel_relaxed(1, base + GIC_DIST_CTRL);-------------(f)

}

(a)Distributor Control Register用來控制全局的中斷forward情況。寫入0表示Distributor不向CPU interface發送中斷請求信号,也就disable了全部的中斷請求(group 0和group 1),CPU interace再也收不到中斷請求信号了。在初始化的最後,step(f)那裡會進行enable的動作(這裡隻是enable了group 0的中斷)。在初始化代碼中,并沒有設定interrupt source的group(寄存器是GIC_DIST_IGROUP),我相信預設值就是設定為group 0的。

(b)我們先看看gic_get_cpumask的代碼:

static u8 gic_get_cpumask(struct gic_chip_data *gic)

{

    void __iomem *base = gic_data_dist_base(gic);

    u32 mask, i;

    for (i = mask = 0; i < 32; i += 4) {

        mask = readl_relaxed(base + GIC_DIST_TARGET + i);

        mask |= mask >> 16;

        mask |= mask >> 8;

        if (mask)

            break;

    }

    return mask;

}

這裡操作的寄存器是Interrupt Processor Targets Registers,該寄存器組中,每個GIC上的interrupt ID都有8個bit來控制送達的target CPU。我們來看看下面的圖檔:

Linux中斷 - GIC代碼分析

GIC_DIST_TARGETn(Interrupt Processor Targets Registers)位于Distributor HW block中,能控制送達的CPU interface,并不是具體的CPU,如果具體的實作中CPU interface和CPU是嚴格按照上圖中那樣一一對應,那麼GIC_DIST_TARGET送達了CPU Interface n,也就是送達了CPU n。當然現實未必如你所願,那麼怎樣來擷取這個CPU的mask呢?我們知道SGI和PPI不需要使用GIC_DIST_TARGET控制target CPU。SGI送達目标CPU有自己特有的寄存器來控制(Software Generated Interrupt Register),對于PPI,其是CPU私有的,是以不需要控制target CPU。GIC_DIST_TARGET0~GIC_DIST_TARGET7是控制0~31這32個interrupt ID(SGI和PPI)的target CPU的,但是實際上SGI和PPI是不需要控制target CPU的,是以,這些寄存器是read only的,讀取這些寄存器傳回的就是cpu mask值。假設CPU0接在CPU interface 4上,那麼運作在CPU 0上的程式在讀GIC_DIST_TARGET0~GIC_DIST_TARGET7的時候,傳回的就是0b00010000。

當然,由于GIC-400隻支援8個CPU,是以CPU mask值隻需要8bit,但是寄存器GIC_DIST_TARGETn傳回32個bit的值,怎麼對應?很簡單,cpu mask重複四次就OK了。了解了這些知識,回頭看代碼就很簡單了。

(c)step (b)中擷取了8個bit的cpu mask值,通過簡單的copy,擴充為32個bit,每8個bit都是cpu mask的值,這麼做是為了下一步設定所有IRQ(對于GIC而言就是SPI類型的中斷)的CPU mask。

(d)設定每個SPI類型的中斷都是隻送達該CPU。

(e)配置GIC distributor的其他寄存器,代碼如下:

void __init gic_dist_config(void __iomem *base, int gic_irqs,  void (*sync_access)(void))

{

    unsigned int i;

    for (i = 32; i < gic_irqs; i += 16)

        writel_relaxed(0, base + GIC_DIST_CONFIG + i / 4);

    for (i = 32; i < gic_irqs; i += 4)

        writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i);

    for (i = 32; i < gic_irqs; i += 32)

        writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i / 8);

    if (sync_access)

        sync_access();

}

程式的注釋已經非常清楚了,這裡就不細述了。需要注意的是:這裡設定的都是預設值,實際上,在各種driver的初始化過程中,還是有可能改動這些設定的(例如觸發方式)。

(2)CPU interface初始化,代碼如下:

static void gic_cpu_init(struct gic_chip_data *gic)

{

    void __iomem *dist_base = gic_data_dist_base(gic);-------Distributor的基位址空間

    void __iomem *base = gic_data_cpu_base(gic);-------CPU interface的基位址空間

    unsigned int cpu_mask, cpu = smp_processor_id();------擷取CPU的邏輯ID

    int i;

    cpu_mask = gic_get_cpumask(gic);-------------(a)

    gic_cpu_map[cpu] = cpu_mask;

    for (i = 0; i < NR_GIC_CPU_IF; i++)

        if (i != cpu)

            gic_cpu_map[i] &= ~cpu_mask; ------------(b)

    gic_cpu_config(dist_base, NULL); --------------(c)

    writel_relaxed(0xf0, base + GIC_CPU_PRIMASK);-------(d)

    writel_relaxed(1, base + GIC_CPU_CTRL);-----------(e)

}

(a)系統軟體實際上是使用CPU 邏輯ID這個概念的,通過smp_processor_id可以獲得本CPU的邏輯ID。gic_cpu_map這個全部lookup table就是用CPU 邏輯ID作為是以,去尋找其cpu mask,後續通過cpu mask值來控制中斷是否送達該CPU。在gic_init_bases函數中,我們将該lookup table中的值都初始化為0xff,也就是說不進行mask,送達所有的CPU。這裡,我們會進行重新修正。

(b)清除lookup table中其他entry中本cpu mask的那個bit。

(c)設定SGI和PPI的初始值。具體代碼如下:

void gic_cpu_config(void __iomem *base, void (*sync_access)(void))

{

    int i;

    writel_relaxed(0xffff0000, base + GIC_DIST_ENABLE_CLEAR);

    writel_relaxed(0x0000ffff, base + GIC_DIST_ENABLE_SET);

    for (i = 0; i < 32; i += 4)

        writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);

    if (sync_access)

        sync_access();

}

程式的注釋已經非常清楚了,這裡就不細述了。

(d)通過Distributor中的寄存器可以控制送達CPU interface,中斷來到了GIC的CPU interface是否可以真正送達CPU呢?也不一定,還有一道關卡,也就是CPU interface中的Interrupt Priority Mask Register。這個寄存器設定了一個中斷優先級的值,隻有中斷優先級高過該值的中斷請求才會被送到CPU上去。我們在前面初始化的時候,給每個interrupt ID設定的預設優先級是0xa0,這裡設定的priority filter的優先級值是0xf0。數值越小,優先級越過。是以,這樣的設定就是讓所有的interrupt source都可以送達CPU,在CPU interface這裡不做控制了。

(e)設定CPU interface的control register。enable了group 0的中斷,disable了group 1的中斷,group 0的interrupt source觸發IRQ中斷(而不是FIQ中斷)。

(3)GIC電源管理初始化,代碼如下:

static void __init gic_pm_init(struct gic_chip_data *gic)

{

    gic->saved_ppi_enable = __alloc_percpu(DIV_ROUND_UP(32, 32) * 4, sizeof(u32));

    gic->saved_ppi_conf = __alloc_percpu(DIV_ROUND_UP(32, 16) * 4,  sizeof(u32));

    if (gic == &gic_data[0])

        cpu_pm_register_notifier(&gic_notifier_block);

}

這段代碼前面主要是配置設定兩個per cpu的記憶體。這些記憶體在系統進入sleep狀态的時候儲存PPI的寄存器狀态資訊,在resume的時候,寫回寄存器。對于root GIC,需要注冊一個和電源管理的事件通知callback函數。不得不吐槽一下gic_notifier_block和gic_notifier這兩個符号的命名,看不出來和電源管理有任何關系。更優雅的名字應該包括pm這樣的符号,以便讓其他工程師看到名字就立刻知道是和電源管理相關的。

四、GIC callback函數分析

1、irq domain相關callback函數分析

irq domain相關callback函數包括:

(1)gic_irq_domain_map函數:建立IRQ number和GIC hw interrupt ID之間映射關系的時候,需要調用該回調函數。具體代碼如下:

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)

{

    if (hw < 32) {------------------SGI或者PPI

        irq_set_percpu_devid(irq);--------------------------(a)

        irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);-------(b)

        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);--------------(c)

    } else {

        irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);----------(d)

        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);

        gic_routable_irq_domain_ops->map(d, irq, hw);----------------(e)

    }

    irq_set_chip_data(irq, d->host_data);-----設定irq chip的私有資料

    return 0;

}

(a)SGI或者PPI和SPI最大的不同是per cpu的,SPI是所有CPU共享的,是以需要配置設定per cpu的記憶體,設定一些per cpu的flag。

(b)設定該中斷描述符的irq chip和high level的handler

(c)設定irq flag是有效的(因為已經設定好了chip和handler了),并且request後不是auto enable的。

(d)對于SPI,設定的high level irq event handler是handle_fasteoi_irq。對于SPI,是可以probe,并且request後是auto enable的。

(e)有些SOC會在各種外設中斷和GIC之間增加cross bar(例如TI的OMAP晶片),這裡是為那些ARM SOC準備的

(2)gic_irq_domain_unmap是gic_irq_domain_map的逆過程也就是解除IRQ number和GIC hw interrupt ID之間映射關系的時候,需要調用該回調函數。

(3)gic_irq_domain_xlate函數:除了标準的屬性之外,各個具體的interrupt controller可以定義自己的device binding。這些device bindings都需在irq chip driver這個層面進行解析。要給定某個外設的device tree node 和interrupt specifier,該函數可以解碼出該裝置使用的hw interrupt ID和linux irq type value 。具體的代碼如下:

static int gic_irq_domain_xlate(struct irq_domain *d,

                struct device_node *controller,

                const u32 *intspec, unsigned int intsize,--------輸入參數

                unsigned long *out_hwirq, unsigned int *out_type)----輸出參數

{

    unsigned long ret = 0; 

    *out_hwirq = intspec[1] + 16; ---------------------(a)

    *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK; -----------(b)

    return ret;

}

(a)根據gic binding文檔的描述,其interrupt specifier包括3個cell,分别是interrupt type(0 表示SPI,1表示PPI),interrupt number(對于PPI,範圍是[0-15],對于SPI,範圍是[0-987]),interrupt flag(觸發方式)。GIC interrupt specifier中的interrupt number需要加上16(也就是加上SGI的那些ID号),才能轉換成GIC的HW interrupt ID。

(b)取出bits[3:0]的資訊,這些bits儲存了觸發方式的資訊

2、電源管理的callback函數

TODO

3、irq chip回調函數分析

(1)gic_mask_irq函數

這個函數用來mask一個interrupt source。代碼如下:

static void gic_mask_irq(struct irq_data *d)

{

    u32 mask = 1 << (gic_irq(d) % 32);

    raw_spin_lock(&irq_controller_lock);

    writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_CLEAR + (gic_irq(d) / 32) * 4);

    if (gic_arch_extn.irq_mask)

        gic_arch_extn.irq_mask(d);

    raw_spin_unlock(&irq_controller_lock);

}

GIC有若幹個叫做Interrupt Clear-Enable Registers(具體數目是和GIC支援的hw interrupt數目相關,我們前面說過的,GIC是一個高度可配置的interrupt controller)。這些Interrupt Clear-Enable Registers寄存器的每個bit可以控制一個interrupt source是否forward到CPU interface,寫入1表示Distributor不再forward該interrupt,是以CPU也就感覺不到該中斷,也就是mask了該中斷。特别需要注意的是:寫入0無效,而不是unmask的操作。

由于不同的SOC廠商在內建GIC的時候可能會修改,也就是說,也有可能mask的代碼要微調,這是通過gic_arch_extn這個全局變量實作的。在gic-irq.c中這個變量的全部成員都設定為NULL,各個廠商在初始中斷控制器的時候可以設定其特定的操作函數。

(2)gic_unmask_irq函數

這個函數用來unmask一個interrupt source。代碼如下:

static void gic_unmask_irq(struct irq_data *d)

{

    u32 mask = 1 << (gic_irq(d) % 32);

    raw_spin_lock(&irq_controller_lock);

    if (gic_arch_extn.irq_unmask)

        gic_arch_extn.irq_unmask(d);

    writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4);

    raw_spin_unlock(&irq_controller_lock);

}

GIC有若幹個叫做Interrupt Set-Enable Registers的寄存器。這些寄存器的每個bit可以控制一個interrupt source。當寫入1的時候,表示Distributor會forward該interrupt到CPU interface,也就是意味這unmask了該中斷。特别需要注意的是:寫入0無效,而不是mask的操作。

(3)gic_eoi_irq函數

當processor進行中斷的時候就會調用這個函數用來結束中斷處理。代碼如下:

static void gic_eoi_irq(struct irq_data *d)

{

    if (gic_arch_extn.irq_eoi) {

        raw_spin_lock(&irq_controller_lock);

        gic_arch_extn.irq_eoi(d);

        raw_spin_unlock(&irq_controller_lock);

    }

    writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI);

}

對于GIC而言,其中斷狀态有四種:

中斷狀态 描述
Inactive 中斷未觸發狀态,該中斷即沒有Pending也沒有Active
Pending 由于外設硬體産生了中斷事件(或者軟體觸發)該中斷事件已經通過硬體信号通知到GIC,等待GIC配置設定的那個CPU進行處理
Active CPU已經應答(acknowledge)了該interrupt請求,并且正在進行中
Active and Pending 當一個中斷源處于Active狀态的時候,同一中斷源又觸發了中斷,進入pending狀态

processor ack了一個中斷後,該中斷會被設定為active。當處理完成後,仍然要通知GIC,中斷已經處理完畢了。這時候,如果沒有pending的中斷,GIC就會将該interrupt設定為inactive狀态。操作GIC中的End of Interrupt Register可以完成end of interrupt事件通知。

(4)gic_set_type函數

這個函數用來設定一個interrupt source的type,例如是level sensitive還是edge triggered。代碼如下:

static int gic_set_type(struct irq_data *d, unsigned int type)

{

    void __iomem *base = gic_dist_base(d);

    unsigned int gicirq = gic_irq(d);

    u32 enablemask = 1 << (gicirq % 32);

    u32 enableoff = (gicirq / 32) * 4;

    u32 confmask = 0x2 << ((gicirq % 16) * 2);

    u32 confoff = (gicirq / 16) * 4;

    bool enabled = false;

    u32 val;

    if (gicirq < 16)

        return -EINVAL;

    if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING)

        return -EINVAL;

    raw_spin_lock(&irq_controller_lock);

    if (gic_arch_extn.irq_set_type)

        gic_arch_extn.irq_set_type(d, type);

    val = readl_relaxed(base + GIC_DIST_CONFIG + confoff);

    if (type == IRQ_TYPE_LEVEL_HIGH)

        val &= ~confmask;

    else if (type == IRQ_TYPE_EDGE_RISING)

        val |= confmask;

    if (readl_relaxed(base + GIC_DIST_ENABLE_SET + enableoff) & enablemask) {

        writel_relaxed(enablemask, base + GIC_DIST_ENABLE_CLEAR + enableoff);

        enabled = true;

    }

    writel_relaxed(val, base + GIC_DIST_CONFIG + confoff);

    if (enabled)

        writel_relaxed(enablemask, base + GIC_DIST_ENABLE_SET + enableoff);

    raw_spin_unlock(&irq_controller_lock);

    return 0;

}

對于SGI類型的interrupt,是不能修改其type的,因為GIC中SGI固定就是edge-triggered。對于GIC,其type隻支援高電平觸發(IRQ_TYPE_LEVEL_HIGH)和上升沿觸發(IRQ_TYPE_EDGE_RISING)的中斷。另外需要注意的是,在更改其type的時候,先disable,然後修改type,然後再enable。

(5)gic_retrigger

這個接口用來resend一個IRQ到CPU。

static int gic_retrigger(struct irq_data *d)

{

    if (gic_arch_extn.irq_retrigger)

        return gic_arch_extn.irq_retrigger(d);

    return 0;

}

看起來這是功能不是通用GIC擁有的功能,各個廠家在內建GIC的時候,有可能進行功能擴充。

(6)gic_set_affinity

在多處理器的環境下,外部裝置産生了一個中斷就需要送到一個或者多個處理器去,這個設定是通過設定處理器的affinity進行的。具體代碼如下:

static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val,    bool force)

{

    void __iomem *reg = gic_dist_base(d) + GIC_DIST_TARGET + (gic_irq(d) & ~3);

    unsigned int cpu, shift = (gic_irq(d) % 4) * 8;

    u32 val, mask, bit;

    if (!force)

        cpu = cpumask_any_and(mask_val, cpu_online_mask);---随機選取一個online的cpu

    else

        cpu = cpumask_first(mask_val); --------選取mask中的第一個cpu,不管是否online

    raw_spin_lock(&irq_controller_lock);

    mask = 0xff << shift;

    bit = gic_cpu_map[cpu] << shift;-------将CPU的邏輯ID轉換成要設定的cpu mask

    val = readl_relaxed(reg) & ~mask;

    writel_relaxed(val | bit, reg);

    raw_spin_unlock(&irq_controller_lock);

    return IRQ_SET_MASK_OK;

}

GIC Distributor中有一個寄存器叫做Interrupt Processor Targets Registers,這個寄存器用來設定制定的中斷送到哪個process去。由于GIC最大支援8個process,是以每個hw interrupt ID需要8個bit來表示送達的process。每一個Interrupt Processor Targets Registers由32個bit組成,是以每個Interrupt Processor Targets Registers可以表示4個HW interrupt ID的affinity,是以上面的代碼中的shift就是計算該HW interrupt ID在寄存器中的偏移。

(7)gic_set_wake

這個接口用來設定喚醒CPU的interrupt source。對于GIC,代碼如下:

static int gic_set_wake(struct irq_data *d, unsigned int on)

{

    int ret = -ENXIO;

    if (gic_arch_extn.irq_set_wake)

        ret = gic_arch_extn.irq_set_wake(d, on);

    return ret;

}

設定喚醒的interrupt和具體的廠商相關,這裡不再贅述。

4、BSP(bootstrap processor)之外,其他CPU的callback函數

對于multi processor系統,不可能初始化代碼在所有的processor上都執行一遍,實際上,系統的硬體會選取一個processor作為引導處理器,我們稱之BSP。這個processor會首先執行,其他的CPU都是處于reset狀态,等到BSP初始化完成之後,release所有的non-BSP,這時候,系統中的各種外設硬體條件和軟體條件(例如per CPU變量)都準備好了,各個non-BSP執行自己CPU specific的初始化就OK了。

上面描述的都是BSP的初始化過程,具體包括:

……

    gic_dist_init(gic);------初始化GIC的Distributor

    gic_cpu_init(gic);------初始化BSP的CPU interface

    gic_pm_init(gic);------初始化GIC的Power management

……

對于GIC的Distributor和Power management,這兩部分是全局性的,BSP執行初始化一次就OK了。對于CPU interface,每個processor負責初始化自己的連接配接的那個CPU interface HW block。我們用下面這個圖檔來描述這個過程:

Linux中斷 - GIC代碼分析

  假設CPUx被標明為BSP,那麼第三章描述的初始化過程在該CPU上歡暢的執行。這時候,被初始化的GIC硬體包括:root GIC的Distributor、root GIC CPU Interface x(連接配接BSP的那個CPU interface)以及其他的級聯的非root GIC(上圖中綠色block,當然,我偷懶,沒有畫non-root GIC)。

BSP初始化完成之後,各個其他的CPU運作起來,會發送CPU_STARTING消息給關注該消息的子產品。毫無疑問,GIC driver子產品當然要關注這樣的消息,在初始化過程中會注冊callback函數如下:

register_cpu_notifier(&gic_cpu_notifier);

GIC相關的回調函數定義如下:

static struct notifier_block gic_cpu_notifier = {

    .notifier_call = gic_secondary_init,

    .priority = 100,

};

static int gic_secondary_init(struct notifier_block *nfb, unsigned long action,  void *hcpu)

{

    if (action == CPU_STARTING || action == CPU_STARTING_FROZEN)

        gic_cpu_init(&gic_data[0]);---------初始化那些非BSP的CPU interface

    return NOTIFY_OK;

}

是以,當non-BSP booting up的時候,發送CPU_STARTING消息,調用GIC的callback函數,對上圖中的紫色的CPU Interface HW block進行初始化,這樣,就完成了全部GIC硬體的初始化過程。