[REPOST PATCH 16/16] selftests: KVM: aarch64: Extend the vCPU migration test to multi-vCPUs

Reiji Watanabe reijiw at google.com
Tue Mar 7 20:44:01 PST 2023


Hi Raghu,

On Tue, Feb 14, 2023 at 5:07 PM Raghavendra Rao Ananta
<rananta at google.com> wrote:
>
> To test KVM's handling of multiple vCPU contexts together, that are
> frequently migrating across random pCPUs in the system, extend the test
> to create a VM with multiple vCPUs and validate the behavior.
>
> Signed-off-by: Raghavendra Rao Ananta <rananta at google.com>
> ---
>  .../testing/selftests/kvm/aarch64/vpmu_test.c | 166 ++++++++++++------
>  1 file changed, 114 insertions(+), 52 deletions(-)
>
> diff --git a/tools/testing/selftests/kvm/aarch64/vpmu_test.c b/tools/testing/selftests/kvm/aarch64/vpmu_test.c
> index 239fc7e06b3b9..c9d8e5f9a22ab 100644
> --- a/tools/testing/selftests/kvm/aarch64/vpmu_test.c
> +++ b/tools/testing/selftests/kvm/aarch64/vpmu_test.c
> @@ -19,11 +19,12 @@
>   * higher exception levels (EL2, EL3). Verify this functionality by
>   * configuring and trying to count the events for EL2 in the guest.
>   *
> - * 4. Since the PMU registers are per-cpu, stress KVM by frequently
> - * migrating the guest vCPU to random pCPUs in the system, and check
> - * if the vPMU is still behaving as expected. The sub-tests include
> - * testing basic functionalities such as basic counters behavior,
> - * overflow, overflow interrupts, and chained events.
> + * 4. Since the PMU registers are per-cpu, stress KVM by creating a
> + * multi-vCPU VM, then frequently migrate the guest vCPUs to random
> + * pCPUs in the system, and check if the vPMU is still behaving as
> + * expected. The sub-tests include testing basic functionalities such
> + * as basic counters behavior, overflow, overflow interrupts, and
> + * chained events.
>   *
>   * Copyright (c) 2022 Google LLC.
>   *
> @@ -348,19 +349,22 @@ struct guest_irq_data {
>         struct spinlock lock;
>  };
>
> -static struct guest_irq_data guest_irq_data;
> +static struct guest_irq_data guest_irq_data[KVM_MAX_VCPUS];
>
>  #define VCPU_MIGRATIONS_TEST_ITERS_DEF         1000
>  #define VCPU_MIGRATIONS_TEST_MIGRATION_FREQ_MS 2
> +#define VCPU_MIGRATIONS_TEST_NR_VPUS_DEF       2
>
>  struct test_args {
>         int vcpu_migration_test_iter;
>         int vcpu_migration_test_migrate_freq_ms;
> +       int vcpu_migration_test_nr_vcpus;
>  };
>
>  static struct test_args test_args = {
>         .vcpu_migration_test_iter = VCPU_MIGRATIONS_TEST_ITERS_DEF,
>         .vcpu_migration_test_migrate_freq_ms = VCPU_MIGRATIONS_TEST_MIGRATION_FREQ_MS,
> +       .vcpu_migration_test_nr_vcpus = VCPU_MIGRATIONS_TEST_NR_VPUS_DEF,
>  };
>
>  static void guest_sync_handler(struct ex_regs *regs)
> @@ -396,26 +400,34 @@ static void guest_validate_irq(int pmc_idx, uint32_t pmovsclr, uint32_t pmc_idx_
>         }
>  }
>
> +static struct guest_irq_data *get_irq_data(void)
> +{
> +       uint32_t cpu = guest_get_vcpuid();
> +
> +       return &guest_irq_data[cpu];
> +}
> +
>  static void guest_irq_handler(struct ex_regs *regs)
>  {
>         uint32_t pmc_idx_bmap;
>         uint64_t i, pmcr_n = get_pmcr_n();
>         uint32_t pmovsclr = read_pmovsclr();
>         unsigned int intid = gic_get_and_ack_irq();
> +       struct guest_irq_data *irq_data = get_irq_data();
>
>         /* No other IRQ apart from the PMU IRQ is expected */
>         GUEST_ASSERT_1(intid == PMU_IRQ, intid);
>
> -       spin_lock(&guest_irq_data.lock);
> -       pmc_idx_bmap = READ_ONCE(guest_irq_data.pmc_idx_bmap);
> +       spin_lock(&irq_data->lock);
> +       pmc_idx_bmap = READ_ONCE(irq_data->pmc_idx_bmap);
>
>         for (i = 0; i < pmcr_n; i++)
>                 guest_validate_irq(i, pmovsclr, pmc_idx_bmap);
>         guest_validate_irq(ARMV8_PMU_CYCLE_COUNTER_IDX, pmovsclr, pmc_idx_bmap);
>
>         /* Mark IRQ as recived for the corresponding PMCs */
> -       WRITE_ONCE(guest_irq_data.irq_received_bmap, pmovsclr);
> -       spin_unlock(&guest_irq_data.lock);
> +       WRITE_ONCE(irq_data->irq_received_bmap, pmovsclr);
> +       spin_unlock(&irq_data->lock);
>
>         gic_set_eoi(intid);
>  }
> @@ -423,35 +435,40 @@ static void guest_irq_handler(struct ex_regs *regs)
>  static int pmu_irq_received(int pmc_idx)
>  {
>         bool irq_received;
> +       struct guest_irq_data *irq_data = get_irq_data();
>
> -       spin_lock(&guest_irq_data.lock);
> -       irq_received = READ_ONCE(guest_irq_data.irq_received_bmap) & BIT(pmc_idx);
> -       WRITE_ONCE(guest_irq_data.irq_received_bmap, guest_irq_data.pmc_idx_bmap & ~BIT(pmc_idx));
> -       spin_unlock(&guest_irq_data.lock);
> +       spin_lock(&irq_data->lock);
> +       irq_received = READ_ONCE(irq_data->irq_received_bmap) & BIT(pmc_idx);
> +       WRITE_ONCE(irq_data->irq_received_bmap, irq_data->pmc_idx_bmap & ~BIT(pmc_idx));
> +       spin_unlock(&irq_data->lock);
>
>         return irq_received;
>  }
>
>  static void pmu_irq_init(int pmc_idx)
>  {
> +       struct guest_irq_data *irq_data = get_irq_data();
> +
>         write_pmovsclr(BIT(pmc_idx));
>
> -       spin_lock(&guest_irq_data.lock);
> -       WRITE_ONCE(guest_irq_data.irq_received_bmap, guest_irq_data.pmc_idx_bmap & ~BIT(pmc_idx));
> -       WRITE_ONCE(guest_irq_data.pmc_idx_bmap, guest_irq_data.pmc_idx_bmap | BIT(pmc_idx));
> -       spin_unlock(&guest_irq_data.lock);
> +       spin_lock(&irq_data->lock);
> +       WRITE_ONCE(irq_data->irq_received_bmap, irq_data->pmc_idx_bmap & ~BIT(pmc_idx));
> +       WRITE_ONCE(irq_data->pmc_idx_bmap, irq_data->pmc_idx_bmap | BIT(pmc_idx));
> +       spin_unlock(&irq_data->lock);
>
>         enable_irq(pmc_idx);
>  }
>
>  static void pmu_irq_exit(int pmc_idx)
>  {
> +       struct guest_irq_data *irq_data = get_irq_data();
> +
>         write_pmovsclr(BIT(pmc_idx));
>
> -       spin_lock(&guest_irq_data.lock);
> -       WRITE_ONCE(guest_irq_data.irq_received_bmap, guest_irq_data.pmc_idx_bmap & ~BIT(pmc_idx));
> -       WRITE_ONCE(guest_irq_data.pmc_idx_bmap, guest_irq_data.pmc_idx_bmap & ~BIT(pmc_idx));
> -       spin_unlock(&guest_irq_data.lock);
> +       spin_lock(&irq_data->lock);
> +       WRITE_ONCE(irq_data->irq_received_bmap, irq_data->pmc_idx_bmap & ~BIT(pmc_idx));
> +       WRITE_ONCE(irq_data->pmc_idx_bmap, irq_data->pmc_idx_bmap & ~BIT(pmc_idx));
> +       spin_unlock(&irq_data->lock);
>
>         disable_irq(pmc_idx);
>  }
> @@ -783,7 +800,8 @@ static void test_event_count(uint64_t event, int pmc_idx, bool expect_count)
>  static void test_basic_pmu_functionality(void)
>  {
>         local_irq_disable();
> -       gic_init(GIC_V3, 1, (void *)GICD_BASE_GPA, (void *)GICR_BASE_GPA);
> +       gic_init(GIC_V3, test_args.vcpu_migration_test_nr_vcpus,
> +                       (void *)GICD_BASE_GPA, (void *)GICR_BASE_GPA);
>         gic_irq_enable(PMU_IRQ);
>         local_irq_enable();
>
> @@ -1093,11 +1111,13 @@ static void guest_evtype_filter_test(void)
>
>  static void guest_vcpu_migration_test(void)
>  {
> +       int iter = test_args.vcpu_migration_test_iter;
> +
>         /*
>          * While the userspace continuously migrates this vCPU to random pCPUs,
>          * run basic PMU functionalities and verify the results.
>          */
> -       while (test_args.vcpu_migration_test_iter--)
> +       while (iter--)
>                 test_basic_pmu_functionality();
>  }
>
> @@ -1472,17 +1492,23 @@ static void run_kvm_evtype_filter_test(void)
>
>  struct vcpu_migrate_data {
>         struct vpmu_vm *vpmu_vm;
> -       pthread_t *pt_vcpu;
> -       bool vcpu_done;
> +       pthread_t *pt_vcpus;
> +       unsigned long *vcpu_done_map;
> +       pthread_mutex_t vcpu_done_map_lock;
>  };
>
> +struct vcpu_migrate_data migrate_data;
> +
>  static void *run_vcpus_migrate_test_func(void *arg)
>  {
> -       struct vcpu_migrate_data *migrate_data = arg;
> -       struct vpmu_vm *vpmu_vm = migrate_data->vpmu_vm;
> +       struct vpmu_vm *vpmu_vm = migrate_data.vpmu_vm;
> +       unsigned int vcpu_idx = (unsigned long)arg;
>
> -       run_vcpu(vpmu_vm->vcpus[0]);
> -       migrate_data->vcpu_done = true;
> +       run_vcpu(vpmu_vm->vcpus[vcpu_idx]);
> +
> +       pthread_mutex_lock(&migrate_data.vcpu_done_map_lock);
> +       __set_bit(vcpu_idx, migrate_data.vcpu_done_map);
> +       pthread_mutex_unlock(&migrate_data.vcpu_done_map_lock);
>
>         return NULL;
>  }
> @@ -1504,7 +1530,7 @@ static uint32_t get_pcpu(void)
>         return pcpu;
>  }
>
> -static int migrate_vcpu(struct vcpu_migrate_data *migrate_data)
> +static int migrate_vcpu(int vcpu_idx)
>  {
>         int ret;
>         cpu_set_t cpuset;
> @@ -1513,9 +1539,9 @@ static int migrate_vcpu(struct vcpu_migrate_data *migrate_data)
>         CPU_ZERO(&cpuset);
>         CPU_SET(new_pcpu, &cpuset);
>
> -       pr_debug("Migrating vCPU to pCPU: %u\n", new_pcpu);
> +       pr_debug("Migrating vCPU %d to pCPU: %u\n", vcpu_idx, new_pcpu);
>
> -       ret = pthread_setaffinity_np(*migrate_data->pt_vcpu, sizeof(cpuset), &cpuset);
> +       ret = pthread_setaffinity_np(migrate_data.pt_vcpus[vcpu_idx], sizeof(cpuset), &cpuset);
>
>         /* Allow the error where the vCPU thread is already finished */
>         TEST_ASSERT(ret == 0 || ret == ESRCH,
> @@ -1526,48 +1552,74 @@ static int migrate_vcpu(struct vcpu_migrate_data *migrate_data)
>
>  static void *vcpus_migrate_func(void *arg)
>  {
> -       struct vcpu_migrate_data *migrate_data = arg;
> +       struct vpmu_vm *vpmu_vm = migrate_data.vpmu_vm;
> +       int i, n_done, nr_vcpus = vpmu_vm->nr_vcpus;
> +       bool vcpu_done;
>
> -       while (!migrate_data->vcpu_done) {
> +       do {
>                 usleep(msecs_to_usecs(test_args.vcpu_migration_test_migrate_freq_ms));
> -               migrate_vcpu(migrate_data);
> -       }
> +               for (n_done = 0, i = 0; i < nr_vcpus; i++) {
> +                       pthread_mutex_lock(&migrate_data.vcpu_done_map_lock);
> +                       vcpu_done = test_bit(i, migrate_data.vcpu_done_map);
> +                       pthread_mutex_unlock(&migrate_data.vcpu_done_map_lock);

Do we need to hold the lock here ?


> +
> +                       if (vcpu_done) {
> +                               n_done++;
> +                               continue;
> +                       }
> +
> +                       migrate_vcpu(i);
> +               }
> +
> +       } while (nr_vcpus != n_done);
>
>         return NULL;
>  }
>
>  static void run_vcpu_migration_test(uint64_t pmcr_n)
>  {
> -       int ret;
> +       int i, nr_vcpus, ret;
>         struct vpmu_vm *vpmu_vm;
> -       pthread_t pt_vcpu, pt_sched;
> -       struct vcpu_migrate_data migrate_data = {
> -               .pt_vcpu = &pt_vcpu,
> -               .vcpu_done = false,
> -       };
> +       pthread_t pt_sched, *pt_vcpus;
>
>         __TEST_REQUIRE(get_nprocs() >= 2, "At least two pCPUs needed for vCPU migration test");
>
>         guest_data.test_stage = TEST_STAGE_VCPU_MIGRATION;
>         guest_data.expected_pmcr_n = pmcr_n;
>
> -       migrate_data.vpmu_vm = vpmu_vm = create_vpmu_vm(1, guest_code, NULL);
> +       nr_vcpus = test_args.vcpu_migration_test_nr_vcpus;
> +
> +       migrate_data.vcpu_done_map = bitmap_zalloc(nr_vcpus);
> +       TEST_ASSERT(migrate_data.vcpu_done_map, "Failed to create vCPU done bitmap");
> +       pthread_mutex_init(&migrate_data.vcpu_done_map_lock, NULL);
> +
> +       migrate_data.pt_vcpus = pt_vcpus = calloc(nr_vcpus, sizeof(*pt_vcpus));
> +       TEST_ASSERT(pt_vcpus, "Failed to create vCPU thread pointers");
> +
> +       migrate_data.vpmu_vm = vpmu_vm = create_vpmu_vm(nr_vcpus, guest_code, NULL);
>
>         /* Initialize random number generation for migrating vCPUs to random pCPUs */
>         srand(time(NULL));
>
> -       /* Spawn a vCPU thread */
> -       ret = pthread_create(&pt_vcpu, NULL, run_vcpus_migrate_test_func, &migrate_data);
> -       TEST_ASSERT(!ret, "Failed to create the vCPU thread");
> +       /* Spawn vCPU threads */
> +       for (i = 0; i < nr_vcpus; i++) {
> +               ret = pthread_create(&pt_vcpus[i], NULL,
> +                                       run_vcpus_migrate_test_func,  (void *)(unsigned long)i);
> +               TEST_ASSERT(!ret, "Failed to create the vCPU thread: %d", i);
> +       }
>
>         /* Spawn a scheduler thread to force-migrate vCPUs to various pCPUs */
> -       ret = pthread_create(&pt_sched, NULL, vcpus_migrate_func, &migrate_data);
> +       ret = pthread_create(&pt_sched, NULL, vcpus_migrate_func, NULL);
>         TEST_ASSERT(!ret, "Failed to create the scheduler thread for migrating the vCPUs");
>
>         pthread_join(pt_sched, NULL);
> -       pthread_join(pt_vcpu, NULL);
> +
> +       for (i = 0; i < nr_vcpus; i++)
> +               pthread_join(pt_vcpus[i], NULL);
>
>         destroy_vpmu_vm(vpmu_vm);
> +       free(pt_vcpus);
> +       bitmap_free(migrate_data.vcpu_done_map);
>  }
>
>  static void run_tests(uint64_t pmcr_n)
> @@ -1596,12 +1648,14 @@ static uint64_t get_pmcr_n_limit(void)
>
>  static void print_help(char *name)
>  {
> -       pr_info("Usage: %s [-h] [-i vcpu_migration_test_iterations] [-m vcpu_migration_freq_ms]\n",
> -               name);
> +       pr_info("Usage: %s [-h] [-i vcpu_migration_test_iterations] [-m vcpu_migration_freq_ms]"
> +               "[-n vcpu_migration_nr_vcpus]\n", name);
>         pr_info("\t-i: Number of iterations of vCPU migrations test (default: %u)\n",
>                 VCPU_MIGRATIONS_TEST_ITERS_DEF);
>         pr_info("\t-m: Frequency (in ms) of vCPUs to migrate to different pCPU. (default: %u)\n",
>                 VCPU_MIGRATIONS_TEST_MIGRATION_FREQ_MS);
> +       pr_info("\t-n: Number of vCPUs for vCPU migrations test. (default: %u)\n",
> +               VCPU_MIGRATIONS_TEST_NR_VPUS_DEF);
>         pr_info("\t-h: print this help screen\n");
>  }
>
> @@ -1609,7 +1663,7 @@ static bool parse_args(int argc, char *argv[])
>  {
>         int opt;
>
> -       while ((opt = getopt(argc, argv, "hi:m:")) != -1) {
> +       while ((opt = getopt(argc, argv, "hi:m:n:")) != -1) {
>                 switch (opt) {
>                 case 'i':
>                         test_args.vcpu_migration_test_iter =
> @@ -1619,6 +1673,14 @@ static bool parse_args(int argc, char *argv[])
>                         test_args.vcpu_migration_test_migrate_freq_ms =
>                                 atoi_positive("vCPU migration frequency", optarg);
>                         break;
> +               case 'n':
> +                       test_args.vcpu_migration_test_nr_vcpus =
> +                               atoi_positive("Nr vCPUs for vCPU migrations", optarg);
> +                       if (test_args.vcpu_migration_test_nr_vcpus > KVM_MAX_VCPUS) {
> +                               pr_info("Max allowed vCPUs: %u\n", KVM_MAX_VCPUS);
> +                               goto err;
> +                       }
> +                       break;
>                 case 'h':
>                 default:
>                         goto err;
> --
> 2.39.1.581.gbfd45094c4-goog
>
>

Thanks,
Reiji



More information about the linux-arm-kernel mailing list