linux Irq domain
文章引用:https://blog.csdn.net/longwang155069/article/details/105812097
为什么会引入IRQ_domain?
早期中断数量较少,所以可以分布在一个interrupt_controler,中断映射也很简单,每个中断号对应一个interrupt_controler。
而当一个系统中有多个interrupt-controller的时候,而且中断号也逐渐增加。linux内核为了应对此问题,引入了IRQ-domain的概念。irq-domain的引入相当于一个中断控制器就是一个irq-domain。就是一个中断区域。这样一来所有的irq-contoller会出现级联的布局。
IRQ-Domain的作用
中断之间连接在root-interrupt-controler中断控制器上,其中softirq_num为irq_req的irq号,hw_irq为dts(devices tree source) 中配置的irq号。
hwirq到softirq的映射
当开机之后,内核会自动将dts全部解析,然后会进行填充,对于中断使用如下函数进行解析dts,然后map,
of_irq_parse_one会将dts中的中断信息进行解析,然后通过irq_create_of_mapping函数进行hwirq到softirq的map
unsigned int irq_of_parse_and_map(struct device_node *dev, int index) { struct of_phandle_args oirq; if (of_irq_parse_one(dev, index, &oirq)) return 0; return irq_create_of_mapping(&oirq); }
首先dts中配置的中断号都是hwirq,刚开机hwirq没有对应的softirq的,所以第一次开机需要进行hwirq和softirq之间建立map
int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,int node, const struct cpumask *affinity) { unsigned int hint; if (virq >= 0) { virq = __irq_alloc_descs(virq, virq, cnt, node, THIS_MODULE,affinity); } else { hint = hwirq % nr_irqs; if (hint == 0) hint++; virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,affinity); if (virq <= 0 && hint > 1) { virq = __irq_alloc_descs(-1, 1, cnt, node, THIS_MODULE,affinity); } } return virq; }
通过上述的函数分配virq,也就是softirq,分配一个Irq_data结构,然后将virq设置到irq_data结构中
static struct irq_data *irq_domain_insert_irq_data(struct irq_domain *domain,struct irq_data *child) { struct irq_data *irq_data; irq_data = kzalloc_node(sizeof(*irq_data), GFP_KERNEL, irq_data_get_node(child)); if (irq_data) { child->parent_data = irq_data; irq_data->irq = child->irq; irq_data->common = child->common; irq_data->domain = domain; }
return irq_data;
}
将hwirq和irq_data→irq建立映射。这里有两种映射方式一种是线性映射,一种是树形映射
static void irq_domain_set_mapping(struct irq_domain *domain,irq_hw_number_t hwirq,
struct irq_data *irq_data) { if (hwirq < domain->revmap_size) { domain->linear_revmap[hwirq] = irq_data->irq; } else { mutex_lock(&domain->revmap_tree_mutex); radix_tree_insert(&domain->revmap_tree, hwirq, irq_data); mutex_unlock(&domain->revmap_tree_mutex); } }
irq和irq_desc的关系
在分配一个softirq的时候,其实最终也会分配一个irq_desc结构的
这里有两种管理方式,一种是通过线性固定开机固定分配好了的,一种是动态分配的
static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,const struct cpumask *affinity,struct module *owner) { struct irq_desc *desc; desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node); 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, node)) goto err_kstat; raw_spin_lock_init(&desc->lock); lockdep_set_class(&desc->lock, &irq_desc_lock_class); mutex_init(&desc->request_mutex); init_rcu_head(&desc->rcu); desc_set_defaults(irq, desc, node, affinity, owner); irqd_set(&desc->irq_data, flags); kobject_init(&desc->kobj, &irq_kobj_type); return desc; err_kstat: free_percpu(desc->kstat_irqs); err_desc: kfree(desc); return NULL; }
开机已经分配好了的,用于irq比较少
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_to_desc函数来获取desc通过irq的值,两种方式
struct irq_desc *irq_to_desc(unsigned int irq) { return radix_tree_lookup(&irq_desc_tree, irq); } struct irq_desc *irq_to_desc(unsigned int irq) { return (irq < NR_IRQS) ? irq_desc + irq : NULL; }
申请中断
通过request_irq函数来设置中断的回调函数,最终会设置到Irqaction中去
int request_threaded_irq(unsigned int irq, irq_handler_t handler,irq_handler_t thread_fn,
unsigned long irqflags,const char *devname, void *dev_id) { struct irqaction *action; struct irq_desc *desc; action->handler = handler; //设置中断回调函数 action->thread_fn = thread_fn; //如果中断是线程化,则需要设置此回调 action->flags = irqflags; action->name = devname; action->dev_id = dev_id; }
处理中断
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags) { irqreturn_t retval = IRQ_NONE; unsigned int irq = desc->irq_data.irq; struct irqaction *action; record_irq_time(desc); for_each_action_of_desc(desc, action) { irqreturn_t res; trace_irq_handler_entry(irq, action); res = action->handler(irq, action->dev_id); //处理中断 trace_irq_handler_exit(irq, action, res); if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",irq, action->handler)) local_irq_disable(); 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_wake_thread(desc, action); /* Fall through to add to randomness */ case IRQ_HANDLED: *flags |= action->flags; break; default: break; } retval |= res; } return retval; }
处理中断,action->handler(irq, action->dev_id);
如果是中断线程的话,唤醒线程 __irq_wake_thread(desc, action);