[Linux] Arm Generic Timer と arm_arch_timer ドライバ

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 レジスタで行います。

参考


コメント

タイトルとURLをコピーしました