- 了解中断描述符struct irq_desc 参考韦东山百问网
1.通用中断代码处理
通用中断处理示意图:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyRQpkLwUTM4QDM0kDMwMTOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
对于每一个外设的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.中断相关结构体
- 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);
};
解释如下:
- 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初始化中断:
走到of_irq_to_resource就脱离了DT过程,函数调用流程图如下:
- A : 找到匹配的irq_domain,irq_domain会在中断控制器注册的时候加入;
- B : 判断irq_domain是否是hierarchy,不同的类型需要不同的操作,从图中可以看出虽然调用函数不一样,但是具体要执行的步骤是一样的:
- 搜索hwirq是否已经被映射到virq,如果是直接返回;
- 如果没有得到virq,那么把hwirq映射到virq;
- 调用irq_domain的相关函数初始化irq_desc.