Implement a stress test for KVM by frequently force-migrating the
vCPU to random pCPUs in the system. This would validate the
save/restore functionality of KVM and starting/stopping of
PMU counters as necessary.
Signed-off-by: Raghavendra Rao Ananta <rananta@google.com>
---
.../testing/selftests/kvm/aarch64/vpmu_test.c | 195 +++++++++++++++++-
1 file changed, 193 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/aarch64/vpmu_test.c b/tools/testing/selftests/kvm/aarch64/vpmu_test.c
index 5c166df245589..0c9d801f4e602 100644
--- a/tools/testing/selftests/kvm/aarch64/vpmu_test.c
+++ b/tools/testing/selftests/kvm/aarch64/vpmu_test.c
@@ -19,9 +19,15 @@
* 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.
+ *
* Copyright (c) 2022 Google LLC.
*
*/
+#define _GNU_SOURCE
+
#include <kvm_util.h>
#include <processor.h>
#include <test_util.h>
@@ -30,6 +36,11 @@
#include <linux/arm-smccc.h>
#include <linux/bitfield.h>
#include <linux/bitmap.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <sys/sysinfo.h>
+
+#include "delay.h"
/* The max number of the PMU event counters (excluding the cycle counter) */
#define ARMV8_PMU_MAX_GENERAL_COUNTERS (ARMV8_PMU_MAX_COUNTERS - 1)
@@ -37,6 +48,8 @@
/* The max number of event numbers that's supported */
#define ARMV8_PMU_MAX_EVENTS 64
+#define msecs_to_usecs(msec) ((msec) * 1000LL)
+
/*
* The macros and functions below for reading/writing PMEV{CNTR,TYPER}<n>_EL0
* were basically copied from arch/arm64/kernel/perf_event.c.
@@ -265,6 +278,7 @@ enum test_stage {
TEST_STAGE_COUNTER_ACCESS = 1,
TEST_STAGE_KVM_EVENT_FILTER,
TEST_STAGE_KVM_EVTYPE_FILTER,
+ TEST_STAGE_VCPU_MIGRATION,
};
struct guest_data {
@@ -275,6 +289,19 @@ struct guest_data {
static struct guest_data guest_data;
+#define VCPU_MIGRATIONS_TEST_ITERS_DEF 1000
+#define VCPU_MIGRATIONS_TEST_MIGRATION_FREQ_MS 2
+
+struct test_args {
+ int vcpu_migration_test_iter;
+ int vcpu_migration_test_migrate_freq_ms;
+};
+
+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,
+};
+
static void guest_sync_handler(struct ex_regs *regs)
{
uint64_t esr, ec;
@@ -352,7 +379,6 @@ static bool pmu_event_is_supported(uint64_t event)
GUEST_ASSERT_3(!(_tval & mask), _tval, mask, set_expected);\
}
-
/*
* Extra instructions inserted by the compiler would be difficult to compensate
* for, so hand assemble everything between, and including, the PMCR accesses
@@ -459,6 +485,13 @@ static void test_event_count(uint64_t event, int pmc_idx, bool expect_count)
}
}
+static void test_basic_pmu_functionality(void)
+{
+ /* Test events on generic and cycle counters */
+ test_instructions_count(0, true);
+ test_cycles_count(true);
+}
+
/*
* Check if @mask bits in {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers
* are set or cleared as specified in @set_expected.
@@ -748,6 +781,16 @@ static void guest_evtype_filter_test(void)
GUEST_ASSERT_2(cnt == 0, cnt, typer);
}
+static void guest_vcpu_migration_test(void)
+{
+ /*
+ * 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--)
+ test_basic_pmu_functionality();
+}
+
static void guest_code(void)
{
switch (guest_data.test_stage) {
@@ -760,6 +803,9 @@ static void guest_code(void)
case TEST_STAGE_KVM_EVTYPE_FILTER:
guest_evtype_filter_test();
break;
+ case TEST_STAGE_VCPU_MIGRATION:
+ guest_vcpu_migration_test();
+ break;
default:
GUEST_ASSERT_1(0, guest_data.test_stage);
}
@@ -837,6 +883,7 @@ create_vpmu_vm(void *guest_code, struct kvm_pmu_event_filter *pmu_event_filters)
vpmu_vm->vm = vm = vm_create(1);
vm_init_descriptor_tables(vm);
+
/* Catch exceptions for easier debugging */
for (ec = 0; ec < ESR_EC_NUM; ec++) {
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ec,
@@ -881,6 +928,8 @@ static void run_vcpu(struct kvm_vcpu *vcpu)
struct ucall uc;
sync_global_to_guest(vcpu->vm, guest_data);
+ sync_global_to_guest(vcpu->vm, test_args);
+
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
@@ -1098,11 +1147,112 @@ static void run_kvm_evtype_filter_test(void)
destroy_vpmu_vm(vpmu_vm);
}
+struct vcpu_migrate_data {
+ struct vpmu_vm *vpmu_vm;
+ pthread_t *pt_vcpu;
+ bool vcpu_done;
+};
+
+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;
+
+ run_vcpu(vpmu_vm->vcpu);
+ migrate_data->vcpu_done = true;
+
+ return NULL;
+}
+
+static uint32_t 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 the vCPU on */
+ do {
+ pcpu = rand() % nproc_conf;
+ } while (!CPU_ISSET(pcpu, &online_cpuset));
+
+ return pcpu;
+}
+
+static int migrate_vcpu(struct vcpu_migrate_data *migrate_data)
+{
+ int ret;
+ cpu_set_t cpuset;
+ uint32_t new_pcpu = get_pcpu();
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(new_pcpu, &cpuset);
+
+ pr_debug("Migrating vCPU to pCPU: %u\n", new_pcpu);
+
+ ret = pthread_setaffinity_np(*migrate_data->pt_vcpu, sizeof(cpuset), &cpuset);
+
+ /* Allow the error where the vCPU thread is already finished */
+ TEST_ASSERT(ret == 0 || ret == ESRCH,
+ "Failed to migrate the vCPU to pCPU: %u; ret: %d\n", new_pcpu, ret);
+
+ return ret;
+}
+
+static void *vcpus_migrate_func(void *arg)
+{
+ struct vcpu_migrate_data *migrate_data = arg;
+
+ while (!migrate_data->vcpu_done) {
+ usleep(msecs_to_usecs(test_args.vcpu_migration_test_migrate_freq_ms));
+ migrate_vcpu(migrate_data);
+ }
+
+ return NULL;
+}
+
+static void run_vcpu_migration_test(uint64_t pmcr_n)
+{
+ int 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,
+ };
+
+ __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(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 a scheduler thread to force-migrate vCPUs to various pCPUs */
+ ret = pthread_create(&pt_sched, NULL, vcpus_migrate_func, &migrate_data);
+ TEST_ASSERT(!ret, "Failed to create the scheduler thread for migrating the vCPUs");
+
+ pthread_join(pt_sched, NULL);
+ pthread_join(pt_vcpu, NULL);
+
+ destroy_vpmu_vm(vpmu_vm);
+}
+
static void run_tests(uint64_t pmcr_n)
{
run_counter_access_tests(pmcr_n);
run_kvm_event_filter_test();
run_kvm_evtype_filter_test();
+ run_vcpu_migration_test(pmcr_n);
}
/*
@@ -1121,12 +1271,53 @@ static uint64_t get_pmcr_n_limit(void)
return FIELD_GET(ARMV8_PMU_PMCR_N, pmcr);
}
-int main(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("\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-h: print this help screen\n");
+}
+
+static bool parse_args(int argc, char *argv[])
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "hi:m:")) != -1) {
+ switch (opt) {
+ case 'i':
+ test_args.vcpu_migration_test_iter =
+ atoi_positive("Nr vCPU migration iterations", optarg);
+ break;
+ case 'm':
+ test_args.vcpu_migration_test_migrate_freq_ms =
+ atoi_positive("vCPU migration frequency", optarg);
+ break;
+ case 'h':
+ default:
+ goto err;
+ }
+ }
+
+ return true;
+
+err:
+ print_help(argv[0]);
+ return false;
+}
+
+int main(int argc, char *argv[])
{
uint64_t pmcr_n;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
+ if (!parse_args(argc, argv))
+ exit(KSFT_SKIP);
+
pmcr_n = get_pmcr_n_limit();
run_tests(pmcr_n);
--
2.39.1.581.gbfd45094c4-goog
Hi Raghu, On Tue, Feb 14, 2023 at 5:07 PM Raghavendra Rao Ananta <rananta@google.com> wrote: > > Implement a stress test for KVM by frequently force-migrating the > vCPU to random pCPUs in the system. This would validate the > save/restore functionality of KVM and starting/stopping of > PMU counters as necessary. > > Signed-off-by: Raghavendra Rao Ananta <rananta@google.com> > --- > .../testing/selftests/kvm/aarch64/vpmu_test.c | 195 +++++++++++++++++- > 1 file changed, 193 insertions(+), 2 deletions(-) > > diff --git a/tools/testing/selftests/kvm/aarch64/vpmu_test.c b/tools/testing/selftests/kvm/aarch64/vpmu_test.c > index 5c166df245589..0c9d801f4e602 100644 > --- a/tools/testing/selftests/kvm/aarch64/vpmu_test.c > +++ b/tools/testing/selftests/kvm/aarch64/vpmu_test.c > @@ -19,9 +19,15 @@ > * 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. > + * > * Copyright (c) 2022 Google LLC. > * > */ > +#define _GNU_SOURCE > + > #include <kvm_util.h> > #include <processor.h> > #include <test_util.h> > @@ -30,6 +36,11 @@ > #include <linux/arm-smccc.h> > #include <linux/bitfield.h> > #include <linux/bitmap.h> > +#include <stdlib.h> > +#include <pthread.h> > +#include <sys/sysinfo.h> > + > +#include "delay.h" > > /* The max number of the PMU event counters (excluding the cycle counter) */ > #define ARMV8_PMU_MAX_GENERAL_COUNTERS (ARMV8_PMU_MAX_COUNTERS - 1) > @@ -37,6 +48,8 @@ > /* The max number of event numbers that's supported */ > #define ARMV8_PMU_MAX_EVENTS 64 > > +#define msecs_to_usecs(msec) ((msec) * 1000LL) > + > /* > * The macros and functions below for reading/writing PMEV{CNTR,TYPER}<n>_EL0 > * were basically copied from arch/arm64/kernel/perf_event.c. > @@ -265,6 +278,7 @@ enum test_stage { > TEST_STAGE_COUNTER_ACCESS = 1, > TEST_STAGE_KVM_EVENT_FILTER, > TEST_STAGE_KVM_EVTYPE_FILTER, > + TEST_STAGE_VCPU_MIGRATION, > }; > > struct guest_data { > @@ -275,6 +289,19 @@ struct guest_data { > > static struct guest_data guest_data; > > +#define VCPU_MIGRATIONS_TEST_ITERS_DEF 1000 > +#define VCPU_MIGRATIONS_TEST_MIGRATION_FREQ_MS 2 > + > +struct test_args { > + int vcpu_migration_test_iter; > + int vcpu_migration_test_migrate_freq_ms; > +}; > + > +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, > +}; > + > static void guest_sync_handler(struct ex_regs *regs) > { > uint64_t esr, ec; > @@ -352,7 +379,6 @@ static bool pmu_event_is_supported(uint64_t event) > GUEST_ASSERT_3(!(_tval & mask), _tval, mask, set_expected);\ > } > > - > /* > * Extra instructions inserted by the compiler would be difficult to compensate > * for, so hand assemble everything between, and including, the PMCR accesses > @@ -459,6 +485,13 @@ static void test_event_count(uint64_t event, int pmc_idx, bool expect_count) > } > } > > +static void test_basic_pmu_functionality(void) > +{ > + /* Test events on generic and cycle counters */ > + test_instructions_count(0, true); > + test_cycles_count(true); > +} > + > /* > * Check if @mask bits in {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers > * are set or cleared as specified in @set_expected. > @@ -748,6 +781,16 @@ static void guest_evtype_filter_test(void) > GUEST_ASSERT_2(cnt == 0, cnt, typer); > } > > +static void guest_vcpu_migration_test(void) > +{ > + /* > + * 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--) > + test_basic_pmu_functionality(); > +} > + > static void guest_code(void) > { > switch (guest_data.test_stage) { > @@ -760,6 +803,9 @@ static void guest_code(void) > case TEST_STAGE_KVM_EVTYPE_FILTER: > guest_evtype_filter_test(); > break; > + case TEST_STAGE_VCPU_MIGRATION: > + guest_vcpu_migration_test(); > + break; > default: > GUEST_ASSERT_1(0, guest_data.test_stage); > } > @@ -837,6 +883,7 @@ create_vpmu_vm(void *guest_code, struct kvm_pmu_event_filter *pmu_event_filters) > > vpmu_vm->vm = vm = vm_create(1); > vm_init_descriptor_tables(vm); > + > /* Catch exceptions for easier debugging */ > for (ec = 0; ec < ESR_EC_NUM; ec++) { > vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ec, > @@ -881,6 +928,8 @@ static void run_vcpu(struct kvm_vcpu *vcpu) > struct ucall uc; > > sync_global_to_guest(vcpu->vm, guest_data); > + sync_global_to_guest(vcpu->vm, test_args); > + > vcpu_run(vcpu); > switch (get_ucall(vcpu, &uc)) { > case UCALL_ABORT: > @@ -1098,11 +1147,112 @@ static void run_kvm_evtype_filter_test(void) > destroy_vpmu_vm(vpmu_vm); > } > > +struct vcpu_migrate_data { > + struct vpmu_vm *vpmu_vm; > + pthread_t *pt_vcpu; Nit: Originally, I wasn't sure what 'pt' stands for. Also, the 'pt_vcpu' made me think this would be a pointer to a vCPU. Perhaps renaming this to 'vcpu_pthread' might be more clear ? > + bool vcpu_done; > +}; > + > +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; > + > + run_vcpu(vpmu_vm->vcpu); > + migrate_data->vcpu_done = true; > + > + return NULL; > +} > + > +static uint32_t 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 the vCPU on */ > + do { > + pcpu = rand() % nproc_conf; > + } while (!CPU_ISSET(pcpu, &online_cpuset)); > + > + return pcpu; > +} > + > +static int migrate_vcpu(struct vcpu_migrate_data *migrate_data) Nit: You might want to pass a pthread_t rather than migrate_data unless the function uses some more fields of the data in the following patches. > +{ > + int ret; > + cpu_set_t cpuset; > + uint32_t new_pcpu = get_pcpu(); > + > + CPU_ZERO(&cpuset); > + CPU_SET(new_pcpu, &cpuset); > + > + pr_debug("Migrating vCPU to pCPU: %u\n", new_pcpu); > + > + ret = pthread_setaffinity_np(*migrate_data->pt_vcpu, sizeof(cpuset), &cpuset); > + > + /* Allow the error where the vCPU thread is already finished */ > + TEST_ASSERT(ret == 0 || ret == ESRCH, > + "Failed to migrate the vCPU to pCPU: %u; ret: %d\n", new_pcpu, ret); > + > + return ret; > +} > + > +static void *vcpus_migrate_func(void *arg) > +{ > + struct vcpu_migrate_data *migrate_data = arg; > + > + while (!migrate_data->vcpu_done) { > + usleep(msecs_to_usecs(test_args.vcpu_migration_test_migrate_freq_ms)); > + migrate_vcpu(migrate_data); > + } > + > + return NULL; > +} > + > +static void run_vcpu_migration_test(uint64_t pmcr_n) > +{ > + int 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, > + }; > + > + __TEST_REQUIRE(get_nprocs() >= 2, "At least two pCPUs needed for vCPU migration test"); Considering that get_pcpu() chooses the target CPU from CPUs returned from sched_getaffinity(), I would think the test should use the number of the bits set in the returned cpu_set_t from sched_getaffinity() here instead of get_nprocs(), as those numbers could be different (e.g. if the test runs with taskset with a subset of the CPUs on the system). > + > + guest_data.test_stage = TEST_STAGE_VCPU_MIGRATION; > + guest_data.expected_pmcr_n = pmcr_n; > + > + migrate_data.vpmu_vm = vpmu_vm = create_vpmu_vm(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 a scheduler thread to force-migrate vCPUs to various pCPUs */ > + ret = pthread_create(&pt_sched, NULL, vcpus_migrate_func, &migrate_data); Why do you want to spawn another thread to run vcpus_migrate_func(), rather than calling that from the current thread ? > + TEST_ASSERT(!ret, "Failed to create the scheduler thread for migrating the vCPUs"); > + > + pthread_join(pt_sched, NULL); > + pthread_join(pt_vcpu, NULL); > + > + destroy_vpmu_vm(vpmu_vm); > +} > + > static void run_tests(uint64_t pmcr_n) > { > run_counter_access_tests(pmcr_n); > run_kvm_event_filter_test(); > run_kvm_evtype_filter_test(); > + run_vcpu_migration_test(pmcr_n); > } > > /* > @@ -1121,12 +1271,53 @@ static uint64_t get_pmcr_n_limit(void) > return FIELD_GET(ARMV8_PMU_PMCR_N, pmcr); > } > > -int main(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("\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-h: print this help screen\n"); > +} > + > +static bool parse_args(int argc, char *argv[]) > +{ > + int opt; > + > + while ((opt = getopt(argc, argv, "hi:m:")) != -1) { > + switch (opt) { > + case 'i': > + test_args.vcpu_migration_test_iter = > + atoi_positive("Nr vCPU migration iterations", optarg); > + break; > + case 'm': > + test_args.vcpu_migration_test_migrate_freq_ms = > + atoi_positive("vCPU migration frequency", optarg); > + break; > + case 'h': > + default: > + goto err; > + } > + } > + > + return true; > + > +err: > + print_help(argv[0]); > + return false; > +} > + > +int main(int argc, char *argv[]) > { > uint64_t pmcr_n; > > TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3)); > > + if (!parse_args(argc, argv)) > + exit(KSFT_SKIP); > + > pmcr_n = get_pmcr_n_limit(); > run_tests(pmcr_n); > > -- > 2.39.1.581.gbfd45094c4-goog > Thanks, Reiji
On Mon, Mar 6, 2023 at 7:44 PM Reiji Watanabe <reijiw@google.com> wrote: > > Hi Raghu, > > On Tue, Feb 14, 2023 at 5:07 PM Raghavendra Rao Ananta > <rananta@google.com> wrote: > > > > Implement a stress test for KVM by frequently force-migrating the > > vCPU to random pCPUs in the system. This would validate the > > save/restore functionality of KVM and starting/stopping of > > PMU counters as necessary. > > > > Signed-off-by: Raghavendra Rao Ananta <rananta@google.com> > > --- > > .../testing/selftests/kvm/aarch64/vpmu_test.c | 195 +++++++++++++++++- > > 1 file changed, 193 insertions(+), 2 deletions(-) > > > > diff --git a/tools/testing/selftests/kvm/aarch64/vpmu_test.c b/tools/testing/selftests/kvm/aarch64/vpmu_test.c > > index 5c166df245589..0c9d801f4e602 100644 > > --- a/tools/testing/selftests/kvm/aarch64/vpmu_test.c > > +++ b/tools/testing/selftests/kvm/aarch64/vpmu_test.c > > @@ -19,9 +19,15 @@ > > * 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. > > + * > > * Copyright (c) 2022 Google LLC. > > * > > */ > > +#define _GNU_SOURCE > > + > > #include <kvm_util.h> > > #include <processor.h> > > #include <test_util.h> > > @@ -30,6 +36,11 @@ > > #include <linux/arm-smccc.h> > > #include <linux/bitfield.h> > > #include <linux/bitmap.h> > > +#include <stdlib.h> > > +#include <pthread.h> > > +#include <sys/sysinfo.h> > > + > > +#include "delay.h" > > > > /* The max number of the PMU event counters (excluding the cycle counter) */ > > #define ARMV8_PMU_MAX_GENERAL_COUNTERS (ARMV8_PMU_MAX_COUNTERS - 1) > > @@ -37,6 +48,8 @@ > > /* The max number of event numbers that's supported */ > > #define ARMV8_PMU_MAX_EVENTS 64 > > > > +#define msecs_to_usecs(msec) ((msec) * 1000LL) > > + > > /* > > * The macros and functions below for reading/writing PMEV{CNTR,TYPER}<n>_EL0 > > * were basically copied from arch/arm64/kernel/perf_event.c. > > @@ -265,6 +278,7 @@ enum test_stage { > > TEST_STAGE_COUNTER_ACCESS = 1, > > TEST_STAGE_KVM_EVENT_FILTER, > > TEST_STAGE_KVM_EVTYPE_FILTER, > > + TEST_STAGE_VCPU_MIGRATION, > > }; > > > > struct guest_data { > > @@ -275,6 +289,19 @@ struct guest_data { > > > > static struct guest_data guest_data; > > > > +#define VCPU_MIGRATIONS_TEST_ITERS_DEF 1000 > > +#define VCPU_MIGRATIONS_TEST_MIGRATION_FREQ_MS 2 > > + > > +struct test_args { > > + int vcpu_migration_test_iter; > > + int vcpu_migration_test_migrate_freq_ms; > > +}; > > + > > +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, > > +}; > > + > > static void guest_sync_handler(struct ex_regs *regs) > > { > > uint64_t esr, ec; > > @@ -352,7 +379,6 @@ static bool pmu_event_is_supported(uint64_t event) > > GUEST_ASSERT_3(!(_tval & mask), _tval, mask, set_expected);\ > > } > > > > - > > /* > > * Extra instructions inserted by the compiler would be difficult to compensate > > * for, so hand assemble everything between, and including, the PMCR accesses > > @@ -459,6 +485,13 @@ static void test_event_count(uint64_t event, int pmc_idx, bool expect_count) > > } > > } > > > > +static void test_basic_pmu_functionality(void) > > +{ > > + /* Test events on generic and cycle counters */ > > + test_instructions_count(0, true); > > + test_cycles_count(true); > > +} > > + > > /* > > * Check if @mask bits in {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers > > * are set or cleared as specified in @set_expected. > > @@ -748,6 +781,16 @@ static void guest_evtype_filter_test(void) > > GUEST_ASSERT_2(cnt == 0, cnt, typer); > > } > > > > +static void guest_vcpu_migration_test(void) > > +{ > > + /* > > + * 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--) > > + test_basic_pmu_functionality(); > > +} > > + > > static void guest_code(void) > > { > > switch (guest_data.test_stage) { > > @@ -760,6 +803,9 @@ static void guest_code(void) > > case TEST_STAGE_KVM_EVTYPE_FILTER: > > guest_evtype_filter_test(); > > break; > > + case TEST_STAGE_VCPU_MIGRATION: > > + guest_vcpu_migration_test(); > > + break; > > default: > > GUEST_ASSERT_1(0, guest_data.test_stage); > > } > > @@ -837,6 +883,7 @@ create_vpmu_vm(void *guest_code, struct kvm_pmu_event_filter *pmu_event_filters) > > > > vpmu_vm->vm = vm = vm_create(1); > > vm_init_descriptor_tables(vm); > > + > > /* Catch exceptions for easier debugging */ > > for (ec = 0; ec < ESR_EC_NUM; ec++) { > > vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ec, > > @@ -881,6 +928,8 @@ static void run_vcpu(struct kvm_vcpu *vcpu) > > struct ucall uc; > > > > sync_global_to_guest(vcpu->vm, guest_data); > > + sync_global_to_guest(vcpu->vm, test_args); > > + > > vcpu_run(vcpu); > > switch (get_ucall(vcpu, &uc)) { > > case UCALL_ABORT: > > @@ -1098,11 +1147,112 @@ static void run_kvm_evtype_filter_test(void) > > destroy_vpmu_vm(vpmu_vm); > > } > > > > +struct vcpu_migrate_data { > > + struct vpmu_vm *vpmu_vm; > > + pthread_t *pt_vcpu; > > Nit: Originally, I wasn't sure what 'pt' stands for. > Also, the 'pt_vcpu' made me think this would be a pointer to a vCPU. > Perhaps renaming this to 'vcpu_pthread' might be more clear ? > Haha, no problem. I'll change it to vcpu_pthread. > > > + bool vcpu_done; > > +}; > > + > > +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; > > + > > + run_vcpu(vpmu_vm->vcpu); > > + migrate_data->vcpu_done = true; > > + > > + return NULL; > > +} > > + > > +static uint32_t 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 the vCPU on */ > > + do { > > + pcpu = rand() % nproc_conf; > > + } while (!CPU_ISSET(pcpu, &online_cpuset)); > > + > > + return pcpu; > > +} > > + > > +static int migrate_vcpu(struct vcpu_migrate_data *migrate_data) > > Nit: You might want to pass a pthread_t rather than migrate_data > unless the function uses some more fields of the data in the > following patches. > The upcoming patch, which introduces multiple-vcpus, moves migrate_date into a global array (one element per-vCPU). That patch passes only the vcpu index as an arg to migrate_vcpu(). I originally thought we would embed more stuff into migrate_data, and passed this. But I guess I can just pass pthread_t. > > +{ > > + int ret; > > + cpu_set_t cpuset; > > + uint32_t new_pcpu = get_pcpu(); > > + > > + CPU_ZERO(&cpuset); > > + CPU_SET(new_pcpu, &cpuset); > > + > > + pr_debug("Migrating vCPU to pCPU: %u\n", new_pcpu); > > + > > + ret = pthread_setaffinity_np(*migrate_data->pt_vcpu, sizeof(cpuset), &cpuset); > > + > > + /* Allow the error where the vCPU thread is already finished */ > > + TEST_ASSERT(ret == 0 || ret == ESRCH, > > + "Failed to migrate the vCPU to pCPU: %u; ret: %d\n", new_pcpu, ret); > > + > > + return ret; > > +} > > + > > +static void *vcpus_migrate_func(void *arg) > > +{ > > + struct vcpu_migrate_data *migrate_data = arg; > > + > > + while (!migrate_data->vcpu_done) { > > + usleep(msecs_to_usecs(test_args.vcpu_migration_test_migrate_freq_ms)); > > + migrate_vcpu(migrate_data); > > + } > > + > > + return NULL; > > +} > > + > > +static void run_vcpu_migration_test(uint64_t pmcr_n) > > +{ > > + int 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, > > + }; > > + > > + __TEST_REQUIRE(get_nprocs() >= 2, "At least two pCPUs needed for vCPU migration test"); > > Considering that get_pcpu() chooses the target CPU from CPUs returned > from sched_getaffinity(), I would think the test should use the number of > the bits set in the returned cpu_set_t from sched_getaffinity() here > instead of get_nprocs(), as those numbers could be different (e.g. if the > test runs with taskset with a subset of the CPUs on the system). > I'm not familiar with tasksets, but if you feel the current approach could cause problems, I'll switch to your suggestion. Thanks. > > > + > > + guest_data.test_stage = TEST_STAGE_VCPU_MIGRATION; > > + guest_data.expected_pmcr_n = pmcr_n; > > + > > + migrate_data.vpmu_vm = vpmu_vm = create_vpmu_vm(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 a scheduler thread to force-migrate vCPUs to various pCPUs */ > > + ret = pthread_create(&pt_sched, NULL, vcpus_migrate_func, &migrate_data); > > Why do you want to spawn another thread to run vcpus_migrate_func(), > rather than calling that from the current thread ? > > I suppose it should be fine calling from the current thread (unless I'm forgetting a reason why I had a similar behavior in arch_timer test). Thank you. Raghavendra > > + TEST_ASSERT(!ret, "Failed to create the scheduler thread for migrating the vCPUs"); > > + > > + pthread_join(pt_sched, NULL); > > + pthread_join(pt_vcpu, NULL); > > + > > + destroy_vpmu_vm(vpmu_vm); > > +} > > + > > static void run_tests(uint64_t pmcr_n) > > { > > run_counter_access_tests(pmcr_n); > > run_kvm_event_filter_test(); > > run_kvm_evtype_filter_test(); > > + run_vcpu_migration_test(pmcr_n); > > } > > > > /* > > @@ -1121,12 +1271,53 @@ static uint64_t get_pmcr_n_limit(void) > > return FIELD_GET(ARMV8_PMU_PMCR_N, pmcr); > > } > > > > -int main(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("\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-h: print this help screen\n"); > > +} > > + > > +static bool parse_args(int argc, char *argv[]) > > +{ > > + int opt; > > + > > + while ((opt = getopt(argc, argv, "hi:m:")) != -1) { > > + switch (opt) { > > + case 'i': > > + test_args.vcpu_migration_test_iter = > > + atoi_positive("Nr vCPU migration iterations", optarg); > > + break; > > + case 'm': > > + test_args.vcpu_migration_test_migrate_freq_ms = > > + atoi_positive("vCPU migration frequency", optarg); > > + break; > > + case 'h': > > + default: > > + goto err; > > + } > > + } > > + > > + return true; > > + > > +err: > > + print_help(argv[0]); > > + return false; > > +} > > + > > +int main(int argc, char *argv[]) > > { > > uint64_t pmcr_n; > > > > TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3)); > > > > + if (!parse_args(argc, argv)) > > + exit(KSFT_SKIP); > > + > > pmcr_n = get_pmcr_n_limit(); > > run_tests(pmcr_n); > > > > -- > > 2.39.1.581.gbfd45094c4-goog > > > > Thanks, > Reiji
© 2016 - 2025 Red Hat, Inc.