深入分析Linux中斷子系統(tǒng)之中斷控制器及驅(qū)動
說明:
Kernel版本:4.14
ARM64處理器,Contex-A53,雙核
使用工具:Source Insight 3.5, Visio
1. 概述
從這篇文章開始,來聊一聊中斷子系統(tǒng)。中斷是處理器用于異步處理外圍設(shè)備請求的一種機制,可以說中斷處理是操作系統(tǒng)管理外圍設(shè)備的基石,此外系統(tǒng)調(diào)度、核間交互等都離不開中斷,它的重要性不言而喻。
來一張概要的分層圖:

硬件層:最下層為硬件連接層,對應(yīng)的是具體的外設(shè)與SoC的物理連接,中斷信號是從外設(shè)到中斷控制器,由中斷控制器統(tǒng)一管理,再路由到處理器上;
硬件相關(guān)層:這個層包括兩部分代碼,一部分是架構(gòu)相關(guān)的,比如ARM64處理器處理中斷相關(guān),另一部分是中斷控制器的驅(qū)動代碼;
通用層:這部分也可以認為是框架層,是硬件無關(guān)層,這部分代碼在所有硬件平臺上是通用的;
用戶層:這部分也就是中斷的使用者了,主要是各類設(shè)備驅(qū)動,通過中斷相關(guān)接口來進行申請和注冊,最終在外設(shè)觸發(fā)中斷時,進行相應(yīng)的回調(diào)處理;
中斷子系統(tǒng)系列文章,會包括硬件相關(guān)、中斷框架層、上半部與下半部、Softirq、Workqueue等機制的介紹,本文會先介紹硬件相關(guān)的原理及驅(qū)動,前戲結(jié)束,直奔主題。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


2. GIC硬件原理
ARM公司提供了一個通用的中斷控制器
GIC(Generic Interrupt Controller)
,GIC
的版本包括V1 ~ V4
,由于本人使用的SoC中的中斷控制器是V2
版本,本文將圍繞GIC-V2
來展開介紹;
來一張功能版的框圖:

GIC-V2
從功能上說,除了常用的中斷使能、中斷屏蔽、優(yōu)先級管理等功能外,還支持安全擴展、虛擬化等;GIC-V2
從組成上說,主要分為Distributor
和CPU Interface
兩個模塊,Distributor
主要負責中斷源的管理,包括優(yōu)先級的處理,屏蔽、搶占等,并將最高優(yōu)先級的中斷分發(fā)給CPU Interface
,CPU Interface
主要用于連接處理器,與處理器進行交互;Virtual Distributor
和Virtual CPU Interface
都與虛擬化相關(guān),本文不深入分析;
再來一張細節(jié)圖看看Distributor
和CPU Interface
的功能:

GIC-V2
支持三種類型的中斷:SGI(software-generated interrupts)
:軟件產(chǎn)生的中斷,主要用于核間交互,內(nèi)核中的IPI:inter-processor interrupts
就是基于SGI
,中斷號ID0 - ID15
用于SGI
;PPI(Private Peripheral Interrupt)
:私有外設(shè)中斷,每個CPU都有自己的私有中斷,典型的應(yīng)用有local timer
,中斷號ID16 - ID31
用于PPI
;SPI(Shared Peripheral Interrupt)
:共享外設(shè)中斷,中斷產(chǎn)生后,可以分發(fā)到某一個CPU上,中斷號ID32 - ID1019
用于SPI
,ID1020 - ID1023
保留用于特殊用途;Distributor
功能:全局開關(guān)控制
Distributor
分發(fā)到CPU Interface
;打開或關(guān)閉每個中斷;
設(shè)置每個中斷的優(yōu)先級;
設(shè)置每個中斷將路由的CPU列表;
設(shè)置每個外設(shè)中斷的觸發(fā)方式:電平觸發(fā)、邊緣觸發(fā);
設(shè)置每個中斷的Group:Group0或Group1,其中Group0用于安全中斷,支持FIQ和IRQ,Group1用于非安全中斷,只支持IRQ;
將
SGI
中斷分發(fā)到目標CPU上;每個中斷的狀態(tài)可見;
提供軟件機制來設(shè)置和清除外設(shè)中斷的pending狀態(tài);
CPU Interface
功能:使能中斷請求信號到CPU上;
中斷的確認;
標識中斷處理的完成;
為處理器設(shè)置中斷優(yōu)先級掩碼;
設(shè)置處理器的中斷搶占策略;
確定處理器的最高優(yōu)先級pending中斷;
中斷處理的狀態(tài)機如下圖:

Inactive
:無中斷狀態(tài);Pending
:硬件或軟件觸發(fā)了中斷,但尚未傳遞到目標CPU,在電平觸發(fā)模式下,產(chǎn)生中斷的同時保持pending
狀態(tài);Active
:發(fā)生了中斷并將其傳遞給目標CPU,并且目標CPU可以處理該中斷;Active and pending
:發(fā)生了中斷并將其傳遞給目標CPU,同時發(fā)生了相同的中斷并且該中斷正在等待處理;
GIC檢測中斷流程如下:
GIC捕獲中斷信號,中斷信號assert,標記為pending狀態(tài);
Distributor
確定好目標CPU后,將中斷信號發(fā)送到目標CPU上,同時,對于每個CPU,Distributor
會從pending信號中選擇最高優(yōu)先級中斷發(fā)送至CPU Interface
;CPU Interface
來決定是否將中斷信號發(fā)送至目標CPU;CPU完成中斷處理后,發(fā)送一個完成信號
EOI(End of Interrupt)
給GIC;
3. GIC驅(qū)動分析
3.1 設(shè)備信息添加
ARM平臺的設(shè)備信息,都是通過Device Tree
設(shè)備樹來添加,設(shè)備樹信息放置在arch/arm64/boot/dts/
下
下圖就是一個中斷控制器的設(shè)備樹信息:

compatible
字段:用于與具體的驅(qū)動來進行匹配,比如圖片中arm, gic-400
,可以根據(jù)這個名字去匹配對應(yīng)的驅(qū)動程序;interrupt-cells
字段:用于指定編碼一個中斷源所需要的單元個數(shù),這個值為3。比如在外設(shè)在設(shè)備樹中添加中斷信號時,通常能看到類似interrupts = <0 23 4>;
的信息,第一個單元0,表示的是中斷類型(1:PPI,0:SPI
),第二個單元23表示的是中斷號,第三個單元4表示的是中斷觸發(fā)的類型;reg
字段:描述中斷控制器的地址信息以及地址范圍,比如圖片中分別制定了GIC Distributor(GICD)
和GIC CPU Interface(GICC)
的地址信息;interrupt-controller
字段:表示該設(shè)備是一個中斷控制器,外設(shè)可以連接在該中斷控制器上;關(guān)于設(shè)備數(shù)的各個字段含義,詳細可以參考
Documentation/devicetree/bindings
下的對應(yīng)信息;
設(shè)備樹的信息,是怎么添加到系統(tǒng)中的呢?Device Tree
最終會編譯成dtb
文件,并通過Uboot傳遞給內(nèi)核,在內(nèi)核啟動后會將dtb
文件解析成device_node
結(jié)構(gòu)。關(guān)于設(shè)備樹的相關(guān)知識,本文先不展開,后續(xù)再找機會補充。來一張圖,先簡要介紹下關(guān)鍵路徑:

設(shè)備樹的節(jié)點信息,最終會變成
device_node
結(jié)構(gòu),在內(nèi)存中維持一個樹狀結(jié)構(gòu);設(shè)備與驅(qū)動,會根據(jù)
compatible
字段進行匹配;
3.2 驅(qū)動流程分析
GIC驅(qū)動的執(zhí)行流程如下圖所示:

首先需要了解一下鏈接腳本
vmlinux.lds
,腳本中定義了一個__irqchip_of_table
段,該段用于存放中斷控制器信息,用于最終來匹配設(shè)備;在GIC驅(qū)動程序中,使用
IRQCHIP_DECLARE
宏來聲明結(jié)構(gòu)信息,包括compatible
字段和回調(diào)函數(shù),該宏會將這個結(jié)構(gòu)放置到__irqchip_of_table
字段中;在內(nèi)核啟動初始化中斷的函數(shù)中,
of_irq_init
函數(shù)會去查找設(shè)備節(jié)點信息,該函數(shù)的傳入?yún)?shù)就是__irqchip_of_table
段,由于IRQCHIP_DECLARE
已經(jīng)將信息填充好了,of_irq_init
函數(shù)會根據(jù)arm,gic-400
去查找對應(yīng)的設(shè)備節(jié)點,并獲取設(shè)備的信息。中斷控制器也存在級聯(lián)的情況,of_irq_init
函數(shù)中也處理了這種情況;or_irq_init
函數(shù)中,最終會回調(diào)IRQCHIP_DECLARE
聲明的回調(diào)函數(shù),也就是gic_of_init
,而這個函數(shù)就是GIC驅(qū)動的初始化入口函數(shù)了;GIC的工作,本質(zhì)上是由中斷信號來驅(qū)動,因此驅(qū)動本身的工作就是完成各類信息的初始化,注冊好相應(yīng)的回調(diào)函數(shù),以便能在信號到來之時去執(zhí)行;
set_smp_process_call
設(shè)置__smp_cross_call
函數(shù)指向gic_raise_softirq
,本質(zhì)上就是通過軟件來觸發(fā)GIC的SGI中斷
,用于核間交互;cpuhp_setup_state_nocalls
函數(shù),設(shè)置好CPU進行熱插拔時GIC的回調(diào)函數(shù),以便在CPU熱插拔時做相應(yīng)處理;set_handle_irq
函數(shù)的設(shè)置很關(guān)鍵,它將全局函數(shù)指針handle_arch_irq
指向了gic_handle_irq
,而處理器在進入中斷異常時,會跳轉(zhuǎn)到handle_arch_irq
執(zhí)行,所以,可以認為它就是中斷處理的入口函數(shù)了;驅(qū)動中完成了各類函數(shù)的注冊,此外還完成了
irq_chip
,?irq_domain
等結(jié)構(gòu)體的初始化,這些結(jié)構(gòu)在下文會進一步分析;最后,完成GIC硬件模塊的初始化設(shè)置,以及電源管理相關(guān)的注冊等工作;
3.3 數(shù)據(jù)結(jié)構(gòu)分析
先來張圖:

GIC驅(qū)動中,使用
struct gic_chip_data
結(jié)構(gòu)體來描述GIC控制器的信息,整個驅(qū)動都是圍繞著該結(jié)構(gòu)體的初始化,驅(qū)動中將函數(shù)指針都初始化好,實際的工作是由中斷信號觸發(fā),也就是在中斷來臨的時候去進行回調(diào);struct irq_chip
結(jié)構(gòu),描述的是中斷控制器的底層操作函數(shù)集,這些函數(shù)集最終完成對控制器硬件的操作;struct irq_domain
結(jié)構(gòu),用于硬件中斷號和Linux IRQ中斷號(virq,虛擬中斷號)之間的映射;
還是上一下具體的數(shù)據(jù)結(jié)構(gòu)代碼吧,關(guān)鍵注釋如下:
3.3.1 IRQ domain
IRQ domain用于將硬件的中斷號,轉(zhuǎn)換成Linux系統(tǒng)中的中斷號(virtual irq, virq
),來張圖:

每個中斷控制器都對應(yīng)一個IRQ Domain;
中斷控制器驅(qū)動通過
irq_domain_add_*()
接口來創(chuàng)建IRQ Domain;IRQ Domain支持三種映射方式:linear map(線性映射),tree map(樹映射),no map(不映射);
linear map:維護固定大小的表,索引是硬件中斷號,如果硬件中斷最大數(shù)量固定,并且數(shù)值不大,可以選擇線性映射;
tree map:硬件中斷號可能很大,可以選擇樹映射;
no map:硬件中斷號直接就是Linux的中斷號;
三種映射的方式如下圖:

圖中描述了三個中斷控制器,對應(yīng)到三種不同的映射方式;
各個控制器的硬件中斷號可以一樣,最終在Linux內(nèi)核中映射的中斷號是唯一的;
4. Arch-speicific代碼分析
中斷也是異常模式的一種,當外設(shè)觸發(fā)中斷時,處理器會切換到特定的異常模式進行處理,而這部分代碼都是架構(gòu)相關(guān)的;ARM64的代碼位于
arch/arm64/kernel/entry.S
。ARM64處理器有四個異常級別Exception Level:0~3,EL0級對應(yīng)用戶態(tài)程序,EL1級對應(yīng)操作系統(tǒng)內(nèi)核態(tài),EL2級對應(yīng)Hypervisor,EL3級對應(yīng)Secure Monitor;
異常觸發(fā)時,處理器進行切換,并且跳轉(zhuǎn)到異常向量表開始執(zhí)行,針對中斷異常,最終會跳轉(zhuǎn)到
irq_handler
中;
代碼比較簡單,如下:
來張圖:

中斷觸發(fā),處理器去異常向量表找到對應(yīng)的入口,比如EL0的中斷跳轉(zhuǎn)到
el0_irq
處,EL1則跳轉(zhuǎn)到el1_irq
處;在GIC驅(qū)動中,會調(diào)用
set_handle_irq
接口來設(shè)置handle_arch_irq
的函數(shù)指針,讓它指向gic_handle_irq
,因此中斷觸發(fā)的時候會跳轉(zhuǎn)到gic_handle_irq
處執(zhí)行;gic_handle_irq
函數(shù)處理時,分為兩種情況,一種是外設(shè)觸發(fā)的中斷,硬件中斷號在16 ~ 1020
之間,一種是軟件觸發(fā)的中斷,用于處理器之間的交互,硬件中斷號在16以內(nèi);外設(shè)觸發(fā)中斷后,根據(jù)
irq domain
去查找對應(yīng)的Linux IRQ中斷號,進而得到中斷描述符irq_desc
,最終也就能調(diào)用到外設(shè)的中斷處理函數(shù)了;
原文作者:LoyenWang
