KVM: Fix host oops due to guest changing efer

If the guest changes efer from long mode with sce disabled to legacy mode,
then load_transition_efer() zeros vmx->host_state.guest_efer_loaded, but
the SCE-disabled efer remains in effect. So when we return to the host,
we disable SCE and syscalls no longer work.

Fix by (a) not touching vmx->host_state.guest_efer_loaded if we're not
setting it, and instead (b) clearing it explicitly when we switch back.
Also switch back when the guest writes to efer so we start from a clean
slate.

Signed-off-by: Avi Kivity <avi@qumranet.com>
diff --git a/drivers/kvm/vmx.c b/drivers/kvm/vmx.c
index 7f168ad..c342981 100644
--- a/drivers/kvm/vmx.c
+++ b/drivers/kvm/vmx.c
@@ -354,17 +354,24 @@
 	if (guest_efer & EFER_LMA)
 		ignore_bits &= ~(u64)EFER_SCE;
 #endif
-	vmx->host_state.guest_efer_loaded
-		= (guest_efer & ~ignore_bits) != (host_efer & ~ignore_bits);
-
-	if (!vmx->host_state.guest_efer_loaded)
+	if ((guest_efer & ~ignore_bits) == (host_efer & ~ignore_bits))
 		return;
+
+	vmx->host_state.guest_efer_loaded = 1;
 	guest_efer &= ~ignore_bits;
 	guest_efer |= host_efer & ignore_bits;
 	wrmsrl(MSR_EFER, guest_efer);
 	vmx->vcpu.stat.efer_reload++;
 }
 
+static void reload_host_efer(struct vcpu_vmx *vmx)
+{
+	if (vmx->host_state.guest_efer_loaded) {
+		vmx->host_state.guest_efer_loaded = 0;
+		load_msrs(vmx->host_msrs + vmx->msr_offset_efer, 1);
+	}
+}
+
 static void vmx_save_host_state(struct kvm_vcpu *vcpu)
 {
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
@@ -439,8 +446,7 @@
 	reload_tss();
 	save_msrs(vmx->guest_msrs, vmx->save_nmsrs);
 	load_msrs(vmx->host_msrs, vmx->save_nmsrs);
-	if (vmx->host_state.guest_efer_loaded)
-		load_msrs(vmx->host_msrs + vmx->msr_offset_efer, 1);
+	reload_host_efer(vmx);
 }
 
 /*
@@ -728,8 +734,10 @@
 #ifdef CONFIG_X86_64
 	case MSR_EFER:
 		ret = kvm_set_msr_common(vcpu, msr_index, data);
-		if (vmx->host_state.loaded)
+		if (vmx->host_state.loaded) {
+			reload_host_efer(vmx);
 			load_transition_efer(vmx);
+		}
 		break;
 	case MSR_FS_BASE:
 		vmcs_writel(GUEST_FS_BASE, data);