天天看點

linux IRQ Management(五)- irq_desc

  • 了解中斷描述符struct irq_desc 參考韋東山百問網

1.通用中斷代碼處理

  通用中斷處理示意圖:

linux IRQ Management(五)- irq_desc

  對于每一個外設的IRQ 都用struct irq_desc來描述,稱之為中斷描述符。該資料結構儲存了關于所有IRQ的中斷描述符資訊(上圖中紅色框圖内)。

  當中斷發生時,首先擷取觸發中斷的HW interupt ID,然後通過irq domain譯碼成IRQ number,然後通過IRQ number擷取對應的中斷描述符。調用中斷描述符中的highlevel irq-events handler來進行中斷處理。而highlevel irq-events handler主要進行下面兩個操作:

  • 調用中斷描述符的底層irq chip driver進行mask,ack等callback函數,進行interrupt flow control。
  • 調用該中斷描述符上的action list中的specific handler(用這個術語來區分具體中斷handler和high level的handler)。這個步驟不一定會執行,這是和中斷描述符的目前狀态相關,實際上,interrupt flow control是軟體(設定一些标志位,軟體根據标志位進行處理)和硬體(mask或者unmask interrupt controller等)一起控制完成的。

2.irq_desc 組織方式

  irq_desc在核心中有兩種組織方式,這是根據宏CONFIG_SPARSE_IRQ是否定義來決定的(詳見linux IRQ Management(一)- 綜述),這兩種方式分别是:

  • radix-tree方式,這是以基數樹的方式來組織irq_desc;
  • 數組的方式 ,在系統初始化的時候會定義一個全局數組。
kernel/irq/irqdesc.c:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq = handle_bad_irq,
        .depth      = 1,
        .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};
           

  系統中每一個連接配接外設的中斷線(irq request line)用一個中斷描述符來描述,每一個外設的interrupt request line配置設定一個中斷号(irq number),系統中有多少個中斷線(或者叫做中斷源)就有多少個中斷描述符(struct irq_desc)。NR_IRQS定義了該硬體平台IRQ的最大數目。

  總之,一個靜态定義的表格,irq number作為index,每個描述符都是緊密的排在一起,一切看起來很美好,但是現實很殘酷的。有些系統可能會定義一個很大的NR_IRQS,但是隻是想用其中的若幹個,換句話說,這個靜态定義的表格不是每個entry都是有效的,有空洞,如果使用靜态定義的表格就會導緻了記憶體很大的浪費。在這種情況下,靜态表格不适合了,改用一個radix tree來儲存中斷描述符(HW interrupt作為索引)。這時候,每一個中斷描述符都是動态配置設定,然後插入到radix tree中。如果采用這種政策,那麼需要打開CONFIG_SPARSE_IRQ選項。

  最終的目标是建立hwirq和virq的映射,兩種方式代表兩種不同的映射方式。irq_desc建立完後,要做的就是初始化,在核心中有兩方面會涉及到irq_desc的初始化,一個是在申請中斷的時候(主要是初始化irq_desc->action),另一個是在驅動初始化的時候。

2.1.中斷相關結構體

linux IRQ Management(五)- irq_desc
  • struct irq_desc:一個中斷描述符,一個中斷所需要的資源都集中在這個結構體中描述,如果沒有定義選項CONFIG_SPARSE_IRQ,irq_desc會在系統初始化的時候被配置設定到一個數組中存放,其中數組下标代表的就是virq(虛拟中斷,而不是硬中斷),如果定義了選項那麼irq_desc會被配置設定到radix tree中;
  • struct irq_data:一個具體中斷控制器,包括兩個結構體,分别是irq_chip和irq_domain,其中irq_chip中的每個字段是描述了具體操作中斷控制器的功能,irq_domain主要的功能是對hwirq和virq建立映射,把某些中斷所具有的同樣功能抽象出來,然後組成一個domain,這樣就可以被其他中斷控制器所用;
  • struct irqaction:一個中斷要做什麼,此結構體組成一個連結清單,因為一個中斷線上可能會挂載好幾個裝置,這幾個裝置會共用這個中斷,具體是哪個裝置需要判斷。

2.2.struct irq_desc

struct irq_desc { 
    struct irq_data        irq_data; 
    unsigned int __percpu    *kstat_irqs;------IRQ的統計資訊 
    irq_flow_handler_t    handle_irq;--------(1) 
    struct irqaction    *action; -----------(2) 
    unsigned int        status_use_accessors;-----中斷描述符的狀态
    unsigned int        core_internal_state__do_not_mess_with_it;----(3) 
    unsigned int        depth;----------(4) 
    unsigned int        wake_depth;--------(5) 
    unsigned int        irq_count;  ---------(6) 
    unsigned long        last_unhandled;   
    unsigned int        irqs_unhandled; 
    raw_spinlock_t        lock;-----------(7) 
    struct cpumask        *percpu_enabled;-------(8) 
#ifdef CONFIG_SMP 
    const struct cpumask    *affinity_hint;----和irq affinity相關,後續單獨文檔描述 
    struct irq_affinity_notify *affinity_notify; 
#ifdef CONFIG_GENERIC_PENDING_IRQ 
    cpumask_var_t        pending_mask; 
#endif 
#endif 
    unsigned long        threads_oneshot; -----(9) 
    atomic_t        threads_active; 
    wait_queue_head_t       wait_for_threads; 
#ifdef CONFIG_PROC_FS 
    struct proc_dir_entry    *dir;--------該IRQ對應的proc接口 
#endif 
    int            parent_irq; 
    struct module        *owner; 
    const char        *name; 
} ____cacheline_internodealigned_in_smp
           
  • (1)handle_irq就是highlevel irq-events handler,high level是和specific相對,specific handler處理具體的事務,例如處理一個按鍵中斷、處理一個磁盤中斷。而high level則是對處理各種中斷互動過程的一個抽象,根據下列硬體的不同:
    • (a)中斷控制器
    • (b)IRQ trigger type

    highlevel irq-events handler可以分成:

    (a)處理電平觸發類型的中斷handler(handle_level_irq)

    (b)處理邊緣觸發類型的中斷handler(handle_edge_irq)

    (c)處理簡單類型的中斷handler(handle_simple_irq)

    (d)處理EOI類型的中斷handler(handle_fasteoi_irq)

  • (2)action指向一個struct irqaction的連結清單。如果一個interrupt request line允許共享,那麼該連結清單中的成員可以是多個,否則,該連結清單隻有一個節點。
  • (3)core_internal_state__do_not_mess_with_it在具體使用的時候被被簡化成istate,表示internal state。
  • (4)通過enable和disable一個指定的IRQ來控制核心的并發,進而保護臨界區的資料。對一個IRQ進行enable和disable的操作可以嵌套(當然一定要成對使用),depth是描述嵌套深度的資訊。
  • (5)wake_depth是和電源管理中的wake up source相關。通過irq_set_irq_wake接口可以enable或者disable一個IRQ中斷是否可以把系統從suspend狀态喚醒。同樣的,對一個IRQ進行wakeup source的enable和disable的操作可以嵌套(當然一定要成對使用),wake_depth是描述嵌套深度的資訊。
  • (6)irq_count、last_unhandled和irqs_unhandled用于處理broken IRQ 的處理。所謂broken IRQ就是由于種種原因(例如錯誤firmware),IRQ handler沒有定向到指定的IRQ上,當一個IRQ沒有被處理的時候,kernel可以為這個沒有被處理的handler啟動scan過程,讓系統中所有的handler來認領該IRQ。
  • (7)保護該中斷描述符的spin lock。
  • (8)一個中斷描述符可能會有兩種情況,一種是該IRQ是global,一旦disable了該irq,那麼對于所有的CPU而言都是disable的。還有一種情況,就是該IRQ是per CPU的,也就是說,在某個CPU上disable了該irq隻是disable了本CPU的IRQ而已,其他的CPU仍然是enable的。percpu_enabled是一個描述該IRQ在各個CPU上是否enable成員。
  • (9)threads_oneshot、threads_active和wait_for_threads是和IRQ thread相關。

2.2.1.struct irq_data

  中斷描述符中應該會包括底層irq chip相關的資料結構,linux kernel中把這些資料組織在一起,形成struct irq_data:

struct irq_data { 
    u32            mask;----------TODO 
    unsigned int        irq;--------IRQ number 
    unsigned long        hwirq;-------HW interrupt ID 
    struct irq_common_data *common;
    struct irq_chip        *chip;----------該中斷描述符對應的irq chip資料結構 
    struct irq_domain    *domain;--------該中斷描述符對應的irq domain資料結構 
    void            *chip_data;---------和中斷控制器相關的私有資料 
};
           

2.2.2.struct irq_chip:用于通路底層硬體,非常重要結構體。

449 struct irq_chip {
 450     struct device   *parent_device;
 451     const char  *name;
 452     unsigned int    (*irq_startup)(struct irq_data *data);
 453     void        (*irq_shutdown)(struct irq_data *data);
 454     void        (*irq_enable)(struct irq_data *data);
 455     void        (*irq_disable)(struct irq_data *data);
 456 
 457     void        (*irq_ack)(struct irq_data *data);
 458     void        (*irq_mask)(struct irq_data *data);
 459     void        (*irq_mask_ack)(struct irq_data *data);
 460     void        (*irq_unmask)(struct irq_data *data);
 461     void        (*irq_eoi)(struct irq_data *data);
 462 
 463     int     (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
 464     int     (*irq_retrigger)(struct irq_data *data);
 465     int     (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
 466     int     (*irq_set_wake)(struct irq_data *data, unsigned int on);
 467 
 468     void        (*irq_bus_lock)(struct irq_data *data);                                                 
 469     void        (*irq_bus_sync_unlock)(struct irq_data *data);
 470 
 471     void        (*irq_cpu_online)(struct irq_data *data);
 472     void        (*irq_cpu_offline)(struct irq_data *data);
};
           

解釋如下:

linux IRQ Management(五)- irq_desc
  • irq_enable:在中斷注冊或使能時調用,一般在使能中斷前需要清除中斷狀态。
  • irq_disable:在中斷登出或關閉時調用,一般在關閉中斷前不清除中斷狀态。
  • irq_mask_ack:在進入中斷處理函數前調用,一般在屏蔽中斷前需要清除中斷源信号。
  • irq_unmask:在退出中斷處理函數後調用,一般在去屏蔽中斷前不清除清除中斷源信号。
  • irq_enable/irq_unmask"用于中斷使能,"irq_disable/irq_mask"用于中斷屏蔽。

2.3.irq_desc的配置設定

#define irq_alloc_descs(irq, from, cnt, node)   \
    __irq_alloc_descs(irq, from, cnt, node, THIS_MODULE)

#define irq_alloc_desc(node)            \
    irq_alloc_descs(-1, 0, 1, node)

#define irq_alloc_desc_at(at, node)     \
    irq_alloc_descs(at, at, 1, node)

#define irq_alloc_desc_from(from, node)     \
    irq_alloc_descs(-1, from, 1, node)

#define irq_alloc_descs_from(from, cnt, node)   \
    irq_alloc_descs(-1, from, cnt, node)
           

  最終都是調用到函數__irq_alloc_descs,此函數最終會調用到函數alloc_descs,由于irq_desc組織方式有兩種,是以alloc_descs也有兩個接口:

2.3.1.線性數組方式

static inline int alloc_descs(unsigned int start, unsigned int cnt, int node,
                  struct module *owner)
{
    u32 i;

    for (i = 0; i < cnt; i++) {
        struct irq_desc *desc = irq_to_desc(start + i);

        desc->owner = owner;
    }
    return start;
}

struct irq_desc *irq_to_desc(unsigned int irq)
{
    return (irq < NR_IRQS) ? irq_desc + irq : NULL;   //直接傳回irq_desc[irq]
}
           

2.3.2.基數樹方式

static int alloc_descs(unsigned int start, unsigned int cnt, int node,
               struct module *owner)
{
    struct irq_desc *desc;
    int i;

    for (i = 0; i < cnt; i++) {
        desc = alloc_desc(start + i, node, owner);   //配置設定一個irq_desc
        if (!desc)
            goto err;
        mutex_lock(&sparse_irq_lock);
        irq_insert_desc(start + i, desc);            //把irq_desc插入到radix tree
        mutex_unlock(&sparse_irq_lock);
    }
    return start;

err:
    for (i--; i >= 0; i--)
        free_desc(start + i);

    mutex_lock(&sparse_irq_lock);
    bitmap_clear(allocated_irqs, start, cnt);
    mutex_unlock(&sparse_irq_lock);
    return -ENOMEM;
}

static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
{
    struct irq_desc *desc;
    gfp_t gfp = GFP_KERNEL;

    desc = kzalloc_node(sizeof(*desc), gfp, node);     //為irq_desc配置設定記憶體
    if (!desc)
        return NULL;
    /* allocate based on nr_cpu_ids */
    desc->kstat_irqs = alloc_percpu(unsigned int);
    if (!desc->kstat_irqs)
        goto err_desc;

    if (alloc_masks(desc, gfp, node))
        goto err_kstat;

    raw_spin_lock_init(&desc->lock);
    lockdep_set_class(&desc->lock, &irq_desc_lock_class);
    init_rcu_head(&desc->rcu);

    desc_set_defaults(irq, desc, node, owner);

    return desc;

err_kstat:
    free_percpu(desc->kstat_irqs);
err_desc:
    kfree(desc);
    return NULL;
}
           

2.4.irq_desc的初始化

  各個驅動中斷的配置在DT中,DT初始化中斷:

linux IRQ Management(五)- irq_desc

  走到of_irq_to_resource就脫離了DT過程,函數調用流程圖如下:

linux IRQ Management(五)- irq_desc
linux IRQ Management(五)- irq_desc
  • A : 找到比對的irq_domain,irq_domain會在中斷控制器注冊的時候加入;
  • B : 判斷irq_domain是否是hierarchy,不同的類型需要不同的操作,從圖中可以看出雖然調用函數不一樣,但是具體要執行的步驟是一樣的:
    • 搜尋hwirq是否已經被映射到virq,如果是直接傳回;
    • 如果沒有得到virq,那麼把hwirq映射到virq;
    • 調用irq_domain的相關函數初始化irq_desc.

繼續閱讀