天天看點

中斷控制器介紹及初始化源代碼分析(二)

1.GIC中斷控制器介紹

ARM處理器中最常用的中斷控制器是GIC(Global Interrupt Controller)。GIC支援3中中斷類型。

(1)SGI(Software Generated Interrupt):軟體産生的中斷,通常用于多核之間的通信。一個CPU可通過寫GIC的寄存器給另外一個CPU産生中斷(IPI_WAKEUP、IPI_TIMER等)。SGI中斷通常在Linux核心裡被稱為IPI中斷(interprocess interrupts)。

(2)PPI(Private Peripheral Interrupt):某個CPU私有外設的中斷,這類外設的中斷隻能發給綁定的CPU,如CPU本地時鐘中斷。

(3)SPI(Shared Peripheral interrupt):共享外設中斷,這類外設的中斷可以發送給任何一個CPU。

GIC中斷控制器主要由三部分組成,分别為distributor子產品、CPU Interface子產品和Virtual CPU interfaces子產品。distributor子產品為每個中斷源維護一個狀态機,支援的狀态有inactive、pending、active和active and pending狀态,distributor子產品會記錄和比較出目前優先級最高的處于pending狀态的中斷,然後将高優先級的中斷發送給CPU Interface子產品。每個CPU對應一個CPU Interface子產品,CPU Interface子產品決定這個中斷是否可以發送給對應的CPU。這裡不讨論Virtual CPU interfaces子產品。

PPI和SPI的觸發方式有邊沿觸發(Edge-triggered)和水準觸發(Level-sensitive)方式。

中斷控制器介紹及初始化源代碼分析(二)

GIC進行中斷的流程如下:

(1)當GIC檢測到一個中斷發生時,會将該中斷标記為pending狀态。

(2)對處于pending狀态的中斷,仲裁單元會将中斷請求發送到目标CPU。

(3)對于每個CPU,仲裁單元從衆多pending狀态的中斷中選擇一個優先級最高的中斷,發送到CPU Interface子產品上。

(4)CPU Interface子產品決定這個中斷是否可以發送給CPU。如果這個中斷的優先級滿足要求,GIC會給CPU發送一個中斷請求。

(5)當CPU收到中斷請求後,會進入異常處理模式,讀取GICC_IAR寄存器擷取硬體中斷号來響應來響應中斷(通常是Linux核心中斷處理程式讀取寄存器)。在硬體中斷處理完成之前(寫GICC_EOIR寄存器表示硬體中斷處理完成)GIC不會再向此cpu發送任何中斷。對于SPI和PPI中斷,傳回的是硬體中斷号,對于SGI中斷,傳回的是發出中斷CPU的ID和硬體中斷号。當GIC感覺到軟體讀取了該寄存器後,又分為如下情況(詳細的中斷狀态變化可參考《ARM® Generic Interrupt Controller Architecture version 2.0》第39頁):

 (a.)如果該中斷源是pending狀态且是邊緣觸發,那麼狀态将變成active,如果該中斷源是pending狀态且是電平觸發,那麼狀态将變成active and pending。對于狀态為active and pending的中斷,gic不會再将此中斷發給任何cpu,直到狀态變為inactive後,gic才會重新發送中斷。

 (b.)如果該中斷是active狀态,将變成active and pending狀态。

(6)當處理器完成中斷服務後,軟體寫GICC_EOIR寄存器,表示中斷處理完成。

下圖是gic-400中斷控制器的時序圖,圖中的中斷N和M都是SPI類型的FIQ中斷,高電平觸發,N的優先級比M高,目标CPU相同。

(1)T1時刻:GIC distributor子產品檢測到中斷M的電平變化。

(2)T2時刻:distributor子產品設定中斷M的狀态為pending。

(3)T17時刻:CPU Interface子產品拉低nFIQCPU[n]通知CPU發生了中斷。

(4)T42時刻:GIC distributor子產品檢測到了另外一個優先級更高的中斷N。

(5)T43時刻:GIC distributor子產品用中斷N替換了目前pending狀态下優先級最高的中斷,并設定中斷N為pending狀态。

(6)T58時刻:CPU Interface子產品将中斷N的硬體中斷号更新到GICC_IAR寄存器Interrupt ID區域。

(7)T61時刻:CPU讀取GICC_IAR寄存器擷取了硬體中斷号同時響應了中斷N,distributor子產品将中斷N的狀态由pending狀态變為active and pending狀态(電平觸發的中斷cpu響應後中斷狀态将會從pending直接變為active and pending)。

(8)T61-T131時刻:中斷處理程式進行中斷N。

 (a)CPU響應中斷N 3個時鐘後,CPU Interface子產品拉高nFIQCPU[n]。

 (b)T126時刻cpu清除了外設的中斷N,則外設的中斷N引腳電平被拉低。

 (c)T128時刻distributor子產品将中斷的N狀态由active and pending更新為active狀态。

 (d)T131時刻cpu将中斷N的硬體中斷号寫入GICC_EOIR寄存器,表示中斷N處理完畢。distributor子產品将中斷的N狀态由active更新為inactive狀态。

(9)T146時刻:中斷N完成後,經過tph時鐘,distributor子產品選擇下一個最高優先級的中斷,即将中斷M發送給CPU Interface子產品,CPU Interface子產品拉低nFIQCPU[n]通知CPU發生了中斷。

(10)T211時刻:CPU讀取GICC_IAR寄存器擷取了硬體中斷号同時響應了中斷M,distributor子產品将中斷M的狀态由pending狀态變為active and pending狀态。

(11)T214時刻:CPU響應中斷M 3個時鐘後,CPU Interface子產品拉高nFIQCPU[n]。

中斷控制器介紹及初始化源代碼分析(二)

2.zynq7k中斷控制器

2.1.簡介

zynq7020有兩個Cortex-A9核心,采用GIC pl390(屬于GIC V1版本)中斷控制器。zynq7020中斷控制器有16個SGI中斷,硬體中斷号範圍0-15,5個PPI中斷,硬體中斷号範圍16-31,其中16-26保留,沒有使用,60個SPI中斷,硬體中斷号範圍32-95,93-95保留,沒有使用。SPI中斷可通過讀取spi_status_0和spi_status_1寄存器,擷取發生中斷的硬體中斷号。

中斷控制器介紹及初始化源代碼分析(二)

2.2.裝置樹節點

zynq7k裝置樹中的中斷控制器描述如下。相容屬性

compatible

arm,cortex-a9-gic

,用來與驅動程式比對,比對成功後,驅動會被正确加載。

#interrupt-cells = <3>

表示要用3個32位整數描述中斷,第一個整數說明中斷類型,0表示SPI中斷,1表示PPI中斷;第二個整數說明中斷号;第三個整數說明中斷觸發類型。

interrupt-controller

屬性說明此節點是一個中斷控制器節點,

reg

表示中斷控制器寄存器基位址及範圍,

0xF8F01000

distributor

子產品寄存器基位址,長度為

0x1000

0xF8F00100

CPU Interface

子產品寄存器的基位址,長度為

0x100

。intc節點無

interrupt-parent

屬性,說明此中斷控制器為根(root)中斷控制器,直接連接配接cpu。核心啟動的時候會解析裝置樹節點,将裝置樹節點轉換為

device_node

結構體,然後将這些

device_node

以樹的形式組織起來,若某個驅動的屬性和裝置樹節點中的屬性一緻,則驅動會被初始化并被加載到系統中。

intc: [email protected] {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0xF8F01000 0x1000>,  // ICDXX
          <0xF8F00100 0x100>;   // ICCXX
};
           

gpio0也是一個中斷控制器,具有

interrupt-controller

屬性,其父中斷控制器為

intc

,即上面提到的zynq7k的根中斷控制器。

gpio0: [email protected] {
    compatible = "xlnx,zynq-gpio-1.0";
    #gpio-cells = <2>;
    clocks = <&clkc 42>;
    gpio-controller;
    interrupt-controller;
    #interrupt-cells = <2>;
    interrupt-parent = <&intc>;  // 說明父中斷控制器為intc
    interrupts = <0 20 4>;  // SPI中斷,硬體中斷号為20
    reg = <0xe000a000 0x1000>;
};
           

3.中斷控制器的初始化過程

Linux核心使用

struct irq_desc

描述中斷,每個中斷都對應一個

irq_desc

結構體。

irq_desc

有兩種組織形式,如果定義了

CONFIG_SPARSE_IRQ

,則采用

radix-tree

組織

irq_desc

,初始化時需要動态配置設定

irq_desc

,并将其插入到

irq_desc_tree

中,此種組織形式中斷号不連續;反之,使用靜态定義的數組

irq_desc

組織

irq_desc

,隻需要初始化數組元素即可,不需要動态配置設定

irq_desc

,此種組織形式中斷号連續。

[kernel/irq/irqdesc.c]  // 靜态定義的數組,存放所有irq_desc,元素數量為NR_IRQS
    struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = 
    {
        [0 ... NR_IRQS-1] = {
            .handle_irq	= handle_bad_irq,
            .depth		= 1,
            .lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
        }
    };
    [inlcude/asm-generic/irq.h]
    #define NR_IRQS 64
    [kernel/irq/irqdesc.c]  // 定義的`radix-tree`根節點
    static RADIX_TREE(irq_desc_tree, GFP_KERNEL)
           

Linux核心啟動期間,會初始化中斷控制器。根據是否定義

CONFIG_SPARSE_IRQ

,初始化流程有所差別。

首先分析定義了

CONFIG_SPARSE_IRQ

的初始化流程。調用

early_irq_init

擷取中斷數量,為每一個中斷動态配置設定一個

irq_desc

結構體,同時設定位圖,将對應的bit設定為1,最後将配置設定的

irq_desc

插入到

irq_desc_tree

中,可以看出所有的中斷都是通過

irq_desc_tree

進行管理。接着調用

init_IRQ

,将裝置樹中的節點與

__irqchip_of_table

段中儲存的中斷控制器資訊進行比對,隻有相容屬性一緻且裝置樹節點含有

interrupt-controller

屬性才能比對成功,将比對成功的中斷控制器初始化函數儲存到

irq_init_cb

中,比對完成後調用初始化函數,需要注意的是隻有父控制器的初始化函數會被調用。對于zynq7k,父中斷控制器始化函數是

gic_of_init

start_kernel  // 核心入口函數
      ->local_irq_disable()  // 如果沒有禁止中斷,則會禁止中斷
      ->early_irq_init
        ->init_irq_default_affinity  // 設定中斷對每個cpu的親和度
        ->arch_probe_nr_irqs  // 擷取中斷數量
            // 預設從machine_desc結構體中的nr_irqs中擷取,若為0則使用預設值NR_IRQS
            nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS
        ->alloc_desc  // 為每個中斷配置設定一個irq_desc結構體并初始化
          ->kzalloc_node  // 配置設定irq_desc
          ->esc->kstat_irqs = alloc_percpu(unsigned int)  // 配置設定每cpu變量 
          ->desc_set_defaults  // 使用預設值初始化irq_desc
        ->set_bit(i, allocated_irqs);  // 設定位圖,每一位表示一個irq_desc,将對應的bit設定為1
        ->irq_insert_desc  // 将配置設定的irq_desc插入到irq_desc_tree中
      ->init_IRQ
        ->irqchip_init
          // __irqchip_of_table在連結腳本中定義,存放中斷控制器資訊,可與裝置樹中的中斷控制器節點比對
          ->of_irq_init(__irqchip_of_table)
            // 周遊所有裝置節點,并與__irqchip_of_table中定義的中斷控制器資訊進行比對,并判斷是否具有
            // interrupt-controller屬性
            ->for_each_matching_node_and_match  
              kzalloc  // 滿足要求,則配置設定一個of_intc_desc結構體,以連結清單的形式組織起來
              // 設定irq_init_cb指向比對成功的中斷控制器的初始化函數
              desc->irq_init_cb = match->data
            // 比對完成開始周遊比對成功的of_intc_desc結構體連結清單
            ->while (!list_empty(&intc_desc_list))
              // 調用父中斷控制器的初始化函數
              desc->irq_init_cb(desc->dev, desc->interrupt_parent)
          ->acpi_probe_device_table  // 中斷控制器電源相關,忽略
      ...
      ->local_irq_enable()  // 開啟本地中斷
           

接着分析沒有定義

CONFIG_SPARSE_IRQ

的初始化流程。可以看出,是否定義

CONFIG_SPARSE_IRQ

,隻在

early_irq_init

函數中不同。沒有定義

CONFIG_SPARSE_IRQ

,核心會初始化靜态定義的

irq_desc

數組,不會動态配置設定

irq_desc

結構體。

start_kernel  // 核心入口函數
      ->local_irq_disable()  // 如果沒有禁止中斷,則會禁止中斷
      ->early_irq_init
        ->init_irq_default_affinity  // 設定中斷對每個cpu的親和度
          desc = irq_desc  // 擷取儲存irq_desc數組的首位址
          count = ARRAY_SIZE(irq_desc)  // 擷取數組元素數量,即中斷數量
        ->desc_set_defaults  // 初始化數組元素
      ->init_IRQ
        ->irqchip_init  // irqchip_init的初始化流程與定義CONFIG_SPARSE_IRQ一緻
        ...
      ...
      ->local_irq_enable()  // 開啟本地中斷
           

中斷的初始化函數最終調用到了父中斷控制器的初始化函數。zynq7k的父中斷控制器為

intc

,驅動程式位于irq-gic.c檔案中。

IRQCHIP_DECLARE

定義了與裝置樹比對的比對表。

IRQCHIP_DECLARE

定義的内容編譯器會将其放在

__irqchip_of_table

段中。gic驅動程式定義的

"arm,cortex-a9-gic"

屬性和

intc

裝置樹節點定義的相容屬性一緻,兩者可以比對,比對成功後調用的

irq_init_cb

函數就是

gic_of_init

函數。

[drivers/irqchip/irq-gic.c]
    // 定義中斷控制器屬性及入口函數,屬性和裝置樹intc節點的屬性一緻
    IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
    [include/linux/irqchip.h]
    #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
    #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  }
           

下面詳細分析一下

gic_of_init

函數的調用流程。

init_IRQ

傳入兩個

device_node

參數,第一個為中斷控制器的

device_node

,第二個為中斷控制器父節點的

device_node

,由于

intc

為根中斷控制器,無父節點,是以第二個參數為

NULL

。Linux裝置驅動程式的初始化都圍繞着裝置結構體進行,gic中斷控制器定義了

struct gic_chip_data

裝置結構體。

gic_of_init

需要初始化裝置結構體中兩個比較重要的成員,分别為

chip

domain

chip

指向了

gic_chip

domain

ops

指向

gic_irq_domain_hierarchy_ops

,配置設定的

domain

挂到了全局連結清單

irq_domain_list

。最後設定底層中斷處理函數指針

handle_arch_irq

指向

gic_handle_irq

[drivers/irqchip/irq-gic.c]
    struct gic_chip_data {    // gic驅動定義的裝置結構體,gic初始化函數都圍繞此結構體展開
        struct irq_chip chip;  // gic中斷控制器操作函數集合
        union gic_base dist_base;  // distributor子產品映射的虛拟基位址
        union gic_base cpu_base;   // CPU Interface子產品映射的虛拟基位址
        ......
        struct irq_domain *domain;  // 硬體中斷号映射為軟體中斷号
        unsigned int gic_irqs;  // 此中斷控制器支援的中斷數量
        ......
    };

    // 靜态定義的gic裝置結構體數組,有多少個中斷控制器,就會初始化多少個元素
    static struct gic_chip_data gic_data[CONFIG_ARM_GIC_MAX_NR] __read_mostly;

    [drivers/irqchip/irq-gic.c]    
    gic_of_init
      ->of_iomap  // 映射distributor子產品的寄存器
      ->of_iomap  // 映射CPU Interface子產品寄存器位址
      ->gic_check_eoimode  // 中斷分步相關,gic v2可以将中斷分為兩步,gic v1隻能是一步
      ->of_property_read_u32  // 擷取cpu-offset屬性,zynq7k裝置樹無此屬性
      ->__gic_init_bases  // gic的初始化函數,重要
          gic = &gic_data[gic_nr]  // 擷取gic裝置結構體數組的位址,gic_nr為中斷控制器序号
          gic->chip = gic_chip  // 設定chip
          gic->chip.irq_set_affinity = gic_set_affinity  // 如是SMP系統,還需要設定cpu親和度函數
          // 擷取中斷控制器ICDICTR寄存器的低五位,通過低五位可計算出支援的中斷數量
          // zynq7k ICDICTR寄存器的低五位預設值為0x2,有64條外部中斷線,總共64個外部中斷
          gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f
          // gic_irqs = (0x2 + 1) * 32 = 96
          gic_irqs = (gic_irqs + 1) * 32
          gic->gic_irqs = gic_irqs  // 設定支援的中斷數量為96
          ->irq_domain_create_linear  // 初始化irq_domain
            ->__irq_domain_add  // 配置設定irq_domain
                ->kzalloc_node  // 配置設定irq_domain結構體記憶體
                INIT_RADIX_TREE  // 初始化revmap_tree
                domain->ops = ops  // 設定ops,ops為gic_irq_domain_hierarchy_ops
                domain->host_data = host_data  // 設定私有資料,指向中斷控制器裝置結構體
                domain->hwirq_max = hwirq_max  // 設定支援的最大硬體中斷号,為96
                domain->revmap_size = size  // 設定線性映射中斷号的數量,size為96
                domain->revmap_direct_max_irq = direct_max  // 設定為0
                ->irq_domain_check_hierarchy  // 檢查中斷控制器是否級聯
                  // 如果ops含有alloc函數,則設定支援級聯的标志
                  if (domain->ops->alloc)
                      domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;  // 支援級聯
                ->list_add  // 将irq_domain添加到全局irq_domain_list連結清單中
          // 在根中斷控制器中,設定SMP系統的回調函數,調用gic_raise_softirq可觸發IPI(SGI)中斷,
          // 用于多核之間的通信
          ->set_smp_cross_call(gic_raise_softirq)  
          // 注冊通知函數,當cpu狀态變化時,需要調用gic_cpu_notifier通知gic
          ->register_cpu_notifier(&gic_cpu_notifier)
           // 設定中斷處理函數,底層發生中斷,最終要調用gic_handle_irq進行中斷
           // 這就是底層handle_arch_irq指向的函數
          ->set_handle_irq(gic_handle_irq)
          ->gic_dist_init  // 初始化distributor子產品
          ->gic_cpu_init  // 初始化CPU Interface子產品
          ->gic_pm_init  // gic電源管理初始化
           

4.中斷号的處理過程

gic_of_init

函數中設定了

irq_domain

ops

成員,指向了

gic_irq_domain_hierarchy_ops

結構體。此結構體主要用于映射中斷号。

[drivers/irqchip/irq-gic.c]
    static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
        .translate = gic_irq_domain_translate,  // 将中斷引腳号轉換為硬體中斷号
        .alloc = gic_irq_domain_alloc,  // 完成硬體中斷号向軟體中斷号的映射
        .free = irq_domain_free_irqs_top,  // 解除硬體中斷号和軟體中斷号的映射關系
    };
           

4.1.中斷引腳号到硬體中斷号的轉換過程分析

首先分析一下

gic_irq_domain_translate

函數是如何将中斷引腳号轉換成硬體中斷号的。由于裝置樹中提供的是中斷引腳号,沒有考慮SGI和PPI中斷,是以需要将中斷引腳号加上SGI和PPI預留的硬體中斷号範圍,才能得到實際的硬體中斷号。

gic_irq_domain_translate
        if (fwspec->param_count < 3)  // 如果interrupts屬性值數量小于3個,傳回-EINVAL
            return -EINVAL;
        *hwirq = fwspec->param[1] + 16  // 由于0-15中斷号預留給SGI中斷,是以需要加16
        if (!fwspec->param[0])  // 對于SPI中斷,需要再加16以跳過給PPI預留的中斷号
            *hwirq += 16;  // 則序列槽最終的硬體中斷号為27+16+16=59,和手冊中一緻
        *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK  // 擷取中斷類型
           

4.2.硬體中斷号到軟體中斷号的映射過程分析

接着分析一下

gic_irq_domain_alloc

函數是如何将硬體中斷号映射為軟體中斷号的。每個軟體中斷号對應一個

irq_desc

irq_desc

中嵌入了一個和中斷控制器有關的結構體

irq_data

,将硬體中斷号填入

irq_data

hwirq

變量中就完成了中斷号的映射工作。得到軟體中斷号的過程将在下一章分析。

// 傳入的參數為中斷控制器的指針domain、軟體中斷号、映射的中斷号數量及參數
    gic_irq_domain_alloc
        // 将裝置樹中的中斷引腳号轉換為硬體中斷号,擷取中斷觸發類型
        ->gic_irq_domain_translate  
        ->gic_irq_domain_map  // 進行中斷映射
            gic_chip_data *gic = d->host_data  // 擷取gic裝置結構體
            ->irq_domain_set_info
              ->irq_domain_set_hwirq_and_chip
                ->irq_domain_get_irq_data  // 擷取軟體中斷号對應的irq_data
                irq_data->hwirq = hwirq  // 設定硬體中斷号
                irq_data->chip = chip  // 設定chip,chip為gic裝置結構體中的chip成員,即gic_chip結構體
                irq_data->chip_data = chip_data  // 設定gic裝置結構體
            ->__irq_set_handler
              ->__irq_do_set_handler
              desc->handle_irq = handle // 設定中斷處理函數為handle_fasteoi_irq
            ->irq_set_handler_data
              desc->irq_common_data.handler_data = data
        ->irq_set_probe  // 設定一些标志

    // gic chip結構體,即irq_data中chip指向的結構體,包含了gic控制器操作函數集合
    static struct irq_chip gic_chip = {
        .irq_mask		= gic_mask_irq,  // 屏蔽中斷
        .irq_unmask		= gic_unmask_irq,  // 解除屏蔽
        .irq_eoi		= gic_eoi_irq,  // 中斷結束時使用此接口通知gic中斷控制器
        .irq_set_type		= gic_set_type,  // 設定中斷觸發類型
        .irq_get_irqchip_state	= gic_irq_get_irqchip_state,  // 擷取中斷控制器狀态
        .irq_set_irqchip_state	= gic_irq_set_irqchip_state,  // 設定中斷控制器狀态
        // 中斷控制器标志
        .flags			= IRQCHIP_SET_TYPE_MASKED |
                        IRQCHIP_SKIP_SET_WAKE |
                        IRQCHIP_MASK_ON_SUSPEND,
    };
           

4.資料結構介紹

4.1.中斷描述符irq_desc

每一個外設的中斷都對應一個中斷描述符,Linux核心使用

irq_desc

結構體描述中斷。成員

irq_data

和具體硬體相關,内部包含了

irq_chip

,是中斷控制器操作函數的集合;成員

handle_irq

在中斷發生時首先調用,然後根據不同的中斷類型執行不同的中斷處理函數,同一個中斷控制器,其

handle_irq

是一樣的;

action

是中斷處理函數,裝置驅動使用

request_irq

request_threaded_irq

注冊的中斷處理函數就儲存在此處,如果是共享中斷,則

action

是一個連結清單,儲存了多個中斷處理函數。

[include/linux/irq.h]
    struct irq_desc   // 中斷描述符
    {
        struct irq_common_data	irq_common_data;
        struct irq_data		irq_data;  // 包含了irq_chip,和中斷控制器相關
        unsigned int __percpu	*kstat_irqs;  // 中斷的統計資訊
        irq_flow_handler_t	handle_irq;  // 中斷産生後,首先執行此函數指針指向的函數,然後再執行action
        ......
        struct irqaction	*action;	// 具體裝置的中斷處理函數,是一個連結清單,共享中斷時有多個處理函數
        unsigned int		status_use_accessors;  // 中斷描述符狀态
        unsigned int		core_internal_state__do_not_mess_with_it;
        unsigned int		depth;		/* nested irq disables */
        unsigned int		wake_depth;	/* nested wake enables */
        unsigned int		irq_count;	/* For detecting broken IRQs */
        unsigned long		last_unhandled;	/* Aging timer for unhandled count */
        unsigned int		irqs_unhandled;
        atomic_t		threads_handled;
        int			threads_handled_last;
        raw_spinlock_t		lock;
        ......
        int			parent_irq;
        const char		*name;    // 産生中斷的裝置名稱
    } ____cacheline_internodealigned_in_smp
           

struct irq_desc

在Linux核心中有兩種組織方式,第一種為數組形式,應用于線性映射,另一種是基數樹,應用于非線性映射。

中斷控制器介紹及初始化源代碼分析(二)

4.2.硬體相關的irq_data和irq_chip

irq_data

将硬體相關的内容進行了封裝,主要成員有Linux軟體(虛拟)中斷号

irq

、硬體中斷号

hwirq

、中斷控制器操作函數集合

chip

及中斷号映射結構體

domain

[include/linux/irq.h]
    struct irq_data     // 中斷控制器對應的資料
    {
        u32			mask;
        unsigned int		irq;  // 中斷号
        unsigned long		hwirq;  // 硬體中斷号
        struct irq_common_data	*common;  // 所有中斷控制器共享的資料
        struct irq_chip		*chip;  // 包含了底層中斷控制器的操作函數
        struct irq_domain	*domain;  // 中斷号映射結構體,可将硬體中斷号映射成linux的軟體中斷号
    #ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_data		*parent_data;  // 指向父中斷控制器的irq_data
    #endif
        void			*chip_data;  //irq_chip的私有資料 
    };
           

irq_chip

是中斷控制器操作函數的集合,主要成員有啟動中斷控制器

irq_startup

、關閉中斷控制器

irq_shutdown

、使能中斷

irq_enable

、禁止中斷

irq_disable

、響應中斷

irq_ack

、屏蔽中斷

irq_mask

、響應并屏蔽某個中斷

irq_mask_ack

等。cpu調用這些函數控制中斷控制器。

[include/linux/irq.h]
    struct irq_chip {  // 中斷控制器操作函數集合
        const char	*name;  // 中斷控制器的名稱,可在/proc/interrupts中檢視
        unsigned int	(*irq_startup)(struct irq_data *data);  // 啟動中斷,如為NULL則預設啟動
        void		(*irq_shutdown)(struct irq_data *data);  // 關閉中斷,如為NULL則預設關閉
        void		(*irq_enable)(struct irq_data *data);  // 使能中斷
        void		(*irq_disable)(struct irq_data *data);  // 禁止中斷
         // 響應中斷,有的中斷控制器在中斷響應後(清除pending的中斷)才可以重新觸發中斷
        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);  // 中斷結束後調用
        // 在smp系統中,設定中斷對某個cpu的親和度
        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);
        // 使用慢速總線時,需要lock總線
        void		(*irq_bus_lock)(struct irq_data *data);
        // unlock慢速總線
        void		(*irq_bus_sync_unlock)(struct irq_data *data);
        void		(*irq_cpu_online)(struct irq_data *data);
        void		(*irq_cpu_offline)(struct irq_data *data);
        ......
        unsigned long	flags;
    };
           

4.3.中斷号映射相關的irq_domain和irq_domain_ops

中斷發生時,隻能從中斷控制器得到硬體中斷号,但Linux核心使用軟體(虛拟)中斷号描述中斷,是以需要将兩者聯系起來。

irq_domain

是硬體中斷号和軟體中斷号的橋梁,可以将硬體中斷号映射為軟體中斷号。每個中斷控制器都對應一個

irq_domain

irq_domain

的主要成員有操作函數集合

ops

、最大硬體中斷号

hwirq_max

、線性映射的中斷号數量

revmap_size

、radix tree映射的根節點

revmap_tree

及線性映射的查找表

linear_revmap

.

[include/linux/irqdomain.h]
    struct irq_domain  // 将硬體中斷号映射為Linux軟體中斷号,每個中斷控制器都對應一個irq_domain
    {
        struct list_head link;  // 挂接到全局的irq_domain_list連結清單
        const char *name;  // interrupt domain的名稱
        const struct irq_domain_ops *ops;  //  irq_domain的操作函數,包含映射和解除映射函數
        void *host_data;  // irq_domain私有資料
        unsigned int flags;

        struct fwnode_handle *fwnode;  // 指向和irq_domain相關聯的裝置樹節點
        enum irq_domain_bus_token bus_token;
        struct irq_domain_chip_generic *gc;
    #ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_domain *parent;  // 配置級聯,則指向父中斷控制器的irq_domain
    #endif
        irq_hw_number_t hwirq_max;  // irq_domain中最大的硬體中斷号
        unsigned int revmap_direct_max_irq; // 線性映射時可寫入中斷控制器的最大硬體中斷号
        unsigned int revmap_size;  // 線性映射中斷号的數量
        struct radix_tree_root revmap_tree;  // radix tree映射的根節點
        unsigned int linear_revmap[];  // 線性映射用到的查找表
    };

    [include/linux/irqdomain.h]
    struct irq_domain_ops   // irq_domain映射中斷号時用到的操作函數
    {
        // 中斷控制器裝置樹節點和irq_domain比對
        int (*match)(struct irq_domain *d, struct device_node *node,
                enum irq_domain_bus_token bus_token);
        // 映射中斷号
        int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
        // 取消映射
        void (*unmap)(struct irq_domain *d, unsigned int virq);
        // 将裝置樹提供的中斷号轉換為真實的硬體中斷号,如zynq7k gpio裝置樹節點中的中斷号為20,
        // 由于0-31的硬體中斷号被PPI和SGI中斷占用,gpio真實的硬體中斷号為52,需要加上一個偏移量
        int (*xlate)(struct irq_domain *d, struct device_node *node,
                const u32 *intspec, unsigned int intsize,
                unsigned long *out_hwirq, unsigned int *out_type);
        ......
    };
    
    [kernel/irq/irqdesc.c]
    // 定義了一個位圖,如果定義CONFIG_SPARSE_IRQ則大小為(NR_IRQS + 8196)bit,否則為NR_IRQS bit
    static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS);
           

5.總結

Linux中斷子系統關鍵資料結構及其之間的聯系可總結如下:

中斷控制器介紹及初始化源代碼分析(二)

參考資料

  1. Linux kernel V4.6版本源碼
  2. https://www.cnblogs.com/LoyenWang/p/12996812.html
  3. 《奔跑吧 Linux核心:基于Linux 4.x核心源代碼問題分析》
  4. 《Zynq-7000 SoC Technical Reference Manual》
  5. 《ARM® Generic Interrupt Controller Architecture version 2.0》
  6. 《CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual》