4.2.4 根据DTS完成timer初始化
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!
4.2.4 根据DTS完成timer初始化
4.2.4.1 初始化入口arch_timer_of_init
这一章节来分析timer设备的初始化,它依赖于device tree,也可以说它“消费”device tree。device tree的初始化,参考《4.2.2 Linux解析DTS/DTB流程》。
内核实现的套路和《4.2.3 根据DTS完成中断控制器初始化》基本一样,那就仿写一下。
Arch timer时钟源驱动drivers/clocksource/arm_arch_timer.c中,定义了时钟初始化入口:TIMER_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_of_init)。
这个宏其实就是初始化了一个静态常量:struct of_device_id __of_table_armv8_arch_timer。
//本质是定义struct of_device_id __of_table_armv8_arch_timer
TIMER_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_of_init);#define TIMER_OF_DECLARE(name, compat, fn) \OF_DECLARE_1_RET(timer, name, compat, fn)#define OF_DECLARE_1_RET(table, name, compat, fn) \_OF_DECLARE(table, name, compat, fn, of_init_fn_1_ret)#define _OF_DECLARE(table, name, compat, fn, fn_type) \static const struct of_device_id __of_table_##name \__used __section(__##table##_of_table) \__aligned(__alignof__(struct of_device_id)) \= { .compatible = compat, \.data = (fn == (fn_type)NULL) ? fn : fn }
根据_OF_DECLARE,struct of_device_id __of_table_armv8_arch_timer被指定放在了__timer_of_table段(section)中。这个段定义arch/arm64/kernel/vmlinux.lds中,在编译Linux的过程中,变量__of_table_armv8_arch_timer会被放在___timer_of_table段(section)中。
TIMER_OF_DECLARE(armv8_arch_timer, "arm,armv8-timer", arch_timer_of_init)最终展开为:
static const struct of_device_id __of_table_armv8_arch_timer __used __section(___timer_of_table) = {
.compatible = "arm,armv8-timer",
.data = arch_timer_of_init
}
timer_probe从__timer_of_table中找出变量__of_table_armv8_arch_timer,根据.compatible = "arm,armv8-timer"成员,遍历struct device_node设备树,找到匹配的struct device_node节点。调用arch_timer_of_init (.data = arch_timer_of_init)进行arch timer的初始化。
device tree中是否有匹配的节点?以QEMU virt为例,图中的DTS中的timer节点,完全符合上述分析。
这是arch timer初始化运行的调用堆栈,与上述分析一致。
找到了初始化的入口,接下来分析arch timer的Linux virq与hwirq的映射关系是如何建立的。
4.2.4.2 建立Linux virq与hwirq的映射关系-上半部分
arch_timer_of_init循环调用irq_of_parse_and_map,依次把DTS中timer节点定义的4个物理中断号,都转换成Linux virq存到数组arch_timer_ppi中。
irq_of_parse_and_map首先调用of_irq_parse_one从DTS timer节点得到中断的属性值存入struct of_handle_args oirq结构体变量中。例如对于ARCH_TIMER_VIRT_PPI,解析出来的3个参数如下图,与DTS timer节点中的定义完全相同。其中oriq.args[0]是1,代表是PPI中断;oriq.args[1]等于11,就是0xb,代表PPI中断号;oriq.args[2]等于4,代表中断属性。注意oriq.args[1]等于11,但是11只是PPI中断号,并不是GIC V3的硬件中断号,后面irq_of_parse_and_map会调用irq_create_of_mapping(&oirq)继续转换。
irq_create_of_mapping(&oirq)调用of_phandle_args_to_fwspec函数,把irq_data(传入struct of_handle_args oirq)转换为中断专用的数据结构变量struct irq_fwspec fwspec,3个数值保持一致。irq_create_of_mapping(&oirq)继续调用irq_create_fwspec_mapping(&fwspec)完成映射。
irq_create_fwspec_mapping(&fwspec)首先通过fwspec找到irq_domain,然后调用irq_domain_translate(domain, fwspec, &hwirq, &type)。irq_domain_translate通过调用domain->ops->translate回调函数,实际调用了GIC V3中断的gic_irq_domain_translate函数。根据fwspec->param[0],判断是PPI中断,把fwspec->param[1] + 16得到27存入*hwirq。
小结一下,irq_create_of_mapping-> irq_create_fwspec_mapping->irq_domain_translate返回后,变量hwirq里面存储的就是DTS PPI 11对应的硬件中断号27。既然确定了hwirq,那它对应的Linux virq是多少?接下来调用irq_find_mapping(domain, hwirq),在irq_domain中寻找此hwirq(27)是否已经分配过对应的Linux irq。显然是没有的,所以irq_find_mapping(domain, hwirq)一定返回0。
接下来irq_create_fwspec_mapping中,if (irq_domain_is_hierarchy(domain))判断为真,走到irq_domain_alloc_irqs->__irq_domain_alloc_irqs,分配Linux virq并且与hwirq建立映射关系。注意,irq_domain_alloc_irqs传入的第4个参数是fwspec,而不是已经算出的hwirq。估计后面还会再重新计算一次,拭目以待。
4.2.4.3 建立Linux virq与hwirq的映射关系-下半部分
__irq_domain_alloc_irqs分为如下几步,且出现了几个新的和中断相关的数据结构,这里一一分析。
第一步,irq_domain_alloc_descs调用__irq_alloc_descs分配virq及对应的irq_desc数据结构。保留其中最核心的两个函数调用,来展开分析。
int __ref
__irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,struct module *owner, const struct cpumask *affinity)
{start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,from, cnt, 0);ret = alloc_descs(start, cnt, node, affinity, owner);
}
1. bitmap_find_next_zero_area
这个地方,清晰的说明逻辑中断号virq是通过位图变量allocated_irqs按照先申请先得的规则分配的。allocated_irqs和IRQ_BITMAP_BITS的分析,详见《3.4.1.2 IPIPE对Linux中断号的改造》。
2. alloc_descs
说到irq_desc数据结构,内核提供了两种管理方式。一种是静态数组struct irq_desc irq_desc[NR_IRQS],一种是基数树static RADIX_TREE(irq_desc_tree, GFP_KERNEL)。
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);#define RADIX_TREE(name, mask) \struct radix_tree_root name = RADIX_TREE_INIT(name, mask)struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {[0 ... NR_IRQS-1] = {.handle_irq = handle_bad_irq,.depth = 1,.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),}
};
对于静态数组,struct irq_desc相当于编译的时候就已经分配了,逻辑比较简单,只要根据virq来索引即可。对于基数树,需要针对virq进行对应struct irq_desc进行申请和释放。当前默认是CONFIG_SPARSE_IRQ=y,使用基数树。alloc_descs的最重要的主体为:
static int alloc_descs(unsigned int start, unsigned int cnt, int node,const struct cpumask *affinity, struct module *owner)
{desc = alloc_desc(start + i, node, flags, mask, owner);irq_insert_desc(start + i, desc);
}
调用alloc_desc申请struct irq_desc。每个virq都拥有一个struct irq_desc与其对应,且struct irq_desc包含一个struct irq_data结构体。
alloc_descs->alloc_desc-> desc_set_defaults会把virq保存起来:desc->irq_data.irq = irq。注意,irq_data.hwirq此时还没有初始化哦,所以已知virq,是找不到hwirq的。
以virq为索引值,调用irq_insert_desc插入基数树irq_desc_tree。
第二步,irq_domain_alloc_irq_data初始化irq_data
特别注意,struct irq_data实体是直接定义在struct irq_desc中,所以在上一步分配struct irq_desc时,struct irq_data实际上也分配了。这里最重要的是给irq_data->domain赋值,让struct irq_desc通过irq_data与domain关联起来。
第三步,irq_domain_alloc_irqs_hierarchy确定virq和hwirq的关系。
irq_domain_alloc_irqs_hierarchy通过钩子domain->ops->alloc调用了gic_irq_domain_alloc,注意传入的第4个参数不是上面已经计算出来的hwirq,反而是fwspec。所以,在最终到达gic_irq_domain_alloc时。
1. 调用gic_irq_domain_translate重新计算hwirq。
2. 调用gic_irq_domain_map完成virq到hwirq的映射关系:irq_data->hwirq = hwirq。至此之后,通过virq可以找到irq_desc.irq_data.hwirq。函数irq_get_irq_data可以方便的从virq找到irq_data。
第四步,调用irq_domain_insert_irq->irq_domain_set_mapping,完成hwirq到virq的反向映射。
对于GIC V3来说,创建domain的调用过程:irq_domain_create_tree->__irq_domain_add(fwnode, 0, ~0, 0, ops, host_data),其中第2个参数为domain->revmap_size赋值,即0。
所以,在irq_domain_set_mapping中,通过radix_tree_insert,以hwirq为索引,向domain->revmap_tree插入irq_data。至此之后,通过hwirq可以找到virq即irq_data.irq。内核封装了irq_find_mapping函数来找出hwirq对应的virq。
总结一下:
- Linux virq是通过位图变量allocated_irqs按照先申请先得的规则分配的。
- 每个virq都拥有一个struct irq_desc与其对应,且struct irq_desc包含一个struct irq_data结构体。struct irq_desc通过静态数组或基数树组织起来,索引值为virq。
- struct irq_data结构体中的irq代表virq,在alloc_desc时初始化为virq。
- struct irq_data结构体中的hwirq,会存储硬件中断号。
- 已知hwirq,通过irq_domain的revmap_tree或linear_revmap,可以找到irq_data.irq(virq),可以使用API irq_find_mapping函数。
- 已知virq,通过irq_desc.irq_data.hwirq快速找到hwirq,可以使用API irq_get_irq_data.
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-CSDN博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!