irq domain

本文记录关于irq domain的学习笔记。

irq domain是什么?

我的理解,irq domain主要是完成从硬件irq no到软件irq no的转换的。
因为硬件设备中存在多个级联的irq控制器,而每个irq控制器都有自己的irq no,当然这个是纯硬件的编码。完全存在多个irq控制器中的irq no都是相同的情况。
linux内核为了方便管理,需要在全系统提供一个irq no,它能唯一地描述唯一的某个硬件中断。这个irq no是个纯软件虚拟出来的。
对于中断控制器来说,它知道自己有多少个硬件中断,因此它需要向系统申请响应的软件中断号,然后把这个硬中断号到软中断号的映射存储下来,方便后面查阅。
这就是irq domain的作用。

映射关系有哪些?以及如何存储?

线性映射

其实,这里说的线性不一定是我们平常认为的线性,它只是说存储映射关系使用的是数组,看起来像是线性的。
hw irq no作为数组的index,而value就是virt irq no(hw irq no指硬件中断号,virt irq no指软件中断号)。
这样的存储结构对hw irq no是有要求的。
比如hw irq no不能太大,举例来说,如果只有两个中断,但hw irq no是100和101,难道我们要申请一个数组大小为102,但是只使用array[100]和array[101],那就太浪费了。
当然,hw irq no还需要比较紧密,距离来说,还是只有两个中断,分别是1和20,那就需要申请一个数组大小为21,但是只使用array[1]和array[20],这样也非常浪费。

Radix Tree map

建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。如果的确不能满足线性映射的条件,可以考虑Radix Tree map。

no map

有些中断控制器很强,可以通过寄存器配置HW interrupt ID而不是由物理连接决定的。
例如PowerPC 系统使用的MPIC (Multi-Processor Interrupt Controller)。在这种情况下,不需要进行映射,我们直接把IRQ number写入HW interrupt ID配置寄存器就OK了,这时候,生成的HW interrupt ID就是IRQ number,也就不需要进行mapping了。

存储使用的数据结构

来看看相关的数据结构体内容

struct irq_domain {
    ......
    /* 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;
    unsigned int linear_revmap[];
};

有个小疑问:irq domain总是把hw no转成virt no,而不做相反的操作。为什么呢?它似乎总是占用hw的角度来处理。
hwirq_max:代表这个domain里最大的hw irq no;
revmap_direct_max_irq:是no map那种映射的最大irq no;
revmap_size和linear_revmap:线性映射用的,revmap_size代表数组的大小;
revmap_tree:radix tree map使用的,代表的是radix tree的root node

对于线性映射

hwirq_max:一般情况下等于revmap_size?
revmap_direct_max_irq:0
revmap_size:下面的数组大小
revmap_tree:null
linear_revmap[]:有意义

对于radix tree map

hwirq_max:有意义
revmap_direct_max_irq:0
revmap_size:0
revmap_tree:执行root node
linear_revmap[]:null

下面就要来看个函数,来加深对上面数据结构的理解

unsigned int irq_find_mapping(struct irq_domain *domain,
                  irq_hw_number_t hwirq)
{
    struct irq_data *data;

    // 1st, check no map
    if (hwirq < domain->revmap_direct_max_irq) {
        data = irq_domain_get_irq_data(domain, hwirq);
        if (data && data->hwirq == hwirq)
            return hwirq;
    }

    // 2nd, check linear map
    if (hwirq < domain->revmap_size)
        return domain->linear_revmap[hwirq];

    // 3rd, check radix tree map
    rcu_read_lock();
    data = radix_tree_lookup(&domain->revmap_tree, hwirq);
    rcu_read_unlock();
    return data ? data->irq : 0;
}

这个函数完成在irq domain中,根据hw irq查询virt irq的过程。
如代码注释,三种映射关系都考虑了,但是有不同优先级,先是no map,再是线性映射,最后是radix tree。

管理irq domain

系统中维护一个list保存所有的irq domain,对它的读写受irq_domain_mutex的保护。
每个结构体中有个link,可以将自己挂入到全局list中。

static LIST_HEAD(irq_domain_list);
static DEFINE_MUTEX(irq_domain_mutex);

struct irq_domain {
    struct list_head link;
    ......
};

创建irq domain
创建的入口只有这一个,特别注意动态申请的空间大小,包含了线性映射需要的数组空间。当然,如果不是线性映射,这里的size就是0,也不会额外申请。
由于使用的是kzalloc,所以物理地址是连续的。irq domain访问数组时,数组虽然不在它的结构体内,但是物理上连续,可以直接访问。而size则帮忙指定边界,不要越界访问。

struct irq_domain *__irq_domain_add(struct device_node *of_node, int size,
                    irq_hw_number_t hwirq_max, int direct_max,
                    const struct irq_domain_ops *ops,
                    void *host_data)
{
    struct irq_domain *domain;
    // 特别注意这里申请的空间,包含了线性映射需要的数组空间
    domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                  GFP_KERNEL, of_node_to_nid(of_node));

    // 填充size并初始化radix tree
    INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
    domain->hwirq_max = hwirq_max;
    domain->revmap_size = size;
    domain->revmap_direct_max_irq = direct_max;

    // 挂入全局list
    mutex_lock(&irq_domain_mutex);
    list_add(&domain->link, &irq_domain_list);
    mutex_unlock(&irq_domain_mutex);

    return domain;
}

参考资料

1.http://www.wowotech.net/linux_kenrel/irq-domain.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。