1. 程式人生 > 其它 >linux驅動移植-中斷註冊

linux驅動移植-中斷註冊

在之前我們介紹了linux中斷子系統向驅動程式提供了註冊中斷的API:

  • request_threaded_irq;

  • request_irq;

一、中斷註冊

1.1 ISR的安裝

我們在編寫裝置驅動程式時,如果需要使用到某個中斷,那麼我們就需要在中斷到來之前註冊該中斷的處理處理函式,也就是ISR - Interrupt Service Routine。

由於早期處理器,存在IRQ編號共享問題,因此Linux的中斷子系統是按照服務中斷共享的模式設計的,因此ISR的安裝需要分為兩級:

  • 第一級是針對這個IRQ線的,稱為generic handler,通過generic_handle_irq,中斷的控制被傳遞到了與體系結構無關的中斷流控層,
    也就是我們上一節介紹的中斷描述符的中斷流控處理回撥(desc->handle_irq);
  • 第二級是針對掛載在這個IRQ線上的不同裝置的,稱為specific handler;

上一節我們分析了S3C2440中斷相關的程式碼,Generic handler的初始化通過s3c24xx_irq_map中的irq_set_chip_and_handler函式實現的。

Specific handler的安裝則是由request_threaded_irq函式完成的。request_threaded_irq可以將中斷執行緒化,那為什麼要進行中斷執行緒化。

1.2 中斷執行緒化

在linux中,中斷具有最高優先順序,無論什麼時刻,只要有中斷產生,核心將會立即執行相應的中斷處理處理,等到所有掛起的中斷和軟中斷處理完畢後才能執行正常的任務,因此可能造成實時任務得不到及時的處理。

中斷執行緒化之後,中斷將作為核心執行緒執行而被賦予不同的實時優先順序,實時任務可以有比中斷執行緒更高的優先順序。這樣,具有最高優先順序的實時任務就能得到優先處理,即使在嚴重負載下仍有實時性保證。但是,並不是所有的中斷都可以執行緒化,比如時鐘中斷,主要用來維護系統時間以及定時器等,其中定時器是作業系統的脈搏,一旦被執行緒化,就有可能被掛起,這樣後果將不堪設想,所以不應當被執行緒化。

1.3 request_threaded_irq

request_threaded_irq函式定義在kernel/irq/manage.c檔案中:

/**
 *      request_threaded_irq - allocate an interrupt line
 *      @irq: Interrupt line to allocate
 *      @handler: Function to be called when the IRQ occurs.
 *                Primary handler for threaded interrupts
 *                If NULL and thread_fn != NULL the default
 *                primary handler is installed
 *      @thread_fn: Function called from the irq handler thread
 *                  If NULL, no irq thread is created
 *      @irqflags: Interrupt type flags
 *      @devname: An ascii name for the claiming device
 *      @dev_id: A cookie passed back to the handler function
 *
 *      This call allocates interrupt resources and enables the
 *      interrupt line and IRQ handling. From the point this
 *      call is made your handler function may be invoked. Since
 *      your handler function must clear any interrupt the board
 *      raises, you must take care both to initialise your hardware
 *      and to set up the interrupt handler in the right order.
 *
 *      If you want to set up a threaded irq handler for your device
 *      then you need to supply @handler and @thread_fn. @handler is
 *      still called in hard interrupt context and has to check
 *      whether the interrupt originates from the device. If yes it
 *      needs to disable the interrupt on the device and return
 *      IRQ_WAKE_THREAD which will wake up the handler thread and run
 *      @thread_fn. This split handler design is necessary to support
 *      shared interrupts.
 *
 *      Dev_id must be globally unique. Normally the address of the
 *      device data structure is used as the cookie. Since the handler
 *      receives this value it makes sense to use it.
 *
 *      If your interrupt is shared you must pass a non NULL dev_id
 *      as this is required when freeing the interrupt.
 *
 *      Flags:
 *
 *      IRQF_SHARED             Interrupt is shared
 *      IRQF_TRIGGER_*          Specify active edge(s) or level
 *
 
*/ int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) { struct irqaction *action; struct irq_desc *desc; int retval; if (irq == IRQ_NOTCONNECTED) return -ENOTCONN; /* * Sanity-check: shared interrupts must pass in a real dev-ID, * otherwise we'll have trouble later trying to figure out * which interrupt is which (messes up the interrupt freeing * logic etc). * * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and * it cannot be set along with IRQF_NO_SUSPEND. */ if (((irqflags & IRQF_SHARED) && !dev_id) || (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) || ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND))) return -EINVAL; desc = irq_to_desc(irq); // 根據IRQ編號獲取中斷描述符 if (!desc) return -EINVAL; if (!irq_settings_can_request(desc) || WARN_ON(irq_settings_is_per_cpu_devid(desc))) return -EINVAL; if (!handler) { if (!thread_fn) return -EINVAL; handler = irq_default_primary_handler; } action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); // 分配irqaction結構 if (!action) return -ENOMEM; action->handler = handler; action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id; retval = irq_chip_pm_get(&desc->irq_data); if (retval < 0) { kfree(action); return retval; } retval = __setup_irq(irq, desc, action); if (retval) { irq_chip_pm_put(&desc->irq_data); kfree(action->secondary); kfree(action); } return retval; }

該函式主要實現以下功能:

  • 首先呼叫irq_to_desc根據IRQ編號獲取中斷描述符desc;
  • 然後分配一個irqaction結構,用引數handler,thread_fn,irqflags,devname,dev_id初始化irqaction結構的各欄位;
  • 最後把大部分工作委託給__setup_irq函式:

1.3 __setup_irq

__setup_irq函式也是定義在kernel/irq/manage.c檔案中:

/*
 * Internal function to register an irqaction - typically used to
 * allocate special interrupts that are part of the architecture.
 *
 * Locking rules:
 *
 * desc->request_mutex  Provides serialization against a concurrent free_irq()
 *   chip_bus_lock      Provides serialization for slow bus operations
 *     desc->lock       Provides serialization against hard interrupts
 *
 * chip_bus_lock and desc->lock are sufficient for all other management and
 * interrupt related functions. desc->request_mutex solely serializes
 * request/free_irq().
 */
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
        struct irqaction *old, **old_ptr;
        unsigned long flags, thread_mask = 0;
        int ret, nested, shared = 0;

        if (!desc)
                return -EINVAL;

        if (desc->irq_data.chip == &no_irq_chip)
                return -ENOSYS;
        if (!try_module_get(desc->owner))
                return -ENODEV;

        new->irq = irq;

        /*
         * If the trigger type is not specified by the caller,
         * then use the default for this interrupt.
         */
        if (!(new->flags & IRQF_TRIGGER_MASK))
                new->flags |= irqd_get_trigger_type(&desc->irq_data);

        /*
         * Check whether the interrupt nests into another interrupt
         * thread.
         */
        nested = irq_settings_is_nested_thread(desc);
        if (nested) {
                if (!new->thread_fn) {
                        ret = -EINVAL;
                        goto out_mput;
                }
                /*
                 * Replace the primary handler which was provided from
                 * the driver for non nested interrupt handling by the
                 * dummy function which warns when called.
                 */
                new->handler = irq_nested_primary_handler;
        } else {
                if (irq_settings_can_thread(desc)) {
                        ret = irq_setup_forced_threading(new);
                        if (ret)
                                goto out_mput;
                }
        }
 /*
         * Create a handler thread when a thread function is supplied
         * and the interrupt does not nest into another interrupt
         * thread.
         */
        if (new->thread_fn && !nested) {
                ret = setup_irq_thread(new, irq, false);
                if (ret)
                        goto out_mput;
                if (new->secondary) {
                        ret = setup_irq_thread(new->secondary, irq, true);
                        if (ret)
                                goto out_thread;
                }
        }

        /*
         * Drivers are often written to work w/o knowledge about the
         * underlying irq chip implementation, so a request for a
         * threaded irq without a primary hard irq context handler
         * requires the ONESHOT flag to be set. Some irq chips like
         * MSI based interrupts are per se one shot safe. Check the
         * chip flags, so we can avoid the unmask dance at the end of
         * the threaded handler for those.
         */
        if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
                new->flags &= ~IRQF_ONESHOT;

        /*
         * Protects against a concurrent __free_irq() call which might wait
         * for synchronize_hardirq() to complete without holding the optional
         * chip bus lock and desc->lock. Also protects against handing out
         * a recycled oneshot thread_mask bit while it's still in use by
         * its previous owner.
         */
        mutex_lock(&desc->request_mutex);

        /*
         * Acquire bus lock as the irq_request_resources() callback below
         * might rely on the serialization or the magic power management
         * functions which are abusing the irq_bus_lock() callback,
         */
        chip_bus_lock(desc);
 /* First installed action requests resources. */
        if (!desc->action) {
                ret = irq_request_resources(desc);
                if (ret) {
                        pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
                               new->name, irq, desc->irq_data.chip->name);
                        goto out_bus_unlock;
                }
        }

        /*
         * The following block of code has to be executed atomically
         * protected against a concurrent interrupt and any of the other
         * management calls which are not serialized via
         * desc->request_mutex or the optional bus lock.
         */
        raw_spin_lock_irqsave(&desc->lock, flags);
        old_ptr = &desc->action;
        old = *old_ptr;
        if (old) {
                /*
                 * Can't share interrupts unless both agree to and are
                 * the same type (level, edge, polarity). So both flag
                 * fields must have IRQF_SHARED set and the bits which
                 * set the trigger type must match. Also all must
                 * agree on ONESHOT.
                 * Interrupt lines used for NMIs cannot be shared.
                 */
                unsigned int oldtype;

                if (desc->istate & IRQS_NMI) {
                        pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n",
                                new->name, irq, desc->irq_data.chip->name);
                        ret = -EINVAL;
                        goto out_unlock;
                }

                /*
                 * If nobody did set the configuration before, inherit
                 * the one provided by the requester.
                 */
                if (irqd_trigger_type_was_set(&desc->irq_data)) {
                        oldtype = irqd_get_trigger_type(&desc->irq_data);
                } else {
                        oldtype = new->flags & IRQF_TRIGGER_MASK;
                        irqd_set_trigger_type(&desc->irq_data, oldtype);
                }

                if (!((old->flags & new->flags) & IRQF_SHARED) ||
                    (oldtype != (new->flags & IRQF_TRIGGER_MASK)) ||
                    ((old->flags ^ new->flags) & IRQF_ONESHOT))
                        goto mismatch;

                /* All handlers must agree on per-cpuness */
                if ((old->flags & IRQF_PERCPU) !=
                    (new->flags & IRQF_PERCPU))
                        goto mismatch;

                /* add new interrupt at end of irq queue */
                do {
                        /*
                         * Or all existing action->thread_mask bits,
                         * so we can find the next zero bit for this
                         * new action.
                         */
                        thread_mask |= old->thread_mask;
                        old_ptr = &old->next;
                        old = *old_ptr;
                } while (old);
                shared = 1;
        }
       ...
}
View Code

該函式實現程式碼實在太長了,下面我們具體介紹。

二、__setup_irq

2.1 中斷觸發型別

如果沒有設定中斷觸發型別,則使用預設值;

        /*
         * If the trigger type is not specified by the caller,
         * then use the default for this interrupt.
         */
        if (!(new->flags & IRQF_TRIGGER_MASK))
                new->flags |= irqd_get_trigger_type(&desc->irq_data);

中斷觸發型別主要包括:

flag定義 描述
IRQF_TRIGGER_XXX 描述該interrupt line觸發型別的flag
IRQF_DISABLED 首先要說明的是這是一個廢棄的flag,在新的核心中,該flag沒有任何的作用了。具體可以參考:Disabling IRQF_DISABLED 
舊的核心(2.6.35版本之前)認為有兩種interrupt handler:slow handler和fast handle。在request irq的時候,對於fast handler,需要傳遞IRQF_DISABLED的引數,確保其中斷處理過程中是關閉CPU的中斷,因為是fast handler,執行很快,即便是關閉CPU中斷不會影響系統的效能。但是,並不是每一種外設中斷的handler都是那麼快(例如磁碟),因此就有 slow handler的概念,說明其在中斷處理過程中會耗時比較長。對於這種情況,在執行interrupt handler的時候不能關閉CPU中斷,否則對系統的performance會有影響。 
新的核心已經不區分slow handler和fast handle,都是fast handler,都是需要關閉CPU中斷的,那些需要後續處理的內容推到threaded interrupt handler中去執行。
IRQF_SHARED

這是flag用來描述一個interrupt line是否允許在多個裝置中共享。如果中斷控制器可以支援足夠多的interrupt source,那麼在兩個外設間共享一個interrupt request line是不推薦的,畢竟有一些額外的開銷(發生中斷的時候要逐個詢問是不是你的中斷,軟體上就是遍歷action list),因此外設的irq handler中最好是一開始就啟動判斷,看看是否是自己的中斷,如果不是,返回IRQ_NONE,表示這個中斷不歸我管。 早期PC時代,使用8259中斷控制器,級聯的8259最多支援15個外部中斷,但是PC外設那麼多,因此需要irq share。現在,ARM平臺上的系統設計很少會採用外設共享IRQ方式,畢竟一般ARM SOC提供的有中斷功能的GPIO非常的多,足夠用的。 當然,如果確實需要兩個外設共享IRQ,那也只能如此設計了。對於HW,中斷控制器的一個interrupt source的引腳要接到兩個外設的interrupt request line上,怎麼接?直接連線可以嗎?當然不行,對於低電平觸發的情況,我們可以考慮用與門連線中斷控制器和外設。

IRQF_PROBE_SHARED IRQF_SHARED用來表示該interrupt action descriptor是允許和其他device共享一個interrupt line(IRQ number),但是實際上是否能夠share還是需要其他條件:例如觸發方式必須相同。有些驅動程式可能有這樣的呼叫場景:我只是想scan一個irq table,看看哪一個是OK的,這時候,如果即便是不能和其他的驅動程式share這個interrupt line,我也沒有關係,我就是想scan看看情況。這時候,caller其實可以預見sharing mismatche的發生,因此,不需要核心列印“Flags mismatch irq……“這樣冗餘的資訊
IRQF_PERCPU 在SMP的架構下,中斷有兩種mode,一種中斷是在所有processor之間共享的,也就是global的,一旦中斷產生,interrupt controller可以把這個中斷送達多個處理器。當然,在具體實現的時候不會同時將中斷送達多個CPU,一般是軟體和硬體協同處理,將中斷送達一個CPU處理。但是一段時間內產生的中斷可以平均(或者按照既定的策略)分配到一組CPU上。這種interrupt mode下,interrupt controller針對該中斷的operational register是global的,所有的CPU看到的都是一套暫存器,一旦一個CPU ack了該中斷,那麼其他的CPU看到的該interupt source的狀態也是已經ack的狀態。 
和global對應的就是per cpu interrupt了,對於這種interrupt,不是processor之間共享的,而是特定屬於一個CPU的。例如GIC中interrupt ID等於30的中斷就是per cpu的(這個中斷event被用於各個CPU的local timer),這個中斷號雖然只有一個,但是,實際上控制該interrupt ID的暫存器有n組(如果系統中有n個processor),每個CPU看到的是不同的控制暫存器。在具體實現中,這些暫存器組有兩種形態,一種是banked,所有CPU操作同樣的暫存器地址,硬體系統會根據訪問的cpu定向到不同的暫存器,另外一種是non banked,也就是說,對於該interrupt source,每個cpu都有自己獨特的訪問地址。
IRQF_NOBALANCING 這也是和multi-processor相關的一個flag。對於那些可以在多個CPU之間共享的中斷,具體送達哪一個processor是有策略的,我們可以在多個CPU之間進行平衡。如果你不想讓你的中斷參與到irq balancing的過程中那麼就設定這個flag
IRQF_IRQPOLL  
IRQF_ONESHOT one shot本身的意思的只有一次的,結合到中斷這個場景,則表示中斷是一次性觸發的,不能巢狀。對於primary handler,當然是不會巢狀,但是對於threaded interrupt handler,我們有兩種選擇,一種是mask該interrupt source,另外一種是unmask該interrupt source。一旦mask住該interrupt source,那麼該interrupt source的中斷在整個threaded interrupt handler處理過程中都是不會再次觸發的,也就是one shot了。這種handler不需要考慮重入問題。 
具體是否要設定one shot的flag是和硬體系統有關的,我們舉一個例子,比如電池驅動,電池裡面有一個電量計,是使用HDQ協議進行通訊的,電池驅動會註冊一個threaded interrupt handler,在這個handler中,會通過HDQ協議和電量計進行通訊。對於這個handler,通過HDQ進行通訊是需要一個完整的HDQ互動過程,如果中間被打斷,整個通訊過程會出問題,因此,這個handler就必須是one shot的。
IRQF_NO_SUSPEND 這個flag比較好理解,就是說在系統suspend的時候,不用disable這個中斷,如果disable,可能會導致系統不能正常的resume。
IRQF_FORCE_RESUME 在系統resume的過程中,強制必須進行enable的動作,即便是設定了IRQF_NO_SUSPEND這個flag。這是和特定的硬體行為相關的。
IRQF_NO_THREAD 有些low level的interrupt是不能執行緒化的(例如系統timer的中斷),這個flag就是起這個作用的。另外,有些級聯的interrupt controller對應的IRQ也是不能執行緒化的(例如secondary GIC對應的IRQ),它的執行緒化可能會影響一大批附屬於該interrupt controller的外設的中斷響應延遲。
IRQF_EARLY_RESUME  
IRQF_TIMER  

2.2 巢狀中斷

檢查中斷是否是執行緒巢狀中斷;如果不是,而且提供了thread_fn函式,將會建立一箇中斷執行緒;

 /*
         * Check whether the interrupt nests into another interrupt
         * thread.
         */
        nested = irq_settings_is_nested_thread(desc);   // 判斷是不是巢狀中斷執行緒
        if (nested) {
                if (!new->thread_fn) {   // 巢狀中斷不需要handler,但是需要thread_fn
                        ret = -EINVAL;
                        goto out_mput;
                }
                /*
                 * Replace the primary handler which was provided from
                 * the driver for non nested interrupt handling by the
                 * dummy function which warns when called.
                 */
                new->handler = irq_nested_primary_handler;  // 丟擲一個警告,內嵌中斷呼叫父中斷的handler處理
        } else {
                if (irq_settings_can_thread(desc)) {
                        ret = irq_setup_forced_threading(new);  // 強制中斷執行緒化
                        if (ret)
                                goto out_mput;
                }
        }

        /*
         * Create a handler thread when a thread function is supplied
         * and the interrupt does not nest into another interrupt
         * thread.
         */
        if (new->thread_fn && !nested) {
                ret = setup_irq_thread(new, irq, false);   // 建立中斷執行緒
                if (ret)
                        goto out_mput;
                if (new->secondary) {
                        ret = setup_irq_thread(new->secondary, irq, true);
                        if (ret)
                                goto out_thread;
                }
        }

在之前我們介紹了級聯中斷,那什麼是巢狀中斷呢。為了方便對比,這裡我們也會回顧一下級聯中斷。具體http://www.javashuo.com/article/p-yastdrbq-pz.html:

 

 

 

 

關於巢狀中斷型別:

  • 當多箇中斷共享某一根中斷線時,我們可以把這個中斷線作為父中斷,共享該中斷的各個裝置作為子中斷;
  • 在父中斷的中斷執行緒中決定和分發響應哪個裝置的請求,在得出真正發出請求的子裝置後,呼叫handle_nested_irq來響應中斷;
  •  handle_nested_irq 處理的irq的型別是IRQ_NESTED_THREAD,父中斷在初始化中斷時呼叫irq_set_nested_thread設定;
  • 對於IRQ_NESTED_THREAD型別的子中斷,__setup_irq中不會為其建立單獨的執行緒,子中斷在父中斷的執行緒上下文中執行;

2.3 共享中斷處理

如果一個IRQ編號被若干個裝置共享,那麼一個IRQ編號對應著若干個irqaction,在編寫裝置驅動時,進行裝置中斷註冊和釋放的時候我們需要通過dev_id區分具體是哪一個irqaction。

同樣當中斷髮生的會後,linux中斷子系統會去遍歷IRQ編號上註冊的irqaction的handler回撥函式,這樣,雖然只是一個外設產生的中斷,linux kernel還是把所有共享的那些中斷handler都逐個呼叫執行。為了讓系統的performance不受影響,irqaction的handler函式必須在函式的最開始進行判斷,是否是自己的硬體裝置產生了中斷(讀取硬體的暫存器),如果不是,儘快的退出。

程式碼通過判斷desc->action來識別這是不是一個共享中斷。如果desc->action不為空,說名這個中斷已經被其他裝置申請過,也就是這是一個共享中斷。

接下來會判斷這個新申請的中斷與已經申請的舊中斷的以下幾個標誌是否一致:

  • 一定要設定了IRQF_SHARED標誌(共享中斷必須設定該函式);
  • 電氣觸發方式要完全一樣(IRQF_TRIGGER_XXXX);
  • IRQF_PERCPU要一致;
  • IRQF_ONESHOT要一致;
        /*
         * The following block of code has to be executed atomically
         * protected against a concurrent interrupt and any of the other
         * management calls which are not serialized via
         * desc->request_mutex or the optional bus lock.
         */
        raw_spin_lock_irqsave(&desc->lock, flags);
        old_ptr = &desc->action;
        old = *old_ptr;
        if (old) {
                /*
                 * Can't share interrupts unless both agree to and are
                 * the same type (level, edge, polarity). So both flag
                 * fields must have IRQF_SHARED set and the bits which
                 * set the trigger type must match. Also all must
                 * agree on ONESHOT.
                 * Interrupt lines used for NMIs cannot be shared.
                 */
                unsigned int oldtype;

                if (desc->istate & IRQS_NMI) {
                        pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n",
                                new->name, irq, desc->irq_data.chip->name);
                        ret = -EINVAL;
                        goto out_unlock;
                }

                /*
                 * If nobody did set the configuration before, inherit
                 * the one provided by the requester.
                 */
                if (irqd_trigger_type_was_set(&desc->irq_data)) {
                        oldtype = irqd_get_trigger_type(&desc->irq_data);
                } else {
                        oldtype = new->flags & IRQF_TRIGGER_MASK;
                        irqd_set_trigger_type(&desc->irq_data, oldtype);
                }

                if (!((old->flags & new->flags) & IRQF_SHARED) ||      // 校驗IRQF_SHARED、IRQF_ONESHOP是否一致
                    (oldtype != (new->flags & IRQF_TRIGGER_MASK)) ||
                    ((old->flags ^ new->flags) & IRQF_ONESHOT))
                        goto mismatch;

                /* All handlers must agree on per-cpuness */
                if ((old->flags & IRQF_PERCPU) !=           // 校驗IRQF_PERCPU是否一致
                    (new->flags & IRQF_PERCPU))
                        goto mismatch;

                /* add new interrupt at end of irq queue */
                do {
                        /*
                         * Or all existing action->thread_mask bits,
                         * so we can find the next zero bit for this
                         * new action.
                         */
                        thread_mask |= old->thread_mask;
                        old_ptr = &old->next;
                        old = *old_ptr;
                } while (old);
                shared = 1;
        }

檢查這些條件都是因為多個裝置試圖共享一根中斷線,試想一下,如果一個裝置要求上升沿中斷,一個裝置要求電平中斷,然而只有一根中斷線,實際上只可能設定一種觸發型別。因此共享中斷必須具有相同電氣觸發型別(電平、邊沿、極性)、以及共同標誌 IRQF_SHARED、IRQF_ONESHOT、IRQF_PERCPU。

完成檢查後,函式找出action連結串列中最後一個irqaction例項的指標。並設定共享中斷標誌位shared=1。

2.4 非共享中斷處理

如果這不是一個共享中斷,或者是共享中斷的第一次申請,將進行如下操作:

  • 呼叫init_waitqueue_head初始化irq_desc結構中斷執行緒等待佇列:wait_for_threads;
  • 接下來設定中斷的電氣觸發型別;
  • 呼叫irq_activate啟用中斷,
  • 然後處理一些必要的IRQF_XXXX標誌位。如果沒有設定IRQF_NOAUTOEN標誌,則呼叫irq_startup開啟該irq,在irq_startup函式中irq_desc中的enable_irq/disable_irq巢狀深度欄位depth設定為0,代表該irq已經開啟,如果在沒有任何disable_irq被呼叫的情況下,enable_irq將會列印一個警告資訊。
 if (!shared) {   // 非共享中斷
                init_waitqueue_head(&desc->wait_for_threads);

                /* Setup the type (level, edge polarity) if configured: */
                if (new->flags & IRQF_TRIGGER_MASK) {    // 如果配置了中斷觸發方式
                        ret = __irq_set_trigger(desc,     // 設定中斷觸發方式  
                                                new->flags & IRQF_TRIGGER_MASK);

                        if (ret)
                                goto out_unlock;
                }

                /*
                 * Activate the interrupt. That activation must happen
                 * independently of IRQ_NOAUTOEN. request_irq() can fail
                 * and the callers are supposed to handle
                 * that. enable_irq() of an interrupt requested with
                 * IRQ_NOAUTOEN is not supposed to fail. The activation
                 * keeps it in shutdown mode, it merily associates
                 * resources if necessary and if that's not possible it
                 * fails. Interrupts which are in managed shutdown mode
                 * will simply ignore that activation request.
                 */
                ret = irq_activate(desc);
                if (ret)
                        goto out_unlock;

                desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
                                  IRQS_ONESHOT | IRQS_WAITING);
                irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);

                if (new->flags & IRQF_PERCPU) {
                        irqd_set(&desc->irq_data, IRQD_PER_CPU);
                        irq_settings_set_per_cpu(desc);
                }

                if (new->flags & IRQF_ONESHOT)
                        desc->istate |= IRQS_ONESHOT;

                /* Exclude IRQ from balancing if requested */
                if (new->flags & IRQF_NOBALANCING) {
                        irq_settings_set_no_balancing(desc);
                        irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
                }

                if (irq_settings_can_autoenable(desc)) {
                        irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
                } else {
                        /*
                         * Shared interrupts do not go well with disabling
                         * auto enable. The sharing interrupt might request
                         * it while it's still disabled and then wait for
                         * interrupts forever.
                         */
                        WARN_ON_ONCE(new->flags & IRQF_SHARED);
                        /* Undo nested disables: */
                        desc->depth = 1;
                }

        } else if (new->flags & IRQF_TRIGGER_MASK) {
                unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
                unsigned int omsk = irqd_get_trigger_type(&desc->irq_data);

                if (nmsk != omsk)
                        /* hope the handler works with current  trigger mode */
                        pr_warn("irq %d uses trigger mode %u; requested %u\n",
                                irq, omsk, nmsk);
        }

2.5 設定irqaction

如果是非共享中斷,則設定desc->action=new;否則把新的irqaction例項連結到action連結串列的最後:

 *old_ptr = new;

2.6 註冊/proc檔案節點

最後,喚醒中斷執行緒,註冊相關的/proc檔案節點:

/*
         * Strictly no need to wake it up, but hung_task complains
         * when no hard interrupt wakes the thread up.
         */
        if (new->thread)
                wake_up_process(new->thread);
        if (new->secondary)
                wake_up_process(new->secondary->thread);

        register_irq_proc(irq, desc);
        new->dir = NULL;
        register_handler_proc(irq, new);

至此,irq的申請宣告完畢,當中斷髮生時,處理的路徑將會沿著:irq_desc.handle_irq,irqaction.handler,irqaction.thread_fn(irqaction.handler的返回值是IRQ_WAKE_THREAD)這個過程進行處理。

參考文章

[1]Linux中斷(interrupt)子系統之三:中斷流控處理層

[2]Linux的中斷處理機制 [三] - hardirq

[3]kernel 中斷分析之四——中斷申請 [下]

[4]Linux kernel中斷子系統之(五):驅動申請中斷API【轉】