Linux内核笔记之中断映射

前言
本文基于linux 4.19, 体系结构是aarch64.

1. 硬中断和虚拟中断号

Linux 内核笔记之高层中断处理一文中,介绍了ARM gic中断控制器对于硬中断的处理过程。gic的中断处理程序是从ack一个硬件中断开始的, 在gic的中断处理过程中,会根据中断的映射去寻找对应的虚拟中断号, 再去进行后续的中断处理。
gic_handle_irq->handle_domain_irq

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
...
#ifdef CONFIG_IRQ_DOMAIN
	if (lookup)
		irq = irq_find_mapping(domain, hwirq);   ---- 获得虚拟中断号
#endif
...
}

那么问题来了,为什么要有一个虚拟中断号的概念?
当前的SOC,通常内部会有多个中断控制器(比如gic interrupt controller, gpio interrupt controller), 每一个中断控制器对应多个中断号, 而硬件中断号在不同的中断控制器上是会重复编码的, 这时仅仅用硬中断号已经不能唯一标识一个外设中断。 对于软件工程师而言,我们不需要care是中断哪个中断控制器的第几个中断号, 因此linux kernel提供了一个虚拟中断号的概念。

2. irq_domain

接下来讨论硬件中断号是如何映射到虚拟中断号的
linux kernel提供irq_domain的管理框架, 将hwirq映射到虚拟中断号上。
每一个中断控制器都需要注册一个irq_domain。

2.1 irq_domain数据结构:

struct irq_domain {
        struct list_head link;
        const char *name;
        const struct irq_domain_ops *ops;
        void *host_data;
        unsigned int flags;
        unsigned int mapcount;

        /* Optional data */
        struct fwnode_handle *fwnode;
        enum irq_domain_bus_token bus_token;
        struct irq_domain_chip_generic *gc;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
        struct dentry           *debugfs_file;
#endif

        /* 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;
        struct mutex revmap_tree_mutex;
        unsigned int linear_revmap[];
};

link: 用于将irq domain连接到全局链表irq_domain_list中;
name: irq domain的名称;
ops: irq domain映射操作使用方法的集合;
mapcount: 映射好的中断的数量;
fwnode: 对应中断控制器的device node;
parent: 指向父级irqdomain的指针,用于支持级联irq_domain;
hwirq_max: 该irq domain支持的中断最大数量;
revmap_tree: Radix Tree 映射的根节点;
linear_revmap: hwirq->virq 反向映射的线性表;

从该结构体中我们可以看出irq_domain支持多种类型的映射。

2.2 irq_domain映射类型
(1) 线性映射
线性映射保留一张固定的表,通过hwirq number来索引.当hwirq被映射后, 会相应地分配
一个irq_desc, IRQ number就被存在表中.
当hwirqs是固定的而且小于256, 用线性映射更好.它的优势是寻找时间固定,并且irq_descs
只在in-use IRQs分配.缺点是表格和hwirq 最大numbers一样大.

irq_domain_add_linear

(2) 树映射
此种方法使用radix tree来维护映射, 通过key来查找
此方法适合hwirq number非常大的时候, 因为它不需要分配和hwirq一样大的table.
缺点是查表效率依赖与table里的entries数量.

irq_domain_add_tree

(3) 不映射
当有些硬件可以对hwirq number编程时,IRQ number被编进硬件寄存器里,那么就不需要映射了.
这种情况下通过irq_create_direct_mapping()实现.

irq_domain_add_nomap()

3. 中断映射的完整过程

以arm64 dtb启动为例分析完整的中断映射过程。

(1) interrupt controller初始化的过程中,注册irq domain
, 以gic 注册irq_domain为例

+-> gic_of_init
	+-> gic_init_bases
		+-> irq_domain_add_linear
			+-> _irq_domain_add
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data)
{
	struct device_node *of_node = to_of_node(fwnode);
	struct irqchip_fwid *fwid;
	struct irq_domain *domain;

	static atomic_t unknown_domains;

	domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
			      GFP_KERNEL, of_node_to_nid(of_node));       
	if (WARN_ON(!domain))
		return NULL;
	...
	of_node_get(of_node);
	/* Fill structure */
	INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	mutex_init(&domain->revmap_tree_mutex);
	domain->ops = ops;
	domain->host_data = host_data;
	domain->hwirq_max = hwirq_max;
	domain->revmap_size = size;
	domain->revmap_direct_max_irq = direct_max;
	irq_domain_check_hierarchy(domain);

	mutex_lock(&irq_domain_mutex);
	debugfs_add_domain_dir(domain);
	list_add(&domain->link, &irq_domain_list);     
	mutex_unlock(&irq_domain_mutex);

	pr_debug("Added domain %s\n", domain->name);
	return domain;
}

irq_domain_add()用于初始化一个irq_domain数据结构。
irq_domain分配的内存大小为sizeof(*domain) + (sizeof(unsigned int) * size), (sizeof(unsigned int) * size)大小的空间是用于linear_revmap[]成员。最后,irq_domain添加到全局的链表irq_domain_list中。

(2) 外设的驱动初始化过程中,创建硬中断和虚拟中断号的映射关系
设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该device node中和中断相关的内容的解析,并建立映射关系

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);
}

of_irq_parse_one()函数用于解析DTS文件中设备定义的属性,如"reg", “interrupt”, 最后把DTS中的"interrupts"存放在*out_irq->args[1].

+-> irq_create_of_mapping
	+->irq_create_fwspec_mapping
		+-> irq_find_matching_fwspec // 找到device node对应的irq_domain, 每一个irq_domain都定义了一系列的映射相关的方法
		+-> irq_domain_translate //解析中断信息,如硬件中断号, 中断出发类型
			+-> domain->ops->translate (gic_irq_domain_translate)
		+-> irq_create_mapping // 映射硬件中断号到虚拟中断号
			+-> irq_domain_alloc_descs // 分配一个虚拟中断号 从allocated_irq位图中取第一个空闲的bit位作为虚拟中断号
				+-> irq_domain_alloc_irqs_hierarchy
					+-> domain->ops->alloc (gic_irq_domain_alloc)
						+-> gic_irq_domain_map // gic创建硬中断和虚拟中断号的映射,并且根据中断类型设置struct irq_desc->handle_irq处理函数
			+-> irq_domain_associate
					+-> domain->ops->map 
		
+->gic_irq_domain_map 
	+-> irq_domain_set_info
		+-> irq_domain_set_hwirq_and_chip
int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,
				  irq_hw_number_t hwirq, struct irq_chip *chip,
				  void *chip_data)
{
	struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);

	if (!irq_data)
		return -ENOENT;

	irq_data->hwirq = hwirq;
	irq_data->chip = chip ? chip : &no_irq_chip;
	irq_data->chip_data = chip_data;

	return 0;
}

通过虚拟中断号获取irq_data结构体,并将hwirq设置到irq_data->hwirq中, 完成了硬中断到虚拟中断号的映射。

在这里插入图片描述

4. 参考资料

  1. 《奔跑吧linux内核》
  2. IRQ-domain.txt
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章