From mboxrd@z Thu Jan 1 00:00:00 1970 From: "Nadav Har'El" Subject: Re: [PATCH 18/24] Exiting from L2 to L1 Date: Tue, 14 Sep 2010 15:07:50 +0200 Message-ID: <20100914130750.GB26346@fermat.math.technion.ac.il> References: <1276431753-nyh@il.ibm.com> <201006131231.o5DCVlKB013102@rice.haifa.ibm.com> <4C161AB8.4060905@redhat.com> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Cc: kvm@vger.kernel.org To: Avi Kivity Return-path: Received: from mailgw12.technion.ac.il ([132.68.225.12]:28971 "EHLO mailgw12.technion.ac.il" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753596Ab0INNHz (ORCPT ); Tue, 14 Sep 2010 09:07:55 -0400 Content-Disposition: inline In-Reply-To: <4C161AB8.4060905@redhat.com> Sender: kvm-owner@vger.kernel.org List-ID: On Mon, Jun 14, 2010, Avi Kivity wrote about "Re: [PATCH 18/24] Exiting from L2 to L1": > >+int switch_back_vmcs(struct kvm_vcpu *vcpu) > >+{ > IIUC vpids are not exposed to the guest yet? So the VPID should not > change between guest and nested guest. Right. Removed. > > >+ > >+ vmcs_write64(IO_BITMAP_A, src->io_bitmap_a); > >+ vmcs_write64(IO_BITMAP_B, src->io_bitmap_b); > > > > Why change the I/O bitmap? >... > Or the msr bitmap? After all, we're switching the entire vmcs? >... > Why write all these? What could have changed them? You were right - most of these copies were utterly useless, and apparently remained in our code since prehistory (when the same hardware vmcs was reused for both L1 and L2). The great thing is that this removes several dozens of vmwrites from the L2->L1 exit path in one fell swoop. In fact, the whole function switch_back_vmcs() is now gone. Thanks for spotting this! > >+ vmx_set_cr0(vcpu, > >+ (vmx->nested.l1_shadow_vmcs->cr0_guest_host_mask& > >+ vmx->nested.l1_shadow_vmcs->cr0_read_shadow) | > >+ (~vmx->nested.l1_shadow_vmcs->cr0_guest_host_mask& > >+ vmx->nested.l1_shadow_vmcs->guest_cr0)); > > > > Helper wanted. Done. The new helper looks like this: static inline unsigned long guest_readable_cr0(struct vmcs_fields *fields) { return (fields->guest_cr0 & ~fields->cr0_guest_host_mask) | (fields->cr0_read_shadow & fields->cr0_guest_host_mask); } And is used in two places in the code (the above place, and another one). > >+ vmcs_write64(GUEST_PDPTR3, src->guest_pdptr3); > >+ } > > > > A kvm_set_cr3(src->host_cr3) should do all that and more, no? > >+ > >+ vmx_set_cr4(vcpu, vmx->nested.l1_state.cr4); > >+ > > > > Again, the kvm_set_crx() versions have more meat. I have to admit, I still don't understand this part of the code completely. The fact that kvm_set_cr4 does more than vmx_set_cr4 doesn't always mean that we want (or need) to do those things. In particular: > > >+ if (enable_ept) { > >+ vcpu->arch.cr3 = vmx->nested.l1_shadow_vmcs->guest_cr3; > >+ vmcs_write32(GUEST_CR3, > >vmx->nested.l1_shadow_vmcs->guest_cr3); > >+ } else { > >+ kvm_set_cr3(vcpu, vmx->nested.l1_state.cr3); > >+ } > > > > kvm_set_cr3() will load the PDPTRs in the EPT case (correctly in case > the nested guest was able to corrupted the guest's PDPT). kvm_set_cr3 calls vmx_set_cr3 which calls ept_load_pdptrs which assumes that vcpu->arch.pdptrs[] is correct. I am guessing (but am not yet completely sure) that this code tried to avoid assuming that this cache is up-to-date. Again, I still need to better understand this part of the code before I can correct it (because, as the saying goes, "if it ain't broken, don't fix it" - or at least fix it carefully). > >+ kvm_mmu_reset_context(vcpu); > >+ kvm_mmu_load(vcpu); > > > > kvm_mmu_load() unneeded, usually. Again, I'll need to look into this deeper and report back. In the meantime, attached below is the current version of this patch. Thanks, Nadav. Subject: [PATCH 19/26] nVMX: Exiting from L2 to L1 This patch implements nested_vmx_vmexit(), called when the nested L2 guest exits and we want to run its L1 parent and let it handle this exit. Note that this will not necessarily be called on every L2 exit. L0 may decide to handle a particular exit on its own, without L1's involvement; In that case, L0 will handle the exit, and resume running L2, without running L1 and without calling nested_vmx_vmexit(). The logic for deciding whether to handle a particular exit in L1 or in L0, i.e., whether to call nested_vmx_vmexit(), will appear in the next patch. Signed-off-by: Nadav Har'El --- arch/x86/kvm/vmx.c | 242 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 1 deletion(-) --- .before/arch/x86/kvm/vmx.c 2010-09-14 15:02:37.000000000 +0200 +++ .after/arch/x86/kvm/vmx.c 2010-09-14 15:02:37.000000000 +0200 @@ -4970,9 +4970,13 @@ static void vmx_complete_interrupts(stru int type; bool idtv_info_valid; + vmx->exit_reason = vmcs_read32(VM_EXIT_REASON); + + if (vmx->nested.nested_mode) + return; + exit_intr_info = vmcs_read32(VM_EXIT_INTR_INFO); - vmx->exit_reason = vmcs_read32(VM_EXIT_REASON); /* Handle machine checks before interrupts are enabled */ if ((vmx->exit_reason == EXIT_REASON_MCE_DURING_VMENTRY) @@ -5961,6 +5965,242 @@ static int nested_vmx_run(struct kvm_vcp return 1; } +/* + * On a nested exit from L2 to L1, vmcs12.guest_cr0 might not be up-to-date + * because L2 may have changed some cr0 bits directly (see CRO_GUEST_HOST_MASK) + * without L0 trapping the change and updating vmcs12. + * This function returns the value we should put in vmcs12.guest_cr0. It's not + * enough to just return the current (vmcs02) GUEST_CR0. This may not be the + * guest cr0 that L1 thought it was giving its L2 guest - it is possible that + * L1 wished to allow its guest to set a cr0 bit directly, but we (L0) asked + * to trap this change and instead set just the read shadow. If this is the + * case, we need to copy these read-shadow bits back to vmcs12.guest_cr0, where + * L1 believes they already are. + */ +static inline unsigned long +vmcs12_guest_cr0(struct kvm_vcpu *vcpu, struct vmcs_fields *vmcs12) +{ + unsigned long guest_cr0_bits = + vcpu->arch.cr0_guest_owned_bits | vmcs12->cr0_guest_host_mask; + return (vmcs_readl(GUEST_CR0) & guest_cr0_bits) | + (vmcs_readl(CR0_READ_SHADOW) & ~guest_cr0_bits); +} + +static inline unsigned long +vmcs12_guest_cr4(struct kvm_vcpu *vcpu, struct vmcs_fields *vmcs12) +{ + unsigned long guest_cr4_bits = + vcpu->arch.cr4_guest_owned_bits | vmcs12->cr4_guest_host_mask; + return (vmcs_readl(GUEST_CR4) & guest_cr4_bits) | + (vmcs_readl(CR4_READ_SHADOW) & ~guest_cr4_bits); +} + +/* + * prepare_vmcs12 is called when the nested L2 guest exits and we want to + * prepare to run its L1 parent. L1 keeps a vmcs for L2 (vmcs12), and this + * function updates it to reflect the changes to the guest state while L2 was + * running (and perhaps made some exits which were handled directly by L0 + * without going back to L1), and to reflect the exit reason. + * Note that we do not have to copy here all VMCS fields, just those that + * could have changed by the L2 guest or the exit - i.e., the guest-state and + * exit-information fields only. Other fields are modified by L1 with VMWRITE, + * which already writes to vmcs12 directly. + */ +void prepare_vmcs12(struct kvm_vcpu *vcpu) +{ + struct vmcs_fields *vmcs12 = get_vmcs12_fields(vcpu); + + /* update guest state fields: */ + vmcs12->guest_cr0 = vmcs12_guest_cr0(vcpu, vmcs12); + vmcs12->guest_cr4 = vmcs12_guest_cr4(vcpu, vmcs12); + + vmcs12->guest_dr7 = vmcs_readl(GUEST_DR7); + vmcs12->guest_rsp = vmcs_readl(GUEST_RSP); + vmcs12->guest_rip = vmcs_readl(GUEST_RIP); + vmcs12->guest_rflags = vmcs_readl(GUEST_RFLAGS); + + vmcs12->guest_es_selector = vmcs_read16(GUEST_ES_SELECTOR); + vmcs12->guest_cs_selector = vmcs_read16(GUEST_CS_SELECTOR); + vmcs12->guest_ss_selector = vmcs_read16(GUEST_SS_SELECTOR); + vmcs12->guest_ds_selector = vmcs_read16(GUEST_DS_SELECTOR); + vmcs12->guest_fs_selector = vmcs_read16(GUEST_FS_SELECTOR); + vmcs12->guest_gs_selector = vmcs_read16(GUEST_GS_SELECTOR); + vmcs12->guest_ldtr_selector = vmcs_read16(GUEST_LDTR_SELECTOR); + vmcs12->guest_tr_selector = vmcs_read16(GUEST_TR_SELECTOR); + vmcs12->guest_es_limit = vmcs_read32(GUEST_ES_LIMIT); + vmcs12->guest_cs_limit = vmcs_read32(GUEST_CS_LIMIT); + vmcs12->guest_ss_limit = vmcs_read32(GUEST_SS_LIMIT); + vmcs12->guest_ds_limit = vmcs_read32(GUEST_DS_LIMIT); + vmcs12->guest_fs_limit = vmcs_read32(GUEST_FS_LIMIT); + vmcs12->guest_gs_limit = vmcs_read32(GUEST_GS_LIMIT); + vmcs12->guest_ldtr_limit = vmcs_read32(GUEST_LDTR_LIMIT); + vmcs12->guest_tr_limit = vmcs_read32(GUEST_TR_LIMIT); + vmcs12->guest_gdtr_limit = vmcs_read32(GUEST_GDTR_LIMIT); + vmcs12->guest_idtr_limit = vmcs_read32(GUEST_IDTR_LIMIT); + vmcs12->guest_es_ar_bytes = vmcs_read32(GUEST_ES_AR_BYTES); + vmcs12->guest_cs_ar_bytes = vmcs_read32(GUEST_CS_AR_BYTES); + vmcs12->guest_ss_ar_bytes = vmcs_read32(GUEST_SS_AR_BYTES); + vmcs12->guest_ds_ar_bytes = vmcs_read32(GUEST_DS_AR_BYTES); + vmcs12->guest_fs_ar_bytes = vmcs_read32(GUEST_FS_AR_BYTES); + vmcs12->guest_gs_ar_bytes = vmcs_read32(GUEST_GS_AR_BYTES); + vmcs12->guest_ldtr_ar_bytes = vmcs_read32(GUEST_LDTR_AR_BYTES); + vmcs12->guest_tr_ar_bytes = vmcs_read32(GUEST_TR_AR_BYTES); + vmcs12->guest_es_base = vmcs_readl(GUEST_ES_BASE); + vmcs12->guest_cs_base = vmcs_readl(GUEST_CS_BASE); + vmcs12->guest_ss_base = vmcs_readl(GUEST_SS_BASE); + vmcs12->guest_ds_base = vmcs_readl(GUEST_DS_BASE); + vmcs12->guest_fs_base = vmcs_readl(GUEST_FS_BASE); + vmcs12->guest_gs_base = vmcs_readl(GUEST_GS_BASE); + vmcs12->guest_ldtr_base = vmcs_readl(GUEST_LDTR_BASE); + vmcs12->guest_tr_base = vmcs_readl(GUEST_TR_BASE); + vmcs12->guest_gdtr_base = vmcs_readl(GUEST_GDTR_BASE); + vmcs12->guest_idtr_base = vmcs_readl(GUEST_IDTR_BASE); + + /* TODO: These cannot have changed unless we have MSR bitmaps and + * the relevant bit asks not to trap the change */ + vmcs12->guest_ia32_debugctl = vmcs_read64(GUEST_IA32_DEBUGCTL); + if (vmcs_config.vmentry_ctrl & VM_EXIT_SAVE_IA32_PAT) + vmcs12->guest_ia32_pat = vmcs_read64(GUEST_IA32_PAT); + vmcs12->guest_sysenter_cs = vmcs_read32(GUEST_SYSENTER_CS); + vmcs12->guest_sysenter_esp = vmcs_readl(GUEST_SYSENTER_ESP); + vmcs12->guest_sysenter_eip = vmcs_readl(GUEST_SYSENTER_EIP); + + vmcs12->guest_activity_state = vmcs_read32(GUEST_ACTIVITY_STATE); + vmcs12->guest_interruptibility_info = + vmcs_read32(GUEST_INTERRUPTIBILITY_INFO); + vmcs12->guest_pending_dbg_exceptions = + vmcs_readl(GUEST_PENDING_DBG_EXCEPTIONS); + vmcs12->vmcs_link_pointer = vmcs_read64(VMCS_LINK_POINTER); + + /* update exit information fields: */ + + vmcs12->vm_exit_reason = vmcs_read32(VM_EXIT_REASON); + vmcs12->exit_qualification = vmcs_readl(EXIT_QUALIFICATION); + + if(enable_ept){ + vmcs12->guest_physical_address = + vmcs_read64(GUEST_PHYSICAL_ADDRESS); + vmcs12->guest_linear_address = vmcs_readl(GUEST_LINEAR_ADDRESS); + } + + vmcs12->vm_exit_intr_info = vmcs_read32(VM_EXIT_INTR_INFO); + vmcs12->vm_exit_intr_error_code = vmcs_read32(VM_EXIT_INTR_ERROR_CODE); + vmcs12->idt_vectoring_info_field = + vmcs_read32(IDT_VECTORING_INFO_FIELD); + vmcs12->idt_vectoring_error_code = + vmcs_read32(IDT_VECTORING_ERROR_CODE); + vmcs12->vm_exit_instruction_len = vmcs_read32(VM_EXIT_INSTRUCTION_LEN); + vmcs12->vmx_instruction_info = vmcs_read32(VMX_INSTRUCTION_INFO); + + /* clear vm-entry fields which are to be cleared on exit */ + if (!(vmcs12->vm_exit_reason & VMX_EXIT_REASONS_FAILED_VMENTRY)) + vmcs12->vm_entry_intr_info_field &= ~INTR_INFO_VALID_MASK; +} + +static int nested_vmx_vmexit(struct kvm_vcpu *vcpu, bool is_interrupt) +{ + struct vcpu_vmx *vmx = to_vmx(vcpu); + int efer_offset; + struct vmcs_fields *vmcs01 = vmx->nested.vmcs01_fields; + + if (!vmx->nested.nested_mode) { + printk(KERN_INFO "WARNING: %s called but not in nested mode\n", + __func__); + return 0; + } + + sync_cached_regs_to_vmcs(vcpu); + + prepare_vmcs12(vcpu); + if (is_interrupt) + get_vmcs12_fields(vcpu)->vm_exit_reason = + EXIT_REASON_EXTERNAL_INTERRUPT; + + vmx->nested.current_vmcs12->launched = vmx->launched; + vmx->nested.current_vmcs12->cpu = vcpu->cpu; + + vmx->vmcs = vmx->nested.vmcs01; + vcpu->cpu = vmx->nested.l1_state.cpu; + vmx->launched = vmx->nested.l1_state.launched; + + vmx_vcpu_load(vcpu, get_cpu()); + put_cpu(); + + vcpu->arch.efer = vmx->nested.l1_state.efer; + if ((vcpu->arch.efer & EFER_LMA) && + !(vcpu->arch.efer & EFER_SCE)) + vcpu->arch.efer |= EFER_SCE; + + efer_offset = __find_msr_index(vmx, MSR_EFER); + if (update_transition_efer(vmx, efer_offset)) + wrmsrl(MSR_EFER, vmx->guest_msrs[efer_offset].data); + + /* + * L2 perhaps switched to real mode and set vmx->rmode, but we're back + * in L1 and as it is running VMX, it can't be in real mode. + */ + vmx->rmode.vm86_active = 0; + + /* + * We're running a regular L1 guest again, so we do the regular KVM + * thing: run vmx_set_cr0 with the cr0 bits the guest thinks it has. + * vmx_set_cr0 might use slightly different bits on the new guest_cr0 + * it sets, e.g., add TS when !fpu_active. + * Note that vmx_set_cr0 refers to rmode and efer set above. + */ + vmx_set_cr0(vcpu, guest_readable_cr0(vmcs01)); + /* + * If we did fpu_activate()/fpu_deactive() during l2's run, we need to + * apply the same changes to l1's vmcs. We just set cr0 correctly, but + * now we need to also update cr0_guest_host_mask and exception_bitmap. + */ + vmcs_write32(EXCEPTION_BITMAP, + (vmx->nested.vmcs01_fields->exception_bitmap & + ~(1u<fpu_active ? 0 : (1u<arch.cr0_guest_owned_bits = (vcpu->fpu_active ? X86_CR0_TS : 0); + vmcs_writel(CR0_GUEST_HOST_MASK, ~vcpu->arch.cr0_guest_owned_bits); + + + vmx_set_cr4(vcpu, vmx->nested.l1_state.cr4); + + if (enable_ept) { + vcpu->arch.cr3 = vmcs01->guest_cr3; + vmcs_write32(GUEST_CR3, vmcs01->guest_cr3); + vmcs_write64(EPT_POINTER, vmcs01->ept_pointer); + vmcs_write64(GUEST_PDPTR0, vmcs01->guest_pdptr0); + vmcs_write64(GUEST_PDPTR1, vmcs01->guest_pdptr1); + vmcs_write64(GUEST_PDPTR2, vmcs01->guest_pdptr2); + vmcs_write64(GUEST_PDPTR3, vmcs01->guest_pdptr3); + } else { + kvm_set_cr3(vcpu, vmx->nested.l1_state.cr3); + } + + kvm_register_write(vcpu, VCPU_REGS_RSP, + vmx->nested.vmcs01_fields->guest_rsp); + kvm_register_write(vcpu, VCPU_REGS_RIP, + vmx->nested.vmcs01_fields->guest_rip); + + vmx->nested.nested_mode = 0; + + kvm_mmu_reset_context(vcpu); + kvm_mmu_load(vcpu); + + if (unlikely(vmx->fail)) { + /* + * When L1 launches L2 and then we (L0) fail to launch L2, + * we nested_vmx_vmexit back to L1, but now should let it know + * that the VMLAUNCH failed - with the same error that we + * got when launching L2. + */ + vmx->fail = 0; + nested_vmx_failValid(vcpu, vmcs_read32(VM_INSTRUCTION_ERROR)); + } else + nested_vmx_succeed(vcpu); + + return 0; +} + static struct kvm_x86_ops vmx_x86_ops = { .cpu_has_kvm_support = cpu_has_kvm_support, .disabled_by_bios = vmx_disabled_by_bios, -- Nadav Har'El | Sunday, Sep 12 2010, 4 Tishri 5771 nyh@math.technion.ac.il |----------------------------------------- Phone +972-523-790466, ICQ 13349191 |Anyone is entitled to their own opinions. http://nadav.harel.org.il |No one is entitled to their own facts.