Hi Raghu, On Tue, Feb 14, 2023 at 5:07 PM Raghavendra Rao Ananta <rananta@xxxxxxxxxx> 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@xxxxxxxxxx> > --- > .../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