硬中断和虚拟中断号
在Linux 内核笔记之高层中断处理一文中,介绍了ARM gic中断控制器对于硬中断的处理过程。gic的中断处理程序是从ack一个硬件中断开始的, 在gic的中断处理过程中,会根据中断的映射去寻找对应的虚拟中断号, 再去进行后续的中断处理。gic_handle_irq->handle_domain_irq
代码语言:javascript复制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提供了一个虚拟中断号的概念。
irq_domain
接下来讨论硬件中断号是如何映射到虚拟中断号的linux kernel提供irq_domain的管理框架, 将hwirq映射到虚拟中断号上。每一个中断控制器都需要注册一个irq_domain。
irq_domain数据结构:
代码语言:javascript复制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支持多种类型的映射。
irq_domain映射类型
- 线性映射
线性映射保留一张固定的表,通过hwirq number来索引.当hwirq被映射后, 会相应地分配 一个irq_desc, IRQ number就被存在表中。当hwirqs是固定的而且小于256, 用线性映射更好。它的优势是寻找时间固定,并且irq_descs只在in-use IRQs分配.缺点是表格和hwirq 最大numbers一样大。
代码语言:javascript复制irq_domain_add_linear
- 树映射
此种方法使用radix tree来维护映射, 通过key来查找此方法适合hwirq number非常大的时候, 因为它不需要分配和hwirq一样大的table。缺点是查表效率依赖与table里的entries数量。
代码语言:javascript复制irq_domain_add_tree
- 不映射
当有些硬件可以对hwirq number编程时,IRQ number被编进硬件寄存器里,那么就不需要映射了。这种情况下通过irq_create_direct_mapping()实现。
代码语言:javascript复制irq_domain_add_nomap
中断映射的完整过程
以arm64 dtb启动为例分析完整的中断映射过程。
- interrupt controller初始化的过程中,注册irq domain
以gic 注册irq_domain为例
代码语言:javascript复制 -> gic_of_init
-> gic_init_bases
-> irq_domain_add_linear
-> _irq_domain_add
代码语言:javascript复制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 %sn", 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中。
- 外设的驱动初始化过程中,创建硬中断和虚拟中断号的映射关系设备的驱动在初始化的时候可以调用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].
代码语言:javascript复制 -> 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
代码语言:javascript复制 ->gic_irq_domain_map
-> irq_domain_set_info
-> irq_domain_set_hwirq_and_chip
代码语言:javascript复制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中, 完成了硬中断到虚拟中断号的映射。
转自:https://blog.csdn.net/yhb1047818384/article/details/63687126