Linux IRQ Sub System VI

GIC-V3分析

GIC-V3概述

GIC:The Generic Interrupt Controller

ARM架构下GIC支持好几个版本,GIC-v1, GIC-v2, GIC-v3, GIC-v4

在此重点聚焦在GIC-V3版本,而GIC-V3版本的典型代表就是GIC-500

.关于GIC-500的特性

.GIC-500可以最大支持128Cores

.GIC-500目前只支持ARMv8架构

.GIC支持四种中断类型

.支持CPU-Interface, Distributor

如下,GIC-500和CPU之间的框图

在这里插入图片描述
.中断的产生是通过physical interrupt signals(外设中断信号)或者Message-based Interrupts 或者SGIS。其实这就是GIC支持的几种中断类型

.GIC是通过AXI4-Stream专用接口连接到CPU上的

GIC-500的内部布局如下所示;
在这里插入图片描述
可以看到GIC内部有两个重要的模块
.Distributor(仲裁器):
.CPU Interface : CPU-Interface更倾向于CPU侧,每一个CPU都存在一个Interface。

接下来,
在这里插入图片描述
涉及的概念,Distributor, CPU Interface, Redistributor, SGI, PPI, SPI, LPL
可以先看下面的中断状态和Distributor和CPU-interface的概念
SPI会先从Distributor路由到Target Redsitributor,然后再路由到CPU-interface模块
PPI直接会路由到local的Redsitributor
SGI中断类型是由软件触发的,则会从core路由到CPU-interface和Redsitributor模块,然后会到Dsitributor模块,决定路由到一个或者多个cpu上

中断类型:
GIC-v3中定义了四种中断类型

SGI(Software Generated Interrupt)
中断号是0-15之间
用于core之间相互通信,由软件触发的中断,也可以称为IPI中断

PPI(Private Perpheral Interrupt)
中断号在16-31之间
此类中断是每个core私有的,只用于当前core处理一些业务时使用,比如每个core上有一个tick中断,用于进程调度使用

SPI(Shared Perpheral Interrupt)
中断号在32-1020之间
此类中断是由外设触发的中断信号产线,比如touch的触摸屏中断等

LPI(Local-sperical Perpherial Interrupt)
此中断不只支持GIC-v1,GIC-v2.
只基于消息类型的中断

Distributor仲裁器:
仲裁器的主要作用是对中断优先级排序,以及将SPI和PPI中断分发到Redistributor和CPU-Interface模块,仲裁器对应的寄存器为GICD_CTLR
使能或者关闭此中断(使能状态)
对中断进行设置优先级(优先级)
设置此中断的触发方式,是边沿触发还是电平触发(触发方式)
控制中断的状态(中断状态)
使能或者关闭Securiy(中断安全状态)
设置中断的Affinity(中断的亲合性)
中断的路由情况,是由那个cpu去处理此中断(中断路由信息)

CPU interface:
每一个CPU对应到一个CPU Inrerface模块,当触发中断后,会由Distributor模块来设置中断的状态,以及路由到那个cpu

控制中断的状态,是否已经处理完毕(状态控制)
标识一个中断,获取中断的中断号(获取中断号)
设置中断的优先级,如果多个中断同时到CPU-interface模块,需要区分优先级(优先级)
决定是都要mask此中断等(MASK)

中断的状态:
InActive 中断当前没有触发
Pending 中断已经触发了,正在等待相应的core处理中断,代表此中断已经路由到CPU interface模块了
Active 代表此中断已经在处理中
Active and Pending 代表相同的中断在处理中,又触发了一个相同的中断
中断状态的改变
Inactive -> Pending
此中断由外设触发了
Pending -> Active
此中断已经由CPU在处理了
Active -> Inactive
此中断已经处理完毕了

中断处理流程如下:
在这里插入图片描述
.外设或者软件触发一个中断,中断的状态设置为Pending
.此中断会走到Dirtibutor模块,进行优先级,状态,亲合性,以及路由到那个core
.CPUinterface会将此中断路由到相应的core
.此时CPU会访问Interrupt Acknowledge Register(ICC_IAR0)寄存器,会获取对应的INTID,然后会将中断的状态由pending修改为Active
.当CPU处理完此中断后,软件上会写此EOI(End of Interrupt)标识此中断处理完毕
.将中断的状态由active修改为inactive

DTS中GIC-V3的描述

    gic: interrupt-controller {
        compatible = "arm,gic-v3";
        #interrupt-cells = <3>;
        #address-cells = <2>;
        #size-cells = <2>;
        #redistributor-regions = <1>;
        interrupt-parent = <&gic>;
        interrupt-controller;
        reg = <0 0x0c000000 0 0x40000>, // distributor
              <0 0x0c040000 0 0x200000>, // redistributor
              <0 0x0c53d668 0 0x100>; //INTPOL
        interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
    };

compatible: 用于和对应的驱动匹配,不再详说
interrupt-cells用于描述一个中断源的详细信息,此值等于3代表interrupts中有三个字段
第一个字段代表中断类型(GIC_PPI, GIC_SPI)
第二个字段物理中断号,根据中断类型中断号的范围不同。SPI(0-987)PPI(0-15)
第三个字段代表的中断触发方式
比如:

  pio: pinctrl {
        compatible = "mediatek,mt6853-pinctrl";
        reg_bases = <&gpio>,
                <&iocfg_rb>,
                <&iocfg_rm>,
                <&iocfg_bm>,
                <&iocfg_bl>,
                <&iocfg_br>,
                <&iocfg_lm>,
                <&iocfg_rt>,
                <&iocfg_tl>;
        reg_base_eint = <&eint>;
        pins-are-numbered;
        gpio-controller;
        gpio-ranges = <&pio 0 0 203>;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;
        interrupts = <GIC_SPI 212 IRQ_TYPE_LEVEL_HIGH>;
        interrupt-parent = <&gic>;
    };

interrupt-controller: 描述此字段是一个中断控制器
interrupt-parent: 代表此中断控制器是否是级联的,如果没有此字段,则跟随父字段
reg描述的是中断控制器中的涉及的寄存器
0x0c000000代表的是Distributor的基址寄存器,GICD
0x0c040000代表的是Redistributor的基址寄存器,GICR

加载KERNEL到GIC-V3初始化流程

7.3.1 kernel — > early_irq_init

工作流程如下所示:
在这里插入图片描述

start_kernel:
    early_irq_init();
		initcnt = arch_probe_nr_irqs();/* 预设中断号*/
		
		for (i = 0; i < initcnt; i++) {
			desc = alloc_desc(i, node, 0, NULL, NULL);/* 分配并初始化数组 irq_desc[virq]*/
				desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node);
				desc->kstat_irqs = alloc_percpu(unsigned int);
				raw_spin_lock_init(&desc->lock);
				lockdep_set_class(&desc->lock, &irq_desc_lock_class);
				mutex_init(&desc->request_mutex);
				init_rcu_head(&desc->rcu);
				desc_set_defaults(irq, desc, node, affinity, owner);
				irqd_set(&desc->irq_data, flags);
				kobject_init(&desc->kobj, &irq_kobj_type);
			set_bit(i, allocated_irqs);/* 置于位图中*/
			irq_insert_desc(i, desc);/* 以为virq为索引,把irq_desc[]数组插入 radix tree 中*/
				radix_tree_insert(&irq_desc_tree, irq, desc);
		}										
    init_IRQ();
		irqchip_init();
			of_irq_init(__irqchip_of_table);/*这里就关联了gic-v3的入口了*/


7.3.1 init_IRQ阶段

工作流程如下所示;
在这里插入图片描述

init_IRQ();
		irqchip_init();
			of_irq_init(__irqchip_of_table);/* Scan and init matching interrupt controllers in DT  ;__irqchip_of_table 要理解这里的宏定义,实际上就是吧所有的inc node 都保存在这里*/
				for_each_matching_node_and_match(np, matches, &match)/* 遍寻dts里面所有的intc*/
				
				/*分配of_intc_desc结构体并填充,添加到链表里面 */
				desc = kzalloc(sizeof(*desc), GFP_KERNEL);
			    desc->irq_init_cb = match->data;/* 注意这里就是 gic-v3的初始化函数*/
				desc->dev = of_node_get(np);
				desc->interrupt_parent = of_irq_find_parent(np);
				if (desc->interrupt_parent == np)
					desc->interrupt_parent = NULL;
				list_add_tail(&desc->list, &intc_desc_list);
				
				/* 从链表中取出 gic-v3 并进行初始化设置 */
				list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
					ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);
a.	内核加载的过程中解析和查找 gic-v3 的设备node,匹配上后并调用 gic-v3 driver 的初始化函数,这样就跟 gic-v3 driver code 关联上了,这里值得注意的是” __irqchip_of_table”的实现方式;
/*
__irqchip_of_table 的源头:

mt6853_r_tee/kernel-4.14$ vim include/linux/irqchip.h +27
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)

mt6853_r_tee/kernel-4.14$ vim include/linux/of.h
#define OF_DECLARE_2(table, name, compat, fn) \
        _OF_DECLARE(table, name, compat, fn, of_init_fn_2)

#if defined(CONFIG_OF) && !defined(MODULE)
#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  }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type)           \
    static const struct of_device_id __of_table_##name      \
        __attribute__((unused))                 \
         = { .compatible = compat,              \
             .data = (fn == (fn_type)NULL) ? fn : fn }
#endif

可见,
此处定义了 CONFIG_OF 而未定义 MODULE, OF_DECLARE 宏声明了struct of_device_id 的结构体变量 of_table##name,它存放在##table##_of_table 的 section 中

根据前面 _OF_DECLARE 和 OF_DECLARE_2 的宏定义,IRQCHIP_DECLARE 实际声明并定义了 struct of_device_id 的结构体变量 _of_table##name,将它存放在 __irqchip_of_talbe 的 section 中。
对于每个中断控制器,都会通过 IRQCHIP_DECLARE 静态声明自己的 struct of_device_id 的结构体变量 _of_table##name,并将其加入 _irqchip_of_talbe 的 section 中。

所以,

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);
等价于
static const struct of_device_id __of_table_gic_v3             \
                __used __section("__irqchip_of_table")               \
                 = { .compatible = "arm,gic-v3",                              \
                     .data = gicv3_of_init}
of_irq_init 就是遍历 __irqchip_of_table 段中所有的中断控制器,对其执行相应的中断控制器初始化。
*/

接下来,分析一下初始化函数的操作动作,

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);
	static int __init gicv3_of_init(struct device_node *node, struct device_node *parent)
		dist_base = of_iomap(node, 0);/*映射 distributor 寄存器的地址空间*/
		
		/* 获取 gic-v3 dts 中 nr_redist_regions 值,并通过此获取 redisttibutor 的地址 */
		if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
		for (i = 0; i < nr_redist_regions; i++) {
			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;
		}		

		/* 根据获取的数据,调用 gic_init_bases 做具体的初始化操作 */
		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);

b.可见;
of_iomap获取终端控制器distributor的基址
gic_validate_dist_version根据基址判断当前GIC版本
读取redisttibutor的属性,获取对应寄存器的基址
最终会调用到gic_init_bases函数中做相应的初始化

GIC-V3初始化流程

在这里插入图片描述
c.接下来重点分析一下 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)
	/*
		1.初始化 struct gic_chip_data 结构体 gic_data;
	*/
	static struct gic_chip_data gic_data __read_mostly;/* 定义一个全局变量结构体 */
	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);
	gic_data.rdists.id_bits = GICD_TYPER_ID_BITS(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,/* 创建 irq domain */
						 &gic_data);/* 创建 irq_domain 域,后面 处理irq event 的函数时回调用到 */
						 return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data);
							struct irq_domain *domain;
							domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),GFP_KERNEL, of_node_to_nid(of_node));
							INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);/* 这个很重要 ,初始化一个radix tree ,创建对应的hwirq -- virq 有大作用*/
							domain->ops = ops;
							domain->host_data = host_data;
							domain->hwirq_max = hwirq_max;
							domain->revmap_size = size;
							domain->revmap_direct_max_irq = direct_max;							
	gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
	gic_data.rdists.has_vlpis = true;
	gic_data.rdists.has_direct_lpi = true;	
				
	/* 2.设置中断回调函数 ,前面也讲解过了,后面概括性讲解
		static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
			irqnr = gic_read_iar();/*去读IAR寄存器确定中断号,hwirq */
			err = handle_domain_irq(gic_data.domain, irqnr, regs);/*这里调用domain之一*/
				return __handle_domain_irq(domain, hwirq, true, regs);
					irq = irq_find_mapping(domain, hwirq);/* 通过 hwirq < --- irq_domain --- > 映射的方式获取到 virq */
					generic_handle_irq(irq);
						struct irq_desc *desc = irq_to_desc(irq);/*通过 virq 获取对应的 irq_desc */
						generic_handle_irq_desc(desc);
							desc->handle_irq(desc);/* 通过这一函数指针对具体handler进行处理 */
	*/
	set_handle_irq(gic_handle_irq);/*  置中断回调函数;handle_arch_irq = handle_irq; 底层汇编中调用 c实现中断处理的过程 */
		/*可见,handle_arch_irq = desc->handle_irq(desc);*/
	
	/* 一些其他相关的初始化 动作 */
	gic_smp_init();
	gic_dist_init();
	gic_cpu_init();
	gic_cpu_pm_init();


可见,初始化的时候主要是做了一下动作;
is_hyp_mode_available判断当前是否在Hyp虚拟化模式
根据参数初始化gic_data结构

通过读取GICD_TYPER寄存器获取到当前GIC支持的最大中断数量。如果中断数量超过1020则赋值最大值为1020.
irq_domain_create_tree通过此函数来创建一个irq domain,irq doamin就是对中断的区域的管理,用于级联
set_handle_irq(gic_handle_irq);重点中的重点,用于设置中断处理的回调函数,当中断处理时,首先会调用此函数的
gic_smp_init 软中断的初始化,设置软中断的回调
gic_dist_init distributor的初始化
gic_cpu_init cpu interface的初始化
gic_cpu_pm_init power相关的初始化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人在路上……

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值