diff mbox

[PULL,13/21] apic_common: vapic_paddr synchronization fix

Message ID 1410530338-17615-14-git-send-email-pbonzini@redhat.com
State New
Headers show

Commit Message

Paolo Bonzini Sept. 12, 2014, 1:58 p.m. UTC
From: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>

This patch postpones vapic_paddr initialization, which is performed
during migration. When vapic_paddr is synchronized within the migration
process, apic_common functions could operate with incorrect apic state,
if it hadn't loaded yet. This patch postpones the synchronization until
the virtual machine is started, ensuring that the whole virtual machine
state has been loaded.

Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
Tested-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 hw/i386/kvmvapic.c | 37 ++++++++++++++++++++++++++-----------
 1 file changed, 26 insertions(+), 11 deletions(-)

Comments

Pavel Dovgalyuk Sept. 19, 2014, 10:43 a.m. UTC | #1
Hi, Paolo!

> From: Paolo Bonzini [mailto:paolo.bonzini@gmail.com] On Behalf Of Paolo Bonzini
> From: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> 
> This patch postpones vapic_paddr initialization, which is performed
> during migration. When vapic_paddr is synchronized within the migration
> process, apic_common functions could operate with incorrect apic state,
> if it hadn't loaded yet. This patch postpones the synchronization until
> the virtual machine is started, ensuring that the whole virtual machine
> state has been loaded.
> 
> 
> +static int vapic_post_load(void *opaque, int version_id)
> +{
> +    VAPICROMState *s = opaque;
> +
>      /*
>       * The old implementation of qemu-kvm did not provide the state
>       * VAPIC_STANDBY. Reconstruct it.
> @@ -752,17 +776,8 @@ static int vapic_post_load(void *opaque, int version_id)
>              return -1;
>          }
>      }
> -    if (s->state == VAPIC_ACTIVE) {
> -        if (smp_cpus == 1) {
> -            run_on_cpu(first_cpu, do_vapic_enable, s);
> -        } else {
> -            zero = g_malloc0(s->rom_state.vapic_size);
> -            cpu_physical_memory_write(s->vapic_paddr, zero,
> -                                      s->rom_state.vapic_size);
> -            g_free(zero);
> -        }
> -    }
> 
> +    s->vmsentry = qemu_add_vm_change_state_handler(kvmvapic_vm_state_change, s);
>      return 0;

I've tested this patch with replay. I enabled VM reset (which was previously disabled for replay)
while loading the VM state and discovered the following problem.
vapic_enable function in kvmapic.c retrieves cpu number with the get_kpcr_number() function.
When cpu number is -1 vapic_enable exits and does not call apic_enable_vapic, which should 
setup vapic_paddr field.
Without this call vapic_paddr remains initialized with default value and behavior of the virtual
machine becomes different.

Pavel Dovgalyuk
Paolo Bonzini Sept. 19, 2014, 12:04 p.m. UTC | #2
Il 19/09/2014 12:43, Pavel Dovgaluk ha scritto:
> I've tested this patch with replay. I enabled VM reset (which was previously disabled for replay)
> while loading the VM state and discovered the following problem.
> vapic_enable function in kvmapic.c retrieves cpu number with the get_kpcr_number() function.
> When cpu number is -1 vapic_enable exits and does not call apic_enable_vapic, which should 
> setup vapic_paddr field.
> Without this call vapic_paddr remains initialized with default value and behavior of the virtual
> machine becomes different.

IIUC the fix would be to move part of vapic_enable out to its separate
function, and call it from do_vapic_enable?  Could you prepare a patch?

Paolo
Pavel Dovgalyuk Sept. 19, 2014, 12:50 p.m. UTC | #3
> From: Paolo Bonzini [mailto:paolo.bonzini@gmail.com] On Behalf Of Paolo Bonzini
> Il 19/09/2014 12:43, Pavel Dovgaluk ha scritto:
> > I've tested this patch with replay. I enabled VM reset (which was previously disabled for
> replay)
> > while loading the VM state and discovered the following problem.
> > vapic_enable function in kvmapic.c retrieves cpu number with the get_kpcr_number() function.
> > When cpu number is -1 vapic_enable exits and does not call apic_enable_vapic, which should
> > setup vapic_paddr field.
> > Without this call vapic_paddr remains initialized with default value and behavior of the
> virtual
> > machine becomes different.
> 
> IIUC the fix would be to move part of vapic_enable out to its separate
> function, and call it from do_vapic_enable?  Could you prepare a patch?

static int vapic_enable(VAPICROMState *s, X86CPU *cpu)
{
    int cpu_number = get_kpcr_number(cpu);
    hwaddr vapic_paddr;
    static const uint8_t enabled = 1;

    if (cpu_number < 0) {
        return -1;
    }
    vapic_paddr = s->vapic_paddr +
        (((hwaddr)cpu_number) << VAPIC_CPU_SHIFT);
    cpu_physical_memory_write(vapic_paddr + offsetof(VAPICState, enabled),
                              &enabled, sizeof(enabled));
    apic_enable_vapic(cpu->apic_state, vapic_paddr);

    s->state = VAPIC_ACTIVE;

    return 0;
}

vapic_paddr depends on cpu_number. cpu_number cannot be retrieved when do_vapic_enable executes.
Thus we cannot reconstruct vapic_paddr in that function.

Pavel Dovgalyuk
Paolo Bonzini Sept. 19, 2014, 1:41 p.m. UTC | #4
Il 19/09/2014 14:50, Pavel Dovgaluk ha scritto:
> vapic_paddr depends on cpu_number. cpu_number cannot be retrieved when do_vapic_enable executes.
> Thus we cannot reconstruct vapic_paddr in that function.

cpu_number will always be zero, because do_vapic_enable is only executed
for smp_cpus == 1.

Paolo
Paolo Bonzini Sept. 22, 2014, 9:16 a.m. UTC | #5
Il 22/09/2014 10:21, Pavel Dovgaluk ha scritto:
> 
>> -----Original Message-----
>> From: Paolo Bonzini [mailto:pbonzini@redhat.com]
>> Sent: Friday, September 19, 2014 5:42 PM
>> To: Pavel Dovgaluk; qemu-devel@nongnu.org
>> Subject: Re: [PULL 13/21] apic_common: vapic_paddr synchronization fix
>>
>> Il 19/09/2014 14:50, Pavel Dovgaluk ha scritto:
>>> vapic_paddr depends on cpu_number. cpu_number cannot be retrieved when do_vapic_enable
>> executes.
>>> Thus we cannot reconstruct vapic_paddr in that function.
>>
>> cpu_number will always be zero, because do_vapic_enable is only executed
>> for smp_cpus == 1.
> 
> Right. Here it is:

This patch was already applied.  Please make a new patch on top of
current master, and send it with your Signed-off-by.

Paolo
Pavel Dovgalyuk Sept. 26, 2014, 11:18 a.m. UTC | #6
> From: Paolo Bonzini [mailto:paolo.bonzini@gmail.com] On Behalf Of Paolo Bonzini
> 
> This patch postpones vapic_paddr initialization, which is performed
> during migration. When vapic_paddr is synchronized within the migration
> process, apic_common functions could operate with incorrect apic state,
> if it hadn't loaded yet. This patch postpones the synchronization until
> the virtual machine is started, ensuring that the whole virtual machine
> state has been loaded.
> 
> Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> Tested-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  hw/i386/kvmvapic.c | 37 ++++++++++++++++++++++++++-----------
>  1 file changed, 26 insertions(+), 11 deletions(-)

One more issue for this patch.

> -static int vapic_post_load(void *opaque, int version_id)
> +static void kvmvapic_vm_state_change(void *opaque, int running,
> +                                     RunState state)
>  {
>      VAPICROMState *s = opaque;
>      uint8_t *zero;
> 
> +    if (!running) {

Exitting here doesn't remove vmsentry. When we load VM state for multiple times,
list of the handlers will be filled with garbage.

> +        return;
> +    }
> +
> +    if (s->state == VAPIC_ACTIVE) {
> +        if (smp_cpus == 1) {
> +            run_on_cpu(first_cpu, do_vapic_enable, s);
> +        } else {
> +            zero = g_malloc0(s->rom_state.vapic_size);
> +            cpu_physical_memory_write(s->vapic_paddr, zero,
> +                                      s->rom_state.vapic_size);
> +            g_free(zero);
> +        }
> +    }
> +
> +    qemu_del_vm_change_state_handler(s->vmsentry);
> +}


Pavel Dovgalyuk
Paolo Bonzini Sept. 26, 2014, 11:18 a.m. UTC | #7
Il 26/09/2014 13:18, Pavel Dovgaluk ha scritto:
>> From: Paolo Bonzini [mailto:paolo.bonzini@gmail.com] On Behalf Of Paolo Bonzini
>>
>> This patch postpones vapic_paddr initialization, which is performed
>> during migration. When vapic_paddr is synchronized within the migration
>> process, apic_common functions could operate with incorrect apic state,
>> if it hadn't loaded yet. This patch postpones the synchronization until
>> the virtual machine is started, ensuring that the whole virtual machine
>> state has been loaded.
>>
>> Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
>> Tested-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> ---
>>  hw/i386/kvmvapic.c | 37 ++++++++++++++++++++++++++-----------
>>  1 file changed, 26 insertions(+), 11 deletions(-)
> 
> One more issue for this patch.
> 
>> -static int vapic_post_load(void *opaque, int version_id)
>> +static void kvmvapic_vm_state_change(void *opaque, int running,
>> +                                     RunState state)
>>  {
>>      VAPICROMState *s = opaque;
>>      uint8_t *zero;
>>
>> +    if (!running) {
> 
> Exitting here doesn't remove vmsentry. When we load VM state for multiple times,
> list of the handlers will be filled with garbage.

Thanks.

Paolo

>> +        return;
>> +    }
>> +
>> +    if (s->state == VAPIC_ACTIVE) {
>> +        if (smp_cpus == 1) {
>> +            run_on_cpu(first_cpu, do_vapic_enable, s);
>> +        } else {
>> +            zero = g_malloc0(s->rom_state.vapic_size);
>> +            cpu_physical_memory_write(s->vapic_paddr, zero,
>> +                                      s->rom_state.vapic_size);
>> +            g_free(zero);
>> +        }
>> +    }
>> +
>> +    qemu_del_vm_change_state_handler(s->vmsentry);
>> +}
> 
> 
> Pavel Dovgalyuk
>
diff mbox

Patch

diff --git a/hw/i386/kvmvapic.c b/hw/i386/kvmvapic.c
index ee95963..2cca7a4 100644
--- a/hw/i386/kvmvapic.c
+++ b/hw/i386/kvmvapic.c
@@ -59,6 +59,7 @@  typedef struct VAPICROMState {
     GuestROMState rom_state;
     size_t rom_size;
     bool rom_mapped_writable;
+    VMChangeStateEntry *vmsentry;
 } VAPICROMState;
 
 #define TYPE_VAPIC "kvmvapic"
@@ -734,11 +735,34 @@  static void do_vapic_enable(void *data)
     vapic_enable(s, cpu);
 }
 
-static int vapic_post_load(void *opaque, int version_id)
+static void kvmvapic_vm_state_change(void *opaque, int running,
+                                     RunState state)
 {
     VAPICROMState *s = opaque;
     uint8_t *zero;
 
+    if (!running) {
+        return;
+    }
+
+    if (s->state == VAPIC_ACTIVE) {
+        if (smp_cpus == 1) {
+            run_on_cpu(first_cpu, do_vapic_enable, s);
+        } else {
+            zero = g_malloc0(s->rom_state.vapic_size);
+            cpu_physical_memory_write(s->vapic_paddr, zero,
+                                      s->rom_state.vapic_size);
+            g_free(zero);
+        }
+    }
+
+    qemu_del_vm_change_state_handler(s->vmsentry);
+}
+
+static int vapic_post_load(void *opaque, int version_id)
+{
+    VAPICROMState *s = opaque;
+
     /*
      * The old implementation of qemu-kvm did not provide the state
      * VAPIC_STANDBY. Reconstruct it.
@@ -752,17 +776,8 @@  static int vapic_post_load(void *opaque, int version_id)
             return -1;
         }
     }
-    if (s->state == VAPIC_ACTIVE) {
-        if (smp_cpus == 1) {
-            run_on_cpu(first_cpu, do_vapic_enable, s);
-        } else {
-            zero = g_malloc0(s->rom_state.vapic_size);
-            cpu_physical_memory_write(s->vapic_paddr, zero,
-                                      s->rom_state.vapic_size);
-            g_free(zero);
-        }
-    }
 
+    s->vmsentry = qemu_add_vm_change_state_handler(kvmvapic_vm_state_change, s);
     return 0;
 }