Linux/arm64 では 基準となるタイマに Arm Generic Timer を使用してスケジューラや時刻の管理を行います。このタイマは CPU 内に実装されており、どの SoC でもほぼ共通でこのタイマを使用しています。
Devicetree での設定
devicetree では以下のように記述することで、arm_arch_timer ドライバが probe されタイマ割込みを受けながら kernel 内部でカウンタ処理が行われます。
timer { compatible = "arm,armv8-timer"; interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4)|IRQ_TYPE_LEVEL_HIGH)>, <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4)|IRQ_TYPE_LEVEL_HIGH)>, <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4)|IRQ_TYPE_LEVEL_HIGH)>, <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4)|IRQ_TYPE_LEVEL_HIGH)>; };
改めて説明すると、この interrupt プロパティは 3つの項目からなり、1つ目の"GIC_PPI" は PPI (CPUごとに入る割込み) であることを表し、2つ目は割込み番号、3つ目は linux/irq.h に定義されている割込みフラグ(主に Level/Edge, High/Low など)を示します。
このタイマの制御I/Fは拡張レジスタ上に定義されているため、制御は MRS/MSR 命令で行うことになっており、この記述では reg プロパティはありません。
4つのタイマ
devicetree では割込みを4つ定義しています。arm_arch_timer では状況に合わせて下の表にある4つのタイマのどれか1つを使用します。
Devicetree | PPI# | kernel での表現 | arm資料での表現 |
---|---|---|---|
10 | 26 | hypervisor timer | non-secure EL2 physical timer |
11 | 27 | virtual timer | EL1 virtual timer |
13 | 29 | secure physical timer | EL3 physical timer |
14 | 30 | non-secure physical timer | EL1 physical timer |
hypervisor timer は kernel が EL2 で起動したとき、つまり kernel が KVM などで hypervisor として動作する場合にこのタイマを使用します。kernel が EL2 で起動するのは Armv8.1 に実装される VHE(Virtual Host Extension) が CPU に定義されている場合に限ります。
virtual timer は kernel が EL1 かつ virtual mode で起動したとき、つまり hypervisor の上の guest OS として動作する場合にこのタイマを使用します。
non-secure physical timer は通常起動で EL1 で起動したときにこのタイマを使用します。secure physical timer は aarch64 では使用することはありません。
これらの選択は以下の arch_timer_select_ppi() で行われています(kernel 5.10)。
static enum arch_timer_ppi_nr __init arch_timer_select_ppi(void) { if (is_kernel_in_hyp_mode()) return ARCH_TIMER_HYP_PPI; if (!is_hyp_mode_available() && arch_timer_ppi[ARCH_TIMER_VIRT_PPI]) return ARCH_TIMER_VIRT_PPI; if (IS_ENABLED(CONFIG_ARM64)) return ARCH_TIMER_PHYS_NONSECURE_PPI; return ARCH_TIMER_PHYS_SECURE_PPI; }
ドライバが正しく probe されていれば、各 CPU が起動した後、それぞれの CPU にタイマ割込みが入りスケジューラが動作します。このタイマ割込みが入り続けなければ最初のプロセスである init が起動できず、do_idle() に陥ったままになります(panicにもなりません)。
タイマの起動
この Arm Generic Timer はコアに内蔵されているため、その制御はドライバ内部にて CP15 命令で行いますが、カウントの発信源となるHW部分はコア外部に実装することになっていて、実装依存部分になっています。そのため、発信源の起動は予め TF-A や BIOS などで行う必要があります。
この実装依存のレジスタマップは Arm で定義されていて、CNTControlBase (制御用レジスタ群) と CNTReadBase (参照用レジスタ群) から成り、SoC のメモリマップ上に実装されています。カウント発信源の起動は CNTControlBase 上にある CNTCR レジスタで行います。
参考
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/devicetree/bindings/timer/arm,arch_timer.yaml
- https://developer.arm.com/architectures/learn-the-architecture/generic-timer/single-page
コメント