GIC , SPI , PPI (窩窩科技的文章題目改了下)【轉】

轉自:https://www.cnblogs.com/tureno/articles/6403408.htmlhtml

轉載於:  http://www.wowotech.net/irq_subsystem/gic-irq-chip-driver.htmlnode

 

GIC驅動代碼分析(廢棄)linux

這份文檔狀態是:廢棄,新的文檔請訪問http://www.wowotech.net/linux_kenrel/gic_driver.html數據結構

1、前言dom

GIC(Generic Interrupt Controller)是ARM公司提供的一個通用的中斷控制器。GIC經過AMBA(Advanced Microcontroller Bus Architecture)這樣的片上總線鏈接到一個或者多個ARM processor上。本文主要分析了linux kernel中GIC中斷控制器的驅動代碼。異步

具體的分析方法是按照source code爲索引,逐段分析。對於每一段分析的代碼,力求作到每一個細節都清清楚楚。這不可避免要引入不少對GIC的硬件描述,此外,具體GIC中斷控制器的驅動代碼和linux kernel中斷子系統的交互也會描述,但本文不會描述linux kernel的generic interrupt subsystem。函數

本文以OMAP4460這款SOC爲例,OMAP4460內部集成了GIC的功能。具體的linux kernel的版本是linux3.14.。編碼

 

2、GIC DTS描述spa

一、中斷系統概述操作系統

對於中斷系統,主要有三個角色:

(1)processor。主要用於處理中斷

(2)Interrupt Generating Device。經過硬件的interrupt line代表自身須要處理器的進一步處理(例若有數據到來、異常狀態等)

(3)interrupt controller。負責收集各個外設的異步事件,用有序、可控的方式通知一個或者多個processor。

 

二、DTS如何描述Interrupt Generating Device

對於Interrupt Generating Device,咱們須要定義下面兩個屬性:

(1)Interrupt屬性。該屬性主要描述了中斷的HW interrupt ID以及類型。

(2)interrupt-parent 屬性。該屬性主要描述了該設備的interrupt request line鏈接到哪個interrupt controller。

在OMAP4460系統中,咱們以一個簡單的串口爲例子,具體的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:

uart3: serial@48020000 { 
            compatible = "ti,omap4-uart"; 
            reg = <0x48020000 0x100="">; 
            interrupts = <gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>; 
            ti,hwmods = "uart3"; 
            clock-frequency = <48000000>; 
        };

對於uart3,interrupts屬性用3個cell(對於device tree,cell是指由32bit組成的一個信息單位)表示。GIC_SPI 描述了interrupt type。對於GIC,它能夠管理4種類型的中斷:

(1)外設中斷(Peripheral interrupt)。根據目標CPU的不一樣,外設的中斷能夠分紅PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI只能分配給一個肯定的processor,而SPI能夠由Distributor將中斷分配給一組Processor中的一個進行處理。外設類型的中斷通常經過一個interrupt request line的硬件信號線鏈接到中斷控制器,多是電平觸發的(Level-sensitive),也多是邊緣觸發的(Edge-triggered)。

(2)軟件觸發的中斷(SGI,Software-generated interrupt)。軟件能夠經過寫GICD_SGIR寄存器來觸發一箇中斷事件,這樣的中斷,能夠用於processor之間的通訊。

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

在DTS中,外設的interrupt type有兩種,一種是SPI,另一種是PPI。SGI用於processor之間的通訊,和外設無關。

uart3的interrupt屬性中的74表示該外設使用的GIC interrupt ID號。GIC最大支持1020個HW interrupt ID,具體的ID分配狀況以下:

(1)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。

(2)ID32~ID1019用於SPI。

uart3的interrupt屬性中的IRQ_TYPE_LEVEL_HIGH用來描述觸發類型。

很奇怪,uart3並無定義interrupt-parent屬性,這裏定義interrupt-parent屬性的是root node,具體的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:

/ { 
    compatible = "ti,omap4430", "ti,omap4"; 
    interrupt-parent = <&gic>;

略去無關內容

}

難道root node會產生中斷到interrupt controller嗎?固然不會,只不過若是一個可以產生中斷的device node沒有定義interrupt-parent的話,其interrupt-parent屬性就是跟隨parent node。所以,與其在全部的下游設備中定義interrupt-parent,不如統一在root node中定義了。

三、DTS如何描述GIC

linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中,

    gic: interrupt-controller@48241000 { 
        compatible = "arm,cortex-a9-gic"; 
        interrupt-controller; 
        #interrupt-cells = <3>; 
        reg = <0x48241000 0x1000="">, 
              <0x48240100 0x0100="">; 
    };

compatible屬性用來描述GIC的programming model。該屬性的值是string list,定義了一系列的modle(每一個string是一個model)。這些字符串列表被操做系統用來選擇用哪個driver來驅動該設備。假設定義該屬性:compatible = 「a廠商,p產品」, 「標準bbb類型設備」。那麼linux kernel可能首先使用「a廠商,p產品」來匹配適合的driver,若是沒有匹配到,那麼使用字符串「標準bbb類型設備」來繼續尋找適合的driver。compatible屬性有兩個應用場景:

(1)對於root node,compatible屬性是用來匹配machine type的(參考Device Tree相關文檔)

(2)對於普通的HW block的節點,例如interrupt-controller,compatible屬性是用來匹配適合的driver的。

interrupt-controller這個沒有定義value的屬性用來代表本設備節點就是一個interrupt controller。理解#interrupt-cells這個屬性須要理解interrupt specifier和interrupt domain這兩個概念。interrupt specifier其實就是外設interrupt的屬性值,對於uart3而言,其interrupt specifier就是<gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,也就是說,interrupt specifier定義了一個外設產生中斷的規格(HW interrupt ID + interrupt type)。具體如何解析interrupt specifier?這個須要限定在必定的上下文中,不一樣的interrupt controller會有不一樣的解釋。所以,對於一個包含多個interrupt controller的系統,每一個interrupt controller及其相連的外設組成一個interrupt domain,各個外設的interrupt specifier只能在屬於它的那個interrupt domain中獲得解析。#interrupt-cells定義了在該interrupt domain中,用多少個cell來描述一個外設的interrupt specifier。

reg屬性定義了GIC的memory map的地址,有兩段,分別描述了CPU interface和Distributor的地址段。理解CPU interface和Distributor這兩個術語須要GIC的一些基本的硬件知識,參考下節描述。

 

3、GIC的HW block diagram描述

一、GIC HW概述

GIC的block diagram以下圖所示:

gic

GIC能夠清晰的劃分紅兩個block,一個block是Distributor(上圖的左邊的block),一個是CPU interface。CPU interface有兩種,一種就是和普通processor接口,另一種是和虛擬機接口的。Virtual CPU interface在本文中不會詳細描述。

 

二、Distributor

Distributor的主要的做用是檢測各個interrupt source的狀態,控制各個interrupt source的行爲,分發各個interrupt source產生的中斷事件到各個processor。Distributor對中斷的控制包括:

(1)中斷enable或者disable的控制。Distributor對中斷的控制分紅兩個級別。一個是全局中斷的控制。一旦disable了全局的中斷,那麼任何的interrupt source產生的interrupt event都不會被傳遞到CPU interface。另一個級別是對針對各個interrupt source進行控制,disable某一個interrupt source會致使該interrupt event不會分發到CPU interface,但不影響其餘interrupt source產生interrupt event的分發。

(2)控制中斷事件分發到process。一個interrupt事件能夠分發給一個process,也能夠分發給若干個process。

(3)優先級控制。

(3)interrupt屬性設定。例如是level-sensitive仍是edge-triggered,是屬於group 0仍是group 1。

Distributor能夠管理若干個interrupt source,這些interrupt source用ID來標識,咱們稱之interrupt ID。

 

二、CPU interface

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

(1)enable或者disable。對於ARM,CPU interface block和process之間的中斷信號線是nIRQ和nFIQ這兩個signal。若是disable了中斷,那麼即使是Distributor分發了一箇中斷事件到CPU interface,可是也不會assert指定的nIRQ或者nFIQ通知processor。

(2)ackowledging中斷。processor會向CPU interface block應答中斷,中斷一旦被應答,Distributor就會把該中斷的狀態從pending狀態修改爲active。若是沒有後續pending的中斷,那麼CPU interface就會deassert nIRQ或者nFIQ的signal。若是在這個過程當中又產生了新的中斷,那麼Distributor就會把該中斷的狀態從pending狀態修改爲pending and active。這時候,CPU interface仍然會保持nIRQ或者nFIQ信號的asserted狀態,也就是向processor signal下一個中斷。

(3)中斷處理完畢的通知。當interrupt handler處理完了一箇中斷的時候,會向寫CPU interface的寄存器從而通知GIC CPU已經處理完該中斷。作這個動做一方面是通知Distributor將中斷狀態修改成deactive,另一方面,若是一箇中斷沒有完成處理,那麼後續比該中斷優先級低的中斷不會assert到processor。一旦標記中斷處理完成,被block掉的那些比當前優先級低的中斷就會遞交給processor。

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

(5)設定preemption的策略

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

 

4、系統啓動過程當中,如何調用GIC driver的初始化函數

在linux-3.14\drivers\irqchip目錄下保存在各類不一樣的中斷控制器的驅動代碼,irq-gic.c就是GIC的驅動代碼。

一、IRQCHIP_DECLARE宏定義

在linux-3.14\drivers\irqchip目錄下的irqchip.h文件中定義了IRQCHIP_DECLARE宏以下:

#define IRQCHIP_DECLARE(name,compstr,fn)                \ 
    static const struct of_device_id irqchip_of_match_##name    \ 
    __used __section(__irqchip_of_table)                \ 
    = { .compatible = compstr, .data = fn }

這個宏就是被各個irq chip driver用來聲明其DT compatible string和初始化函數的對應關係的。IRQCHIP_DECLARE中的compstr就是DT compatible string,fun就是初始化函數。irq-gic.c文件中聲明的對應關係包括:

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(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init); 
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

從上面的定義能夠看出來,A9和A15的gic初始化函數都是gic_of_init。另外兩個定義是和高通的CPU相關,我猜想是高通使用了GIC,可是本身又作了一些簡單的修改,但不管如何,初始化函數都是一個,那就是gic_of_init。

在linux kernel編譯的時候,你能夠配置多個irq chip進入內核,編譯系統會把全部的IRQCHIP_DECLARE宏定義的數據放入到一個特殊的section中(section name是__irqchip_of_table),咱們稱這個特殊的section叫作irq chip table。這個table也就保存了kernel支持的全部的中斷控制器的驅動代碼初始化函數和DT compatible string的對應關係。具體執行哪個初始化函數是由bootloader傳遞給kernel的DTB決定的。

二、OMAP4460的machine定義

在linux-3.14/arch/arm/mach-omap2目錄下的board-generic.c的文件中定義了OMAP4460的machine以下:

DT_MACHINE_START(OMAP4_DT, "Generic OMAP4 (Flattened Device Tree)") 
  。。。。。。刪除無關代碼 
    .init_irq    = omap_gic_of_init, 
。。。。。。刪除無關代碼 
MACHINE_END

在系統初始化的時候,會調用start_kernel->init_IRQ->machine_desc->init_irq()函數。

 

三、omap_gic_of_init過程分析

omap_gic_of_init的代碼以下(刪除了無關代碼):

void __init omap_gic_of_init(void) 

    irqchip_init(); 
}

在driver/irqchip/irqchip.c文件中定義了irqchip_init函數,以下:

void __init irqchip_init(void) 

    of_irq_init(__irqchip_begin); 
}

__irqchip_begin就是內核irq chip table的首地址,這個table也就保存了kernel支持的全部的中斷控制器的驅動代碼初始化函數和DT compatible string的對應關係。of_irq_init函數scan bootloader傳遞來的DTB,找到全部的中斷控制器節點,並造成樹狀結構(系統能夠有多個interrupt controller)。以後,從root interrupt controller開始,對於每個interrupt controller,scan irq chip table,匹配DT compatible string,一旦匹配到,就調用該interrupt controller的初始化函數。具體的代碼分析能夠參考Device Tree代碼分析文檔

 

5、GIC driver初始化代碼分析

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;

    if (WARN_ON(!node)) 
        return -ENODEV;

    dist_base = of_iomap(node, 0);----------------映射GIC Distributor的寄存器地址空間 
    WARN(!dist_base, "unable to map gic dist registers\n");

    cpu_base = of_iomap(node, 1);----------------映射GIC CPU interface的寄存器地址空間 
    WARN(!cpu_base, "unable to map gic cpu registers\n");

    if (of_property_read_u32(node, "cpu-offset", &percpu_offset))--------處理cpu-offset binding。 
        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); 
        gic_cascade_irq(gic_cnt, irq); 
    } 
    gic_cnt++; 
    return 0; 
}

咱們首先看看這個函數的參數,node參數表明須要初始化的那個interrupt controller的device node,parent參數指向其parent。對於cpu-offset binding,能夠參考linux-3.14/Documentation/devicetree/bindings/arm/gic.txt文件中關於cpu-offset 的描述。對於OMAP4460,其GIC支持banked register,所以percpu_offset等於0。

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;

    BUG_ON(gic_nr >= MAX_GIC_NR);

    gic = &gic_data[gic_nr]; 
     WARN(percpu_offset, 
             "GIC_NON_BANKED not enabled, ignoring %08x offset!", 
             percpu_offset); 
        gic->dist_base.common_base = dist_base; 
        gic->cpu_base.common_base = cpu_base; 
        gic_set_base_accessor(gic, gic_get_common_base);

    /* 
     * Initialize the CPU interface map to all CPUs. 
     * It will be refined as each CPU probes its ID. 
     */ 
    for (i = 0; i < NR_GIC_CPU_IF; i++) 
        gic_cpu_map[i] = 0xff;

    /* 
     * For primary GICs, skip over SGIs. 
     * For secondary GICs, skip over PPIs, too. 
     */ 
    if (gic_nr == 0 && (irq_start & 31) > 0) { 
        hwirq_base = 16; 
        if (irq_start != -1) 
            irq_start = (irq_start & ~31) + 16; 
    } else { 
        hwirq_base = 32; 
    }

    /* 
     * Find out how many interrupts are supported. 
     * The GIC only supports up to 1020 interrupt sources. 
     */ 
    gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; 
    gic_irqs = (gic_irqs + 1) * 32; 
    if (gic_irqs > 1020) 
        gic_irqs = 1020; 
    gic->gic_irqs = gic_irqs;

    gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ 
    irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); 
    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, 
                    hwirq_base, &gic_irq_domain_ops, gic); 
    if (WARN_ON(!gic->domain)) 
        return;

    if (gic_nr == 0) { 
#ifdef CONFIG_SMP 
        set_smp_cross_call(gic_raise_softirq);---------設定raise SGI的方法 
        register_cpu_notifier(&gic_cpu_notifier);---------在multi processor環境下,當其餘processor online的時候,須要調用回調函數來初始化GIC的cpu interface。 
#endif 
        set_handle_irq(gic_handle_irq); 
    }

    gic_chip.flags |= gic_arch_extn.flags; 
    gic_dist_init(gic);---------具體的硬件初始代碼,再也不贅述,能夠參考GIC reference manual 
    gic_cpu_init(gic); 
    gic_pm_init(gic); 
}

這個函數的前半部分都是爲了向系統中註冊一個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的數目能夠經過其支持的HW interrupt的數目減去hwirq_base獲得,那麼如何獲取GIC支持的HW interrupt的數目呢?GIC有一個寄存器叫作Interrupt Controller Type Register,經過這個寄存器能夠得到下列信息:

(1)whether the GIC implements the Security Extensions 
(2)the maximum number of interrupt IDs that the GIC supports 
(3)the number of CPU interfaces implemented 
(4)if the GIC implements the Security Extensions, the maximum number of implemented Lockable Shared Peripheral Interrupts (LSPIs).

得到了GIC支持的IRQ數目後,就能夠調用irq_alloc_descs函數分配IRQ描述符資源了。以後,經過調用irq_domain_add_legacy函數註冊GIC的irq domain數據結構。這裏有一個重要的數據結構gic_irq_domain_ops,其類型是struct irq_domain_ops ,定義以下:

struct irq_domain_ops { 
    int (*match)(struct irq_domain *d, struct device_node *node); 
    int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); 
    void (*unmap)(struct irq_domain *d, unsigned int virq); 
    int (*xlate)(struct irq_domain *d, struct device_node *node, 
             const u32 *intspec, unsigned int intsize, 
             unsigned long *out_hwirq, unsigned int *out_type); 
};

對於GIC,其irq domain的操做函數是gic_irq_domain_ops,定義以下:

const struct irq_domain_ops gic_irq_domain_ops = { 
    .map = gic_irq_domain_map, 
    .xlate = gic_irq_domain_xlate, 
};

irq domain的概念是一個通用中斷子系統的概念,在irq chip這個層次,咱們須要和通用中斷子系統中的irq domain core code進行接口,gic_irq_domain_ops就是這個接口的定義。

(1)map成員函數:用來創建irq number和hw interrupt ID之間的聯繫 
(2)Xlate成員函數:給定某個外設的device tree node 和interrupt specifier,該函數能夠解碼出該設備使用的hw interrupt ID和linux irq type value

具體向系統註冊irq domain是經過irq_domain_add_legacy函數進行的:

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, 
                     unsigned int size, 
                     unsigned int first_irq, 
                     irq_hw_number_t first_hwirq, 
                     const struct irq_domain_ops *ops, 
                     void *host_data) 

    struct irq_domain *domain;

    domain = __irq_domain_add(of_node, first_hwirq + size, 
                  first_hwirq + size, 0, ops, host_data); 
    if (!domain) 
        return NULL;

    irq_domain_associate_many(domain, first_irq, first_hwirq, size);

    return domain; 
}

這個函數主要進行兩個步驟,一個是經過__irq_domain_add將GIC對應的irq domain加入到系統,另一個就是調用irq_domain_associate_many來創建IRQ number和hw interrupt ID之間的關係。而這個關係的創建其實是經過irq domain的操做函數中的map回調函數進行的,對於GIC而言就是gic_irq_domain_map,代碼以下:

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

    if (hw < 32) { 
        irq_set_percpu_devid(irq); 
        irq_set_chip_and_handler(irq, &gic_chip, 
                     handle_percpu_devid_irq); 
        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); 
    } else { 
        irq_set_chip_and_handler(irq, &gic_chip, 
                     handle_fasteoi_irq); 
        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); 
    } 
    irq_set_chip_data(irq, d->host_data); 
    return 0; 
}

這個函數的主要功能就是把一個IRQ號(參數中的unsigned int irq)和一個hw interrupt ID(參數中的irq_hw_number_t hw)聯繫起來。每一個IRQ都有其對應的描述符,用struct irq_desc表示:

struct irq_desc { 
    struct irq_data        irq_data; 

    irq_flow_handler_t    handle_irq; 
…… 
} __

函數irq_set_chip_and_handler其實主要就是設定struct irq_desc中的irq_data和handle_irq成員。handle_irq就是發生了一個IRQ後須要調用的中斷處理函數。irq_data數據結構中的chip成員就是指向具體的硬件信息,也就是GIC的描述符。咱們定義以下:

static struct irq_chip gic_chip = { 
    .name            = "GIC", 
    .irq_mask        = gic_mask_irq, 
    .irq_unmask        = gic_unmask_irq, 
    .irq_eoi        = gic_eoi_irq, 
    .irq_set_type        = gic_set_type, 
    .irq_retrigger        = gic_retrigger, 
#ifdef CONFIG_SMP 
    .irq_set_affinity    = gic_set_affinity, 
#endif 
    .irq_set_wake        = gic_set_wake, 
};

struct irq_chip數據結構中有各類各樣的操做函數,例如enable或者disable一箇中斷,ack一箇中斷等。

綜上所述,通過了初始化過程以後,對於linux的內存管理系統而言,增長了GIC的地址空間映射。對於linux kernel的generic interrupt subsystem,增長了若干個IRQ的描述符,而且設定了這些IRQ描述符的處理函數以及相關的irq_data數據結構(和具體HW相關,該數據結構的成員包括抽象具體硬件的interrupt controller的描述符以及irq domain數據結構)。

6、GIC硬件操做

一、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,各個廠商在初始中斷控制器的時候能夠設定其特定的操做函數。

二、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的操做。

三、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事件通知。

四、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;

    /* Interrupt configuration for SGIs can't be changed */ 
    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;

    /* 
     * As recommended by the spec, disable the interrupt before changing 
     * the configuration 
     */ 
    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。

五、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);

    /* the genirq layer expects 0 if we can't retrigger in hardware */ 
    return 0; 
}

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

六、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 shift = (gic_irq(d) % 4) * 8; 
    unsigned int cpu = cpumask_any_and(mask_val, cpu_online_mask); 
    u32 val, mask, bit;

    if (cpu >= NR_GIC_CPU_IF || cpu >= nr_cpu_ids) 
        return -EINVAL;

    raw_spin_lock(&irq_controller_lock); 
    mask = 0xff << shift; 
    bit = gic_cpu_map[cpu] << shift; 
    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。

七、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和具體的廠商相關,這裏再也不贅述。

 

原創文章,轉發請註明出處。蝸窩科技,www.wowotech.net

標籤: GIC 代碼分析

GIC驅動代碼分析(廢棄)

做者:linuxer 發佈於:2014-7-4 14:34 分類:中斷子系統

這份文檔狀態是:廢棄,新的文檔請訪問http://www.wowotech.net/linux_kenrel/gic_driver.html

1、前言

GIC(Generic Interrupt Controller)是ARM公司提供的一個通用的中斷控制器。GIC經過AMBA(Advanced Microcontroller Bus Architecture)這樣的片上總線鏈接到一個或者多個ARM processor上。本文主要分析了linux kernel中GIC中斷控制器的驅動代碼。

具體的分析方法是按照source code爲索引,逐段分析。對於每一段分析的代碼,力求作到每一個細節都清清楚楚。這不可避免要引入不少對GIC的硬件描述,此外,具體GIC中斷控制器的驅動代碼和linux kernel中斷子系統的交互也會描述,但本文不會描述linux kernel的generic interrupt subsystem。

本文以OMAP4460這款SOC爲例,OMAP4460內部集成了GIC的功能。具體的linux kernel的版本是linux3.14.。

 

2、GIC DTS描述

一、中斷系統概述

對於中斷系統,主要有三個角色:

(1)processor。主要用於處理中斷

(2)Interrupt Generating Device。經過硬件的interrupt line代表自身須要處理器的進一步處理(例若有數據到來、異常狀態等)

(3)interrupt controller。負責收集各個外設的異步事件,用有序、可控的方式通知一個或者多個processor。

 

二、DTS如何描述Interrupt Generating Device

對於Interrupt Generating Device,咱們須要定義下面兩個屬性:

(1) Interrupt屬性。該屬性主要描述了中斷的HW interrupt ID以及類型。

(2)interrupt-parent 屬性。該屬性主要描述了該設備的interrupt request line鏈接到哪個interrupt controller。

在OMAP4460系統中,咱們以一個簡單的串口爲例子,具體的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:

uart3: serial@48020000 { 
            compatible = "ti,omap4-uart"; 
            reg = <0x48020000 0x100="">; 
            interrupts = <gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>; 
            ti,hwmods = "uart3"; 
            clock-frequency = <48000000>; 
        };

對於uart3,interrupts屬性用3個cell(對於device tree,cell是指由32bit組成的一個信息單位)表示。GIC_SPI 描述了interrupt type。對於GIC,它能夠管理4種類型的中斷:

(1)外設中斷(Peripheral interrupt)。根據目標CPU的不一樣,外設的中斷能夠分紅PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI只能分配給一個肯定的processor,而SPI能夠由Distributor將中斷分配給一組Processor中的一個進行處理。外設類型的中斷通常經過一個interrupt request line的硬件信號線鏈接到中斷控制器,多是電平觸發的(Level-sensitive),也多是邊緣觸發的(Edge-triggered)。

(2)軟件觸發的中斷(SGI,Software-generated interrupt)。軟件能夠經過寫GICD_SGIR寄存器來觸發一箇中斷事件,這樣的中斷,能夠用於processor之間的通訊。

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

在DTS中,外設的interrupt type有兩種,一種是SPI,另一種是PPI。SGI用於processor之間的通訊,和外設無關。

uart3的interrupt屬性中的74表示該外設使用的GIC interrupt ID號。GIC最大支持1020個HW interrupt ID,具體的ID分配狀況以下:

(1)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。

(2)ID32~ID1019用於SPI。

uart3的interrupt屬性中的IRQ_TYPE_LEVEL_HIGH用來描述觸發類型。

很奇怪,uart3並無定義interrupt-parent屬性,這裏定義interrupt-parent屬性的是root node,具體的描述在linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中:

/ { 
    compatible = "ti,omap4430", "ti,omap4"; 
    interrupt-parent = <&gic>;

略去無關內容

}

難道root node會產生中斷到interrupt controller嗎?固然不會,只不過若是一個可以產生中斷的device node沒有定義interrupt-parent的話,其interrupt-parent屬性就是跟隨parent node。所以,與其在全部的下游設備中定義interrupt-parent,不如統一在root node中定義了。

三、DTS如何描述GIC

linux-3.14\arch\arm\boot\dts\omap4.dtsi文件中,

    gic: interrupt-controller@48241000 { 
        compatible = "arm,cortex-a9-gic"; 
        interrupt-controller; 
        #interrupt-cells = <3>; 
        reg = <0x48241000 0x1000="">, 
              <0x48240100 0x0100="">; 
    };

compatible屬性用來描述GIC的programming model。該屬性的值是string list,定義了一系列的modle(每一個string是一個model)。這些字符串列表被操做系統用來選擇用哪個driver來驅動該設備。假設定義該屬性:compatible = 「a廠商,p產品」, 「標準bbb類型設備」。那麼linux kernel可能首先使用「a廠商,p產品」來匹配適合的driver,若是沒有匹配到,那麼使用字符串「標準bbb類型設備」來繼續尋找適合的driver。compatible屬性有兩個應用場景:

(1)對於root node,compatible屬性是用來匹配machine type的(參考Device Tree相關文檔)

(2)對於普通的HW block的節點,例如interrupt-controller,compatible屬性是用來匹配適合的driver的。

interrupt-controller這個沒有定義value的屬性用來代表本設備節點就是一個interrupt controller。理解#interrupt-cells這個屬性須要理解interrupt specifier和interrupt domain這兩個概念。interrupt specifier其實就是外設interrupt的屬性值,對於uart3而言,其interrupt specifier就是<gic_spi 74="" irq_type_level_high=""><GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,也就是說,interrupt specifier定義了一個外設產生中斷的規格(HW interrupt ID + interrupt type)。具體如何解析interrupt specifier?這個須要限定在必定的上下文中,不一樣的interrupt controller會有不一樣的解釋。所以,對於一個包含多個interrupt controller的系統,每一個interrupt controller及其相連的外設組成一個interrupt domain,各個外設的interrupt specifier只能在屬於它的那個interrupt domain中獲得解析。#interrupt-cells定義了在該interrupt domain中,用多少個cell來描述一個外設的interrupt specifier。

reg屬性定義了GIC的memory map的地址,有兩段,分別描述了CPU interface和Distributor的地址段。理解CPU interface和Distributor這兩個術語須要GIC的一些基本的硬件知識,參考下節描述。

 

3、GIC的HW block diagram描述

一、GIC HW概述

GIC的block diagram以下圖所示:

gic

GIC能夠清晰的劃分紅兩個block,一個block是Distributor(上圖的左邊的block),一個是CPU interface。CPU interface有兩種,一種就是和普通processor接口,另一種是和虛擬機接口的。Virtual CPU interface在本文中不會詳細描述。

 

二、Distributor

Distributor的主要的做用是檢測各個interrupt source的狀態,控制各個interrupt source的行爲,分發各個interrupt source產生的中斷事件到各個processor。Distributor對中斷的控制包括:

(1)中斷enable或者disable的控制。Distributor對中斷的控制分紅兩個級別。一個是全局中斷的控制。一旦disable了全局的中斷,那麼任何的interrupt source產生的interrupt event都不會被傳遞到CPU interface。另一個級別是對針對各個interrupt source進行控制,disable某一個interrupt source會致使該interrupt event不會分發到CPU interface,但不影響其餘interrupt source產生interrupt event的分發。

(2)控制中斷事件分發到process。一個interrupt事件能夠分發給一個process,也能夠分發給若干個process。

(3)優先級控制。

(3)interrupt屬性設定。例如是level-sensitive仍是edge-triggered,是屬於group 0仍是group 1。

Distributor能夠管理若干個interrupt source,這些interrupt source用ID來標識,咱們稱之interrupt ID。

 

二、CPU interface

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

(1)enable或者disable。對於ARM,CPU interface block和process之間的中斷信號線是nIRQ和nFIQ這兩個signal。若是disable了中斷,那麼即使是Distributor分發了一箇中斷事件到CPU interface,可是也不會assert指定的nIRQ或者nFIQ通知processor。

(2)ackowledging中斷。processor會向CPU interface block應答中斷,中斷一旦被應答,Distributor就會把該中斷的狀態從pending狀態修改爲active。若是沒有後續pending的中斷,那麼CPU interface就會deassert nIRQ或者nFIQ的signal。若是在這個過程當中又產生了新的中斷,那麼Distributor就會把該中斷的狀態從pending狀態修改爲pending and active。這時候,CPU interface仍然會保持nIRQ或者nFIQ信號的asserted狀態,也就是向processor signal下一個中斷。

(3)中斷處理完畢的通知。當interrupt handler處理完了一箇中斷的時候,會向寫CPU interface的寄存器從而通知GIC CPU已經處理完該中斷。作這個動做一方面是通知Distributor將中斷狀態修改成deactive,另一方面,若是一箇中斷沒有完成處理,那麼後續比該中斷優先級低的中斷不會assert到processor。一旦標記中斷處理完成,被block掉的那些比當前優先級低的中斷就會遞交給processor。

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

(5)設定preemption的策略

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

 

4、系統啓動過程當中,如何調用GIC driver的初始化函數

在linux-3.14\drivers\irqchip目錄下保存在各類不一樣的中斷控制器的驅動代碼,irq-gic.c就是GIC的驅動代碼。

一、IRQCHIP_DECLARE宏定義

在linux-3.14\drivers\irqchip目錄下的irqchip.h文件中定義了IRQCHIP_DECLARE宏以下:

#define IRQCHIP_DECLARE(name,compstr,fn)                \ 
    static const struct of_device_id irqchip_of_match_##name    \ 
    __used __section(__irqchip_of_table)                \ 
    = { .compatible = compstr, .data = fn }

這個宏就是被各個irq chip driver用來聲明其DT compatible string和初始化函數的對應關係的。IRQCHIP_DECLARE中的compstr就是DT compatible string,fun就是初始化函數。irq-gic.c文件中聲明的對應關係包括:

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(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init); 
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

從上面的定義能夠看出來,A9和A15的gic初始化函數都是gic_of_init。另外兩個定義是和高通的CPU相關,我猜想是高通使用了GIC,可是本身又作了一些簡單的修改,但不管如何,初始化函數都是一個,那就是gic_of_init。

在linux kernel編譯的時候,你能夠配置多個irq chip進入內核,編譯系統會把全部的IRQCHIP_DECLARE宏定義的數據放入到一個特殊的section中(section name是__irqchip_of_table),咱們稱這個特殊的section叫作irq chip table。這個table也就保存了kernel支持的全部的中斷控制器的驅動代碼初始化函數和DT compatible string的對應關係。具體執行哪個初始化函數是由bootloader傳遞給kernel的DTB決定的。

二、OMAP4460的machine定義

在linux-3.14/arch/arm/mach-omap2目錄下的board-generic.c的文件中定義了OMAP4460的machine以下:

DT_MACHINE_START(OMAP4_DT, "Generic OMAP4 (Flattened Device Tree)") 
  。。。。。。刪除無關代碼 
    .init_irq    = omap_gic_of_init, 
。。。。。。刪除無關代碼 
MACHINE_END

在系統初始化的時候,會調用start_kernel->init_IRQ->machine_desc->init_irq()函數。

 

三、omap_gic_of_init過程分析

omap_gic_of_init的代碼以下(刪除了無關代碼):

void __init omap_gic_of_init(void) 

    irqchip_init(); 
}

在driver/irqchip/irqchip.c文件中定義了irqchip_init函數,以下:

void __init irqchip_init(void) 

    of_irq_init(__irqchip_begin); 
}

__irqchip_begin就是內核irq chip table的首地址,這個table也就保存了kernel支持的全部的中斷控制器的驅動代碼初始化函數和DT compatible string的對應關係。of_irq_init函數scan bootloader傳遞來的DTB,找到全部的中斷控制器節點,並造成樹狀結構(系統能夠有多個interrupt controller)。以後,從root interrupt controller開始,對於每個interrupt controller,scan irq chip table,匹配DT compatible string,一旦匹配到,就調用該interrupt controller的初始化函數。具體的代碼分析能夠參考Device Tree代碼分析文檔

 

5、GIC driver初始化代碼分析

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;

    if (WARN_ON(!node)) 
        return -ENODEV;

    dist_base = of_iomap(node, 0);----------------映射GIC Distributor的寄存器地址空間 
    WARN(!dist_base, "unable to map gic dist registers\n");

    cpu_base = of_iomap(node, 1);----------------映射GIC CPU interface的寄存器地址空間 
    WARN(!cpu_base, "unable to map gic cpu registers\n");

    if (of_property_read_u32(node, "cpu-offset", &percpu_offset))--------處理cpu-offset binding。 
        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); 
        gic_cascade_irq(gic_cnt, irq); 
    } 
    gic_cnt++; 
    return 0; 
}

咱們首先看看這個函數的參數,node參數表明須要初始化的那個interrupt controller的device node,parent參數指向其parent。對於cpu-offset binding,能夠參考linux-3.14/Documentation/devicetree/bindings/arm/gic.txt文件中關於cpu-offset 的描述。對於OMAP4460,其GIC支持banked register,所以percpu_offset等於0。

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;

    BUG_ON(gic_nr >= MAX_GIC_NR);

    gic = &gic_data[gic_nr]; 
     WARN(percpu_offset, 
             "GIC_NON_BANKED not enabled, ignoring %08x offset!", 
             percpu_offset); 
        gic->dist_base.common_base = dist_base; 
        gic->cpu_base.common_base = cpu_base; 
        gic_set_base_accessor(gic, gic_get_common_base);

    /* 
     * Initialize the CPU interface map to all CPUs. 
     * It will be refined as each CPU probes its ID. 
     */ 
    for (i = 0; i < NR_GIC_CPU_IF; i++) 
        gic_cpu_map[i] = 0xff;

    /* 
     * For primary GICs, skip over SGIs. 
     * For secondary GICs, skip over PPIs, too. 
     */ 
    if (gic_nr == 0 && (irq_start & 31) > 0) { 
        hwirq_base = 16; 
        if (irq_start != -1) 
            irq_start = (irq_start & ~31) + 16; 
    } else { 
        hwirq_base = 32; 
    }

    /* 
     * Find out how many interrupts are supported. 
     * The GIC only supports up to 1020 interrupt sources. 
     */ 
    gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; 
    gic_irqs = (gic_irqs + 1) * 32; 
    if (gic_irqs > 1020) 
        gic_irqs = 1020; 
    gic->gic_irqs = gic_irqs;

    gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ 
    irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); 
    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, 
                    hwirq_base, &gic_irq_domain_ops, gic); 
    if (WARN_ON(!gic->domain)) 
        return;

    if (gic_nr == 0) { 
#ifdef CONFIG_SMP 
        set_smp_cross_call(gic_raise_softirq);---------設定raise SGI的方法 
        register_cpu_notifier(&gic_cpu_notifier);---------在multi processor環境下,當其餘processor online的時候,須要調用回調函數來初始化GIC的cpu interface。 
#endif 
        set_handle_irq(gic_handle_irq); 
    }

    gic_chip.flags |= gic_arch_extn.flags; 
    gic_dist_init(gic);---------具體的硬件初始代碼,再也不贅述,能夠參考GIC reference manual 
    gic_cpu_init(gic); 
    gic_pm_init(gic); 
}

這個函數的前半部分都是爲了向系統中註冊一個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的數目能夠經過其支持的HW interrupt的數目減去hwirq_base獲得,那麼如何獲取GIC支持的HW interrupt的數目呢?GIC有一個寄存器叫作Interrupt Controller Type Register,經過這個寄存器能夠得到下列信息:

(1)whether the GIC implements the Security Extensions 
(2)the maximum number of interrupt IDs that the GIC supports 
(3)the number of CPU interfaces implemented 
(4)if the GIC implements the Security Extensions, the maximum number of implemented Lockable Shared Peripheral Interrupts (LSPIs).

得到了GIC支持的IRQ數目後,就能夠調用irq_alloc_descs函數分配IRQ描述符資源了。以後,經過調用irq_domain_add_legacy函數註冊GIC的irq domain數據結構。這裏有一個重要的數據結構gic_irq_domain_ops,其類型是struct irq_domain_ops ,定義以下:

struct irq_domain_ops { 
    int (*match)(struct irq_domain *d, struct device_node *node); 
    int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); 
    void (*unmap)(struct irq_domain *d, unsigned int virq); 
    int (*xlate)(struct irq_domain *d, struct device_node *node, 
             const u32 *intspec, unsigned int intsize, 
             unsigned long *out_hwirq, unsigned int *out_type); 
};

對於GIC,其irq domain的操做函數是gic_irq_domain_ops,定義以下:

const struct irq_domain_ops gic_irq_domain_ops = { 
    .map = gic_irq_domain_map, 
    .xlate = gic_irq_domain_xlate, 
};

irq domain的概念是一個通用中斷子系統的概念,在irq chip這個層次,咱們須要和通用中斷子系統中的irq domain core code進行接口,gic_irq_domain_ops就是這個接口的定義。

(1)map成員函數:用來創建irq number和hw interrupt ID之間的聯繫 
(2)Xlate成員函數:給定某個外設的device tree node 和interrupt specifier,該函數能夠解碼出該設備使用的hw interrupt ID和linux irq type value

具體向系統註冊irq domain是經過irq_domain_add_legacy函數進行的:

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, 
                     unsigned int size, 
                     unsigned int first_irq, 
                     irq_hw_number_t first_hwirq, 
                     const struct irq_domain_ops *ops, 
                     void *host_data) 

    struct irq_domain *domain;

    domain = __irq_domain_add(of_node, first_hwirq + size, 
                  first_hwirq + size, 0, ops, host_data); 
    if (!domain) 
        return NULL;

    irq_domain_associate_many(domain, first_irq, first_hwirq, size);

    return domain; 
}

這個函數主要進行兩個步驟,一個是經過__irq_domain_add將GIC對應的irq domain加入到系統,另一個就是調用irq_domain_associate_many來創建IRQ number和hw interrupt ID之間的關係。而這個關係的創建其實是經過irq domain的操做函數中的map回調函數進行的,對於GIC而言就是gic_irq_domain_map,代碼以下:

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

    if (hw < 32) { 
        irq_set_percpu_devid(irq); 
        irq_set_chip_and_handler(irq, &gic_chip, 
                     handle_percpu_devid_irq); 
        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN); 
    } else { 
        irq_set_chip_and_handler(irq, &gic_chip, 
                     handle_fasteoi_irq); 
        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); 
    } 
    irq_set_chip_data(irq, d->host_data); 
    return 0; 
}

這個函數的主要功能就是把一個IRQ號(參數中的unsigned int irq)和一個hw interrupt ID(參數中的irq_hw_number_t hw)聯繫起來。每一個IRQ都有其對應的描述符,用struct irq_desc表示:

struct irq_desc { 
    struct irq_data        irq_data; 

    irq_flow_handler_t    handle_irq; 
…… 
} __

函數irq_set_chip_and_handler其實主要就是設定struct irq_desc中的irq_data和handle_irq成員。handle_irq就是發生了一個IRQ後須要調用的中斷處理函數。irq_data數據結構中的chip成員就是指向具體的硬件信息,也就是GIC的描述符。咱們定義以下:

static struct irq_chip gic_chip = { 
    .name            = "GIC", 
    .irq_mask        = gic_mask_irq, 
    .irq_unmask        = gic_unmask_irq, 
    .irq_eoi        = gic_eoi_irq, 
    .irq_set_type        = gic_set_type, 
    .irq_retrigger        = gic_retrigger, 
#ifdef CONFIG_SMP 
    .irq_set_affinity    = gic_set_affinity, 
#endif 
    .irq_set_wake        = gic_set_wake, 
};

struct irq_chip數據結構中有各類各樣的操做函數,例如enable或者disable一箇中斷,ack一箇中斷等。

綜上所述,通過了初始化過程以後,對於linux的內存管理系統而言,增長了GIC的地址空間映射。對於linux kernel的generic interrupt subsystem,增長了若干個IRQ的描述符,而且設定了這些IRQ描述符的處理函數以及相關的irq_data數據結構(和具體HW相關,該數據結構的成員包括抽象具體硬件的interrupt controller的描述符以及irq domain數據結構)。

6、GIC硬件操做

一、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,各個廠商在初始中斷控制器的時候能夠設定其特定的操做函數。

二、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的操做。

三、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事件通知。

四、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;

    /* Interrupt configuration for SGIs can't be changed */ 
    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;

    /* 
     * As recommended by the spec, disable the interrupt before changing 
     * the configuration 
     */ 
    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。

五、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);

    /* the genirq layer expects 0 if we can't retrigger in hardware */ 
    return 0; 
}

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

六、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 shift = (gic_irq(d) % 4) * 8; 
    unsigned int cpu = cpumask_any_and(mask_val, cpu_online_mask); 
    u32 val, mask, bit;

    if (cpu >= NR_GIC_CPU_IF || cpu >= nr_cpu_ids) 
        return -EINVAL;

    raw_spin_lock(&irq_controller_lock); 
    mask = 0xff << shift; 
    bit = gic_cpu_map[cpu] << shift; 
    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。

七、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和具體的廠商相關,這裏再也不贅述。

 

原創文章,轉發請註明出處。蝸窩科技,www.wowotech.net

相關文章
相關標籤/搜索