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)方式。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL90EVPFzY610dFRVT3V1MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5ETO3MDOxcTM0EzMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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中斷子系統關鍵資料結構及其之間的聯系可總結如下:
參考資料
- Linux kernel V4.6版本源碼
- https://www.cnblogs.com/LoyenWang/p/12996812.html
- 《奔跑吧 Linux核心:基于Linux 4.x核心源代碼問題分析》
- 《Zynq-7000 SoC Technical Reference Manual》
- 《ARM® Generic Interrupt Controller Architecture version 2.0》
- 《CoreLink GIC-400 Generic Interrupt Controller Technical Reference Manual》