selftests: vm: add test for Soft-Dirty PTE bit
authorGabriel Krisman Bertazi <krisman@collabora.com>
Fri, 29 Apr 2022 06:16:11 +0000 (23:16 -0700)
committerakpm <akpm@linux-foundation.org>
Fri, 29 Apr 2022 06:16:11 +0000 (23:16 -0700)
This introduces three tests:

1) Sanity check soft dirty basic semantics: allocate area, clean,
   dirty, check if the SD bit is flipped.

2) Check VMA reuse: validate the VM_SOFTDIRTY usage

3) Check soft-dirty on huge pages

This was motivated by Will Deacon's fix commit 912efa17e512 ("mm: proc:
Invalidate TLB after clearing soft-dirty page state").  I was tracking the
same issue that he fixed, and this test would have caught it.

Link: https://lkml.kernel.org/r/20220420084036.4101604-2-usama.anjum@collabora.com
Signed-off-by: Gabriel Krisman Bertazi <krisman@collabora.com>
Signed-off-by: Muhammad Usama Anjum <usama.anjum@collabora.com>
Co-developed-by: Muhammad Usama Anjum <usama.anjum@collabora.com>
Cc: Will Deacon <will@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
tools/testing/selftests/vm/.gitignore
tools/testing/selftests/vm/Makefile
tools/testing/selftests/vm/config
tools/testing/selftests/vm/soft-dirty.c [new file with mode: 0644]

index d7507f3..3cb4fa7 100644 (file)
@@ -29,5 +29,6 @@ write_to_hugetlbfs
 hmm-tests
 memfd_secret
 local_config.*
+soft-dirty
 split_huge_page_test
 ksm_tests
index 6fd9678..f122837 100644 (file)
@@ -50,6 +50,7 @@ TEST_GEN_FILES += on-fault-limit
 TEST_GEN_FILES += thuge-gen
 TEST_GEN_FILES += transhuge-stress
 TEST_GEN_FILES += userfaultfd
+TEST_GEN_PROGS += soft-dirty
 TEST_GEN_PROGS += split_huge_page_test
 TEST_GEN_FILES += ksm_tests
 
@@ -95,6 +96,7 @@ KSFT_KHDR_INSTALL := 1
 include ../lib.mk
 
 $(OUTPUT)/madv_populate: vm_util.c
+$(OUTPUT)/soft-dirty: vm_util.c
 $(OUTPUT)/split_huge_page_test: vm_util.c
 
 ifeq ($(MACHINE),x86_64)
index 60e82da..be087c4 100644 (file)
@@ -4,3 +4,5 @@ CONFIG_TEST_VMALLOC=m
 CONFIG_DEVICE_PRIVATE=y
 CONFIG_TEST_HMM=m
 CONFIG_GUP_TEST=y
+CONFIG_TRANSPARENT_HUGEPAGE=y
+CONFIG_MEM_SOFT_DIRTY=y
diff --git a/tools/testing/selftests/vm/soft-dirty.c b/tools/testing/selftests/vm/soft-dirty.c
new file mode 100644 (file)
index 0000000..08ab62a
--- /dev/null
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <malloc.h>
+#include <sys/mman.h>
+#include "../kselftest.h"
+#include "vm_util.h"
+
+#define PAGEMAP_FILE_PATH "/proc/self/pagemap"
+#define TEST_ITERATIONS 10000
+
+static void test_simple(int pagemap_fd, int pagesize)
+{
+       int i;
+       char *map;
+
+       map = aligned_alloc(pagesize, pagesize);
+       if (!map)
+               ksft_exit_fail_msg("mmap failed\n");
+
+       clear_softdirty();
+
+       for (i = 0 ; i < TEST_ITERATIONS; i++) {
+               if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
+                       ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
+                       break;
+               }
+
+               clear_softdirty();
+               // Write something to the page to get the dirty bit enabled on the page
+               map[0]++;
+
+               if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
+                       ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
+                       break;
+               }
+
+               clear_softdirty();
+       }
+       free(map);
+
+       ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
+}
+
+static void test_vma_reuse(int pagemap_fd, int pagesize)
+{
+       char *map, *map2;
+
+       map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
+       if (map == MAP_FAILED)
+               ksft_exit_fail_msg("mmap failed");
+
+       // The kernel always marks new regions as soft dirty
+       ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
+                        "Test %s dirty bit of allocated page\n", __func__);
+
+       clear_softdirty();
+       munmap(map, pagesize);
+
+       map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
+       if (map2 == MAP_FAILED)
+               ksft_exit_fail_msg("mmap failed");
+
+       // Dirty bit is set for new regions even if they are reused
+       if (map == map2)
+               ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
+                                "Test %s dirty bit of reused address page\n", __func__);
+       else
+               ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__);
+
+       munmap(map2, pagesize);
+}
+
+static void test_hugepage(int pagemap_fd, int pagesize)
+{
+       char *map;
+       int i, ret;
+       size_t hpage_len = read_pmd_pagesize();
+
+       map = memalign(hpage_len, hpage_len);
+       if (!map)
+               ksft_exit_fail_msg("memalign failed\n");
+
+       ret = madvise(map, hpage_len, MADV_HUGEPAGE);
+       if (ret)
+               ksft_exit_fail_msg("madvise failed %d\n", ret);
+
+       for (i = 0; i < hpage_len; i++)
+               map[i] = (char)i;
+
+       if (check_huge(map)) {
+               ksft_test_result_pass("Test %s huge page allocation\n", __func__);
+
+               clear_softdirty();
+               for (i = 0 ; i < TEST_ITERATIONS ; i++) {
+                       if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
+                               ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
+                               break;
+                       }
+
+                       clear_softdirty();
+                       // Write something to the page to get the dirty bit enabled on the page
+                       map[0]++;
+
+                       if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
+                               ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
+                               break;
+                       }
+                       clear_softdirty();
+               }
+
+               ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
+       } else {
+               // hugepage allocation failed. skip these tests
+               ksft_test_result_skip("Test %s huge page allocation\n", __func__);
+               ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
+       }
+       free(map);
+}
+
+int main(int argc, char **argv)
+{
+       int pagemap_fd;
+       int pagesize;
+
+       ksft_print_header();
+       ksft_set_plan(5);
+
+       pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
+       if (pagemap_fd < 0)
+               ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
+
+       pagesize = getpagesize();
+
+       test_simple(pagemap_fd, pagesize);
+       test_vma_reuse(pagemap_fd, pagesize);
+       test_hugepage(pagemap_fd, pagesize);
+
+       close(pagemap_fd);
+
+       return ksft_exit_pass();
+}