深入分析request_irq的dev_id參數作用

轉自:http://blog.chinaunix.net/uid-20696246-id-1891946.html


request_irq的作用是申請使用IRQ並註冊中斷處理程序。

request_irq()函數的原型如下:

/* kernel/irq/manage.c */
int request_irq(

unsigned int irq,
    irqreturn_t (*handler)(int, void *, struct pt_regs *),
    unsigned long irqflags, 
    const char *devname, 
    void *dev_id );

 

我們知道,當使用內核共享中斷時,request_irq必須要提供dev_id參數,並且dev_id的值必須唯一。那麼這裏提供唯一的dev_id值的究竟是做什麼用的?

 

起先我以爲dev_id的值是提供給kernel進行判斷共享中斷線上的哪一個設備產生了中斷(即哪個irqaction產生中斷),然後執行相應的中斷處理函數(irqaction->handler)。實際上不是的,我們來看看《Linux Kernel Development – Second Edition》第六章中Shared Handlers這一節,其中有段總結性的文字如下:

 

When the kernel receives an interrupt, it invokes sequentially each registered handler on the line. Therefore, it is important that the handler be capable of distinguishing whether it generated a given interrupt. The handler must quickly exit if its associated device did not generate the interrupt. This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware does indeed have such a feature.

 

這段話的大概意思是,發生中斷時,內核並不判斷究竟是共享中斷線上的哪個設備產生了中斷,它會循環執行所有該中斷線上註冊的中斷處理函數(即irqaction->handler函數)。因此irqaction->handler函數有責任識別出是否是自己的硬件設備產生了中斷,然後再執行該中斷處理函數。通常是通過讀取該硬件設備提供的中斷flag標誌位進行判斷。

 

那既然kernel循環執行該中斷線上註冊的所有irqaction->handler函數,把識別究竟是哪個硬件設備產生了中斷這件事交給中斷處理函數本身去做,那request_irqdev_id參數究竟是做什麼用的?

 

我總結了一下,實際上dev_id作用主要有兩點:

一.在中斷處理程序釋放時使用到dev_id

When your driver unloads, you need to unregister your interrupt handler and potentially disable the interrupt line. To do this, call

void free_irq(unsigned int irq, void *dev_id)

……

The fifth parameter, dev_id, is used primarily for shared interrupt lines. When an interrupt handler is freed (discussed later), dev_id provides a unique cookie to allow the removal of only the desired interrupt handler from the interrupt line. Without this parameter, it would be impossible for the kernel to know which handler to remove on a given interrupt line.

 

這裏《LKD2》講了很清楚,當調用free_irq註銷中斷處理函數時(通常卸載驅動時其中斷處理函數也會被註銷掉),因爲dev_id是唯一的,所以可以通過它來判斷從共享中斷線上的多箇中斷處理程序中刪除指定的一個。如果沒有這個參數,那麼kernel不可能知道給定的中斷線上到底要刪除哪一個處理程序。

 

下面我們來看一下free_irq的代碼:

void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc;
    struct irqaction **p;
    unsigned long flags;

    WARN_ON(in_interrupt());
    if (irq >= NR_IRQS)
        return;

    desc = irq_desc + irq;    /* 獲取該中斷號對應的irq_desc */
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;        /* 找到該irq的irqaction鏈表 */
    for (;;) {
        struct irqaction *action = *p;

        if (action) {
            struct irqaction **pp = p;

            p = &action->next;
            if (action->dev_id != dev_id) 
                continue;   /* 這兩句進行循環判斷,直到找到相應dev_id設備的irqaction */

            /* Found it - now remove it from the list of entries */
            *pp = action->next;   /* 指向下一個irqaction */

            /* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
            if (desc->chip->release)
                desc->chip->release(irq, dev_id);
#endif

            if (!desc->action) {
                desc->status |= IRQ_DISABLED;
                if (desc->chip->shutdown)
                    desc->chip->shutdown(irq);
                else
                    desc->chip->disable(irq);
            }
            recalculate_desc_flags(desc);
            spin_unlock_irqrestore(&desc->lock, flags);
            unregister_handler_proc(irq, action);

            /* Make sure it's not being used on another CPU */
            synchronize_irq(irq);
            kfree(action);        /* 刪除該設備(dev_id)的irqaction */
            return;
        }
        printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);
        spin_unlock_irqrestore(&desc->lock, flags);
        return;
    }
}

 

二.將使用該中斷處理程序的設備結構體傳遞給該中斷處理程序:

首先看兩個基礎條件:

1)  內核中的各個設備結構體肯定是唯一的,因此滿足dev_id唯一這個要求

2)  dev_id參數會在發生中斷時傳遞給該中斷的服務程序。

典型的中斷服務程序定義如下:

 

static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs);

 

request_irqdev_id參數會傳遞給該中斷服務程序的dev_id。因此也可以將驅動程序的設備結構體通過dev_id傳遞給中斷服務程序。

這樣做作用主要有兩個:

1) 中斷服務程序可能使用到設備結構體中的信息

如s3c2410的iis音頻驅動,它是將DMA Channel結構體通過dev_id傳遞給中斷服務程序

/* arch/arm/mach-s3c2410/dma.c */
int s3c2410_dma_request(unsigned int channel, struct s3c2410_dma_client *client,void *dev)
{
     
err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED, client->name, (void *)chan);
……
}

 

目的是中斷處理函數s3c2410_dma_irq內需要用到chan結構體的duf成員和load_state成員等。

使用方法如下:

/* arch/arm/mach-s3c2410/dma.c */
static irqreturn_t
s3c2410_dma_irq(int irq, void *devpw, struct pt_regs *regs)
{
    struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw; /* 這裏強制轉化成request_irq時傳給dev_id的設備類型,此處即s3c2410_dma_chan ,下面便可以使用它*/
    struct s3c2410_dma_buf *buf;
    buf = chan->curr;
    ……
}

 

因此,我們可以通過將設備結構傳遞給request_irq的dev_id參數這種機制,在中斷處理函數中使用該設備結構體,這是大部分驅動程序通用的一種手法。

 

2)前面我們講了若使用共享中斷,那麼中斷處理函數自身需要能識別是否是自己的設備產生了中斷。通常這是通過讀取該硬件設備提供的中斷flag標誌位進行判斷的。

而往往驅動程序裏定義的設備結構體通常包含了該設備的IO地址,加上偏移就可以算出中斷狀態寄存器及中斷flag標誌位的IO地址信息。因此將設備結構體通過dev_id傳遞給設備中斷處理程序的另一個作用就是使用共享中斷時,可以在中斷處理函數內通過讀取該設備結構(dev_id) 中提供的中斷Flag標誌位地址信息進行判斷,是否是該設備產生了中斷,然後再進一步判斷是否繼續往下執行還是跳到下一個irqaction->handler函數再判斷執行。當然,如果本來就知道該設備中斷狀態寄存器的IO地址,也可以選擇直接readl該地址信息進行判斷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章