Linux 中断 —— GIC (数据结构 irq_domain/irq_desc/irq_data/irq_chip/irqaction)

前面两篇文章简述了 GIC 的初始化和中断号的匹配过程,这里有必要在了解一下中断相关的数据结构,以及他们之间的关系。

目录

 

1. 中断描述符 irq_desc

2. 响应函数 irqaction

3. 中断数据 irq_data

4. 操作合集 irq_chip

5. 对应关系

6. 相关数据结构初始化


1. 中断描述符 irq_desc

在 Linux kernel 中,对于每一个外设的IRQ都用 struct irq_desc 来描述,我们称之中断描述符(struct irq_desc)。linux kernel中会有一个数据结构保存了关于所有IRQ的中断描述符信息,我们称之中断描述符DB(上图中红色框图内)。当发生中断后,首先获取触发中断的HW interupt ID,然后通过irq domain翻译成IRQ number,然后通过IRQ number就可以获取对应的中断描述符

通用中断处理模块可以用一个线性的table来管理一个个的外部中断,这个表的每个元素就是一个irq描述符,在kernel中定义如下:

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都是有效的,有空洞,如果使用静态定义的表格就会导致了内存很大的浪费。为什么会有这种需求?我猜是和各个interrupt controller硬件的interrupt ID映射到irq number的算法有关。在这种情况下,静态表格不适合了,我们改用一个radix tree来保存中断描述符(HW interrupt作为索引)。这时候,每一个中断描述符都是动态分配,然后插入到radix tree中。如果你的系统采用这种策略,那么需要打开 CONFIG_SPARSE_IRQ 选项。上面的示意图描述的是静态表格的中断描述符DB,如果打开CONFIG_SPARSE_IRQ选项,系统使用Radix tree来保存中断描述符 DB,不过概念和静态表格是类似的。

struct irq_desc { struct irq_data        irq_data; unsigned int __percpu    *kstat_irqs;------IRQ的统计信息 irq_flow_handler_t    handle_irq;--------流控函数 struct irqaction    *action; -----------处理函数unsigned int        status_use_accessors;-----中断描述符的状态,参考IRQ_xxxx unsigned int        core_internal_state__do_not_mess_with_it;unsigned int        depth;----------描述嵌套深度的信息unsigned int        wake_depth;--------电源管理中的wake up source相关unsigned int        irq_count; unsigned long        last_unhandled;   unsigned int        irqs_unhandled; raw_spinlock_t        lock; struct cpumask        *percpu_enabled;
#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; 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

2. 响应函数 irqaction

在 irq_desc 中,struct irqaction  *action,主要是用来存用户注册的中断处理函数,

一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断.

所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。

所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断

struct irqaction {irq_handler_t handler;      //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数unsigned long flags;         //中断标志,注册时设置,比如上升沿中断,下降沿中断等cpumask_t mask;           //中断掩码const char *name;          //中断名称,产生中断的硬件的名字void *dev_id;              //设备idstruct irqaction *next;        //指向下一个成员int irq;                    //中断号,struct proc_dir_entry *dir;    //指向IRQn相关的/proc/irq/};

 

3. 中断数据 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 unsigned int        node;-------NUMA node index unsigned int        state_use_accessors;--------底层状态,参考IRQD_xxxx struct irq_chip        *chip;----------该中断描述符对应的irq chip数据结构 struct irq_domain    *domain;--------该中断描述符对应的irq domain数据结构 void            *handler_data;--------和外设specific handler相关的私有数据 void            *chip_data;---------和中断控制器相关的私有数据 struct msi_desc        *msi_desc; cpumask_var_t        affinity;-------和irq affinity相关 
};

4. 操作合集 irq_chip

struct irq_chip {const char    *name;unsigned int    (*irq_startup)(struct irq_data *data);-------------初始化中断void        (*irq_shutdown)(struct irq_data *data);----------------结束中断void        (*irq_enable)(struct irq_data *data);------------------使能中断void        (*irq_disable)(struct irq_data *data);-----------------关闭中断void        (*irq_ack)(struct irq_data *data);---------------------应答中断void        (*irq_mask)(struct irq_data *data);--------------------屏蔽中断void        (*irq_mask_ack)(struct irq_data *data);----------------应答并屏蔽中断void        (*irq_unmask)(struct irq_data *data);------------------解除中断屏蔽void        (*irq_eoi)(struct irq_data *data);---------------------发送EOI信号,表示硬件中断处理已经完成。int        (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);--------绑定中断到某个CPUint        (*irq_retrigger)(struct irq_data *data);----------------重新发送中断到CPUint        (*irq_set_type)(struct irq_data *data, unsigned int flow_type);----------------------------设置触发类型int        (*irq_set_wake)(struct irq_data *data, unsigned int on);-----------------------------------使能/关闭中断在电源管理中的唤醒功能。void        (*irq_bus_lock)(struct irq_data *data);void        (*irq_bus_sync_unlock)(struct irq_data *data);void        (*irq_cpu_online)(struct irq_data *data);void        (*irq_cpu_offline)(struct irq_data *data);void        (*irq_suspend)(struct irq_data *data);void        (*irq_resume)(struct irq_data *data);void        (*irq_pm_shutdown)(struct irq_data *data);
...unsigned long    flags;
}

 

5. 对应关系

如果针对线性的中断描述符,从数据结构上来看,不难看出:

 

 

6. 相关数据结构初始化

还记得上一篇《Linux 中断 —— GIC (中断号映射)》内容么:

1. 在系统初始化阶段,由 DTS 解析到 interrupt controller,分配一个 irq_domain 来表达这个控制器

2. 在驱动初始化,DTS 解析到 interrupt 后,调用 

irq_of_parse_and_map

|---->irq_create_of_mapping

        |----> domain->ops_xlate

        |----> irq_domain_alloc_irqs (每个版本调用有差异,不过差不多流程一样)

这里 irq_domain_alloc_irqs()调用 __irq_domain_alloc_irqs()

int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,unsigned int nr_irqs, int node, void *arg,bool realloc)
{
...if (realloc && irq_base >= 0) {virq = irq_base;} else {virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node);-------从allocated_irqs位图中查找第一个nr_irqs个空闲的比特位,最终调用__irq_alloc_descs。if (virq < 0) {pr_debug("cannot allocate IRQ(base %d, count %d)\n",irq_base, nr_irqs);return virq;}}if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {--------------分配struct irq_data数据结构。pr_debug("cannot allocate memory for IRQ%d\n", virq);ret = -ENOMEM;goto out_free_desc;}mutex_lock(&irq_domain_mutex);ret = irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);----调用struct irq_domain中的alloc回调函数进行硬件中断号和软件中断号的映射。if (ret < 0) {mutex_unlock(&irq_domain_mutex);goto out_free_irq_data;}for (i = 0; i < nr_irqs; i++)irq_domain_insert_irq(virq + i);mutex_unlock(&irq_domain_mutex);return virq;
...
}

__irq_domain_alloc_irqs 首先通过  irq_domain_alloc_descs->__irq_alloc_descs,通过一个名为表达当前 irq desc 的位图,来搜寻一个未使用的 irq number,这个位图的定义是:

#ifdef CONFIG_SPARSE_IRQ 
# define IRQ_BITMAP_BITS    (NR_IRQS + 8196)---对于Radix tree,除了预分配的,还可以动态 
#else                                                                             分配8196个中断描述符 
# define IRQ_BITMAP_BITS    NR_IRQS---对于静态定义的,IRQ最大值就是NR_IRQS 
#endif
__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,struct module *owner)
{
...mutex_lock(&sparse_irq_lock);start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,from, cnt, 0);-------------------在allocated_irqs位图中查找第一个连续cnt个为0的比特位区域。
...bitmap_set(allocated_irqs, start, cnt);-------------bitmap_set()设置这些比特位,表示这些比特位已经被占用。mutex_unlock(&sparse_irq_lock);return alloc_descs(start, cnt, node, owner);--------这里要看是否定义了CONFIG_SPARSE_IRQ,如果定义了需要动态分配一个struct irq_desc数据结构,以Radix Tree方式存储;没有的话则从irq_desc全局变量中加上偏移即可。err:mutex_unlock(&sparse_irq_lock);return ret;
}

在调用 alloc_descs 来分配一个 irq_desc 的结构,对于线性的 irq desc 来说,是一个以 irq number 为 index 的 irq desc 结构。

好了,这里有了驱动对应中断的 irq desc 结构了。

__irq_domain_alloc_irqs->irq_domain_alloc_irqs_recursive

static int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,unsigned int irq_base,unsigned int nr_irqs, void *arg)
{int ret = 0;struct irq_domain *parent = domain->parent;bool recursive = irq_domain_is_auto_recursive(domain);BUG_ON(recursive && !parent);if (recursive)ret = irq_domain_alloc_irqs_recursive(parent, irq_base,nr_irqs, arg);if (ret >= 0)ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);if (ret < 0 && recursive)irq_domain_free_irqs_recursive(parent, irq_base, nr_irqs);return ret;
}

会调用到 domain->ops->alloc

最终到 gic_irq_domain_alloc():

static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{int i, ret;irq_hw_number_t hwirq;unsigned int type = IRQ_TYPE_NONE;struct of_phandle_args *irq_data = arg;ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args,irq_data->args_count, &hwirq, &type);---------------首先根据args翻译出硬件中断号和中断类型。if (ret)return ret;for (i = 0; i < nr_irqs; i++)gic_irq_domain_map(domain, virq + i, hwirq + i);---------------执行软硬件的映射,并且根据中断类型设置struct irq_desc->handle_irq处理函数。return 0;
}

走到 gic_irq_domain_map:

gic_irq_domain_map()入参有struct irq_domain和软硬件中断号,主要分SGI/PPI一组,SPI一组:

主要工作由 irq_domain_set_info() 处理,irq_domain_set_hwirq_and_chip()通过Linux中断号获取struct irq_data数据结构,设置关联硬件中断号和struct irq_chip gic_chip关联。

__irq_set_handler()设置中断描述符irq_desc->handler_irq回调函数,对SPI类型来说就是handle_fasteoi_irq()。:

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw)
{if (hw < 32) {irq_set_percpu_devid(irq);-------------------------------PerCPU类型的中断有自己的特殊flag。irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,handle_percpu_devid_irq, NULL, NULL);set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);} else {irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,handle_fasteoi_irq, NULL, NULL);set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);gic_routable_irq_domain_ops->map(d, irq, hw);}return 0;
}
void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data, irq_flow_handler_t handler,void *handler_data, const char *handler_name)
{irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);__irq_set_handler(virq, handler, 0, handler_name);irq_set_handler_data(virq, handler_data);
}
int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data)
{struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);if (!irq_data)return -ENOENT;irq_data->hwirq = hwirq;irq_data->chip = chip ? chip : &no_irq_chip;irq_data->chip_data = chip_data;return 0;
}
void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)
{unsigned long flags;struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
...desc->handle_irq = handle;--------------------irq_desc->handler_irq和name赋值。desc->name = name;
...
}

好了,函数贴完了,看看入参:

在调用 irq_domain_set_info 的时候:

对于 SGI/PPI :

irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,handle_percpu_devid_irq, NULL, NULL);

对于 SPI :
 

irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,handle_fasteoi_irq, NULL, NULL);

1. 这里将 virq(irq number)和 hwirq 传入,并进行 irq_desc 下的 hwirq 赋值

2. 传入 irq chip 操作函数集,进行 irq_desc.irqdata->irq_chip 的赋值,这里直接赋值了 gic_chip 的操作函数集合:

static const struct irq_chip gic_chip = {.irq_mask        = gic_mask_irq,.irq_unmask        = gic_unmask_irq,.irq_eoi        = gic_eoi_irq,.irq_set_type        = gic_set_type,.irq_get_irqchip_state    = gic_irq_get_irqchip_state,.irq_set_irqchip_state    = gic_irq_set_irqchip_state,.flags            = IRQCHIP_SET_TYPE_MASKED |IRQCHIP_SKIP_SET_WAKE |IRQCHIP_MASK_ON_SUSPEND,
};

3. 将 irq_desc 对应的 handle_irq 进行赋值 handle_fasteoi_irq/handle_percpu_devid_irq

 

至此,一个驱动的相关数据结构均初始化完毕,流程如下:

 

参考文献 & 鸣谢:

http://www.wowotech.net/irq_subsystem/interrupt_descriptor.html

https://www.cnblogs.com/arnoldlu/p/8659981.html


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部