diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index ea635c17572a..95330c2ca3d8 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -615,7 +615,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
 	FUSE_ARGS(args);
 	struct fuse_forget_link *forget;
 	struct fuse_create_in inarg;
-	struct fuse_open_out outopen;
+	struct fuse_open_out *outopenp;
 	struct fuse_entry_out outentry;
 	struct fuse_inode *fi;
 	struct fuse_file *ff;
@@ -659,8 +659,10 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
 	args.out_numargs = 2;
 	args.out_args[0].size = sizeof(outentry);
 	args.out_args[0].value = &outentry;
-	args.out_args[1].size = sizeof(outopen);
-	args.out_args[1].value = &outopen;
+	/* Store outarg for fuse_finish_open() */
+	outopenp = &ff->args->open_outarg;
+	args.out_args[1].size = sizeof(*outopenp);
+	args.out_args[1].value = outopenp;
 
 	err = get_create_ext(&args, dir, entry, mode);
 	if (err)
@@ -676,9 +678,9 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
 	    fuse_invalid_attr(&outentry.attr))
 		goto out_free_ff;
 
-	ff->fh = outopen.fh;
+	ff->fh = outopenp->fh;
 	ff->nodeid = outentry.nodeid;
-	ff->open_flags = outopen.open_flags;
+	ff->open_flags = outopenp->open_flags;
 	inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
 			  &outentry.attr, ATTR_TIMEOUT(&outentry), 0);
 	if (!inode) {
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index eb226457c4bd..04be04b6b2af 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -50,12 +50,6 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
 	return fuse_simple_request(fm, &args);
 }
 
-struct fuse_release_args {
-	struct fuse_args args;
-	struct fuse_release_in inarg;
-	struct inode *inode;
-};
-
 struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
 {
 	struct fuse_file *ff;
@@ -66,9 +60,8 @@ struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
 
 	ff->fm = fm;
 	if (release) {
-		ff->release_args = kzalloc(sizeof(*ff->release_args),
-					   GFP_KERNEL_ACCOUNT);
-		if (!ff->release_args) {
+		ff->args = kzalloc(sizeof(*ff->args), GFP_KERNEL_ACCOUNT);
+		if (!ff->args) {
 			kfree(ff);
 			return NULL;
 		}
@@ -87,7 +80,7 @@ struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release)
 
 void fuse_file_free(struct fuse_file *ff)
 {
-	kfree(ff->release_args);
+	kfree(ff->args);
 	mutex_destroy(&ff->readdir.lock);
 	kfree(ff);
 }
@@ -110,7 +103,7 @@ static void fuse_release_end(struct fuse_mount *fm, struct fuse_args *args,
 static void fuse_file_put(struct fuse_file *ff, bool sync)
 {
 	if (refcount_dec_and_test(&ff->count)) {
-		struct fuse_release_args *ra = ff->release_args;
+		struct fuse_release_args *ra = &ff->args->release_args;
 		struct fuse_args *args = (ra ? &ra->args : NULL);
 
 		if (ra && ra->inode)
@@ -147,20 +140,21 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
 	/* Default for no-open */
 	ff->open_flags = FOPEN_KEEP_CACHE | (isdir ? FOPEN_CACHE_DIR : 0);
 	if (!noopen) {
-		struct fuse_open_out outarg;
+		/* Store outarg for fuse_finish_open() */
+		struct fuse_open_out *outargp = &ff->args->open_outarg;
 		int err;
 
-		err = fuse_send_open(fm, nodeid, open_flags, opcode, &outarg);
+		err = fuse_send_open(fm, nodeid, open_flags, opcode, outargp);
 		if (!err) {
-			ff->fh = outarg.fh;
-			ff->open_flags = outarg.open_flags;
+			ff->fh = outargp->fh;
+			ff->open_flags = outargp->open_flags;
 		} else if (err != -ENOSYS) {
 			fuse_file_free(ff);
 			return ERR_PTR(err);
 		} else {
 			/* No release needed */
-			kfree(ff->release_args);
-			ff->release_args = NULL;
+			kfree(ff->args);
+			ff->args = NULL;
 			if (isdir)
 				fc->no_opendir = 1;
 			else
@@ -299,7 +293,7 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
 				 unsigned int flags, int opcode, bool sync)
 {
 	struct fuse_conn *fc = ff->fm->fc;
-	struct fuse_release_args *ra = ff->release_args;
+	struct fuse_release_args *ra = &ff->args->release_args;
 
 	/* Inode is NULL on error path of fuse_create_open() */
 	if (likely(fi)) {
@@ -317,6 +311,8 @@ static void fuse_prepare_release(struct fuse_inode *fi, struct fuse_file *ff,
 	if (!ra)
 		return;
 
+	/* ff->args was used for open outarg */
+	memset(ff->args, 0, sizeof(*ff->args));
 	ra->inarg.fh = ff->fh;
 	ra->inarg.flags = flags;
 	ra->args.in_numargs = 1;
@@ -339,7 +335,7 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
 		       unsigned int open_flags, fl_owner_t id, bool isdir)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
-	struct fuse_release_args *ra = ff->release_args;
+	struct fuse_release_args *ra = &ff->args->release_args;
 	int opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
 
 	fuse_prepare_release(fi, ff, open_flags, opcode, false);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index fb9ef02cbf45..eea8f1ffc766 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -213,15 +213,15 @@ enum {
 
 struct fuse_conn;
 struct fuse_mount;
-struct fuse_release_args;
+union fuse_file_args;
 
 /** FUSE specific file data */
 struct fuse_file {
 	/** Fuse connection for this file */
 	struct fuse_mount *fm;
 
-	/* Argument space reserved for release */
-	struct fuse_release_args *release_args;
+	/* Argument space reserved for open/release */
+	union fuse_file_args *args;
 
 	/** Kernel file handle guaranteed to be unique */
 	u64 kh;
@@ -320,6 +320,19 @@ struct fuse_args_pages {
 	unsigned int num_pages;
 };
 
+struct fuse_release_args {
+	struct fuse_args args;
+	struct fuse_release_in inarg;
+	struct inode *inode;
+};
+
+union fuse_file_args {
+	/* Used during open() */
+	struct fuse_open_out open_outarg;
+	/* Used during release() */
+	struct fuse_release_args release_args;
+};
+
 #define FUSE_ARGS(args) struct fuse_args args = {}
 
 /** The request IO state (for asynchronous processing) */
diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
index acd0833ae873..48105f3c00f6 100644
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -138,9 +138,40 @@ void fuse_file_uncached_io_end(struct inode *inode)
 		wake_up(&fi->direct_io_waitq);
 }
 
+/*
+ * Open flags that are allowed in combination with FOPEN_PASSTHROUGH.
+ * A combination of FOPEN_PASSTHROUGH and FOPEN_DIRECT_IO means that read/write
+ * operations go directly to the server, but mmap is done on the backing file.
+ * FOPEN_PASSTHROUGH mode should not co-exist with any users of the fuse inode
+ * page cache, so FOPEN_KEEP_CACHE is a strange and undesired combination.
+ */
+#define FOPEN_PASSTHROUGH_MASK \
+	(FOPEN_PASSTHROUGH | FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES | \
+	 FOPEN_NOFLUSH)
+
+static int fuse_file_passthrough_open(struct file *file, struct inode *inode)
+{
+	struct fuse_file *ff = file->private_data;
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	int err;
+
+	/* Check allowed conditions for file open in passthrough mode */
+	if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough ||
+	    (ff->open_flags & ~FOPEN_PASSTHROUGH_MASK))
+		return -EINVAL;
+
+	/* TODO: implement backing file open */
+	return -EOPNOTSUPP;
+
+	/* First passthrough file open denies caching inode io mode */
+	err = fuse_file_uncached_io_start(inode);
+
+	return err;
+}
+
 /* Open flags to determine regular file io mode */
 #define FOPEN_IO_MODE_MASK \
-	(FOPEN_DIRECT_IO | FOPEN_CACHE_IO)
+	(FOPEN_DIRECT_IO | FOPEN_CACHE_IO | FOPEN_PASSTHROUGH)
 
 /* Request access to submit new io to inode via open file */
 int fuse_file_io_open(struct file *file, struct inode *inode)
@@ -162,7 +193,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	 * implement open.
 	 */
 	err = -EINVAL;
-	if (FUSE_IS_DAX(inode) || !ff->release_args) {
+	if (FUSE_IS_DAX(inode) || !ff->args) {
 		if (iomode_flags)
 			goto fail;
 		return 0;
@@ -170,7 +201,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 
 	/*
 	 * FOPEN_CACHE_IO is an internal flag that is set on file not open in
-	 * direct io mode and it cannot be set explicitly by the server.
+	 * direct io or passthrough mode and it cannot be set by the server.
 	 * This includes a file open with O_DIRECT, but server did not specify
 	 * FOPEN_DIRECT_IO. In this case, a later fcntl() could remove O_DIRECT,
 	 * so we put the inode in caching mode to prevent parallel dio.
@@ -178,7 +209,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	 */
 	if (ff->open_flags & FOPEN_CACHE_IO) {
 		goto fail;
-	} else if (!(ff->open_flags & FOPEN_DIRECT_IO)) {
+	} else if (!(ff->open_flags & FOPEN_IO_MODE_MASK)) {
 		ff->open_flags |= FOPEN_CACHE_IO;
 		ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES;
 	}
@@ -189,6 +220,8 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	err = 0;
 	if (ff->open_flags & FOPEN_CACHE_IO)
 		err = fuse_file_cached_io_start(inode);
+	else if (ff->open_flags & FOPEN_PASSTHROUGH)
+		err = fuse_file_passthrough_open(file, inode);
 	if (err)
 		goto fail;
 
@@ -206,17 +239,18 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
 	return -EIO;
 }
 
-/* Request access to submit new io to inode via mmap */
+/* Request access to submit cached io to inode via mmap */
 int fuse_file_io_mmap(struct fuse_file *ff, struct inode *inode)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
 	int err = 0;
 
 	/* There are no io modes if server does not implement open */
-	if (!ff->release_args)
+	if (!ff->args)
 		return 0;
 
-	if (WARN_ON(!ff->io_opened))
+	if (WARN_ON(ff->open_flags & FOPEN_PASSTHROUGH) ||
+	    WARN_ON(!ff->io_opened))
 		return -ENODEV;
 
 	spin_lock(&fi->lock);
