drm/nouveau: block a bunch of classes from userspace
[linux-2.6-microblaze.git] / lib / test_hmm.c
index 15f2e2d..c259842 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/swapops.h>
 #include <linux/sched/mm.h>
 #include <linux/platform_device.h>
+#include <linux/rmap.h>
 
 #include "test_hmm_uapi.h"
 
@@ -46,6 +47,7 @@ struct dmirror_bounce {
        unsigned long           cpages;
 };
 
+#define DPT_XA_TAG_ATOMIC 1UL
 #define DPT_XA_TAG_WRITE 3UL
 
 /*
@@ -218,7 +220,7 @@ static bool dmirror_interval_invalidate(struct mmu_interval_notifier *mni,
         * the invalidation is handled as part of the migration process.
         */
        if (range->event == MMU_NOTIFY_MIGRATE &&
-           range->migrate_pgmap_owner == dmirror->mdevice)
+           range->owner == dmirror->mdevice)
                return true;
 
        if (mmu_notifier_range_blockable(range))
@@ -619,6 +621,52 @@ static void dmirror_migrate_alloc_and_copy(struct migrate_vma *args,
        }
 }
 
+static int dmirror_check_atomic(struct dmirror *dmirror, unsigned long start,
+                            unsigned long end)
+{
+       unsigned long pfn;
+
+       for (pfn = start >> PAGE_SHIFT; pfn < (end >> PAGE_SHIFT); pfn++) {
+               void *entry;
+
+               entry = xa_load(&dmirror->pt, pfn);
+               if (xa_pointer_tag(entry) == DPT_XA_TAG_ATOMIC)
+                       return -EPERM;
+       }
+
+       return 0;
+}
+
+static int dmirror_atomic_map(unsigned long start, unsigned long end,
+                             struct page **pages, struct dmirror *dmirror)
+{
+       unsigned long pfn, mapped = 0;
+       int i;
+
+       /* Map the migrated pages into the device's page tables. */
+       mutex_lock(&dmirror->mutex);
+
+       for (i = 0, pfn = start >> PAGE_SHIFT; pfn < (end >> PAGE_SHIFT); pfn++, i++) {
+               void *entry;
+
+               if (!pages[i])
+                       continue;
+
+               entry = pages[i];
+               entry = xa_tag_pointer(entry, DPT_XA_TAG_ATOMIC);
+               entry = xa_store(&dmirror->pt, pfn, entry, GFP_ATOMIC);
+               if (xa_is_err(entry)) {
+                       mutex_unlock(&dmirror->mutex);
+                       return xa_err(entry);
+               }
+
+               mapped++;
+       }
+
+       mutex_unlock(&dmirror->mutex);
+       return mapped;
+}
+
 static int dmirror_migrate_finalize_and_map(struct migrate_vma *args,
                                            struct dmirror *dmirror)
 {
@@ -661,6 +709,72 @@ static int dmirror_migrate_finalize_and_map(struct migrate_vma *args,
        return 0;
 }
 
+static int dmirror_exclusive(struct dmirror *dmirror,
+                            struct hmm_dmirror_cmd *cmd)
+{
+       unsigned long start, end, addr;
+       unsigned long size = cmd->npages << PAGE_SHIFT;
+       struct mm_struct *mm = dmirror->notifier.mm;
+       struct page *pages[64];
+       struct dmirror_bounce bounce;
+       unsigned long next;
+       int ret;
+
+       start = cmd->addr;
+       end = start + size;
+       if (end < start)
+               return -EINVAL;
+
+       /* Since the mm is for the mirrored process, get a reference first. */
+       if (!mmget_not_zero(mm))
+               return -EINVAL;
+
+       mmap_read_lock(mm);
+       for (addr = start; addr < end; addr = next) {
+               unsigned long mapped;
+               int i;
+
+               if (end < addr + (ARRAY_SIZE(pages) << PAGE_SHIFT))
+                       next = end;
+               else
+                       next = addr + (ARRAY_SIZE(pages) << PAGE_SHIFT);
+
+               ret = make_device_exclusive_range(mm, addr, next, pages, NULL);
+               mapped = dmirror_atomic_map(addr, next, pages, dmirror);
+               for (i = 0; i < ret; i++) {
+                       if (pages[i]) {
+                               unlock_page(pages[i]);
+                               put_page(pages[i]);
+                       }
+               }
+
+               if (addr + (mapped << PAGE_SHIFT) < next) {
+                       mmap_read_unlock(mm);
+                       mmput(mm);
+                       return -EBUSY;
+               }
+       }
+       mmap_read_unlock(mm);
+       mmput(mm);
+
+       /* Return the migrated data for verification. */
+       ret = dmirror_bounce_init(&bounce, start, size);
+       if (ret)
+               return ret;
+       mutex_lock(&dmirror->mutex);
+       ret = dmirror_do_read(dmirror, start, end, &bounce);
+       mutex_unlock(&dmirror->mutex);
+       if (ret == 0) {
+               if (copy_to_user(u64_to_user_ptr(cmd->ptr), bounce.ptr,
+                                bounce.size))
+                       ret = -EFAULT;
+       }
+
+       cmd->cpages = bounce.cpages;
+       dmirror_bounce_fini(&bounce);
+       return ret;
+}
+
 static int dmirror_migrate(struct dmirror *dmirror,
                           struct hmm_dmirror_cmd *cmd)
 {
@@ -948,6 +1062,15 @@ static long dmirror_fops_unlocked_ioctl(struct file *filp,
                ret = dmirror_migrate(dmirror, &cmd);
                break;
 
+       case HMM_DMIRROR_EXCLUSIVE:
+               ret = dmirror_exclusive(dmirror, &cmd);
+               break;
+
+       case HMM_DMIRROR_CHECK_EXCLUSIVE:
+               ret = dmirror_check_atomic(dmirror, cmd.addr,
+                                       cmd.addr + (cmd.npages << PAGE_SHIFT));
+               break;
+
        case HMM_DMIRROR_SNAPSHOT:
                ret = dmirror_snapshot(dmirror, &cmd);
                break;