iommufd: Add IOMMU_IOAS_MAP_FILE
authorSteve Sistare <steven.sistare@oracle.com>
Fri, 25 Oct 2024 13:11:57 +0000 (06:11 -0700)
committerJason Gunthorpe <jgg@nvidia.com>
Mon, 28 Oct 2024 16:24:24 +0000 (13:24 -0300)
Define the IOMMU_IOAS_MAP_FILE ioctl interface, which allows a user to
register memory by passing a memfd plus offset and length.  Implement it
using the memfd_pin_folios() kAPI.

Link: https://patch.msgid.link/r/1729861919-234514-8-git-send-email-steven.sistare@oracle.com
Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Steve Sistare <steven.sistare@oracle.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
drivers/iommu/iommufd/io_pagetable.c
drivers/iommu/iommufd/io_pagetable.h
drivers/iommu/iommufd/ioas.c
drivers/iommu/iommufd/iommufd_private.h
drivers/iommu/iommufd/main.c
drivers/iommu/iommufd/pages.c
include/uapi/linux/iommufd.h

index 874ee9e..8a790e5 100644 (file)
@@ -268,7 +268,14 @@ static int iopt_alloc_area_pages(struct io_pagetable *iopt,
                /* Use the first entry to guess the ideal IOVA alignment */
                elm = list_first_entry(pages_list, struct iopt_pages_list,
                                       next);
-               start = elm->start_byte + (uintptr_t)elm->pages->uptr;
+               switch (elm->pages->type) {
+               case IOPT_ADDRESS_USER:
+                       start = elm->start_byte + (uintptr_t)elm->pages->uptr;
+                       break;
+               case IOPT_ADDRESS_FILE:
+                       start = elm->start_byte + elm->pages->start;
+                       break;
+               }
                rc = iopt_alloc_iova(iopt, dst_iova, start, length);
                if (rc)
                        goto out_unlock;
@@ -446,6 +453,33 @@ int iopt_map_user_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
                               uptr - pages->uptr, iommu_prot, flags);
 }
 
+/**
+ * iopt_map_file_pages() - Like iopt_map_user_pages, but map a file.
+ * @ictx: iommufd_ctx the iopt is part of
+ * @iopt: io_pagetable to act on
+ * @iova: If IOPT_ALLOC_IOVA is set this is unused on input and contains
+ *        the chosen iova on output. Otherwise is the iova to map to on input
+ * @file: file to map
+ * @start: map file starting at this byte offset
+ * @length: Number of bytes to map
+ * @iommu_prot: Combination of IOMMU_READ/WRITE/etc bits for the mapping
+ * @flags: IOPT_ALLOC_IOVA or zero
+ */
+int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
+                       unsigned long *iova, struct file *file,
+                       unsigned long start, unsigned long length,
+                       int iommu_prot, unsigned int flags)
+{
+       struct iopt_pages *pages;
+
+       pages = iopt_alloc_file_pages(file, start, length,
+                                     iommu_prot & IOMMU_WRITE);
+       if (IS_ERR(pages))
+               return PTR_ERR(pages);
+       return iopt_map_common(ictx, iopt, pages, iova, length,
+                              start - pages->start, iommu_prot, flags);
+}
+
 struct iova_bitmap_fn_arg {
        unsigned long flags;
        struct io_pagetable *iopt;
index 5ac4eed..9b40b22 100644 (file)
@@ -220,6 +220,8 @@ struct iopt_pages {
 
 struct iopt_pages *iopt_alloc_user_pages(void __user *uptr,
                                         unsigned long length, bool writable);
+struct iopt_pages *iopt_alloc_file_pages(struct file *file, unsigned long start,
+                                        unsigned long length, bool writable);
 void iopt_release_pages(struct kref *kref);
 static inline void iopt_put_pages(struct iopt_pages *pages)
 {
index 2c4b2bb..c05d33f 100644 (file)
@@ -2,6 +2,7 @@
 /*
  * Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES
  */
+#include <linux/file.h>
 #include <linux/interval_tree.h>
 #include <linux/iommu.h>
 #include <linux/iommufd.h>
@@ -197,6 +198,52 @@ static int conv_iommu_prot(u32 map_flags)
        return iommu_prot;
 }
 
+int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd)
+{
+       struct iommu_ioas_map_file *cmd = ucmd->cmd;
+       unsigned long iova = cmd->iova;
+       struct iommufd_ioas *ioas;
+       unsigned int flags = 0;
+       struct file *file;
+       int rc;
+
+       if (cmd->flags &
+            ~(IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_WRITEABLE |
+              IOMMU_IOAS_MAP_READABLE))
+               return -EOPNOTSUPP;
+
+       if (cmd->iova >= ULONG_MAX || cmd->length >= ULONG_MAX)
+               return -EOVERFLOW;
+
+       if (!(cmd->flags &
+             (IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_READABLE)))
+               return -EINVAL;
+
+       ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id);
+       if (IS_ERR(ioas))
+               return PTR_ERR(ioas);
+
+       if (!(cmd->flags & IOMMU_IOAS_MAP_FIXED_IOVA))
+               flags = IOPT_ALLOC_IOVA;
+
+       file = fget(cmd->fd);
+       if (!file)
+               return -EBADF;
+
+       rc = iopt_map_file_pages(ucmd->ictx, &ioas->iopt, &iova, file,
+                                cmd->start, cmd->length,
+                                conv_iommu_prot(cmd->flags), flags);
+       if (rc)
+               goto out_put;
+
+       cmd->iova = iova;
+       rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
+out_put:
+       iommufd_put_object(ucmd->ictx, &ioas->obj);
+       fput(file);
+       return rc;
+}
+
 int iommufd_ioas_map(struct iommufd_ucmd *ucmd)
 {
        struct iommu_ioas_map *cmd = ucmd->cmd;
index f1d865e..8f3c21a 100644 (file)
@@ -69,6 +69,10 @@ int iopt_map_user_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
                        unsigned long *iova, void __user *uptr,
                        unsigned long length, int iommu_prot,
                        unsigned int flags);
+int iopt_map_file_pages(struct iommufd_ctx *ictx, struct io_pagetable *iopt,
+                       unsigned long *iova, struct file *file,
+                       unsigned long start, unsigned long length,
+                       int iommu_prot, unsigned int flags);
 int iopt_map_pages(struct io_pagetable *iopt, struct list_head *pages_list,
                   unsigned long length, unsigned long *dst_iova,
                   int iommu_prot, unsigned int flags);
@@ -276,6 +280,7 @@ void iommufd_ioas_destroy(struct iommufd_object *obj);
 int iommufd_ioas_iova_ranges(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_allow_iovas(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_map(struct iommufd_ucmd *ucmd);
+int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_copy(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd);
 int iommufd_ioas_option(struct iommufd_ucmd *ucmd);
index b5f5d27..826a2b2 100644 (file)
@@ -378,6 +378,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = {
                 struct iommu_ioas_iova_ranges, out_iova_alignment),
        IOCTL_OP(IOMMU_IOAS_MAP, iommufd_ioas_map, struct iommu_ioas_map,
                 iova),
+       IOCTL_OP(IOMMU_IOAS_MAP_FILE, iommufd_ioas_map_file,
+                struct iommu_ioas_map_file, iova),
        IOCTL_OP(IOMMU_IOAS_UNMAP, iommufd_ioas_unmap, struct iommu_ioas_unmap,
                 length),
        IOCTL_OP(IOMMU_OPTION, iommufd_option, struct iommu_option,
index 5f371fa..2ee6fcd 100644 (file)
@@ -45,6 +45,7 @@
  * last_iova + 1 can overflow. An iopt_pages index will always be much less than
  * ULONG_MAX so last_index + 1 cannot overflow.
  */
+#include <linux/file.h>
 #include <linux/highmem.h>
 #include <linux/iommu.h>
 #include <linux/iommufd.h>
@@ -1340,6 +1341,26 @@ struct iopt_pages *iopt_alloc_user_pages(void __user *uptr,
        return pages;
 }
 
+struct iopt_pages *iopt_alloc_file_pages(struct file *file, unsigned long start,
+                                        unsigned long length, bool writable)
+
+{
+       struct iopt_pages *pages;
+       unsigned long start_down = ALIGN_DOWN(start, PAGE_SIZE);
+       unsigned long end;
+
+       if (length && check_add_overflow(start, length - 1, &end))
+               return ERR_PTR(-EOVERFLOW);
+
+       pages = iopt_alloc_pages(start - start_down, length, writable);
+       if (IS_ERR(pages))
+               return pages;
+       pages->file = get_file(file);
+       pages->start = start_down;
+       pages->type = IOPT_ADDRESS_FILE;
+       return pages;
+}
+
 void iopt_release_pages(struct kref *kref)
 {
        struct iopt_pages *pages = container_of(kref, struct iopt_pages, kref);
@@ -1352,6 +1373,8 @@ void iopt_release_pages(struct kref *kref)
        mutex_destroy(&pages->mutex);
        put_task_struct(pages->source_task);
        free_uid(pages->source_user);
+       if (pages->type == IOPT_ADDRESS_FILE)
+               fput(pages->file);
        kfree(pages);
 }
 
index 72010f7..41b1a01 100644 (file)
@@ -51,6 +51,7 @@ enum {
        IOMMUFD_CMD_HWPT_GET_DIRTY_BITMAP = 0x8c,
        IOMMUFD_CMD_HWPT_INVALIDATE = 0x8d,
        IOMMUFD_CMD_FAULT_QUEUE_ALLOC = 0x8e,
+       IOMMUFD_CMD_IOAS_MAP_FILE = 0x8f,
 };
 
 /**
@@ -213,6 +214,30 @@ struct iommu_ioas_map {
 };
 #define IOMMU_IOAS_MAP _IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_MAP)
 
+/**
+ * struct iommu_ioas_map_file - ioctl(IOMMU_IOAS_MAP_FILE)
+ * @size: sizeof(struct iommu_ioas_map_file)
+ * @flags: same as for iommu_ioas_map
+ * @ioas_id: same as for iommu_ioas_map
+ * @fd: the memfd to map
+ * @start: byte offset from start of file to map from
+ * @length: same as for iommu_ioas_map
+ * @iova: same as for iommu_ioas_map
+ *
+ * Set an IOVA mapping from a memfd file.  All other arguments and semantics
+ * match those of IOMMU_IOAS_MAP.
+ */
+struct iommu_ioas_map_file {
+       __u32 size;
+       __u32 flags;
+       __u32 ioas_id;
+       __s32 fd;
+       __aligned_u64 start;
+       __aligned_u64 length;
+       __aligned_u64 iova;
+};
+#define IOMMU_IOAS_MAP_FILE _IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_MAP_FILE)
+
 /**
  * struct iommu_ioas_copy - ioctl(IOMMU_IOAS_COPY)
  * @size: sizeof(struct iommu_ioas_copy)