Skip to contentSkip to conversation

HMM tests and minor fixes

PATCH Applied Ralph Campbell 5 participants 15 messages Nov 4, 2019 – Nov 18, 2019

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

v4 cover HMM tests and minor fixes

These changes are based on Jason's rdma/hmm branch (5.4.0-rc5). Patch 1 was previously posted here [1] but was dropped from that orginal series. Hopefully, the tests will reduce concerns about edge conditions. I'm sure more tests could be usefully added but I thought this was a good starting point.

Changes since v3:
patch 1:
Unchanged except rebased on Jason's latest hmm (bbe3329e354d3ab5dc18). patch 2:
Is now part of Jason's tree.
patch 3 (now 2):
Major changes to incorporate Jason's review feedback.
* drivers/char/hmm_dmirror.c driver moved to lib/test_hmm.c
* XArray used instead of "page tables".
* platform device driver removed.
* remove redundant copyright.

Changes since v2:
patch 1:
Removed hmm_range_needs_fault() and just use hmm_range_need_fault(). Updated the change log to include that it fixes a bug where hmm_range_fault() incorrectly returned an error when no fault is requested. patch 2:
Removed the confusing change log wording about DMA.
Changed hmm_range_fault() to return the PFN of the zero page like any other page.
patch 3:
Adjusted the test code to match the new zero page behavior.

Changes since v1:
Rebased to Jason's rdma/hmm branch (5.4.0-rc1). Cleaned up locking for the test driver's page tables. Incorporated Christoph Hellwig's comments.

[1] https://lore.kernel.org/linux-mm/[email protected]/

Ralph Campbell (2):
mm/hmm: make full use of walk_page_range()
mm/hmm/test: add self tests for HMM

 MAINTAINERS                            |    3 +
 include/uapi/linux/test_hmm.h          |   59 ++
 lib/Kconfig.debug                      |   11 +
 lib/Makefile                           |    1 +
 lib/test_hmm.c                         | 1306 ++++++++++++++++++++++++
 mm/hmm.c                               |  121 ++-
 tools/testing/selftests/vm/.gitignore  |    1 +
 tools/testing/selftests/vm/Makefile    |    3 +
 tools/testing/selftests/vm/config      |    2 +
 tools/testing/selftests/vm/hmm-tests.c | 1295 +++++++++++++++++++++++
 tools/testing/selftests/vm/run_vmtests |   16 +
 tools/testing/selftests/vm/test_hmm.sh |   97 ++
 12 files changed, 2852 insertions(+), 63 deletions(-)
 create mode 100644 include/uapi/linux/test_hmm.h
 create mode 100644 lib/test_hmm.c
 create mode 100644 tools/testing/selftests/vm/hmm-tests.c
 create mode 100755 tools/testing/selftests/vm/test_hmm.sh
Signature · 1 line
2.20.1
#1 lore raw

PATCH v4 1/2 mm/hmm: make full use of walk_page_range()

hmm_range_fault() calls find_vma() and walk_page_range() in a loop. This is unnecessary duplication since walk_page_range() calls find_vma() in a loop already.
Simplify hmm_range_fault() by defining a walk_test() callback function to filter unhandled vmas.
This also fixes a bug where hmm_range_fault() was not checking start >= vma->vm_start before checking vma->vm_flags so hmm_range_fault() could return an error based on the wrong vma for the requested range. It also fixes a bug when the vma has no read access and the caller did not request a fault, there shouldn't be any error return code.

Cc "Jérôme Glisse" <[email protected]>
Cc Jason Gunthorpe <[email protected]>
Cc Christoph Hellwig <[email protected]>
---
 mm/hmm.c | 121 ++++++++++++++++++++++++++-----------------------------
 1 file changed, 58 insertions(+), 63 deletions(-)
mm/hmm.c +58 -63
index 9e9d3f4ea17c..d4984a08ed9b 100644
@@ -65,18 +65,15 @@ static int hmm_vma_do_fault(struct mm_walk *walk, unsigned long addr,
 	return -EFAULT;
 }
 
-static int hmm_pfns_bad(unsigned long addr,
-			unsigned long end,
-			struct mm_walk *walk)
+static int hmm_pfns_fill(unsigned long addr, unsigned long end,
+		struct hmm_range *range, enum hmm_pfn_value_e value)
 {
-	struct hmm_vma_walk *hmm_vma_walk = walk->private;
-	struct hmm_range *range = hmm_vma_walk->range;
 	uint64_t *pfns = range->pfns;
 	unsigned long i;
 
 	i = (addr - range->start) >> PAGE_SHIFT;
 	for (; addr < end; addr += PAGE_SIZE, i++)
-		pfns[i] = range->values[HMM_PFN_ERROR];
+		pfns[i] = range->values[value];
 
 	return 0;
 }
@@ -403,7 +400,7 @@ static int hmm_vma_walk_pmd(pmd_t *pmdp,
 		}
 		return 0;
 	} else if (!pmd_present(pmd))
-		return hmm_pfns_bad(start, end, walk);
+		return hmm_pfns_fill(start, end, range, HMM_PFN_ERROR);
 
 	if (pmd_devmap(pmd) || pmd_trans_huge(pmd)) {
 		/*
@@ -431,7 +428,7 @@ static int hmm_vma_walk_pmd(pmd_t *pmdp,
 	 * recover.
 	 */
 	if (pmd_bad(pmd))
-		return hmm_pfns_bad(start, end, walk);
+		return hmm_pfns_fill(start, end, range, HMM_PFN_ERROR);
 
 	ptep = pte_offset_map(pmdp, addr);
 	i = (addr - range->start) >> PAGE_SHIFT;
@@ -589,13 +586,47 @@ static int hmm_vma_walk_hugetlb_entry(pte_t *pte, unsigned long hmask,
 #define hmm_vma_walk_hugetlb_entry NULL
 #endif /* CONFIG_HUGETLB_PAGE */
 
-static void hmm_pfns_clear(struct hmm_range *range,
-			   uint64_t *pfns,
-			   unsigned long addr,
-			   unsigned long end)
+static int hmm_vma_walk_test(unsigned long start, unsigned long end,
+			     struct mm_walk *walk)
 {
-	for (; addr < end; addr += PAGE_SIZE, pfns++)
-		*pfns = range->values[HMM_PFN_NONE];
+	struct hmm_vma_walk *hmm_vma_walk = walk->private;
+	struct hmm_range *range = hmm_vma_walk->range;
+	struct vm_area_struct *vma = walk->vma;
+
+	/*
+	 * Skip vma ranges that don't have struct page backing them or
+	 * map I/O devices directly.
+	 */
+	if (vma->vm_flags & (VM_IO | VM_PFNMAP | VM_MIXEDMAP))
+		return -EFAULT;
+
+	/*
+	 * If the vma does not allow read access, then assume that it does not
+	 * allow write access either. HMM does not support architectures
+	 * that allow write without read.
+	 */
+	if (!(vma->vm_flags & VM_READ)) {
+		bool fault, write_fault;
+
+		/*
+		 * Check to see if a fault is requested for any page in the
+		 * range.
+		 */
+		hmm_range_need_fault(hmm_vma_walk, range->pfns +
+					((start - range->start) >> PAGE_SHIFT),
+					(end - start) >> PAGE_SHIFT,
+					0, &fault, &write_fault);
+		if (fault || write_fault)
+			return -EFAULT;
+
+		hmm_pfns_fill(start, end, range, HMM_PFN_NONE);
+		hmm_vma_walk->last = end;
+
+		/* Skip this vma and continue processing the next vma. */
+		return 1;
+	}
+
+	return 0;
 }
 
 static const struct mm_walk_ops hmm_walk_ops = {
@@ -603,6 +634,7 @@ static const struct mm_walk_ops hmm_walk_ops = {
 	.pmd_entry	= hmm_vma_walk_pmd,
 	.pte_hole	= hmm_vma_walk_hole,
 	.hugetlb_entry	= hmm_vma_walk_hugetlb_entry,
+	.test_walk	= hmm_vma_walk_test,
 };
 
 /**
@@ -635,11 +667,13 @@ static const struct mm_walk_ops hmm_walk_ops = {
  */
 long hmm_range_fault(struct hmm_range *range, unsigned int flags)
 {
-	const unsigned long device_vma = VM_IO | VM_PFNMAP | VM_MIXEDMAP;
-	unsigned long start = range->start, end;
-	struct hmm_vma_walk hmm_vma_walk;
+	unsigned long start = range->start;
+	struct hmm_vma_walk hmm_vma_walk = {
+		.range = range,
+		.last = start,
+		.flags = flags,
+	};
 	struct mm_struct *mm = range->notifier->mm;
-	struct vm_area_struct *vma;
 	int ret;
 
 	lockdep_assert_held(&mm->mmap_sem);
@@ -648,52 +682,13 @@ long hmm_range_fault(struct hmm_range *range, unsigned int flags)
 		/* If range is no longer valid force retry. */
 		if (mmu_range_check_retry(range->notifier, range->notifier_seq))
 			return -EBUSY;
+		ret = walk_page_range(mm, start, range->end, &hmm_walk_ops,
+				      &hmm_vma_walk);
+		start = hmm_vma_walk.last;
+	} while (ret == -EBUSY);
 
-		vma = find_vma(mm, start);
-		if (vma == NULL || (vma->vm_flags & device_vma))
-			return -EFAULT;
-
-		if (!(vma->vm_flags & VM_READ)) {
-			/*
-			 * If vma do not allow read access, then assume that it
-			 * does not allow write access, either. HMM does not
-			 * support architecture that allow write without read.
-			 */
-			hmm_pfns_clear(range, range->pfns,
-				range->start, range->end);
-			return -EPERM;
-		}
-
-		hmm_vma_walk.pgmap = NULL;
-		hmm_vma_walk.last = start;
-		hmm_vma_walk.flags = flags;
-		hmm_vma_walk.range = range;
-		end = min(range->end, vma->vm_end);
-
-		walk_page_range(vma->vm_mm, start, end, &hmm_walk_ops,
-				&hmm_vma_walk);
-
-		do {
-			ret = walk_page_range(vma->vm_mm, start, end,
-					&hmm_walk_ops, &hmm_vma_walk);
-			start = hmm_vma_walk.last;
-
-			/* Keep trying while the range is valid. */
-		} while (ret == -EBUSY &&
-			 !mmu_range_check_retry(range->notifier,
-						range->notifier_seq));
-
-		if (ret) {
-			unsigned long i;
-
-			i = (hmm_vma_walk.last - range->start) >> PAGE_SHIFT;
-			hmm_pfns_clear(range, &range->pfns[i],
-				hmm_vma_walk.last, range->end);
-			return ret;
-		}
-		start = end;
-
-	} while (start < range->end);
+	if (ret)
+		return ret;
 
 	return (hmm_vma_walk.last - range->start) >> PAGE_SHIFT;
 }
Signature · 1 line
2.20.1
Reviewed-by on v4 1/2
#2 lore raw

Re: [PATCH v4 1/2] mm/hmm: make full use of walk_page_range()

Looks good,

Although we could clean this up a tidbit more by removing the start variable:

mm/hmm.c +3 -5
index d4984a08ed9b..b5b1ed646c2f 100644
@@ -667,10 +667,9 @@ static const struct mm_walk_ops hmm_walk_ops = {
  */
 long hmm_range_fault(struct hmm_range *range, unsigned int flags)
 {
-	unsigned long start = range->start;
 	struct hmm_vma_walk hmm_vma_walk = {
 		.range = range,
-		.last = start,
+		.last = range->start,
 		.flags = flags,
 	};
 	struct mm_struct *mm = range->notifier->mm;
@@ -682,9 +681,8 @@ long hmm_range_fault(struct hmm_range *range, unsigned int flags)
 		/* If range is no longer valid force retry. */
 		if (mmu_range_check_retry(range->notifier, range->notifier_seq))
 			return -EBUSY;
-		ret = walk_page_range(mm, start, range->end, &hmm_walk_ops,
-				      &hmm_vma_walk);
-		start = hmm_vma_walk.last;
+		ret = walk_page_range(mm, hmm_vma_walk.last, range->end,
+				      &hmm_walk_ops, &hmm_vma_walk);
 	} while (ret == -EBUSY);
 
 	if (ret)
#3 lore raw

Re: [PATCH v4 1/2] mm/hmm: make full use of walk_page_range()

Quoted 26 lines · Christoph Hellwig

Looks good,

Although we could clean this up a tidbit more by removing the start variable:

mm/hmm.c +1 -2
index d4984a08ed9b..b5b1ed646c2f 100644
@@ -667,10 +667,9 @@ static const struct mm_walk_ops hmm_walk_ops = {
   */
  long hmm_range_fault(struct hmm_range *range, unsigned int flags)
  {
-	unsigned long start = range->start;
  	struct hmm_vma_walk hmm_vma_walk = {
  		.range = range,
-		.last = start,
+		.last = range->start,
  		.flags = flags,
  	};
  	struct mm_struct *mm = range->notifier->mm;
@@ -682,9 +681,8 @@ long hmm_range_fault(struct hmm_range *range, unsigned int flags)
  		/* If range is no longer valid force retry. */

mm/hmm.c long hmm_range_fault(struct hmm_range *range, unsigned int flags) · line 688

Quoted 10 lines
mm/hmm.c +2 -3
  		if (mmu_range_check_retry(range->notifier, range->notifier_seq))
  			return -EBUSY;
-		ret = walk_page_range(mm, start, range->end, &hmm_walk_ops,
-				      &hmm_vma_walk);
-		start = hmm_vma_walk.last;
+		ret = walk_page_range(mm, hmm_vma_walk.last, range->end,
+				      &hmm_walk_ops, &hmm_vma_walk);
  	} while (ret == -EBUSY);
 
  	if (ret)

Thanks for the review.
I'll add this to patch 1 since I need to send a v5 for patch 2.

#4 lore raw

Re: [PATCH v4 1/2] mm/hmm: make full use of walk_page_range()

In reply to Ralph Campbell
Quoted 11 lines · Ralph Campbell

hmm_range_fault() calls find_vma() and walk_page_range() in a loop. This is unnecessary duplication since walk_page_range() calls find_vma() in a loop already.
Simplify hmm_range_fault() by defining a walk_test() callback function to filter unhandled vmas.
This also fixes a bug where hmm_range_fault() was not checking start >= vma->vm_start before checking vma->vm_flags so hmm_range_fault() could return an error based on the wrong vma for the requested range. It also fixes a bug when the vma has no read access and the caller did not request a fault, there shouldn't be any error return code.

Quoted 8 lines
Cc "Jérôme Glisse" <[email protected]>
Cc Jason Gunthorpe <[email protected]>
Cc Christoph Hellwig <[email protected]>
---
 mm/hmm.c | 121 ++++++++++++++++++++++++++-----------------------------
 1 file changed, 58 insertions(+), 63 deletions(-)

Applied to hmm.git with Christoph's hunk merged in

Thanks,
Jason

#5 lore raw

PATCH v4 2/2 mm/hmm/test: add self tests for HMM

In reply to Ralph Campbell

Add self tests for HMM.

---
 MAINTAINERS                            |    3 +
 include/uapi/linux/test_hmm.h          |   59 ++
 lib/Kconfig.debug                      |   11 +
 lib/Makefile                           |    1 +
 lib/test_hmm.c                         | 1306 ++++++++++++++++++++++++
 tools/testing/selftests/vm/.gitignore  |    1 +
 tools/testing/selftests/vm/Makefile    |    3 +
 tools/testing/selftests/vm/config      |    2 +
 tools/testing/selftests/vm/hmm-tests.c | 1295 +++++++++++++++++++++++
 tools/testing/selftests/vm/run_vmtests |   16 +
 tools/testing/selftests/vm/test_hmm.sh |   97 ++
 11 files changed, 2794 insertions(+)
 create mode 100644 include/uapi/linux/test_hmm.h
 create mode 100644 lib/test_hmm.c
 create mode 100644 tools/testing/selftests/vm/hmm-tests.c
 create mode 100755 tools/testing/selftests/vm/test_hmm.sh
MAINTAINERS +3 -0
index c6c34d04ce95..8d53e39df193 100644
@@ -7428,7 +7428,10 @@ L:	[email protected]
 S:	Maintained
 F:	mm/hmm*
 F:	include/linux/hmm*
+F:	include/uapi/linux/test_hmm*
 F:	Documentation/vm/hmm.rst
+F:	lib/test_hmm*
+F:	tools/testing/selftests/vm/*hmm*
 
 HOST AP DRIVER
 M:	Jouni Malinen <[email protected]>
include/uapi/linux/test_hmm.h new file +59 -0

59 lines not on this page · raw message

lib/Kconfig.debug +11 -0
index 93d97f9b0157..0354379d6cd0 100644
@@ -2057,6 +2057,17 @@ config TEST_MEMINIT
 
 	  If unsure, say N.
 
+config TEST_HMM
+	tristate "Test HMM (Heterogeneous Memory Management)"
+	depends on HMM_MIRROR
+	depends on DEVICE_PRIVATE
+	help
+	  This is a pseudo device driver solely for testing HMM.
+	  Say M here if you want to build the HMM test module.
+	  Doing so will allow you to run tools/testing/selftest/vm/hmm-tests.
+
+	  If unsure, say N.
+
 endif # RUNTIME_TESTING_MENU
 
 config MEMTEST
lib/Makefile +1 -0
index c5892807e06f..6a9a0e564866 100644
@@ -89,6 +89,7 @@ obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
 obj-$(CONFIG_TEST_STACKINIT) += test_stackinit.o
 obj-$(CONFIG_TEST_BLACKHOLE_DEV) += test_blackhole_dev.o
 obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o
+obj-$(CONFIG_TEST_HMM) += test_hmm.o
 
 obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
 
lib/test_hmm.c new file +1306 -0

1,306 lines not on this page · raw message

tools/testing/selftests/vm/.gitignore +1 -0
index 31b3c98b6d34..3054565b3f07 100644
@@ -14,3 +14,4 @@ virtual_address_range
 gup_benchmark
 va_128TBswitch
 map_fixed_noreplace
+hmm-tests
tools/testing/selftests/vm/Makefile +3 -0
index 9534dc2bc929..5643cfb5e3d6 100644
@@ -5,6 +5,7 @@ CFLAGS = -Wall -I ../../../../usr/include $(EXTRA_CFLAGS)
 LDLIBS = -lrt
 TEST_GEN_FILES = compaction_test
 TEST_GEN_FILES += gup_benchmark
+TEST_GEN_FILES += hmm-tests
 TEST_GEN_FILES += hugepage-mmap
 TEST_GEN_FILES += hugepage-shm
 TEST_GEN_FILES += map_hugetlb
@@ -26,6 +27,8 @@ TEST_FILES := test_vmalloc.sh
 KSFT_KHDR_INSTALL := 1
 include ../lib.mk
 
+$(OUTPUT)/hmm-tests: LDLIBS += -lhugetlbfs -lpthread
+
 $(OUTPUT)/userfaultfd: LDLIBS += -lpthread
 
 $(OUTPUT)/mlock-random-test: LDLIBS += -lcap
tools/testing/selftests/vm/config +2 -0
index 1c0d76cb5adf..7fd96113575e 100644
@@ -1,2 +1,4 @@
 CONFIG_SYSVIPC=y
 CONFIG_USERFAULTFD=y
+CONFIG_HMM_MIRROR=y
+CONFIG_DEVICE_PRIVATE=y
tools/testing/selftests/vm/hmm-tests.c new file +1295 -0

1,295 lines not on this page · raw message

tools/testing/selftests/vm/run_vmtests +16 -0
index 951c507a27f7..634cfefdaffd 100755
@@ -227,4 +227,20 @@ else
 	exitcode=1
 fi
 
+echo "------------------------------------"
+echo "running HMM smoke test"
+echo "------------------------------------"
+./test_hmm.sh smoke
+ret_val=$?
+
+if [ $ret_val -eq 0 ]; then
+	echo "[PASS]"
+elif [ $ret_val -eq $ksft_skip ]; then
+	echo "[SKIP]"
+	exitcode=$ksft_skip
+else
+	echo "[FAIL]"
+	exitcode=1
+fi
+
 exit $exitcode
tools/testing/selftests/vm/test_hmm.sh new file +97 -0

97 lines not on this page · raw message

Signature · 1 line
2.20.1
#6 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Shouldn't this go into mm/ instead? It certainly doesn't seem like a library.

Quoted 8 lines
lib/test_hmm.c new file +8 -0
+static int dmirror_bounce_copy_from(struct dmirror_bounce *bounce,
+				    unsigned long addr)
+{
+	unsigned long end = addr + bounce->size;
+	char __user *uptr = (void __user *)addr;
+	void *ptr = bounce->ptr;
+
+	for (; addr < end; addr += PAGE_SIZE, ptr += PAGE_SIZE,

lib/test_hmm.c line 145

Quoted 10 lines
lib/test_hmm.c new file +10 -0
+					      uptr += PAGE_SIZE) {
+		int ret;
+
+		ret = copy_from_user(ptr, uptr, PAGE_SIZE);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}

Why does this iterate in page sized chunks? I don't remember a page size limit on copy_{from,to}_user.

Quoted 4 lines
lib/test_hmm.c new file +4 -0
+static int dmirror_invalidate_range_start(struct mmu_notifier *mn,
+				const struct mmu_notifier_range *update)
+{
+	struct dmirror *dmirror = container_of(mn, struct dmirror, notifier);

lib/test_hmm.c line 198

Quoted 10 lines
lib/test_hmm.c new file +10 -0
+
+	if (mmu_notifier_range_blockable(update))
+		mutex_lock(&dmirror->mutex);
+	else if (!mutex_trylock(&dmirror->mutex))
+		return -EAGAIN;
+
+	dmirror_do_update(dmirror, update->start, update->end);
+	mutex_unlock(&dmirror->mutex);
+	return 0;
+}

Can we adopts this to Jasons new interval tree invalidate?

lib/test_hmm.c line 254

Quoted 9 lines
lib/test_hmm.c new file +9 -0
+static int dmirror_fops_open(struct inode *inode, struct file *filp)
+{
+	struct cdev *cdev = inode->i_cdev;
+	struct dmirror_device *mdevice;
+	struct dmirror *dmirror;
+
+	/* No exclusive opens. */
+	if (filp->f_flags & O_EXCL)
+		return -EINVAL;

Device files usually just ignore O_EXCL, I don't see why this one would be any different.

lib/test_hmm.c line 266

Quoted 11 lines
lib/test_hmm.c new file +11 -0
+	mdevice = container_of(cdev, struct dmirror_device, cdevice);
+	dmirror = dmirror_new(mdevice);
+	if (!dmirror)
+		return -ENOMEM;
+
+	/* Only the first open registers the address space. */
+	mutex_lock(&mdevice->devmem_lock);
+	if (filp->private_data)
+		goto err_busy;
+	filp->private_data = dmirror;
+	mutex_unlock(&mdevice->devmem_lock);

->open is only called for the first open of a given file structure..

lib/test_hmm.c line 281

Quoted 6 lines
lib/test_hmm.c new file +6 -0
+static int dmirror_fops_release(struct inode *inode, struct file *filp)
+{
+	struct dmirror *dmirror = filp->private_data;
+
+	if (!dmirror)
+		return 0;

This can't happen if your ->open never returns 0 without setting the private data.

lib/test_hmm.c line 283

Quoted 1 line
lib/test_hmm.c new file +1 -0
+	filp->private_data = NULL;

The file is feed afterwards, no need to clear the private data.

#7 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 2 lines · Christoph Hellwig

Shouldn't this go into mm/ instead? It certainly doesn't seem like a library.

I was following the convention for the other vm test kernel modules. I see a couple of modules in mm/ but I don't have a personal preference for where to place it.

Andrew, do you have a preference?

Quoted 13 lines
Quoted 13 lines
lib/test_hmm.c new file +13 -0
+static int dmirror_bounce_copy_from(struct dmirror_bounce *bounce,
+				    unsigned long addr)
+{
+	unsigned long end = addr + bounce->size;
+	char __user *uptr = (void __user *)addr;
+	void *ptr = bounce->ptr;
+
+	for (; addr < end; addr += PAGE_SIZE, ptr += PAGE_SIZE,
+					      uptr += PAGE_SIZE) {
+		int ret;
+
+		ret = copy_from_user(ptr, uptr, PAGE_SIZE);
+		if (ret)
Quoted 8 lines

lib/test_hmm.c line 145

Quoted 5 lines
lib/test_hmm.c new file +5 -0
+			return ret;
+	}
+
+	return 0;
+}

Why does this iterate in page sized chunks? I don't remember a page size limit on copy_{from,to}_user.

Good point. I'll fix that.

Quoted 8 lines
Quoted 8 lines
lib/test_hmm.c new file +8 -0
+static int dmirror_invalidate_range_start(struct mmu_notifier *mn,
+				const struct mmu_notifier_range *update)
+{
+	struct dmirror *dmirror = container_of(mn, struct dmirror, notifier);
+
+	if (mmu_notifier_range_blockable(update))
+		mutex_lock(&dmirror->mutex);
+	else if (!mutex_trylock(&dmirror->mutex))
Quoted 8 lines

lib/test_hmm.c line 198

Quoted 6 lines
lib/test_hmm.c new file +6 -0
+		return -EAGAIN;
+
+	dmirror_do_update(dmirror, update->start, update->end);
+	mutex_unlock(&dmirror->mutex);
+	return 0;
+}

Can we adopts this to Jasons new interval tree invalidate?

Well, it would mean registering for the whole process address space. I'll give it a try.

Quoted 4 lines
Quoted 4 lines
lib/test_hmm.c new file +4 -0
+static int dmirror_fops_open(struct inode *inode, struct file *filp)
+{
+	struct cdev *cdev = inode->i_cdev;
+	struct dmirror_device *mdevice;
Quoted 8 lines

lib/test_hmm.c line 254

Quoted 5 lines
lib/test_hmm.c new file +5 -0
+	struct dmirror *dmirror;
+
+	/* No exclusive opens. */
+	if (filp->f_flags & O_EXCL)
+		return -EINVAL;

Device files usually just ignore O_EXCL, I don't see why this one would be any different.

OK, I'll remove that test.

Quoted 24 lines

lib/test_hmm.c line 266

Quoted 11 lines
lib/test_hmm.c new file +11 -0
+	mdevice = container_of(cdev, struct dmirror_device, cdevice);
+	dmirror = dmirror_new(mdevice);
+	if (!dmirror)
+		return -ENOMEM;
+
+	/* Only the first open registers the address space. */
+	mutex_lock(&mdevice->devmem_lock);
+	if (filp->private_data)
+		goto err_busy;
+	filp->private_data = dmirror;
+	mutex_unlock(&mdevice->devmem_lock);

->open is only called for the first open of a given file structure..

lib/test_hmm.c line 281

Quoted 6 lines
lib/test_hmm.c new file +6 -0
+static int dmirror_fops_release(struct inode *inode, struct file *filp)
+{
+	struct dmirror *dmirror = filp->private_data;
+
+	if (!dmirror)
+		return 0;

This can't happen if your ->open never returns 0 without setting the private data.

Quoted 3 lines

lib/test_hmm.c line 283

Quoted 1 line
lib/test_hmm.c new file +1 -0
+	filp->private_data = NULL;

The file is feed afterwards, no need to clear the private data.

OK, I'll clean that up.

#8 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 17 lines · Ralph Campbell
Quoted 8 lines
Quoted 8 lines
lib/test_hmm.c new file +8 -0
+static int dmirror_invalidate_range_start(struct mmu_notifier *mn,
+				const struct mmu_notifier_range *update)
+{
+	struct dmirror *dmirror = container_of(mn, struct dmirror, notifier);
+
+	if (mmu_notifier_range_blockable(update))
+		mutex_lock(&dmirror->mutex);
+	else if (!mutex_trylock(&dmirror->mutex))
Quoted 8 lines

lib/test_hmm.c line 198

Quoted 6 lines
lib/test_hmm.c new file +6 -0
+		return -EAGAIN;
+
+	dmirror_do_update(dmirror, update->start, update->end);
+	mutex_unlock(&dmirror->mutex);
+	return 0;
+}

Can we adopts this to Jasons new interval tree invalidate?

Quoted 2 lines

Well, it would mean registering for the whole process address space. I'll give it a try.

I'm not sure it makes much sense that this testing is essentially modeled after nouveau's usage which is very strange compared to the other drivers.

Jason

#9 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 6 lines · Jason Gunthorpe
Quoted 2 lines

Well, it would mean registering for the whole process address space. I'll give it a try.

I'm not sure it makes much sense that this testing is essentially modeled after nouveau's usage which is very strange compared to the other drivers.

Which means we really should make the test cases fit the proper usage. Maybe defer the tests for 5.5 and just merge the first patch for now?

#10 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 11 lines · Christoph Hellwig
Quoted 6 lines · Jason Gunthorpe
Quoted 2 lines

Well, it would mean registering for the whole process address space. I'll give it a try.

I'm not sure it makes much sense that this testing is essentially modeled after nouveau's usage which is very strange compared to the other drivers.

Which means we really should make the test cases fit the proper usage. Maybe defer the tests for 5.5 and just merge the first patch for now?

I think this a good point to discuss.
Some devices will want to register for all changes to the process address space because there is no requirement to preregister regions that the device can access verses devices like InfiniBand where a range of addresses have to be registered before the device can access those addresses. So for nouveau and the hmm-test driver, the mmu_range_notifier_insert() and mmu_range_notifier_remove() are only used long enough to get a stable copy of a small part of the process address space and copy it to the device's page table. Then the regular process wide invalidations are required to keep the device's page tables consistent with the process page table.

The "hacky" part of the current design is the interaction between the short term narrow address range invalidations verses the long term process wide invalidations. (double callbacks, double locking of the device page table)

Both types of invalidate callbacks seem useful to me so forcing a driver to use only one type doesn't make sense to me.

#11 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 14 lines · Ralph Campbell
Quoted 11 lines · Christoph Hellwig
Quoted 6 lines · Jason Gunthorpe
Quoted 2 lines

Well, it would mean registering for the whole process address space. I'll give it a try.

I'm not sure it makes much sense that this testing is essentially modeled after nouveau's usage which is very strange compared to the other drivers.

Which means we really should make the test cases fit the proper usage. Maybe defer the tests for 5.5 and just merge the first patch for now?

Quoted 5 lines

I think this a good point to discuss.
Some devices will want to register for all changes to the process address space because there is no requirement to preregister regions that the device can access verses devices like InfiniBand where a range of addresses have to be registered before the device can access those addresses.

But this is a very bad idea to register and do HW actions for ranges that can't possibly have any pages registered. It slows down the entire application

I think the ODP approach might be saner, when it mirrors the entire address space it chops it up into VA chunks, and once a page is registered on the HW the VA chunk goes into the interval tree.

Presumably the GPU also has some kind of page table tree and you could set one of the levels as the VA interval when there are populated children

Jason

#12 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 25 lines · Jason Gunthorpe
Quoted 14 lines · Ralph Campbell
Quoted 11 lines · Christoph Hellwig
Quoted 6 lines · Jason Gunthorpe
Quoted 2 lines

Well, it would mean registering for the whole process address space. I'll give it a try.

I'm not sure it makes much sense that this testing is essentially modeled after nouveau's usage which is very strange compared to the other drivers.

Which means we really should make the test cases fit the proper usage. Maybe defer the tests for 5.5 and just merge the first patch for now?

Quoted 5 lines

I think this a good point to discuss.
Some devices will want to register for all changes to the process address space because there is no requirement to preregister regions that the device can access verses devices like InfiniBand where a range of addresses have to be registered before the device can access those addresses.

But this is a very bad idea to register and do HW actions for ranges that can't possibly have any pages registered. It slows down the entire application

Quoted 8 lines

I think the ODP approach might be saner, when it mirrors the entire address space it chops it up into VA chunks, and once a page is registered on the HW the VA chunk goes into the interval tree.

Presumably the GPU also has some kind of page table tree and you could set one of the levels as the VA interval when there are populated children

Jason

I wasn't suggesting that HW invalidates happen in two places. I'm suggesting the two styles of invalidates can work together. For example, what if a driver calls mmu_notifier_register(mn, mm) to register for address space wide invalidations, then some time later there is a device page table fault and the driver calls mmu_range_notifier_insert() but with a NULL ops.invalidate. The fault handler follows the nouveau/test_hmm pattern to call
mmu_range_read_begin()
hmm_range_fault()
device lock
mmu_range_read_retry()
update device page tables
device unlock
mmu_range_notifier_remove()

The global invalidate() callback would get the device lock and call into mm to update the sequence number of any affected ranges instead of having a range invalidate callback, and then do the HW invalidations.

#13 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

Quoted 34 lines · Ralph Campbell
Quoted 32 lines · Jason Gunthorpe
Quoted 14 lines · Ralph Campbell
Quoted 11 lines · Christoph Hellwig
Quoted 6 lines · Jason Gunthorpe
Quoted 2 lines

Well, it would mean registering for the whole process address space. I'll give it a try.

I'm not sure it makes much sense that this testing is essentially modeled after nouveau's usage which is very strange compared to the other drivers.

Which means we really should make the test cases fit the proper usage. Maybe defer the tests for 5.5 and just merge the first patch for now?

Quoted 5 lines

I think this a good point to discuss.
Some devices will want to register for all changes to the process address space because there is no requirement to preregister regions that the device can access verses devices like InfiniBand where a range of addresses have to be registered before the device can access those addresses.

But this is a very bad idea to register and do HW actions for ranges that can't possibly have any pages registered. It slows down the entire application

I think the ODP approach might be saner, when it mirrors the entire address space it chops it up into VA chunks, and once a page is registered on the HW the VA chunk goes into the interval tree.

Presumably the GPU also has some kind of page table tree and you could set one of the levels as the VA interval when there are populated children

Quoted 8 lines
Quoted 1 line

Jason

I wasn't suggesting that HW invalidates happen in two places. I'm suggesting the two styles of invalidates can work together. For example, what if a driver calls mmu_notifier_register(mn, mm) to register for address space wide invalidations, then some time later there is a device page table fault and the driver calls mmu_range_notifier_insert() but with a NULL ops.invalidate.

I'm saying drivers shouldn't do that, it is a basically a hack that it works at all.

Quoted 4 lines

The global invalidate() callback would get the device lock and call into mm to update the sequence number of any affected ranges instead of having a range invalidate callback, and then do the HW invalidations.

No, I just finished eliminating all the range iteration code in the drivers - and you can't update the sequence number from any place other than the interval invalidation callback anyhow.

Jason

maintainer
#14 lore raw

Re: [PATCH v4 2/2] mm/hmm/test: add self tests for HMM

In reply to Ralph Campbell
Quoted 9 lines · On Tue, 12 Nov 2019 13:51:11 -0800 Ralph Campbell <[email protected]> wrote:
Quoted 2 lines · Christoph Hellwig

Shouldn't this go into mm/ instead? It certainly doesn't seem like a library.

I was following the convention for the other vm test kernel modules. I see a couple of modules in mm/ but I don't have a personal preference for where to place it.

Andrew, do you have a preference?

q:/usr/src/25> ls -l lib/test_*.c|wc

     33     297    2051

lib/ is a somewhat strange place, but I'd use that for now.

Presumably one day someone will (pointlessly) move these into lib/test-modules/.

Then into lib/test-modules/mm/, etc..