*/
};
+/* userfaultfd ops defined in userfaultfd_k.h */
+struct vm_uffd_ops;
+
/*
* These are the virtual MM functions - opening of an area, closing and
* unmapping it (needed to keep files on disk up-to-date etc), pointer
struct page *(*find_normal_page)(struct vm_area_struct *vma,
unsigned long addr);
#endif /* CONFIG_FIND_NORMAL_PAGE */
+#ifdef CONFIG_USERFAULTFD
+ /*
+ * Userfaultfd operations. Memory types need to define their own
+ * operations to support different features. anon vmas use the
+ * default_uffd_ops.
+ */
+ const struct vm_uffd_ops *userfaultfd_ops;
+#endif /* CONFIG_USERFAULTFD */
};
#ifdef CONFIG_NUMA_BALANCING
NR_MFILL_ATOMIC_MODES,
};
+struct vm_uffd_ops {
+ /* Required features below */
+ ssize_t (*is_dst_valid)(struct vm_area_struct *dst_vma,
+ unsigned long dst_start, unsigned long len);
+};
+
#define MFILL_ATOMIC_MODE_BITS (const_ilog2(NR_MFILL_ATOMIC_MODES - 1) + 1)
#define MFILL_ATOMIC_BIT(nr) BIT(MFILL_ATOMIC_MODE_BITS + (nr))
#define MFILL_ATOMIC_FLAG(nr) ((__force uffd_flags_t) MFILL_ATOMIC_BIT(nr))
return 0;
}
+#ifdef CONFIG_USERFAULTFD
+static ssize_t hugetlb_is_dst_valid(struct vm_area_struct *dst_vma,
+ unsigned long dst_start, unsigned long len);
+static const struct vm_uffd_ops hugetlb_uffd_ops = {
+ .is_dst_valid = hugetlb_is_dst_valid,
+};
+#endif
+
/*
* When a new function is introduced to vm_operations_struct and added
* to hugetlb_vm_ops, please consider adding the function to shm_vm_ops.
.close = hugetlb_vm_op_close,
.may_split = hugetlb_vm_op_split,
.pagesize = hugetlb_vm_op_pagesize,
+#ifdef CONFIG_USERFAULTFD
+ .userfaultfd_ops = &hugetlb_uffd_ops,
+#endif /* CONFIG_USERFAULTFD */
};
static pte_t make_huge_pte(struct vm_area_struct *vma, struct folio *folio,
return folio;
}
+static ssize_t hugetlb_is_dst_valid(struct vm_area_struct *dst_vma,
+ unsigned long dst_start, unsigned long len)
+{
+ unsigned long vma_hpagesize;
+
+ vma_hpagesize = vma_kernel_pagesize(dst_vma);
+ if (dst_start & (vma_hpagesize - 1) || len & (vma_hpagesize - 1))
+ return -EINVAL;
+
+ if (!is_vm_hugetlb_page(dst_vma))
+ return -ENOENT;
+
+ if (vma_hpagesize != vma_kernel_pagesize(dst_vma))
+ return -EINVAL;
+
+ return 0;
+}
/*
* Used by userfaultfd UFFDIO_* ioctls. Based on userfaultfd's mfill_atomic_pte
* with modifications for hugetlb pages.
#endif /* CONFIG_TMPFS_QUOTA */
#ifdef CONFIG_USERFAULTFD
+static ssize_t shmem_is_dst_valid(struct vm_area_struct *dst_vma,
+ unsigned long dst_start, unsigned long len)
+{
+ /*
+ * shmem_zero_setup is invoked in mmap for MAP_ANONYMOUS|MAP_SHARED but
+ * it will overwrite vm_ops, so vma_is_anonymous must return false.
+ */
+ if (WARN_ON_ONCE(vma_is_anonymous(dst_vma) &&
+ dst_vma->vm_flags & VM_SHARED))
+ return -EINVAL;
+
+ return 0;
+}
+
int shmem_mfill_atomic_pte(struct vm_area_struct *dst_vma,
unsigned long dst_addr,
unsigned long src_addr,
return 0;
}
+#ifdef CONFIG_USERFAULTFD
+static const struct vm_uffd_ops shmem_uffd_ops = {
+ .is_dst_valid = shmem_is_dst_valid,
+};
+#endif
+
static const struct address_space_operations shmem_aops = {
.dirty_folio = noop_dirty_folio,
#ifdef CONFIG_TMPFS
.set_policy = shmem_set_policy,
.get_policy = shmem_get_policy,
#endif
+#ifdef CONFIG_USERFAULTFD
+ .userfaultfd_ops = &shmem_uffd_ops,
+#endif
+
};
int shmem_init_fs_context(struct fs_context *fc)
return pmd_alloc(mm, pud, address);
}
+
+/* 0 for success, error code otherwise */
+static ssize_t uffd_def_is_dst_valid(struct vm_area_struct *dst_vma,
+ unsigned long dst_start, unsigned long len)
+{
+ return 0;
+}
+
+/* Anon vma ops */
+static const struct vm_uffd_ops default_uffd_ops = {
+ .is_dst_valid = uffd_def_is_dst_valid,
+};
+
+static inline const struct vm_uffd_ops *vma_get_uffd_ops(struct vm_area_struct *vma)
+{
+ if (vma->vm_ops && vma->vm_ops->userfaultfd_ops)
+ return vma->vm_ops->userfaultfd_ops;
+
+ return &default_uffd_ops;
+}
+
+static inline ssize_t
+uffd_ctx_lock_and_validate_dst(struct userfaultfd_ctx *ctx,
+ struct vm_area_struct *dst_vma, unsigned long dst_start,
+ unsigned long len)
+{
+ const struct vm_uffd_ops *uffd_ops;
+
+ /*
+ * If memory mappings are changing because of non-cooperative
+ * operation (e.g. mremap) running in parallel, bail out and
+ * request the user to retry later
+ */
+ down_read(&ctx->map_changing_lock);
+ if (atomic_read(&ctx->mmap_changing))
+ return -EAGAIN;
+
+ uffd_ops = vma_get_uffd_ops(dst_vma);
+ WARN_ON_ONCE(!uffd_ops || !uffd_ops->is_dst_valid);
+ return uffd_ops->is_dst_valid(dst_vma, dst_start, len);
+}
+
#ifdef CONFIG_HUGETLB_PAGE
/*
* mfill_atomic processing for HUGETLB vmas. Note that this routine is
folio = NULL;
vma_hpagesize = vma_kernel_pagesize(dst_vma);
- /*
- * Validate alignment based on huge page size
- */
- err = -EINVAL;
- if (dst_start & (vma_hpagesize - 1) || len & (vma_hpagesize - 1))
- goto out_unlock;
-
retry:
/*
* On routine entry dst_vma is set. If we had to drop mmap_lock and
goto out;
}
- /*
- * If memory mappings are changing because of non-cooperative
- * operation (e.g. mremap) running in parallel, bail out and
- * request the user to retry later
- */
- down_read(&ctx->map_changing_lock);
- err = -EAGAIN;
- if (atomic_read(&ctx->mmap_changing))
- goto out_unlock;
-
- err = -ENOENT;
- if (!is_vm_hugetlb_page(dst_vma))
- goto out_unlock;
-
- err = -EINVAL;
- if (vma_hpagesize != vma_kernel_pagesize(dst_vma))
+ err = uffd_ctx_lock_and_validate_dst(ctx, dst_vma, dst_start, len);
+ if (err)
goto out_unlock;
}
uffd_flags_t flags);
#endif /* CONFIG_HUGETLB_PAGE */
-static inline ssize_t
-uffd_ctx_lock_and_validate_dst(struct userfaultfd_ctx *ctx,
- struct vm_area_struct *dst_vma)
-{
- /*
- * If memory mappings are changing because of non-cooperative
- * operation (e.g. mremap) running in parallel, bail out and
- * request the user to retry later
- */
- down_read(&ctx->map_changing_lock);
- if (atomic_read(&ctx->mmap_changing))
- return -EAGAIN;
-
- /*
- * shmem_zero_setup is invoked in mmap for MAP_ANONYMOUS|MAP_SHARED but
- * it will overwrite vm_ops, so vma_is_anonymous must return false.
- */
- if (WARN_ON_ONCE(vma_is_anonymous(dst_vma) &&
- dst_vma->vm_flags & VM_SHARED))
- return -EINVAL;
-
- return 0;
-}
-
static inline ssize_t
uffd_failed_do_unlock(struct userfaultfd_ctx *ctx, struct vm_area_struct *dst,
struct folio *folio, unsigned long src_addr)
goto out;
}
- err = uffd_ctx_lock_and_validate_dst(ctx, dst_vma);
+ err = uffd_ctx_lock_and_validate_dst(ctx, dst_vma, dst_start, len);
if (err)
goto out_unlock;