KVM/ARMにおける割り込み制御

LWNのSupporting KVM on the ARM architectureという記事の中で、ゲスト実行中にハードウェア割り込みが起きた時は、一度HYPモードにトラップした後、SVCモードへ移行、割り込み禁止を解除することでもう一度SVCモードで割り込みを受け取る、という記述があったので調べてみました。

KVMな人にはお馴染みのkvm_arch_vcpu_ioctl_run関数の中の、ゲストから復帰した直後に以下のようなコメントがあります。

		/**************************************************************
		 * Enter the guest
		 */
		trace_kvm_entry(*vcpu_pc(vcpu));
		kvm_guest_enter();
		vcpu->mode = IN_GUEST_MODE;

		ret = kvm_call_hyp(__kvm_vcpu_run, vcpu);

		vcpu->mode = OUTSIDE_GUEST_MODE;
		vcpu->arch.last_pcpu = smp_processor_id();
		kvm_guest_exit();
		trace_kvm_exit(*vcpu_pc(vcpu));
		/*
		 * We may have taken a host interrupt in HYP mode (ie
		 * while executing the guest). This interrupt is still
		 * pending, as we haven't serviced it yet!
		 *
		 * We're now back in SVC mode, with interrupts
		 * disabled.  Enabling the interrupts now will have
		 * the effect of taking the interrupt again, in SVC
		 * mode this time.
		 */
		local_irq_enable();

		/*
		 * Back from guest
		 *************************************************************/

確かに、ゲストで受け取った割り込みを再度ホストで発生させていることがわかります。

...と、これだけでは面白くないので、KVM/ARMにおける割り込み制御について調べてみました。(注意:説明を簡単にするためにセキュリティ拡張については省略しています。実際はもっと複雑です。)

上記の動作で不明なのは、ゲストでは割り込みが起きるとHYPモードにトラップするのに、ホスト(SVCモード)では普通にホストの割り込みハンドラ(IRQ/FIQモード)が起動する点です。

ARMv7のリファレンスマニュアルを読んでみると*1、CP15のHCRレジスタでHYPモードにトラップするか否かを制御できると書いてあります。HCR.IMOビットが1ならハードウェア割り込み時にHYPモードにトラップするといった具合です。ゲストに入る前にこのビットを設定しているのだと思われます。

実際にLinuxの当該コードを見てみましょう。arch/arm/kvm/interrupts_head.Sにconfigure_hyp_roleというマクロがあります。このマクロはworld switchする前後で呼ばれます(この辺りを参照ください)。ここでHCR.IMOを含むHYPモードへトラップする条件をいろいろ設定しています。

/* Enable/Disable: stage-2 trans., trap interrupts, trap wfi, trap smc */
.macro configure_hyp_role operation
        mrc     p15, 4, r2, c1, c1, 0   @ HCR  /* HCRレジスタの内容を読み込む */
        bic     r2, r2, #HCR_VIRT_EXCP_MASK  /* 仮想割り込み用ビットをクリアする */
        ldr     r3, =HCR_GUEST_MASK  /* マスクを読み込む(マスクについては下記参照) */
        .if \operation == vmentry
        orr     r2, r2, r3  /* マスクのビットをすべて1にする */
        ldr     r3, [vcpu, #VCPU_IRQ_LINES]  /* vcpu構造体に用意してあった仮想割り込み用ビットを読み込む(下記参照) */
        orr     r2, r2, r3  /* 仮想割り込み用ビットを設定する */
        .else
        bic     r2, r2, r3  /* マスクをすべてクリアする(ホスト実行時にHYPモードへトラップしないように) */
        .endif
        mcr     p15, 4, r2, c1, c1, 0  /* HCRに更新した値を書き込む */
.endm

HCR_VIRT_EXCP_MASKとHCR_GUEST_MASKはarch/arm/include/asm/kvm_arm.hにあります。確かにIMOが1に設定されてそうですね。

/*
 * The bits we set in HCR:
 * TAC:         Trap ACTLR
 * TSC:         Trap SMC
 * TSW:         Trap cache operations by set/way
 * TWI:         Trap WFI
 * TIDCP:       Trap L2CTLR/L2ECTLR
 * BSU_IS:      Upgrade barriers to the inner shareable domain
 * FB:          Force broadcast of all maintainance operations
 * AMO:         Override CPSR.A and enable signaling with VA
 * IMO:         Override CPSR.I and enable signaling with VI
 * FMO:         Override CPSR.F and enable signaling with VF
 * SWIO:        Turn set/way invalidates into set/way clean+invalidate
 */
#define HCR_GUEST_MASK (HCR_TSC | HCR_TSW | HCR_TWI | HCR_VM | HCR_BSU_IS | \
                        HCR_FB | HCR_TAC | HCR_AMO | HCR_IMO | HCR_FMO | \
                        HCR_SWIO | HCR_TIDCP)
#define HCR_VIRT_EXCP_MASK (HCR_VA | HCR_VI | HCR_VF)

これでゲスト動作時のハードウェア割り込みの制御についてはわかりました。ついでにconfigure_hyp_roleで設定していた、仮想割り込みについても調べてみましょう。

再びリファレンスマニュアルを読んでみると*2、同じくHCRレジスタのHCR.VIとHCR.VFで仮想割り込みを挿入できることがわかります。上記HCR_VIRT_EXCP_MASKでマスクしているビットですね。

上記アセンブリにあったVCPU_IRQ_LINES

  DEFINE(VCPU_IRQ_LINES,        offsetof(struct kvm_vcpu, arch.irq_lines));

と定義されており、kvm_vcpu->arch.irq_linesを参照していることがわかります。 このメンバ変数irq_linesはvcpu_interrupt_line関数で設定されています。この関数の呼び出し元はkvm_vm_ioctl_irq_lineなので、qemuのデバイスエミュレータから仮想割り込みを挿入したい時に使われているのだと思います。

なおリファレンスマニュアルによると、仮想割り込みを挿入してもゲスト側で割り込み禁止にしていた場合*3は、ゲストが割り込みを許可するまで割り込みはペンディングされるようです。Intel VT-xと同じですね。

*1:B1.8.4 Processor mode for taking exceptions

*2:B1.8.11 Virtual exceptions in the Virtualization Extensions

*3:CPSR.Iを1にしているとき