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