2023-07-17 21:06:23

by Anna Schumaker

[permalink] [raw]
Subject: [PATCH v5 0/5] NFSv4.2: Various READ_PLUS fixes

From: Anna Schumaker <[email protected]>

These patches are a handul of fixes I've done recently to the READ_PLUS
code. This includes fixing some smatch warnings, fixing the XDR reply
length calculation, improving scratch buffer handling, and having
xdr_inline_decode() kmap pages if we detect that they're HIGHMEM so we
don't oops during READ_PLUS xdr decoding.

I also (optimistically) have a patch at the end to enable
CONFIG_READ_PLUS by default. My hope right now is that we can enable
READ_PLUS for Linux 6.6, and remove it entirely in 6.7 if all goes well.

Thoughts? Patch #4 probably needs the most review, and I'm open to other
approaches if something else makes more sense!

Thanks,
Anna

Anna Schumaker (5):
NFSv4.2: Fix READ_PLUS smatch warnings
NFSv4.2: Fix READ_PLUS size calculations
NFSv4.2: Rework scratch handling for READ_PLUS (again)
SUNRPC: kmap() the xdr pages during decode
NFS: Enable the READ_PLUS operation by default

fs/nfs/Kconfig | 6 ++----
fs/nfs/internal.h | 1 +
fs/nfs/nfs42.h | 1 +
fs/nfs/nfs42xdr.c | 17 +++++++++++------
fs/nfs/nfs4proc.c | 13 +------------
fs/nfs/read.c | 10 ++++++++++
include/linux/sunrpc/xdr.h | 2 ++
net/sunrpc/clnt.c | 1 +
net/sunrpc/svc.c | 2 ++
net/sunrpc/xdr.c | 17 ++++++++++++++++-
10 files changed, 47 insertions(+), 23 deletions(-)

--
2.41.0



2023-07-17 21:06:23

by Anna Schumaker

[permalink] [raw]
Subject: [PATCH v5 4/5] SUNRPC: kmap() the xdr pages during decode

From: Anna Schumaker <[email protected]>

If the pages are in HIGHMEM then we need to make sure they're mapped
before trying to read data off of them, otherwise we could end up with a
NULL pointer dereference.

The downside to this is that we need an extra cleanup step at the end of
decode to kunmap() the last page.

Reported-by: Krzysztof Kozlowski <[email protected]>
Signed-off-by: Anna Schumaker <[email protected]>
---
v5: Also clean up kmapped pages on the server
---
include/linux/sunrpc/xdr.h | 2 ++
net/sunrpc/clnt.c | 1 +
net/sunrpc/svc.c | 2 ++
net/sunrpc/xdr.c | 17 ++++++++++++++++-
4 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
index 6bffd10b7a33..60ddad33b49b 100644
--- a/include/linux/sunrpc/xdr.h
+++ b/include/linux/sunrpc/xdr.h
@@ -228,6 +228,7 @@ struct xdr_stream {
struct kvec *iov; /* pointer to the current kvec */
struct kvec scratch; /* Scratch buffer */
struct page **page_ptr; /* pointer to the current page */
+ void *page_kaddr; /* kmapped address of the current page */
unsigned int nwords; /* Remaining decode buffer length */

struct rpc_rqst *rqst; /* For debugging */
@@ -253,6 +254,7 @@ extern void xdr_truncate_decode(struct xdr_stream *xdr, size_t len);
extern int xdr_restrict_buflen(struct xdr_stream *xdr, int newbuflen);
extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
unsigned int base, unsigned int len);
+extern void xdr_stream_unmap_current_page(struct xdr_stream *xdr);
extern unsigned int xdr_stream_pos(const struct xdr_stream *xdr);
extern unsigned int xdr_page_pos(const struct xdr_stream *xdr);
extern void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf,
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index d7c697af3762..8080a1830ff3 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -2602,6 +2602,7 @@ call_decode(struct rpc_task *task)
case 0:
task->tk_action = rpc_exit_task;
task->tk_status = rpcauth_unwrap_resp(task, &xdr);
+ xdr_stream_unmap_current_page(&xdr);
return;
case -EAGAIN:
task->tk_status = 0;
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index 587811a002c9..5f32817579db 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -1370,6 +1370,8 @@ svc_process_common(struct svc_rqst *rqstp)
rc = process.dispatch(rqstp);
if (procp->pc_release)
procp->pc_release(rqstp);
+ xdr_stream_unmap_current_page(xdr);
+
if (!rc)
goto dropit;
if (rqstp->rq_auth_stat != rpc_auth_ok)
diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
index 94bddd1dd1d7..2b972954327f 100644
--- a/net/sunrpc/xdr.c
+++ b/net/sunrpc/xdr.c
@@ -1306,6 +1306,14 @@ static unsigned int xdr_set_tail_base(struct xdr_stream *xdr,
return xdr_set_iov(xdr, buf->tail, base, len);
}

+void xdr_stream_unmap_current_page(struct xdr_stream *xdr)
+{
+ if (xdr->page_kaddr) {
+ kunmap_local(xdr->page_kaddr);
+ xdr->page_kaddr = NULL;
+ }
+}
+
static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
unsigned int base, unsigned int len)
{
@@ -1323,12 +1331,18 @@ static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
if (len > maxlen)
len = maxlen;

+ xdr_stream_unmap_current_page(xdr);
xdr_stream_page_set_pos(xdr, base);
base += xdr->buf->page_base;

pgnr = base >> PAGE_SHIFT;
xdr->page_ptr = &xdr->buf->pages[pgnr];
- kaddr = page_address(*xdr->page_ptr);
+
+ if (PageHighMem(*xdr->page_ptr)) {
+ xdr->page_kaddr = kmap_local_page(*xdr->page_ptr);
+ kaddr = xdr->page_kaddr;
+ } else
+ kaddr = page_address(*xdr->page_ptr);

pgoff = base & ~PAGE_MASK;
xdr->p = (__be32*)(kaddr + pgoff);
@@ -1382,6 +1396,7 @@ void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p,
struct rpc_rqst *rqst)
{
xdr->buf = buf;
+ xdr->page_kaddr = NULL;
xdr_reset_scratch_buffer(xdr);
xdr->nwords = XDR_QUADLEN(buf->len);
if (xdr_set_iov(xdr, buf->head, 0, buf->len) == 0 &&
--
2.41.0


2023-07-17 21:06:23

by Anna Schumaker

[permalink] [raw]
Subject: [PATCH v5 3/5] NFSv4.2: Rework scratch handling for READ_PLUS (again)

From: Anna Schumaker <[email protected]>

I found that the read code might send multiple requests using the same
nfs_pgio_header, but nfs4_proc_read_setup() is only called once. This is
how we ended up occasionally double-freeing the scratch buffer, but also
means we set a NULL pointer but non-zero length to the xdr scratch
buffer. This results in an oops the first time decoding needs to copy
something to scratch, which frequently happens when decoding READ_PLUS
hole segments.

I fix this by moving scratch handling into the pageio read code. I
provide a function to allocate scratch space for decoding read replies,
and free the scratch buffer when the nfs_pgio_header is freed.

Fixes: fbd2a05f29a9 (NFSv4.2: Rework scratch handling for READ_PLUS)
Signed-off-by: Anna Schumaker <[email protected]>
---
fs/nfs/internal.h | 1 +
fs/nfs/nfs42.h | 1 +
fs/nfs/nfs42xdr.c | 2 +-
fs/nfs/nfs4proc.c | 13 +------------
fs/nfs/read.c | 10 ++++++++++
5 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 913c09806c7f..41abea340ad8 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -493,6 +493,7 @@ extern const struct nfs_pgio_completion_ops nfs_async_read_completion_ops;
extern void nfs_pageio_init_read(struct nfs_pageio_descriptor *pgio,
struct inode *inode, bool force_mds,
const struct nfs_pgio_completion_ops *compl_ops);
+extern bool nfs_read_alloc_scratch(struct nfs_pgio_header *hdr, size_t size);
extern int nfs_read_add_folio(struct nfs_pageio_descriptor *pgio,
struct nfs_open_context *ctx,
struct folio *folio);
diff --git a/fs/nfs/nfs42.h b/fs/nfs/nfs42.h
index 0fe5aacbcfdf..b59876b01a1e 100644
--- a/fs/nfs/nfs42.h
+++ b/fs/nfs/nfs42.h
@@ -13,6 +13,7 @@
* more? Need to consider not to pre-alloc too much for a compound.
*/
#define PNFS_LAYOUTSTATS_MAXDEV (4)
+#define READ_PLUS_SCRATCH_SIZE (16)

/* nfs4.2proc.c */
#ifdef CONFIG_NFS_V4_2
diff --git a/fs/nfs/nfs42xdr.c b/fs/nfs/nfs42xdr.c
index 78193f04d892..9e3ae53e2205 100644
--- a/fs/nfs/nfs42xdr.c
+++ b/fs/nfs/nfs42xdr.c
@@ -1433,7 +1433,7 @@ static int nfs4_xdr_dec_read_plus(struct rpc_rqst *rqstp,
struct compound_hdr hdr;
int status;

- xdr_set_scratch_buffer(xdr, res->scratch, sizeof(res->scratch));
+ xdr_set_scratch_buffer(xdr, res->scratch, READ_PLUS_SCRATCH_SIZE);

status = decode_compound_hdr(xdr, &hdr);
if (status)
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index e1a886b58354..c2bdbcef5c6c 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -5438,18 +5438,8 @@ static bool nfs4_read_plus_not_supported(struct rpc_task *task,
return false;
}

-static inline void nfs4_read_plus_scratch_free(struct nfs_pgio_header *hdr)
-{
- if (hdr->res.scratch) {
- kfree(hdr->res.scratch);
- hdr->res.scratch = NULL;
- }
-}
-
static int nfs4_read_done(struct rpc_task *task, struct nfs_pgio_header *hdr)
{
- nfs4_read_plus_scratch_free(hdr);
-
if (!nfs4_sequence_done(task, &hdr->res.seq_res))
return -EAGAIN;
if (nfs4_read_stateid_changed(task, &hdr->args))
@@ -5469,8 +5459,7 @@ static bool nfs42_read_plus_support(struct nfs_pgio_header *hdr,
/* Note: We don't use READ_PLUS with pNFS yet */
if (nfs_server_capable(hdr->inode, NFS_CAP_READ_PLUS) && !hdr->ds_clp) {
msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READ_PLUS];
- hdr->res.scratch = kmalloc(32, GFP_KERNEL);
- return hdr->res.scratch != NULL;
+ return nfs_read_alloc_scratch(hdr, READ_PLUS_SCRATCH_SIZE);
}
return false;
}
diff --git a/fs/nfs/read.c b/fs/nfs/read.c
index f71eeee67e20..7dc21a48e3e7 100644
--- a/fs/nfs/read.c
+++ b/fs/nfs/read.c
@@ -47,6 +47,8 @@ static struct nfs_pgio_header *nfs_readhdr_alloc(void)

static void nfs_readhdr_free(struct nfs_pgio_header *rhdr)
{
+ if (rhdr->res.scratch != NULL)
+ kfree(rhdr->res.scratch);
kmem_cache_free(nfs_rdata_cachep, rhdr);
}

@@ -108,6 +110,14 @@ void nfs_pageio_reset_read_mds(struct nfs_pageio_descriptor *pgio)
}
EXPORT_SYMBOL_GPL(nfs_pageio_reset_read_mds);

+bool nfs_read_alloc_scratch(struct nfs_pgio_header *hdr, size_t size)
+{
+ WARN_ON(hdr->res.scratch != NULL);
+ hdr->res.scratch = kmalloc(size, GFP_KERNEL);
+ return hdr->res.scratch != NULL;
+}
+EXPORT_SYMBOL_GPL(nfs_read_alloc_scratch);
+
static void nfs_readpage_release(struct nfs_page *req, int error)
{
struct folio *folio = nfs_page_to_folio(req);
--
2.41.0


2023-07-17 21:06:31

by Anna Schumaker

[permalink] [raw]
Subject: [PATCH v5 5/5] NFS: Enable the READ_PLUS operation by default

From: Anna Schumaker <[email protected]>

Now that the remaining issues have been worked out, including some
unexpected 32 bit issues, we can safely enable the feature by default.

Signed-off-by: Anna Schumaker <[email protected]>
---
fs/nfs/Kconfig | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig
index b6fc169be1b1..7df2503cef6c 100644
--- a/fs/nfs/Kconfig
+++ b/fs/nfs/Kconfig
@@ -209,8 +209,6 @@ config NFS_DISABLE_UDP_SUPPORT
config NFS_V4_2_READ_PLUS
bool "NFS: Enable support for the NFSv4.2 READ_PLUS operation"
depends on NFS_V4_2
- default n
+ default y
help
- This is intended for developers only. The READ_PLUS operation has
- been shown to have issues under specific conditions and should not
- be used in production.
+ Choose Y here to enable use of the NFS v4.2 READ_PLUS operation.
--
2.41.0


2023-07-18 14:07:02

by Chuck Lever

[permalink] [raw]
Subject: Re: [PATCH v5 4/5] SUNRPC: kmap() the xdr pages during decode

On Mon, Jul 17, 2023 at 04:52:38PM -0400, Anna Schumaker wrote:
> From: Anna Schumaker <[email protected]>
>
> If the pages are in HIGHMEM then we need to make sure they're mapped
> before trying to read data off of them, otherwise we could end up with a
> NULL pointer dereference.
>
> The downside to this is that we need an extra cleanup step at the end of
> decode to kunmap() the last page.
>
> Reported-by: Krzysztof Kozlowski <[email protected]>
> Signed-off-by: Anna Schumaker <[email protected]>
> ---
> v5: Also clean up kmapped pages on the server
> ---
> include/linux/sunrpc/xdr.h | 2 ++
> net/sunrpc/clnt.c | 1 +
> net/sunrpc/svc.c | 2 ++
> net/sunrpc/xdr.c | 17 ++++++++++++++++-
> 4 files changed, 21 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
> index 6bffd10b7a33..60ddad33b49b 100644
> --- a/include/linux/sunrpc/xdr.h
> +++ b/include/linux/sunrpc/xdr.h
> @@ -228,6 +228,7 @@ struct xdr_stream {
> struct kvec *iov; /* pointer to the current kvec */
> struct kvec scratch; /* Scratch buffer */
> struct page **page_ptr; /* pointer to the current page */
> + void *page_kaddr; /* kmapped address of the current page */
> unsigned int nwords; /* Remaining decode buffer length */
>
> struct rpc_rqst *rqst; /* For debugging */
> @@ -253,6 +254,7 @@ extern void xdr_truncate_decode(struct xdr_stream *xdr, size_t len);
> extern int xdr_restrict_buflen(struct xdr_stream *xdr, int newbuflen);
> extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
> unsigned int base, unsigned int len);
> +extern void xdr_stream_unmap_current_page(struct xdr_stream *xdr);

xdr_stream_unmap_current_page() is now effectively a matching
book-end for xdr_init_decode(). I think it would be more clear
(for human readers) if the name matched that organization rather
than being about the one specific thing it happens to be doing
now.

Something like xdr_finish_decode() ?


> extern unsigned int xdr_stream_pos(const struct xdr_stream *xdr);
> extern unsigned int xdr_page_pos(const struct xdr_stream *xdr);
> extern void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf,
> diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
> index d7c697af3762..8080a1830ff3 100644
> --- a/net/sunrpc/clnt.c
> +++ b/net/sunrpc/clnt.c
> @@ -2602,6 +2602,7 @@ call_decode(struct rpc_task *task)
> case 0:
> task->tk_action = rpc_exit_task;
> task->tk_status = rpcauth_unwrap_resp(task, &xdr);
> + xdr_stream_unmap_current_page(&xdr);
> return;
> case -EAGAIN:
> task->tk_status = 0;
> diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> index 587811a002c9..5f32817579db 100644
> --- a/net/sunrpc/svc.c
> +++ b/net/sunrpc/svc.c
> @@ -1370,6 +1370,8 @@ svc_process_common(struct svc_rqst *rqstp)
> rc = process.dispatch(rqstp);
> if (procp->pc_release)
> procp->pc_release(rqstp);
> + xdr_stream_unmap_current_page(xdr);
> +
> if (!rc)
> goto dropit;
> if (rqstp->rq_auth_stat != rpc_auth_ok)
> diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
> index 94bddd1dd1d7..2b972954327f 100644
> --- a/net/sunrpc/xdr.c
> +++ b/net/sunrpc/xdr.c
> @@ -1306,6 +1306,14 @@ static unsigned int xdr_set_tail_base(struct xdr_stream *xdr,
> return xdr_set_iov(xdr, buf->tail, base, len);
> }
>
> +void xdr_stream_unmap_current_page(struct xdr_stream *xdr)
> +{
> + if (xdr->page_kaddr) {
> + kunmap_local(xdr->page_kaddr);
> + xdr->page_kaddr = NULL;
> + }
> +}
> +
> static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
> unsigned int base, unsigned int len)
> {
> @@ -1323,12 +1331,18 @@ static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
> if (len > maxlen)
> len = maxlen;
>
> + xdr_stream_unmap_current_page(xdr);
> xdr_stream_page_set_pos(xdr, base);
> base += xdr->buf->page_base;
>
> pgnr = base >> PAGE_SHIFT;
> xdr->page_ptr = &xdr->buf->pages[pgnr];
> - kaddr = page_address(*xdr->page_ptr);
> +
> + if (PageHighMem(*xdr->page_ptr)) {
> + xdr->page_kaddr = kmap_local_page(*xdr->page_ptr);
> + kaddr = xdr->page_kaddr;
> + } else
> + kaddr = page_address(*xdr->page_ptr);
>
> pgoff = base & ~PAGE_MASK;
> xdr->p = (__be32*)(kaddr + pgoff);
> @@ -1382,6 +1396,7 @@ void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p,
> struct rpc_rqst *rqst)
> {
> xdr->buf = buf;
> + xdr->page_kaddr = NULL;
> xdr_reset_scratch_buffer(xdr);
> xdr->nwords = XDR_QUADLEN(buf->len);
> if (xdr_set_iov(xdr, buf->head, 0, buf->len) == 0 &&
> --
> 2.41.0
>

2023-07-18 14:36:16

by Anna Schumaker

[permalink] [raw]
Subject: Re: [PATCH v5 4/5] SUNRPC: kmap() the xdr pages during decode

On Tue, Jul 18, 2023 at 10:03 AM Chuck Lever <[email protected]> wrote:
>
> On Mon, Jul 17, 2023 at 04:52:38PM -0400, Anna Schumaker wrote:
> > From: Anna Schumaker <[email protected]>
> >
> > If the pages are in HIGHMEM then we need to make sure they're mapped
> > before trying to read data off of them, otherwise we could end up with a
> > NULL pointer dereference.
> >
> > The downside to this is that we need an extra cleanup step at the end of
> > decode to kunmap() the last page.
> >
> > Reported-by: Krzysztof Kozlowski <[email protected]>
> > Signed-off-by: Anna Schumaker <[email protected]>
> > ---
> > v5: Also clean up kmapped pages on the server
> > ---
> > include/linux/sunrpc/xdr.h | 2 ++
> > net/sunrpc/clnt.c | 1 +
> > net/sunrpc/svc.c | 2 ++
> > net/sunrpc/xdr.c | 17 ++++++++++++++++-
> > 4 files changed, 21 insertions(+), 1 deletion(-)
> >
> > diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h
> > index 6bffd10b7a33..60ddad33b49b 100644
> > --- a/include/linux/sunrpc/xdr.h
> > +++ b/include/linux/sunrpc/xdr.h
> > @@ -228,6 +228,7 @@ struct xdr_stream {
> > struct kvec *iov; /* pointer to the current kvec */
> > struct kvec scratch; /* Scratch buffer */
> > struct page **page_ptr; /* pointer to the current page */
> > + void *page_kaddr; /* kmapped address of the current page */
> > unsigned int nwords; /* Remaining decode buffer length */
> >
> > struct rpc_rqst *rqst; /* For debugging */
> > @@ -253,6 +254,7 @@ extern void xdr_truncate_decode(struct xdr_stream *xdr, size_t len);
> > extern int xdr_restrict_buflen(struct xdr_stream *xdr, int newbuflen);
> > extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
> > unsigned int base, unsigned int len);
> > +extern void xdr_stream_unmap_current_page(struct xdr_stream *xdr);
>
> xdr_stream_unmap_current_page() is now effectively a matching
> book-end for xdr_init_decode(). I think it would be more clear
> (for human readers) if the name matched that organization rather
> than being about the one specific thing it happens to be doing
> now.
>
> Something like xdr_finish_decode() ?

I like xdr_finish_decode() much better! I'll rename that

Thanks,
Anna

>
>
> > extern unsigned int xdr_stream_pos(const struct xdr_stream *xdr);
> > extern unsigned int xdr_page_pos(const struct xdr_stream *xdr);
> > extern void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf,
> > diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
> > index d7c697af3762..8080a1830ff3 100644
> > --- a/net/sunrpc/clnt.c
> > +++ b/net/sunrpc/clnt.c
> > @@ -2602,6 +2602,7 @@ call_decode(struct rpc_task *task)
> > case 0:
> > task->tk_action = rpc_exit_task;
> > task->tk_status = rpcauth_unwrap_resp(task, &xdr);
> > + xdr_stream_unmap_current_page(&xdr);
> > return;
> > case -EAGAIN:
> > task->tk_status = 0;
> > diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> > index 587811a002c9..5f32817579db 100644
> > --- a/net/sunrpc/svc.c
> > +++ b/net/sunrpc/svc.c
> > @@ -1370,6 +1370,8 @@ svc_process_common(struct svc_rqst *rqstp)
> > rc = process.dispatch(rqstp);
> > if (procp->pc_release)
> > procp->pc_release(rqstp);
> > + xdr_stream_unmap_current_page(xdr);
> > +
> > if (!rc)
> > goto dropit;
> > if (rqstp->rq_auth_stat != rpc_auth_ok)
> > diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c
> > index 94bddd1dd1d7..2b972954327f 100644
> > --- a/net/sunrpc/xdr.c
> > +++ b/net/sunrpc/xdr.c
> > @@ -1306,6 +1306,14 @@ static unsigned int xdr_set_tail_base(struct xdr_stream *xdr,
> > return xdr_set_iov(xdr, buf->tail, base, len);
> > }
> >
> > +void xdr_stream_unmap_current_page(struct xdr_stream *xdr)
> > +{
> > + if (xdr->page_kaddr) {
> > + kunmap_local(xdr->page_kaddr);
> > + xdr->page_kaddr = NULL;
> > + }
> > +}
> > +
> > static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
> > unsigned int base, unsigned int len)
> > {
> > @@ -1323,12 +1331,18 @@ static unsigned int xdr_set_page_base(struct xdr_stream *xdr,
> > if (len > maxlen)
> > len = maxlen;
> >
> > + xdr_stream_unmap_current_page(xdr);
> > xdr_stream_page_set_pos(xdr, base);
> > base += xdr->buf->page_base;
> >
> > pgnr = base >> PAGE_SHIFT;
> > xdr->page_ptr = &xdr->buf->pages[pgnr];
> > - kaddr = page_address(*xdr->page_ptr);
> > +
> > + if (PageHighMem(*xdr->page_ptr)) {
> > + xdr->page_kaddr = kmap_local_page(*xdr->page_ptr);
> > + kaddr = xdr->page_kaddr;
> > + } else
> > + kaddr = page_address(*xdr->page_ptr);
> >
> > pgoff = base & ~PAGE_MASK;
> > xdr->p = (__be32*)(kaddr + pgoff);
> > @@ -1382,6 +1396,7 @@ void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p,
> > struct rpc_rqst *rqst)
> > {
> > xdr->buf = buf;
> > + xdr->page_kaddr = NULL;
> > xdr_reset_scratch_buffer(xdr);
> > xdr->nwords = XDR_QUADLEN(buf->len);
> > if (xdr_set_iov(xdr, buf->head, 0, buf->len) == 0 &&
> > --
> > 2.41.0
> >