cxl: Fix coredump generation when cxl_get_fd() is used
authorFrederic Barrat <fbarrat@linux.vnet.ibm.com>
Fri, 18 Nov 2016 12:00:31 +0000 (23:00 +1100)
committerMichael Ellerman <mpe@ellerman.id.au>
Fri, 18 Nov 2016 12:02:17 +0000 (23:02 +1100)
If a process dumps core while owning a cxl file descriptor obtained
from an AFU driver (e.g. cxlflash) through the cxl_get_fd() API, the
following error occurs:

  [  868.027591] Unable to handle kernel paging request for data at address ...
  [  868.027778] Faulting instruction address: 0xc00000000035edb0
  cpu 0x8c: Vector: 300 (Data Access) at [c000003c688275e0]
      pc: c00000000035edb0: elf_core_dump+0xd60/0x1300
      lr: c00000000035ed80: elf_core_dump+0xd30/0x1300
      sp: c000003c68827860
     msr: 9000000100009033
     dar: c
  dsisr: 40000000
   current = 0xc000003c68780000
   paca    = 0xc000000001b73200   softe: 0        irq_happened: 0x01
      pid   = 46725, comm = hxesurelock
  enter ? for help
  [c000003c68827a60c00000000036948c do_coredump+0xcec/0x11e0
  [c000003c68827c20c0000000000ce9e0 get_signal+0x540/0x7b0
  [c000003c68827d10c000000000017354 do_signal+0x54/0x2b0
  [c000003c68827e00c00000000001777c do_notify_resume+0xbc/0xd0
  [c000003c68827e30c000000000009838 ret_from_except_lite+0x64/0x68
  --- Exception: 300 (Data Access) at 00003fff98ad2918

The root cause is that the address_space structure for the file
doesn't define a 'host' member.

When cxl allocates a file descriptor, it's using the anonymous inode
to back the file, but allocates a private address_space for each
context. The private address_space allows to track memory allocation
for each context. cxl doesn't define the 'host' member of the address
space, i.e. the inode. We don't want to define it as the anonymous
inode, since there's no longer a 1-to-1 relation between address_space
and inode.

To fix it, instead of using the anonymous inode, we introduce a simple
pseudo filesystem so that cxl can allocate its own inodes. So we now
have one inode for each file and address_space. The pseudo filesystem
is only mounted on the first allocation of a file descriptor by
cxl_get_fd().

Tested with cxlflash.

Signed-off-by: Frederic Barrat <fbarrat@linux.vnet.ibm.com>
Reviewed-by: Matthew R. Ochs <mrochs@linux.vnet.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
drivers/misc/cxl/api.c
drivers/misc/cxl/context.c
drivers/misc/cxl/cxl.h
drivers/misc/cxl/file.c

index e2efc6489c6e1508fedc32b4716d53604b1bc2f8..1b35e33d24341e2013b4fe7aec78dca70f7cc2cd 100644 (file)
 
 #include <linux/pci.h>
 #include <linux/slab.h>
-#include <linux/anon_inodes.h>
 #include <linux/file.h>
 #include <misc/cxl.h>
-#include <linux/fs.h>
 #include <asm/pnv-pci.h>
 #include <linux/msi.h>
+#include <linux/module.h>
+#include <linux/mount.h>
 
 #include "cxl.h"
 
+/*
+ * Since we want to track memory mappings to be able to force-unmap
+ * when the AFU is no longer reachable, we need an inode. For devices
+ * opened through the cxl user API, this is not a problem, but a
+ * userland process can also get a cxl fd through the cxl_get_fd()
+ * API, which is used by the cxlflash driver.
+ *
+ * Therefore we implement our own simple pseudo-filesystem and inode
+ * allocator. We don't use the anonymous inode, as we need the
+ * meta-data associated with it (address_space) and it is shared by
+ * other drivers/processes, so it could lead to cxl unmapping VMAs
+ * from random processes.
+ */
+
+#define CXL_PSEUDO_FS_MAGIC    0x1697697f
+
+static int cxl_fs_cnt;
+static struct vfsmount *cxl_vfs_mount;
+
+static const struct dentry_operations cxl_fs_dops = {
+       .d_dname        = simple_dname,
+};
+
+static struct dentry *cxl_fs_mount(struct file_system_type *fs_type, int flags,
+                               const char *dev_name, void *data)
+{
+       return mount_pseudo(fs_type, "cxl:", NULL, &cxl_fs_dops,
+                       CXL_PSEUDO_FS_MAGIC);
+}
+
+static struct file_system_type cxl_fs_type = {
+       .name           = "cxl",
+       .owner          = THIS_MODULE,
+       .mount          = cxl_fs_mount,
+       .kill_sb        = kill_anon_super,
+};
+
+
+void cxl_release_mapping(struct cxl_context *ctx)
+{
+       if (ctx->kernelapi && ctx->mapping)
+               simple_release_fs(&cxl_vfs_mount, &cxl_fs_cnt);
+}
+
+static struct file *cxl_getfile(const char *name,
+                               const struct file_operations *fops,
+                               void *priv, int flags)
+{
+       struct qstr this;
+       struct path path;
+       struct file *file;
+       struct inode *inode = NULL;
+       int rc;
+
+       /* strongly inspired by anon_inode_getfile() */
+
+       if (fops->owner && !try_module_get(fops->owner))
+               return ERR_PTR(-ENOENT);
+
+       rc = simple_pin_fs(&cxl_fs_type, &cxl_vfs_mount, &cxl_fs_cnt);
+       if (rc < 0) {
+               pr_err("Cannot mount cxl pseudo filesystem: %d\n", rc);
+               file = ERR_PTR(rc);
+               goto err_module;
+       }
+
+       inode = alloc_anon_inode(cxl_vfs_mount->mnt_sb);
+       if (IS_ERR(inode)) {
+               file = ERR_CAST(inode);
+               goto err_fs;
+       }
+
+       file = ERR_PTR(-ENOMEM);
+       this.name = name;
+       this.len = strlen(name);
+       this.hash = 0;
+       path.dentry = d_alloc_pseudo(cxl_vfs_mount->mnt_sb, &this);
+       if (!path.dentry)
+               goto err_inode;
+
+       path.mnt = mntget(cxl_vfs_mount);
+       d_instantiate(path.dentry, inode);
+
+       file = alloc_file(&path, OPEN_FMODE(flags), fops);
+       if (IS_ERR(file))
+               goto err_dput;
+       file->f_flags = flags & (O_ACCMODE | O_NONBLOCK);
+       file->private_data = priv;
+
+       return file;
+
+err_dput:
+       path_put(&path);
+err_inode:
+       iput(inode);
+err_fs:
+       simple_release_fs(&cxl_vfs_mount, &cxl_fs_cnt);
+err_module:
+       module_put(fops->owner);
+       return file;
+}
+
 struct cxl_context *cxl_dev_context_init(struct pci_dev *dev)
 {
-       struct address_space *mapping;
        struct cxl_afu *afu;
        struct cxl_context  *ctx;
        int rc;
@@ -35,28 +136,13 @@ struct cxl_context *cxl_dev_context_init(struct pci_dev *dev)
 
        ctx->kernelapi = true;
 
-       /*
-        * Make our own address space since we won't have one from the
-        * filesystem like the user api has, and even if we do associate a file
-        * with this context we don't want to use the global anonymous inode's
-        * address space as that can invalidate unrelated users:
-        */
-       mapping = kmalloc(sizeof(struct address_space), GFP_KERNEL);
-       if (!mapping) {
-               rc = -ENOMEM;
-               goto err_ctx;
-       }
-       address_space_init_once(mapping);
-
        /* Make it a slave context.  We can promote it later? */
-       rc = cxl_context_init(ctx, afu, false, mapping);
+       rc = cxl_context_init(ctx, afu, false);
        if (rc)
-               goto err_mapping;
+               goto err_ctx;
 
        return ctx;
 
-err_mapping:
-       kfree(mapping);
 err_ctx:
        kfree(ctx);
        return ERR_PTR(rc);
@@ -337,6 +423,11 @@ struct file *cxl_get_fd(struct cxl_context *ctx, struct file_operations *fops,
 {
        struct file *file;
        int rc, flags, fdtmp;
+       char *name = NULL;
+
+       /* only allow one per context */
+       if (ctx->mapping)
+               return ERR_PTR(-EEXIST);
 
        flags = O_RDWR | O_CLOEXEC;
 
@@ -360,12 +451,13 @@ struct file *cxl_get_fd(struct cxl_context *ctx, struct file_operations *fops,
        } else /* use default ops */
                fops = (struct file_operations *)&afu_fops;
 
-       file = anon_inode_getfile("cxl", fops, ctx, flags);
+       name = kasprintf(GFP_KERNEL, "cxl:%d", ctx->pe);
+       file = cxl_getfile(name, fops, ctx, flags);
+       kfree(name);
        if (IS_ERR(file))
                goto err_fd;
 
-       file->f_mapping = ctx->mapping;
-
+       cxl_context_set_mapping(ctx, file->f_mapping);
        *fd = fdtmp;
        return file;
 
index 5e506c19108ad22da4a002957fd056711138b0f3..ff5e7e8cb1d151f32bc04e813b7ba05f531e04d2 100644 (file)
@@ -34,8 +34,7 @@ struct cxl_context *cxl_context_alloc(void)
 /*
  * Initialises a CXL context.
  */
-int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master,
-                    struct address_space *mapping)
+int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master)
 {
        int i;
 
@@ -44,7 +43,7 @@ int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master,
        ctx->master = master;
        ctx->pid = ctx->glpid = NULL; /* Set in start work ioctl */
        mutex_init(&ctx->mapping_lock);
-       ctx->mapping = mapping;
+       ctx->mapping = NULL;
 
        /*
         * Allocate the segment table before we put it in the IDR so that we
@@ -114,6 +113,14 @@ int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master,
        return 0;
 }
 
+void cxl_context_set_mapping(struct cxl_context *ctx,
+                       struct address_space *mapping)
+{
+       mutex_lock(&ctx->mapping_lock);
+       ctx->mapping = mapping;
+       mutex_unlock(&ctx->mapping_lock);
+}
+
 static int cxl_mmap_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
 {
        struct cxl_context *ctx = vma->vm_file->private_data;
@@ -300,8 +307,6 @@ static void reclaim_ctx(struct rcu_head *rcu)
        if (ctx->ff_page)
                __free_page(ctx->ff_page);
        ctx->sstp = NULL;
-       if (ctx->kernelapi)
-               kfree(ctx->mapping);
 
        kfree(ctx->irq_bitmap);
 
@@ -313,6 +318,8 @@ static void reclaim_ctx(struct rcu_head *rcu)
 
 void cxl_context_free(struct cxl_context *ctx)
 {
+       if (ctx->kernelapi && ctx->mapping)
+               cxl_release_mapping(ctx);
        mutex_lock(&ctx->afu->contexts_lock);
        idr_remove(&ctx->afu->contexts_idr, ctx->pe);
        mutex_unlock(&ctx->afu->contexts_lock);
index a144073593fa1e5170bba669d7ba467eb06ada5b..b24d76723fb0900b2588fa0a8f192ca9174ad199 100644 (file)
@@ -817,8 +817,9 @@ void cxl_dump_debug_buffer(void *addr, size_t size);
 void init_cxl_native(void);
 
 struct cxl_context *cxl_context_alloc(void);
-int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master,
-                    struct address_space *mapping);
+int cxl_context_init(struct cxl_context *ctx, struct cxl_afu *afu, bool master);
+void cxl_context_set_mapping(struct cxl_context *ctx,
+                       struct address_space *mapping);
 void cxl_context_free(struct cxl_context *ctx);
 int cxl_context_iomap(struct cxl_context *ctx, struct vm_area_struct *vma);
 unsigned int cxl_map_irq(struct cxl *adapter, irq_hw_number_t hwirq,
@@ -877,6 +878,7 @@ void cxl_native_err_irq_dump_regs(struct cxl *adapter);
 void cxl_stop_trace(struct cxl *cxl);
 int cxl_pci_vphb_add(struct cxl_afu *afu);
 void cxl_pci_vphb_remove(struct cxl_afu *afu);
+void cxl_release_mapping(struct cxl_context *ctx);
 
 extern struct pci_driver cxl_pci_driver;
 extern struct platform_driver cxl_of_driver;
index 77080cc5fa0aa4cdbc476729e4cdabcac8afae7b..859959f19f1072ff0a8de823b6ad063423dea93a 100644 (file)
@@ -86,9 +86,12 @@ static int __afu_open(struct inode *inode, struct file *file, bool master)
                goto err_put_afu;
        }
 
-       if ((rc = cxl_context_init(ctx, afu, master, inode->i_mapping)))
+       rc = cxl_context_init(ctx, afu, master);
+       if (rc)
                goto err_put_afu;
 
+       cxl_context_set_mapping(ctx, inode->i_mapping);
+
        pr_devel("afu_open pe: %i\n", ctx->pe);
        file->private_data = ctx;
        cxl_ctx_get();