KVM/ARMにおけるMMIOハンドリング
XenがHYPモードで動いているか知りたくてHardware accelerated Virtualization in the ARM Cortex〓 Processors [pdf]を読んでいると、スライドの中に
- “Syndrome” information on aborts available for some loads/stores
- Syndrome unpacks key information about the instruction
- Source/Destination register, Size of data transfer, Size of the instruction, SignExtension etc
という記述がありました。どうやらARMの仮想化支援機能にはゲストMMIOのエミュレーションを支援する(当該命令をデコードしてくれる)機能があるようです。
ご存知の通りIntel VT-xやAMD-Vにはこのデコードしてくれる機能がありません*1。そのため、MMIOによるVM exitが起きた際に、ゲストのEIPが指す命令をVMMがデコードしてやらないといけません*2。ARMではこのデコードをハードウェアがやってくれるようです。(エミュレーション自体はソフトウェアがやる必要があります。) 確かにRISCの命令セットならば、ハードウェアでデコードするのも難しくはなさそうです。
いつものリファレンスマニュアルを参照してみると、HYPモードへトラップした例外原因がCP15のHSR(Hyp Syndrome Register)に書き込まれているようです*3。HSR.ECに例外原因が書き込まれ、HSR.ISSに個々の原因毎の詳細情報がエンコードされているようです。MMIOの場合はData Abortが例外要因となります。
Data AbortのときのHSR.ISSのエンコーディングを少し書きだしてみると
- ISS[23:22]: アクセス幅(1バイト or 2バイト or 4バイト)
- ISS[21]: 符号拡張が必要か否か
- ISS[19:16]: レジスタ番号(ロード命令ならデータ書き込み先、ストア命令ならデータ読み込み元)
- ISS[6]: 読み書きのどちらか?
といった感じです。
ではKVM/ARMのコードを見ていきましょう。
まずゲストでData Abortが起きてHYPモードにトラップされたときに、どのハンドラが呼ばれることになるかですが、arm_exit_handlersに例外要因毎にハンドラが用意されています。
static exit_handle_fn arm_exit_handlers[] = {
[HSR_EC_WFI] = kvm_handle_wfi,
[HSR_EC_CP15_32] = kvm_handle_cp15_32,
[HSR_EC_CP15_64] = kvm_handle_cp15_64,
[HSR_EC_CP14_MR] = kvm_handle_cp14_access,
[HSR_EC_CP14_LS] = kvm_handle_cp14_load_store,
[HSR_EC_CP14_64] = kvm_handle_cp14_access,
[HSR_EC_CP_0_13] = kvm_handle_cp_0_13_access,
[HSR_EC_CP10_ID] = kvm_handle_cp10_id,
[HSR_EC_SVC_HYP] = handle_svc_hyp,
[HSR_EC_HVC] = handle_hvc,
[HSR_EC_SMC] = handle_smc,
[HSR_EC_IABT] = kvm_handle_guest_abort,
[HSR_EC_IABT_HYP] = handle_pabt_hyp,
[HSR_EC_DABT] = kvm_handle_guest_abort,
[HSR_EC_DABT_HYP] = handle_dabt_hyp,
};
Data Abortのときは kvm_handle_guest_abort関数が呼ばれます。同関数内では、MMIOは最終的に以下の箇所にたどり着きio_mem_abort関数が呼ばれます。
/* * The IPA is reported as [MAX:12], so we need to * complement it with the bottom 12 bits from the * faulting VA. This is always 12 bits, irrespective * of the page size. */ fault_ipa |= kvm_vcpu_get_hfar(vcpu) & ((1 << 12) - 1); ret = io_mem_abort(vcpu, run, fault_ipa); goto out_unlock;
io_mem_abortでは、decode_hsr関数でHSRを読んでMMIOの処理内容がデコードされ、VGIC(仮想割り込みコントローラ)に対するMMIOの場合はvgic_handle_mmioで処理され、そうでなければkvm_prepare_mmio関数でqemuにMMIOの内容を渡す準備をします。
decode_hsrは以下のようなkvm_vcpu_dabt_*関数を使ってHSRをデコードするだけで、命令解析はしていません。
static inline bool kvm_vcpu_dabt_iswrite(struct kvm_vcpu *vcpu) { return kvm_vcpu_get_hsr(vcpu) & HSR_WNR; } static inline bool kvm_vcpu_dabt_issext(struct kvm_vcpu *vcpu) { return kvm_vcpu_get_hsr(vcpu) & HSR_SSE; } static inline int kvm_vcpu_dabt_get_rd(struct kvm_vcpu *vcpu) { return (kvm_vcpu_get_hsr(vcpu) & HSR_SRT_MASK) >> HSR_SRT_SHIFT; } static inline bool kvm_vcpu_dabt_isextabt(struct kvm_vcpu *vcpu) { return kvm_vcpu_get_hsr(vcpu) & HSR_DABT_EA; } static inline bool kvm_vcpu_dabt_iss1tw(struct kvm_vcpu *vcpu) { return kvm_vcpu_get_hsr(vcpu) & HSR_DABT_S1PTW; } /* Get Access Size from a data abort */ static inline int kvm_vcpu_dabt_get_as(struct kvm_vcpu *vcpu) { switch ((kvm_vcpu_get_hsr(vcpu) >> 22) & 0x3) { case 0: return 1; case 1: return 2; case 2: return 4; default: kvm_err("Hardware is weird: SAS 0b11 is reserved\n"); return -EFAULT; } } /* This one is not specific to Data Abort */ static inline bool kvm_vcpu_trap_il_is32bit(struct kvm_vcpu *vcpu) { return kvm_vcpu_get_hsr(vcpu) & HSR_IL; }
というわけで、ARM/KVMの場合は仮想化拡張機能のおかげでゲストMMIOのハンドリングはx86(VT-x/AMD-V)と比べて簡単なことがわかりました。
VGICに関してはまた機会があれば。。。