前言
編寫驅動的時候,經常會用到中斷,這時候我們在驅動初始化時就得申請中斷,那么問題來了,中斷號是多少呢?以前的中斷號在板級相關的頭文件里面已經靜態定義好了,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月