如有侵權,請告知,將刪除 https://blog.csdn.net/yhb1047818384/article/details/86708769
changelog:
2019年02月17日 初稿
2020年03月1日 fix typos以及增加中斷路由
1. 前言
GIC,Generic Interrupt Controller。是ARM公司提供的一個通用的中斷控制器。主要作用為:
接受硬件中斷信號,並經過一定處理后,分發給對應的CPU進行處理。
當前GIC 有四個版本,GIC v1~v4, 主要區別如下表:
本文主要介紹GIC v3控制器, 基於linux kernel 4.19.0。
2. GIC v3中斷類別
GICv3定義了以下中斷類型:
SPI (Shared Peripheral Interrupt)
公用的外部設備中斷,也定義為共享中斷。可以多個Cpu或者說Core處理,不限定特定的Cpu。比如按鍵觸發一個中斷,手機觸摸屏觸發的中斷。
PPI (Private Peripheral Interrupt)
私有外設中斷。這是每個核心私有的中斷。PPI會送達到指定的CPU上,應用場景有CPU本地時鍾。
SGI (Software Generated Interrupt)
軟件觸發的中斷。軟件可以通過寫GICD_SGIR寄存器來觸發一個中斷事件,一般用於核間通信。
LPI (Locality-specific Peripheral Interrupt)
LPI是GICv3中的新特性,它們在很多方面與其他類型的中斷不同。LPI始終是基於消息的中斷,它們的配置保存在表中而不是寄存器。比如PCIe的MSI/MSI-x中斷。
| 硬件中斷號 | 中斷類型 |
|---|---|
| 0-15 | SGI |
| 16 - 31 | PPI |
| 32 - 1019 | SPI |
| 1020 - 1023 | 用於指示特殊情況的特殊中斷 |
| 1024 - 8191 | Reservd |
| 8192 - MAX | LPI |
3. GIC v3組成

GICv3控制器由以下部分組成:
distributor: SPI中斷的管理,將中斷發送給redistributor
redistributor: PPI,SGI,LPI中斷的管理,將中斷發送給cpu interface
cpu interface: 傳輸中斷給core
ITS: Interrupt Translation Service, 用來解析LPI中斷
其中,cpu interface是實現在core內部的,distributor,redistributor,ITS是實現在gic內部的.
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。
Redistributor詳述
對於每個連接的PE,都有一個Redistributor.
該block的主要功能包括:
(1)啟用和禁用SGI和PPI。
(2)設置SGI和PPI的優先級。
(3)將每個PPI設置為電平觸發或邊緣觸發。
(4)將每個SGI和PPI分配給中斷組。
(5)控制SGI和PPI的狀態。
(6)內存中數據結構的基址控制,支持LPI的相關中斷屬性和掛起狀態。
(7)電源管理支持。
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
3. 中斷路由
gicv3使用hierarchy來標識一個具體的core, 如下圖是一個4層的結構(aarch64)
<affinity level 3>.<affinity level 2>.<affinity level 1>.<affinity level 0> 組成一個PE的路由。
每一個core的affnity值可以通過MPDIR_EL1寄存器獲取, 每一個affinity占用8bit.
配置對應core的MPIDR值,可以將中斷路由到該core上。
各個affinity的定義是根據SOC自己的定義
比如可能affinity3代表socketid,affinity2 代表clusterid, affnity1代表coreid, affnity0代表thread id.
以gic 設置中斷路由為例:
中斷親和性的設置的通用函數為irq_set_affinity, 具體調用如下:
+-> irq_set_affinity() ... +-> irq_do_set_affinity() +-> chip->set_affnity() +->gic_set_affinity()
static int gic_set_affinity(struct irq_data *d, const struct cpumask *mask_val, bool force) { /* If interrupt was enabled, disable it first */ enabled = gic_peek_irq(d, GICD_ISENABLER); -------- (1) if (enabled) gic_mask_irq(d); reg = gic_dist_base(d) + GICD_IROUTER + (gic_irq(d) * 8); val = gic_mpidr_to_affinity(cpu_logical_map(cpu)); --------- (2) gic_write_irouter(val, reg); ------ (3) irq_data_update_effective_affinity(d, cpumask_of(cpu)); return IRQ_SET_MASK_OK_DONE; }
gic_set_affinity先判斷當前中斷是否使能,如果使能則disable掉該中斷;
然后根據gic_mpidr_to_affinity函數獲取需要綁定中斷到對應core的路由,
static u64 gic_mpidr_to_affinity(unsigned long mpidr) { u64 aff; aff = ((u64)MPIDR_AFFINITY_LEVEL(mpidr, 3) << 32 | MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 | MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 | MPIDR_AFFINITY_LEVEL(mpidr, 0)); return aff; }
aff 就是通過對應core的MPIDR_EL1寄存器獲取affinity0~3的值,並組成一個新的32bit value;
最后將獲取的value寫進gic irouter寄存器並更新中斷的親和性配置。
4. 中斷處理流程

從上圖可以看出,中斷處理可以分成兩類:
(1)中斷要通過distributor的中斷流程
–>外設發起中斷,發送給distributor
–>distributor將該中斷,分發給合適的re-distributor
–>re-distributor將中斷信息,發送給cpu interface。
–>cpu interface產生合適的中斷異常給處理器
–>處理器接收該異常,並且軟件處理該中斷
它的中斷狀態機如下圖所示
有四種中斷狀態:
| 中斷狀態 | 描述 |
|---|---|
| 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事件通知。
(2)中斷不通過distributor,比如LPI中斷
外設發起中斷,發送給ITS
–>ITS分析中斷,決定將來發送的re-distributor
–>ITS將中斷發送給合適的re-distributor
4. LPI
LPI是基於消息的中斷。中斷信息不在通過中斷線進行傳遞,而是通過memory。
gic內部提供一個寄存器,當外設往這個地址寫入數據時,就往gic發送了一個中斷。
在soc系統中,外設想要發送中斷給gic,是需要一根中斷線的。如果現在一個外設,需要增加一個中斷,那么就要增加一根中斷線,然后連接到gic。這樣就需要修改設計。而引入了LPI之后,當外設需要增加中斷,只需要使用LPI方式,傳輸中斷即可,不需要修改soc設計。
傳統的GIC流程:
在傳統的GIC流程中如上圖,外圍設備的中斷觸發線是引出到GIC上的,這樣可以理解為一個物理的SIGNAL,比如一個高電平信號,邊沿觸發信號。
消息中斷流程:
使用消息將中斷從外設轉發到中斷控制器,無需每個中斷源提供專用信號。 這樣的一個好處是,可以減少中斷線的個數。
在GICv3中,SPI可以是基於消息的中斷,但LPI始終是基於消息的中斷。
5.ITS
引入了LPI之后,gicv3中,還加入了ITS組件,interrupt translation service。ITS將接收到的LPI中斷,進行解析,然后發送到對應的redistributor,再由redistributor將中斷信息,發送給cpu interface。
外設,通過寫GITS_TRANSLATER寄存器,發起LPI中斷。寫操作,給ITS提供2個信息:
EventID: 值保存在GITS_TRANSLATER寄存器中,表示外設發送中斷的事件類型
DeviceID: 表示哪一個外設發起LPI中斷。該值的傳遞,是實現自定義,例如,可以使用AXI的user信號來傳遞。
ITS將DeviceID和eventID,通過一系列查表,得到LPI中斷號,再使用LPI中斷號查表,得到該中斷的目標cpu。
ITS將LPI中斷號,LPI中斷對應的目標cpu,發送給對應的redistributor。redistributor再將該中斷信息,發送給CPU。
6.參考資料
GICv3_Software_Overview_Official_Release_B
在前一篇博文(ARM GICv3中斷控制器)中, 介紹了GIC的一些基本概念,本文主要分析了linux kernel中GIC v3中斷控制器的代碼(drivers/irqchip/irq-gic-v3.c)
linux kernel版本是linux 4.19.29, 體系結構是arm64.
GICv3 DTS設備描述
首先,在討論GICv3驅動代碼分析前,先看下GICv3在DTS里是怎么定義的。
一個gicv3定義的例子
gic: interrupt-controller@2c010000 { compatible = "arm,gic-v3"; #interrupt-cells = <4>; #address-cells = <2>; #size-cells = <2>; ranges; interrupt-controller; redistributor-stride = <0x0 0x40000>; // 256kB stride #redistributor-regions = <2>; reg = <0x0 0x2c010000 0 0x10000>, // GICD <0x0 0x2d000000 0 0x800000>, // GICR 1: CPUs 0-31 <0x0 0x2e000000 0 0x800000>; // GICR 2: CPUs 32-63 <0x0 0x2c040000 0 0x2000>, // GICC <0x0 0x2c060000 0 0x2000>, // GICH <0x0 0x2c080000 0 0x2000>; // GICV interrupts = <1 9 4>; gic-its@2c200000 { compatible = "arm,gic-v3-its"; msi-controller; #msi-cells = <1>; reg = <0x0 0x2c200000 0 0x20000>; }; gic-its@2c400000 { compatible = "arm,gic-v3-its"; msi-controller; #msi-cells = <1>; reg = <0x0 0x2c400000 0 0x20000>; }; };
- compatible: 用於匹配GICv3驅動
- #interrupt-cells: 這是一個中斷控制器節點的屬性。它聲明了該中斷控制器的中斷指示符(-interrupts)中 cell 的個數
- #address-cells , #size-cells, ranges:用於尋址, #address-cells表示reg中address元素的個數,#size-cells用來表示length元素的個數
- interrupt-controller: 表示該節點是一個中斷控制器
- redistributor-stride: 一個GICR的大小
- #redistributor-regions: GICR域個數。
- reg :GIC的物理基地址,分別對應GICD,GICR,GICC…
- interrupts: 分別代表中斷類型,中斷號,中斷類型, PPI中斷親和, 保留字段。
a為0表示SPI,1表示PPI;b表示中斷號(注意SPI/PPI的中斷號范圍);c為1表示沿中斷,4表示電平中斷。 - msi-controller: 表示節點是MSI控制器
GICv3 初始化流程
1. irq chip driver聲明
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
- 1
定義IRQCHIP_DECLARE之后,相應的內容會保存到__irqchip_of_table里邊。
#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 }
__irqchip_of_table在vmlinux.lds文件里邊被放到了__irqchip_begin和__irqchip_of_end之間
#ifdef CONFIG_IRQCHIP #define IRQCHIP_OF_MATCH_TABLE() \ . = ALIGN(8); \ VMLINUX_SYMBOL(__irqchip_begin) = .; \ *(__irqchip_of_table) \ *(__irqchip_of_end) #endif
__irqchip_begin和__irqchip_of_end的內容被drivers/irqchip/irqchip.c文件讀出並根據其在device tree里邊的內容進行初始化。
2. gic_of_init流程
static int __init gic_of_init(struct device_node *node, struct device_node *parent) { void __iomem *dist_base; struct redist_region *rdist_regs; u64 redist_stride; u32 nr_redist_regions; int err, i; dist_base = of_iomap(node, 0); ------------- (1) if (!dist_base) { pr_err("%pOF: unable to map gic dist registers\n", node); return -ENXIO; } err = gic_validate_dist_version(dist_base); --------------- (2) if (err) { pr_err("%pOF: no distributor detected, giving up\n", node); goto out_unmap_dist; } if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions)) ------- (3) nr_redist_regions = 1; rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs), GFP_KERNEL); if (!rdist_regs) { err = -ENOMEM; goto out_unmap_dist; } for (i = 0; i < nr_redist_regions; i++) { --------- (4) struct resource res; int ret; ret = of_address_to_resource(node, 1 + i, &res); rdist_regs[i].redist_base = of_iomap(node, 1 + i); if (ret || !rdist_regs[i].redist_base) { pr_err("%pOF: couldn't map region %d\n", node, i); err = -ENODEV; goto out_unmap_rdist; } rdist_regs[i].phys_base = res.start; } if (of_property_read_u64(node, "redistributor-stride", &redist_stride)) ----------- (5) redist_stride = 0; err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions, redist_stride, &node->fwnode); ------------- (6) if (err) goto out_unmap_rdist; gic_populate_ppi_partitions(node