[kvm-unit-tests PATCH 32/33] arm: Add memtest support

Suzuki K Poulose
2024-04-12T10:39:18Z

From: Joey Gouly <joey.gouly@arm.com>

Add a test to touch all memory allocated to the guest.
Provides options to allocate in block, shared mode etc.
Also adds a "memstress" variant which would test all the
combinations in order.

PS: The memory allocator fragments the available memory
on page allocation and doesn't allow merging them for a
higher order allocation. Hence, all the block alloc tests
are run one after the other, before any page allocation
tests are run

Signed-off-by: Joey Gouly <joey.gouly@arm.com>
Co-developed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
---
arm/selftest.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 122 insertions(+), 1 deletion(-)

diff --git a/arm/selftest.c b/arm/selftest.c
index 7bc5fb76..d9fd9750 100644
--- a/arm/selftest.c
+++ b/arm/selftest.c
@@ -9,6 +9,7 @@
#include <util.h>
#include <devicetree.h>
#include <memregions.h>
+#include <alloc_page.h>
#include <vmalloc.h>
#include <asm/setup.h>
#include <asm/ptrace.h>
@@ -435,6 +436,123 @@ static void cpu_report(void *data __unused)
report_info("CPU%3d: MPIDR=%010" PRIx64, cpu, mpidr);
}

+
+/*
+ * do_memtest: Accepts the following paramters.
+ *
+ * shared[=0/1] - Use shared page for the memtests.
+ * block[=0/1] - Use SZ_2M allocation/free.
+ * nofree - Do not free the pages after the test.
+ */
+static void do_memtest(int argc, char *argv[])
+{
+ int i;
+ int npages = 0;
+ bool result = true;
+ const char pattern = 0xFB;
+ void *prev_page = NULL;
+ uintptr_t *page_to_free = NULL;
+ int size;
+ void* (*alloc_order_fn)(unsigned int);
+ void (*free_order_fn)(void *, unsigned int);
+ bool shared = false;
+ bool block = false;
+ bool nofree = false;
+ int order = 0;
+
+ for (i = 2; i < argc; i++) {
+ long val, len;
+
+ len = parse_keyval(argv[i], &val);
+ if (len == -1) {
+ if (!strcmp(argv[i], "shared")) {
+ shared = true;
+ continue;
+ } else if (!strcmp(argv[i], "nofree")) {
+ nofree = true;
+ continue;
+ } else if (!strcmp(argv[i], "block")) {
+ block = true;
+ } else {
+ printf("Unknown options %s\n", argv[i]);
+ abort();
+ }
+ } else if (!strncmp(argv[i], "block", len)) {
+ block = !!val;
+ } else if (!strncmp(argv[i], "shared", len)) {
+ shared = !!val;
+ }
+ }
+
+ /* Block mapping is 2MB */
+ if (block)
+ order = (21 - PAGE_SHIFT);
+
+ size = (1 << order) * PAGE_SIZE;
+ if (shared) {
+ alloc_order_fn = &alloc_pages_shared;
+ free_order_fn = &free_pages_shared_by_order;
+ } else {
+ alloc_order_fn = &alloc_pages;
+ free_order_fn = &free_pages_by_order;
+ }
+
+ report_info("Running %smemtest with size %dK%s, order=%d",
+ shared ? "shared " : "",
+ size >> 10,
+ nofree ? " with no freeing" :"",
+ order);
+
+ while (1) {
+ void *page = alloc_order_fn(order);
+
+ if (!page)
+ break;
+ npages += 1;
+
+ memset(page, pattern, size);
+
+ for (i = 0; i < size; i += 1) {
+ if (((char *)page)[i] != pattern) {
+ result = false;
+ report(false, "Failed to find the pattern in page %p, expected: %d, got: %d\n",
+ page, pattern, ((char *)page)[i]);
+ goto exit;
+ }
+ }
+
+ /*
+ * Save a pointer to the allocated page so that it can be
+ * free'd at the end of the test.
+ */
+ *(uintptr_t *)page = (uintptr_t)prev_page;
+ prev_page = page;
+ }
+
+ page_to_free = prev_page;
+ while (!nofree && page_to_free) {
+ prev_page = (uintptr_t *)(*page_to_free);
+ free_order_fn(page_to_free, order);
+ page_to_free = prev_page;
+ }
+
+exit:
+ report(result, "Tested with %dKB", (npages * size) >> 10);
+}
+
+static void do_memstress(void)
+{
+ char shared[16] = "shared";
+ char block[16] = "block";
+ char nofree[16] = "nofree";
+ char null[4] = "";
+
+ do_memtest(4, &((char *[]){ null, null, shared, block })[0]);
+ do_memtest(3, &((char *[]){ null, null, block })[0]);
+ do_memtest(3, &((char *[]){ null, null, shared })[0]);
+ do_memtest(3, &((char *[]){ null, null, nofree })[0]);
+}
+
int main(int argc, char **argv)
{
report_prefix_push("selftest");
@@ -466,7 +584,10 @@ int main(int argc, char **argv)
smp_rmb(); /* Paired with wmb in cpu_report(). */
report(cpumask_full(&valid), "MPIDR test on all CPUs");
report_info("%d CPUs reported back", nr_cpus);
-
+ } else if (strcmp(argv[1], "memtest") == 0) {
+ do_memtest(argc, argv);
+ } else if (strcmp(argv[1], "memstress") == 0) {
+ do_memstress();
} else {
printf("Unknown subtest\n");
abort();