#include <linux/bitmap.h>
 #include <linux/bitops.h>
 #include <asm/barrier.h>
+#include <linux/atomic.h>
 
 #include "kvm_util.h"
 #include "test_util.h"
 static uint64_t host_track_next_count;
 
 /* Whether dirty ring reset is requested, or finished */
-static sem_t dirty_ring_vcpu_stop;
-static sem_t dirty_ring_vcpu_cont;
+static sem_t sem_vcpu_stop;
+static sem_t sem_vcpu_cont;
+/*
+ * This is only set by main thread, and only cleared by vcpu thread.  It is
+ * used to request vcpu thread to stop at the next GUEST_SYNC, since GUEST_SYNC
+ * is the only place that we'll guarantee both "dirty bit" and "dirty data"
+ * will match.  E.g., SIG_IPI won't guarantee that if the vcpu is interrupted
+ * after setting dirty bit but before the data is written.
+ */
+static atomic_t vcpu_sync_stop_requested;
 /*
  * This is updated by the vcpu thread to tell the host whether it's a
  * ring-full event.  It should only be read until a sem_wait() of
- * dirty_ring_vcpu_stop and before vcpu continues to run.
+ * sem_vcpu_stop and before vcpu continues to run.
  */
 static bool dirty_ring_vcpu_ring_full;
 /*
        kvm_vm_clear_dirty_log(vm, slot, bitmap, 0, num_pages);
 }
 
+/* Should only be called after a GUEST_SYNC */
+static void vcpu_handle_sync_stop(void)
+{
+       if (atomic_read(&vcpu_sync_stop_requested)) {
+               /* It means main thread is sleeping waiting */
+               atomic_set(&vcpu_sync_stop_requested, false);
+               sem_post(&sem_vcpu_stop);
+               sem_wait_until(&sem_vcpu_cont);
+       }
+}
+
 static void default_after_vcpu_run(struct kvm_vm *vm, int ret, int err)
 {
        struct kvm_run *run = vcpu_state(vm, VCPU_ID);
        TEST_ASSERT(get_ucall(vm, VCPU_ID, NULL) == UCALL_SYNC,
                    "Invalid guest sync status: exit_reason=%s\n",
                    exit_reason_str(run->exit_reason));
+
+       vcpu_handle_sync_stop();
 }
 
 static bool dirty_ring_supported(void)
 {
        /* This makes sure that hardware PML cache flushed */
        vcpu_kick();
-       sem_wait_until(&dirty_ring_vcpu_stop);
+       sem_wait_until(&sem_vcpu_stop);
 }
 
 static void dirty_ring_continue_vcpu(void)
 {
        pr_info("Notifying vcpu to continue\n");
-       sem_post(&dirty_ring_vcpu_cont);
+       sem_post(&sem_vcpu_cont);
 }
 
 static void dirty_ring_collect_dirty_pages(struct kvm_vm *vm, int slot,
                /* Update the flag first before pause */
                WRITE_ONCE(dirty_ring_vcpu_ring_full,
                           run->exit_reason == KVM_EXIT_DIRTY_RING_FULL);
-               sem_post(&dirty_ring_vcpu_stop);
+               sem_post(&sem_vcpu_stop);
                pr_info("vcpu stops because %s...\n",
                        dirty_ring_vcpu_ring_full ?
                        "dirty ring is full" : "vcpu is kicked out");
-               sem_wait_until(&dirty_ring_vcpu_cont);
+               sem_wait_until(&sem_vcpu_cont);
                pr_info("vcpu continues now.\n");
        } else {
                TEST_ASSERT(false, "Invalid guest sync status: "
 static void dirty_ring_before_vcpu_join(void)
 {
        /* Kick another round of vcpu just to make sure it will quit */
-       sem_post(&dirty_ring_vcpu_cont);
+       sem_post(&sem_vcpu_cont);
 }
 
 struct log_mode {
                usleep(p->interval * 1000);
                log_mode_collect_dirty_pages(vm, TEST_MEM_SLOT_INDEX,
                                             bmap, host_num_pages);
+
+               /*
+                * See vcpu_sync_stop_requested definition for details on why
+                * we need to stop vcpu when verify data.
+                */
+               atomic_set(&vcpu_sync_stop_requested, true);
+               sem_wait_until(&sem_vcpu_stop);
+               /*
+                * NOTE: for dirty ring, it's possible that we didn't stop at
+                * GUEST_SYNC but instead we stopped because ring is full;
+                * that's okay too because ring full means we're only missing
+                * the flush of the last page, and since we handle the last
+                * page specially verification will succeed anyway.
+                */
+               assert(host_log_mode == LOG_MODE_DIRTY_RING ||
+                      atomic_read(&vcpu_sync_stop_requested) == false);
                vm_dirty_log_verify(mode, bmap);
+               sem_post(&sem_vcpu_cont);
+
                iteration++;
                sync_global_to_guest(vm, iteration);
        }
        };
        int opt, i;
 
-       sem_init(&dirty_ring_vcpu_stop, 0, 0);
-       sem_init(&dirty_ring_vcpu_cont, 0, 0);
+       sem_init(&sem_vcpu_stop, 0, 0);
+       sem_init(&sem_vcpu_cont, 0, 0);
 
        guest_modes_append_default();