diff --git a/fs/nfs/nfs42.h b/fs/nfs/nfs42.h
index 0fe5aacbcfdf..c3e9fcd54c72 100644
--- a/fs/nfs/nfs42.h
+++ b/fs/nfs/nfs42.h
@@ -20,6 +20,7 @@ int nfs42_proc_allocate(struct file *, loff_t, loff_t);
 ssize_t nfs42_proc_copy(struct file *, loff_t, struct file *, loff_t, size_t,
 			struct nl4_server *, nfs4_stateid *, bool);
 int nfs42_proc_deallocate(struct file *, loff_t, loff_t);
+int nfs42_proc_io_advise(struct file *, loff_t, u64, u32);
 loff_t nfs42_proc_llseek(struct file *, loff_t, int);
 int nfs42_proc_layoutstats_generic(struct nfs_server *,
 				   struct nfs42_layoutstat_data *);
diff --git a/fs/nfs/nfs42proc.c b/fs/nfs/nfs42proc.c
index ecb428512fe1..af40f705386a 100644
--- a/fs/nfs/nfs42proc.c
+++ b/fs/nfs/nfs42proc.c
@@ -172,6 +172,73 @@ int nfs42_proc_deallocate(struct file *filep, loff_t offset, loff_t len)
 	return err;
 }
 
+static loff_t _nfs42_proc_io_advise(struct file *filep,
+		struct nfs_lock_context *lock, loff_t offset,
+		u64 count, u32 hints)
+{
+	struct inode *inode = file_inode(filep);
+	struct nfs42_io_advise_args args = {
+		.fh	= NFS_FH(inode),
+		.offset	= offset,
+		.count	= count,
+		.hints	= hints,
+	};
+	struct nfs42_io_advise_res res;
+	struct rpc_message msg = {
+		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_IO_ADVISE],
+		.rpc_argp = &args,
+		.rpc_resp = &res,
+	};
+	struct nfs_server *server = NFS_SERVER(inode);
+	int status;
+
+	if (!nfs_server_capable(inode, NFS_CAP_IO_ADVISE))
+		return -ENOTSUPP;
+
+	status = nfs4_set_rw_stateid(&args.stateid, lock->open_context,
+			lock, FMODE_READ);
+	if (status) {
+		if (status == -EAGAIN)
+			status = -NFS4ERR_BAD_STATEID;
+		return status;
+	}
+
+	status = nfs4_call_sync(server->client, server, &msg,
+				&args.seq_args, &res.seq_res, 0);
+	if (status == -ENOTSUPP)
+		server->caps &= ~NFS_CAP_IO_ADVISE;
+	return status;
+}
+
+int nfs42_proc_io_advise(struct file *filep, loff_t offset, u64 count, u32 hints)
+{
+	struct nfs_server *server = NFS_SERVER(file_inode(filep));
+	struct nfs4_exception exception = { };
+	struct nfs_lock_context *lock;
+	loff_t err;
+
+	lock = nfs_get_lock_context(nfs_file_open_context(filep));
+	if (IS_ERR(lock))
+		return PTR_ERR(lock);
+
+	exception.inode = file_inode(filep);
+	exception.state = lock->open_context->state;
+
+	do {
+		err = _nfs42_proc_io_advise(filep, lock, offset, count, hints);
+		if (err >= 0)
+			break;
+		if (err == -ENOTSUPP) {
+			err = -EOPNOTSUPP;
+			break;
+		}
+		err = nfs4_handle_exception(server, err, &exception);
+	} while (exception.retry);
+
+	nfs_put_lock_context(lock);
+	return err;
+}
+
 static int handle_async_copy(struct nfs42_copy_res *res,
 			     struct nfs_server *dst_server,
 			     struct nfs_server *src_server,
diff --git a/fs/nfs/nfs42xdr.c b/fs/nfs/nfs42xdr.c
index d80ee88ca996..1e30729b0ed5 100644
--- a/fs/nfs/nfs42xdr.c
+++ b/fs/nfs/nfs42xdr.c
@@ -45,6 +45,14 @@
 #define encode_deallocate_maxsz		(op_encode_hdr_maxsz + \
 					 encode_fallocate_maxsz)
 #define decode_deallocate_maxsz		(op_decode_hdr_maxsz)
+#define nfs42_io_advise_bitmap_maxsz	2 /* bitmap length and hints mask */
+#define encode_io_advise_maxsz		(op_encode_hdr_maxsz + \
+					 encode_stateid_maxsz + \
+					 2 /* offset */ + \
+					 2 /* count */  + \
+					 nfs42_io_advise_bitmap_maxsz /* hints */)
+#define decode_io_advise_maxsz		(op_decode_hdr_maxsz + \
+					 nfs42_io_advise_bitmap_maxsz /* hints */)
 #define encode_read_plus_maxsz		(op_encode_hdr_maxsz + \
 					 encode_stateid_maxsz + 3)
 #define NFS42_READ_PLUS_DATA_SEGMENT_SIZE \
@@ -138,6 +146,14 @@
 					 decode_putfh_maxsz + \
 					 decode_deallocate_maxsz + \
 					 decode_getattr_maxsz)
+#define NFS4_enc_io_advise_sz		(compound_encode_hdr_maxsz + \
+					 encode_sequence_maxsz + \
+					 encode_putfh_maxsz + \
+					 encode_io_advise_maxsz)
+#define NFS4_dec_io_advise_sz		(compound_decode_hdr_maxsz + \
+					 decode_sequence_maxsz + \
+					 decode_putfh_maxsz + \
+					 decode_io_advise_maxsz)
 #define NFS4_enc_read_plus_sz		(compound_encode_hdr_maxsz + \
 					 encode_sequence_maxsz + \
 					 encode_putfh_maxsz + \
@@ -342,6 +358,17 @@ static void encode_deallocate(struct xdr_stream *xdr,
 	encode_fallocate(xdr, args);
 }
 
+static void encode_io_advise(struct xdr_stream *xdr,
+			const struct nfs42_io_advise_args *args,
+			struct compound_hdr *hdr)
+{
+	encode_op_hdr(xdr, OP_IO_ADVISE, decode_io_advise_maxsz, hdr);
+	encode_nfs4_stateid(xdr, &args->stateid);
+	encode_uint64(xdr, args->offset);
+	encode_uint64(xdr, args->count);
+	xdr_encode_bitmap4(xdr, &args->hints, 1);
+}
+
 static void encode_read_plus(struct xdr_stream *xdr,
 			     const struct nfs_pgio_args *args,
 			     struct compound_hdr *hdr)
@@ -764,6 +791,25 @@ static void nfs4_xdr_enc_deallocate(struct rpc_rqst *req,
 	encode_nops(&hdr);
 }
 
+/*
+ * Encode IO_ADVISE request
+ */
+static void nfs4_xdr_enc_io_advise(struct rpc_rqst *req,
+				   struct xdr_stream *xdr,
+				   const void *data)
+{
+	const struct nfs42_io_advise_args *args = data;
+	struct compound_hdr hdr = {
+		.minorversion = nfs4_xdr_minorversion(&args->seq_args),
+	};
+
+	encode_compound_hdr(xdr, req, &hdr);
+	encode_sequence(xdr, &args->seq_args, &hdr);
+	encode_putfh(xdr, args->fh, &hdr);
+	encode_io_advise(xdr, args, &hdr);
+	encode_nops(&hdr);
+}
+
 /*
  * Encode READ_PLUS request
  */
@@ -1034,6 +1080,19 @@ static int decode_deallocate(struct xdr_stream *xdr, struct nfs42_falloc_res *re
 	return decode_op_hdr(xdr, OP_DEALLOCATE);
 }
 
+static int decode_io_advise(struct xdr_stream *xdr, struct nfs42_io_advise_res *res)
+{
+	int status;
+
+	status = decode_op_hdr(xdr, OP_IO_ADVISE);
+	if (status)
+		return status;
+
+	if (unlikely(decode_bitmap4(xdr, &res->hints, 1) < 0))
+		return -EIO;
+	return 0;
+}
+
 struct read_plus_segment {
 	enum data_content4 type;
 	uint64_t offset;
@@ -1337,6 +1396,31 @@ static int nfs4_xdr_dec_deallocate(struct rpc_rqst *rqstp,
 	return status;
 }
 
+/*
+ * Decode IO_ADVISE response
+ */
+static int nfs4_xdr_dec_io_advise(struct rpc_rqst *rqstp,
+				  struct xdr_stream *xdr,
+				  void *data)
+{
+	struct nfs42_io_advise_res *res = data;
+	struct compound_hdr hdr;
+	int status;
+
+	status = decode_compound_hdr(xdr, &hdr);
+	if (status)
+		goto out;
+	status = decode_sequence(xdr, &res->seq_res, rqstp);
+	if (status)
+		goto out;
+	status = decode_putfh(xdr);
+	if (status)
+		goto out;
+	status = decode_io_advise(xdr, res);
+out:
+	return status;
+}
+
 /*
  * Decode READ_PLUS request
  */
diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
index 2563ed8580f3..5f6e4b5f1559 100644
--- a/fs/nfs/nfs4file.c
+++ b/fs/nfs/nfs4file.c
@@ -7,6 +7,7 @@
 #include <linux/fs.h>
 #include <linux/file.h>
 #include <linux/falloc.h>
+#include <linux/fadvise.h>
 #include <linux/mount.h>
 #include <linux/nfs_fs.h>
 #include <linux/nfs_ssc.h>
@@ -236,6 +237,41 @@ static long nfs42_fallocate(struct file *filep, int mode, loff_t offset, loff_t
 	return nfs42_proc_allocate(filep, offset, len);
 }
 
+static int nfs42_file_fadvise(struct file *filep, loff_t start, loff_t len, int advice)
+{
+	int ret;
+	unsigned int hints = 0;
+
+	switch (advice) {
+	case POSIX_FADV_NORMAL:
+		hints = NFS_IO_ADVISE4_NORMAL;
+		break;
+	case POSIX_FADV_RANDOM:
+		hints = NFS_IO_ADVISE4_RANDOM;
+		break;
+	case POSIX_FADV_SEQUENTIAL:
+		hints = NFS_IO_ADVISE4_SEQUENTIAL;
+		break;
+	case POSIX_FADV_WILLNEED:
+		hints = NFS_IO_ADVISE4_WILLNEED;
+		break;
+	case POSIX_FADV_DONTNEED:
+		hints = NFS_IO_ADVISE4_DONTNEED;
+		break;
+	case POSIX_FADV_NOREUSE:
+		hints = NFS_IO_ADVISE4_NOREUSE;
+		break;
+	default:
+		goto out;
+	}
+
+	ret = nfs42_proc_io_advise(filep, start, len, hints);
+	if (ret < 0)
+		return ret;
+out:
+	return generic_fadvise(filep, start, len, advice);
+}
+
 static loff_t nfs42_remap_file_range(struct file *src_file, loff_t src_off,
 		struct file *dst_file, loff_t dst_off, loff_t count,
 		unsigned int remap_flags)
@@ -462,6 +498,7 @@ const struct file_operations nfs4_file_operations = {
 	.copy_file_range = nfs4_copy_file_range,
 	.llseek		= nfs4_file_llseek,
 	.fallocate	= nfs42_fallocate,
+	.fadvise	= nfs42_file_fadvise,
 	.remap_file_range = nfs42_remap_file_range,
 #else
 	.llseek		= nfs_file_llseek,
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 40d749f29ed3..89b21b94f948 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -10529,6 +10529,7 @@ static const struct nfs4_minor_version_ops nfs_v4_2_minor_ops = {
 		| NFS_CAP_OFFLOAD_CANCEL
 		| NFS_CAP_COPY_NOTIFY
 		| NFS_CAP_DEALLOCATE
+		| NFS_CAP_IO_ADVISE
 		| NFS_CAP_SEEK
 		| NFS_CAP_LAYOUTSTATS
 		| NFS_CAP_CLONE
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index deec76cf5afe..962c5b023769 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -7698,6 +7698,7 @@ const struct rpc_procinfo nfs4_procedures[] = {
 	PROC42(SEEK,		enc_seek,		dec_seek),
 	PROC42(ALLOCATE,	enc_allocate,		dec_allocate),
 	PROC42(DEALLOCATE,	enc_deallocate,		dec_deallocate),
+	PROC42(IO_ADVISE,	enc_io_advise,		dec_io_advise),
 	PROC42(LAYOUTSTATS,	enc_layoutstats,	dec_layoutstats),
 	PROC42(CLONE,		enc_clone,		dec_clone),
 	PROC42(COPY,		enc_copy,		dec_copy),
diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h
index 8d04b6a5964c..a1d925b1f06b 100644
--- a/include/linux/nfs4.h
+++ b/include/linux/nfs4.h
@@ -557,6 +557,7 @@ enum {
 	NFSPROC4_CLNT_LISTXATTRS,
 	NFSPROC4_CLNT_REMOVEXATTR,
 	NFSPROC4_CLNT_READ_PLUS,
+	NFSPROC4_CLNT_IO_ADVISE,
 };
 
 /* nfs41 types */
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index ea2f7e6b1b0b..38eb0cd86979 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -270,6 +270,7 @@ struct nfs_server {
 #define NFS_CAP_LGOPEN		(1U << 5)
 #define NFS_CAP_CASE_INSENSITIVE	(1U << 6)
 #define NFS_CAP_CASE_PRESERVING	(1U << 7)
+#define NFS_CAP_IO_ADVISE	(1U << 13)
 #define NFS_CAP_POSIX_LOCK	(1U << 14)
 #define NFS_CAP_UIDGID_NOMAP	(1U << 15)
 #define NFS_CAP_STATEID_NFSV41	(1U << 16)
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index e86cf6642d21..3a52e5dc2fea 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -17,6 +17,18 @@
 
 #define NFS_BITMASK_SZ		3
 
+#define NFS_IO_ADVISE4_NORMAL			(1U << 0)
+#define NFS_IO_ADVISE4_SEQUENTIAL		(1U << 1)
+#define NFS_IO_ADVISE4_SEQUENTIAL_BACKWARDS	(1U << 2)
+#define NFS_IO_ADVISE4_RANDOM			(1U << 3)
+#define NFS_IO_ADVISE4_WILLNEED			(1U << 4)
+#define NFS_IO_ADVISE4_WILLNEED_OPPORTUNISTIC	(1U << 5)
+#define NFS_IO_ADVISE4_DONTNEED			(1U << 6)
+#define NFS_IO_ADVISE4_NOREUSE			(1U << 7)
+#define NFS_IO_ADVISE4_READ			(1U << 8)
+#define NFS_IO_ADVISE4_WRITE			(1U << 9)
+#define NFS_IO_ADVISE4_INIT_PROXIMITY		(1U << 10)
+
 struct nfs4_string {
 	unsigned int len;
 	char *data;
@@ -1449,6 +1461,22 @@ struct nfs42_falloc_res {
 	const struct nfs_server		*falloc_server;
 };
 
+struct nfs42_io_advise_args {
+	struct nfs4_sequence_args	seq_args;
+
+	struct nfs_fh			*fh;
+	nfs4_stateid			stateid;
+	loff_t				offset;
+	u64				count;
+	u32				hints;
+};
+
+struct nfs42_io_advise_res {
+	struct nfs4_sequence_res	seq_res;
+
+	u32				hints;
+};
+
 struct nfs42_copy_args {
 	struct nfs4_sequence_args	seq_args;
 
