http://www.cnblogs.com/pengdonglin137/p/6349209.html 基於tiny4412的Linux內核移植 — 实例学习中断背后的知识(1)
http://www.cnblogs.com/pengdonglin137/p/6848851.html 基于设备树的TQ2440的中断(2)
http://blog.csdn.net/tiantao2012/article/details/52232490?locationNum=4 gic v3
http://blog.csdn.net/sunsissy/article/details/73882718 gic 的介绍
一,主中断控制器(root层)
中断的发生流程:
当中断信号发生的时候,会跳转到汇编代码entry-armv.S
中__irq_svc
处,会进入中断异常,此时pc指针指向中断向量表,即执行handle_arch_irq
函数指针,
arch/arm/kernel/entry-armv.S
41 .macro irq_handler
42 #ifdef CONFIG_MULTI_IRQ_HANDLER
43 ldr r1, =handle_arch_irq
44 mov r0, sp
45 badr lr, 9997f
46 ldr pc, [r1]
47 #else
48 arch_irq_handler_default
49 #endif
这是所有中断的总入口,每个中断都要进入这个函数。这个函数会在相应的root 中断处理器driver初始化中会被赋值,在gic中为set_handle_irq(gic_handle_irq);
即 handle_arch_irq = gic_handle_irq
,不同的主中断处理器driver会有不同的入口函数实现,所以中断的总入口被重新定向。
进入handle_arch_irq
,会去读写相应的中断状态寄存器,查看到底是哪个bit即哪个中断线有信号到来,就可以得到实际的hwirq号,通过这个实际的irq号,通过当前主中断号的doamin层的映射关系查找出一个系统中唯一的系统中断号virq(在内核启动的时候,每层中断处理器在自己doamin中所拥有的hwirq和virq已经建立了映射关系,新的dts方式的中断不一定为存在的每一个hwirq映射一个virq,只对在dts中引用这个中断的时候去建立这个映射,不引用的不建立映射关系;不同的domain层可能会有相同的hwirq,但是映射出的virq是系统中唯一的,不会有相同的),然后通过generic_handle_irq(irq_find_mapping(virq);
传入这个唯一的virq,在generic_handle_irq
中通过virq找到virq对应的irq_desc(irq_desc只和virq对应和hwirq没有直接联系,唯一的联系是我们需要通过hw_irq去查找已经映射好的virq),然后将irq_desc执行desc->handle_irq(desc),desc->handle_irq是每个domain实现的用来解析desc的,
然后通过handle_irq(desc)去执行这个desc中的irq_data中的irq_action->handle,即通过request_irq()来注册的中断处理函数
desc->handle_irq
:这个函数用来触发和virq绑定的中断处理事件,这个函数一定要用本级实现的函数去重新赋值,否则是空的
desc->handle_irq的初始话,在建立映射表的时候通过irq_domain_alloc_irqs_recursive
去调用ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
然后call到该层中断控制器的domain的ops的alloc函数。
gic driver中的实现:
-->gic_irq_domain_alloc
-->gic_irq_domain_map
-->irq_domain_set_info//在这个函数中将handle_fasteoi_irq赋值给desc->handle_irq
desc->handle_irq = handle_fasteoi_irq
//所以上面的generic_handle_irq会call到各个中断控制器实现的的desc->handle_irq,gic是handle_fasteoi_irq,这个函数的调用如下
handle_fasteoi_irq的调用
-->handle_fasteoi_irq
-->handle_irq_event
-->handle_irq_event_percpu
-->res = action->handler(irq, action->dev_id);//这里可以看到执行了中断处理事件,这个action->handler就是通过request_irq注册的与该virq绑定的中断处理事件
此时整个的一个中断触发处理流程完毕。
hw_irq和virq的映射过程:
dts方式的中断使用,其hw_irq和virq的映射过程是在解析dts的时候就已经完成的
先看下调用栈:
SPI中断(相对应的是PPI和SGI中断)的调用栈
[<c30174b4>] (unwind_backtrace) from [<c3013afc>] (show_stack+0x10/0x14)
[<c3013afc>] (show_stack) from [<c3225284>] (dump_stack+0x84/0x98)
[<c3225284>] (dump_stack) from [<c324be00>] (gic_irq_domain_alloc+0x28/0x7c)
[<c324be00>] (gic_irq_domain_alloc) from [<c3020d7c>] (tech_irq_domain_alloc+0x154/0x158)
[<c3020d7c>] (tech_irq_domain_alloc) from [<c306ca34>] (__irq_domain_alloc_irqs+0x130/0x324)
[<c306ca34>] (__irq_domain_alloc_irqs) from [<c306cd9c>] (irq_create_fwspec_mapping+0x174/0x1f8)
[<c306cd9c>] (irq_create_fwspec_mapping) from [<c306ce74>] (irq_create_of_mapping+0x54/0x5c)
[<c306ce74>] (irq_create_of_mapping) from [<c339d4f4>] (irq_of_parse_and_map+0x24/0x2c)
[<c339d4f4>] (irq_of_parse_and_map) from [<c339d514>] (of_irq_to_resource+0x18/0xbc)
[<c339d514>] (of_irq_to_resource) from [<c339d5f4>] (of_irq_to_resource_table+0x3c/0x54)
[<c339d5f4>] (of_irq_to_resource_table) from [<c339aad0>] (of_device_alloc+0x108/0x1a4)
[<c339aad0>] (of_device_alloc) from [<c339abb4>] (of_platform_device_create_pdata+0x48/0xc4)
[<c339abb4>] (of_platform_device_create_pdata) from [<c339ad38>] (of_platform_bus_create+0xfc/0x214)
[<c339ad38>] (of_platform_bus_create) from [<c339ad98>] (of_platform_bus_create+0x15c/0x214)
[<c339ad98>] (of_platform_bus_create) from [<c339afd0>] (of_platform_populate+0x5c/0xac)
[<c339afd0>] (of_platform_populate) from [<c36f9888>] (customize_machine+0x20/0x40)
[<c36f9888>] (customize_machine) from [<c300972c>] (do_one_initcall+0xc0/0x200)
[<c300972c>] (do_one_initcall) from [<c36f8d8c>] (kernel_init_freeable+0x150/0x1e0)
[<c36f8d8c>] (kernel_init_freeable) from [<c3528550>] (kernel_init+0xc/0xe0)
[<c3528550>] (kernel_init) from [<c3010138>] (ret_from_fork+0x14/0x3c)
在内核启动期间解析设备树,对于使用了中断的节点而言,不论是使用主中断还是级联的子中断,将device_node转换为platform_device的时候会处理节点中的interrupts属性,将其转换为irq resource,同时在所属的irq domain中建立起hwirq到virq的映射,下面列出主要的函数调用
of_platform_default_populate_init
---> of_platform_default_populate
---> of_platform_populate
---> of_platform_bus_create
---> of_platform_device_create_pdata
---> of_device_alloc
---> of_irq_to_resource_table
---> of_irq_to_resource
---> irq_of_parse_and_map
---> of_irq_parse_one//仅仅是将解析出的该节点的中断相关信息进行检测和二次存储
---> irq_create_of_mapping/
---> irq_create_fwspec_mapping
---> irq_domain_translate // 解析参数,通过已经解析出的该节点的使用中断的信息,去得到hw_irq,即dts中传入的以gic16号中断为0号硬件中断的硬件中断号(这里默认指gic中断处理器,其他中断处理器这个偏移根据具体的driver而定)这个函数会回调属于当前doamin的ops结构体中的 translate成员函数
---> irq_create_mapping // 创建hwirq到virq的映射,返回一个系统中唯一的virq,同时将这种映射关系存入当前层的domainde linear_revmap[]中,并且建立该virq和irq_desc的绑定
---irq_domain_alloc_irqs//或者走这个分支,利用bitmap算法分配一个唯一的virq号
---> irq_domain_alloc
--->irq_domain_alloc_descs这个里面会同时建立virq 和irq_desc 的绑定
但是要注意,hwirq仅仅在所处的irq_domain或者说irq_chip内才有意义,不同的irq_domain可能会有相同的hwirq,比如gpx2_2的hwirq也是2,但是每一个hwirq对应的virq是系统唯一的,virq其实就是全局变量
各层中断处理器的driver:
看到上面hw_irq和virq的映射过程可以看到,整个映射过程是需要各层的中断处理器driver支持的。中断处理器driver中重要的一点是实现属于该中断处理器的doamin,去处理hw_irq
和virq的转化关系
外设对各层中断的使用:
如果一个外设要使用一个中断,那么就要在该外设的dts节点中知名使用的哪一级的中断处理器,同时按照这一即的中断处理器的格式要求去引用需要的中断号。
此外设driver中 ,需要使用 下列操作去获取virq,然后使用这个virq使用request_irq()去注册和这个中断号关联的中断处理函数.
irq1 = platform_get_irq(pdev, 0);//获取该节点引用的第一个节点
irq2 = platform_get_irq(pdev, 1);//获取该节点引用的第二个节点
platform_get_irq这里有一个hwirq映射为virq的关键操作:
-->platform_get_irq
-->of_irq_get
-->of_irq_parse_one仅仅是将解析出的该节点的中断相关信息进行检测和二次存储
-->irq_create_of_mapping
---> irq_create_fwspec_mapping
--> irq_domain_translate // 解析参数,通过已经解析出的该节点的使用中断的信息,去得到hw_irq
-->irq_find_mapping//其实可以看到整个流程和之前建立hw_irq和virq的过程有点类似,唯一的区别在这里
在进入 irq_create_fwspec_mapping函数通过 irq_domain_translate已经得到了hw_irq,此时会调用irq_find_mapping函数,这个函数会根据传入的domain和hw_irq去寻找该domain层和这个
hw_irq所对应的virq,如果之前我们已经建立过了这种映射,即已经在dts的解析阶段建立了映射,则会在domain中查找到一个virq(此时不是分配,是根据之前的映射结果去查找)
同样的对于上面的"hw_irq和virq的映射过程"也会执行到这个函数,但是那个时候对于这个hw_irq因为还没有建立映射它会查找不到一个virq,所以会走else,执行irq_create_mapping去分配一个virq
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
struct irq_domain *domain;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
int virq;
if (fwspec->fwnode)
//找到当前的中断所依附的domain
domain = irq_find_matching_fwnode(fwspec->fwnode, DOMAIN_BUS_ANY);
else
domain = irq_default_domain;
if (!domain) {
pr_warn("no irq domain found for %s !\n",
of_node_full_name(to_of_node(fwspec->fwnode)));
return 0;
}
//回调当前domain的ops的translate函数得到该中断的硬件中断号
if (irq_domain_translate(domain, fwspec, &hwirq, &type))
return 0;
if (irq_domain_is_hierarchy(domain)) {
/*
* If we've already configured this interrupt,
* don't do it again, or hell will break loose.
*/
//节点的映射有在dts解析的时候按照各个节点的dts引用中断的情况会进入该函数,
//会先执行irq_find_mapping,因为没有映射过,直接返回,进入irq_create_mapping去建立该中断的映射关系
//第二次,当外设driver去解析自己节点的中断的时候,就会进入在隔离去找对应的软中断号,进入
//irq_find_mapping函数,因为之前已经映射好了,所以可以找到virq,直接返回
//这里当有节点去获取软件中断号的时候,先在这里找一下,如果之前已经有对该
//中断号做了map,那么先找一下,找到了就直接返回
//通过硬件中断号找到软件中断号,如果是0代表没找到
virq = irq_find_mapping(domain, hwirq);
if (virq)
return virq;
printk("%s %d\n",__FUNCTION__,__LINE__);
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
if (virq <= 0)
return 0;
} else {
/* Create mapping */
printk("%s %d\n",__FUNCTION__,__LINE__);
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;
}
二,特殊的级联中段(子中断控制器)
级联中断最经典的例子就是gpio中断的例子了,因为节省主中断控制器的中断线的原因(中断线也是资源),会将所有的gpio接到一个主中断线中,即所有的gpio中断信号都汇聚到一个主中断假设是以gic的16号中断为起始的0号中断。但是soc这一层有gpio的中断控制器,硬件层面,当gpio的某个pin有中断发生时,中断信号都会触发0号中断。然后0号中断去执行该中断绑定的中断处理函数,那如何知道是哪个pin呢,可以该层被触发的中断处理函数中去check gpio的中断状态寄存器,就可以知道是哪个pin有中断来了
既然硬件支持级联中断,那么每个pin就可以当作一个中断源,我们就可以为每一个pin绑定一个中断处理函数,像普通中断那样使用。但是怎么使用?
这里就要将gpio的中断处理器以级联中断的方式在系统中生成一个子中断控制器,gic是主中断控制器。
基本思路是给每一组gpio建立一个中断控制器抽象,同时建立属于该中断控制器的domain。每32个gpio为一组,每一组都有从0开始的hw_irq,都会在该组的domain映射出一个系统唯一的virq,只要hw_irq和virq的关系建立起来了,我们就可以像使用普通中断一样去使用每个gpio中断了。
看每组的gpio中断控制器的实现:
首先,所有的gpio中断都连在0号(假设是0号,这里的0指的是以gic16号中断为起始)主中断上,所以先作如下操作:
1.vm_gpio->irq = platform_get_irq(pdev, 0);//获取0号主中断的virq
2.ret = devm_request_irq(&pdev->dev, vm_gpio->irq, vm_gpio_irq_cascade,
IRQF_TRIGGER_NONE | IRQF_SHARED, dev_name(&pdev->dev), vm_gpio);
为这个0号中断绑定中断处理函数,因为几组gpio使用的都是同一个0号主中断,故使用共享中断模式,这样每组gpio在初始化的时候都可以注册成功。vm_gpio_irq_cascade是0号中断的中断处理函数
3.vm_gpio->domain = irq_domain_add_linear(dn, vm_gpio->gc.ngpio, &irq_generic_chip_ops, vm_gpio);将改组的domain加入系统
4.ret = irq_alloc_domain_generic_chips(vm_gpio->domain, vm_gpio->gc.ngpio, 1, vm_gpio->gc.label,
handle_edge_irq, IRQ_NOREQUEST, IRQ_NOPROBE, IRQ_GC_INIT_MASK_CACHE);//添加该层的执行中断号绑定的事件的函数,即将handle_edge_irq函数赋值给generic_handle_irq中的desc->handle_irq(desc)。联想中断的发生流程中关于generic_handle_irq的解释
5.各种初始化:
gc->reg_base = vm_gpio->base;
gc->chip_types[0].type = IRQ_TYPE_EDGE_BOTH;
gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
gc->chip_types[0].chip.irq_set_type = vm_gpio_irq_set_type;
gc->chip_types[0].regs.ack = OFFSET_TO_REG_INT_STATUS;
gc->chip_types[0].regs.mask = OFFSET_TO_REG_INT_EN;
6.重要的一点实现:vm_gpio->gc.to_irq = vm_gpio_to_irq;
中断的发生过程:
来看看2中的vm_gpio_irq_cascade:
static irqreturn_t vm_gpio_irq_cascade(int irq, void *data)
{
struct vm_gpio *vm_gpio = data;
u32 r = vm_reg_read(vm_gpio, OFFSET_TO_REG_INT_STATUS);
u32 m = vm_reg_read(vm_gpio, OFFSET_TO_REG_INT_EN);
const unsigned long bits = r & m;
int i = 0;
for_each_set_bit(i, &bits, 32)
generic_handle_irq(irq_find_mapping(vm_gpio->domain, i));
return IRQ_HANDLED;
}
当任意一个gpio中断发生后,就会触发0号主中断,然后会执行该中断的处理函数即这里的vm_gpio_irq_cascade,在这个函数中,读改组的gpio中断状态寄存器,看是哪个bit即哪个gpio有中断到来,即得到了该组中断的hw_irq,(看到了吗,hw_irq只和该层中断有关,即不同层的中断会有相同的硬件中断号,但是映射的virq是系统中唯一的)因为在该组的domain中已经将这个hw_irq分配了相应的virq,即已经建立好了,所以通过irq_find_mapping(vm_gpio->domain, i)即可以查找返回一个属于这个hw_irq的virq,然后再使用
generic_handle_irq(virq)去执行和这个virq相关的一个中断处理函数
hw_irq和virq的关系建立:
和上面主中断控制器中hw_irq和virq的关系建立所描述的,只有当在dts中有其他节点引用gpio中断节点和中断号的时候,才会建立该中断号的映射关系。建立方法一致,会将相关信息保存在该组的domain中
gpio中断当作普通中断的的使用:
主控制器的节点表示为:
gic: interrupt-controller@1bf01000 {
compatible = "arm,cortex-a7-gic";
interrupt-controller;
#interrupt-cells = <3>;
reg = <0x1bf01000 0x1000>,
<0x1bf02000 0x1000>,
<0x1bf04000 0x2000>,
<0x1bf06000 0x2000>;
};
而普通外设引用主中断的操作:
timer {
compatible = "arm,armv7-timer";
interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
interrupt-parent=<&igic>;
clock-frequency = <27000000>;
arm,cpu-registers-not-fw-configured;
gpio中的一个组作为一个子中断控制器的节点表示为:
gpio_la: gpio@1808D000 {
compatible = "tech,vm-gpio";
reg = <0x1808D000 0x20>;
offsets = <0x0 0x4 0x8 0xc 0x10 0x14 0x18>;
interrupts = <0 0 4>;
gpio-controller;
#gpio-cells = <2>;
ngpio = <32>;
gpio-base = <0>;
interrupt-parent=<&gic>;//他需要用到主中断
interrupt-controller;
gpio-ranges = <&pinctrl 0 0 15>;
#interrupt-cells = <1>;
};
而普通外设使用goio中断的节点为:
xxx: xx@xxx {
compatible = "tech,xxx";
reg = <0x1808D000 0x20>;
interrupts = <2 4>;//使用了改组的2号gpio中断
interrupt-parent=<&gpio_la>;
#interrupt-cells = <1>;
};
在使用gpio中断的driver中,首先使用gpio_to _irq回调到该gpio组的vm_gpio_to_irq这个函数,得到一个virq
static int vm_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
{
struct vm_gpio *vm_gpio = to_vm_gpio(chip);
return irq_create_mapping(vm_gpio->domain, offset);//其中的offset就是该组gpio的哪个bit,在这里就是该组的哪个hw_irq,因为之前已经映射好了,所以这里直接使用irq_create_mapping就可以查找出在这个属于这个组的中断控制器的domain中的virq
}
然后再去使用这个系统的virq去注册一个中断事件
问题:对于级联的中断,中断的触发总是先走主中断的入口函数,然后找出主中断层的hwirq,然后找到virq,然后通过generic_handle_irq去执行刚virq绑定的事件,那级联的怎么执行到呢?
思路是这样的,对于级联的中断,先触发的是级联中断所连的主中断的事件,在这个事件中,去找到级联层的中断号,并使用级联层domain找到这个中断号对应的virq,再去使用
generic_handle_irq
去call到该层的desc_handle函数执行这个virq绑定的事件
老的方式在start_kernel中先对中断做了先期初始化,新的4.4的内核虽然使用了,但是好像没有用来初始化静态表,
好像新的内核就没有使用静态表,是动态的方式
asmlinkage void __init start_kernel(void)
{
……
trap_init();
……
early_irq_init();
init_IRQ();
……
}
early_irq_init();中3.多的内核用与初始化静态的中断号描述符表格;不过ARM体系没有实现arch_early_irq_init。
int __init early_irq_init(void)
{
int count, i, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc); //irq_descwei
for (i = 0; i < count; i++) {
desc[i].kstat_irqs = alloc_percpu(unsigned int);
alloc_masks(&desc[i], GFP_KERNEL, node);
raw_spin_lock_init(&desc[i].lock);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
desc_set_defaults(i, &desc[i], node, NULL);
}
return arch_early_irq_init();
}
在内核启动期间解析设备树,将device_node转换为platform_device的时候会处理节点中的interrupts属性,将其转换为irq resource,同时在所属的irq domain中建立起hwirq到virq的映射,下面列出主要的函数调用,期间就会调用上面中断控制器的irq_domain的s3c24xx_irq_ops_of中的xlate和map:
of_platform_default_populate_init
---> of_platform_default_populate
---> of_platform_populate
---> of_platform_bus_create
---> of_platform_device_create_pdata
---> of_device_alloc
---> of_irq_to_resource_table
---> of_irq_to_resource
---> irq_of_parse_and_map
---> of_irq_parse_one
---> irq_create_of_mapping
---> irq_create_fwspec_mapping
---> irq_domain_translate // 解析参数
---> s3c24xx_irq_xlate_of
---> irq_create_mapping // 创建hwirq到virq的映射
---> irq_domain_associate
---> s3c24xx_irq_map_of
但是要注意,hwirq仅仅在所处的irq_domain或者说irq_chip内才有意义,不同的irq_domain可能会有相同的hwirq,比如gpx2_2的hwirq也是2,但是每一个hwirq对应的virq是系统唯一的,virq其实就是全局变量