- UID
- 1029342
- 性别
- 男
|
我们首先看看这个函数的参数,node参数代表需要初始化的那个interrupt controller的device node,parent参数指向其parent。在映射GIC-400的memory map I/O space的时候,我们只是映射了Distributor和CPU interface的寄存器地址空间,和虚拟化处理相关的寄存器没有映射,因此这个版本的GIC driver应该是不支持虚拟化的(不知道后续版本是否支持,在一个嵌入式平台上支持虚拟化有实际意义吗?最先支持虚拟化的应该是ARM64+GICV3/4这样的平台)。
要了解cpu-offset属性,首先要了解什么是banked register。所谓banked register就是在一个地址上提供多个寄存器副本。比如说系统中有四个CPU,这些CPU访问某个寄存器的时候地址是一样的,但是对于banked register,实际上,不同的CPU访问的是不同的寄存器,虽然它们的地址是一样的。如果GIC没有banked register,那么需要提供根据CPU index给出一系列地址偏移,而地址偏移=cpu-offset * cpu-nr。
interrupt controller可以级联。对于root GIC,其传入的parent是NULL,因此不会执行级联部分的代码。对于second GIC,它是作为其parent(root GIC)的一个普通的irq source,因此,也需要注册该IRQ的handler。由此可见,非root的GIC的初始化分成了两个部分:一部分是作为一个interrupt controller,执行和root GIC一样的初始化代码。另外一方面,GIC又作为一个普通的interrupt generating device,需要象一个普通的设备驱动一样,注册其中断handler。理解irq_of_parse_and_map需要irq domain的知识,请参考linux kernel的中断子系统之(二):irq domain介绍。
(2)gic_init_bases的代码如下:
void __init gic_init_bases(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base,
u32 percpu_offset, struct device_node *node)
{
irq_hw_number_t hwirq_base;
struct gic_chip_data *gic;
int gic_irqs, irq_base, i;
gic = &gic_data[gic_nr];
gic->dist_base.common_base = dist_base; ----省略了non banked的情况
gic->cpu_base.common_base = cpu_base;
gic_set_base_accessor(gic, gic_get_common_base);
for (i = 0; i < NR_GIC_CPU_IF; i++) ---后面会具体描述gic_cpu_map的含义
gic_cpu_map = 0xff;
if (gic_nr == 0 && (irq_start & 31) > 0) { --------------------(a)
hwirq_base = 16;
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32;
}
gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; ----(b)
gic_irqs = (gic_irqs + 1) * 32;
if (gic_irqs > 1020)
gic_irqs = 1020;
gic->gic_irqs = gic_irqs;
gic_irqs -= hwirq_base;----------------------------(c)
if (of_property_read_u32(node, "arm,routable-irqs",----------------(d)
&nr_routable_irqs)) {
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id()); -------(e)
if (IS_ERR_VALUE(irq_base)) {
WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
irq_start);
irq_base = irq_start;
}
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, -------(f)
hwirq_base, &gic_irq_domain_ops, gic);
} else {
gic->domain = irq_domain_add_linear(node, nr_routable_irqs, --------(f)
&gic_irq_domain_ops,
gic);
}
if (gic_nr == 0) { ---只对root GIC操作,因为设定callback、注册Notifier只需要一次就OK了
#ifdef CONFIG_SMP
set_smp_cross_call(gic_raise_softirq);------------------(g)
register_cpu_notifier(&gic_cpu_notifier);------------------(h)
#endif
set_handle_irq(gic_handle_irq); ---这个函数名字也不好,实际上是设定arch相关的irq handler
}
gic_chip.flags |= gic_arch_extn.flags;
gic_dist_init(gic);---------具体的硬件初始代码,参考下节的描述
gic_cpu_init(gic);
gic_pm_init(gic);
}
(a)gic_nr标识GIC number,等于0就是root GIC。hwirq的意思就是GIC上的HW interrupt ID,并不是GIC上的每个interrupt ID都有map到linux IRQ framework中的一个IRQ number,对于SGI,是属于软件中断,用于CPU之间通信,没有必要进行HW interrupt ID到IRQ number的mapping。变量hwirq_base表示该GIC上要进行map的base ID,hwirq_base = 16也就意味着忽略掉16个SGI。对于系统中其他的GIC,其PPI也没有必要mapping,因此hwirq_base = 32。
在本场景中,irq_start = -1,表示不指定IRQ number。有些场景会指定IRQ number,这时候,需要对IRQ number进行一个对齐的操作。
(b)变量gic_irqs保存了该GIC支持的最大的中断数目。该信息是从GIC_DIST_CTR寄存器(这是V1版本的寄存器名字,V2中是GICD_TYPER,Interrupt Controller Type Register,)的低五位ITLinesNumber获取的。如果ITLinesNumber等于N,那么最大支持的中断数目是32(N+1)。此外,GIC规范规定最大的中断数目不能超过1020,1020-1023是有特别用户的interrupt ID。
(c)减去不需要map(不需要分配IRQ)的那些interrupt ID,OK,这时候gic_irqs的数值终于和它的名字一致了。gic_irqs从字面上看不就是该GIC需要分配的IRQ number的数目吗?
(d)of_property_read_u32函数把arm,routable-irqs的属性值读出到nr_routable_irqs变量中,如果正确返回0。在有些SOC的设计中,外设的中断请求信号线不是直接接到GIC,而是通过crossbar/multiplexer这个的HW block连接到GIC上。arm,routable-irqs这个属性用来定义那些不直接连接到GIC的中断请求数目。
(e)对于那些直接连接到GIC的情况,我们需要通过调用irq_alloc_descs分配中断描述符。如果irq_start大于0,那么说明是指定IRQ number的分配,对于我们这个场景,irq_start等于-1,因此不指定IRQ 号。如果不指定IRQ number的,就需要搜索,第二个参数16就是起始搜索的IRQ number。gic_irqs指明要分配的irq number的数目。如果没有正确的分配到中断描述符,程序会认为可能是之前已经准备好了。
(f)这段代码主要是向系统中注册一个irq domain的数据结构。为何需要struct irq_domain这样一个数据结构呢?从linux kernel的角度来看,任何外部的设备的中断都是一个异步事件,kernel都需要识别这个事件。在内核中,用IRQ number来标识某一个设备的某个interrupt request。有了IRQ number就可以定位到该中断的描述符(struct irq_desc)。但是,对于中断控制器而言,它不并知道IRQ number,它只是知道HW interrupt number(中断控制器会为其支持的interrupt source进行编码,这个编码被称为Hardware interrupt number )。不同的软件模块用不同的ID来识别interrupt source,这样就需要映射了。如何将Hardware interrupt number 映射到IRQ number呢?这需要一个translation object,内核定义为struct irq_domain。 |
|