* [PATCH v7 26/42] KVM: selftests: Add helpers for calling ioctls on guest_memfd
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Sean Christopherson <seanjc@google.com>
Add helper functions to kvm_util.h to support calling ioctls, specifically
KVM_SET_MEMORY_ATTRIBUTES2, on a guest_memfd file descriptor.
Introduce gmem_ioctl() and __gmem_ioctl() macros, modeled after the
existing vm_ioctl() helpers, to provide a standard way to call ioctls
on a guest_memfd.
Add gmem_set_memory_attributes() and its derivatives (gmem_set_private(),
gmem_set_shared()) to set memory attributes on a guest_memfd region.
Also provide "__" variants that return the ioctl error code instead of
aborting the test. These helpers will be used by upcoming guest_memfd
tests.
To avoid code duplication, factor out the check for supported memory
attributes into a new macro, TEST_ASSERT_SUPPORTED_ATTRIBUTES, and use
it in both the existing vm_set_memory_attributes() and the new
gmem_set_memory_attributes() helpers.
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/include/kvm_util.h | 94 +++++++++++++++++++++++---
1 file changed, 86 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index fb54694e6568b..d4c285c6fbe44 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -392,6 +392,16 @@ static __always_inline void static_assert_is_vcpu(struct kvm_vcpu *vcpu) { }
__TEST_ASSERT_VM_VCPU_IOCTL(!ret, #cmd, ret, (vcpu)->vm); \
})
+#define __gmem_ioctl(gmem_fd, cmd, arg) \
+ kvm_do_ioctl(gmem_fd, cmd, arg)
+
+#define gmem_ioctl(gmem_fd, cmd, arg) \
+({ \
+ int ret = __gmem_ioctl(gmem_fd, cmd, arg); \
+ \
+ TEST_ASSERT(!ret, __KVM_IOCTL_ERROR(#cmd, ret)); \
+})
+
/*
* Looks up and returns the value corresponding to the capability
* (KVM_CAP_*) given by cap.
@@ -418,8 +428,16 @@ static inline void vm_enable_cap(struct kvm_vm *vm, u32 cap, u64 arg0)
vm_ioctl(vm, KVM_ENABLE_CAP, &enable_cap);
}
+/*
+ * KVM_SET_MEMORY_ATTRIBUTES{,2} overwrites _all_ attributes. These
+ * flows need significant enhancements to support multiple attributes.
+ */
+#define TEST_ASSERT_SUPPORTED_ATTRIBUTES(attributes) \
+ TEST_ASSERT(!(attributes) || (attributes) == KVM_MEMORY_ATTRIBUTE_PRIVATE, \
+ "Update me to support multiple attributes!")
+
static inline void vm_set_memory_attributes(struct kvm_vm *vm, gpa_t gpa,
- u64 size, u64 attributes)
+ size_t size, u64 attributes)
{
struct kvm_memory_attributes attr = {
.attributes = attributes,
@@ -428,17 +446,11 @@ static inline void vm_set_memory_attributes(struct kvm_vm *vm, gpa_t gpa,
.flags = 0,
};
- /*
- * KVM_SET_MEMORY_ATTRIBUTES overwrites _all_ attributes. These flows
- * need significant enhancements to support multiple attributes.
- */
- TEST_ASSERT(!attributes || attributes == KVM_MEMORY_ATTRIBUTE_PRIVATE,
- "Update me to support multiple attributes!");
+ TEST_ASSERT_SUPPORTED_ATTRIBUTES(attributes);
vm_ioctl(vm, KVM_SET_MEMORY_ATTRIBUTES, &attr);
}
-
static inline void vm_mem_set_private(struct kvm_vm *vm, gpa_t gpa,
u64 size)
{
@@ -451,6 +463,72 @@ static inline void vm_mem_set_shared(struct kvm_vm *vm, gpa_t gpa,
vm_set_memory_attributes(vm, gpa, size, 0);
}
+static inline int __gmem_set_memory_attributes(int fd, u64 offset,
+ size_t size, u64 attributes,
+ u64 *error_offset)
+{
+ struct kvm_memory_attributes2 attr = {
+ .attributes = attributes,
+ .offset = offset,
+ .size = size,
+ .flags = 0,
+ .error_offset = 0,
+ };
+ int r;
+
+ r = __gmem_ioctl(fd, KVM_SET_MEMORY_ATTRIBUTES2, &attr);
+
+ /* Copy error_offset regardless of r so caller can check. */
+ if (error_offset)
+ *error_offset = attr.error_offset;
+
+ return r;
+}
+
+static inline int __gmem_set_private(int fd, u64 offset, size_t size,
+ u64 *error_offset)
+{
+ return __gmem_set_memory_attributes(fd, offset, size,
+ KVM_MEMORY_ATTRIBUTE_PRIVATE,
+ error_offset);
+}
+
+static inline int __gmem_set_shared(int fd, u64 offset, size_t size,
+ u64 *error_offset)
+{
+ return __gmem_set_memory_attributes(fd, offset, size, 0,
+ error_offset);
+}
+
+static inline void gmem_set_memory_attributes(int fd, u64 offset,
+ size_t size, u64 attributes)
+{
+ struct kvm_memory_attributes2 attr = {
+ .attributes = attributes,
+ .offset = offset,
+ .size = size,
+ .flags = 0,
+ };
+
+ TEST_ASSERT_SUPPORTED_ATTRIBUTES(attributes);
+
+ __TEST_REQUIRE(kvm_check_cap(KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES) > 0,
+ "No valid attributes for guest_memfd ioctl!");
+
+ gmem_ioctl(fd, KVM_SET_MEMORY_ATTRIBUTES2, &attr);
+}
+
+static inline void gmem_set_private(int fd, u64 offset, size_t size)
+{
+ gmem_set_memory_attributes(fd, offset, size,
+ KVM_MEMORY_ATTRIBUTE_PRIVATE);
+}
+
+static inline void gmem_set_shared(int fd, u64 offset, size_t size)
+{
+ gmem_set_memory_attributes(fd, offset, size, 0);
+}
+
void vm_guest_mem_fallocate(struct kvm_vm *vm, gpa_t gpa, u64 size,
bool punch_hole);
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 25/42] KVM: selftests: Add selftests global for guest memory attributes capability
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Sean Christopherson <seanjc@google.com>
Add a global variable, kvm_has_gmem_attributes, to make the result of
checking for KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES available to all tests.
kvm_has_gmem_attributes is true if guest_memfd tracks memory attributes, as
opposed to VM-level tracking.
This global variable is synced to the guest for testing convenience, to
avoid introducing subtle bugs when host/guest state is desynced.
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/include/test_util.h | 2 ++
tools/testing/selftests/kvm/lib/kvm_util.c | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h
index d9b433b834f1b..c280c3233f502 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -115,6 +115,8 @@ struct guest_random_state {
extern u32 guest_random_seed;
extern struct guest_random_state guest_rng;
+extern bool kvm_has_gmem_attributes;
+
struct guest_random_state new_guest_random_state(u32 seed);
u32 guest_random_u32(struct guest_random_state *state);
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 4eaf553fbab11..daa0c1e835a71 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -24,6 +24,8 @@ u32 guest_random_seed;
struct guest_random_state guest_rng;
static u32 last_guest_seed;
+bool kvm_has_gmem_attributes;
+
static size_t vcpu_mmap_sz(void);
int __open_path_or_exit(const char *path, int flags, const char *enoent_help)
@@ -521,6 +523,7 @@ struct kvm_vm *__vm_create(struct vm_shape shape, u32 nr_runnable_vcpus,
}
guest_rng = new_guest_random_state(guest_random_seed);
sync_global_to_guest(vm, guest_rng);
+ sync_global_to_guest(vm, kvm_has_gmem_attributes);
kvm_arch_vm_post_create(vm, nr_runnable_vcpus);
@@ -2287,6 +2290,8 @@ void __attribute((constructor)) kvm_selftest_init(void)
guest_random_seed = last_guest_seed = random();
pr_info("Random seed: 0x%x\n", guest_random_seed);
+ kvm_has_gmem_attributes = kvm_has_cap(KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES);
+
kvm_selftest_arch_init();
}
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 27/42] KVM: selftests: Test basic single-page conversion flow
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add a selftest for the guest_memfd memory attribute conversion ioctls.
The test starts the guest_memfd as all-private (the default state), and
verifies the basic flow of converting a single page to shared and then back
to private.
Add infrastructure that supports extensions to other conversion flow
tests. This infrastructure will be used in upcoming patches for other
conversion tests.
Add test as an x86-specific test since guest_memfd's testing
vehicle (KVM_X86_SW_PROTECTED_VM) is x86-specific.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../kvm/x86/guest_memfd_conversions_test.c | 199 +++++++++++++++++++++
2 files changed, 200 insertions(+)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 9118a5a51b89f..6232881be500a 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -148,6 +148,7 @@ TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test
TEST_GEN_PROGS_x86 += x86/triple_fault_event_test
TEST_GEN_PROGS_x86 += x86/recalc_apic_map_test
TEST_GEN_PROGS_x86 += x86/aperfmperf_test
+TEST_GEN_PROGS_x86 += x86/guest_memfd_conversions_test
TEST_GEN_PROGS_x86 += access_tracking_perf_test
TEST_GEN_PROGS_x86 += coalesced_io_test
TEST_GEN_PROGS_x86 += dirty_log_perf_test
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
new file mode 100644
index 0000000000000..8e09e241723e5
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024, Google LLC.
+ */
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <linux/align.h>
+#include <linux/kvm.h>
+#include <linux/sizes.h>
+
+#include "kvm_util.h"
+#include "kselftest_harness.h"
+#include "test_util.h"
+#include "ucall_common.h"
+
+FIXTURE(gmem_conversions) {
+ struct kvm_vcpu *vcpu;
+ int gmem_fd;
+ /* HVA of the first byte of the memory mmap()-ed from gmem_fd. */
+ char *mem;
+};
+
+typedef FIXTURE_DATA(gmem_conversions) test_data_t;
+
+FIXTURE_SETUP(gmem_conversions) { }
+
+static size_t page_size;
+
+static void guest_do_rmw(void);
+#define GUEST_MEMFD_SHARING_TEST_GVA 0x90000000ULL
+
+/*
+ * Defer setup until the individual test is invoked so that tests can specify
+ * the number of pages and flags for the guest_memfd instance.
+ */
+static void gmem_conversions_do_setup(test_data_t *t, int nr_pages,
+ int gmem_flags)
+{
+ const struct vm_shape shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = KVM_X86_SW_PROTECTED_VM,
+ };
+ /*
+ * Use high GPA above APIC_DEFAULT_PHYS_BASE to avoid clashing with
+ * APIC_DEFAULT_PHYS_BASE.
+ */
+ const gpa_t gpa = SZ_4G;
+ const u32 slot = 1;
+ struct kvm_vm *vm;
+
+ vm = __vm_create_shape_with_one_vcpu(shape, &t->vcpu, nr_pages, guest_do_rmw);
+
+ vm_mem_add(vm, VM_MEM_SRC_SHMEM, gpa, slot, nr_pages,
+ KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags);
+
+ t->gmem_fd = kvm_slot_to_fd(vm, slot);
+ t->mem = addr_gpa2hva(vm, gpa);
+ virt_map(vm, GUEST_MEMFD_SHARING_TEST_GVA, gpa, nr_pages);
+}
+
+static void gmem_conversions_do_teardown(test_data_t *t)
+{
+ /* No need to close gmem_fd, it's owned by the VM structure. */
+ kvm_vm_free(t->vcpu->vm);
+}
+
+FIXTURE_TEARDOWN(gmem_conversions)
+{
+ gmem_conversions_do_teardown(self);
+}
+
+/*
+ * In these test definition macros, __nr_pages and nr_pages is used to set up
+ * the total number of pages in the guest_memfd under test. This will be
+ * available in the test definitions as nr_pages.
+ */
+
+#define __GMEM_CONVERSION_TEST(test, __nr_pages, flags) \
+static void __gmem_conversions_##test(test_data_t *t, int nr_pages); \
+ \
+TEST_F(gmem_conversions, test) \
+{ \
+ gmem_conversions_do_setup(self, __nr_pages, flags); \
+ __gmem_conversions_##test(self, __nr_pages); \
+} \
+static void __gmem_conversions_##test(test_data_t *t, int nr_pages) \
+
+#define GMEM_CONVERSION_TEST(test, __nr_pages, flags) \
+ __GMEM_CONVERSION_TEST(test, __nr_pages, (flags) | GUEST_MEMFD_FLAG_MMAP)
+
+#define __GMEM_CONVERSION_TEST_INIT_PRIVATE(test, __nr_pages) \
+ GMEM_CONVERSION_TEST(test, __nr_pages, 0)
+
+#define GMEM_CONVERSION_TEST_INIT_PRIVATE(test) \
+ __GMEM_CONVERSION_TEST_INIT_PRIVATE(test, 1)
+
+struct guest_check_data {
+ void *mem;
+ char expected_val;
+ char write_val;
+};
+static struct guest_check_data guest_data;
+
+static void guest_do_rmw(void)
+{
+ for (;;) {
+ char *mem = READ_ONCE(guest_data.mem);
+
+ GUEST_ASSERT_EQ(READ_ONCE(*mem), READ_ONCE(guest_data.expected_val));
+ WRITE_ONCE(*mem, READ_ONCE(guest_data.write_val));
+
+ GUEST_SYNC(0);
+ }
+}
+
+static void run_guest_do_rmw(struct kvm_vcpu *vcpu, u64 pgoff,
+ char expected_val, char write_val)
+{
+ struct ucall uc;
+ int r;
+
+ guest_data.mem = (void *)GUEST_MEMFD_SHARING_TEST_GVA + pgoff * page_size;
+ guest_data.expected_val = expected_val;
+ guest_data.write_val = write_val;
+ sync_global_to_guest(vcpu->vm, guest_data);
+
+ do {
+ r = __vcpu_run(vcpu);
+ } while (r == -1 && errno == EINTR);
+
+ TEST_ASSERT_EQ(r, 0);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ case UCALL_SYNC:
+ break;
+ default:
+ TEST_FAIL("Unexpected ucall %lu", uc.cmd);
+ }
+}
+
+static void host_do_rmw(char *mem, u64 pgoff, char expected_val,
+ char write_val)
+{
+ TEST_ASSERT_EQ(READ_ONCE(mem[pgoff * page_size]), expected_val);
+ WRITE_ONCE(mem[pgoff * page_size], write_val);
+}
+
+static void test_private(test_data_t *t, u64 pgoff, char starting_val,
+ char write_val)
+{
+ TEST_EXPECT_SIGBUS(WRITE_ONCE(t->mem[pgoff * page_size], write_val));
+ run_guest_do_rmw(t->vcpu, pgoff, starting_val, write_val);
+ TEST_EXPECT_SIGBUS(READ_ONCE(t->mem[pgoff * page_size]));
+}
+
+static void test_convert_to_private(test_data_t *t, u64 pgoff,
+ char starting_val, char write_val)
+{
+ gmem_set_private(t->gmem_fd, pgoff * page_size, page_size);
+ test_private(t, pgoff, starting_val, write_val);
+}
+
+static void test_shared(test_data_t *t, u64 pgoff, char starting_val,
+ char host_write_val, char write_val)
+{
+ host_do_rmw(t->mem, pgoff, starting_val, host_write_val);
+ run_guest_do_rmw(t->vcpu, pgoff, host_write_val, write_val);
+ TEST_ASSERT_EQ(READ_ONCE(t->mem[pgoff * page_size]), write_val);
+}
+
+static void test_convert_to_shared(test_data_t *t, u64 pgoff,
+ char starting_val, char host_write_val,
+ char write_val)
+{
+ gmem_set_shared(t->gmem_fd, pgoff * page_size, page_size);
+ test_shared(t, pgoff, starting_val, host_write_val, write_val);
+}
+
+GMEM_CONVERSION_TEST_INIT_PRIVATE(init_private)
+{
+ test_private(t, 0, 0, 'A');
+ test_convert_to_shared(t, 0, 'A', 'B', 'C');
+ test_convert_to_private(t, 0, 'C', 'E');
+}
+
+
+int main(int argc, char *argv[])
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES) &
+ KVM_MEMORY_ATTRIBUTE_PRIVATE);
+
+ page_size = getpagesize();
+
+ return test_harness_run(argc, argv);
+}
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 28/42] KVM: selftests: Test conversion flow when INIT_SHARED
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add a test case to verify that conversions between private and shared
memory work correctly when the memory is initially created as shared.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../testing/selftests/kvm/x86/guest_memfd_conversions_test.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index 8e09e241723e5..5b070d3374eae 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -95,6 +95,12 @@ static void __gmem_conversions_##test(test_data_t *t, int nr_pages) \
#define GMEM_CONVERSION_TEST_INIT_PRIVATE(test) \
__GMEM_CONVERSION_TEST_INIT_PRIVATE(test, 1)
+#define __GMEM_CONVERSION_TEST_INIT_SHARED(test, __nr_pages) \
+ GMEM_CONVERSION_TEST(test, __nr_pages, GUEST_MEMFD_FLAG_INIT_SHARED)
+
+#define GMEM_CONVERSION_TEST_INIT_SHARED(test) \
+ __GMEM_CONVERSION_TEST_INIT_SHARED(test, 1)
+
struct guest_check_data {
void *mem;
char expected_val;
@@ -186,6 +192,12 @@ GMEM_CONVERSION_TEST_INIT_PRIVATE(init_private)
test_convert_to_private(t, 0, 'C', 'E');
}
+GMEM_CONVERSION_TEST_INIT_SHARED(init_shared)
+{
+ test_shared(t, 0, 0, 'A', 'B');
+ test_convert_to_private(t, 0, 'B', 'C');
+ test_convert_to_shared(t, 0, 'C', 'D', 'E');
+}
int main(int argc, char *argv[])
{
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 29/42] KVM: selftests: Test conversion precision in guest_memfd
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
The existing guest_memfd conversion tests only use single-page memory
regions. This provides no coverage for multi-page guest_memfd objects,
specifically whether KVM correctly handles the page index for conversion
operations. An incorrect implementation could, for example, always operate
on the first page regardless of the index provided.
Add a new test case to verify that conversions between private and shared
memory correctly target the specified page within a multi-page guest_memfd.
This test also verifies the precision of memory conversions by converting a
single page an then iterating through all other pages ensure they remain in
their original state.
To support this test, add a new GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED
macro that handles setting up and tearing down the VM for each page
iteration. The teardown logic is adjusted to prevent a double-free in this
new scenario.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../kvm/x86/guest_memfd_conversions_test.c | 66 ++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index 5b070d3374eae..8e17d5c08aeb8 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -61,8 +61,13 @@ static void gmem_conversions_do_setup(test_data_t *t, int nr_pages,
static void gmem_conversions_do_teardown(test_data_t *t)
{
+ /* Use NULL to avoid second free in FIXTURE_TEARDOWN (multipage tests). */
+ if (!t->vcpu)
+ return;
+
/* No need to close gmem_fd, it's owned by the VM structure. */
kvm_vm_free(t->vcpu->vm);
+ t->vcpu = NULL;
}
FIXTURE_TEARDOWN(gmem_conversions)
@@ -101,6 +106,29 @@ static void __gmem_conversions_##test(test_data_t *t, int nr_pages) \
#define GMEM_CONVERSION_TEST_INIT_SHARED(test) \
__GMEM_CONVERSION_TEST_INIT_SHARED(test, 1)
+/*
+ * Repeats test over nr_pages in a guest_memfd of size nr_pages, providing each
+ * test iteration with test_page, the index of the page under test in
+ * guest_memfd. test_page takes values 0..(nr_pages - 1) inclusive.
+ */
+#define GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(test, __nr_pages) \
+static void __gmem_conversions_multipage_##test(test_data_t *t, int nr_pages, \
+ const int test_page); \
+ \
+TEST_F(gmem_conversions, test) \
+{ \
+ const u64 flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_INIT_SHARED; \
+ int i; \
+ \
+ for (i = 0; i < __nr_pages; ++i) { \
+ gmem_conversions_do_setup(self, __nr_pages, flags); \
+ __gmem_conversions_multipage_##test(self, __nr_pages, i); \
+ gmem_conversions_do_teardown(self); \
+ } \
+} \
+static void __gmem_conversions_multipage_##test(test_data_t *t, int nr_pages, \
+ const int test_page)
+
struct guest_check_data {
void *mem;
char expected_val;
@@ -199,6 +227,44 @@ GMEM_CONVERSION_TEST_INIT_SHARED(init_shared)
test_convert_to_shared(t, 0, 'C', 'D', 'E');
}
+GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(indexing, 4)
+{
+ int i;
+
+ /* Get a char that varies with both i and n. */
+#define combine(x, n) ((x << 4) + (n))
+#define i_(n) (combine(i, n))
+#define t_(n) (combine(test_page, n))
+
+ /*
+ * Start with the highest index, to catch any errors when, perhaps, the
+ * first page is returned even for the last index.
+ */
+ for (i = nr_pages - 1; i >= 0; --i)
+ test_shared(t, i, 0, i_(0), i_(2));
+
+ test_convert_to_private(t, test_page, t_(2), t_(3));
+
+ for (i = 0; i < nr_pages; ++i) {
+ if (i == test_page)
+ test_private(t, test_page, t_(3), t_(4));
+ else
+ test_shared(t, i, i_(2), i_(3), i_(4));
+ }
+
+ test_convert_to_shared(t, test_page, t_(4), t_(5), t_(6));
+
+ for (i = 0; i < nr_pages; ++i) {
+ char expected = i == test_page ? t_(6) : i_(4);
+
+ test_shared(t, i, expected, i_(7), i_(8));
+ }
+
+#undef t_
+#undef i_
+#undef combine
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 30/42] KVM: selftests: Test conversion before allocation
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add two test cases to the guest_memfd conversions selftest to cover
the scenario where a conversion is requested before any memory has been
allocated in the guest_memfd region.
The KVM_SET_MEMORY_ATTRIBUTES2 ioctl can be called on a memory region at
any time. If the guest had not yet faulted in any pages for that region,
the kernel must record the conversion request and apply the requested state
when the pages are eventually allocated.
The new tests cover both conversion directions.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../selftests/kvm/x86/guest_memfd_conversions_test.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index 8e17d5c08aeb8..b43ac196330f1 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -265,6 +265,20 @@ GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(indexing, 4)
#undef combine
}
+/*
+ * Test that even if there are no folios yet, conversion requests are recorded
+ * in guest_memfd.
+ */
+GMEM_CONVERSION_TEST_INIT_SHARED(before_allocation_shared)
+{
+ test_convert_to_private(t, 0, 0, 'A');
+}
+
+GMEM_CONVERSION_TEST_INIT_PRIVATE(before_allocation_private)
+{
+ test_convert_to_shared(t, 0, 0, 'A', 'B');
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 31/42] KVM: selftests: Convert with allocated folios in different layouts
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add a guest_memfd selftest to verify that memory conversions work
correctly with allocated folios in different layouts.
By iterating through which pages are initially faulted, the test covers
various layouts of contiguous allocated and unallocated regions, exercising
conversion with different range layouts.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../kvm/x86/guest_memfd_conversions_test.c | 30 ++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index b43ac196330f1..0b024fb7227f0 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -279,6 +279,36 @@ GMEM_CONVERSION_TEST_INIT_PRIVATE(before_allocation_private)
test_convert_to_shared(t, 0, 0, 'A', 'B');
}
+/*
+ * Test that when some of the folios in the conversion range are allocated,
+ * conversion requests are handled correctly in guest_memfd. Vary the ranges
+ * allocated before conversion, using test_page, to cover various layouts of
+ * contiguous allocated and unallocated regions.
+ */
+GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(unallocated_folios, 8)
+{
+ const int second_page_to_fault = 4;
+ int i;
+
+ /*
+ * Fault 2 of the pages to test filemap range operations except when
+ * test_page == second_page_to_fault.
+ */
+ host_do_rmw(t->mem, test_page, 0, 'A');
+ if (test_page != second_page_to_fault)
+ host_do_rmw(t->mem, second_page_to_fault, 0, 'A');
+
+ gmem_set_private(t->gmem_fd, 0, nr_pages * page_size);
+ for (i = 0; i < nr_pages; ++i) {
+ char expected = (i == test_page || i == second_page_to_fault) ? 'A' : 0;
+
+ test_private(t, i, expected, 'B');
+ }
+
+ for (i = 0; i < nr_pages; ++i)
+ test_convert_to_shared(t, i, 'B', 'C', 'D');
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 32/42] KVM: selftests: Test that truncation does not change shared/private status
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add a test to verify that deallocating a page in a guest memfd region via
fallocate() with FALLOC_FL_PUNCH_HOLE does not alter the shared or private
status of the corresponding memory range.
When a page backing a guest memfd mapping is deallocated, e.g., by punching
a hole or truncating the file, and then subsequently faulted back in, the
new page must inherit the correct shared/private status tracked by
guest_memfd.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../selftests/kvm/x86/guest_memfd_conversions_test.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index 0b024fb7227f0..f03af2c46426f 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -10,6 +10,7 @@
#include <linux/sizes.h>
#include "kvm_util.h"
+#include "kvm_syscalls.h"
#include "kselftest_harness.h"
#include "test_util.h"
#include "ucall_common.h"
@@ -309,6 +310,19 @@ GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(unallocated_folios, 8)
test_convert_to_shared(t, i, 'B', 'C', 'D');
}
+/* Truncation should not affect shared/private status. */
+GMEM_CONVERSION_TEST_INIT_SHARED(truncate)
+{
+ host_do_rmw(t->mem, 0, 0, 'A');
+ kvm_fallocate(t->gmem_fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, page_size);
+ host_do_rmw(t->mem, 0, 0, 'A');
+
+ test_convert_to_private(t, 0, 'A', 'B');
+
+ kvm_fallocate(t->gmem_fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, 0, page_size);
+ test_private(t, 0, 0, 'A');
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 33/42] KVM: selftests: Test that shared/private status is consistent across processes
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Sean Christopherson <seanjc@google.com>
Add a test to verify that a guest_memfd's shared/private status is
consistent across processes, and that any shared pages previously mapped in
any process are unmapped from all processes.
The test forks a child process after creating the shared guest_memfd
region so that the second process exists alongside the main process for the
entire test.
The processes then take turns to access memory to check that the
shared/private status is consistent across processes.
Signed-off-by: Sean Christopherson <seanjc@google.com>
Co-developed-by: Ackerley Tng <ackerleytng@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
.../kvm/x86/guest_memfd_conversions_test.c | 74 ++++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index f03af2c46426f..04e457409f75e 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -323,6 +323,80 @@ GMEM_CONVERSION_TEST_INIT_SHARED(truncate)
test_private(t, 0, 0, 'A');
}
+/* Test that shared/private memory protections work and are seen from any process. */
+GMEM_CONVERSION_TEST_INIT_SHARED(forked_accesses)
+{
+ /*
+ * No races are intended in this test, shared memory is only used to
+ * coordinate between processes.
+ */
+ static enum {
+ STATE_INIT,
+ STATE_CHECK_SHARED,
+ STATE_DONE_CHECKING_SHARED,
+ STATE_CHECK_PRIVATE,
+ STATE_DONE_CHECKING_PRIVATE,
+ } *test_state;
+ pid_t child_pid;
+
+ test_state = kvm_mmap(sizeof(*test_state), PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1);
+
+#define TEST_STATE_AWAIT(__state) \
+ while (READ_ONCE(*test_state) != __state) { \
+ if (child_pid != 0) { \
+ int status; \
+ pid_t pid; \
+ do { \
+ pid = waitpid(child_pid, &status, WNOHANG); \
+ } while (pid == -1 && errno == EINTR); \
+ if (pid == -1) \
+ TEST_FAIL("Couldn't check child status."); \
+ else if (pid != 0) \
+ TEST_FAIL("Child exited prematurely."); \
+ } \
+ }
+
+#define TEST_STATE_SET(__state) WRITE_ONCE(*test_state, __state)
+
+ child_pid = fork();
+ TEST_ASSERT(child_pid != -1, "fork failed");
+
+ if (child_pid == 0) {
+ const char inconsequential = 0xdd;
+
+ TEST_STATE_AWAIT(STATE_CHECK_SHARED);
+
+ /*
+ * This maps the pages into the child process as well, and tests
+ * that the conversion process will unmap the guest_memfd memory
+ * from all processes.
+ */
+ host_do_rmw(t->mem, 0, 0xB, 0xC);
+
+ TEST_STATE_SET(STATE_DONE_CHECKING_SHARED);
+ TEST_STATE_AWAIT(STATE_CHECK_PRIVATE);
+
+ TEST_EXPECT_SIGBUS(READ_ONCE(t->mem[0]));
+ TEST_EXPECT_SIGBUS(WRITE_ONCE(t->mem[0], inconsequential));
+
+ TEST_STATE_SET(STATE_DONE_CHECKING_PRIVATE);
+ exit(0);
+ }
+
+ test_shared(t, 0, 0, 0xA, 0xB);
+
+ TEST_STATE_SET(STATE_CHECK_SHARED);
+ TEST_STATE_AWAIT(STATE_DONE_CHECKING_SHARED);
+
+ test_convert_to_private(t, 0, 0xC, 0xD);
+
+ TEST_STATE_SET(STATE_CHECK_PRIVATE);
+ TEST_STATE_AWAIT(STATE_DONE_CHECKING_PRIVATE);
+
+ kvm_munmap(test_state, sizeof(*test_state));
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 34/42] KVM: selftests: Test conversion with elevated page refcount
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add a selftest to verify that converting a shared guest_memfd page to a
private page fails if the page has an elevated reference count.
When KVM converts a shared page to a private one, it expects the page to
have a reference count equal to the reference counts taken by the
filemap. If another kernel subsystem holds a reference to the page, for
example via pin_user_pages(), the conversion must be aborted.
This test uses vmsplice to increment the refcount of a specific page. The
reference is kept on the page by not reading data out from vmsplice's
destination pipe. It then attempts to convert a range of pages, including
the page with elevated refcount, from shared to private.
The test asserts that both bulk and single-page conversion attempts
correctly fail with EAGAIN for the pinned page. After the page is unpinned,
the test verifies that subsequent conversions succeed.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../kvm/x86/guest_memfd_conversions_test.c | 79 ++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
index 04e457409f75e..a4a9b4dd592dc 100644
--- a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c
@@ -397,6 +397,85 @@ GMEM_CONVERSION_TEST_INIT_SHARED(forked_accesses)
kvm_munmap(test_state, sizeof(*test_state));
}
+static int pin_pipe[2] = { -1, -1 };
+
+static void pin_pages(void *vaddr, size_t size)
+{
+ struct iovec iov = {
+ .iov_base = vaddr,
+ .iov_len = size,
+ };
+
+ if (pin_pipe[1] < 0)
+ TEST_ASSERT_EQ(pipe(pin_pipe), 0);
+
+ TEST_ASSERT_EQ(vmsplice(pin_pipe[1], &iov, 1, 0), size);
+}
+
+static void unpin_pages(void)
+{
+ close(pin_pipe[1]);
+ pin_pipe[1] = -1;
+ close(pin_pipe[0]);
+ pin_pipe[0] = -1;
+}
+
+static void test_convert_to_private_fails(test_data_t *t, u64 pgoff,
+ size_t nr_pages,
+ u64 expected_error_offset)
+{
+ /* +1 to make it anything but expected_error_offset. */
+ u64 error_offset = expected_error_offset + 1;
+ u64 offset = pgoff * page_size;
+ int ret;
+
+ do {
+ ret = __gmem_set_private(t->gmem_fd, offset,
+ nr_pages * page_size, &error_offset);
+ } while (ret == -1 && errno == EINTR);
+ TEST_ASSERT(ret == -1 && errno == EAGAIN,
+ "Wanted EAGAIN on page %lu, got %d (ret = %d)", pgoff,
+ errno, ret);
+ TEST_ASSERT_EQ(error_offset, expected_error_offset);
+}
+
+GMEM_CONVERSION_MULTIPAGE_TEST_INIT_SHARED(elevated_refcount, 4)
+{
+ int i;
+
+ pin_pages(t->mem + test_page * page_size, page_size);
+
+ for (i = 0; i < nr_pages; i++)
+ test_shared(t, i, 0, 'A', 'B');
+
+ /*
+ * Converting in bulk should fail as long any page in the range has
+ * unexpected refcounts.
+ */
+ test_convert_to_private_fails(t, 0, nr_pages, test_page * page_size);
+
+ for (i = 0; i < nr_pages; i++) {
+ /*
+ * Converting page-wise should also fail as long any page in the
+ * range has unexpected refcounts.
+ */
+ if (i == test_page)
+ test_convert_to_private_fails(t, i, 1, test_page * page_size);
+ else
+ test_convert_to_private(t, i, 'B', 'C');
+ }
+
+ unpin_pages();
+
+ gmem_set_private(t->gmem_fd, 0, nr_pages * page_size);
+
+ for (i = 0; i < nr_pages; i++) {
+ char expected = i == test_page ? 'B' : 'C';
+
+ test_private(t, i, expected, 'D');
+ }
+}
+
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 35/42] KVM: selftests: Reset shared memory after hole-punching
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
private_mem_conversions_test used to reset the shared memory that was used
for the test to an initial pattern at the end of each test iteration. Then,
it would punch out the pages, which would zero memory.
Without in-place conversion, the resetting would write shared memory, and
hole-punching will zero private memory, hence resetting the test to the
state at the beginning of the for loop.
With in-place conversion, resetting writes memory as shared, and
hole-punching zeroes the same physical memory, hence undoing the reset
done before the hole punch.
Move the resetting after the hole-punching, and reset the entire
PER_CPU_DATA_SIZE instead of just the tested range.
With in-place conversion, this zeroes and then resets the same physical
memory. Without in-place conversion, the private memory is zeroed, and the
shared memory is reset to init_p.
This is sufficient since at each test stage, the memory is assumed to start
as shared, and private memory is always assumed to start zeroed. Conversion
zeroes memory, so the future test stages will work as expected.
Fixes: 43f623f350ce1 ("KVM: selftests: Add x86-only selftest for private memory conversions")
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/x86/private_mem_conversions_test.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c b/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
index 861baff201e78..289ad10063fca 100644
--- a/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
@@ -202,15 +202,18 @@ static void guest_test_explicit_conversion(u64 base_gpa, bool do_fallocate)
guest_sync_shared(gpa, size, p3, p4);
memcmp_g(gpa, p4, size);
- /* Reset the shared memory back to the initial pattern. */
- memset((void *)gpa, init_p, size);
-
/*
* Free (via PUNCH_HOLE) *all* private memory so that the next
* iteration starts from a clean slate, e.g. with respect to
* whether or not there are pages/folios in guest_mem.
*/
guest_map_shared(base_gpa, PER_CPU_DATA_SIZE, true);
+
+ /*
+ * Hole-punching above zeroed private memory. Reset shared
+ * memory in preparation for the next GUEST_STAGE.
+ */
+ memset((void *)base_gpa, init_p, PER_CPU_DATA_SIZE);
}
}
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 36/42] KVM: selftests: Provide function to look up guest_memfd details from gpa
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Introduce a new helper, kvm_gpa_to_guest_memfd(), to find the
guest_memfd-related details of a memory region that contains a given guest
physical address (GPA).
The function returns the file descriptor for the memfd, the offset into
the file that corresponds to the GPA, and the number of bytes remaining
in the region from that GPA.
kvm_gpa_to_guest_memfd() was factored out from vm_guest_mem_fallocate();
refactor vm_guest_mem_fallocate() to use the new helper.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
tools/testing/selftests/kvm/include/kvm_util.h | 3 +++
tools/testing/selftests/kvm/lib/kvm_util.c | 37 ++++++++++++++++----------
2 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index d4c285c6fbe44..e9b4ae9596e05 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -428,6 +428,9 @@ static inline void vm_enable_cap(struct kvm_vm *vm, u32 cap, u64 arg0)
vm_ioctl(vm, KVM_ENABLE_CAP, &enable_cap);
}
+int kvm_gpa_to_guest_memfd(struct kvm_vm *vm, gpa_t gpa, off_t *fd_offset,
+ size_t *nr_bytes);
+
/*
* KVM_SET_MEMORY_ATTRIBUTES{,2} overwrites _all_ attributes. These
* flows need significant enhancements to support multiple attributes.
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index daa0c1e835a71..f8f0cd62f2f17 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -1283,27 +1283,20 @@ void vm_guest_mem_fallocate(struct kvm_vm *vm, u64 base, u64 size,
bool punch_hole)
{
const int mode = FALLOC_FL_KEEP_SIZE | (punch_hole ? FALLOC_FL_PUNCH_HOLE : 0);
- struct userspace_mem_region *region;
u64 end = base + size;
- gpa_t gpa, len;
off_t fd_offset;
- int ret;
+ int fd, ret;
+ size_t len;
+ gpa_t gpa;
for (gpa = base; gpa < end; gpa += len) {
- u64 offset;
-
- region = userspace_mem_region_find(vm, gpa, gpa);
- TEST_ASSERT(region && region->region.flags & KVM_MEM_GUEST_MEMFD,
- "Private memory region not found for GPA 0x%lx", gpa);
+ fd = kvm_gpa_to_guest_memfd(vm, gpa, &fd_offset, &len);
+ len = min(end - gpa, len);
- offset = gpa - region->region.guest_phys_addr;
- fd_offset = region->region.guest_memfd_offset + offset;
- len = min_t(u64, end - gpa, region->region.memory_size - offset);
-
- ret = fallocate(region->region.guest_memfd, mode, fd_offset, len);
+ ret = fallocate(fd, mode, fd_offset, len);
TEST_ASSERT(!ret, "fallocate() failed to %s at %lx (len = %lu), fd = %d, mode = %x, offset = %lx",
punch_hole ? "punch hole" : "allocate", gpa, len,
- region->region.guest_memfd, mode, fd_offset);
+ fd, mode, fd_offset);
}
}
@@ -1640,6 +1633,22 @@ void *addr_gpa2alias(struct kvm_vm *vm, gpa_t gpa)
return (void *) ((uintptr_t) region->host_alias + offset);
}
+int kvm_gpa_to_guest_memfd(struct kvm_vm *vm, gpa_t gpa, off_t *fd_offset,
+ size_t *nr_bytes)
+{
+ struct userspace_mem_region *region;
+ gpa_t gpa_offset;
+
+ region = userspace_mem_region_find(vm, gpa, gpa);
+ TEST_ASSERT(region && region->region.flags & KVM_MEM_GUEST_MEMFD,
+ "guest_memfd memory region not found for GPA 0x%lx", gpa);
+
+ gpa_offset = gpa - region->region.guest_phys_addr;
+ *fd_offset = region->region.guest_memfd_offset + gpa_offset;
+ *nr_bytes = region->region.memory_size - gpa_offset;
+ return region->region.guest_memfd;
+}
+
/* Create an interrupt controller chip for the specified VM. */
void vm_create_irqchip(struct kvm_vm *vm)
{
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 37/42] KVM: selftests: Provide common function to set memory attributes
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Sean Christopherson <seanjc@google.com>
Introduce vm_mem_set_memory_attributes(), which handles setting of memory
attributes for a range of guest physical addresses, regardless of whether
the attributes should be set via guest_memfd or via the memory attributes
at the VM level.
Refactor existing vm_mem_set_{shared,private} functions to use the new
function. Opportunistically update the size parameter to use size_t instead
of u64.
Signed-off-by: Sean Christopherson <seanjc@google.com>
Co-developed-by: Ackerley Tng <ackerleytng@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/include/kvm_util.h | 46 +++++++++++++++++++-------
1 file changed, 34 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index e9b4ae9596e05..a86418cdf5f4f 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -454,18 +454,6 @@ static inline void vm_set_memory_attributes(struct kvm_vm *vm, gpa_t gpa,
vm_ioctl(vm, KVM_SET_MEMORY_ATTRIBUTES, &attr);
}
-static inline void vm_mem_set_private(struct kvm_vm *vm, gpa_t gpa,
- u64 size)
-{
- vm_set_memory_attributes(vm, gpa, size, KVM_MEMORY_ATTRIBUTE_PRIVATE);
-}
-
-static inline void vm_mem_set_shared(struct kvm_vm *vm, gpa_t gpa,
- u64 size)
-{
- vm_set_memory_attributes(vm, gpa, size, 0);
-}
-
static inline int __gmem_set_memory_attributes(int fd, u64 offset,
size_t size, u64 attributes,
u64 *error_offset)
@@ -532,6 +520,40 @@ static inline void gmem_set_shared(int fd, u64 offset, size_t size)
gmem_set_memory_attributes(fd, offset, size, 0);
}
+static inline void vm_mem_set_memory_attributes(struct kvm_vm *vm, gpa_t gpa,
+ size_t size, u64 attrs)
+{
+ if (kvm_has_gmem_attributes) {
+ gpa_t end = gpa + size;
+ off_t fd_offset;
+ gpa_t addr;
+ size_t len;
+ int fd;
+
+ for (addr = gpa; addr < end; addr += len) {
+ fd = kvm_gpa_to_guest_memfd(vm, addr, &fd_offset, &len);
+ len = min(end - addr, len);
+
+ gmem_set_memory_attributes(fd, fd_offset, len, attrs);
+ }
+ } else {
+ vm_set_memory_attributes(vm, gpa, size, attrs);
+ }
+}
+
+static inline void vm_mem_set_private(struct kvm_vm *vm, gpa_t gpa,
+ size_t size)
+{
+ vm_mem_set_memory_attributes(vm, gpa, size,
+ KVM_MEMORY_ATTRIBUTE_PRIVATE);
+}
+
+static inline void vm_mem_set_shared(struct kvm_vm *vm, gpa_t gpa,
+ size_t size)
+{
+ vm_mem_set_memory_attributes(vm, gpa, size, 0);
+}
+
void vm_guest_mem_fallocate(struct kvm_vm *vm, gpa_t gpa, u64 size,
bool punch_hole);
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 38/42] KVM: selftests: Check fd/flags provided to mmap() when setting up memslot
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Sean Christopherson <seanjc@google.com>
Check that a valid fd provided to mmap() must be accompanied by MAP_SHARED.
With an invalid fd (usually used for anonymous mappings), there are no
constraints on mmap() flags.
Add this check to make sure that when a guest_memfd is used as region->fd,
the flag provided to mmap() will include MAP_SHARED.
Signed-off-by: Sean Christopherson <seanjc@google.com>
[Rephrase assertion message.]
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/lib/kvm_util.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index f8f0cd62f2f17..21c7e52a2bdac 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -1088,6 +1088,9 @@ void vm_mem_add(struct kvm_vm *vm, enum vm_mem_backing_src_type src_type,
src_type == VM_MEM_SRC_SHARED_HUGETLB);
}
+ TEST_ASSERT(region->fd == -1 || backing_src_is_shared(src_type),
+ "A valid fd provided to mmap() must be accompanied by MAP_SHARED.");
+
region->mmap_start = __kvm_mmap(region->mmap_size, PROT_READ | PROT_WRITE,
vm_mem_backing_src_alias(src_type)->flag,
region->fd, mmap_offset);
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 39/42] KVM: selftests: Make TEST_EXPECT_SIGBUS thread-safe
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
The TEST_EXPECT_SIGBUS macro is not thread-safe as it uses a global
sigjmp_buf and installs a global SIGBUS signal handler. If multiple threads
execute the macro concurrently, they will race on installing the signal
handler and stomp on other threads' jump buffers, leading to incorrect test
behavior.
Make TEST_EXPECT_SIGBUS thread-safe with the following changes:
Share the KVM tests' global signal handler. sigaction() applies to all
threads; without sharing a global signal handler, one thread may have
removed the signal handler that another thread added, hence leading to
unexpected signals.
The alternative of layering signal handlers was considered, but calling
sigaction() within TEST_EXPECT_SIGBUS() necessarily creates a race. To
avoid adding new setup and teardown routines to do sigaction() and keep
usage of TEST_EXPECT_SIGBUS() simple, share the KVM tests' global signal
handler.
Opportunistically rename report_unexpected_signal to
catchall_signal_handler.
To continue to only expect SIGBUS within specific regions of code, use a
thread-specific variable, expecting_sigbus, to replace installing and
removing signal handlers.
Make the execution environment for the thread, sigjmp_buf, a
thread-specific variable.
As part of TEST_EXPECT_SIGBUS(), assert the prerequisite for this setup,
that the current signal handler is the catchall_signal_handler.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/include/test_util.h | 32 +++++++++++++------------
tools/testing/selftests/kvm/lib/kvm_util.c | 18 ++++++++++----
tools/testing/selftests/kvm/lib/test_util.c | 7 ------
3 files changed, 30 insertions(+), 27 deletions(-)
diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h
index c280c3233f502..c9ba4e010f0b8 100644
--- a/tools/testing/selftests/kvm/include/test_util.h
+++ b/tools/testing/selftests/kvm/include/test_util.h
@@ -82,21 +82,23 @@ do { \
__builtin_unreachable(); \
} while (0)
-extern sigjmp_buf expect_sigbus_jmpbuf;
-void expect_sigbus_handler(int signum);
-
-#define TEST_EXPECT_SIGBUS(action) \
-do { \
- struct sigaction sa_old, sa_new = { \
- .sa_handler = expect_sigbus_handler, \
- }; \
- \
- sigaction(SIGBUS, &sa_new, &sa_old); \
- if (sigsetjmp(expect_sigbus_jmpbuf, 1) == 0) { \
- action; \
- TEST_FAIL("'%s' should have triggered SIGBUS", #action); \
- } \
- sigaction(SIGBUS, &sa_old, NULL); \
+extern __thread sigjmp_buf expect_sigbus_jmpbuf;
+extern __thread volatile sig_atomic_t expecting_sigbus;
+extern void catchall_signal_handler(int signum);
+
+#define TEST_EXPECT_SIGBUS(action) \
+do { \
+ struct sigaction __sa = {}; \
+ \
+ TEST_ASSERT_EQ(sigaction(SIGBUS, NULL, &__sa), 0); \
+ TEST_ASSERT_EQ(__sa.sa_handler, &catchall_signal_handler); \
+ \
+ expecting_sigbus = true; \
+ if (sigsetjmp(expect_sigbus_jmpbuf, 1) == 0) { \
+ action; \
+ TEST_FAIL("'%s' should have triggered SIGBUS", #action);\
+ } \
+ expecting_sigbus = false; \
} while (0)
size_t parse_size(const char *size);
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 21c7e52a2bdac..a7725fff58b46 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -2270,13 +2270,20 @@ __weak void kvm_selftest_arch_init(void)
{
}
-static void report_unexpected_signal(int signum)
+__thread sigjmp_buf expect_sigbus_jmpbuf;
+__thread volatile sig_atomic_t expecting_sigbus;
+
+void catchall_signal_handler(int signum)
{
+ switch (signum) {
+ case SIGBUS: {
+ if (expecting_sigbus)
+ siglongjmp(expect_sigbus_jmpbuf, 1);
+
+ TEST_FAIL("Unexpected SIGBUS (%d)\n", signum);
+ }
#define KVM_CASE_SIGNUM(sig) \
case sig: TEST_FAIL("Unexpected " #sig " (%d)\n", signum)
-
- switch (signum) {
- KVM_CASE_SIGNUM(SIGBUS);
KVM_CASE_SIGNUM(SIGSEGV);
KVM_CASE_SIGNUM(SIGILL);
KVM_CASE_SIGNUM(SIGFPE);
@@ -2288,12 +2295,13 @@ static void report_unexpected_signal(int signum)
void __attribute((constructor)) kvm_selftest_init(void)
{
struct sigaction sig_sa = {
- .sa_handler = report_unexpected_signal,
+ .sa_handler = catchall_signal_handler,
};
/* Tell stdout not to buffer its content. */
setbuf(stdout, NULL);
+ expecting_sigbus = false;
sigaction(SIGBUS, &sig_sa, NULL);
sigaction(SIGSEGV, &sig_sa, NULL);
sigaction(SIGILL, &sig_sa, NULL);
diff --git a/tools/testing/selftests/kvm/lib/test_util.c b/tools/testing/selftests/kvm/lib/test_util.c
index bab1bd2b775b6..30eb701e4becd 100644
--- a/tools/testing/selftests/kvm/lib/test_util.c
+++ b/tools/testing/selftests/kvm/lib/test_util.c
@@ -18,13 +18,6 @@
#include "test_util.h"
-sigjmp_buf expect_sigbus_jmpbuf;
-
-void __attribute__((used)) expect_sigbus_handler(int signum)
-{
- siglongjmp(expect_sigbus_jmpbuf, 1);
-}
-
/*
* Random number generator that is usable from guest code. This is the
* Park-Miller LCG using standard constants.
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 40/42] KVM: selftests: Update private_mem_conversions_test to mmap() guest_memfd
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Update the private memory conversions selftest to also test conversions
that are done "in-place" via per-guest_memfd memory attributes. In-place
conversions require the host to be able to mmap() the guest_memfd so that
the host and guest can share the same backing physical memory.
This includes several updates, that are conditioned on the system
supporting per-guest_memfd attributes (kvm_has_gmem_attributes):
1. Set up guest_memfd requesting MMAP and INIT_SHARED.
2. With in-place conversions, the host's mapping points directly to the
guest's memory. When the guest converts a region to private, host access
to that region is blocked. Update the test to expect a SIGBUS when
attempting to access the host virtual address (HVA) of private memory.
3. Use vm_mem_set_memory_attributes(), which chooses how to set memory
attributes based on whether kvm_has_gmem_attributes.
Restrict the test to using VM_MEM_SRC_SHMEM because guest_memfd's required
mmap() flags and page sizes happens to align with those of
VM_MEM_SRC_SHMEM. As long as VM_MEM_SRC_SHMEM is used for src_type,
vm_mem_add() works as intended.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
.../kvm/x86/private_mem_conversions_test.c | 44 ++++++++++++++++++----
1 file changed, 36 insertions(+), 8 deletions(-)
diff --git a/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c b/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
index 289ad10063fca..4308c67952310 100644
--- a/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
+++ b/tools/testing/selftests/kvm/x86/private_mem_conversions_test.c
@@ -306,9 +306,12 @@ static void handle_exit_hypercall(struct kvm_vcpu *vcpu)
if (do_fallocate)
vm_guest_mem_fallocate(vm, gpa, size, map_shared);
- if (set_attributes)
- vm_set_memory_attributes(vm, gpa, size,
- map_shared ? 0 : KVM_MEMORY_ATTRIBUTE_PRIVATE);
+ if (set_attributes) {
+ u64 attrs = map_shared ? 0 : KVM_MEMORY_ATTRIBUTE_PRIVATE;
+
+ vm_mem_set_memory_attributes(vm, gpa, size, attrs);
+ }
+
run->hypercall.ret = 0;
}
@@ -352,8 +355,20 @@ static void *__test_mem_conversions(void *__vcpu)
size_t nr_bytes = min_t(size_t, vm->page_size, size - i);
u8 *hva = addr_gpa2hva(vm, gpa + i);
- /* In all cases, the host should observe the shared data. */
- memcmp_h(hva, gpa + i, uc.args[3], nr_bytes);
+ /*
+ * When using per-guest_memfd memory attributes,
+ * i.e. in-place conversion, host accesses will
+ * point at guest memory and should SIGBUS when
+ * guest memory is private. When using per-VM
+ * attributes, i.e. separate backing for shared
+ * vs. private, the host should always observe
+ * the shared data.
+ */
+ if (kvm_has_gmem_attributes &&
+ uc.args[0] == SYNC_PRIVATE)
+ TEST_EXPECT_SIGBUS(READ_ONCE(*hva));
+ else
+ memcmp_h(hva, gpa + i, uc.args[3], nr_bytes);
/* For shared, write the new pattern to guest memory. */
if (uc.args[0] == SYNC_SHARED)
@@ -382,6 +397,7 @@ static void test_mem_conversions(enum vm_mem_backing_src_type src_type, u32 nr_v
const size_t slot_size = memfd_size / nr_memslots;
struct kvm_vcpu *vcpus[KVM_MAX_VCPUS];
pthread_t threads[KVM_MAX_VCPUS];
+ u64 gmem_flags;
struct kvm_vm *vm;
int memfd, i;
@@ -397,12 +413,17 @@ static void test_mem_conversions(enum vm_mem_backing_src_type src_type, u32 nr_v
vm_enable_cap(vm, KVM_CAP_EXIT_HYPERCALL, (1 << KVM_HC_MAP_GPA_RANGE));
- memfd = vm_create_guest_memfd(vm, memfd_size, 0);
+ if (kvm_has_gmem_attributes)
+ gmem_flags = GUEST_MEMFD_FLAG_MMAP | GUEST_MEMFD_FLAG_INIT_SHARED;
+ else
+ gmem_flags = 0;
+
+ memfd = vm_create_guest_memfd(vm, memfd_size, gmem_flags);
for (i = 0; i < nr_memslots; i++)
vm_mem_add(vm, src_type, BASE_DATA_GPA + slot_size * i,
BASE_DATA_SLOT + i, slot_size / vm->page_size,
- KVM_MEM_GUEST_MEMFD, memfd, slot_size * i, 0);
+ KVM_MEM_GUEST_MEMFD, memfd, slot_size * i, gmem_flags);
for (i = 0; i < nr_vcpus; i++) {
gpa_t gpa = BASE_DATA_GPA + i * per_cpu_size;
@@ -452,17 +473,24 @@ static void usage(const char *cmd)
int main(int argc, char *argv[])
{
- enum vm_mem_backing_src_type src_type = DEFAULT_VM_MEM_SRC;
+ enum vm_mem_backing_src_type src_type;
u32 nr_memslots = 1;
u32 nr_vcpus = 1;
int opt;
TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
+ src_type = kvm_has_gmem_attributes ? VM_MEM_SRC_SHMEM :
+ DEFAULT_VM_MEM_SRC;
+
while ((opt = getopt(argc, argv, "hm:s:n:")) != -1) {
switch (opt) {
case 's':
src_type = parse_backing_src_type(optarg);
+ TEST_ASSERT(!kvm_has_gmem_attributes ||
+ src_type == VM_MEM_SRC_SHMEM,
+ "Testing in-place conversions, only %s mem_type supported\n",
+ vm_mem_backing_src_alias(VM_MEM_SRC_SHMEM)->name);
break;
case 'n':
nr_vcpus = atoi_positive("nr_vcpus", optarg);
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 41/42] KVM: selftests: Add script to exercise private_mem_conversions_test
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Ackerley Tng <ackerleytng@google.com>
Add a wrapper script to simplify running the private_mem_conversions_test
with a variety of configurations. Manually invoking the test for all
supported memory backing source types is tedious.
The script automatically detects the availability of 2MB and 1GB hugepages
and builds a list of source types to test. It then iterates through the
list, running the test for each type with both a single memslot and
multiple memslots.
This makes it easier to get comprehensive test coverage across different
memory configurations.
Add and use a helper program in C to be able to read
KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES as defined in header files and then
issue the ioctl to read the KVM CAP.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 4 +
.../selftests/kvm/kvm_has_gmem_attributes.c | 17 +++
.../kvm/x86/private_mem_conversions_test.sh | 128 +++++++++++++++++++++
3 files changed, 149 insertions(+)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 6232881be500a..e5769268936a7 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -54,6 +54,7 @@ LIBKVM_loongarch += lib/loongarch/exception.S
# Non-compiled test targets
TEST_PROGS_x86 += x86/nx_huge_pages_test.sh
+TEST_PROGS_x86 += x86/private_mem_conversions_test.sh
# Compiled test targets valid on all architectures with libkvm support
TEST_GEN_PROGS_COMMON = demand_paging_test
@@ -67,6 +68,8 @@ TEST_GEN_PROGS_COMMON += set_memory_region_test
TEST_GEN_PROGS_COMMON += memslot_modification_stress_test
TEST_GEN_PROGS_COMMON += memslot_perf_test
+TEST_GEN_PROGS_EXTENDED_COMMON += kvm_has_gmem_attributes
+
# Compiled test targets
TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON)
TEST_GEN_PROGS_x86 += x86/cpuid_test
@@ -245,6 +248,7 @@ SPLIT_TESTS += get-reg-list
TEST_PROGS += $(TEST_PROGS_$(ARCH))
TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH))
+TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_COMMON)
TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH))
LIBKVM += $(LIBKVM_$(ARCH))
diff --git a/tools/testing/selftests/kvm/kvm_has_gmem_attributes.c b/tools/testing/selftests/kvm/kvm_has_gmem_attributes.c
new file mode 100644
index 0000000000000..4f361349412fb
--- /dev/null
+++ b/tools/testing/selftests/kvm/kvm_has_gmem_attributes.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Utility to check if KVM supports guest_memfd attributes.
+ *
+ * Copyright (C) 2025, Google LLC.
+ */
+
+#include <stdio.h>
+
+#include "kvm_util.h"
+
+int main(void)
+{
+ printf("%u\n", kvm_check_cap(KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES) > 0);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/kvm/x86/private_mem_conversions_test.sh b/tools/testing/selftests/kvm/x86/private_mem_conversions_test.sh
new file mode 100755
index 0000000000000..7179a4fcdd498
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/private_mem_conversions_test.sh
@@ -0,0 +1,128 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Wrapper script which runs different test setups of
+# private_mem_conversions_test.
+#
+# Copyright (C) 2025, Google LLC.
+
+NUM_VCPUS_TO_TEST=4
+NUM_MEMSLOTS_TO_TEST=$NUM_VCPUS_TO_TEST
+
+# Required pages are based on the test setup in the C code.
+REQUIRED_NUM_2M_HUGEPAGES=$((1024 * NUM_VCPUS_TO_TEST))
+REQUIRED_NUM_1G_HUGEPAGES=$((2 * NUM_VCPUS_TO_TEST))
+
+get_hugepage_count() {
+ local page_size_kb=$1
+ local path="/sys/kernel/mm/hugepages/hugepages-${page_size_kb}kB/nr_hugepages"
+ if [ -f "$path" ]; then
+ cat "$path"
+ else
+ echo 0
+ fi
+}
+
+get_default_hugepage_size_in_kb() {
+ local size=$(grep "Hugepagesize:" /proc/meminfo | awk '{print $2}')
+ echo "$size"
+}
+
+run_tests() {
+ local executable_path=$1
+ local src_type=$2
+ local num_memslots=$3
+ local num_vcpus=$4
+
+ echo "$executable_path -s $src_type -m $num_memslots -n $num_vcpus"
+ "$executable_path" -s "$src_type" -m "$num_memslots" -n "$num_vcpus"
+}
+
+script_dir=$(dirname "$(realpath "$0")")
+test_executable="${script_dir}/private_mem_conversions_test"
+kvm_has_gmem_attributes_tool="${script_dir}/../kvm_has_gmem_attributes"
+
+if [ ! -f "$test_executable" ]; then
+ echo "Error: Test executable not found at '$test_executable'" >&2
+ exit 1
+fi
+
+if [ ! -f "$kvm_has_gmem_attributes_tool" ]; then
+ echo "Error: kvm_has_gmem_attributes utility not found at '$kvm_has_gmem_attributes_tool'" >&2
+ exit 1
+fi
+
+kvm_has_gmem_attributes=$("$kvm_has_gmem_attributes_tool" | tail -n1)
+
+if [ "$kvm_has_gmem_attributes" -eq 1 ]; then
+ backing_src_types=("shmem")
+else
+ hugepage_2mb_count=$(get_hugepage_count 2048)
+ hugepage_2mb_enabled=$((hugepage_2mb_count >= REQUIRED_NUM_2M_HUGEPAGES))
+ hugepage_1gb_count=$(get_hugepage_count 1048576)
+ hugepage_1gb_enabled=$((hugepage_1gb_count >= REQUIRED_NUM_1G_HUGEPAGES))
+
+ default_hugepage_size_kb=$(get_default_hugepage_size_in_kb)
+ hugepage_default_enabled=0
+ if [ "$default_hugepage_size_kb" -eq 2048 ]; then
+ hugepage_default_enabled=$hugepage_2mb_enabled
+ elif [ "$default_hugepage_size_kb" -eq 1048576 ]; then
+ hugepage_default_enabled=$hugepage_1gb_enabled
+ fi
+
+ backing_src_types=("anonymous" "anonymous_thp")
+
+ if [ "$hugepage_default_enabled" -eq 1 ]; then
+ backing_src_types+=("anonymous_hugetlb")
+ else
+ echo "skipping anonymous_hugetlb backing source type"
+ fi
+
+ if [ "$hugepage_2mb_enabled" -eq 1 ]; then
+ backing_src_types+=("anonymous_hugetlb_2mb")
+ else
+ echo "skipping anonymous_hugetlb_2mb backing source type"
+ fi
+
+ if [ "$hugepage_1gb_enabled" -eq 1 ]; then
+ backing_src_types+=("anonymous_hugetlb_1gb")
+ else
+ echo "skipping anonymous_hugetlb_1gb backing source type"
+ fi
+
+ backing_src_types+=("shmem")
+
+ if [ "$hugepage_default_enabled" -eq 1 ]; then
+ backing_src_types+=("shared_hugetlb")
+ else
+ echo "skipping shared_hugetlb backing source type"
+ fi
+fi
+
+return_code=0
+for i in "${!backing_src_types[@]}"; do
+ src_type=${backing_src_types[$i]}
+ if [ "$i" -gt 0 ]; then
+ echo
+ fi
+
+ if ! run_tests "$test_executable" "$src_type" 1 1; then
+ return_code=$?
+ echo "Test failed for source type '$src_type'. Arguments: -s $src_type -m 1 -n 1" >&2
+ break
+ fi
+
+ if ! run_tests "$test_executable" "$src_type" 1 "$NUM_VCPUS_TO_TEST"; then
+ return_code=$?
+ echo "Test failed for source type '$src_type'. Arguments: -s $src_type -m 1 -n $NUM_VCPUS_TO_TEST" >&2
+ break
+ fi
+
+ if ! run_tests "$test_executable" "$src_type" "$NUM_MEMSLOTS_TO_TEST" "$NUM_VCPUS_TO_TEST"; then
+ return_code=$?
+ echo "Test failed for source type '$src_type'. Arguments: -s $src_type -m $NUM_MEMSLOTS_TO_TEST -n $NUM_VCPUS_TO_TEST" >&2
+ break
+ fi
+done
+
+exit "$return_code"
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7 42/42] KVM: selftests: Update private memory exits test to work with per-gmem attributes
From: Ackerley Tng via B4 Relay @ 2026-05-23 0:18 UTC (permalink / raw)
To: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
ira.weiny, jmattson, jthoughton, michael.roth, oupton,
pankaj.gupta, qperret, rick.p.edgecombe, rientjes, shivankg,
steven.price, tabba, willy, wyihan, yan.y.zhao, forkloop,
pratyush, suzuki.poulose, aneesh.kumar, liam, Paolo Bonzini,
Sean Christopherson, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, Dave Hansen, x86, H. Peter Anvin, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Jonathan Corbet, Shuah Khan,
Shuah Khan, Vishal Annapurve, Andrew Morton, Chris Li,
Kairui Song, Kemeng Shi, Nhat Pham, Baoquan He, Barry Song,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Youngjun Park, Qi Zheng,
Shakeel Butt, Kiryl Shutsemau, Jason Gunthorpe, Vlastimil Babka
Cc: kvm, linux-kernel, linux-trace-kernel, linux-doc, linux-kselftest,
linux-mm, linux-coco, Ackerley Tng
In-Reply-To: <20260522-gmem-inplace-conversion-v7-0-2f0fae496530@google.com>
From: Sean Christopherson <seanjc@google.com>
Skip setting memory to private in the private memory exits test when using
per-gmem memory attributes, as memory is initialized to private by default
for guest_memfd, and using vm_mem_set_private() on a guest_memfd instance
requires creating guest_memfd with GUEST_MEMFD_FLAG_MMAP (which is totally
doable, but would need to be conditional and is ultimately unnecessary).
Expect an emulated MMIO instead of a memory fault exit when attributes are
per-gmem, as deleting the memslot effectively drops the private status,
i.e. the GPA becomes shared and thus supports emulated MMIO.
Skip the "memslot not private" test entirely, as private vs. shared state
for x86 software-protected VMs comes from the memory attributes themselves,
and so when doing in-place conversions there can never be a disconnect
between the expected and actual states.
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
---
.../selftests/kvm/x86/private_mem_kvm_exits_test.c | 36 ++++++++++++++++++----
1 file changed, 30 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/kvm/x86/private_mem_kvm_exits_test.c b/tools/testing/selftests/kvm/x86/private_mem_kvm_exits_test.c
index 10db9fe6d9063..70ed16066c63e 100644
--- a/tools/testing/selftests/kvm/x86/private_mem_kvm_exits_test.c
+++ b/tools/testing/selftests/kvm/x86/private_mem_kvm_exits_test.c
@@ -62,8 +62,9 @@ static void test_private_access_memslot_deleted(void)
virt_map(vm, EXITS_TEST_GVA, EXITS_TEST_GPA, EXITS_TEST_NPAGES);
- /* Request to access page privately */
- vm_mem_set_private(vm, EXITS_TEST_GPA, EXITS_TEST_SIZE);
+ /* Request to access page privately. */
+ if (!kvm_has_gmem_attributes)
+ vm_mem_set_private(vm, EXITS_TEST_GPA, EXITS_TEST_SIZE);
pthread_create(&vm_thread, NULL,
(void *(*)(void *))run_vcpu_get_exit_reason,
@@ -74,10 +75,26 @@ static void test_private_access_memslot_deleted(void)
pthread_join(vm_thread, &thread_return);
exit_reason = (u32)(u64)thread_return;
- TEST_ASSERT_EQ(exit_reason, KVM_EXIT_MEMORY_FAULT);
- TEST_ASSERT_EQ(vcpu->run->memory_fault.flags, KVM_MEMORY_EXIT_FLAG_PRIVATE);
- TEST_ASSERT_EQ(vcpu->run->memory_fault.gpa, EXITS_TEST_GPA);
- TEST_ASSERT_EQ(vcpu->run->memory_fault.size, EXITS_TEST_SIZE);
+ /*
+ * If attributes are tracked per-gmem, deleting the memslot that points
+ * at the gmem instance effectively makes the memory shared, and so the
+ * read should trigger emulated MMIO.
+ *
+ * If attributes are tracked per-VM, deleting the memslot shouldn't
+ * affect the private attribute, and so KVM should generate a memory
+ * fault exit (emulated MMIO on private GPAs is disallowed).
+ */
+ if (kvm_has_gmem_attributes) {
+ TEST_ASSERT_EQ(exit_reason, KVM_EXIT_MMIO);
+ TEST_ASSERT_EQ(vcpu->run->mmio.phys_addr, EXITS_TEST_GPA);
+ TEST_ASSERT_EQ(vcpu->run->mmio.len, sizeof(u64));
+ TEST_ASSERT_EQ(vcpu->run->mmio.is_write, false);
+ } else {
+ TEST_ASSERT_EQ(exit_reason, KVM_EXIT_MEMORY_FAULT);
+ TEST_ASSERT_EQ(vcpu->run->memory_fault.flags, KVM_MEMORY_EXIT_FLAG_PRIVATE);
+ TEST_ASSERT_EQ(vcpu->run->memory_fault.gpa, EXITS_TEST_GPA);
+ TEST_ASSERT_EQ(vcpu->run->memory_fault.size, EXITS_TEST_SIZE);
+ }
kvm_vm_free(vm);
}
@@ -88,6 +105,13 @@ static void test_private_access_memslot_not_private(void)
struct kvm_vcpu *vcpu;
u32 exit_reason;
+ /*
+ * Accessing non-private memory as private with a software-protected VM
+ * isn't possible when doing in-place conversions.
+ */
+ if (kvm_has_gmem_attributes)
+ return;
+
vm = vm_create_shape_with_one_vcpu(protected_vm_shape, &vcpu,
guest_repeatedly_read);
--
2.54.0.794.g4f17f83d09-goog
^ permalink raw reply related
* [PATCH v7] blk-mq: add tracepoint block_rq_tag_wait
From: Aaron Tomlin @ 2026-05-23 20:09 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
john.g.garry, loberman, neelx, sean, mproche, chjohnst,
linux-block, linux-kernel, linux-trace-kernel
In high-performance storage environments, particularly when utilising
RAID controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe
latency spikes can occur when fast devices (SSDs) are starved of hardware
tags when sharing the same blk_mq_tag_set.
Currently, diagnosing this specific hardware queue contention is
difficult. When a CPU thread exhausts the tag pool, blk_mq_get_tag()
forces the current thread to block uninterruptible via io_schedule().
While this can be inferred via sched:sched_switch or dynamically
traced by attaching a kprobe to blk_mq_mark_tag_wait(), there is no
dedicated, out-of-the-box observability for this event.
This patch introduces the block_rq_tag_wait tracepoint in the tag
allocation slow-path. It triggers immediately before the task state
is altered to TASK_UNINTERRUPTIBLE (ensuring safety for PREEMPT_RT
locks). It exposes the exact hardware context (hctx) that is starved,
the specific pool experiencing starvation (driver, software scheduler,
or reserved), and the exact pool depth.
This provides storage engineers with a zero-configuration, low-overhead
mechanism to definitively identify shared-tag bottlenecks. For example,
userspace can trivially replicate tag starvation counters using bpftrace:
# bpftrace -e 'tracepoint:block:block_rq_tag_wait { @tag_waits[cpu] = count(); }'
Attaching 1 probe...
^C
@tag_waits[4]: 12
@tag_waits[12]: 87
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Laurence Oberman <loberman@redhat.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
Changes since v6 [1]:
- Dropped Patch 2. Observability is now driven entirely by the tracepoint,
with the commit message updated to demonstrate how userspace (e.g.,
bpftrace) can safely replicate counting out-of-band (Jens Axboe)
- Moved tracepoint call above sbitmap_prepare_to_wait(). This prevents
inadvertently resetting the task state under PREEMPT_RT locks
- Updated the tracepoint signature and TP_fast_assign block to evaluate
the allocation flags. If the submitting context is starved of a reserved
tag (BLK_MQ_REQ_RESERVED), the tracepoint now accurately reports the
severely constrained nr_reserved_tags depth instead of the total nr_tags
depth.
Changes since v5 [2]:
- Replaced this_cpu_inc() with raw_cpu_inc() within
blk_mq_debugfs_inc_wait_tags(). This resolves a preemption warning
triggered under CONFIG_DEBUG_PREEMPT=y, as the routine is invoked from a
preemptible context immediately prior to io_schedule(). This adjustment
deliberately prioritises the reduction of execution overhead over
absolute statistical precision for this diagnostic interface.
Changes since v4 [3]:
- Prevented a NULL pointer dereference in the tracepoint fast-assign for
disk-less request queues by safely checking q->disk before resolving the
dev_t
- Fixed a Use-After-Free (UAF) and permanent memory leak by decoupling
the per-CPU counter allocation from the volatile debugfs lifecycle and
tying it directly to the core hctx lifecycle (i.e., blk_mq_init_hctx()
and blk_mq_exit_hctx())
- Fixed a potential compiler double-fetch bug by wrapping the per-CPU
pointer evaluations with READ_ONCE() in blk_mq_debugfs_inc_wait_tags()
- Passed the appropriate gfp_t flags down to the allocation routines to
maintain the strict GFP_NOIO context
- Updated kernel-doc descriptions to clarify that the NULL pointer
checks guard against memory allocation failures under pressure, rather
than initialisation race conditions
Changes since v3 [4]:
- Transitioned tracking architecture from shared atomic_t variables to
dynamically allocated per-CPU counters to resolve cache line bouncing
(Bart Van Assche)
Changes since v2 [5]:
- Added "Reviewed-by:" and "Tested-by:" tags for patch 1
- Evaluate is_sched_tag directly within TP_fast_assign (Steven Rostedt)
- Introduced atomic counters via debugfs
Changes since v1 [6]:
- Improved the description of the trace point (Damien Le Moal)
- Removed the redundant "active requests" (Laurence Oberman)
- Introduced pool-specific starvation tracking
[1]: https://lore.kernel.org/lkml/20260517213614.350367-1-atomlin@atomlin.com/
[2]: https://lore.kernel.org/lkml/20260427020142.358912-1-atomlin@atomlin.com/
[3]: https://lore.kernel.org/lkml/20260419023036.1419514-1-atomlin@atomlin.com/
[4]: https://lore.kernel.org/lkml/20260319221956.332770-1-atomlin@atomlin.com/
[5]: https://lore.kernel.org/lkml/20260319015300.287653-1-atomlin@atomlin.com/
[6]: https://lore.kernel.org/lkml/20260317182835.258183-1-atomlin@atomlin.com/
---
block/blk-mq-tag.c | 6 +++++
include/trace/events/block.h | 50 ++++++++++++++++++++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/block/blk-mq-tag.c b/block/blk-mq-tag.c
index 33946cdb5716..35deee5bbc73 100644
--- a/block/blk-mq-tag.c
+++ b/block/blk-mq-tag.c
@@ -13,6 +13,7 @@
#include <linux/kmemleak.h>
#include <linux/delay.h>
+#include <trace/events/block.h>
#include "blk.h"
#include "blk-mq.h"
#include "blk-mq-sched.h"
@@ -181,6 +182,11 @@ unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data)
if (tag != BLK_MQ_NO_TAG)
break;
+ /* Log the starvation event before altering task state */
+ trace_block_rq_tag_wait(data->q, data->hctx,
+ data->rq_flags & RQF_SCHED_TAGS,
+ data->flags);
+
sbitmap_prepare_to_wait(bt, ws, &wait, TASK_UNINTERRUPTIBLE);
tag = __blk_mq_get_tag(data, bt);
diff --git a/include/trace/events/block.h b/include/trace/events/block.h
index 6aa79e2d799c..15b2e0edd2d4 100644
--- a/include/trace/events/block.h
+++ b/include/trace/events/block.h
@@ -226,6 +226,56 @@ DECLARE_EVENT_CLASS(block_rq,
IOPRIO_PRIO_LEVEL(__entry->ioprio), __entry->comm)
);
+/**
+ * block_rq_tag_wait - triggered when a request is starved of a tag
+ * @q: request queue of the target device
+ * @hctx: hardware context of the request experiencing starvation
+ * @is_sched_tag: indicates whether the starved pool is the software scheduler
+ * @alloc_flags: allocation flags dictating the specific tag pool
+ *
+ * Called immediately before the submitting context is forced to block due
+ * to the exhaustion of available tags (i.e., physical hardware driver
+ * tags, software scheduler tags, or reserved tags). This trace point
+ * indicates that the context will be placed into an uninterruptible state
+ * via io_schedule() until an active request completes and relinquishes its
+ * assigned tag.
+ */
+TRACE_EVENT(block_rq_tag_wait,
+
+ TP_PROTO(struct request_queue *q, struct blk_mq_hw_ctx *hctx,
+ bool is_sched_tag, unsigned int alloc_flags),
+
+ TP_ARGS(q, hctx, is_sched_tag, alloc_flags),
+
+ TP_STRUCT__entry(
+ __field( dev_t, dev )
+ __field( u32, hctx_id )
+ __field( u32, nr_tags )
+ __field( bool, is_sched_tag )
+ ),
+
+ TP_fast_assign(
+ __entry->dev = q->disk ? disk_devt(q->disk) : 0;
+ __entry->hctx_id = hctx->queue_num;
+ __entry->is_sched_tag = is_sched_tag;
+
+ if (is_sched_tag) {
+ __entry->nr_tags = hctx->sched_tags->nr_tags;
+ } else if (alloc_flags & BLK_MQ_REQ_RESERVED) {
+ __entry->nr_tags = hctx->tags->nr_reserved_tags;
+ } else {
+ __entry->nr_tags = hctx->tags->nr_tags;
+ }
+
+ ),
+
+ TP_printk("%d,%d hctx=%u starved on %s tags (depth=%u)",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->hctx_id,
+ __entry->is_sched_tag ? "scheduler" : "hardware",
+ __entry->nr_tags)
+);
+
/**
* block_rq_insert - insert block operation request into queue
* @rq: block IO operation request
base-commit: 6779b50faa562e6cca1aa6a4649a4d764c6c7e28
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v7] blk-mq: add tracepoint block_rq_tag_wait
From: Aaron Tomlin @ 2026-05-23 23:55 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
john.g.garry, loberman, neelx, sean, mproche, chjohnst,
linux-block, linux-kernel, linux-trace-kernel
In-Reply-To: <20260523200942.587199-1-atomlin@atomlin.com>
[-- Attachment #1: Type: text/plain, Size: 2171 bytes --]
On Sat, May 23, 2026 at 04:09:42PM -0400, Aaron Tomlin wrote:
> +/**
> + * block_rq_tag_wait - triggered when a request is starved of a tag
> + * @q: request queue of the target device
> + * @hctx: hardware context of the request experiencing starvation
> + * @is_sched_tag: indicates whether the starved pool is the software scheduler
> + * @alloc_flags: allocation flags dictating the specific tag pool
> + *
> + * Called immediately before the submitting context is forced to block due
> + * to the exhaustion of available tags (i.e., physical hardware driver
> + * tags, software scheduler tags, or reserved tags). This trace point
> + * indicates that the context will be placed into an uninterruptible state
> + * via io_schedule() until an active request completes and relinquishes its
> + * assigned tag.
> + */
> +TRACE_EVENT(block_rq_tag_wait,
> +
> + TP_PROTO(struct request_queue *q, struct blk_mq_hw_ctx *hctx,
> + bool is_sched_tag, unsigned int alloc_flags),
> +
> + TP_ARGS(q, hctx, is_sched_tag, alloc_flags),
> +
> + TP_STRUCT__entry(
> + __field( dev_t, dev )
> + __field( u32, hctx_id )
> + __field( u32, nr_tags )
> + __field( bool, is_sched_tag )
> + ),
> +
> + TP_fast_assign(
> + __entry->dev = q->disk ? disk_devt(q->disk) : 0;
> + __entry->hctx_id = hctx->queue_num;
> + __entry->is_sched_tag = is_sched_tag;
> +
> + if (is_sched_tag) {
> + __entry->nr_tags = hctx->sched_tags->nr_tags;
> + } else if (alloc_flags & BLK_MQ_REQ_RESERVED) {
> + __entry->nr_tags = hctx->tags->nr_reserved_tags;
> + } else {
> + __entry->nr_tags = hctx->tags->nr_tags;
> + }
> +
> + ),
> +
> + TP_printk("%d,%d hctx=%u starved on %s tags (depth=%u)",
> + MAJOR(__entry->dev), MINOR(__entry->dev),
> + __entry->hctx_id,
> + __entry->is_sched_tag ? "scheduler" : "hardware",
> + __entry->nr_tags)
> +);
> +
> /**
> * block_rq_insert - insert block operation request into queue
> * @rq: block IO operation request
I completely overlooked that a request could legitimately have both
RQF_SCHED_TAGS and BLK_MQ_REQ_RESERVED set simultaneously.
--
Aaron Tomlin
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* [PATCH v8] blk-mq: add tracepoint block_rq_tag_wait
From: Aaron Tomlin @ 2026-05-24 1:42 UTC (permalink / raw)
To: axboe, rostedt, mhiramat, mathieu.desnoyers
Cc: bvanassche, johannes.thumshirn, kch, dlemoal, ritesh.list,
john.g.garry, loberman, neelx, sean, mproche, chjohnst,
linux-block, linux-kernel, linux-trace-kernel
In high-performance storage environments, particularly when utilising
RAID controllers with shared tag sets (BLK_MQ_F_TAG_HCTX_SHARED), severe
latency spikes can occur when fast devices (SSDs) are starved of hardware
tags when sharing the same blk_mq_tag_set.
Currently, diagnosing this specific hardware queue contention is
difficult. When a CPU thread exhausts the tag pool, blk_mq_get_tag()
forces the current thread to block uninterruptible via io_schedule().
While this can be inferred via sched:sched_switch or dynamically
traced by attaching a kprobe to blk_mq_mark_tag_wait(), there is no
dedicated, out-of-the-box observability for this event.
This patch introduces the block_rq_tag_wait tracepoint in the tag
allocation slow-path. It triggers immediately before the task state
is altered to TASK_UNINTERRUPTIBLE (ensuring safety for PREEMPT_RT
locks). It exposes the exact hardware context (hctx) that is starved,
the specific pool experiencing starvation (driver, software scheduler,
or reserved), and the exact pool depth.
This provides storage engineers with a zero-configuration, low-overhead
mechanism to definitively identify shared-tag bottlenecks. For example,
userspace can trivially replicate tag starvation counters using bpftrace:
# bpftrace -e 'tracepoint:block:block_rq_tag_wait { @tag_waits[cpu] = count(); }'
Attaching 1 probe...
^C
@tag_waits[4]: 12
@tag_waits[12]: 87
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Chaitanya Kulkarni <kch@nvidia.com>
Reviewed-by: Laurence Oberman <loberman@redhat.com>
Tested-by: Laurence Oberman <loberman@redhat.com>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
Changes since v7 [1]:
- Added an is_reserved boolean to the trace record to explicitly expose
reserved pool starvation to userspace
- Fixed TP_fast_assign to report the correct nr_reserved_tags depth
when I/O schedulers utilise the reserved pool
Changes since v6 [2]:
- Dropped Patch 2. Observability is now driven entirely by the tracepoint,
with the commit message updated to demonstrate how userspace (e.g.,
bpftrace) can safely replicate counting out-of-band (Jens Axboe)
- Moved tracepoint call above sbitmap_prepare_to_wait(). This prevents
inadvertently resetting the task state under PREEMPT_RT locks
- Updated the tracepoint signature and TP_fast_assign block to evaluate
the allocation flags. If the submitting context is starved of a reserved
tag (BLK_MQ_REQ_RESERVED), the tracepoint now accurately reports the
severely constrained nr_reserved_tags depth instead of the total nr_tags
depth.
Changes since v5 [3]:
- Replaced this_cpu_inc() with raw_cpu_inc() within
blk_mq_debugfs_inc_wait_tags(). This resolves a preemption warning
triggered under CONFIG_DEBUG_PREEMPT=y, as the routine is invoked from a
preemptible context immediately prior to io_schedule(). This adjustment
deliberately prioritises the reduction of execution overhead over
absolute statistical precision for this diagnostic interface.
Changes since v4 [4]:
- Prevented a NULL pointer dereference in the tracepoint fast-assign for
disk-less request queues by safely checking q->disk before resolving the
dev_t
- Fixed a Use-After-Free (UAF) and permanent memory leak by decoupling
the per-CPU counter allocation from the volatile debugfs lifecycle and
tying it directly to the core hctx lifecycle (i.e., blk_mq_init_hctx()
and blk_mq_exit_hctx())
- Fixed a potential compiler double-fetch bug by wrapping the per-CPU
pointer evaluations with READ_ONCE() in blk_mq_debugfs_inc_wait_tags()
- Passed the appropriate gfp_t flags down to the allocation routines to
maintain the strict GFP_NOIO context
- Updated kernel-doc descriptions to clarify that the NULL pointer
checks guard against memory allocation failures under pressure, rather
than initialisation race conditions
Changes since v3 [5]:
- Transitioned tracking architecture from shared atomic_t variables to
dynamically allocated per-CPU counters to resolve cache line bouncing
(Bart Van Assche)
Changes since v2 [6]:
- Added "Reviewed-by:" and "Tested-by:" tags for patch 1
- Evaluate is_sched_tag directly within TP_fast_assign (Steven Rostedt)
- Introduced atomic counters via debugfs
Changes since v1 [7]:
- Improved the description of the trace point (Damien Le Moal)
- Removed the redundant "active requests" (Laurence Oberman)
- Introduced pool-specific starvation tracking
[1]: https://lore.kernel.org/lkml/20260523200942.587199-1-atomlin@atomlin.com/
[2]: https://lore.kernel.org/lkml/20260517213614.350367-1-atomlin@atomlin.com/
[3]: https://lore.kernel.org/lkml/20260427020142.358912-1-atomlin@atomlin.com/
[4]: https://lore.kernel.org/lkml/20260419023036.1419514-1-atomlin@atomlin.com/
[5]: https://lore.kernel.org/lkml/20260319221956.332770-1-atomlin@atomlin.com/
[6]: https://lore.kernel.org/lkml/20260319015300.287653-1-atomlin@atomlin.com/
[7]: https://lore.kernel.org/lkml/20260317182835.258183-1-atomlin@atomlin.com/
---
block/blk-mq-tag.c | 6 ++++
include/trace/events/block.h | 55 ++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
diff --git a/block/blk-mq-tag.c b/block/blk-mq-tag.c
index 33946cdb5716..35deee5bbc73 100644
--- a/block/blk-mq-tag.c
+++ b/block/blk-mq-tag.c
@@ -13,6 +13,7 @@
#include <linux/kmemleak.h>
#include <linux/delay.h>
+#include <trace/events/block.h>
#include "blk.h"
#include "blk-mq.h"
#include "blk-mq-sched.h"
@@ -181,6 +182,11 @@ unsigned int blk_mq_get_tag(struct blk_mq_alloc_data *data)
if (tag != BLK_MQ_NO_TAG)
break;
+ /* Log the starvation event before altering task state */
+ trace_block_rq_tag_wait(data->q, data->hctx,
+ data->rq_flags & RQF_SCHED_TAGS,
+ data->flags);
+
sbitmap_prepare_to_wait(bt, ws, &wait, TASK_UNINTERRUPTIBLE);
tag = __blk_mq_get_tag(data, bt);
diff --git a/include/trace/events/block.h b/include/trace/events/block.h
index 6aa79e2d799c..736e176f6d17 100644
--- a/include/trace/events/block.h
+++ b/include/trace/events/block.h
@@ -226,6 +226,61 @@ DECLARE_EVENT_CLASS(block_rq,
IOPRIO_PRIO_LEVEL(__entry->ioprio), __entry->comm)
);
+/**
+ * block_rq_tag_wait - triggered when a request is starved of a tag
+ * @q: request queue of the target device
+ * @hctx: hardware context of the request experiencing starvation
+ * @is_sched_tag: indicates whether the starved pool is the software scheduler
+ * @alloc_flags: allocation flags dictating the specific tag pool
+ *
+ * Called immediately before the submitting context is forced to block due
+ * to the exhaustion of available tags (i.e., physical hardware driver
+ * tags, software scheduler tags, or reserved tags). This trace point
+ * indicates that the context will be placed into an uninterruptible state
+ * via io_schedule() until an active request completes and relinquishes its
+ * assigned tag.
+ */
+TRACE_EVENT(block_rq_tag_wait,
+
+ TP_PROTO(struct request_queue *q, struct blk_mq_hw_ctx *hctx,
+ bool is_sched_tag, unsigned int alloc_flags),
+
+ TP_ARGS(q, hctx, is_sched_tag, alloc_flags),
+
+ TP_STRUCT__entry(
+ __field( dev_t, dev )
+ __field( u32, hctx_id )
+ __field( u32, nr_tags )
+ __field( bool, is_sched_tag )
+ __field( bool, is_reserved )
+ ),
+
+ TP_fast_assign(
+ __entry->dev = q->disk ? disk_devt(q->disk) : 0;
+ __entry->hctx_id = hctx->queue_num;
+ __entry->is_sched_tag = is_sched_tag;
+ __entry->is_reserved = alloc_flags & BLK_MQ_REQ_RESERVED;
+
+ if (__entry->is_reserved) {
+ __entry->nr_tags = is_sched_tag ?
+ hctx->sched_tags->nr_reserved_tags :
+ hctx->tags->nr_reserved_tags;
+ } else {
+ __entry->nr_tags = is_sched_tag ?
+ hctx->sched_tags->nr_tags :
+ hctx->tags->nr_tags;
+ }
+
+ ),
+
+ TP_printk("%d,%d hctx=%u starved on %s%s tags (depth=%u)",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->hctx_id,
+ __entry->is_sched_tag ? "scheduler" : "hardware",
+ __entry->is_reserved ? " reserved" : "",
+ __entry->nr_tags)
+);
+
/**
* block_rq_insert - insert block operation request into queue
* @rq: block IO operation request
base-commit: 6779b50faa562e6cca1aa6a4649a4d764c6c7e28
--
2.51.0
^ permalink raw reply related
* Re: [RFC PATCH 2/2] tracing: Record and show boot ID in last_boot_info
From: Masami Hiramatsu @ 2026-05-24 1:44 UTC (permalink / raw)
To: Steven Rostedt
Cc: Theodore Ts'o, Jason A . Donenfeld, Mathieu Desnoyers,
linux-kernel, linux-trace-kernel
In-Reply-To: <20260521111630.1f558754@gandalf.local.home>
On Thu, 21 May 2026 11:16:30 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> On Thu, 21 May 2026 23:57:16 +0900
> "Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
>
> > @@ -4804,6 +4806,7 @@ struct trace_mod_entry {
> > struct trace_scratch {
> > unsigned int clock_id;
> > unsigned long text_addr;
> > + u8 boot_id[UUID_SIZE];
> > unsigned long nr_entries;
> > struct trace_mod_entry entries[];
> > };
>
> I just don't like wasting scratch space if boot_id isn't defined. But I
> can't figure out a way to optionally have it there without wasting space
> anyway.
Yeah, it needs to be placed in the scratch area or ring-buffer meta page.
In most cases the boot_id is enabled (random subsystem seems to provide
this UUID always), so it will be rarely waste of memory except
CONFIG_SYSCTL=n.
>
> If the get_boot_id() is accepted by the random folks, then I'm fine with
> this change.
Yeah, BTW, Sashiko found this can be initialized before we get enough
entropy for random seed. Maybe we need one more delay.
Thank you,
>
> -- Steve
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* Re: [PATCH v6] tracing/eprobes: Allow use of BTF names to dereference pointers
From: Masami Hiramatsu @ 2026-05-24 10:15 UTC (permalink / raw)
To: Steven Rostedt
Cc: LKML, Linux trace kernel, Mathieu Desnoyers, Mark Rutland,
Peter Zijlstra, Namhyung Kim, Takaya Saeki, Douglas Raillard,
Tom Zanussi, Andrew Morton, Thomas Gleixner, Ian Rogers,
Jiri Olsa, sashiko-bot@kernel.org,
sashiko-reviews@lists.linux.dev
In-Reply-To: <20260522104521.74981686@gandalf.local.home>
On Fri, 22 May 2026 10:45:21 -0400
Steven Rostedt <rostedt@kernel.org> wrote:
> On Fri, 22 May 2026 07:23:22 -0400
> Steven Rostedt <rostedt@goodmis.org> wrote:
>
> > > > @@ -653,6 +686,20 @@ static int parse_btf_arg(char *varname,
> > > > return -EOPNOTSUPP;
> > > > }
> > > >
> > > > + if (ctx->flags & TPARG_FL_TEVENT) {
> > > > + int ret;
> > > > +
> > > > + ret = parse_trace_event(varname, code, ctx);
> > > > + if (ret < 0)
> > > > + return ret;
> >
> > > When parse_trace_event() returns a negative error code (such as -EINVAL or
> > > -ENOENT) because a field name is invalid, the error is propagated back up
> > > the stack. Does this path miss calling trace_probe_log_err()?
> > > If so, users might receive a generic failure without context or a caret
> > > pointing to the specific syntax error.
> >
> > Hmm, there's a comment in the parse_trace_event() that sets ctx->offset for
> > backward compatibility. I'll investigate to see if we can fix that now.
>
> Masami,
>
> I looked at the code for parse_trace_event() that has:
>
> /* backward compatibility */
> ctx->offset = 0;
> return -EINVAL;
>
> And it was originally introduced by commit 1b8b0cd754cd ("tracing/probes:
> Move event parameter fetching code to common parser"), with:
>
> + ret = parse_trace_event_arg(arg, code, ctx);
> + if (!ret)
> + return 0;
> + if (strcmp(arg, "comm") == 0 || strcmp(arg, "COMM") == 0) {
> + code->op = FETCH_OP_COMM;
> + return 0;
> + }
> + /* backward compatibility */
> + ctx->offset = 0;
> + goto inval;
> + }
> +
>
>
> What was the reason for the "backward compatibility"? Can we make it a real
> error now?
This is because a wrong eprobe syntax parser error position indicator.
In tools/testing/selftests/ftrace/test.d/dynevent/eprobes_syntax_errors.tc:
check_error 'e:foo/bar syscalls/sys_enter_openat arg=^dfd' # BAD_FETCH_ARG
check_error 'e:foo/bar syscalls/sys_enter_openat ^arg=$foo' # BAD_ATTACH_ARG
BAD_FETCH_ARG points the fetcharg name correctly, but the
BAD_ATTACH_ARG points wrong place in the test case.
I think we should fix test case. (Previously, since it was
a cleanup, I didn't changed it)
Thank you,
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
^ permalink raw reply
* [BUG] tracing/uprobe: oversized dynamic ustring triggers WARN_ON_ONCE panic
From: Yifei Chu @ 2026-05-24 14:44 UTC (permalink / raw)
To: linux-trace-kernel
Cc: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, linux-kernel
[-- Attachment #1.1: Type: text/plain, Size: 1802 bytes --]
Hello,
Short version: I can make trace_uprobe hit WARN_ON_ONCE() by creating an
uprobe/uretprobe event with several dynamic ustring fetch args. With
panic_on_warn=1, this becomes a reproducible panic.
The setup is pretty direct. The reproducers mount tracefs, create a trace
event with several ustring arguments pointing at a 4095-byte userspace
string, and then trigger the event. At probe hit time, the dynamic string
sizes are accumulated and prepare_uprobe_buffer() sees a payload larger
than MAX_UCB_BUFFER_SIZE/PAGE_SIZE:
WARN_ON_ONCE(ucb->dsize > MAX_UCB_BUFFER_SIZE)
I reproduced the same class through both uprobe and uretprobe events.
Tested environment:
Linux version 7.0.9, x86_64 QEMU
gcc 12.3.0, GNU ld 2.38
Boot args included: panic_on_warn=1 nokaslr console=ttyS0
Uprobe result:
WARNING: kernel/trace/trace_uprobe.c:982 at
prepare_uprobe_buffer.part.0+0x458/0x5b0
Kernel panic - not syncing: kernel: panic_on_warn set …
Uretprobe result:
triggering uretprobe oversized ustring buffer at offset 0x1db0
WARNING: kernel/trace/trace_uprobe.c:982 at
prepare_uprobe_buffer.part.0+0x458/0x5b0
uretprobe_dispatcher+0x328/0x3e0
Kernel panic - not syncing: kernel: panic_on_warn set …
I checked current mainline source and still see the runtime WARN path in
kernel/trace/trace_uprobe.c. I have reproduced the panic on the 7.0.9 QEMU
build above; I have not yet runtime-tested current mainline.
My expectation is that oversized user-controlled dynamic trace data should
be rejected, capped, or dropped before it reaches a WARN invariant. A
tracefs user should not be able to turn a long string fetch into a kernel
warning/panic.
The attached tarball has README files, both C reproducers, and the full
QEMU logs.
Thanks,
Chuyifei
[-- Attachment #1.2: Type: text/html, Size: 1911 bytes --]
[-- Attachment #2: trace_uprobe_uretprobe_ustring_warn_panic.tar.gz --]
[-- Type: application/x-tar, Size: 26669 bytes --]
^ permalink raw reply
* [BUG] tracing/kprobe: perf dynamic ustring sample can exceed PERF_MAX_TRACE_SIZE and WARN
From: Yifei Chu @ 2026-05-24 14:44 UTC (permalink / raw)
To: linux-trace-kernel
Cc: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, linux-kernel
[-- Attachment #1.1: Type: text/plain, Size: 1776 bytes --]
Hello,
Short version: I can make a kprobe/kretprobe trace event with dynamic
ustring fetch args ask perf_trace_buf_alloc() for more than
PERF_MAX_TRACE_SIZE. That hits WARN_ONCE(), and with panic_on_warn=1 it
becomes a reproducible kernel panic.
The reproducers create a kprobe or kretprobe trace event with several
ustring args pointing at a 4095-byte userspace string, open the event
through perf_event_open(PERF_TYPE_TRACEPOINT), and trigger it. The dynamic
payload size is then passed to perf_trace_buf_alloc():
WARN_ONCE(size > PERF_MAX_TRACE_SIZE, …)
I reproduced this through both kprobe and kretprobe events.
Tested environment:
Linux version 7.0.9, x86_64 QEMU
gcc 12.3.0, GNU ld 2.38
Boot args included: panic_on_warn=1 nokaslr console=ttyS0
Kprobe result:
perf buffer not large enough, wanted 16420, have 8192
WARNING: kernel/trace/trace_event_perf.c:405 at
perf_trace_buf_alloc+0x111/0x160
Kernel panic - not syncing: kernel: panic_on_warn set …
Kretprobe result:
perf buffer not large enough, wanted 16428, have 8192
WARNING: kernel/trace/trace_event_perf.c:405 at
perf_trace_buf_alloc+0x111/0x160
kretprobe_perf_func+0x24b/0x750
Kernel panic - not syncing: kernel: panic_on_warn set …
I checked current mainline source and still see PERF_MAX_TRACE_SIZE as 8192
and the WARN_ONCE path in perf_trace_buf_alloc(). I have reproduced the
panic on the 7.0.9 QEMU build above; I have not yet runtime-tested current
mainline.
My expectation is that a user-defined dynamic trace payload that is too
large for the perf trace buffer should be rejected, capped, or dropped
without reaching WARN_ONCE().
The attached tarball has README files, both C reproducers, and the full
QEMU logs.
Thanks,
Chuyifei
[-- Attachment #1.2: Type: text/html, Size: 1888 bytes --]
[-- Attachment #2: trace_kprobe_kretprobe_perf_ustring_warn_panic.tar.gz --]
[-- Type: application/x-tar, Size: 26750 bytes --]
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox