中断是处理器用于异步处理外围设备请求的一种机制,可以说中断处理是操作系统管理外围设备的基石,此外系统调度、核间交互等都离不开中断,它的重要性不言而喻,本文结整理 linux 中断相关知识点。注意:本文只讨论 arm 架构,对于其他架构的不做深入研究。
一、数据结构
1、irq_domain
用于建立中断控制器的 hwirq 与 virq 之间的映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct irq_domain { struct list_head link ; const char *name; const struct irq_domain_ops *ops ; void *host_data; unsigned int flags; struct fwnode_handle *fwnode ; enum irq_domain_bus_token bus_token ; struct irq_domain_chip_generic *gc ; #ifdef CONFIG_IRQ_DOMAIN_HIERARCHY struct irq_domain *parent ; #endif irq_hw_number_t hwirq_max; unsigned int revmap_direct_max_irq; unsigned int revmap_size; struct radix_tree_root revmap_tree ; unsigned int linear_revmap[]; };
2、irq_chip
中断控制器描述符,用来描述中断控制器的相关操作函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct irq_chip { struct device *parent_device ; const char *name; unsigned int (*irq_startup) (struct irq_data *data) ; void (*irq_shutdown)(struct irq_data *data); void (*irq_enable)(struct irq_data *data); void (*irq_disable)(struct irq_data *data); void (*irq_ack)(struct irq_data *data); void (*irq_mask)(struct irq_data *data); void (*irq_mask_ack)(struct irq_data *data); ...... void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest); unsigned long flags; };
2、irq_chip
virq 中断描述符,每一个 virq 都有一个关联的 irq_desc 用来保存该 virq 相关信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct irq_desc { struct irq_common_data irq_common_data ; struct irq_data irq_data ; unsigned int __percpu *kstat_irqs; irq_flow_handler_t handle_irq; #ifdef CONFIG_IRQ_PREFLOW_FASTEOI irq_preflow_handler_t preflow_handler; #endif struct irqaction *action ; ...... int parent_irq; struct module *owner ; const char *name; } ____cacheline_internodealigned_in_smp;
3、irq_data
用来保存中断信息
1 2 3 4 5 6 7 8 9 10 struct irq_data { u32 mask; unsigned int irq; unsigned long hwirq; ... struct irq_chip *chip ; struct irq_domain *domain ; ... void *chip_data; };
4、irqaction
中断操作函数描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct irqaction { irq_handler_t handler; void *dev_id; void __percpu *percpu_dev_id; struct irqaction *next ; irq_handler_t thread_fn; struct task_struct *thread ; struct irqaction *secondary ; unsigned int irq; unsigned int flags; unsigned long thread_flags; unsigned long thread_mask; const char *name; struct proc_dir_entry *dir ; } ____cacheline_internodealigned_in_smp;
二、linux 中断架构
linux 内核的中断架构采用级联的方式,一般情况如下所示。
采用这种级联的方式可以很大程度的节省硬件资源,而 arm cpu 就采用了 irq/fiq 两根物理信号线来连接。上图紫色的部分在 ARM 中称为 GIC (Generic Interrupt Controller),到目前已经更新到 v4 版本了。GIC v3/v4 用于 ARMv8 架构,即 64 位 ARM 芯片。而 GIC v2 用于 ARMv7 和其他更低的架构。
linux 对中断的处理有两个原则,中断不能嵌套,即在 linux 中没有优先级 、中断的处理要越快越好, 因此中断的处理就被分为上半部和下半部,对于中断的下半部的实现方式有三种,软中断、工作队列、中断线程化。
三、arm 异常处理
中断属于异常的一种,arm 具有七种异常
异常
含义
arm工作模式
偏移地址
rest
复位异常
svc模式
0x00
swi
软中断异常
svc模式
0x04
undefine
未定义指令异常
und模式
0x08
prefetch abort
取指异常源
abort模式
0x0c
data abort
数据异常
abort模式
0x10
reserved
reserved
reserved
0x14
irq
中断异常
IRQ模式
0x18
fiq
快中断异常
FIQ模式
0x1c
这七种异常构建的表格就叫做异常向量表 ,如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 //arch/arm/kernel/entry-armv.S .section .vectors, "ax", %progbits .L__vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, .L__vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq // arch/arm/kernel/vmlinux.lds.S __vectors_start = .; .vectors 0xffff0000 : AT(__vectors_start) { *(.vectors) } . = __vectors_start + SIZEOF(.vectors); __vectors_end = .;
由上述代码也可以知道异常向量表被放在了 .vectors 这个段,也就是 __vectors_start 到 __vectors_end 这段内存空间。arm 支持两个固定的位置存放这个表,分别为 0x00000000 和 0xFFFF0000 。在操作系统中一般使用第二个地址。因此这个向量表在运行的时候需要被放入到 0xFFFF0000 这个地址,在 linux 中一般情况是不会直接使用物理地址。因此 0xFFFF0000 在内核中只是一个虚拟地址 ,整个向量表的构建流程如下。
1 2 3 4 5 6 7 8 9 10 // arch/arm/kernel/head.S bl __create_page_tables // 创建页表 ldr r13, =__mmap_switched // 当使能 mmu 之后将跳转到 __mmap_switched 1: b __enable_mmu // 使能 mmu // arch/arm/kernel/head-common.S __mmap_switched: --> start_kernel --> paging_init --> devicemaps_init 最终调用到这个函数
首先创建页表,在使能 mmu 之后跳转到 __mmap_switched,在经过上述调用链,最终调用到 devicemaps_init 这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static void __init devicemaps_init (const struct machine_desc *mdesc) { struct map_desc map ; unsigned long addr; void *vectors; vectors = early_alloc(PAGE_SIZE * 2 ); early_trap_init(vectors); ...... map .pfn = __phys_to_pfn(virt_to_phys(vectors)); map .virtual = 0xffff0000 ; map .length = PAGE_SIZE; #ifdef CONFIG_KUSER_HELPERS map .type = MT_HIGH_VECTORS; #else map .type = MT_LOW_VECTORS; #endif create_mapping(&map ); if (!vectors_high()) { map .virtual = 0 ; map .length = PAGE_SIZE * 2 ; map .type = MT_LOW_VECTORS; create_mapping(&map ); } map .pfn += 1 ; map .virtual = 0xffff0000 + PAGE_SIZE; map .length = PAGE_SIZE; map .type = MT_LOW_VECTORS; create_mapping(&map ); ...... }
具体的复制过程也不复杂就是将 __vectors_start 到 __vectors_end 的段内数据,即异常向量表,复制到 vectors 也就是前面分配的 8k 物理空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 void __init early_trap_init (void *vectors_base) { #ifndef CONFIG_CPU_V7M ...... memcpy ((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy ((void *)vectors + 0x1000 , __stubs_start, __stubs_end - __stubs_start); kuser_init(vectors_base); #endif }
整个流程总结如下
在内核中申请一片 8k 物理内存空间
将向量表从 .vectors 这个段搬到这个 8k 物理内存空间
将虚拟地址 0xffff0000 映射到前面的 8k 物理内存空间
至此为止异常向量的构建也就完成了,当 cpu 触发异常,就会跳转到这个地址,并根据不同的异常执行不同偏移地址的代码,如果是中断产生的异常则会执行 0xFFFF0018 处的代码。即 vector_irq 这个标号处执行。这个标号为宏代码块如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 //arch/arm/kernel/entry-armv.S macro vector_stub, name, mode, correction=0 .align 5 vector_\name: // 将 name 替换为 irq 就是 vector_irq, 因此跳转到这里执行 .if \correction sub lr, lr, #\correction .endif @ @ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr // 读取 spsr str lr, [sp, #8] @ save spsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ mrs r0, cpsr eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) msr spsr_cxsf, r0 @ @ the branch table must immediately follow this code @ and lr, lr, #0x0f THUMB( adr r0, 1f ) THUMB( ldr lr, [r0, lr, lsl #2] ) mov r0, sp ARM( ldr lr, [pc, lr, lsl #2] ) movs pc, lr @ branch to handler in SVC mode ENDPROC(vector_\name) ....... vector_stub irq, IRQ_MODE, 4 .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .long __irq_svc @ 3 (SVC_26 / SVC_32) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 .long __irq_invalid @ 7 .long __irq_invalid @ 8 .long __irq_invalid @ 9 .long __irq_invalid @ a .long __irq_invalid @ b .long __irq_invalid @ c .long __irq_invalid @ d .long __irq_invalid @ e .long __irq_invalid @ f
当发生中断会根据中断时所处的模式调用不同的代码,如果是用户态则调用 __irq_usr 内核态调用 __irq_svc ,整个流程如下图所示。
可以看出无论什么情况下都会调用 handle_arch_irq 这个函数,这个函数由连接到该 cpu 的中断控制器提供,对于 arm 架构他由 gic 提供。
四、gic 通用中断控制器
1、基本架构
ARM 的 cpu 使用 gic 来进行管理,gicv2 最多能支持 8 core ,架构如下
可以看出 gicv2 的组件主要由三部分分组成:
Distributor 采集所有中断源,对全局中断的使能控制、启用或禁止每个中断、设置每个中断的优先级、设置每个中断的目标处理器列表、设置中断的触发模式为电平(level-sensitive)或跳边沿(edge-triggered)触发、将每个中断设置为 Group 0 或 Group 1、将 SGI 转发到一个或多个目标处理器、每个中断的可见性以及通过软件设置或清除外设中断挂起状态的机制。
CPU interfaces 使能和发送一个具体的中断信号到特定对应的 CPU 上、确认具体中断已经被 CPU 接受,处理,以及处理完成、设置 cpu 能接受的中断的优先级别,以及对应的基于级别的中断抢断等处理。
virtual interface control 将 GICD 发送的虚拟中断信息,通过VIRQ,VFIQ 管脚,传输给 core
从上图也可以看出,它将中断分为三类:
SGI(Software Generated Interrupt) , 软件产生的中断,中断号范围 0 - 15 ,也就是最前的 16 个中断,它被用来进行核间通信。
PPI(Private Peripheral Interrupt) , 私有物理中断,中断号范围 16 - 31 ,被 cpu 独占的中断,产生的中断直接发给对应的 cpu。
SPI(Shared peripheral Interrupt) , 这是常见的外部设备中断,硬件中断号范围 32 - 1019 ,也称为共享中断,比如按键触发一个中断,手机触摸屏触发的中断,共享的意思是说可以多个 cpu 或者说 core 处理,不限定特定的 cpu。
2、gic 中断触发流程
中断信号先到 Distributor, 然后根据设定目标 CPU,送到对应的 CPU Interface 上,在这里仲裁是否优先级足够高,是否可以抢断或者打断当前的终端处理等,如果可以,那么 CPU Interface 就发送一个物理的信号到 CPU 的 IRQ/FIQ 接线上,CPU 感知到中断信号触发 cpu 的异常,CPU 进入异常处理模式处理对应的中断。区别在于 SGI 的触发是处理器向 Distributor 中的 GICD_SGIR 寄存器写值来发出 SGI。而 SPI 和 PPI 则是由连接到 GIC 上具体的物理信号信号线发出的电平(跳边沿) 触发。
3、git 中断控制器初始化
由前面可以知道 cpu 触发异常之后最终会调用 handle_arch_irq 这个函数,这个函数由 gic 提供,内核 gic 节点的 dts 代码如下。
1 2 3 4 5 6 7 8 interrupt-controller@00 a01000 { compatible = "arm,cortex-a7-gic" ; #interrupt-cells = <0x3> ; interrupt-controller; reg = <0xa01000 0x1000 0xa02000 0x100 >; linux,phandle = <0x1e >; phandle = <0x1e >; };
在内核中搜索 arm,cortex-a7-gic 得到如下结果。
1 2 3 4 5 6 7 8 9 10 11 12 IRQCHIP_DECLARE(gic_400, "arm,gic-400" , gic_of_init); IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic" , gic_of_init); IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic" , gic_of_init); 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(cortex_a7_gic, "arm,cortex-a7-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); IRQCHIP_DECLARE(pl390, "arm,pl390" , gic_of_init);
这些宏怎么和 dts 进行匹配呢,匹配方式如下所示。
1) IRQCHIP_DECLARE 宏分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn) #define OF_DECLARE_2(table, name, compat, fn) \ _OF_DECLARE(table, name, compat, fn, of_init_fn_2) #if defined(CONFIG_OF) && !defined(MODULE) #define _OF_DECLARE(table, name, compat, fn, fn_type) \ static const struct of_device_id __of_table_##name \ __used __section(__##table##_of_table) \ = { .compatible = compat, \ .data = (fn == (fn_type)NULL) ? fn : fn } #else #define _OF_DECLARE(table, name, compat, fn, fn_type) \ static const struct of_device_id __of_table_##name \ __attribute__((unused)) \ = { .compatible = compat, \ .data = (fn == (fn_type)NULL) ? fn : fn } #endif
IRQCHIP_DECLARE 的宏展开将得到一个 of_device_id 结构,将前面的宏 IRQCHIP_DECLARE(cortex_a7_gic, “arm,cortex-a7-gic”, gic_of_init); 展开得到下面的结构体。
1 2 3 4 5 6 static const struct of_device_id __of_table_cortex_a7_gic __used __section(__irqchip_of_table) // 结构被放到了 __irqchip_of_table 这个段 = { .compatible = "arm,cortex-a7-gic", .data = gic_of_init }
由结构体 __of_table_cortex_a7_gic 也可以知道这些结构被放到了 __irqchip_of_table 这个段,在内核中搜索这个段标志,最后在 irqchip_init 中找到。
1 2 3 4 5 6 7 8 9 10 11 static const struct of_device_id irqchip_of_match_end __used __section (__irqchip_of_table_end );extern struct of_device_id __irqchip_of_table [];void __init irqchip_init (void ) { of_irq_init(__irqchip_of_table); acpi_probe_device_table(irqchip); }
我们反过来跟踪一下代码,发现代码的调用流程如下。
1 2 3 4 start_kernel (init) --> init_IRQ --> irqchip_init (drivers/irqchip/irqchip.c) --> of_irq_init (drivers/of/irq.c)
来看看 of_irq_init 究竟干了什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 void __init of_irq_init (const struct of_device_id *matches) { const struct of_device_id *match ; struct device_node *np , *parent = NULL ; struct of_intc_desc *desc , *temp_desc ; struct list_head intc_desc_list , intc_parent_list ; INIT_LIST_HEAD(&intc_desc_list); INIT_LIST_HEAD(&intc_parent_list); for_each_matching_node_and_match(np, matches, &match) { if (!of_find_property(np, "interrupt-controller" , NULL ) || !of_device_is_available(np)) continue ; if (WARN(!match->data, "of_irq_init: no init function for %s\n" , match->compatible)) continue ; desc = kzalloc(sizeof (*desc), GFP_KERNEL); if (WARN_ON(!desc)) { of_node_put(np); goto err; } desc->irq_init_cb = match->data; desc->dev = of_node_get(np); desc->interrupt_parent = of_irq_find_parent(np); if (desc->interrupt_parent == np) desc->interrupt_parent = NULL ; list_add_tail(&desc->list , &intc_desc_list); } while (!list_empty(&intc_desc_list)) { list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list ) { int ret; if (desc->interrupt_parent != parent) continue ; ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent); } }
该函数主要做了下面的事情
在 __irqchip_of_table 这个段中找到和设备树中 interrupt-controller 节点中名字匹配的 of_device_id 结构。
并用匹配成功的 of_device_id 初始化动态创建的 of_intc_desc 并将挂接到 intc_desc_list
调用回调函数 desc->irq_init_cb 也就是前面注册的 gic_of_init
2) 初始化代码分析
由前面的分析可以得到,最终调用 gic_of_init 来对 gic 中断控制器来初始化。
2.1) gic_of_init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 gic_of_init(struct device_node *node, struct device_node *parent) { struct gic_chip_data *gic ; int irq, ret; if (WARN_ON(!node)) return -ENODEV; if (WARN_ON(gic_cnt >= CONFIG_ARM_GIC_MAX_NR)) return -EINVAL; gic = &gic_data[gic_cnt]; ret = gic_of_setup(gic, node); if (ret) return ret; if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base)) static_key_slow_dec(&supports_deactivate); ret = __gic_init_bases(gic, -1 , &node->fwnode); if (ret) { gic_teardown(gic); return ret; } if (!gic_cnt) { gic_init_physaddr(node); gic_of_setup_kvm_info(node); } if (parent) { irq = irq_of_parse_and_map(node, 0 ); gic_cascade_irq(gic_cnt, irq); } if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain); gic_cnt++; return 0 ; }
2.2) __gic_init_bases
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static int __init __gic_init_bases(struct gic_chip_data *gic, int irq_start, struct fwnode_handle *handle) { char *name; int i, ret; if (WARN_ON(!gic || gic->domain)) return -EINVAL; if (gic == &gic_data[0 ]) { for (i = 0 ; i < NR_GIC_CPU_IF; i++) gic_cpu_map[i] = 0xff ; #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); #endif cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING, "AP_IRQ_GIC_STARTING" , gic_starting_cpu, NULL ); set_handle_irq(gic_handle_irq); if (static_key_true(&supports_deactivate)) pr_info("GIC: Using split EOI/Deactivate mode\n" ); } if (static_key_true(&supports_deactivate) && gic == &gic_data[0 ]) { name = kasprintf(GFP_KERNEL, "GICv2" ); gic_init_chip(gic, NULL , name, true ); } else { name = kasprintf(GFP_KERNEL, "GIC-%d" , (int )(gic-&gic_data[0 ])); gic_init_chip(gic, NULL , name, false ); } ret = gic_init_bases(gic, irq_start, handle); if (ret) kfree(name); return ret; }
2.3) gic_init_bases
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = { .translate = gic_irq_domain_translate, .alloc = gic_irq_domain_alloc, .free = irq_domain_free_irqs_top, }; static int gic_init_bases (struct gic_chip_data *gic, int irq_start, struct fwnode_handle *handle) { irq_hw_number_t hwirq_base; int gic_irqs, irq_base, ret; if (handle) { gic->domain = irq_domain_create_linear(handle, gic_irqs, &gic_irq_domain_hierarchy_ops, gic); } else { if (gic == &gic_data[0 ] && (irq_start & 31 ) > 0 ) { hwirq_base = 16 ; if (irq_start != -1 ) irq_start = (irq_start & ~31 ) + 16 ; } else { hwirq_base = 32 ; } gic_irqs -= hwirq_base; irq_base = irq_alloc_descs(irq_start, 16 , gic_irqs, numa_node_id()); if (irq_base < 0 ) { WARN(1 , "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n" , irq_start); irq_base = irq_start; } gic->domain = irq_domain_add_legacy(NULL , gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); } if (WARN_ON(!gic->domain)) { ret = -ENODEV; goto error; } gic_dist_init(gic); ret = gic_cpu_init(gic); if (ret) goto error; ret = gic_pm_init(gic); if (ret) goto error; return 0 ; error: if (IS_ENABLED(CONFIG_GIC_NON_BANKED) && gic->percpu_offset) { free_percpu(gic->dist_base.percpu_base); free_percpu(gic->cpu_base.percpu_base); } return ret; }
2.4) 总结
gic 的初始化做了下面的工作
映射 gic 的寄存器地址,对 gic 的寄存器做一些初始化设置
设置异常处理回调函数 handle_arch_irq = gic_handle_irq
为中断控制器创建一个中断域,使用线性映射方式进行初始化
设置中断域映射方式的回调函数 gic_irq_domain_hierarchy_ops
五、硬件中断的注册
我们发现采用 dts 的方式,gic 中断控制器的初始化中仅仅只是创建了一个线性中断域,并且设置了 gic_irq_domain_hierarchy_ops 这个回调接口,整个过程都没有创建 hwirq 与 virq 的关联。从上面的代码分析我们也可以知道对于老式的不使用 dts 的时候,内核会在初始化 gic 的时候就为每一个 hwirq 创建一个 irq_descs 并且建立 hwirq 与 virq 的关联。
那采用 dts 方式时中断控制器中的硬件中断在什么时候被注册的呢?答案是在设备模型创建设备的时候。对于使用到该中断控制器的中断,则会调用其所属的中断域中提供 irq_domain_ops 回调函数来创建。这样设计是很合理的,对于用到的中断我们才会去注册,能够提内存的使用效率。举个栗子说明一下,在 dts 中做如下配置
1 2 3 4 5 6 7 8 9 gpc: gpc@020 dc000 { compatible = "fsl,imx6ul-gpc" , "fsl,imx6q-gpc" ; reg = <0x020dc000 0x4000 >; interrupt-controller; #interrupt-cells = <3> ; interrupts = <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>; interrupt-parent = <&intc>; fsl,mf-mix-wakeup-irq = <0xfc00000 0x7d00 0x0 0x1400640 >; };
上述节点会在解析的时候创建出一个 platform 设备,具体流程如下
1 2 3 4 5 6 7 8 9 10 11 of_platform_default_populate_init --> of_platform_default_populate --> of_platform_populate --> of_platform_bus_create --> of_platform_device_create_pdata --> of_platform_device_create_pdata --> of_device_alloc --> of_irq_count --> of_irq_to_resource_table --> of_irq_to_resource --> irq_of_parse_and_map
最终调用到了 irq_of_parse_and_map 这个函数。关于 dts 解析为 platform 设备的流程可以看我设备模型的文章。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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)) return 0 ; return irq_create_of_mapping(&oirq); }
这个函数首先获取 dts 中的中断信息,再调用 irq_create_of_mapping 创建中断映射。
1 2 3 4 5 6 7 8 unsigned int irq_create_of_mapping (struct of_phandle_args *irq_data) { struct irq_fwspec fwspec ; of_phandle_args_to_fwspec(irq_data, &fwspec); return irq_create_fwspec_mapping(&fwspec); }
该函数将中断信息转存到 fwspec 之后调用 irq_create_fwspec_mapping 函数。由于涉及的函数实在太多,我这里就不把每个函数都写出来了,直接给出调用链。感兴趣可以对着这个调用链跟一下源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 irq_create_fwspec_mapping --> irq_find_matching_fwspec --> irq_domain->ops->translate --> irq_domain->ops->xlate if (domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY) --> irq_domain_alloc_irqs --> irq_domain_alloc_descs --> irq_domain_alloc_irq_data --> domain->ops->alloc --> gic_irq_domain_alloc --> gic_irq_domain_translate --> gic_irq_domain_map else --> irq_create_mapping --> irq_domain_alloc_descs --> irq_domain_associate if (domain->ops->map ) --> domain->ops->map else --> 使用 linear_revmap 或 radix_tree 建立映射关系。
结论:
在开机的时候设备模型就自动的帮我们将 dts 中配置的中断 hwirq 创建了一个 irq_desc 以及相关联的 virq。
对于 gic 中断控制器对 irq_desc->handle_irq 设置两个默认值,记住这两个默认回调函数,后面会用到,如下所示
1 2 hwirq < 32 : irq_desc->handle_irq = handle_percpu_devid_irq hwirq >= 32 : irq_desc->handle_irq = handle_fasteoi_irq
六、gic 中断处理流程
有了前面的知识,就可以知道 gic 的中断处理流程,就比如当前面注册的 gic 上的 89 号中断触发时的流程:
gic 中断控制器发出 irq 信号给 cpu
cpu 接受到 irq 信号,进入异常处理模式,最终调用 handle_arch_irq
handle_arch_irq 也就是 gic 设置的 gic_handle_irq 其调用链如下
1 2 3 4 5 6 7 gic_handle_irq --> handle_domain_irq --> __handle_domain_irq --> irq_find_mapping --> generic_handle_irq --> generic_handle_irq_desc --> desc->handle_irq(desc);
最终会调用到对应 virq 的 desc->handle_irq(desc),这里有有两种情况。
1)默认中断处理流程
由前面的分析可以知道 desc->handle_irq 默认情况下会调用函数 handle_fasteoi_irq ,他的调用链如下
1 2 3 4 handle_irq_event --> handle_irq_event_percpu --> __handle_irq_event_percpu --> action->handler
在默认情况下就会调用到该 virq 对应的 irq_desc 上的 action->handler , 这是什么这不就是我们 request_irq 注册的中断处理函数么,也就是我们常常说的中断顶半部分。
2) 调用下一个中断控制器的回调函数
由前面可以知道中断 linux 是可以级联的,而实现级联的关键点也在这个地方。对于下一级中断控制器可以覆盖掉 gic 默认设置的 desc->handle_irq(desc) ,而注册自己的中断控制器的 handler 函数,当发生中断时就会调用到该处理函数。以 imx6ul 为例。dts 部分如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 gpio1: gpio@0209c000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x0209c000 0x4000>; // 中断控制器寄存器的物理地址 interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, // 使用 gic 的 66 号 spi 中断 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; // 声明这是一个 gpio 控制器 #gpio-cells = <2>; // 声明这个 gpio 控制器上的 gpio 由两个 cell 描述 interrupt-controller; // 声明这是一个中断控制器 #interrupt-cells = <2>; // 对于这个中断控制器上的中断,由两个 cell 描述 }; gpio2: gpio@020a0000 { ... }; gpio3: gpio@020a4000 { ... }
在内核中搜索对应的 compatible 最后找到 “fsl,imx35-gpio” 相关的 probe 函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 static int mxc_gpio_probe (struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct mxc_gpio_port *port ; struct resource *iores ; int irq_base = 0 ; int err; mxc_gpio_get_hw(pdev); port = devm_kzalloc(&pdev->dev, sizeof (*port), GFP_KERNEL); if (!port) return -ENOMEM; iores = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); port->base = devm_ioremap_resource(&pdev->dev, iores); if (IS_ERR(port->base)) return PTR_ERR(port->base); port->irq_high = platform_get_irq(pdev, 1 ); port->irq = platform_get_irq(pdev, 0 ); if (port->irq < 0 ) return port->irq; ...... if (mxc_gpio_hwtype == IMX21_GPIO) irq_set_chained_handler(port->irq, mx2_gpio_irq_handler); } else { irq_set_chained_handler_and_data(port->irq, mx3_gpio_irq_handler, port); if (port->irq_high > 0 ) irq_set_chained_handler_and_data(port->irq_high, mx3_gpio_irq_handler, port); } err = bgpio_init(&port->gc, &pdev->dev, 4 , port->base + GPIO_PSR, port->base + GPIO_DR, NULL , port->base + GPIO_GDIR, NULL , BGPIOF_READ_OUTPUT_REG_SET); if (err) goto out_bgio; if (of_property_read_bool(np, "gpio_ranges" )) port->gpio_ranges = true ; else port->gpio_ranges = false ; port->gc.request = mxc_gpio_request; port->gc.free = mxc_gpio_free; port->gc.parent = &pdev->dev; port->gc.to_irq = mxc_gpio_to_irq; port->gc.base = (pdev->id < 0 ) ? of_alias_get_id(np, "gpio" ) * 32 : pdev->id * 32 ; err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port); if (err) goto out_bgio; irq_base = irq_alloc_descs(-1 , 0 , 32 , numa_node_id()); if (irq_base < 0 ) { err = irq_base; goto out_bgio; } port->domain = irq_domain_add_legacy(np, 32 , irq_base, 0 , &irq_domain_simple_ops, NULL ); if (!port->domain) { err = -ENODEV; goto out_irqdesc_free; } err = mxc_gpio_init_gc(port, irq_base, &pdev->dev); if (err < 0 ) goto out_irqdomain_remove; list_add_tail(&port->node, &mxc_gpio_ports); platform_set_drvdata(pdev, port); pm_runtime_put(&pdev->dev); return 0 ; out_pm_dis: pm_runtime_disable(&pdev->dev); clk_disable_unprepare(port->clk); out_irqdomain_remove: irq_domain_remove(port->domain); out_irqdesc_free: irq_free_descs(irq_base, 32 ); out_bgio: dev_info(&pdev->dev, "%s failed with errno %d\n" , __func__, err); return err; }
对于 gpio 中断控制器,在初始化时做了下面操做
重新注册使用到的 gic 的中断号对应的 desc->handle_irq
设置该中断控制器的操作函数并将其注册进内核
这里采用了老式的方式创建映射,调用 irq_domain_add_legacy 直接对 gpio 控制器上的每一个 gpio 创建 irq_desc 以及中断域映射。这里和前面的的 gic 采用的时两种方式,前面的 gic 是在设备模型创建的时候动态创建映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 static int mxc_gpio_init_gc (struct mxc_gpio_port *port, int irq_base, struct device *dev) { struct irq_chip_generic *gc ; struct irq_chip_type *ct ; gc = irq_alloc_generic_chip("gpio-mxc" , 1 , irq_base, port->base, handle_level_irq); if (!gc) return -ENOMEM; gc->private = port; ct = gc->chip_types; ct->chip.parent_device = dev; ct->chip.irq_ack = irq_gc_ack_set_bit; ct->chip.irq_mask = irq_gc_mask_clr_bit; ct->chip.irq_unmask = irq_gc_mask_set_bit; ct->chip.irq_set_type = gpio_set_irq_type; ct->chip.irq_set_wake = gpio_set_wake_irq; ct->chip.irq_request_resources = mxc_gpio_irq_reqres; ct->chip.irq_release_resources = mxc_gpio_irq_relres, ct->chip.flags = IRQCHIP_MASK_ON_SUSPEND; ct->regs.ack = GPIO_ISR; ct->regs.mask = GPIO_IMR; irq_setup_generic_chip(gc, IRQ_MSK(32 ), IRQ_GC_INIT_NESTED_LOCK, IRQ_NOREQUEST, 0 ); return 0 ; } void irq_setup_generic_chip (struct irq_chip_generic *gc, u32 msk, enum irq_gc_flags flags, unsigned int clr, unsigned int set ) { ...... for (i = gc->irq_base; msk; msk >>= 1 , i++) { if (!(msk & 0x01 )) continue ; ...... irq_set_chip_and_handler(i, chip, ct->handler); irq_set_chip_data(i, gc); irq_modify_status(i, clr, set ); } gc->irq_cnt = i - gc->irq_base; } EXPORT_SYMBOL_GPL(irq_setup_generic_chip);
这里需要了解的就是 irq_setup_generic_chip 为该 gpio 中断控制器每一个 virq 设置 d esc->handle_irq 为对应的 irq_chip_generic->chip_types->handler 也就是上面的 handle_level_irq 函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void handle_level_irq(struct irq_desc *desc) { raw_spin_lock(&desc->lock); mask_ack_irq(desc); if (!irq_may_run(desc)) goto out_unlock; desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); /* * If its disabled or no action available * keep it masked and get out of here */ if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { desc->istate |= IRQS_PENDING; goto out_unlock; } kstat_incr_irqs_this_cpu(desc); handle_irq_event(desc); //调用 handle_irq_event cond_unmask_irq(desc); out_unlock: raw_spin_unlock(&desc->lock); }
这个函数最终调用到 handle_irq_event 来处理中断,这个函数在前面已经出现过了,也就是最终调用到对应的 action->handler 来处理中断函数,对于gpio中断的调用流程总结如下
1 2 3 4 5 6 7 8 9 10 11 12 13 mx3_gpio_irq_handler // 从 gpio 中断控制器的寄存器中获取到对应的硬件中断号 // 并通过中断域获取到对应的 irq_desc --> mxc_gpio_irq_handler --> generic_handle_irq --> generic_handle_irq_desc --> desc->handle_irq // 调用到 gpio 中断控制器 hwir 对应的 virq 的 handle_irq --> handle_level_irq --> handle_irq_event --> handle_irq_event --> handle_irq_event_percpu --> __handle_irq_event_percpu --> action->handler
七、mix6ull gpio 中断调用流程
最后对于 mix6ull 的 gpio 中断调用做一个整体的梳理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 `````````````````````````````````````````` arm 异常处理流程 cpu 触发异常跳转到 0xFFFF0018 --> __irq_uer / _irq_scv --> irq_hander --> handler_arch_irq ------------------------------------------ gic 中断控制器处理流程 gic_handle_irq --> handle_domain_irq --> __handle_domain_irq --> irq_find_mapping --> generic_handle_irq --> generic_handle_irq_desc --> desc->handle_irq(desc); ------------------------------------------- gpio 中断控制器处理流程 mx3_gpio_irq_handler --> mxc_gpio_irq_handler --> generic_handle_irq --> generic_handle_irq_desc --> desc->handle_irq --> handle_level_irq --> handle_irq_event --> handle_irq_event --> handle_irq_event_percpu --> __handle_irq_event_percpu --> action->handler
八、linux 下使用中断
对于使用来说就相当简单了,只需要调用对应的接口就行了,使用流程如下。
在 dts 中配置中断信息
获取配置的中断的 virq
像 virq 注册中断回调函数。
编写中断处理函数
1、在 dts 中配置中断信息
对于 dts 配置中断需要了解中断相关的 dts 描述,以下整理出 dts 描述
interrupt-parent
: 用来描述该设备使用的父中断控制
#interrupt-cells
: 用来描述该中断控制器的信息(interrupts)需要几个 cell 描述
interrupts
: 用来描述该设备使用具体中断,也就是前面的 cell 的具体描述,具体的解释由中断控制器给出。举个栗子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 intc: interrupt-controller@00a01000 { compatible = "arm,cortex-a7-gic"; #interrupt-cells = <3>; // 该中断控制器上的中断由三个 cell 描述 interrupt-controller; reg = <0x00a01000 0x1000>, // GIC 中断控制器的物理地址 <0x00a02000 0x100>; }; soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; interrupt-parent = <&gpc>; ranges; gpio1: gpio@0209c000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x0209c000 0x4000>; // 第一个参数表示使用的中断类型为 SPI 中断,第二个表示中断号为 66 第三个参数表示 触发方式为高电平 interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, // 使用 gic 的 66 号 spi 中断 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>; // 使用 gic 的 66 号 spi 中断 gpio-controller; #gpio-cells = <2>; interrupt-controller; // 表明这是一个中断控制器 #interrupt-cells = <2>; }; } my_dev { compatible = "fsl,fxls8471"; interrupt-parent = <&gpio1>; //表示使用的中断控制器为 gpio1 // 第 1 个表示中断号,第 2 个参数表示触发方式 interrupts = <0 8>; // 使用 gpio 的 0 号中断,触发方式为低电平触发 };
在内核中触发方式可以在 dts 中可以只接写对应的数字
1 表示上升沿触发
2 表示下降沿触发
4 表示高电平触发
8 表示低电平触发
他们可以进行组合,例如双边沿触发的话直接设置 3(1+2) 就可以了。下面是 mtk 平台下的 gpio 中断控制器描述,mtk 有些平台就采用了 4 cell 个来描述中断如下所示的一个引脚配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 button: button { compatible = "mediatek,mt6785-key"; }; &button { button_k2:button_k2 { compatible = "mediatek,k2"; interrupt-parent = <&pio>; // 使用 pio 这个中断控制器 // 153 中断号 // IRQ_TYPE_LEVEL_LOW 中断触发电平 // 1 中断触发方式: 1 是边沿触发 4 是电平触发 // 0 for future 现在未使用,默认为 0 interrupts = <153 IRQ_TYPE_LEVEL_LOW 1 0>; // 中断引脚为 153 触发方式 IRQ_TYPE_LEVEL_LOW irq-gpio = <&pio 153 0>; //用到的 gpio 为 153,这个是用来操作这个引脚用的 status = "okay"; }; };
2、获取配置的中断的 virq
1 2 3 // dev 设备指针 // num 获取该设备上 interrupts 描述的第 num 个中断号的 virq int platform_get_irq(struct platform_device *dev, unsigned int num)
2.2) i2c/spi 设备
**i2c_client->irq :**用来保存该 i2c 设备的中断号对应的 virq
**spi_device->irq :**用来保存该 spi 设备的中断号对应的 virq
他们分别在 i2c_device_probe 和 spi_drv_probe 两个函数中解析
2.3) gpio
gpio 和中 hwirq 存在一种映射关系,一般情况下都是线性映射。因此内核为gpio 提供了一组转换函数,可以直接将 gpio 描述符转换为对应的 virq。
1 2 3 4 5 // 老式接口,gpio 表示 gpio 号 static inline int gpio_to_irq(unsigned int gpio) // 新接口推荐使用这个接口,将 gpio 描述符转换为 virq int gpiod_to_irq(const struct gpio_desc *desc)
2.4) 通用接口
1 2 3 // dev 设备节点,注意和前面的 dev 区分 // index 获取该设备上 interrupts 描述的第 index 个中断号的 virq static inline int of_irq_get(struct device_node *dev, int index)
3、注册中断回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // irq 要申请的中断的 virq // handler 中断回调函数,也就是底半部分中断 // flags 中断标志位,可以设置触发方式 // name 中断名称 // dev 私有数据,将作为参数传递给 handler static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) // 功能和前面相同,只是多了自动释放资源的功能 static inline int __must_check devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) // irq 要申请的中断的 virq // handler 中断回调函数 // thread_fn 线程处理函数,当 handler 处理完,调用该函数处理底半部分。当 handler 传入为空时直接调用该函数 // flags 中断标志位,可以设置触发方式 // name 中断名称 // dev 私有数据,将作为参数传递给 handler int __must_check request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev); // 功能和前面相同,只是多了自动释放资源的功能 int __must_check devm_request_threaded_irq(struct device *dev, unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id);
4、完整代码
4.1) 驱动程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <linux/poll.h> #include <linux/types.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/cdev.h> #include <linux/slab.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_gpio.h> #include <asm/device.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #define BUTTON_DEBUG 0 #define BUTTON_INFO(fmt, args...) \ do {\ if (BUTTON_DEBUG) {\ pr_info(fmt, ##args); \ } \ } while (0) #define button_NAME "button_name" struct work_struct button_eint_work ;struct workqueue_struct *button_eint_workqueue ;static DECLARE_WAIT_QUEUE_HEAD (button_waitq) ;static volatile int ev_press = 0 ;struct button_cdev { dev_t dev; struct cdev cdev ; struct class *button_class ; struct device *button_class_dev ; }; static struct button_cdev * button_cdevp = NULL ;struct button_desc { int irq; unsigned long button_eint_type; unsigned long irq_pin; unsigned int gpio_value; unsigned int key_val; char * gpio_name; char * irq_name; }; static struct button_desc button_irq_desc = { .irq = 0 , .button_eint_type = IRQ_TYPE_EDGE_BOTH, .irq_pin = 0 , .gpio_value = 0 , .key_val = 0 , .gpio_name = "irq-gpio" , .irq_name = "button" , }; static irqreturn_t button_eint_func (int irq,void *data) { BUTTON_INFO("button_eint_func\n" ); if (!work_pending(&button_eint_work)) queue_work(button_eint_workqueue, &button_eint_work); return IRQ_HANDLED; } static int button_setup_eint (void ) { int ret; struct device_node *node = NULL ; BUTTON_INFO("button_setup_eint\n" ); node = of_find_compatible_node(NULL , NULL , "mediatek,k2" ); if (!node) { pr_err("mt6785-button node get fail!!!\n" ); return -1 ; } button_irq_desc.irq = irq_of_parse_and_map(node, 0 ); ret = request_irq(button_irq_desc.irq, button_eint_func,button_irq_desc.button_eint_type, button_irq_desc.irq_name, (void *)&(button_irq_desc)); if (ret > 0 ) { pr_err("[button]EINT IRQ LINE NOT AVAILABLE\n" ); } pr_err("[button] set EINT finished, %s:irq=%d\n" , button_irq_desc.irq_name, button_irq_desc.irq); return 0 ; } static void button_eint_work_callback (struct work_struct* work) { button_irq_desc.gpio_value = __gpio_get_value(button_irq_desc.irq_pin); BUTTON_INFO("button_value = %d\n" ,button_irq_desc.gpio_value); if (button_irq_desc.gpio_value) { button_irq_desc.key_val = 0x80 | button_irq_desc.gpio_value; } else { button_irq_desc.key_val = button_irq_desc.gpio_value; } ev_press = 1 ; wake_up_interruptible(&button_waitq); } static void button_setup_workqueue (void ) { button_eint_workqueue = create_singlethread_workqueue("button_eint" ); INIT_WORK(&button_eint_work, button_eint_work_callback); } static int button_get_gpio_info (void ) { struct device_node *node = NULL ; node = of_find_compatible_node(NULL , NULL , "mediatek,k2" ); if (!node) { pr_err("mt6785-button node get fail!!!\n" ); return -1 ; } button_irq_desc.irq_pin = of_get_named_gpio(node, button_irq_desc.gpio_name, 0 ); if (!button_irq_desc.irq_pin) { printk("get irq pin fail\n" ); } printk("button = %d\n" , button_irq_desc.irq_pin); return 0 ; } ssize_t button_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *ppos) { if (size != 1 ) return -EINVAL; wait_event_interruptible(button_waitq, ev_press); copy_to_user(buf, &button_irq_desc.key_val, 1 ); ev_press = 0 ; return 1 ; } static int button_drv_open (struct inode *inode, struct file *filp) { struct button_cdev * button_cdevp ; button_cdevp = container_of(inode->i_cdev, struct button_cdev, cdev); filp->private_data = button_cdevp; return 0 ; } int button_drv_close (struct inode *inode, struct file *file) { return 0 ; } static struct file_operations sencod_drv_fops = { .owner = THIS_MODULE, .open = button_drv_open, .read = button_drv_read, .release = button_drv_close, }; void button_setup_cdev (struct button_cdev* devp) { int err; cdev_init(&devp->cdev,&sencod_drv_fops); devp->cdev.owner = THIS_MODULE; err = cdev_add(&devp->cdev,devp->dev,1 ); if (err) { printk(KERN_NOTICE "Error %d adding button" , err); } } static int button_probe (struct platform_device *dev) { int ret = 0 ; BUTTON_INFO("button_probe\n" ); button_cdevp = kzalloc(sizeof (struct button_cdev), GFP_KERNEL); if (!button_cdevp) { return -ENOMEM; } ret = alloc_chrdev_region(&button_cdevp->dev,0 ,1 ,"button" ); if (ret < 0 ) { return ret; } button_setup_cdev(button_cdevp); button_cdevp->button_class = class_create(THIS_MODULE, "button_drv" ); button_cdevp->button_class_dev = device_create(button_cdevp->button_class, NULL , button_cdevp->dev, NULL , "buttons" ); if (IS_ERR(button_cdevp->button_class_dev)) { printk("Err: failed in creating class./n" ); return -1 ; } ret = button_setup_eint(); if (ret) { return ret; } ret = button_get_gpio_info(); if (ret) { return ret; } button_setup_workqueue(); BUTTON_INFO("button_probe end\n" ); return 0 ; } static const struct of_device_id button_switch_of_match [] = { {.compatible = "mediatek,mt6785-key" }, {}, }; static struct platform_driver button_driver = { .probe = button_probe, .driver = { .name = "button_driver" , .owner = THIS_MODULE, .of_match_table = button_switch_of_match, }, }; static int button_mod_init (void ) { int ret = 0 ; BUTTON_INFO("[button]button_mod_init begin!\n" ); ret = platform_driver_register(&button_driver); if (ret) { pr_err("[button]platform driver register error:(%d)\n" , ret); return ret; } else { pr_err("[button]platform driver register done!\n" ); } BUTTON_INFO("[button]button_mod_init done!\n" ); return 0 ; } static void button_mod_exit (void ) { BUTTON_INFO("[button]button_mod_exit\n" ); cdev_del(&button_cdevp->cdev); class_destroy(button_cdevp->button_class); unregister_chrdev_region(button_cdevp->dev,1 ); free_irq(button_irq_desc.irq, (void *)&(button_irq_desc)); platform_driver_unregister(&button_driver); BUTTON_INFO("[button]button_mod_exit Done!\n" ); } module_init(button_mod_init); module_exit(button_mod_exit); MODULE_DESCRIPTION(" button driver" ); MODULE_AUTHOR("baron-z" ); MODULE_LICENSE("GPL" );
4.2) dts文件
文件名 k85v1_64.dts
1 2 3 4 5 6 7 8 9 10 &button { button_k2:button_k2 { compatible = "mediatek,k2"; //当前节点名 interrupt-parent = <&pio>; //中断属于pio interrupts = <153 IRQ_TYPE_LEVEL_LOW 1 0>; //中断引脚为153 触发方式 IRQ_TYPE_LEVEL_LOW 这个会在代码中重新设置设 irq-gpio = <&pio 153 0>; //用到的gpio为153,这个是用来操作这个引脚用的 status = "okay"; }; };
文件名 mt6785.dts
1 2 3 button: button { compatible = "mediatek,mt6785-key"; //button根节点,表示生成这样一个设备 };
4.3) 验证程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> #include <string.h> #include <fcntl.h> #include <stdlib.h> #include <ctype.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> const char* device_name = "/dev/buttons"; int main(int argc, char **argv) { int fd; unsigned char key_val; fd = open(device_name, O_RDWR); if (fd < 0) { printf("can't open!\n"); } while (1) { read(fd, &key_val, 1); //调用读取函数,当没有按键按下时,内核里面会调用wait_event_interruptible 函数使该进程进入休眠。 printf("key_val = 0x%x\n", key_val); } return 0; }
4.4) 测试过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 k85v1_64:/cache # insmod key_one.ko k85v1_64:/cache # cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 ...... 24: 0 0 0 0 0 0 0 0 mt-eint 1 Edge TOUCH_PANEL-eint 30: 11 11 17 11 5 2 10 3 mt-eint 7 Edge type_c_port0-IRQ 31: 0 0 0 0 0 0 0 0 mt-eint 8 Edge 11240000.msdc cd 39: 29 60 57 39 29 14 12 16 mt-eint 16 Edge 5-0034 176: 0 0 0 0 0 0 0 0 mt-eint 153 Edge button 216: 2 29 14 10 5 6 6 5 mt-eint 193 Level mt6358-irq ...... k85v1_64:/cache # k85v1_64:/cache # k85v1_64:/cache # ./libshowlogotest & [1] 3020 k85v1_64:/cache # key_val = 0x0 key_val = 0x81 key_val = 0x0 key_val = 0x81 key_val = 0x0 key_val = 0x81 key_val = 0x0 key_val = 0x81
参考文献: