On Wed, Oct 23, 2019 at 12:55:15PM -0700, Ralph Campbell wrote: > Add self tests for HMM. > > Signed-off-by: Ralph Campbell <rcampbell@xxxxxxxxxx> You can add my signoff Signed-off-by: Jérôme Glisse <jglisse@xxxxxxxxxx> > --- > MAINTAINERS | 3 + > drivers/char/Kconfig | 11 + > drivers/char/Makefile | 1 + > drivers/char/hmm_dmirror.c | 1566 ++++++++++++++++++++++++ > include/Kbuild | 1 + > include/uapi/linux/hmm_dmirror.h | 74 ++ > tools/testing/selftests/vm/.gitignore | 1 + > tools/testing/selftests/vm/Makefile | 3 + > tools/testing/selftests/vm/config | 3 + > tools/testing/selftests/vm/hmm-tests.c | 1311 ++++++++++++++++++++ > tools/testing/selftests/vm/run_vmtests | 16 + > tools/testing/selftests/vm/test_hmm.sh | 97 ++ > 12 files changed, 3087 insertions(+) > create mode 100644 drivers/char/hmm_dmirror.c > create mode 100644 include/uapi/linux/hmm_dmirror.h > create mode 100644 tools/testing/selftests/vm/hmm-tests.c > create mode 100755 tools/testing/selftests/vm/test_hmm.sh > > diff --git a/MAINTAINERS b/MAINTAINERS > index 296de2b51c83..9890b6b8eea0 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -7427,8 +7427,11 @@ M: Jérôme Glisse <jglisse@xxxxxxxxxx> > L: linux-mm@xxxxxxxxx > S: Maintained > F: mm/hmm* > +F: drivers/char/hmm* > F: include/linux/hmm* > +F: include/uapi/linux/hmm* > F: Documentation/vm/hmm.rst > +F: tools/testing/selftests/vm/*hmm* > > HOST AP DRIVER > M: Jouni Malinen <j@xxxxx> > diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig > index df0fc997dc3e..cc8ddb99550d 100644 > --- a/drivers/char/Kconfig > +++ b/drivers/char/Kconfig > @@ -535,6 +535,17 @@ config ADI > and SSM (Silicon Secured Memory). Intended consumers of this > driver include crash and makedumpfile. > > +config HMM_DMIRROR > + tristate "HMM driver for testing Heterogeneous Memory Management" > + depends on HMM_MIRROR > + depends on DEVICE_PRIVATE > + help > + This is a pseudo device driver solely for testing HMM. > + Say Y here if you want to build the HMM test driver. > + Doing so will allow you to run tools/testing/selftest/vm/hmm-tests. > + > + If in doubt, say "N". > + > endmenu > > config RANDOM_TRUST_CPU > diff --git a/drivers/char/Makefile b/drivers/char/Makefile > index 7c5ea6f9df14..d4a168c0c138 100644 > --- a/drivers/char/Makefile > +++ b/drivers/char/Makefile > @@ -52,3 +52,4 @@ js-rtc-y = rtc.o > obj-$(CONFIG_XILLYBUS) += xillybus/ > obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o > obj-$(CONFIG_ADI) += adi.o > +obj-$(CONFIG_HMM_DMIRROR) += hmm_dmirror.o > diff --git a/drivers/char/hmm_dmirror.c b/drivers/char/hmm_dmirror.c > new file mode 100644 > index 000000000000..5a1ed34e72e1 > --- /dev/null > +++ b/drivers/char/hmm_dmirror.c > @@ -0,0 +1,1566 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2013 Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation; either version 2 of > + * the License, or (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * Authors: Jérôme Glisse <jglisse@xxxxxxxxxx> > + */ > +/* > + * This is a driver to exercice the HMM (heterogeneous memory management) > + * mirror and zone device private memory migration APIs of the kernel. > + * Userspace programs can register with the driver to mirror their own address > + * space and can use the device to read/write any valid virtual address. > + * > + * In some ways it can also serve as an example driver for people wanting to use > + * HMM inside their own device driver. > + */ > +#include <linux/init.h> > +#include <linux/fs.h> > +#include <linux/mm.h> > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/cdev.h> > +#include <linux/device.h> > +#include <linux/mutex.h> > +#include <linux/rwsem.h> > +#include <linux/sched.h> > +#include <linux/slab.h> > +#include <linux/highmem.h> > +#include <linux/delay.h> > +#include <linux/pagemap.h> > +#include <linux/hmm.h> > +#include <linux/vmalloc.h> > +#include <linux/swap.h> > +#include <linux/swapops.h> > +#include <linux/sched/mm.h> > +#include <linux/platform_device.h> > + > +#include <uapi/linux/hmm_dmirror.h> > + > +#define DMIRROR_NDEVICES 2 > +#define DMIRROR_RANGE_FAULT_TIMEOUT 1000 > +#define DEVMEM_CHUNK_SIZE (256 * 1024 * 1024U) > +#define DEVMEM_CHUNKS_RESERVE 16 > + > +static const struct dev_pagemap_ops dmirror_devmem_ops; > +static dev_t dmirror_dev; > +static struct platform_device *dmirror_platform_devices[DMIRROR_NDEVICES]; > +static struct page *dmirror_zero_page; > + > +struct dmirror_device; > + > +struct dmirror_bounce { > + void *ptr; > + unsigned long size; > + unsigned long addr; > + unsigned long cpages; > +}; > + > +#define DPT_SHIFT PAGE_SHIFT > +#define DPT_VALID (1UL << 0) > +#define DPT_WRITE (1UL << 1) > +#define DPT_DPAGE (1UL << 2) > +#define DPT_ZPAGE 0x20UL > + > +const uint64_t dmirror_hmm_flags[HMM_PFN_FLAG_MAX] = { > + [HMM_PFN_VALID] = DPT_VALID, > + [HMM_PFN_WRITE] = DPT_WRITE, > + [HMM_PFN_DEVICE_PRIVATE] = DPT_DPAGE, > +}; > + > +static const uint64_t dmirror_hmm_values[HMM_PFN_VALUE_MAX] = { > + [HMM_PFN_NONE] = 0, > + [HMM_PFN_ERROR] = 0x10, > + [HMM_PFN_SPECIAL] = 0x10, > +}; > + > +struct dmirror_pt { > + u64 pgd[PTRS_PER_PGD]; > + struct rw_semaphore lock; > +}; > + > +/* > + * Data attached to the open device file. > + * Note that it might be shared after a fork(). > + */ > +struct dmirror { > + struct hmm_mirror mirror; > + struct dmirror_device *mdevice; > + struct dmirror_pt pt; > + struct mutex mutex; > +}; > + > +/* > + * ZONE_DEVICE pages for migration and simulating device memory. > + */ > +struct dmirror_chunk { > + struct dev_pagemap pagemap; > + struct dmirror_device *mdevice; > +}; > + > +/* > + * Per device data. > + */ > +struct dmirror_device { > + struct cdev cdevice; > + struct hmm_devmem *devmem; > + struct platform_device *pdevice; > + > + unsigned int devmem_capacity; > + unsigned int devmem_count; > + struct dmirror_chunk **devmem_chunks; > + struct mutex devmem_lock; /* protects the above */ > + > + unsigned long calloc; > + unsigned long cfree; > + struct page *free_pages; > + spinlock_t lock; /* protects the above */ > +}; > + > +static inline unsigned long dmirror_pt_pgd(unsigned long addr) > +{ > + return (addr >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1); > +} > + > +static inline unsigned long dmirror_pt_pud(unsigned long addr) > +{ > + return (addr >> PUD_SHIFT) & (PTRS_PER_PUD - 1); > +} > + > +static inline unsigned long dmirror_pt_pmd(unsigned long addr) > +{ > + return (addr >> PMD_SHIFT) & (PTRS_PER_PMD - 1); > +} > + > +static inline unsigned long dmirror_pt_pte(unsigned long addr) > +{ > + return (addr >> PAGE_SHIFT) & (PTRS_PER_PTE - 1); > +} > + > +static inline struct page *dmirror_pt_page(u64 *dptep) > +{ > + u64 dpte = *dptep; > + > + if (dpte == DPT_ZPAGE) > + return dmirror_zero_page; > + if (!(dpte & DPT_VALID)) > + return NULL; > + return pfn_to_page((u64)dpte >> DPT_SHIFT); > +} > + > +static inline struct page *dmirror_pt_page_write(u64 *dptep) > +{ > + u64 dpte = *dptep; > + > + if (!(dpte & DPT_VALID) || !(dpte & DPT_WRITE)) > + return NULL; > + return pfn_to_page((u64)dpte >> DPT_SHIFT); > +} > + > +static inline u64 dmirror_pt_from_page(struct page *page) > +{ > + if (!page) > + return 0; > + return (page_to_pfn(page) << DPT_SHIFT) | DPT_VALID; > +} > + > +static struct page *populate_pt(struct dmirror *dmirror, u64 *dptep) > +{ > + struct page *page; > + > + /* > + * Since we don't free page tables until the process exits, > + * we can unlock and relock without the page table being freed > + * from under us. > + */ > + mutex_unlock(&dmirror->mutex); > + page = alloc_page(GFP_HIGHUSER | __GFP_ZERO); > + mutex_lock(&dmirror->mutex); > + if (page) { > + if (unlikely(*dptep)) { > + __free_page(page); > + page = dmirror_pt_page(dptep); > + } else > + *dptep = dmirror_pt_from_page(page); > + } else if (*dptep) > + page = dmirror_pt_page(dptep); > + return page; > +} > + > +static inline unsigned long dmirror_pt_pud_end(unsigned long addr) > +{ > + return (addr & PGDIR_MASK) + ((unsigned long)PTRS_PER_PUD << PUD_SHIFT); > +} > + > +static inline unsigned long dmirror_pt_pmd_end(unsigned long addr) > +{ > + return (addr & PUD_MASK) + ((unsigned long)PTRS_PER_PMD << PMD_SHIFT); > +} > + > +static inline unsigned long dmirror_pt_pte_end(unsigned long addr) > +{ > + return (addr & PMD_MASK) + ((unsigned long)PTRS_PER_PTE << PAGE_SHIFT); > +} > + > +typedef int (*dmirror_walk_cb_t)(struct dmirror *dmirror, > + unsigned long start, > + unsigned long end, > + u64 *dptep, > + void *private); > + > +static int dmirror_pt_walk(struct dmirror *dmirror, > + dmirror_walk_cb_t cb, > + unsigned long start, > + unsigned long end, > + void *private, > + bool populate) > +{ > + u64 *dpgdp = &dmirror->pt.pgd[dmirror_pt_pgd(start)]; > + unsigned long addr; > + int ret = -ENOENT; > + > + for (addr = start; addr < end; dpgdp++) { > + u64 *dpudp; > + unsigned long pud_end; > + struct page *pud_page; > + > + pud_end = min(end, dmirror_pt_pud_end(addr)); > + pud_page = dmirror_pt_page(dpgdp); > + if (!pud_page) { > + if (!populate) { > + addr = pud_end; > + continue; > + } > + pud_page = populate_pt(dmirror, dpgdp); > + if (!pud_page) > + return -ENOMEM; > + } > + dpudp = kmap(pud_page); > + dpudp += dmirror_pt_pud(addr); > + for (; addr != pud_end; dpudp++) { > + u64 *dpmdp; > + unsigned long pmd_end; > + struct page *pmd_page; > + > + pmd_end = min(end, dmirror_pt_pmd_end(addr)); > + pmd_page = dmirror_pt_page(dpudp); > + if (!pmd_page) { > + if (!populate) { > + addr = pmd_end; > + continue; > + } > + pmd_page = populate_pt(dmirror, dpudp); > + if (!pmd_page) { > + kunmap(pud_page); > + return -ENOMEM; > + } > + } > + dpmdp = kmap(pmd_page); > + dpmdp += dmirror_pt_pmd(addr); > + for (; addr != pmd_end; dpmdp++) { > + u64 *dptep; > + unsigned long pte_end; > + struct page *pte_page; > + > + pte_end = min(end, dmirror_pt_pte_end(addr)); > + pte_page = dmirror_pt_page(dpmdp); > + if (!pte_page) { > + if (!populate) { > + addr = pte_end; > + continue; > + } > + pte_page = populate_pt(dmirror, dpmdp); > + if (!pte_page) { > + kunmap(pmd_page); > + kunmap(pud_page); > + return -ENOMEM; > + } > + } > + if (!cb) { > + addr = pte_end; > + continue; > + } > + dptep = kmap(pte_page); > + dptep += dmirror_pt_pte(addr); > + ret = cb(dmirror, addr, pte_end, dptep, > + private); > + kunmap(pte_page); > + if (ret) { > + kunmap(pmd_page); > + kunmap(pud_page); > + return ret; > + } > + addr = pte_end; > + } > + kunmap(pmd_page); > + addr = pmd_end; > + } > + kunmap(pud_page); > + addr = pud_end; > + } > + > + return ret; > +} > + > +static void dmirror_pt_free(struct dmirror *dmirror) > +{ > + u64 *dpgdp = dmirror->pt.pgd; > + > + for (; dpgdp != dmirror->pt.pgd + PTRS_PER_PGD; dpgdp++) { > + u64 *dpudp, *dpudp_orig; > + u64 *dpudp_end; > + struct page *pud_page; > + > + pud_page = dmirror_pt_page(dpgdp); > + if (!pud_page) > + continue; > + > + dpudp_orig = kmap_atomic(pud_page); > + dpudp = dpudp_orig; > + dpudp_end = dpudp + PTRS_PER_PUD; > + for (; dpudp != dpudp_end; dpudp++) { > + u64 *dpmdp, *dpmdp_orig; > + u64 *dpmdp_end; > + struct page *pmd_page; > + > + pmd_page = dmirror_pt_page(dpudp); > + if (!pmd_page) > + continue; > + > + dpmdp_orig = kmap_atomic(pmd_page); > + dpmdp = dpmdp_orig; > + dpmdp_end = dpmdp + PTRS_PER_PMD; > + for (; dpmdp != dpmdp_end; dpmdp++) { > + struct page *pte_page; > + > + pte_page = dmirror_pt_page(dpmdp); > + if (!pte_page) > + continue; > + > + *dpmdp = 0; > + __free_page(pte_page); > + } > + kunmap_atomic(dpmdp_orig); > + *dpudp = 0; > + __free_page(pmd_page); > + } > + kunmap_atomic(dpudp_orig); > + *dpgdp = 0; > + __free_page(pud_page); > + } > +} > + > +static int dmirror_bounce_init(struct dmirror_bounce *bounce, > + unsigned long addr, > + unsigned long size) > +{ > + bounce->addr = addr; > + bounce->size = size; > + bounce->cpages = 0; > + bounce->ptr = vmalloc(size); > + if (!bounce->ptr) > + return -ENOMEM; > + return 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) > + return ret; > + } > + > + return 0; > +} > + > +static int dmirror_bounce_copy_to(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_to_user(uptr, ptr, PAGE_SIZE); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static void dmirror_bounce_fini(struct dmirror_bounce *bounce) > +{ > + vfree(bounce->ptr); > +} > + > +static int dmirror_do_update(struct dmirror *dmirror, > + unsigned long addr, > + unsigned long end, > + u64 *dptep, > + void *private) > +{ > + /* > + * The page table doesn't hold references to pages since it relies on > + * the mmu notifier to clear pointers when they become stale. > + * Therefore, it is OK to just clear the pte. > + */ > + for (; addr < end; addr += PAGE_SIZE, ++dptep) > + *dptep = 0; > + > + return 0; > +} > + > +static int dmirror_update(struct hmm_mirror *mirror, > + const struct mmu_notifier_range *update) > +{ > + struct dmirror *dmirror = container_of(mirror, struct dmirror, mirror); > + > + if (mmu_notifier_range_blockable(update)) > + mutex_lock(&dmirror->mutex); > + else if (!mutex_trylock(&dmirror->mutex)) > + return -EAGAIN; > + > + dmirror_pt_walk(dmirror, dmirror_do_update, update->start, > + update->end, NULL, false); > + mutex_unlock(&dmirror->mutex); > + return 0; > +} > + > +static const struct hmm_mirror_ops dmirror_ops = { > + .sync_cpu_device_pagetables = &dmirror_update, > +}; > + > +/* > + * dmirror_new() - allocate and initialize dmirror struct. > + * > + * @mdevice: The device this mirror is associated with. > + * @filp: The active device file descriptor this mirror is associated with. > + */ > +static struct dmirror *dmirror_new(struct dmirror_device *mdevice) > +{ > + struct mm_struct *mm = get_task_mm(current); > + struct dmirror *dmirror; > + int ret; > + > + if (!mm) > + goto err; > + > + /* Mirror this process address space */ > + dmirror = kzalloc(sizeof(*dmirror), GFP_KERNEL); > + if (dmirror == NULL) > + goto err_mmput; > + > + dmirror->mdevice = mdevice; > + dmirror->mirror.ops = &dmirror_ops; > + mutex_init(&dmirror->mutex); > + > + down_write(&mm->mmap_sem); > + ret = hmm_mirror_register(&dmirror->mirror, mm); > + up_write(&mm->mmap_sem); > + if (ret) > + goto err_free; > + > + mmput(mm); > + return dmirror; > + > +err_free: > + kfree(dmirror); > +err_mmput: > + mmput(mm); > +err: > + return NULL; > +} > + > +static void dmirror_del(struct dmirror *dmirror) > +{ > + hmm_mirror_unregister(&dmirror->mirror); > + dmirror_pt_free(dmirror); > + kfree(dmirror); > +} > + > +/* > + * Below are the file operation for the dmirror device file. Only ioctl matters. > + * > + * Note this is highly specific to the dmirror device driver and should not be > + * construed as an example on how to design the API a real device driver would > + * expose to userspace. > + */ > +static ssize_t dmirror_fops_read(struct file *filp, > + char __user *buf, > + size_t count, > + loff_t *ppos) > +{ > + return -EINVAL; > +} > + > +static ssize_t dmirror_fops_write(struct file *filp, > + const char __user *buf, > + size_t count, > + loff_t *ppos) > +{ > + return -EINVAL; > +} > + > +static int dmirror_fops_mmap(struct file *filp, struct vm_area_struct *vma) > +{ > + /* Forbid mmap of the dmirror device file. */ > + return -EINVAL; > +} > + > +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; > + > + 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); > + > + return 0; > + > +err_busy: > + mutex_unlock(&mdevice->devmem_lock); > + dmirror_del(dmirror); > + return -EBUSY; > +} > + > +static int dmirror_fops_release(struct inode *inode, struct file *filp) > +{ > + struct dmirror *dmirror = filp->private_data; > + > + if (!dmirror) > + return 0; > + > + dmirror_del(dmirror); > + filp->private_data = NULL; > + > + return 0; > +} > + > +static inline struct dmirror_device *dmirror_page_to_device(struct page *page) > + > +{ > + struct dmirror_chunk *devmem; > + > + devmem = container_of(page->pgmap, struct dmirror_chunk, pagemap); > + return devmem->mdevice; > +} > + > +static bool dmirror_device_is_mine(struct dmirror_device *mdevice, > + struct page *page) > +{ > + if (!is_zone_device_page(page)) > + return false; > + return page->pgmap->ops == &dmirror_devmem_ops && > + dmirror_page_to_device(page) == mdevice; > +} > + > +static int dmirror_do_fault(struct dmirror *dmirror, > + unsigned long addr, > + unsigned long end, > + u64 *dptep, > + void *private) > +{ > + struct hmm_range *range = private; > + unsigned long idx = (addr - range->start) >> PAGE_SHIFT; > + uint64_t *pfns = range->pfns; > + > + for (; addr < end; addr += PAGE_SIZE, ++dptep, ++idx) { > + struct page *page; > + u64 dpte; > + > + /* > + * HMM_PFN_ERROR is returned if it is accessing invalid memory > + * either because of memory error (hardware detected memory > + * corruption) or more likely because of truncate on mmap > + * file. > + */ > + if (pfns[idx] == range->values[HMM_PFN_ERROR]) > + return -EFAULT; > + /* > + * The only special PFN HMM returns is the read-only zero page > + * which doesn't have a matching struct page. > + */ > + if (pfns[idx] == range->values[HMM_PFN_SPECIAL]) { > + *dptep = DPT_ZPAGE; > + continue; > + } > + if (!(pfns[idx] & range->flags[HMM_PFN_VALID])) > + return -EFAULT; > + page = hmm_device_entry_to_page(range, pfns[idx]); > + /* We asked for pages to be populated but check anyway. */ > + if (!page) > + return -EFAULT; > + dpte = dmirror_pt_from_page(page); > + if (is_zone_device_page(page)) { > + if (!dmirror_device_is_mine(dmirror->mdevice, page)) > + continue; > + dpte |= DPT_DPAGE; > + } > + if (pfns[idx] & range->flags[HMM_PFN_WRITE]) > + dpte |= DPT_WRITE; > + else if (range->default_flags & range->flags[HMM_PFN_WRITE]) > + return -EFAULT; > + *dptep = dpte; > + } > + > + return 0; > +} > + > +static int dmirror_fault(struct dmirror *dmirror, > + unsigned long start, > + unsigned long end, > + bool write) > +{ > + struct mm_struct *mm = dmirror->mirror.hmm->mmu_notifier.mm; > + unsigned long addr; > + unsigned long next; > + uint64_t pfns[64]; > + struct hmm_range range = { > + .pfns = pfns, > + .flags = dmirror_hmm_flags, > + .values = dmirror_hmm_values, > + .pfn_shift = DPT_SHIFT, > + .pfn_flags_mask = ~(dmirror_hmm_flags[HMM_PFN_VALID] | > + dmirror_hmm_flags[HMM_PFN_WRITE]), > + .default_flags = dmirror_hmm_flags[HMM_PFN_VALID] | > + (write ? dmirror_hmm_flags[HMM_PFN_WRITE] : 0), > + }; > + int ret = 0; > + > + for (addr = start; addr < end; ) { > + long count; > + > + next = min(addr + (ARRAY_SIZE(pfns) << PAGE_SHIFT), end); > + range.start = addr; > + range.end = next; > + > + down_read(&mm->mmap_sem); > + > + ret = hmm_range_register(&range, &dmirror->mirror); > + if (ret) { > + up_read(&mm->mmap_sem); > + break; > + } > + > + if (!hmm_range_wait_until_valid(&range, > + DMIRROR_RANGE_FAULT_TIMEOUT)) { > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + continue; > + } > + > + count = hmm_range_fault(&range, 0); > + if (count < 0) { > + ret = count; > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + break; > + } > + > + if (!hmm_range_valid(&range)) { > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + continue; > + } > + mutex_lock(&dmirror->mutex); > + ret = dmirror_pt_walk(dmirror, dmirror_do_fault, > + addr, next, &range, true); > + mutex_unlock(&dmirror->mutex); > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + if (ret) > + break; > + > + addr = next; > + } > + > + return ret; > +} > + > +static int dmirror_do_read(struct dmirror *dmirror, > + unsigned long addr, > + unsigned long end, > + u64 *dptep, > + void *private) > +{ > + struct dmirror_bounce *bounce = private; > + void *ptr; > + > + ptr = bounce->ptr + ((addr - bounce->addr) & PAGE_MASK); > + > + for (; addr < end; addr += PAGE_SIZE, ++dptep) { > + struct page *page; > + void *tmp; > + > + page = dmirror_pt_page(dptep); > + if (!page) > + return -ENOENT; > + > + tmp = kmap(page); > + memcpy(ptr, tmp, PAGE_SIZE); > + kunmap(page); > + > + ptr += PAGE_SIZE; > + bounce->cpages++; > + } > + > + return 0; > +} > + > +static int dmirror_read(struct dmirror *dmirror, > + struct hmm_dmirror_cmd *cmd) > +{ > + struct dmirror_bounce bounce; > + unsigned long start, end; > + unsigned long size = cmd->npages << PAGE_SHIFT; > + int ret; > + > + start = cmd->addr; > + end = start + size; > + if (end < start) > + return -EINVAL; > + > + ret = dmirror_bounce_init(&bounce, start, size); > + if (ret) > + return ret; > + > +again: > + mutex_lock(&dmirror->mutex); > + ret = dmirror_pt_walk(dmirror, dmirror_do_read, start, end, &bounce, > + false); > + mutex_unlock(&dmirror->mutex); > + if (ret == 0) > + ret = dmirror_bounce_copy_to(&bounce, cmd->ptr); > + else if (ret == -ENOENT) { > + start = cmd->addr + (bounce.cpages << PAGE_SHIFT); > + ret = dmirror_fault(dmirror, start, end, false); > + if (ret == 0) { > + cmd->faults++; > + goto again; > + } > + } > + > + cmd->cpages = bounce.cpages; > + dmirror_bounce_fini(&bounce); > + return ret; > +} > + > +static int dmirror_do_write(struct dmirror *dmirror, > + unsigned long addr, > + unsigned long end, > + u64 *dptep, > + void *private) > +{ > + struct dmirror_bounce *bounce = private; > + void *ptr; > + > + ptr = bounce->ptr + ((addr - bounce->addr) & PAGE_MASK); > + > + for (; addr < end; addr += PAGE_SIZE, ++dptep) { > + struct page *page; > + void *tmp; > + > + page = dmirror_pt_page_write(dptep); > + if (!page) > + return -ENOENT; > + > + tmp = kmap(page); > + memcpy(tmp, ptr, PAGE_SIZE); > + kunmap(page); > + > + ptr += PAGE_SIZE; > + bounce->cpages++; > + } > + > + return 0; > +} > + > +static int dmirror_write(struct dmirror *dmirror, > + struct hmm_dmirror_cmd *cmd) > +{ > + struct dmirror_bounce bounce; > + unsigned long start, end; > + unsigned long size = cmd->npages << PAGE_SHIFT; > + int ret; > + > + start = cmd->addr; > + end = start + size; > + if (end < start) > + return -EINVAL; > + > + ret = dmirror_bounce_init(&bounce, start, size); > + if (ret) > + return ret; > + ret = dmirror_bounce_copy_from(&bounce, cmd->ptr); > + if (ret) > + return ret; > + > +again: > + mutex_lock(&dmirror->mutex); > + ret = dmirror_pt_walk(dmirror, dmirror_do_write, > + start, end, &bounce, false); > + mutex_unlock(&dmirror->mutex); > + if (ret == -ENOENT) { > + start = cmd->addr + (bounce.cpages << PAGE_SHIFT); > + ret = dmirror_fault(dmirror, start, end, true); > + if (ret == 0) { > + cmd->faults++; > + goto again; > + } > + } > + > + cmd->cpages = bounce.cpages; > + dmirror_bounce_fini(&bounce); > + return ret; > +} > + > +static bool dmirror_allocate_chunk(struct dmirror_device *mdevice, > + struct page **ppage) > +{ > + struct dmirror_chunk *devmem; > + struct resource *res; > + unsigned long pfn; > + unsigned long pfn_first; > + unsigned long pfn_last; > + void *ptr; > + > + mutex_lock(&mdevice->devmem_lock); > + > + if (mdevice->devmem_count == mdevice->devmem_capacity) { > + struct dmirror_chunk **new_chunks; > + unsigned int new_capacity; > + > + new_capacity = mdevice->devmem_capacity + > + DEVMEM_CHUNKS_RESERVE; > + new_chunks = krealloc(mdevice->devmem_chunks, > + sizeof(new_chunks[0]) * new_capacity, > + GFP_KERNEL); > + if (!new_chunks) > + goto err; > + mdevice->devmem_capacity = new_capacity; > + mdevice->devmem_chunks = new_chunks; > + } > + > + res = devm_request_free_mem_region(&mdevice->pdevice->dev, > + &iomem_resource, DEVMEM_CHUNK_SIZE); > + if (IS_ERR(res)) > + goto err; > + > + devmem = kzalloc(sizeof(*devmem), GFP_KERNEL); > + if (!devmem) > + goto err; > + > + devmem->pagemap.type = MEMORY_DEVICE_PRIVATE; > + devmem->pagemap.res = *res; > + devmem->pagemap.ops = &dmirror_devmem_ops; > + ptr = devm_memremap_pages(&mdevice->pdevice->dev, &devmem->pagemap); > + if (IS_ERR(ptr)) > + goto err_free; > + > + devmem->mdevice = mdevice; > + pfn_first = devmem->pagemap.res.start >> PAGE_SHIFT; > + pfn_last = pfn_first + > + (resource_size(&devmem->pagemap.res) >> PAGE_SHIFT); > + mdevice->devmem_chunks[mdevice->devmem_count++] = devmem; > + > + mutex_unlock(&mdevice->devmem_lock); > + > + pr_info("added new %u MB chunk (total %u chunks, %u MB) PFNs [0x%lx 0x%lx)\n", > + DEVMEM_CHUNK_SIZE / (1024 * 1024), > + mdevice->devmem_count, > + mdevice->devmem_count * (DEVMEM_CHUNK_SIZE / (1024 * 1024)), > + pfn_first, pfn_last); > + > + spin_lock(&mdevice->lock); > + for (pfn = pfn_first; pfn < pfn_last; pfn++) { > + struct page *page = pfn_to_page(pfn); > + > + page->zone_device_data = mdevice->free_pages; > + mdevice->free_pages = page; > + } > + if (ppage) { > + *ppage = mdevice->free_pages; > + mdevice->free_pages = (*ppage)->zone_device_data; > + mdevice->calloc++; > + } > + spin_unlock(&mdevice->lock); > + > + return true; > + > +err_free: > + kfree(devmem); > +err: > + mutex_unlock(&mdevice->devmem_lock); > + return false; > +} > + > +static struct page *dmirror_devmem_alloc_page(struct dmirror_device *mdevice) > +{ > + struct page *dpage = NULL; > + struct page *rpage; > + > + /* > + * This is a fake device so we alloc real system memory to store > + * our device memory. > + */ > + rpage = alloc_page(GFP_HIGHUSER); > + if (!rpage) > + return NULL; > + > + spin_lock(&mdevice->lock); > + > + if (mdevice->free_pages) { > + dpage = mdevice->free_pages; > + mdevice->free_pages = dpage->zone_device_data; > + mdevice->calloc++; > + spin_unlock(&mdevice->lock); > + } else { > + spin_unlock(&mdevice->lock); > + if (!dmirror_allocate_chunk(mdevice, &dpage)) > + goto error; > + } > + > + dpage->zone_device_data = rpage; > + get_page(dpage); > + lock_page(dpage); > + return dpage; > + > +error: > + __free_page(rpage); > + return NULL; > +} > + > +static void dmirror_migrate_alloc_and_copy(struct migrate_vma *args, > + struct dmirror *dmirror) > +{ > + struct dmirror_device *mdevice = dmirror->mdevice; > + const unsigned long *src = args->src; > + unsigned long *dst = args->dst; > + unsigned long addr; > + > + for (addr = args->start; addr < args->end; addr += PAGE_SIZE, > + src++, dst++) { > + struct page *spage; > + struct page *dpage; > + struct page *rpage; > + > + if (!(*src & MIGRATE_PFN_MIGRATE)) > + continue; > + > + /* > + * Note that spage might be NULL which is OK since it is an > + * unallocated pte_none() or read-only zero page. > + */ > + spage = migrate_pfn_to_page(*src); > + > + /* > + * Don't migrate device private pages from our own driver or > + * others. For our own we would do a device private memory copy > + * not a migration and for others, we would need to fault the > + * other device's page into system memory first. > + */ > + if (spage && is_zone_device_page(spage)) > + continue; > + > + dpage = dmirror_devmem_alloc_page(mdevice); > + if (!dpage) > + continue; > + > + rpage = dpage->zone_device_data; > + if (spage) > + copy_highpage(rpage, spage); > + else > + clear_highpage(rpage); > + > + /* > + * Normally, a device would use the page->zone_device_data to > + * point to the mirror but here we use it to hold the page for > + * the simulated device memory and that page holds the pointer > + * to the mirror. > + */ > + rpage->zone_device_data = dmirror; > + > + *dst = migrate_pfn(page_to_pfn(dpage)) | > + MIGRATE_PFN_LOCKED; > + if ((*src & MIGRATE_PFN_WRITE) || > + (!spage && args->vma->vm_flags & VM_WRITE)) > + *dst |= MIGRATE_PFN_WRITE; > + } > + /* Try to pre-allocate page tables. */ > + mutex_lock(&dmirror->mutex); > + dmirror_pt_walk(dmirror, NULL, args->start, args->end, NULL, true); > + mutex_unlock(&dmirror->mutex); > +} > + > +struct dmirror_migrate { > + struct hmm_dmirror_cmd *cmd; > + const unsigned long *src; > + const unsigned long *dst; > + unsigned long start; > +}; > + > +static int dmirror_do_migrate(struct dmirror *dmirror, > + unsigned long addr, > + unsigned long end, > + u64 *dptep, > + void *private) > +{ > + struct dmirror_migrate *migrate = private; > + const unsigned long *src = migrate->src; > + const unsigned long *dst = migrate->dst; > + unsigned long idx = (addr - migrate->start) >> PAGE_SHIFT; > + > + for (; addr < end; addr += PAGE_SIZE, ++dptep, ++idx) { > + struct page *page; > + u64 dpte; > + > + if (!(src[idx] & MIGRATE_PFN_MIGRATE)) > + continue; > + > + page = migrate_pfn_to_page(dst[idx]); > + if (!page) > + continue; > + > + /* > + * Map the page that holds the data so dmirror_pt_walk() > + * doesn't have to deal with ZONE_DEVICE private pages. > + */ > + page = page->zone_device_data; > + dpte = dmirror_pt_from_page(page) | DPT_DPAGE; > + if (dst[idx] & MIGRATE_PFN_WRITE) > + dpte |= DPT_WRITE; > + *dptep = dpte; > + } > + > + return 0; > +} > + > +static void dmirror_migrate_finalize_and_map(struct migrate_vma *args, > + struct dmirror *dmirror, > + struct hmm_dmirror_cmd *cmd) > +{ > + struct dmirror_migrate migrate; > + > + migrate.cmd = cmd; > + migrate.src = args->src; > + migrate.dst = args->dst; > + migrate.start = args->start; > + > + /* Map the migrated pages into the device's page tables. */ > + mutex_lock(&dmirror->mutex); > + dmirror_pt_walk(dmirror, dmirror_do_migrate, args->start, args->end, > + &migrate, true); > + mutex_unlock(&dmirror->mutex); > +} > + > +static int dmirror_migrate(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->mirror.hmm->mmu_notifier.mm; > + struct vm_area_struct *vma; > + unsigned long src_pfns[64]; > + unsigned long dst_pfns[64]; > + struct dmirror_bounce bounce; > + struct migrate_vma args; > + unsigned long next; > + int ret; > + > + start = cmd->addr; > + end = start + size; > + if (end < start) > + return -EINVAL; > + > + down_read(&mm->mmap_sem); > + for (addr = start; addr < end; addr = next) { > + next = min(end, addr + (ARRAY_SIZE(src_pfns) << PAGE_SHIFT)); > + > + vma = find_vma(mm, addr); > + if (!vma || addr < vma->vm_start) { > + ret = -EINVAL; > + goto out; > + } > + if (next > vma->vm_end) > + next = vma->vm_end; > + > + args.vma = vma; > + args.src = src_pfns; > + args.dst = dst_pfns; > + args.start = addr; > + args.end = next; > + ret = migrate_vma_setup(&args); > + if (ret) > + goto out; > + > + dmirror_migrate_alloc_and_copy(&args, dmirror); > + migrate_vma_pages(&args); > + dmirror_migrate_finalize_and_map(&args, dmirror, cmd); > + migrate_vma_finalize(&args); > + } > + up_read(&mm->mmap_sem); > + > + /* Return the migrated data for verification. */ > + ret = dmirror_bounce_init(&bounce, start, size); > + if (ret) > + return ret; > + mutex_lock(&dmirror->mutex); > + ret = dmirror_pt_walk(dmirror, dmirror_do_read, start, end, &bounce, > + false); > + mutex_unlock(&dmirror->mutex); > + if (ret == 0) > + ret = dmirror_bounce_copy_to(&bounce, cmd->ptr); > + cmd->cpages = bounce.cpages; > + dmirror_bounce_fini(&bounce); > + return ret; > + > +out: > + up_read(&mm->mmap_sem); > + return ret; > +} > + > +static void dmirror_mkentry(struct dmirror *dmirror, > + struct hmm_range *range, > + unsigned char *perm, > + uint64_t entry) > +{ > + struct page *page; > + > + if (entry == range->values[HMM_PFN_ERROR]) { > + *perm = HMM_DMIRROR_PROT_ERROR; > + return; > + } > + page = hmm_device_entry_to_page(range, entry); > + if (!page) { > + *perm = HMM_DMIRROR_PROT_NONE; > + return; > + } > + if (entry & range->flags[HMM_PFN_DEVICE_PRIVATE]) { > + /* Is the page migrated to this device or some other? */ > + if (dmirror->mdevice == dmirror_page_to_device(page)) > + *perm = HMM_DMIRROR_PROT_DEV_PRIVATE_LOCAL; > + else > + *perm = HMM_DMIRROR_PROT_DEV_PRIVATE_REMOTE; > + } else if (is_zero_pfn(page_to_pfn(page))) > + *perm = HMM_DMIRROR_PROT_ZERO; > + else > + *perm = HMM_DMIRROR_PROT_NONE; > + if (entry & range->flags[HMM_PFN_WRITE]) > + *perm |= HMM_DMIRROR_PROT_WRITE; > + else > + *perm |= HMM_DMIRROR_PROT_READ; > +} > + > +static int dmirror_snapshot(struct dmirror *dmirror, > + struct hmm_dmirror_cmd *cmd) > +{ > + struct mm_struct *mm = dmirror->mirror.hmm->mmu_notifier.mm; > + unsigned long start, end; > + unsigned long size = cmd->npages << PAGE_SHIFT; > + unsigned long addr; > + unsigned long next; > + uint64_t pfns[64]; > + unsigned char perm[64]; > + char __user *uptr; > + struct hmm_range range = { > + .pfns = pfns, > + .flags = dmirror_hmm_flags, > + .values = dmirror_hmm_values, > + .pfn_shift = DPT_SHIFT, > + .pfn_flags_mask = ~0ULL, > + }; > + int ret = 0; > + > + start = cmd->addr; > + end = start + size; > + uptr = (void __user *)cmd->ptr; > + > + for (addr = start; addr < end; ) { > + long count; > + unsigned long i; > + unsigned long n; > + > + next = min(addr + (ARRAY_SIZE(pfns) << PAGE_SHIFT), end); > + range.start = addr; > + range.end = next; > + > + down_read(&mm->mmap_sem); > + > + ret = hmm_range_register(&range, &dmirror->mirror); > + if (ret) { > + up_read(&mm->mmap_sem); > + break; > + } > + > + if (!hmm_range_wait_until_valid(&range, > + DMIRROR_RANGE_FAULT_TIMEOUT)) { > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + continue; > + } > + > + count = hmm_range_fault(&range, HMM_FAULT_SNAPSHOT); > + if (count < 0) { > + ret = count; > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + if (ret == -EBUSY) > + continue; > + break; > + } > + > + if (!hmm_range_valid(&range)) { > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + continue; > + } > + > + n = (next - addr) >> PAGE_SHIFT; > + for (i = 0; i < n; i++) > + dmirror_mkentry(dmirror, &range, perm + i, pfns[i]); > + hmm_range_unregister(&range); > + up_read(&mm->mmap_sem); > + > + ret = copy_to_user(uptr, perm, n); > + if (ret) > + break; > + > + cmd->cpages += n; > + uptr += n; > + addr = next; > + } > + > + return ret; > +} > + > +static long dmirror_fops_unlocked_ioctl(struct file *filp, > + unsigned int command, > + unsigned long arg) > +{ > + void __user *uarg = (void __user *)arg; > + struct hmm_dmirror_cmd cmd; > + struct dmirror *dmirror; > + int ret; > + > + dmirror = filp->private_data; > + if (!dmirror) > + return -EINVAL; > + > + ret = copy_from_user(&cmd, uarg, sizeof(cmd)); > + if (ret) > + return ret; > + > + if (cmd.addr & ~PAGE_MASK) > + return -EINVAL; > + if (cmd.addr >= (cmd.addr + (cmd.npages << PAGE_SHIFT))) > + return -EINVAL; > + > + cmd.cpages = 0; > + cmd.faults = 0; > + > + switch (command) { > + case HMM_DMIRROR_READ: > + ret = dmirror_read(dmirror, &cmd); > + break; > + > + case HMM_DMIRROR_WRITE: > + ret = dmirror_write(dmirror, &cmd); > + break; > + > + case HMM_DMIRROR_MIGRATE: > + ret = dmirror_migrate(dmirror, &cmd); > + break; > + > + case HMM_DMIRROR_SNAPSHOT: > + ret = dmirror_snapshot(dmirror, &cmd); > + break; > + > + default: > + return -EINVAL; > + } > + if (ret) > + return ret; > + > + return copy_to_user(uarg, &cmd, sizeof(cmd)); > +} > + > +static const struct file_operations dmirror_fops = { > + .read = dmirror_fops_read, > + .write = dmirror_fops_write, > + .mmap = dmirror_fops_mmap, > + .open = dmirror_fops_open, > + .release = dmirror_fops_release, > + .unlocked_ioctl = dmirror_fops_unlocked_ioctl, > + .llseek = default_llseek, > + .owner = THIS_MODULE, > +}; > + > +static void dmirror_devmem_free(struct page *page) > +{ > + struct page *rpage = page->zone_device_data; > + struct dmirror_device *mdevice; > + > + if (rpage) > + __free_page(rpage); > + > + mdevice = dmirror_page_to_device(page); > + > + spin_lock(&mdevice->lock); > + mdevice->cfree++; > + page->zone_device_data = mdevice->free_pages; > + mdevice->free_pages = page; > + spin_unlock(&mdevice->lock); > +} > + > +static vm_fault_t dmirror_devmem_fault_alloc_and_copy(struct migrate_vma *args, > + struct dmirror_device *mdevice) > +{ > + struct vm_area_struct *vma = args->vma; > + const unsigned long *src = args->src; > + unsigned long *dst = args->dst; > + unsigned long start = args->start; > + unsigned long end = args->end; > + unsigned long addr; > + > + for (addr = start; addr < end; addr += PAGE_SIZE, > + src++, dst++) { > + struct page *dpage, *spage; > + > + spage = migrate_pfn_to_page(*src); > + if (!spage || !(*src & MIGRATE_PFN_MIGRATE)) > + continue; > + if (!dmirror_device_is_mine(mdevice, spage)) > + continue; > + spage = spage->zone_device_data; > + > + dpage = alloc_page_vma(GFP_HIGHUSER_MOVABLE, vma, addr); > + if (!dpage) > + continue; > + > + lock_page(dpage); > + copy_highpage(dpage, spage); > + *dst = migrate_pfn(page_to_pfn(dpage)) | > + MIGRATE_PFN_LOCKED; > + if (*src & MIGRATE_PFN_WRITE) > + *dst |= MIGRATE_PFN_WRITE; > + } > + return 0; > +} > + > +static void dmirror_devmem_fault_finalize_and_map(struct migrate_vma *args, > + struct dmirror *dmirror) > +{ > + /* Invalidate the device's page table mapping. */ > + mutex_lock(&dmirror->mutex); > + dmirror_pt_walk(dmirror, dmirror_do_update, args->start, args->end, > + NULL, false); > + mutex_unlock(&dmirror->mutex); > +} > + > +static vm_fault_t dmirror_devmem_fault(struct vm_fault *vmf) > +{ > + struct migrate_vma args; > + unsigned long src_pfns; > + unsigned long dst_pfns; > + struct page *rpage; > + struct dmirror *dmirror; > + vm_fault_t ret; > + > + /* FIXME demonstrate how we can adjust migrate range */ > + args.vma = vmf->vma; > + args.start = vmf->address; > + args.end = args.start + PAGE_SIZE; > + args.src = &src_pfns; > + args.dst = &dst_pfns; > + > + if (migrate_vma_setup(&args)) > + return VM_FAULT_SIGBUS; > + > + /* > + * Normally, a device would use the page->zone_device_data to point to > + * the mirror but here we use it to hold the page for the simulated > + * device memory and that page holds the pointer to the mirror. > + */ > + rpage = vmf->page->zone_device_data; > + dmirror = rpage->zone_device_data; > + > + ret = dmirror_devmem_fault_alloc_and_copy(&args, dmirror->mdevice); > + if (ret) > + return ret; > + migrate_vma_pages(&args); > + dmirror_devmem_fault_finalize_and_map(&args, dmirror); > + migrate_vma_finalize(&args); > + return 0; > +} > + > +static const struct dev_pagemap_ops dmirror_devmem_ops = { > + .page_free = dmirror_devmem_free, > + .migrate_to_ram = dmirror_devmem_fault, > +}; > + > +static void dmirror_pdev_del(void *arg) > +{ > + struct dmirror_device *mdevice = arg; > + unsigned int i; > + > + if (mdevice->devmem_chunks) { > + for (i = 0; i < mdevice->devmem_count; i++) > + kfree(mdevice->devmem_chunks[i]); > + kfree(mdevice->devmem_chunks); > + } > + > + cdev_del(&mdevice->cdevice); > + kfree(mdevice); > +} > + > +static int dmirror_probe(struct platform_device *pdev) > +{ > + struct dmirror_device *mdevice; > + int ret; > + > + mdevice = kzalloc(sizeof(*mdevice), GFP_KERNEL); > + if (!mdevice) > + return -ENOMEM; > + > + mdevice->pdevice = pdev; > + mutex_init(&mdevice->devmem_lock); > + spin_lock_init(&mdevice->lock); > + > + cdev_init(&mdevice->cdevice, &dmirror_fops); > + ret = cdev_add(&mdevice->cdevice, pdev->dev.devt, 1); > + if (ret) { > + kfree(mdevice); > + return ret; > + } > + > + platform_set_drvdata(pdev, mdevice); > + ret = devm_add_action_or_reset(&pdev->dev, dmirror_pdev_del, mdevice); > + if (ret) > + return ret; > + > + /* Build list of free struct page */ > + dmirror_allocate_chunk(mdevice, NULL); > + > + return 0; > +} > + > +static int dmirror_remove(struct platform_device *pdev) > +{ > + /* all probe actions are unwound by devm */ > + return 0; > +} > + > +static struct platform_driver dmirror_device_driver = { > + .probe = dmirror_probe, > + .remove = dmirror_remove, > + .driver = { > + .name = "HMM_DMIRROR", > + }, > +}; > + > +static int __init hmm_dmirror_init(void) > +{ > + int ret; > + int id; > + > + ret = platform_driver_register(&dmirror_device_driver); > + if (ret) > + return ret; > + > + ret = alloc_chrdev_region(&dmirror_dev, 0, DMIRROR_NDEVICES, > + "HMM_DMIRROR"); > + if (ret) > + goto err_unreg; > + > + for (id = 0; id < DMIRROR_NDEVICES; id++) { > + struct platform_device *pd; > + > + pd = platform_device_alloc("HMM_DMIRROR", id); > + if (!pd) { > + ret = -ENOMEM; > + goto err_chrdev; > + } > + pd->dev.devt = MKDEV(MAJOR(dmirror_dev), id); > + ret = platform_device_add(pd); > + if (ret) { > + platform_device_put(pd); > + goto err_chrdev; > + } > + dmirror_platform_devices[id] = pd; > + } > + > + /* > + * Allocate a zero page to simulate a reserved page of device private > + * memory which is always zero. The zero_pfn page isn't used just to > + * make the code here simpler (i.e., we need a struct page for it). > + */ > + dmirror_zero_page = alloc_page(GFP_HIGHUSER | __GFP_ZERO); > + if (!dmirror_zero_page) > + goto err_chrdev; > + > + pr_info("hmm_dmirror loaded. This is only for testing HMM.\n"); > + return 0; > + > +err_chrdev: > + while (--id >= 0) { > + platform_device_unregister(dmirror_platform_devices[id]); > + dmirror_platform_devices[id] = NULL; > + } > + unregister_chrdev_region(dmirror_dev, 1); > +err_unreg: > + platform_driver_unregister(&dmirror_device_driver); > + return ret; > +} > + > +static void __exit hmm_dmirror_exit(void) > +{ > + int id; > + > + if (dmirror_zero_page) > + __free_page(dmirror_zero_page); > + for (id = 0; id < DMIRROR_NDEVICES; id++) > + platform_device_unregister(dmirror_platform_devices[id]); > + unregister_chrdev_region(dmirror_dev, DMIRROR_NDEVICES); > + platform_driver_unregister(&dmirror_device_driver); > + mmu_notifier_synchronize(); > +} > + > +module_init(hmm_dmirror_init); > +module_exit(hmm_dmirror_exit); > +MODULE_LICENSE("GPL"); > diff --git a/include/Kbuild b/include/Kbuild > index ffba79483cc5..6ffb44a45957 100644 > --- a/include/Kbuild > +++ b/include/Kbuild > @@ -1063,6 +1063,7 @@ header-test- += uapi/linux/coda_psdev.h > header-test- += uapi/linux/errqueue.h > header-test- += uapi/linux/eventpoll.h > header-test- += uapi/linux/hdlc/ioctl.h > +header-test- += uapi/linux/hmm_dmirror.h > header-test- += uapi/linux/input.h > header-test- += uapi/linux/kvm.h > header-test- += uapi/linux/kvm_para.h > diff --git a/include/uapi/linux/hmm_dmirror.h b/include/uapi/linux/hmm_dmirror.h > new file mode 100644 > index 000000000000..61d3643aff95 > --- /dev/null > +++ b/include/uapi/linux/hmm_dmirror.h > @@ -0,0 +1,74 @@ > +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ > +/* > + * Copyright 2013 Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation; either version 2 of > + * the License, or (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * Authors: Jérôme Glisse <jglisse@xxxxxxxxxx> > + */ > +/* > + * This is a dummy driver to exercise the HMM (heterogeneous memory management) > + * API of the kernel. It allows a userspace program to expose its entire address > + * space through the HMM dummy driver file. > + */ > +#ifndef _UAPI_LINUX_HMM_DMIRROR_H > +#define _UAPI_LINUX_HMM_DMIRROR_H > + > +#include <linux/types.h> > +#include <linux/ioctl.h> > + > +/* > + * Structure to pass to the HMM test driver to mimic a device accessing > + * system memory and ZONE_DEVICE private memory through device page tables. > + * > + * @addr: (in) user address the device will read/write > + * @ptr: (in) user address where device data is copied to/from > + * @npages: (in) number of pages to read/write > + * @cpages: (out) number of pages copied > + * @faults: (out) number of device page faults seen > + */ > +struct hmm_dmirror_cmd { > + __u64 addr; > + __u64 ptr; > + __u64 npages; > + __u64 cpages; > + __u64 faults; > +}; > + > +/* Expose the address space of the calling process through hmm dummy dev 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 _IOWR('H', 0x02, struct hmm_dmirror_cmd) > +#define HMM_DMIRROR_SNAPSHOT _IOWR('H', 0x03, struct hmm_dmirror_cmd) > + > +/* > + * Values returned in hmm_dmirror_cmd.ptr for HMM_DMIRROR_SNAPSHOT. > + * HMM_DMIRROR_PROT_ERROR: no valid mirror PTE for this page > + * HMM_DMIRROR_PROT_NONE: unpopulated PTE or PTE with no access > + * HMM_DMIRROR_PROT_READ: read-only PTE > + * HMM_DMIRROR_PROT_WRITE: read/write PTE > + * HMM_DMIRROR_PROT_ZERO: special read-only zero page > + * HMM_DMIRROR_PROT_DEV_PRIVATE_LOCAL: Migrated device private page on the > + * device the ioctl() is made > + * HMM_DMIRROR_PROT_DEV_PRIVATE_REMOTE: Migrated device private page on some > + * other device > + */ > +enum { > + HMM_DMIRROR_PROT_ERROR = 0xFF, > + HMM_DMIRROR_PROT_NONE = 0x00, > + HMM_DMIRROR_PROT_READ = 0x01, > + HMM_DMIRROR_PROT_WRITE = 0x02, > + HMM_DMIRROR_PROT_ZERO = 0x10, > + HMM_DMIRROR_PROT_DEV_PRIVATE_LOCAL = 0x20, > + HMM_DMIRROR_PROT_DEV_PRIVATE_REMOTE = 0x30, > +}; > + > +#endif /* _UAPI_LINUX_HMM_DMIRROR_H */ > diff --git a/tools/testing/selftests/vm/.gitignore b/tools/testing/selftests/vm/.gitignore > index 31b3c98b6d34..3054565b3f07 100644 > --- a/tools/testing/selftests/vm/.gitignore > +++ b/tools/testing/selftests/vm/.gitignore > @@ -14,3 +14,4 @@ virtual_address_range > gup_benchmark > va_128TBswitch > map_fixed_noreplace > +hmm-tests > diff --git a/tools/testing/selftests/vm/Makefile b/tools/testing/selftests/vm/Makefile > index 9534dc2bc929..5643cfb5e3d6 100644 > --- a/tools/testing/selftests/vm/Makefile > +++ b/tools/testing/selftests/vm/Makefile > @@ -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 > diff --git a/tools/testing/selftests/vm/config b/tools/testing/selftests/vm/config > index 1c0d76cb5adf..34cfab18e737 100644 > --- a/tools/testing/selftests/vm/config > +++ b/tools/testing/selftests/vm/config > @@ -1,2 +1,5 @@ > CONFIG_SYSVIPC=y > CONFIG_USERFAULTFD=y > +CONFIG_HMM_MIRROR=y > +CONFIG_DEVICE_PRIVATE=y > +CONFIG_HMM_DMIRROR=m > diff --git a/tools/testing/selftests/vm/hmm-tests.c b/tools/testing/selftests/vm/hmm-tests.c > new file mode 100644 > index 000000000000..f4ae6188fd0e > --- /dev/null > +++ b/tools/testing/selftests/vm/hmm-tests.c > @@ -0,0 +1,1311 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2013 Red Hat Inc. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation; either version 2 of > + * the License, or (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * Authors: Jérôme Glisse <jglisse@xxxxxxxxxx> > + */ > + > +/* > + * HMM stands for Heterogeneous Memory Management, it is a helper layer inside > + * the linux kernel to help device drivers mirror a process address space in > + * the device. This allows the device to use the same address space which > + * makes communication and data exchange a lot easier. > + * > + * This framework's sole purpose is to exercise various code paths inside > + * the kernel to make sure that HMM performs as expected and to flush out any > + * bugs. > + */ > + > +#include "../kselftest_harness.h" > + > +#include <errno.h> > +#include <fcntl.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <stdint.h> > +#include <unistd.h> > +#include <strings.h> > +#include <time.h> > +#include <pthread.h> > +#include <hugetlbfs.h> > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <sys/mman.h> > +#include <sys/ioctl.h> > +#include <linux/hmm_dmirror.h> > + > +struct hmm_buffer { > + void *ptr; > + void *mirror; > + unsigned long size; > + int fd; > + uint64_t cpages; > + uint64_t faults; > +}; > + > +#define TWOMEG (1 << 21) > +#define HMM_BUFFER_SIZE (1024 << 12) > +#define HMM_PATH_MAX 64 > +#define NTIMES 256 > + > +#define ALIGN(x, a) (((x) + (a - 1)) & (~((a) - 1))) > + > +FIXTURE(hmm) > +{ > + int fd; > + unsigned int page_size; > + unsigned int page_shift; > +}; > + > +FIXTURE(hmm2) > +{ > + int fd0; > + int fd1; > + unsigned int page_size; > + unsigned int page_shift; > +}; > + > +static int hmm_open(int unit) > +{ > + char pathname[HMM_PATH_MAX]; > + int fd; > + > + snprintf(pathname, sizeof(pathname), "/dev/hmm_dmirror%d", unit); > + fd = open(pathname, O_RDWR, 0); > + if (fd < 0) > + fprintf(stderr, "could not open hmm dmirror driver (%s)\n", > + pathname); > + return fd; > +} > + > +FIXTURE_SETUP(hmm) > +{ > + self->page_size = sysconf(_SC_PAGE_SIZE); > + self->page_shift = ffs(self->page_size) - 1; > + > + self->fd = hmm_open(0); > + ASSERT_GE(self->fd, 0); > +} > + > +FIXTURE_SETUP(hmm2) > +{ > + self->page_size = sysconf(_SC_PAGE_SIZE); > + self->page_shift = ffs(self->page_size) - 1; > + > + self->fd0 = hmm_open(0); > + ASSERT_GE(self->fd0, 0); > + self->fd1 = hmm_open(1); > + ASSERT_GE(self->fd1, 0); > +} > + > +FIXTURE_TEARDOWN(hmm) > +{ > + int ret = close(self->fd); > + > + ASSERT_EQ(ret, 0); > + self->fd = -1; > +} > + > +FIXTURE_TEARDOWN(hmm2) > +{ > + int ret = close(self->fd0); > + > + ASSERT_EQ(ret, 0); > + self->fd0 = -1; > + > + ret = close(self->fd1); > + ASSERT_EQ(ret, 0); > + self->fd1 = -1; > +} > + > +static int hmm_dmirror_cmd(int fd, > + unsigned long request, > + struct hmm_buffer *buffer, > + unsigned long npages) > +{ > + struct hmm_dmirror_cmd cmd; > + int ret; > + > + /* Simulate a device reading system memory. */ > + cmd.addr = (__u64)buffer->ptr; > + cmd.ptr = (__u64)buffer->mirror; > + cmd.npages = npages; > + > + for (;;) { > + ret = ioctl(fd, request, &cmd); > + if (ret == 0) > + break; > + if (errno == EINTR) > + continue; > + return -errno; > + } > + buffer->cpages = cmd.cpages; > + buffer->faults = cmd.faults; > + > + return 0; > +} > + > +static void hmm_buffer_free(struct hmm_buffer *buffer) > +{ > + if (buffer == NULL) > + return; > + > + if (buffer->ptr) > + munmap(buffer->ptr, buffer->size); > + free(buffer->mirror); > + free(buffer); > +} > + > +/* > + * Create a temporary file that will be deleted on close. > + */ > +static int hmm_create_file(unsigned long size) > +{ > + char path[HMM_PATH_MAX]; > + int fd; > + > + strcpy(path, "/tmp"); > + fd = open(path, O_TMPFILE | O_EXCL | O_RDWR, 0600); > + if (fd >= 0) { > + int r; > + > + do { > + r = ftruncate(fd, size); > + } while (r == -1 && errno == EINTR); > + if (!r) > + return fd; > + close(fd); > + } > + return -1; > +} > + > +/* > + * Return a random unsigned number. > + */ > +static unsigned int hmm_random(void) > +{ > + static int fd = -1; > + unsigned int r; > + > + if (fd < 0) { > + fd = open("/dev/urandom", O_RDONLY); > + if (fd < 0) { > + fprintf(stderr, "%s:%d failed to open /dev/urandom\n", > + __FILE__, __LINE__); > + return ~0U; > + } > + } > + read(fd, &r, sizeof(r)); > + return r; > +} > + > +static void hmm_nanosleep(unsigned int n) > +{ > + struct timespec t; > + > + t.tv_sec = 0; > + t.tv_nsec = n; > + nanosleep(&t, NULL); > +} > + > +/* > + * Simple NULL test of device open/close. > + */ > +TEST_F(hmm, open_close) > +{ > +} > + > +/* > + * Read private anonymous memory. > + */ > +TEST_F(hmm, anon_read) > +{ > + 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 = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* > + * Initialize buffer in system memory but leave the first two pages > + * zero (pte_none and pfn_zero). > + */ > + i = 2 * self->page_size / sizeof(*ptr); > + for (ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Set buffer permission to read-only. */ > + ret = mprotect(buffer->ptr, size, PROT_READ); > + ASSERT_EQ(ret, 0); > + > + /* Populate the CPU page table with a special zero page. */ > + val = *(int *)(buffer->ptr + self->page_size); > + ASSERT_EQ(val, 0); > + > + /* Simulate a device reading system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device read. */ > + ptr = buffer->mirror; > + for (i = 0; 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); > +} > + > +/* > + * Read private anonymous memory which has been protected with > + * mprotect() PROT_NONE. > + */ > +TEST_F(hmm, anon_read_prot) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer in system memory. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Initialize mirror buffer so we can verify it isn't written. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = -i; > + > + /* Protect buffer from reading. */ > + ret = mprotect(buffer->ptr, size, PROT_NONE); > + ASSERT_EQ(ret, 0); > + > + /* Simulate a device reading system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages); > + ASSERT_EQ(ret, -EFAULT); > + > + /* Allow CPU to read the buffer so we can check it. */ > + ret = mprotect(buffer->ptr, size, PROT_READ); > + ASSERT_EQ(ret, 0); > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], -i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Write private anonymous memory. > + */ > +TEST_F(hmm, anon_write) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Write private anonymous memory which has been protected with > + * mprotect() PROT_READ. > + */ > +TEST_F(hmm, anon_write_prot) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Simulate a device reading a zero page of memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, 1); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, 1); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, -EPERM); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], 0); > + > + /* Now allow writing and see that the zero page is replaced. */ > + ret = mprotect(buffer->ptr, size, PROT_WRITE | PROT_READ); > + ASSERT_EQ(ret, 0); > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Check that a device writing an anonymous private mapping > + * will copy-on-write if a child process inherits the mapping. > + */ > +TEST_F(hmm, anon_write_child) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + pid_t pid; > + int child_fd; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer->ptr so we can tell if it is written. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = -i; > + > + pid = fork(); > + if (pid == -1) > + ASSERT_EQ(pid, 0); > + if (pid != 0) { > + waitpid(pid, &ret, 0); > + ASSERT_EQ(WIFEXITED(ret), 1); > + > + /* Check that the parent's buffer did not change. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + return; > + } > + > + /* Check that we see the parent's values. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], -i); > + > + /* The child process needs its own mirror to its own mm. */ > + child_fd = hmm_open(0); > + ASSERT_GE(child_fd, 0); > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(child_fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], -i); > + > + close(child_fd); > + exit(0); > +} > + > +/* > + * Check that a device writing an anonymous shared mapping > + * will not copy-on-write if a child process inherits the mapping. > + */ > +TEST_F(hmm, anon_write_child_shared) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + pid_t pid; > + int child_fd; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_SHARED | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer->ptr so we can tell if it is written. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = -i; > + > + pid = fork(); > + if (pid == -1) > + ASSERT_EQ(pid, 0); > + if (pid != 0) { > + waitpid(pid, &ret, 0); > + ASSERT_EQ(WIFEXITED(ret), 1); > + > + /* Check that the parent's buffer did change. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], -i); > + return; > + } > + > + /* Check that we see the parent's values. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], -i); > + > + /* The child process needs its own mirror to its own mm. */ > + child_fd = hmm_open(0); > + ASSERT_GE(child_fd, 0); > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(child_fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], -i); > + > + close(child_fd); > + exit(0); > +} > + > +/* > + * Write private anonymous huge page. > + */ > +TEST_F(hmm, anon_write_huge) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + void *old_ptr; > + void *map; > + int *ptr; > + int ret; > + > + size = 2 * TWOMEG; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + size = TWOMEG; > + npages = size >> self->page_shift; > + map = (void *)ALIGN((uintptr_t)buffer->ptr, size); > + ret = madvise(map, size, MADV_HUGEPAGE); > + ASSERT_EQ(ret, 0); > + old_ptr = buffer->ptr; > + buffer->ptr = map; > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + buffer->ptr = old_ptr; > + hmm_buffer_free(buffer); > +} > + > +/* > + * Write huge TLBFS page. > + */ > +TEST_F(hmm, anon_write_hugetlbfs) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + long pagesizes[4]; > + int n, idx; > + > + /* Skip test if we can't allocate a hugetlbfs page. */ > + > + n = gethugepagesizes(pagesizes, 4); > + if (n <= 0) > + return; > + for (idx = 0; --n > 0; ) { > + if (pagesizes[n] < pagesizes[idx]) > + idx = n; > + } > + size = ALIGN(TWOMEG, pagesizes[idx]); > + npages = size >> self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->ptr = get_hugepage_region(size, GHR_STRICT); > + if (buffer->ptr == NULL) { > + free(buffer); > + return; > + } > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + free_hugepage_region(buffer->ptr); > + buffer->ptr = NULL; > + hmm_buffer_free(buffer); > +} > + > +/* > + * Read mmap'ed file memory. > + */ > +TEST_F(hmm, file_read) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + int fd; > + off_t off; > + ssize_t len; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + fd = hmm_create_file(size); > + ASSERT_GE(fd, 0); > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = fd; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + /* Write initial contents of the file. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + off = lseek(fd, 0, SEEK_SET); > + ASSERT_EQ(off, 0); > + len = write(fd, buffer->mirror, size); > + ASSERT_EQ(len, size); > + memset(buffer->mirror, 0, size); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ, > + MAP_SHARED, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Simulate a device reading system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Write mmap'ed file memory. > + */ > +TEST_F(hmm, file_write) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + int fd; > + off_t off; > + ssize_t len; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + fd = hmm_create_file(size); > + ASSERT_GE(fd, 0); > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = fd; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_SHARED, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize data that the device will write to buffer->ptr. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Simulate a device writing system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_WRITE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device wrote. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + /* Check that the device also wrote the file. */ > + off = lseek(fd, 0, SEEK_SET); > + ASSERT_EQ(off, 0); > + len = read(fd, buffer->mirror, size); > + ASSERT_EQ(len, size); > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Migrate anonymous memory to device private memory. > + */ > +TEST_F(hmm, migrate) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer in system memory. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Migrate memory to device. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_MIGRATE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Migrate anonymous memory to device private memory and fault it back to system > + * memory. > + */ > +TEST_F(hmm, migrate_fault) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer in system memory. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Migrate memory to device. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_MIGRATE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + /* Fault pages back to system memory and check them. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Try to migrate various memory types to device private memory. > + */ > +TEST_F(hmm2, migrate_mixed) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + int *ptr; > + unsigned char *p; > + int ret; > + int val; > + > + npages = 6; > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + /* Reserve a range of addresses. */ > + buffer->ptr = mmap(NULL, size, > + PROT_NONE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + p = buffer->ptr; > + > + /* Now try to migrate everything to device 1. */ > + ret = hmm_dmirror_cmd(self->fd1, HMM_DMIRROR_MIGRATE, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, 6); > + > + /* Punch a hole after the first page address. */ > + ret = munmap(buffer->ptr + self->page_size, self->page_size); > + ASSERT_EQ(ret, 0); > + > + /* We expect an error if the vma doesn't cover the range. */ > + ret = hmm_dmirror_cmd(self->fd1, HMM_DMIRROR_MIGRATE, buffer, 3); > + ASSERT_EQ(ret, -EINVAL); > + > + /* Page 2 will be a read-only zero page. */ > + ret = mprotect(buffer->ptr + 2 * self->page_size, self->page_size, > + PROT_READ); > + ASSERT_EQ(ret, 0); > + ptr = (int *)(buffer->ptr + 2 * self->page_size); > + val = *ptr + 3; > + ASSERT_EQ(val, 3); > + > + /* Page 3 will be read-only. */ > + ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, > + PROT_READ | PROT_WRITE); > + ASSERT_EQ(ret, 0); > + ptr = (int *)(buffer->ptr + 3 * self->page_size); > + *ptr = val; > + ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, > + PROT_READ); > + ASSERT_EQ(ret, 0); > + > + /* Page 4 will be read-write. */ > + ret = mprotect(buffer->ptr + 4 * self->page_size, self->page_size, > + PROT_READ | PROT_WRITE); > + ASSERT_EQ(ret, 0); > + ptr = (int *)(buffer->ptr + 4 * self->page_size); > + *ptr = val; > + > + /* Page 5 won't be migrated to device 0 because it's on device 1. */ > + buffer->ptr = p + 5 * self->page_size; > + ret = hmm_dmirror_cmd(self->fd0, HMM_DMIRROR_MIGRATE, buffer, 1); > + ASSERT_EQ(ret, -ENOENT); > + buffer->ptr = p; > + > + /* Now try to migrate pages 2-3 to device 1. */ > + buffer->ptr = p + 2 * self->page_size; > + ret = hmm_dmirror_cmd(self->fd1, HMM_DMIRROR_MIGRATE, buffer, 2); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, 2); > + buffer->ptr = p; > + > + hmm_buffer_free(buffer); > +} > + > +/* > + * Migrate anonymous memory to device private memory and fault it back to system > + * memory multiple times. > + */ > +TEST_F(hmm, migrate_multiple) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + unsigned long c; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + for (c = 0; c < NTIMES; c++) { > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer in system memory. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i; > + > + /* Migrate memory to device. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_MIGRATE, buffer, > + npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + /* Fault pages back to system memory and check them. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i); > + > + hmm_buffer_free(buffer); > + } > +} > + > +/* > + * Read anonymous memory multiple times. > + */ > +TEST_F(hmm, anon_read_multiple) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + unsigned long i; > + unsigned long c; > + int *ptr; > + int ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + for (c = 0; c < NTIMES; c++) { > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer in system memory. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i + c; > + > + /* Simulate a device reading system memory. */ > + ret = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, > + npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; i < size / sizeof(*ptr); ++i) > + ASSERT_EQ(ptr[i], i + c); > + > + hmm_buffer_free(buffer); > + } > +} > + > +void *unmap_buffer(void *p) > +{ > + struct hmm_buffer *buffer = p; > + > + /* Delay for a bit and then unmap buffer while it is being read. */ > + hmm_nanosleep(hmm_random() % 32000); > + munmap(buffer->ptr + buffer->size / 2, buffer->size / 2); > + buffer->ptr = NULL; > + > + return NULL; > +} > + > +/* > + * Try reading anonymous memory while it is being unmapped. > + */ > +TEST_F(hmm, anon_teardown) > +{ > + unsigned long npages; > + unsigned long size; > + unsigned long c; > + void *ret; > + > + npages = ALIGN(HMM_BUFFER_SIZE, self->page_size) >> self->page_shift; > + ASSERT_NE(npages, 0); > + size = npages << self->page_shift; > + > + for (c = 0; c < NTIMES; ++c) { > + pthread_t thread; > + struct hmm_buffer *buffer; > + unsigned long i; > + int *ptr; > + int rc; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(size); > + ASSERT_NE(buffer->mirror, NULL); > + > + buffer->ptr = mmap(NULL, size, > + PROT_READ | PROT_WRITE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + > + /* Initialize buffer in system memory. */ > + for (i = 0, ptr = buffer->ptr; i < size / sizeof(*ptr); ++i) > + ptr[i] = i + c; > + > + rc = pthread_create(&thread, NULL, unmap_buffer, buffer); > + ASSERT_EQ(rc, 0); > + > + /* Simulate a device reading system memory. */ > + rc = hmm_dmirror_cmd(self->fd, HMM_DMIRROR_READ, buffer, > + npages); > + if (rc == 0) { > + ASSERT_EQ(buffer->cpages, npages); > + ASSERT_EQ(buffer->faults, 1); > + > + /* Check what the device read. */ > + for (i = 0, ptr = buffer->mirror; > + i < size / sizeof(*ptr); > + ++i) > + ASSERT_EQ(ptr[i], i + c); > + } > + > + pthread_join(thread, &ret); > + hmm_buffer_free(buffer); > + } > +} > + > +/* > + * Test memory snapshot without faulting in pages accessed by the device. > + */ > +TEST_F(hmm2, snapshot) > +{ > + struct hmm_buffer *buffer; > + unsigned long npages; > + unsigned long size; > + int *ptr; > + unsigned char *p; > + unsigned char *m; > + int ret; > + int val; > + > + npages = 7; > + size = npages << self->page_shift; > + > + buffer = malloc(sizeof(*buffer)); > + ASSERT_NE(buffer, NULL); > + > + buffer->fd = -1; > + buffer->size = size; > + buffer->mirror = malloc(npages); > + ASSERT_NE(buffer->mirror, NULL); > + > + /* Reserve a range of addresses. */ > + buffer->ptr = mmap(NULL, size, > + PROT_NONE, > + MAP_PRIVATE | MAP_ANONYMOUS, > + buffer->fd, 0); > + ASSERT_NE(buffer->ptr, MAP_FAILED); > + p = buffer->ptr; > + > + /* Punch a hole after the first page address. */ > + ret = munmap(buffer->ptr + self->page_size, self->page_size); > + ASSERT_EQ(ret, 0); > + > + /* Page 2 will be read-only zero page. */ > + ret = mprotect(buffer->ptr + 2 * self->page_size, self->page_size, > + PROT_READ); > + ASSERT_EQ(ret, 0); > + ptr = (int *)(buffer->ptr + 2 * self->page_size); > + val = *ptr + 3; > + ASSERT_EQ(val, 3); > + > + /* Page 3 will be read-only. */ > + ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, > + PROT_READ | PROT_WRITE); > + ASSERT_EQ(ret, 0); > + ptr = (int *)(buffer->ptr + 3 * self->page_size); > + *ptr = val; > + ret = mprotect(buffer->ptr + 3 * self->page_size, self->page_size, > + PROT_READ); > + ASSERT_EQ(ret, 0); > + > + /* Page 4-6 will be read-write. */ > + ret = mprotect(buffer->ptr + 4 * self->page_size, 3 * self->page_size, > + PROT_READ | PROT_WRITE); > + ASSERT_EQ(ret, 0); > + ptr = (int *)(buffer->ptr + 4 * self->page_size); > + *ptr = val; > + > + /* Page 5 will be migrated to device 0. */ > + buffer->ptr = p + 5 * self->page_size; > + ret = hmm_dmirror_cmd(self->fd0, HMM_DMIRROR_MIGRATE, buffer, 1); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, 1); > + > + /* Page 6 will be migrated to device 1. */ > + buffer->ptr = p + 6 * self->page_size; > + ret = hmm_dmirror_cmd(self->fd1, HMM_DMIRROR_MIGRATE, buffer, 1); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, 1); > + > + /* Simulate a device snapshotting CPU pagetables. */ > + buffer->ptr = p; > + ret = hmm_dmirror_cmd(self->fd0, HMM_DMIRROR_SNAPSHOT, buffer, npages); > + ASSERT_EQ(ret, 0); > + ASSERT_EQ(buffer->cpages, npages); > + > + /* Check what the device saw. */ > + m = buffer->mirror; > + ASSERT_EQ(m[0], HMM_DMIRROR_PROT_NONE); > + ASSERT_EQ(m[1], HMM_DMIRROR_PROT_NONE); > + ASSERT_EQ(m[2], HMM_DMIRROR_PROT_ZERO | HMM_DMIRROR_PROT_READ); > + ASSERT_EQ(m[3], HMM_DMIRROR_PROT_READ); > + ASSERT_EQ(m[4], HMM_DMIRROR_PROT_WRITE); > + ASSERT_EQ(m[5], HMM_DMIRROR_PROT_DEV_PRIVATE_LOCAL | > + HMM_DMIRROR_PROT_WRITE); > + ASSERT_EQ(m[6], HMM_DMIRROR_PROT_DEV_PRIVATE_REMOTE | > + HMM_DMIRROR_PROT_WRITE); > + > + hmm_buffer_free(buffer); > +} > + > +TEST_HARNESS_MAIN > diff --git a/tools/testing/selftests/vm/run_vmtests b/tools/testing/selftests/vm/run_vmtests > index 951c507a27f7..634cfefdaffd 100755 > --- a/tools/testing/selftests/vm/run_vmtests > +++ b/tools/testing/selftests/vm/run_vmtests > @@ -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 > diff --git a/tools/testing/selftests/vm/test_hmm.sh b/tools/testing/selftests/vm/test_hmm.sh > new file mode 100755 > index 000000000000..268d32752045 > --- /dev/null > +++ b/tools/testing/selftests/vm/test_hmm.sh > @@ -0,0 +1,97 @@ > +#!/bin/bash > +# SPDX-License-Identifier: GPL-2.0 > +# > +# Copyright (C) 2018 Uladzislau Rezki (Sony) <urezki@xxxxxxxxx> > +# > +# This is a test script for the kernel test driver to analyse vmalloc > +# allocator. Therefore it is just a kernel module loader. You can specify > +# and pass different parameters in order to: > +# a) analyse performance of vmalloc allocations; > +# b) stressing and stability check of vmalloc subsystem. > + > +TEST_NAME="test_hmm" > +DRIVER="hmm_dmirror" > + > +# 1 if fails > +exitcode=1 > + > +# Kselftest framework requirement - SKIP code is 4. > +ksft_skip=4 > + > +check_test_requirements() > +{ > + uid=$(id -u) > + if [ $uid -ne 0 ]; then > + echo "$0: Must be run as root" > + exit $ksft_skip > + fi > + > + if ! which modprobe > /dev/null 2>&1; then > + echo "$0: You need modprobe installed" > + exit $ksft_skip > + fi > + > + if ! modinfo $DRIVER > /dev/null 2>&1; then > + echo "$0: You must have the following enabled in your kernel:" > + echo "CONFIG_HMM_DMIRROR=m" > + exit $ksft_skip > + fi > +} > + > +load_driver() > +{ > + modprobe $DRIVER > /dev/null 2>&1 > + if [ $? == 0 ]; then > + major=$(awk "\$2==\"HMM_DMIRROR\" {print \$1}" /proc/devices) > + mknod /dev/hmm_dmirror0 c $major 0 > + mknod /dev/hmm_dmirror1 c $major 1 > + fi > +} > + > +unload_driver() > +{ > + modprobe -r $DRIVER > /dev/null 2>&1 > + rm -f /dev/hmm_dmirror? > +} > + > +run_smoke() > +{ > + echo "Running smoke test. Note, this test provides basic coverage." > + > + load_driver > + ./hmm-tests > + unload_driver > +} > + > +usage() > +{ > + echo -n "Usage: $0" > + echo > + echo "Example usage:" > + echo > + echo "# Shows help message" > + echo "./${TEST_NAME}.sh" > + echo > + echo "# Smoke testing" > + echo "./${TEST_NAME}.sh smoke" > + echo > + exit 0 > +} > + > +function run_test() > +{ > + if [ $# -eq 0 ]; then > + usage > + else > + if [ "$1" = "smoke" ]; then > + run_smoke > + else > + usage > + fi > + fi > +} > + > +check_test_requirements > +run_test $@ > + > +exit 0 > -- > 2.20.1 >