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);
}