Bootstrap

【GIC LPI 及 ITS 介绍】

1 前言

写作动机:有客户先bootloader初始化GIC, 然后一个新的 image 重新初始化了 GIC 包括 ITS device,然后启动发现收不到中断。且 LPI enable bit 无法清零。

1.1 What is GIC?

GIC控制器将来自外设的中断分出优先级,然后分配给合适的core。 GIC是Arm Cortex-A 以及 Arm Cortex-R 的标准中断控制器。GIC提供灵活的可裁剪的方法进行中断管理,支持1个core到多个core的芯片设计。GIC有GICv3, GICv4等版本且在不断进化,往往ARM core版本越高,GIC也相应是高版本。

2 GIC 功能介绍

参考arm官网

2.1 GIC 中断分类

SPI, PPI, SGI 中断,和LPI中断。详情看这页arm官网

2.2 提供两种中断信号发送到中断控制器的方式

  • 硬件方式:每个外设通过一条专用的硬件信号线与中断控制器相连。
  • 软件消息信号中断MSI方式:不需要硬件接线,外设通过向中断控制器的特定寄存器写入消息来发出中断请求(消息包含了中断的相关信息,如中断号、优先级等)。
    对于共享外设中断(SPI),可以选择使用MSI或传统方式。
    对于低功耗中断(LPI),总是使用MSI方式。

不同类型的中断使用不同的寄存器来传递消息。

2.3 中断状态机

SPI, PPI, SGI使用同一种状态机,包括四部分:
Inactive:中断源当前未被设置
Pending:中断被设置了,但中断源还没被PE通知。
Active:中断被设置了,但中断源被PE通知了。
Active and Pending:一个实例被通知了,另外一个处于pengding。
中断的生命周期取决于是否被配置为电平敏感边沿触发的。

  1. 对电平敏感中断,中断的上升沿输入造成中断状态为pending,当外设取消中断信号后,中断才被设置。
  2. 对边沿敏感中断,一个中断的上升沿输入造成中断状态为pending, 但中断没有被设置。

LPI中断与他们不同,LPI只有两种状态:Inactive , Pending。

2.4 编程模型

GICv3中断控制器的寄存器接口被分为3组:

  • Distributor
  • Redistributor
  • CPU

这三种接口,前俩 用于配置中断,后者用于处理中断。

简单介绍上面的接口:

  • Distributor:分配寄存器是内存映射的,用于配置SPI的。提供了一个编程接口用来:
    SPI中断优先级和分配,
    使能或失能SPI,
    设置SPI优先级,
    设置电平敏感还是边沿敏感,
    控制SPI的pending状态还是active状态,
    决定每个安全状态中使用的程序模型:affinity routing or legacy。

  • Redistributors
    每个被连接的core都有一个Redistributor,Redistributors 提供了一个编程模型用来:
    使能或失能SGIs 和 PPIs,
    设置SGIs 和 PPIs的优先级,边沿敏感还是电平敏感
    分配每个 SGI and PPI 到一个中断组
    控制SGIs and PPI的状态
    为memory中支持LPIs的中断属性和pending状态分配的数据结构体分配基地址。
    为所有连接的PE提供电源管理支持

  • CPU interfaces
    每个core包含处理器接口,这是在中断处理期间使用的中断寄存器。CPU接口提供编程接口。
    提供通用控制和配置来使能中断处理。
    确认中断
    执行优先级下降和中断停用。
    设置PE中断优先级掩码
    定义PE抢占策略
    确定 PE 的最高优先级挂起中断
    在使用这些寄存器之前,软件必须启用系统寄存器接口。这由 ICC_SRE_ELn 寄存器中的 SRE 位控制,其中 n 指定异常级别:EL1-EL3。

3 GIC v3/v4 LPI 中断介绍

参考arm官网之LPI中断

3.1 LPI简介(引出ITS,MSI)

这个文档为需要理解MSI是如何被GIC转换的、LPI是如何被管理的人以及需要在裸核条件下配置GIC的人设计的。
先引出几个概念:

LPI(Locality-specific Peripheral Interrupts)

MSIs(Message-Signaled Interrupts) 基于消息的中断,上一节讲了

ITS(Interrupt Translation Service)

一句话总结概括:LPI是一种MSI中断,ITS 负责将来自外设的消息翻译成LPI,然后转发到合适的 Redistributor 中,Redistributor将中断送给cpu interface处理并记录状态。
在这里插入图片描述

LPI 通常用于 MSIs 的外设。 LPI 的配置和管理不同于其他中断,因为他的状态存储在一段memory中而非寄存器中,LPI属于MSI。上图展示了两种 LPI 的触发方式,一种是直接发送 LPI INTID 到 Redistributors , 另一种是通过 ITS 将来自设备的 EventID 转化为 LPI INTID 后发送给 Redistributors。

然后文档从 Redistributors 和 ITS 如何配置展开介绍 LPI 中断的配置生成处理过程。其中延申除了一些名词,理解LPI中断就是理解这些名词之间的关系。之后才能理解代码。

3.2 Redistributors

开局一张图:
LPI Configuration and LPI Pending tables

一句话总结概括:LPI 的配置信息状态信息被存储在位于memory中的不同种类的 table 中,Redistributors 通过不同的寄存器来分别记录这些不同种类table的地址来监控以及配置LPI。

【插播一条,下面会提到很多寄存器,可能会蒙圈,先有个概念:】

GIC_v3中多了很多寄存器,对寄存器存在两种访问方式,一种是memory-mapped的方式(实现在GIC内部),一种是系统寄存器访问(在core内部实现)。

memory-mapped访问的寄存器:
GICC: cpu interface 寄存器
GICD: distributor 寄存器
GICH: virtual interface 控制寄存器,在hypervisor模式访问
GICR: redistributor 寄存器
GICV: virtual cpu interface 寄存器
GITS: ITS 寄存器,

系统寄存器访问的寄存器:
ICC: 物理 cpu interface 系统寄存器
ICV: 虚拟 cpu interface 系统寄存器
ICH: 虚拟 cpu interface 控制系统寄存器

概念展开:

  • LPI Configuration table 配置表:

    每个 INTID 在配置表中占一个字节。GIC能读取这块内存,但不能写入。
    LPI 配置表是全局的,一个系统就一个配置表被所有 Redistributors 共享。
    Redistributors 的 GICR_PENDBASER 寄存器指向 LPI 配置表。
    详情看官网。

  • LPI Pending table 状态表(字节起的名):

    因为LPI中断只有两种状态(inactive or pending),所以每个 INTID 在 LPI 状态表中只占一位。
    每个 Redistributors 有自己的 LPI Pending table,由 GICR_PENDBASER 指向。
    中断控制器必须能够读取和写入为 LPI Pending table 分配的内存。
    Redistributor 在需要缓存的待处理中断过多或进入低功耗状态时将状态信息写入 LPI Pending table。
    软件绝不会直接访问 LPI Pending table(无法理解)。
    GICR_PROPBASER.ID 位控制着 INTID 范围的大小,因此他影响着 LPI Configuration table 和 LPI Pending table 的大小。

理解了概念后,咱们看Redistributor如何初始化:

  1. 为 LPI Configuration table 分配内存,并使用每个 LPI 的适当配置初始化该表。
  2. 设置每个Redistributor 的 GICR_PROPBASER 指向 LPI Configuration table
  3. 为每个 Redistributor 申请 LPI Pending table 并初始化,系统启动时,意味着 zeroing the memory, 意味着 LPI INTIDs 处于 inactive state.
  4. 设置每个 Redistributor 的 GICR_PENDBASER 指向与自身关联的 LPI Pending table.
  5. 设置每个 Redistributor 的 GICR_CTLR.EnableLPIs 位为1.
    LPI配置信息存储在 memory 中的一个table中,而非寄存器中,出于性能原因,Redistributors 会缓存LPI 配置信息。这意味着为了重新配置LPI,软件必须:
  6. 更新LPI Configuration table中的entry
  7. 确保更新的全局可见性
  8. 使Redistributors中的任何缓存配置无效

3.3 ITS

一句话总结概括:ITS完成从message到可传输到连接的core的INTID的转换。
外围设备通过写入 message 到 ITS 中的 GITS_TRANSLATER 来生成 LPI, ITS 将 message 处理,配合 ITS 的一系列 table 将 message 其转化成可供 core 识别的 INTID。

概念展开:
table 包含 Device table,Interrupt Translation Tables(ITT),Collection table,然后他们之间的关系如下图所示:
An ITS forwarding an LPI to a Redistributor
图示来源

看图解释:

  1. 外设的 message 包含 EventID(标识外设正在发送哪个中断) 和 DeviceID(标识是哪个外设)。
  2. 在 Device table 中用 DeviceID 选择合适的entry。这个 entry 定义了使用哪个 ITT(每个DeviceID or peripheral一个)。
  3. 用 EventID 从所选 ITT 中选择适当的entry ,该 entry 提供了 INTID 和 Collection ID。
  4. 用 Collection ID 选择 Collection table 中的需要的entry, 返回路由信息。
  5. ITS 将中断转发到目标 Redistributor.

ITS 是由 ITS 的命令控制,ITS 命令存储在命令队列中, 命令队列是受ITS的三个寄存器控制的一段循环缓冲区:

  • GITS_CBASER:寄存器定义了基地址和命令队列的大小,命令队列必须是64K对齐的,并且大小必须是4K的倍数。命令队列中的每个 entry 32 个字节。GITS_CBASER 还指定 ITS 在访问命令队列时使用的可缓存性和可共享性设置。
  • GITS_CREADR:该寄存器指向 ITS 将处理的下一个命令。
  • GITS_CWRITER:该寄存器指向队列中应写入下一个新命令的条目。

【注】:Arm Generic Interrupt Controller Architecture Specification GIC architecture version 3.0 and 4.0 中提供了所有ITS支持的命令细节已经如何编码。

然后文档后续讲解了 ITS 的初始化配置流程,如何申请Collection and Device tables,向命令队列添加命令。

ARM GICv3 GIC代码分析

先看看设备树:

	gic: interrupt-controller@6000000 {
		compatible = "arm,gic-v3";
		reg = <0x0 0x06000000 0 0x10000>, // GIC Dist (GICD)
			<0x0 0x06200000 0 0x200000>,  // GICR (RD_base +  SGI_base)
			<0x0 0x0c0c0000 0 0x2000>,    // GICC
			<0x0 0x0c0d0000 0 0x1000>,    // GICH
			<0x0 0x0c0e0000 0 0x20000>;   // GICV
		#interrupt-cells = <3>;
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
		interrupt-controller;
		interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;

		its: gic-its@6020000 {
			compatible = "arm,gic-v3-its";
			msi-controller;
			reg = <0x0 0x6020000 0 0x20000>;
		};
	};

然后看代码:

static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
		/* map设备树中第 0 个mmio内存,也就是GICD */
        dist_base = of_iomap(node, 0);
		/* 获取GIC版本号:dist_base + GICD_PIDR2(0xFFE8) */
        err = gic_validate_dist_version(dist_base);

		/* 获取有多少个distributor,我们dts没设置,默认1个,所以在这被置1 */
        if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
                nr_redist_regions = 1;
        rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),
                             GFP_KERNEL);
		
		/*  */
        for (i = 0; i < nr_redist_regions; i++) {
                struct resource res;
                int ret;
				/* 获取设备树中 reg 中第i+1个地址范围 */
                ret = of_address_to_resource(node, 1 + i, &res);
				/* map设备树中第 i+1 个mmio内存,依次为 GICR GICC GICH GICV */
                rdist_regs[i].redist_base = of_iomap(node, 1 + i); 

                rdist_regs[i].phys_base = res.start;
        }
		/* 获取redistributor的宽度,我这个好像没 */
        if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
                redist_stride = 0;

		...

        err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
                             redist_stride, &node->fwnode);
		...
}
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)
{
		...
		
        /* 从 GICD_TYPER 获取支持的最大中断号是多少 */
        typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
        
		...

        irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);

        gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
        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);
                if (err)
                        pr_err("Failed to initialize MBIs\n");
        }
		
		/* 注册中断上半部回调函数 */
        set_handle_irq(gic_handle_irq);

		...
		/* 初始化各个寄存器 */
        gic_dist_init(); 
        gic_cpu_init(); // 初始化CPU interface.
        gic_smp_init();
		/* 电源管理相关,不展开 */
        gic_cpu_pm_init();

        if (gic_dist_supports_lpis()) {
				/* 如果支持 ITS ,初始化LPI/ITS */
                its_init(handle, &gic_data.rdists, gic_data.domain);
                its_cpu_init();
        } else {
                if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
                        gicv2m_init(handle, gic_data.domain);
        }

        gic_enable_nmi_support();
		...
}
static void __init gic_dist_init(void)
{
        unsigned int i;
#ifndef CONFIG_GIC_GENTLE_CONFIG
        u64 affinity;
#endif
        void __iomem *base = gic_data.dist_base;
        u32 val;

#ifdef CONFIG_GIC_GENTLE_CONFIG
       /* 在运行多个操作系统的 SoC 中,这些操作系统在 ARM 集群上共享同一个 GIC,当另一个操作系统已经配置了 distributor 时,我们会注意不要重新配置 distributor,否则可能会干扰 */
       u32 gicd_ctlr = readl_relaxed(base + GICD_CTLR);
#endif
        /* 禁用 distributor */
        writel_relaxed(0, base + GICD_CTLR);
		/* 通过监控GICD_CTLR特定位等待禁用操作完成 */
        gic_dist_wait_for_rwp();

        /*
         * Configure SPIs as non-secure Group-1. This will only matter
         * if the GIC only has a single security state. This will not
         * do the right thing if the kernel is running in secure mode,
         * but that's not the intended use case anyway.
         */
        for (i = 32; i < GIC_LINE_NR; i += 32)
                writel_relaxed(~0, base + GICD_IGROUPR + i / 8);

        /* Extended SPI range, not handled by the GICv2/GICv3 common code */
        for (i = 0; i < GIC_ESPI_NR; i += 32) {
                writel_relaxed(~0U, base + GICD_ICENABLERnE + i / 8);
                writel_relaxed(~0U, base + GICD_ICACTIVERnE + i / 8);
        }

        for (i = 0; i < GIC_ESPI_NR; i += 32)
                writel_relaxed(~0U, base + GICD_IGROUPRnE + i / 8);

        for (i = 0; i < GIC_ESPI_NR; i += 16)
                writel_relaxed(0, base + GICD_ICFGRnE + i / 4);
        for (i = 0; i < GIC_ESPI_NR; i += 4)
                writel_relaxed(GICD_INT_DEF_PRI_X4, base + GICD_IPRIORITYRnE + i);

        /* Now do the common stuff, and wait for the distributor to drain */
        gic_dist_config(base, GIC_LINE_NR, gic_dist_wait_for_rwp);

        val = GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1;
        if (gic_data.rdists.gicd_typer2 & GICD_TYPER2_nASSGIcap) {
                pr_info("Enabling SGIs without active state\n");
                val |= GICD_CTLR_nASSGIreq;
        }

        /* 使能 distributor */
        writel_relaxed(val, base + GICD_CTLR);

#ifndef CONFIG_GIC_GENTLE_CONFIG
        /* In a SoC running multiple OSes on ARM clusters sharing the same GIC,
         * do not set the affinity to all interrupts as this
         * would conflict with the other cluster's GIC configuration.
         * This is now done in function gic_set_type() (called by request_irq)
         * which allows to limit this to the interrupts registered by the
         * cluster.
         */
        /*
         * Set all global interrupts to the boot CPU only. ARE must be
         * enabled.
         */
		/* 初始化中断亲和性,全部绑定中断到当前cpu */
        affinity = gic_mpidr_to_affinity(cpu_logical_map(smp_processor_id()));
        for (i = 32; i < GIC_LINE_NR; i++)
                gic_write_irouter(affinity, base + GICD_IROUTER + i * 8);

        for (i = 0; i < GIC_ESPI_NR; i++)
                gic_write_irouter(affinity, base + GICD_IROUTERnE + i * 8);
#endif
}
static void gic_cpu_init(void)
{
        /* 填充 redistributor 结构体基地址 */
        if (gic_populate_rdist())
                return;
		/* 使能redistributor 就是写GICR_WAKER,然后忙等结果 */
        gic_enable_redist(true);
		...
		/* 清除PPI\SGI中断设置默认中断优先级 */
		gic_cpu_config(rbase, gic_data.ppi_nr + 16, gic_redist_wait_for_rwp);
		/* 初始化系统寄存器 */
		gic_cpu_sys_reg_init();
}
static int gic_populate_rdist(void)
{
        pr_info("%s(): GICTT CPU-%d will call gic_iterate_rdists\n", __func__, smp_processor_id());
        if (gic_iterate_rdists(__gic_populate_rdist) == 0)
                return 0;
		...
}
/* 填充redistributor */
static int __gic_populate_rdist(struct redist_region *region, void __iomem *ptr)
{
        unsigned long mpidr = cpu_logical_map(smp_processor_id());
        u64 typer;
        u32 aff;

		/*
		 * 生成以一个当前核心的中断亲和值
		 * #define MPIDR_AFFINITY_LEVEL(mpidr, level) ((mpidr >> (8 * level)) & 0b11111111)
		 */
        aff = (MPIDR_AFFINITY_LEVEL(mpidr, 3) << 24 |
               MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 |
               MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 |
               MPIDR_AFFINITY_LEVEL(mpidr, 0));
		/* 读取 GICR_TYPER,存储 Redistributor 的配置信息 */
        typer = gic_read_typer(ptr + GICR_TYPER);
		/* 如果 Redistributor 绑定的就是当前核心 */
        if ((typer >> 32) == aff) {
				/* 填充结构体基地址 */
                u64 offset = ptr - region->redist_base;
                raw_spin_lock_init(&gic_data_rdist()->rd_lock);
                gic_data_rdist_rd_base() = ptr;
                gic_data_rdist()->phys_base = region->phys_base + offset;
                pr_info("CPU%d: GICTT found redistributor %lx region %d:%pa phy:0x%llX offset:0x%llX\n",
                        smp_processor_id(), mpidr,
                        (int)(region - gic_data.redist_regions),
                        &gic_data_rdist()->phys_base, gic_data_rdist()->phys_base, offset);
                return 0;
        }

        /* Try next one */
        return 1;
}

/***********************************************************************/

static void __init gic_smp_init(void)
{
        struct irq_fwspec sgi_fwspec = {
                .fwnode         = gic_data.fwnode,
                .param_count    = 1,
        };
        int base_sgi;

        cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                                  "irqchip/arm/gicv3:starting",
                                  gic_starting_cpu, NULL);

        /* Register all 16 non-secure SGIs */
        base_sgi = __irq_domain_alloc_irqs(gic_data.domain, -1, 16,
                                           NUMA_NO_NODE, &sgi_fwspec,
                                           false, NULL);
        if (WARN_ON(base_sgi <= 0))
                return;

        set_smp_ipi_range(base_sgi, 16);
}


ITS通过命令队列来配置前面提到的三个 table ,分别是
CBASER:指向一个内存空间,代表命令存放的环形队列,命令就放入这个环形队列中
CREADR:指示GIC目前读取环形队列的位置,读取GIC处理了多少数据
CWRITER:指示当前环形缓冲区写入位置,告诉GIC写入数据了

下面我们开始看代码:
我们从 gic_init_bases 开始看,在 gic_init_bases 的结尾初始化ITS,分别调用了 its_init 和 its_cpu_init 两个关键函数:

gic_of_init
|
|-> gic_init_bases
	|-->gic_cpu_init() //初始化CPU interface.
	|	|-->gic_populate_rdist()
	|
	|
	|-->its_init()
		|-->its_of_probe()初始化 its node 数据结构, 为 its tables 分配内存, 初始化its domain并注册its domain相关操作。
			|
			|->its_probe_one()三个入口参数
				|
				|-->alloc_pages_node // 为 命令队列 申请内存
				|-->its_alloc_tables // 分配设备表???
						its_setup_baser
							alloc_pages_node
				|-->its_init_domain //猜测:初始化its domain并注册its domain相关操作。 并创建基数树
				在its_init_domain中的info->ops得到了如下ops接口
					|--	its_msi_prepare
						|
						|-->its_find_device 根据dev_id查找entry. 返回值为its_device类型结构体,其实就是device. 但是返回值为空的。没找到。
						|
						|-->its_create_device
							|
							|-->its_alloc_device_table // 申请 device_table
							|-->its_lpi_alloc 申请内核空间存放 lpi中断。
							|-->its_send_mapd //Map device to its ITT
								|-->its_send_single_command(dev->its, its_build_mapd_cmd, &desc); //填充 Device Table
		|					
		|		
		|-->allocate_lpi_tables() 初始化LPI需要的两张表(LPI configuration table, LPI pending tables(每个Redistributor一个))
		|	
		|-->register_syscore_ops()注册两个低功耗流程会用到的函数
	|
	|->its_cpu_init //进行its的一些额外的配置,如 enable lpi 以及绑定 its collection 到 its 目的 redistributour。
		|--> its_cpu_init_lpis // 配置 GICR (redistributor寄存器)寄存器,GICR_PROPBASER 指向 LPI Configuration table, GICR_PENDBASER 指向与自身关联的 LPI Pending table. 以及使能lpi,函数第一行rbase得到的就是位于percpu区域的GICR(redistributor寄存器)基地址。
		|
		|--> its_cpu_init_collections // 绑定每一个 collection 到 target redistributor
			|
			|--> its_send_mapc // 发送its mapc command, mapc 主要用于映射 collection 到目的 redistributor
					|-->its_send_single_command(its, its_build_mapc_cmd, &desc); //填充 Collection Table 
			|--> its_send_invall //指定 memory中的LPI中断的配置信息和cache中保存的必须一致
					|-->its_send_single_command(its, its_build_invall_cmd, &desc); //指定ITS必须保证由ICID定义的中断collection的任何缓存与所有Redistributor内存中LPI配置表保持一致

ITS使用命令来填他的三个转换表,基本的ITS命令去看 《IHI0069H_b_gic_architecture_specification.pdf 5.3》 章节。
最主要的命令有下面三个:
MAPD : 填充 Device Table(将DeviceID相关的设备表项映射到由ITT_addr和Size定义的ITT)
MAPI、MAPTI: 填充 Interrupt Traslation Table表(ITT)
MAPC : 填充 Collection Table(将ICID定义的collection表映射到RDbase定义的Redistributor)

ITS表的配置原理

上面出现了三次 its_send_single_command 应该是发送命令,下面来看一下代码里面是怎么组织命令和发送命令的,结合 MAPD 命令来一窥命令的发送过程:

// drivers/irqchip/.irq-gic-v3-its.c
/* Warning, macro hell follows */
#define BUILD_SINGLE_CMD_FUNC(name, buildtype, synctype, buildfn)       \
void name(struct its_node *its,                                         \
          buildtype builder,                                            \
          struct its_cmd_desc *desc)                                    \
{                                                                       \
        struct its_cmd_block *cmd, *sync_cmd, *next_cmd;                \
        synctype *sync_obj;                                             \
        unsigned long flags;                                            \
        u64 rd_idx;                                                     \
                                                                        \
        raw_spin_lock_irqsave(&its->lock, flags);                       \
                                                                        \
        cmd = its_allocate_entry(its);                                  \
        if (!cmd) {             /* We're soooooo screewed... */         \
                raw_spin_unlock_irqrestore(&its->lock, flags);          \
                return;                                                 \
        }                                                               \
        sync_obj = builder(its, cmd, desc);                             \
		/* 把cache中的数据刷到内存 */
        its_flush_cmd(its, cmd);                                        \
                                                                        \
        if (sync_obj) {                                                 \
		如果上面的builder需要做sync操作会返回值,然后根据宏定义代码块组织发送sync命令
                sync_cmd = its_allocate_entry(its);                     \
                if (!sync_cmd)                                          \
                        goto post;                                      \
                                                                        \
                buildfn(its, sync_cmd, sync_obj);                       \
				/* 把cache中的数据刷到内存 */
                its_flush_cmd(its, sync_cmd);                           \
        }                                                               \
                                                                        \
post:                                                                   \
		/* 前面说了让记住仨 ITS 寄存器。 记住没? 这就用上了 */
		/* 读 CREADR 寄存器的值,GIC目前读取环形队列的位置 */
        rd_idx = readl_relaxed(its->base + GITS_CREADR);                \
		/* 更新 CWRITER 寄存器的值,告诉GIC有命令写入 */
        next_cmd = its_post_commands(its);                              \
        raw_spin_unlock_irqrestore(&its->lock, flags);                  \
                                                                        \
		/* 读 CREADR 寄存器的值,等待处理完成 */
        if (its_wait_for_range_completion(its, rd_idx, next_cmd))       \
                pr_err_ratelimited("ITS cmd %ps failed\n", builder);    \
}
static struct its_cmd_block *its_allocate_entry(struct its_node *its)
{
        struct its_cmd_block *cmd;
        u32 count = 1000000;    /* 1s! */

        while (its_queue_full(its)) {
			// 队列满了
                count--;
                if (!count) {
                        pr_err_ratelimited("ITS queue not draining\n");
                        return NULL;
                }
                cpu_relax();
                udelay(1); // 盲等,延迟 1 微秒
        }
		// 队列没满
		/* 使用下一个空 command */
        cmd = its->cmd_write++;

        /* Handle queue wrapping */
        if (its->cmd_write == (its->cmd_base + ITS_CMD_QUEUE_NR_ENTRIES))
                its->cmd_write = its->cmd_base;

        /* Clear command  */
        cmd->raw_cmd[0] = 0;
        cmd->raw_cmd[1] = 0;
        cmd->raw_cmd[2] = 0;
        cmd->raw_cmd[3] = 0;

        return cmd;
}
static BUILD_SINGLE_CMD_FUNC(its_send_single_command, its_cmd_builder_t,
                             struct its_collection, its_build_sync_cmd)

上面这段代码是定义一个 its_send_single_command 函数,该函数在 its_send_mapc 等函数被调用,its_cmd_builder_t 为回调, its_build_sync_cmd 为同步命令,需要时使用。【其实这个宏相关函数的理解是关键】

static void its_send_mapc(struct its_node *its, struct its_collection *col,
                          int valid)
{
        struct its_cmd_desc desc;

        desc.its_mapc_cmd.col = col;
        desc.its_mapc_cmd.valid = !!valid;

        its_send_single_command(its, its_build_mapc_cmd, &desc);
}
回调如下函数:
static struct its_collection *its_build_mapc_cmd(struct its_node *its,
                                                 struct its_cmd_block *cmd,
                                                 struct its_cmd_desc *desc)
{
        its_encode_cmd(cmd, GITS_CMD_MAPC);
        its_encode_collection(cmd, desc->its_mapc_cmd.col->col_id);
        its_encode_target(cmd, desc->its_mapc_cmd.col->target_address);
        its_encode_valid(cmd, desc->its_mapc_cmd.valid);

        its_fixup_cmd(cmd);

        return desc->its_mapc_cmd.col;
}

总结: 观察ITS队列有空闲位置就往空闲位置里面写入数据,数据写入之后将cache刷出内存,如果要等待ITS响应命令就用sync命令去等待ITS操作。

参考

参考:https://blog.csdn.net/yhb1047818384/article/details/87561438
参考:https://blog.csdn.net/yhb1047818384/article/details/89061672
主要参考:https://blog.csdn.net/qq_16054639/article/details/142334293
挖坑1: arm64中断处理流程。
挖坑2: Linux irq_domain 相关介绍,可参考,无意间发现kernel 竟然有翻译介绍文档:./Documentation/translations/zh_CN/core-api/irq/irq-domain.rst 。

;