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
