(1)中断描述符结构 struct irq_desc:
1)打开CONFIG_SPARSE_IRQ宏(中断编号不连续),中断描述符以radix-
tree 来组织,用户在初始化时进行动态分配,然后再插入 radix-tree 中;
2)关闭CONFIG_SPARSE_IRQ宏(中断编号连续),中断描述符以数组的
形式组织,并且已经分配好;
3)不管哪种形式,最终都可以通过linuxirq号来找到对应的中断描述符;
(2)struct irq_chip 用于对中断控制器的硬件操作;struct irq_domain 与中断控制器对应,完成的工作是硬件中断号到Linux irq的映射;struct irq_domain_ops是中断中对应的操作函数;这三者主要在中断控制器驱动中进行初始化设置。
(3)struct irqaction 中handler的设置,用于指向我们设备驱动程序中的中断处理函数,在设备申请注册中断的过程中进行设置
(4)结构体之间的关系
struct irq_desc 包含 struct irqaction 和 struct irq_data
struct irq_data 包含 struct irq_domain 和 struct irq_chip
struct irq_domain 包含 struct irq_domain ops
struct irqaction 包含 设备驱动中的中断处理函数——irq_handler_t
(5)中断的处理主要有以下几个功能模块:
1)硬件中断号到Linux irq 中断号的映射,并创建好 irq_desc中断描述符;
2)中断注册时,先获取设备的中断号,根据中断号找到对应的irq_desc,并将设备的中断处理函数添加到irq_desc中;
3)设备触发中断信号时,根据硬件中断号得到Linux irq中断号,找到对应的irq_desc,最终调用到设备的中断处理函数;
(1)platform_get_irq(struct platform_device *dev,unsigned int num)
/**drivers\base\Platform.c **/
platform_get_irq(struct platform_device *dev,unsigned int num)
---→_platform_get_irq(dev,num)
---→of_irq_get(dev->dev.of_node,num)
// 返回 Linux irq number-driverslof\Irq.c
---→of_irq_parse_one(dev,index,&oirq)
// 从设备数据中解析中断信息
---→irq_find_host(oirq.np)
// 遍历 irq_domain_list链表,找到匹配的irq_domain
---→irq_create_of_mapping(&oirq) // kernel\irq\lrqdomain.c
// irq_of_parse_and_map也可以调用到该函数,其将中断映射到Linux 空间;irq_of_parse_and_map是个wrapper,更容易调用——drivers\of\Irq.c
(2) irq_create_of_mapping(&oirq)
irq_create_of_mapping(&oirq)
---→irq_create_fwspec_mapping(&fwspec)//kernel\irq\lrqdomain.c---
---→irq_find_matching_fwspec(fwspec,DOMAIN_BUS_WIRED)
// 遍历irq_domain_list链表,找到匹配的 irq domain
---→irq_domain_translate(domain,fwspec,&hwirq,&type)
---→d->ops->translate(d,fwspec,hwirq,type)
// 回调到 gic 驱动中的 gic_irq_domain_translate 函数,解析中断源的相关信息:中断号、触发方式等
---→irq_find_mapping(domain,hwirq)
// 从硬件 irq number 中找到 Linux irq
(2) Virq=irq_find_mapping(domain,hwirq)
Virq=irq_find_mapping(domain,hwirq)
---→若中断映射已建立好,则返回中断
---→irq_domain_alloc_irqs(domain,,,fwspec)
// 创建映射关系
---→_irq_domain_alloc_irqs()
// 为irq_domain 分配Irq,并始化数据结构
(3)_irq_domain_alloc_irqs()
_irq_domain_alloc_irqs()
---→irq_domain_alloc_descs(irq_base,nr_irqs,0,node,affinity);
// 分配和初始化中断描述符irq_desc
---→_irq_alloc_descs()
---→alloc_descs() ———— kernel\irq\Irqdesc.c
---→irq_domain_alloc_irqs_hierarchy(domain,virq,nr_irqs,arg)
---→domain->ops->alloc(domain,irq_base,nr_irqs,arg)
(4)
1)硬件设备的中断信息都在设备树device tree中进行了描述,在系统启动过程中,这些信息都已经加载到内存中并得到了解析;
2)驱动中通常会使用platform_get_irq或 irq_of_parse_and_map 接口,去根据设备树的信息去创建映射关系(硬件中断号到linux irq 中断号映射)
3)如果已经创建好了映射,那么可以直接进行返回linux irq中断号了,否则的话需要 irq_domain_alloc_irqs来创建映射关系;
irq_domain_alloc_irqs 完成两个工作:
a)针对linux irq 中断号创建一个irq_desc中断描述符;
b)调用domain->ops->alloc函数来完成映射,在ARM GICv2驱动中对应gic_irq_domain_alloc 函数
(5)gic_irq_domain_alloc()
gic_irq_domain_alloc() // drivers\irqchip\Irq-gic.c
---→gic_irq_domain_translate(domain, fwspec,&hwirq,&type)
// 解析出硬件中断号,以及中断触发类型(边缘触发、电平触发等))
---→gic_irq_domain_map(domain,virq +i,hwirq +i)
---→hw<32,为私有中断
irq_domain_set_info(,,,,,handle_percpu_devid_irq,,)
// 私有中断的 处理函数为handle_percpu_devid_irq
---→hw>=32,为共享中断
irq_domain_set_info(,,,,,handle_fasteoi_irq,,)
// 共享中断的 处理函数为 handle_fasteoi_irq
---→irq_domain_set_info()
// 为domain 中的virq 设置完整数据
---→irq_domain_set_hwirq_and_chip
// 设置 irq_desc->irq_data 中的hwirq、chip和chip_data 字段
---→_irq_set_handler
// 设置irq_desc->handle_irq 字段
---→irq_set_handler_data
// 设置irq_desc->irq_common_data.handler_data 字段
(6)上述函数执行完成后,完成了两大工作:
1)硬件中断号与Linux中断号完成映射,并为Linux 中断号创建了 irq_desc中断描述符;
2)数据结构的绑定及初始化,关键的地方是设置了中断处理往上执行的入口
(1)request_irq()
--→request_irq()
/** include\linux\lnterrupt.h **/
----→request_threaded_irq(irq, handler, NULL, flags,name,dev)
// 此调用分配中断资源并启用中断线和IRQ 处理 ———— kernel\irq\Manage.c
----→irq_to_desc(irq)
// 根据Linux 中断号,获取中断描述符irq_desc
----→action =kzalloc(sizeof(struct irqaction),GFP_KERNEL)
// 分配irqaction 结构,并进行填充
----→setup_irq(irq,desc,action) ———— kernel\irq\Manage.c
// 注册中断
(2)_setup_irq(irq,desc,action)
_setup_irq(irq,desc,action)
----→irq_settings_is_nested_thread(desc)
// 检查中断是否嵌套在另一个中断线程中
----→nested--N--→irq_settings_can_thread(desc)
// 判断条件--→中断是否可以线程化
----→rq_setup_forced_threading(new)
// 中断强制线程化
----→new->thread_fn&&!nested--Y--→setup_irq_thread(new,irq,false)----→kthread_create(irq_thread,,,,)
// 为中断创建一个内核线程
----→!desc->action --Y--> irq_request_resources(desc)
// 第一个irqaction 需要请求分配资源
----→old_ptr=&desc->action--Y-->
// 中断需共享
---->do{
thread_mask |=old->thread_mask;old_ptr=&old->next;
old=*old_ptr;
}while (old);
// 共享中断,irqaction会形成链表,当所有共享中断处理完之后才能 unmask中
断;在末尾添加一个新中断
----→!shared --Y--> // 中断非共享情况
----→init_waitqueue_head(&desc->wait_for_threads)
----→irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS)
----→if (new->flags & IRQF_XXX){
irqd_XXX0;
}
// 非共享中断处理,包括中断balance、percpu等设置
_setup_irq 用于完成中断的相关设置,包括中断线程化的处理:
1)中断线程化用于减少系统关中断的时间,增强系统的实时性;
2)ARM64默认开启了CONFIG_IRQ_FORCED_THREADING,引导参数传入threadirqs时,则除了IRQF_NO_THREAD外的中断,其他的都将强制线程化处理;
3)中断线程化会为每个中断都创建一个内核线程,如果中断进行共享,对应irqaction 将连接成链表,每个irqaction 都有thread_mask 位图字段,当所有共享中断都处理完成后才能unmask中断,解除中断屏蔽;
generic_handle_irq
// 为一个irq来触发handler————kernel\irq\lrqdesc.c
generic_handle_irq
// 为一个irq来触发handler-kernel\irq\lrqdesc.c
----→irq_to_desc
// 根据中断号获取 irq_desc
----→generic_handle_irq_desc
// IRQ layer来处理中断
----→desc->handle_irq(desc)
// irq_domain_set_info 建立 map 阶段设置了函数指针
----→handle_fasteoi_irq
// 共享中断处理 ———— kernel\irq\Chip.c
----→handle_irq_event(desc)
----→handle_irq_event_percpu(desc)
----→_handle_irq_event_percpu(desc,&flags)
// kernel\irq\Handle.c
----→action->handler(irq,action->dev_id)
// 遍历action 链表,并执行处理函数
----→case IRQ_WAKE_THREAD:
_irq_wake_thread(desc,action);
// 遍历 action链表,若中断为线程化处理,则还需唤醒操作
---→handle_percpu_devid_irq
// per-cpu中断处理--kernel\irq\Chip.c
----→chip->irq_ack//中断控制器进行ack确认
----→action->handler(irq,raw_cpu_ptr(action->percpu_dev_id))
// 调用注册的中断函数
----→chip->irq_eoi
// 中断控制器进行eoi确认
1)handle_fasteoi_irq:处理共享中断,并且遍历irqaction链表,逐个调用action->handler(函数,这个函数正是设备驱动程序调用 request_irq/request_threaded_irq 接口注册的中断处理函数,此外如果中断线程化处理的话,还会调用_irq_wake_thread()唤醒内核线程;
2)handle_percpu_devid_irq:处理 per-CPU中断处理,在这个过程中会分别调用中断控制器的处理函数进行硬件操作,该函数调用action->handler()来进行中断处理;
中断线程化处理后的唤醒流程:
参考链接:kernel 中断分析五——irq_thread_Burning Water-CSDN博客
_handle_irq_event_percpu
_handle_irq_event_percpu
----→_irq_wake_thread(desc,action)
----→(唤醒)irq_thread(void *data)
// kernel\irq\Manage.c
----→handler_fn =irq_forced_thread_fn
// 没有明确申请thread interrupt的中断,需要将硬中断上下文的bh/抢占关闭
----→handler_fn=irq_thread_fn
// 多数线程化的中断都会被抢占然后睡眠
----→action->thread_fn(action->irq,action->dev_id)
// 运行最终的线程化处理函数
----→irq_wait_for_interrupt(action)
----→set_current_state(TASK_INTERRUPTIBLE)
// 设置线程状态
----→(test_and_clear_bit(IRQTF_RUNTHREAD,&action->thread_flags)
// 测试中断线程的唤醒条件
----→schedule()
// 不满足唤醒条件则让出CPU
1)irq_thread 内核线程将 while(lirq_wait_for_interrupt)循环进行中断的处理,当满足条件时,执行handler_fn,在该函数中最终调用 action->thread_fn,也就是完成了中断的处理;
2)irq_wait_for_interrupt函数,将会判断中断线程的唤醒条件,如果满足了,则将当前任务设置成TASK_RUNNING状态,并返回0,这样就能执行中断的处理,否则就调用schedule()进行调度,让出CPU,并将任务设置成TASK_INTERRUPTIBLE可中断睡眠状态;
中断的处理,总体来说可以分为两部分来看:
从上到下:围绕irq_desc中断描述符建立好连接关系,这个过程就包括:中断源信息的解析(设备树),硬件中断号到Linux中断号的映射关系、irq_desc结构的分配及初始化(内部各个结构的组织关系)、中断的注册(填充 irq_desc结构,包括handler处理函数)等,总而言之,就是完成静态关系创建,为中断处理做好准备;
从下到上:当外设触发中断信号时,中断控制器接收到信号并发送到处理器,此时处理器进行异常模式切换,并逐步从处理器架构相关代码逐级回调。如果涉及到中断线程化,则还需要进行中断内核线程的唤醒操作,最终完成中断处理函数的执行。