diff mbox series

[v5,12/19] nfs/localio: move managing nfsd_open_local_fh symbol to nfs_common

Message ID 20240618201949.81977-13-snitzer@kernel.org (mailing list archive)
State New, archived
Headers show
Series nfs/nfsd: add support for localio | expand

Commit Message

Mike Snitzer June 18, 2024, 8:19 p.m. UTC
Get nfsd_open_local_fh and store it in rpc_client during client
creation, put the symbol during nfs_local_disable -- which is also
called during client destruction.

Eliminates the need for nfs_local_open_ctx and extra locking and
refcounting work in fs/nfs/localio.c

Also makes it so the reference to the nfsd_open_local_fh symbol is
managed by the nfs_common module instead of the nfs client modules.

Signed-off-by: Mike Snitzer <snitzer@kernel.org>
---
 fs/nfs/client.c            |  1 +
 fs/nfs/inode.c             |  1 -
 fs/nfs/internal.h          | 18 +++++---
 fs/nfs/localio.c           | 86 +++-----------------------------------
 fs/nfs_common/nfslocalio.c | 26 ++++++++++++
 include/linux/nfs.h        |  4 --
 include/linux/nfs_fs_sb.h  |  2 +
 include/linux/nfslocalio.h |  8 ++++
 8 files changed, 54 insertions(+), 92 deletions(-)

Comments

Jeff Layton June 18, 2024, 9:32 p.m. UTC | #1
On Tue, 2024-06-18 at 16:19 -0400, Mike Snitzer wrote:
> Get nfsd_open_local_fh and store it in rpc_client during client
> creation, put the symbol during nfs_local_disable -- which is also
> called during client destruction.
> 
> Eliminates the need for nfs_local_open_ctx and extra locking and
> refcounting work in fs/nfs/localio.c
> 
> Also makes it so the reference to the nfsd_open_local_fh symbol is
> managed by the nfs_common module instead of the nfs client modules.
> 
> Signed-off-by: Mike Snitzer <snitzer@kernel.org>
> ---
>  fs/nfs/client.c            |  1 +
>  fs/nfs/inode.c             |  1 -
>  fs/nfs/internal.h          | 18 +++++---
>  fs/nfs/localio.c           | 86 +++-----------------------------------
>  fs/nfs_common/nfslocalio.c | 26 ++++++++++++
>  include/linux/nfs.h        |  4 --
>  include/linux/nfs_fs_sb.h  |  2 +
>  include/linux/nfslocalio.h |  8 ++++
>  8 files changed, 54 insertions(+), 92 deletions(-)
> 
> diff --git a/fs/nfs/client.c b/fs/nfs/client.c
> index 7044b8b3b332..cbabcdf3d785 100644
> --- a/fs/nfs/client.c
> +++ b/fs/nfs/client.c
> @@ -171,6 +171,7 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
>  
>  	INIT_LIST_HEAD(&clp->cl_superblocks);
>  	clp->cl_rpcclient = clp->cl_rpcclient_localio = ERR_PTR(-EINVAL);
> +	clp->nfsd_open_local_fh = NULL;
>  
>  	clp->cl_flags = cl_init->init_flags;
>  	clp->cl_proto = cl_init->proto;
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 4f88b860494f..f9923cbf6058 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -2499,7 +2499,6 @@ static int __init init_nfs_fs(void)
>  	if (err)
>  		goto out1;
>  
> -	nfs_local_init();
>  	err = register_nfs_fs();
>  	if (err)
>  		goto out0;
> diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
> index fb2fb59e7ed0..d30a2e63063c 100644
> --- a/fs/nfs/internal.h
> +++ b/fs/nfs/internal.h
> @@ -464,15 +464,22 @@ nfs_init_localioclient(struct nfs_client *clp,
>  		goto out;
>  	clp->cl_rpcclient_localio = rpc_bind_new_program(clp->cl_rpcclient,
>  							 program, vers);
> +	if (IS_ERR(clp->cl_rpcclient_localio))
> +		goto out;
> +	/* No errors! Assume that localio is supported */
> +	clp->nfsd_open_local_fh = get_nfsd_open_local_fh();
> +	if (!clp->nfsd_open_local_fh) {
> +		rpc_shutdown_client(clp->cl_rpcclient_localio);
> +		clp->cl_rpcclient_localio = ERR_PTR(-EINVAL);
> +	}
>  out:
> -	dfprintk_rcu(CLIENT, "%s: server (%s) %s NFSv%u LOCALIO\n", __func__,
> -		rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_ADDR),
> -		(IS_ERR(clp->cl_rpcclient_localio) ?
> -		 "does not support" : "supports"), vers);
> +	dfprintk_rcu(CLIENT, "%s: server (%s) %s NFSv%u LOCALIO, nfsd_open_local_fh is %s.\n",
> +		__func__, rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_ADDR),
> +		(IS_ERR(clp->cl_rpcclient_localio) ? "does not support" : "supports"), vers,
> +		(clp->nfsd_open_local_fh ? "set" : "not set"));
>  }
>  
>  /* localio.c */
> -extern void nfs_local_init(void);
>  extern void nfs_local_disable(struct nfs_client *);
>  extern void nfs_local_probe(struct nfs_client *);
>  extern struct file *nfs_local_open_fh(struct nfs_client *, const struct cred *,
> @@ -489,7 +496,6 @@ extern int nfs_local_commit(struct file *, struct nfs_commit_data *,
>  extern bool nfs_server_is_local(const struct nfs_client *clp);
>  
>  #else
> -static inline void nfs_local_init(void) {}
>  static inline void nfs_local_disable(struct nfs_client *clp) {}
>  static inline void nfs_local_probe(struct nfs_client *clp) {}
>  static inline struct file *nfs_local_open_fh(struct nfs_client *clp,
> diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c
> index 54c41933173c..ddd17549812e 100644
> --- a/fs/nfs/localio.c
> +++ b/fs/nfs/localio.c
> @@ -29,26 +29,6 @@
>  
>  #define NFSDBG_FACILITY		NFSDBG_VFS
>  
> -extern int nfsd_open_local_fh(struct rpc_clnt *rpc_clnt,
> -			      const struct cred *cred,
> -			      const struct nfs_fh *nfs_fh, const fmode_t fmode,
> -			      struct file **pfilp);
> -/*
> - * The localio code needs to call into nfsd to do the filehandle -> struct path
> - * mapping, but cannot be statically linked, because that will make the nfs
> - * module depend on the nfsd module.
> - *
> - * Instead, do dynamic linking to the nfsd module. This way the nfs module
> - * will only hold a reference on nfsd when it's actually in use. This also
> - * allows some sanity checking, like giving up on localio if nfsd isn't loaded.
> - */
> -
> -struct nfs_local_open_ctx {
> -	spinlock_t lock;
> -	nfs_to_nfsd_open_t open_f;
> -	atomic_t refcount;
> -};
> -
>  struct nfs_local_kiocb {
>  	struct kiocb		kiocb;
>  	struct bio_vec		*bvec;
> @@ -135,8 +115,6 @@ nfs4errno(int errno)
>  	return NFS4ERR_SERVERFAULT;
>  }
>  
> -static struct nfs_local_open_ctx __local_open_ctx __read_mostly;
> -
>  static bool localio_enabled __read_mostly = true;
>  module_param(localio_enabled, bool, 0644);
>  
> @@ -151,65 +129,12 @@ bool nfs_server_is_local(const struct nfs_client *clp)
>  }
>  EXPORT_SYMBOL_GPL(nfs_server_is_local);
>  
> -void
> -nfs_local_init(void)
> -{
> -	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
> -
> -	ctx->open_f = NULL;
> -	spin_lock_init(&ctx->lock);
> -	atomic_set(&ctx->refcount, 0);
> -}
> -
> -static bool
> -nfs_local_get_lookup_ctx(void)
> -{
> -	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
> -	nfs_to_nfsd_open_t fn = NULL;
> -
> -	spin_lock(&ctx->lock);
> -	if (ctx->open_f == NULL) {
> -		spin_unlock(&ctx->lock);
> -
> -		fn = symbol_request(nfsd_open_local_fh);
> -		if (!fn)
> -			return false;
> -
> -		spin_lock(&ctx->lock);
> -		/* catch race */
> -		if (ctx->open_f == NULL) {
> -			ctx->open_f = fn;
> -			fn = NULL;
> -		}
> -	}
> -	atomic_inc(&ctx->refcount);
> -	spin_unlock(&ctx->lock);
> -	if (fn)
> -		symbol_put(nfsd_open_local_fh);
> -	return true;
> -}
> -
> -static void
> -nfs_local_put_lookup_ctx(void)
> -{
> -	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
> -	nfs_to_nfsd_open_t fn;
> -
> -	if (atomic_dec_and_lock(&ctx->refcount, &ctx->lock)) {
> -		fn = ctx->open_f;
> -		ctx->open_f = NULL;
> -		spin_unlock(&ctx->lock);
> -		if (fn)
> -			symbol_put(nfsd_open_local_fh);
> -	}
> -}
> -


It seems like the new nfs_common infrastructure should be added earlier
in the series so you don't need to rip out the code above.

>  /*
>   * nfs_local_enable - attempt to enable local i/o for an nfs_client
>   */
>  static void nfs_local_enable(struct nfs_client *clp)
>  {
> -	if (nfs_local_get_lookup_ctx()) {
> +	if (READ_ONCE(clp->nfsd_open_local_fh)) {
>  		set_bit(NFS_CS_LOCAL_IO, &clp->cl_flags);
>  		trace_nfs_local_enable(clp);
>  	}
> @@ -218,12 +143,12 @@ static void nfs_local_enable(struct nfs_client *clp)
>  /*
>   * nfs_local_disable - disable local i/o for an nfs_client
>   */
> -void
> -nfs_local_disable(struct nfs_client *clp)
> +void nfs_local_disable(struct nfs_client *clp)
>  {
>  	if (test_and_clear_bit(NFS_CS_LOCAL_IO, &clp->cl_flags)) {
>  		trace_nfs_local_disable(clp);
> -		nfs_local_put_lookup_ctx();
> +		put_nfsd_open_local_fh();
> +		clp->nfsd_open_local_fh = NULL;
>  		if (!IS_ERR(clp->cl_rpcclient_localio)) {
>  			rpc_shutdown_client(clp->cl_rpcclient_localio);
>  			clp->cl_rpcclient_localio = ERR_PTR(-EINVAL);
> @@ -312,14 +237,13 @@ struct file *
>  nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred,
>  		  struct nfs_fh *fh, const fmode_t mode)
>  {
> -	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
>  	struct file *filp;
>  	int status;
>  
>  	if (mode & ~(FMODE_READ | FMODE_WRITE))
>  		return ERR_PTR(-EINVAL);
>  
> -	status = ctx->open_f(clp->cl_rpcclient, cred, fh, mode, &filp);
> +	status = clp->nfsd_open_local_fh(clp->cl_rpcclient, cred, fh, mode, &filp);
>  	if (status < 0) {
>  		dprintk("%s: open local file failed error=%d\n",
>  				__func__, status);
> diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c
> index f214cc6754a1..c454c4100976 100644
> --- a/fs/nfs_common/nfslocalio.c
> +++ b/fs/nfs_common/nfslocalio.c
> @@ -40,3 +40,29 @@ bool nfsd_uuid_is_local(const uuid_t *uuid)
>  	return !uuid_is_null(nfsd_uuid);
>  }
>  EXPORT_SYMBOL_GPL(nfsd_uuid_is_local);
> +
> +/*
> + * The nfs localio code needs to call into nfsd to do the filehandle -> struct path
> + * mapping, but cannot be statically linked, because that will make the nfs module
> + * depend on the nfsd module.
> + *
> + * Instead, do dynamic linking to the nfsd module (via nfs_common module). The
> + * nfs_common module will only hold a reference on nfsd when localio is in use.
> + * This allows some sanity checking, like giving up on localio if nfsd isn't loaded.
> + */
> +
> +extern int nfsd_open_local_fh(struct rpc_clnt *rpc_clnt,
> +			const struct cred *cred, const struct nfs_fh *nfs_fh,
> +			const fmode_t fmode, struct file **pfilp);
> +
> +nfs_to_nfsd_open_t get_nfsd_open_local_fh(void)
> +{
> +	return symbol_request(nfsd_open_local_fh);
> +}
> +EXPORT_SYMBOL_GPL(get_nfsd_open_local_fh);
> +
> +void put_nfsd_open_local_fh(void)
> +{
> +	symbol_put(nfsd_open_local_fh);
> +}
> +EXPORT_SYMBOL_GPL(put_nfsd_open_local_fh);
> diff --git a/include/linux/nfs.h b/include/linux/nfs.h
> index 2dacfe9742c6..64ed672a0b34 100644
> --- a/include/linux/nfs.h
> +++ b/include/linux/nfs.h
> @@ -48,10 +48,6 @@ enum nfs3_stable_how {
>  	NFS_INVALID_STABLE_HOW = -1
>  };
>  
> -typedef int (*nfs_to_nfsd_open_t)(struct rpc_clnt *, const struct cred *,
> -				  const struct nfs_fh *, const fmode_t,
> -				  struct file **);
> -
>  #ifdef CONFIG_CRC32
>  /**
>   * nfs_fhandle_hash - calculate the crc32 hash for the filehandle
> diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
> index efcdb4d8e9de..f5760b05ec87 100644
> --- a/include/linux/nfs_fs_sb.h
> +++ b/include/linux/nfs_fs_sb.h
> @@ -8,6 +8,7 @@
>  #include <linux/wait.h>
>  #include <linux/nfs_xdr.h>
>  #include <linux/sunrpc/xprt.h>
> +#include <linux/nfslocalio.h>
>  
>  #include <linux/atomic.h>
>  #include <linux/refcount.h>
> @@ -131,6 +132,7 @@ struct nfs_client {
>  	struct timespec64	cl_nfssvc_boot;
>  	seqlock_t		cl_boot_lock;
>  	struct rpc_clnt *	cl_rpcclient_localio;	/* localio RPC client handle */
> +	nfs_to_nfsd_open_t	nfsd_open_local_fh;
>  };
>  
>  /*
> diff --git a/include/linux/nfslocalio.h b/include/linux/nfslocalio.h
> index d0bbacd0adcf..b8df1b9f248d 100644
> --- a/include/linux/nfslocalio.h
> +++ b/include/linux/nfslocalio.h
> @@ -7,6 +7,7 @@
>  
>  #include <linux/list.h>
>  #include <linux/uuid.h>
> +#include <linux/nfs.h>
>  
>  /*
>   * Global list of nfsd_uuid_t instances, add/remove
> @@ -26,4 +27,11 @@ typedef struct {
>  
>  bool nfsd_uuid_is_local(const uuid_t *uuid);
>  
> +typedef int (*nfs_to_nfsd_open_t)(struct rpc_clnt *, const struct cred *,
> +				  const struct nfs_fh *, const fmode_t,
> +				  struct file **);
> +
> +nfs_to_nfsd_open_t get_nfsd_open_local_fh(void);
> +void put_nfsd_open_local_fh(void);
> +
>  #endif  /* __LINUX_NFSLOCALIO_H */
diff mbox series

Patch

diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index 7044b8b3b332..cbabcdf3d785 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -171,6 +171,7 @@  struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
 
 	INIT_LIST_HEAD(&clp->cl_superblocks);
 	clp->cl_rpcclient = clp->cl_rpcclient_localio = ERR_PTR(-EINVAL);
+	clp->nfsd_open_local_fh = NULL;
 
 	clp->cl_flags = cl_init->init_flags;
 	clp->cl_proto = cl_init->proto;
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 4f88b860494f..f9923cbf6058 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -2499,7 +2499,6 @@  static int __init init_nfs_fs(void)
 	if (err)
 		goto out1;
 
-	nfs_local_init();
 	err = register_nfs_fs();
 	if (err)
 		goto out0;
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index fb2fb59e7ed0..d30a2e63063c 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -464,15 +464,22 @@  nfs_init_localioclient(struct nfs_client *clp,
 		goto out;
 	clp->cl_rpcclient_localio = rpc_bind_new_program(clp->cl_rpcclient,
 							 program, vers);
+	if (IS_ERR(clp->cl_rpcclient_localio))
+		goto out;
+	/* No errors! Assume that localio is supported */
+	clp->nfsd_open_local_fh = get_nfsd_open_local_fh();
+	if (!clp->nfsd_open_local_fh) {
+		rpc_shutdown_client(clp->cl_rpcclient_localio);
+		clp->cl_rpcclient_localio = ERR_PTR(-EINVAL);
+	}
 out:
-	dfprintk_rcu(CLIENT, "%s: server (%s) %s NFSv%u LOCALIO\n", __func__,
-		rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_ADDR),
-		(IS_ERR(clp->cl_rpcclient_localio) ?
-		 "does not support" : "supports"), vers);
+	dfprintk_rcu(CLIENT, "%s: server (%s) %s NFSv%u LOCALIO, nfsd_open_local_fh is %s.\n",
+		__func__, rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_ADDR),
+		(IS_ERR(clp->cl_rpcclient_localio) ? "does not support" : "supports"), vers,
+		(clp->nfsd_open_local_fh ? "set" : "not set"));
 }
 
 /* localio.c */
-extern void nfs_local_init(void);
 extern void nfs_local_disable(struct nfs_client *);
 extern void nfs_local_probe(struct nfs_client *);
 extern struct file *nfs_local_open_fh(struct nfs_client *, const struct cred *,
@@ -489,7 +496,6 @@  extern int nfs_local_commit(struct file *, struct nfs_commit_data *,
 extern bool nfs_server_is_local(const struct nfs_client *clp);
 
 #else
-static inline void nfs_local_init(void) {}
 static inline void nfs_local_disable(struct nfs_client *clp) {}
 static inline void nfs_local_probe(struct nfs_client *clp) {}
 static inline struct file *nfs_local_open_fh(struct nfs_client *clp,
diff --git a/fs/nfs/localio.c b/fs/nfs/localio.c
index 54c41933173c..ddd17549812e 100644
--- a/fs/nfs/localio.c
+++ b/fs/nfs/localio.c
@@ -29,26 +29,6 @@ 
 
 #define NFSDBG_FACILITY		NFSDBG_VFS
 
-extern int nfsd_open_local_fh(struct rpc_clnt *rpc_clnt,
-			      const struct cred *cred,
-			      const struct nfs_fh *nfs_fh, const fmode_t fmode,
-			      struct file **pfilp);
-/*
- * The localio code needs to call into nfsd to do the filehandle -> struct path
- * mapping, but cannot be statically linked, because that will make the nfs
- * module depend on the nfsd module.
- *
- * Instead, do dynamic linking to the nfsd module. This way the nfs module
- * will only hold a reference on nfsd when it's actually in use. This also
- * allows some sanity checking, like giving up on localio if nfsd isn't loaded.
- */
-
-struct nfs_local_open_ctx {
-	spinlock_t lock;
-	nfs_to_nfsd_open_t open_f;
-	atomic_t refcount;
-};
-
 struct nfs_local_kiocb {
 	struct kiocb		kiocb;
 	struct bio_vec		*bvec;
@@ -135,8 +115,6 @@  nfs4errno(int errno)
 	return NFS4ERR_SERVERFAULT;
 }
 
-static struct nfs_local_open_ctx __local_open_ctx __read_mostly;
-
 static bool localio_enabled __read_mostly = true;
 module_param(localio_enabled, bool, 0644);
 
@@ -151,65 +129,12 @@  bool nfs_server_is_local(const struct nfs_client *clp)
 }
 EXPORT_SYMBOL_GPL(nfs_server_is_local);
 
-void
-nfs_local_init(void)
-{
-	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
-
-	ctx->open_f = NULL;
-	spin_lock_init(&ctx->lock);
-	atomic_set(&ctx->refcount, 0);
-}
-
-static bool
-nfs_local_get_lookup_ctx(void)
-{
-	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
-	nfs_to_nfsd_open_t fn = NULL;
-
-	spin_lock(&ctx->lock);
-	if (ctx->open_f == NULL) {
-		spin_unlock(&ctx->lock);
-
-		fn = symbol_request(nfsd_open_local_fh);
-		if (!fn)
-			return false;
-
-		spin_lock(&ctx->lock);
-		/* catch race */
-		if (ctx->open_f == NULL) {
-			ctx->open_f = fn;
-			fn = NULL;
-		}
-	}
-	atomic_inc(&ctx->refcount);
-	spin_unlock(&ctx->lock);
-	if (fn)
-		symbol_put(nfsd_open_local_fh);
-	return true;
-}
-
-static void
-nfs_local_put_lookup_ctx(void)
-{
-	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
-	nfs_to_nfsd_open_t fn;
-
-	if (atomic_dec_and_lock(&ctx->refcount, &ctx->lock)) {
-		fn = ctx->open_f;
-		ctx->open_f = NULL;
-		spin_unlock(&ctx->lock);
-		if (fn)
-			symbol_put(nfsd_open_local_fh);
-	}
-}
-
 /*
  * nfs_local_enable - attempt to enable local i/o for an nfs_client
  */
 static void nfs_local_enable(struct nfs_client *clp)
 {
-	if (nfs_local_get_lookup_ctx()) {
+	if (READ_ONCE(clp->nfsd_open_local_fh)) {
 		set_bit(NFS_CS_LOCAL_IO, &clp->cl_flags);
 		trace_nfs_local_enable(clp);
 	}
@@ -218,12 +143,12 @@  static void nfs_local_enable(struct nfs_client *clp)
 /*
  * nfs_local_disable - disable local i/o for an nfs_client
  */
-void
-nfs_local_disable(struct nfs_client *clp)
+void nfs_local_disable(struct nfs_client *clp)
 {
 	if (test_and_clear_bit(NFS_CS_LOCAL_IO, &clp->cl_flags)) {
 		trace_nfs_local_disable(clp);
-		nfs_local_put_lookup_ctx();
+		put_nfsd_open_local_fh();
+		clp->nfsd_open_local_fh = NULL;
 		if (!IS_ERR(clp->cl_rpcclient_localio)) {
 			rpc_shutdown_client(clp->cl_rpcclient_localio);
 			clp->cl_rpcclient_localio = ERR_PTR(-EINVAL);
@@ -312,14 +237,13 @@  struct file *
 nfs_local_open_fh(struct nfs_client *clp, const struct cred *cred,
 		  struct nfs_fh *fh, const fmode_t mode)
 {
-	struct nfs_local_open_ctx *ctx = &__local_open_ctx;
 	struct file *filp;
 	int status;
 
 	if (mode & ~(FMODE_READ | FMODE_WRITE))
 		return ERR_PTR(-EINVAL);
 
-	status = ctx->open_f(clp->cl_rpcclient, cred, fh, mode, &filp);
+	status = clp->nfsd_open_local_fh(clp->cl_rpcclient, cred, fh, mode, &filp);
 	if (status < 0) {
 		dprintk("%s: open local file failed error=%d\n",
 				__func__, status);
diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c
index f214cc6754a1..c454c4100976 100644
--- a/fs/nfs_common/nfslocalio.c
+++ b/fs/nfs_common/nfslocalio.c
@@ -40,3 +40,29 @@  bool nfsd_uuid_is_local(const uuid_t *uuid)
 	return !uuid_is_null(nfsd_uuid);
 }
 EXPORT_SYMBOL_GPL(nfsd_uuid_is_local);
+
+/*
+ * The nfs localio code needs to call into nfsd to do the filehandle -> struct path
+ * mapping, but cannot be statically linked, because that will make the nfs module
+ * depend on the nfsd module.
+ *
+ * Instead, do dynamic linking to the nfsd module (via nfs_common module). The
+ * nfs_common module will only hold a reference on nfsd when localio is in use.
+ * This allows some sanity checking, like giving up on localio if nfsd isn't loaded.
+ */
+
+extern int nfsd_open_local_fh(struct rpc_clnt *rpc_clnt,
+			const struct cred *cred, const struct nfs_fh *nfs_fh,
+			const fmode_t fmode, struct file **pfilp);
+
+nfs_to_nfsd_open_t get_nfsd_open_local_fh(void)
+{
+	return symbol_request(nfsd_open_local_fh);
+}
+EXPORT_SYMBOL_GPL(get_nfsd_open_local_fh);
+
+void put_nfsd_open_local_fh(void)
+{
+	symbol_put(nfsd_open_local_fh);
+}
+EXPORT_SYMBOL_GPL(put_nfsd_open_local_fh);
diff --git a/include/linux/nfs.h b/include/linux/nfs.h
index 2dacfe9742c6..64ed672a0b34 100644
--- a/include/linux/nfs.h
+++ b/include/linux/nfs.h
@@ -48,10 +48,6 @@  enum nfs3_stable_how {
 	NFS_INVALID_STABLE_HOW = -1
 };
 
-typedef int (*nfs_to_nfsd_open_t)(struct rpc_clnt *, const struct cred *,
-				  const struct nfs_fh *, const fmode_t,
-				  struct file **);
-
 #ifdef CONFIG_CRC32
 /**
  * nfs_fhandle_hash - calculate the crc32 hash for the filehandle
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index efcdb4d8e9de..f5760b05ec87 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -8,6 +8,7 @@ 
 #include <linux/wait.h>
 #include <linux/nfs_xdr.h>
 #include <linux/sunrpc/xprt.h>
+#include <linux/nfslocalio.h>
 
 #include <linux/atomic.h>
 #include <linux/refcount.h>
@@ -131,6 +132,7 @@  struct nfs_client {
 	struct timespec64	cl_nfssvc_boot;
 	seqlock_t		cl_boot_lock;
 	struct rpc_clnt *	cl_rpcclient_localio;	/* localio RPC client handle */
+	nfs_to_nfsd_open_t	nfsd_open_local_fh;
 };
 
 /*
diff --git a/include/linux/nfslocalio.h b/include/linux/nfslocalio.h
index d0bbacd0adcf..b8df1b9f248d 100644
--- a/include/linux/nfslocalio.h
+++ b/include/linux/nfslocalio.h
@@ -7,6 +7,7 @@ 
 
 #include <linux/list.h>
 #include <linux/uuid.h>
+#include <linux/nfs.h>
 
 /*
  * Global list of nfsd_uuid_t instances, add/remove
@@ -26,4 +27,11 @@  typedef struct {
 
 bool nfsd_uuid_is_local(const uuid_t *uuid);
 
+typedef int (*nfs_to_nfsd_open_t)(struct rpc_clnt *, const struct cred *,
+				  const struct nfs_fh *, const fmode_t,
+				  struct file **);
+
+nfs_to_nfsd_open_t get_nfsd_open_local_fh(void);
+void put_nfsd_open_local_fh(void);
+
 #endif  /* __LINUX_NFSLOCALIO_H */