ARM64 ハイパーバイザー: 割り込みの仮想化
ハイパーバイザー (EL2) の実装に必要な ARM64 の割り込みの仮想化についてまとめています。
ハードウェアによる仮想化支援
ハイパーバイザー上で動作するvCPU(ゲストOS, VM)でCPUの特定の命令が実行されたり、特定領域のメモリアクセス、割り込み等をハイパーバイザー側でトラップ(検出して必要があれば特定の処理を行い、結果を返すなどのエミュレーション)する必要があります。
ARMの場合、ARMv7-A
からハードウェアでこの仕組みが拡張機能としてサポートされています。
割り込み仮想化の概要
割り込みの仮想化に関しては、大きく分けて2種類の対応方法があります。
1つは、割り込みコントローラー自体をハイパーバイザー側で仮想化(ソフトウェアエミュレーション)する方法です。
もう一つは、ARM 標準でかつほぼデファクトスタンダードとして利用されているGIC
(ARM Generic Interrupt Controller) の仮想化の拡張機能を利用する方法です。
今回は、例外はあるもののARM64を搭載しているシステムで最も一般的な割り込みコントローラであるGIC
の方について説明します。さらに今回は、ラズパイ4でも利用されている GICv2 (version 2) にフォーカスします。
GIC v2仕様書
割り込みハードウェア
以下の図が割り込み関連のブロック図です。GICブロック内のDistributor
やCPU interface
といった名前はGICv2仕様書の中で出てくるサブブロックの名称ですが、仮想化サポートのため通常の割り込み系統とは別で、vCPU (EL1のゲストOS) 用にCPUから仮装割り込みのvIRQ
, vFIQ
を生成できるようになっています。
ハイパーバイザー (EL2) では、(EL1向けの)割り込みを一旦全て EL2 にルーティングするように設定し、必要に応じてを EL1 に仮想割り込みを発行することになります。vIRQ
およびvFIQ
はEL1から見ると通常のIRQ
, FIQ
と同じに見えます。
※FIQのソースはシステム(SoC)によって実装が異なります(例. ウォッチドックが発火した場合に緊急処理するための割り込み信号がつながっているなど)
処理フロー
システムブート時の初期設定から実際の割り込み発生時の処理までのざっくりとした処理フローを説明します。なお、レジスタ設定に依存関係がない場合は多少処理が前後しても問題ありません。
- EL2 割り込みベクターテーブルの設定
- GIC Distributor (GICD_*レジスタ) の設定
- GIC CPU interface (GICC_*レジスタ) の設定
- GIC Virtual interface control (GICH_*レジスタ) の設定
- 該当割り込みの有効化と割り込みハンドラの設定
- 外部割り込み発生
- vIRQ発行のための設定 (GICH_LR[n]レジスタ)
- 割り込みクリア
- EL2 から 該当の EL1 (vCPU) にモード遷移(ここで vIRQ が割り込み発生)
1) 割り込みベクターテーブルの設定
EL毎 (EL3, EL2, EL1) に割り込みベクタテーブルを設定することが可能です。ハイパーバイザーの場合、まずは VBAR_EL2
レジスタに割り込みベクタテーブルの先頭アドレスを設定します。
.globl irq_init_irq_vector_table
irq_init_irq_vector_table:
adr x0, vector_table_el2 // これが別途定義している割り込みベクタの先頭アドレス
msr vbar_el2, x0
ret
ベクターテーブルのメモリフォーマット(サイズは0x800
バイト)は以下です(0x800バイトアライメントなので定義するときに注意)。
Current EL with SP0 / Current EL with SPx
CUrrent EL
と書かれている通り、現在と同じEL (ハイパーバイザの場合はEL2) で発生した割り込みを示します。
SP0
とSPx
の違いは、Nested IRQかどうかです。つまり、スタックの切り替えが必要かどうかです。ということで、Nested IRQをしない限りはSP0
のやつは使う必要がないはずです。
Lower EL using AArch64 / Lower EL using AArch32
EL1/EL0向けの割り込みがトラップした場合にコールされます。ここで必要な処理を行い、その後でLE1/EL0向けに仮想割り込みを発行することになります。ゲストの動作モード(64 or 32ビット) ごとに別々のベクタを設定することが可能になっています。
参照元: https://developer.arm.com/documentation/100933/0100/AArch64-exception-vector-table
2) EL1からEL2にルーティング
HCR_EL2
レジスタの4ビット目のIMO
に1を設定し、割り込みをEL2側にルーティングします。
.globl irq_route_irq_el2
irq_route_irq_el2:
mrs x0, hcr_el2
orr x1, x0, #0x10
msr hcr_el2, x1
ret
HCR_EL2
レジスタの詳細についてはこちらもどうぞ。
3) GICの設定
ここから割り込みコントローラのGICの設定を行なっていきます。レジスタはサブブロック毎に以下の4種類があります。
- GICD_*: Distributor
- GICC_*: CPU interface
- GICH_*: Hypervisor
- GICV_*: vCPU interface
MMU Stage2を利用した仮想化
ゲストOS側には何も意識させずに通常通りのGICを見せてあげる必要がある(見かけ上)ため、GICの仮想化が必要です。ここでは詳細は説明しませんが(別途、別の記事で)、MMUのStage2の機能を利用して対応が必要です。
まず、GICD_*レジスタは、ゲストOSにはアクセス禁止にして、ハイパーバイザー側でトラップして、ゲスト毎にレジスタ制御をエミュレートしてあげる必要があります。
次に、GICC_*レジスタは、ゲストOS側にはそれらの代わりにメモリ空間を少しシフトさせてGICV_*レジスタを見せてあげる必要があります。
初期設定
GICD_CTLR
レジスタに1を設定し、Distributorを有効化します。同様にGICC_CTLR
レジスタに1を設定し、CPU interfaceブロックを有効化します。
対象ハードウェアの割り込みを有効化
対象の割り込みIDを有効化、優先度および割り込み通知先のCPUコアを設定します。
どのハードウェアが割り込みID何番なのかというのはチップ毎に異なるため、ユーザマニュアル等から調べる必要があります。目的の割り込みIDが分かったら、GICD_ISENABLERn
(nは複数個ある。例えば0から31まで。割り込みID個数分存在する) の対象ビットに設定します。LSB側から順番にID=0, ID=1, ..., ID=31のように対応しているため、以下のようにmodを取る感じで設定することが可能です。
GICD_ISENABLER[id / 32] |= 1 << (id % 32);
次に、割り込みの優先度をGICD_IPRIORITYRn
レジスタに設定します。1つの割り込みあたり、0-255(8ビット)を設定することが可能で、値が小さいほど優先度が高いことを示します。ビット割り当てもLSB側からID=0となり、GICD_ISENABLERn
と同じ感じです(1IDあたり8ビットですが)。
最後に、割り込み通知先のCPUコアを設定していきます。なお、1つの割り込みを複数コアに通知することも可能です。優先度設定同様、1つのIDあたり8ビットがLSB側から割り当てられているため、目的のコア番号(0-255) を設定します。
4) 割り込み発生時の処理
実際割り込みが発生してから、EL2でやることそしてEL1でやることを説明していきます。なお、コンテキストスイッチを考慮して、Lower EL using AArch64
のケースを前提とします。
- EL1からEL2にコンテキストスイッチする(ハイパーバイザーで動く)ため、必要なレジスタ(EL1で動作していた関連するレジスタも全て含む)を全てスタックに保存する
- 割り込み要因を確認
- 必要な処理を行う(ハードウェアのエミュレーション)
- EL1へ
vIRQ/vFIQ
発行 - 割り込みをクリア
- No1.で保存したデータを全て復元し、EL2からEL1にコンテキストスイッチ
-
eret
命令で割り込みベクタ終了
割り込み要因の確認
GICC_IAR
レジスタの下位10ビットが割り込みIDを示しています。
割り込みをクリア
GICC_EOIR
レジスタに先ほどリードしたであろうGICC_IAR
レジスタの値をそのままライトするだけです。
EL1へvIRQ/vFIQ発行
HCR_EL2
のVI
(7ビット目)、VF
(6ビット目) に1をセットすることで発行することが可能です。HCR_EL2
レジスタの詳細についてはこちらもどうぞ。
.globl assert_virq_el2
assert_virq_el2:
mrs x0, hcr_el2
orr x1, x0, #0x80
msr hcr_el2, x1
ret
Discussion