#define  MEM_ALL  (MEM_ANON | MEM_SHMEM | MEM_SHMEM_PRIVATE | \
                   MEM_HUGETLB | MEM_HUGETLB_PRIVATE)
 
+#define ALIGN_UP(x, align_to) \
+       ((__typeof__(x))((((unsigned long)(x)) + ((align_to)-1)) & ~((align_to)-1)))
+
 struct mem_type {
        const char *name;
        unsigned int mem_flag;
        uffd_test_pass();
 }
 
+static void
+uffd_move_handle_fault_common(struct uffd_msg *msg, struct uffd_args *args,
+                             unsigned long len)
+{
+       unsigned long offset;
+
+       if (msg->event != UFFD_EVENT_PAGEFAULT)
+               err("unexpected msg event %u", msg->event);
+
+       if (msg->arg.pagefault.flags &
+           (UFFD_PAGEFAULT_FLAG_WP | UFFD_PAGEFAULT_FLAG_MINOR | UFFD_PAGEFAULT_FLAG_WRITE))
+               err("unexpected fault type %llu", msg->arg.pagefault.flags);
+
+       offset = (char *)(unsigned long)msg->arg.pagefault.address - area_dst;
+       offset &= ~(len-1);
+
+       if (move_page(uffd, offset, len))
+               args->missing_faults++;
+}
+
+static void uffd_move_handle_fault(struct uffd_msg *msg,
+                                  struct uffd_args *args)
+{
+       uffd_move_handle_fault_common(msg, args, page_size);
+}
+
+static void uffd_move_pmd_handle_fault(struct uffd_msg *msg,
+                                      struct uffd_args *args)
+{
+       uffd_move_handle_fault_common(msg, args, read_pmd_pagesize());
+}
+
+static void
+uffd_move_test_common(uffd_test_args_t *targs, unsigned long chunk_size,
+                     void (*handle_fault)(struct uffd_msg *msg, struct uffd_args *args))
+{
+       unsigned long nr;
+       pthread_t uffd_mon;
+       char c;
+       unsigned long long count;
+       struct uffd_args args = { 0 };
+       char *orig_area_src, *orig_area_dst;
+       unsigned long step_size, step_count;
+       unsigned long src_offs = 0;
+       unsigned long dst_offs = 0;
+
+       /* Prevent source pages from being mapped more than once */
+       if (madvise(area_src, nr_pages * page_size, MADV_DONTFORK))
+               err("madvise(MADV_DONTFORK) failure");
+
+       if (uffd_register(uffd, area_dst, nr_pages * page_size,
+                         true, false, false))
+               err("register failure");
+
+       args.handle_fault = handle_fault;
+       if (pthread_create(&uffd_mon, NULL, uffd_poll_thread, &args))
+               err("uffd_poll_thread create");
+
+       step_size = chunk_size / page_size;
+       step_count = nr_pages / step_size;
+
+       if (chunk_size > page_size) {
+               char *aligned_src = ALIGN_UP(area_src, chunk_size);
+               char *aligned_dst = ALIGN_UP(area_dst, chunk_size);
+
+               if (aligned_src != area_src || aligned_dst != area_dst) {
+                       src_offs = (aligned_src - area_src) / page_size;
+                       dst_offs = (aligned_dst - area_dst) / page_size;
+                       step_count--;
+               }
+               orig_area_src = area_src;
+               orig_area_dst = area_dst;
+               area_src = aligned_src;
+               area_dst = aligned_dst;
+       }
+
+       /*
+        * Read each of the pages back using the UFFD-registered mapping. We
+        * expect that the first time we touch a page, it will result in a missing
+        * fault. uffd_poll_thread will resolve the fault by moving source
+        * page to destination.
+        */
+       for (nr = 0; nr < step_count * step_size; nr += step_size) {
+               unsigned long i;
+
+               /* Check area_src content */
+               for (i = 0; i < step_size; i++) {
+                       count = *area_count(area_src, nr + i);
+                       if (count != count_verify[src_offs + nr + i])
+                               err("nr %lu source memory invalid %llu %llu\n",
+                                   nr + i, count, count_verify[src_offs + nr + i]);
+               }
+
+               /* Faulting into area_dst should move the page or the huge page */
+               for (i = 0; i < step_size; i++) {
+                       count = *area_count(area_dst, nr + i);
+                       if (count != count_verify[dst_offs + nr + i])
+                               err("nr %lu memory corruption %llu %llu\n",
+                                   nr, count, count_verify[dst_offs + nr + i]);
+               }
+
+               /* Re-check area_src content which should be empty */
+               for (i = 0; i < step_size; i++) {
+                       count = *area_count(area_src, nr + i);
+                       if (count != 0)
+                               err("nr %lu move failed %llu %llu\n",
+                                   nr, count, count_verify[src_offs + nr + i]);
+               }
+       }
+       if (step_size > page_size) {
+               area_src = orig_area_src;
+               area_dst = orig_area_dst;
+       }
+
+       if (write(pipefd[1], &c, sizeof(c)) != sizeof(c))
+               err("pipe write");
+       if (pthread_join(uffd_mon, NULL))
+               err("join() failed");
+
+       if (args.missing_faults != step_count || args.minor_faults != 0)
+               uffd_test_fail("stats check error");
+       else
+               uffd_test_pass();
+}
+
+static void uffd_move_test(uffd_test_args_t *targs)
+{
+       uffd_move_test_common(targs, page_size, uffd_move_handle_fault);
+}
+
+static void uffd_move_pmd_test(uffd_test_args_t *targs)
+{
+       uffd_move_test_common(targs, read_pmd_pagesize(),
+                             uffd_move_pmd_handle_fault);
+}
+
+static int prevent_hugepages(const char **errmsg)
+{
+       /* This should be done before source area is populated */
+       if (madvise(area_src, nr_pages * page_size, MADV_NOHUGEPAGE)) {
+               /* Ignore only if CONFIG_TRANSPARENT_HUGEPAGE=n */
+               if (errno != EINVAL) {
+                       if (errmsg)
+                               *errmsg = "madvise(MADV_NOHUGEPAGE) failed";
+                       return -errno;
+               }
+       }
+       return 0;
+}
+
+static int request_hugepages(const char **errmsg)
+{
+       /* This should be done before source area is populated */
+       if (madvise(area_src, nr_pages * page_size, MADV_HUGEPAGE)) {
+               if (errmsg) {
+                       *errmsg = (errno == EINVAL) ?
+                               "CONFIG_TRANSPARENT_HUGEPAGE is not set" :
+                               "madvise(MADV_HUGEPAGE) failed";
+               }
+               return -errno;
+       }
+       return 0;
+}
+
+struct uffd_test_case_ops uffd_move_test_case_ops = {
+       .post_alloc = prevent_hugepages,
+};
+
+struct uffd_test_case_ops uffd_move_test_pmd_case_ops = {
+       .post_alloc = request_hugepages,
+};
+
 /*
  * Test the returned uffdio_register.ioctls with different register modes.
  * Note that _UFFDIO_ZEROPAGE is tested separately in the zeropage test.
                .mem_targets = MEM_ALL,
                .uffd_feature_required = 0,
        },
+       {
+               .name = "move",
+               .uffd_fn = uffd_move_test,
+               .mem_targets = MEM_ANON,
+               .uffd_feature_required = UFFD_FEATURE_MOVE,
+               .test_case_ops = &uffd_move_test_case_ops,
+       },
+       {
+               .name = "move-pmd",
+               .uffd_fn = uffd_move_pmd_test,
+               .mem_targets = MEM_ANON,
+               .uffd_feature_required = UFFD_FEATURE_MOVE,
+               .test_case_ops = &uffd_move_test_pmd_case_ops,
+       },
        {
                .name = "wp-fork",
                .uffd_fn = uffd_wp_fork_test,