中断管理基础学习笔记 - 2.中断控制器初始化【转】
目录
1. 前言
2. init_IRQ
|- -of_irq_init
|- - -gic_of_init
参考文档
1. 前言
本专题我们开始学习进程管理部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
本节记录ARM架构下中断是如何管理的,Linux内核中的中断管理机制是如何设计与实现的,以及常用的下半部机制,如软中断、tasklet、workqueue等。本文及后续中断相关笔记均以qemu 5.0.0内嵌平台为例,中断控制器采用GIC-400控制器,支持GIC version2技术规范。本文主要以GIC为例记录中断控制器的初始化流程。
kernel版本:5.10
平台:arm64
注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“
2. init_IRQ
在kernel启动流程-start_kernel的执行_1.概述一文介绍start_kernel总体流程的时候有对init_IRQ做过简单的说明,本节将对init_IRQ做详细的介绍
void __init init_IRQ(void)
|--init_irq_stacks();//CONFIG_VMAP_STACK
|--irqchip_init();//初始化irq控制器,并注册irq_domain
| | //扫描__irqchip_of_table,匹配DTB中定义的中断控制器,匹配成功则调用中断控制器设置的初始化函数
| |--of_irq_init(__irqchip_of_table);
| |--acpi_probe_device_table(irqchip);
|--if (system_uses_irq_prio_masking())
local_daif_restore(DAIF_PROCCTX_NOIRQ);
init_IRQ初始化中断,包括中断栈的初始化,中断控制器的初始化
init_irq_stacks:分配per cpu中断栈。此处定义了CONFIG_VMAP_STACK,则中断栈将从vmalloc区域分配,每个cpu的中断栈指针将保存在全局per-cpu变量irq_stack_ptr中
irqchip_init:初始化irq控制器,并注册irq_domain,其中of_irq_init扫描__irqchip_of_table,匹配DTB中定义的中断控制器,匹配成功则调用中断控制器设置的初始化函数
|- -of_irq_init
在介绍of_irq_init函数之前,首先要澄清它的参数matches,对应init_IRQ中就是__irqchip_of_table,下面我们来看下__irqchip_of_table的来历:
<include/linux/of.h>
#if defined(CONFIG_OF) && !defined(MODULE)
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section("__" #table "_of_table") \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__attribute__((unused)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#endif
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
此处定义了CONFIG_OF而未定义MODULE, OF_DECLARE宏声明了struct of_device_id 的结构体变量__of_table##name,它存放在__#table_of_talbe的section中
<include/linux/irqchip.h>
/*
* This macro must be used by the different irqchip drivers to declare
* the association between their DT compatible string and their
* initialization function.
*
* @name: name that must be unique across all IRQCHIP_DECLARE of the
* same file.
* @compstr: compatible string of the irqchip driver
* @fn: initialization function
*/
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
根据前面_OF_DECLARE和OF_DECLARE_2的宏定义,IRQCHIP_DECLARE实际声明并定义了struct of_device_id 的结构体变量__of_table_##name,将它存放在__irqchip_of_talbe的section中。
对于每个中断控制器,都会通过IRQCHIP_DECLARE静态声明自己的struct of_device_id 的结构体变量__of_table_##name,并将其加入_irqchip_of_talbe的section中。
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
1
如上为cortex_a15的声明,它的初始化函数为gic_of_init,至此如上语句等价为:
static const struct of_device_id __of_table_cortex_a15_gic \
__used __section("__irqchip_of_table") \
= { .compatible = "arm,cortex-a15-gic", \
.data = gic_of_init}
而不同的中断控制器都将有对应的IRQCHIP_DECLARE声明,它们都位于__irqchip_of_table段,of_irq_init就是遍历__irqchip_of_table段中所有的中断控制器,对其执行相应的中断控制器初始化。
void __init of_irq_init(const struct of_device_id *matches)
|--const struct of_device_id *match;
| struct of_intc_desc *desc
| //初始化两个list,分别为dts节点中int-controller属性的list和int-controller-parent属性的list
|--INIT_LIST_HEAD(&intc_desc_list);
|--INIT_LIST_HEAD(&intc_parent_list);
| //遍历每个中断控制器,为其创建intc_desc, 并设置每个中断控制器的parent
|--for_each_matching_node_and_match(np, matches, &match)
| //如果当前节点不是interrupt-controller或者处于disabled状态,则继续遍历
| if (!of_find_property(np, "interrupt-controller", NULL) || !of_device_is_available(np))
| continue;
| //如果当前节点是interrupt-controller,分配一个struct of_intc_desc的结构体并初始化
| desc = kzalloc(sizeof(*desc), GFP_KERNEL);
| //中断控制器初始化回调,match->data就是上面分析的__of_table_cortex_a15_gic.data
| desc->irq_init_cb = match->data;
| //解析当前node的interrupt-parent
| desc->interrupt_parent = of_irq_find_parent(np);
| //如果是根中断控制器, 将interrupt_parent设为NULL
| if (desc->interrupt_parent == np)
| desc->interrupt_parent = NULL;
| //将该中断控制器加入intc_desc_list中
| list_add_tail(&desc->list, &intc_desc_list)
|--while (!list_empty(&intc_desc_list)) //遍历并初始化所有的中断控制器
| //遍历中断控制器对其进行初始化,第一次找到的是root中断控制器
| list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list)
| //找到具有父节点的中断控制器,第一次parent为NULL,因此找到的是root中断控制器,如GIC
| if (desc->interrupt_parent != parent)
| continue;
| list_del(&desc->list);
| of_node_set_flag(desc->dev, OF_POPULATED);
| //执行中断控制器的初始化函数,对root控制器,如GIC的gic_of_init
| ret = desc->irq_init_cb(desc->dev, desc->interrupt_parent);
| //将所有具有儿子的中断控制器加入intc_parent_list链表
| list_add_tail(&desc->list, &intc_parent_list);
| desc = list_first_entry_or_null(&intc_parent_list,typeof(*desc), list);
| list_del(&desc->list);
| //更新parent为下一级中断控制器
| parent = desc->dev;
| kfree(desc);
of_irq_init就是执行了各个注册的中断控制器的初始化函数,下面说明gic中断控制器的初始化函数gic_of_init
|- - -gic_of_init
<drivers/irqchip/irq-gic.c>
gic_of_init(struct device_node *node, struct device_node *parent)
|--struct gic_chip_data *gic;
| gic = &gic_data[gic_cnt];//gic_cnt为0
|--gic_of_setup(gic, node);//根据device node初始化gic
|--__gic_init_bases(gic, &node->fwnode)
| |--for (i = 0; i < NR_GIC_CPU_IF; i++)
| | gic_cpu_map[i] = 0xff;
| | //设置中断顶层处理函数handle_arch_irq为gic_handle_irq,此为中断处理的主函数
| |--set_handle_irq(gic_handle_irq);
| | //进一步初始化gic->irq_chip
| |--gic_init_chip(gic, NULL, name, true);
| |--gic_init_bases(gic, handle)
| | //初始化distributor和cpu interface的物理基地址
| |--gic->dist_base.common_base = gic->raw_dist_base;
| |--gic->cpu_base.common_base = gic->raw_cpu_base;
| | //初始化硬中断数目
| |--gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
| | gic_irqs = (gic_irqs + 1) * 32;
| | gic->gic_irqs = gic_irqs;
| |--if (handle) //DT/ACPI
| | gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);
| | else //Legacy support
| | irq_base = irq_alloc_descs(16, 16, gic_irqs,numa_node_id());
| | gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,16, &gic_irq_domain_ops, gic);
| |--gic_dist_init(gic);//gic的distributor初始化
| |--gic_cpu_init(gic);//gic的cpu interface初始化
| |--gic_pm_init(gic);
|--gic_init_physaddr(node);
|--gic_of_setup_kvm_info(node);
|--if (parent)
| irq = irq_of_parse_and_map(node, 0);
| gic_cascade_irq(gic_cnt, irq);
|--if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
| gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);
\--gic_cnt++
gic_of_init主要完成gic中断控制器的初始化
gic_of_setup:根据device node初始化gic,包括gic->raw_dist_base,gic->raw_cpu_base,gic->percpu_offset等;
__gic_init_bases:初始化全局的中断顶层处理函数handle_arch_irq,初始化中断控制器硬件相关信息gic->irq_chip,创建并注册irq_domain,其中gic->domain->ops初始化为gic_irq_domain_hierarchy_ops
static inline struct irq_domain *irq_domain_create_linear(fwnode,size, ops, host_data)
| //Allocate a new irq_domain data structure
|--__irq_domain_add(fwnode, size, size, 0, ops, host_data)
|--struct irq_domain *domain;
| //注意此处除分配irq_domain外,还分配了(sizeof(unsigned int) * size用于linear_revmap[]成员
|--domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
| GFP_KERNEL, of_node_to_nid(to_of_node(fwnode)));
| // 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;
list_add(&domain->link, &irq_domain_list);
关于__irq_domain_add的注释如上,它分配了一个新的irq_domain并连入全局irq_domain_list链表,从而完成了注册。
参考文档
奔跑吧,Linux内核
————————————————
版权声明:本文为CSDN博主「HZero.chen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jasonactions/article/details/115541386