linux irq 自动探测

前言

  编写驱动的时候,经常会用到中断,这时候我们在驱动初始化时就得申请中断,那么问题来了,中断号是多少呢?以前的中断号在板级相关的头文件里面已经静态定义好了,bsp的代码在内核启动过程也会根据那个帮我们建立好hw irq到irq的映射,我们直接用它静态定义的irq就可以了。但是在硬件越来越复杂的今天,经常会碰到一个系统里同时有几个中断控制器同时存在的情况(级联),内核因此实现了另一套处理机制(irq domain),这个时候,我们通过翻看datasheet,也仅仅能知道hw irq,最终具体映射到哪个irq上了呢,还真不是那么容易知道的,运气好的呢,在sdk开发文档里面已经标明清楚了,否则我们就只能read the fucking source code了。当然,我们还有另一种尝试方法,就是内核提供的irq探测机制。

irq探测机制使用

以下是LDD3中的并口示例代码:

int count = 0;
do
{
        unsigned long mask;
        mask = probe_irq_on();
        outb_p(0x10,short_base+2); /* enable reporting */
        outb_p(0x00,short_base); /* clear the bit */
        outb_p(0xFF,short_base); /* set the bit: interrupt! */
        outb_p(0x00,short_base+2); /* disable reporting */
        udelay(5); /* give it some time */
        short_irq = probe_irq_off(mask);
        if (short_irq == 0) { /* none of them? */
                printk(KERN_INFO "short: no irq reported by probe\n");
                short_irq = -1;
        }
} while (short_irq < 0 && count++ < 5);
if (short_irq < 0)
        printk("short: probe failed %i times, giving up\n", count);

调用probe_irq_on函数之后,驱动程序要安排设备产生至少一次中断,上面的例子就是通过控制寄存器来使设备触发一次中断,在设备产生中断之后,驱动程序要调用probe_irq_off,并将前面probe_irq_on返回的位掩码作为参数传递给它(其实该掩码暂时无意义,后面分析内部实现的时候会发现这点)。probe_irq_off返回probe_irq_on之后发生的中断编号。如果没有中断发生,就返回0。如果产生了多次中断,出现了二义性,就返回负数(该负数是最小中断号的负数)。

使用内核提供的接口探测中断号时,需要注意在调用probe_irq_on之后启用设备中断,在调用probe_irq_off之前禁用中断。另外,在probe_irq_off之后,需要处理设备上待处理的中断,同时,irq探测机制是不适合中断共享方式的。

irq 探测原理

需要注意的是,只有在配置了CONFIG_GENERIC_IRQ_PROBE的时候,irq探测机制才会生效。下面看probe_irq_on内部实现:

/*
 * Autodetection depends on the fact that any interrupt that
 * comes in on to an unassigned handler will get stuck with
 * "IRQS_WAITING" cleared and the interrupt disabled.
 */


unsigned long probe_irq_on(void)
{
    struct irq_desc *desc;
    unsigned long mask = 0;
    int i;

    /*
     * quiesce the kernel, or at least the asynchronous portion
     */
    async_synchronize_full();
    mutex_lock(&probing_active);
    /*
     * something may have generated an irq long ago and we want to
     * flush such a longstanding irq before considering it as spurious.
     */
    for_each_irq_desc_reverse(i, desc) {
        raw_spin_lock_irq(&desc->lock);
        if (!desc->action && irq_settings_can_probe(desc)) {
            /*
             * Some chips need to know about probing in
             * progress:
             */
            if (desc->irq_data.chip->irq_set_type)
                desc->irq_data.chip->irq_set_type(&desc->irq_data,
                             IRQ_TYPE_PROBE);
            irq_startup(desc, false);
        }
        raw_spin_unlock_irq(&desc->lock);
    }

    /* Wait for longstanding interrupts to trigger. */
    msleep(20);

    /*
     * enable any unassigned irqs
     * (we must startup again here because if a longstanding irq
     * happened in the previous stage, it may have masked itself)
     */
    for_each_irq_desc_reverse(i, desc) {
        raw_spin_lock_irq(&desc->lock);
        if (!desc->action && irq_settings_can_probe(desc)) {
            desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;
            if (irq_startup(desc, false))
                desc->istate |= IRQS_PENDING;
        }
        raw_spin_unlock_irq(&desc->lock);
    }

    /*
     * Wait for spurious interrupts to trigger
     */
    msleep(100);

    /*
     * Now filter out any obviously spurious interrupts
     */
    for_each_irq_desc(i, desc) {
        raw_spin_lock_irq(&desc->lock);

        if (desc->istate & IRQS_AUTODETECT) {
            /* It triggered already - consider it spurious. */
            if (!(desc->istate & IRQS_WAITING)) {
                desc->istate &= ~IRQS_AUTODETECT;
                irq_shutdown(desc);
            } else
                if (i < 32)
                    mask |= 1 << i;
        }
        raw_spin_unlock_irq(&desc->lock);
    }

    return mask;
}

三次循环遍历irq_desc,第一次反向遍历,如果对应的irq还没有注册,即desc->action为NULL,且该irq允许探测irq_settings_can_probe(desc),那么使能该中断;第二次还是反向遍历,如果对应的irq还没有注册且允许探测,那么就在该irq对应的desc上的istate里添加IRQS_AUTODETECT | IRQS_WAITING标志;第三次,正向遍历,如果发现irq对应的desc上的istate的IRQS_AUTODETECT存在,但是它的IRQS_WAITING被清除了,那么说明运行到这里,发生了莫名其妙的中断,这个中断当然不是我们将要探测的中断,毕竟我们都还没触发它产生中断,于是就清除掉IRQS_AUTODETECT并禁用它的中断。从上面的分析可以看出,probe_irq_on就是在适合的中断上添加了IRQS_AUTODETECT | IRQS_WAITING标志,并暂时开启了它们的中断能力。

分析完probe_irq_on,现在继续分析probe_irq_off

int probe_irq_off(unsigned long val)
{
    int i, irq_found = 0, nr_of_irqs = 0;
    struct irq_desc *desc;

    for_each_irq_desc(i, desc) {
        raw_spin_lock_irq(&desc->lock);

        if (desc->istate & IRQS_AUTODETECT) {
            if (!(desc->istate & IRQS_WAITING)) {
                if (!nr_of_irqs)
                    irq_found = i;
                nr_of_irqs++;
            }
            desc->istate &= ~IRQS_AUTODETECT;
            irq_shutdown(desc);
        }
        raw_spin_unlock_irq(&desc->lock);
    }
    mutex_unlock(&probing_active);

    if (nr_of_irqs > 1)
        irq_found = -irq_found;

    return irq_found;
}

注意,在probe_irq_off前,需要我们自己触发中断,这个请参考前面的例子。probe_irq_off里面就是一次遍历,寻找有设置IRQS_AUTODETECT,但是无IRQS_WAITING的,这种情况,意味着该irq号对应的设备有中断产生(后面我会分析具体清除IRQS_WAITING的地方)。另外,我们也会发现,probe_irq_off只会记录第一个发现的中断,并且在发现有多个中断产生时,会将第一个发现的中断号取反并返回。

下面分析下具体清除IRQS_WAITING的地方,以以前的中断处理为例,毕竟现在的中断控制器都会定义自己控制器的中断处理,各式各样,这里面的思想是一样的。当一个中断产生时,调用的逻辑如下:

irq_handler-->arch_irq_handler_default-->asm_do_IRQ-->handle_IRQ-->generic_handle_irq-->generic_handle_irq_desc-->desc->handle_irq-->handle_level_irq|handle_edge_irq

handle_level_irq中:

void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
...
...
...
    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
...
...
...
}

这说明,只要某个irq有中断产生,它对应的desc里面的IRQS_WAITING就会被清除。

总结

本文讨论的中断探测机制在我们不知道中断号的情况时,也不失为一种获取中断号的方法,对吧!

完!
2013年7月

posted @ 2017-10-14 10:19 rongpmcu 阅读(...) 评论(...) 编辑 收藏