zoukankan      html  css  js  c++  java
  • 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
    ");
                    short_irq = -1;
            }
    } while (short_irq < 0 && count++ < 5);
    if (short_irq < 0)
            printk("short: probe failed %i times, giving up
    ", 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月

  • 相关阅读:
    反正我是猜错,关于javascript包装对象的一个坑
    《编写可维护的javascript》推荐的编码规范之——编程实践
    《编写可维护的javascript》推荐的编码规范之——编程风格
    35行的山寨版jQuery
    Css进度条
    nginx的location和proxy_pass是否带斜杠的区别
    mongodb笔记
    在安装完 docker for windows 之后,hype-v打开,之后再关闭,vmware无法使用,虚拟机报错:传输(vmdb)错误-32:pipe:read failed
    awk笔记备忘
    sed命令笔记备忘
  • 原文地址:https://www.cnblogs.com/rongpmcu/p/7662223.html
Copyright © 2011-2022 走看看