在本系列文章的第一篇:Linux中止(interrupt)子体系之一:中止体系根本原理,我把通用中止子体系分为了4个层次,其间的驱动程序接口层和中止通用逻辑层的边界实际上不是很清晰,由于中止通用逻辑层的许多接口,既能够被驱动程序运用,也能够被硬件封装层运用,所以我把这两部分的内容放在一同进行评论。
本章我将会评论这两层对外供给的规范接口和内部完结机制,简直一切的接口都是围绕着irq_desc和irq_chip这两个结构体进行的,对这两个结构体不熟悉的读者能够现读一下前面几篇文章。
1. irq的翻开和封闭
中止子体系为咱们供给了一系列用于irq的翻开和封闭的函数接口,其间最根本的一对是:
disable_irq(unsigned int irq);
enable_irq(unsigned int irq);
这两个API应该配对运用,disable_irq能够被屡次嵌套调用,要想从头翻开irq,enable_irq有必要也要被调用相同的次数,为此,irq_desc结构中的depth字段专门用于这两个API嵌套深度的办理。当某个irq初次被驱动程序请求时,默许情况下,设置depth的初始值是0,对应的irq处于翻开状况。咱们看看disable_irq的调用进程:
图1.1 disable_irq的调用进程
函数的开端运用异步办法的内部函数__disable_irq_nosync(),所谓异步办法便是不睬会当时该irq是否正在被处理(有handler在运转或许有中止线程没有结束)。有些中止操控器或许挂在某个慢速的总线上,所以在进一步处理前,先经过irq_get_desc_buslock取得总线锁(终究会调用chip->irq_bus_lock),然后进入内部函数__disable_irq:
[cpp] view plain copy
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);
}
前面几句是对suspend的处理,终究两句,只需之前的depth为0,才会经过irq_disable函数,调用中止操控器的回调chip->irq_mask,不然仅仅简略地把depth的值加1。irq_disable函数还会经过irq_state_set_disabled和irq_state_set_masked,设置irq_data.flag的IRQD_IRQ_DISABLED和IRQD_IRQ_MASK标志。
disable_irq的终究,调用了synchronize_irq,该函数经过IRQ_INPROGRESS标志,保证acTIon链表中一切的handler都现已处理结束,然后还要经过wait_event等候该irq一切的中止线程退出。正由于这样,在中止上下文中,不应该运用该API来封闭irq,一起要保证调用该API的函数不能具有该irq处理函数或线程的资源,不然就会产生死锁!!假如必定要在这两种情况下封闭irq,中止子体系为咱们供给了别的一个API,它不会做出任何等候动作:
disable_irq_nosync();
中止子体系翻开irq的的API是:
enable_irq();
翻开irq无需供给同步的版别,由于irq翻开前,没有handler和线程在运转,咱们重视一下他对depth的处理,他在内部函数__enable_irq中处理:
[cpp] view plain copy
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–;
}
}
当depth的值为1时,才真实地调用irq_enable(),它终究经过chip->unmask或chip->enable回调敞开中止操控器中相应的中止线,假如depth不是1,仅仅简略地减去1。假如现已是0,驱动还要调用enable_irq,阐明驱动程序处理不妥,形成enable与disable不平衡,内核会打印一句正告信息:Unbalanced enable for IRQ xxx。
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请求中止服务:
[cpp] view plain copy
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
irq 需求请求的irq编号,关于ARM体系,irq编号一般在渠道级的代码中事前界说好,有时候也能够动态请求。
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函数:
[cpp] view plain copy
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);
进入__setup_irq函数,假如参数flag中设置了IRQF_SAMPLE_RANDOM标志,它会调用rand_initialize_irq,以便对随机数的生成产生影响。假如请求的不是一个线程嵌套中止(关于线程嵌套中止,请参阅Linux中止(interrupt)子体系之三:中止流控处理层中的handle_nested_irq一节),而且供给了thread_fn参数,它将创立一个内核线程:
[cpp] view plain copy
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;
}
假如irq_desc结构中止action链表不为空,阐明这个irq现已被其它设备请求过,也便是说,这是一个同享中止,所以接下来会判别这个新请求的中止与现已请求的旧中止的以下几个标志是否共同:
必定要设置了IRQF_SHARED标志
电气触发办法要彻底相同(IRQF_TRIGGER_XXXX)
IRQF_PERCPU要共同
IRQF_ONESHOT要共同
查看这些条件都是由于多个设备企图同享一根中止线,试想一下,假如一个设备要求上升沿中止,一个设备要求电平中止,当中止抵达时,内核将不知怎么挑选适宜的流控操作。完结查看后,函数找出action链表中终究一个irqaction实例的指针。
[cpp] view plain copy
/* 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;
假如这不是一个同享中止,或许是同享中止的第一次请求,函数将初始化irq_desc结构中止线程等候结构:wait_for_threads,disable_irq函数会运用该字段等候一切irq线程的结束。接下来设置中止操控器的电气触发类型,然后处理一些必要的IRQF_XXXX标志位。假如没有设置IRQF_NOAUTOEN标志,则调用irq_startup()翻开该irq,在irq_startup()函数中irq_desc中的enable_irq/disable_irq嵌套深度字段depth设置为0,代表该irq现已翻开,假如在没有任何disable_irq被调用的情况下,enable_irq将会打印一个正告信息。
[cpp] view plain copy
if (irq_settings_can_autoenable(desc))
irq_startup(desc);
else
/* Undo nested disables: */
desc->depth = 1;
接着,设置cpu和irq的亲缘联系:
[cpp] view plain copy
/* Set default affinity mask once everything is setup */
setup_affinity(irq, desc, mask);
然后,把新的irqaction实例链接到action链表的终究:
[cpp] view plain copy
new->irq = irq;
*old_ptr = new;
终究,唤醒中止线程,注册相关的/proc文件节点:
[cpp] view plain copy
if (new->thread)
wake_up_process(new->thread);
register_irq_proc(irq, desc);
new->dir = NULL;
register_handler_proc(irq, new);
至此,irq的请求宣告结束,当中止产生时,处理的途径将会沿着:irq_desc.handle_irq,irqaction.handler,irqaction.thread_fn(irqaction.handler的回来值是IRQ_WAKE_THREAD)这个进程进行处理。下图表明晰某个irq被请求后,各个数据结构之间的联系:
图3.1 irq各个数据结构之间的联系
4. 动态扩展irq编号
在ARM体系的移动设备中,irq的编号一般在渠道级或板级代码中事前依据硬件的衔接界说好,最大的irq数目也用NR_IRQS常量指定。几种情况下,咱们期望能够动态地添加体系中irq的数量:
装备了CONFIG_SPARSE_IRQ内核装备项,运用基数树动态办理irq_desc结构。
针对多功用复合设备,内部具有多个中止源,但中止触发引脚只需一个,为了完结驱动程序的跨渠道,不期望这些中止源的irq被硬编码在板级代码中。
中止子体系为咱们供给了以下几个api,用于动态请求/扩展irq编号:
irq_alloc_desc(node) 请求一个irq,node是对应内存节点的编号;
irq_alloc_desc_at(at, node) 在指定方位请求一个irq,假如指定方位现已被占用,则请求失利;
irq_alloc_desc_from(from, node) 从指定方位开端查找,请求一个irq;
irq_alloc_descs(irq, from, cnt, node) 请求多个接连的irq编号,从from方位开端查找;
irq_free_descs(irq, cnt) 开释irq资源;
以上这些请求函数(宏),会为咱们请求相应的irq_desc结构并初始化为默许状况,要想这些irq能够正常作业,咱们还要运用第二节说到的api,对必要的字段进行设置,例如:
irq_set_chip_and_handler_name
irq_set_handler_data
irq_set_chip_data
关于没有装备CONFIG_SPARSE_IRQ内核装备项的内核,irq_desc是一个数组,底子不或许做到动态扩展,可是许多驱动又的确运用到了上述api,尤其是mfd驱动,这些驱动并没有咱们必定要装备CONFIG_SPARSE_IRQ选项,要想不对这些驱动做出修正,你只能退让一下,在你的板级代码中把NR_IRQS界说得大一些,留出满足的保存数量
5. 多功用复合设备的中止处理
在移动设备体系中,存在着很多的多功用复合设备,最常见的是一个芯片中,内部集成了多个功用部件,或许是一个模块单元内部集成了功用部件,这些内部功用部件能够各自产生中止请求,可是芯片或许硬件模块对外只需一个中止请求引脚,咱们能够运用多种办法处理这些设备的中止请求,以下咱们逐一评论这些办法。
5.1 单一中止形式
关于这种复合设备,一般设备中会供给某种办法,以便让CPU获取真实的中止来历, 办法能够是一个内部寄存器,gpio的状况等等。单一中止形式是指驱动程序只请求一个irq,然后在中止处理程序中经过读取设备的内部寄存器,获取中止源,然后依据不同的中止源做出不同的处理,以下是一个简化后的代码:
[cpp] view plain copy
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编号,中止服务经过父中止进行分发。
关于父中止,详细的完结进程如下:
首要,父中止的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 中止线程嵌套形式
该形式与中止操控器级联形式大体类似,只不过级联形式时,父中止无需经过request_threaded_irq请求中止服务,而是直接更换了父中止的流控回调,在父中止的流控回调中完结子中止的二次分发。可是这在有些情况下会给咱们带来不方便,由于流控回调要获取子操控器的中止源,而流控回调运转在中止上下文中,关于那些子操控器需求经过慢速总线拜访的设备,在中止上下文中拜访明显不太适宜,这时咱们能够把子中止分发放在父中止的中止线程中进行,这便是我所说的所谓中止线程嵌套形式。下面是大约的完结进程:
关于父中止,详细的完结进程如下:
首要,父中止的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编号,依照正常流程请求中止服务即可。
应为子设备irq的线程嵌套特性被翻开,运用request_threaded_irq请求子设备的中止服务时,便是是供给了handler参数,中止子体系也不会运用它,一起也不会为它创立中止线程,子设备的thread_fn回调是在父中止的中止线程中,经过handle_nested_irq调用的,也便是说,尽管子中止有自己独立的irq编号,可是它们没有独立的中止线程,仅仅同享了父中止的中止服务线程。