目录
上一篇的传送门:linux arm64 中断处理流程完整分析 (一)—— 中断向量表、中断处理流程汇编部分
本文以gic 为例,探索linux 内核中断控制器的注册过程,中断回调注册过程,以及相关中断处理流程。
GIC V3 结构简单介绍
为了便于看gic 的代码,这里简单介绍gic 的结构。详细可以参考这个页面,以及附录中的arm 官方文档。
GICv3 包含以下几个逻辑组件:
- 一个 Distributor
- 每个PE(Process Element,处理器核) 一个Redistributor
- 每个PE 一个 CPU interface
linux 中断数据结构
irq_domain
irq_domain 用来描述一个中断控制器。在irq-gic-v3.c 中,gic driver在probe 时,会创建一个irq_domian。
一个irq_domain 结构体上可以挂接多个中断号。比如一个gic 上接入了n个中断线,那么对应的irq_domain上就挂接了n 个中断号。
irq_domain->ops 中包含了该控制器对应的操作函数,在irq-gic-v3.c 中,实现了.translate, .alloc, .free, .select 这四个方法。
详细定义如下:
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops;
void *host_data;
unsigned int flags;
unsigned int mapcount;
/* Optional data */
struct fwnode_handle *fwnode;
enum irq_domain_bus_token bus_token;
struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
#endif
/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;
unsigned int revmap_direct_max_irq;
unsigned int revmap_size;
struct radix_tree_root revmap_tree;
struct mutex revmap_tree_mutex;
unsigned int linear_revmap[];
};
irq_desc
- irq_desc 用来描述一个中断(号)。
- 在dts 被解析时,如果一个dts 中的设备节点中引用了中断号,那么将对每一个中断号创建一个irq_desc 数据结构。
- 所有的irq_desc 会被插入irqdesc.c 中的全局static 变量irq_desc_tree 中:radix_tree_insert(&irq_desc_tree, irq, desc);
此后通过radix_tree_lookup(&irq_desc_tree, irq); 即可从(int)irq 号获得这个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;
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 tot_count;
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;
const struct cpumask *percpu_affinity;
...
irq_data
irq_desc 直接包含irq_data。 会以hwirq 为索引,被插入到 irq_domain 的revmap_tree中:radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
同时,通过container_of,就可以从irq_data 得到相应的irq_desc。
irq_data->chip 在解析dts,初始化中断资源时,会指向irq-gic-v3.c 中定义的chip 结构体。irq_chip 的定义与作用见下小节
struct irq_data {
u32 mask;
unsigned int irq;
unsigned long hwirq;
struct irq_common_data *common;
struct irq_chip *chip;
struct irq_domain *domain;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_data *parent_data;
#endif
void *chip_data;
};
irq_chip
irq_chip 中定义了很多方法,这些方法都是需要中断控制器去实现的,主要用来操作的中断控制器的硬件。
例如,irq_mask 方法在irq-gic-v3.c 中指向gic_mask_irq()。disable_irq() 接口会通过virq找到对应的irq_desc,进而通过irq_desc->irq_data.chip->irq_mask ,最终调用到gic_mask_irq()。这个函数最终会通过写gic 中的 GICD_ICENABLER寄存器来mask 中断。
struct irq_chip {
struct device *parent_device;
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);
...
};
gic 初始化流程
这里主要做的事情:
- 创建一个irq_domain
- gic 初始化
- 设置内核中断回调,使得中断发生时能从中断向量表跳过来
主要代码分析如下
/*drivers/irqchip/irq-gic-v3.c*/
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
gic_of_init(struct device_node *node, struct device_node *parent)
"从dts中获取distributor,以及redistributor 的地址"
gic_init_bases(dist_base, rdist_regs, nr_redist_regions, redist_stride, &node->fwnode);
"获取一共有多少个中断"
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data)
__irq_domain_add(fwnode, size=0, hwirq_max=~0, direct_max=0, ops, host_data)
/*这里分配并填充了一个domain*/
domain = kzalloc_node(...)
domain->name = name;
domain->revmap_size = size; /*此处size==0*/
domain->revmap_direct_max_irq = direct_max; /*此处direct_max==0*/
...
/* 这里设置了IRQ_DOMAIN_FLAG_HIERARCHY 这个标志位。表面是层级结构。此处关系到后续中断号的映射方式 */
irq_domain_check_hierarchy(domain);
if (domain->ops->alloc)
domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;
...
return domian;
/*设置irq handle 为gic_handle_irq,使得有中断时从中断向量表跳过来*/
set_handle_irq(gic_handle_irq);
内核初始化时的中断资源初始化过程
主要做的事情:
- 内核启动时,在解析dts 被创建device 时,会对这个dts node 中引用的每个中断进行 irq_create_of_mapping。所谓create mapping 实现的作用就是分配一个irq_desc,并将这个irq_desc 中的irq_data 以hwirq 为index插入所在irq_domain 的 domain->revmap_tree。
- 同时virq 也是在此时分配的。virq 是整个内核全局通用的,使用allocated_irqs 这个全局变量来记录(见kernel/irq/irqdesc.c)。hwirq 是domain 内部的。
设备的中断信息会被描述在dts中。内核在解析dts 时,对每个中断资源进行了初始化,主要是创建irq_desc,建立hwirq与virq 的mapping关系。
of_device_alloc 为入口
在 of_device_alloc时(内核是如何调到这个函数的可以参考这个博客),对每个设备节点中描述的中断都进程了资源初始化。具体如下:
of_device_alloc
of_irq_to_resource_table(np, res, num_irq)
for (i = 0; i < nr_irqs; i++, res++)
of_irq_to_resource(dev, i, res)
irq = of_irq_get(dev, index);
/*Decode a node's IRQ and return it as a Linux IRQ number*/
of_irq_parse_one(dev, index, &oirq);
/*在irq_domain_list 中找到当前irq 对应的irq_domain*/
domain = irq_find_host(oirq.np);
/*
* 如果domain 都没找到,那就稍后重试(等domian 创建之后)。
* domain 是在gic 初始化时创建的,详见本文上一小节
*/
if (!domain)
return -EPROBE_DEFER;
irq_create_of_mapping(&oirq)
of_phandle_args_to_fwspec(irq_data=oirq, &fwspec);
irq_create_fwspec_mapping(&fwspec);
/*
* 从下边find mapping 的过程就可以看出来,创建mapping,肯定就是去创建data 来记录这个
* hwirq。创建mapping 是在后边 irq_domain_alloc_irqs --> irq_domain_insert_irq 中
* 完成的。
*/
virq = irq_find_mapping(domain, hwirq);
data = radix_tree_lookup(&domain->revmap_tree, hwirq);
return data ? data->irq : 0;
if (virq == 0)
/*对于gic来说,是hierarchy*/
if (irq_domain_is_hierarchy(domain))
/*单独分析下边的函数*/
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
res->start = res -> end = irq;
...
irq_domain_alloc_irqs 详解
/*
* 这个函数的主要工作:
* 1. irq_domain_alloc_descs
* 2. irq_domain_alloc_irq_data
* 3. domain->ops->alloc
* 4. irq_domain_insert_irq(virq + i); 将irq_data 以hwirq 作为index插入 domain->revmap_tree_mutex。
* 此后irq_find_mapping即可返回有效值。
*/
irq_domain_alloc_irqs(domain, nr_irqs = 1, node = NUMA_NO_NODE, arg = fwspec);
__irq_domain_alloc_irqs(domain, irq_base = -1, nr_irqs = 1, node, arg, realloc = false, affinity = NULL);
/*irq_domain_alloc_descs在后边展开*/
virq = irq_domain_alloc_descs(irq_base = -1, nr_irqs = 1, 0, node, affinity);
irq_domain_alloc_irq_data(domain, virq, nr_irqs = 1)
/* The outermost irq_data is embedded in struct irq_desc */
/* virq 对应的irq_data 嵌入在上面已经分配好的irq_desc 中,这里只是找到这个irq_data,然后填充了domain等 */
irq_data = irq_get_irq_data(virq + i);
irq_data->domain = domain;
/* domian->parent不为空时,为其创建了一个irq_data */
for (parent = domain->parent; parent; parent = parent->parent) {
irq_data = irq_domain_insert_irq_data(domain = parent, child = irq_data);
irq_data = kzalloc_node(sizeof(*irq_data), GFP_KERNEL, irq_data_get_node(child));
child->parent_data = irq_data;
irq_data->irq = child->irq;
irq_data->common = child->common;
irq_data->domain = domain;
return irq_data;
mutex_lock(&irq_domain_mutex);
irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
domain->ops->alloc(domain, irq_base, nr_irqs, arg);
/*
* 主要作用:
* 1. 填充irq_data->hwirq。至此也就建立了virq 和hwirq的联系————两者都保存在irq_data中
* 2. 填充了irq_data->chip。
* 3. 设置了desc->handle_irq = handle_fasteoi_irq。每次中断的必经之路
* 后边有代码分析
*/
gic_irq_domain_alloc(domain, irq_base, nr_irqs, arg)
irq_domain_insert_irq(virq + i);
data = irq_get_irq_data(virq)
domain->mapcount++;
irq_domain_set_mapping(domain, data->hwirq, data);
mutex_lock(&domain->revmap_tree_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
mutex_unlock(&domain->revmap_tree_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST);
mutex_unlock(&irq_domain_mutex);
irq_domain_alloc_descs 详解
virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity);
__irq_alloc_descs(irq=-1, from = hint = hwirq % nr_irqs, cnt=1, node, THIS_MODULE, affinity=NULL);
/*找到并判断该中断desc 是否已经被创建*/
start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS, from, cnt, 0);
/*此时的start 大概率还是等于hwirq*/
alloc_descs(start, cnt, node, affinity, owner);
for (i = 0; i < cnt; i++)
desc = alloc_desc(irq = start + i, node, flags, mask, owner);
desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node);
/* 为每个cpu 都分配了一个kstat,即该中断在每个cpu 上都有独立的stat 状态 */
desc->kstat_irqs = alloc_percpu(unsigned int);
alloc_masks(desc, node);
/*接下来是一系列的初始化*/
raw_spin_lock_init(&desc->lock);
lockdep_set_class(&desc->lock, &irq_desc_lock_class); /*Linux 死锁检测模块*/
mutex_init(&desc->request_mutex);
init_rcu_head(&desc->rcu); /*rcu, Read Copy Update。一种多线程读,单线程写场景下适用的机制*/
desc_set_defaults(irq, desc, node, affinity, owner);
irqd_set(&desc->irq_data, flags);
desc->irq_data.common->state_use_accessors |= flags;
kobject_init(&desc->kobj, &irq_kobj_type);
irq_insert_desc(start + i, desc);
/* 将该irq 插入irq_desc_tree 这个radix树 */
radix_tree_insert(&irq_desc_tree, irq, desc);
irq_sysfs_add(start + i, desc);
irq_add_debugfs_entry(start + i, desc);
/* 表示该中断desc已经被创建 */
bitmap_set(allocated_irqs, start, cnt);
return start; /*所以最终的virq 就是allocated_irqs 这个bitsmap中的某个bit的偏移*/
gic_irq_domain_alloc详解
主要作用:
- 填充irq_data->hwirq。至此也就建立了virq 和hwirq的联系————两者都保存在irq_data中
- 填充了irq_data->chip。
- 设置了desc->handle_irq = handle_fasteoi_irq。每次中断的必经之路
gic_irq_domain_alloc(domain, irq_base, nr_irqs, arg)
/*从dts 的interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH> 中获取hwirq,及中断触发类型*/
gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
gic_irq_domain_map(domain, virq, hwirq);
/* 这里很关键,设置了desc->handle_irq。也就是说中断到来之后,会跑到handle_fasteoi_irq */
irq_domain_set_info(domain, virq, hwirq, chip=&gic_chip, chip_data=d->host_data,
handler=handle_fasteoi_irq, NULL, NULL);
/* 重点! irq_data.chip 就是在这个时候设置的 */
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
irq_data = irq_domain_get_irq_data(domain, virq);
irq_data->hwirq = hwirq;
irq_data->chip = chip ? chip : &no_irq_chip;
irq_data->chip_data = chip_data;
__irq_set_handler(virq, handler, is_chained=0, NULL);
desc->handle_irq = handle;
desc->name = name;
irq_set_handler_data(virq, NULL);
desc->irq_common_data.handler_data = data;
irq_set_probe(irq);
irq_modify_status(irq, IRQ_NOPROBE, 0);
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
/* 没看出来IRQD_SINGLE_TARGET 有什么作用 */
__irqd_to_state(d) & IRQD_SINGLE_TARGET;
中断注册过程
request_irq,也就是中断注册过程,主要做了以下几件事:
- irq_to_desc, 根据irq 找到相应的desc
- 分配一个irqaction 数据结构,且将request_irq 所传进来的handler, flags, dev等参数保存在这个action 中
- kthread_create 新建一个kthread。这个thread 的函数体为统一的irq_thread,irq_thread 的参数为上面新分配的action
- 将上述action 挂接到当前desc 的action 链表上。以便于中断到来时逐个执行action
从request_irq 开始的关键代码分析如下
/*include/linux/interrupt.h*/
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
request_threaded_irq(irq, handler, thread_fn=NULL, irqflags=flags, devname=name, dev_id=dev)
desc = irq_to_desc(irq);
/* 关键,这里分配并填充了一个action,保存了request_irq 传递下来的所有参数 */
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
/*电源域上电*/
irq_chip_pm_get(&desc->irq_data);
/* 这里大有文章。这里假设IRQF_ONESHOT ,且是shared*/
__setup_irq(irq, desc, new=action);
/* 将irq 标记为线程化irq */
irq_setup_forced_threading(new);
new->flags |= IRQF_ONESHOT;
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
new->thread_fn = new->handler;
/* 可以看到这个handler 会直接返回IRQ_WAKE_THREAD */
new->handler = irq_default_primary_handler {return IRQ_WAKE_THREAD;};
setup_irq_thread(new, irq, secondary=false);
/*这里创建了一个线程,irq_thread 是一个loop,后边分析*/
t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
/* 设置该线程的优先级 */
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
/* 引用一下这个task,避免irq_tread 退出后被释放 */
get_task_struct(t);
new->thread = t;
/* IRQTF_AFFINITY 的含义是irq_thread 被要求调整affinity */
set_bit(IRQTF_AFFINITY, &new->thread_flags);
/* 还没搞清楚是干嘛的?*/
irq_request_resources(desc);
/* */
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
/* 如果old 存在,表明这个中断是shared。 */
接下来检查了new 和old 的是否存在冲突,包括以下几个方面:
IRQF_SHARED, IRQF_TRIGGER_MASK, IRQF_ONESHOT, IRQF_PERCPU
/*
* 如下这个循环遍历完,old_ptr 等于最后一个action->next的地址。最后给*old_ptr = new,就相当于
* 最后一个action->next = new
*/
do {
//Or all existing action->thread_mask bits,so we can find the next zero bit for thisnew action.
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}
if (new->flags & IRQF_ONESHOT)
/*
* thread_mask 用来标记一个IRQF_ONESHOT thread handler被唤醒。
* 当这个thread 完成后,该bit 清0。
* 当一个shared 中断的所有thread 完成后,
* desc->threads_active becomes zero and the interrupt line is unmasked
*/
new->thread_mask = 1UL << ffz(thread_mask);
/* 将当前action 挂到action 链表的末尾 */
*old_ptr = new;
irq_pm_install_action(desc, new);
desc->nr_actions++;
desc->irq_count = 0;
desc->irqs_unhandled = 0;
/* 释放各种锁 */
raw_spin_unlock_irqrestore(&desc->lock, flags);
chip_bus_sync_unlock(desc);
mutex_unlock(&desc->request_mutex);
/* 没看懂,测量啥? */
irq_setup_timings(desc, new);
/*注册proc 文件节点*/
register_irq_proc(irq, desc);
new->dir = NULL;
/* create /proc/irq/1234/handler/ */
register_handler_proc(irq, new);
中断响应过程
gic_handle_irq
接下来分析 gic_handle_irq 过程。如何从中断向量表中跳的该函数请参考上一篇文章。
/*regs入参是在汇编代码中将系统寄存器保存进来的*/
gic_handle_irq(struct pt_regs *regs)
do {
/*此处读的是系统寄存器ICC_IAR1_EL1,及中断号INTID*/
irqnr = gic_read_iar();
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
/*
* eoir(ICC_EOIR1_EL1, End Of Interrupt Register),
* to inform the CPU interface that it has completed
* the processing of the specified Group 1 interrupt.
*/
gic_write_eoir(irqnr);
handle_domain_irq(gic_data.domain, irqnr, regs);
/*保存中断发生时的现场堆栈到__irq_regs,此后就可以通过get_irq_regs 获得中断发生时的现场堆栈。*/
old_regs = set_irq_regs(regs);
/*进入中断上下文。告诉系统,现在正在处理中断的上半部分工作,不可以进行调度*/
irq_enter();
if (lookup)
irq = irq_find_mapping(domain, hwirq);
generic_handle_irq(irq);
desc = irq_to_desc(irq);
generic_handle_irq_desc(desc);
desc->handle_irq(desc);
/*后边分析代码*/
handle_fasteoi_irq(desc)
/* 退出中断上下文 */
irq_exit();
set_irq_regs(old_regs);
}
} while ( while (irqnr != ICC_IAR1_EL1_SPURIOUS))
handle_fasteoi_irq
handle_fasteoi_irq(struct irq_desc *desc)
raw_spin_lock(&desc->lock);
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/* 中断统计,会统计每个cpu 以及所有的*/
kstat_incr_irqs_this_cpu(desc);
handle_irq_event(desc);
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
/*此后没有修改desc 的地方,所以unlock了*/
raw_spin_unlock(&desc->lock);
handle_irq_event_percpu(desc);
__handle_irq_event_percpu(desc, &flags);
record_irq_time(desc);
for_each_action_of_desc(desc, action) {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
/*
* 在注册中断时,在函数request_irq -> request_threaded_irq -> __setup_irq ->
* irq_setup_forced_threading 中,
* new->handler = irq_default_primary_handler {return IRQ_WAKE_THREAD;};
* 因此下边这里会直接返回 IRQ_WAKE_THREAD。真正的中断回调在被唤醒的线程中
*/
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
/* 此处唤醒中断回调线程,即irq_thread 处执行,见下小节 */
__irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
/* 将设备两次中断的时间间隔作为噪声源将随机数据加入熵池 */
add_interrupt_randomness(desc->irq_data.irq, flags);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
cond_unmask_eoi_irq(desc, chip);
raw_spin_unlock(&desc->lock);
irq_thread
这里就已经不是真正意义的中断上下文了,只是一个普通的线程
static int irq_thread(void *data)
/* irq_wait_for_interrupt(action)中会schedule(); */
while (!irq_wait_for_interrupt(action)) {
action_ret = handler_fn(desc, action);
irq_forced_thread_fn(desc, action);
ret = action->thread_fn(action->irq, action->dev_id);
}
终于写完这一篇,这个可能是耗时最长的一个博客了。
过程中遇到很多linux 进程管理的内容,下一个主题就看进程管理吧。
附录
非IRQ_DOMAIN_FLAG_HIERARCHY 中断mapping 流程
gic 属于HIERARCHY属性的irq_domain,这里额外看一下没有HIERARCHY 属性时,irq_create_mapping 的过程。假设定义了CONFIG_SPARSE_IRQ宏。
irq_create_mapping(domain, hwirq);
/* 为hwirq号分配一个irq_desc*/
virq = irq_domain_alloc_descs(-1, cnt=1, hwirq, of_node_to_nid(of_node), NULL);
/* 建立domain, virq, hwirq 的联系 */
irq_domain_associate(domain, virq(start), hwirq)
irq_data = irq_get_irq_data(virq);
desc = irq_to_desc(virq); /*这里就是用radixtree 去查找的*/
return &desc->irq_data
mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
/*gic-v3 这个 domain的ops 并没有map 函数,因此这里没有调用map*/
domain->mapcount++;
irq_domain_set_mapping(domain, hwirq, irq_data);
/*注:domain->revmap_size == 0*/
/*
* 以hwirq 为索引,将irq_data 插入&domain->revmap_tree ,此后就可以通过hwirq 得到irq_data 了。
* 同时irq_data 隶属于irq_desc,所以根据container_of 可以直接得到对应的irq_desc
*/
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST);
desc = irq_get_desc_lock(irq, &flags, 0); /*未完。。。*/