• 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
    ", node);
    		return -ENXIO;
    	}
    
    	err = gic_validate_dist_version(dist_base);        --------------- (2)
    	if (err) {
    		pr_err("%pOF: no distributor detected, giving up
    ", 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
    ", 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
    ",
       	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
    ");
       }
    
       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
    ");
    		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
  • 相关阅读:
    Fast exit from dram self-refresh
    关于Net开发中一些SQLServer性能优化的建议
    收集一些优秀的DoNet开源项目
    收集一些优秀的DoNet开源项目
    收集一些优秀的DoNet开源项目
    LINQ表达式用法整理
    LINQ表达式用法整理
    LINQ表达式用法整理
    SQL 拼接多个字段的值&一个字段多条记录的拼接
    你应该知道的jQuery技巧【收藏】
  • 原文地址:https://www.cnblogs.com/erhu-67786482/p/13957507.html
Copyright © 2020-2023  润新知