本章我将会讨论这两层对外提供的标准接口和内部实现机制,几乎所有的接口都是围绕着irq_desc和irq_chip这两个结构体进行的,对这两个结构体不熟悉的读者可以现读一下前面几篇文章。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
1. irq的打开和关闭
中断子系统为我们提供了一系列用于irq的打开和关闭的函数接口,其中最基本的一对是:
- disable_irq(unsigned int irq);
- enable_irq(unsigned int irq);
![](https://img-my.csdn.net/uploads/201204/25/1335342341_2084.png)
图1.1 disable_irq的调用过程
函数的开始使用异步方式的内部函数__disable_irq_nosync(),所谓异步方式就是不理会当前该irq是否正在被处理(有handler在运行或者有中断线程尚未结束)。有些中断控制器可能挂在某个慢速的总线上,所以在进一步处理前,先通过irq_get_desc_buslock获得总线锁(最终会调用chip->irq_bus_lock),然后进入内部函数__disable_irq:
- void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend)
- {
- if (suspend) {
- if (!desc->action || (desc->action->flags & IRQF_NO_SUSPEND))
- return;
- desc->istate |= IRQS_SUSPENDED;
- }
- if (!desc->depth++)
- irq_disable(desc);
- }
disable_irq的最后,调用了synchronize_irq,该函数通过IRQ_INPROGRESS标志,确保action链表中所有的handler都已经处理完毕,然后还要通过wait_event等待该irq所有的中断线程退出。正因为这样,在中断上下文中,不应该使用该API来关闭irq,同时要确保调用该API的函数不能拥有该irq处理函数或线程的资源,否则就会发生死锁!!如果一定要在这两种情况下关闭irq,中断子系统为我们提供了另外一个API,它不会做出任何等待动作:
- disable_irq_nosync();
- enable_irq();
- void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume)
- {
- if (resume) {
- ......
- }
- switch (desc->depth) {
- case 0:
- err_out:
- WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);
- break;
- case 1: {
- ......
- irq_enable(desc);
- ......
- }
- default:
- desc->depth--;
- }
- }
2. 中断子系统内部数据结构访问接口
我们知道,中断子系统内部定义了几个重要的数据结构,例如:irq_desc,irq_chip,irq_data等等,这些数据结构的各个字段控制或影响着中断子系统和各个irq的行为和实现方式。通常,驱动程序不应该直接访问这些数据结构,直接访问会破会中断子系统的封装性,为此,中断子系统为我们提供了一系列的访问接口函数,用于访问这些数据结构。
存取irq_data结构相关字段的API:
irq_set_chip(irq, *chip) / irq_get_chip(irq) 通过irq编号,设置、获取irq_cip结构指针;
irq_set_handler_data(irq, *data) / irq_get_handler_data(irq) 通过irq编号,设置、获取irq_desc.irq_data.handler_data字段,该字段是每个irq的私有数据,通常用于硬件封装层,例如中断控制器级联时,父irq用该字段保存子irq的起始编号。
irq_set_chip_data(irq, *data) / irq_get_chip_data(irq) 通过irq编号,设置、获取irq_desc.irq_data.chip_data字段,该字段是每个中断控制器的私有数据,通常用于硬件封装层。
irq_set_irq_type(irq, type) 用于设置中断的电气类型,可选的类型有:
- IRQ_TYPE_EDGE_RISING
- IRQ_TYPE_EDGE_FALLING
- IRQ_TYPE_EDGE_BOTH
- IRQ_TYPE_LEVEL_HIGH
- IRQ_TYPE_LEVEL_LOW
irq_get_irq_data(irq) 通过irq编号,获取irq_data结构指针;
irq_data_get_irq_chip(irq_data *d) 通过irq_data指针,获取irq_chip字段;
irq_data_get_irq_chip_data(irq_data *d) 通过irq_data指针,获取chip_data字段;
irq_data_get_irq_handler_data(irq_data *d) 通过irq_data指针,获取handler_data字段;
设置中断流控处理回调API:
irq_set_handler(irq, handle) 设置中断流控回调字段:irq_desc.handle_irq,参数handle的类型是irq_flow_handler_t。
irq_set_chip_and_handler(irq, *chip, handle) 同时设置中断流控回调字段和irq_chip指针:irq_desc.handle_irq和irq_desc.irq_data.chip。
irq_set_chip_and_handler_name(irq, *chip, handle, *name) 同时设置中断流控回调字段和irq_chip指针以及irq名字:irq_desc.handle_irq、irq_desc.irq_data.chip、irq_desc.name。
irq_set_chained_handler(irq, *chip, handle) 设置中断流控回调字段:irq_desc.handle_irq,同时设置标志:IRQ_NOREQUEST、IRQ_NOPROBE、IRQ_NOTHREAD,该api通常用于中断控制器的级联,父控制器通过该api设置流控回调后,同时设置上述三个标志位,使得父控制器的中断线不允许被驱动程序申请。
3. 在驱动程序中申请中断
系统启动阶段,中断子系统完成了必要的初始化工作,为驱动程序申请中断服务做好了准备,通常,我们用一下API申请中断服务:
- request_threaded_irq(unsigned int irq, irq_handler_t handler,
- irq_handler_t thread_fn,
- unsigned long flags, const char *name, void *dev);
handler 中断服务回调函数,该回调运行在中断上下文中,并且cpu的本地中断处于关闭状态,所以该回调函数应该只是执行需要快速响应的操作,执行时间应该尽可能短小,耗时的工作最好留给下面的thread_fn回调处理。
thread_fn 如果该参数不为NULL,内核会为该irq创建一个内核线程,当中断发生时,如果handler回调返回值是IRQ_WAKE_THREAD,内核将会激活中断线程,在中断线程中,该回调函数将被调用,所以,该回调函数运行在进程上下文中,允许进行阻塞操作。
flags 控制中断行为的位标志,IRQF_XXXX,例如:IRQF_TRIGGER_RISING,IRQF_TRIGGER_LOW,IRQF_SHARED等,在include/linux/interrupt.h中定义。
name 申请本中断服务的设备名称,同时也作为中断线程的名称,该名称可以在/proc/interrupts文件中显示。
dev 当多个设备的中断线共享同一个irq时,它会作为handler的参数,用于区分不同的设备。
下面我们分析一下request_threaded_irq的工作流程。函数先是根据irq编号取出对应的irq_desc实例的指针,然后分配了一个irqaction结构,用参数handler,thread_fn,irqflags,devname,dev_id初始化irqaction结构的各字段,同时做了一些必要的条件判断:该irq是否禁止申请?handler和thread_fn不允许同时为NULL,最后把大部分工作委托给__setup_irq函数:
- desc = irq_to_desc(irq);
- if (!desc)
- return -EINVAL;
- if (!irq_settings_can_request(desc) ||
- WARN_ON(irq_settings_is_per_cpu_devid(desc)))
- return -EINVAL;
- if (!handler) {
- if (!thread_fn)
- return -EINVAL;
- handler = irq_default_primary_handler;
- }
- action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
- if (!action)
- return -ENOMEM;
- action->handler = handler;
- action->thread_fn = thread_fn;
- action->flags = irqflags;
- action->name = devname;
- action->dev_id = dev_id;
- chip_bus_lock(desc);
- retval = __setup_irq(irq, desc, action);
- chip_bus_sync_unlock(desc);
- if (new->thread_fn && !nested) {
- struct task_struct *t;
- t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
- new->name);
- if (IS_ERR(t)) {
- ret = PTR_ERR(t);
- goto out_mput;
- }
- /*
- * We keep the reference to the task struct even if
- * the thread dies to avoid that the interrupt code
- * references an already freed task_struct.
- */
- get_task_struct(t);
- new->thread = t;
- }
- 一定要设置了IRQF_SHARED标志
- 电气触发方式要完全一样(IRQF_TRIGGER_XXXX)
- IRQF_PERCPU要一致
- IRQF_ONESHOT要一致
- /* add new interrupt at end of irq queue */
- do {
- thread_mask |= old->thread_mask;
- old_ptr = &old->next;
- old = *old_ptr;
- } while (old);
- shared = 1;
- if (irq_settings_can_autoenable(desc))
- irq_startup(desc);
- else
- /* Undo nested disables: */
- desc->depth = 1;
- /* Set default affinity mask once everything is setup */
- setup_affinity(irq, desc, mask);
- new->irq = irq;
- *old_ptr = new;
- if (new->thread)
- wake_up_process(new->thread);
- register_irq_proc(irq, desc);
- new->dir = NULL;
- register_handler_proc(irq, new);
图3.1 irq各个数据结构之间的关系
4. 动态扩展irq编号
- 配置了CONFIG_SPARSE_IRQ内核配置项,使用基数树动态管理irq_desc结构。
- 针对多功能复合设备,内部具备多个中断源,但中断触发引脚只有一个,为了实现驱动程序的跨平台,不希望这些中断源的irq被硬编码在板级代码中。
- irq_set_chip_and_handler_name
- irq_set_handler_data
- irq_set_chip_data
5. 多功能复合设备的中断处理
在移动设备系统中,存在着大量的多功能复合设备,最常见的是一个芯片中,内部集成了多个功能部件,或者是一个模块单元内部集成了功能部件,这些内部功能部件可以各自产生中断请求,但是芯片或者硬件模块对外只有一个中断请求引脚,我们可以使用多种方式处理这些设备的中断请求,以下我们逐一讨论这些方法。
对于这种复合设备,通常设备中会提供某种方式,以便让CPU获取真正的中断来源, 方式可以是一个内部寄存器,gpio的状态等等。单一中断模式是指驱动程序只申请一个irq,然后在中断处理程序中通过读取设备的内部寄存器,获取中断源,然后根据不同的中断源做出不同的处理,以下是一个简化后的代码:
- static int xxx_probe(device *dev)
- {
- ......
- irq = get_irq_from_dev(dev);
- ret = request_threaded_irq(irq, NULL, xxx_irq_thread,
- IRQF_TRIGGER_RISING,
- "xxx_dev", NULL);
- ......
- return 0;
- }
- static irqreturn_t xxx_irq_thread(int irq, void *data)
- {
- ......
- irq_src = read_device_irq();
- switch (irq_src) {
- case IRQ_SUB_DEV0:
- ret = handle_sub_dev0_irq();
- break;
- case IRQ_SUB_DEV1:
- ret = handle_sub_dev1_irq();
- break;
- ......
- default:
- ret = IRQ_NONE;
- break;
- }
- ......
- return ret;
- }
5.2 共享中断模式
共享中断模式充分利用了通用中断子系统的特性,经过前面的讨论,我们知道,irq对应的irq_desc结构中的action字段,本质上是一个链表,这给我们实现中断共享提供了必要的基础,只要我们以相同的irq编号多次申请中断服务,那么,action链表上就会有多个irqaction实例,当中断发生时,中断子系统会遍历action链表,逐个执行irqaction实例中的handler回调,根据handler回调的返回值不同,决定是否唤醒中断线程。需要注意到是,申请多个中断时,irq编号要保持一致,flag参数最好也能保持一致,并且都要设上IRQF_SHARED标志。在使用共享中断时,最好handler和thread_fn都要提供,在各自的中断处理回调handler中,做出以下处理:
- 判断中断是否来自本设备;
- 如果不是来自本设备:
- 直接返回IRQ_NONE;
- 如果是来自本设备:
- 关闭irq;
- 返回IRQ_WAKE_THREAD,唤醒中断线程,thread_fn将会被执行;
5.3 中断控制器级联模式
- 首先,父中断的irq编号可以从板级代码的预定义中获得,或者通过device的platform_data字段获得;
- 使用父中断的irq编号,利用irq_set_chained_handler函数修改父中断的流控函数;
- 使用父中断的irq编号,利用irq_set_handler_data设置流控函数的参数,该参数要能够用于判别子控制器的中断来源;
- 实现父中断的流控函数,其中只需获得并计算子设备的irq编号,然后调用generic_handle_irq即可;
- 为设备内的中断控制器实现一个irq_chip结构,实现其中必要的回调,例如irq_mask,irq_unmask,irq_ack等;
- 循环每一个子设备,做以下动作:
- 为每个子设备,使用irq_alloc_descs函数申请irq编号;
- 使用irq_set_chip_data设置必要的cookie数据;
- 使用irq_set_chip_and_handler设置子控制器的irq_chip实例和子irq的流控处理程序,通常使用标准的流控函数,例如handle_edge_irq;
- 子设备的驱动程序使用自身申请到的irq编号,按照正常流程申请中断服务即可。
5.4 中断线程嵌套模式
- 首先,父中断的irq编号可以从板级代码的预定义中获得,或者通过device的platform_data字段获得;
- 使用父中断的irq编号,利用request_threaded_irq函数申请中断服务,需要提供thread_fn参数和dev_id参数;
- dev_id参数要能够用于判别子控制器的中断来源;
- 实现父中断的thread_fn函数,其中只需获得并计算子设备的irq编号,然后调用handle_nested_irq即可;
- 为设备内的中断控制器实现一个irq_chip结构,实现其中必要的回调,例如irq_mask,irq_unmask,irq_ack等;
- 循环每一个子设备,做以下动作:
- 为每个子设备,使用irq_alloc_descs函数申请irq编号;
- 使用irq_set_chip_data设置必要的cookie数据;
- 使用irq_set_chip_and_handler设置子控制器的irq_chip实例和子irq的流控处理程序,通常使用标准的流控函数,例如handle_edge_irq;
- 使用irq_set_nested_thread函数,把子设备irq的线程嵌套特性打开;
- 子设备的驱动程序使用自身申请到的irq编号,按照正常流程申请中断服务即可。