1. 设备中断注册
gic-v3的中断号划分在《IHI0069H_gic_architecture_specification.pdf》有所罗列:
前边介绍,在gic初始化时,已经使用了0-7
号SGI来作为IPI。
对于设备中断,使用的是PPI和SPI。设备probe时调用irq_of_parse_and_map
等函数,如timer_of_irq_init
。会调用函数来向系统注册中断,常用函数有如下几个:
1/// include/linux/of_irq.h
2extern int of_irq_get(struct device_node *dev, int index);
3extern int of_irq_get_byname(struct device_node *dev, const char *name);
4/// ... ...
5/*
6 * irq_of_parse_and_map() is used by all OF enabled platforms; but SPARC
7 * implements it differently. However, the prototype is the same for all,
8 * so declare it here regardless of the CONFIG_OF_IRQ setting.
9 */
10extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);
中断注册函数的调用关系如下:
2. irq_create_fwspec_mapping分析
注册主要查找活建立hwirq和struct irq_desc
的映射关系,这个映射记录到struct irq_domain
中。
主要流程是找到设备在所属的domain中使用的hwirq,然后irq_find_mapping
根据domain和hwirq查找struct irq_desc
,如果查找成功,将struct irq_desc
转为virq,irq_create_fwspec_mapping
完成工作。
如果irq_find_mapping
查找失败,则调用irq_create_mapping_affinity_locked
分配struct irq_desc
(非线性映射),创建映射。
1/// kernel/irq/irqdomain.c
2unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
3{
4 /// ... ...
5 /// 通过fwspec找到设备在所属domain中的hwirq
6 if (irq_domain_translate(domain, fwspec, &hwirq, &type))
7 return 0;
8
9 /*
10 * WARN if the irqchip returns a type with bits
11 * outside the sense mask set and clear these bits.
12 */
13 if (WARN_ON(type & ~IRQ_TYPE_SENSE_MASK))
14 type &= IRQ_TYPE_SENSE_MASK;
15
16 mutex_lock(&domain->root->mutex);
17
18 /*
19 * If we've already configured this interrupt,
20 * don't do it again, or hell will break loose.
21 */
22 virq = irq_find_mapping(domain, hwirq);
23 if (virq) {
24 /// 已经存在映射关系,进行一些检查后直接使用goto out返回
25 }
26
27 if (irq_domain_is_hierarchy(domain)) {
28 /// 级联中断,暂不分析
29 virq = irq_domain_alloc_irqs_locked(domain, -1, 1, NUMA_NO_NODE,
30 fwspec, false, NULL);
31 if (virq <= 0) {
32 virq = 0;
33 goto out;
34 }
35 } else {
36 /* Create mapping */
37 virq = irq_create_mapping_affinity_locked(domain, hwirq, NULL);
38 if (!virq)
39 goto out;
40 }
41
42 irq_data = irq_get_irq_data(virq);
43 if (WARN_ON(!irq_data)) {
44 virq = 0;
45 goto out;
46 }
47
48 /* Store trigger type */
49 irqd_set_trigger_type(irq_data, type);
50out:
51 mutex_unlock(&domain->root->mutex);
52
53 return virq;
54}
55EXPORT_SYMBOL_GPL(irq_create_fwspec_mapping);
2.1. 查找映射
查找映射关系由irq_find_mapping
实现,其内部调用__irq_resolve_mapping
。
1/// include/linux/irqdomain.h
2/**
3 * irq_find_mapping() - Find a linux irq from a hw irq number.
4 * @domain: domain owning this hardware interrupt
5 * @hwirq: hardware irq number in that domain space
6 */
7static inline unsigned int irq_find_mapping(struct irq_domain *domain,
8 irq_hw_number_t hwirq)
9{
10 unsigned int irq;
11
12 if (__irq_resolve_mapping(domain, hwirq, &irq))
13 return irq;
14
15 return 0;
16}
__irq_resolve_mapping
分析如下
1/// kernel/irq/irqdomain.c
2/**
3 * __irq_resolve_mapping() - Find a linux irq from a hw irq number.
4 * @domain: domain owning this hardware interrupt
5 * @hwirq: hardware irq number in that domain space
6 * @irq: optional pointer to return the Linux irq if required
7 *
8 * Returns the interrupt descriptor.
9 */
10struct irq_desc *__irq_resolve_mapping(struct irq_domain *domain,
11 irq_hw_number_t hwirq,
12 unsigned int *irq)
13{
14 struct irq_desc *desc = NULL;
15 struct irq_data *data;
16
17 /* Look for default domain if necessary */
18 if (domain == NULL)
19 domain = irq_default_domain; /// irq_default_domain默认为NULL
20 if (domain == NULL)
21 return desc;
22
23 if (irq_domain_is_nomap(domain)) {
24 /// ... ...
25 return desc;
26 }
27
28 rcu_read_lock();
29 /* Check if the hwirq is in the linear revmap. */
30 if (hwirq < domain->revmap_size) /// 线性映射
31 data = rcu_dereference(domain->revmap[hwirq]);
32 else
33 /// 非线性映射
34 data = radix_tree_lookup(&domain->revmap_tree, hwirq);
35
36 /// 中断已经映射过,无需重新映射
37 if (likely(data)) {
38 desc = irq_data_to_desc(data);
39 if (irq)
40 *irq = data->irq;
41 }
42
43 rcu_read_unlock();
44 return desc;
45}
46EXPORT_SYMBOL_GPL(__irq_resolve_mapping);
struct irq_desc
内嵌了struct irq_data
,通过irq_data_to_desc
即可获取struct irq_desc
。
1/// include/linux/irqdesc.h
2struct irq_desc {
3 struct irq_common_data irq_common_data;
4 struct irq_data irq_data;
5 /// ... ...
6} ____cacheline_internodealigned_in_smp;
2.2. 创建映射
irq_create_mapping_affinity_locked
的流程并不复杂,参考上边流程图对比代码分析即可。
注意struct maple_tree sparse_irqs
不论是线性映射还是非线性映射都会声明,线性映射和非线性映射在申请中断时,都会调用irq_insert_desc
将struct irq_desc
插入到sparse_irqs
中。
2.2.1. irq_create_mapping_affinity_locked
irq_domain_alloc_descs
函数用于分配中断号,最后会调到alloc_descs
,这个函数依据线性映射或者非线性映射由不同的实现。
1/// kernel/irq/irqdomain.c
2static unsigned int irq_create_mapping_affinity_locked(struct irq_domain *domain,
3 irq_hw_number_t hwirq,
4 const struct irq_affinity_desc *affinity)
5{
6 struct device_node *of_node = irq_domain_get_of_node(domain);
7 int virq;
8
9 pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq);
10
11 /* Allocate a virtual interrupt number */
12 virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node),
13 affinity);
14 if (virq <= 0) {
15 pr_debug("-> virq allocation failed\n");
16 return 0;
17 }
18
19 if (irq_domain_associate_locked(domain, virq, hwirq)) {
20 irq_free_desc(virq);
21 return 0;
22 }
23
24 pr_debug("irq %lu on domain %s mapped to virtual irq %u\n",
25 hwirq, of_node_full_name(of_node), virq);
26
27 return virq;
28}
2.2.2. irq_domain_associate_locked
irq_domain_associate_locked
通过irq_get_irq_data
从virq获取struct irq_data
1/// kernel/irq/irqdomain.c
2static int irq_domain_associate_locked(struct irq_domain *domain, unsigned int virq,
3 irq_hw_number_t hwirq)
4{
5 struct irq_data *irq_data = irq_get_irq_data(virq);
6 int ret;
7
8 /// ... ...
9
10 irq_data->hwirq = hwirq;
11 irq_data->domain = domain;
12 if (domain->ops->map) {
13 ret = domain->ops->map(domain, virq, hwirq);
14 if (ret != 0) {
15 /*
16 * If map() returns -EPERM, this interrupt is protected
17 * by the firmware or some other service and shall not
18 * be mapped. Don't bother telling the user about it.
19 */
20 if (ret != -EPERM) {
21 pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
22 domain->name, hwirq, virq, ret);
23 }
24 irq_data->domain = NULL;
25 irq_data->hwirq = 0;
26 return ret;
27 }
28 }
29
30 domain->mapcount++;
31 irq_domain_set_mapping(domain, hwirq, irq_data);
32
33 irq_clear_status_flags(virq, IRQ_NOREQUEST);
34
35 return 0;
36}
2.2.3. irq_domain_set_mapping
irq_domain
记录hwirq和irq_data
的关系
1/// kernel/irq/irqdomain.c
2static void irq_domain_set_mapping(struct irq_domain *domain,
3 irq_hw_number_t hwirq,
4 struct irq_data *irq_data)
5{
6 /*
7 * This also makes sure that all domains point to the same root when
8 * called from irq_domain_insert_irq() for each domain in a hierarchy.
9 */
10 lockdep_assert_held(&domain->root->mutex);
11
12 if (irq_domain_is_nomap(domain))
13 return;
14
15 if (hwirq < domain->revmap_size)
16 rcu_assign_pointer(domain->revmap[hwirq], irq_data);
17 else
18 radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
19}