--- /dev/null
+From 0389c305ef56cbadca4cbef44affc0ec3213ed30 Mon Sep 17 00:00:00 2001
+From: Lance Yang <lance.yang@linux.dev>
+Date: Wed, 17 Sep 2025 21:31:37 +0800
+Subject: selftests/mm: skip soft-dirty tests when CONFIG_MEM_SOFT_DIRTY is disabled
+
+From: Lance Yang <lance.yang@linux.dev>
+
+commit 0389c305ef56cbadca4cbef44affc0ec3213ed30 upstream.
+
+The madv_populate and soft-dirty kselftests currently fail on systems
+where CONFIG_MEM_SOFT_DIRTY is disabled.
+
+Introduce a new helper softdirty_supported() into vm_util.c/h to ensure
+tests are properly skipped when the feature is not enabled.
+
+Link: https://lkml.kernel.org/r/20250917133137.62802-1-lance.yang@linux.dev
+Fixes: 9f3265db6ae8 ("selftests: vm: add test for Soft-Dirty PTE bit")
+Signed-off-by: Lance Yang <lance.yang@linux.dev>
+Acked-by: David Hildenbrand <david@redhat.com>
+Suggested-by: David Hildenbrand <david@redhat.com>
+Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
+Cc: Shuah Khan <shuah@kernel.org>
+Cc: Gabriel Krisman Bertazi <krisman@collabora.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ tools/testing/selftests/mm/madv_populate.c | 21 -------
+ tools/testing/selftests/mm/soft-dirty.c | 5 +
+ tools/testing/selftests/mm/vm_util.c | 77 +++++++++++++++++++++++++++++
+ tools/testing/selftests/mm/vm_util.h | 1
+ 4 files changed, 84 insertions(+), 20 deletions(-)
+
+--- a/tools/testing/selftests/mm/madv_populate.c
++++ b/tools/testing/selftests/mm/madv_populate.c
+@@ -264,23 +264,6 @@ static void test_softdirty(void)
+ munmap(addr, SIZE);
+ }
+
+-static int system_has_softdirty(void)
+-{
+- /*
+- * There is no way to check if the kernel supports soft-dirty, other
+- * than by writing to a page and seeing if the bit was set. But the
+- * tests are intended to check that the bit gets set when it should, so
+- * doing that check would turn a potentially legitimate fail into a
+- * skip. Fortunately, we know for sure that arm64 does not support
+- * soft-dirty. So for now, let's just use the arch as a corse guide.
+- */
+-#if defined(__aarch64__)
+- return 0;
+-#else
+- return 1;
+-#endif
+-}
+-
+ int main(int argc, char **argv)
+ {
+ int nr_tests = 16;
+@@ -288,7 +271,7 @@ int main(int argc, char **argv)
+
+ pagesize = getpagesize();
+
+- if (system_has_softdirty())
++ if (softdirty_supported())
+ nr_tests += 5;
+
+ ksft_print_header();
+@@ -300,7 +283,7 @@ int main(int argc, char **argv)
+ test_holes();
+ test_populate_read();
+ test_populate_write();
+- if (system_has_softdirty())
++ if (softdirty_supported())
+ test_softdirty();
+
+ err = ksft_get_fail_cnt();
+--- a/tools/testing/selftests/mm/soft-dirty.c
++++ b/tools/testing/selftests/mm/soft-dirty.c
+@@ -193,8 +193,11 @@ int main(int argc, char **argv)
+ int pagesize;
+
+ ksft_print_header();
+- ksft_set_plan(15);
+
++ if (!softdirty_supported())
++ ksft_exit_skip("soft-dirty is not support\n");
++
++ ksft_set_plan(15);
+ pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
+ if (pagemap_fd < 0)
+ ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
+--- a/tools/testing/selftests/mm/vm_util.c
++++ b/tools/testing/selftests/mm/vm_util.c
+@@ -97,6 +97,42 @@ uint64_t read_pmd_pagesize(void)
+ return strtoul(buf, NULL, 10);
+ }
+
++char *__get_smap_entry(void *addr, const char *pattern, char *buf, size_t len)
++{
++ int ret;
++ FILE *fp;
++ char *entry = NULL;
++ char addr_pattern[MAX_LINE_LENGTH];
++
++ ret = snprintf(addr_pattern, MAX_LINE_LENGTH, "%08lx-",
++ (unsigned long)addr);
++ if (ret >= MAX_LINE_LENGTH)
++ ksft_exit_fail_msg("%s: Pattern is too long\n", __func__);
++
++ fp = fopen(SMAP_FILE_PATH, "r");
++ if (!fp)
++ ksft_exit_fail_msg("%s: Failed to open file %s\n", __func__,
++ SMAP_FILE_PATH);
++
++ if (!check_for_pattern(fp, addr_pattern, buf, len))
++ goto err_out;
++
++ /* Fetch the pattern in the same block */
++ if (!check_for_pattern(fp, pattern, buf, len))
++ goto err_out;
++
++ /* Trim trailing newline */
++ entry = strchr(buf, '\n');
++ if (entry)
++ *entry = '\0';
++
++ entry = buf + strlen(pattern);
++
++err_out:
++ fclose(fp);
++ return entry;
++}
++
+ bool __check_huge(void *addr, char *pattern, int nr_hpages,
+ uint64_t hpage_size)
+ {
+@@ -269,3 +305,44 @@ int uffd_unregister(int uffd, void *addr
+
+ return ret;
+ }
++
++static bool check_vmflag(void *addr, const char *flag)
++{
++ char buffer[MAX_LINE_LENGTH];
++ const char *flags;
++ size_t flaglen;
++
++ flags = __get_smap_entry(addr, "VmFlags:", buffer, sizeof(buffer));
++ if (!flags)
++ ksft_exit_fail_msg("%s: No VmFlags for %p\n", __func__, addr);
++
++ while (true) {
++ flags += strspn(flags, " ");
++
++ flaglen = strcspn(flags, " ");
++ if (!flaglen)
++ return false;
++
++ if (flaglen == strlen(flag) && !memcmp(flags, flag, flaglen))
++ return true;
++
++ flags += flaglen;
++ }
++}
++
++bool softdirty_supported(void)
++{
++ char *addr;
++ bool supported = false;
++ const size_t pagesize = getpagesize();
++
++ /* New mappings are expected to be marked with VM_SOFTDIRTY (sd). */
++ addr = mmap(0, pagesize, PROT_READ | PROT_WRITE,
++ MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
++ if (!addr)
++ ksft_exit_fail_msg("mmap failed\n");
++
++ supported = check_vmflag(addr, "sd");
++ munmap(addr, pagesize);
++ return supported;
++}
+--- a/tools/testing/selftests/mm/vm_util.h
++++ b/tools/testing/selftests/mm/vm_util.h
+@@ -51,6 +51,7 @@ int uffd_register(int uffd, void *addr,
+ int uffd_unregister(int uffd, void *addr, uint64_t len);
+ int uffd_register_with_ioctls(int uffd, void *addr, uint64_t len,
+ bool miss, bool wp, bool minor, uint64_t *ioctls);
++bool softdirty_supported(void);
+
+ /*
+ * On ppc64 this will only work with radix 2M hugepage size