interrupt

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其实就是全局变量

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章