本文记录关于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;
}