From: Mika Penttilä <[email protected]>

Enhance the hmm test driver (lib/test_hmm) with a migrate on fault
case: a single hmm_range_fault() walk with HMM_PFN_REQ_FAULT |
HMM_PFN_REQ_MIGRATE faults in and collects the range, which is then
migrated to device private memory and mapped into the dmirror page
table. The selftest leaves the first two pages unwritten, so page 0
has to be faulted in and page 1 maps the zero page.

If mmu_interval_read_retry() trips after migrate_vma_pages(), the
dmirror mapping is skipped and the -EBUSY retry re-walks. Normally the
trip comes from an unmap or zap: the migration ptes are gone,
migrate_vma_finalize() releases the destination pages and the retry
migrates again. Pages committed to device memory without a dmirror
mapping (e.g. after a huge zero pmd split in migrate_vma_insert_page())
are still consistent, since any invalidation also clears the dmirror
page table, and a real device would map them on its next fault. The
selftest doesn't hit that case: the unwritten pages share a pmd with
written ones, so there is no huge zero pmd.

Cc: David Hildenbrand <[email protected]>
Cc: Jason Gunthorpe <[email protected]>
Cc: Leon Romanovsky <[email protected]>
Cc: Alistair Popple <[email protected]>
Cc: Balbir Singh <[email protected]>
Cc: Zi Yan <[email protected]>
Cc: Matthew Brost <[email protected]>
Signed-off-by: Marco Pagani <[email protected]>
Signed-off-by: Mika Penttilä <[email protected]>
---
 lib/test_hmm.c                         | 172 +++++++++++++++++++++++--
 lib/test_hmm_uapi.h                    |  21 +--
 tools/testing/selftests/mm/hmm-tests.c |  56 ++++++++
 3 files changed, 226 insertions(+), 23 deletions(-)

diff --git a/lib/test_hmm.c b/lib/test_hmm.c
index f05f801b8404..2045896493ae 100644
--- a/lib/test_hmm.c
+++ b/lib/test_hmm.c
@@ -36,6 +36,7 @@
 #define DMIRROR_RANGE_FAULT_TIMEOUT    1000
 #define DEVMEM_CHUNK_SIZE              (256 * 1024 * 1024U)
 #define DEVMEM_CHUNKS_RESERVE          16
+#define PFNS_ARRAY_SIZE                        64
 
 /*
  * For device_private pages, dpage is just a dummy struct page
@@ -355,6 +356,7 @@ static int dmirror_range_fault(struct dmirror *dmirror,
        struct mm_struct *mm = dmirror->notifier.mm;
        unsigned long timeout =
                jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+       bool migrate = range->default_flags & HMM_PFN_REQ_MIGRATE;
        int ret;
 
        while (true) {
@@ -364,9 +366,15 @@ static int dmirror_range_fault(struct dmirror *dmirror,
                }
 
                range->notifier_seq = mmu_interval_read_begin(range->notifier);
-               mmap_read_lock(mm);
-               ret = hmm_range_fault(range);
-               mmap_read_unlock(mm);
+
+               /* mmap lock held for whole migrate on fault operation */
+               if (!migrate) {
+                       mmap_read_lock(mm);
+                       ret = hmm_range_fault(range);
+                       mmap_read_unlock(mm);
+               } else {
+                       ret = hmm_range_fault(range);
+               }
                if (ret) {
                        if (ret == -EBUSY)
                                continue;
@@ -382,7 +390,9 @@ static int dmirror_range_fault(struct dmirror *dmirror,
                break;
        }
 
-       ret = dmirror_do_fault(dmirror, range);
+       /* update device page table after migration */
+       if (!migrate)
+               ret = dmirror_do_fault(dmirror, range);
 
        mutex_unlock(&dmirror->mutex);
 out:
@@ -994,8 +1004,12 @@ static int dmirror_atomic_map(unsigned long addr, struct 
page *page,
        return 0;
 }
 
-static int dmirror_migrate_finalize_and_map(struct migrate_vma *args,
-                                           struct dmirror *dmirror)
+/*
+ * Map the migrated pages into the device's page table. Caller must hold
+ * dmirror->mutex.
+ */
+static int __dmirror_migrate_map_locked(struct migrate_vma *args,
+                                       struct dmirror *dmirror)
 {
        unsigned long start = args->start;
        unsigned long end = args->end;
@@ -1005,9 +1019,6 @@ static int dmirror_migrate_finalize_and_map(struct 
migrate_vma *args,
        const unsigned long start_pfn = start >> PAGE_SHIFT;
        const unsigned long end_pfn = end >> PAGE_SHIFT;
 
-       /* Map the migrated pages into the device's page tables. */
-       mutex_lock(&dmirror->mutex);
-
        for (pfn = start_pfn; pfn < end_pfn; pfn++, src++, dst++) {
                struct page *dpage;
                void *entry;
@@ -1036,17 +1047,27 @@ static int dmirror_migrate_finalize_and_map(struct 
migrate_vma *args,
                        if (*dst & MIGRATE_PFN_WRITE)
                                entry = xa_tag_pointer(entry, DPT_XA_TAG_WRITE);
                        entry = xa_store(&dmirror->pt, pfn + i, entry, 
GFP_ATOMIC);
-                       if (xa_is_err(entry)) {
-                               mutex_unlock(&dmirror->mutex);
+                       if (xa_is_err(entry))
                                return xa_err(entry);
-                       }
                }
        }
 
-       mutex_unlock(&dmirror->mutex);
        return 0;
 }
 
+static int dmirror_migrate_finalize_and_map(struct migrate_vma *args,
+                                           struct dmirror *dmirror)
+{
+       int ret;
+
+       /* Map the migrated pages into the device's page tables. */
+       mutex_lock(&dmirror->mutex);
+       ret = __dmirror_migrate_map_locked(args, dmirror);
+       mutex_unlock(&dmirror->mutex);
+
+       return ret;
+}
+
 static int dmirror_exclusive(struct dmirror *dmirror,
                             struct hmm_dmirror_cmd *cmd)
 {
@@ -1377,6 +1398,127 @@ static int dmirror_migrate_to_device(struct dmirror 
*dmirror,
        return ret;
 }
 
+static int do_fault_and_migrate(struct dmirror *dmirror, struct hmm_range 
*range)
+{
+       struct migrate_vma *migrate = range->migrate;
+       unsigned long timeout =
+               jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
+       int ret;
+
+       do {
+               mmap_read_lock(dmirror->notifier.mm);
+
+               /* Fault-in pages for migration */
+               ret = dmirror_range_fault(dmirror, range);
+               /*
+                * Set this up even on error: hmm_range_fault() may have 
collected
+                * part of the range before failing.
+                */
+               migrate_hmm_range_setup(range);
+               if (ret) {
+                       /*
+                        * dst[] entries are empty, so this marks every 
collected
+                        * migration as failed. finalize then restores the 
source
+                        * mappings and drops the associated locks/references.
+                        */
+                       migrate_vma_pages(migrate);
+                       migrate_vma_finalize(migrate);
+                       goto unlock;
+               }
+
+               pr_debug("Migrating from sys mem to device mem\n");
+
+               dmirror_migrate_alloc_and_copy(migrate, dmirror);
+               migrate_vma_pages(migrate);
+
+               mutex_lock(&dmirror->mutex);
+               if (mmu_interval_read_retry(&dmirror->notifier,
+                                           range->notifier_seq))
+                       ret = -EBUSY;
+               else
+                       ret = __dmirror_migrate_map_locked(migrate, dmirror);
+               mutex_unlock(&dmirror->mutex);
+
+               migrate_vma_finalize(migrate);
+unlock:
+               mmap_read_unlock(dmirror->notifier.mm);
+       } while (ret == -EBUSY && !time_after(jiffies, timeout));
+
+       return ret;
+}
+
+static int dmirror_fault_and_migrate_to_device(struct dmirror *dmirror,
+                                              struct hmm_dmirror_cmd *cmd)
+{
+       unsigned long start, size, end, next;
+       unsigned long src_pfns[PFNS_ARRAY_SIZE] = { 0 };
+       unsigned long dst_pfns[PFNS_ARRAY_SIZE] = { 0 };
+       struct migrate_vma migrate = { 0 };
+       struct hmm_range range = { 0 };
+       struct dmirror_bounce bounce;
+       int ret = 0;
+
+       /* Whole range */
+       start = cmd->addr;
+       size = cmd->npages << PAGE_SHIFT;
+       end = start + size;
+
+       if (!mmget_not_zero(dmirror->notifier.mm)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       migrate.pgmap_owner = dmirror->mdevice;
+       migrate.src = src_pfns;
+       migrate.dst = dst_pfns;
+       migrate.flags = MIGRATE_VMA_SELECT_SYSTEM;
+
+       range.migrate = &migrate;
+       range.hmm_pfns = src_pfns;
+       range.pfn_flags_mask = 0;
+       range.default_flags = HMM_PFN_REQ_FAULT | HMM_PFN_REQ_MIGRATE;
+       range.dev_private_owner = dmirror->mdevice;
+       range.notifier = &dmirror->notifier;
+
+       for (next = start; next < end; next = range.end) {
+               range.start = next;
+               range.end = min(end, next + (PFNS_ARRAY_SIZE << PAGE_SHIFT));
+
+               pr_debug("Fault and migrate range start:%#lx end:%#lx\n",
+                        range.start, range.end);
+
+               ret = do_fault_and_migrate(dmirror, &range);
+               if (ret)
+                       goto out_mmput;
+       }
+
+       /*
+        * Return the migrated data for verification.
+        * Only for pages in device zone
+        */
+       ret = dmirror_bounce_init(&bounce, start, size);
+       if (ret)
+               goto out_mmput;
+
+       mutex_lock(&dmirror->mutex);
+       ret = dmirror_do_read(dmirror, start, end, &bounce);
+       mutex_unlock(&dmirror->mutex);
+       if (ret == 0) {
+               ret = copy_to_user(u64_to_user_ptr(cmd->ptr), bounce.ptr, 
bounce.size);
+               if (ret)
+                       ret = -EFAULT;
+       }
+
+       cmd->cpages = bounce.cpages;
+       dmirror_bounce_fini(&bounce);
+
+
+out_mmput:
+       mmput(dmirror->notifier.mm);
+out:
+       return ret;
+}
+
 static void dmirror_mkentry(struct dmirror *dmirror, struct hmm_range *range,
                            unsigned char *perm, unsigned long entry)
 {
@@ -1645,6 +1787,10 @@ static long dmirror_fops_unlocked_ioctl(struct file 
*filp,
                ret = dmirror_migrate_to_device(dmirror, &cmd);
                break;
 
+       case HMM_DMIRROR_MIGRATE_ON_FAULT_TO_DEV:
+               ret = dmirror_fault_and_migrate_to_device(dmirror, &cmd);
+               break;
+
        case HMM_DMIRROR_MIGRATE_TO_SYS:
                ret = dmirror_migrate_to_system(dmirror, &cmd);
                break;
diff --git a/lib/test_hmm_uapi.h b/lib/test_hmm_uapi.h
index ea9b0ec404fb..67d267a3b999 100644
--- a/lib/test_hmm_uapi.h
+++ b/lib/test_hmm_uapi.h
@@ -29,16 +29,17 @@ struct hmm_dmirror_cmd {
 };
 
 /* Expose the address space of the calling process through hmm device file */
-#define HMM_DMIRROR_READ               _IOWR('H', 0x00, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_WRITE              _IOWR('H', 0x01, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_MIGRATE_TO_DEV     _IOWR('H', 0x02, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_MIGRATE_TO_SYS     _IOWR('H', 0x03, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_SNAPSHOT           _IOWR('H', 0x04, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_EXCLUSIVE          _IOWR('H', 0x05, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_CHECK_EXCLUSIVE    _IOWR('H', 0x06, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_RELEASE            _IOWR('H', 0x07, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_FLAGS              _IOWR('H', 0x08, struct hmm_dmirror_cmd)
-#define HMM_DMIRROR_READ_UNLOCKED      _IOWR('H', 0x09, struct hmm_dmirror_cmd)
+#define HMM_DMIRROR_READ                       _IOWR('H', 0x00, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_WRITE                      _IOWR('H', 0x01, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_MIGRATE_TO_DEV             _IOWR('H', 0x02, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_MIGRATE_TO_SYS             _IOWR('H', 0x03, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_SNAPSHOT                   _IOWR('H', 0x04, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_EXCLUSIVE                  _IOWR('H', 0x05, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_CHECK_EXCLUSIVE            _IOWR('H', 0x06, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_RELEASE                    _IOWR('H', 0x07, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_FLAGS                      _IOWR('H', 0x08, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_READ_UNLOCKED              _IOWR('H', 0x09, struct 
hmm_dmirror_cmd)
+#define HMM_DMIRROR_MIGRATE_ON_FAULT_TO_DEV    _IOWR('H', 0x0a, struct 
hmm_dmirror_cmd)
 
 #define HMM_DMIRROR_FLAG_FAIL_ALLOC    (1ULL << 0)
 
diff --git a/tools/testing/selftests/mm/hmm-tests.c 
b/tools/testing/selftests/mm/hmm-tests.c
index e5f273ca84c1..bacf573d83a9 100644
--- a/tools/testing/selftests/mm/hmm-tests.c
+++ b/tools/testing/selftests/mm/hmm-tests.c
@@ -333,6 +333,13 @@ static int hmm_migrate_sys_to_dev(int fd,
        return hmm_dmirror_cmd(fd, HMM_DMIRROR_MIGRATE_TO_DEV, buffer, npages);
 }
 
+static int hmm_migrate_on_fault_sys_to_dev(int fd,
+                                          struct hmm_buffer *buffer,
+                                          unsigned long npages)
+{
+       return hmm_dmirror_cmd(fd, HMM_DMIRROR_MIGRATE_ON_FAULT_TO_DEV, buffer, 
npages);
+}
+
 static int hmm_migrate_dev_to_sys(int fd,
                                   struct hmm_buffer *buffer,
                                   unsigned long npages)
@@ -937,6 +944,55 @@ TEST_F(hmm, migrate)
        hmm_buffer_free(buffer);
 }
 
+
+/*
+ * Fault and migrate anonymous memory to device private memory.
+ */
+TEST_F(hmm, migrate_on_fault)
+{
+       struct hmm_buffer *buffer;
+       unsigned long npages;
+       unsigned long size;
+       unsigned long i;
+       int *ptr;
+       int ret;
+       int val;
+
+       npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift;
+       ASSERT_NE(npages, 0);
+       size = npages << self->page_shift;
+
+       buffer = hmm_buffer_alloc(size, size,
+                                 PROT_READ | PROT_WRITE,
+                                 MAP_PRIVATE | MAP_ANONYMOUS, -1);
+       ASSERT_NE(buffer, NULL);
+
+       /*
+        * Initialize buffer in system memory but leave the first two pages
+        * zero: page 0 stays pte_none so that hmm_range_fault() has to fault
+        * it in, page 1 is mapped to the zero page.
+        */
+       i = 2 * self->page_size / sizeof(*ptr);
+       for (ptr = buffer->ptr; i < size / sizeof(*ptr); ++i)
+               ptr[i] = i;
+
+       val = *(int *)(buffer->ptr + self->page_size);
+       ASSERT_EQ(val, 0);
+
+       /* Fault and migrate memory to device. */
+       ret = hmm_migrate_on_fault_sys_to_dev(self->fd, buffer, npages);
+       ASSERT_EQ(ret, 0);
+       ASSERT_EQ(buffer->cpages, npages);
+
+       /* Check what the device read. */
+       for (i = 0, ptr = buffer->mirror; i < 2 * self->page_size / 
sizeof(*ptr); ++i)
+               ASSERT_EQ(ptr[i], 0);
+       for (; i < size / sizeof(*ptr); ++i)
+               ASSERT_EQ(ptr[i], i);
+
+       hmm_buffer_free(buffer);
+}
+
 /*
  * Migrate private file memory to device private memory.
  */
-- 
2.55.0

Reply via email to