PPI中断为外设私有中断,在ARM64上arch_timer为PPI中断。这里以arch_timer为例(代码位置drivers/clocksource/arm_arch_timer.c),作应用实例讲解。
先对ARM64通用定时器作简要介绍。通用定时器为Arm core提供标准定时器。通用定时器包括一个系统counter和一组每core定时器。
其中system counter处于always on的电源域,它提供固定频率的系统计数。它对系统上所有core都是广播的。每个core都有一组timer,它们与system counter进行比较,软件可配置每个timer在将来某个点产生中断或event。如上图,当中断产生时会通过GIC中GICR传递给GICC,最终由本core来完成中断的处理。
在ARM64中如下中断ID(INTID)用于由SBSA定义的每个定时器,如下:
Timer | SBSA推荐的INTID |
EL1 physical timer | 30 |
EL1 virtual timer | 27 |
Non-secure EL2 physical timer | 26 |
Non-secure EL2 virtual timer | 28 |
EL3 physical timer | 29 |
Secure EL2 physical timer | 20 |
Secure EL2 virtual timer | 19 |
当支持Timer虚拟化时,Non-secure EL2 physical timer用于hypervisor上arch_timer,EL1 virtual timer用于guest上arch_timer。
这里以这种情况进行介绍。
在ACPI情况下,TIMER是在GTDT表中定义的。
[GTDT表介绍可自行查看协议]
ARM系统的timer定义为TIMER_ACPI_DECLARE(arch_timer, ACPI_SIG_GTDT, arch_timer_acpi_init)。这里分析在hypervisor上的情况。
- 通过函数acpi_gtdt_init()获取GTDT表中定义的定时器数目;
- 从GTDT表中获取HYP中断号,并通过函数acpi_register_gsi()注册中断;
- 从GTDT表中获取非安全EL1 physical中断号,并通过函数acpi_register_gsi()注册中断;
- 从GTDT表中获取EL1 virtual中断号,并通过函数acpi_register_gsi()注册中断;
- 将EL1 physical中断号和EL1 virtual中断号传递给KVM用;
- 读取系统计数器频率;
- 在Host上选择HYP中断为arch_timer,注意在guest上会选择EL1 virtual中断为arch_timer;
- 检查定时器是否处于always on电源域;
- 在host上注册HYP中断对应的中断处理函数arch_timer_handler_phys,在guest上注册virtual中断对应的中断处理函数arch_timer_handler_virt;
- 调用arch_timer_common_init()将arch_timer注册到clocksource和sched_clock中。
无论是arch_timer_handler_phys()还是arch_timer_handler_virt(),最终会调用用户注册的定时器中断处理函数。
对于arch_timer_common_init(),调用如下:
- 若在guest上为VIRT timer情况,获取计数接口为arch_counter_get_cntvct(),通过vtimer对应的寄存器获取计数;
- 若在host上为ptimer情况,获取计数接口为arch_counter_get_cntpct(),通过ptimer对应的寄存器获取计数;
- 以当前时钟频率注册clocksource;
- 采用上述步骤(1)中的计数接口来注册sched clock。