您的位置 首页 动态

Linux中止(interrupt)子系统之一:中止流控处理层

Linux中断(interrupt)子系统之一:中断流控处理层-通用中断子系统把几种常用的流控类型进行了抽象,并为它们实现了相应的标准函数,我们只要选择相应的函数,赋值给irq所对应的irq_desc结构的handle_irq字段中即可。

1.  中止流控层简介

前期的内核版别中,简直一切的中止都是由__do_IRQ函数进行处理,可是,由于各种中止恳求的电气特性会有所不同,又或许中止操控器的特性也不同,这会导致以下这些处理也会有所不同:

何时对中止操控器宣布ack回应;

mask_irq和unmask_irq的处理;

中止操控器是否需求eoi回应?

何时翻开cpu的本地irq中止?以便答应irq的嵌套;

中止数据结构的同步和维护;


为此,通用中止子体系把几种常用的流控类型进行了笼统,并为它们完结了相应的规范函数,咱们只需挑选相应的函数,赋值给irq所对应的irq_desc结构的handle_irq字段中即可。这些规范的回调函数都是irq_flow_handler_t类型:

[cpp] view plain copy

typedef void (*irq_flow_handler_t)(unsigned int irq,  

struct irq_desc *desc);  

现在的通用中止子体系完结了以下这些规范流控回调函数,这些函数都界说在:kernel/irq/chip.c中,

handle_simple_irq  用于简易流控处理;

handle_level_irq  用于电平触发中止的流控处理;

handle_edge_irq  用于边缘触发中止的流控处理;

handle_fasteoi_irq  用于需求呼应eoi的中止操控器;

handle_percpu_irq  用于只在单一cpu呼应的中止;

handle_nested_irq  用于处理运用线程的嵌套中止;

驱动程序和板级代码能够经过以下几个API设置irq的流控函数:

irq_set_handler();

irq_set_chip_and_handler();

irq_set_chip_and_handler_name();

以下这个序列图展现了整个通用中止子体系的中止呼应进程,flow_handle一栏便是中止流控层的生命周期:

图1.1  通用中止子体系的中止呼应进程

2.  handle_simple_irq

该函数没有完结任何实质性的流控操作,在把irq_desc结构住后,直接调用handle_irq_event处理irq_desc中的action链表,它一般用于多路复用(类似于中止操控器级联)中的子中止,由父中止的流控回调中调用。或许用于无需进行硬件操控的中止中。以下是它的经过简化的代码:

[cpp] view plain copy

void  

handle_simple_irq(unsigned int irq, struct irq_desc *desc)  

{  

raw_spin_lock(&desc->lock);  

……  

handle_irq_event(desc);  

out_unlock:  

raw_spin_unlock(&desc->lock);  

}  

3.  handle_level_irq

该函数用于处理电平中止的流控操作。电平中止的特点是,只需设备的中止恳求引脚(中止线)坚持在预设的触发电平,中止就会一向被恳求,所以,为了防止同一中止被重复呼应,有必要在处理中止前先把mask irq,然后ack irq,以便复位设备的中止恳求引脚,呼应完结后再unmask irq。实践的状况稍稍杂乱一点,在mask和ack之后,还要判别IRQ_INPROGRESS标志位,假如该标志现已置位,则直接退出,不再做实质性的处理,IRQ_INPROGRESS标志在handle_irq_event的开端设置,在handle_irq_event完毕时铲除,假如监测到IRQ_INPROGRESS被置位,标明该irq正在被另一个CPU处理中,所以直接退出,对电平中止来说是正确的处理方法。可是我觉得在ARM体系中,这种状况底子就不会产生,由于在没有进入handle_level_irq之前,中止操控器没有收到ack告诉,它不会向第二个CPU再次宣布中止恳求,而当程序进入handle_level_irq之后,第一个动作便是mask irq,然后ack irq(一般是联合起来的:mask_ack_irq),这时分就算设备再次宣布中止恳求,也是在handle_irq_event完毕,unmask irq之后,这时IRQ_INPROGRESS标志现已被铲除。我不知道其他像X86之类的体系是否有不同的行为,有知道的朋友请奉告我一下。以下是handle_level_irq经过简化之后的代码:

[cpp] view plain copy

void  

handle_level_irq(unsigned int irq, struct irq_desc *desc)  

{  

raw_spin_lock(&desc->lock);  

mask_ack_irq(desc);  

if (unlikely(irqd_irq_inprogress(&desc->irq_data)))  

goto out_unlock;  

……  

if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))  

goto out_unlock;  

handle_irq_event(desc);  

if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))  

unmask_irq(desc);  

out_unlock:  

raw_spin_unlock(&desc->lock);  

}  

尽管handle_level_irq对电平中止的流控进行了必要的处理,由于电平中止的特性:只需没有ack irq,中止线会一向有用,所以咱们不会错失某次中止恳求,可是驱动程序的开发人员假如对该进程了解不透彻,特别简略产生某次中止被屡次处理的状况。特别是运用了中止线程(action->thread_fn)来呼应中止的时分:一般mask_ack_irq只会铲除中止操控器的pending状况,许多慢速设备(例如经过i2c或spi操控的设备)需求在中止线程中铲除中止线的pending状况,可是未比及中止线程被调度履行的时分,handle_level_irq早就返回了,这时现已履行过unmask_irq,设备的中止线pending处于有用状况,中止操控器会再次宣布中止恳求,结果是设备的一次中止恳求,产生了两次中止呼应。要防止这种状况,最好的方法便是不要独自运用中止线程处理中止,而是要完结request_threaded_irq()的第二个参数irq_handler_t:handler,在handle回调中运用disable_irq()封闭该irq,然后在退出中止线程回调前再enable_irq()。假定action->handler没有屏蔽irq,以下这幅图展现了电平中止期间IRQ_PROGRESS标志、本地中止状况和触发其他CPU的状况:

图3.1  电平触发中止状况

上图中色彩别离代表不同的状况:

状况赤色绿色IRQ_PROGRESS           TRUE       FALSE是否答应本地cpu中止            制止                答应  是否答应该设备再次触发中止(或许由其它cpu呼应)            制止          答应

4.  handle_edge_irq

该函数用于处理边缘触发中止的流控操作。边缘触发中止的特点是,只要设备的中止恳求引脚(中止线)的电平产生跳变时(由高变低或许有低变高),才会宣布中止恳求,由于跳变是一会儿,并且不会像电平中止能坚持住电平,所以处理不妥就特别简略漏掉一次中止恳求,为了防止这种状况,屏蔽中止的时刻有必要越短越好。内核的开发者们明显意识到这一点,在正是处理中止前,判别IRQ_PROGRESS标志没有被设置的状况下,仅仅ack irq,并没有mask irq,以便复位设备的中止恳求引脚,在这之后的中止处理期间,别的的cpu能够再次呼应同一个irq恳求,假如IRQ_PROGRESS现已置位,标明另一个CPU正在处理该irq的上一次恳求,这种状况下,他仅仅简略地设置IRQS_PENDING标志,然后mask_ack_irq后退出,中止恳求交由本来的CPU持续处理。由于是mask_ack_irq,所以体系实践上只答应挂起一次中止。

[cpp] view plain copy

if (unlikely(irqd_irq_disabled(&desc->irq_data) ||  

irqd_irq_inprogress(&desc->irq_data) || !desc->acTIon)) {  

if (!irq_check_poll(desc)) {  

desc->istate |= IRQS_PENDING;  

mask_ack_irq(desc);  

goto out_unlock;  

}  

}  

desc->irq_data.chip->irq_ack(&desc->irq_data);  

从上面的剖析能够知道,处理中止期间,另一次恳求或许由另一个cpu呼应后挂起,所以在处理完本次恳求后还要判别IRQS_PENDING标志,假如被置位,当时cpu要接着处理被另一个cpu“托付”的恳求。内核在这里设置了一个循环来处理这种状况,直到IRQS_PENDING标志无效停止,并且由于另一个cpu在呼应并挂起irq时,会mask irq,所以在循环中要再次unmask irq,以便另一个cpu能够再次呼应并挂起irq:

[cpp] view plain copy

do {  

……  

if (unlikely(desc->istate & IRQS_PENDING)) {  

if (!irqd_irq_disabled(&desc->irq_data) &&  

irqd_irq_masked(&desc->irq_data))  

unmask_irq(desc);  

}  

handle_irq_event(desc);  

} while ((desc->istate & IRQS_PENDING) &&  

!irqd_irq_disabled(&desc->irq_data));  

IRQS_PENDING标志会在handle_irq_event中铲除。

图4.1   边缘触发中止状况

上图中色彩别离代表不同的状况:

状况        赤色        绿色IRQ_PROGRESS        TRUE        FALSE是否答应本地cpu中止        制止        答应是否答应该设备再次触发中止(或许由其它cpu呼应)        制止        答应是否处于中止上下文    处于中止上下文    处于进程上下文
由图4.1也能够看出,在处理软件中止(sofTIrq)期间,此刻依然处于中止上下文中,可是cpu的本地中止是处于翻开状况的,这标明此刻嵌套中止答应产生,不过这不要紧,由于重要的处理现已完结,被嵌套的也仅仅软件中止部分罢了。这个也便是内核区别top和bottom两个部分的初衷吧。

5.  handle_fasteoi_irq

现代的中止操控器一般会在硬件上完结了中止流控功用,例如ARM体系中的GIC通用中止操控器。关于这种中止操控器,CPU只需求在每次处理完中止后宣布一个end of interrupt(eoi),咱们无需重视何时mask,何时unmask。不过尽管想着很完美,工作总有特别的时分,所以内核仍是给了咱们干预的时机,它使用irq_desc结构中的preflow_handler字段,在正式处理中止前会经过preflow_handler函数调用该回调。

[cpp] view plain copy

void  

handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)  

{  

raw_spin_lock(&desc->lock);  

if (unlikely(irqd_irq_inprogress(&desc->irq_data)))  

if (!irq_check_poll(desc))  

goto out;  

……  

if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {  

desc->istate |= IRQS_PENDING;  

mask_irq(desc);  

goto out;  

}  

if (desc->istate & IRQS_ONESHOT)  

mask_irq(desc);  

preflow_handler(desc);  

handle_irq_event(desc);  

out_eoi:  

desc->irq_data.chip->irq_eoi(&desc->irq_data);  

out_unlock:  

raw_spin_unlock(&desc->lock);  

return;  

……  

}  

此外,内核还供给了别的一个eoi版的函数:handle_edge_eoi_irq,它的处理类似于handle_edge_irq,仅仅无需完结mask和unmask的逻辑。

6.  handle_percpu_irq

该函数用于smp体系,当某个irq只在一个cpu上处理时,咱们能够无需用自旋锁对数据进行维护,也无需处理cpu之间的中止嵌套重入,所以函数很简略:

[cpp] view plain copy

void  

handle_percpu_irq(unsigned int irq, struct irq_desc *desc)  

{  

struct irq_chip *chip = irq_desc_get_chip(desc);  

kstat_incr_irqs_this_cpu(irq, desc);  

if (chip->irq_ack)  

chip->irq_ack(&desc->irq_data);  

handle_irq_event_percpu(desc, desc->acTIon);  

if (chip->irq_eoi)  

chip->irq_eoi(&desc->irq_data);  

}  

7.  handle_nested_irq

该函数用于完结其间一种中止同享机制,当多个中止同享某一根中止线时,咱们能够把这个中止线作为父中止,同享该中止的各个设备作为子中止,在父中止的中止线程中决议和分发呼应哪个设备的恳求,在得出真实宣布恳求的子设备后,调用handle_nested_irq来呼应中止。所以,该函数是在进程上下文履行的,咱们也无需扫描和履行irq_desc结构中的acTIon链表。父中止在初始化时有必要经过irq_set_nested_thread函数清晰奉告中止子体系:这些子中止归于线程嵌套中止类型,这样驱动程序在请求这些子中止时,内核不会为它们树立自己的中止线程,一切的子中止同享父中止的中止线程。

[cpp] view plain copy

void handle_nested_irq(unsigned int irq)  

{  

……  

might_sleep();  

raw_spin_lock_irq(&desc->lock);  

……  

action = desc->action;  

if (unlikely(!action || irqd_irq_disabled(&desc->irq_data)))  

goto out_unlock;  

irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);  

raw_spin_unlock_irq(&desc->lock);  

action_ret = action->thread_fn(action->irq, action->dev_id);  

raw_spin_lock_irq(&desc->lock);  

irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);  

out_unlock:  

raw_spin_unlock_irq(&desc->lock);  

}  

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/dongtai/89437.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部