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月


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM