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相关的初始化