Add support for SVM atomics in Nouveau
Subsystems (19)
Alistair Popple
reviewerv9 cover Add support for SVM atomics in Nouveau
This is a repost of the previous series to rebase on v5.13-rc2 and to address comments.
Outside of some code comment updates the primary change was to split the renaming of migrate_pgmap_owner into a separate patch and to further simplify the handling of device exclusive entries in copy_pte_range(). This may result in temporary fork() failures if the process is using a device whilst forking, but such usage is unlikely to be practical.
This resulted in a new clean-up patch for the series (patch 6) so that device exclusive entries can be handled inside copy_nonpresent_pte(), although more extensive clean-ups of copy_pte_range() are planned as further development work in future.
Introduction
============
Some devices have features such as atomic PTE bits that can be used to implement atomic access to system memory. To support atomic operations to a shared virtual memory page such a device needs access to that page which is exclusive of the CPU. This series introduces a mechanism to temporarily unmap pages granting exclusive access to a device.
These changes are required to support OpenCL atomic operations in Nouveau to shared virtual memory (SVM) regions allocated with the CL_MEM_SVM_ATOMICS clSVMAlloc flag. A more complete description of the OpenCL SVM feature is available at
https://www.khronos.org/registry/OpenCL/specs/3.0-unified/html/
OpenCL_API.html#_shared_virtual_memory .
Implementation
==============
Exclusive device access is implemented by adding a new swap entry type (SWAP_DEVICE_EXCLUSIVE) which is similar to a migration entry. The main difference is that on fault the original entry is immediately restored by the fault handler instead of waiting.
Restoring the entry triggers calls to MMU notifers which allows a device driver to revoke the atomic access permission from the GPU prior to the CPU finalising the entry.
Patches
=======
Patches 1 & 2 refactor existing migration and device private entry functions.
Patches 3 & 4 rework try_to_unmap_one() by splitting out unrelated functionality into separate functions - try_to_migrate_one() and try_to_munlock_one().
Patch 5 renames some existing code but does not introduce functionality.
Patch 6 is a small clean-up to swap entry handling in copy_pte_range().
Patch 7 contains the bulk of the implementation for device exclusive memory.
Patch 8 contains some additions to the HMM selftests to ensure everything works as expected.
Patch 9 is a cleanup for the Nouveau SVM implementation.
Patch 10 contains the implementation of atomic access for the Nouveau driver.
Testing
=======
This has been tested with upstream Mesa 21.1.0 and a simple OpenCL program which checks that GPU atomic accesses to system memory are atomic. Without this series the test fails as there is no way of write-protecting the page mapping which results in the device clobbering CPU writes. For reference the test is available at https://ozlabs.org/~apopple/opencl_svm_atomics/
Further testing has been performed by adding support for testing exclusive access to the hmm-tests kselftests.
Alistair Popple (10):
mm: Remove special swap entry functions
mm/swapops: Rework swap entry manipulation code
mm/rmap: Split try_to_munlock from try_to_unmap
mm/rmap: Split migration into its own function
mm: Rename migrate_pgmap_owner
mm/memory.c: Allow different return codes for copy_nonpresent_pte()
mm: Device exclusive memory access
mm: Selftests for exclusive device memory
nouveau/svm: Refactor nouveau_range_fault
nouveau/svm: Implement atomic SVM access
Documentation/vm/hmm.rst | 19 +- Documentation/vm/unevictable-lru.rst | 33 +- arch/s390/mm/pgtable.c | 2 +- drivers/gpu/drm/nouveau/include/nvif/if000c.h | 1 + drivers/gpu/drm/nouveau/nouveau_svm.c | 156 ++++- drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmm.h | 1 + .../drm/nouveau/nvkm/subdev/mmu/vmmgp100.c | 6 + fs/proc/task_mmu.c | 23 +- include/linux/mmu_notifier.h | 26 +- include/linux/rmap.h | 11 +- include/linux/swap.h | 11 +- include/linux/swapops.h | 123 ++-- lib/test_hmm.c | 126 +++- lib/test_hmm_uapi.h | 2 + mm/debug_vm_pgtable.c | 12 +- mm/hmm.c | 12 +- mm/huge_memory.c | 45 +- mm/hugetlb.c | 10 +- mm/memcontrol.c | 2 +- mm/memory.c | 160 ++++- mm/migrate.c | 51 +- mm/mlock.c | 10 +- mm/mprotect.c | 18 +- mm/page_vma_mapped.c | 15 +- mm/rmap.c | 601 +++++++++++++++--- tools/testing/selftests/vm/hmm-tests.c | 158 +++++ 26 files changed, 1317 insertions(+), 317 deletions(-)
Signature · 1 line
2.20.1
Alistair Popple
reviewerPATCH v9 1/10 mm: Remove special swap entry functions
Remove multiple similar inline functions for dealing with different types of special swap entries.
Both migration and device private swap entries use the swap offset to store a pfn. Instead of multiple inline functions to obtain a struct page for each swap entry type use a common function pfn_swap_entry_to_page(). Also open-code the various entry_to_pfn() functions as this results is shorter code that is easier to understand.
---
v9:
* Rebased on v5.13-rc2
v8:
* No changes
v7:
* Reworded commit message to include pfn_swap_entry_to_page()
* Added Christoph's Reviewed-by
v6:
* Removed redundant compound_page() call from inside PageLocked()
* Fixed a minor build issue for s390 reported by kernel test bot
v4:
* Added pfn_swap_entry_to_page()
* Reinstated check that migration entries point to locked pages
* Removed #define swapcache_prepare which isn't needed for CONFIG_SWAP=0 builds
--- arch/s390/mm/pgtable.c | 2 +- fs/proc/task_mmu.c | 23 +++++--------- include/linux/swap.h | 4 +-- include/linux/swapops.h | 69 ++++++++++++++--------------------------- mm/hmm.c | 5 ++- mm/huge_memory.c | 4 +-- mm/memcontrol.c | 2 +- mm/memory.c | 10 +++--- mm/migrate.c | 6 ++-- mm/page_vma_mapped.c | 6 ++-- 10 files changed, 50 insertions(+), 81 deletions(-)
arch/s390/mm/pgtable.c +1 -1
@@ -691,7 +691,7 @@ static void ptep_zap_swap_entry(struct mm_struct *mm, swp_entry_t entry)if (!non_swap_entry (entry))dec_mm_counter (mm,MM_SWAPENTS );else if (is_migration_entry (entry)) { -struct page *page =migration_entry_to_page (entry); +struct page *page =pfn_swap_entry_to_page (entry);dec_mm_counter (mm,mm_counter (page)); }
fs/proc/task_mmu.c +8 -15
@@ -514,10 +514,8 @@ static void smaps_pte_entry(pte_t *pte, unsigned long addr, }else { mss->swap_pss += (u64 )PAGE_SIZE <<PSS_SHIFT ; } - }else if (is_migration_entry (swpent)) - page =migration_entry_to_page (swpent); -else if (is_device_private_entry (swpent)) - page =device_private_entry_to_page (swpent); + }else if (is_pfn_swap_entry (swpent)) + page =pfn_swap_entry_to_page (swpent); }else if (unlikely (IS_ENABLED (CONFIG_SHMEM ) && mss->check_shmem_swap &&pte_none (*pte))) { page = xa_load(&vma->vm_file->f_mapping->i_pages,
@@ -549,7 +547,7 @@ static void smaps_pmd_entry(pmd_t *pmd, unsigned long addr,swp_entry_t entry =pmd_to_swp_entry (*pmd);if (is_migration_entry (entry)) - page =migration_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry); }if (IS_ERR_OR_NULL (page))return ;
@@ -694,10 +692,8 @@ static int smaps_hugetlb_range(pte_t *pte, unsigned long hmask, }else if (is_swap_pte (*pte)) {swp_entry_t swpent =pte_to_swp_entry (*pte); -if (is_migration_entry (swpent)) - page =migration_entry_to_page (swpent); -else if (is_device_private_entry (swpent)) - page =device_private_entry_to_page (swpent); +if (is_pfn_swap_entry (swpent)) + page =pfn_swap_entry_to_page (swpent); }if (page) {int mapcount =page_mapcount (page);
@@ -1384,11 +1380,8 @@ static pagemap_entry_t pte_to_pagemap_entry(struct pagemapread *pm, frame =swp_type (entry) | (swp_offset (entry) <<MAX_SWAPFILES_SHIFT ); flags |=PM_SWAP ; -if (is_migration_entry (entry)) - page =migration_entry_to_page (entry); - -if (is_device_private_entry (entry)) - page =device_private_entry_to_page (entry); +if (is_pfn_swap_entry (entry)) + page =pfn_swap_entry_to_page (entry); }if (page && !PageAnon (page))
@@ -1445,7 +1438,7 @@ static int pagemap_pmd_range(pmd_t *pmdp, unsigned long addr, unsigned long end,if (pmd_swp_soft_dirty (pmd)) flags |=PM_SOFT_DIRTY ;VM_BUG_ON (!is_pmd_migration_entry (pmd)); - page = migration_entry_to_page(entry); + page = pfn_swap_entry_to_page(entry); }#endif
include/linux/swap.h +2 -2
@@ -545,8 +545,8 @@ static inline void show_swap_cache_info(void) { } -#define free_swap_and_cache (e) ({(is_migration_entry(e) || is_device_private_entry(e));}) -#define swapcache_prepare (e) ({(is_migration_entry(e) || is_device_private_entry(e));}) +/* used to sanity check ptes in zap_pte_range when CONFIG_SWAP=0 */ +#define free_swap_and_cache (e) is_pfn_swap_entry(e)static inline int add_swap_count_continuation (swp_entry_t swp,gfp_t gfp_mask) {
include/linux/swapops.h +23 -46
99 lines not on this page · raw message
mm/hmm.c +2 -3
@@ -214,7 +214,7 @@ static inline bool hmm_is_device_private_entry(struct hmm_range *range, swp_entry_t entry) {return is_device_private_entry (entry) && -device_private_entry_to_page (entry)->pgmap->owner == +pfn_swap_entry_to_page (entry)->pgmap->owner == range->dev_private_owner; }
@@ -257,8 +257,7 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr, cpu_flags =HMM_PFN_VALID ;if (is_write_device_private_entry (entry)) cpu_flags |=HMM_PFN_WRITE ; - *hmm_pfn =device_private_entry_to_pfn (entry) | - cpu_flags; + *hmm_pfn =swp_offset (entry) | cpu_flags;return 0 ; }
mm/huge_memory.c +2 -2
@@ -1694,7 +1694,7 @@ int zap_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,VM_BUG_ON (!is_pmd_migration_entry (orig_pmd)); entry =pmd_to_swp_entry (orig_pmd); - page =migration_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry); flush_needed =0 ; }else WARN_ONCE (1 ,"Non present huge pmd without pmd migration enabled!" );
@@ -2102,7 +2102,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,swp_entry_t entry; entry =pmd_to_swp_entry (old_pmd); - page =migration_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry); write =is_write_migration_entry (entry); young = false; soft_dirty =pmd_swp_soft_dirty (old_pmd);
mm/memcontrol.c +1 -1
@@ -5349,7 +5349,7 @@ static struct page *mc_handle_swap_pte(struct vm_area_struct *vma,* as special swap entry in the CPU page table. */ if (is_device_private_entry (ent)) { - page =device_private_entry_to_page (ent); + page =pfn_swap_entry_to_page (ent);/* * MEMORY_DEVICE_PRIVATE means ZONE_DEVICE page and which have * a refcount of 1 when free (unlike normal page)
mm/memory.c +5 -5
@@ -730,7 +730,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, } rss[MM_SWAPENTS ]++; }else if (is_migration_entry (entry)) { - page =migration_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry); rss[mm_counter (page)]++;
@@ -749,7 +749,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,set_pte_at (src_mm, addr, src_pte, pte); } }else if (is_device_private_entry (entry)) { - page =device_private_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry);/* * Update rss count even for unaddressable pages, as
@@ -1280,7 +1280,7 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb, entry =pte_to_swp_entry (ptent);if (is_device_private_entry (entry)) { -struct page *page =device_private_entry_to_page (entry); +struct page *page =pfn_swap_entry_to_page (entry);if (unlikely (details && details->check_mapping)) {/*
@@ -1309,7 +1309,7 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,else if (is_migration_entry (entry)) {struct page *page; - page =migration_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry); rss[mm_counter (page)]--; }if (unlikely (!free_swap_and_cache (entry)))
@@ -3327,7 +3327,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)migration_entry_wait (vma->vm_mm, vmf->pmd, vmf->address); }else if (is_device_private_entry (entry)) { - vmf->page =device_private_entry_to_page (entry); + vmf->page =pfn_swap_entry_to_page (entry); ret = vmf->page->pgmap->ops->migrate_to_ram (vmf); }else if (is_hwpoison_entry (entry)) { ret =VM_FAULT_HWPOISON ;
mm/migrate.c +3 -3
@@ -294,7 +294,7 @@ void __migration_entry_wait(struct mm_struct *mm, pte_t *ptep,if (!is_migration_entry (entry)) goto out; - page =migration_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry);/* * Once page cache replacement of page migration started, page_count
@@ -334,7 +334,7 @@ void pmd_migration_entry_wait(struct mm_struct *mm, pmd_t *pmd) ptl =pmd_lock (mm, pmd);if (!is_pmd_migration_entry (*pmd)) goto unlock; - page =migration_entry_to_page (pmd_to_swp_entry (*pmd)); + page =pfn_swap_entry_to_page (pmd_to_swp_entry (*pmd));if (!get_page_unless_zero (page)) goto unlock;spin_unlock (ptl);
@@ -2399,7 +2399,7 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,if (!is_device_private_entry (entry)) goto next; - page =device_private_entry_to_page (entry); + page =pfn_swap_entry_to_page (entry);if (!(migrate->flags &MIGRATE_VMA_SELECT_DEVICE_PRIVATE ) || page->pgmap->owner != migrate->pgmap_owner)
mm/page_vma_mapped.c +3 -3
@@ -96,7 +96,7 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)if (!is_migration_entry (entry))return false; - pfn =migration_entry_to_pfn (entry); + pfn =swp_offset (entry); }else if (is_swap_pte (*pvmw->pte)) {swp_entry_t entry;
@@ -105,7 +105,7 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)if (!is_device_private_entry (entry))return false; - pfn =device_private_entry_to_pfn (entry); + pfn =swp_offset (entry); }else {if (!pte_present (*pvmw->pte))return false;
@@ -200,7 +200,7 @@ bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)if (is_migration_entry (pmd_to_swp_entry (*pvmw->pmd))) {swp_entry_t entry =pmd_to_swp_entry (*pvmw->pmd); -if (migration_entry_to_page (entry) != page) +if (pfn_swap_entry_to_page (entry) != page)return not_found (pvmw);return true; }
Signature · 1 line
2.20.1
Alistair Popple
reviewerPATCH v9 2/10 mm/swapops: Rework swap entry manipulation code
In reply to Alistair PoppleBoth migration and device private pages use special swap entries that are manipluated by a range of inline functions. The arguments to these are somewhat inconsitent so rework them to remove flag type arguments and to make the arguments similar for both read and write entry creation.
--- include/linux/swapops.h | 56 ++++++++++++++++++++++------------------- mm/debug_vm_pgtable.c | 12 ++++----- mm/hmm.c | 2 +- mm/huge_memory.c | 26 +++++++++++++------ mm/hugetlb.c | 10 +++++--- mm/memory.c | 10 +++++--- mm/migrate.c | 26 ++++++++++++++----- mm/mprotect.c | 10 +++++--- mm/rmap.c | 10 +++++--- 9 files changed, 100 insertions(+), 62 deletions(-)
include/linux/swapops.h +30 -26
@@ -100,35 +100,35 @@ static inline void *swp_to_radix_entry(swp_entry_t entry) }#if IS_ENABLED (CONFIG_DEVICE_PRIVATE ) -static inline swp_entry_t make_device_private_entry (struct page *page,bool write) +static inline swp_entry_t make_readable_device_private_entry (pgoff_t offset) { -return swp_entry (write ?SWP_DEVICE_WRITE :SWP_DEVICE_READ , -page_to_pfn (page)); +return swp_entry (SWP_DEVICE_READ , offset); } -static inline bool is_device_private_entry (swp_entry_t entry) +static inline swp_entry_t make_writable_device_private_entry (pgoff_t offset) { -int type =swp_type (entry); -return type ==SWP_DEVICE_READ || type ==SWP_DEVICE_WRITE ; +return swp_entry (SWP_DEVICE_WRITE , offset); } -static inline void make_device_private_entry_read (swp_entry_t *entry) +static inline bool is_device_private_entry (swp_entry_t entry) { - *entry =swp_entry (SWP_DEVICE_READ ,swp_offset (*entry)); +int type =swp_type (entry); +return type ==SWP_DEVICE_READ || type ==SWP_DEVICE_WRITE ; } -static inline bool is_write_device_private_entry (swp_entry_t entry) +static inline bool is_writable_device_private_entry (swp_entry_t entry) {return unlikely (swp_type (entry) ==SWP_DEVICE_WRITE ); }#else /* CONFIG_DEVICE_PRIVATE */ -static inline swp_entry_t make_device_private_entry (struct page *page,bool write) +static inline swp_entry_t make_readable_device_private_entry (pgoff_t offset) {return swp_entry (0 ,0 ); } -static inline void make_device_private_entry_read (swp_entry_t *entry) +static inline swp_entry_t make_writable_device_private_entry (pgoff_t offset) { +return swp_entry (0 ,0 ); }static inline bool is_device_private_entry (swp_entry_t entry)
@@ -136,35 +136,32 @@ static inline bool is_device_private_entry(swp_entry_t entry)return false; } -static inline bool is_write_device_private_entry (swp_entry_t entry) +static inline bool is_writable_device_private_entry (swp_entry_t entry) {return false; }#endif /* CONFIG_DEVICE_PRIVATE */ #ifdef CONFIG_MIGRATION -static inline swp_entry_t make_migration_entry (struct page *page,int write) -{ -BUG_ON (!PageLocked (compound_head (page))); - -return swp_entry (write ?SWP_MIGRATION_WRITE :SWP_MIGRATION_READ , -page_to_pfn (page)); -} -static inline int is_migration_entry (swp_entry_t entry) {return unlikely (swp_type (entry) ==SWP_MIGRATION_READ ||swp_type (entry) ==SWP_MIGRATION_WRITE ); } -static inline int is_write_migration_entry (swp_entry_t entry) +static inline int is_writable_migration_entry (swp_entry_t entry) {return unlikely (swp_type (entry) ==SWP_MIGRATION_WRITE ); } -static inline void make_migration_entry_read (swp_entry_t *entry) +static inline swp_entry_t make_readable_migration_entry (pgoff_t offset) { - *entry =swp_entry (SWP_MIGRATION_READ ,swp_offset (*entry)); +return swp_entry (SWP_MIGRATION_READ , offset); +} + +static inline swp_entry_t make_writable_migration_entry (pgoff_t offset) +{ +return swp_entry (SWP_MIGRATION_WRITE , offset); } extern void __migration_entry_wait(struct mm_struct *mm, pte_t *ptep,
@@ -174,21 +171,28 @@ extern void migration_entry_wait(struct mm_struct *mm, pmd_t *pmd,extern void migration_entry_wait_huge (struct vm_area_struct *vma,struct mm_struct *mm,pte_t *pte);#else +static inline swp_entry_t make_readable_migration_entry (pgoff_t offset) +{ +return swp_entry (0 ,0 ); +} + +static inline swp_entry_t make_writable_migration_entry (pgoff_t offset) +{ +return swp_entry (0 ,0 ); +} -#define make_migration_entry (page, write) swp_entry(0, 0)static inline int is_migration_entry (swp_entry_t swp) {return 0 ; } -static inline void make_migration_entry_read (swp_entry_t *entryp) { }static inline void __migration_entry_wait (struct mm_struct *mm,pte_t *ptep,spinlock_t *ptl) { }static inline void migration_entry_wait (struct mm_struct *mm,pmd_t *pmd,unsigned long address) { }static inline void migration_entry_wait_huge (struct vm_area_struct *vma,struct mm_struct *mm,pte_t *pte) { } -static inline int is_write_migration_entry (swp_entry_t entry) +static inline int is_writable_migration_entry (swp_entry_t entry) {return 0 ; }
mm/debug_vm_pgtable.c +6 -6
@@ -817,17 +817,17 @@ static void __init swap_migration_tests(void)* locked, otherwise it stumbles upon a BUG_ON(). */ __SetPageLocked (page); - swp =make_migration_entry (page,1 ); + swp =make_writable_migration_entry (page_to_pfn (page));WARN_ON (!is_migration_entry (swp)); -WARN_ON (!is_write_migration_entry (swp)); +WARN_ON (!is_writable_migration_entry (swp)); -make_migration_entry_read (&swp); + swp =make_readable_migration_entry (swp_offset (swp));WARN_ON (!is_migration_entry (swp)); -WARN_ON (is_write_migration_entry (swp)); +WARN_ON (is_writable_migration_entry (swp)); - swp =make_migration_entry (page,0 ); + swp =make_readable_migration_entry (page_to_pfn (page));WARN_ON (!is_migration_entry (swp)); -WARN_ON (is_write_migration_entry (swp)); +WARN_ON (is_writable_migration_entry (swp));__ClearPageLocked (page);__free_page (page); }
mm/hmm.c +1 -1
@@ -255,7 +255,7 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,*/ if (hmm_is_device_private_entry (range, entry)) { cpu_flags =HMM_PFN_VALID ; -if (is_write_device_private_entry (entry)) +if (is_writable_device_private_entry (entry)) cpu_flags |=HMM_PFN_WRITE ; *hmm_pfn =swp_offset (entry) | cpu_flags;return 0 ;
mm/huge_memory.c +18 -8
@@ -1050,8 +1050,9 @@ int copy_huge_pmd(struct mm_struct *dst_mm, struct mm_struct *src_mm,swp_entry_t entry =pmd_to_swp_entry (pmd);VM_BUG_ON (!is_pmd_migration_entry (pmd)); -if (is_write_migration_entry (entry)) { -make_migration_entry_read (&entry); +if (is_writable_migration_entry (entry)) { + entry =make_readable_migration_entry ( +swp_offset (entry)); pmd =swp_entry_to_pmd (entry);if (pmd_swp_soft_dirty (*src_pmd)) pmd =pmd_swp_mksoft_dirty (pmd);
@@ -1819,13 +1820,14 @@ int change_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd,swp_entry_t entry =pmd_to_swp_entry (*pmd);VM_BUG_ON (!is_pmd_migration_entry (*pmd)); -if (is_write_migration_entry (entry)) { +if (is_writable_migration_entry (entry)) {pmd_t newpmd;/* * A protection check is difficult so * just be safe and disable write */ -make_migration_entry_read (&entry); + entry =make_readable_migration_entry ( +swp_offset (entry)); newpmd =swp_entry_to_pmd (entry);if (pmd_swp_soft_dirty (*pmd)) newpmd =pmd_swp_mksoft_dirty (newpmd);
@@ -2103,7 +2105,7 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd, entry =pmd_to_swp_entry (old_pmd); page =pfn_swap_entry_to_page (entry); - write =is_write_migration_entry (entry); + write =is_writable_migration_entry (entry); young = false; soft_dirty =pmd_swp_soft_dirty (old_pmd); uffd_wp =pmd_swp_uffd_wp (old_pmd);
@@ -2135,7 +2137,12 @@ static void __split_huge_pmd_locked(struct vm_area_struct *vma, pmd_t *pmd,*/ if (freeze || pmd_migration) {swp_entry_t swp_entry; - swp_entry =make_migration_entry (page + i, write); +if (write) + swp_entry =make_writable_migration_entry ( +page_to_pfn (page + i)); +else + swp_entry =make_readable_migration_entry ( +page_to_pfn (page + i)); entry =swp_entry_to_pte (swp_entry);if (soft_dirty) entry =pte_swp_mksoft_dirty (entry);
@@ -3212,7 +3219,10 @@ void set_pmd_migration_entry(struct page_vma_mapped_walk *pvmw, pmdval =pmdp_invalidate (vma, address, pvmw->pmd);if (pmd_dirty (pmdval))set_page_dirty (page); - entry =make_migration_entry (page,pmd_write (pmdval)); +if (pmd_write (pmdval)) + entry =make_writable_migration_entry (page_to_pfn (page)); +else + entry =make_readable_migration_entry (page_to_pfn (page)); pmdswp =swp_entry_to_pmd (entry);if (pmd_soft_dirty (pmdval)) pmdswp =pmd_swp_mksoft_dirty (pmdswp);
@@ -3238,7 +3248,7 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new) pmde =pmd_mkold (mk_huge_pmd (new, vma->vm_page_prot));if (pmd_swp_soft_dirty (*pvmw->pmd)) pmde =pmd_mksoft_dirty (pmde); -if (is_write_migration_entry (entry)) +if (is_writable_migration_entry (entry)) pmde =maybe_pmd_mkwrite (pmde, vma);flush_cache_range (vma, mmun_start, mmun_start +HPAGE_PMD_SIZE );
mm/hugetlb.c +6 -4
@@ -3989,12 +3989,13 @@ int copy_hugetlb_page_range(struct mm_struct *dst, struct mm_struct *src, is_hugetlb_entry_hwpoisoned(entry))) {swp_entry_t swp_entry =pte_to_swp_entry (entry); -if (is_write_migration_entry (swp_entry) && cow) { +if (is_writable_migration_entry (swp_entry) && cow) {/* * COW mappings require pages in both * parent and child to be set to read. */ -make_migration_entry_read (&swp_entry); + swp_entry =make_readable_migration_entry ( +swp_offset (swp_entry)); entry =swp_entry_to_pte (swp_entry);set_huge_swap_pte_at (src, addr, src_pte, entry, sz);
@@ -5237,10 +5238,11 @@ unsigned long hugetlb_change_protection(struct vm_area_struct *vma,if (unlikely (is_hugetlb_entry_migration (pte))) {swp_entry_t entry =pte_to_swp_entry (pte); -if (is_write_migration_entry (entry)) { +if (is_writable_migration_entry (entry)) {pte_t newpte; -make_migration_entry_read (&entry); + entry =make_readable_migration_entry ( +swp_offset (entry)); newpte =swp_entry_to_pte (entry);set_huge_swap_pte_at (mm, address, ptep, newpte,huge_page_size (h));
mm/memory.c +6 -4
@@ -734,13 +734,14 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, rss[mm_counter (page)]++; -if (is_write_migration_entry (entry) && +if (is_writable_migration_entry (entry) &&is_cow_mapping (vm_flags)) {/* * COW mappings require pages in both * parent and child to be set to read. */ -make_migration_entry_read (&entry); + entry =make_readable_migration_entry ( +swp_offset (entry)); pte =swp_entry_to_pte (entry);if (pte_swp_soft_dirty (*src_pte)) pte =pte_swp_mksoft_dirty (pte);
@@ -771,9 +772,10 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,* when a device driver is involved (you cannot easily * save and restore device driver state). */ -if (is_write_device_private_entry (entry) && +if (is_writable_device_private_entry (entry) &&is_cow_mapping (vm_flags)) { -make_device_private_entry_read (&entry); + entry =make_readable_device_private_entry ( +swp_offset (entry)); pte =swp_entry_to_pte (entry);if (pte_swp_uffd_wp (*src_pte)) pte =pte_swp_mkuffd_wp (pte);
mm/migrate.c +20 -6
@@ -210,13 +210,18 @@ static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,* Recheck VMA as permissions can change since migration started */ entry =pte_to_swp_entry (*pvmw.pte); -if (is_write_migration_entry (entry)) +if (is_writable_migration_entry (entry)) pte =maybe_mkwrite (pte, vma);else if (pte_swp_uffd_wp (*pvmw.pte)) pte =pte_mkuffd_wp (pte);if (unlikely (is_device_private_page (new))) { - entry =make_device_private_entry (new,pte_write (pte)); +if (pte_write (pte)) + entry =make_writable_device_private_entry ( +page_to_pfn (new)); +else + entry =make_readable_device_private_entry ( +page_to_pfn (new)); pte =swp_entry_to_pte (entry);if (pte_swp_soft_dirty (*pvmw.pte)) pte =pte_swp_mksoft_dirty (pte);
@@ -2407,7 +2412,7 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp, mpfn =migrate_pfn (page_to_pfn (page)) |MIGRATE_PFN_MIGRATE ; -if (is_write_device_private_entry (entry)) +if (is_writable_device_private_entry (entry)) mpfn |=MIGRATE_PFN_WRITE ; }else {if (!(migrate->flags &MIGRATE_VMA_SELECT_SYSTEM ))
@@ -2453,8 +2458,12 @@ static int migrate_vma_collect_pmd(pmd_t *pmdp,ptep_get_and_clear (mm, addr, ptep);/* Setup special migration page table entry */ - entry =make_migration_entry (page, mpfn & -MIGRATE_PFN_WRITE ); +if (mpfn &MIGRATE_PFN_WRITE ) + entry =make_writable_migration_entry ( +page_to_pfn (page)); +else + entry =make_readable_migration_entry ( +page_to_pfn (page)); swp_pte =swp_entry_to_pte (entry);if (pte_present (pte)) {if (pte_soft_dirty (pte))
@@ -2927,7 +2936,12 @@ static void migrate_vma_insert_page(struct migrate_vma *migrate,if (is_device_private_page (page)) {swp_entry_t swp_entry; - swp_entry =make_device_private_entry (page, vma->vm_flags &VM_WRITE ); +if (vma->vm_flags &VM_WRITE ) + swp_entry =make_writable_device_private_entry ( +page_to_pfn (page)); +else + swp_entry =make_readable_device_private_entry ( +page_to_pfn (page)); entry =swp_entry_to_pte (swp_entry); }else {/*
mm/mprotect.c +6 -4
@@ -143,23 +143,25 @@ static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd,swp_entry_t entry =pte_to_swp_entry (oldpte);pte_t newpte; -if (is_write_migration_entry (entry)) { +if (is_writable_migration_entry (entry)) {/* * A protection check is difficult so * just be safe and disable write */ -make_migration_entry_read (&entry); + entry =make_readable_migration_entry ( +swp_offset (entry)); newpte =swp_entry_to_pte (entry);if (pte_swp_soft_dirty (oldpte)) newpte =pte_swp_mksoft_dirty (newpte);if (pte_swp_uffd_wp (oldpte)) newpte =pte_swp_mkuffd_wp (newpte); - }else if (is_write_device_private_entry (entry)) { + }else if (is_writable_device_private_entry (entry)) {/* * We do not preserve soft-dirtiness. See * copy_one_pte() for explanation. */ -make_device_private_entry_read (&entry); + entry =make_readable_device_private_entry ( +swp_offset (entry)); newpte =swp_entry_to_pte (entry);if (pte_swp_uffd_wp (oldpte)) newpte =pte_swp_mkuffd_wp (newpte);
mm/rmap.c +7 -3
@@ -1526,7 +1526,7 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,* pte. do_swap_page() will wait until the migration * pte is removed and then restart fault handling. */ - entry =make_migration_entry (page,0 ); + entry =make_readable_migration_entry (page_to_pfn (page)); swp_pte =swp_entry_to_pte (entry);/*
@@ -1622,8 +1622,12 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,* pte. do_swap_page() will wait until the migration * pte is removed and then restart fault handling. */ - entry =make_migration_entry (subpage, -pte_write (pteval)); +if (pte_write (pteval)) + entry =make_writable_migration_entry ( +page_to_pfn (subpage)); +else + entry =make_readable_migration_entry ( +page_to_pfn (subpage)); swp_pte =swp_entry_to_pte (entry);if (pte_soft_dirty (pteval)) swp_pte =pte_swp_mksoft_dirty (swp_pte);
Signature · 1 line
2.20.1
Alistair Popple
reviewerPATCH v9 3/10 mm/rmap: Split try_to_munlock from try_to_unmap
In reply to Alistair PoppleThe behaviour of try_to_unmap_one() is difficult to follow because it performs different operations based on a fairly large set of flags used in different combinations.
TTU_MUNLOCK is one such flag. However it is exclusively used by try_to_munlock() which specifies no other flags. Therefore rather than overload try_to_unmap_one() with unrelated behaviour split this out into it's own function and remove the flag.
---
v9:
* Improved comments
v8:
* Renamed try_to_munlock to page_mlock to better reflect what the function actually does.
* Removed the TODO from the documentation that this patch addresses.
v7:
* Added Christoph's Reviewed-by
v4:
* Removed redundant check for VM_LOCKED
--- Documentation/vm/unevictable-lru.rst | 33 ++++++--------- include/linux/rmap.h | 3 +- mm/mlock.c | 10 ++--- mm/rmap.c | 61 ++++++++++++++++++++-------- 4 files changed, 63 insertions(+), 44 deletions(-)
Documentation/vm/unevictable-lru.rst +13 -20
@@ -389,14 +389,14 @@ mlocked, munlock_vma_page() updates that zone statistics for the number of mlocked pages. Note, however, that at this point we haven't checked whether the page is mapped by other VM_LOCKED VMAs. -We can't call try_to_munlock(), the function that walks the reverse map to +We can't call page_mlock(), the function that walks the reverse map to check for other VM_LOCKED VMAs, without first isolating the page from the LRU. -try_to_munlock() is a variant of try_to_unmap() and thus requires that the page +page_mlock() is a variant of try_to_unmap() and thus requires that the page not be on an LRU list [more on these below]. However, the call to -isolate_lru_page() could fail, in which case we couldn't try_to_munlock(). So, +isolate_lru_page() could fail, in which case we can't call page_mlock(). So, we go ahead and clear PG_mlocked up front, as this might be the only chance we -have. If we can successfully isolate the page, we go ahead and -try_to_munlock(), which will restore the PG_mlocked flag and update the zone +have. If we can successfully isolate the page, we go ahead and call +page_mlock(), which will restore the PG_mlocked flag and update the zone page statistics if it finds another VMA holding the page mlocked. If we fail to isolate the page, we'll have left a potentially mlocked page on the LRU. This is fine, because we'll catch it later if and if vmscan tries to reclaim
@@ -545,31 +545,24 @@ munlock or munmap system calls, mm teardown (munlock_vma_pages_all), reclaim, holepunching, and truncation of file pages and their anonymous COWed pages. -try_to_munlock() Reverse Map Scan +page_mlock() Reverse Map Scan --------------------------------- -.. warning:: - [!] TODO/FIXME: a better name might be page_mlocked() - analogous to the - page_referenced() reverse map walker. - When munlock_vma_page() [see section :ref:`munlock()/munlockall() System Call Handling <munlock_munlockall_handling>` above] tries to munlock a page, it needs to determine whether or not the page is mapped by any VM_LOCKED VMA without actually attempting to unmap all PTEs from the page. For this purpose, the unevictable/mlock infrastructure -introduced a variant of try_to_unmap() called try_to_munlock(). +introduced a variant of try_to_unmap() called page_mlock(). -try_to_munlock() calls the same functions as try_to_unmap() for anonymous and -mapped file and KSM pages with a flag argument specifying unlock versus unmap -processing. Again, these functions walk the respective reverse maps looking -for VM_LOCKED VMAs. When such a VMA is found, as in the try_to_unmap() case, -the functions mlock the page via mlock_vma_page() and return SWAP_MLOCK. This -undoes the pre-clearing of the page's PG_mlocked done by munlock_vma_page. +page_mlock() walks the respective reverse maps looking for VM_LOCKED VMAs. When +such a VMA is found the page is mlocked via mlock_vma_page(). This undoes the +pre-clearing of the page's PG_mlocked done by munlock_vma_page. -Note that try_to_munlock()'s reverse map walk must visit every VMA in a page's +Note that page_mlock()'s reverse map walk must visit every VMA in a page's reverse map to determine that a page is NOT mapped into any VM_LOCKED VMA. However, the scan can terminate when it encounters a VM_LOCKED VMA. -Although try_to_munlock() might be called a great many times when munlocking a +Although page_mlock() might be called a great many times when munlocking a large region or tearing down a large address space that has been mlocked via mlockall(), overall this is a fairly rare event.
@@ -602,7 +595,7 @@ inactive lists to the appropriate node's unevictable list. shrink_inactive_list() should only see SHM_LOCK'd pages that became SHM_LOCK'd after shrink_active_list() had moved them to the inactive list, or pages mapped into VM_LOCKED VMAs that munlock_vma_page() couldn't isolate from the LRU to -recheck via try_to_munlock(). shrink_inactive_list() won't notice the latter, +recheck via page_mlock(). shrink_inactive_list() won't notice the latter, but will pass on to shrink_page_list(). shrink_page_list() again culls obviously unevictable pages that it could
include/linux/rmap.h +1 -2
@@ -87,7 +87,6 @@ struct anon_vma_chain {enum ttu_flags {TTU_MIGRATION =0x1 ,/* migration mode */ -TTU_MUNLOCK =0x2 ,/* munlock mode */ TTU_SPLIT_HUGE_PMD =0x4 ,/* split huge PMD if any */ TTU_IGNORE_MLOCK =0x8 ,/* ignore mlock */
@@ -239,7 +238,7 @@ int page_mkclean(struct page *);* called in munlock()/munmap() path to check for other vmas holding * the page mlocked. */ -void try_to_munlock (struct page *); +void page_mlock (struct page *page);void remove_migration_ptes (struct page *old,struct page *new,bool locked);
mm/mlock.c +5 -5
@@ -108,7 +108,7 @@ void mlock_vma_page(struct page *page)/* * Finish munlock after successful page isolation * -* Page must be locked. This is a wrapper for try_to_munlock() +* Page must be locked. This is a wrapper for page_mlock() * and putback_lru_page() with munlock accounting. */ static void __munlock_isolated_page (struct page *page)
@@ -118,7 +118,7 @@ static void __munlock_isolated_page(struct page *page)* and we don't need to check all the other vmas. */ if (page_mapcount (page) >1 ) -try_to_munlock (page); +page_mlock (page);/* Did try_to_unlock() succeed or punt? */ if (!PageMlocked (page))
@@ -158,7 +158,7 @@ static void __munlock_isolation_failed(struct page *page)* munlock()ed or munmap()ed, we want to check whether other vmas hold the * page locked so that we can leave it on the unevictable lru list and not * bother vmscan with it. However, to walk the page's rmap list in -* try_to_munlock() we must isolate the page from the LRU. If some other +* page_mlock() we must isolate the page from the LRU. If some other * task has removed the page from the LRU, we won't be able to do that. * So we clear the PageMlocked as we might not get another chance. If we * can't isolate the page, we leave it for putback_lru_page() and vmscan
@@ -168,7 +168,7 @@ unsigned int munlock_vma_page(struct page *page) {int nr_pages; -/* For try_to_munlock() and to serialize with page migration */ +/* For page_mlock() and to serialize with page migration */ BUG_ON (!PageLocked (page));VM_BUG_ON_PAGE (PageTail (page), page);
@@ -205,7 +205,7 @@ static int __mlock_posix_error_return(long retval)* * The fast path is available only for evictable pages with single mapping. * Then we can bypass the per-cpu pvec and get better performance. -* when mapcount > 1 we need try_to_munlock() which can fail. +* when mapcount > 1 we need page_mlock() which can fail. * when !page_evictable(), we need the full redo logic of putback_lru_page to * avoid leaving evictable page in unevictable list. *
mm/rmap.c +44 -17
96 lines not on this page · raw message
Signature · 1 line
2.20.1
Re: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
Quoted 202 lines · Alistair Popple
The behaviour of
try_to_unmap_one()is difficult to follow because it performs different operations based on a fairly large set of flags used in different combinations.
TTU_MUNLOCKis one such flag. However it is exclusively used bytry_to_munlock()which specifies no other flags. Therefore rather than overloadtry_to_unmap_one()with unrelated behaviour split this out into it's own function and remove the flag.Signed-off-by Alistair Popple <[email protected]>Reviewed-by Ralph Campbell <[email protected]>Reviewed-by Christoph Hellwig <[email protected]>---
v9:
* Improved commentsv8:
* Renamedtry_to_munlocktopage_mlockto better reflect what the function actually does.
* Removed the TODO from the documentation that this patch addresses.v7:
* Added Christoph's Reviewed-byv4:
* Removed redundant check forVM_LOCKED--- Documentation/vm/unevictable-lru.rst | 33 ++++++--------- include/linux/rmap.h | 3 +- mm/mlock.c | 10 ++--- mm/rmap.c | 61 ++++++++++++++++++++-------- 4 files changed, 63 insertions(+), 44 deletions(-)Documentation/vm/unevictable-lru.rst +13
-20@@ -389,14 +389,14 @@ mlocked, munlock_vma_page() updates that zone statistics for the number of mlocked pages. Note, however, that at this point we haven't checked whether the page is mapped by other VM_LOCKED VMAs. -We can't call try_to_munlock(), the function that walks the reverse map to +We can't call page_mlock(), the function that walks the reverse map to check for other VM_LOCKED VMAs, without first isolating the page from the LRU. -try_to_munlock() is a variant of try_to_unmap() and thus requires that the page +page_mlock() is a variant of try_to_unmap() and thus requires that the page not be on an LRU list [more on these below]. However, the call to -isolate_lru_page() could fail, in which case we couldn't try_to_munlock(). So, +isolate_lru_page() could fail, in which case we can't call page_mlock(). So, we go ahead and clear PG_mlocked up front, as this might be the only chance we -have. If we can successfully isolate the page, we go ahead and -try_to_munlock(), which will restore the PG_mlocked flag and update the zone +have. If we can successfully isolate the page, we go ahead and call +page_mlock(), which will restore the PG_mlocked flag and update the zone page statistics if it finds another VMA holding the page mlocked. If we fail to isolate the page, we'll have left a potentially mlocked page on the LRU. This is fine, because we'll catch it later if and if vmscan tries to reclaim@@ -545,31 +545,24 @@ munlock or munmap system calls, mm teardown (munlock_vma_pages_all), reclaim, holepunching, and truncation of file pages and their anonymous COWed pages. -try_to_munlock() Reverse Map Scan +page_mlock() Reverse Map Scan --------------------------------- -.. warning:: - [!] TODO/FIXME: a better name might be page_mlocked() - analogous to the - page_referenced() reverse map walker. - When munlock_vma_page() [see section :ref:`munlock()/munlockall() System Call Handling <munlock_munlockall_handling>` above] tries to munlock a page, it needs to determine whether or not the page is mapped by any VM_LOCKED VMA without actually attempting to unmap all PTEs from the page. For this purpose, the unevictable/mlock infrastructure -introduced a variant of try_to_unmap() called try_to_munlock(). +introduced a variant of try_to_unmap() called page_mlock(). -try_to_munlock() calls the same functions as try_to_unmap() for anonymous and -mapped file and KSM pages with a flag argument specifying unlock versus unmap -processing. Again, these functions walk the respective reverse maps looking -for VM_LOCKED VMAs. When such a VMA is found, as in the try_to_unmap() case, -the functions mlock the page via mlock_vma_page() and return SWAP_MLOCK. This -undoes the pre-clearing of the page's PG_mlocked done by munlock_vma_page. +page_mlock() walks the respective reverse maps looking for VM_LOCKED VMAs. When +such a VMA is found the page is mlocked via mlock_vma_page(). This undoes the +pre-clearing of the page's PG_mlocked done by munlock_vma_page. -Note that try_to_munlock()'s reverse map walk must visit every VMA in a page's +Note that page_mlock()'s reverse map walk must visit every VMA in a page's reverse map to determine that a page is NOT mapped into any VM_LOCKED VMA. However, the scan can terminate when it encounters a VM_LOCKED VMA. -Although try_to_munlock() might be called a great many times when munlocking a +Although page_mlock() might be called a great many times when munlocking a large region or tearing down a large address space that has been mlocked via mlockall(), overall this is a fairly rare event.@@ -602,7 +595,7 @@ inactive lists to the appropriate node's unevictable list. shrink_inactive_list() should only see SHM_LOCK'd pages that became SHM_LOCK'd after shrink_active_list() had moved them to the inactive list, or pages mapped into VM_LOCKED VMAs that munlock_vma_page() couldn't isolate from the LRU to -recheck via try_to_munlock(). shrink_inactive_list() won't notice the latter, +recheck via page_mlock(). shrink_inactive_list() won't notice the latter, but will pass on to shrink_page_list(). shrink_page_list() again culls obviously unevictable pages that it couldinclude/linux/rmap.h +1
-2@@ -87,7 +87,6 @@ struct anon_vma_chain {enum ttu_flags {TTU_MIGRATION =0x1 ,/* migration mode */ -TTU_MUNLOCK =0x2 ,/* munlock mode */ TTU_SPLIT_HUGE_PMD =0x4 ,/* split huge PMD if any */ TTU_IGNORE_MLOCK =0x8 ,/* ignore mlock */ @@ -239,7 +238,7 @@ int page_mkclean(struct page *);* called in munlock()/munmap() path to check for other vmas holding * the page mlocked. */ -void try_to_munlock (struct page *); +void page_mlock (struct page *page);void remove_migration_ptes (struct page *old,struct page *new,bool locked);mm/mlock.c +5
-5@@ -108,7 +108,7 @@ void mlock_vma_page(struct page *page)/* * Finish munlock after successful page isolation * -* Page must be locked. This is a wrapper for try_to_munlock() +* Page must be locked. This is a wrapper for page_mlock() * and putback_lru_page() with munlock accounting. */ static void __munlock_isolated_page (struct page *page)@@ -118,7 +118,7 @@ static void __munlock_isolated_page(struct page *page)* and we don't need to check all the other vmas. */ if (page_mapcount (page) >1 ) -try_to_munlock (page); +page_mlock (page);/* Did try_to_unlock() succeed or punt? */ if (!PageMlocked (page))@@ -158,7 +158,7 @@ static void __munlock_isolation_failed(struct page *page)* munlock()ed or munmap()ed, we want to check whether other vmas hold the * page locked so that we can leave it on the unevictable lru list and not * bother vmscan with it. However, to walk the page's rmap list in -* try_to_munlock() we must isolate the page from the LRU. If some other +* page_mlock() we must isolate the page from the LRU. If some other * task has removed the page from the LRU, we won't be able to do that. * So we clear the PageMlocked as we might not get another chance. If we * can't isolate the page, we leave it for putback_lru_page() and vmscan @@ -168,7 +168,7 @@ unsigned int munlock_vma_page(struct page *page) {int nr_pages; -/* For try_to_munlock() and to serialize with page migration */ +/* For page_mlock() and to serialize with page migration */ BUG_ON (!PageLocked (page));VM_BUG_ON_PAGE (PageTail (page), page);@@ -205,7 +205,7 @@ static int __mlock_posix_error_return(long retval)* * The fast path is available only for evictable pages with single mapping. * Then we can bypass the per-cpu pvec and get better performance. -* when mapcount > 1 we need try_to_munlock() which can fail. +* when mapcount > 1 we need page_mlock() which can fail. * when !page_evictable(), we need the full redo logic of putback_lru_page to * avoid leaving evictable page in unevictable list. * mm/rmap.c +0
-6@@ -1405,10 +1405,6 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,struct mmu_notifier_range range;enum ttu_flags flags = (enum ttu_flags )(long )arg; -/* munlock has nothing to gain from examining un-locked vmas */ -if ((flags &TTU_MUNLOCK ) && !(vma->vm_flags &VM_LOCKED )) -return true; -if (IS_ENABLED (CONFIG_MIGRATION ) && (flags &TTU_MIGRATION ) &&is_zone_device_page (page) && !is_device_private_page (page))return true;@@ -1469,8 +1465,6 @@ static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,page_vma_mapped_walk_done (&pvmw);break ; } -if (flags &TTU_MUNLOCK ) -continue ; }/* Unexpected PMD-mapped THP? */
mm/rmap.c bool try_to_unmap(struct page *page, enum ttu_flags flags) · line 1784
Quoted 7 lines
mm/rmap.c +4
-0@@ -1784,20 +1778,53 @@ bool try_to_unmap(struct page *page, enum ttu_flags flags)return !page_mapcount (page) ? true : false; } +/* +* Walks the vma's mapping a page and mlocks the page if any locked vma's are +* found. Once one is found the page is locked and the scan can be terminated. +*/
Can you please add that this requires the mmap_sem() lock to the comments?
Quoted 77 lines
mm/rmap.c +40
-11+static bool page_mlock_one (struct page *page,struct vm_area_struct *vma, +unsigned long address,void *unused) +{ +struct page_vma_mapped_walk pvmw = { + .page = page, + .vma = vma, + .address = address, + }; + +/* An un-locked vma doesn't have any pages to lock, continue the scan */ +if (!(vma->vm_flags &VM_LOCKED )) +return true; + +while (page_vma_mapped_walk (&pvmw)) { +/* PTE-mapped THP are never mlocked */ +if (!PageTransCompound (page)) +mlock_vma_page (page); +page_vma_mapped_walk_done (&pvmw); + +/* +* no need to continue scanning other vma's if the page has +* been locked. +*/ +return false; + } + +return true; +} +/** -* try_to_munlock - try to munlock a page -* @page: the page to be munlocked +* page_mlock - try to mlock a page +* @page: the page to be mlocked * -* Called from munlock code. Checks all of the VMAs mapping the page -* to make sure nobody else has this page mlocked. The page will be -* returned with PG_mlocked cleared if no other vmas have it mlocked. +* Called from munlock code. Checks all of the VMAs mapping the page and mlocks +* the page if any are found. The page will be returned with PG_mlocked cleared +* if it is not mapped by any locked vmas. +* +* mmap_lock should be held for read or write. */ - -void try_to_munlock (struct page *page) +void page_mlock (struct page *page) {struct rmap_walk_control rwc = { - .rmap_one = try_to_unmap_one, - .arg = (void *)TTU_MUNLOCK , + .rmap_one = page_mlock_one, .done = page_not_mapped, .anon_lock = page_lock_anon_vma_read,@@ -1849,7 +1876,7 @@ static struct anon_vma *rmap_walk_anon_lock(struct page *page,* Find all the mappings of a page using the mapping pointer and the vma chains * contained in the anon_vma struct it points to. * -* When called from try_to_munlock(), the mmap_lock of the mm containing the vma +* When called from page_mlock(), the mmap_lock of the mm containing the vma * where the page was found will be held for write. So, we won't recheck * vm_flags for that VMA. That should be OK, because that vma shouldn't be * LOCKED. @@ -1901,7 +1928,7 @@ static void rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc,* Find all the mappings of a page using the mapping pointer and the vma chains * contained in the address_space struct it points to. * -* When called from try_to_munlock(), the mmap_lock of the mm containing the vma +* When called from page_mlock(), the mmap_lock of the mm containing the vma * where the page was found will be held for write. So, we won't recheck * vm_flags for that VMA. That should be OK, because that vma shouldn't be * LOCKED. Signature · 1 line
2.20.1
I believe munlock_vma_pages_range() still references the old function name?
Thanks,
Liam
Re: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
Quoted 1 line · Liam Howlett
[...]
Quoted 9 lines
Quoted 5 lines
+/*
+ * Walks the vma's mapping a page and mlocks the page if any locked vma's are
+ * found. Once one is found the page is locked and the scan can be terminated.
+ */Can you please add that this requires the
mmap_sem()lock to the comments?
Why does this require mmap_sem() lock? Also mmap_sem() lock of which mm_struct?
Quoted 28 lines
Quoted 28 lines
mm/rmap.c +28
-0+static bool page_mlock_one (struct page *page,struct vm_area_struct *vma, +unsigned long address,void *unused) +{ +struct page_vma_mapped_walk pvmw = { + .page = page, + .vma = vma, + .address = address, + }; + +/* An un-locked vma doesn't have any pages to lock, continue the scan */ +if (!(vma->vm_flags &VM_LOCKED )) +return true; + +while (page_vma_mapped_walk (&pvmw)) { +/* PTE-mapped THP are never mlocked */ +if (!PageTransCompound (page)) +mlock_vma_page (page); +page_vma_mapped_walk_done (&pvmw); + +/* +* no need to continue scanning other vma's if the page has +* been locked. +*/ +return false; + } + +return true; +}
Re: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
Quoted 9 lines · Shakeel Butt
Quoted 1 line · Liam Howlett
[...]
Quoted 6 lines
Quoted 5 lines
+/*
+ * Walks the vma's mapping a page and mlocks the page if any locked vma's are
+ * found. Once one is found the page is locked and the scan can be terminated.
+ */
Quoted 5 lines
Quoted 3 lines
Can you please add that this requires the
mmap_sem()lock to the comments?Why does this require
mmap_sem()lock? Alsommap_sem()lock of whichmm_struct?
Doesn't the mlock_vma_page() require the mmap_sem() for reading? The mm_struct in vma->vm_mm;
From what I can see, at least the following paths have mmap_lock held for writing:
munlock_vma_pages_range() from __do_munmap()munlokc_vma_pages_range() from remap_file_pages()
Quoted 21 lines
Quoted 21 lines
Quoted 21 lines
mm/rmap.c +20
-0+static bool page_mlock_one (struct page *page,struct vm_area_struct *vma, +unsigned long address,void *unused) +{ +struct page_vma_mapped_walk pvmw = { + .page = page, + .vma = vma, + .address = address, + }; + +/* An un-locked vma doesn't have any pages to lock, continue the scan */ +if (!(vma->vm_flags &VM_LOCKED )) +return true; + +while (page_vma_mapped_walk (&pvmw)) { +/* PTE-mapped THP are never mlocked */ +if (!PageTransCompound (page)) +mlock_vma_page (page); +page_vma_mapped_walk_done (&pvmw); + +/*
Quoted 8 lines
Quoted 8 lines
mm/rmap.c bool try_to_unmap(struct page *page, enum ttu_flags flags) · line 1812
Quoted 8 lines
mm/rmap.c +8
-0+* no need to continue scanning other vma's if the page has +* been locked. +*/ +return false; + } + +return true; +}
munlock_vma_pages_range() comments still references try_to_{munlock|unmap}
Re: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
Quoted 15 lines · Liam Howlett
Quoted 13 lines · Shakeel Butt
Quoted 1 line · Liam Howlett
[...]
Quoted 9 lines
Quoted 5 lines
+/*
+ * Walks the vma's mapping a page and mlocks the page if any locked vma's are
+ * found. Once one is found the page is locked and the scan can be terminated.
+ */Can you please add that this requires the
mmap_sem()lock to the comments?
Quoted 6 lines
Quoted 1 line
Why does this require
mmap_sem()lock? Alsommap_sem()lock of whichmm_struct?Doesn't the
mlock_vma_page()require themmap_sem()for reading? Themm_structinvma->vm_mm;
We are traversing all the vmas where this page is mapped of possibly different mm_structs. I don't think we want to take mmap_sem() of all those mm_structs. The commit b87537d9e2fe ("mm: rmap use pte lock not mmap_sem to set PageMlocked") removed exactly that.
Quoted 7 lines
From what I can see, at least the following paths have
mmap_lockheld for writing:
munlock_vma_pages_range()from__do_munmap()munlokc_vma_pages_range()fromremap_file_pages()
The following path does not hold mmap_sem:
exit_mmap() -> munlock_vma_pages_all() -> munlock_vma_pages_range().
I would really suggest all to carefully read the commit message of b87537d9e2fe ("mm: rmap use pte lock not mmap_sem to set PageMlocked").
Particularly the following paragraph:
...
Vlastimil Babka points out another race which this patch protects against.
try_to_unmap_one() might reach its mlock_vma_page() TestSetPageMlocked a
moment after munlock_vma_pages_all() did its Phase 1 TestClearPageMlocked:
leaving PageMlocked and unevictable when it should be evictable. mmap_sem is ineffective because exit_mmap() does not hold it; page lock ineffective because __munlock_pagevec() only takes it afterwards, in Phase 2; pte lock is effective because __munlock_pagevec_fill() takes it to get the page, after VM_LOCKED was cleared from vm_flags, so visible to try_to_unmap_one.
...
Alistair, please bring back the VM_LOCKED check with pte lock held and the comment "Holding pte lock, we do *not* need mmap_lock here".
One positive outcome of this cleanup patch is the removal of unnecessary invalidation (unmapping for kvm case) of secondary mmus.
Re: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
Quoted 32 lines · Shakeel Butt
Quoted 15 lines · Liam Howlett
Quoted 13 lines · Shakeel Butt
Quoted 1 line · Liam Howlett
[...]
Quoted 9 lines
Quoted 5 lines
+/*
+ * Walks the vma's mapping a page and mlocks the page if any locked vma's are
+ * found. Once one is found the page is locked and the scan can be terminated.
+ */Can you please add that this requires the
mmap_sem()lock to the comments?Quoted 6 lines
Quoted 1 line
Why does this require
mmap_sem()lock? Alsommap_sem()lock of whichmm_struct?Doesn't the
mlock_vma_page()require themmap_sem()for reading? Themm_structinvma->vm_mm;We are traversing all the vmas where this page is mapped of possibly different
mm_structs. I don't think we want to takemmap_sem()of all thosemm_structs. The commitb87537d9e2fe("mm: rmap use pte lock notmmap_semto set PageMlocked") removed exactly that.Quoted 4 lines
From what I can see, at least the following paths have
mmap_lockheld for writing:
Quoted 7 lines
Quoted 3 lines
munlock_vma_pages_range()from__do_munmap()munlokc_vma_pages_range()fromremap_file_pages()The following path does not hold
mmap_sem:
exit_mmap()->munlock_vma_pages_all()->munlock_vma_pages_range().
Isn't this the benign race referenced by Hugh in the commit you point to below?
Quoted 5 lines
I would really suggest all to carefully read the commit message of
b87537d9e2fe("mm: rmap use pte lock notmmap_semto set PageMlocked").
Quoted 11 lines
Particularly the following paragraph:
...
Vlastimil Babka points out another race which this patch protects against.
try_to_unmap_one()might reach itsmlock_vma_page()TestSetPageMlocked a
moment aftermunlock_vma_pages_all()did its Phase 1 TestClearPageMlocked:
leaving PageMlocked and unevictable when it should be evictable.mmap_semis ineffective becauseexit_mmap()does not hold it; page lock ineffective because__munlock_pagevec()only takes it afterwards, in Phase 2; pte lock is effective because__munlock_pagevec_fill()takes it to get the page, afterVM_LOCKEDwas cleared fromvm_flags, so visible totry_to_unmap_one.
...
So this is saying the race with exit_mmap() isn't benign after all?
Quoted 6 lines
Alistair, please bring back the
VM_LOCKEDcheck with pte lock held and the comment "Holding pte lock, we do *not* needmmap_lockhere".One positive outcome of this cleanup patch is the removal of unnecessary invalidation (unmapping for kvm case) of secondary mmus.
Re: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
Quoted 13 lines · Liam Howlett
Quoted 12 lines
Particularly the following paragraph:
...
Vlastimil Babka points out another race which this patch protects against.
try_to_unmap_one()might reach itsmlock_vma_page()TestSetPageMlocked a
moment aftermunlock_vma_pages_all()did its Phase 1 TestClearPageMlocked:
leaving PageMlocked and unevictable when it should be evictable.mmap_semis ineffective becauseexit_mmap()does not hold it; page lock ineffective because__munlock_pagevec()only takes it afterwards, in Phase 2; pte lock is effective because__munlock_pagevec_fill()takes it to get the page, afterVM_LOCKEDwas cleared fromvm_flags, so visible totry_to_unmap_one.
...
Quoted 2 lines
So this is saying the race with
exit_mmap()isn't benign after all?
Yes, not benign at all.
Alistair Popple
reviewerRe: [PATCH v9 03/10] mm/rmap: Split try_to_munlock from try_to_unmap
In reply to Shakeel ButtQuoted 7 lines · AEST Shakeel Butt
External email: Use caution opening links or attachments
Quoted 3 lines · Liam Howlett
Quoted 1 line · Shakeel Butt
On Tue, May 25, 2021 at 11:40 AM Liam Howlett <[email protected]>
Quoted 5 lines · wrote:
Quoted 5 lines
Quoted 5 lines
Quoted 1 line
[...]
Quoted 3 lines
Quoted 3 lines
+/*
+ * Walks the vma's mapping a page and mlocks the page if any locked
vma's are
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
+ * found. Once one is found the page is locked and the scan can be
terminated.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Quoted 5 lines
Quoted 1 line
+ */
Can you please add that this requires the
mmap_sem()lock to the comments?Why does this require
mmap_sem()lock? Alsommap_sem()lock of which
mm_struct?
Quoted 23 lines
Quoted 5 lines
Doesn't the
mlock_vma_page()require themmap_sem()for reading? Themm_structinvma->vm_mm;We are traversing all the vmas where this page is mapped of possibly different
mm_structs. I don't think we want to takemmap_sem()of all thosemm_structs. The commitb87537d9e2fe("mm: rmap use pte lock notmmap_semto set PageMlocked") removed exactly that.Quoted 7 lines
From what I can see, at least the following paths have
mmap_lockheld for writing:
munlock_vma_pages_range()from__do_munmap()munlokc_vma_pages_range()fromremap_file_pages()The following path does not hold
mmap_sem:
exit_mmap()->munlock_vma_pages_all()->munlock_vma_pages_range().
Quoted 7 lines
I would really suggest all to carefully read the commit message of
b87537d9e2fe("mm: rmap use pte lock notmmap_semto set PageMlocked").Particularly the following paragraph:
...
Vlastimil Babka points out another race which this patch protects
against.
Quoted 1 line
try_to_unmap_one()might reach itsmlock_vma_page()TestSetPageMlocked
a
Quoted 1 line
moment after
munlock_vma_pages_all()did its Phase 1
TestClearPageMlocked:
Quoted 1 line
leaving PageMlocked and unevictable when it should be evictable.
mmap_sem
Quoted 1 line
is ineffective because
exit_mmap()does not hold it; page lock
ineffective
Quoted 1 line
because
__munlock_pagevec()only takes it afterwards, in Phase 2; pte
lock
Quoted 2 lines
is effective because
__munlock_pagevec_fill()takes it to get the page, afterVM_LOCKEDwas cleared fromvm_flags, so visible to
try_to_unmap_one.
Quoted 4 lines
...
Alistair, please bring back the
VM_LOCKEDcheck with pte lock held and the comment "Holding pte lock, we do *not* needmmap_lockhere".
Actually thanks for highlighting that paragraph. I have gone back through the code again in munlock_vma_pages_range() and think I have a better understanding of it now. So now I agree - the check of VM_LOCKED under the PTL is important to ensure mlock_vma_page() does not run after VM_LOCKED has been cleared and __munlock_pagevec_fill() has run.
Will post v10 to fix this and the try_to_munlock reference pointed out by Liam which I missed for v9. Thanks Shakeel for taking the time to point this out.
Quoted 2 lines
One positive outcome of this cleanup patch is the removal of unnecessary invalidation (unmapping for kvm case) of secondary mmus.
Alistair Popple
reviewerPATCH v9 4/10 mm/rmap: Split migration into its own function
In reply to Alistair PoppleMigration is currently implemented as a mode of operation for try_to_unmap_one() generally specified by passing the TTU_MIGRATION flag or in the case of splitting a huge anonymous page TTU_SPLIT_FREEZE.
However it does not have much in common with the rest of the unmap functionality of try_to_unmap_one() and thus splitting it into a separate function reduces the complexity of try_to_unmap_one() making it more readable.
Several simplifications can also be made in try_to_migrate_one() based on the following observations:
- All users of TTU_MIGRATION also set TTU_IGNORE_MLOCK.
- No users of TTU_MIGRATION ever set TTU_IGNORE_HWPOISON.
- No users of TTU_MIGRATION ever set TTU_BATCH_FLUSH.
TTU_SPLIT_FREEZE is a special case of migration used when splitting an anonymous page. This is most easily dealt with by calling the correct function from unmap_page() in mm/huge_memory.c - either try_to_migrate() for PageAnon or try_to_unmap().
---
v5:
* Added comments about how PMD splitting works for migration vs. unmapping
* Tightened up the flag check in try_to_migrate() to be explicit about which TTU_XXX flags are supported.
--- include/linux/rmap.h | 4 +- mm/huge_memory.c | 15 +- mm/migrate.c | 9 +- mm/rmap.c | 358 ++++++++++++++++++++++++++++++++----------- 4 files changed, 280 insertions(+), 106 deletions(-)
include/linux/rmap.h +1 -3
@@ -86,8 +86,6 @@ struct anon_vma_chain { };enum ttu_flags { -TTU_MIGRATION =0x1 ,/* migration mode */ -TTU_SPLIT_HUGE_PMD =0x4 ,/* split huge PMD if any */ TTU_IGNORE_MLOCK =0x8 ,/* ignore mlock */ TTU_IGNORE_HWPOISON =0x20 ,/* corrupted page is recoverable */
@@ -96,7 +94,6 @@ enum ttu_flags {* do a final flush if necessary */ TTU_RMAP_LOCKED = 0x80, /* do not grab rmap lock:* caller holds it */ - TTU_SPLIT_FREEZE = 0x100, /* freeze pte under splitting thp */ };#ifdef CONFIG_MMU
@@ -193,6 +190,7 @@ static inline void page_dup_rmap(struct page *page, bool compound)int page_referenced (struct page *,int is_locked,struct mem_cgroup *memcg,unsigned long *vm_flags); +bool try_to_migrate (struct page *page,enum ttu_flags flags);bool try_to_unmap (struct page *,enum ttu_flags flags);/* Avoid racy checks */
mm/huge_memory.c +10 -5
@@ -2345,16 +2345,21 @@ void vma_adjust_trans_huge(struct vm_area_struct *vma,static void unmap_page (struct page *page) { -enum ttu_flags ttu_flags =TTU_IGNORE_MLOCK | -TTU_RMAP_LOCKED |TTU_SPLIT_HUGE_PMD ; +enum ttu_flags ttu_flags =TTU_RMAP_LOCKED |TTU_SPLIT_HUGE_PMD ;bool unmap_success;VM_BUG_ON_PAGE (!PageHead (page), page);if (PageAnon (page)) - ttu_flags |=TTU_SPLIT_FREEZE ; - - unmap_success =try_to_unmap (page, ttu_flags); + unmap_success =try_to_migrate (page, ttu_flags); +else +/* +* Don't install migration entries for file backed pages. This +* helps handle cases when i_size is in the middle of the page +* as there is no need to unmap pages beyond i_size manually. +*/ + unmap_success =try_to_unmap (page, ttu_flags | +TTU_IGNORE_MLOCK );VM_BUG_ON_PAGE (!unmap_success, page); }
mm/migrate.c +4 -5
@@ -1103,7 +1103,7 @@ static int __unmap_and_move(struct page *page, struct page *newpage,/* Establish migration ptes */ VM_BUG_ON_PAGE (PageAnon (page) && !PageKsm (page) && !anon_vma, page); -try_to_unmap (page,TTU_MIGRATION |TTU_IGNORE_MLOCK ); +try_to_migrate (page,0 ); page_was_mapped =1 ; }
@@ -1305,7 +1305,7 @@ static int unmap_and_move_huge_page(new_page_t get_new_page,if (page_mapped (hpage)) {bool mapping_locked = false; -enum ttu_flags ttu =TTU_MIGRATION |TTU_IGNORE_MLOCK ; +enum ttu_flags ttu =0 ;if (!PageAnon (hpage)) {/*
@@ -1322,7 +1322,7 @@ static int unmap_and_move_huge_page(new_page_t get_new_page, ttu |=TTU_RMAP_LOCKED ; } -try_to_unmap (hpage, ttu); +try_to_migrate (hpage, ttu); page_was_mapped =1 ;if (mapping_locked)
@@ -2712,7 +2712,6 @@ static void migrate_vma_prepare(struct migrate_vma *migrate)*/ static void migrate_vma_unmap (struct migrate_vma *migrate) { -int flags =TTU_MIGRATION |TTU_IGNORE_MLOCK ;const unsigned long npages = migrate->npages;const unsigned long start = migrate->start;unsigned long addr, i, restore =0 ;
@@ -2724,7 +2723,7 @@ static void migrate_vma_unmap(struct migrate_vma *migrate)continue ;if (page_mapped (page)) { -try_to_unmap (page, flags); +try_to_migrate (page,0 );if (page_mapped (page)) goto restore; }
mm/rmap.c +265 -93
394 lines not on this page · raw message
Signature · 1 line
2.20.1
Alistair Popple
reviewerPATCH v9 5/10 mm: Rename migrate_pgmap_owner
In reply to Alistair PoppleMMU notifier ranges have a migrate_pgmap_owner field which is used by drivers to store a pointer. This is subsequently used by the driver callback to filter MMU_NOTIFY_MIGRATE events. Other notifier event types can also benefit from this filtering, so rename the 'migrate_pgmap_owner' field to 'owner' and create a new notifier initialisation function to initialise this field.
---
v9:
Previously part of the next patch in the series ('mm: Device exclusive memory access') but now split out as a separate change as suggested by Peter Xu.
--- Documentation/vm/hmm.rst | 2 +- drivers/gpu/drm/nouveau/nouveau_svm.c | 2 +- include/linux/mmu_notifier.h | 20 ++++++++++---------- lib/test_hmm.c | 2 +- mm/migrate.c | 10 +++++----- 5 files changed, 18 insertions(+), 18 deletions(-)
Documentation/vm/hmm.rst +1 -1
@@ -332,7 +332,7 @@ between device driver specific code and shared common code: walks to fill in the ``args->src`` array with PFNs to be migrated. The ``invalidate_range_start()`` callback is passed a ``struct mmu_notifier_range`` with the ``event`` field set to - ``MMU_NOTIFY_MIGRATE`` and the ``migrate_pgmap_owner`` field set to + ``MMU_NOTIFY_MIGRATE`` and the ``owner`` field set to the ``args->pgmap_owner`` field passed to migrate_vma_setup(). This is allows the device driver to skip the invalidation callback and only invalidate device private MMU mappings that are actually migrating.
drivers/gpu/drm/nouveau/nouveau_svm.c +1 -1
@@ -265,7 +265,7 @@ nouveau_svmm_invalidate_range_start(struct mmu_notifier *mn,* the invalidation is handled as part of the migration process. */ if (update->event ==MMU_NOTIFY_MIGRATE && - update->migrate_pgmap_owner == svmm->vmm->cli->drm->dev) + update->owner == svmm->vmm->cli->drm->dev) goto out;if (limit > svmm->unmanaged.start && start < svmm->unmanaged.limit) {
include/linux/mmu_notifier.h +10 -10
@@ -41,7 +41,7 @@ struct mmu_interval_notifier;* * @MMU_NOTIFY_MIGRATE: used during migrate_vma_collect() invalidate to signal * a device driver to possibly ignore the invalidation if the -* migrate_pgmap_owner field matches the driver's device private pgmap owner. +* owner field matches the driver's device private pgmap owner. */ enum mmu_notifier_event {MMU_NOTIFY_UNMAP =0 ,
@@ -269,7 +269,7 @@ struct mmu_notifier_range {unsigned long end;unsigned flags;enum mmu_notifier_event event; -void *migrate_pgmap_owner; +void *owner; };static inline int mm_has_notifiers (struct mm_struct *mm)
@@ -521,14 +521,14 @@ static inline void mmu_notifier_range_init(struct mmu_notifier_range *range, range->flags = flags; } -static inline void mmu_notifier_range_init_migrate ( -struct mmu_notifier_range *range,unsigned int flags, +static inline void mmu_notifier_range_init_owner ( +struct mmu_notifier_range *range, +enum mmu_notifier_event event,unsigned int flags,struct vm_area_struct *vma,struct mm_struct *mm, -unsigned long start,unsigned long end,void *pgmap) +unsigned long start,unsigned long end,void *owner) { -mmu_notifier_range_init (range,MMU_NOTIFY_MIGRATE , flags, vma, mm, - start, end); - range->migrate_pgmap_owner = pgmap; +mmu_notifier_range_init (range, event, flags, vma, mm, start, end); + range->owner = owner; }#define ptep_clear_flush_young_notify (__vma, __address, __ptep) \
@@ -655,8 +655,8 @@ static inline void _mmu_notifier_range_init(struct mmu_notifier_range *range,#define mmu_notifier_range_init (range,event,flags,vma,mm,start,end) \ _mmu_notifier_range_init(range, start, end) -#define mmu_notifier_range_init_migrate (range, flags, vma, mm, start, end, \ - pgmap) \ +#define mmu_notifier_range_init_owner (range, event, flags, vma, mm, start, \ + end, owner) \ _mmu_notifier_range_init(range, start, end) static inline bool
lib/test_hmm.c +1 -1
@@ -218,7 +218,7 @@ static bool dmirror_interval_invalidate(struct mmu_interval_notifier *mni,* the invalidation is handled as part of the migration process. */ if (range->event ==MMU_NOTIFY_MIGRATE && - range->migrate_pgmap_owner == dmirror->mdevice) + range->owner == dmirror->mdevice)return true;if (mmu_notifier_range_blockable (range))
mm/migrate.c +5 -5
@@ -2526,8 +2526,8 @@ static void migrate_vma_collect(struct migrate_vma *migrate)* that the registered device driver can skip invalidating device * private page mappings that won't be migrated. */ -mmu_notifier_range_init_migrate (&range,0 , migrate->vma, - migrate->vma->vm_mm, migrate->start, migrate->end, +mmu_notifier_range_init_owner (&range,MMU_NOTIFY_MIGRATE ,0 , + migrate->vma, migrate->vma->vm_mm, migrate->start, migrate->end, migrate->pgmap_owner);mmu_notifier_invalidate_range_start (&range);
@@ -3037,9 +3037,9 @@ void migrate_vma_pages(struct migrate_vma *migrate)if (!notified) { notified = true; -mmu_notifier_range_init_migrate (&range,0 , - migrate->vma, migrate->vma->vm_mm, - addr, migrate->end, +mmu_notifier_range_init_owner (&range, +MMU_NOTIFY_MIGRATE ,0 , migrate->vma, + migrate->vma->vm_mm, addr, migrate->end, migrate->pgmap_owner);mmu_notifier_invalidate_range_start (&range); }
Signature · 6 lines
2.20.1 _______________________________________________ Nouveau mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/nouveau
Re: [PATCH v9 05/10] mm: Rename migrate_pgmap_owner
Quoted 9 lines · Alistair Popple
include/linux/mmu_notifier.h +3
-2@@ -521,14 +521,14 @@ static inline void mmu_notifier_range_init(struct mmu_notifier_range *range, range->flags = flags; } -static inline void mmu_notifier_range_init_migrate( - struct mmu_notifier_range *range, unsigned int flags, +static inline void mmu_notifier_range_init_owner( + struct mmu_notifier_range *range, + enum mmu_notifier_event event, unsigned int flags,
include/linux/mmu_notifier.h static inline void mmu_notifier_range_init(struct mmu_notifier_range *range, · line 532
Quoted 10 lines
include/linux/mmu_notifier.h +3
-4struct vm_area_struct *vma, struct mm_struct *mm, - unsigned long start, unsigned long end, void *pgmap) + unsigned long start, unsigned long end, void *owner) { -mmu_notifier_range_init (range ,MMU_NOTIFY_MIGRATE ,flags ,vma ,mm , -start ,end ); - range->migrate_pgmap_owner = pgmap; +mmu_notifier_range_init (range ,event ,flags ,vma ,mm ,start ,end ); + range->owner = owner; }
mmu_notifier_range_init_migrate() can even be kept to just call the new helper, then existing callers are unaffected. Not a big deal, though:
Thanks,
Signature · 1 line
Peter Xu
Alistair Popple
reviewerPATCH v9 6/10 mm/memory.c: Allow different return codes for copy_nonpresent_pte()
In reply to Alistair PoppleCurrently if copy_nonpresent_pte() returns a non-zero value it is assumed to be a swap entry which requires further processing outside the loop in copy_pte_range() after dropping locks. This prevents other values being returned to signal conditions such as failure which a subsequent change requires.
Instead make copy_nonpresent_pte() return an error code if further processing is required and read the value for the swap entry in the main loop under the ptl.
---
v9:
New for v9 to allow device exclusive handling to occur in copy_nonpresent_pte().
--- mm/memory.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-)
mm/memory.c +7 -5
@@ -718,7 +718,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,if (likely (!non_swap_entry (entry))) {if (swap_duplicate (entry) <0 ) -return entry.val; +return -EAGAIN ;/* make sure dst_mm is on swapoff's mmlist. */ if (unlikely (list_empty (&dst_mm->mmlist))) {
@@ -974,11 +974,13 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,continue ; }if (unlikely (!pte_present (*src_pte))) { - entry.val =copy_nonpresent_pte (dst_mm, src_mm, - dst_pte, src_pte, - src_vma, addr, rss); -if (entry.val) + ret =copy_nonpresent_pte (dst_mm, src_mm, + dst_pte, src_pte, + src_vma, addr, rss); +if (ret == -EAGAIN ) { + entry =pte_to_swp_entry (*src_pte);break ; + } progress +=8 ;continue ; }
Signature · 1 line
2.20.1
Re: [PATCH v9 06/10] mm/memory.c: Allow different return codes for copy_nonpresent_pte()
Quoted 43 lines · Alistair Popple
Currently if
copy_nonpresent_pte()returns a non-zero value it is assumed to be a swap entry which requires further processing outside the loop incopy_pte_range()after dropping locks. This prevents other values being returned to signal conditions such as failure which a subsequent change requires.Instead make
copy_nonpresent_pte()return an error code if further processing is required and read the value for the swap entry in the main loop under the ptl.Signed-off-by Alistair Popple <[email protected]>---
v9:
New for v9 to allow device exclusive handling to occur in
copy_nonpresent_pte().--- mm/memory.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-)mm/memory.c +1
-5@@ -718,7 +718,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm,if (likely (!non_swap_entry (entry))) {if (swap_duplicate (entry) <0 ) -return entry.val; +return -EAGAIN ;/* make sure dst_mm is on swapoff's mmlist. */ if (unlikely (list_empty (&dst_mm->mmlist))) {@@ -974,11 +974,13 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,continue ; }if (unlikely (!pte_present (*src_pte))) { - entry.val =copy_nonpresent_pte (dst_mm, src_mm, - dst_pte, src_pte, - src_vma, addr, rss); -if (entry.val)
mm/memory.c copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, · line 986
Quoted 10 lines
mm/memory.c +6
-0+ ret =copy_nonpresent_pte (dst_mm, src_mm, + dst_pte, src_pte, + src_vma, addr, rss); +if (ret == -EAGAIN ) { + entry =pte_to_swp_entry (*src_pte);break ; + } progress +=8 ;continue ; }
Note that -EAGAIN was previously used by copy_present_page() for early cow use. Here later although we check entry.val first:
if (entry.val) {
if (add_swap_count_continuation(entry, GFP_KERNEL) < 0) {
ret = -ENOMEM;
goto out;
}
entry.val = 0;
} else if (ret) {
WARN_ON_ONCE(ret != -EAGAIN);
prealloc = page_copy_prealloc(src_mm, src_vma, addr);
if (!prealloc)
return -ENOMEM;
/* We've captured and resolved the error. Reset, try again. */
ret = 0;
}We didn't reset "ret" in entry.val case (maybe we should?). Then in the next round of "goto again" if "ret" is unluckily untouched, it could reach the 2nd if check, and I think it could cause an unexpected page_copy_prealloc().
Signature · 1 line
Peter Xu
Alistair Popple
reviewerRe: [PATCH v9 06/10] mm/memory.c: Allow different return codes for copy_nonpresent_pte()
Quoted 84 lines · AEST Peter Xu
Quoted 54 lines · Alistair Popple
Currently if
copy_nonpresent_pte()returns a non-zero value it is assumed to be a swap entry which requires further processing outside the loop incopy_pte_range()after dropping locks. This prevents other values being returned to signal conditions such as failure which a subsequent change requires.Instead make
copy_nonpresent_pte()return an error code if further processing is required and read the value for the swap entry in the main loop under the ptl.Signed-off-by Alistair Popple <[email protected]>---
v9:
New for v9 to allow device exclusive handling to occur in
copy_nonpresent_pte().
---mm/memory.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-)mm/memory.c +0
-0@@ -718,7 +718,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct
mm_struct*src_mm,>
if (likely(!non_swap_entry(entry))) {if (
swap_duplicate(entry)< 0)mm/memory.c +1
-1-return entry.val; +return -EAGAIN ;/* make sure dst_mm is on swapoff's mmlist. */ if (unlikely (list_empty (&dst_mm->mmlist))) {@@ -974,11 +974,13 @@ copy_pte_range(struct vm_area_struct *dst_vma,struct
vm_area_struct*src_vma,>continue; } if (unlikely(!pte_present(*src_pte))) {mm/memory.c +4
-4- entry.val =copy_nonpresent_pte (dst_mm, src_mm, - dst_pte, src_pte, - src_vma, addr, rss); -if (entry.val) + ret =copy_nonpresent_pte (dst_mm, src_mm, + dst_pte, src_pte, + src_vma, addr, rss); +if (ret == -EAGAIN ) {mm/memory.c copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, · line 986
Quoted 10 lines
mm/memory.c +2
-0+ entry =pte_to_swp_entry (*src_pte);break ; + } progress +=8 ;continue ; }Note that
-EAGAINwas previously used bycopy_present_page()for early cow use. Here later although we check entry.val first:if (entry.val) { if (add_swap_count_continuation(entry, GFP_KERNEL) < 0) { ret = -ENOMEM; goto out; } entry.val = 0; } else if (ret) { WARN_ON_ONCE(ret != -EAGAIN); prealloc = page_copy_prealloc(src_mm, src_vma, addr); if (!prealloc) return -ENOMEM; /* We've captured and resolved the error. Reset, try again. */ ret = 0; }
Quoted 4 lines
We didn't reset "ret" in entry.val case (maybe we should?). Then in the next round of "goto again" if "ret" is unluckily untouched, it could reach the 2nd if check, and I think it could cause an unexpected
page_copy_prealloc().
Thanks, I had considered that but saw "ret" was always set either by copy_nonpresent_pte() or copy_present_pte(). However missed the "unlucky" case at the start of the loop:
if (progress >= 32) {
progress = 0;
if (need_resched() ||
spin_needbreak(src_ptl) || pin_needbreak(dst_ptl))
break;Looking at this again though checking different variables to figure out what to do outside the locks and reusing error codes seems error prone. I reused - EAGAIN for copy_nonpresent_pte() simply because that seemed the most sensible error code, but I don't think that aids readability and it might be better to use a unique error code for each case needing extra handling.
So it might be better if I update this patch to:
1) Use unique error codes for each case requiring special handling outside the lock.
2) Only check "ret" to determine what to do outside locks (ie. not entry.val)
3) Document these.
4) Always reset ret after handling.
Thoughts?
- Alistair
Quoted 2 lines
--
Peter Xu
Re: [PATCH v9 06/10] mm/memory.c: Allow different return codes for copy_nonpresent_pte()
Quoted 106 lines · Alistair Popple
Quoted 84 lines · AEST Peter Xu
Quoted 54 lines · Alistair Popple
Currently if
copy_nonpresent_pte()returns a non-zero value it is assumed to be a swap entry which requires further processing outside the loop incopy_pte_range()after dropping locks. This prevents other values being returned to signal conditions such as failure which a subsequent change requires.Instead make
copy_nonpresent_pte()return an error code if further processing is required and read the value for the swap entry in the main loop under the ptl.Signed-off-by Alistair Popple <[email protected]>---
v9:
New for v9 to allow device exclusive handling to occur in
copy_nonpresent_pte().
---mm/memory.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-)mm/memory.c +0
-0@@ -718,7 +718,7 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct
mm_struct*src_mm,>
if (likely(!non_swap_entry(entry))) {if (
swap_duplicate(entry)< 0)mm/memory.c +1
-1-return entry.val; +return -EAGAIN ;/* make sure dst_mm is on swapoff's mmlist. */ if (unlikely (list_empty (&dst_mm->mmlist))) {@@ -974,11 +974,13 @@ copy_pte_range(struct vm_area_struct *dst_vma,struct
vm_area_struct*src_vma,>continue; } if (unlikely(!pte_present(*src_pte))) {mm/memory.c +4
-4- entry.val =copy_nonpresent_pte (dst_mm, src_mm, - dst_pte, src_pte, - src_vma, addr, rss); -if (entry.val) + ret =copy_nonpresent_pte (dst_mm, src_mm, + dst_pte, src_pte, + src_vma, addr, rss); +if (ret == -EAGAIN ) {mm/memory.c copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, · line 986
Quoted 10 lines
mm/memory.c +2
-0+ entry =pte_to_swp_entry (*src_pte);break ; + } progress +=8 ;continue ; }Note that
-EAGAINwas previously used bycopy_present_page()for early cow use. Here later although we check entry.val first:if (entry.val) { if (add_swap_count_continuation(entry, GFP_KERNEL) < 0) { ret = -ENOMEM; goto out; } entry.val = 0; } else if (ret) { WARN_ON_ONCE(ret != -EAGAIN); prealloc = page_copy_prealloc(src_mm, src_vma, addr); if (!prealloc) return -ENOMEM; /* We've captured and resolved the error. Reset, try again. */ ret = 0; }Quoted 4 lines
We didn't reset "ret" in entry.val case (maybe we should?). Then in the next round of "goto again" if "ret" is unluckily untouched, it could reach the 2nd if check, and I think it could cause an unexpected
page_copy_prealloc().Thanks, I had considered that but saw "ret" was always set either by
copy_nonpresent_pte()orcopy_present_pte(). However missed the "unlucky" case at the start of the loop:if (progress >= 32) { progress = 0; if (need_resched() || spin_needbreak(src_ptl) || pin_needbreak(dst_ptl)) break;Looking at this again though checking different variables to figure out what to do outside the locks and reusing error codes seems error prone. I reused - EAGAIN for
copy_nonpresent_pte()simply because that seemed the most sensible error code, but I don't think that aids readability and it might be better to use a unique error code for each case needing extra handling.
Quoted 8 lines
So it might be better if I update this patch to:
1) Use unique error codes for each case requiring special handling outside the lock.
2) Only check "ret" to determine what to do outside locks (ie. not entry.val)
3) Document these.
4) Always reset ret after handling.Thoughts?
Looks good to me. Thanks,
Signature · 1 line
Peter Xu
Alistair Popple
reviewerPATCH v9 7/10 mm: Device exclusive memory access
In reply to Alistair PoppleSome devices require exclusive write access to shared virtual memory (SVM) ranges to perform atomic operations on that memory. This requires CPU page tables to be updated to deny access whilst atomic operations are occurring.
In order to do this introduce a new swap entry
type (SWP_DEVICE_EXCLUSIVE). When a SVM range needs to be marked for exclusive access by a device all page table mappings for the particular range are replaced with device exclusive swap entries. This causes any CPU access to the page to result in a fault.
Faults are resovled by replacing the faulting entry with the original mapping. This results in MMU notifiers being called which a driver uses to update access permissions such as revoking atomic access. After notifiers have been called the device will no longer have exclusive access to the region.
Walking of the page tables to find the target pages is handled by get_user_pages() rather than a direct page table walk. A direct page table walk similar to what migrate_vma_collect()/unmap() does could also have been utilised. However this resulted in more code similar in functionality to what get_user_pages() provides as page faulting is required to make the PTEs present and to break COW.
---
v9:
* Split rename of migrate_pgmap_owner into a separate patch.
* Added comments explaining SWP_DEVICE_EXCLUSIVE_* entries.
* Renamed try_to_protect{_one} to page_make_device_exclusive{_one} based somewhat on a suggestion from Peter Xu. I was never particularly happy with try_to_protect() as a name so think this is better.
* Removed unneccesary code and reworded some comments based on feedback from Peter Xu.
* Removed the VMA walk when restoring PTEs for device-exclusive entries.
* Simplified implementation of copy_pte_range() to fail if the page cannot be locked. This might lead to occasional fork() failures but at this stage we don't think that will be an issue.
v8:
* Remove device exclusive entries on fork rather than copy them.
v7:
* Added Christoph's Reviewed-by.
* Minor cosmetic cleanups suggested by Christoph.
* Replace mmu_notifier_range_init_migrate/exclusive with mmu_notifier_range_init_owner as suggested by Christoph.
* Replaced lock_page() with lock_page_retry() when handling faults.
* Restrict to anonymous pages for now.
v6:
* Fixed a bisectablity issue due to incorrectly applying the rename of migrate_pgmap_owner to the wrong patches for Nouveau and hmm_test.
v5:
* Renamed range->migrate_pgmap_owner to range->owner.
* Added MMU_NOTIFY_EXCLUSIVE to allow passing of a driver cookie which allows notifiers called as a result of make_device_exclusive_range() to be ignored.
* Added a check to try_to_protect_one() to detect if the pages originally returned from get_user_pages() have been unmapped or not.
* Removed check_device_exclusive_range() as it is no longer required with the other changes.
* Documentation update.
v4:
* Add function to check that mappings are still valid and exclusive.
* s/long/unsigned long/ in make_device_exclusive_entry().
--- Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)
Documentation/vm/hmm.rst +17 -0
@@ -405,6 +405,23 @@ between device driver specific code and shared common code: The lock can now be released. +Exclusive access memory +======================= + +Some devices have features such as atomic PTE bits that can be used to implement +atomic access to system memory. To support atomic operations to a shared virtual +memory page such a device needs access to that page which is exclusive of any +userspace access from the CPU. The ``make_device_exclusive_range()`` function +can be used to make a memory range inaccessible from userspace. + +This replaces all mappings for pages in the given range with special swap +entries. Any attempt to access the swap entry results in a fault which is +resovled by replacing the entry with the original mapping. A driver gets +notified that the mapping has been changed by MMU notifiers, after which point +it will no longer have exclusive access to the page. Exclusive access is +guranteed to last until the driver drops the page lock and page reference, at +which point any CPU faults on the page may proceed as described. + Memory cgroup (memcg) and rss accounting ========================================
include/linux/mmu_notifier.h +6 -0
@@ -42,6 +42,11 @@ struct mmu_interval_notifier;* @MMU_NOTIFY_MIGRATE: used during migrate_vma_collect() invalidate to signal * a device driver to possibly ignore the invalidation if the * owner field matches the driver's device private pgmap owner. +* +* @MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will no +* longer have exclusive access to the page. May ignore the invalidation that's +* part of make_device_exclusive_range() if the owner field +* matches the value passed to make_device_exclusive_range(). */ enum mmu_notifier_event {MMU_NOTIFY_UNMAP =0 ,
@@ -51,6 +56,7 @@ enum mmu_notifier_event {MMU_NOTIFY_SOFT_DIRTY ,MMU_NOTIFY_RELEASE ,MMU_NOTIFY_MIGRATE , + MMU_NOTIFY_EXCLUSIVE, };#define MMU_NOTIFIER_RANGE_BLOCKABLE (1 << 0)
include/linux/rmap.h +4 -0
@@ -193,6 +193,10 @@ int page_referenced(struct page *, int is_locked,bool try_to_migrate (struct page *page,enum ttu_flags flags);bool try_to_unmap (struct page *,enum ttu_flags flags); +int make_device_exclusive_range (struct mm_struct *mm,unsigned long start, +unsigned long end,struct page **pages, +void *arg); +/* Avoid racy checks */ #define PVMW_SYNC (1 << 0)/* Look for migarion entries rather than present PTEs */
include/linux/swap.h +6 -1
@@ -63,11 +63,16 @@ static inline int current_is_kswapd(void)* * When a page is migrated from CPU to device, we set the CPU page table entry * to a special SWP_DEVICE_* entry. +* +* When a page is mapped by the device for exclusive access we set the CPU page +* table entries to special SWP_DEVICE_EXCLUSIVE_* entries. */ #ifdef CONFIG_DEVICE_PRIVATE -#define SWP_DEVICE_NUM 2 +#define SWP_DEVICE_NUM 4#define SWP_DEVICE_WRITE (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM)#define SWP_DEVICE_READ (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+1) +#define SWP_DEVICE_EXCLUSIVE_WRITE (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+2) +#define SWP_DEVICE_EXCLUSIVE_READ (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+3)#else #define SWP_DEVICE_NUM 0#endif
include/linux/swapops.h +43 -1
62 lines not on this page · raw message
mm/hmm.c +5 -0
@@ -26,6 +26,8 @@#include <linux/mmu_notifier.h> #include <linux/memory_hotplug.h> +#include "internal.h" +struct hmm_vma_walk {struct hmm_range *range;unsigned long last;
@@ -271,6 +273,9 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,if (!non_swap_entry (entry)) goto fault; +if (is_device_exclusive_entry (entry)) + goto fault; +if (is_migration_entry (entry)) {pte_unmap (ptep); hmm_vma_walk->last = addr;
mm/memory.c +124 -4
177 lines not on this page · raw message
mm/mprotect.c +8 -0
@@ -165,6 +165,14 @@ static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd, newpte =swp_entry_to_pte (entry);if (pte_swp_uffd_wp (oldpte)) newpte =pte_swp_mkuffd_wp (newpte); + }else if (is_writable_device_exclusive_entry (entry)) { + entry =make_readable_device_exclusive_entry ( +swp_offset (entry)); + newpte =swp_entry_to_pte (entry); +if (pte_swp_soft_dirty (oldpte)) + newpte =pte_swp_mksoft_dirty (newpte); +if (pte_swp_uffd_wp (oldpte)) + newpte =pte_swp_mkuffd_wp (newpte); }else { newpte = oldpte; }
mm/page_vma_mapped.c +6 -3
@@ -41,7 +41,8 @@ static bool map_pte(struct page_vma_mapped_walk *pvmw)/* Handle un-addressable ZONE_DEVICE memory */ entry =pte_to_swp_entry (*pvmw->pte); -if (!is_device_private_entry (entry)) +if (!is_device_private_entry (entry) && + !is_device_exclusive_entry (entry))return false; }else if (!pte_present (*pvmw->pte))return false;
@@ -93,7 +94,8 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)return false; entry =pte_to_swp_entry (*pvmw->pte); -if (!is_migration_entry (entry)) +if (!is_migration_entry (entry) && + !is_device_exclusive_entry (entry))return false; pfn =swp_offset (entry);
@@ -102,7 +104,8 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)/* Handle un-addressable ZONE_DEVICE memory */ entry =pte_to_swp_entry (*pvmw->pte); -if (!is_device_private_entry (entry)) +if (!is_device_private_entry (entry) && + !is_device_exclusive_entry (entry))return false; pfn =swp_offset (entry);
mm/rmap.c +186 -0
192 lines not on this page · raw message
Signature · 1 line
2.20.1
Andrew Morton
maintainerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 26 lines · On Mon, 24 May 2021 23:27:22 +1000 Alistair Popple <[email protected]> wrote:
Some devices require exclusive write access to shared virtual memory (SVM) ranges to perform atomic operations on that memory. This requires CPU page tables to be updated to deny access whilst atomic operations are occurring.
In order to do this introduce a new swap entry
type (SWP_DEVICE_EXCLUSIVE). When a SVM range needs to be marked for exclusive access by a device all page table mappings for the particular range are replaced with device exclusive swap entries. This causes any CPU access to the page to result in a fault.Faults are resovled by replacing the faulting entry with the original mapping. This results in MMU notifiers being called which a driver uses to update access permissions such as revoking atomic access. After notifiers have been called the device will no longer have exclusive access to the region.
Walking of the page tables to find the target pages is handled by
get_user_pages()rather than a direct page table walk. A direct page table walk similar to what migrate_vma_collect()/unmap() does could also have been utilised. However this resulted in more code similar in functionality to whatget_user_pages()provides as page faulting is required to make the PTEs present and to break COW....
Quoted 12 lines
Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)
This is quite a lot of code added to core MM for a single driver.
Is there any expectation that other drivers will use this code?
Is there a way of reducing the impact (code size, at least) for systems which don't need this code?
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
Thanks.
_______________________________________________
Nouveau mailing list
[email protected]
https://lists.freedesktop.org/mailman/listinfo/nouveau
Re: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 15 lines · Andrew Morton
Quoted 14 lines
...
Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)
Quoted 3 lines
This is quite a lot of code added to core MM for a single driver.
Is there any expectation that other drivers will use this code?
Yes! This should work for GPUs (and potentially, other devices) that support OpenCL SVM atomic accesses on the device. I haven't looked into how amdgpu works in any detail, but that's certainly at the top of the list of likely additional callers.
Quoted 3 lines
Is there a way of reducing the impact (code size, at least) for systems which don't need this code?
I'll leave this question to others for the moment, in order to answer the "do we need it at all" points.
Quoted 5 lines
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
So this is interesting. Right now, OpenCL support in Nouveau is rather new and so probably not a huge impact yet. However, we've built up enough experience with CUDA and OpenCL to learn that atomic operations, as part of the user space programming model, are a super big deal. Atomic operations are so useful and important that I'd expect many OpenCL SVM users to be uninterested in programming models that lack atomic operations for GPU compute programs.
Again, this doesn't rule out future, non-GPU accelerator devices that may come along.
Atomic ops are just a really important piece of high-end multi-threaded programming, it turns out. So this is the beginning of support for an important building block for general purpose programming on devices that have GPU-like memory models.
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Alistair Popple
reviewerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 15 lines · AEST John Hubbard
Quoted 14 lines · Andrew Morton
Quoted 13 lines
...
Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)
Quoted 11 lines
Quoted 3 lines
This is quite a lot of code added to core MM for a single driver.
Is there any expectation that other drivers will use this code?
Yes! This should work for GPUs (and potentially, other devices) that support OpenCL SVM atomic accesses on the device. I haven't looked into how amdgpu works in any detail, but that's certainly at the top of the list of likely additional callers.
Quoted 2 lines
Is there a way of reducing the impact (code size, at least) for systems which don't need this code?
All of the code added to mm/rmap.c is specific to implementing this feature and not depended on by other core MM code so could be put behind something like CONFIG_DEVICE_PRIVATE to reduce the code size impact (I realise now it currently isn't but should be).
The impact on compiled code size in mm/memory.c also ends up being minimised by the compiler because all of it is of the form:
if (is_device_exclusive_entry(...)) {
[...]
}Meaning it should get thrown away when the feature is not configured given is_device_exclusive_entry() is a static inline always returning false in that case.
Quoted 25 lines
I'll leave this question to others for the moment, in order to answer the "do we need it at all" points.
Quoted 3 lines
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
So this is interesting. Right now, OpenCL support in Nouveau is rather new and so probably not a huge impact yet. However, we've built up enough experience with CUDA and OpenCL to learn that atomic operations, as part of the user space programming model, are a super big deal. Atomic operations are so useful and important that I'd expect many OpenCL SVM users to be uninterested in programming models that lack atomic operations for GPU compute programs.
Again, this doesn't rule out future, non-GPU accelerator devices that may come along.
Atomic ops are just a really important piece of high-end multi-threaded programming, it turns out. So this is the beginning of support for an important building block for general purpose programming on devices that have GPU-like memory models.
thanks,
Re: [PATCH v9 07/10] mm: Device exclusive memory access
In reply to Andrew MortonQuoted 43 lines · Andrew Morton
Quoted 26 lines · On Mon, 24 May 2021 23:27:22 +1000 Alistair Popple <[email protected]> wrote:
Some devices require exclusive write access to shared virtual memory (SVM) ranges to perform atomic operations on that memory. This requires CPU page tables to be updated to deny access whilst atomic operations are occurring.
In order to do this introduce a new swap entry
type (SWP_DEVICE_EXCLUSIVE). When a SVM range needs to be marked for exclusive access by a device all page table mappings for the particular range are replaced with device exclusive swap entries. This causes any CPU access to the page to result in a fault.Faults are resovled by replacing the faulting entry with the original mapping. This results in MMU notifiers being called which a driver uses to update access permissions such as revoking atomic access. After notifiers have been called the device will no longer have exclusive access to the region.
Walking of the page tables to find the target pages is handled by
get_user_pages()rather than a direct page table walk. A direct page table walk similar to what migrate_vma_collect()/unmap() does could also have been utilised. However this resulted in more code similar in functionality to whatget_user_pages()provides as page faulting is required to make the PTEs present and to break COW....
Quoted 12 lines
Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)This is quite a lot of code added to core MM for a single driver.
Quoted 8 lines
Is there any expectation that other drivers will use this code?
Is there a way of reducing the impact (code size, at least) for systems which don't need this code?
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
That is a very good question! I've not reviewed the code, but a sample program with the described use case would make things easy to parse. I suspect that is not easy to build at the moment?
I wonder how we co-ordinate all the work the mm is doing, page migration, reclaim with device exclusive access? Do we have any numbers for the worst case page fault latency when something is marked away for exclusive access? I presume for now this is anonymous memory only? SWP_DEVICE_EXCLUSIVE would only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the page is used by the GPU.
Balbir Singh.
Re: [PATCH v9 07/10] mm: Device exclusive memory access
On 5/25/21 4:51 AM, Balbir Singh wrote:
...
Quoted 8 lines
Quoted 3 lines
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
That is a very good question! I've not reviewed the code, but a sample program with the described use case would make things easy to parse. I suspect that is not easy to build at the moment?
The cover letter says this:
This has been tested with upstream Mesa 21.1.0 and a simple OpenCL program which checks that GPU atomic accesses to system memory are atomic. Without this series the test fails as there is no way of write-protecting the page mapping which results in the device clobbering CPU writes. For reference the test is available at https://ozlabs.org/~apopple/opencl_svm_atomics/
Further testing has been performed by adding support for testing exclusive access to the hmm-tests kselftests.
...so that seems to cover the "sample program" request, at least.
Quoted 3 lines
I wonder how we co-ordinate all the work the mm is doing, page migration, reclaim with device exclusive access? Do we have any numbers for the worst case page fault latency when something is marked away for exclusive access?
CPU page fault latency is approximately "terrible", if a page is resident on the GPU. We have to spin up a DMA engine on the GPU and have it copy the page over the PCIe bus, after all.
Quoted 1 line
I presume for now this is anonymous memory only?
SWP_DEVICE_EXCLUSIVEwould
Yes, for now.
Quoted 7 lines
only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the page is used by the GPU.
I'm not sure that that is *necessarily* something we can conclude. It depends upon access patterns of each program. For example, a "reduction" parallel program sends over lots of data to the GPU, and only a tiny bit of (reduced!) data comes back to the CPU. In that case, freeing the physical page on the CPU is actually the best decision for the OS to make (if the OS is sufficiently prescient).
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Alistair Popple
reviewerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 19 lines · AEST John Hubbard
On 5/25/21 4:51 AM, Balbir Singh wrote:
...Quoted 7 lines
Quoted 3 lines
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
That is a very good question! I've not reviewed the code, but a sample program with the described use case would make things easy to parse. I suspect that is not easy to build at the moment?
The cover letter says this:
This has been tested with upstream Mesa 21.1.0 and a simple OpenCL program which checks that GPU atomic accesses to system memory are atomic. Without this series the test fails as there is no way of write-protecting the page mapping which results in the device clobbering CPU writes. For reference the test is available at https://ozlabs.org/~apopple/opencl_svm_atomics/
Quoted 4 lines
Further testing has been performed by adding support for testing exclusive access to the hmm-tests kselftests.
...so that seems to cover the "sample program" request, at least.
It is also sufficiently easy to build, assuming of course you have the appropriate Mesa/LLVM/OpenCL libraries installed :-)
If you are interested I have some scripts which may help with building Mesa, etc. Not that that is especially hard either, it's just there are a couple of different dependencies required.
Quoted 8 lines
Quoted 4 lines
I wonder how we co-ordinate all the work the mm is doing, page migration, reclaim with device exclusive access? Do we have any numbers for the worst case page fault latency when something is marked away for exclusive access?
CPU page fault latency is approximately "terrible", if a page is resident on the GPU. We have to spin up a DMA engine on the GPU and have it copy the page over the PCIe bus, after all.
Although for clarity that describes latency for CPU faults to device private pages which are always resident on the GPU. A CPU fault to a page being exclusively accessed will be slightly less terrible as it only requires the GPU MMU/TLB mappings to be taken down in much the same as for any other MMU notifier callback as the page is mapped by the GPU rather than resident there.
Quoted 22 lines
Quoted 2 lines
I presume for now this is anonymous memory only?
SWP_DEVICE_EXCLUSIVEwouldYes, for now.
Quoted 8 lines
only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the>
page is used by the GPU.I'm not sure that that is *necessarily* something we can conclude. It depends upon access patterns of each program. For example, a "reduction" parallel program sends over lots of data to the GPU, and only a tiny bit of (reduced!) data comes back to the CPU. In that case, freeing the physical page on the CPU is actually the best decision for the OS to make (if the OS is sufficiently prescient).
thanks,
Re: [PATCH v9 07/10] mm: Device exclusive memory access
In reply to John HubbardQuoted 19 lines · John Hubbard
On 5/25/21 4:51 AM, Balbir Singh wrote:
...Quoted 8 lines
Quoted 3 lines
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
That is a very good question! I've not reviewed the code, but a sample program with the described use case would make things easy to parse. I suspect that is not easy to build at the moment?
The cover letter says this:
This has been tested with upstream Mesa 21.1.0 and a simple OpenCL program which checks that GPU atomic accesses to system memory are atomic. Without this series the test fails as there is no way of write-protecting the page mapping which results in the device clobbering CPU writes. For reference the test is available at https://ozlabs.org/~apopple/opencl_svm_atomics/
Quoted 4 lines
Further testing has been performed by adding support for testing exclusive access to the hmm-tests kselftests.
...so that seems to cover the "sample program" request, at least.
Thanks, I'll take a look
Quoted 21 lines
Quoted 4 lines
I wonder how we co-ordinate all the work the mm is doing, page migration, reclaim with device exclusive access? Do we have any numbers for the worst case page fault latency when something is marked away for exclusive access?
CPU page fault latency is approximately "terrible", if a page is resident on the GPU. We have to spin up a DMA engine on the GPU and have it copy the page over the PCIe bus, after all.
Quoted 1 line
I presume for now this is anonymous memory only?
SWP_DEVICE_EXCLUSIVEwouldYes, for now.
Quoted 7 lines
only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the page is used by the GPU.
Quoted 6 lines
I'm not sure that that is *necessarily* something we can conclude. It depends upon access patterns of each program. For example, a "reduction" parallel program sends over lots of data to the GPU, and only a tiny bit of (reduced!) data comes back to the CPU. In that case, freeing the physical page on the CPU is actually the best decision for the OS to make (if the OS is sufficiently prescient).
With a shared device or a device exclusive range, it would be good to get the device usage pattern and update the mm with that knowledge, so that the LRU can be better maintained. With your comment you seem to suggest that a page used by the GPU might be a good candidate for reclaim based on the CPU's understanding of the age of the page should not account for use by the device
(are GPU workloads - access once and discard?)
Balbir Singh.
Re: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 55 lines · Balbir Singh
Quoted 19 lines · John Hubbard
On 5/25/21 4:51 AM, Balbir Singh wrote:
...Quoted 8 lines
Quoted 3 lines
How beneficial is this code to nouveau users? I see that it permits a part of OpenCL to be implemented, but how useful/important is this in the real world?
That is a very good question! I've not reviewed the code, but a sample program with the described use case would make things easy to parse. I suspect that is not easy to build at the moment?
The cover letter says this:
This has been tested with upstream Mesa 21.1.0 and a simple OpenCL program which checks that GPU atomic accesses to system memory are atomic. Without this series the test fails as there is no way of write-protecting the page mapping which results in the device clobbering CPU writes. For reference the test is available at https://ozlabs.org/~apopple/opencl_svm_atomics/
Quoted 4 lines
Further testing has been performed by adding support for testing exclusive access to the hmm-tests kselftests.
...so that seems to cover the "sample program" request, at least.
Thanks, I'll take a look
Quoted 21 lines
Quoted 4 lines
I wonder how we co-ordinate all the work the mm is doing, page migration, reclaim with device exclusive access? Do we have any numbers for the worst case page fault latency when something is marked away for exclusive access?
CPU page fault latency is approximately "terrible", if a page is resident on the GPU. We have to spin up a DMA engine on the GPU and have it copy the page over the PCIe bus, after all.
Quoted 1 line
I presume for now this is anonymous memory only?
SWP_DEVICE_EXCLUSIVEwouldYes, for now.
Quoted 7 lines
only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the page is used by the GPU.Quoted 6 lines
I'm not sure that that is *necessarily* something we can conclude. It depends upon access patterns of each program. For example, a "reduction" parallel program sends over lots of data to the GPU, and only a tiny bit of (reduced!) data comes back to the CPU. In that case, freeing the physical page on the CPU is actually the best decision for the OS to make (if the OS is sufficiently prescient).
Quoted 6 lines
With a shared device or a device exclusive range, it would be good to get the device usage pattern and update the mm with that knowledge, so that the LRU can be better maintained. With your comment you seem to suggest that a page used by the GPU might be a good candidate for reclaim based on the CPU's understanding of the age of the page should not account for use by the device
(are GPU workloads - access once and discard?)
Hmm, besides the aging info, this reminded me: do we need to isolate the page from lru too when marking device exclusive access?
Afaict the current patch didn't do that so I think it's reclaimable. If we still have the rmap then we'll get a mmu notify CLEAR when unmapping that special pte, so device driver should be able to drop the ownership. However we dropped the rmap when marking exclusive. Now I don't know whether and how it'll work if page reclaim runs with the page being exclusively owned if without isolating the page..
Signature · 1 line
Peter Xu
Alistair Popple
reviewerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 78 lines · AEST Peter Xu
External email: Use caution opening links or attachments
Quoted 64 lines · Balbir Singh
Quoted 24 lines · John Hubbard
On 5/25/21 4:51 AM, Balbir Singh wrote:
...Quoted 9 lines
Quoted 5 lines
How beneficial is this code to nouveau users? I see that it permits a
part of OpenCL to be implemented, but how useful/important is this in
the real world?That is a very good question! I've not reviewed the code, but a sample program with the described use case would make things easy to parse. I suspect that is not easy to build at the moment?
The cover letter says this:
This has been tested with upstream Mesa 21.1.0 and a simple OpenCL program
which checks that GPU atomic accesses to system memory are atomic. Without
this series the test fails as there is no way of write-protecting the page
mapping which results in the device clobbering CPU writes. For reference the test is available at https://ozlabs.org/~apopple/opencl_svm_atomics/Quoted 5 lines
Further testing has been performed by adding support for testing exclusive
access to the hmm-tests kselftests....so that seems to cover the "sample program" request, at least.
Thanks, I'll take a look
Quoted 25 lines
Quoted 6 lines
I wonder how we co-ordinate all the work the mm is doing, page migration,
reclaim with device exclusive access? Do we have any numbers for the worst
case page fault latency when something is marked away for exclusive access?CPU page fault latency is approximately "terrible", if a page is resident on the GPU. We have to spin up a DMA engine on the GPU and have it copy the page over the PCIe bus, after all.
Quoted 2 lines
I presume for now this is anonymous memory only?
SWP_DEVICE_EXCLUSIVEwouldYes, for now.
Quoted 8 lines
only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the> > >
page is used by the GPU.Quoted 6 lines
I'm not sure that that is *necessarily* something we can conclude. It depends upon access patterns of each program. For example, a "reduction" parallel program sends over lots of data to the GPU, and only a tiny bit of (reduced!) data comes back to the CPU. In that case, freeing the physical page on the CPU is actually the best decision for the OS to make (if the OS is sufficiently prescient).>
Quoted 7 lines
With a shared device or a device exclusive range, it would be good to get the device usage pattern and update the mm with that knowledge, so that the LRU can be better maintained. With your comment you seem to suggest that a page used by the GPU might be a good candidate for reclaim based on the CPU's understanding of the age of the page should not account for use by the device
(are GPU workloads - access once and discard?)Hmm, besides the aging info, this reminded me: do we need to isolate the page from lru too when marking device exclusive access?
Quoted 6 lines
Afaict the current patch didn't do that so I think it's reclaimable. If we still have the rmap then we'll get a mmu notify CLEAR when unmapping that special pte, so device driver should be able to drop the ownership. However we dropped the rmap when marking exclusive. Now I don't know whether and how it'll work if page reclaim runs with the page being exclusively owned if without isolating the page..
Reclaim won't run on the page due to the extra references from the special swap entries.
Quoted 2 lines
--
Peter Xu
Re: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 2 lines · Alistair Popple
Reclaim won't run on the page due to the extra references from the special swap entries.
That sounds reasonable, but I didn't find the point that stops it, probably due to my limited knowledge on the reclaim code. Could you elaborate?
Signature · 1 line
Peter Xu
Alistair Popple
reviewerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 8 lines · AEST Peter Xu
External email: Use caution opening links or attachments
Quoted 2 lines · Alistair Popple
Reclaim won't run on the page due to the extra references from the special swap entries.
That sounds reasonable, but I didn't find the point that stops it, probably due to my limited knowledge on the reclaim code. Could you elaborate?
Sure, it isn't immediately obvious but it ends up being detected at the start of is_page_cache_freeable() in the pageout code:
static pageout_t pageout(struct page *page, struct address_space *mapping) {
[...]
if (!is_page_cache_freeable(page)) return PAGE_KEEP;
- Alistair
Quoted 2 lines
--
Peter Xu
Re: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 14 lines · Alistair Popple
Quoted 8 lines · AEST Peter Xu
External email: Use caution opening links or attachments
Quoted 2 lines · Alistair Popple
Reclaim won't run on the page due to the extra references from the special swap entries.
That sounds reasonable, but I didn't find the point that stops it, probably due to my limited knowledge on the reclaim code. Could you elaborate?
Sure, it isn't immediately obvious but it ends up being detected at the start of
is_page_cache_freeable()in the pageout code:
Quoted 7 lines
static
pageout_tpageout(struct page *page, struct address_space *mapping){[...]
if (!is_page_cache_freeable(page)) return PAGE_KEEP;
I did look at pageout() but still missed this small helper indeed (while it's so important to know..), thanks!
Signature · 1 line
Peter Xu
Re: [PATCH v9 07/10] mm: Device exclusive memory access
In reply to Balbir SinghOn 6/2/21 1:50 AM, Balbir Singh wrote:
...
Quoted 15 lines
Quoted 8 lines
Quoted 7 lines
only impact the address space of programs using the GPU. Should the exclusively marked range live in the unreclaimable list and recycled back to active/in-active to account for the fact that
1. It is not reclaimable and reclaim will only hurt via page faults?
2. It ages the page correctly or at-least allows for that possibility when the page is used by the GPU.Quoted 6 lines
I'm not sure that that is *necessarily* something we can conclude. It depends upon access patterns of each program. For example, a "reduction" parallel program sends over lots of data to the GPU, and only a tiny bit of (reduced!) data comes back to the CPU. In that case, freeing the physical page on the CPU is actually the best decision for the OS to make (if the OS is sufficiently prescient).
Quoted 2 lines
With a shared device or a device exclusive range, it would be good to get the device usage pattern and update the mm with that knowledge, so that the LRU can be better
Integrating a GPU (or "device") processor and it's mm behavior with the Linux kernel is always an interesting concept. Certainly worth exploring, although it's probably not a small project by any means.
Quoted 5 lines
maintained. With your comment you seem to suggest that a page used by the GPU might be a good candidate for reclaim based on the CPU's understanding of the age of the page should not account for use by the device
(are GPU workloads - access once and discard?)
Well, that's a little too narrow of an interpretation. The GPU is a fairly general purpose processor, and so it has all kinds of workloads. I'm trying to discourage any hopes that one can know, in advance, precisely how the GPU's pages need to be managed. It's similar to the the CPU, in that regard. My example was just one, out of a vast pool of possible behaviors.
thanks,
Signature · 2 lines
John Hubbard NVIDIA
Re: [PATCH v9 07/10] mm: Device exclusive memory access
In reply to Alistair PoppleQuoted 117 lines · Alistair Popple
Some devices require exclusive write access to shared virtual memory (SVM) ranges to perform atomic operations on that memory. This requires CPU page tables to be updated to deny access whilst atomic operations are occurring.
In order to do this introduce a new swap entry
type (SWP_DEVICE_EXCLUSIVE). When a SVM range needs to be marked for exclusive access by a device all page table mappings for the particular range are replaced with device exclusive swap entries. This causes any CPU access to the page to result in a fault.Faults are resovled by replacing the faulting entry with the original mapping. This results in MMU notifiers being called which a driver uses to update access permissions such as revoking atomic access. After notifiers have been called the device will no longer have exclusive access to the region.
Walking of the page tables to find the target pages is handled by
get_user_pages()rather than a direct page table walk. A direct page table walk similar to what migrate_vma_collect()/unmap() does could also have been utilised. However this resulted in more code similar in functionality to whatget_user_pages()provides as page faulting is required to make the PTEs present and to break COW.Signed-off-by Alistair Popple <[email protected]>Reviewed-by Christoph Hellwig <[email protected]>---
v9:
* Split rename ofmigrate_pgmap_ownerinto a separate patch.
* Added comments explaining SWP_DEVICE_EXCLUSIVE_* entries.
* Renamed try_to_protect{_one} to page_make_device_exclusive{_one} based somewhat on a suggestion from Peter Xu. I was never particularly happy withtry_to_protect()as a name so think this is better.
* Removed unneccesary code and reworded some comments based on feedback from Peter Xu.
* Removed the VMA walk when restoring PTEs for device-exclusive entries.
* Simplified implementation ofcopy_pte_range()to fail if the page cannot be locked. This might lead to occasionalfork()failures but at this stage we don't think that will be an issue.v8:
* Remove device exclusive entries on fork rather than copy them.v7:
* Added Christoph's Reviewed-by.
* Minor cosmetic cleanups suggested by Christoph.
* Replace mmu_notifier_range_init_migrate/exclusive withmmu_notifier_range_init_owneras suggested by Christoph.
* Replacedlock_page()withlock_page_retry()when handling faults.
* Restrict to anonymous pages for now.v6:
* Fixed a bisectablity issue due to incorrectly applying the rename ofmigrate_pgmap_ownerto the wrong patches for Nouveau andhmm_test.v5:
* Renamedrange->migrate_pgmap_ownertorange->owner.
* AddedMMU_NOTIFY_EXCLUSIVEto allow passing of a driver cookie which allows notifiers called as a result ofmake_device_exclusive_range()to be ignored.
* Added a check totry_to_protect_one()to detect if the pages originally returned fromget_user_pages()have been unmapped or not.
* Removedcheck_device_exclusive_range()as it is no longer required with the other changes.
* Documentation update.v4:
* Add function to check that mappings are still valid and exclusive.
* s/long/unsigned long/ inmake_device_exclusive_entry().--- Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)Documentation/vm/hmm.rst +17
-0@@ -405,6 +405,23 @@ between device driver specific code and shared common code: The lock can now be released. +Exclusive access memory +======================= + +Some devices have features such as atomic PTE bits that can be used to implement +atomic access to system memory. To support atomic operations to a shared virtual +memory page such a device needs access to that page which is exclusive of any +userspace access from the CPU. The ``make_device_exclusive_range()`` function +can be used to make a memory range inaccessible from userspace. + +This replaces all mappings for pages in the given range with special swap +entries. Any attempt to access the swap entry results in a fault which is +resovled by replacing the entry with the original mapping. A driver gets +notified that the mapping has been changed by MMU notifiers, after which point +it will no longer have exclusive access to the page. Exclusive access is +guranteed to last until the driver drops the page lock and page reference, at +which point any CPU faults on the page may proceed as described. + Memory cgroup (memcg) and rss accounting ========================================include/linux/mmu_notifier.h +0
-0
include/linux/mmu_notifier.h struct mmu_interval_notifier; · line 49
Quoted 8 lines
include/linux/mmu_notifier.h +5
-0@@ -42,6 +42,11 @@ struct mmu_interval_notifier;* @MMU_NOTIFY_MIGRATE: used during migrate_vma_collect() invalidate to signal * a device driver to possibly ignore the invalidation if the * owner field matches the driver's device private pgmap owner. +* +* @MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will no +* longer have exclusive access to the page. May ignore the invalidation that's +* part of make_device_exclusive_range() if the owner field +* matches the value passed to make_device_exclusive_range().
Perhaps s/matches/does not match/?
Quoted 31 lines
include/linux/mmu_notifier.h +1
-0*/ enum mmu_notifier_event {MMU_NOTIFY_UNMAP =0 ,@@ -51,6 +56,7 @@ enum mmu_notifier_event {MMU_NOTIFY_SOFT_DIRTY ,MMU_NOTIFY_RELEASE ,MMU_NOTIFY_MIGRATE , + MMU_NOTIFY_EXCLUSIVE, };#define MMU_NOTIFIER_RANGE_BLOCKABLE (1 << 0)include/linux/rmap.h +4
-0@@ -193,6 +193,10 @@ int page_referenced(struct page *, int is_locked,bool try_to_migrate (struct page *page,enum ttu_flags flags);bool try_to_unmap (struct page *,enum ttu_flags flags); +int make_device_exclusive_range (struct mm_struct *mm,unsigned long start, +unsigned long end,struct page **pages, +void *arg); +/* Avoid racy checks */ #define PVMW_SYNC (1 << 0)/* Look for migarion entries rather than present PTEs */ include/linux/swap.h +0
-0
include/linux/swap.h static inline int current_is_kswapd(void) · line 65
Quoted 3 lines
include/linux/swap.h +0
-0@@ -63,11 +63,16 @@ static inline int current_is_kswapd(void)* * When a page is migrated from CPU to device, we set the CPU page table entry * to a special SWP_DEVICE_* entry.
s/SWP_DEVICE_*/SWP_DEVICE_{READ|WRITE}/? Since SWP_DEVICE_* covers all four too.
Quoted 151 lines
include/linux/swap.h +6
-1+* +* When a page is mapped by the device for exclusive access we set the CPU page +* table entries to special SWP_DEVICE_EXCLUSIVE_* entries. */ #ifdef CONFIG_DEVICE_PRIVATE -#define SWP_DEVICE_NUM 2 +#define SWP_DEVICE_NUM 4#define SWP_DEVICE_WRITE (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM)#define SWP_DEVICE_READ (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+1) +#define SWP_DEVICE_EXCLUSIVE_WRITE (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+2) +#define SWP_DEVICE_EXCLUSIVE_READ (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+3)#else #define SWP_DEVICE_NUM 0#endif include/linux/swapops.h +43
-1@@ -120,6 +120,27 @@ static inline bool is_writable_device_private_entry(swp_entry_t entry) {return unlikely (swp_type (entry) ==SWP_DEVICE_WRITE ); } + +static inline swp_entry_t make_readable_device_exclusive_entry (pgoff_t offset) +{ +return swp_entry (SWP_DEVICE_EXCLUSIVE_READ , offset); +} + +static inline swp_entry_t make_writable_device_exclusive_entry (pgoff_t offset) +{ +return swp_entry (SWP_DEVICE_EXCLUSIVE_WRITE , offset); +} + +static inline bool is_device_exclusive_entry (swp_entry_t entry) +{ +return swp_type (entry) ==SWP_DEVICE_EXCLUSIVE_READ || +swp_type (entry) ==SWP_DEVICE_EXCLUSIVE_WRITE ; +} + +static inline bool is_writable_device_exclusive_entry (swp_entry_t entry) +{ +return unlikely (swp_type (entry) ==SWP_DEVICE_EXCLUSIVE_WRITE ); +}#else /* CONFIG_DEVICE_PRIVATE */ static inline swp_entry_t make_readable_device_private_entry (pgoff_t offset) {@@ -140,6 +161,26 @@ static inline bool is_writable_device_private_entry(swp_entry_t entry) {return false; } + +static inline swp_entry_t make_readable_device_exclusive_entry (pgoff_t offset) +{ +return swp_entry (0 ,0 ); +} + +static inline swp_entry_t make_writable_device_exclusive_entry (pgoff_t offset) +{ +return swp_entry (0 ,0 ); +} + +static inline bool is_device_exclusive_entry (swp_entry_t entry) +{ +return false; +} + +static inline bool is_writable_device_exclusive_entry (swp_entry_t entry) +{ +return false; +}#endif /* CONFIG_DEVICE_PRIVATE */ #ifdef CONFIG_MIGRATION @@ -219,7 +260,8 @@ static inline struct page *pfn_swap_entry_to_page(swp_entry_t entry)*/ static inline bool is_pfn_swap_entry (swp_entry_t entry) { -return is_migration_entry (entry) ||is_device_private_entry (entry); +return is_migration_entry (entry) ||is_device_private_entry (entry) || +is_device_exclusive_entry (entry); }struct page_vma_mapped_walk ;mm/hmm.c +5
-0@@ -26,6 +26,8 @@#include <linux/mmu_notifier.h> #include <linux/memory_hotplug.h> +#include "internal.h" +struct hmm_vma_walk {struct hmm_range *range;unsigned long last;@@ -271,6 +273,9 @@ static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,if (!non_swap_entry (entry)) goto fault; +if (is_device_exclusive_entry (entry)) + goto fault; +if (is_migration_entry (entry)) {pte_unmap (ptep); hmm_vma_walk->last = addr;mm/memory.c +37
-0@@ -700,6 +700,68 @@ struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr, }#endif +static void restore_exclusive_pte (struct vm_area_struct *vma, +struct page *page,unsigned long address, +pte_t *ptep) +{ +pte_t pte; +swp_entry_t entry; + + pte =pte_mkold (mk_pte (page,READ_ONCE (vma->vm_page_prot))); +if (pte_swp_soft_dirty (*ptep)) + pte =pte_mksoft_dirty (pte); + + entry =pte_to_swp_entry (*ptep); +if (pte_swp_uffd_wp (*ptep)) + pte =pte_mkuffd_wp (pte); +else if (is_writable_device_exclusive_entry (entry)) + pte =maybe_mkwrite (pte_mkdirty (pte), vma); + +set_pte_at (vma->vm_mm, address, ptep, pte); + +/* +* No need to take a page reference as one was already +* created when the swap entry was made. +*/ +if (PageAnon (page)) +page_add_anon_rmap (page, vma, address, false); +else +/* +* Currently device exclusive access only supports anonymous +* memory so the entry shouldn't point to a filebacked page. +*/ +WARN_ON_ONCE (!PageAnon (page)); + +if (vma->vm_flags &VM_LOCKED ) +mlock_vma_page (page); + +/* +* No need to invalidate - it was non-present before. However
mm/memory.c struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr, · line 749
Quoted 10 lines
mm/memory.c +10
-0+* secondary CPUs may have mappings that need invalidating. +*/ +update_mmu_cache (vma, address, ptep); +} + +/* +* Tries to restore an exclusive pte if the page lock can be acquired without +* sleeping. +*/ +static unsigned long
Better return a int?
mm/memory.c struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr, · line 751
Quoted 2 lines
mm/memory.c +2
-0+try_restore_exclusive_pte(struct mm_struct *src_mm, pte_t *src_pte, + struct vm_area_struct *vma, unsigned long addr)
Raised in the other thread too: src_mm can be dropped.
Quoted 42 lines
mm/memory.c +28
-0+{ +swp_entry_t entry =pte_to_swp_entry (*src_pte); +struct page *page =pfn_swap_entry_to_page (entry); + +if (trylock_page (page)) { +restore_exclusive_pte (vma, page, addr, src_pte); +unlock_page (page); +return 0 ; + } + +return -EBUSY ; +} +/* * copy one vm_area from one task to the other. Assumes the page tables * already present in the new task to be cleared in the whole range @@ -781,6 +843,17 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct mm_struct *src_mm, pte =pte_swp_mkuffd_wp (pte);set_pte_at (src_mm, addr, src_pte, pte); } + }else if (is_device_exclusive_entry (entry)) { +/* +* Make device exclusive entries present by restoring the +* original entry then copying as for a present pte. Device +* exclusive entries currently only support private writable +* (ie. COW) mappings. +*/ +VM_BUG_ON (!is_cow_mapping (vma->vm_flags)); +if (try_restore_exclusive_pte (src_mm, src_pte, vma, addr)) +return -EBUSY ; +return -ENOENT ; }set_pte_at (dst_mm, addr, dst_pte, pte);return 0 ;@@ -980,9 +1053,18 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma,if (ret == -EAGAIN ) { entry =pte_to_swp_entry (*src_pte);break ; + }else if (ret == -EBUSY ) { +break ; + }else if (!ret) { + progress +=8 ;
mm/memory.c copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, · line 1067
Quoted 10 lines
mm/memory.c +7
-2+continue ; } - progress +=8 ; -continue ; + +/* +* Device exclusive entry restored, continue by copying +* the now present pte. +*/ +WARN_ON_ONCE (ret != -ENOENT );
The change looks right, thanks. It's just that we should start to consider document all these err code now in copy_pte_range() some day (perhaps on top of this patch)..
Quoted 43 lines
mm/memory.c +13
-2}/* copy_present_pte() will clear `*prealloc' if consumed */ ret = copy_present_pte(dst_vma, src_vma, dst_pte, src_pte,@@ -1019,6 +1101,8 @@ copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, goto out; } entry.val =0 ; + }else if (ret == -EBUSY ) { +return -EBUSY ; }else if (ret) {WARN_ON_ONCE (ret != -EAGAIN ); prealloc =page_copy_prealloc (src_mm, src_vma, addr);@@ -1283,7 +1367,8 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb, } entry =pte_to_swp_entry (ptent); -if (is_device_private_entry (entry)) { +if (is_device_private_entry (entry) || +is_device_exclusive_entry (entry)) {struct page *page =pfn_swap_entry_to_page (entry);if (unlikely (details && details->check_mapping)) {@@ -1299,7 +1384,10 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,pte_clear_not_present_full (mm, addr, pte, tlb->fullmm); rss[mm_counter (page)]--; -page_remove_rmap (page, false); + +if (is_device_private_entry (entry)) +page_remove_rmap (page, false); +put_page (page);continue ; }@@ -3303,6 +3391,35 @@ void unmap_mapping_range(struct address_space *mapping, }EXPORT_SYMBOL (unmap_mapping_range); +/* +* Restore a potential device exclusive pte to a working pte entry +*/ +static vm_fault_t remove_device_exclusive_entry (struct vm_fault *vmf) +{
mm/memory.c void unmap_mapping_range(struct address_space *mapping, · line 3408
Quoted 10 lines
mm/memory.c +10
-0+struct page *page = vmf->page; +struct vm_area_struct *vma = vmf->vma; +vm_fault_t ret =0 ; +struct mmu_notifier_range range; + +if (!lock_page_or_retry (page, vma->vm_mm, vmf->flags)) +return VM_FAULT_RETRY ; +mmu_notifier_range_init (&range,MMU_NOTIFY_CLEAR ,0 , vma, vma->vm_mm, + vmf->address &PAGE_MASK , + (vmf->address &PAGE_MASK ) +PAGE_SIZE );
@MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will no longer have exclusive access to the page.
Shouldn't this be the place to use new MMU_NOTIFY_EXCLUSIVE?
mm/memory.c void unmap_mapping_range(struct address_space *mapping, · line 3420
Quoted 12 lines
mm/memory.c +12
-0+mmu_notifier_invalidate_range_start (&range); + + vmf->pte =pte_offset_map_lock (vma->vm_mm, vmf->pmd, vmf->address, + &vmf->ptl); +if (likely (pte_same (*vmf->pte, vmf->orig_pte))) +restore_exclusive_pte (vma, page, vmf->address, vmf->pte); + +pte_unmap_unlock (vmf->pte, vmf->ptl); +unlock_page (page); + +mmu_notifier_invalidate_range_end (&range); +return ret;
We can drop "ret" and return 0 here directly.
Quoted 91 lines
mm/memory.c +5
-0+} +/* * We enter with non-exclusive mmap_lock (to exclude vma changes, * but allow concurrent faults), and pte mapped but not yet locked. @@ -3330,6 +3447,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)if (is_migration_entry (entry)) {migration_entry_wait (vma->vm_mm, vmf->pmd, vmf->address); + }else if (is_device_exclusive_entry (entry)) { + vmf->page =pfn_swap_entry_to_page (entry); + ret =remove_device_exclusive_entry (vmf); }else if (is_device_private_entry (entry)) { vmf->page =pfn_swap_entry_to_page (entry); ret = vmf->page->pgmap->ops->migrate_to_ram (vmf);mm/mprotect.c +8
-0@@ -165,6 +165,14 @@ static unsigned long change_pte_range(struct vm_area_struct *vma, pmd_t *pmd, newpte =swp_entry_to_pte (entry);if (pte_swp_uffd_wp (oldpte)) newpte =pte_swp_mkuffd_wp (newpte); + }else if (is_writable_device_exclusive_entry (entry)) { + entry =make_readable_device_exclusive_entry ( +swp_offset (entry)); + newpte =swp_entry_to_pte (entry); +if (pte_swp_soft_dirty (oldpte)) + newpte =pte_swp_mksoft_dirty (newpte); +if (pte_swp_uffd_wp (oldpte)) + newpte =pte_swp_mkuffd_wp (newpte); }else { newpte = oldpte; }mm/page_vma_mapped.c +6
-3@@ -41,7 +41,8 @@ static bool map_pte(struct page_vma_mapped_walk *pvmw)/* Handle un-addressable ZONE_DEVICE memory */ entry =pte_to_swp_entry (*pvmw->pte); -if (!is_device_private_entry (entry)) +if (!is_device_private_entry (entry) && + !is_device_exclusive_entry (entry))return false; }else if (!pte_present (*pvmw->pte))return false;@@ -93,7 +94,8 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)return false; entry =pte_to_swp_entry (*pvmw->pte); -if (!is_migration_entry (entry)) +if (!is_migration_entry (entry) && + !is_device_exclusive_entry (entry))return false; pfn =swp_offset (entry);@@ -102,7 +104,8 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)/* Handle un-addressable ZONE_DEVICE memory */ entry =pte_to_swp_entry (*pvmw->pte); -if (!is_device_private_entry (entry)) +if (!is_device_private_entry (entry) && + !is_device_exclusive_entry (entry))return false; pfn =swp_offset (entry);mm/rmap.c +15
-0@@ -2008,6 +2008,192 @@ void page_mlock(struct page *page)rmap_walk (page, &rwc); } +struct make_exclusive_args { +struct mm_struct *mm; +unsigned long address; +void *owner; +bool valid; +}; + +static bool page_make_device_exclusive_one (struct page *page, +struct vm_area_struct *vma,unsigned long address,void *priv) +{ +struct mm_struct *mm = vma->vm_mm; +struct page_vma_mapped_walk pvmw = { + .page = page, + .vma = vma, + .address = address,
mm/rmap.c void page_mlock(struct page *page) · line 2035
Quoted 10 lines
mm/rmap.c +10
-0+ }; +struct make_exclusive_args *args = priv; +pte_t pteval; +struct page *subpage; +bool ret = true; +struct mmu_notifier_range range; +swp_entry_t entry; +pte_t swp_pte; + + mmu_notifier_range_init_owner(&range, MMU_NOTIFY_EXCLUSIVE, 0, vma,
Similar question here, EXCLUSIVE comment says it gets notified when the device does not have exclusive access.
If you prefer to keep using EXCLUSIVE for both mark/restore, then we need to change the comment above MMU_NOTIFY_EXCLUSIVE?
Quoted 5 lines
mm/rmap.c +5
-0+ vma->vm_mm, address,min (vma->vm_end, + address + page_size(page)), args->owner); +mmu_notifier_invalidate_range_start (&range); + +while (page_vma_mapped_walk (&pvmw)) {
mm/rmap.c void page_mlock(struct page *page) · line 2050
Quoted 10 lines
mm/rmap.c +10
-0+/* Unexpected PMD-mapped THP? */ +VM_BUG_ON_PAGE (!pvmw.pte, page); + +if (!pte_present (*pvmw.pte)) { + ret = false; +page_vma_mapped_walk_done (&pvmw); +break ; + } + + subpage = page -page_to_pfn (page) +pte_pfn (*pvmw.pte);
I see that all pages passed in should be done after FOLL_SPLIT_PMD, so is this needed? Or say, should subpage==page always be true?
Quoted 6 lines
mm/rmap.c +6
-0+ address = pvmw.address; + +/* Nuke the page table entry. */ +flush_cache_page (vma, address,pte_pfn (*pvmw.pte)); + pteval =ptep_clear_flush (vma, address, pvmw.pte); +
mm/rmap.c void page_mlock(struct page *page) · line 2066
Quoted 10 lines
mm/rmap.c +10
-0+/* Move the dirty bit to the page. Now the pte is gone. */ +if (pte_dirty (pteval)) +set_page_dirty (page); + +if (arch_unmap_one (mm, vma, address, pteval) <0 ) { +set_pte_at (mm, address, pvmw.pte, pteval); + ret = false; +page_vma_mapped_walk_done (&pvmw); +break ; + }
Didn't notice this previously, but also suggest to drop this.
Two reasons:
1. It's introduced in ca827d55ebaa ("mm, swap: Add infrastructure for saving page metadata on swap", 2018-03-18) for sparc-only use so far. If we really want this, we'll also want to call arch_do_swap_page() when restoring the pte just like what we do in do_swap_page(); NOTE: current code path of SWP_DEVICE_EXCLUSIVE will skip the arch_do_swap_page() in do_swap_page() so it's not even paired with the above arch_unmap_one(), so I believe this won't even work for sparc at all.
2. I highly doubt whether sparc is also on the list of platforms to support for device atomic ops even in the future. IMHO we'd better not copy-paste code clips if never used at all, because once merged, removing it would need more justifications.
Quoted 21 lines
mm/rmap.c +21
-0+ +/* +* Check that our target page is still mapped at the expected +* address. +*/ +if (args->mm == mm && args->address == address && +pte_write (pteval)) + args->valid = true; + +/* +* Store the pfn of the page in a special migration +* pte. do_swap_page() will wait until the migration +* pte is removed and then restart fault handling. +*/ +if (pte_write (pteval)) + entry =make_writable_device_exclusive_entry ( +page_to_pfn (subpage)); +else + entry =make_readable_device_exclusive_entry ( +page_to_pfn (subpage)); + swp_pte =swp_entry_to_pte (entry);
mm/rmap.c void page_mlock(struct page *page) · line 2097
Quoted 10 lines
mm/rmap.c +10
-0+if (pte_soft_dirty (pteval)) + swp_pte =pte_swp_mksoft_dirty (swp_pte); +if (pte_uffd_wp (pteval)) + swp_pte =pte_swp_mkuffd_wp (swp_pte); + +/* Take a reference for the swap entry */ +get_page (page); +set_pte_at (mm, address, pvmw.pte, swp_pte); + +page_remove_rmap (subpage,PageHuge (page));
Why PageHuge()? Should it be a constant "false"?
mm/rmap.c void page_mlock(struct page *page) · line 2098
Quoted 1 line
mm/rmap.c +1
-0+put_page (page);
Should we drop this put_page() along with get_page() above?
page_count() should be >0 anyway as we've got a mapcount before at least when dropping the pte. Then IMHO we can simply keep the old page reference.
mm/rmap.c void page_mlock(struct page *page) · line 2107
Quoted 9 lines
mm/rmap.c +9
-0+ } + +mmu_notifier_invalidate_range_end (&range); + +return ret; +} + +/** +* page_make_device_exclusive - replace page table mappings with swap entries
"with swap entries" looks a bit blurred to me (although below longer comment explains much better). How about below (or something similar):
page_make_device_exclusive - Mark the page exclusively owned by the device
?
It'll also match with comment above make_device_exclusive_range().
No strong opinion.
The rest looks good. Thanks,
Quoted 95 lines
mm/rmap.c +89
-0+* @page: the page to replace page table entries for +* @mm: the mm_struct where the page is expected to be mapped +* @address: address where the page is expected to be mapped +* @owner: passed to MMU_NOTIFY_EXCLUSIVE range notifier callbacks +* +* Tries to remove all the page table entries which are mapping this page and +* replace them with special device exclusive swap entries to grant a device +* exclusive access to the page. Caller must hold the page lock. +* +* Returns false if the page is still mapped, or if it could not be unmapped +* from the expected address. Otherwise returns true (success). +*/ +static bool page_make_device_exclusive (struct page *page,struct mm_struct *mm, +unsigned long address,void *owner) +{ +struct make_exclusive_args args = { + .mm = mm, + .address = address, + .owner = owner, + .valid = false, + }; +struct rmap_walk_control rwc = { + .rmap_one = page_make_device_exclusive_one, + .done = page_not_mapped, + .anon_lock = page_lock_anon_vma_read, + .arg = &args, + }; + +/* +* Restrict to anonymous pages for now to avoid potential writeback +* issues. +*/ +if (!PageAnon (page)) +return false; + +rmap_walk (page, &rwc); + +return args.valid && !page_mapcount (page); +} + +/** +* make_device_exclusive_range() - Mark a range for exclusive use by a device +* @mm: mm_struct of assoicated target process +* @start: start of the region to mark for exclusive device access +* @end: end address of region +* @pages: returns the pages which were successfully marked for exclusive access +* @owner: passed to MMU_NOTIFY_EXCLUSIVE range notifier to allow filtering +* +* Returns: number of pages found in the range by GUP. A page is marked for +* exclusive access only if the page pointer is non-NULL. +* +* This function finds ptes mapping page(s) to the given address range, locks +* them and replaces mappings with special swap entries preventing userspace CPU +* access. On fault these entries are replaced with the original mapping after +* calling MMU notifiers. +* +* A driver using this to program access from a device must use a mmu notifier +* critical section to hold a device specific lock during programming. Once +* programming is complete it should drop the page lock and reference after +* which point CPU access to the page will revoke the exclusive access. +*/ +int make_device_exclusive_range (struct mm_struct *mm,unsigned long start, +unsigned long end,struct page **pages, +void *owner) +{ +unsigned long npages = (end - start) >>PAGE_SHIFT ; +unsigned long i; + + npages =get_user_pages_remote (mm, start, npages, +FOLL_GET |FOLL_WRITE |FOLL_SPLIT_PMD , + pages,NULL ,NULL ); +for (i =0 ; i < npages; i++, start +=PAGE_SIZE ) { +if (!trylock_page (pages[i])) { +put_page (pages[i]); + pages[i] =NULL ; +continue ; + } + +if (!page_make_device_exclusive (pages[i], mm, start, owner)) { +unlock_page (pages[i]); +put_page (pages[i]); + pages[i] =NULL ; + } + } + +return npages; +} +EXPORT_SYMBOL_GPL (make_device_exclusive_range); +void __put_anon_vma (struct anon_vma *anon_vma) {struct anon_vma *root = anon_vma->root;Signature · 1 line
2.20.1
Signature · 1 line
Peter Xu
Alistair Popple
reviewerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 100 lines · AEST Peter Xu
Quoted 99 lines · Alistair Popple
Some devices require exclusive write access to shared virtual memory (SVM) ranges to perform atomic operations on that memory. This requires CPU page tables to be updated to deny access whilst atomic operations are occurring.
In order to do this introduce a new swap entry
type (SWP_DEVICE_EXCLUSIVE). When a SVM range needs to be marked for exclusive access by a device all page table mappings for the particular range are replaced with device exclusive swap entries. This causes any CPU access to the page to result in a fault.Faults are resovled by replacing the faulting entry with the original mapping. This results in MMU notifiers being called which a driver uses to update access permissions such as revoking atomic access. After notifiers have been called the device will no longer have exclusive access to the region.
Walking of the page tables to find the target pages is handled by
get_user_pages()rather than a direct page table walk. A direct page table walk similar to what migrate_vma_collect()/unmap() does could also have been utilised. However this resulted in more code similar in functionality to whatget_user_pages()provides as page faulting is required to make the PTEs present and to break COW.Signed-off-by Alistair Popple <[email protected]>Reviewed-by Christoph Hellwig <[email protected]>---
v9:
* Split rename ofmigrate_pgmap_ownerinto a separate patch.
* Added comments explaining SWP_DEVICE_EXCLUSIVE_* entries.
* Renamed try_to_protect{_one} to page_make_device_exclusive{_one} basedsomewhat on a suggestion from Peter Xu. I was never particularly happy with
try_to_protect()as a name so think this is better.* Removed unneccesary code and reworded some comments based on feedback
from Peter Xu.
* Removed the VMA walk when restoring PTEs for device-exclusive entries.
* Simplified implementation ofcopy_pte_range()to fail if the pagecannot be locked. This might lead to occasional
fork()failures but at this stage we don't think that will be an issue.v8:
* Remove device exclusive entries on fork rather than copy them.v7:
* Added Christoph's Reviewed-by.
* Minor cosmetic cleanups suggested by Christoph.
* Replace mmu_notifier_range_init_migrate/exclusive with
mmu_notifier_range_init_owneras suggested by Christoph.* Replaced
lock_page()withlock_page_retry()when handling faults.
* Restrict to anonymous pages for now.v6:
* Fixed a bisectablity issue due to incorrectly applying the rename of
migrate_pgmap_ownerto the wrong patches for Nouveau andhmm_test.v5:
* Renamedrange->migrate_pgmap_ownertorange->owner.
* AddedMMU_NOTIFY_EXCLUSIVEto allow passing of a driver cookie whichallows notifiers called as a result of
make_device_exclusive_range()to be ignored.* Added a check to
try_to_protect_one()to detect if the pages originallyreturned from
get_user_pages()have been unmapped or not.* Removed
check_device_exclusive_range()as it is no longer required withthe other changes.
* Documentation update.
v4:
* Add function to check that mappings are still valid and exclusive.
* s/long/unsigned long/ inmake_device_exclusive_entry().
---Documentation/vm/hmm.rst | 17 ++++ include/linux/mmu_notifier.h | 6 ++ include/linux/rmap.h | 4 + include/linux/swap.h | 7 +- include/linux/swapops.h | 44 ++++++++- mm/hmm.c | 5 + mm/memory.c | 128 +++++++++++++++++++++++- mm/mprotect.c | 8 ++ mm/page_vma_mapped.c | 9 +- mm/rmap.c | 186 +++++++++++++++++++++++++++++++++++ 10 files changed, 405 insertions(+), 9 deletions(-)
Quoted 6 lines
Documentation/vm/hmm.rst between device driver specific code and shared common
Quoted 6 lines
Documentation/vm/hmm.rst +0
-0@@ -405,6 +405,23 @@ between device driver specific code and shared common
code:
Quoted 35 lines
Quoted 35 lines
Documentation/vm/hmm.rst +4
-0The lock can now be released. +Exclusive access memory +======================= + +Some devices have features such as atomic PTE bits that can be used toimplement +atomic access to system memory. To support atomic operations to a shared virtual +memory page such a device needs access to that page which is exclusive of any +userspace access from the CPU. The ``make_device_exclusive_range()`` function +can be used to make a memory range inaccessible from userspace.
Documentation/vm/hmm.rst +5
-0+ +This replaces all mappings for pages in the given range with special swap +entries. Any attempt to access the swap entry results in a fault which is +resovled by replacing the entry with the original mapping. A driver gets +notified that the mapping has been changed by MMU notifiers, after whichpoint +it will no longer have exclusive access to the page. Exclusive access is +guranteed to last until the driver drops the page lock and page reference, at +which point any CPU faults on the page may proceed as described.
Documentation/vm/hmm.rst +1
-0+ Memory cgroup (memcg) and rss accounting ========================================include/linux/mmu_notifier.h +0
-0@@ -42,6 +42,11 @@ struct mmu_interval_notifier; * @MMU_NOTIFY_MIGRATE: used during migrate_vma_collect() invalidate to signal * a device driver to possibly ignore the invalidation if the * owner field matches the driver's device private pgmap owner.
Quoted 8 lines
Quoted 6 lines
include/linux/mmu_notifier.h +2
-0+* +* @MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will no + * longer have exclusive access to the page. May ignore the invalidation that's + * part of
make_device_exclusive_range()if the owner field
+ * matches the value passed tomake_device_exclusive_range().Perhaps s/matches/does not match/?
No, "matches" is correct. The MMU_NOTIFY_EXCLUSIVE notifier is to notify a listener that a range is being invalidated for the purpose of making the range available for some device to have exclusive access to. Which does also mean a device getting the notification no longer has exclusive access if it already did.
A unique type is needed because when creating the range a driver needs to form a mmu critical section (with mmu_interval_read_begin()/ mmu_interval_read_end()) to ensure the entry remains valid long enough to program the device pte and hasn't been invalidated.
However without a way of filtering any invalidations will result in a retry, but make_device_exclusive_range() needs to do an invalidation during installation of the entry. To avoid this causing infinite retries the driver ignores specific invalidation events that it knows don't apply, ie. the invalidations that are a result of that driver asking for device exclusive entries.
Agree the comment could be improved though.
Quoted 43 lines
Quoted 43 lines
*/
enum
mmu_notifier_event{
MMU_NOTIFY_UNMAP= 0,include/linux/mmu_notifier.h +1
-0@@ -51,6 +56,7 @@ enum mmu_notifier_event {MMU_NOTIFY_SOFT_DIRTY ,MMU_NOTIFY_RELEASE ,MMU_NOTIFY_MIGRATE , + MMU_NOTIFY_EXCLUSIVE,};
#define
MMU_NOTIFIER_RANGE_BLOCKABLE(1 << 0)include/linux/rmap.h +4
-0@@ -193,6 +193,10 @@ int page_referenced(struct page *, int is_locked,bool try_to_migrate (struct page *page,enum ttu_flags flags);bool try_to_unmap (struct page *,enum ttu_flags flags); +int make_device_exclusive_range (struct mm_struct *mm,unsigned long start, +unsigned long end,struct page **pages, +void *arg); +/* Avoid racy checks */
#definePVMW_SYNC(1 << 0)
/* Look for migarion entries rather than present PTEs */include/linux/swap.h +0
-0@@ -63,11 +63,16 @@ static inline int current_is_kswapd(void)
Quoted 6 lines
include/linux/swap.h static inline int current_is_kswapd(void) · line 66
Quoted 3 lines
include/linux/swap.h +0
-0* * When a page is migrated from CPU to device, we set the CPU page table entry * to a special SWP_DEVICE_* entry. s/SWP_DEVICE_*/SWP_DEVICE_{READ|WRITE}/? Since SWP_DEVICE_* covers all four too.
Sure.
Quoted 192 lines
Quoted 192 lines
include/linux/swap.h +2
-0+* +* When a page is mapped by the device for exclusive access we set the CPU page + * table entries to special SWP_DEVICE_EXCLUSIVE_* entries.
*/
#ifdef
CONFIG_DEVICE_PRIVATEinclude/linux/swap.h +2
-1-#define SWP_DEVICE_NUM 2 +#define SWP_DEVICE_NUM 4#define SWP_DEVICE_WRITE (MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM)#define SWP_DEVICE_READ (MAX_SWAPFILES +SWP_HWPOISON_NUM +SWP_MIGRATION_NUM +1 )> +#define SWP_DEVICE_EXCLUSIVE_WRITE(MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+2) +#define
SWP_DEVICE_EXCLUSIVE_READ(MAX_SWAPFILES+SWP_HWPOISON_NUM+SWP_MIGRATION_NUM+3)>
#else
#defineSWP_DEVICE_NUM0
#endifinclude/linux/swapops.h +0
-0@@ -120,6 +120,27 @@ static inline bool
is_writable_device_private_entry(swp_entry_t entry)>
{return unlikely(swp_type(entry) == SWP_DEVICE_WRITE);}
include/linux/swapops.h +2
-0+ +static inline swp_entry_t make_readable_device_exclusive_entry(pgoff_toffset) +{
include/linux/swapops.h +4
-0+return swp_entry (SWP_DEVICE_EXCLUSIVE_READ , offset); +} + +static inline swp_entry_t make_writable_device_exclusive_entry(pgoff_toffset) +{
include/linux/swapops.h +13
-0+ return swp_entry(SWP_DEVICE_EXCLUSIVE_WRITE, offset); +} + +static inline bool is_device_exclusive_entry (swp_entry_t entry) +{ +return swp_type (entry) ==SWP_DEVICE_EXCLUSIVE_READ || +swp_type (entry) ==SWP_DEVICE_EXCLUSIVE_WRITE ; +} + +static inline bool is_writable_device_exclusive_entry (swp_entry_t entry) +{ +return unlikely (swp_type (entry) ==SWP_DEVICE_EXCLUSIVE_WRITE ); +}#else /* CONFIG_DEVICE_PRIVATE */ static inline swp_entry_t make_readable_device_private_entry (pgoff_t offset) {@@ -140,6 +161,26 @@ static inline bool
is_writable_device_private_entry(swp_entry_t entry)>
{return false;}
include/linux/swapops.h +2
-0+ +static inline swp_entry_t make_readable_device_exclusive_entry(pgoff_toffset) +{
include/linux/swapops.h +4
-0+return swp_entry (0 ,0 ); +} + +static inline swp_entry_t make_writable_device_exclusive_entry(pgoff_toffset) +{
include/linux/swapops.h +13
-1+ return swp_entry(0, 0); +} + +static inline bool is_device_exclusive_entry (swp_entry_t entry) +{ +return false; +} + +static inline bool is_writable_device_exclusive_entry (swp_entry_t entry) +{ +return false; +}#endif /* CONFIG_DEVICE_PRIVATE */ #ifdef CONFIG_MIGRATION @@ -219,7 +260,8 @@ static inline struct page*pfn_swap_entry_to_page(swp_entry_t entry)> */ static inline bool is_pfn_swap_entry (swp_entry_t entry) { -return is_migration_entry (entry) ||is_device_private_entry (entry); +return is_migration_entry (entry) ||is_device_private_entry (entry)||
include/linux/swapops.h +1
-0+is_device_exclusive_entry (entry); }struct page_vma_mapped_walk ;mm/hmm.c +2
-0@@ -26,6 +26,8 @@#include <linux/mmu_notifier.h> #include <linux/memory_hotplug.h> +#include "internal.h" +struct hmm_vma_walk {struct hmm_range *range; unsigned long last;mm/hmm.c +0
-0@@ -271,6 +273,9 @@ static int hmm_vma_handle_pte(struct mm_walk *walk,unsigned long addr,>
if (!non_swap_entry(entry)) goto fault;mm/hmm.c +3
-0+if (is_device_exclusive_entry (entry)) + goto fault; +if (is_migration_entry (entry)) {pte_unmap (ptep); hmm_vma_walk->last = addr;mm/memory.c +41
-0@@ -700,6 +700,68 @@ struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr,> }#endif +static void restore_exclusive_pte (struct vm_area_struct *vma, +struct page *page,unsigned long address, +pte_t *ptep) +{ +pte_t pte; +swp_entry_t entry; + + pte =pte_mkold (mk_pte (page,READ_ONCE (vma->vm_page_prot))); +if (pte_swp_soft_dirty (*ptep)) + pte =pte_mksoft_dirty (pte); + + entry =pte_to_swp_entry (*ptep); +if (pte_swp_uffd_wp (*ptep)) + pte =pte_mkuffd_wp (pte); +else if (is_writable_device_exclusive_entry (entry)) + pte =maybe_mkwrite (pte_mkdirty (pte), vma); + +set_pte_at (vma->vm_mm, address, ptep, pte); + +/* +* No need to take a page reference as one was already +* created when the swap entry was made. +*/ +if (PageAnon (page)) +page_add_anon_rmap (page, vma, address, false); +else +/* +* Currently device exclusive access only supports anonymous +* memory so the entry shouldn't point to a filebacked page. +*/ +WARN_ON_ONCE (!PageAnon (page)); + +if (vma->vm_flags &VM_LOCKED ) +mlock_vma_page (page); + +/* +* No need to invalidate - it was non-present before. However +* secondary CPUs may have mappings that need invalidating. +*/ +update_mmu_cache (vma, address, ptep); +}
Quoted 8 lines
Quoted 6 lines
mm/memory.c +3
-0+ +/* +* Tries to restore an exclusive pte if the page lock can be acquired without + * sleeping.
+ */
+static unsigned longBetter return a int?
Ok.
Quoted 4 lines
mm/memory.c struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr, · line 751
Quoted 2 lines
mm/memory.c +2
-0+try_restore_exclusive_pte(struct mm_struct *src_mm, pte_t *src_pte, + struct vm_area_struct *vma, unsigned long addr)Raised in the other thread too:
src_mmcan be dropped.
Ack, sorry I must have missed that.
Quoted 58 lines
Quoted 58 lines
mm/memory.c +13
-0+{ +swp_entry_t entry =pte_to_swp_entry (*src_pte); +struct page *page =pfn_swap_entry_to_page (entry); + +if (trylock_page (page)) { +restore_exclusive_pte (vma, page, addr, src_pte); +unlock_page (page); +return 0 ; + } + +return -EBUSY ; +} +/* * copy one vm_area from one task to the other. Assumes the page tables * already present in the new task to be cleared in the whole range @@ -781,6 +843,17 @@ copy_nonpresent_pte(struct mm_struct *dst_mm, struct
mm_struct*src_mm,>pte = pte_swp_mkuffd_wp(pte); set_pte_at(src_mm, addr, src_pte, pte); }mm/memory.c +11
-0+ }else if (is_device_exclusive_entry (entry)) { +/* +* Make device exclusive entries present by restoring the +* original entry then copying as for a present pte. Device +* exclusive entries currently only support private writable +* (ie. COW) mappings. +*/ +VM_BUG_ON (!is_cow_mapping (vma->vm_flags)); +if (try_restore_exclusive_pte (src_mm, src_pte, vma, addr)) +return -EBUSY ; +return -ENOENT ; }set_pte_at (dst_mm, addr, dst_pte, pte);return 0 ;@@ -980,9 +1053,18 @@ copy_pte_range(struct vm_area_struct *dst_vma,struct
vm_area_struct*src_vma,>if (ret == -EAGAIN) { entry = pte_to_swp_entry(*src_pte); break;mm/memory.c +5
-0+ }else if (ret == -EBUSY ) { +break ; + }else if (!ret) { + progress +=8 ; +continue ; }
Quoted 12 lines
mm/memory.c copy_pte_range(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma, · line 1067
Quoted 8 lines
mm/memory.c +3
-2- progress +=8 ; -continue ; + +/* +* Device exclusive entry restored, continue by copying + * the now present pte.
mm/memory.c +2
-0+*/ +WARN_ON_ONCE (ret != -ENOENT );The change looks right, thanks. It's just that we should start to consider document all these err code now in
copy_pte_range()some day (perhaps on top of this patch)..
I tried to write the documentation but with the new clean-up patch using a unique return code for each case the code ends up being rather self documenting IMHO. It seems reasonably obvious what function returns what due to the "if (ret == ...) break;" statements after each so the comments ended up repeating the code (ie. copy_present_pte() returns this for this case, etc.), but lets see what we think once I've updated.
Of course the whole thing is still a bit clunky, so it's still on my list of things to look at reworking/cleaning up in future.
Quoted 75 lines
Quoted 61 lines
} /* copy_present_pte() will clear `*prealloc' if consumed */ ret = copy_present_pte(dst_vma, src_vma, dst_pte, src_pte,mm/memory.c +0
-0@@ -1019,6 +1101,8 @@ copy_pte_range(struct vm_area_struct *dst_vma,struct
vm_area_struct*src_vma,>goto out; } entry.val = 0;mm/memory.c +4
-1+ }else if (ret == -EBUSY ) { +return -EBUSY ; }else if (ret) {WARN_ON_ONCE (ret != -EAGAIN ); prealloc =page_copy_prealloc (src_mm, src_vma, addr);@@ -1283,7 +1367,8 @@ static unsigned long zap_pte_range(struct mmu_gather *tlb,> } entry =pte_to_swp_entry (ptent); -if (is_device_private_entry (entry)) { +if (is_device_private_entry (entry) || +is_device_exclusive_entry (entry)) {struct page *page = pfn_swap_entry_to_page(entry); if (unlikely(details && details->check_mapping)) {mm/memory.c +0
-0@@ -1299,7 +1384,10 @@ static unsigned long zap_pte_range(struct
mmu_gather*tlb,>pte_clear_not_present_full(mm, addr, pte, tlb->fullmm); rss[mm_counter(page)]--;mm/memory.c +10
-1-page_remove_rmap (page, false); + +if (is_device_private_entry (entry)) +page_remove_rmap (page, false); +put_page (page);continue ; }@@ -3303,6 +3391,35 @@ void unmap_mapping_range(struct address_space *mapping,> }EXPORT_SYMBOL (unmap_mapping_range); +/* +* Restore a potential device exclusive pte to a working pte entry +*/ +static vm_fault_t remove_device_exclusive_entry (struct vm_fault *vmf) +{ +struct page *page = vmf->page;mm/memory.c void unmap_mapping_range(struct address_space · line 3410
Quoted 10 lines
mm/memory.c +9
-0+struct vm_area_struct *vma = vmf->vma; +vm_fault_t ret =0 ; +struct mmu_notifier_range range; + +if (!lock_page_or_retry (page, vma->vm_mm, vmf->flags)) +return VM_FAULT_RETRY ; +mmu_notifier_range_init (&range,MMU_NOTIFY_CLEAR ,0 , vma, vma->vm_mm, + vmf->address &PAGE_MASK , + (vmf->address &PAGE_MASK ) +PAGE_SIZE );@MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will no longer have exclusive access to the page.
Quoted 1 line
Shouldn't this be the place to use new
MMU_NOTIFY_EXCLUSIVE?
No. We could introduce another type to notify the range is going away due to fault but as mentioned in the other thread I didn't think that was necessary as the only sensible thing a driver can do is invalidate the entry anyway.
MMU_NOTIFY_EXCLUSIVE is to signal the invalidation is occurring because the range is being marked for exclusive access (hopefully the explanation earlier makes sense).
Quoted 6 lines
Quoted 6 lines
mm/memory.c +6
-0+mmu_notifier_invalidate_range_start (&range); + + vmf->pte =pte_offset_map_lock (vma->vm_mm, vmf->pmd, vmf->address, + &vmf->ptl); +if (likely (pte_same (*vmf->pte, vmf->orig_pte))) +restore_exclusive_pte (vma, page, vmf->address, vmf->pte);
Quoted 8 lines
mm/memory.c void unmap_mapping_range(struct address_space *mapping, · line 3420
Quoted 6 lines
mm/memory.c +6
-0+ +pte_unmap_unlock (vmf->pte, vmf->ptl); +unlock_page (page); + +mmu_notifier_invalidate_range_end (&range); +return ret;We can drop "ret" and return 0 here directly.
Agreed, was left over from cleaning this function up in the last version.
Quoted 139 lines
Quoted 125 lines
mm/memory.c +2
-0+} +/*
* We enter with non-exclusive
mmap_lock(to exclude vma changes,
* but allow concurrent faults), and pte mapped but not yet locked.mm/memory.c +3
-0@@ -3330,6 +3447,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)if (is_migration_entry (entry)) {migration_entry_wait (vma->vm_mm, vmf->pmd, vmf->address); + }else if (is_device_exclusive_entry (entry)) { + vmf->page =pfn_swap_entry_to_page (entry); + ret =remove_device_exclusive_entry (vmf); }else if (is_device_private_entry (entry)) { vmf->page =pfn_swap_entry_to_page (entry); ret = vmf->page->pgmap->ops->migrate_to_ram (vmf);mm/mprotect.c +0
-0@@ -165,6 +165,14 @@ static unsigned long change_pte_range(struct
vm_area_struct*vma,pmd_t*pmd,>newpte = swp_entry_to_pte(entry); if (pte_swp_uffd_wp(oldpte)) newpte = pte_swp_mkuffd_wp(newpte);+ } else if
(is_writable_device_exclusive_entry(entry)) { +
entry = make_readable_device_exclusive_entry( +swp_offset(entry)); + newpte = swp_entry_to_pte(entry);mm/mprotect.c +2
-0+if (pte_swp_soft_dirty (oldpte)) + newpte =
pte_swp_mksoft_dirty(newpte); + if
(pte_swp_uffd_wp(oldpte))mm/mprotect.c +1
-0+ newpte =pte_swp_mkuffd_wp (newpte); }else { newpte = oldpte; }mm/page_vma_mapped.c +2
-1@@ -41,7 +41,8 @@ static bool map_pte(struct page_vma_mapped_walk *pvmw)/* Handle un-addressable ZONE_DEVICE memory */ entry =pte_to_swp_entry (*pvmw->pte); -if (!is_device_private_entry (entry)) +if (!is_device_private_entry (entry) && + !is_device_exclusive_entry (entry))return false; } else if (!pte_present(*pvmw->pte)) return false;mm/page_vma_mapped.c +2
-1@@ -93,7 +94,8 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)return false; entry =pte_to_swp_entry (*pvmw->pte); -if (!is_migration_entry (entry)) +if (!is_migration_entry (entry) && + !is_device_exclusive_entry (entry))return false; pfn = swp_offset(entry);mm/page_vma_mapped.c +2
-1@@ -102,7 +104,8 @@ static bool check_pte(struct page_vma_mapped_walk *pvmw)>/* Handle un-addressable ZONE_DEVICE memory */ entry =pte_to_swp_entry (*pvmw->pte); -if (!is_device_private_entry (entry)) +if (!is_device_private_entry (entry) && + !is_device_exclusive_entry (entry))return false;pfn = swp_offset(entry);mm/rmap.c +14
-0@@ -2008,6 +2008,192 @@ void page_mlock(struct page *page)rmap_walk (page, &rwc); } +struct make_exclusive_args { +struct mm_struct *mm; +unsigned long address; +void *owner; +bool valid; +}; + +static bool page_make_device_exclusive_one (struct page *page, +struct vm_area_struct *vma,unsigned long address,void *priv) +{ +struct mm_struct *mm = vma->vm_mm; +struct page_vma_mapped_walk pvmw = { + .page = page, + .vma = vma, + .address = address,mm/rmap.c void page_mlock(struct page *page) · line 2037
Quoted 10 lines
mm/rmap.c +10
-0+ }; +struct make_exclusive_args *args = priv; +pte_t pteval; +struct page *subpage; +bool ret = true; +struct mmu_notifier_range range; +swp_entry_t entry; +pte_t swp_pte; + + mmu_notifier_range_init_owner(&range, MMU_NOTIFY_EXCLUSIVE, 0, vma,Similar question here, EXCLUSIVE comment says it gets notified when the device does not have exclusive access.
Quoted 2 lines
If you prefer to keep using EXCLUSIVE for both mark/restore, then we need to change the comment above
MMU_NOTIFY_EXCLUSIVE?
Yeah, sorry for the confusion that comment was stating the somewhat obvious (any invalidation notifier means a device no longer has exclusive access) but not enough detail about why a driver might treat this specific invalidation reason differently.
Quoted 10 lines
Quoted 10 lines
mm/rmap.c +2
-0+ vma->vm_mm, address,min (vma->vm_end, + address + page_size(page)),
args->owner); +mmu_notifier_invalidate_range_start(&range);mm/rmap.c +7
-0+ +while (page_vma_mapped_walk (&pvmw)) { +/* Unexpected PMD-mapped THP? */ +VM_BUG_ON_PAGE (!pvmw.pte, page); + +if (!pte_present (*pvmw.pte)) { + ret = false;
Quoted 8 lines
mm/rmap.c void page_mlock(struct page *page) · line 2050
Quoted 5 lines
mm/rmap.c +5
-0+page_vma_mapped_walk_done (&pvmw); +break ; + } + + subpage = page -page_to_pfn (page) +pte_pfn (*pvmw.pte);I see that all pages passed in should be done after
FOLL_SPLIT_PMD, so is this needed? Or say, should subpage==page always be true?
Not always, in the case of a thp there are small ptes which will get device exclusive entries.
Quoted 29 lines
Quoted 6 lines
mm/rmap.c +6
-0+ address = pvmw.address; + +/* Nuke the page table entry. */ +flush_cache_page (vma, address,pte_pfn (*pvmw.pte)); + pteval =ptep_clear_flush (vma, address, pvmw.pte); +mm/rmap.c void page_mlock(struct page *page) · line 2066
Quoted 10 lines
mm/rmap.c +10
-0+/* Move the dirty bit to the page. Now the pte is gone. */ +if (pte_dirty (pteval)) +set_page_dirty (page); + +if (arch_unmap_one (mm, vma, address, pteval) <0 ) { +set_pte_at (mm, address, pvmw.pte, pteval); + ret = false; +page_vma_mapped_walk_done (&pvmw); +break ; + }Didn't notice this previously, but also suggest to drop this.
Two reasons:
1. It's introduced in
ca827d55ebaa("mm, swap: Add infrastructure for saving page metadata on swap", 2018-03-18) for sparc-only use so far. If we really want this, we'll also want to callarch_do_swap_page()when restoring the pte just like what we do indo_swap_page(); NOTE: current code path ofSWP_DEVICE_EXCLUSIVEwill skip thearch_do_swap_page()indo_swap_page()so it's not even paired with the abovearch_unmap_one(), so I believe this won't even work for sparc at all.
Quoted 4 lines
2. I highly doubt whether sparc is also on the list of platforms to support for device atomic ops even in the future. IMHO we'd better not copy-paste code clips if never used at all, because once merged, removing it would need more justifications.
That seems reasonable, I am not aware of any need to support this on sparc now or in the future and we can always add it then. And as you say I had missed the need to pair it with arch_do_swap_page() anyway.
Quoted 26 lines
Quoted 26 lines
mm/rmap.c +3
-0+ +/* +* Check that our target page is still mapped at the expected
mm/rmap.c +14
-0+* address. +*/ +if (args->mm == mm && args->address == address && +pte_write (pteval)) + args->valid = true; + +/* +* Store the pfn of the page in a special migration +* pte. do_swap_page() will wait until the migration +* pte is removed and then restart fault handling. +*/ +if (pte_write (pteval)) + entry = make_writable_device_exclusive_entry( +
page_to_pfn(subpage)); + elsemm/rmap.c +2
-0+ entry = make_readable_device_exclusive_entry( +
page_to_pfn(subpage)); +swp_pte=swp_entry_to_pte(entry);mm/rmap.c +4
-0+if (pte_soft_dirty (pteval)) + swp_pte =pte_swp_mksoft_dirty (swp_pte); +if (pte_uffd_wp (pteval)) + swp_pte =pte_swp_mkuffd_wp (swp_pte);
Quoted 8 lines
mm/rmap.c void page_mlock(struct page *page) · line 2097
Quoted 6 lines
mm/rmap.c +6
-0+ +/* Take a reference for the swap entry */ +get_page (page); +set_pte_at (mm, address, pvmw.pte, swp_pte); + +page_remove_rmap (subpage,PageHuge (page));Why
PageHuge()? Should it be a constant "false"?
Yes.
Quoted 7 lines
Quoted 1 line
+
put_page(page);Should we drop this
put_page()along withget_page()above?
page_count()should be >0 anyway as we've got a mapcount before at least when dropping the pte. Then IMHO we can simply keep the old page reference.
I had debated doing that when I wrote it but left it there to keep things obvious whilst checking the refcounting. However a comment here works just as well so have done that.
Quoted 13 lines
Quoted 10 lines
mm/rmap.c +9
-0+ } + +mmu_notifier_invalidate_range_end (&range); + +return ret; +} + +/** +* page_make_device_exclusive - replace page table mappings with swap entries
"with swap entries" looks a bit blurred to me (although below longer comment explains much better). How about below (or something similar):
Quoted 3 lines
page_make_device_exclusive- Mark the page exclusively owned by the device?
Seems good, will do.
Quoted 5 lines
It'll also match with comment above
make_device_exclusive_range().No strong opinion.
The rest looks good. Thanks,
Thanks again for looking.
Quoted 103 lines
Quoted 99 lines
+ * @page: the page to replace page table entries for
+ * @mm: themm_structwhere the page is expected to be mapped
+ * @address: address where the page is expected to be mapped
+ * @owner: passed toMMU_NOTIFY_EXCLUSIVErange notifier callbacks
+ *
+ * Tries to remove all the page table entries which are mapping this page and + * replace them with special device exclusive swap entries to grant a device + * exclusive access to the page. Caller must hold the page lock.
+ *
+ * Returns false if the page is still mapped, or if it could not be unmapped + * from the expected address. Otherwise returns true (success).
+ */
+static bool page_make_device_exclusive(struct page *page, structmm_struct*mm, + unsigned long address, void
*owner)mm/rmap.c +28
-0+{ +struct make_exclusive_args args = { + .mm = mm, + .address = address, + .owner = owner, + .valid = false, + }; +struct rmap_walk_control rwc = { + .rmap_one = page_make_device_exclusive_one, + .done = page_not_mapped, + .anon_lock = page_lock_anon_vma_read, + .arg = &args, + }; + +/* +* Restrict to anonymous pages for now to avoid potential writeback +* issues. +*/ +if (!PageAnon (page)) +return false; + +rmap_walk (page, &rwc); + +return args.valid && !page_mapcount (page); +} + +/** +* make_device_exclusive_range() - Mark a range for exclusive use by a device + * @mm:
mm_structof assoicated target process
+ * @start: start of the region to mark for exclusive device access
+ * @end: end address of region
+ * @pages: returns the pages which were successfully marked for exclusive access + * @owner: passed toMMU_NOTIFY_EXCLUSIVErange notifier to allow filtering + *
+ * Returns: number of pages found in the range by GUP. A page is marked for + * exclusive access only if the page pointer is non-NULL.
+ *
+ * This function finds ptes mapping page(s) to the given address range, locks + * them and replaces mappings with special swap entries preventing userspace CPU + * access. On fault these entries are replaced with the original mapping after + * calling MMU notifiers.
+ *
+ * A driver using this to program access from a device must use a mmu notifier + * critical section to hold a device specific lock during programming. Once + * programming is complete it should drop the page lock and reference after + * which point CPU access to the page will revoke the exclusive access. + */
+int make_device_exclusive_range(structmm_struct*mm, unsigned long start,mm/rmap.c +8
-0+ unsigned long end, struct page **pages, + void *owner) +{ + unsigned long npages = (end - start) >> PAGE_SHIFT; +unsigned long i; + + npages = get_user_pages_remote(mm, start, npages, + FOLL_GET | FOLL_WRITE |
FOLL_SPLIT_PMD, + pages, NULL, NULL);mm/rmap.c +8
-0+for (i =0 ; i < npages; i++, start +=PAGE_SIZE ) { +if (!trylock_page (pages[i])) { +put_page (pages[i]); + pages[i] =NULL ; +continue ; + } + +if (!page_make_device_exclusive (pages[i], mm, start, owner)){ +
unlock_page(pages[i]);mm/rmap.c +9
-1+put_page (pages[i]); + pages[i] =NULL ; + } + } + +return npages; +} +EXPORT_SYMBOL_GPL (make_device_exclusive_range); +void __put_anon_vma (struct anon_vma *anon_vma) {struct anon_vma *root = anon_vma->root; --Quoted 1 line
2.20.1
--
Peter Xu
Re: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 20 lines · Alistair Popple
Quoted 8 lines
Quoted 6 lines
+ *
+ * @MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will no + * longer have exclusive access to the page. May ignore the invalidation that's + * part ofmake_device_exclusive_range()if the owner field
+ * matches the value passed tomake_device_exclusive_range().Perhaps s/matches/does not match/?
No, "matches" is correct. The
MMU_NOTIFY_EXCLUSIVEnotifier is to notify a listener that a range is being invalidated for the purpose of making the range available for some device to have exclusive access to. Which does also mean a device getting the notification no longer has exclusive access if it already did.A unique type is needed because when creating the range a driver needs to form a mmu critical section (with mmu_interval_read_begin()/
mmu_interval_read_end()) to ensure the entry remains valid long enough to program the device pte and hasn't been invalidated.
Quoted 6 lines
However without a way of filtering any invalidations will result in a retry, but
make_device_exclusive_range()needs to do an invalidation during installation of the entry. To avoid this causing infinite retries the driver ignores specific invalidation events that it knows don't apply, ie. the invalidations that are a result of that driver asking for device exclusive entries.
OK I think I get it now.. so the driver checks both EXCLUSIVE and owner, if all match it skips the notify, otherwise it's treated like all the rest. Thanks.
However then it's still confusing (as I raised it too in previous comment) that we use CLEAR when re-installing the valid pte. It's merely against what CLEAR means.
How about sending EXCLUSIVE for both mark/restore? Just that when restore we notify with owner==NULL telling that no one is owning it anymore so driver needs to drop the ownership. I assume your driver patch does not need change too. Would that be much cleaner than CLEAR? I bet it also makes commenting the new notify easier.
What do you think?
[...]
Quoted 19 lines
Quoted 10 lines
Quoted 10 lines
(quoted diff) +2
-0+ vma->vm_mm, address, min(vma->vm_end, + address + page_size(page)),
args->owner); +mmu_notifier_invalidate_range_start(&range);mm/rmap.c +7
-0+ +while (page_vma_mapped_walk (&pvmw)) { +/* Unexpected PMD-mapped THP? */ +VM_BUG_ON_PAGE (!pvmw.pte, page); + +if (!pte_present (*pvmw.pte)) { + ret = false;Quoted 8 lines
mm/rmap.c void page_mlock(struct page *page) · line 2050
Quoted 5 lines
mm/rmap.c +5
-0+page_vma_mapped_walk_done (&pvmw); +break ; + } + + subpage = page -page_to_pfn (page) +pte_pfn (*pvmw.pte);I see that all pages passed in should be done after
FOLL_SPLIT_PMD, so is this needed? Or say, should subpage==page always be true?
Quoted 2 lines
Not always, in the case of a thp there are small ptes which will get device exclusive entries.
FOLL_SPLIT_PMD will first split the huge thp into smaller pages, then do follow_page_pte() on them (in follow_pmd_mask):
if (flags & FOLL_SPLIT_PMD) {
int ret;
page = pmd_page(*pmd);
if (is_huge_zero_page(page)) {
spin_unlock(ptl);
ret = 0;
split_huge_pmd(vma, pmd, address);
if (pmd_trans_unstable(pmd))
ret = -EBUSY;
} else {
spin_unlock(ptl);
split_huge_pmd(vma, pmd, address);
ret = pte_alloc(mm, pmd) ? -ENOMEM : 0;
}
return ret ? ERR_PTR(ret) :
follow_page_pte(vma, address, pmd, flags, &ctx->pgmap);
}So I thought all pages are small pages?
Signature · 1 line
Peter Xu
Alistair Popple
reviewerRe: [PATCH v9 07/10] mm: Device exclusive memory access
Quoted 29 lines · AEST Peter Xu
Quoted 21 lines · Alistair Popple
Quoted 9 lines
Quoted 7 lines
+ *
+ * @MMU_NOTIFY_EXCLUSIVE: to signal a device driver that the device will
no + * longer have exclusive access to the page. May ignore the invalidation that's + * part ofmake_device_exclusive_range()if the owner field
+ * matches the value passed tomake_device_exclusive_range().Perhaps s/matches/does not match/?
No, "matches" is correct. The
MMU_NOTIFY_EXCLUSIVEnotifier is to notify a listener that a range is being invalidated for the purpose of making the range available for some device to have exclusive access to. Which does also mean a device getting the notification no longer has exclusive access if it already did.A unique type is needed because when creating the range a driver needs to form a mmu critical section (with mmu_interval_read_begin()/
mmu_interval_read_end()) to ensure the entry remains valid long enough to program the device pte and hasn't been invalidated.Quoted 6 lines
However without a way of filtering any invalidations will result in a retry, but
make_device_exclusive_range()needs to do an invalidation during installation of the entry. To avoid this causing infinite retries the driver ignores specific invalidation events that it knows don't apply, ie. the invalidations that are a result of that driver asking for device exclusive entries.
Quoted 7 lines
OK I think I get it now.. so the driver checks both EXCLUSIVE and owner, if all match it skips the notify, otherwise it's treated like all the rest. Thanks.
However then it's still confusing (as I raised it too in previous comment) that we use CLEAR when re-installing the valid pte. It's merely against what CLEAR means.
Oh, thanks. I understand where you are coming from now - the pte is already invalid so ordinarily wouldn't need clearing.
Quoted 7 lines
How about sending EXCLUSIVE for both mark/restore? Just that when restore we notify with owner==NULL telling that no one is owning it anymore so driver needs to drop the ownership. I assume your driver patch does not need change too. Would that be much cleaner than CLEAR? I bet it also makes commenting the new notify easier.
What do you think?
That seems like a good and avoids adding another type. And as you say they driver patch shouldn't need changing either (will need to confirm though).
Quoted 47 lines
[...]
Quoted 21 lines
Quoted 12 lines
Quoted 12 lines
+
vma->vm_mm, address,
min(vma->vm_end,
+ address +page_size(page)),args->owner); +mmu_notifier_invalidate_range_start(&range);mm/rmap.c +8
-0+ +while (page_vma_mapped_walk (&pvmw)) { +/* Unexpected PMD-mapped THP? */ +VM_BUG_ON_PAGE (!pvmw.pte, page); + +if (!pte_present (*pvmw.pte)) { + ret = false; +page_vma_mapped_walk_done (&pvmw);Quoted 8 lines
mm/rmap.c void page_mlock(struct page *page) · line 2050
Quoted 4 lines
mm/rmap.c +4
-0+break ; + } + + subpage = page -page_to_pfn (page) +pte_pfn (*pvmw.pte);I see that all pages passed in should be done after
FOLL_SPLIT_PMD, so is
this needed? Or say, should subpage==page always be true?Quoted 3 lines
Not always, in the case of a thp there are small ptes which will get device
exclusive entries.
FOLL_SPLIT_PMDwill first split the huge thp into smaller pages, then dofollow_page_pte()on them (infollow_pmd_mask):if (flags & FOLL_SPLIT_PMD) { int ret; page = pmd_page(*pmd); if (is_huge_zero_page(page)) { spin_unlock(ptl); ret = 0; split_huge_pmd(vma, pmd, address); if (pmd_trans_unstable(pmd)) ret = -EBUSY; } else { spin_unlock(ptl); split_huge_pmd(vma, pmd, address); ret = pte_alloc(mm, pmd) ? -ENOMEM : 0; } return ret ? ERR_PTR(ret) : follow_page_pte(vma, address, pmd, flags,
Quoted 3 lines
&ctx->pgmap); }So I thought all pages are small pages?
The page will remain as a transparent huge page though (at least as I understand things). FOLL_SPLIT_PMD turns it into a pte mapped thp by splitting the pmd and creating pte's mapping the subpages but doesn't split the page itself. For comparison FOLL_SPLIT (which has been removed in v5.13 due to lack of use) is what would be used to split the page in the above GUP code by calling split_huge_page() rather than split_huge_pmd().
This was done to avoid adding code for handling device exclusive entries at the pmd level as well which would have made the changes more complicated and seems unnecessary at least for now.
Quoted 2 lines
--
Peter Xu
Re: [PATCH v9 07/10] mm: Device exclusive memory access
On Fri, May 28, 2021 at 11:48:40AM +1000, Alistair Popple wrote:
[...]
Quoted 42 lines
Quoted 42 lines
Quoted 16 lines
Quoted 7 lines
Quoted 7 lines
mm/rmap.c +7
-0+while (page_vma_mapped_walk (&pvmw)) { +/* Unexpected PMD-mapped THP? */ +VM_BUG_ON_PAGE (!pvmw.pte, page); + +if (!pte_present (*pvmw.pte)) { + ret = false; +page_vma_mapped_walk_done (&pvmw);Quoted 8 lines
mm/rmap.c void page_mlock(struct page *page) · line 2050
Quoted 4 lines
mm/rmap.c +4
-0+break ; + } + + subpage = page -page_to_pfn (page) +pte_pfn (*pvmw.pte);I see that all pages passed in should be done after
FOLL_SPLIT_PMD, so is
this needed? Or say, should subpage==page always be true?Quoted 3 lines
Not always, in the case of a thp there are small ptes which will get device
exclusive entries.
FOLL_SPLIT_PMDwill first split the huge thp into smaller pages, then dofollow_page_pte()on them (infollow_pmd_mask):if (flags & FOLL_SPLIT_PMD) { int ret; page = pmd_page(*pmd); if (is_huge_zero_page(page)) { spin_unlock(ptl); ret = 0; split_huge_pmd(vma, pmd, address); if (pmd_trans_unstable(pmd)) ret = -EBUSY; } else { spin_unlock(ptl); split_huge_pmd(vma, pmd, address); ret = pte_alloc(mm, pmd) ? -ENOMEM : 0; } return ret ? ERR_PTR(ret) : follow_page_pte(vma, address, pmd, flags,
&ctx->pgmap); }
Quoted 8 lines
Quoted 1 line
So I thought all pages are small pages?
The page will remain as a transparent huge page though (at least as I understand things).
FOLL_SPLIT_PMDturns it into a pte mapped thp by splitting the pmd and creating pte's mapping the subpages but doesn't split the page itself. For comparisonFOLL_SPLIT(which has been removed in v5.13 due to lack of use) is what would be used to split the page in the above GUP code by callingsplit_huge_page()rather thansplit_huge_pmd().
But shouldn't FOLL_SPLIT_PMD filled in small pfns for each pte? See __split_huge_pmd_locked():
for (i = 0, addr = haddr; i < HPAGE_PMD_NR; i++, addr += PAGE_SIZE) {
...
} else {
entry = mk_pte(page + i, READ_ONCE(vma->vm_page_prot));
...
}
...
set_pte_at(mm, addr, pte, entry);
}Then iiuc the coming follow_page_pte() will directly fetch the small pages?
Signature · 1 line
Peter Xu
Alistair Popple
reviewerPATCH v9 8/10 mm: Selftests for exclusive device memory
In reply to Alistair PoppleAdds some selftests for exclusive device memory.
--- lib/test_hmm.c | 124 +++++++++++++++++++ lib/test_hmm_uapi.h | 2 + tools/testing/selftests/vm/hmm-tests.c | 158 +++++++++++++++++++++++++ 3 files changed, 284 insertions(+)
lib/test_hmm.c +124 -0
@@ -25,6 +25,7 @@#include <linux/swapops.h> #include <linux/sched/mm.h> #include <linux/platform_device.h> +#include <linux/rmap.h> #include "test_hmm_uapi.h"
@@ -46,6 +47,7 @@ struct dmirror_bounce {unsigned long cpages; }; +#define DPT_XA_TAG_ATOMIC 1UL#define DPT_XA_TAG_WRITE 3UL/*
@@ -619,6 +621,54 @@ static void dmirror_migrate_alloc_and_copy(struct migrate_vma *args, } } +static int dmirror_check_atomic (struct dmirror *dmirror,unsigned long start, +unsigned long end) +{ +unsigned long pfn; + +for (pfn = start >>PAGE_SHIFT ; pfn < (end >>PAGE_SHIFT ); pfn++) { +void *entry; +struct page *page; + + entry =xa_load (&dmirror->pt, pfn); + page =xa_untag_pointer (entry); +if (xa_pointer_tag (entry) ==DPT_XA_TAG_ATOMIC ) +return -EPERM ; + } + +return 0 ; +} + +static int dmirror_atomic_map (unsigned long start,unsigned long end, +struct page **pages,struct dmirror *dmirror) +{ +unsigned long pfn, mapped =0 ; +int i; + +/* Map the migrated pages into the device's page tables. */ +mutex_lock (&dmirror->mutex); + +for (i =0 , pfn = start >>PAGE_SHIFT ; pfn < (end >>PAGE_SHIFT ); pfn++, i++) { +void *entry; + +if (!pages[i]) +continue ; + + entry = pages[i]; + entry =xa_tag_pointer (entry,DPT_XA_TAG_ATOMIC ); + entry =xa_store (&dmirror->pt, pfn, entry,GFP_ATOMIC ); +if (xa_is_err (entry)) { +mutex_unlock (&dmirror->mutex); +return xa_err (entry); + } + + mapped++; + } + +mutex_unlock (&dmirror->mutex); +return mapped; +} +static int dmirror_migrate_finalize_and_map (struct migrate_vma *args,struct dmirror *dmirror) {
@@ -661,6 +711,71 @@ static int dmirror_migrate_finalize_and_map(struct migrate_vma *args,return 0 ; } +static int dmirror_exclusive (struct dmirror *dmirror, +struct hmm_dmirror_cmd *cmd) +{ +unsigned long start, end, addr; +unsigned long size = cmd->npages <<PAGE_SHIFT ; +struct mm_struct *mm = dmirror->notifier.mm; +struct page *pages[64 ]; +struct dmirror_bounce bounce; +unsigned long next; +int ret; + + start = cmd->addr; + end = start + size; +if (end < start) +return -EINVAL ; + +/* Since the mm is for the mirrored process, get a reference first. */ +if (!mmget_not_zero (mm)) +return -EINVAL ; + +mmap_read_lock (mm); +for (addr = start; addr < end; addr = next) { +int i, mapped; + +if (end < addr + (ARRAY_SIZE (pages) <<PAGE_SHIFT )) + next = end; +else + next = addr + (ARRAY_SIZE (pages) <<PAGE_SHIFT ); + + ret =make_device_exclusive_range (mm, addr, next, pages,NULL ); + mapped =dmirror_atomic_map (addr, next, pages, dmirror); +for (i =0 ; i < ret; i++) { +if (pages[i]) { +unlock_page (pages[i]); +put_page (pages[i]); + } + } + +if (addr + (mapped <<PAGE_SHIFT ) < next) { +mmap_read_unlock (mm); +mmput (mm); +return -EBUSY ; + } + } +mmap_read_unlock (mm); +mmput (mm); + +/* Return the migrated data for verification. */ + ret =dmirror_bounce_init (&bounce, start, size); +if (ret) +return ret; +mutex_lock (&dmirror->mutex); + ret =dmirror_do_read (dmirror, start, end, &bounce); +mutex_unlock (&dmirror->mutex); +if (ret ==0 ) { +if (copy_to_user (u64_to_user_ptr (cmd->ptr), bounce.ptr, + bounce.size)) + ret = -EFAULT ; + } + + cmd->cpages = bounce.cpages; +dmirror_bounce_fini (&bounce); +return ret; +} +static int dmirror_migrate (struct dmirror *dmirror,struct hmm_dmirror_cmd *cmd) {
@@ -949,6 +1064,15 @@ static long dmirror_fops_unlocked_ioctl(struct file *filp, ret =dmirror_migrate (dmirror, &cmd);break ; +case HMM_DMIRROR_EXCLUSIVE : + ret =dmirror_exclusive (dmirror, &cmd); +break ; + +case HMM_DMIRROR_CHECK_EXCLUSIVE : + ret =dmirror_check_atomic (dmirror, cmd.addr, + cmd.addr + (cmd.npages <<PAGE_SHIFT )); +break ; +case HMM_DMIRROR_SNAPSHOT : ret =dmirror_snapshot (dmirror, &cmd);break ;
lib/test_hmm_uapi.h +2 -0
@@ -33,6 +33,8 @@ struct hmm_dmirror_cmd {#define HMM_DMIRROR_WRITE _IOWR('H', 0x01, struct hmm_dmirror_cmd)#define HMM_DMIRROR_MIGRATE _IOWR('H', 0x02, struct hmm_dmirror_cmd)#define HMM_DMIRROR_SNAPSHOT _IOWR('H', 0x03, struct hmm_dmirror_cmd) +#define HMM_DMIRROR_EXCLUSIVE _IOWR('H', 0x04, struct hmm_dmirror_cmd) +#define HMM_DMIRROR_CHECK_EXCLUSIVE _IOWR('H', 0x05, struct hmm_dmirror_cmd)/* * Values returned in hmm_dmirror_cmd.ptr for HMM_DMIRROR_SNAPSHOT.
tools/testing/selftests/vm/hmm-tests.c +158 -0
162 lines not on this page · raw message
Signature · 6 lines
2.20.1 _______________________________________________ Nouveau mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/nouveau
Alistair Popple
reviewerPATCH v9 9/10 nouveau/svm: Refactor nouveau_range_fault
In reply to Alistair PoppleCall mmu_interval_notifier_insert() as part of nouveau_range_fault(). This doesn't introduce any functional change but makes it easier for a subsequent patch to alter the behaviour of nouveau_range_fault() to support GPU atomic operations.
---
v9:
* Added Ben's Reviewed-By (thanks!)
--- drivers/gpu/drm/nouveau/nouveau_svm.c | 34 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 14 deletions(-)
drivers/gpu/drm/nouveau/nouveau_svm.c +20 -14
@@ -567,18 +567,27 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm,unsigned long hmm_pfns[1 ];struct hmm_range range = { .notifier = ¬ifier->notifier, - .start = notifier->notifier.interval_tree.start, - .end = notifier->notifier.interval_tree.last +1 , .default_flags = hmm_flags, .hmm_pfns = hmm_pfns, .dev_private_owner = drm->dev, }; -struct mm_struct *mm = notifier->notifier.mm; +struct mm_struct *mm = svmm->notifier.mm;int ret; + ret =mmu_interval_notifier_insert (¬ifier->notifier, mm, + args->p.addr, args->p.size, + &nouveau_svm_mni_ops); +if (ret) +return ret; + + range.start = notifier->notifier.interval_tree.start; + range.end = notifier->notifier.interval_tree.last +1 ; +while (true) { -if (time_after (jiffies, timeout)) -return -EBUSY ; +if (time_after (jiffies, timeout)) { + ret = -EBUSY ; + goto out; + } range.notifier_seq =mmu_interval_read_begin (range.notifier);mmap_read_lock (mm);
@@ -587,7 +596,7 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm,if (ret) {if (ret == -EBUSY )continue ; -return ret; + goto out; }mutex_lock (&svmm->mutex);
@@ -606,6 +615,9 @@ static int nouveau_range_fault(struct nouveau_svmm *svmm, svmm->vmm->vmm.object.client->super = false;mutex_unlock (&svmm->mutex); +out: +mmu_interval_notifier_remove (¬ifier->notifier); +return ret; }
@@ -727,14 +739,8 @@ nouveau_svm_fault(struct nvif_notify *notify) } notifier.svmm = svmm; - ret =mmu_interval_notifier_insert (¬ifier.notifier, mm, - args.i.p.addr, args.i.p.size, - &nouveau_svm_mni_ops); -if (!ret) { - ret =nouveau_range_fault (svmm, svm->drm, &args.i, -sizeof (args), hmm_flags, ¬ifier); -mmu_interval_notifier_remove (¬ifier.notifier); - } + ret =nouveau_range_fault (svmm, svm->drm, &args.i, +sizeof (args), hmm_flags, ¬ifier);mmput (mm); limit = args.i.p.addr + args.i.p.size;
Signature · 1 line
2.20.1
Alistair Popple
reviewerPATCH v9 10/10 nouveau/svm: Implement atomic SVM access
In reply to Alistair PoppleSome NVIDIA GPUs do not support direct atomic access to system memory via PCIe. Instead this must be emulated by granting the GPU exclusive access to the memory. This is achieved by replacing CPU page table entries with special swap entries that fault on userspace access.
The driver then grants the GPU permission to update the page undergoing atomic access via the GPU page tables. When CPU access to the page is required a CPU fault is raised which calls into the device driver via MMU notifiers to revoke the atomic access. The original page table entries are then restored allowing CPU access to proceed.
---
v9:
* Added Ben's Reviewed-By
v7:
* Removed magic values for fault access levels
* Improved readability of fault comparison code
v4:
* Check that page table entries haven't changed before mapping on the device
--- drivers/gpu/drm/nouveau/include/nvif/if000c.h | 1 + drivers/gpu/drm/nouveau/nouveau_svm.c | 126 ++++++++++++++++-- drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmm.h | 1 + .../drm/nouveau/nvkm/subdev/mmu/vmmgp100.c | 6 + 4 files changed, 123 insertions(+), 11 deletions(-)
drivers/gpu/drm/nouveau/include/nvif/if000c.h +1 -0
@@ -77,6 +77,7 @@ struct nvif_vmm_pfnmap_v0 {#define NVIF_VMM_PFNMAP_V0_APER 0x00000000000000f0ULL#define NVIF_VMM_PFNMAP_V0_HOST 0x0000000000000000ULL#define NVIF_VMM_PFNMAP_V0_VRAM 0x0000000000000010ULL +#define NVIF_VMM_PFNMAP_V0_A 0x0000000000000004ULL#define NVIF_VMM_PFNMAP_V0_W 0x0000000000000002ULL#define NVIF_VMM_PFNMAP_V0_V 0x0000000000000001ULL#define NVIF_VMM_PFNMAP_V0_NONE 0x0000000000000000ULL
drivers/gpu/drm/nouveau/nouveau_svm.c +115 -11
192 lines not on this page · raw message
drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmm.h +1 -0
@@ -178,6 +178,7 @@ void nvkm_vmm_unmap_region(struct nvkm_vmm *, struct nvkm_vma *);#define NVKM_VMM_PFN_APER 0x00000000000000f0ULL#define NVKM_VMM_PFN_HOST 0x0000000000000000ULL#define NVKM_VMM_PFN_VRAM 0x0000000000000010ULL +#define NVKM_VMM_PFN_A 0x0000000000000004ULL#define NVKM_VMM_PFN_W 0x0000000000000002ULL#define NVKM_VMM_PFN_V 0x0000000000000001ULL#define NVKM_VMM_PFN_NONE 0x0000000000000000ULL
drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c +6 -0
@@ -88,6 +88,9 @@ gp100_vmm_pgt_pfn(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt,if (!(*map->pfn &NVKM_VMM_PFN_W )) data |=BIT_ULL (6 );/* RO. */ +if (!(*map->pfn &NVKM_VMM_PFN_A )) + data |=BIT_ULL (7 );/* Atomic disable. */ +if (!(*map->pfn &NVKM_VMM_PFN_VRAM )) { addr = *map->pfn >>NVKM_VMM_PFN_ADDR_SHIFT ; addr = dma_map_page(dev, pfn_to_page(addr), 0,
@@ -322,6 +325,9 @@ gp100_vmm_pd0_pfn(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt,if (!(*map->pfn &NVKM_VMM_PFN_W )) data |=BIT_ULL (6 );/* RO. */ +if (!(*map->pfn &NVKM_VMM_PFN_A )) + data |=BIT_ULL (7 );/* Atomic disable. */ +if (!(*map->pfn &NVKM_VMM_PFN_VRAM )) { addr = *map->pfn >>NVKM_VMM_PFN_ADDR_SHIFT ; addr = dma_map_page(dev, pfn_to_page(addr), 0,
Signature · 1 line
2.20.1