天天看點

Linux核心中斷注冊源代碼分析(四)

1.概述

外設産生中斷後,Linux核心會執行一個具體的函數來響應中斷,此函數被稱為中斷服務函數。中斷服務函數運作在中斷上下文中,若中斷線程化後,則中斷事件在程序上下文中處理。不同的外設中斷源,其對應的中斷服務函數一般也不同。是以在使用具體的外設中斷之前,需要注冊對應的中斷服務函數,并指明軟體中斷号、中斷标志、中斷名稱及傳遞給中斷服務函數的參數等。

2.中斷注冊接口介紹

2.1.request_irq

使用

request_irq

注冊一個中斷服務函數,内部調用的是

request_threaded_irq

函數,隻是将

thread_fn

設定為

NULL

irq

為軟體中斷号,

handler

為中斷服務函數,

flags

為中斷标志,

name

中斷名稱,

dev

為傳遞給中斷服務函數的參數,可為

NULL

,指明

IRQF_SHARED

時,必須傳遞此參數。傳回值為0表示注冊成功,非0表示注冊失敗。

[include/linux/interrupt.h]
    static inline int __must_check
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
        const char *name, void *dev)
    irq:軟體中斷号
    handler:中斷服務函數
    flags:中斷标志
        #define IRQF_TRIGGER_RISING	0x00000001    // 上升沿觸發
        #define IRQF_TRIGGER_FALLING 0x00000002   // 下降沿觸發
        #define IRQF_TRIGGER_HIGH	0x00000004    // 高電平觸發
        #define IRQF_TRIGGER_LOW	0x00000008    // 下降沿觸發
        // 共享中斷,多個裝置共享一個中斷号(中斷引腳),在中斷處理程式中需要查詢那個外設發生了中斷
        #define IRQF_SHARED		    0x00000080
        #define IRQF_PROBE_SHARED	0x00000100    // 中斷處理程式允許sharing mismach發生
        #define __IRQF_TIMER		0x00000200
        // 時鐘中斷
        #define IRQF_TIMER		(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
        #define IRQF_PERCPU		    0x00000400    // 某個CPU特有的中斷
        #define IRQF_NOBALANCING	0x00000800    // 禁止CPU之間的中斷均衡
        #define IRQF_IRQPOLL		0x00001000    // 中斷被用作輪訓
        // 中斷線程執行完成之前不會打開中斷,直至中斷線程處理完所有中斷,才會重新使能中斷
        #define IRQF_ONESHOT		0x00002000
        #define IRQF_NO_SUSPEND		0x00004000    // 系統suspend期間不要關閉中斷
        #define IRQF_FORCE_RESUME	0x00008000    // 系統resume時必須強制使能此中斷
        #define IRQF_NO_THREAD		0x00010000    // 此中斷不能中斷線程化
        #define IRQF_EARLY_RESUME	0x00020000    // 系統resume時提前使能此中斷
    name:中斷名稱
    dev:傳遞給中斷服務函數的參數,可為NULL,共享中斷必須傳遞此參數
           

2.2.request_threaded_irq

使用

request_threaded_irq

注冊一個中斷服務函數和中斷線程化後調用的函數。

irq

為軟體中斷号,

handler

為中斷服務函數,

thread_fn

中斷線程化後核心線程執行的函數,

flags

為中斷标志,

name

中斷名稱,

dev

為傳遞給中斷服務函數的參數,可為

NULL

,指明

IRQF_SHARED

時,必須傳遞此參數。傳回值為0表示注冊成功,非0表示注冊失敗。

[include/linux/interrupt.h]
    int __must_check
    request_threaded_irq(unsigned int irq, irq_handler_t handler,
            irq_handler_t thread_fn,
            unsigned long flags, const char *name, void *dev)
    irq:軟體中斷号
    handler:中斷服務函數
    thread_fn:中斷線程化後調用的函數
    flags:中斷标志,和request_irq的中斷标志含義一樣
    name:中斷名稱
    dev:給中斷服務函數傳遞的參數
           

2.3.free_irq

request_irq

request_threaded_irq

注冊的中斷服務函數使用

free_irq

函數釋放。

[include/linux/interrupt.h]
    void free_irq(unsigned int irq, void *dev_id)
    irq:軟體中斷号
    dev_id:給中斷服務函數傳遞的參數
           

3.源代碼分析

由于

request_irq

内部調用的是

request_threaded_irq

,是以這裡隻分析

request_threaded_irq

函數。

request_threaded_irq

函數的主要工作是配置設定一個

struct irqaction

結構體并設定相關成員,設定的主要成員有中斷服務函數

handler

,軟體中斷号

irq

、中斷線程化執行的函數

thread_fn

及中斷标志

flags

handler

irq

thread_fn

flags

根據傳入的參數設定。最後将配置設定的

struct irqaction

結構體挂接到

struct irq_desc

結構體中的

action

連結清單下面,非共享中斷

action

連結清單隻有一個節點,共享中斷

action

連結清單有多個節點。中斷發生後,會周遊

action

連結清單,使用

handler

thread_fn

等進行中斷。使用

request_threaded_irq

注冊中斷時需要注意一下幾點:

(1)IRQ為軟體(虛拟)中斷号,是由硬體中斷号映射而來。

(2)primary handler(中斷上半部分處理函數)和threaded_fn(中斷下半部分處理函數)不能同時為NULL。

(3)當primary handler為NULL且硬體中斷控制器不支援硬體ONESHOT功能時,應設定IRQF_ONESHOT标志來確定不會産生中斷風暴。

(4)啟用了中斷線程化,primary handler函數應該傳回IRQ_WAKE_THREAD,以喚醒中斷處理線程。

[include/linux/interrupt.h]
    // 和具體的中斷邏輯處理相關
    struct irqaction {
        irq_handler_t		handler;  // 中斷服務函數,注冊中斷時設定
        void			*dev_id;  // 中斷服務函數參數
        void __percpu		*percpu_dev_id;
        struct irqaction	*next;  // 共享中斷有多個irqaction結構體,next指向下一個irqaction
        irq_handler_t		thread_fn;  // 中斷線程化執行的函數
        struct task_struct	*thread;  // 指向被中斷線程的task_struct
        struct irqaction	*secondary;  // 指向中斷下半部分的irqaction
        unsigned int		irq;  // 軟體中斷号
        unsigned int		flags;  // 中斷标志
        unsigned long		thread_flags;  // 線程标志
        unsigned long		thread_mask;  // 跟蹤中斷線程活動的位圖
        const char		*name;  // 中斷名稱
        struct proc_dir_entry	*dir;  // 指向/proc/irq/NN/name
    } ____cacheline_internodealigned_in_smp;

    [include/linux/interrupt.h]
    request_threaded_irq
      // 檢查參數,設定IRQF_SHARED标志時,必須向中斷服務函數傳遞參數,即必須傳入dev_id參數,共享中斷根據
      // dev_id區分産生中斷的外設
      if (((irqflags & IRQF_SHARED) && !dev_id) ||
         (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
         ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
      ->irq_to_desc  // 擷取軟體中斷号對應的irq_desc
        ->radix_tree_lookup  // 定義CONFIG_SPARSE_IRQ,從irq_desc_tree中查找
        // 沒有定義CONFIG_SPARSE_IRQ,從irq_desc數組中查找,索引為軟體中斷号
        return (irq < NR_IRQS) ? irq_desc + irq : NULL
      ->handler = irq_default_primary_handler  // 若沒有設定中斷服務函數,則設定預設的中斷服務函數
      ->kzalloc  // 配置設定irqaction結構體記憶體
      // 設定irqaction結構體
      action->handler = handler;  // 設定中斷處理函數
      action->thread_fn = thread_fn;  // 設定中斷線程化核心線程執行的函數
      action->flags = irqflags;  // 中斷标志
      action->name = devname;  // 中斷名稱
      action->dev_id = dev_id;  // 傳遞給中斷服務函數的參數
      ->__setup_irq
        new->irq = irq  // 設定irqaction結構體中的軟體中斷号irq
        ->irq_settings_is_nested_thread  // 檢查此中斷是否嵌套到另一個中斷線程中
            return desc->status_use_accessors & _IRQ_NESTED_THREAD
        ->new->handler = irq_nested_primary_handler  // 如嵌套,則設定新的中斷處理函數
        ->irq_settings_can_thread  // 中斷是否可以線程化
        ->irq_setup_forced_threading
           

Linux中斷強制線程化是一個過渡方案,目前還有很多的驅動使用舊版本的API注冊中斷,這些驅動的中斷處理通常采用上下半部分的方式。

force_irqthreads  // 如果force_irqthreads定義為true,則強制将中斷線程化
          new->flags |= IRQF_ONESHOT  // 強制中斷服務函數線程化,其在關中斷的狀态下運作,是以需要設定此标志
          // 如果handler不為irq_default_primary_handler且設定了中斷線程化執行的函數
          new->secondary = kzalloc()  // 則配置設定中斷下半部分使用的irqaction結構體
          // 設定強制中斷線程化的中斷服務函數
          new->secondary->handler = irq_forced_secondary_handler;
          // 設定中斷線程化核心線程執行的函數
          new->secondary->thread_fn = new->thread_fn;
          new->secondary->dev_id = new->dev_id;  // 設定傳遞給中斷服務函數的參數
          new->secondary->irq = new->irq;  // 設定軟體中斷号
          new->secondary->name = new->name;  // 設定中斷名稱
          ->set_bit  // 設定強制中斷線程化标志,設定到irqaction的thread_flags标志中
          new->thread_fn = new->handler
          // 設定預設的中斷服務函數為irq_default_primary_handler
          new->handler = irq_default_primary_handler  
        ->setup_irq_thread  // 如果傳入了thread_fn,則建立中斷線程,優先級為50,排程政策為SCHED_FIFO
          if (!secondary) {  // 沒有強制中斷線程化
              // kthread_create建立的核心線程由核心線程kthreadd建立,傳回建立線程的task_struct指針
              // 中斷線程喚醒後運作的第一個函數為irq_thread
              t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
          } else {  // 強制中斷線程化
              t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq, new->name);
              param.sched_priority -= 1;
          }
          ->sched_setscheduler_nocheck  // 設定核心線程的優先級
          new->thread = t  // 使irqaction中的thread指向建立線程的task_struct
          ->set_bit  // 設定irqaction中的标記為IRQTF_AFFINITY
        ->setup_irq_thread  // 如果需要中斷下半部分,則啟動進行中斷下半部分的核心線程
        // 将新配置設定的irqaction結構體追加到irq_desc結構體的action連結清單的末尾,共享中斷,action
        // 連結清單有多個節點,非共享中斷,action連結清單隻有一個節點
        *old_ptr = new
        ->irq_pm_install_action  // 電源管理相關  
        desc->irq_count = 0
        desc->irqs_unhandled = 0
        // kthread_create建立的核心線程預設不運作,需要使用wake_up_process喚醒
        ->wake_up_process(new->thread)  // 喚醒中斷線程化的線程
        ->wake_up_process(new->secondary->thread)  // 喚醒中斷下半部分的線程
        ->register_irq_proc  // 将中斷注冊到proc檔案系統中
           

下面分析一下

free_irq

的執行過程,主要功能是釋放掉

request_threaded_irq

注冊的資源。

[include/linux/interrupt.h]
    free_irq
      ->irq_to_desc  // 根據軟體中斷号擷取中斷描述符
      desc->affinity_notify = NULL  // 如果定義CONFIG_SMP,則将中斷對CPU的親和力設定為NULL
      ->__free_irq
        ->irq_to_desc  // 根據軟體中斷号擷取中斷描述符
        ->in_interrupt  // 檢測是否在中斷上下文中free IRQ
        ->raw_spin_lock_irqsave  // 擷取自旋鎖并禁止中斷
        // 周遊action連結清單,查找要釋放的action
        action_ptr = &desc->action;
        for (;;) {
            action = *action_ptr;
            if (action->dev_id == dev_id)  // action中的dev_id和傳入的dev_id相等,則跳出循環
                break;
            action_ptr = &action->next;
        }
        // 從連結清單中移除此action
        *action_ptr = action->next
        ->irq_pm_remove_action  // 電源管理相關操作
        // 如果action連結清單為空,說明此中斷不需要處理,需要關閉中斷
        if (!desc->action) {  // action連結清單為空
            irq_shutdown(desc);  // 關閉此中斷
            irq_release_resources(desc);  // 釋放中斷資源
        }
        ->raw_spin_unlock_irqrestore  // 釋放自旋鎖
        ->unregister_handler_proc  // 登出proc檔案系統中的中斷資訊
        ->synchronize_irq  // 同步中斷,等待其他CPU處理完要free的中斷任務
          ->irq_to_desc  // 擷取中斷描述符
          ->__synchronize_hardirq
            do {
                unsigned long flags;
                while (irqd_irq_inprogress(&desc->irq_data))  // 擷取中斷處理狀态
                    // arm32 cpu_relax執行一條記憶體屏障指令,arm64執行一條yield指令和記憶體屏障指令
                    // yield指令可以降低CPU功耗
                    cpu_relax();
                // 上述擷取中斷處理狀态缺乏嚴格的記憶體屏障,可能存在誤讀的情況,是以這裡再次讀取
                raw_spin_lock_irqsave(&desc->lock, flags);  // 加鎖
                inprogress = irqd_irq_inprogress(&desc->irq_data);  // 讀取
                raw_spin_unlock_irqrestore(&desc->lock, flags);  // 解鎖
            } while (inprogress);  // 循環讀取,直到其他CPU處理完中斷
          ->wait_event  // 等待中斷處理線程處理完中斷
        ->kthread_stop  // 停止中斷處理線程
        ->kthread_stop  // 停止中斷下半部分處理線程
        ->kfree  // 釋放中斷下半部分secondary的記憶體
      ->kfree  // 釋放action對應的記憶體
           

4.zynq7k序列槽0的中斷注冊

zynq7k序列槽0的中斷注冊在

cdns_uart_startup

函數中完成,

irq

為已經映射好的軟體中斷号,

cdns_uart_isr

為注冊的中斷處理函數,

flags

為0,中斷類型和中斷觸發方式已在裝置樹中指定,即序列槽0的中斷類型為SPI中斷,中斷觸發方式為高電平,中斷名稱為

"xuartps"

port

為給中斷處理函數傳遞的參數。中斷處理函數

cdns_uart_startup

在Linux核心高層中斷進行中分析。

[drivers/tty/serial/xilinx_uartps.c]
    #define CDNS_UART_NAME		"xuartps"  // 中斷名稱
    cdns_uart_startup
      ->request_irq(port->irq, cdns_uart_isr, 0, CDNS_UART_NAME, (void *)port)  // 注冊中斷
           

參考資料

  1. Linux kernel V4.6版本源碼
  2. 《奔跑吧 Linux核心:基于Linux 4.x核心源代碼問題分析》
  3. 《Zynq-7000 SoC Technical Reference Manual》
  4. 《ARM® Generic Interrupt Controller Architecture version 2.0》

繼續閱讀