* [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test
@ 2026-10-09 13:00 Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 1/5] KVM: selftests: memstress: Add struct memstress_params, make memstress_create_vm() use it Leonardo Bras
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Leonardo Bras @ 2026-10-09 13:00 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Shuah Khan, David Matlack,
Yosry Ahmed, Marc Zyngier, Fuad Tabba, Oliver Upton,
Leonardo Bras, Ackerley Tng, Josh Hilke, Wu Fei, Steffen Eiden,
Claudio Imbrenda
Cc: kvm, linux-kselftest, linux-kernel
Add support to dirty-ring on dirty_log_perf_test, so we can measure
performance differences when we change / improve the mechanism.
One big change in this set is that dirty-ring needs to be enabled
after the VM was created, but before any cpu is created, so for that,
I made a few changes on memstress so it can actually take dirty-ring
size as a parameter for VM creation. (Patches #1 & #2)
Also added some checks in dirty-ring enable, so an error message on
ring size is more informative.
Please provide feedback :)
Thanks!
Leo
Changes since v1:
- Converted memstress to take a memstress_params struct instead of a lot of
parameters, so it could be more easily extended in the future (Sean)
- Moved dirty-ring cap detection to a helper, so it could be used instead
of open-coded options (Sean)
- Improved the error messages in case the dirty-ring fails to setup, with
a more specific message on what went wrong (Sean)
Link: https://lore.kernel.org/all/20260929113711.2064390-1-leo.bras@arm.com/
Changes since RFCv4:
- Series title
- Rebase on v7.3-rc5
Link: https://lore.kernel.org/all/20260729161519.3778839-2-leo.bras@arm.com/
- Using acquire/release barriers to make sure there is no reorder between
incrementing iteration and summing the time spent on cleaning.
- Improved ring-size checks, although not perfect due to reserved entries
affecting minimum size.
- Do not allocate bitmaps for dirty-ring tests, as they are unused.
- Improve help text
- Make sure ret==0 before going back to vcpu_run in dirty-ring case
Link: https://lore.kernel.org/all/20260708152232.2568907-1-leo.bras@arm.com/
Changes since RFCv2:
- Added mutex to make sure only a single vcpu is
collecting/cleaning at a time. This gets us the ability to
test return values, and not account for kvm.slot_lock waiting time.
- Stuff reported by Sashiko
- Return value always positive, so assert was pointless
- Concurrency inflating the time for dirty-ring cleaning
Link: https://lore.kernel.org/all/20260629105950.1790259-1-leo.bras@arm.com/
Changes since RFCv1:
- Stuff reported by Sashiko
- Fixed elements / byte size wrapping issue
- Removed element count due to concurrency
- Testing exit_reason instead of ioctl return value
- Some nits
Link: https://lore.kernel.org/all/20260624171656.1737580-1-leo.bras@arm.com/
Leonardo Bras (5):
KVM: selftests: memstress: Add struct memstress_params, make
memstress_create_vm() use it
KVM: selftests: memstress: Add option to enable dirty-ring on VM
creation
KVM: selftests: Introduce kvm_get_dirty_ring_cap()
KVM: selftests: Check dirty-ring size before enabling
KVM: selftests: dirty_log_perf_test: Add dirty-ring support
.../testing/selftests/kvm/include/kvm_util.h | 11 ++
.../testing/selftests/kvm/include/memstress.h | 15 +-
.../selftests/kvm/access_tracking_perf_test.c | 12 +-
.../selftests/kvm/demand_paging_test.c | 12 +-
.../selftests/kvm/dirty_log_perf_test.c | 140 ++++++++++++++++--
tools/testing/selftests/kvm/dirty_log_test.c | 3 +-
tools/testing/selftests/kvm/lib/kvm_util.c | 20 ++-
tools/testing/selftests/kvm/lib/memstress.c | 68 +++++----
.../kvm/memslot_modification_stress_test.c | 14 +-
.../kvm/x86/dirty_log_page_splitting_test.c | 12 +-
10 files changed, 247 insertions(+), 60 deletions(-)
base-commit: a90ee4305c4a5df72c11b31dacfdc76e00fcf78a
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/5] KVM: selftests: memstress: Add struct memstress_params, make memstress_create_vm() use it
2026-10-09 13:00 [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test Leonardo Bras
@ 2026-10-09 13:00 ` Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 2/5] KVM: selftests: memstress: Add option to enable dirty-ring on VM creation Leonardo Bras
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Leonardo Bras @ 2026-10-09 13:00 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Shuah Khan, David Matlack,
Yosry Ahmed, Marc Zyngier, Fuad Tabba, Oliver Upton,
Leonardo Bras, Ackerley Tng, Josh Hilke, Wu Fei, Steffen Eiden,
Claudio Imbrenda
Cc: kvm, linux-kselftest, linux-kernel
memstress_create_vm() already takes 6 parameters, and adding an extra one
for a new feature doesn't look a good idea.
Introduce "struct memstress_params" which contains all it's current
parameters and change memstress_create_vm() to take only that struct as a
parameter. This will be helpful for adding new features in the future, as
they can be introduced in the struct and only set when used, not requiring
to rework every user.
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Leonardo Bras <leo.bras@arm.com>
---
.../testing/selftests/kvm/include/memstress.h | 14 ++++--
.../selftests/kvm/access_tracking_perf_test.c | 12 ++++-
.../selftests/kvm/demand_paging_test.c | 12 ++++-
.../selftests/kvm/dirty_log_perf_test.c | 13 ++++--
tools/testing/selftests/kvm/lib/memstress.c | 45 ++++++++++---------
.../kvm/memslot_modification_stress_test.c | 14 ++++--
.../kvm/x86/dirty_log_page_splitting_test.c | 12 ++++-
7 files changed, 84 insertions(+), 38 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/memstress.h b/tools/testing/selftests/kvm/include/memstress.h
index 0d1d6230cc05..cea2062e1dcb 100644
--- a/tools/testing/selftests/kvm/include/memstress.h
+++ b/tools/testing/selftests/kvm/include/memstress.h
@@ -46,26 +46,32 @@ struct memstress_args {
bool pin_vcpus;
/* The vCPU=>pCPU pinning map. Only valid if pin_vcpus is true. */
u32 vcpu_to_pcpu[KVM_MAX_VCPUS];
/* Test is done, stop running vCPUs. */
bool stop_vcpus;
struct memstress_vcpu_args vcpu_args[KVM_MAX_VCPUS];
};
+struct memstress_params {
+ enum vm_guest_mode mode;
+ int nr_vcpus;
+ u64 vcpu_memory_bytes;
+ int slots;
+ enum vm_mem_backing_src_type backing_src;
+ bool partition_vcpu_memory_access;
+};
+
extern struct memstress_args memstress_args;
-struct kvm_vm *memstress_create_vm(enum vm_guest_mode mode, int nr_vcpus,
- u64 vcpu_memory_bytes, int slots,
- enum vm_mem_backing_src_type backing_src,
- bool partition_vcpu_memory_access);
+struct kvm_vm *memstress_create_vm(struct memstress_params *params);
void memstress_destroy_vm(struct kvm_vm *vm);
void memstress_set_write_percent(struct kvm_vm *vm, u32 write_percent);
void memstress_set_random_access(struct kvm_vm *vm, bool random_access);
void memstress_start_vcpu_threads(int vcpus, void (*vcpu_fn)(struct memstress_vcpu_args *));
void memstress_join_vcpu_threads(int vcpus);
void memstress_guest_code(u32 vcpu_id);
u64 memstress_nested_pages(int nr_vcpus);
diff --git a/tools/testing/selftests/kvm/access_tracking_perf_test.c b/tools/testing/selftests/kvm/access_tracking_perf_test.c
index 4415c94b2866..2e262a1982eb 100644
--- a/tools/testing/selftests/kvm/access_tracking_perf_test.c
+++ b/tools/testing/selftests/kvm/access_tracking_perf_test.c
@@ -401,22 +401,30 @@ static void mark_memory_idle(struct kvm_vm *vm, int nr_vcpus)
iteration_work = ITERATION_MARK_IDLE;
run_iteration(vm, nr_vcpus, "Mark memory idle (page_idle)");
}
static void run_test(enum vm_guest_mode mode, void *arg)
{
struct test_params *params = arg;
struct kvm_vm *vm;
int nr_vcpus = params->nr_vcpus;
- vm = memstress_create_vm(mode, nr_vcpus, params->vcpu_memory_bytes, 1,
- params->backing_src, !overlap_memory_access);
+ struct memstress_params ms_par = {
+ .mode = mode,
+ .nr_vcpus = nr_vcpus,
+ .vcpu_memory_bytes = params->vcpu_memory_bytes,
+ .slots = 1,
+ .backing_src = params->backing_src,
+ .partition_vcpu_memory_access = !overlap_memory_access,
+ };
+
+ vm = memstress_create_vm(&ms_par);
/*
* If guest_page_size is larger than the host's page size, the
* guest (memstress) will only fault in a subset of the host's pages.
*/
test_pages = params->nr_vcpus * params->vcpu_memory_bytes /
max(memstress_args.guest_page_size,
(u64)getpagesize());
memstress_start_vcpu_threads(nr_vcpus, vcpu_thread_main);
diff --git a/tools/testing/selftests/kvm/demand_paging_test.c b/tools/testing/selftests/kvm/demand_paging_test.c
index f8b3d0b68830..4181c2c4ca40 100644
--- a/tools/testing/selftests/kvm/demand_paging_test.c
+++ b/tools/testing/selftests/kvm/demand_paging_test.c
@@ -154,22 +154,30 @@ static void run_test(enum vm_guest_mode mode, void *arg)
struct memstress_vcpu_args *vcpu_args;
struct test_params *p = arg;
struct uffd_desc **uffd_descs = NULL;
u64 uffd_region_size;
struct timespec start;
struct timespec ts_diff;
double vcpu_paging_rate;
struct kvm_vm *vm;
int i, num_uffds = 0;
- vm = memstress_create_vm(mode, nr_vcpus, guest_percpu_mem_size, 1,
- p->src_type, p->partition_vcpu_memory_access);
+ struct memstress_params ms_par = {
+ .mode = mode,
+ .nr_vcpus = nr_vcpus,
+ .vcpu_memory_bytes = guest_percpu_mem_size,
+ .slots = 1,
+ .backing_src = p->src_type,
+ .partition_vcpu_memory_access = p->partition_vcpu_memory_access,
+ };
+
+ vm = memstress_create_vm(&ms_par);
demand_paging_size = get_backing_src_pagesz(p->src_type);
guest_data_prototype = malloc(demand_paging_size);
TEST_ASSERT(guest_data_prototype,
"Failed to allocate buffer for guest data pattern");
memset(guest_data_prototype, 0xAB, demand_paging_size);
if (p->uffd_mode == UFFDIO_REGISTER_MODE_MINOR) {
num_uffds = p->single_uffd ? 1 : nr_vcpus;
diff --git a/tools/testing/selftests/kvm/dirty_log_perf_test.c b/tools/testing/selftests/kvm/dirty_log_perf_test.c
index 7c5abe1ae9e0..173f750344f4 100644
--- a/tools/testing/selftests/kvm/dirty_log_perf_test.c
+++ b/tools/testing/selftests/kvm/dirty_log_perf_test.c
@@ -110,23 +110,30 @@ static void run_test(enum vm_guest_mode mode, void *arg)
u64 host_num_pages;
u64 pages_per_slot;
struct timespec start;
struct timespec ts_diff;
struct timespec get_dirty_log_total = (struct timespec){0};
struct timespec vcpu_dirty_total = (struct timespec){0};
struct timespec avg;
struct timespec clear_dirty_log_total = (struct timespec){0};
int i;
- vm = memstress_create_vm(mode, nr_vcpus, guest_percpu_mem_size,
- p->slots, p->backing_src,
- p->partition_vcpu_memory_access);
+ struct memstress_params ms_par = {
+ .mode = mode,
+ .nr_vcpus = nr_vcpus,
+ .vcpu_memory_bytes = guest_percpu_mem_size,
+ .slots = p->slots,
+ .backing_src = p->backing_src,
+ .partition_vcpu_memory_access = p->partition_vcpu_memory_access,
+ };
+
+ vm = memstress_create_vm(&ms_par);
memstress_set_write_percent(vm, p->write_percent);
guest_num_pages = (nr_vcpus * guest_percpu_mem_size) >> vm->page_shift;
guest_num_pages = vm_adjust_num_guest_pages(mode, guest_num_pages);
host_num_pages = vm_num_host_pages(mode, guest_num_pages);
pages_per_slot = host_num_pages / p->slots;
bitmaps = memstress_alloc_bitmaps(p->slots, pages_per_slot);
diff --git a/tools/testing/selftests/kvm/lib/memstress.c b/tools/testing/selftests/kvm/lib/memstress.c
index 2864562f57b4..880657df65e9 100644
--- a/tools/testing/selftests/kvm/lib/memstress.c
+++ b/tools/testing/selftests/kvm/lib/memstress.c
@@ -114,67 +114,66 @@ void memstress_setup_vcpus(struct kvm_vm *vm, int nr_vcpus,
}
vcpu_args_set(vcpus[i], 1, i);
pr_debug("Added VCPU %d with test mem gpa [%lx, %lx)\n",
i, vcpu_args->gpa, vcpu_args->gpa +
(vcpu_args->pages * args->guest_page_size));
}
}
-struct kvm_vm *memstress_create_vm(enum vm_guest_mode mode, int nr_vcpus,
- u64 vcpu_memory_bytes, int slots,
- enum vm_mem_backing_src_type backing_src,
- bool partition_vcpu_memory_access)
+struct kvm_vm *memstress_create_vm(struct memstress_params *params)
{
struct memstress_args *args = &memstress_args;
struct kvm_vm *vm;
u64 guest_num_pages, slot0_pages = 0;
- u64 backing_src_pagesz = get_backing_src_pagesz(backing_src);
+ u64 backing_src_pagesz = get_backing_src_pagesz(params->backing_src);
u64 region_end_gfn;
int i;
- pr_info("Testing guest mode: %s\n", vm_guest_mode_string(mode));
+ pr_info("Testing guest mode: %s\n", vm_guest_mode_string(params->mode));
/* By default vCPUs will write to memory. */
args->write_percent = 100;
/*
* Snapshot the non-huge page size. This is used by the guest code to
* access/dirty pages at the logging granularity.
*/
- args->guest_page_size = vm_guest_mode_params[mode].page_size;
+ args->guest_page_size = vm_guest_mode_params[params->mode].page_size;
- guest_num_pages = vm_adjust_num_guest_pages(mode,
- (nr_vcpus * vcpu_memory_bytes) / args->guest_page_size);
+ guest_num_pages = vm_adjust_num_guest_pages(params->mode,
+ (params->nr_vcpus * params->vcpu_memory_bytes) /
+ args->guest_page_size);
- TEST_ASSERT(vcpu_memory_bytes % getpagesize() == 0,
+ TEST_ASSERT(params->vcpu_memory_bytes % getpagesize() == 0,
"Guest memory size is not host page size aligned.");
- TEST_ASSERT(vcpu_memory_bytes % args->guest_page_size == 0,
+ TEST_ASSERT(params->vcpu_memory_bytes % args->guest_page_size == 0,
"Guest memory size is not guest page size aligned.");
- TEST_ASSERT(guest_num_pages % slots == 0,
+ TEST_ASSERT(guest_num_pages % params->slots == 0,
"Guest memory cannot be evenly divided into %d slots.",
- slots);
+ params->slots);
/*
* If using nested, allocate extra pages for the nested page tables and
* in-memory data structures.
*/
if (args->nested)
- slot0_pages += memstress_nested_pages(nr_vcpus);
+ slot0_pages += memstress_nested_pages(params->nr_vcpus);
/*
* Pass guest_num_pages to populate the page tables for test memory.
* The memory is also added to memslot 0, but that's a benign side
* effect as KVM allows aliasing HVAs in meslots.
*/
- vm = __vm_create_with_vcpus(VM_SHAPE(mode), nr_vcpus,
+ vm = __vm_create_with_vcpus(VM_SHAPE(params->mode),
+ params->nr_vcpus,
slot0_pages + guest_num_pages,
memstress_guest_code, vcpus);
args->vm = vm;
/* Put the test region at the top guest physical memory. */
region_end_gfn = vm->max_gfn + 1;
#ifdef __x86_64__
/*
@@ -185,47 +184,49 @@ struct kvm_vm *memstress_create_vm(enum vm_guest_mode mode, int nr_vcpus,
region_end_gfn = min(region_end_gfn, (1UL << 48) / args->guest_page_size);
#endif
/*
* If there should be more memory in the guest test region than there
* can be pages in the guest, it will definitely cause problems.
*/
TEST_ASSERT(guest_num_pages < region_end_gfn,
"Requested more guest memory than address space allows.\n"
" guest pages: %" PRIx64 " max gfn: %" PRIx64
" nr_vcpus: %d wss: %" PRIx64 "]",
- guest_num_pages, region_end_gfn - 1, nr_vcpus, vcpu_memory_bytes);
+ guest_num_pages, region_end_gfn - 1, params->nr_vcpus,
+ params->vcpu_memory_bytes);
args->gpa = (region_end_gfn - guest_num_pages - 1) * args->guest_page_size;
args->gpa = align_down(args->gpa, backing_src_pagesz);
args->size = guest_num_pages * args->guest_page_size;
pr_info("guest physical test memory: [0x%lx, 0x%lx)\n",
args->gpa, args->gpa + args->size);
/* Add extra memory slots for testing */
- for (i = 0; i < slots; i++) {
- u64 region_pages = guest_num_pages / slots;
+ for (i = 0; i < params->slots; i++) {
+ u64 region_pages = guest_num_pages / params->slots;
gpa_t region_start = args->gpa + region_pages * args->guest_page_size * i;
- vm_userspace_mem_region_add(vm, backing_src, region_start,
+ vm_userspace_mem_region_add(vm, params->backing_src, region_start,
MEMSTRESS_MEM_SLOT_INDEX + i,
region_pages, 0);
}
/* Do mapping for the demand paging memory slot */
virt_map(vm, guest_test_virt_mem, args->gpa, guest_num_pages);
- memstress_setup_vcpus(vm, nr_vcpus, vcpus, vcpu_memory_bytes,
- partition_vcpu_memory_access);
+ memstress_setup_vcpus(vm, params->nr_vcpus, vcpus,
+ params->vcpu_memory_bytes,
+ params->partition_vcpu_memory_access);
if (args->nested) {
pr_info("Configuring vCPUs to run in L2 (nested).\n");
- memstress_setup_nested(vm, nr_vcpus, vcpus);
+ memstress_setup_nested(vm, params->nr_vcpus, vcpus);
}
/* Export the shared variables to the guest. */
sync_global_to_guest(vm, memstress_args);
return vm;
}
void memstress_destroy_vm(struct kvm_vm *vm)
{
diff --git a/tools/testing/selftests/kvm/memslot_modification_stress_test.c b/tools/testing/selftests/kvm/memslot_modification_stress_test.c
index 9c7578a098c3..d834f5a7560d 100644
--- a/tools/testing/selftests/kvm/memslot_modification_stress_test.c
+++ b/tools/testing/selftests/kvm/memslot_modification_stress_test.c
@@ -81,23 +81,31 @@ struct test_params {
u64 nr_iterations;
bool partition_vcpu_memory_access;
bool disable_slot_zap_quirk;
};
static void run_test(enum vm_guest_mode mode, void *arg)
{
struct test_params *p = arg;
struct kvm_vm *vm;
- vm = memstress_create_vm(mode, nr_vcpus, guest_percpu_mem_size, 1,
- VM_MEM_SRC_ANONYMOUS,
- p->partition_vcpu_memory_access);
+ struct memstress_params ms_par = {
+ .mode = mode,
+ .nr_vcpus = nr_vcpus,
+ .vcpu_memory_bytes = guest_percpu_mem_size,
+ .slots = 1,
+ .backing_src = VM_MEM_SRC_ANONYMOUS,
+ .partition_vcpu_memory_access = p->partition_vcpu_memory_access,
+ };
+
+ vm = memstress_create_vm(&ms_par);
+
#ifdef __x86_64__
if (p->disable_slot_zap_quirk)
vm_enable_cap(vm, KVM_CAP_DISABLE_QUIRKS2, KVM_X86_QUIRK_SLOT_ZAP_ALL);
pr_info("Memslot zap quirk %s\n", p->disable_slot_zap_quirk ?
"disabled" : "enabled");
#endif
pr_info("Finished creating vCPUs\n");
diff --git a/tools/testing/selftests/kvm/x86/dirty_log_page_splitting_test.c b/tools/testing/selftests/kvm/x86/dirty_log_page_splitting_test.c
index 388ba4101f97..fffef5243ad8 100644
--- a/tools/testing/selftests/kvm/x86/dirty_log_page_splitting_test.c
+++ b/tools/testing/selftests/kvm/x86/dirty_log_page_splitting_test.c
@@ -93,22 +93,30 @@ static void run_test(enum vm_guest_mode mode, void *unused)
u64 host_num_pages;
u64 pages_per_slot;
int i;
struct kvm_page_stats stats_populated;
struct kvm_page_stats stats_dirty_logging_enabled;
struct kvm_page_stats stats_dirty_pass[ITERATIONS];
struct kvm_page_stats stats_clear_pass[ITERATIONS];
struct kvm_page_stats stats_dirty_logging_disabled;
struct kvm_page_stats stats_repopulated;
- vm = memstress_create_vm(mode, VCPUS, guest_percpu_mem_size,
- SLOTS, backing_src, false);
+ struct memstress_params ms_par = {
+ .mode = mode,
+ .nr_vcpus = VCPUS,
+ .vcpu_memory_bytes = guest_percpu_mem_size,
+ .slots = SLOTS,
+ .backing_src = backing_src,
+ .partition_vcpu_memory_access = false,
+ };
+
+ vm = memstress_create_vm(&ms_par);
guest_num_pages = (VCPUS * guest_percpu_mem_size) >> vm->page_shift;
guest_num_pages = vm_adjust_num_guest_pages(mode, guest_num_pages);
host_num_pages = vm_num_host_pages(mode, guest_num_pages);
pages_per_slot = host_num_pages / SLOTS;
TEST_ASSERT_EQ(host_num_pages, pages_per_slot * SLOTS);
TEST_ASSERT(!(host_num_pages % 512),
"Number of pages, '%lu' not a multiple of 2MiB", host_num_pages);
bitmaps = memstress_alloc_bitmaps(SLOTS, pages_per_slot);
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 2/5] KVM: selftests: memstress: Add option to enable dirty-ring on VM creation
2026-10-09 13:00 [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 1/5] KVM: selftests: memstress: Add struct memstress_params, make memstress_create_vm() use it Leonardo Bras
@ 2026-10-09 13:00 ` Leonardo Bras
2026-10-09 13:11 ` sashiko-bot
2026-10-09 13:00 ` [PATCH v2 3/5] KVM: selftests: Introduce kvm_get_dirty_ring_cap() Leonardo Bras
` (2 subsequent siblings)
4 siblings, 1 reply; 7+ messages in thread
From: Leonardo Bras @ 2026-10-09 13:00 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Shuah Khan, David Matlack,
Yosry Ahmed, Marc Zyngier, Fuad Tabba, Oliver Upton,
Leonardo Bras, Ackerley Tng, Josh Hilke, Wu Fei, Steffen Eiden,
Claudio Imbrenda
Cc: kvm, linux-kselftest, linux-kernel
Dirty-ring should be enabled after the VM is created, but before the
creation of any cpu. To to so, add an option on memstress_create_vm() that
takes dirty-ring size, and enables it at the correct moment.
This required a new function memstress_vm_create_with_vcpus() to be
created based on the previously used __vm_create_with_vcpus();
Signed-off-by: Leonardo Bras <leo.bras@arm.com>
---
.../testing/selftests/kvm/include/memstress.h | 1 +
tools/testing/selftests/kvm/lib/memstress.c | 27 +++++++++++++++----
2 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/memstress.h b/tools/testing/selftests/kvm/include/memstress.h
index cea2062e1dcb..465edf0da8f3 100644
--- a/tools/testing/selftests/kvm/include/memstress.h
+++ b/tools/testing/selftests/kvm/include/memstress.h
@@ -53,20 +53,21 @@ struct memstress_args {
struct memstress_vcpu_args vcpu_args[KVM_MAX_VCPUS];
};
struct memstress_params {
enum vm_guest_mode mode;
int nr_vcpus;
u64 vcpu_memory_bytes;
int slots;
enum vm_mem_backing_src_type backing_src;
bool partition_vcpu_memory_access;
+ u32 dirty_ring_size;
};
extern struct memstress_args memstress_args;
struct kvm_vm *memstress_create_vm(struct memstress_params *params);
void memstress_destroy_vm(struct kvm_vm *vm);
void memstress_set_write_percent(struct kvm_vm *vm, u32 write_percent);
void memstress_set_random_access(struct kvm_vm *vm, bool random_access);
diff --git a/tools/testing/selftests/kvm/lib/memstress.c b/tools/testing/selftests/kvm/lib/memstress.c
index 880657df65e9..b1f13499e85f 100644
--- a/tools/testing/selftests/kvm/lib/memstress.c
+++ b/tools/testing/selftests/kvm/lib/memstress.c
@@ -114,20 +114,41 @@ void memstress_setup_vcpus(struct kvm_vm *vm, int nr_vcpus,
}
vcpu_args_set(vcpus[i], 1, i);
pr_debug("Added VCPU %d with test mem gpa [%lx, %lx)\n",
i, vcpu_args->gpa, vcpu_args->gpa +
(vcpu_args->pages * args->guest_page_size));
}
}
+static struct kvm_vm *memstress_vm_create_with_vcpus(struct memstress_params *params,
+ u64 extra_mem_pages)
+{
+ struct kvm_vm *vm;
+ int i;
+
+ TEST_ASSERT(params->nr_vcpus > 0, "Invalid number of vCPUs (%d)",
+ params->nr_vcpus);
+
+ vm = __vm_create(VM_SHAPE(params->mode), params->nr_vcpus, extra_mem_pages);
+
+ if (params->dirty_ring_size)
+ vm_enable_dirty_ring(vm, params->dirty_ring_size);
+
+ for (i = 0; i < params->nr_vcpus; ++i)
+ vcpus[i] = vm_vcpu_add(vm, i, memstress_guest_code);
+
+ kvm_arch_vm_finalize_vcpus(vm);
+ return vm;
+}
+
struct kvm_vm *memstress_create_vm(struct memstress_params *params)
{
struct memstress_args *args = &memstress_args;
struct kvm_vm *vm;
u64 guest_num_pages, slot0_pages = 0;
u64 backing_src_pagesz = get_backing_src_pagesz(params->backing_src);
u64 region_end_gfn;
int i;
pr_info("Testing guest mode: %s\n", vm_guest_mode_string(params->mode));
@@ -158,25 +179,21 @@ struct kvm_vm *memstress_create_vm(struct memstress_params *params)
* in-memory data structures.
*/
if (args->nested)
slot0_pages += memstress_nested_pages(params->nr_vcpus);
/*
* Pass guest_num_pages to populate the page tables for test memory.
* The memory is also added to memslot 0, but that's a benign side
* effect as KVM allows aliasing HVAs in meslots.
*/
- vm = __vm_create_with_vcpus(VM_SHAPE(params->mode),
- params->nr_vcpus,
- slot0_pages + guest_num_pages,
- memstress_guest_code, vcpus);
-
+ vm = memstress_vm_create_with_vcpus(params, slot0_pages + guest_num_pages);
args->vm = vm;
/* Put the test region at the top guest physical memory. */
region_end_gfn = vm->max_gfn + 1;
#ifdef __x86_64__
/*
* When running vCPUs in L2, restrict the test region to 48 bits to
* avoid needing 5-level page tables to identity map L2.
*/
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 3/5] KVM: selftests: Introduce kvm_get_dirty_ring_cap()
2026-10-09 13:00 [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 1/5] KVM: selftests: memstress: Add struct memstress_params, make memstress_create_vm() use it Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 2/5] KVM: selftests: memstress: Add option to enable dirty-ring on VM creation Leonardo Bras
@ 2026-10-09 13:00 ` Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 4/5] KVM: selftests: Check dirty-ring size before enabling Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 5/5] KVM: selftests: dirty_log_perf_test: Add dirty-ring support Leonardo Bras
4 siblings, 0 replies; 7+ messages in thread
From: Leonardo Bras @ 2026-10-09 13:00 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Shuah Khan, David Matlack,
Yosry Ahmed, Marc Zyngier, Fuad Tabba, Oliver Upton,
Leonardo Bras, Ackerley Tng, Josh Hilke, Wu Fei, Steffen Eiden,
Claudio Imbrenda
Cc: kvm, linux-kselftest, linux-kernel
In a preparation to introduce dirty-ring support in dirty_log_perf_test,
add a helper to detect the Dirty-ring capability and replace the checking
of individual capabilities in the dirty_log_test.
Suggested-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Leonardo Bras <leo.bras@arm.com>
---
tools/testing/selftests/kvm/include/kvm_util.h | 11 +++++++++++
tools/testing/selftests/kvm/dirty_log_test.c | 3 +--
2 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 5a7a455b5387..a89758e66a68 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -300,20 +300,31 @@ static inline int get_kvm_param_integer(const char *param)
return kvm_get_module_param_integer("kvm", param);
}
unsigned int kvm_check_cap(long cap);
static inline bool kvm_has_cap(long cap)
{
return kvm_check_cap(cap);
}
+static inline long kvm_get_dirty_ring_cap(void)
+{
+ if (kvm_has_cap(KVM_CAP_DIRTY_LOG_RING_ACQ_REL))
+ return KVM_CAP_DIRTY_LOG_RING_ACQ_REL;
+
+ if (kvm_has_cap(KVM_CAP_DIRTY_LOG_RING))
+ return KVM_CAP_DIRTY_LOG_RING;
+
+ return 0;
+}
+
/*
* Use the "inner", double-underscore macro when reporting errors from within
* other macros so that the name of ioctl() and not its literal numeric value
* is printed on error. The "outer" macro is strongly preferred when reporting
* errors "directly", i.e. without an additional layer of macros, as it reduces
* the probability of passing in the wrong string.
*/
#define __KVM_IOCTL_ERROR(_name, _ret) __KVM_SYSCALL_ERROR(_name, _ret)
#define KVM_IOCTL_ERROR(_ioctl, _ret) __KVM_IOCTL_ERROR(#_ioctl, _ret)
diff --git a/tools/testing/selftests/kvm/dirty_log_test.c b/tools/testing/selftests/kvm/dirty_log_test.c
index af5eb0334a74..558a71d631da 100644
--- a/tools/testing/selftests/kvm/dirty_log_test.c
+++ b/tools/testing/selftests/kvm/dirty_log_test.c
@@ -284,22 +284,21 @@ static void default_after_vcpu_run(struct kvm_vcpu *vcpu)
TEST_ASSERT(get_ucall(vcpu, NULL) == UCALL_SYNC,
"Invalid guest sync status: exit_reason=%s",
exit_reason_str(run->exit_reason));
vcpu_handle_sync_stop();
}
static bool dirty_ring_supported(void)
{
- return (kvm_has_cap(KVM_CAP_DIRTY_LOG_RING) ||
- kvm_has_cap(KVM_CAP_DIRTY_LOG_RING_ACQ_REL));
+ return kvm_get_dirty_ring_cap();
}
static void dirty_ring_create_vm_done(struct kvm_vm *vm)
{
u64 pages;
u32 limit;
/*
* We rely on vcpu exit due to full dirty ring state. Adjust
* the ring buffer size to ensure we're able to reach the
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 4/5] KVM: selftests: Check dirty-ring size before enabling
2026-10-09 13:00 [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test Leonardo Bras
` (2 preceding siblings ...)
2026-10-09 13:00 ` [PATCH v2 3/5] KVM: selftests: Introduce kvm_get_dirty_ring_cap() Leonardo Bras
@ 2026-10-09 13:00 ` Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 5/5] KVM: selftests: dirty_log_perf_test: Add dirty-ring support Leonardo Bras
4 siblings, 0 replies; 7+ messages in thread
From: Leonardo Bras @ 2026-10-09 13:00 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Shuah Khan, David Matlack,
Yosry Ahmed, Marc Zyngier, Fuad Tabba, Oliver Upton,
Leonardo Bras, Ackerley Tng, Josh Hilke, Wu Fei, Steffen Eiden,
Claudio Imbrenda
Cc: kvm, linux-kselftest, linux-kernel
As of today, trying to enable dirty-ring with a size bigger than the
maximum will return an "argument list too long" error.
Change vm_enable_dirty_ring() to get the maximum size, then compare it to
the desired size before enabling. If the value is invalid, print a more
precise error message.
Signed-off-by: Leonardo Bras <leo.bras@arm.com>
---
tools/testing/selftests/kvm/lib/kvm_util.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 9ddc047d5c27..fbd0aaa679d6 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -168,24 +168,36 @@ unsigned int kvm_check_cap(long cap)
ret = __kvm_ioctl(kvm_fd, KVM_CHECK_EXTENSION, (void *)cap);
TEST_ASSERT(ret >= 0, KVM_IOCTL_ERROR(KVM_CHECK_EXTENSION, ret));
kvm_free_fd(kvm_fd);
return (unsigned int)ret;
}
void vm_enable_dirty_ring(struct kvm_vm *vm, u32 ring_size)
{
- if (vm_check_cap(vm, KVM_CAP_DIRTY_LOG_RING_ACQ_REL))
- vm_enable_cap(vm, KVM_CAP_DIRTY_LOG_RING_ACQ_REL, ring_size);
- else
- vm_enable_cap(vm, KVM_CAP_DIRTY_LOG_RING, ring_size);
+ long cap = kvm_get_dirty_ring_cap();
+ int max_size;
+
+ TEST_ASSERT(cap, "Dirty-ring not supported, check CONFIG_HAVE_KVM_DIRTY_RING");
+
+ TEST_ASSERT(is_power_of_2(ring_size),
+ "Dirty-ring size '0x%x' must be a power-of-2", ring_size);
+ TEST_ASSERT(ring_size >= getpagesize(),
+ "Dirty-ring size '0x%x' must be at least one (host) page", ring_size);
+
+ max_size = vm_check_cap(vm, cap);
+ TEST_ASSERT(ring_size <= max_size,
+ "Dirty-ring size '0x%x' is larger than KVM's limit of '0x%x'",
+ ring_size, max_size);
+
+ vm_enable_cap(vm, cap, ring_size);
vm->dirty_ring_size = ring_size;
}
static void vm_open(struct kvm_vm *vm)
{
vm->kvm_fd = _open_kvm_dev_path_or_exit(O_RDWR);
TEST_REQUIRE(kvm_has_cap(KVM_CAP_IMMEDIATE_EXIT));
vm->fd = __kvm_ioctl(vm->kvm_fd, KVM_CREATE_VM, (void *)vm->type);
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 5/5] KVM: selftests: dirty_log_perf_test: Add dirty-ring support
2026-10-09 13:00 [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test Leonardo Bras
` (3 preceding siblings ...)
2026-10-09 13:00 ` [PATCH v2 4/5] KVM: selftests: Check dirty-ring size before enabling Leonardo Bras
@ 2026-10-09 13:00 ` Leonardo Bras
4 siblings, 0 replies; 7+ messages in thread
From: Leonardo Bras @ 2026-10-09 13:00 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini, Shuah Khan, David Matlack,
Yosry Ahmed, Marc Zyngier, Fuad Tabba, Oliver Upton,
Leonardo Bras, Ackerley Tng, Josh Hilke, Wu Fei, Steffen Eiden,
Claudio Imbrenda
Cc: kvm, linux-kselftest, linux-kernel
dirty_log_test supports both dirty-bitmap and dirty-ring as dirty-page
tracking mechanisms, while dirty_log_perf_test only supports dirty-bitmap.
Add support to dirty-ring on dirty_log_perf_test so it can be used to
compare performance between changes in the mechanism.
vcpu_last_completed_iteration now needs smp_load_acquire/smp_store_relase()
for iterations >0 (after populating) as dirty-ring will save per-cpu time
spent on cleaning, and we can't have that reordered, as it may break the
summing-up.
smp_load_acquire(&vcpu_last_completed_iteration) is not needed on iteration
zero (populating) as dirty-logging should not be enabled, and we do not
access it from the run_test thread.
Signed-off-by: Leonardo Bras <leo.bras@arm.com>
---
.../selftests/kvm/dirty_log_perf_test.c | 127 ++++++++++++++++--
1 file changed, 114 insertions(+), 13 deletions(-)
diff --git a/tools/testing/selftests/kvm/dirty_log_perf_test.c b/tools/testing/selftests/kvm/dirty_log_perf_test.c
index 173f750344f4..247302b7cd4e 100644
--- a/tools/testing/selftests/kvm/dirty_log_perf_test.c
+++ b/tools/testing/selftests/kvm/dirty_log_perf_test.c
@@ -6,68 +6,134 @@
*
* Copyright (C) 2018, Red Hat, Inc.
* Copyright (C) 2020, Google, Inc.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <linux/bitmap.h>
+#include <asm/barrier.h>
#include "kvm_util.h"
#include "test_util.h"
#include "memstress.h"
#include "guest_modes.h"
#include "ucall_common.h"
/* How many host loops to run by default (one KVM_GET_DIRTY_LOG for each loop)*/
#define TEST_HOST_LOOP_N 2UL
static int nr_vcpus = 1;
static u64 guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE;
static bool run_vcpus_while_disabling_dirty_logging;
/* Host variables */
static u64 dirty_log_manual_caps;
+static u32 dirty_ring_size;
static bool host_quit;
static int iteration;
static int vcpu_last_completed_iteration[KVM_MAX_VCPUS];
+static struct timespec vcpu_dirty_ring_collect[KVM_MAX_VCPUS];
+
+static void dirty_ring_collect(struct kvm_vcpu *vcpu, u32 *ring_idx,
+ struct timespec *ts)
+{
+ static pthread_mutex_t collect = PTHREAD_MUTEX_INITIALIZER;
+ struct timespec start;
+ struct kvm_dirty_gfn *dirty_gfns = vcpu_map_dirty_ring(vcpu);
+ u32 idx = *ring_idx;
+ u32 ring_size = vcpu->vm->dirty_ring_size / sizeof(struct kvm_dirty_gfn);
+ int cleared, count;
+
+ pthread_mutex_lock(&collect);
+
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ while (true) {
+ struct kvm_dirty_gfn *cur;
+
+ cur = &dirty_gfns[idx % ring_size];
+ if (smp_load_acquire(&cur->flags) != KVM_DIRTY_GFN_F_DIRTY)
+ break;
+
+ smp_store_release(&cur->flags, KVM_DIRTY_GFN_F_RESET);
+ idx++;
+ }
+
+ count = idx - *ring_idx;
+ *ring_idx = idx;
+
+ cleared = kvm_vm_reset_dirty_ring(vcpu->vm);
+
+ /* Cleared pages should be the same as collected, as KVM is supposed to
+ * clear only the entries that have been harvested, and a single vcpu will
+ * harvest at time.
+ */
+ TEST_ASSERT(cleared == count, "Reset dirty pages (%u) mismatch "
+ "with collected (%u)", cleared, count);
+
+ *ts = timespec_add(*ts, timespec_elapsed(start));
+
+ pthread_mutex_unlock(&collect);
+}
static void vcpu_worker(struct memstress_vcpu_args *vcpu_args)
{
struct kvm_vcpu *vcpu = vcpu_args->vcpu;
int vcpu_idx = vcpu_args->vcpu_idx;
u64 pages_count = 0;
struct kvm_run *run;
struct timespec start;
struct timespec ts_diff;
struct timespec total = (struct timespec){0};
struct timespec avg;
+ bool use_dirty_ring = !!vcpu->vm->dirty_ring_size;
+ u32 ring_idx = 0;
int ret;
run = vcpu->run;
while (!READ_ONCE(host_quit)) {
int current_iteration = READ_ONCE(iteration);
+ struct timespec collect = (struct timespec){0};
clock_gettime(CLOCK_MONOTONIC, &start);
- ret = _vcpu_run(vcpu);
+
+ do {
+ ret = _vcpu_run(vcpu);
+ if (!use_dirty_ring)
+ break;
+
+ dirty_ring_collect(vcpu, &ring_idx, &collect);
+ } while (!ret && run->exit_reason == KVM_EXIT_DIRTY_RING_FULL);
+
ts_diff = timespec_elapsed(start);
+ if (use_dirty_ring) {
+ ts_diff = timespec_sub(ts_diff, collect);
+ vcpu_dirty_ring_collect[vcpu_idx] = collect;
+ }
+
TEST_ASSERT(ret == 0, "vcpu_run failed: %d", ret);
TEST_ASSERT(get_ucall(vcpu, NULL) == UCALL_SYNC,
"Invalid guest sync status: exit_reason=%s",
exit_reason_str(run->exit_reason));
pr_debug("Got sync event from vCPU %d\n", vcpu_idx);
- vcpu_last_completed_iteration[vcpu_idx] = current_iteration;
+ /*
+ * Make sure vcpu_last_completed_iteration write happens before
+ * updating current iteration. Pairs with run_test, after populating..
+ */
+ smp_store_release(&vcpu_last_completed_iteration[vcpu_idx],
+ current_iteration);
pr_debug("vCPU %d updated last completed iteration to %d\n",
vcpu_idx, vcpu_last_completed_iteration[vcpu_idx]);
if (current_iteration) {
pages_count += vcpu_args->pages;
total = timespec_add(total, ts_diff);
pr_debug("vCPU %d iteration %d dirty memory time: %ld.%.9lds\n",
vcpu_idx, current_iteration, ts_diff.tv_sec,
ts_diff.tv_nsec);
} else {
@@ -117,44 +183,48 @@ static void run_test(enum vm_guest_mode mode, void *arg)
struct timespec clear_dirty_log_total = (struct timespec){0};
int i;
struct memstress_params ms_par = {
.mode = mode,
.nr_vcpus = nr_vcpus,
.vcpu_memory_bytes = guest_percpu_mem_size,
.slots = p->slots,
.backing_src = p->backing_src,
.partition_vcpu_memory_access = p->partition_vcpu_memory_access,
+ .dirty_ring_size = dirty_ring_size,
};
vm = memstress_create_vm(&ms_par);
memstress_set_write_percent(vm, p->write_percent);
guest_num_pages = (nr_vcpus * guest_percpu_mem_size) >> vm->page_shift;
guest_num_pages = vm_adjust_num_guest_pages(mode, guest_num_pages);
host_num_pages = vm_num_host_pages(mode, guest_num_pages);
pages_per_slot = host_num_pages / p->slots;
- bitmaps = memstress_alloc_bitmaps(p->slots, pages_per_slot);
+ if (!dirty_ring_size)
+ bitmaps = memstress_alloc_bitmaps(p->slots, pages_per_slot);
if (dirty_log_manual_caps)
vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2,
dirty_log_manual_caps);
/* Start the iterations */
iteration = 0;
host_quit = false;
clock_gettime(CLOCK_MONOTONIC, &start);
- for (i = 0; i < nr_vcpus; i++)
+ for (i = 0; i < nr_vcpus; i++) {
vcpu_last_completed_iteration[i] = -1;
+ vcpu_dirty_ring_collect[i] = (struct timespec){0};
+ }
/*
* Use 100% writes during the population phase to ensure all
* memory is actually populated and not just mapped to the zero
* page. The prevents expensive copy-on-write faults from
* occurring during the dirty memory iterations below, which
* would pollute the performance results.
*/
memstress_set_write_percent(vm, 100);
memstress_set_random_access(vm, false);
@@ -185,30 +255,45 @@ static void run_test(enum vm_guest_mode mode, void *arg)
while (iteration < p->iterations) {
/*
* Incrementing the iteration number will start the vCPUs
* dirtying memory again.
*/
clock_gettime(CLOCK_MONOTONIC, &start);
iteration++;
pr_debug("Starting iteration %d\n", iteration);
for (i = 0; i < nr_vcpus; i++) {
- while (READ_ONCE(vcpu_last_completed_iteration[i])
+ while (smp_load_acquire(&vcpu_last_completed_iteration[i])
!= iteration)
;
}
ts_diff = timespec_elapsed(start);
vcpu_dirty_total = timespec_add(vcpu_dirty_total, ts_diff);
pr_info("Iteration %d dirty memory time: %ld.%.9lds\n",
iteration, ts_diff.tv_sec, ts_diff.tv_nsec);
+ if (dirty_ring_size) {
+ struct timespec iteration_sum = (struct timespec){0};
+
+ for (i = 0; i < nr_vcpus; i++)
+ iteration_sum = timespec_add(iteration_sum,
+ vcpu_dirty_ring_collect[i]);
+
+ pr_info("Iteration %d clear dirty ring time: %ld.%.9lds\n",
+ iteration, iteration_sum.tv_sec, iteration_sum.tv_nsec);
+
+ clear_dirty_log_total = timespec_add(clear_dirty_log_total,
+ iteration_sum);
+ continue;
+ }
+
clock_gettime(CLOCK_MONOTONIC, &start);
memstress_get_dirty_log(vm, bitmaps, p->slots);
ts_diff = timespec_elapsed(start);
get_dirty_log_total = timespec_add(get_dirty_log_total,
ts_diff);
pr_info("Iteration %d get dirty log time: %ld.%.9lds\n",
iteration, ts_diff.tv_sec, ts_diff.tv_nsec);
if (dirty_log_manual_caps) {
clock_gettime(CLOCK_MONOTONIC, &start);
@@ -238,46 +323,53 @@ static void run_test(enum vm_guest_mode mode, void *arg)
ts_diff.tv_sec, ts_diff.tv_nsec);
/*
* Tell the vCPU threads to quit. No need to manually check that vCPUs
* have stopped running after disabling dirty logging, the join will
* wait for them to exit.
*/
host_quit = true;
memstress_join_vcpu_threads(nr_vcpus);
- avg = timespec_div(get_dirty_log_total, p->iterations);
- pr_info("Get dirty log over %lu iterations took %ld.%.9lds. (Avg %ld.%.9lds/iteration)\n",
- p->iterations, get_dirty_log_total.tv_sec,
- get_dirty_log_total.tv_nsec, avg.tv_sec, avg.tv_nsec);
+ if (!dirty_ring_size) {
+ avg = timespec_div(get_dirty_log_total, p->iterations);
+ pr_info("Get dirty log over %lu iterations took %ld.%.9lds. (Avg %ld.%.9lds/iteration)\n",
+ p->iterations, get_dirty_log_total.tv_sec,
+ get_dirty_log_total.tv_nsec, avg.tv_sec, avg.tv_nsec);
+ }
- if (dirty_log_manual_caps) {
+ if (dirty_log_manual_caps || dirty_ring_size) {
avg = timespec_div(clear_dirty_log_total, p->iterations);
pr_info("Clear dirty log over %lu iterations took %ld.%.9lds. (Avg %ld.%.9lds/iteration)\n",
p->iterations, clear_dirty_log_total.tv_sec,
clear_dirty_log_total.tv_nsec, avg.tv_sec, avg.tv_nsec);
}
- memstress_free_bitmaps(bitmaps, p->slots);
+ if (!dirty_ring_size)
+ memstress_free_bitmaps(bitmaps, p->slots);
memstress_destroy_vm(vm);
}
static void help(char *name)
{
puts("");
printf("usage: %s [-h] [-a] [-i iterations] [-p offset] [-g] "
"[-m mode] [-n] [-b vcpu bytes] [-v vcpus] [-o] [-r random seed ] [-s mem type]"
- "[-x memslots] [-w percentage] [-c physical cpus to run test on]\n", name);
+ "[-x memslots] [-w percentage] [-c physical cpus to run test on] \n"
+ "[-d dirty-ring entries]\n", name);
puts("");
printf(" -a: access memory randomly rather than in order.\n");
printf(" -i: specify iteration counts (default: %"PRIu64")\n",
TEST_HOST_LOOP_N);
+ printf(" -d: enable dirty-ring for tracking dirty pages, with given entries count.\n"
+ " If non-zero, will cause dirty-ring to be used instead of\n"
+ " dirty-bitmap. Must be a power of two.\n");
printf(" -g: Do not enable KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2. This\n"
" makes KVM_GET_DIRTY_LOG clear the dirty log (i.e.\n"
" KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE is not enabled)\n"
" and writes will be tracked as soon as dirty logging is\n"
" enabled on the memslot (i.e. KVM_DIRTY_LOG_INITIALLY_SET\n"
" is not enabled).\n");
printf(" -p: specify guest physical test memory offset\n"
" Warning: a low offset can conflict with the loaded test code.\n");
guest_modes_help();
printf(" -n: Run the vCPUs in nested mode (L2)\n");
@@ -309,42 +401,51 @@ int main(int argc, char *argv[])
int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
const char *pcpu_list = NULL;
struct test_params p = {
.iterations = TEST_HOST_LOOP_N,
.partition_vcpu_memory_access = true,
.backing_src = DEFAULT_VM_MEM_SRC,
.slots = 1,
.write_percent = 100,
};
int opt;
+ u64 tmp;
/* Override the seed to be deterministic by default. */
kvm_random_seed = 1;
dirty_log_manual_caps =
kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2);
dirty_log_manual_caps &= (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE |
KVM_DIRTY_LOG_INITIALLY_SET);
guest_modes_append_default();
- while ((opt = getopt(argc, argv, "ab:c:eghi:m:nop:r:s:v:x:w:")) != -1) {
+ while ((opt = getopt(argc, argv, "ab:c:d:eghi:m:nop:r:s:v:x:w:")) != -1) {
switch (opt) {
case 'a':
p.random_access = true;
break;
case 'b':
guest_percpu_mem_size = parse_size(optarg);
break;
case 'c':
pcpu_list = optarg;
break;
+ case 'd':
+ tmp = parse_size(optarg);
+ TEST_ASSERT(tmp, "Dirty-ring size should be > 0");
+ TEST_ASSERT(tmp <= UINT32_MAX / sizeof(struct kvm_dirty_gfn),
+ "Dirty-ring size (%lu) is too large.", tmp);
+ dirty_ring_size = tmp * sizeof(struct kvm_dirty_gfn);
+ dirty_log_manual_caps = 0;
+ break;
case 'e':
/* 'e' is for evil. */
run_vcpus_while_disabling_dirty_logging = true;
break;
case 'g':
dirty_log_manual_caps = 0;
break;
case 'h':
help(argv[0]);
break;
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2 2/5] KVM: selftests: memstress: Add option to enable dirty-ring on VM creation
2026-10-09 13:00 ` [PATCH v2 2/5] KVM: selftests: memstress: Add option to enable dirty-ring on VM creation Leonardo Bras
@ 2026-10-09 13:11 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-09 13:11 UTC (permalink / raw)
To: Leonardo Bras; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] kvm: selftests: missing KVM_MAX_VCPUS bounds check in memstress_vm_create_with_vcpus()
--
commit 8f8ed5a2bb5936b315dc077d1f97ad188346d5a2
Author: Leonardo Bras <leo.bras@arm.com>
KVM: selftests: memstress: Add option to enable dirty-ring on VM creation
This commit adds an option to memstress_create_vm() to take a dirty-ring size
and enables it before vCPU creation. It introduces a new function
memstress_vm_create_with_vcpus() to handle this setup correctly.
> diff --git a/tools/testing/selftests/kvm/lib/memstress.c b/tools/testing/selftests/kvm/lib/memstress.c
> index 880657df65e9e..b1f13499e85f4 100644
> --- a/tools/testing/selftests/kvm/lib/memstress.c
> +++ b/tools/testing/selftests/kvm/lib/memstress.c
> @@ -121,6 +121,27 @@ void memstress_setup_vcpus(struct kvm_vm *vm, int nr_vcpus,
[ ... ]
> +static struct kvm_vm *memstress_vm_create_with_vcpus(struct memstress_params *params,
> + u64 extra_mem_pages)
> +{
> + struct kvm_vm *vm;
> + int i;
> +
> + TEST_ASSERT(params->nr_vcpus > 0, "Invalid number of vCPUs (%d)",
> + params->nr_vcpus);
> +
> + vm = __vm_create(VM_SHAPE(params->mode), params->nr_vcpus, extra_mem_pages);
> +
> + if (params->dirty_ring_size)
> + vm_enable_dirty_ring(vm, params->dirty_ring_size);
> +
> + for (i = 0; i < params->nr_vcpus; ++i)
[Severity: High]
Can this loop overflow the statically sized vcpus array?
The vcpus array is bounded by KVM_MAX_VCPUS (512). When a user runs a
memstress-based test with an argument greater than 512 on a host kernel where
KVM_CAP_MAX_VCPUS is greater than 512, or runs mmu_stress_test on a host with
more than ~682 physical cores, the option parser checks against
KVM_CAP_MAX_VCPUS but not against KVM_MAX_VCPUS.
Passing this unbounded params->nr_vcpus value to
memstress_vm_create_with_vcpus() will blindly write past the end of the vcpus
array, causing memory corruption and crashing the selftest.
> + vcpus[i] = vm_vcpu_add(vm, i, memstress_guest_code);
> +
> + kvm_arch_vm_finalize_vcpus(vm);
> + return vm;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261009130054.3312154-2-leo.bras@arm.com?part=2
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-09 13:11 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-09 13:00 [PATCH v2 0/5] KVM: selftests: Add support for dirty-ring on dirty_log_perf_test Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 1/5] KVM: selftests: memstress: Add struct memstress_params, make memstress_create_vm() use it Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 2/5] KVM: selftests: memstress: Add option to enable dirty-ring on VM creation Leonardo Bras
2026-10-09 13:11 ` sashiko-bot
2026-10-09 13:00 ` [PATCH v2 3/5] KVM: selftests: Introduce kvm_get_dirty_ring_cap() Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 4/5] KVM: selftests: Check dirty-ring size before enabling Leonardo Bras
2026-10-09 13:00 ` [PATCH v2 5/5] KVM: selftests: dirty_log_perf_test: Add dirty-ring support Leonardo Bras
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.