KVM: arm64: selftests: arch_timer: Support vCPU migration
authorRaghavendra Rao Ananta <rananta@google.com>
Thu, 7 Oct 2021 23:34:39 +0000 (23:34 +0000)
committerMarc Zyngier <maz@kernel.org>
Sun, 17 Oct 2021 10:17:22 +0000 (11:17 +0100)
Since the timer stack (hardware and KVM) is per-CPU, there
are potential chances for races to occur when the scheduler
decides to migrate a vCPU thread to a different physical CPU.
Hence, include an option to stress-test this part as well by
forcing the vCPUs to migrate across physical CPUs in the
system at a particular rate.

Originally, the bug for the fix with commit 3134cc8beb69d0d
("KVM: arm64: vgic: Resample HW pending state on deactivation")
was discovered using arch_timer test with vCPU migrations and
can be easily reproduced.

Signed-off-by: Raghavendra Rao Ananta <rananta@google.com>
Reviewed-by: Andrew Jones <drjones@redhat.com>
Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20211007233439.1826892-16-rananta@google.com
tools/testing/selftests/kvm/aarch64/arch_timer.c

index 3b6ea6a..bf6a45b 100644 (file)
@@ -14,6 +14,8 @@
  *
  * The test provides command-line options to configure the timer's
  * period (-p), number of vCPUs (-n), and iterations per stage (-i).
+ * To stress-test the timer stack even more, an option to migrate the
+ * vCPUs across pCPUs (-m), at a particular rate, is also provided.
  *
  * Copyright (c) 2021, Google LLC.
  */
@@ -24,6 +26,8 @@
 #include <pthread.h>
 #include <linux/kvm.h>
 #include <linux/sizes.h>
+#include <linux/bitmap.h>
+#include <sys/sysinfo.h>
 
 #include "kvm_util.h"
 #include "processor.h"
 #define NR_TEST_ITERS_DEF              5
 #define TIMER_TEST_PERIOD_MS_DEF       10
 #define TIMER_TEST_ERR_MARGIN_US       100
+#define TIMER_TEST_MIGRATION_FREQ_MS   2
 
 struct test_args {
        int nr_vcpus;
        int nr_iter;
        int timer_period_ms;
+       int migration_freq_ms;
 };
 
 static struct test_args test_args = {
        .nr_vcpus = NR_VCPUS_DEF,
        .nr_iter = NR_TEST_ITERS_DEF,
        .timer_period_ms = TIMER_TEST_PERIOD_MS_DEF,
+       .migration_freq_ms = TIMER_TEST_MIGRATION_FREQ_MS,
 };
 
 #define msecs_to_usecs(msec)           ((msec) * 1000LL)
@@ -80,6 +87,9 @@ static struct test_vcpu_shared_data vcpu_shared_data[KVM_MAX_VCPUS];
 
 static int vtimer_irq, ptimer_irq;
 
+static unsigned long *vcpu_done_map;
+static pthread_mutex_t vcpu_done_map_lock;
+
 static void
 guest_configure_timer_action(struct test_vcpu_shared_data *shared_data)
 {
@@ -215,6 +225,11 @@ static void *test_vcpu_run(void *arg)
 
        vcpu_run(vm, vcpuid);
 
+       /* Currently, any exit from guest is an indication of completion */
+       pthread_mutex_lock(&vcpu_done_map_lock);
+       set_bit(vcpuid, vcpu_done_map);
+       pthread_mutex_unlock(&vcpu_done_map_lock);
+
        switch (get_ucall(vm, vcpuid, &uc)) {
        case UCALL_SYNC:
        case UCALL_DONE:
@@ -233,9 +248,78 @@ static void *test_vcpu_run(void *arg)
        return NULL;
 }
 
+static uint32_t test_get_pcpu(void)
+{
+       uint32_t pcpu;
+       unsigned int nproc_conf;
+       cpu_set_t online_cpuset;
+
+       nproc_conf = get_nprocs_conf();
+       sched_getaffinity(0, sizeof(cpu_set_t), &online_cpuset);
+
+       /* Randomly find an available pCPU to place a vCPU on */
+       do {
+               pcpu = rand() % nproc_conf;
+       } while (!CPU_ISSET(pcpu, &online_cpuset));
+
+       return pcpu;
+}
+
+static int test_migrate_vcpu(struct test_vcpu *vcpu)
+{
+       int ret;
+       cpu_set_t cpuset;
+       uint32_t new_pcpu = test_get_pcpu();
+
+       CPU_ZERO(&cpuset);
+       CPU_SET(new_pcpu, &cpuset);
+
+       pr_debug("Migrating vCPU: %u to pCPU: %u\n", vcpu->vcpuid, new_pcpu);
+
+       ret = pthread_setaffinity_np(vcpu->pt_vcpu_run,
+                                       sizeof(cpuset), &cpuset);
+
+       /* Allow the error where the vCPU thread is already finished */
+       TEST_ASSERT(ret == 0 || ret == ESRCH,
+                       "Failed to migrate the vCPU:%u to pCPU: %u; ret: %d\n",
+                       vcpu->vcpuid, new_pcpu, ret);
+
+       return ret;
+}
+
+static void *test_vcpu_migration(void *arg)
+{
+       unsigned int i, n_done;
+       bool vcpu_done;
+
+       do {
+               usleep(msecs_to_usecs(test_args.migration_freq_ms));
+
+               for (n_done = 0, i = 0; i < test_args.nr_vcpus; i++) {
+                       pthread_mutex_lock(&vcpu_done_map_lock);
+                       vcpu_done = test_bit(i, vcpu_done_map);
+                       pthread_mutex_unlock(&vcpu_done_map_lock);
+
+                       if (vcpu_done) {
+                               n_done++;
+                               continue;
+                       }
+
+                       test_migrate_vcpu(&test_vcpu[i]);
+               }
+       } while (test_args.nr_vcpus != n_done);
+
+       return NULL;
+}
+
 static void test_run(struct kvm_vm *vm)
 {
        int i, ret;
+       pthread_t pt_vcpu_migration;
+
+       pthread_mutex_init(&vcpu_done_map_lock, NULL);
+       vcpu_done_map = bitmap_zalloc(test_args.nr_vcpus);
+       TEST_ASSERT(vcpu_done_map, "Failed to allocate vcpu done bitmap\n");
 
        for (i = 0; i < test_args.nr_vcpus; i++) {
                ret = pthread_create(&test_vcpu[i].pt_vcpu_run, NULL,
@@ -243,8 +327,23 @@ static void test_run(struct kvm_vm *vm)
                TEST_ASSERT(!ret, "Failed to create vCPU-%d pthread\n", i);
        }
 
+       /* Spawn a thread to control the vCPU migrations */
+       if (test_args.migration_freq_ms) {
+               srand(time(NULL));
+
+               ret = pthread_create(&pt_vcpu_migration, NULL,
+                                       test_vcpu_migration, NULL);
+               TEST_ASSERT(!ret, "Failed to create the migration pthread\n");
+       }
+
+
        for (i = 0; i < test_args.nr_vcpus; i++)
                pthread_join(test_vcpu[i].pt_vcpu_run, NULL);
+
+       if (test_args.migration_freq_ms)
+               pthread_join(pt_vcpu_migration, NULL);
+
+       bitmap_free(vcpu_done_map);
 }
 
 static void test_init_timer_irq(struct kvm_vm *vm)
@@ -301,6 +400,8 @@ static void test_print_help(char *name)
                NR_TEST_ITERS_DEF);
        pr_info("\t-p: Periodicity (in ms) of the guest timer (default: %u)\n",
                TIMER_TEST_PERIOD_MS_DEF);
+       pr_info("\t-m: Frequency (in ms) of vCPUs to migrate to different pCPU. 0 to turn off (default: %u)\n",
+               TIMER_TEST_MIGRATION_FREQ_MS);
        pr_info("\t-h: print this help screen\n");
 }
 
@@ -308,7 +409,7 @@ static bool parse_args(int argc, char *argv[])
 {
        int opt;
 
-       while ((opt = getopt(argc, argv, "hn:i:p:")) != -1) {
+       while ((opt = getopt(argc, argv, "hn:i:p:m:")) != -1) {
                switch (opt) {
                case 'n':
                        test_args.nr_vcpus = atoi(optarg);
@@ -335,6 +436,13 @@ static bool parse_args(int argc, char *argv[])
                                goto err;
                        }
                        break;
+               case 'm':
+                       test_args.migration_freq_ms = atoi(optarg);
+                       if (test_args.migration_freq_ms < 0) {
+                               pr_info("0 or positive value needed for -m\n");
+                               goto err;
+                       }
+                       break;
                case 'h':
                default:
                        goto err;
@@ -358,6 +466,11 @@ int main(int argc, char *argv[])
        if (!parse_args(argc, argv))
                exit(KSFT_SKIP);
 
+       if (test_args.migration_freq_ms && get_nprocs() < 2) {
+               print_skip("At least two physical CPUs needed for vCPU migration");
+               exit(KSFT_SKIP);
+       }
+
        vm = test_vm_create();
        test_run(vm);
        kvm_vm_free(vm);