天天看點

linux中斷子系統 - irq_desc的建立文章系列1. irq_desc組織方式2.irq_desc的配置設定3.irq_desc的初始化4. change log

文章系列

linux中斷子系統 - 中斷及執行流程

linux中斷子系統 - 申請中斷

linux中斷子系統 - irq_desc的建立

linux中斷子系統 - 中斷控制器的注冊

irq_desc的代碼主要在kernel/irq/irqdesc.c中
linux4.6.3

1. irq_desc組織方式

1.1 組織方式

irq_desc在核心中有兩種組織方式,這是根據宏CONFIG_SPARSE_IRQ是否定義來決定的,這兩種方式分别是:

(1)radix-tree方式,這是以基數樹的方式來組織irq_desc

(2)數組的方式 ,前面的文章介紹irq結構時,就是用此方式舉例的,在系統初始化的時候會定義一個全局數組,詳細見下面代碼,NR_IRQS就代表總共的irq數量

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [ ... NR_IRQS-] = {
        .handle_irq = handle_bad_irq,
        .depth      = ,
        .lock       = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};
           

我們最終的目标是建立hwirq和virq的映射,上面兩種方式代表兩種不同的映射方式,數組簡單就是線性映射,而基數樹具體在這裡不說了。

irq_desc建立完後,要做的就是初始化,在核心中有兩方面會涉及到irq_desc的初始化,一個是在申請中斷的時候(主要是初始化irq_desc->action),另一個是在驅動初始化的時候,本文重點就在這裡

1.2 struct irq_desc

struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data     irq_data;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t  handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t   preflow_handler;
#endif
    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status_use_accessors;
    unsigned int        core_internal_state__do_not_mess_with_it;
    unsigned int        depth;      /* nested irq disables */
    unsigned int        wake_depth; /* nested wake enables */
    unsigned int        irq_count;  /* For detecting broken IRQs */
    unsigned long       last_unhandled; /* Aging timer for unhandled count */
    unsigned int        irqs_unhandled;
    atomic_t        threads_handled;
    int         threads_handled_last;
    raw_spinlock_t      lock;
    struct cpumask      *percpu_enabled;
#ifdef CONFIG_SMP
    const struct cpumask    *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t       pending_mask;
#endif
#endif
    unsigned long       threads_oneshot;
    atomic_t        threads_active;
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
    unsigned int        nr_actions;
    unsigned int        no_suspend_depth;
    unsigned int        cond_suspend_depth;
    unsigned int        force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry   *dir;
#endif
    int         parent_irq;
    struct module       *owner;
    const char      *name;
} ____cacheline_internodealigned_in_smp;
           

2.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(-, , , node)

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

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

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

從上面可以看出最終都是調用到函數__irq_alloc_descs,此函數最終會調用到函數alloc_descs,從第一節我們知道irq_desc組織方式有兩種,那麼alloc_descs會有兩個接口,下面分别介紹:

2.1線性數組方式

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

    for (i = ; 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.2基數樹方式

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

    for (i = ; 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 >= ; 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;
}
           

3.irq_desc的初始化

各個驅動中斷的配置在DT中(DT參考我的文章裝置樹的解釋),DT初始化中斷的一個簡易流程如下圖

linux中斷子系統 - irq_desc的建立文章系列1. irq_desc組織方式2.irq_desc的配置設定3.irq_desc的初始化4. change log

走到of_irq_to_resource就脫離了DT過程,正式進入到本節的主題,函數調用流程圖如下:

linux中斷子系統 - irq_desc的建立文章系列1. irq_desc組織方式2.irq_desc的配置設定3.irq_desc的初始化4. change log

這些沒什麼說的,隻是一個參數解析過程,重要的是下面這些函數

linux中斷子系統 - irq_desc的建立文章系列1. irq_desc組織方式2.irq_desc的配置設定3.irq_desc的初始化4. change log
  • A : 找到比對的irq_domain,irq_domain會在中斷控制器注冊的時候加入
  • B : 判斷irq_domain是否是hierarchy,不同的類型需要不同的操作,從圖中可以看出雖然調用函數不一樣,但是具體要執行的步驟是一樣的:
()搜尋hwirq是否已經被映射到virq,如果是直接傳回
()如果沒有得到virq,那麼把hwirq映射到virq
(3)調用irq_domain的相關函數初始化irq_desc
           

上面的簡化分析不能說明問題,下面通過代碼來仔細分析,隻分析不是hierarchy的情況:

一、先分析函數irq_find_mapping

unsigned int irq_find_mapping(struct irq_domain *domain,
                  irq_hw_number_t hwirq)
{
    struct irq_data *data;

    /* Look for default domain if nececssary */
    if (domain == NULL)
        domain = irq_default_domain;
    if (domain == NULL)
        return ;

    if (hwirq < domain->revmap_direct_max_irq) {
        data = irq_domain_get_irq_data(domain, hwirq);
        if (data && data->hwirq == hwirq)
            return hwirq;
    }

    /* Check if the hwirq is in the linear revmap. */
    if (hwirq < domain->revmap_size)
        return domain->linear_revmap[hwirq];-----------------線性映射傳回

    rcu_read_lock();
    data = radix_tree_lookup(&domain->revmap_tree, hwirq);
    rcu_read_unlock();
    return data ? data->irq : 0;-----------------------------基數樹映射傳回
}
--------------linear_revmap或者revmap_tree的建立是在中斷控制器注冊的時候初始化的
           

二、再分析函數irq_domain_alloc_descs

int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,
               int node)
{
    unsigned int hint;

    if (virq >= ) {--------------------從上面函數的分析可知道virq==
        virq = irq_alloc_descs(virq, virq, cnt, node);-----真正的irq_desc配置設定函數
    } else {
        hint = hwirq % nr_irqs;
        if (hint == )
            hint++;
        virq = irq_alloc_descs_from(hint, cnt, node);
        if (virq <=  && hint > )
            virq = irq_alloc_descs_from(, cnt, node);
    }

    return virq;
}
           

三、最後分析函數irq_domain_associate

int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
             irq_hw_number_t hwirq)
{
    struct irq_data *irq_data = irq_get_irq_data(virq);
    int ret;

    if (WARN(hwirq >= domain->hwirq_max,
         "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
        return -EINVAL;
    if (WARN(!irq_data, "error: virq%i is not allocated", virq))
        return -EINVAL;
    if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
        return -EINVAL;

    mutex_lock(&irq_domain_mutex);
    irq_data->hwirq = hwirq;
    irq_data->domain = domain;
    if (domain->ops->map) {
        ret = domain->ops->map(domain, virq, hwirq);---------------調用irq_domain->irq_domain_ops->map函數初始化irq_desc,具體操作在中斷控制器注冊文章中介紹
        if (ret != ) {
            /*
             * If map() returns -EPERM, this interrupt is protected
             * by the firmware or some other service and shall not
             * be mapped. Don't bother telling the user about it.
             */
            if (ret != -EPERM) {
                pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
                       domain->name, hwirq, virq, ret);
            }
            irq_data->domain = NULL;
            irq_data->hwirq = ;
            mutex_unlock(&irq_domain_mutex);
            return ret;
        }

        /* If not already assigned, give the domain the chip's name */
        if (!domain->name && irq_data->chip)
            domain->name = irq_data->chip->name;
    }

    if (hwirq < domain->revmap_size) {
        domain->linear_revmap[hwirq] = virq;-----------------------virq設定到domain
    } else {
        mutex_lock(&revmap_trees_mutex);
        radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);--virq設定到domain
        mutex_unlock(&revmap_trees_mutex);
    }
    mutex_unlock(&irq_domain_mutex);

    irq_clear_status_flags(virq, IRQ_NOREQUEST);

    return 0;
}
           

4. change log

日期 修改内容 核心版本
2016.11.9 增加結構體irq_desc内容 linux4.4

繼續閱讀