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
posted @ 2020-11-11 10:44  二虎  阅读(3053)  评论(0编辑  收藏  举报