天天看点

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.

继续阅读