GIC———-ARM Generic Interrupt Controller
一、GIC简介:
GIC是的ARM研发的一个通用的中断控制器,它在硬件上的实现形态分为两种:
一种是ARM体系中的半导体公司在研发自己的SOC的时候,向ARM公司购买GIC的IP,这些GIC的型号有:GIC-400,GIC-500等等。另一种形态是ARM vensor直接购买ARM公司已经集成了GIC的多核方案,比如Cortex A9或者A15的IP,Cortex A9或者A15中会包括了GIC的实现,这些实现是符合GIC V2的规格。
ARM SMP多核处理器一般都会搭载一个GIC来提供中断控制功能。本章是基于Cortex A9平台来做介绍。ARM平台上一般把中断分为三种类型,分别是PPI(per processor interrupts)、SPI(shared processor interrupts)和SGI(software generated interrupts)。
主GIC是直接连接到CPU上的,并且除了SPI,还拥有PPI和SGI中断。而第二个GIC是级联到主GIC上的,它只有SPI中断,没有PPI和SGI中断。
硬件中断号的分配:
(1)ID0~ID31
是用于分发到一个特定的process的interrupt。标识这些interrupt不能仅仅依靠ID,还必须指定process的ID,因此识别这些interrupt需要interrupt ID + CPU interface number。
(a)ID0~ID15属于SGI中断,SGI是通过软件写入GIC的GICD_SGIR寄存器而触发的中断,它可以用于processor之间的通信。 GIC通过processor source ID、中断ID和target processor ID来唯一识别一个SGI。
(b)ID16~ID31属于PPI中断,PPI类型的中断和SPI一样属于外设的中断,区别就是它会被送到其私有的process上,而和其他的process无关。
(2)ID32~ID1019用于SPI。 这是GIC规范的最大范围,实际上Cortex-A15和A9上的GIC最多支持224个SPI。
二、GIC驱动
在kernel/drivers/irqchip目录下保存在各种不同的中断控制器的驱动代码, irq-gic.c是GIC的驱动代码。
1、device node和irq chip driver的匹配过程
(1)irq chip driver中的声明
在irqchip.h文件中定义了IRQCHIP_DECLARE宏如下:
#define IRQCHIP_DECLARE(name,compstr,fn) \
static const struct of_device_id irqchip_of_match_##name \
__used __section(__irqchip_of_table) \
= { .compatible = compstr, .data = fn }
#endif
这个宏其实就是初始化了一个struct of_device_id的静态常量,并放置在__irqchip_of_table 段(section)中。irq-gic.c文件中使用IRQCHIP_DECLARE来定义了若干个静态的struct of_device_id常量,如下:
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);
兼容GIC-V2的GIC实现有很多,不过其初始化函数都是gic_of_init。编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(section name是__irqchip_of_table),我们称这个特殊的section叫做irq chip table。这个table也就保存了kernel支持的所有的中断控制器的ID信息.
struct of_device_id 这个数据结构主要被用来进行Device node和driver模块进行匹配用的。从该数据结构的定义可以看出,在匹配过程中,device name、device type和DT compatible string都是考虑的因素。更细节的内容请参考__of_device_is_compatible函数。
(2)device node
不同的GIC-V2的实现总会有一些不同,这些信息可以通过Device tree的机制来传递。可以通过查看Documentation/devicetree/bindings/arm/gic.txt文件来确认配置规则。
以cortex-a9-gic为例。
Example:
intc: interrupt-controller@fff11000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
#address-cells = <1>;
interrupt-controller;
reg = <0xfff11000 0x1000>,
<0xfff10100 0x100>;
};
(3)device node和irq chip driver的匹配
在系统启动machine初始化的时候会调用irqchip_init函数进行irq chip driver的初始化。在driver/irqchip/irqchip.c文件中定义了irqchip_init函数,如下:
void __init irqchip_init(void)
{
of_irq_init(__irqchip_begin);
}
__irqchip_begin就是内核irq chip table的首地址,这个table也就保存了kernel支持的所有的中断控制器的of_device_id信息。of_irq_init函数执行之前,系统已经完成了device tree的初始化,因此系统中的所有的设备节点都已经形成了一个树状结构,每个节点代表一个设备的device node。of_irq_init是在所有的device node中寻找中断控制器节点,形成树状结构(系统可以有多个interrupt controller,之所以形成中断控制器的树状结构,是为了让系统中所有的中断控制器驱动按照一定的顺序进行初始化)。之后,从root interrupt controller节点开始,对于每一个interrupt controller的device node,扫描irq chip table,进行匹配,一旦匹配到,就调用该interrupt controller的初始化函数,并把该中断控制器的device node以及parent中断控制器的device node作为参数传递给irq chip driver。更详细的信息可以参考Device Tree代码分析文档。
2.驱动代码
当设备device node和irq chip driver匹配以后,我们就进入函数gic_of_init开始了GIC的初始化工作。
#ifdef CONFIG_OF
static int gic_cnt __initdata;
int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *cpu_base;
void __iomem *dist_base;
u32 percpu_offset;
int irq;
if (WARN_ON(!node))
return -ENODEV;
dist_base = of_iomap(node, 0);
WARN(!dist_base, "unable to map gic dist registers\n");
cpu_base = of_iomap(node, 1);
WARN(!cpu_base, "unable to map gic cpu registers\n");
if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
percpu_offset = 0;
gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
if (!gic_cnt)
gic_init_physaddr(node);
if (parent) {
irq = irq_of_parse_and_map(node, 0);
gic_cascade_irq(gic_cnt, irq);
}
gic_cnt++;
return 0;
}
这个函数调用的最关键的函数就是gic_init_bases,它完成了主要的工作,其中就包括了irq domain的注册,通过irq_domain_add_legacy完成了注册过程,主要就是建立hwirq和内核中的irq num之间的映射关系。之后就是irq domain来负责对中断号进行转换并处理了。
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;
BUG_ON(gic_nr >= MAX_GIC_NR);
gic = &gic_data[gic_nr];
#ifdef CONFIG_GIC_NON_BANKED
if (percpu_offset) { /* Frankein-GIC without banked registers... */
unsigned int cpu;
gic->dist_base.percpu_base = alloc_percpu(void __iomem *);
gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);
if (WARN_ON(!gic->dist_base.percpu_base ||
!gic->cpu_base.percpu_base)) {
free_percpu(gic->dist_base.percpu_base);
free_percpu(gic->cpu_base.percpu_base);
return;
}
for_each_possible_cpu(cpu) {
unsigned long offset = percpu_offset * cpu_logical_map(cpu);
*per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset;
*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset;
}
gic_set_base_accessor(gic, gic_get_percpu_base);
} else
#endif
{ /* Normal, sane GIC... */
WARN(percpu_offset,
"GIC_NON_BANKED not enabled, ignoring %08x offset!",
percpu_offset);
gic->dist_base.common_base = dist_base;
gic->cpu_base.common_base = cpu_base;
gic_set_base_accessor(gic, gic_get_common_base);
}
/* * Initialize the CPU interface map to all CPUs. * It will be refined as each CPU probes its ID. */
for (i = 0; i < NR_GIC_CPU_IF; i++)
gic_cpu_map[i] = 0xff;
/* * For primary GICs, skip over SGIs. * For secondary GICs, skip over PPIs, too. */
if (gic_nr == 0 && (irq_start & 31) > 0) {
hwirq_base = 16;
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32;
}
/* * Find out how many interrupts are supported. * The GIC only supports up to 1020 interrupt sources. */
gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
gic_irqs = (gic_irqs + 1) * 32;
if (gic_irqs > 1020)
gic_irqs = 1020;
gic->gic_irqs = gic_irqs;
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());
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,
hwirq_base, &gic_irq_domain_ops, gic);
if (WARN_ON(!gic->domain))
return;
#ifdef CONFIG_SMP
set_smp_cross_call(gic_raise_softirq);
register_cpu_notifier(&gic_cpu_notifier);
#endif
set_handle_irq(gic_handle_irq);
gic_chip.flags |= gic_arch_extn.flags;
gic_dist_init(gic);
gic_cpu_init(gic);
gic_pm_init(gic);
}
作为一个interrupt controller,除了注册自己管理的irq_domain,还需要提供给上级使用的irq_handler,如果作为second GIC,上级是root GIC,那么就需要调用irq_set_chained_handler注册irq_handler到root GIC中;如果作为root GIC,上级是CPU,就需要调用set_handle_irq(gic_handle_irq)把这个irq_handler注册到平台的irq处理接口中,这条语句的功能就是,当CPU发生了中断最先调用的就是root GIC的gic_handle_irq,然后在此函数中进行gic的irq domain处理。
三、irq domain的注册
Irq_domain的注册,需要一个irq_domain_ops的结构体,我们来看一下他的定义:
static const struct irq_domain_ops gic_irq_domain_ops = {
.map = gic_irq_domain_map,
.unmap = gic_irq_domain_unmap,
.xlate = gic_irq_domain_xlate,
};
对于struct irq_domain_ops,它有如下几个callback成员:
struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
void (*unmap)(struct irq_domain *d, unsigned int virq);
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
};
xlate是负责翻译的回调函数,在dts文件中,各个设备通过一些属性,例如interrupts和interrupt-parent来提供中断信息给kernel和驱动,而xlate函数就是将指定的设备上若干个中断属性翻译成hwirq和trigger类型,比如对于#interrupt-cells = <3>;的中断控制器来说,描述该域中的一个interrupt需要三个cell来表示,那么这三个cell就是通过xlate来解析的。
match用来判断interrupt controller是否和一个irq domain匹配的,如果是就返回1。实际上,该callback函数很少被设置,内核中提供了默认的匹配函数,就是通过of node来进行匹配的(irq_domain结构体中会保存一个of node)。
map和unmap是映射和解除映射操作。Map回调函数是在创建hwirq到irq num关系的时候被调用的,注册irq domain只是一个空的关系表,而这个是实质上关系的创建是在irq_of_parse_and_map里面进行的。在map回调函数中,我们一般需要做如下几个操作:
irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
irq_set_chip_data(irq, d->host_data);
其中irq_set_chip_and_handler函数是用来设置irq chip和相应的上层irq handler的,一般内核中断子系统已经实现了相应的函数,我们只需要按需赋值即可,它负责对一个irq num调用所有通过irq_request注册的irq handler.我们称之为上层中断服务程序。
以上注册的回调函数的调用流程如下所示:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
if (of_irq_parse_one(dev, index, &oirq))----分析device node中的interrupt相关属性
return 0;
return irq_create_of_mapping(&oirq);-----创建映射
}
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
struct irq_domain *domain;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
unsigned int virq;
domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;
if (!domain) {
return 0;
}
if (domain->ops->xlate == NULL)
hwirq = irq_data->args[0];
else {
if (domain->ops->xlate(domain, irq_data->np, irq_data->args,
irq_data->args_count, &hwirq, &type))
return 0;
}
/* Create mapping */
virq = irq_create_mapping(domain, hwirq);
if (!virq)
return virq;
/* Set type if specified and different than the current one */
if (type != IRQ_TYPE_NONE &&
type != irq_get_trigger_type(virq))
irq_set_irq_type(virq, type);
return virq;
}
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
unsigned int hint;
int virq;
virq = irq_find_mapping(domain, hwirq); //如果映射已经存在,那么不需要映射,直接返回
if (virq) {
return virq;
}
hint = hwirq % nr_irqs;-------分配一个IRQ 描述符以及对应的irq number
if (hint == 0)
hint++;
virq = irq_alloc_desc_from(hint, of_node_to_nid(domain->of_node));
if (virq <= 0)
virq = irq_alloc_desc_from(1, of_node_to_nid(domain->of_node));
if (virq <= 0) {
pr_debug("-> virq allocation failed\n");
return 0;
}
if (irq_domain_associate(domain, virq, hwirq)) {---建立mapping
irq_free_desc(virq);
return 0;
}
return virq;
}
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq)
{
struct irq_data *irq_data = irq_get_irq_data(virq);
int ret;
mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (domain->ops->map) {
ret = domain->ops->map(domain, virq, hwirq);---调用irq domain的map callback函数
}
if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq;----填写线性映射lookup table的数据
} else {
mutex_lock(&revmap_trees_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);--向radix tree插入一个node
mutex_unlock(&revmap_trees_mutex);
}
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST); ---该IRQ已经可以申请了,因此clear相关flag
return 0;
}
四、中断调用流程
上面已经提到,一个root gic驱动除了提供irq domain以外,还要注册到CPU中断服务程序入口,而这个中断服务的入口就是gic_handle_irq。
asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & ~0x1c00;
if (likely(irqnr > 15 && irqnr < 1021)) {
irqnr = irq_find_mapping(gic->domain, irqnr);
handle_IRQ(irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
如上所示,中断来的时候会最先调用这个函数,它中会读取GIC寄存器获得hwirq,并且查找对应的irq num,irq_find_mapping是查找irq domain中映射关系的关键函数。然后会调用handle_IRQ来处理对应的irq num,紧接着会调用相应的上层irq handler。