ARM GICv3中斷控制器(轉)


如有侵權,請告知,將刪除 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>; }; }; 

 

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); -------------- (7) if (static_branch_likely(&supports_deactivate_key)) gic_of_setup_kvm_info(node); return 0; out_unmap_rdist: for (i = 0; i < nr_redist_regions; i++) if (rdist_regs[i].redist_base) iounmap(rdist_regs[i].redist_base); kfree(rdist_regs); out_unmap_dist: iounmap(dist_base); return err; } 

(1)映射GICD的寄存器地址空間。 通過設備結點直接進行設備內存區間的 ioremap(),index是內存段的索引。若設備結點的reg屬性有多段,可通過index標示要ioremap的是哪一段,只有1段的情況, index為0。采用Device Tree后,大量的設備驅動通過of_iomap()進行映射,而不再通過傳統的ioremap。

(2) 驗證GICD的版本是否為GICv3 or GICv4。 主要通過讀GICD_PIDR2寄存器bit[7:4]. 0x1代表GICv1, 0x2代表GICv2…以此類推。

(3) 通過DTS讀取redistributor-regions的值。redistributor-regions代表GICR獨立的區域數量(地址連續)。
假設一個64核的arm64 服務器,redistributor-regions=2, 那么64個核可以用2個連續的GICR連續空間表示。

(4) 為一個GICR域 分配基地址。

(5) 通過DTS讀取redistributor-stride的值. redistributor-stride代表GICR域中每一個GICR的大小,正常情況下一個CPU對應一個GICR(redistributor-stride必須是64KB的倍數)

(6) 主要處理流程,下面介紹。

(7) 可以設置一組PPI的親和性。

3. gic_init_bases流程

static int __init gic_init_bases(void __iomem *dist_base, struct redist_region *rdist_regs, u32 nr_redist_regions, u64 redist_stride, struct fwnode_handle *handle) { u32 typer; int gic_irqs; int err; gic_data.fwnode = handle; gic_data.dist_base = dist_base; gic_data.redist_regions = rdist_regs; gic_data.nr_redist_regions = nr_redist_regions; gic_data.redist_stride = redist_stride; /* * Find out how many interrupts are supported. * The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI) */ typer = readl_relaxed(gic_data.dist_base + GICD_TYPER); ------------- (1) gic_data.rdists.gicd_typer = typer; gic_irqs = GICD_TYPER_IRQS(typer); if (gic_irqs > 1020) gic_irqs = 1020; gic_data.irq_nr = gic_irqs; gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data); -------------- (2) irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED); ------------- (3) gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist)); gic_data.rdists.has_vlpis = true; gic_data.rdists.has_direct_lpi = true; if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) { err = -ENOMEM; goto out_free; } gic_data.has_rss = !!(typer & GICD_TYPER_RSS); ------------ (4) pr_info("Distributor has %sRange Selector support\n", gic_data.has_rss ? "" : "no "); if (typer & GICD_TYPER_MBIS) { err = mbi_init(handle, gic_data.domain); ------------ (5) if (err) pr_err("Failed to initialize MBIs\n"); } set_handle_irq(gic_handle_irq); ------------------- (6) gic_update_vlpi_properties(); ------------------- (7) if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) its_init(handle, &gic_data.rdists, gic_data.domain); ------------- (8) gic_smp_init(); ----------(9) gic_dist_init(); ----------(10) gic_cpu_init(); ----------(11) gic_cpu_pm_init(); ----------(12) return 0; out_free: if (gic_data.domain) irq_domain_remove(gic_data.domain); free_percpu(gic_data.rdists.rdist); return err; }

(1) 確認支持SPI 中斷號最大的值為多少,GICv3最多支持1020個中斷(SPI+SGI+SPI).GICD_TYPER寄存器bit[4:0], 如果該字段的值為N,則最大SPI INTID為32(N + 1)-1。 例如,0x00011指定最大SPI INTID為127。

(2) 向系統中注冊一個irq domain的數據結構. irq_domain主要作用是將硬件中斷號映射到IRQ number。 可以參考[Linux內核筆記之中斷映射]

(3) 主要作用是給irq_find_host()函數使用,找到對應的irq_domain。 這里使用 DOMAIN_BUS_WIRED,主要作用就是區分其他domain, 如MSI。

(4) 判斷GICD 是否支持rss, rss(Range Selector Support)表示SGI中斷親和性的范圍 GICD_TYPER寄存器bit[26], 如果該字段為0,表示中斷路由(IRI) 支持affinity 0-15的SGI,如果該字段為1, 表示支持affinity 0 - 255的SGI

(5) 判斷是否支持通過寫GICD寄存器生成消息中斷。GICD_TYPER寄存器bit[16]

(6) 設定arch相關的irq handler。gic_irq_handle是內核gic中斷處理的入口函數, 可以參考系列文章 [Linux 內核筆記之高層中斷處理]

(7) 更新vlpi相關配置。gic虛擬化相關。

(8) 初始化ITS。 Interrupt Translation Service, 用來解析LPI中斷。 初始化之前需要先判斷GIC是否支持LPI,該功能在ARM里是可選的。可以參考系列文章[ARM GICv3 ITS介紹及代碼分析]

(9) 該函數主要包含兩個作用。 1.設置核間通信函數。當一個CPU core上的軟件控制行為需要傳遞到其他的CPU上的時候,就會調用這個callback函數(例如在某一個CPU上運行的進程調用了系統調用進行reboot)。對於GIC v3,這個callback定義為gic_raise_softirq. 2. 設置CPU 上下線流程中和GIC相關的狀態機

(10) 初始化GICD。

(11) 初始化CPU interface.

(12) 初始化GIC電源管理。

參考資料

IHI0069D_gic_architecture_specification

 

前言:

在ARM gicv3中斷控制器,有提到過ITS的作用,本篇就ITS進行更詳細的介紹以及分析linux 內核中ITS代碼的實現。
本文基於linux 4.19,介紹DT方式初始化的ITS代碼。

ITS概述:

在GICv3中定義了一種新的中斷類型,LPI(locality-specific peripheral interrupts)。LPI是一種基於消息的中斷。中斷信息不再通過中斷線進行傳遞。

GICv3定義了兩種方法實現LPI中斷:

  • forwarding方式
    外設可以通過訪問redistributor的寄存器GICR_SERLPIR,直接發送LPI中斷
  • 使用ITS方式
    ITS(Interrupt Translation Service)在GICv3中是可選的。ITS負責接收來自外設的中斷,並將它們轉化為LPI INTID發送到相應的Redistributor

一般而言比較推薦使用ITS實現LPI,因為ITS提供了很多特性,在中斷源比較多的場景,可以更加高效。

外設通過寫GITS_TRANSLATER寄存器,發起LPI中斷。此時ITS會獲得2個信息:
EventID: 值保存在GITS_TRANSLATER寄存器中,表示外設發送中斷的事件類型
DeviceID: 表示哪一個外設發起LPI中斷。
ITS將DeviceID和eventID,通過一系列查表,得到LPI中斷號,再使用LPI中斷號查表,得到該中斷的目標cpu。

The ITS table

當前,ITS使用三種類型的表來處理LPI的轉換和路由:
device table: 映射deviceID到中斷轉換表
interrupt translation table:映射EventID到INTID。以及INTID屬於的collection組
collection table:映射collection到Redistributor
在這里插入圖片描述

所以一個ITS完整的處理流程是:
當外設往GITS_TRANSLATER寄存器中寫數據后,ITS做如下操作:

  1. 使用DeviceID,從設備表(device table entry)中選擇索引為DeviceID的表項。從該表項中,得到中斷 轉換表(interrupt translation table)的位置
  2. 使用EventID,從中斷轉換表中選擇索引為EventID的表項。得到中斷號,以及中斷所屬的collection號
  3. 使用collection號,從collection表格中,選擇索引為collection號的表項。得到redistributor的映射信息
  4. 根據collection表項的映射信息,將中斷信息,發送給對應的redistributor
    在這里插入圖片描述

The ITS Command:

its是由its的命令控制的。命令隊列是一個循環buffer, 由三個寄存器定義。
GITS_CBASER: 指定命令隊列的基地址和大小。命令隊列必須64KB對齊,大小必須是4K的倍數。命令隊列中的每一個索引是32字節。該寄存器還指定訪問命令隊列時its的cacheability和shareability的設置。
GITS_CREADR: 指向ITS將處理的下一個命令
GITS_CWRITER: 指向隊列中應寫入下一個新命令的索引。
在這里插入圖片描述

在its的初始化過程以及lpi中斷上報等過程中,會涉及到ITS command的發送。 具體的its commad指令參考spec.


現在我們已經知道ITS的具體作用以及處理流程,結合linux內核的實現進行分析。

ITS代碼分析

its的代碼位於drivers/irqchip/irq-gic-v3-its.c

1. ITS數據結構

struct its_node { raw_spinlock_t lock; struct list_head entry; void __iomem *base; phys_addr_t phys_base; struct its_cmd_block *cmd_base; struct its_cmd_block *cmd_write; struct its_baser tables[GITS_BASER_NR_REGS]; struct its_collection *collections; struct fwnode_handle *fwnode_handle; u64 (*get_msi_base)(struct its_device *its_dev); u64 cbaser_save; u32 ctlr_save; struct list_head its_device_list; u64 flags; unsigned long list_nr; u32 ite_size; u32 device_ids; int numa_node; unsigned int msi_domain_flags; u32 pre_its_base; /* for Socionext Synquacer */ bool is_v4; int vlpi_redist_offset; }; 

 

base : its node的虛擬地址
phys_base: its node的物理地址
cmd_base: 命令隊列的基地址
cmd_write: 指向隊列中下一個命令的地址
tables[]: 指向device table或vpe table的結構體
collection: 指向its_collection結構體, 主要保存映射到的gicr的地址
cbaser_save: 保存cbaser寄存器的信息
ctlr_save:保存ctlr寄存器的

2. ITS的初始化

在gic初始化時,會進行ITS的初始化。
its的初始化操作主要是為its的device table以及collection table分配內存,並使能its.

static int __init gic_init_bases(void __iomem *dist_base, struct redist_region *rdist_regs, u32 nr_redist_regions, u64 redist_stride, struct fwnode_handle *handle) { ....... if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis()) -------- (1) its_init(handle, &gic_data.rdists, gic_data.domain); ---- (2) its_cpu_init(); ----- (3) } 

 

(1) ITS需要使能內核配置 CONFIG_ARM_GIC_V3_ITS. 如果架構支持LPI, 則進行ITS的初始化。
通過讀GICD_TYPER(Interrupt Controller Type Register)寄存器的bit17查看架構是否支持LPI.

(2)its_init是 its的初始化入口。第三個參數需要注意下,它指定了its的parent domain是gic domain
在這里插入圖片描述

(3) its_cpu_init 是在its初始化完成后,進行its的一些額外的配置,如enable lpi以及綁定its collection到its 目的redistributour。

1.1 its初始化函數its_init

int __init its_init(struct fwnode_handle *handle, struct rdists *rdists, struct irq_domain *parent_domain) { ... its_parent = parent_domain; of_node = to_of_node(handle); if (of_node) its_of_probe(of_node); --------- (1) if (list_empty(&its_nodes)) { pr_warn("ITS: No ITS available, not enabling LPIs\n"); return -ENXIO; } gic_rdists = rdists; err = its_alloc_lpi_tables(); ------- (2) if (err) return err; ... register_syscore_ops(&its_syscore_ops); ------(3) return 0; } 

 

(1) its_of_probe

+->its_of_probe +->its_probe_one +->its_force_quiescent //讓ITS處於非活動狀態,在非靜止狀態改變ITS的配置會有安全的風險 +->its node malloc and init //為its_node分配空間,並對其進行初始化配置 +->its_alloc_tables //為device table 和 vpe table分配內存 +->its_alloc_collections //為collection table中映射到的gicr 地址分配內存; 每一個its都有一個collection table, ct可以保存在寄存器(GITS_BASER)或者內存(GITS_TYPER.HCC) +->its_init_domain // its domain初始化,注冊its domain相關操作 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

its probe過程, 主要是初始化its node數據結構, 為its tables分配內存, 初始化its domain並注冊its domain相關操作。
its_domain初始化過程中,會指定its irq_domain的host_data為msi_domain_info, 在info-ops.prepare過程中會去創建ITS設備, its translation table會在那個階段分配內存。

此外,its probe過程中還有一個標志位會被設置。

if (GITS_TYPER_HCC(typer)) its->flags |= ITS_FLAGS_SAVE_SUSPEND_STATE; 
  • 1
  • 2

在這里插入圖片描述
如果GITS_TYPER.hcc不為0, 那么就會將its->flags置為SUSPEND。
這個標志位可以判斷its需不需要進行suspend或者resume流程,下文再詳細描述。

(2) its_alloc_lpi_tables

+-> its_alloc_lpi_tables +-> its_allocate_prop_table // 初始化lpi中斷的配置表和狀態表 +-> its_lpi_init 
  • 1
  • 2
  • 3

ITS 是為LPI服務的,所以在ITS初始化過程中還需要初始化LPI需要的兩張表
(LPI configuration table, LPI pending tables ), 然后進行lpi的初始化。

LPI的這兩張表就是LPI和其他類型中斷的區別所在: LPI的中斷的配置,以及中斷的狀態,是保存在memory的表中,而不是保存在gic的寄存器中的。

LPI 中斷配置表:
中斷配置表的基地址由GICR_PROPBASER寄存器決定。
對於LPI配置表,每個LPI中斷占用1個字節(bit[7:0]),指定了該中斷的使能(bit 0)和中斷優先級(bit[7:2])。

當外部發送LPI中斷給redistributor,redistributor首先要訪問memory來獲取LPI中斷的配置表。為了加速這過程,redistributor中可以配置cache,用來緩存LPI中斷的配置信息。

因為有了cache,所以LPI中斷的配置信息,就有了2份拷貝,一份在memory中,一份在redistributor的cache中。如果軟件修改了memory中的LPI中斷的配置信息,需要將redistributor中的cache信息給無效掉。
通過該接口刷相關dcache

gic_flush_dcache_to_poc()
  • 1

LPI 中斷狀態表
中單狀態表的基地址由GICR_PENDBASER寄存器決定, 該寄存器還可以設置LPI中斷狀態表memory的屬性,如shareability,cache屬性等。
該狀態表主要用於查看LPI是否pending狀態。
在這里插入圖片描述
該中斷狀態表由redistributor來設置。每個LPI中斷,占用一個bit空間。
0: 該LPI中斷,沒有處於pending狀態
1: 該LPI中斷,處於pending狀態

(3) register_syscore_ops
該操作主要是注冊兩個低功耗流程會用到的函數, suspend和resume。
在系統進行低功耗流程時(suspend 或者hibernate, 當然目前4.19還不支持its的hibernate), suspend時會調用its_save_disable, 保存its的一些寄存器狀態,並disable its, 在 resume時調用its_restore_enable, 恢復之前its保存的寄存器狀態,並enable its.

static struct syscore_ops its_syscore_ops = { .suspend = its_save_disable, .resume = its_restore_enable, }; 
  • 1
  • 2
  • 3
  • 4

這個流程由低功耗的框架保證, 只需要通過register_syscore_ops函數注冊suspend和resume函數即可。

1.2 its_cpu_init

+-> its_cpu_init +-> its_cpu_init_lpis // 配置lpi 配置表和狀態表, 以及使能lpi +-> its_cpu_init_collections // 綁定每一個collection到target redistributor +-> its_send_mapc // 發送its mapc command, mapc主要用於映射collection到目的redistributor +-> its_send_invall //指定 memory中的LPI中斷的配置信息和cache中保存的必須一致 
  • 1
  • 2
  • 3
  • 4
  • 5

3. its中斷上報

和gic類似, 在中斷上報時,如果設備掛載在its 下, 會調用到its domain的一系列operation

static const struct irq_domain_ops its_domain_ops = { .alloc = its_irq_domain_alloc, .free = its_irq_domain_free, .activate = its_irq_domain_activate, .deactivate = its_irq_domain_deactivate, }; 

 

參考資料

  1. Arm Generic Interrupt Controller Architecture Specification
  2. GICv3 and GICv4 Software Overview


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM