summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-20 18:17:08 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-20 18:17:08 -0700
commited3b875bea55a3ec4837113356df2ead11115af9 (patch)
treefd38aec3d0b15bc5db711df2ed20e2df2cf307d8 /tools
parent7f063b2f17eaba2a35e251aa53627f2a70d536e2 (diff)
parent08745c62350126bc31b09548137be87e2866f628 (diff)
downloadlinux-next-ed3b875bea55a3ec4837113356df2ead11115af9.tar.gz
linux-next-ed3b875bea55a3ec4837113356df2ead11115af9.zip
Merge tag 'mm-stable-2026-08-18-18-39' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm
Pull MM updates from Andrew Morton: - "mm: drop "sub" prefix from various places" (Dev Jain) page->folio conversion and a naming cleanup - "mm/kasan: remove redundant initialization for kasan_flag_write_only" (Igor Putko) KASAN cleanup work - "mm/filemap: reduce unnecessary xarray lookups" (Chi Zhiling) Small speedup in the pagecaache read code - "mm/percpu: Fix possible NOFS/NOIO reclaim recursion" (Kaitao Cheng) Improve the vmalloc code - mainly the avoidance of GFP_KERNEL allocations when the caller asked for GFP_NOFS or GFP_NOIO - "mm/kmemleak: avoid soft lockup when scanning task stacks" (Breno Leitao) Avoid a soft lockup watchdog trigger from the kmemleak scanning code in extreme situations - "mm/page_owner: misc cleanups" (Ye Liu) Cleanups to the page_owner code. For some reason lots of people have been working on the page_owner code this cycle. - "mm: convert to walk_page_range_vma() to eliminate find_vma()" (Kefeng Wang) Simplify and accelerate the page walking library function - "mm/migrate: preparatory cleanups for batch copy and offload" (Shivank Garg) Cleanups in the migration code - "mm/page_owner: add per-fd filter infrastructure for print_mode and NUMA filtering" (Zhen Ni) Per-fd filtering to page_owner in order to reduce the sometimes vast amount of output it can produce - "mm: Refactor bootmem gigantic hugepage allocation" (Muchun Song) Fixes and preparatory cleanups around bootmem HugeTLB handling, sparse initialization ordering, and related vmemmap setup - "mm/zsmalloc: reduce lock contention in zs_free()" (Wenchao Hao) Reduce lock contention in zs_free(), which dominates the unmap path under memory pressure on Android (LMK kills) and on x86 servers running zswap-heavy workloads. Up to 1.83x improvement in microbenchmarking. - "move alloc_tag.c file under mm/" (Suren Baghdasaryan) - "samples/damon: handle damon_{start,stop}() failures" (SJ Park) Fix improper handling of damon_start(), damon_stop(), and damon_call() failures across DAMON sample modules to prevent potential memory leaks, operation disruptions and use-after-free bugs - "mm/damon/sysfs: kobject_del() directories that users can create/remove" (SJ Park) Fix delayed sysfs directory removal under DEBUG_KOBJECT_RELEASE causeing creation failures due to duplicate directory names by adding missing kobject_del() calls before creating new directories - "mm: cleanup clear_not_present_full_ptes()" (David Hildenbrand) Clean up the core pte handling code - "selftests/damon: misc fixes for test bugs" (Kunwu Chan) Fix several bugs in the DAMON selftests - "selftests/damon: fix memcg_path staging handling" (Cheng Nie) Fix a bug in _damon_sysfs.py for damos_filter memcg_path setup, and add a test case for it in sysfs.py. - "selftests/damon: test kdamond refresh_ms" (Ruslan Valiyev) Selftest coverage for DAMON's refresh_ms sysfs feature by updating the test control module and verifying that scheme stats update automatically without manual intervention - "mm/damon: five misc fixups" (Akinobu Mita) Miscellaneous DAMON fixups. - "mm/damon/core: detect internal variation above max_nr_regions/2" (Jiayuan Chen) Fix DAMON's region splitting behavior when region counts exceed half the maximum budget by dynamically scaling down the split fraction as the limit approaches, preventing large regions from staying un-split, and add corresponding KUnit test coverage - "mm: preparatory patches for PMD level swap entries" (Usama Arif) Refactor and clean up PMD softleaf helpers, call sites, and architecture flags to lay the groundwork for a follow-up series that introduces PMD page table swap entries - "mm/damon: update, optimize, and clean up doc, tests, and code" (SJ Park) Update DAMON design and ABI documentation, expands unit and selftest coverage, optimize damon_commit_target_regions(), and clean up recently added sysfs interface code for better readability - "mm/vmpressure: reduce CPU, memory and code overhead on cgroup v2" (Usama Arif) Optimize vmpressure() by skipping unnecessary work on cgroup v2 for userspace event notifications and refactor v1-only eventfd handling into mm/memcontrol-v1.c to reduce memory overhead and code complexity - "selftests/mm: refactor pkey helpers and fix mmap error handling" (Hongfu Li) Refactor pkeys shared tracing and assertion helpers into a common file, unify protection key selftests to use consistent diagnostic logging and assertions, and enforce standardized MAP_FAILED return checks for mmap() calls across the tests - "mm/damon: optimize out nr_accesses_bp" (SJ Park) Replace the error-prone, continuously updated nr_accesses_bp field in damon_region with an on-demand moving sum function, reducing structure memory overhead and avoiding state corruption bugs - "Open HugeTLB allocation routine for more generic use" (Ackerley Tng) Decouple HugeTLB folio allocation from VMA dependencies by introducing hugetlb_alloc_folio(), enabling subsystems like guest_memfd to allocate HugeTLB folios without standard VMA reservations or pseudo-VMAs - "mm/damon: provide pseudo moving sum probe_hits" (SJ Park) Integrate DAMON's probe_hits attribute counter into the pseudo moving sum infrastructure, enabling real-time, online monitoring without waiting for full aggregation intervals - "mm: Some cleanups for page allocator APIs" (Brendan Jackman) Simplify and refactor the page allocator entry points and flags by unifying allocation paths, adding internal alloc_flags arguments, and eliminating redundant __ prefixed alloc_pages variants. - "Fix incorrect access of hugetlb pte entries" (Dev Jain) Enforce the consistent use of huge_ptep_get() instead of ptep_get() for HugeTLB entries and fixes an unaligned address issue in arm64's huge_ptep_get() implementation - "mm/damon: validate all parameters in the core" (SJ Park) Consolidate parameter validation into the DAMON core specifically within damon_start() and damon_commit_ctx() to centralize error checking, eliminate caller-side redundant checks and to improve maintenance efficiency - "tools/mm/page_owner_sort: fix filtering and cleanup issues" (Yichong Chen) Rename is_need() to filter_record() for clearer return semantics, fix per-record allocation memory leaks and bound output copies in search_pattern() to address an existing buffer issue - "memcg: bail out reclaim when memcg is dying" (Jiayuan Chen) Mitigate a system-wide stall which occurs when a cgroup is removed while one of its memory control files is doing synchronous reclaim - "mm/memory-failure: add panic option for unrecoverable pages" (Breno Leitao) Introduce an opt-in vm.panic_on_unrecoverable_memory_failure sysctl that immediately panics the kernel on unrecoverable memory errors in kernel-owned pages to preserve error context and prevent delayed, silent data corruption - "mm/damon: refactor damon_{start,stop,commit}() for simple error handling" (SJ Park) Refactor the DAMON core API functions to guarantee that all contexts are fully stopped when damon_start(), damon_stop(), or damon_commit() fail, eliminating the need for complex and error-prone caller-side cleanup code - "Keep tail page private zero at free and folio split" (Zi Yan) Add checks to ensure tail_page->private is zero when freeing compound or high-order pages and when promoting tail pages during large folio splits. By validating these fields at free and split time, it allows the removal of redundant private field clearing inside prep_compound_tail() - "mm: drop redundant lru_add_drain in anon folio reuse paths" (Barry Song) Eliminate redundant lru_add_drain() calls in wp_can_reuse_anon_folio() and do_swap_page() to reduce LRU lock contention and system overhead By validating folio refcounts against the LRU cache before draining and removing unnecessary drains in the swap path, it achieves up to a 30.5% reduction in drain calls during heavy swap workloads - "mm: clean up folio LRU and swap declarations" (Jianyue Wu) Reorganize folio LRU and swap code by relocating page-cluster state to mm/swap_state.c, renaming mm/swap.c to mm/folio.c, and moving MM-internal reclaim declarations into mm/internal.h. - "userfaultfd: working set tracking for VM guest memory" (Kiryl Shutsemau) Add userfaultfd support for tracking the working set of VM guest memory, so a VMM can identify hot pages and reclaim cold ones to tiered or remote storage - "mm: remove CONFIG_HAVE_BOOTMEM_INFO_NODE (Part 2)" (David Hildenbrand) Remove the remaining pieces of CONFIG_HAVE_BOOTMEM_INFO_NODE, performing some smaller cleanups around freeing of reserved vmemmap pages on the way. - "mm/damon: update probe hits for runtime parameter commits" (SJ Park) Ensure that DAMON's probe_hits attribute counter is properly updated when monitoring intervals are changed at runtime, matching the behavior of nr_accesses. To achieve this, it refactors and renames existing helper functions for shared use, applies the updates to probe_hits, and handles edge cases in damon_probe_hits_mvsum() to maintain measurement accuracy. - "KSM: performance optimizations for rmap_walk_ksm" (xu xin) Resolve a severe KSM reverse-mapping performance bottleneck where thousands of split VMAs sharing a single anon_vma cause extended lock contention. By adding an interval-filtering check during the rmap walk, it reduces worst-case anon_vma lock hold times from over 500ms down to under 2ms, preventing application freezes and latency spikes under memory pressure. - "mm: split a couple of headers from internal.h" (Mike Rapoport) Split declarations related to mm_init, memblock, vmalloc and sparse into new headers - "KSM: use linear_page_index in collect_procs_ksm()" (xu xin) Apply the interval tree optimization from rmap_walk_ksm() to collect_procs_ksm() to avoid iterating over non-matching VMAs during KSM memory error handling. It hoists loop-invariant address initialization and restricts the anon_vma_interval_tree_foreach walk to a targeted page offset range, reducing redundant checks and improving lookup efficiency. - "selftests/mm: avoid false failures in hugetlb and KSM tests" (Sayali Patil) Fix issues in the hugetlb and KSM MM selftest categories that can report failures when the prerequisites for the tests are not satisfied - "mm/damon: introduce data attributes only monitoring" (SJ Park) Introduce attribute-weighted region management in DAMON, allowing users to prioritize specific data attributes (such as page sizes or cgroups) over or instead of access monitoring. By assigning weights to attribute probes, DAMON can completely disable access tracking and adjust monitoring regions based on weighted probe-hit counters to optimize monitoring quality for attribute-focused workloads. - "mm/hmm: Add mmap lock-drop support for userfaultfd-backed mappings" (Stanislav Kinsburskii) Extend hmm_range_fault() to support userfaultfd-backed regions by allowing the mmap lock to be dropped during fault handling via a new hmm_range_fault_locked() helper. By accepting a locked pointer and signaling retry status when lock release occurs, it enables page fault resolution in userfaultfd regions while preserving backward compatibility for existing callers. - "mm: make VMA page offset handling more consistent" (Lorenzo Stoakes) Clean up and standardize how vma->vm_pgoff is accessed and manipulated across file-backed and anonymous mappings in the kernel It introduces dedicated helper functions such as vma_start_pgoff(), vma_end_pgoff(), vma_set_pgoff() and linear_page_delta() while renaming rmap interval tree helpers to better reflect their functionality. These changes establish a cleaner foundation for future work that will unify virtual page offset indexing for all anonymous and CoW'd folios. - "mm: handle device-private PMDs in walk callbacks" (Usama Arif) Address kernel panics and state corruption caused by MM walk callbacks reaching non-present device-private PMD swap entries created during HMM migrations It ensures that functions which acquire pmd_trans_huge_lock() properly recognize device-private PMDs instead of assuming a present THP or a standard migration entry. - "mm/rmap: Refactor try_to_unmap_one" (Dev Jain) Refactor try_to_unmap_one by modularizing Hugetlb, anonymous-lazyfree, and anonymous-swapbacked logic into dedicated functions, laying the structural groundwork for batched anonymous large folio unmapping. - "Docs/ABI/damon: sysfs ABI document fixes and additions" (Song Hu) Fix typos and fills in missing entries in the DAMON sysfs ABI document - "dax/kmem: atomic whole-device hotplug via sysfs" (Gregory Price) Introduce an atomic sysfs state attribute and supporting DAX/MM infrastructure to prevent userland races when offlining and removing entire memory regions By adding an unplugged state alongside standard online modes, it enables whole-device atomic hotplug control while preserving backward compatibility. - "mm: convert more vm_flags_t users to vma_flags_t" (Lorenzo Stoakes) Continue transitioning the kernel from the deprecated vm_flags_t type to vma_flags_t across core memory management infrastructure. It replaces legacy type usage in core functions such as do_mmap(), unmapped area allocation, mm->def_vma_flags, and VMA operations like mlock, mprotect, and mremap. - "Two small patches to clean up mm/mm_slot.h" (xu xin) Refactor mm_slot.h by introducing mm_slot_remove() to unify duplicate slot deletion sequences in khugepaged and KSM. It also adds code documentation explaining why mm_slot_lookup and mm_slot_insert must remain as preprocessor macros rather than static inline functions. - "mm/damon/core: hide core-private struct fields" (SJ Park) Clean up DAMON core structures by consistently marking internal-only fields with private: comment tags to prevent improper direct access from outer layers. It enforces encapsulation across core structures including damon_region, damon_target, and damon_ctx and updates DAMON_SYSFS to interact through approved access APIs instead of exposing raw struct members. - "mm/damon: unurgent fixes for infinite loop, NULL de-ref and races" (SJ Park) Address potential infinite loops, NULL dereferences, and race conditions identified in DAMON It fixes an infinite loop triggered by extreme user configurations, a NULL pointer dereference within unit tests and minor monitoring accuracy degradation caused by subtle runtime races. - "mm/page_alloc: fixes for free_pages_nolock() on RT/UP" (Brendan Jackman) Fix an NMI safety flaw in __free_frozen_pages() where freeing pages on non-SMP or PREEMPT_RT kernels can bypass can_spin_trylock() checks via non-PCP or isolated migration paths. It also resolves potential kernel crashes and privilege escalation risks triggered when BPF tracing runs in NMI context alongside memory hotplug or large allocation frees. - "mm/page_alloc: couple of followups for recent cleanups" (Brendan Jackman) Clean up and update page allocator nomenclature, documentation, and debug assertions. It aligns internal FPI_ flags with the public "nolock" naming convention, removes outdated internal implementation details from high-level page allocator comments, and eliminates obsolete VM_BUG_ON() assertions in allocation paths. - "mm/mseal: further cleanups" (Lorenzo Stoakes) Refactor and simplify the mseal implementation by clarifying API boundaries and removing unnecessary code complexity. It replaces generic do_mseal() usage outside the syscall with a dedicated mseal_mmap_page_zero() helper for MMAP_PAGE_ZERO, eliminates mm_struct parameters to enforce that sealing applies only to current->mm, and streamlines overall logic and comments with no functional changes intended. - "mm/vmscan: fix swappiness=max and clean up per-node proactive reclaim" (Ridong Chen) Resolve reclaim behavior bugs and clean up function parameters across memory reclaim paths It fixes swappiness=max in both standard reclaim and MGLRU so unswappable anonymous memory no longer falls back to evicting page cache, ensures reclaim_store() returns accurate error codes instead of collapsing all failures into -EAGAIN, and removes the obsolete gfp_mask parameter from __node_reclaim(). - "mm: mincore: misc cleanups" (Kefeng Wang) Clean up and simplifies the mincore code. Most importantly, it removes the historical special behavior that always reports VM_PFNMAP pages as non-resident. - "mm/huge_memory: drop dead split helper variants" (Kiryl Shutsemau) Two trivial cleanups in the folio split API - "mm/damon: fix uninitialized DAMOS field and kunit exec expectation bugs" (SJ Park) Resolve minor operational and testing bugs in DAMON identified by Sashiko. It initializes the damos->last_applied field to prevent occasional efficiency degradation and fixes invalid memory accesses in DAMON KUnit tests during test failure handling. - "cleanup for stable_page_flags()" (Jinjiang Tu) Clean up and refactor stable_page_flags() used by /proc/kpageflags without altering functionality. It uses BIT_ULL() to prevent shift-overflow warnings on 64-bit flag bits, converts folio-specific flag checks to standard folio_test_*() helpers, and removes redundant CONFIG_PAGE_IDLE_FLAG handling. - "Batch unmap of uffd-wp file folios" (Dev Jain) Extend batched folio unmapping support to file folios within userfaultfd write-protect (uffd-wp) VMAs by adding batching capabilities to pte_install_uffd_wp_if_needed(). This removes special-case restrictions on uffd-wp VMAs in try_to_unmap_one(), significantly simplifying the function's control flow and complexity. - "mm/early_ioremap: clarify and clean up early_ioremap_reset()" (Sang-Heon Jeon) Clarify and clean up the architecture-specific usage of __late_set_fixmap() and __late_clear_fixmap() after early_ioremap_reset() It adds explicit documentation regarding when early_ioremap_reset() must be called and removes redundant macro definitions and reset calls in the RISC-V and ARM64 architectures. - "mm: fix reclaim storms in defrag_mode" (Johannes Weiner) Address severe performance regressions, swap storms, and spurious OOMs caused by vm.defrag_mode=1 under high memory pressure in Meta production It updates the page allocator slowpath so non-movable allocation requests actively trigger direct reclaim and direct compaction at pageblock_order scale, allowing them to claim whole pageblocks rather than spinning unproductively. - "zram: lockmap tweaks" (Sebastian Siewior) Optimize and fix lockdep tracking for zram devices by consolidating per-entry lockmaps and isolate lock classes across multiple instances This reduces memory overhead by replacing per-entry lockdep_map instances with a single map per struct zram, and assigns a dynamic lock_class_key to each instance to prevent false deadlock reports when different zram devices are backed by distinct filesystems. * tag 'mm-stable-2026-08-18-18-39' of git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm: (501 commits) selftests/mm: thuge-gen: fix test_shmget() for PAGE_SIZE check selftests/mm: unpoison pages in memory-failure teardown mm/shmem: downgrade final i_blocks check in shmem_evict_inode() to pr_warn() mm/khugepaged: replace mutex_lock/mutex_unlock usage with guard macro mm/zsmalloc: fix release order of locks in zs_page_migrate() Documentation: zram: remove sections numbering ksm: stop iterating VMAs when ksm_test_exit returns true mm: fold userfaultfd_rwp() to false without CONFIG_ARCH_HAS_PTE_PROTNONE mm/migrate: report RCU-tasks quiescent states in migrate_pages_batch() zram: use a custom key for each zram object zram: move lockmap to be per-zram instead per table selftests/mm: fix gup_longterm EINVAL error message mm: page_alloc: fix non-movable reclaim storm in defrag_mode mm: page_alloc: move capture_control to the page allocator mm: compaction: support non-movable compaction for pageblock requests mm: page_alloc: __GFP_FS lockdep annotation for direct compaction hugetlb: evaluate subpool free state while locked mm/damon: remove trailing semicolons after function definitions mm/damon/ops-common: prevent migration fallback to non-target nodes mm/damon: update outdated comment about DAMOS filter handling ...
Diffstat (limited to 'tools')
-rw-r--r--tools/include/linux/gfp_types.h7
-rw-r--r--tools/include/uapi/linux/fs.h1
-rw-r--r--tools/include/uapi/linux/userfaultfd.h54
-rw-r--r--tools/mm/.gitignore1
-rw-r--r--tools/mm/Makefile4
-rw-r--r--tools/mm/page_owner_filter.c310
-rw-r--r--tools/mm/page_owner_sort.c87
-rw-r--r--tools/testing/memblock/internal.h20
-rw-r--r--tools/testing/memblock/mm_init.h24
-rw-r--r--tools/testing/selftests/Makefile1
-rw-r--r--tools/testing/selftests/damon/Makefile1
-rw-r--r--tools/testing/selftests/damon/_damon_sysfs.py23
-rwxr-xr-xtools/testing/selftests/damon/damos_apply_interval.py2
-rwxr-xr-xtools/testing/selftests/damon/damos_quota_goal.py2
-rwxr-xr-xtools/testing/selftests/damon/damos_tried_regions.py4
-rwxr-xr-xtools/testing/selftests/damon/drgn_dump_damon_status.py3
-rwxr-xr-xtools/testing/selftests/damon/sysfs.py35
-rwxr-xr-xtools/testing/selftests/damon/sysfs.sh79
-rwxr-xr-xtools/testing/selftests/damon/sysfs_refresh.py75
-rwxr-xr-xtools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py2
-rw-r--r--tools/testing/selftests/dax/Makefile6
-rw-r--r--tools/testing/selftests/dax/config4
-rwxr-xr-xtools/testing/selftests/dax/dax-kmem-hotplug.sh317
-rw-r--r--tools/testing/selftests/dax/settings1
-rw-r--r--tools/testing/selftests/mincore/mincore_selftest.c12
-rw-r--r--tools/testing/selftests/mm/Makefile1
-rw-r--r--tools/testing/selftests/mm/gup_longterm.c2
-rw-r--r--tools/testing/selftests/mm/hmm-tests.c792
-rw-r--r--tools/testing/selftests/mm/hugepage_settings.c2
-rw-r--r--tools/testing/selftests/mm/hugetlb-vmemmap.c132
-rw-r--r--tools/testing/selftests/mm/ksm_tests.c28
-rw-r--r--tools/testing/selftests/mm/memory-failure.c44
-rw-r--r--tools/testing/selftests/mm/mremap_dontunmap.c2
-rw-r--r--tools/testing/selftests/mm/mseal_test.c106
-rw-r--r--tools/testing/selftests/mm/pagemap_ioctl.c110
-rw-r--r--tools/testing/selftests/mm/pkey-helpers.h4
-rw-r--r--tools/testing/selftests/mm/pkey-powerpc.h2
-rw-r--r--tools/testing/selftests/mm/pkey_sighandler_tests.c115
-rw-r--r--tools/testing/selftests/mm/pkey_util.c90
-rw-r--r--tools/testing/selftests/mm/protection_keys.c99
-rw-r--r--tools/testing/selftests/mm/rmap.c81
-rwxr-xr-xtools/testing/selftests/mm/run_vmtests.sh3
-rw-r--r--tools/testing/selftests/mm/thuge-gen.c15
-rw-r--r--tools/testing/selftests/mm/uffd-common.c9
-rw-r--r--tools/testing/selftests/mm/uffd-unit-tests.c919
-rw-r--r--tools/testing/selftests/mm/vm_util.c26
-rw-r--r--tools/testing/selftests/mm/vm_util.h1
-rw-r--r--tools/testing/vma/Makefile2
-rw-r--r--tools/testing/vma/include/dup.h52
-rw-r--r--tools/testing/vma/include/stubs.h16
-rw-r--r--tools/testing/vma/shared.c9
-rw-r--r--tools/testing/vma/shared.h43
-rw-r--r--tools/testing/vma/tests/merge.c40
-rw-r--r--tools/testing/vma/vma_internal.h4
54 files changed, 2813 insertions, 1011 deletions
diff --git a/tools/include/linux/gfp_types.h b/tools/include/linux/gfp_types.h
index 54ca0c88bab6..da47aa089f01 100644
--- a/tools/include/linux/gfp_types.h
+++ b/tools/include/linux/gfp_types.h
@@ -55,7 +55,6 @@ enum {
#ifdef CONFIG_LOCKDEP
___GFP_NOLOCKDEP_BIT,
#endif
- ___GFP_NO_OBJ_EXT_BIT,
___GFP_LAST_BIT
};
@@ -96,7 +95,6 @@ enum {
#else
#define ___GFP_NOLOCKDEP 0
#endif
-#define ___GFP_NO_OBJ_EXT BIT(___GFP_NO_OBJ_EXT_BIT)
/*
* Physical address zone modifiers (see linux/mmzone.h - low four bits)
@@ -137,17 +135,12 @@ enum {
* node with no fallbacks or placement policy enforcements.
*
* %__GFP_ACCOUNT causes the allocation to be accounted to kmemcg.
- *
- * %__GFP_NO_OBJ_EXT causes slab allocation to have no object extension.
- * mark_obj_codetag_empty() should be called upon freeing for objects allocated
- * with this flag to indicate that their NULL tags are expected and normal.
*/
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)
#define __GFP_WRITE ((__force gfp_t)___GFP_WRITE)
#define __GFP_HARDWALL ((__force gfp_t)___GFP_HARDWALL)
#define __GFP_THISNODE ((__force gfp_t)___GFP_THISNODE)
#define __GFP_ACCOUNT ((__force gfp_t)___GFP_ACCOUNT)
-#define __GFP_NO_OBJ_EXT ((__force gfp_t)___GFP_NO_OBJ_EXT)
/**
* DOC: Watermark modifiers
diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h
index 24ddf7bc4f25..f0a26309b6d5 100644
--- a/tools/include/uapi/linux/fs.h
+++ b/tools/include/uapi/linux/fs.h
@@ -364,6 +364,7 @@ typedef int __bitwise __kernel_rwf_t;
#define PAGE_IS_HUGE (1 << 6)
#define PAGE_IS_SOFT_DIRTY (1 << 7)
#define PAGE_IS_GUARD (1 << 8)
+#define PAGE_IS_ACCESSED (1 << 9)
/*
* struct page_region - Page region with flags
diff --git a/tools/include/uapi/linux/userfaultfd.h b/tools/include/uapi/linux/userfaultfd.h
index 4283de22d5b6..c8e99df95219 100644
--- a/tools/include/uapi/linux/userfaultfd.h
+++ b/tools/include/uapi/linux/userfaultfd.h
@@ -25,7 +25,8 @@
#define UFFD_API ((__u64)0xAA)
#define UFFD_API_REGISTER_MODES (UFFDIO_REGISTER_MODE_MISSING | \
UFFDIO_REGISTER_MODE_WP | \
- UFFDIO_REGISTER_MODE_MINOR)
+ UFFDIO_REGISTER_MODE_MINOR | \
+ UFFDIO_REGISTER_MODE_RWP)
#define UFFD_API_FEATURES (UFFD_FEATURE_PAGEFAULT_FLAG_WP | \
UFFD_FEATURE_EVENT_FORK | \
UFFD_FEATURE_EVENT_REMAP | \
@@ -42,10 +43,13 @@
UFFD_FEATURE_WP_UNPOPULATED | \
UFFD_FEATURE_POISON | \
UFFD_FEATURE_WP_ASYNC | \
- UFFD_FEATURE_MOVE)
+ UFFD_FEATURE_MOVE | \
+ UFFD_FEATURE_RWP | \
+ UFFD_FEATURE_RWP_ASYNC)
#define UFFD_API_IOCTLS \
((__u64)1 << _UFFDIO_REGISTER | \
(__u64)1 << _UFFDIO_UNREGISTER | \
+ (__u64)1 << _UFFDIO_SET_MODE | \
(__u64)1 << _UFFDIO_API)
#define UFFD_API_RANGE_IOCTLS \
((__u64)1 << _UFFDIO_WAKE | \
@@ -54,13 +58,15 @@
(__u64)1 << _UFFDIO_MOVE | \
(__u64)1 << _UFFDIO_WRITEPROTECT | \
(__u64)1 << _UFFDIO_CONTINUE | \
- (__u64)1 << _UFFDIO_POISON)
+ (__u64)1 << _UFFDIO_POISON | \
+ (__u64)1 << _UFFDIO_RWPROTECT)
#define UFFD_API_RANGE_IOCTLS_BASIC \
((__u64)1 << _UFFDIO_WAKE | \
(__u64)1 << _UFFDIO_COPY | \
(__u64)1 << _UFFDIO_WRITEPROTECT | \
(__u64)1 << _UFFDIO_CONTINUE | \
- (__u64)1 << _UFFDIO_POISON)
+ (__u64)1 << _UFFDIO_POISON | \
+ (__u64)1 << _UFFDIO_RWPROTECT)
/*
* Valid ioctl command number range with this API is from 0x00 to
@@ -79,6 +85,8 @@
#define _UFFDIO_WRITEPROTECT (0x06)
#define _UFFDIO_CONTINUE (0x07)
#define _UFFDIO_POISON (0x08)
+#define _UFFDIO_RWPROTECT (0x09)
+#define _UFFDIO_SET_MODE (0x0A)
#define _UFFDIO_API (0x3F)
/* userfaultfd ioctl ids */
@@ -103,6 +111,10 @@
struct uffdio_continue)
#define UFFDIO_POISON _IOWR(UFFDIO, _UFFDIO_POISON, \
struct uffdio_poison)
+#define UFFDIO_RWPROTECT _IOWR(UFFDIO, _UFFDIO_RWPROTECT, \
+ struct uffdio_rwprotect)
+#define UFFDIO_SET_MODE _IOW(UFFDIO, _UFFDIO_SET_MODE, \
+ struct uffdio_set_mode)
/* read() structure */
struct uffd_msg {
@@ -158,6 +170,7 @@ struct uffd_msg {
#define UFFD_PAGEFAULT_FLAG_WRITE (1<<0) /* If this was a write fault */
#define UFFD_PAGEFAULT_FLAG_WP (1<<1) /* If reason is VM_UFFD_WP */
#define UFFD_PAGEFAULT_FLAG_MINOR (1<<2) /* If reason is VM_UFFD_MINOR */
+#define UFFD_PAGEFAULT_FLAG_RWP (1<<3) /* If reason is VM_UFFD_RWP */
struct uffdio_api {
/* userland asks for an API number and the features to enable */
@@ -230,6 +243,18 @@ struct uffdio_api {
*
* UFFD_FEATURE_MOVE indicates that the kernel supports moving an
* existing page contents from userspace.
+ *
+ * UFFD_FEATURE_RWP indicates that the kernel supports
+ * UFFDIO_REGISTER_MODE_RWP for read-write protection tracking.
+ * Pages are made inaccessible via UFFDIO_RWPROTECT and faults
+ * are delivered when the pages are re-accessed.
+ *
+ * UFFD_FEATURE_RWP_ASYNC indicates asynchronous mode for
+ * UFFDIO_REGISTER_MODE_RWP. When set, faults on read-write
+ * protected pages are auto-resolved by the kernel (PTE
+ * permissions restored immediately) without delivering a message
+ * to the userfaultfd handler. Use PAGEMAP_SCAN with inverted
+ * PAGE_IS_ACCESSED to find pages that were not re-accessed.
*/
#define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0)
#define UFFD_FEATURE_EVENT_FORK (1<<1)
@@ -248,6 +273,8 @@ struct uffdio_api {
#define UFFD_FEATURE_POISON (1<<14)
#define UFFD_FEATURE_WP_ASYNC (1<<15)
#define UFFD_FEATURE_MOVE (1<<16)
+#define UFFD_FEATURE_RWP (1<<17)
+#define UFFD_FEATURE_RWP_ASYNC (1<<18)
__u64 features;
__u64 ioctls;
@@ -263,6 +290,7 @@ struct uffdio_register {
#define UFFDIO_REGISTER_MODE_MISSING ((__u64)1<<0)
#define UFFDIO_REGISTER_MODE_WP ((__u64)1<<1)
#define UFFDIO_REGISTER_MODE_MINOR ((__u64)1<<2)
+#define UFFDIO_REGISTER_MODE_RWP ((__u64)1<<3)
__u64 mode;
/*
@@ -356,6 +384,14 @@ struct uffdio_poison {
__s64 updated;
};
+struct uffdio_rwprotect {
+ struct uffdio_range range;
+ /* !RWP means undo RWP-protection */
+#define UFFDIO_RWPROTECT_MODE_RWP ((__u64)1<<0)
+#define UFFDIO_RWPROTECT_MODE_DONTWAKE ((__u64)1<<1)
+ __u64 mode;
+};
+
struct uffdio_move {
__u64 dst;
__u64 src;
@@ -374,6 +410,16 @@ struct uffdio_move {
__s64 move;
};
+struct uffdio_set_mode {
+ /*
+ * Toggle async mode for features at runtime.
+ * Supported: UFFD_FEATURE_RWP_ASYNC.
+ * Setting a bit in both enable and disable is invalid.
+ */
+ __u64 enable;
+ __u64 disable;
+};
+
/*
* Flags for the userfaultfd(2) system call itself.
*/
diff --git a/tools/mm/.gitignore b/tools/mm/.gitignore
index 922879f93fc8..1446a659e540 100644
--- a/tools/mm/.gitignore
+++ b/tools/mm/.gitignore
@@ -2,3 +2,4 @@
slabinfo
page-types
page_owner_sort
+thp_swap_allocator_test
diff --git a/tools/mm/Makefile b/tools/mm/Makefile
index f5725b5c23aa..858186a6eefd 100644
--- a/tools/mm/Makefile
+++ b/tools/mm/Makefile
@@ -3,7 +3,7 @@
#
include ../scripts/Makefile.include
-BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test
+BUILD_TARGETS=page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test
INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps
LIB_DIR = ../lib/api
@@ -23,7 +23,7 @@ $(LIBS):
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean:
- $(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test
+ $(RM) page-types slabinfo page_owner_sort page_owner_filter thp_swap_allocator_test
make -C $(LIB_DIR) clean
sbindir ?= /usr/sbin
diff --git a/tools/mm/page_owner_filter.c b/tools/mm/page_owner_filter.c
new file mode 100644
index 000000000000..1d1f0a38678a
--- /dev/null
+++ b/tools/mm/page_owner_filter.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * User-space helper to filter page_owner output per-fd
+ *
+ * Example use:
+ * ./page_owner_filter -m handle
+ * ./page_owner_filter -m stack_handle
+ * ./page_owner_filter -n 0,1,2
+ *
+ * See Documentation/mm/page_owner.rst
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <signal.h>
+
+#define MAX_CMD_LEN 512
+
+static void usage(const char *prog)
+{
+ fprintf(stderr, "Usage: %s [OPTIONS]\n", prog);
+ fprintf(stderr, "\nOptions:\n");
+ fprintf(stderr, " -m, --mode MODE : print_mode (stack, handle, or stack_handle)\n");
+ fprintf(stderr, " -n, --nid NID_LIST : NUMA node IDs (comma-separated or ranges)\n");
+ fprintf(stderr, " -o, --output FILE : output file (default: stdout)\n");
+ fprintf(stderr, " -h, --help : show this help message\n");
+ fprintf(stderr, "\nExamples:\n");
+ fprintf(stderr, " %s -m stack\n", prog);
+ fprintf(stderr, " %s -m handle\n", prog);
+ fprintf(stderr, " %s -m stack_handle\n", prog);
+ fprintf(stderr, " %s -m stack -o output.txt\n", prog);
+ fprintf(stderr, " %s -n 0,1,2\n", prog);
+ fprintf(stderr, " %s -m stack -n 0\n", prog);
+}
+
+static int validate_mode(const char *mode)
+{
+ if (strcmp(mode, "stack") == 0 ||
+ strcmp(mode, "handle") == 0 ||
+ strcmp(mode, "stack_handle") == 0)
+ return 0;
+
+ fprintf(stderr, "Error: Invalid mode '%s'\n", mode);
+ fprintf(stderr, "Valid modes: stack, handle, stack_handle\n");
+ return -1;
+}
+
+static int validate_nid_list(const char *nid_list)
+{
+ const char *p;
+ int i = 0;
+ int has_digit = 0;
+ int in_range = 0;
+ int prev_num = 0;
+ int curr_num = 0;
+
+ if (!nid_list || strlen(nid_list) == 0)
+ return 0;
+
+ for (p = nid_list; *p; p++) {
+ if (*p == ',') {
+ if (!has_digit) {
+ fprintf(stderr, "Error: Invalid nid_list format\n");
+ return -1;
+ }
+ if (in_range && prev_num > curr_num) {
+ fprintf(stderr,
+ "Error: Invalid range %d-%d (start must be <= end)\n",
+ prev_num, curr_num);
+ return -1;
+ }
+ i = 0;
+ has_digit = 0;
+ in_range = 0;
+ prev_num = 0;
+ curr_num = 0;
+ continue;
+ }
+
+ if (*p == '-') {
+ if (!has_digit) {
+ fprintf(stderr,
+ "Error: Invalid nid_list format ");
+ fprintf(stderr,
+ "(dash without preceding number)\n");
+ return -1;
+ }
+ if (in_range) {
+ fprintf(stderr, "Error: Multiple dashes in nid_list\n");
+ return -1;
+ }
+ prev_num = curr_num;
+ curr_num = 0;
+ i = 0;
+ has_digit = 0;
+ in_range = 1;
+ continue;
+ }
+
+ if (!isdigit((unsigned char)*p)) {
+ fprintf(stderr, "Error: Invalid character '%c' in nid_list\n", *p);
+ return -1;
+ }
+
+ if (i > 5) {
+ fprintf(stderr, "Error: NID too long (max 65536)\n");
+ return -1;
+ }
+ curr_num = curr_num * 10 + (*p - '0');
+ i++;
+ has_digit = 1;
+ }
+
+ if (!has_digit) {
+ fprintf(stderr, "Error: Invalid nid_list format\n");
+ return -1;
+ }
+
+ if (in_range && prev_num > curr_num) {
+ fprintf(stderr,
+ "Error: Invalid range %d-%d (start must be <= end)\n",
+ prev_num, curr_num);
+ return -1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *output_file = NULL;
+ char filter_cmd[MAX_CMD_LEN];
+ FILE *output = NULL;
+ int fd = -1;
+ ssize_t ret;
+ char buf[4096];
+ int opt;
+ size_t cmd_len = 0;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ static struct option long_options[] = {
+ {"mode", required_argument, 0, 'm'},
+ {"nid", required_argument, 0, 'n'},
+ {"output", required_argument, 0, 'o'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ filter_cmd[0] = '\0';
+
+ if (argc > 1) {
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
+ usage(argv[0]);
+ return 0;
+ }
+ }
+ }
+
+ /* Check if page_owner exists and is readable */
+ if (access("/sys/kernel/debug/page_owner", F_OK) != 0) {
+ if (errno == ENOENT)
+ fprintf(stderr, "Error: /sys/kernel/debug/page_owner does not exist\n");
+ else
+ perror("Error accessing /sys/kernel/debug/page_owner");
+ fprintf(stderr, "Make sure page_owner is enabled in kernel\n");
+ return 1;
+ }
+
+ while ((opt = getopt_long(argc, argv, "m:n:o:h", long_options, NULL)) != -1) {
+ int len;
+
+ switch (opt) {
+ case 'm': {
+ const char *mode = optarg;
+
+ if (validate_mode(mode) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%smode=%s", cmd_len > 0 ? " " : "", mode);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
+ case 'n': {
+ const char *nid_list = optarg;
+
+ if (validate_nid_list(nid_list) < 0)
+ return 1;
+ len = snprintf(filter_cmd + cmd_len, MAX_CMD_LEN - cmd_len,
+ "%snid=%s", cmd_len > 0 ? " " : "", nid_list);
+ if (len < 0 || cmd_len + len >= MAX_CMD_LEN) {
+ fprintf(stderr, "Error: Command too long\n");
+ return 1;
+ }
+ cmd_len += len;
+ break;
+ }
+ case 'o':
+ output_file = optarg;
+ break;
+ case 'h':
+ /* Already handled above */
+ break;
+ default:
+ usage(argv[0]);
+ return 1;
+ }
+ }
+
+ /* At least one filter must be specified */
+ if (cmd_len == 0) {
+ fprintf(stderr, "Error: At least one filter (-m or -n) must be specified\n\n");
+ usage(argv[0]);
+ return 1;
+ }
+
+ /* Open page_owner for read-write - this will fail if kernel doesn't support write */
+ fd = open("/sys/kernel/debug/page_owner", O_RDWR);
+ if (fd < 0) {
+ if (errno == EACCES || errno == EPERM) {
+ fprintf(stderr, "Error: /sys/kernel/debug/page_owner ");
+ fprintf(stderr, "does not support write access\n");
+ fprintf(stderr, "This kernel does not support ");
+ fprintf(stderr, "per-fd filtering.\n");
+ fprintf(stderr, "Please ensure you have a kernel with ");
+ fprintf(stderr, "per-fd filtering support.\n");
+ } else {
+ perror("Error opening /sys/kernel/debug/page_owner");
+ }
+ return 1;
+ }
+
+ if (output_file) {
+ output = fopen(output_file, "w");
+ if (!output) {
+ perror("open output file");
+ close(fd);
+ return 1;
+ }
+ } else {
+ output = stdout;
+ }
+
+ ret = write(fd, filter_cmd, strlen(filter_cmd));
+
+ if (ret < 0) {
+ if (errno == EINVAL) {
+ fprintf(stderr, "Error: Kernel rejected the filter command.\n");
+ fprintf(stderr, "Possible causes:\n");
+ fprintf(stderr, " - Kernel does not support per-fd filtering\n");
+ fprintf(stderr, " - NUMA node has no memory\n");
+ fprintf(stderr, " - Unknown reason\n");
+ } else {
+ perror("write filter command");
+ }
+ goto out;
+ }
+
+ if ((size_t)ret != strlen(filter_cmd))
+ fprintf(stderr, "Warning: Partial write (%zd/%zu)\n", ret, strlen(filter_cmd));
+
+ /* Read and display filtered output */
+ ret = 0;
+ while ((ret = read(fd, buf, sizeof(buf))) > 0) {
+ size_t written = fwrite(buf, 1, ret, output);
+
+ if (written != (size_t)ret) {
+ if (errno == EPIPE) {
+ /* Pipe closed, treat as success */
+ ret = 0;
+ goto out;
+ }
+ perror("write output");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (ret < 0) {
+ perror("read page_owner");
+ goto out;
+ }
+
+ if (fflush(output)) {
+ if (errno == EPIPE) {
+ /* Pipe closed, treat as success */
+ ret = 0;
+ } else {
+ perror("flush output");
+ ret = -1;
+ }
+ }
+
+out:
+ close(fd);
+ if (output != stdout)
+ fclose(output);
+ return ret < 0 ? 1 : 0;
+}
diff --git a/tools/mm/page_owner_sort.c b/tools/mm/page_owner_sort.c
index e6954909401c..35d3d254941c 100644
--- a/tools/mm/page_owner_sort.c
+++ b/tools/mm/page_owner_sort.c
@@ -43,6 +43,13 @@ enum FILTER_BIT {
FILTER_TGID = 1<<2,
FILTER_COMM = 1<<3
};
+
+enum FILTER_RESULT {
+ FILTER_ERROR,
+ FILTER_SKIP,
+ FILTER_MATCH
+};
+
enum CULL_BIT {
CULL_PID = 1<<1,
CULL_TGID = 1<<2,
@@ -230,7 +237,8 @@ static int remove_pattern(regex_t *pattern, char *buf, int len)
return len - (pmatch[1].rm_eo - pmatch[1].rm_so);
}
-static int search_pattern(regex_t *pattern, char *pattern_str, char *buf)
+static int search_pattern(regex_t *pattern, char *pattern_str,
+ size_t pattern_str_size, char *buf)
{
int err, val_len;
regmatch_t pmatch[2];
@@ -242,8 +250,14 @@ static int search_pattern(regex_t *pattern, char *pattern_str, char *buf)
return -1;
}
val_len = pmatch[1].rm_eo - pmatch[1].rm_so;
+ if ((size_t)val_len >= pattern_str_size) {
+ if (debug_on)
+ fprintf(stderr, "pattern too long in %s\n", buf);
+ return -1;
+ }
memcpy(pattern_str, buf + pmatch[1].rm_so, val_len);
+ pattern_str[val_len] = '\0';
return 0;
}
@@ -300,7 +314,8 @@ static int get_page_num(char *buf)
char order_str[FIELD_BUFF] = {0};
char *endptr;
- search_pattern(&order_pattern, order_str, buf);
+ if (search_pattern(&order_pattern, order_str, sizeof(order_str), buf) < 0)
+ return 0;
errno = 0;
order_val = strtol(order_str, &endptr, 10);
if (order_val > 64 || errno != 0 || endptr == order_str || *endptr != '\0') {
@@ -318,7 +333,8 @@ static pid_t get_pid(char *buf)
char pid_str[FIELD_BUFF] = {0};
char *endptr;
- search_pattern(&pid_pattern, pid_str, buf);
+ if (search_pattern(&pid_pattern, pid_str, sizeof(pid_str), buf) < 0)
+ return -1;
errno = 0;
pid = strtol(pid_str, &endptr, 10);
if (errno != 0 || endptr == pid_str || *endptr != '\0') {
@@ -337,7 +353,8 @@ static pid_t get_tgid(char *buf)
char tgid_str[FIELD_BUFF] = {0};
char *endptr;
- search_pattern(&tgid_pattern, tgid_str, buf);
+ if (search_pattern(&tgid_pattern, tgid_str, sizeof(tgid_str), buf) < 0)
+ return -1;
errno = 0;
tgid = strtol(tgid_str, &endptr, 10);
if (errno != 0 || endptr == tgid_str || *endptr != '\0') {
@@ -356,7 +373,9 @@ static __u64 get_ts_nsec(char *buf)
char ts_nsec_str[FIELD_BUFF] = {0};
char *endptr;
- search_pattern(&ts_nsec_pattern, ts_nsec_str, buf);
+ if (search_pattern(&ts_nsec_pattern, ts_nsec_str,
+ sizeof(ts_nsec_str), buf) < 0)
+ return -1;
errno = 0;
ts_nsec = strtoull(ts_nsec_str, &endptr, 10);
if (errno != 0 || endptr == ts_nsec_str || *endptr != '\0') {
@@ -372,9 +391,15 @@ static char *get_comm(char *buf)
{
char *comm_str = malloc(TASK_COMM_LEN);
+ if (!comm_str)
+ return NULL;
+
memset(comm_str, 0, TASK_COMM_LEN);
- search_pattern(&comm_pattern, comm_str, buf);
+ if (search_pattern(&comm_pattern, comm_str, TASK_COMM_LEN, buf) < 0) {
+ free(comm_str);
+ return NULL;
+ }
errno = 0;
if (errno != 0) {
if (debug_on)
@@ -386,6 +411,12 @@ static char *get_comm(char *buf)
return comm_str;
}
+static void free_block_list(struct block_list *block)
+{
+ free(block->comm);
+ free(block->txt);
+}
+
static int get_arg_type(const char *arg)
{
if (!strcmp(arg, "pid") || !strcmp(arg, "p"))
@@ -450,39 +481,56 @@ static bool match_str_list(const char *str, char **list, int list_size)
return false;
}
-static bool is_need(char *buf)
+static enum FILTER_RESULT filter_record(char *buf)
{
+ char *comm;
+
if ((filter & FILTER_PID) && !match_num_list(get_pid(buf), fc.pids, fc.pids_size))
- return false;
+ return FILTER_SKIP;
if ((filter & FILTER_TGID) &&
!match_num_list(get_tgid(buf), fc.tgids, fc.tgids_size))
- return false;
+ return FILTER_SKIP;
+ if (!(filter & FILTER_COMM))
+ return FILTER_MATCH;
- char *comm = get_comm(buf);
+ comm = get_comm(buf);
+ if (!comm)
+ return FILTER_ERROR;
- if ((filter & FILTER_COMM) &&
- !match_str_list(comm, fc.comms, fc.comms_size)) {
+ if (!match_str_list(comm, fc.comms, fc.comms_size)) {
free(comm);
- return false;
+ return FILTER_SKIP;
}
free(comm);
- return true;
+ return FILTER_MATCH;
}
static bool add_list(char *buf, int len, char *ext_buf)
{
+ enum FILTER_RESULT filter_result;
+
if (list_size == max_size) {
fprintf(stderr, "max_size too small??\n");
return false;
}
- if (!is_need(buf))
+ filter_result = filter_record(buf);
+ if (filter_result == FILTER_ERROR) {
+ fprintf(stderr, "Out of memory\n");
+ return false;
+ }
+ if (filter_result == FILTER_SKIP)
return true;
list[list_size].pid = get_pid(buf);
list[list_size].tgid = get_tgid(buf);
list[list_size].comm = get_comm(buf);
- list[list_size].txt = malloc(len+1);
+ if (!list[list_size].comm) {
+ fprintf(stderr, "Out of memory\n");
+ return false;
+ }
+ list[list_size].txt = malloc(len + 1);
if (!list[list_size].txt) {
fprintf(stderr, "Out of memory\n");
+ free(list[list_size].comm);
return false;
}
memcpy(list[list_size].txt, buf, len);
@@ -841,8 +889,10 @@ int main(int argc, char **argv)
} else {
list[count-1].num += list[i].num;
list[count-1].page_num += list[i].page_num;
+ free_block_list(&list[i]);
}
}
+ list_size = count;
qsort(list, count, sizeof(list[0]), compare_sort_condition);
@@ -876,8 +926,11 @@ out_free:
free(ext_buf);
if (buf)
free(buf);
- if (list)
+ if (list) {
+ for (i = 0; i < list_size; i++)
+ free_block_list(&list[i]);
free(list);
+ }
out_ts:
regfree(&ts_nsec_pattern);
out_comm:
diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h
index b6b1d147fd75..4168e646af09 100644
--- a/tools/testing/memblock/internal.h
+++ b/tools/testing/memblock/internal.h
@@ -28,22 +28,6 @@ static inline struct page *virt_to_page(void *virt)
return virt;
}
-void memblock_free_pages(unsigned long pfn, unsigned int order)
-{
-}
-
-static inline void accept_memory(phys_addr_t start, unsigned long size)
-{
-}
-
-unsigned long free_reserved_area(void *start, void *end, int poison, const char *s);
-void free_reserved_page(struct page *page);
-
-static inline bool deferred_pages_enabled(void)
-{
- return false;
-}
-
#define for_each_valid_pfn(pfn, start_pfn, end_pfn) \
for ((pfn) = (start_pfn); (pfn) < (end_pfn); (pfn)++)
@@ -60,10 +44,6 @@ static inline bool __is_kernel(unsigned long addr)
#define for_each_valid_pfn(pfn, start_pfn, end_pfn) \
for ((pfn) = (start_pfn); (pfn) < (end_pfn); (pfn)++)
-static inline void init_deferred_page(unsigned long pfn, int nid)
-{
-}
-
#define __SetPageReserved(p) ((void)(p))
#endif
diff --git a/tools/testing/memblock/mm_init.h b/tools/testing/memblock/mm_init.h
new file mode 100644
index 000000000000..95bc5f2e8ed8
--- /dev/null
+++ b/tools/testing/memblock/mm_init.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __MM_MM_INIT_H
+#define __MM_MM_INIT_H
+
+void memblock_free_pages(unsigned long pfn, unsigned int order)
+{
+}
+
+static inline void accept_memory(phys_addr_t start, unsigned long size)
+{
+}
+
+unsigned long free_reserved_area(void *start, void *end, int poison, const char *s);
+void free_reserved_page(struct page *page);
+
+static inline bool deferred_pages_enabled(void)
+{
+ return false;
+}
+
+static inline void init_deferred_page(unsigned long pfn, int nid)
+{
+}
+#endif /* __MM_MM_INIT_H */
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 8a4b6ddc68df..c62642302c84 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -14,6 +14,7 @@ TARGETS += core
TARGETS += cpufreq
TARGETS += cpu-hotplug
TARGETS += damon
+TARGETS += dax
TARGETS += devices/error_logs
TARGETS += devices/probe
TARGETS += dmabuf-heaps
diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
index 2180c328a825..ece244e5c5b9 100644
--- a/tools/testing/selftests/damon/Makefile
+++ b/tools/testing/selftests/damon/Makefile
@@ -13,6 +13,7 @@ TEST_PROGS += sysfs.py
TEST_PROGS += sysfs_update_schemes_tried_regions_wss_estimation.py
TEST_PROGS += damos_quota.py damos_quota_goal.py damos_apply_interval.py
TEST_PROGS += damos_tried_regions.py damon_nr_regions.py
+TEST_PROGS += sysfs_refresh.py
TEST_PROGS += reclaim.sh lru_sort.sh
# regression tests (reproducers of previously found bugs)
diff --git a/tools/testing/selftests/damon/_damon_sysfs.py b/tools/testing/selftests/damon/_damon_sysfs.py
index 8b12cc048440..e6a2265d721e 100644
--- a/tools/testing/selftests/damon/_damon_sysfs.py
+++ b/tools/testing/selftests/damon/_damon_sysfs.py
@@ -271,7 +271,7 @@ class DamosFilter:
self.type_ = type_
self.matching = matching
self.allow = allow
- self.memcg_path = memcg_path,
+ self.memcg_path = memcg_path
self.addr_start = addr_start
self.addr_end = addr_end
self.target_idx = target_idx
@@ -624,17 +624,23 @@ class DamonCtx:
pause = None
idx = None
- def __init__(self, ops='paddr', monitoring_attrs=DamonAttrs(), targets=[],
- schemes=[], pause=False):
+ def __init__(self, ops='paddr', monitoring_attrs=None, targets=None,
+ schemes=None, pause=False):
self.ops = ops
+ if monitoring_attrs is None:
+ monitoring_attrs = DamonAttrs()
self.monitoring_attrs = monitoring_attrs
self.monitoring_attrs.context = self
+ if targets is None:
+ targets = []
self.targets = targets
for idx, target in enumerate(self.targets):
target.idx = idx
target.context = self
+ if schemes is None:
+ schemes = []
self.schemes = schemes
for idx, scheme in enumerate(self.schemes):
scheme.idx = idx
@@ -692,12 +698,14 @@ class DamonCtx:
class Kdamond:
state = None
pid = None
+ refresh_ms = None
contexts = None
idx = None # index of this kdamond between siblings
kdamonds = None # parent
- def __init__(self, contexts=[]):
+ def __init__(self, contexts=[], refresh_ms=None):
self.contexts = contexts
+ self.refresh_ms = refresh_ms
for idx, context in enumerate(self.contexts):
context.idx = idx
context.kdamond = self
@@ -720,6 +728,11 @@ class Kdamond:
err = context.stage()
if err is not None:
return err
+ if self.refresh_ms is not None:
+ err = write_file(os.path.join(self.sysfs_dir(), 'refresh_ms'),
+ '%d' % self.refresh_ms)
+ if err is not None:
+ return err
err = write_file(os.path.join(self.sysfs_dir(), 'state'), 'on')
if err is not None:
return err
@@ -831,7 +844,7 @@ class Kdamond:
for goal in scheme.quota.goals:
err = goal.stage()
if err is not None:
- print('commit_schemes_quota_goals failed stagign: %s'%
+ print('commit_schemes_quota_goals failed staging: %s'%
err)
exit(1)
return write_file(os.path.join(self.sysfs_dir(), 'state'),
diff --git a/tools/testing/selftests/damon/damos_apply_interval.py b/tools/testing/selftests/damon/damos_apply_interval.py
index f04d43702481..0f2f36584e48 100755
--- a/tools/testing/selftests/damon/damos_apply_interval.py
+++ b/tools/testing/selftests/damon/damos_apply_interval.py
@@ -56,7 +56,7 @@ def main():
# Because the second scheme was having the apply interval that is ten times
# lower than that of the first scheme, the second scheme should be tried
# about ten times more frequently than the first scheme. For possible
- # timing errors, check if it was at least nine times more freuqnetly tried.
+ # timing errors, check if it was at least nine times more frequently tried.
ratio = nr_tried_stats[1] / nr_tried_stats[0]
if ratio < 9:
print('%d / %d = %f (< 9)' %
diff --git a/tools/testing/selftests/damon/damos_quota_goal.py b/tools/testing/selftests/damon/damos_quota_goal.py
index f76e0412b564..661e4ba4765a 100755
--- a/tools/testing/selftests/damon/damos_quota_goal.py
+++ b/tools/testing/selftests/damon/damos_quota_goal.py
@@ -66,7 +66,7 @@ def main():
# effective quota was already minimum that cannot be more reduced
if expect_increase is False and last_effective_bytes == 1:
continue
- print('efective bytes not changed: %d' % goal.effective_bytes)
+ print('effective bytes not changed: %d' % goal.effective_bytes)
exit(1)
increased = last_effective_bytes < goal.effective_bytes
diff --git a/tools/testing/selftests/damon/damos_tried_regions.py b/tools/testing/selftests/damon/damos_tried_regions.py
index 3b347eb28bd2..d6472e6a6e08 100755
--- a/tools/testing/selftests/damon/damos_tried_regions.py
+++ b/tools/testing/selftests/damon/damos_tried_regions.py
@@ -55,10 +55,10 @@ def main():
collected_nr_regions.sort()
sample = collected_nr_regions[4]
print('50-th percentile nr_regions: %d' % sample)
- print('expectation (>= 14) is %s' % 'met' if sample >= 14 else 'not met')
+ print('expectation (>= 14) is %s' % ('met' if sample >= 14 else 'not met'))
if collected_nr_regions[4] < 14:
print('full nr_regions:')
- print('\n'.join(collected_nr_regions))
+ print('\n'.join(['%d' % x for x in collected_nr_regions]))
exit(1)
if __name__ == '__main__':
diff --git a/tools/testing/selftests/damon/drgn_dump_damon_status.py b/tools/testing/selftests/damon/drgn_dump_damon_status.py
index 972948e6215f..09552e91bc78 100755
--- a/tools/testing/selftests/damon/drgn_dump_damon_status.py
+++ b/tools/testing/selftests/damon/drgn_dump_damon_status.py
@@ -59,7 +59,6 @@ def region_to_dict(region):
['ar', addr_range_to_dict],
['sampling_addr', int],
['nr_accesses', int],
- ['nr_accesses_bp', int],
['age', int],
])
@@ -163,7 +162,7 @@ def damos_filter_to_dict(damos_filter):
int(damos_filter.addr_range.end)]
elif type_ == 'target':
dict_['target_idx'] = int(damos_filter.target_idx)
- elif type_ == 'hugeapge_size':
+ elif type_ == 'hugepage_size':
dict_['sz_range'] = [int(damos_filter.sz_range.min),
int(damos_filter.sz_range.max)]
return dict_
diff --git a/tools/testing/selftests/damon/sysfs.py b/tools/testing/selftests/damon/sysfs.py
index aa03a1187489..3ffa054b6386 100755
--- a/tools/testing/selftests/damon/sysfs.py
+++ b/tools/testing/selftests/damon/sysfs.py
@@ -119,7 +119,7 @@ def assert_access_pattern_committed(pattern, dump):
'max_nr_accesses', dump)
assert_true(dump['min_age_region'] == pattern.age[0], 'min_age_region',
dump)
- assert_true(dump['max_age_region'] == pattern.age[1], 'miaxage_region',
+ assert_true(dump['max_age_region'] == pattern.age[1], 'max_age_region',
dump)
def assert_scheme_committed(scheme, dump):
@@ -129,7 +129,7 @@ def assert_scheme_committed(scheme, dump):
'cold': 1,
'pageout': 2,
'hugepage': 3,
- 'nohugeapge': 4,
+ 'nohugepage': 4,
'collapse': 5,
'lru_prio': 6,
'lru_deprio': 7,
@@ -250,6 +250,35 @@ def assert_ctxs_committed(kdamonds):
if ctx in ctxs_paused_for_dump:
ctx.pause = False
+def test_memcg_filter_memcg_path_staging():
+ global kdamonds
+ memcg_filter = _damon_sysfs.DamosFilter(
+ type_='memcg', matching=True, allow=True, memcg_path='/')
+ kdamonds = _damon_sysfs.Kdamonds(
+ [_damon_sysfs.Kdamond(
+ contexts=[_damon_sysfs.DamonCtx(
+ targets=[_damon_sysfs.DamonTarget(pid=-1)],
+ schemes=[_damon_sysfs.Damos(
+ ops_filters=[memcg_filter])],
+ )])])
+ kdamonds.start()
+
+ shown, rd_err = _damon_sysfs.read_file(
+ os.path.join(memcg_filter.sysfs_dir(), 'memcg_path'))
+ if rd_err is not None:
+ print('memcg_path staging: sysfs read (%s)' % rd_err)
+ kdamonds.stop()
+ exit(1)
+ if shown.rstrip('\n') != memcg_filter.memcg_path:
+ print('memcg_path staging: memcg_path readback '
+ '(shown=%s, expected=%s)' %
+ (shown.rstrip('\n'), memcg_filter.memcg_path))
+ kdamonds.stop()
+ exit(1)
+
+ kdamonds.stop()
+ kdamonds = None
+
def main():
global kdamonds
kdamonds = _damon_sysfs.Kdamonds(
@@ -356,5 +385,7 @@ def main():
assert_ctxs_committed(kdamonds)
kdamonds.stop()
+ test_memcg_filter_memcg_path_staging()
+
if __name__ == '__main__':
main()
diff --git a/tools/testing/selftests/damon/sysfs.sh b/tools/testing/selftests/damon/sysfs.sh
index 78f4badb5beb..f7fb94b84e71 100755
--- a/tools/testing/selftests/damon/sysfs.sh
+++ b/tools/testing/selftests/damon/sysfs.sh
@@ -3,7 +3,7 @@
source _common.sh
-# Kselftest frmework requirement - SKIP code is 4.
+# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
ensure_write_succ()
@@ -28,7 +28,7 @@ ensure_write_fail()
if (echo "$content" > "$file") 2> /dev/null
then
- echo "writing $content to $file succeed ($fail_reason)"
+ echo "writing $content to $file succeeded ($reason)"
echo "expected failure because $reason"
exit 1
fi
@@ -67,7 +67,7 @@ ensure_file()
echo "$file permission: expected $permission but $perm"
exit 1
fi
- elif [ "$to_ensure" = "not_exist" ] && [ -f "$dir" ]
+ elif [ "$to_ensure" = "not_exist" ] && [ -f "$file" ]
then
echo "$file is not expected but found"
exit 1
@@ -99,14 +99,55 @@ test_stats()
done
}
+test_dest()
+{
+ dest_dir=$1
+ ensure_file "$dest_dir/id" "exist" "600"
+ ensure_file "$dest_dir/weight" "exist" "600"
+}
+
+test_dests()
+{
+ dests_dir=$1
+ ensure_file "$dests_dir/nr_dests" "exist" "600"
+ ensure_write_succ "$dests_dir/nr_dests" "1" "valid input"
+ test_dest "$dests_dir/0"
+
+ ensure_write_succ "$dests_dir/nr_dests" "2" "valid input"
+ test_dest "$dests_dir/0"
+ test_dest "$dests_dir/1"
+
+ ensure_write_succ "$dests_dir/nr_dests" "0" "valid input"
+ ensure_dir "$dests_dir/0" "not_exist"
+ ensure_dir "$dests_dir/1" "not_exist"
+}
+
test_filter()
{
filter_dir=$1
ensure_file "$filter_dir/type" "exist" "600"
- ensure_write_succ "$filter_dir/type" "anon" "valid input"
- ensure_write_succ "$filter_dir/type" "memcg" "valid input"
- ensure_write_succ "$filter_dir/type" "addr" "valid input"
- ensure_write_succ "$filter_dir/type" "target" "valid input"
+
+ local dir_name=$(basename "$(dirname "$filter_dir")")
+ if [ "$dir_name" = "filters" ] || [ "$dir_name" = "ops_filters" ]
+ then
+ ensure_write_succ "$filter_dir/type" "anon" "valid input"
+ ensure_write_succ "$filter_dir/type" "memcg" "valid input"
+ fi
+ if [ "$dir_name" = "filters" ] || [ "$dir_name" = "core_filters" ]
+ then
+ ensure_write_succ "$filter_dir/type" "addr" "valid input"
+ ensure_write_succ "$filter_dir/type" "target" "valid input"
+ fi
+ if [ "$dir_name" = "core_filters" ]
+ then
+ ensure_write_fail "$filter_dir/type" "anon" "ops type"
+ ensure_write_fail "$filter_dir/type" "memcg" "ops type"
+ fi
+ if [ "$dir_name" = "ops_filters" ]
+ then
+ ensure_write_fail "$filter_dir/type" "addr" "core type"
+ ensure_write_fail "$filter_dir/type" "target" "core type"
+ fi
ensure_write_fail "$filter_dir/type" "foo" "invalid input"
ensure_file "$filter_dir/matching" "exist" "600"
ensure_file "$filter_dir/memcg_path" "exist" "600"
@@ -158,6 +199,20 @@ test_goal()
ensure_dir "$goal_dir" "exist"
ensure_file "$goal_dir/target_value" "exist" "600"
ensure_file "$goal_dir/current_value" "exist" "600"
+ ensure_file "$goal_dir/target_metric" "exist" "600"
+ local fpath="$goal_dir/target_metric"
+ ensure_write_succ "$fpath" "user_input" "valid input"
+ ensure_write_succ "$fpath" "some_mem_psi_us" "valid input"
+ ensure_write_succ "$fpath" "node_mem_used_bp" "valid input"
+ ensure_write_succ "$fpath" "node_mem_free_bp" "valid input"
+ ensure_write_succ "$fpath" "node_memcg_used_bp" "valid input"
+ ensure_write_succ "$fpath" "node_memcg_free_bp" "valid input"
+ ensure_write_succ "$fpath" "active_mem_bp" "valid input"
+ ensure_write_succ "$fpath" "inactive_mem_bp" "valid input"
+ ensure_write_succ "$fpath" "node_eligible_mem_bp" "valid input"
+ ensure_write_fail "$fpath" "foo" "invalid input"
+ ensure_file "$goal_dir/nid" "exist" "600"
+ ensure_file "$goal_dir/path" "exist" "600"
}
test_goals()
@@ -207,7 +262,10 @@ test_scheme()
ensure_file "$scheme_dir/apply_interval_us" "exist" "600"
test_quotas "$scheme_dir/quotas"
test_watermarks "$scheme_dir/watermarks"
+ test_dests "$scheme_dir/dests"
test_filters "$scheme_dir/filters"
+ test_filters "$scheme_dir/core_filters"
+ test_filters "$scheme_dir/ops_filters"
test_stats "$scheme_dir/stats"
test_tried_regions "$scheme_dir/tried_regions"
}
@@ -346,8 +404,13 @@ test_probes()
ensure_write_succ "$probes_dir/nr_probes" "1" "valid input"
test_probe "$probes_dir/0"
+ ensure_write_succ "$probes_dir/nr_probes" "2" "valid input"
+ test_probe "$probes_dir/0"
+ test_probe "$probes_dir/1"
+
ensure_write_succ "$probes_dir/nr_probes" "0" "valid input"
ensure_dir "$probes_dir/0" "not_exist"
+ ensure_dir "$probes_dir/1" "not_exist"
}
test_monitoring_attrs()
@@ -363,7 +426,7 @@ test_context()
{
context_dir=$1
ensure_dir "$context_dir" "exist"
- ensure_file "$context_dir/avail_operations" "exit" 400
+ ensure_file "$context_dir/avail_operations" "exist" 400
ensure_file "$context_dir/operations" "exist" 600
ensure_file "$context_dir/addr_unit" "exist" 600
ensure_file "$context_dir/pause" "exist" 600
diff --git a/tools/testing/selftests/damon/sysfs_refresh.py b/tools/testing/selftests/damon/sysfs_refresh.py
new file mode 100755
index 000000000000..012b7e8f509f
--- /dev/null
+++ b/tools/testing/selftests/damon/sysfs_refresh.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+import os
+import subprocess
+import time
+
+import _damon_sysfs
+
+def main():
+ # Continuously access a memory region for far longer than the test needs,
+ # so the kdamond always has a live target to monitor while we poll.
+ sz_region = 10 * 1024 * 1024
+ proc = subprocess.Popen(
+ ['./access_memory', '1', '%d' % sz_region, '60000', 'repeat'])
+
+ # A 'stat' scheme with the default (maximally wide) access pattern matches
+ # every monitored region, so its 'nr_tried' stat increases as the kdamond
+ # runs. refresh_ms should make DAMON update the schemes' stats files under
+ # sysfs on its own, without a manual 'update_schemes_stats' request.
+ kdamond = _damon_sysfs.Kdamond(
+ refresh_ms=100,
+ contexts=[_damon_sysfs.DamonCtx(
+ ops='vaddr',
+ targets=[_damon_sysfs.DamonTarget(pid=proc.pid)],
+ schemes=[_damon_sysfs.Damos(action='stat')],
+ )])
+ kdamonds = _damon_sysfs.Kdamonds([kdamond])
+
+ err = kdamonds.start()
+ if err is not None:
+ # Kernels older than the refresh_ms feature have no such file; treat
+ # that as unsupported rather than a failure.
+ if not os.path.exists(os.path.join(kdamond.sysfs_dir(), 'refresh_ms')):
+ proc.terminate()
+ proc.wait()
+ print('kdamond has no refresh_ms file; skipping')
+ exit(_damon_sysfs.ksft_skip)
+ proc.terminate()
+ proc.wait()
+ print('kdamond start failed: %s' % err)
+ exit(1)
+
+ scheme = kdamond.contexts[0].schemes[0]
+ nr_tried_path = os.path.join(scheme.sysfs_dir(), 'stats', 'nr_tried')
+
+ try:
+ # Poll the stat file directly. We never request an update (e.g.
+ # 'update_schemes_stats'), so 'nr_tried' can become non-zero only
+ # through the periodic refresh that refresh_ms enables.
+ nr_tried = 0
+ deadline = time.monotonic() + 10
+ while time.monotonic() < deadline:
+ if proc.poll() is not None:
+ print('the access_memory target exited unexpectedly')
+ exit(1)
+ content, err = _damon_sysfs.read_file(nr_tried_path)
+ if err is not None:
+ print('reading %s failed: %s' % (nr_tried_path, err))
+ exit(1)
+ nr_tried = int(content)
+ if nr_tried > 0:
+ break
+ time.sleep(0.1)
+ finally:
+ kdamonds.stop()
+ proc.terminate()
+ proc.wait()
+
+ if nr_tried == 0:
+ print('refresh_ms did not auto-update the schemes stats')
+ exit(1)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
index 35c724a63f6c..16fdc6e7fc56 100755
--- a/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
+++ b/tools/testing/selftests/damon/sysfs_update_schemes_tried_regions_wss_estimation.py
@@ -7,7 +7,7 @@ import time
import _damon_sysfs
def pass_wss_estimation(sz_region):
- # access two regions of given size, 2 seocnds per each region
+ # access two regions of given size, 2 seconds per each region
proc = subprocess.Popen(
['./access_memory', '2', '%d' % sz_region, '2000', 'repeat'])
kdamonds = _damon_sysfs.Kdamonds([_damon_sysfs.Kdamond(
diff --git a/tools/testing/selftests/dax/Makefile b/tools/testing/selftests/dax/Makefile
new file mode 100644
index 000000000000..25a4f3d73a5b
--- /dev/null
+++ b/tools/testing/selftests/dax/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+all:
+
+TEST_PROGS := dax-kmem-hotplug.sh
+
+include ../lib.mk
diff --git a/tools/testing/selftests/dax/config b/tools/testing/selftests/dax/config
new file mode 100644
index 000000000000..4c9aaeb6ceb4
--- /dev/null
+++ b/tools/testing/selftests/dax/config
@@ -0,0 +1,4 @@
+CONFIG_DEV_DAX=m
+CONFIG_DEV_DAX_KMEM=m
+CONFIG_MEMORY_HOTPLUG=y
+CONFIG_MEMORY_HOTREMOVE=y
diff --git a/tools/testing/selftests/dax/dax-kmem-hotplug.sh b/tools/testing/selftests/dax/dax-kmem-hotplug.sh
new file mode 100755
index 000000000000..9299120ee509
--- /dev/null
+++ b/tools/testing/selftests/dax/dax-kmem-hotplug.sh
@@ -0,0 +1,317 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Exercise the dax/kmem "state" sysfs attribute:
+# /sys/bus/dax/devices/daxX.Y/state -> unplugged | online | online_kernel | online_movable
+#
+# The test needs a dax device already bound to the kmem driver.
+#
+# This test mutates a device's memory: online/offline cycles migrate any
+# in-use pages, and the optional unbind subtest wedges the device until
+# reboot. The tester must identify the target device and opt into the
+# destructive unbind tests.
+#
+# DAX_KMEM_TEST_DEV=daxX.Y test this specific device
+# DAX_KMEM_TEST_DEV=auto auto-discover the first kmem-bound dax device
+# (best-effort: it may be a device in use!)
+# DAX_KMEM_TEST_UNBIND=1 also run the destructive unbind-while-online test
+#
+# If DAX_KMEM_TEST_DEV is unset the whole test SKIPs.
+#
+# A dax device can be provisioned with the memmap= boot param, e.g.:
+# memmap=2G!4G
+#
+# then, in the booted system:
+#
+# ndctl create-namespace -m devdax -e namespace0.0 -f
+# daxctl reconfigure-device -N -m system-ram dax0.0 # bind kmem
+# DAX_KMEM_TEST_DEV=auto ./dax-kmem-hotplug.sh
+
+# shellcheck disable=SC1091
+DIR="$(dirname "$(readlink -f "$0")")"
+. "$DIR"/../kselftest/ktap_helpers.sh
+
+DAX_BASE=/sys/bus/dax/devices
+MEM_BASE=/sys/devices/system/memory
+
+memtotal_kb() { awk '/^MemTotal:/ {print $2}' /proc/meminfo; }
+get_state() { cat "$HP" 2>/dev/null; }
+# set_state STATE -- write a state to the state attribute; returns the
+# write's exit status (0 = accepted by the kernel)
+set_state() { echo "$1" > "$HP" 2>/dev/null; }
+
+is_kmem_dax() {
+ local drv
+ [ -e "$DAX_BASE/$1/state" ] || return 1
+ drv=$(readlink "$DAX_BASE/$1/driver" 2>/dev/null)
+ [ "$(basename "${drv:-}")" = kmem ]
+}
+
+find_kmem_dax() {
+ local d
+ for d in "$DAX_BASE"/dax*; do
+ is_kmem_dax "$(basename "$d")" || continue
+ basename "$d"
+ return 0
+ done
+ return 1
+}
+
+# find_device_blocks -- print every memoryN block backing this dax device.
+# The blocks are derived from the device's own range(s) in /proc/iomem (the
+# reserved resource is named after the device), so we act on *its* blocks
+# rather than guessing by NUMA node - the target node may also hold unrelated
+# (and non-offlineable) memory.
+find_device_blocks() {
+ local bs
+ bs=$(cat "$MEM_BASE/block_size_bytes" 2>/dev/null) # hex, no leading 0x
+ [ -n "$bs" ] || return 1
+ grep -E " : ${DAX}\$" /proc/iomem | while read -r line; do
+ local range s e i
+ range=${line%% :*}; range=${range// /}
+ s=${range%-*}; e=${range#*-}
+ for (( i = 0x$s / 0x$bs; i <= 0x$e / 0x$bs; i++ )); do
+ echo "memory$i"
+ done
+ done
+}
+
+# find_device_block -- print the first online block backing this dax device.
+find_device_block() {
+ local b
+ for b in $(find_device_blocks); do
+ [ -f "$MEM_BASE/$b/state" ] || continue
+ [ "$(cat "$MEM_BASE/$b/state")" = online ] || continue
+ echo "$b"
+ return 0
+ done
+ return 1
+}
+
+ktap_print_header
+
+if [ "$UID" != 0 ]; then
+ ktap_skip_all "must be run as root"
+ exit "$KSFT_SKIP"
+fi
+
+# Device selection is opt-in - see the header for why.
+DEV_SEL=${DAX_KMEM_TEST_DEV:-}
+if [ -z "$DEV_SEL" ]; then
+ ktap_skip_all "set DAX_KMEM_TEST_DEV=<daxX.Y|auto> to opt in (mutates device memory)"
+ exit "$KSFT_SKIP"
+fi
+if [ "$DEV_SEL" = auto ]; then
+ DAX=$(find_kmem_dax)
+else
+ DAX=$DEV_SEL
+fi
+if [ -z "$DAX" ] || ! is_kmem_dax "$DAX"; then
+ ktap_skip_all "no kmem-bound dax device with a state attribute (${DEV_SEL})"
+ exit "$KSFT_SKIP"
+fi
+HP=$DAX_BASE/$DAX/state
+ORIG=$(get_state)
+
+# A failure to reach the baseline is environmental (memory in use), not an
+# interface failure, so skip rather than fail.
+set_state unplugged; rc=$?
+if [ "$rc" != 0 ] || [ "$(get_state)" != unplugged ]; then
+ ktap_skip_all "$DAX: cannot reach 'unplugged' baseline (memory in use?)"
+ [ -n "$ORIG" ] && set_state "$ORIG"
+ exit "$KSFT_SKIP"
+fi
+mt_unplugged=$(memtotal_kb)
+
+DRV=/sys/bus/dax/drivers/kmem
+AOB=$MEM_BASE/auto_online_blocks
+
+ktap_print_msg "using $DAX (initial state was: $ORIG)"
+ktap_set_plan 10
+
+# A public (N_MEMORY) kmem node onlined into a kernel zone (online/online_kernel)
+# collects unmovable allocations and can then never be offlined, which would
+# wedge the device for the rest of this test. So this test only ever
+# successfully onlines online_movable, the one mode that is reliably unpluggable.
+
+set_state online_movable; rc=$?
+mt_online=$(memtotal_kb)
+if [ "$rc" = 0 ] && [ "$(get_state)" = online_movable ] && [ "$mt_online" -gt "$mt_unplugged" ]; then
+ ktap_test_pass "online_movable: state=online_movable, MemTotal $mt_unplugged -> $mt_online kB"
+else
+ ktap_test_fail "online_movable: rc=$rc state=$(get_state) MemTotal $mt_unplugged -> $mt_online"
+fi
+
+set_state online_movable; rc=$?
+if [ "$rc" = 0 ] && [ "$(get_state)" = online_movable ]; then
+ ktap_test_pass "online_movable idempotent"
+else
+ ktap_test_fail "online_movable idempotent: rc=$rc state=$(get_state)"
+fi
+
+# A different online type is rejected without an intervening unplug. The write
+# is refused before any hotplug, so this never actually onlines a kernel zone.
+set_state online_kernel; rc=$?
+if [ "$rc" != 0 ] && [ "$(get_state)" = online_movable ]; then
+ ktap_test_pass "reject online_kernel without intervening unplug (no kernel-zone online)"
+else
+ ktap_test_fail "online_movable->online_kernel not rejected: rc=$rc state=$(get_state)"
+fi
+
+set_state unplugged; rc=$?
+mt=$(memtotal_kb)
+if [ "$rc" = 0 ] && [ "$(get_state)" = unplugged ] && [ "$mt" -lt "$mt_online" ]; then
+ ktap_test_pass "unplug from online_movable: MemTotal $mt_online -> $mt kB"
+else
+ ktap_test_fail "unplug from online_movable: rc=$rc state=$(get_state) MemTotal $mt_online -> $mt"
+fi
+
+before=$(get_state)
+set_state bogus_state; rc=$?
+if [ "$rc" != 0 ] && [ "$(get_state)" = "$before" ]; then
+ ktap_test_pass "reject invalid state string"
+else
+ ktap_test_fail "invalid state not rejected: rc=$rc state=$(get_state)"
+fi
+
+# An online_movable -> unplug cycle must re-acquire the per-range resources on
+# each online and release them on each unplug. Assert every iteration grows
+# MemTotal past the baseline and returns exactly to it; memory left online after
+# unplug (off > baseline) is a partial-free failure.
+set_state unplugged
+cycle_ok=1; fail_i=0; on=0; off=0
+for i in 1 2 3; do
+ if ! set_state online_movable; then cycle_ok=0; fail_i=$i; break; fi
+ on=$(memtotal_kb)
+ if ! set_state unplugged; then cycle_ok=0; fail_i=$i; break; fi
+ off=$(memtotal_kb)
+ # online must grow past baseline, and unplug must return to it - a
+ # partial free (memory left online) is a failure, not just off == on.
+ if [ "$on" -le "$mt_unplugged" ] || [ "$off" -gt "$mt_unplugged" ]; then
+ cycle_ok=0; fail_i=$i; break
+ fi
+done
+if [ "$cycle_ok" = 1 ]; then
+ ktap_test_pass "online_movable/unplug cycle re-acquires resources (3x: added and freed each time)"
+else
+ ktap_test_fail "online_movable/unplug cycle regressed at iteration $fail_i (on=$on off=$off baseline=$mt_unplugged)"
+fi
+
+# Desync: toggle a block through the legacy per-block memoryN/state interface
+# behind the driver's back, then unplug the whole device via daxX.Y/state.
+#
+# The driver only updates daxX.Y/state on its own writes, so it still reports
+# online_movable while a block underneath is already offline.
+#
+# Whole-device unplug must still succeed (within reason, an actor changing a
+# device from online_movable to online_kernel can no longer guarantee unplug).
+# At the very least, an already-offline block should not produce an error.
+set_state unplugged
+set_state online_movable
+blk=$(find_device_block)
+if [ -n "$blk" ] && echo offline > "$MEM_BASE/$blk/state" 2>/dev/null; then
+ # daxX.Y/state is now stale (still online_movable); unplug the device.
+ set_state unplugged; rc=$?
+ mt=$(memtotal_kb)
+ if [ "$rc" = 0 ] && [ "$(get_state)" = unplugged ] && [ "$mt" -le "$mt_unplugged" ]; then
+ ktap_test_pass "unplug tolerates a block pre-offlined via memoryN/state ($blk)"
+ else
+ ktap_test_fail "desync unplug: rc=$rc state=$(get_state) MemTotal=$mt baseline=$mt_unplugged"
+ fi
+else
+ set_state unplugged 2>/dev/null
+ ktap_test_skip "could not locate a device block to offline for desync test"
+fi
+
+# change system default online policy while the device is unbound, and show
+# the new system default policy is utilized across bindings.
+set_state unplugged
+if [ -w "$AOB" ] && [ -w "$DRV/unbind" ] && [ -w "$DRV/bind" ]; then
+ orig_aob=$(cat "$AOB")
+ echo "$DAX" > "$DRV/unbind" 2>/dev/null
+ echo offline > "$AOB" 2>/dev/null
+ echo "$DAX" > "$DRV/bind" 2>/dev/null
+ sleep 1
+ st=$(get_state)
+ echo "$orig_aob" > "$AOB" 2>/dev/null # restore system policy
+ if [ "$st" = offline ]; then
+ ktap_test_pass "online policy resolved at bind: auto_online_blocks=offline -> state=offline"
+ else
+ ktap_test_fail "bind-time policy not honored: state=$st (expected offline)"
+ fi
+ set_state unplugged 2>/dev/null
+else
+ ktap_test_skip "auto_online_blocks or driver bind/unbind not writable"
+fi
+
+# Blocks offlined out-of-band (via memoryN/state) leave daxX.Y/state stale
+# (still online_movable) while every block is actually offline. A driver unbind
+# must still hot-remove the offline memory and free its resources rather than
+# trust the stale state and leak until reboot. Unbind uses remove_memory(),
+# which never offlines, so removing already-offline blocks is non-destructive and
+# the device rebinds cleanly afterwards.
+if [ -w "$DRV/unbind" ] && [ -w "$DRV/bind" ]; then
+ set_state unplugged
+ set_state online_movable
+ offl_ok=1
+ for b in $(find_device_blocks); do
+ [ -f "$MEM_BASE/$b/state" ] || continue
+ [ "$(cat "$MEM_BASE/$b/state")" = online ] || continue
+ echo offline > "$MEM_BASE/$b/state" 2>/dev/null || offl_ok=0
+ done
+ # daxX.Y/state is now stale (still online_movable) while all blocks are
+ # offline; the unbind must hot-remove them anyway.
+ if [ "$offl_ok" = 1 ] && [ "$(get_state)" = online_movable ]; then
+ echo "$DAX" > "$DRV/unbind" 2>/dev/null
+ mt_after=$(memtotal_kb)
+ leaked=$(grep -cE " : ${DAX}\$" /proc/iomem) # before rebind
+ echo "$DAX" > "$DRV/bind" 2>/dev/null # restore for later steps
+ sleep 1
+ if [ "$mt_after" -le "$mt_unplugged" ] && [ "$leaked" = 0 ]; then
+ ktap_test_pass "unbind with stale online state hot-removes offlined blocks (no leak)"
+ else
+ ktap_test_fail "desync unbind leaked: MemTotal=$mt_after baseline=$mt_unplugged iomem_left=$leaked"
+ fi
+ set_state unplugged 2>/dev/null
+ else
+ ktap_test_skip "could not offline all device blocks for desync-unbind test"
+ fi
+else
+ ktap_test_skip "driver bind/unbind not writable for desync-unbind test"
+fi
+
+[ -n "$ORIG" ] && set_state "$ORIG"
+
+# DESTRUCTIVE and opt-in only (DAX_KMEM_TEST_UNBIND=1):
+#
+# unbinding the driver while memory is online causes the resources to leak - but
+# the unbind should not deadlock. Instead the driver leaks it with a warning.
+
+# This leaves the memory online and the device unbound until reboot, so it runs
+# last and only when explicitly requested. online_movable only: this test
+# never onlines a public node into a kernel zone.
+
+if [ "${DAX_KMEM_TEST_UNBIND:-}" = 1 ] && [ -w "$DRV/unbind" ]; then
+ set_state unplugged; set_state online_movable
+fi
+if [ "${DAX_KMEM_TEST_UNBIND:-}" = 1 ] && [ "$(get_state)" = online_movable ] &&
+ [ -w "$DRV/unbind" ]; then
+ mt_on=$(memtotal_kb)
+ dmesg -C 2>/dev/null
+ echo "$DAX" > "$DRV/unbind" 2>/dev/null
+ mt_after=$(memtotal_kb)
+ # The leaked "System RAM (kmem)" regions stay in the iomem tree; reading
+ # their names dereferences res_name, which a buggy unbind already freed.
+ # Walk /proc/iomem to provoke that use-after-free (caught by KASAN).
+ cat /proc/iomem > /dev/null 2>&1
+ splat=$(dmesg 2>/dev/null | grep -ciE "KASAN|BUG:|use-after-free|general protection|Oops|refcount_t")
+ if [ "$splat" = 0 ] && [ "$mt_after" -ge "$mt_on" ]; then
+ ktap_test_pass "unbind while online: memory left online, no UAF/oops (MemTotal $mt_on -> $mt_after kB)"
+ else
+ ktap_test_fail "unbind while online regressed: splat=$splat MemTotal $mt_on -> $mt_after kB"
+ fi
+else
+ ktap_test_skip "destructive unbind-while-online test (set DAX_KMEM_TEST_UNBIND=1)"
+fi
+
+ktap_finished
diff --git a/tools/testing/selftests/dax/settings b/tools/testing/selftests/dax/settings
new file mode 100644
index 000000000000..ba4d85f74cd6
--- /dev/null
+++ b/tools/testing/selftests/dax/settings
@@ -0,0 +1 @@
+timeout=90
diff --git a/tools/testing/selftests/mincore/mincore_selftest.c b/tools/testing/selftests/mincore/mincore_selftest.c
index cdd022c1c497..18dfbf318b1f 100644
--- a/tools/testing/selftests/mincore/mincore_selftest.c
+++ b/tools/testing/selftests/mincore/mincore_selftest.c
@@ -242,8 +242,10 @@ TEST(check_file_mmap)
}
/*
- * Touch a page in the middle of the mapping. We expect the next
- * few pages (the readahead window) to be populated too.
+ * Touch a page in the middle of the mapping. We expect some
+ * surrounding pages (the readahead window) to be populated too.
+ * Depending on the page size and readahead setting, the pages may
+ * land before the faulted page rather than after it.
*/
addr[FILE_SIZE / 2] = 1;
retval = mincore(addr, FILE_SIZE, vec);
@@ -252,6 +254,12 @@ TEST(check_file_mmap)
TH_LOG("Page not found in memory after use");
}
+ i = FILE_SIZE / 2 / page_size - 1;
+ while (i >= 0 && vec[i]) {
+ ra_pages++;
+ i--;
+ }
+
i = FILE_SIZE / 2 / page_size + 1;
while (i < vec_size && vec[i]) {
ra_pages++;
diff --git a/tools/testing/selftests/mm/Makefile b/tools/testing/selftests/mm/Makefile
index e6df968f0971..277a141d662e 100644
--- a/tools/testing/selftests/mm/Makefile
+++ b/tools/testing/selftests/mm/Makefile
@@ -66,7 +66,6 @@ TEST_GEN_FILES += hugetlb-mremap
TEST_GEN_FILES += hugetlb-read-hwpoison
TEST_GEN_FILES += hugetlb-shm
TEST_GEN_FILES += hugetlb-soft-offline
-TEST_GEN_FILES += hugetlb-vmemmap
TEST_GEN_FILES += khugepaged
TEST_GEN_FILES += madv_populate
TEST_GEN_FILES += map_fixed_noreplace
diff --git a/tools/testing/selftests/mm/gup_longterm.c b/tools/testing/selftests/mm/gup_longterm.c
index eb8963e9d98f..c03b4f8910c0 100644
--- a/tools/testing/selftests/mm/gup_longterm.c
+++ b/tools/testing/selftests/mm/gup_longterm.c
@@ -196,7 +196,7 @@ static void do_test(int fd, size_t size, enum test_type type, bool shared)
args.flags |= rw ? PIN_LONGTERM_TEST_FLAG_USE_WRITE : 0;
ret = ioctl(gup_fd, PIN_LONGTERM_TEST_START, &args);
if (ret && errno == EINVAL) {
- ksft_print_msg("PIN_LONGTERM_TEST_START failed (EINVAL)n");
+ ksft_print_msg("PIN_LONGTERM_TEST_START failed (EINVAL)\n");
result = KSFT_SKIP;
break;
} else if (ret && errno == EFAULT) {
diff --git a/tools/testing/selftests/mm/hmm-tests.c b/tools/testing/selftests/mm/hmm-tests.c
index 2f2b9879d100..e2642eca0d02 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -29,6 +29,10 @@
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/time.h>
+#include <sys/syscall.h>
+#include <sys/eventfd.h>
+#include <linux/userfaultfd.h>
+#include <poll.h>
/*
* This is a private UAPI to the kernel test module so it isn't exported
@@ -225,6 +229,53 @@ static void hmm_buffer_free(struct hmm_buffer *buffer)
}
/*
+ * Allocate a buffer structure with memory mapping and mirror.
+ *
+ * @mmap_size: total size of the mmap region (may differ from @mirror_size
+ * for alignment padding in THP tests).
+ * @mirror_size: size of the mirror data buffer (the actual working set).
+ * @prot: protection flags for the mmap (e.g. PROT_READ | PROT_WRITE).
+ * @flags: flags for the mmap (e.g. MAP_PRIVATE, MAP_SHARED,
+ * MAP_ANONYMOUS, MAP_HUGETLB).
+ * @fd: file descriptor for the mmap; pass -1 for MAP_ANONYMOUS.
+ *
+ * All internal allocations are checked; returns NULL and cleans up on any
+ * failure. Caller must ASSERT_NE or otherwise check the return value.
+ */
+static struct hmm_buffer *hmm_buffer_alloc(unsigned long mmap_size,
+ unsigned long mirror_size,
+ int prot, int flags,
+ int fd)
+{
+ struct hmm_buffer *buffer;
+
+ buffer = malloc(sizeof(*buffer));
+ if (!buffer) {
+ perror("malloc buffer");
+ return NULL;
+ }
+
+ buffer->fd = fd;
+ buffer->size = mmap_size;
+ buffer->mirror = malloc(mirror_size);
+ if (!buffer->mirror) {
+ perror("malloc mirror");
+ free(buffer);
+ return NULL;
+ }
+
+ buffer->ptr = mmap(NULL, mmap_size, prot, flags, fd, 0);
+ if (buffer->ptr == MAP_FAILED) {
+ perror("mmap");
+ free(buffer->mirror);
+ free(buffer);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+/*
* Create a temporary file that will be deleted on close.
*/
static int hmm_create_file(unsigned long size)
@@ -314,20 +365,11 @@ TEST_F(hmm, anon_read)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/*
* Initialize buffer in system memory but leave the first two pages
* zero (pte_none and pfn_zero).
@@ -377,20 +419,11 @@ TEST_F(hmm, anon_read_prot)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -436,20 +469,11 @@ TEST_F(hmm, anon_write)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize data that the device will write to buffer->ptr. */
for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -484,20 +508,11 @@ TEST_F(hmm, anon_write_prot)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Simulate a device reading a zero page of memory. */
ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, 1);
ASSERT_EQ(ret, 0);
@@ -559,20 +574,10 @@ TEST_F(hmm, anon_write_child)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size * 2, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = size * 2;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size * 2,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
old_ptr = buffer->ptr;
if (use_thp) {
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
@@ -661,20 +666,11 @@ TEST_F(hmm, anon_write_child_shared)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer->ptr so we can tell if it is written. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -736,20 +732,10 @@ TEST_F(hmm, anon_write_huge)
size = 2 * read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
size /= 2;
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
@@ -795,22 +781,11 @@ TEST_F(hmm, anon_write_hugetlbfs)
size = ALIGN(TWOMEG, default_hsize);
npages = size >> self->page_shift;
- buffer = malloc(sizeof(*buffer));
- ASSERT_NE(buffer, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
- -1, 0);
- if (buffer->ptr == MAP_FAILED) {
- free(buffer);
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1);
+ if (!buffer)
SKIP(return, "Huge page could not be allocated");
- }
-
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
/* Initialize data that the device will write to buffer->ptr. */
for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i)
@@ -852,14 +827,11 @@ TEST_F(hmm, file_read)
fd = hmm_create_file(size);
ASSERT_GE(fd, 0);
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ,
+ MAP_SHARED, fd);
ASSERT_NE(buffer, NULL);
- buffer->fd = fd;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
/* Write initial contents of the file. */
for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -867,12 +839,6 @@ TEST_F(hmm, file_read)
ASSERT_EQ(len, size);
memset(buffer->mirror, 0, size);
- buffer->ptr = mmap(NULL, size,
- PROT_READ,
- MAP_SHARED,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Simulate a device reading system memory. */
ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages);
ASSERT_EQ(ret, 0);
@@ -907,20 +873,11 @@ TEST_F(hmm, file_write)
fd = hmm_create_file(size);
ASSERT_GE(fd, 0);
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd);
ASSERT_NE(buffer, NULL);
- buffer->fd = fd;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_SHARED,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize data that the device will write to buffer->ptr. */
for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -960,20 +917,11 @@ TEST_F(hmm, migrate)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1010,20 +958,11 @@ TEST_F(hmm, migrate_file_private)
fd = hmm_create_file(size);
ASSERT_GE(fd, 0);
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd);
ASSERT_NE(buffer, NULL);
- buffer->fd = fd;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1058,20 +997,11 @@ TEST_F(hmm, migrate_fault)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1114,18 +1044,11 @@ TEST_F(hmm, migrate_release)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1164,20 +1087,11 @@ TEST_F(hmm, migrate_shared)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Migrate memory to device. */
ret = hmm_migrate_sys_to_dev(self->fd, buffer, npages);
ASSERT_EQ(ret, -ENOENT);
@@ -1201,20 +1115,10 @@ TEST_F(hmm2, migrate_mixed)
npages = 6;
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- /* Reserve a range of addresses. */
- buffer->ptr = mmap(NULL, size,
- PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
p = buffer->ptr;
/* Migrating a protected area should be an error. */
@@ -1295,20 +1199,11 @@ TEST_F(hmm, migrate_multiple)
size = npages << self->page_shift;
for (c = 0; c < NTIMES; c++) {
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1354,20 +1249,11 @@ TEST_F(hmm, anon_read_multiple)
size = npages << self->page_shift;
for (c = 0; c < NTIMES; c++) {
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i + c;
@@ -1420,20 +1306,11 @@ TEST_F(hmm, anon_teardown)
int *ptr;
int rc;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i + c;
@@ -1474,22 +1351,11 @@ TEST_F(hmm, mixedmap)
npages = 1;
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, npages,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, self->fd);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(npages);
- ASSERT_NE(buffer->mirror, NULL);
-
-
- /* Reserve a range of addresses. */
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE,
- self->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Simulate a device snapshotting CPU pagetables. */
ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_SNAPSHOT, buffer, npages);
ASSERT_EQ(ret, 0);
@@ -1519,20 +1385,10 @@ TEST_F(hmm2, snapshot)
npages = 7;
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, npages,
+ PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(npages);
- ASSERT_NE(buffer->mirror, NULL);
-
- /* Reserve a range of addresses. */
- buffer->ptr = mmap(NULL, size,
- PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
p = buffer->ptr;
/* Punch a hole after the first page address. */
@@ -1626,21 +1482,11 @@ TEST_F(hmm, compound)
size = ALIGN(TWOMEG, default_hsize);
npages = size >> self->page_shift;
- buffer = malloc(sizeof(*buffer));
- ASSERT_NE(buffer, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
- -1, 0);
- if (buffer->ptr == MAP_FAILED) {
- free(buffer);
- return;
- }
-
- buffer->size = size;
- buffer->mirror = malloc(npages);
- ASSERT_NE(buffer->mirror, NULL);
+ buffer = hmm_buffer_alloc(size, npages,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1);
+ if (!buffer)
+ SKIP(return, "Huge page could not be allocated");
/* Initialize the pages the device will snapshot in buffer->ptr. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
@@ -1707,21 +1553,11 @@ TEST_F(hmm2, double_map)
npages = 6;
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- /* Reserve a range of addresses. */
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1783,20 +1619,11 @@ TEST_F(hmm, exclusive)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1837,20 +1664,11 @@ TEST_F(hmm, exclusive_mprotect)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1892,20 +1710,11 @@ TEST_F(hmm, exclusive_cow)
ASSERT_NE(npages, 0);
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -1993,20 +1802,11 @@ TEST_F(hmm, hmm_gup_test)
npages = 4;
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -2079,20 +1879,11 @@ TEST_F(hmm, hmm_cow_in_device)
npages = 4;
size = npages << self->page_shift;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
-
- buffer->ptr = mmap(NULL, size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
/* Initialize buffer in system memory. */
for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
ptr[i] = i;
@@ -2152,21 +1943,12 @@ TEST_F(hmm, migrate_anon_huge_empty)
size = read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, size,
+ PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, size);
- buffer->ptr = mmap(NULL, 2 * size,
- PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
ret = madvise(map, size, MADV_HUGEPAGE);
@@ -2204,21 +1986,12 @@ TEST_F(hmm, migrate_anon_huge_zero)
size = read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, size,
+ PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, size);
- buffer->ptr = mmap(NULL, 2 * size,
- PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
ret = madvise(map, size, MADV_HUGEPAGE);
@@ -2267,21 +2040,12 @@ TEST_F(hmm, migrate_anon_huge_free)
size = read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, size);
- buffer->ptr = mmap(NULL, 2 * size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
ret = madvise(map, size, MADV_HUGEPAGE);
@@ -2329,21 +2093,12 @@ TEST_F(hmm, migrate_anon_huge_fault)
size = read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, size);
- buffer->ptr = mmap(NULL, 2 * size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
old_ptr = buffer->ptr;
@@ -2425,21 +2180,11 @@ TEST_F(hmm, migrate_partial_unmap_fault)
for (use_thp = 0; use_thp < 2; ++use_thp) {
for (j = 0; j < ARRAY_SIZE(offsets); ++j) {
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, size);
-
- buffer->ptr = mmap(NULL, 2 * size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
if (use_thp)
@@ -2505,21 +2250,11 @@ TEST_F(hmm, migrate_remap_fault)
if (dont_unmap)
flags |= MREMAP_DONTUNMAP;
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(8 * size, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 8 * size;
- buffer->mirror = malloc(size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, size);
-
- buffer->ptr = mmap(NULL, buffer->size,
- PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS,
- buffer->fd, 0);
- ASSERT_NE(buffer->ptr, MAP_FAILED);
-
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)buffer->ptr, size);
if (use_thp)
@@ -2590,18 +2325,13 @@ TEST_F(hmm, migrate_anon_huge_err)
size = read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, 2 * size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(2 * size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, 2 * size);
- old_ptr = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, buffer->fd, 0);
- ASSERT_NE(old_ptr, MAP_FAILED);
+ old_ptr = buffer->ptr;
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)old_ptr, size);
@@ -2685,18 +2415,13 @@ TEST_F(hmm, migrate_anon_huge_zero_err)
size = read_pmd_pagesize();
- buffer = malloc(sizeof(*buffer));
+ buffer = hmm_buffer_alloc(2 * size, 2 * size,
+ PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
ASSERT_NE(buffer, NULL);
-
- buffer->fd = -1;
- buffer->size = 2 * size;
- buffer->mirror = malloc(2 * size);
- ASSERT_NE(buffer->mirror, NULL);
memset(buffer->mirror, 0xFF, 2 * size);
- old_ptr = mmap(NULL, 2 * size, PROT_READ,
- MAP_PRIVATE | MAP_ANONYMOUS, buffer->fd, 0);
- ASSERT_NE(old_ptr, MAP_FAILED);
+ old_ptr = buffer->ptr;
npages = size >> self->page_shift;
map = (void *)ALIGN((uintptr_t)old_ptr, size);
@@ -2766,19 +2491,6 @@ static double get_time_ms(void)
return (tv.tv_sec * 1000.0) + (tv.tv_usec / 1000.0);
}
-static inline struct hmm_buffer *hmm_buffer_alloc(unsigned long size)
-{
- struct hmm_buffer *buffer;
-
- buffer = malloc(sizeof(*buffer));
-
- buffer->fd = -1;
- buffer->size = size;
- buffer->mirror = malloc(size);
- memset(buffer->mirror, 0xFF, size);
- return buffer;
-}
-
static void print_benchmark_results(const char *test_name, size_t buffer_size,
struct benchmark_results *thp,
struct benchmark_results *regular)
@@ -2823,14 +2535,14 @@ static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_siz
int ret, i;
int *ptr;
- buffer = hmm_buffer_alloc(buffer_size);
-
- /* Map memory */
- buffer->ptr = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-
- if (buffer->ptr == MAP_FAILED)
- return -1;
+ buffer = hmm_buffer_alloc(buffer_size, buffer_size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ if (!buffer) {
+ ret = -1;
+ goto cleanup;
+ }
+ memset(buffer->mirror, 0xFF, buffer_size);
/* Apply THP hint if requested */
if (use_thp)
@@ -2839,7 +2551,7 @@ static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_siz
ret = madvise(buffer->ptr, buffer_size, MADV_NOHUGEPAGE);
if (ret)
- return ret;
+ goto cleanup;
/* Initialize memory to make sure pages are allocated */
ptr = (int *)buffer->ptr;
@@ -2849,11 +2561,11 @@ static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_siz
/* Warmup iteration */
ret = hmm_migrate_sys_to_dev(fd, buffer, npages);
if (ret)
- return ret;
+ goto cleanup;
ret = hmm_migrate_dev_to_sys(fd, buffer, npages);
if (ret)
- return ret;
+ goto cleanup;
/* Benchmark iterations */
for (i = 0; i < iterations; i++) {
@@ -2862,7 +2574,7 @@ static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_siz
ret = hmm_migrate_sys_to_dev(fd, buffer, npages);
if (ret)
- return ret;
+ goto cleanup;
end = get_time_ms();
s2d_total += (end - start);
@@ -2872,7 +2584,7 @@ static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_siz
ret = hmm_migrate_dev_to_sys(fd, buffer, npages);
if (ret)
- return ret;
+ goto cleanup;
end = get_time_ms();
d2s_total += (end - start);
@@ -2886,9 +2598,9 @@ static inline int run_migration_benchmark(int fd, int use_thp, size_t buffer_siz
results->throughput_d2s = (buffer_size / (1024.0 * 1024.0 * 1024.0)) /
(results->dev_to_sys_time / 1000.0);
- /* Cleanup */
+cleanup:
hmm_buffer_free(buffer);
- return 0;
+ return ret;
}
/*
@@ -2949,4 +2661,150 @@ TEST_F_TIMEOUT(hmm, benchmark_thp_migration, 120)
&thp_results, &regular_results);
}
}
+/*
+ * Test that HMM can fault in pages backed by userfaultfd using the
+ * hmm_range_fault_unlocked_timeout() path with no timeout. This exercises
+ * the lock-drop retry logic in the HMM framework.
+ */
+struct uffd_thread_args {
+ int uffd;
+ int stop_fd;
+ void *page_buffer;
+ unsigned long page_size;
+};
+
+static void *uffd_handler_thread(void *arg)
+{
+ struct uffd_thread_args *args = arg;
+ struct uffd_msg msg;
+ struct uffdio_copy copy;
+ struct pollfd pollfd[2];
+ int ret;
+
+ pollfd[0].fd = args->uffd;
+ pollfd[0].events = POLLIN;
+ pollfd[1].fd = args->stop_fd;
+ pollfd[1].events = POLLIN;
+
+ while (1) {
+ ret = poll(pollfd, 2, -1);
+ if (ret <= 0)
+ break;
+ if (pollfd[1].revents)
+ break;
+ if (!(pollfd[0].revents & POLLIN))
+ break;
+
+ ret = read(args->uffd, &msg, sizeof(msg));
+ if (ret != sizeof(msg))
+ break;
+
+ if (msg.event != UFFD_EVENT_PAGEFAULT)
+ break;
+
+ /* Fill the page with a known pattern */
+ memset(args->page_buffer, 0xAB, args->page_size);
+
+ copy.dst = msg.arg.pagefault.address & ~(args->page_size - 1);
+ copy.src = (unsigned long)args->page_buffer;
+ copy.len = args->page_size;
+ copy.mode = 0;
+ copy.copy = 0;
+
+ ret = ioctl(args->uffd, UFFDIO_COPY, &copy);
+ if (ret < 0)
+ break;
+ }
+
+ return NULL;
+}
+
+TEST_F(hmm, userfaultfd_read)
+{
+ struct hmm_buffer *buffer;
+ struct uffd_thread_args uffd_args;
+ unsigned long npages;
+ unsigned long size;
+ unsigned long i;
+ unsigned char *ptr;
+ pthread_t thread;
+ int uffd;
+ int stop_fd;
+ int ret;
+ struct uffdio_api api;
+ struct uffdio_register reg;
+ uint64_t stop = 1;
+ ssize_t nwrite;
+
+ npages = 4;
+ size = npages << self->page_shift;
+
+ /* Create userfaultfd */
+ uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
+ if (uffd < 0)
+ SKIP(return, "userfaultfd not available");
+
+ api.api = UFFD_API;
+ api.features = 0;
+ ret = ioctl(uffd, UFFDIO_API, &api);
+ ASSERT_EQ(ret, 0);
+
+ buffer = malloc(sizeof(*buffer));
+ ASSERT_NE(buffer, NULL);
+
+ buffer->fd = -1;
+ buffer->size = size;
+ buffer->mirror = malloc(size);
+ ASSERT_NE(buffer->mirror, NULL);
+
+ /* Create anonymous mapping */
+ buffer->ptr = mmap(NULL, size,
+ PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS,
+ -1, 0);
+ ASSERT_NE(buffer->ptr, MAP_FAILED);
+
+ /* Register the region with userfaultfd */
+ reg.range.start = (unsigned long)buffer->ptr;
+ reg.range.len = size;
+ reg.mode = UFFDIO_REGISTER_MODE_MISSING;
+ ret = ioctl(uffd, UFFDIO_REGISTER, &reg);
+ ASSERT_EQ(ret, 0);
+
+ /* Set up the handler thread */
+ uffd_args.uffd = uffd;
+ stop_fd = eventfd(0, EFD_CLOEXEC);
+ ASSERT_GE(stop_fd, 0);
+ uffd_args.stop_fd = stop_fd;
+ uffd_args.page_buffer = malloc(self->page_size);
+ ASSERT_NE(uffd_args.page_buffer, NULL);
+ uffd_args.page_size = self->page_size;
+
+ ret = pthread_create(&thread, NULL, uffd_handler_thread, &uffd_args);
+ ASSERT_EQ(ret, 0);
+
+ /*
+ * Use the unlocked read path which allows the mmap lock to be
+ * dropped during the fault, enabling userfaultfd resolution.
+ */
+ ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ_UNLOCKED,
+ buffer, npages);
+ ASSERT_EQ(ret, 0);
+ ASSERT_EQ(buffer->cpages, npages);
+
+ /* Verify the device read the data filled by the uffd handler */
+ ptr = buffer->mirror;
+ for (i = 0; i < size; ++i)
+ ASSERT_EQ(ptr[i], (unsigned char)0xAB);
+
+ nwrite = write(stop_fd, &stop, sizeof(stop));
+ ASSERT_EQ(nwrite, sizeof(stop));
+ pthread_join(thread, NULL);
+ close(stop_fd);
+ free(uffd_args.page_buffer);
+ close(uffd);
+ hmm_buffer_free(buffer);
+}
+
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/mm/hugepage_settings.c b/tools/testing/selftests/mm/hugepage_settings.c
index 2eab2110ac6a..d7917dce3aba 100644
--- a/tools/testing/selftests/mm/hugepage_settings.c
+++ b/tools/testing/selftests/mm/hugepage_settings.c
@@ -437,7 +437,7 @@ void hugetlb_set_nr_pages(unsigned long size, unsigned long nr)
hugetlb_sysfs_path(path, sizeof(path), size, "nr_hugepages");
- write_num(path, nr);
+ write_num_ignore_einval(path, nr);
}
unsigned long hugetlb_free_pages(unsigned long size)
diff --git a/tools/testing/selftests/mm/hugetlb-vmemmap.c b/tools/testing/selftests/mm/hugetlb-vmemmap.c
deleted file mode 100644
index 507df78a158d..000000000000
--- a/tools/testing/selftests/mm/hugetlb-vmemmap.c
+++ /dev/null
@@ -1,132 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * A test case of using hugepage memory in a user application using the
- * mmap system call with MAP_HUGETLB flag. Before running this program
- * make sure the administrator has allocated enough default sized huge
- * pages to cover the 2 MB allocation.
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-#include "vm_util.h"
-#include "hugepage_settings.h"
-
-#define PAGE_COMPOUND_HEAD (1UL << 15)
-#define PAGE_COMPOUND_TAIL (1UL << 16)
-#define PAGE_HUGE (1UL << 17)
-
-#define HEAD_PAGE_FLAGS (PAGE_COMPOUND_HEAD | PAGE_HUGE)
-#define TAIL_PAGE_FLAGS (PAGE_COMPOUND_TAIL | PAGE_HUGE)
-
-#define PM_PFRAME_BITS 55
-#define PM_PFRAME_MASK ~((1UL << PM_PFRAME_BITS) - 1)
-
-static size_t pagesize;
-static size_t maplength;
-
-static void write_bytes(char *addr, size_t length)
-{
- unsigned long i;
-
- for (i = 0; i < length; i++)
- *(addr + i) = (char)i;
-}
-
-static unsigned long virt_to_pfn(void *addr)
-{
- int fd;
- unsigned long pagemap;
-
- fd = open("/proc/self/pagemap", O_RDONLY);
- if (fd < 0)
- return -1UL;
-
- lseek(fd, (unsigned long)addr / pagesize * sizeof(pagemap), SEEK_SET);
- read(fd, &pagemap, sizeof(pagemap));
- close(fd);
-
- return pagemap & ~PM_PFRAME_MASK;
-}
-
-static int check_page_flags(unsigned long pfn)
-{
- int fd, i;
- unsigned long pageflags;
-
- fd = open("/proc/kpageflags", O_RDONLY);
- if (fd < 0)
- return -1;
-
- lseek(fd, pfn * sizeof(pageflags), SEEK_SET);
-
- read(fd, &pageflags, sizeof(pageflags));
- if ((pageflags & HEAD_PAGE_FLAGS) != HEAD_PAGE_FLAGS) {
- close(fd);
- ksft_print_msg("Head page flags (%lx) is invalid\n", pageflags);
- return -1;
- }
-
- /*
- * pages other than the first page must be tail and shouldn't be head;
- * this also verifies kernel has correctly set the fake page_head to tail
- * while hugetlb_free_vmemmap is enabled.
- */
- for (i = 1; i < maplength / pagesize; i++) {
- read(fd, &pageflags, sizeof(pageflags));
- if ((pageflags & TAIL_PAGE_FLAGS) != TAIL_PAGE_FLAGS ||
- (pageflags & HEAD_PAGE_FLAGS) == HEAD_PAGE_FLAGS) {
- close(fd);
- ksft_print_msg("Tail page flags (%lx) is invalid\n", pageflags);
- return -1;
- }
- }
-
- close(fd);
-
- return 0;
-}
-
-int main(int argc, char **argv)
-{
- void *addr;
- unsigned long pfn;
- int ret;
-
- ksft_print_header();
- ksft_set_plan(1);
-
- if (!hugetlb_setup_default(1))
- ksft_exit_skip("Not enough free huge pages\n");
-
- pagesize = psize();
- maplength = default_huge_page_size();
- if (!maplength)
- ksft_exit_skip("Unable to determine huge page size\n");
-
- addr = mmap(NULL, maplength, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
- if (addr == MAP_FAILED)
- ksft_exit_fail_perror("mmap");
-
- /* Trigger allocation of HugeTLB page. */
- write_bytes(addr, maplength);
-
- pfn = virt_to_pfn(addr);
- if (pfn == -1UL) {
- ksft_perror("virt_to_pfn");
- munmap(addr, maplength);
- ksft_exit_fail();
- }
-
- ksft_print_msg("Returned address is %p whose pfn is %lx\n", addr, pfn);
-
- ret = check_page_flags(pfn);
-
- if (munmap(addr, maplength))
- ksft_exit_fail_perror("munmap");
-
- ksft_test_result(!ret, "HugeTLB vmemmap page flags\n");
- ksft_finished();
-}
diff --git a/tools/testing/selftests/mm/ksm_tests.c b/tools/testing/selftests/mm/ksm_tests.c
index a050f4840cfa..5fd7792a0d47 100644
--- a/tools/testing/selftests/mm/ksm_tests.c
+++ b/tools/testing/selftests/mm/ksm_tests.c
@@ -288,8 +288,8 @@ static bool assert_ksm_pages_count(long dupl_page_count)
static int ksm_save_def(struct ksm_sysfs *ksm_sysfs)
{
if (ksm_read_sysfs(KSM_FP("max_page_sharing"), &ksm_sysfs->max_page_sharing) ||
- numa_available() ? 0 :
- ksm_read_sysfs(KSM_FP("merge_across_nodes"), &ksm_sysfs->merge_across_nodes) ||
+ (numa_available() ? 0 :
+ ksm_read_sysfs(KSM_FP("merge_across_nodes"), &ksm_sysfs->merge_across_nodes)) ||
ksm_read_sysfs(KSM_FP("sleep_millisecs"), &ksm_sysfs->sleep_millisecs) ||
ksm_read_sysfs(KSM_FP("pages_to_scan"), &ksm_sysfs->pages_to_scan) ||
ksm_read_sysfs(KSM_FP("run"), &ksm_sysfs->run) ||
@@ -304,8 +304,8 @@ static int ksm_save_def(struct ksm_sysfs *ksm_sysfs)
static int ksm_restore(struct ksm_sysfs *ksm_sysfs)
{
if (ksm_write_sysfs(KSM_FP("max_page_sharing"), ksm_sysfs->max_page_sharing) ||
- numa_available() ? 0 :
- ksm_write_sysfs(KSM_FP("merge_across_nodes"), ksm_sysfs->merge_across_nodes) ||
+ (numa_available() ? 0 :
+ ksm_write_sysfs(KSM_FP("merge_across_nodes"), ksm_sysfs->merge_across_nodes)) ||
ksm_write_sysfs(KSM_FP("pages_to_scan"), ksm_sysfs->pages_to_scan) ||
ksm_write_sysfs(KSM_FP("run"), ksm_sysfs->run) ||
ksm_write_sysfs(KSM_FP("sleep_millisecs"), ksm_sysfs->sleep_millisecs) ||
@@ -440,9 +440,9 @@ static int get_next_mem_node(int node)
mem_node = i % (max_node + 1);
node_size = numa_node_size(mem_node, NULL);
if (node_size > 0)
- break;
+ return mem_node;
}
- return mem_node;
+ return -ENODEV;
}
static int get_first_mem_node(void)
@@ -455,8 +455,8 @@ static int check_ksm_numa_merge(int merge_type, int mapping, int prot, int timeo
{
void *numa1_map_ptr, *numa2_map_ptr;
struct timespec start_time;
+ int first_node, second_node;
int page_count = 2;
- int first_node;
if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_time)) {
ksft_perror("clock_gettime");
@@ -467,17 +467,19 @@ static int check_ksm_numa_merge(int merge_type, int mapping, int prot, int timeo
ksft_print_msg("NUMA support not enabled\n");
return KSFT_SKIP;
}
- if (numa_num_configured_nodes() <= 1) {
- ksft_print_msg("At least 2 NUMA nodes must be available\n");
+ first_node = get_first_mem_node();
+ second_node = get_next_mem_node(first_node);
+
+ if (second_node < 0) {
+ ksft_print_msg("At least 2 NUMA nodes with memory must be available\n");
return KSFT_SKIP;
}
if (ksm_write_sysfs(KSM_FP("merge_across_nodes"), merge_across_nodes))
return KSFT_FAIL;
/* allocate 2 pages in 2 different NUMA nodes and fill them with the same data */
- first_node = get_first_mem_node();
numa1_map_ptr = numa_alloc_onnode(page_size, first_node);
- numa2_map_ptr = numa_alloc_onnode(page_size, get_next_mem_node(first_node));
+ numa2_map_ptr = numa_alloc_onnode(page_size, second_node);
if (!numa1_map_ptr || !numa2_map_ptr) {
ksft_perror("numa_alloc_onnode");
return KSFT_FAIL;
@@ -844,8 +846,8 @@ int main(int argc, char *argv[])
if (ksm_write_sysfs(KSM_FP("run"), 2) ||
ksm_write_sysfs(KSM_FP("sleep_millisecs"), 0) ||
- numa_available() ? 0 :
- ksm_write_sysfs(KSM_FP("merge_across_nodes"), 1) ||
+ (numa_available() ? 0 :
+ ksm_write_sysfs(KSM_FP("merge_across_nodes"), 1)) ||
ksm_write_sysfs(KSM_FP("pages_to_scan"), page_count))
ksft_exit_fail_msg("Cannot set up KSM tunables\n");
diff --git a/tools/testing/selftests/mm/memory-failure.c b/tools/testing/selftests/mm/memory-failure.c
index 032ed952057c..1a5a32e22cce 100644
--- a/tools/testing/selftests/mm/memory-failure.c
+++ b/tools/testing/selftests/mm/memory-failure.c
@@ -46,7 +46,7 @@ FIXTURE(memory_failure)
unsigned long pfn;
int pagemap_fd;
int kpageflags_fd;
- bool triggered;
+ bool injection_attempted;
};
FIXTURE_VARIANT(memory_failure)
@@ -122,13 +122,6 @@ static void teardown_sighandler(void)
sigaction(SIGBUS, &sa, NULL);
}
-FIXTURE_TEARDOWN(memory_failure)
-{
- close(self->kpageflags_fd);
- close(self->pagemap_fd);
- teardown_sighandler();
-}
-
static void prepare(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
void *vaddr)
{
@@ -200,8 +193,7 @@ static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure
ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
}
-static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
- void *vaddr)
+static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self)
{
unsigned long size;
uint64_t pfn_flags;
@@ -217,6 +209,20 @@ static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failu
ASSERT_EQ(size, self->corrupted_size);
}
+FIXTURE_TEARDOWN(memory_failure)
+{
+ /*
+ * Injection may poison the page before failing or delivering SIGBUS, so
+ * clean up after every injection attempt.
+ */
+ if (self->injection_attempted)
+ cleanup(_metadata, self);
+
+ close(self->kpageflags_fd);
+ close(self->pagemap_fd);
+ teardown_sighandler();
+}
+
TEST_F(memory_failure, anon)
{
char *addr;
@@ -231,8 +237,8 @@ TEST_F(memory_failure, anon)
prepare(_metadata, self, addr);
ret = sigsetjmp(signal_jmp_buf, 1);
- if (!self->triggered) {
- self->triggered = true;
+ if (!self->injection_attempted) {
+ self->injection_attempted = true;
ASSERT_EQ(variant->inject(self, addr), 0);
FORCE_READ(*addr);
}
@@ -242,8 +248,6 @@ TEST_F(memory_failure, anon)
else
check(_metadata, self, addr, MADV_SOFT_ANON, ret);
- cleanup(_metadata, self, addr);
-
ASSERT_EQ(munmap(addr, self->page_size), 0);
}
@@ -296,8 +300,8 @@ TEST_F(memory_failure, clean_pagecache)
prepare(_metadata, self, addr);
ret = sigsetjmp(signal_jmp_buf, 1);
- if (!self->triggered) {
- self->triggered = true;
+ if (!self->injection_attempted) {
+ self->injection_attempted = true;
ASSERT_EQ(variant->inject(self, addr), 0);
FORCE_READ(*addr);
}
@@ -307,8 +311,6 @@ TEST_F(memory_failure, clean_pagecache)
else
check(_metadata, self, addr, MADV_SOFT_CLEAN_PAGECACHE, ret);
- cleanup(_metadata, self, addr);
-
ASSERT_EQ(munmap(addr, self->page_size), 0);
ASSERT_EQ(close(fd), 0);
@@ -337,8 +339,8 @@ TEST_F(memory_failure, dirty_pagecache)
prepare(_metadata, self, addr);
ret = sigsetjmp(signal_jmp_buf, 1);
- if (!self->triggered) {
- self->triggered = true;
+ if (!self->injection_attempted) {
+ self->injection_attempted = true;
ASSERT_EQ(variant->inject(self, addr), 0);
FORCE_READ(*addr);
}
@@ -348,8 +350,6 @@ TEST_F(memory_failure, dirty_pagecache)
else
check(_metadata, self, addr, MADV_SOFT_DIRTY_PAGECACHE, ret);
- cleanup(_metadata, self, addr);
-
ASSERT_EQ(munmap(addr, self->page_size), 0);
ASSERT_EQ(close(fd), 0);
diff --git a/tools/testing/selftests/mm/mremap_dontunmap.c b/tools/testing/selftests/mm/mremap_dontunmap.c
index a4f75d836733..96ba537facf7 100644
--- a/tools/testing/selftests/mm/mremap_dontunmap.c
+++ b/tools/testing/selftests/mm/mremap_dontunmap.c
@@ -313,7 +313,7 @@ static void mremap_dontunmap_partial_mapping_overwrite(void)
mremap(source_mapping, 5 * page_size,
5 * page_size,
MREMAP_DONTUNMAP | MREMAP_MAYMOVE | MREMAP_FIXED, dest_mapping);
- BUG_ON(dest_mapping == MAP_FAILED, "mremap");
+ BUG_ON(remapped_mapping == MAP_FAILED, "mremap");
BUG_ON(dest_mapping != remapped_mapping, "expected to remap to dest_mapping");
BUG_ON(check_region_contains_byte(source_mapping, 5 * page_size, 0) !=
diff --git a/tools/testing/selftests/mm/mseal_test.c b/tools/testing/selftests/mm/mseal_test.c
index faad4833366a..93c2e13094d4 100644
--- a/tools/testing/selftests/mm/mseal_test.c
+++ b/tools/testing/selftests/mm/mseal_test.c
@@ -234,7 +234,7 @@ static void test_seal_addseal(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
ret = sys_mseal(ptr, size);
FAIL_TEST_IF_FALSE(!ret);
@@ -250,7 +250,7 @@ static void test_seal_unmapped_start(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* munmap 2 pages from ptr. */
ret = sys_munmap(ptr, 2 * page_size);
@@ -278,7 +278,7 @@ static void test_seal_unmapped_middle(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* munmap 2 pages from ptr + page. */
ret = sys_munmap(ptr + page_size, 2 * page_size);
@@ -310,7 +310,7 @@ static void test_seal_unmapped_end(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* unmap last 2 pages. */
ret = sys_munmap(ptr + 2 * page_size, 2 * page_size);
@@ -339,7 +339,7 @@ static void test_seal_multiple_vmas(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split the vma into 3. */
ret = sys_mprotect(ptr + page_size, 2 * page_size,
@@ -370,7 +370,7 @@ static void test_seal_split_start(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split at middle */
ret = sys_mprotect(ptr, 2 * page_size, PROT_READ | PROT_WRITE);
@@ -395,7 +395,7 @@ static void test_seal_split_end(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split at middle */
ret = sys_mprotect(ptr, 2 * page_size, PROT_READ | PROT_WRITE);
@@ -420,7 +420,7 @@ static void test_seal_invalid_input(void)
int ret;
setup_single_address(8 * page_size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
ret = clean_single_address(ptr + 4 * page_size, 4 * page_size);
FAIL_TEST_IF_FALSE(!ret);
@@ -455,7 +455,7 @@ static void test_seal_zero_length(void)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
ret = sys_mprotect(ptr, 0, PROT_READ | PROT_WRITE);
FAIL_TEST_IF_FALSE(!ret);
@@ -505,7 +505,7 @@ static void test_seal_twice(void)
unsigned long size = 4 * page_size;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
ret = sys_mseal(ptr, size);
FAIL_TEST_IF_FALSE(!ret);
@@ -525,7 +525,7 @@ static void test_seal_mprotect(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr, size);
@@ -549,7 +549,7 @@ static void test_seal_start_mprotect(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr, page_size);
@@ -579,7 +579,7 @@ static void test_seal_end_mprotect(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr + page_size, 3 * page_size);
@@ -609,7 +609,7 @@ static void test_seal_mprotect_unalign_len(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr, page_size * 2 - 1);
@@ -638,7 +638,7 @@ static void test_seal_mprotect_unalign_len_variant_2(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr, page_size * 2 + 1);
FAIL_TEST_IF_FALSE(!ret);
@@ -666,7 +666,7 @@ static void test_seal_mprotect_two_vma(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split */
ret = sys_mprotect(ptr, page_size * 2, PROT_READ | PROT_WRITE);
@@ -701,7 +701,7 @@ static void test_seal_mprotect_two_vma_with_split(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split as two vma. */
ret = sys_mprotect(ptr, page_size * 2, PROT_READ | PROT_WRITE);
@@ -748,7 +748,7 @@ static void test_seal_mprotect_partial_mprotect(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* seal one page. */
if (seal) {
@@ -780,7 +780,7 @@ static void test_seal_mprotect_partial_mprotect_tail(bool seal)
*/
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr + page_size, page_size);
@@ -810,7 +810,7 @@ static void test_seal_mprotect_two_vma_with_gap(void)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split. */
ret = sys_mprotect(ptr, page_size, PROT_READ | PROT_WRITE);
@@ -853,7 +853,7 @@ static void test_seal_mprotect_split(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split. */
ret = sys_mprotect(ptr, page_size, PROT_READ | PROT_WRITE);
@@ -890,7 +890,7 @@ static void test_seal_mprotect_merge(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split one page. */
ret = sys_mprotect(ptr, page_size, PROT_READ | PROT_WRITE);
@@ -924,7 +924,7 @@ static void test_seal_munmap(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -955,7 +955,7 @@ static void test_seal_munmap_two_vma(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect to split */
ret = sys_mprotect(ptr, page_size * 2, PROT_READ | PROT_WRITE);
@@ -996,7 +996,7 @@ static void test_seal_munmap_vma_with_gap(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
ret = sys_munmap(ptr + page_size, page_size * 2);
FAIL_TEST_IF_FALSE(!ret);
@@ -1028,7 +1028,7 @@ static void test_seal_munmap_partial_across_vmas(bool seal)
int prot;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr + page_size, page_size);
@@ -1058,7 +1058,7 @@ static void test_munmap_start_freed(bool seal)
int prot;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* unmap the first page. */
ret = sys_munmap(ptr, page_size);
@@ -1097,7 +1097,7 @@ static void test_munmap_end_freed(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* unmap last page. */
ret = sys_munmap(ptr + page_size * 3, page_size);
@@ -1128,7 +1128,7 @@ static void test_munmap_middle_freed(bool seal)
int prot;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* unmap 2 pages in the middle. */
ret = sys_munmap(ptr + page_size, page_size * 2);
@@ -1172,7 +1172,7 @@ static void test_seal_mremap_shrink(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1201,7 +1201,7 @@ static void test_seal_mremap_expand(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* ummap last 2 pages. */
ret = sys_munmap(ptr + 2 * page_size, 2 * page_size);
FAIL_TEST_IF_FALSE(!ret);
@@ -1233,9 +1233,9 @@ static void test_seal_mremap_move(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
setup_single_address(size, &newPtr);
- FAIL_TEST_IF_FALSE(newPtr != (void *)-1);
+ FAIL_TEST_IF_FALSE(newPtr != MAP_FAILED);
ret = clean_single_address(newPtr, size);
FAIL_TEST_IF_FALSE(!ret);
@@ -1266,7 +1266,7 @@ static void test_seal_mmap_overwrite_prot(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1294,7 +1294,7 @@ static void test_seal_mmap_expand(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* ummap last 4 pages. */
ret = sys_munmap(ptr + 8 * page_size, 4 * page_size);
FAIL_TEST_IF_FALSE(!ret);
@@ -1325,7 +1325,7 @@ static void test_seal_mmap_shrink(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1354,9 +1354,9 @@ static void test_seal_mremap_shrink_fixed(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
setup_single_address(size, &newAddr);
- FAIL_TEST_IF_FALSE(newAddr != (void *)-1);
+ FAIL_TEST_IF_FALSE(newAddr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1385,9 +1385,9 @@ static void test_seal_mremap_expand_fixed(bool seal)
void *ret2;
setup_single_address(page_size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
setup_single_address(size, &newAddr);
- FAIL_TEST_IF_FALSE(newAddr != (void *)-1);
+ FAIL_TEST_IF_FALSE(newAddr != MAP_FAILED);
if (seal) {
ret = sys_mseal(newAddr, size);
@@ -1416,9 +1416,9 @@ static void test_seal_mremap_move_fixed(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
setup_single_address(size, &newAddr);
- FAIL_TEST_IF_FALSE(newAddr != (void *)-1);
+ FAIL_TEST_IF_FALSE(newAddr != MAP_FAILED);
if (seal) {
ret = sys_mseal(newAddr, size);
@@ -1445,7 +1445,7 @@ static void test_seal_mremap_move_fixed_zero(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1476,7 +1476,7 @@ static void test_seal_mremap_move_dontunmap(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1505,7 +1505,7 @@ static void test_seal_mremap_move_dontunmap_anyaddr(bool seal)
void *ret2;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1517,7 +1517,7 @@ static void test_seal_mremap_move_dontunmap_anyaddr(bool seal)
* use allocate/free to similate that.
*/
setup_single_address(size, &ptr2);
- FAIL_TEST_IF_FALSE(ptr2 != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr2 != MAP_FAILED);
ret = sys_munmap(ptr2, size);
FAIL_TEST_IF_FALSE(!ret);
@@ -1547,7 +1547,7 @@ static void test_seal_merge_and_split(void)
/* (24 RO) */
setup_single_address(24 * page_size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
/* use mprotect(NONE) to set out boundary */
/* (1 NONE) (22 RO) (1 NONE) */
@@ -1637,7 +1637,7 @@ static void test_seal_discard_ro_anon_on_rw(bool seal)
int ret;
setup_single_address_rw(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1669,7 +1669,7 @@ static void test_seal_discard_ro_anon_on_pkey(bool seal)
SKIP_TEST_IF_FALSE(pkey_supported());
setup_single_address_rw(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
pkey = sys_pkey_alloc(0, PKEY_UNRESTRICTED);
FAIL_TEST_IF_FALSE(pkey > 0);
@@ -1751,7 +1751,7 @@ static void test_seal_discard_ro_anon_on_shared(bool seal)
unsigned long mapflags = MAP_ANONYMOUS | MAP_SHARED;
ptr = mmap(NULL, size, PROT_READ, mapflags, -1, 0);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = sys_mseal(ptr, size);
@@ -1779,7 +1779,7 @@ static void test_seal_discard_ro_anon(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr, size);
@@ -1809,7 +1809,7 @@ static void test_seal_discard_across_vmas(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr + page_size, page_size);
@@ -1840,7 +1840,7 @@ static void test_seal_madvise_nodiscard(bool seal)
int ret;
setup_single_address(size, &ptr);
- FAIL_TEST_IF_FALSE(ptr != (void *)-1);
+ FAIL_TEST_IF_FALSE(ptr != MAP_FAILED);
if (seal) {
ret = seal_single_address(ptr, size);
diff --git a/tools/testing/selftests/mm/pagemap_ioctl.c b/tools/testing/selftests/mm/pagemap_ioctl.c
index f9bcff8e78fa..1b2dffcc999b 100644
--- a/tools/testing/selftests/mm/pagemap_ioctl.c
+++ b/tools/testing/selftests/mm/pagemap_ioctl.c
@@ -8,6 +8,7 @@
#include <errno.h>
#include <malloc.h>
#include <linux/types.h>
+#include <linux/mman.h>
#include <linux/memfd.h>
#include <linux/userfaultfd.h>
#include <linux/fs.h>
@@ -212,6 +213,8 @@ int userfaultfd_tests(void)
vec_size = mem_size/page_size;
vec = calloc(vec_size, sizeof(struct page_region));
+ if (!vec)
+ ksft_exit_fail_msg("error nomem\n");
written = pagemap_ioctl(mem, mem_size, vec, 1, PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC,
vec_size - 2, PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
@@ -699,6 +702,8 @@ int base_tests(char *prefix, char *mem, unsigned long long mem_size, int skip)
vec_size = mem_size/page_size;
vec = calloc(vec_size, sizeof(struct page_region));
vec2 = calloc(vec_size, sizeof(struct page_region));
+ if (!vec || !vec2)
+ ksft_exit_fail_msg("error nomem\n");
/* 1. all new pages must be not be written (dirty) */
written = pagemap_ioctl(mem, mem_size, vec, 1, PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC,
@@ -1000,6 +1005,8 @@ int unmapped_region_tests(void)
int written, len = 0x00040000;
long vec_size = len / page_size;
struct page_region *vec = calloc(vec_size, sizeof(struct page_region));
+ if (!vec)
+ ksft_exit_fail_msg("error nomem\n");
/* 1. Get written pages */
written = pagemap_ioctl(start, len, vec, vec_size, 0, 0,
@@ -1058,50 +1065,96 @@ static void test_simple(void)
* the generic path (reached e.g. via category_anyof_mask) must report every
* page written.
*/
-static void unpopulated_scan_test(void)
+/*
+ * Populate @mem (optionally collapsing it into a THP first), drop it with
+ * MADV_DONTNEED, then check PAGEMAP_SCAN reports the whole range written via
+ * both the fast and generic query paths. A dropped THP leaves a pmd_none hole
+ * with no page table, exercising pagemap_scan_pte_hole(); a base-page range
+ * leaves pte_none entries.
+ */
+static void unpopulated_written_test(const char *name, char *mem, long size,
+ bool use_thp)
{
- int npages = 16, i;
- long mem_size = npages * page_size;
+ long npages = size / page_size, fast = 0, slow = 0, ret;
struct page_region regions[16];
- long fast = 0, slow = 0, ret;
- char *mem;
-
- mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (mem == MAP_FAILED)
- ksft_exit_fail_msg("%s mmap failed\n", __func__);
+ int i;
- wp_init(mem, mem_size);
+ wp_init(mem, size);
- /* Populate, then drop: the ptes become pte_none without a marker. */
- memset(mem, 1, mem_size);
- if (madvise(mem, mem_size, MADV_DONTNEED))
- ksft_exit_fail_msg("%s MADV_DONTNEED failed\n", __func__);
+ /* Populate, optionally collapse to a THP, then drop it. */
+ memset(mem, 1, size);
+ if (use_thp &&
+ (madvise(mem, size, MADV_COLLAPSE) ||
+ !check_huge_anon(mem, size / hpage_size, hpage_size))) {
+ ksft_test_result_skip("%s could not form a THP\n", name);
+ goto out;
+ }
+ if (madvise(mem, size, MADV_DONTNEED)) {
+ ksft_test_result_fail("%s MADV_DONTNEED failed\n", name);
+ goto out;
+ }
/* Fast path: category_mask == return_mask == PAGE_IS_WRITTEN. */
- ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
+ ret = pagemap_ioctl(mem, size, regions, ARRAY_SIZE(regions), 0, 0,
PAGE_IS_WRITTEN, 0, 0, PAGE_IS_WRITTEN);
- if (ret < 0)
- ksft_exit_fail_msg("%s fast scan failed\n", __func__);
- for (i = 0; i < ret; i++)
+ for (i = 0; ret > 0 && i < ret; i++)
fast += LEN(regions[i]);
/* Generic path: same query expressed via category_anyof_mask. */
- ret = pagemap_ioctl(mem, mem_size, regions, npages, 0, 0,
+ ret = pagemap_ioctl(mem, size, regions, ARRAY_SIZE(regions), 0, 0,
0, PAGE_IS_WRITTEN, 0, PAGE_IS_WRITTEN);
- if (ret < 0)
- ksft_exit_fail_msg("%s generic scan failed\n", __func__);
- for (i = 0; i < ret; i++)
+ for (i = 0; ret > 0 && i < ret; i++)
slow += LEN(regions[i]);
ksft_test_result(fast == npages && slow == npages,
- "%s unpopulated ptes reported written by both paths (%ld, %ld of %d)\n",
- __func__, fast, slow, npages);
+ "%s unpopulated range reported written by both paths (%ld, %ld of %ld)\n",
+ name, fast, slow, npages);
+out:
+ wp_free(mem, size);
+}
- wp_free(mem, mem_size);
+static void unpopulated_scan_test(void)
+{
+ long mem_size = 16 * page_size;
+ char *mem;
+
+ mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (mem == MAP_FAILED) {
+ ksft_test_result_skip("%s mmap failed\n", __func__);
+ return;
+ }
+
+ unpopulated_written_test(__func__, mem, mem_size, false);
munmap(mem, mem_size);
}
+/*
+ * Same as unpopulated_scan_test(), but the range is a THP: a full-PMD
+ * MADV_DONTNEED leaves a pmd_none hole with no page table.
+ */
+static void unpopulated_thp_scan_test(void)
+{
+ char *area, *mem;
+
+ if (!hpage_size) {
+ ksft_test_result_skip("%s THP not supported\n", __func__);
+ return;
+ }
+
+ /* Over-allocate so a PMD-aligned, THP-sized range fits inside. */
+ area = mmap(NULL, 2 * hpage_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (area == MAP_FAILED) {
+ ksft_test_result_skip("%s mmap failed\n", __func__);
+ return;
+ }
+ mem = (char *)(((unsigned long)area + hpage_size - 1) & ~(hpage_size - 1));
+
+ unpopulated_written_test(__func__, mem, hpage_size, true);
+ munmap(area, 2 * hpage_size);
+}
+
int sanity_tests(void)
{
unsigned long long mem_size, vec_size;
@@ -1116,7 +1169,7 @@ int sanity_tests(void)
vec = calloc(vec_size, sizeof(struct page_region));
mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
- if (mem == MAP_FAILED || vec == MAP_FAILED)
+ if (mem == MAP_FAILED || !vec)
ksft_exit_fail_msg("error nomem\n");
wp_init(mem, mem_size);
@@ -1610,7 +1663,7 @@ int main(int __attribute__((unused)) argc, char *argv[])
if (!hugetlb_setup_default(4))
ksft_print_msg("HugeTLB test will be skipped\n");
- ksft_set_plan(118);
+ ksft_set_plan(119);
page_size = getpagesize();
hpage_size = read_pmd_pagesize();
@@ -1790,6 +1843,7 @@ int main(int __attribute__((unused)) argc, char *argv[])
/* 18. Unpopulated pte scan-path consistency */
unpopulated_scan_test();
+ unpopulated_thp_scan_test();
close(pagemap_fd);
ksft_finished();
diff --git a/tools/testing/selftests/mm/pkey-helpers.h b/tools/testing/selftests/mm/pkey-helpers.h
index 2c377f4e9df1..46a8a1878dc1 100644
--- a/tools/testing/selftests/mm/pkey-helpers.h
+++ b/tools/testing/selftests/mm/pkey-helpers.h
@@ -68,7 +68,9 @@ static inline void sigsafe_printf(const char *format, ...)
#define dprintf3(args...) dprintf_level(3, args)
#define dprintf4(args...) dprintf_level(4, args)
-extern void abort_hooks(void);
+void tracing_on(void);
+void tracing_off(void);
+void abort_hooks(void);
#define pkey_assert(condition) do { \
if (!(condition)) { \
dprintf0("# assert() at %s::%d test_nr: %d iteration: %d\n", \
diff --git a/tools/testing/selftests/mm/pkey-powerpc.h b/tools/testing/selftests/mm/pkey-powerpc.h
index 17bf2d1b0192..2ce85580b404 100644
--- a/tools/testing/selftests/mm/pkey-powerpc.h
+++ b/tools/testing/selftests/mm/pkey-powerpc.h
@@ -126,7 +126,7 @@ static inline void *malloc_pkey_with_mprotect_subpage(long size, int prot, u16 p
size, prot, pkey);
pkey_assert(pkey < NR_PKEYS);
ptr = mmap(NULL, size, prot, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
- pkey_assert(ptr != (void *)-1);
+ pkey_assert(ptr != MAP_FAILED);
ret = syscall(__NR_subpage_prot, ptr, size, NULL);
if (ret) {
diff --git a/tools/testing/selftests/mm/pkey_sighandler_tests.c b/tools/testing/selftests/mm/pkey_sighandler_tests.c
index 302fef54049c..c218d0510a2a 100644
--- a/tools/testing/selftests/mm/pkey_sighandler_tests.c
+++ b/tools/testing/selftests/mm/pkey_sighandler_tests.c
@@ -19,7 +19,6 @@
#include <stdint.h>
#include <stdbool.h>
#include <signal.h>
-#include <assert.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
@@ -207,22 +206,25 @@ static void test_sigsegv_handler_with_pkey0_disabled(void)
struct sigaction sa;
pthread_attr_t attr;
pthread_t thr;
+ int ret;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = sigsegv_handler;
sigemptyset(&sa.sa_mask);
- if (sigaction(SIGSEGV, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
+ ret = sigaction(SIGSEGV, &sa, NULL);
+ pkey_assert(ret == 0);
memset(&siginfo, 0, sizeof(siginfo));
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_create(&thr, &attr, thread_segv_with_pkey0_disabled, NULL);
+ ret = pthread_create(&thr, &attr, thread_segv_with_pkey0_disabled, NULL);
+ if (ret) {
+ errno = ret;
+ pkey_assert(0);
+ }
pthread_mutex_lock(&mutex);
while (siginfo.si_signo == 0)
@@ -247,22 +249,25 @@ static void test_sigsegv_handler_cannot_access_stack(void)
struct sigaction sa;
pthread_attr_t attr;
pthread_t thr;
+ int ret;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = sigsegv_handler;
sigemptyset(&sa.sa_mask);
- if (sigaction(SIGSEGV, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
+ ret = sigaction(SIGSEGV, &sa, NULL);
+ pkey_assert(ret == 0);
memset(&siginfo, 0, sizeof(siginfo));
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- pthread_create(&thr, &attr, thread_segv_pkuerr_stack, NULL);
+ ret = pthread_create(&thr, &attr, thread_segv_pkuerr_stack, NULL);
+ if (ret) {
+ errno = ret;
+ pkey_assert(0);
+ }
pthread_mutex_lock(&mutex);
while (siginfo.si_signo == 0)
@@ -285,24 +290,22 @@ static void test_sigsegv_handler_with_different_pkey_for_stack(void)
static stack_t sigstack;
void *stack;
int pkey;
- int parent_pid = 0;
int child_pid = 0;
u64 pkey_reg;
+ long ret;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sa.sa_sigaction = sigsegv_handler;
sigemptyset(&sa.sa_mask);
- if (sigaction(SIGSEGV, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
+ ret = sigaction(SIGSEGV, &sa, NULL);
+ pkey_assert(ret == 0);
stack = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- assert(stack != MAP_FAILED);
+ pkey_assert(stack != MAP_FAILED);
/* Allow access to MPK 0 and MPK 1 */
pkey_reg = pkey_reg_restrictive_default();
@@ -317,33 +320,41 @@ static void test_sigsegv_handler_with_different_pkey_for_stack(void)
/* Set up alternate signal stack that will use the default MPK */
sigstack.ss_sp = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ pkey_assert(sigstack.ss_sp != MAP_FAILED);
sigstack.ss_flags = 0;
sigstack.ss_size = STACK_SIZE;
memset(&siginfo, 0, sizeof(siginfo));
/* Use clone to avoid newer glibcs using rseq on new threads */
- long ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES |
- CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
- CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID |
- CLONE_DETACHED,
- stack + STACK_SIZE,
- &parent_pid,
- &child_pid);
+ ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES |
+ CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
+ CLONE_DETACHED,
+ stack + STACK_SIZE,
+ NULL,
+ NULL);
if (ret < 0) {
errno = -ret;
- perror("clone");
+ pkey_assert(0);
} else if (ret == 0) {
thread_segv_maperr_ptr(&sigstack);
syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0);
}
+ child_pid = ret;
+
pthread_mutex_lock(&mutex);
while (siginfo.si_signo == 0)
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
+ /* Wait for child to exit before returning */
+ do {
+ sched_yield();
+ ret = syscall_raw(SYS_tkill, child_pid, 0, 0, 0, 0, 0);
+ } while (ret != -ESRCH && ret != -EINVAL);
+
ksft_test_result(siginfo.si_signo == SIGSEGV &&
siginfo.si_code == SEGV_MAPERR &&
siginfo.si_addr == NULL,
@@ -358,6 +369,7 @@ static void test_pkru_preserved_after_sigusr1(void)
{
struct sigaction sa;
u64 pkey_reg;
+ int ret;
/* Allow access to MPK 0 and an arbitrary set of keys */
pkey_reg = pkey_reg_restrictive_default();
@@ -369,10 +381,8 @@ static void test_pkru_preserved_after_sigusr1(void)
sa.sa_sigaction = sigusr1_handler;
sigemptyset(&sa.sa_mask);
- if (sigaction(SIGUSR1, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
+ ret = sigaction(SIGUSR1, &sa, NULL);
+ pkey_assert(ret == 0);
memset(&siginfo, 0, sizeof(siginfo));
@@ -441,9 +451,15 @@ static void test_pkru_sigreturn(void)
static stack_t sigstack;
void *stack;
int pkey;
- int parent_pid = 0;
int child_pid = 0;
u64 pkey_reg;
+ long ret;
+
+ /*
+ * SIGSEGV handler is reset to SIG_DFL below; turn tracing off first
+ * so a crash does not leave ftrace enabled.
+ */
+ tracing_off();
sa.sa_handler = SIG_DFL;
sa.sa_flags = 0;
@@ -453,24 +469,20 @@ static void test_pkru_sigreturn(void)
* For this testcase, we do not want to handle SIGSEGV. Reset handler
* to default so that the application can crash if it receives SIGSEGV.
*/
- if (sigaction(SIGSEGV, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
+ ret = sigaction(SIGSEGV, &sa, NULL);
+ pkey_assert(ret == 0);
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sa.sa_sigaction = sigusr2_handler;
sigemptyset(&sa.sa_mask);
- if (sigaction(SIGUSR2, &sa, NULL) == -1) {
- perror("sigaction");
- exit(EXIT_FAILURE);
- }
+ ret = sigaction(SIGUSR2, &sa, NULL);
+ pkey_assert(ret == 0);
stack = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- assert(stack != MAP_FAILED);
+ pkey_assert(stack != MAP_FAILED);
/*
* Allow access to MPK 0 and MPK 2. The child thread (to be created
@@ -490,21 +502,21 @@ static void test_pkru_sigreturn(void)
/* Set up alternate signal stack that will use the default MPK */
sigstack.ss_sp = mmap(0, STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ pkey_assert(sigstack.ss_sp != MAP_FAILED);
sigstack.ss_flags = 0;
sigstack.ss_size = STACK_SIZE;
/* Use clone to avoid newer glibcs using rseq on new threads */
- long ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES |
- CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
- CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID |
- CLONE_DETACHED,
- stack + STACK_SIZE,
- &parent_pid,
- &child_pid);
+ ret = clone_raw(CLONE_VM | CLONE_FS | CLONE_FILES |
+ CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
+ CLONE_DETACHED,
+ stack + STACK_SIZE,
+ NULL,
+ NULL);
if (ret < 0) {
errno = -ret;
- perror("clone");
+ pkey_assert(0);
} else if (ret == 0) {
thread_sigusr2_self(&sigstack);
syscall_raw(SYS_exit, 0, 0, 0, 0, 0, 0);
@@ -530,16 +542,17 @@ static void (*pkey_tests[])(void) = {
int main(int argc, char *argv[])
{
- int i;
-
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(pkey_tests));
if (!is_pkeys_supported())
ksft_exit_skip("pkeys not supported\n");
- for (i = 0; i < ARRAY_SIZE(pkey_tests); i++)
- (*pkey_tests[i])();
+ for (test_nr = 0; test_nr < ARRAY_SIZE(pkey_tests); test_nr++) {
+ tracing_on();
+ (*pkey_tests[test_nr])();
+ tracing_off();
+ }
ksft_finished();
return 0;
diff --git a/tools/testing/selftests/mm/pkey_util.c b/tools/testing/selftests/mm/pkey_util.c
index 255b332f7a08..fbef3cd45447 100644
--- a/tools/testing/selftests/mm/pkey_util.c
+++ b/tools/testing/selftests/mm/pkey_util.c
@@ -2,9 +2,99 @@
#define __SANE_USERSPACE_TYPES__
#include <sys/syscall.h>
#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
#include "pkey-helpers.h"
+int iteration_nr = 1;
+int test_nr;
+int dprint_in_signal;
+
+#if CONTROL_TRACING > 0
+static void cat_into_file(char *str, char *file)
+{
+ int fd = open(file, O_RDWR);
+ int ret;
+
+ dprintf2("%s(): writing '%s' to '%s'\n", __func__, str, file);
+ /*
+ * these need to be raw because they are called under
+ * pkey_assert()
+ */
+ if (fd < 0) {
+ fprintf(stderr, "error opening '%s'\n", file);
+ perror("error: ");
+ exit(__LINE__);
+ }
+
+ ret = write(fd, str, strlen(str));
+ if (ret != strlen(str)) {
+ perror("write to file failed");
+ fprintf(stderr, "filename: '%s' str: '%s'\n", file, str);
+ exit(__LINE__);
+ }
+ close(fd);
+}
+
+static int warned_tracing;
+static int tracing_root_ok(void)
+{
+ if (geteuid() != 0) {
+ if (!warned_tracing)
+ fprintf(stderr, "WARNING: not run as root, "
+ "can not do tracing control\n");
+ warned_tracing = 1;
+ return 0;
+ }
+ return 1;
+}
+#endif
+
+void tracing_on(void)
+{
+#if CONTROL_TRACING > 0
+#define TRACEDIR "/sys/kernel/tracing"
+ char pidstr[32];
+
+ if (!tracing_root_ok())
+ return;
+
+ sprintf(pidstr, "%d", getpid());
+ cat_into_file("0", TRACEDIR "/tracing_on");
+ cat_into_file("\n", TRACEDIR "/trace");
+ if (1) {
+ cat_into_file("function_graph", TRACEDIR "/current_tracer");
+ cat_into_file("1", TRACEDIR "/options/funcgraph-proc");
+ } else {
+ cat_into_file("nop", TRACEDIR "/current_tracer");
+ }
+ cat_into_file(pidstr, TRACEDIR "/set_ftrace_pid");
+ cat_into_file("1", TRACEDIR "/tracing_on");
+ dprintf1("enabled tracing\n");
+#endif
+}
+
+void tracing_off(void)
+{
+#if CONTROL_TRACING > 0
+ if (!tracing_root_ok())
+ return;
+ cat_into_file("0", "/sys/kernel/tracing/tracing_on");
+#endif
+}
+
+void abort_hooks(void)
+{
+ fflush(stdout);
+ fprintf(stderr, "running %s()...\n", __func__);
+ tracing_off();
+#ifdef SLEEP_ON_ABORT
+ sleep(SLEEP_ON_ABORT);
+#endif
+}
+
int sys_pkey_alloc(unsigned long flags, unsigned long init_val)
{
int ret = syscall(SYS_pkey_alloc, flags, init_val);
diff --git a/tools/testing/selftests/mm/protection_keys.c b/tools/testing/selftests/mm/protection_keys.c
index 9a6d954ee371..ae6e1530b354 100644
--- a/tools/testing/selftests/mm/protection_keys.c
+++ b/tools/testing/selftests/mm/protection_keys.c
@@ -49,11 +49,7 @@
#include "hugepage_settings.h"
#include "pkey-helpers.h"
-int iteration_nr = 1;
-int test_nr;
-
u64 shadow_pkey_reg;
-int dprint_in_signal;
noinline int read_ptr(int *ptr)
{
@@ -62,89 +58,6 @@ noinline int read_ptr(int *ptr)
return *ptr;
}
-#if CONTROL_TRACING > 0
-static void cat_into_file(char *str, char *file)
-{
- int fd = open(file, O_RDWR);
- int ret;
-
- dprintf2("%s(): writing '%s' to '%s'\n", __func__, str, file);
- /*
- * these need to be raw because they are called under
- * pkey_assert()
- */
- if (fd < 0) {
- fprintf(stderr, "error opening '%s'\n", str);
- perror("error: ");
- exit(__LINE__);
- }
-
- ret = write(fd, str, strlen(str));
- if (ret != strlen(str)) {
- perror("write to file failed");
- fprintf(stderr, "filename: '%s' str: '%s'\n", file, str);
- exit(__LINE__);
- }
- close(fd);
-}
-
-static int warned_tracing;
-static int tracing_root_ok(void)
-{
- if (geteuid() != 0) {
- if (!warned_tracing)
- fprintf(stderr, "WARNING: not run as root, "
- "can not do tracing control\n");
- warned_tracing = 1;
- return 0;
- }
- return 1;
-}
-#endif
-
-static void tracing_on(void)
-{
-#if CONTROL_TRACING > 0
-#define TRACEDIR "/sys/kernel/tracing"
- char pidstr[32];
-
- if (!tracing_root_ok())
- return;
-
- sprintf(pidstr, "%d", getpid());
- cat_into_file("0", TRACEDIR "/tracing_on");
- cat_into_file("\n", TRACEDIR "/trace");
- if (1) {
- cat_into_file("function_graph", TRACEDIR "/current_tracer");
- cat_into_file("1", TRACEDIR "/options/funcgraph-proc");
- } else {
- cat_into_file("nop", TRACEDIR "/current_tracer");
- }
- cat_into_file(pidstr, TRACEDIR "/set_ftrace_pid");
- cat_into_file("1", TRACEDIR "/tracing_on");
- dprintf1("enabled tracing\n");
-#endif
-}
-
-static void tracing_off(void)
-{
-#if CONTROL_TRACING > 0
- if (!tracing_root_ok())
- return;
- cat_into_file("0", "/sys/kernel/tracing/tracing_on");
-#endif
-}
-
-void abort_hooks(void)
-{
- fflush(stdout);
- fprintf(stderr, "running %s()...\n", __func__);
- tracing_off();
-#ifdef SLEEP_ON_ABORT
- sleep(SLEEP_ON_ABORT);
-#endif
-}
-
/*
* This attempts to have roughly a page of instructions followed by a few
* instructions that do a write, and another page of instructions. That
@@ -669,7 +582,7 @@ static void *malloc_pkey_with_mprotect(long size, int prot, u16 pkey)
size, prot, pkey);
pkey_assert(pkey < NR_PKEYS);
ptr = mmap(NULL, size, prot, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
- pkey_assert(ptr != (void *)-1);
+ pkey_assert(ptr != MAP_FAILED);
ret = mprotect_pkey((void *)ptr, PAGE_SIZE, prot, pkey);
pkey_assert(!ret);
record_pkey_malloc(ptr, size, prot);
@@ -692,7 +605,7 @@ static void *malloc_pkey_anon_huge(long size, int prot, u16 pkey)
*/
size = ALIGN_UP(size, HPAGE_SIZE * 2);
ptr = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
- pkey_assert(ptr != (void *)-1);
+ pkey_assert(ptr != MAP_FAILED);
record_pkey_malloc(ptr, size, prot);
mprotect_pkey(ptr, size, prot, pkey);
@@ -750,7 +663,7 @@ static void *malloc_pkey_hugetlb(long size, int prot, u16 pkey)
size = ALIGN_UP(size, HPAGE_SIZE * 2);
pkey_assert(pkey < NR_PKEYS);
ptr = mmap(NULL, size, PROT_NONE, flags, -1, 0);
- pkey_assert(ptr != (void *)-1);
+ pkey_assert(ptr != MAP_FAILED);
mprotect_pkey(ptr, size, prot, pkey);
record_pkey_malloc(ptr, size, prot);
@@ -779,7 +692,7 @@ static void *malloc_pkey(long size, int prot, u16 pkey)
pkey_assert(malloc_type < nr_malloc_types);
ret = pkey_malloc[malloc_type](size, prot, pkey);
- pkey_assert(ret != (void *)-1);
+ pkey_assert(ret != MAP_FAILED);
malloc_type++;
if (malloc_type >= nr_malloc_types)
@@ -1197,6 +1110,7 @@ static void arch_force_pkey_reg_init(void)
* doing the XSAVE size enumeration dance.
*/
buf = mmap(NULL, 1*MB, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+ pkey_assert(buf != MAP_FAILED);
/* These __builtins require compiling with -mxsave */
@@ -1763,7 +1677,8 @@ int main(void)
ksft_print_msg("running PKEY tests for unsupported CPU/OS\n");
ptr = mmap(NULL, size, PROT_NONE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
- assert(ptr != (void *)-1);
+ if (ptr == MAP_FAILED)
+ ksft_exit_fail_perror("mmap");
test_mprotect_pkey_on_unsupported_cpu(ptr, 1);
ksft_test_result_pass("pkey on unsupported CPU/OS\n");
ksft_finished();
diff --git a/tools/testing/selftests/mm/rmap.c b/tools/testing/selftests/mm/rmap.c
index 53f2058b0ef2..1c293ad3f8b8 100644
--- a/tools/testing/selftests/mm/rmap.c
+++ b/tools/testing/selftests/mm/rmap.c
@@ -430,4 +430,85 @@ TEST_F(migrate, ksm)
propagate_children(_metadata, data);
}
+static bool range_maps_the_same_pfn(int pagemap_fd, void *region, int nr_pages)
+{
+ int i;
+ int retries = 0;
+ unsigned long first_pfn;
+
+retry:
+ if (retries > 10)
+ return false;
+
+ first_pfn = pagemap_get_pfn(pagemap_fd, region);
+ for (i = 0; i < nr_pages; i++) {
+ if (pagemap_get_pfn(pagemap_fd, region + i * getpagesize()) != first_pfn) {
+ /*
+ * Retry up to 10 times at most in case of the low chance of page
+ * compaction migrating the page while we check for pfn.
+ */
+ retries++;
+ goto retry;
+ }
+ }
+
+ return true;
+}
+
+TEST_F(migrate, ksm_and_mremap)
+{
+ unsigned long old_pfn, new_pfn;
+ void *region, *mremap_region;
+ const int nr_pages = 16;
+ size_t mmap_size;
+ int pagemap_fd;
+
+ /* Skip if KSM is not available */
+ if (ksm_stop() < 0)
+ SKIP(return, "accessing \"/sys/kernel/mm/ksm/run\" failed");
+ if (ksm_get_full_scans() < 0)
+ SKIP(return, "accessing \"/sys/kernel/mm/ksm/full_scan\" failed");
+
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0)
+ SKIP(return, "opening pagemap failed");
+
+ /* Allocate and populate twice the anon pages initially. */
+ mmap_size = 2 * nr_pages * getpagesize();
+ region = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ ASSERT_NE(region, MAP_FAILED);
+ memset(region, 0x77, mmap_size);
+
+ /* mremap the second half over the first half, to stress rmap handling */
+ mmap_size /= 2;
+ mremap_region = mremap(region + mmap_size, mmap_size, mmap_size,
+ MREMAP_MAYMOVE | MREMAP_FIXED, region);
+ ASSERT_EQ(mremap_region, region);
+
+ /* Merge all pages into a single KSM page. */
+ madvise(region, mmap_size, MADV_MERGEABLE);
+ ASSERT_EQ(ksm_start(), 0);
+
+ /* The whole range should map the same KSM page. */
+ old_pfn = pagemap_get_pfn(pagemap_fd, region);
+ if (old_pfn == -1ul)
+ SKIP(return, "Obtaining PFN failed");
+ ksm_start();
+ ASSERT_TRUE(range_maps_the_same_pfn(pagemap_fd, region, nr_pages));
+
+ /*
+ * Migrate the KSM page; the whole range should map the new (migrated)
+ * KSM page.
+ */
+ ASSERT_EQ(try_to_move_page(region), 0);
+
+ new_pfn = pagemap_get_pfn(pagemap_fd, region);
+ if (new_pfn == -1ul)
+ SKIP(return, "Obtaining PFN failed");
+ ASSERT_NE(new_pfn, old_pfn);
+ ASSERT_TRUE(range_maps_the_same_pfn(pagemap_fd, region, nr_pages));
+}
+
+
TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/mm/run_vmtests.sh b/tools/testing/selftests/mm/run_vmtests.sh
index 8c296dedf047..687d115e3bd8 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -262,7 +262,6 @@ echo "TAP version 13" | tap_output
CATEGORY="hugetlb" run_test ./hugetlb-mmap
CATEGORY="hugetlb" run_test ./hugetlb-shm
CATEGORY="hugetlb" run_test ./hugetlb-mremap
-CATEGORY="hugetlb" run_test ./hugetlb-vmemmap
CATEGORY="hugetlb" run_test ./hugetlb-madvise
CATEGORY="hugetlb" run_test ./hugetlb_dio
CATEGORY="hugetlb" run_test ./hugetlb_fault_after_madv
@@ -303,7 +302,7 @@ CATEGORY="compaction" run_test ./compaction_test
if command -v sudo &> /dev/null && sudo -u nobody ls ./on-fault-limit >/dev/null;
then
- CATEGORY="mlock" run_test sudo -u nobody ./on-fault-limit
+ CATEGORY="mlock" run_test sudo -u nobody sh -c ./on-fault-limit
else
echo "# SKIP ./on-fault-limit"
fi
diff --git a/tools/testing/selftests/mm/thuge-gen.c b/tools/testing/selftests/mm/thuge-gen.c
index 22b9c2f1c35d..50d0805b65db 100644
--- a/tools/testing/selftests/mm/thuge-gen.c
+++ b/tools/testing/selftests/mm/thuge-gen.c
@@ -71,12 +71,16 @@ void test_mmap(unsigned long size, unsigned flags)
void test_shmget(unsigned long size, unsigned flags)
{
- int id;
- unsigned long before, after;
+ /* values for PAGE_SIZE test */
+ unsigned long before = NUM_PAGES;
+ unsigned long after = 0;
struct shm_info i;
char *map;
+ int id;
+
+ if (size != getpagesize())
+ before = hugetlb_free_pages(size);
- before = hugetlb_free_pages(size);
id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
if (id < 0) {
if (errno == EPERM) {
@@ -97,10 +101,11 @@ void test_shmget(unsigned long size, unsigned flags)
shmctl(id, IPC_RMID, NULL);
memset(map, 0xff, size*NUM_PAGES);
- after = hugetlb_free_pages(size);
+ if (size != getpagesize())
+ after = hugetlb_free_pages(size);
show(size);
- ksft_test_result(size == getpagesize() || (before - after) == NUM_PAGES,
+ ksft_test_result((before - after) == NUM_PAGES,
"%s: mmap %lu %x\n", __func__, size, flags);
if (shmdt(map))
ksft_exit_fail_msg("%s: shmdt: %s\n", __func__, strerror(errno));
diff --git a/tools/testing/selftests/mm/uffd-common.c b/tools/testing/selftests/mm/uffd-common.c
index edd02328f77b..f48f5d4594ab 100644
--- a/tools/testing/selftests/mm/uffd-common.c
+++ b/tools/testing/selftests/mm/uffd-common.c
@@ -639,8 +639,13 @@ int __copy_page(uffd_global_test_opts_t *gopts, unsigned long offset, bool retry
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(gopts->uffd, UFFDIO_COPY, &uffdio_copy)) {
- /* real retval in ufdio_copy.copy */
- if (uffdio_copy.copy != -EEXIST)
+ /*
+ * real retval in uffdio_copy.copy
+ *
+ * -EEXIST: the page was faulted in concurrently
+ * -ENOENT: the destination range was concurrently removed
+ */
+ if (uffdio_copy.copy != -EEXIST && uffdio_copy.copy != -ENOENT)
err("UFFDIO_COPY error: %"PRId64,
(int64_t)uffdio_copy.copy);
wake_range(gopts->uffd, uffdio_copy.dst, gopts->page_size);
diff --git a/tools/testing/selftests/mm/uffd-unit-tests.c b/tools/testing/selftests/mm/uffd-unit-tests.c
index a6c14109e818..ef9b3956bdcf 100644
--- a/tools/testing/selftests/mm/uffd-unit-tests.c
+++ b/tools/testing/selftests/mm/uffd-unit-tests.c
@@ -7,6 +7,8 @@
#include "uffd-common.h"
+#include <linux/fs.h>
+#include <sys/uio.h>
#include "../../../../mm/gup_test.h"
#ifdef __NR_userfaultfd
@@ -109,6 +111,10 @@ static void uffd_test_skip(const char *message)
static void test_uffd_api(bool use_dev)
{
+ const uint64_t expected_ioctls =
+ BIT_ULL(_UFFDIO_REGISTER) |
+ BIT_ULL(_UFFDIO_UNREGISTER) |
+ BIT_ULL(_UFFDIO_API);
struct uffdio_api uffdio_api;
int uffd;
@@ -148,6 +154,15 @@ static void test_uffd_api(bool use_dev)
goto out;
}
+ /* Verify returned fd-level ioctls bitmask */
+ if ((uffdio_api.ioctls & expected_ioctls) != expected_ioctls) {
+ uffd_test_fail("UFFDIO_API missing expected ioctls: "
+ "got=0x%"PRIx64", expected=0x%"PRIx64,
+ (uint64_t)uffdio_api.ioctls,
+ expected_ioctls);
+ goto out;
+ }
+
/* Test double requests of UFFDIO_API with a random feature set */
uffdio_api.features = BIT_ULL(0);
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == 0) {
@@ -602,6 +617,814 @@ void uffd_minor_collapse_test(uffd_global_test_opts_t *gopts, uffd_test_args_t *
uffd_minor_test_common(gopts, true, false);
}
+static int uffd_register_rwp(int uffd, void *addr, uint64_t len)
+{
+ struct uffdio_register reg = {
+ .range = { .start = (unsigned long)addr, .len = len },
+ .mode = UFFDIO_REGISTER_MODE_RWP,
+ };
+
+ if (ioctl(uffd, UFFDIO_REGISTER, &reg) == -1)
+ return -errno;
+ return 0;
+}
+
+static void rwprotect_range(int uffd, __u64 start, __u64 len, bool protect)
+{
+ struct uffdio_rwprotect rwp = {
+ .range = { .start = start, .len = len },
+ .mode = protect ? UFFDIO_RWPROTECT_MODE_RWP : 0,
+ };
+
+ if (ioctl(uffd, UFFDIO_RWPROTECT, &rwp))
+ err("UFFDIO_RWPROTECT failed");
+}
+
+static void set_async_mode(int uffd, bool enable)
+{
+ struct uffdio_set_mode mode = { };
+
+ if (enable)
+ mode.enable = UFFD_FEATURE_RWP_ASYNC;
+ else
+ mode.disable = UFFD_FEATURE_RWP_ASYNC;
+
+ if (ioctl(uffd, UFFDIO_SET_MODE, &mode))
+ err("UFFDIO_SET_MODE failed");
+}
+
+/*
+ * Test async RWP faults on anonymous memory.
+ * Populate pages, register MODE_RWP with RWP_ASYNC,
+ * RW-protect, re-access, verify content preserved and no faults delivered.
+ */
+static void uffd_rwp_async_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+
+ /* Populate all pages with known content */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ /* Register MODE_RWP */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ /* RW-protect all pages (sets protnone) */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Access all pages — should auto-resolve, no faults */
+ for (p = 0; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+ unsigned char expected = p % 255 + 1;
+
+ if (page[0] != expected) {
+ uffd_test_fail("page %lu content mismatch: %u != %u",
+ p, page[0], expected);
+ return;
+ }
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Fault handler for RWP — unprotect the page via UFFDIO_RWPROTECT.
+ */
+static void uffd_handle_rwp_fault(uffd_global_test_opts_t *gopts,
+ struct uffd_msg *msg,
+ struct uffd_args *uargs)
+{
+ if (!(msg->arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_RWP))
+ err("expected RWP fault, got 0x%llx",
+ msg->arg.pagefault.flags);
+
+ rwprotect_range(gopts->uffd, msg->arg.pagefault.address,
+ gopts->page_size, false);
+ uargs->minor_faults++;
+}
+
+/*
+ * Test sync RWP faults on anonymous memory.
+ * Populate pages, register MODE_RWP (sync), RW-protect,
+ * access from worker thread, verify fault delivered, UFFDIO_RWPROTECT resolves.
+ */
+static void uffd_rwp_sync_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ pthread_t uffd_mon;
+ struct uffd_args uargs = { };
+ bool failed = false;
+ char c = '\0';
+ unsigned long p;
+
+ uargs.gopts = gopts;
+ uargs.handle_fault = uffd_handle_rwp_fault;
+
+ /* Populate all pages */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ /* Register MODE_RWP */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ /* RW-protect all pages */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Start fault handler thread */
+ if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &uargs))
+ err("uffd_poll_thread create");
+
+ /* Access all pages — triggers sync RWP faults, handler unprotects */
+ for (p = 0; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+
+ if (page[0] != (p % 255 + 1)) {
+ uffd_test_fail("page %lu content mismatch", p);
+ failed = true;
+ goto out;
+ }
+ }
+
+out:
+ /*
+ * Stop the handler before reading minor_faults: the last fault
+ * resolution rwprotect_range()s before incrementing the counter,
+ * so the main thread can race ahead of the increment.
+ */
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join() failed");
+
+ if (failed)
+ return;
+ if (uargs.minor_faults == 0)
+ uffd_test_fail("expected RWP faults, got 0");
+ else
+ uffd_test_pass();
+}
+
+/*
+ * Test PAGEMAP_SCAN working-set discovery via the "hot" (accessed) scan.
+ *
+ * The working-set primitive is to find pages that were accessed: scan for
+ * PAGE_IS_ACCESSED, which is set once an access clears the protnone+uffd
+ * marker. A VMM treats every access fault as "hot" (RWP here; MINOR/MISSING
+ * for non-resident pages) and reclaims the rest from the backing file.
+ *
+ * We deliberately do NOT use an inverted "cold" scan: that only sees
+ * VMA-resident ptes, so for a file mapping it misses cached-but-unmapped (and
+ * never-faulted, pre-populated) pages, which are pte_none and thus invisible.
+ * Hot tracking + file-level reclaim covers them; a cold pte scan cannot.
+ */
+static void uffd_rwp_pagemap_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+ struct page_region regions[16];
+ struct pm_scan_arg pm_arg;
+ int pagemap_fd;
+ long ret;
+
+ /* Need at least 4 pages */
+ if (nr_pages < 4) {
+ uffd_test_skip("need at least 4 pages");
+ return;
+ }
+
+ /* Populate all pages */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, 0xab, page_size);
+
+ /* Register and RW-protect */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Touch first half of pages to re-activate them (async auto-resolve) */
+ for (p = 0; p < nr_pages / 2; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page;
+ }
+
+ uint64_t start = (uint64_t)gopts->area_dst;
+ uint64_t boundary = start + (nr_pages / 2) * page_size;
+ uint64_t end = start + nr_pages * page_size;
+
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0)
+ err("open pagemap");
+
+ /*
+ * Hot scan: report the pages that were accessed. PAGE_IS_ACCESSED is
+ * set once the protnone+uffd marker is cleared (by the access, async
+ * auto-resolve here). The touched first half must come back as exactly
+ * one hot region [start, boundary); the untouched second half must not
+ * appear.
+ */
+ memset(&pm_arg, 0, sizeof(pm_arg));
+ pm_arg.size = sizeof(pm_arg);
+ pm_arg.start = start;
+ pm_arg.end = end;
+ pm_arg.vec = (uint64_t)regions;
+ pm_arg.vec_len = ARRAY_SIZE(regions);
+ pm_arg.category_mask = PAGE_IS_ACCESSED;
+ pm_arg.return_mask = PAGE_IS_ACCESSED;
+
+ ret = ioctl(pagemap_fd, PAGEMAP_SCAN, &pm_arg);
+ close(pagemap_fd);
+
+ if (ret < 0) {
+ uffd_test_fail("PAGEMAP_SCAN failed: %s", strerror(errno));
+ return;
+ }
+
+ if (ret != 1 || regions[0].start != start ||
+ regions[0].end != boundary) {
+ uffd_test_fail("hot set wrong: got %ld regions [0x%lx,0x%lx), expected 1 [0x%lx,0x%lx)",
+ ret, (unsigned long)regions[0].start,
+ (unsigned long)regions[0].end,
+ (unsigned long)start, (unsigned long)boundary);
+ return;
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Test that RWP protection survives a mprotect(PROT_NONE) ->
+ * mprotect(PROT_READ|PROT_WRITE) round-trip. The uffd-wp bit on a
+ * VM_UFFD_RWP VMA must continue to carry PROT_NONE semantics after
+ * mprotect() changes the base protection; otherwise accesses would
+ * silently succeed and the pagemap bit would stick without a fault
+ * ever clearing it.
+ */
+static void uffd_rwp_mprotect_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+ struct page_region regions[16];
+ struct pm_scan_arg pm_arg;
+ int pagemap_fd;
+ uint64_t value;
+ long ret;
+
+ /* Populate all pages */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, 0xab, page_size);
+
+ /* Register and RW-protect the whole range */
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Round-trip mprotect(): PROT_NONE -> PROT_READ|PROT_WRITE */
+ if (mprotect(gopts->area_dst, nr_pages * page_size, PROT_NONE))
+ err("mprotect() PROT_NONE");
+ if (mprotect(gopts->area_dst, nr_pages * page_size,
+ PROT_READ | PROT_WRITE))
+ err("mprotect() PROT_READ|PROT_WRITE");
+
+ /*
+ * The marker must survive the round-trip; if mprotect() dropped it,
+ * the touches below would not fault and the scan would pass
+ * vacuously.
+ */
+ pagemap_fd = pagemap_open();
+ value = pagemap_get_entry(pagemap_fd, gopts->area_dst);
+ close(pagemap_fd);
+ if (!(value & PM_UFFD_WP)) {
+ uffd_test_fail("RWP marker lost across mprotect()");
+ return;
+ }
+
+ /* Touch every page. Async RWP must auto-resolve each fault. */
+ for (p = 0; p < nr_pages; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page;
+ }
+
+ /*
+ * After touching, no page should remain RW-protected. A stuck
+ * uffd-wp bit would mean mprotect() silently dropped PROT_NONE and
+ * the access never faulted.
+ */
+ pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
+ if (pagemap_fd < 0)
+ err("open pagemap");
+
+ memset(&pm_arg, 0, sizeof(pm_arg));
+ pm_arg.size = sizeof(pm_arg);
+ pm_arg.start = (uint64_t)gopts->area_dst;
+ pm_arg.end = (uint64_t)gopts->area_dst + nr_pages * page_size;
+ pm_arg.vec = (uint64_t)regions;
+ pm_arg.vec_len = ARRAY_SIZE(regions);
+ pm_arg.category_mask = PAGE_IS_ACCESSED;
+ pm_arg.category_inverted = PAGE_IS_ACCESSED;
+ pm_arg.return_mask = PAGE_IS_ACCESSED;
+
+ ret = ioctl(pagemap_fd, PAGEMAP_SCAN, &pm_arg);
+ close(pagemap_fd);
+
+ if (ret < 0) {
+ uffd_test_fail("PAGEMAP_SCAN failed: %s", strerror(errno));
+ return;
+ }
+ if (ret != 0) {
+ uffd_test_fail("expected no cold pages after mprotect()+touch, got %ld regions",
+ ret);
+ return;
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Test that GUP resolves through protnone PTEs (async mode).
+ * vmsplice() into a pipe pins user pages via get_user_pages_fast() --
+ * unlike write(), which goes through copy_from_user() and ordinary
+ * hardware page faults -- so it exercises gup_can_follow_protnone() on
+ * the RW-protected PTE. In async mode the kernel auto-restores
+ * permissions and GUP returns the page.
+ */
+static void uffd_rwp_gup_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ struct iovec iov;
+ char buf;
+ int pipefd[2];
+
+ /* Populate first page with known content */
+ memset(gopts->area_dst, 0xCD, gopts->page_size);
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst, gopts->page_size))
+ err("register failure");
+
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ gopts->page_size, true);
+
+ if (pipe(pipefd))
+ err("pipe");
+
+ /*
+ * One byte's worth of iov is enough to GUP the containing page and
+ * keeps the pipe transfer well under any pipe-capacity limit even on
+ * hugetlb-backed runs.
+ */
+ iov.iov_base = gopts->area_dst;
+ iov.iov_len = 1;
+ if (vmsplice(pipefd[1], &iov, 1, 0) != 1) {
+ uffd_test_fail("vmsplice from RW-protected page failed: %s",
+ strerror(errno));
+ goto out;
+ }
+
+ if (read(pipefd[0], &buf, 1) != 1) {
+ uffd_test_fail("read from pipe failed");
+ goto out;
+ }
+
+ if (buf != (char)0xCD) {
+ uffd_test_fail("content mismatch: got 0x%02x, expected 0xCD",
+ (unsigned char)buf);
+ goto out;
+ }
+
+ uffd_test_pass();
+out:
+ close(pipefd[0]);
+ close(pipefd[1]);
+}
+
+/*
+ * Test runtime toggle between async and sync modes.
+ * Start in async mode (detection), flip to sync (eviction), verify faults
+ * block, resolve them, flip back to async.
+ */
+static void uffd_rwp_async_toggle_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ struct uffd_args uargs = { };
+ pthread_t uffd_mon;
+ char c = '\0';
+ unsigned long p;
+
+ uargs.gopts = gopts;
+ uargs.handle_fault = uffd_handle_rwp_fault;
+
+ /* Populate */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ /* Phase 1: async detection — RW-protect, access first half */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ for (p = 0; p < nr_pages / 2; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page; /* auto-resolves in async mode */
+ }
+
+ /* Phase 2: flip to sync for eviction */
+ set_async_mode(gopts->uffd, false);
+
+ /* Start handler — will receive faults for cold pages */
+ if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &uargs))
+ err("uffd_poll_thread create");
+
+ /* Access second half (cold pages) — should trigger sync faults */
+ for (p = nr_pages / 2; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+ if (page[0] != (p % 255 + 1)) {
+ uffd_test_fail("page %lu content mismatch", p);
+ goto out;
+ }
+ }
+
+ /*
+ * Stop the handler before reading minor_faults: the last fault
+ * resolution rwprotect_range()s before incrementing the counter,
+ * so the main thread can race ahead of the increment. Stopping
+ * here also makes Phase 3 a clean async-only test -- with the
+ * handler still running it would silently resolve any sync fault
+ * the kernel erroneously delivers, masking a regression.
+ */
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join() failed");
+
+ if (uargs.minor_faults == 0) {
+ uffd_test_fail("expected sync faults, got 0");
+ return;
+ }
+
+ /* Phase 3: flip back to async */
+ set_async_mode(gopts->uffd, true);
+
+ /* RW-protect and access again — should auto-resolve */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ for (p = 0; p < nr_pages; p++) {
+ volatile char *page = gopts->area_dst + p * page_size;
+ (void)*page;
+ }
+
+ uffd_test_pass();
+ return;
+out:
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join() failed");
+}
+
+/*
+ * Test that RW-protected pages become accessible after closing uffd.
+ */
+static void uffd_rwp_close_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ unsigned long p;
+
+ /* Populate */
+ for (p = 0; p < nr_pages; p++)
+ memset(gopts->area_dst + p * page_size, p % 255 + 1, page_size);
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failure");
+
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ nr_pages * page_size, true);
+
+ /* Close uffd — should restore protnone PTEs */
+ close(gopts->uffd);
+ gopts->uffd = -1;
+
+ /* All pages should be accessible with original content */
+ for (p = 0; p < nr_pages; p++) {
+ unsigned char *page = (unsigned char *)gopts->area_dst +
+ p * page_size;
+ unsigned char expected = p % 255 + 1;
+
+ if (page[0] != expected) {
+ uffd_test_fail("page %lu not accessible after close", p);
+ return;
+ }
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * Test that RWP protection is preserved across fork() when
+ * UFFD_FEATURE_EVENT_FORK is enabled. Without preservation, the child's
+ * PTEs would lose the uffd-wp marker and RWP-protected accesses would
+ * silently fall through to do_numa_page().
+ */
+static void uffd_rwp_fork_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ int pagemap_fd;
+ uint64_t value;
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst,
+ nr_pages * page_size))
+ err("register failed");
+
+ /* Populate + RWP-protect */
+ *gopts->area_dst = 1;
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst,
+ page_size, true);
+
+ /* Parent: verify uffd-wp bit is set before fork */
+ pagemap_fd = pagemap_open();
+ value = pagemap_get_entry(pagemap_fd, gopts->area_dst);
+ pagemap_check_wp(value, true);
+
+ /*
+ * Fork with EVENT_FORK: child inherits VM_UFFD_RWP. Child reads
+ * its own pagemap and must still see the uffd-wp bit set.
+ */
+ if (pagemap_test_fork(gopts, true, false)) {
+ uffd_test_fail("RWP marker lost in child after fork");
+ goto out;
+ }
+
+ uffd_test_pass();
+out:
+ close(pagemap_fd);
+}
+
+/*
+ * Test that RWP protection on a pinned anon page is preserved across fork().
+ * Pinning forces copy_present_page() in the child path, which must restore
+ * PAGE_NONE on top of the uffd bit. Using async mode, a read in the child
+ * auto-resolves if — and only if — the PTE was actually protnone+uffd; the
+ * cleared uffd bit afterward proves the fault path ran.
+ */
+static void uffd_rwp_fork_pin_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long page_size = gopts->page_size;
+ fork_event_args fevent_args = { .gopts = gopts, .child_uffd = -1 };
+ pin_args pin_args = {};
+ int pagemap_fd, status;
+ pthread_t fevent_thread;
+ uint64_t value;
+ pid_t child;
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst, page_size))
+ err("register failed");
+
+ /* Populate. */
+ *gopts->area_dst = 1;
+
+ /* RO-longterm pin so fork() takes copy_present_page() for this PTE. */
+ if (pin_pages(&pin_args, gopts->area_dst, page_size)) {
+ uffd_test_skip("Possibly CONFIG_GUP_TEST missing or unprivileged");
+ uffd_unregister(gopts->uffd, gopts->area_dst, page_size);
+ return;
+ }
+
+ /* RWP-protect: PTE is now PAGE_NONE + uffd bit. */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst, page_size, true);
+
+ pagemap_fd = pagemap_open();
+ value = pagemap_get_entry(pagemap_fd, gopts->area_dst);
+ pagemap_check_wp(value, true);
+
+ /*
+ * UFFD_FEATURE_EVENT_FORK is required so the child inherits
+ * VM_UFFD_RWP and the marker; without it dup_userfaultfd() resets
+ * the child VMA and the test would pass for the wrong reason.
+ * dup_userfaultfd() blocks until the EVENT_FORK message is consumed,
+ * so spawn a reader before the fork().
+ */
+ gopts->ready_for_fork = false;
+ if (pthread_create(&fevent_thread, NULL, fork_event_consumer,
+ &fevent_args))
+ err("pthread_create() for fork event consumer");
+ while (!gopts->ready_for_fork)
+ ; /* Wait for consumer to start polling. */
+
+ child = fork();
+ if (child < 0)
+ err("fork");
+ if (child == 0) {
+ volatile char c;
+ int cfd;
+
+ /*
+ * Precondition: the child must have inherited the marker.
+ * If copy_present_page() dropped it together with PAGE_NONE,
+ * the read below would succeed without the fault path and
+ * the after-read check would pass for the wrong reason.
+ */
+ cfd = pagemap_open();
+ value = pagemap_get_entry(cfd, gopts->area_dst);
+ if (!(value & PM_UFFD_WP)) {
+ close(cfd);
+ _exit(2);
+ }
+
+ /*
+ * Read the pinned page. Only reaches the fault path if the
+ * child PTE is protnone + uffd; async mode auto-resolves and
+ * clears the uffd bit. If copy_present_page() dropped
+ * PAGE_NONE, the read would silently succeed and the bit
+ * would still be set.
+ */
+ c = *(volatile char *)gopts->area_dst;
+ (void)c;
+
+ value = pagemap_get_entry(cfd, gopts->area_dst);
+ close(cfd);
+ _exit((value & PM_UFFD_WP) ? 1 : 0);
+ }
+ if (waitpid(child, &status, 0) < 0)
+ err("waitpid");
+ if (pthread_join(fevent_thread, NULL))
+ err("pthread_join() for fork event consumer");
+ if (fevent_args.child_uffd >= 0)
+ close(fevent_args.child_uffd);
+
+ unpin_pages(&pin_args);
+ close(pagemap_fd);
+ if (uffd_unregister(gopts->uffd, gopts->area_dst, page_size))
+ err("unregister failed");
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 2) {
+ uffd_test_fail("RWP marker not inherited by child");
+ return;
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ uffd_test_fail("RWP not enforced in child after pinned fork");
+ return;
+ }
+
+ uffd_test_pass();
+}
+
+/*
+ * A non-exclusive (forked, COW-shared) anon page that is RWP-protected and
+ * then swapped out must keep tracking across swap-in. On the write that
+ * swaps it back in, do_swap_page() restores PAGE_NONE and the access retries
+ * through the RWP fault path, instead of being COWed straight to a fresh
+ * accessible page -- which would silently drop the marker for a non-exclusive
+ * folio. Sync mode lets us observe the fault directly: with the bug, the
+ * write COWs without delivering any RWP fault.
+ *
+ * Needs a swap device; skipped if MADV_PAGEOUT cannot evict the page.
+ */
+static void uffd_rwp_swap_cow_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long page_size = gopts->page_size;
+ struct uffd_args uargs = { };
+ int pagemap_fd, go[2], i;
+ pthread_t uffd_mon;
+ char c = '\0';
+ pid_t child;
+
+ uargs.gopts = gopts;
+ uargs.handle_fault = uffd_handle_rwp_fault;
+
+ if (uffd_register_rwp(gopts->uffd, gopts->area_dst, page_size))
+ err("register failed");
+
+ /* Populate one page (exclusive at this point). */
+ *gopts->area_dst = 0x11;
+
+ /* RWP-protect: PTE becomes PAGE_NONE + uffd bit (still exclusive). */
+ rwprotect_range(gopts->uffd, (uint64_t)gopts->area_dst, page_size, true);
+
+ /*
+ * Swap the page out while it is still exclusive: a shared (forked)
+ * folio does not get reclaimed by MADV_PAGEOUT. Retry, since a hot
+ * page may just be rotated on the first reclaim pass.
+ */
+ pagemap_fd = pagemap_open();
+ for (i = 0; i < 100; i++) {
+ if (madvise(gopts->area_dst, page_size, MADV_PAGEOUT))
+ err("MADV_PAGEOUT");
+ if (pagemap_is_swapped(pagemap_fd, gopts->area_dst))
+ break;
+ usleep(10000);
+ }
+ if (!pagemap_is_swapped(pagemap_fd, gopts->area_dst)) {
+ uffd_test_skip("MADV_PAGEOUT did not swap the page; is swap enabled?");
+ close(pagemap_fd);
+ uffd_unregister(gopts->uffd, gopts->area_dst, page_size);
+ return;
+ }
+
+ /*
+ * fork() now: the child duplicates the swap entry, so the slot becomes
+ * non-exclusive. The child parks (keeping the reference) until the
+ * parent has faulted the page back in.
+ */
+ if (pipe(go))
+ err("pipe");
+ child = fork();
+ if (child < 0)
+ err("fork");
+ if (child == 0) {
+ close(go[1]);
+ read(go[0], &c, 1);
+ _exit(0);
+ }
+ close(go[0]);
+
+ if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &uargs))
+ err("uffd_poll_thread create");
+
+ /*
+ * Write the page: swaps it back in (do_swap_page) on a non-exclusive
+ * folio with FAULT_FLAG_WRITE. The marker must survive and deliver an
+ * RWP fault rather than COW silently.
+ */
+ *gopts->area_dst = 0x22;
+
+ if (write(gopts->pipefd[1], &c, sizeof(c)) != sizeof(c))
+ err("pipe write");
+ if (pthread_join(uffd_mon, NULL))
+ err("join failed");
+
+ if (uargs.minor_faults == 0)
+ uffd_test_fail("no RWP fault on swapped-in non-exclusive page");
+ else
+ uffd_test_pass();
+
+ close(pagemap_fd);
+ if (write(go[1], &c, 1) != 1)
+ err("child release");
+ close(go[1]);
+ waitpid(child, NULL, 0);
+}
+
+/*
+ * WP and RWP share the uffd-wp PTE bit and cannot coexist in the same VMA.
+ * Registration requesting both modes must be rejected.
+ */
+static void uffd_rwp_wp_exclusive_test(uffd_global_test_opts_t *gopts,
+ uffd_test_args_t *args)
+{
+ unsigned long nr_pages = gopts->nr_pages;
+ unsigned long page_size = gopts->page_size;
+ struct uffdio_register reg = { };
+
+ reg.range.start = (unsigned long)gopts->area_dst;
+ reg.range.len = nr_pages * page_size;
+ reg.mode = UFFDIO_REGISTER_MODE_WP | UFFDIO_REGISTER_MODE_RWP;
+
+ if (ioctl(gopts->uffd, UFFDIO_REGISTER, &reg) == 0) {
+ uffd_test_fail("register with WP|RWP unexpectedly succeeded");
+ return;
+ }
+ if (errno != EINVAL) {
+ uffd_test_fail("register with WP|RWP: expected EINVAL, got %d",
+ errno);
+ return;
+ }
+ uffd_test_pass();
+}
+
static sigjmp_buf jbuf, *sigbuf;
static void sighndl(int sig, siginfo_t *siginfo, void *ptr)
@@ -1605,6 +2428,83 @@ uffd_test_case_t uffd_tests[] = {
.uffd_feature_required = UFFD_FEATURE_MINOR_SHMEM,
},
{
+ .name = "rwp-async",
+ .uffd_fn = uffd_rwp_async_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-sync",
+ .uffd_fn = uffd_rwp_sync_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required = UFFD_FEATURE_RWP,
+ },
+ {
+ .name = "rwp-pagemap",
+ .uffd_fn = uffd_rwp_pagemap_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-mprotect",
+ .uffd_fn = uffd_rwp_mprotect_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-gup",
+ .uffd_fn = uffd_rwp_gup_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-async-toggle",
+ .uffd_fn = uffd_rwp_async_toggle_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC,
+ },
+ {
+ .name = "rwp-close",
+ .uffd_fn = uffd_rwp_close_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required = UFFD_FEATURE_RWP,
+ },
+ {
+ .name = "rwp-fork",
+ .uffd_fn = uffd_rwp_fork_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_EVENT_FORK,
+ },
+ {
+ .name = "rwp-fork-pin",
+ .uffd_fn = uffd_rwp_fork_pin_test,
+ .mem_targets = MEM_ANON,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC |
+ UFFD_FEATURE_EVENT_FORK,
+ },
+ {
+ .name = "rwp-swap-cow",
+ .uffd_fn = uffd_rwp_swap_cow_test,
+ .mem_targets = MEM_ANON,
+ .uffd_feature_required = UFFD_FEATURE_RWP,
+ },
+ {
+ .name = "rwp-wp-exclusive",
+ .uffd_fn = uffd_rwp_wp_exclusive_test,
+ .mem_targets = MEM_ALL,
+ .uffd_feature_required =
+ UFFD_FEATURE_RWP |
+ UFFD_FEATURE_PAGEFAULT_FLAG_WP |
+ UFFD_FEATURE_WP_HUGETLBFS_SHMEM,
+ },
+ {
.name = "sigbus",
.uffd_fn = uffd_sigbus_test,
.mem_targets = MEM_ALL,
@@ -1814,6 +2714,25 @@ int main(int argc, char *argv[])
uffd_test_skip(errmsg);
continue;
}
+ /*
+ * RWP tracks protection on ptes; a THP-backed shmem/anon
+ * range (e.g. shmem_enabled=always) would split on
+ * rwprotect and change behaviour under the test. Keep
+ * such ranges off THP. hugetlb is huge by definition and
+ * rejects MADV_NOHUGEPAGE, so skip it.
+ */
+ if ((test->uffd_feature_required & UFFD_FEATURE_RWP) &&
+ !(mem_type->mem_flag & (MEM_HUGETLB | MEM_HUGETLB_PRIVATE))) {
+ unsigned long len = gopts.nr_pages * gopts.page_size;
+
+ /*
+ * EINVAL means CONFIG_TRANSPARENT_HUGEPAGE=n:
+ * nothing to opt out of.
+ */
+ if (madvise(gopts.area_dst, len, MADV_NOHUGEPAGE) &&
+ errno != EINVAL)
+ err("madvise(MADV_NOHUGEPAGE)");
+ }
test->uffd_fn(&gopts, &args);
uffd_test_ctx_clear(&gopts);
}
diff --git a/tools/testing/selftests/mm/vm_util.c b/tools/testing/selftests/mm/vm_util.c
index 311fc5b4513e..ef1ea11981a7 100644
--- a/tools/testing/selftests/mm/vm_util.c
+++ b/tools/testing/selftests/mm/vm_util.c
@@ -719,7 +719,7 @@ int read_file(const char *path, char *buf, size_t buflen)
return (unsigned int) numread;
}
-void write_file(const char *path, const char *buf, size_t buflen)
+static void __write_file(const char *path, const char *buf, size_t buflen, bool ignore_einval)
{
int fd, saved_errno;
ssize_t numwritten;
@@ -735,14 +735,22 @@ void write_file(const char *path, const char *buf, size_t buflen)
saved_errno = errno;
close(fd);
errno = saved_errno;
- if (numwritten < 0)
+ if (numwritten < 0) {
+ if (ignore_einval && errno == EINVAL)
+ return;
ksft_exit_fail_msg("%s write(%.*s) failed: %s\n", path, (int)(buflen - 1),
buf, strerror(errno));
+ }
if (numwritten != buflen - 1)
ksft_exit_fail_msg("%s write(%.*s) is truncated, expected %zu bytes, got %zd bytes\n",
path, (int)(buflen - 1), buf, buflen - 1, numwritten);
}
+void write_file(const char *path, const char *buf, size_t buflen)
+{
+ __write_file(path, buf, buflen, /* ignore_einval = */ false);
+}
+
unsigned long read_num(const char *path)
{
char buf[21];
@@ -753,12 +761,22 @@ unsigned long read_num(const char *path)
return strtoul(buf, NULL, 10);
}
-void write_num(const char *path, unsigned long num)
+static void __write_num(const char *path, unsigned long num, bool ignore_einval)
{
char buf[21];
sprintf(buf, "%lu", num);
- write_file(path, buf, strlen(buf) + 1);
+ __write_file(path, buf, strlen(buf) + 1, ignore_einval);
+}
+
+void write_num(const char *path, unsigned long num)
+{
+ return __write_num(path, num, /* ignore_einval = */ false);
+}
+
+void write_num_ignore_einval(const char *path, unsigned long num)
+{
+ return __write_num(path, num, /* ignore_einval = */ true);
}
static unsigned long shmall, shmmax;
diff --git a/tools/testing/selftests/mm/vm_util.h b/tools/testing/selftests/mm/vm_util.h
index ea8fc8fdf0eb..7799154b67ee 100644
--- a/tools/testing/selftests/mm/vm_util.h
+++ b/tools/testing/selftests/mm/vm_util.h
@@ -168,6 +168,7 @@ void write_file(const char *path, const char *buf, size_t buflen);
int read_file(const char *path, char *buf, size_t buflen);
unsigned long read_num(const char *path);
void write_num(const char *path, unsigned long num);
+void write_num_ignore_einval(const char *path, unsigned long num);
void shm_limits_prepare(unsigned long length);
void __shm_limits_restore(void);
diff --git a/tools/testing/vma/Makefile b/tools/testing/vma/Makefile
index e72b45dedda5..ef6cc558afe1 100644
--- a/tools/testing/vma/Makefile
+++ b/tools/testing/vma/Makefile
@@ -10,7 +10,7 @@ OFILES = $(SHARED_OFILES) main.o shared.o maple-shim.o
TARGETS = vma
# These can be varied to test different sizes.
-CFLAGS += -DNUM_VMA_FLAG_BITS=128 -DNUM_MM_FLAG_BITS=128
+CFLAGS += -DNUM_VMA_FLAG_BITS=64 -DNUM_MM_FLAG_BITS=64
main.o: main.c shared.c shared.h vma_internal.h tests/merge.c tests/mmap.c tests/vma.c ../../../mm/vma.c ../../../mm/vma_init.c ../../../mm/vma_exec.c ../../../mm/vma.h include/custom.h include/dup.h include/stubs.h
diff --git a/tools/testing/vma/include/dup.h b/tools/testing/vma/include/dup.h
index bf26b3f48d3a..cdeb53bbdd1b 100644
--- a/tools/testing/vma/include/dup.h
+++ b/tools/testing/vma/include/dup.h
@@ -245,8 +245,10 @@ enum {
#define VM_STACK INIT_VM_FLAG(STACK)
#ifdef CONFIG_STACK_GROWS_UP
#define VM_STACK_EARLY INIT_VM_FLAG(STACK_EARLY)
+#define VMA_STACK_EARLY mk_vma_flags(VMA_STACK_EARLY_BIT)
#else
#define VM_STACK_EARLY VM_NONE
+#define VMA_STACK_EARLY EMPTY_VMA_FLAGS
#endif
#ifdef CONFIG_ARCH_HAS_PKEYS
#define VM_PKEY_SHIFT ((__force int)VMA_HIGH_ARCH_0_BIT)
@@ -315,6 +317,8 @@ enum {
/* Bits set in the VMA until the stack is in its final location */
#define VM_STACK_INCOMPLETE_SETUP (VM_RAND_READ | VM_SEQ_READ | VM_STACK_EARLY)
+#define VMA_STACK_INCOMPLETE_SETUP append_vma_flags( \
+ VMA_STACK_EARLY, VMA_RAND_READ_BIT, VMA_SEQ_READ_BIT)
#define TASK_EXEC_BIT ((current->personality & READ_IMPLIES_EXEC) ? \
VM_EXEC_BIT : VM_READ_BIT)
@@ -1163,6 +1167,11 @@ static inline struct vm_area_struct *vma_next(struct vma_iterator *vmi)
return mas_find(&vmi->mas, ULONG_MAX);
}
+static inline bool vma_is_attached(struct vm_area_struct *vma)
+{
+ return refcount_read(&vma->vm_refcnt);
+}
+
/*
* WARNING: to avoid racing with vma_mark_attached()/vma_mark_detached(), these
* assertions should be made either under mmap_write_lock or when the object
@@ -1170,12 +1179,12 @@ static inline struct vm_area_struct *vma_next(struct vma_iterator *vmi)
*/
static inline void vma_assert_attached(struct vm_area_struct *vma)
{
- WARN_ON_ONCE(!refcount_read(&vma->vm_refcnt));
+ WARN_ON_ONCE(!vma_is_attached(vma));
}
static inline void vma_assert_detached(struct vm_area_struct *vma)
{
- WARN_ON_ONCE(refcount_read(&vma->vm_refcnt));
+ WARN_ON_ONCE(vma_is_attached(vma));
}
static inline void vma_assert_write_locked(struct vm_area_struct *);
@@ -1301,6 +1310,16 @@ static inline unsigned long vma_pages(const struct vm_area_struct *vma)
return (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
}
+static inline pgoff_t vma_start_pgoff(const struct vm_area_struct *vma)
+{
+ return vma->vm_pgoff;
+}
+
+static inline pgoff_t vma_end_pgoff(const struct vm_area_struct *vma)
+{
+ return vma_start_pgoff(vma) + vma_pages(vma);
+}
+
static inline int vfs_mmap_prepare(struct file *file, struct vm_area_desc *desc)
{
return file->f_op->mmap_prepare(desc);
@@ -1532,9 +1551,36 @@ static inline int get_sysctl_max_map_count(void)
#define pgtable_supports_soft_dirty() IS_ENABLED(CONFIG_MEM_SOFT_DIRTY)
#endif
-static inline pgprot_t vma_get_page_prot(vma_flags_t vma_flags)
+static inline pgprot_t vma_flags_to_page_prot(vma_flags_t vma_flags)
{
const vm_flags_t vm_flags = vma_flags_to_legacy(vma_flags);
return vm_get_page_prot(vm_flags);
}
+
+static inline pgoff_t linear_page_delta(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ return (address - vma->vm_start) >> PAGE_SHIFT;
+}
+
+static inline pgoff_t linear_page_index(const struct vm_area_struct *vma,
+ const unsigned long address)
+{
+ pgoff_t pgoff;
+
+ pgoff = linear_page_delta(vma, address);
+ pgoff += vma_start_pgoff(vma);
+ return pgoff;
+}
+
+static inline void vma_assert_can_modify(struct vm_area_struct *vma)
+{
+ if (vma_is_attached(vma))
+ vma_assert_write_locked(vma);
+}
+
+static inline pgprot_t vma_get_page_prot(const struct vm_area_struct *vma)
+{
+ return vma_flags_to_page_prot(vma->flags);
+}
diff --git a/tools/testing/vma/include/stubs.h b/tools/testing/vma/include/stubs.h
index 64164e25658f..d6136e19a8af 100644
--- a/tools/testing/vma/include/stubs.h
+++ b/tools/testing/vma/include/stubs.h
@@ -257,13 +257,13 @@ static inline void vm_acct_memory(long pages)
{
}
-static inline void vma_interval_tree_insert(struct vm_area_struct *vma,
- struct rb_root_cached *rb)
+static inline void mapping_rmap_tree_insert(struct vm_area_struct *vma,
+ struct address_space *mapping)
{
}
-static inline void vma_interval_tree_remove(struct vm_area_struct *vma,
- struct rb_root_cached *rb)
+static inline void mapping_rmap_tree_remove(struct vm_area_struct *vma,
+ struct address_space *mapping)
{
}
@@ -271,13 +271,13 @@ static inline void flush_dcache_mmap_unlock(struct address_space *mapping)
{
}
-static inline void anon_vma_interval_tree_insert(struct anon_vma_chain *avc,
- struct rb_root_cached *rb)
+static inline void anon_rmap_tree_insert(struct anon_vma_chain *avc,
+ struct anon_vma *anon_vma)
{
}
-static inline void anon_vma_interval_tree_remove(struct anon_vma_chain *avc,
- struct rb_root_cached *rb)
+static inline void anon_rmap_tree_remove(struct anon_vma_chain *avc,
+ struct anon_vma *anon_vma)
{
}
diff --git a/tools/testing/vma/shared.c b/tools/testing/vma/shared.c
index 2565a5aecb80..bea9ea6db02a 100644
--- a/tools/testing/vma/shared.c
+++ b/tools/testing/vma/shared.c
@@ -120,12 +120,3 @@ unsigned long rlimit(unsigned int limit)
{
return (unsigned long)-1;
}
-
-void vma_set_range(struct vm_area_struct *vma,
- unsigned long start, unsigned long end,
- pgoff_t pgoff)
-{
- vma->vm_start = start;
- vma->vm_end = end;
- vma->vm_pgoff = pgoff;
-}
diff --git a/tools/testing/vma/shared.h b/tools/testing/vma/shared.h
index 8b9e3b11c3cb..97cd7a679dc1 100644
--- a/tools/testing/vma/shared.h
+++ b/tools/testing/vma/shared.h
@@ -21,19 +21,35 @@
} \
} while (0)
-#define ASSERT_TRUE(_expr) \
- do { \
- if (!(_expr)) { \
- fprintf(stderr, \
- "Assert FAILED at %s:%d:%s(): %s is FALSE.\n", \
- __FILE__, __LINE__, __FUNCTION__, #_expr); \
- return false; \
- } \
+#define __ASSERT_TRUE(_expr, _fmt, ...) \
+ do { \
+ if (!(_expr)) { \
+ fprintf(stderr, \
+ "Assert FAILED at %s:%d:%s(): %s is FALSE" \
+ _fmt ".\n", \
+ __FILE__, __LINE__, __FUNCTION__, #_expr \
+ __VA_OPT__(,) __VA_ARGS__); \
+ return false; \
+ } \
} while (0)
+#define __TO_SCALAR(x) ((unsigned long long)(uintptr_t)(x))
+
+#define ASSERT_TRUE(_expr) __ASSERT_TRUE(_expr, "")
#define ASSERT_FALSE(_expr) ASSERT_TRUE(!(_expr))
-#define ASSERT_EQ(_val1, _val2) ASSERT_TRUE((_val1) == (_val2))
-#define ASSERT_NE(_val1, _val2) ASSERT_TRUE((_val1) != (_val2))
+#define ASSERT_EQ(_val1, _val2) do { \
+ __typeof__(_val1) __val1 = (_val1); \
+ __typeof__(_val2) __val2 = (_val2); \
+ __ASSERT_TRUE(__val1 == __val2, " (0x%llx != 0x%llx)", \
+ __TO_SCALAR(__val1), __TO_SCALAR(__val2)); \
+ } while (0)
+
+#define ASSERT_NE(_val1, _val2) do { \
+ __typeof__(_val1) __val1 = (_val1); \
+ __typeof__(_val2) __val2 = (_val2); \
+ __ASSERT_TRUE(__val1 != __val2, " (0x%llx == 0x%llx)", \
+ __TO_SCALAR(__val1), __TO_SCALAR(__val2)); \
+ } while (0)
#define ASSERT_FLAGS_SAME_MASK(_flags, _flags_other) \
ASSERT_TRUE(vma_flags_same_mask((_flags), (_flags_other)))
@@ -53,8 +69,6 @@
#define ASSERT_FLAGS_NONEMPTY(_flags) \
ASSERT_FALSE(vma_flags_empty(_flags))
-#define IS_SET(_val, _flags) ((_val & _flags) == _flags)
-
extern bool fail_prealloc;
/* Override vma_iter_prealloc() so we can choose to fail it. */
@@ -125,8 +139,3 @@ void __vma_set_dummy_anon_vma(struct vm_area_struct *vma,
/* Provide a simple dummy VMA/anon_vma dummy setup for testing. */
void vma_set_dummy_anon_vma(struct vm_area_struct *vma,
struct anon_vma_chain *avc);
-
-/* Helper function to specify a VMA's range. */
-void vma_set_range(struct vm_area_struct *vma,
- unsigned long start, unsigned long end,
- pgoff_t pgoff);
diff --git a/tools/testing/vma/tests/merge.c b/tools/testing/vma/tests/merge.c
index 03b6f9820e0a..e357accc8499 100644
--- a/tools/testing/vma/tests/merge.c
+++ b/tools/testing/vma/tests/merge.c
@@ -118,7 +118,7 @@ static bool test_simple_merge(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_FLAGS_SAME_MASK(&vma->flags, vma_flags);
detach_free_vma(vma);
@@ -150,7 +150,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
- ASSERT_EQ(vma->vm_pgoff, 1);
+ ASSERT_EQ(vma_start_pgoff(vma), 1);
/*
* Now walk through the three split VMAs and make sure they are as
@@ -162,7 +162,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -171,7 +171,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x1000);
ASSERT_EQ(vma->vm_end, 0x2000);
- ASSERT_EQ(vma->vm_pgoff, 1);
+ ASSERT_EQ(vma_start_pgoff(vma), 1);
detach_free_vma(vma);
vma_iter_clear(&vmi);
@@ -180,7 +180,7 @@ static bool test_simple_modify(void)
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
- ASSERT_EQ(vma->vm_pgoff, 2);
+ ASSERT_EQ(vma_start_pgoff(vma), 2);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -209,7 +209,7 @@ static bool test_simple_expand(void)
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x3000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -227,11 +227,11 @@ static bool test_simple_shrink(void)
ASSERT_FALSE(attach_vma(&mm, vma));
- ASSERT_FALSE(vma_shrink(&vmi, vma, 0, 0x1000, 0));
+ ASSERT_FALSE(vma_shrink(&vmi, vma, 0x1000));
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x1000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
detach_free_vma(vma);
mtree_destroy(&mm.mm_mt);
@@ -324,7 +324,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_TRUE(merged);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x4000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -343,7 +343,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_TRUE(merged);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -364,7 +364,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_TRUE(merged);
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x9000);
- ASSERT_EQ(vma->vm_pgoff, 6);
+ ASSERT_EQ(vma_start_pgoff(vma), 6);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 3);
@@ -384,7 +384,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_TRUE(merged);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x9000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -404,7 +404,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_TRUE(merged);
ASSERT_EQ(vma->vm_start, 0xa000);
ASSERT_EQ(vma->vm_end, 0xc000);
- ASSERT_EQ(vma->vm_pgoff, 0xa);
+ ASSERT_EQ(vma_start_pgoff(vma), 0xa);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -423,7 +423,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_TRUE(merged);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -443,7 +443,7 @@ static bool __test_merge_new(bool is_sticky, bool a_is_sticky, bool b_is_sticky,
ASSERT_NE(vma, NULL);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0xc000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_EQ(vma->anon_vma, &dummy_anon_vma);
detach_free_vma(vma);
@@ -805,7 +805,7 @@ static bool test_vma_merge_new_with_close(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x5000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_EQ(vma->vm_ops, &vm_ops);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -865,7 +865,7 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vma_next->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x2000);
ASSERT_EQ(vma->vm_end, 0x3000);
- ASSERT_EQ(vma->vm_pgoff, 2);
+ ASSERT_EQ(vma_start_pgoff(vma), 2);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_TRUE(vma_write_started(vma_next));
ASSERT_EQ(mm.map_count, 2);
@@ -931,7 +931,7 @@ static bool __test_merge_existing(bool prev_is_sticky, bool middle_is_sticky, bo
ASSERT_EQ(vma_prev->anon_vma, &dummy_anon_vma);
ASSERT_EQ(vma->vm_start, 0x6000);
ASSERT_EQ(vma->vm_end, 0x7000);
- ASSERT_EQ(vma->vm_pgoff, 6);
+ ASSERT_EQ(vma_start_pgoff(vma), 6);
ASSERT_TRUE(vma_write_started(vma_prev));
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 2);
@@ -1416,7 +1416,7 @@ static bool test_merge_extend(void)
ASSERT_EQ(vma_merge_extend(&vmi, vma, 0x2000), vma);
ASSERT_EQ(vma->vm_start, 0);
ASSERT_EQ(vma->vm_end, 0x4000);
- ASSERT_EQ(vma->vm_pgoff, 0);
+ ASSERT_EQ(vma_start_pgoff(vma), 0);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(mm.map_count, 1);
@@ -1456,7 +1456,7 @@ static bool test_expand_only_mode(void)
ASSERT_EQ(vmg.state, VMA_MERGE_SUCCESS);
ASSERT_EQ(vma->vm_start, 0x3000);
ASSERT_EQ(vma->vm_end, 0x9000);
- ASSERT_EQ(vma->vm_pgoff, 3);
+ ASSERT_EQ(vma_start_pgoff(vma), 3);
ASSERT_TRUE(vma_write_started(vma));
ASSERT_EQ(vma_iter_addr(&vmi), 0x3000);
vma_assert_attached(vma);
diff --git a/tools/testing/vma/vma_internal.h b/tools/testing/vma/vma_internal.h
index e12ab2c80f95..4f6c5666ac07 100644
--- a/tools/testing/vma/vma_internal.h
+++ b/tools/testing/vma/vma_internal.h
@@ -14,8 +14,8 @@
#include <stdlib.h>
-#define CONFIG_MMU
-#define CONFIG_PER_VMA_LOCK
+#define CONFIG_MMU 1
+#define CONFIG_PER_VMA_LOCK 1
#ifdef __CONCAT
#undef __CONCAT