1. 设备中断注册

gic-v3的中断号划分在《IHI0069H_gic_architecture_specification.pdf》有所罗列:

gic-v3

前边介绍,在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_descstruct 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}