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関数でqemuMMIOの内容を渡す準備をします。

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に関してはまた機会があれば。。。

*1:使用頻度が高いLAPICには存在するようです

*2:arch/x86/kvm/emulate.cにエミュレーションコードがあります。

*3:B3.13.6 Use of the HSR