KVM: nVMX: Switch to vmcs01 to update PML controls on-demand if L2 is active

If KVM toggles "CPU dirty logging", a.k.a. Page-Modification Logging (PML),
while L2 is active, temporarily load vmcs01 and immediately update the
relevant controls instead of deferring the update until the next nested
VM-Exit.  For PML, deferring the update is relatively straightforward, but
for several APICv related updates, deferring updates creates ordering and
state consistency problems, e.g. KVM at-large thinks APICv is enabled, but
vmcs01 is still running with stale (and effectively unknown) state.

Convert PML first precisely because it's the simplest case to handle: if
something is broken with the vmcs01 <=> vmcs02 dance, then hopefully bugs
will bisect here.

Reviewed-by: Chao Gao <chao.gao@intel.com>
Link: https://patch.msgid.link/20260109034532.1012993-3-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
This commit is contained in:
Sean Christopherson 2026-01-08 19:45:26 -08:00
parent c3a9a27c79
commit 3e013d0a70
3 changed files with 36 additions and 10 deletions

View File

@ -5131,11 +5131,6 @@ void __nested_vmx_vmexit(struct kvm_vcpu *vcpu, u32 vm_exit_reason,
vmx_set_virtual_apic_mode(vcpu);
}
if (vmx->nested.update_vmcs01_cpu_dirty_logging) {
vmx->nested.update_vmcs01_cpu_dirty_logging = false;
vmx_update_cpu_dirty_logging(vcpu);
}
nested_put_vmcs12_pages(vcpu);
if (vmx->nested.reload_vmcs01_apic_access_page) {

View File

@ -1594,6 +1594,41 @@ void vmx_vcpu_put(struct kvm_vcpu *vcpu)
vmx_prepare_switch_to_host(to_vmx(vcpu));
}
static void vmx_switch_loaded_vmcs(struct kvm_vcpu *vcpu,
struct loaded_vmcs *vmcs)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
int cpu;
cpu = get_cpu();
vmx->loaded_vmcs = vmcs;
vmx_vcpu_load_vmcs(vcpu, cpu);
put_cpu();
}
static void vmx_load_vmcs01(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
if (!is_guest_mode(vcpu)) {
WARN_ON_ONCE(vmx->loaded_vmcs != &vmx->vmcs01);
return;
}
WARN_ON_ONCE(vmx->loaded_vmcs != &vmx->nested.vmcs02);
vmx_switch_loaded_vmcs(vcpu, &vmx->vmcs01);
}
static void vmx_put_vmcs01(struct kvm_vcpu *vcpu)
{
if (!is_guest_mode(vcpu))
return;
vmx_switch_loaded_vmcs(vcpu, &to_vmx(vcpu)->nested.vmcs02);
}
DEFINE_GUARD(vmx_vmcs01, struct kvm_vcpu *,
vmx_load_vmcs01(_T), vmx_put_vmcs01(_T))
bool vmx_emulation_required(struct kvm_vcpu *vcpu)
{
return emulate_invalid_guest_state && !vmx_guest_state_valid(vcpu);
@ -8276,10 +8311,7 @@ void vmx_update_cpu_dirty_logging(struct kvm_vcpu *vcpu)
if (WARN_ON_ONCE(!enable_pml))
return;
if (is_guest_mode(vcpu)) {
vmx->nested.update_vmcs01_cpu_dirty_logging = true;
return;
}
guard(vmx_vmcs01)(vcpu);
/*
* Note, nr_memslots_dirty_logging can be changed concurrent with this

View File

@ -133,7 +133,6 @@ struct nested_vmx {
bool change_vmcs01_virtual_apic_mode;
bool reload_vmcs01_apic_access_page;
bool update_vmcs01_cpu_dirty_logging;
bool update_vmcs01_apicv_status;
bool update_vmcs01_hwapic_isr;