interrupt

xiaoxiao2021-02-28  23

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

转载请注明原文地址: https://www.6miu.com/read-2629634.html

最新回复(0)