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