[RFC PATCH 3/4] nfs: make nfs_page pin-aware

Pranjal Shrivastava
2026-04-01T19:45:16Z

The migration to iov_iter_extract_pages() for Direct I/O introduces
page pinning (GUP) instead of standard page referencing. To handle this
correctly, nfs_page must track whether it holds a pin or a standard
reference.

Add a new flag, PG_PINNED, to struct nfs_page. Update the creation
path (nfs_page_create_from_page) to accept a 'pinned' boolean and
set the flag accordingly. If the page is pinned, we skip the standard
get_page() as the pin itself acts as a reference.

Update nfs_clear_request() to use unpin_user_page() instead of
put_page() when the PG_PINNED flag is set. This ensures that memory
remains safely locked for DMA and that kernel page accounting stays
consistent. Ensure subrequests inherit the pin status from their
parent request.

Signed-off-by: Pranjal Shrivastava <praan@google.com>
---
fs/nfs/direct.c | 4 ++--
fs/nfs/pagelist.c | 18 +++++++++++++-----
include/linux/nfs_page.h | 2 ++
3 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/fs/nfs/direct.c b/fs/nfs/direct.c
index 48d89716193a..c8429b430181 100644
--- a/fs/nfs/direct.c
+++ b/fs/nfs/direct.c
@@ -370,7 +370,7 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,
struct nfs_page *req;
unsigned int req_len = min_t(size_t, bytes, PAGE_SIZE - pgbase);
/* XXX do we need to do the eof zeroing found in async_filler? */
- req = nfs_page_create_from_page(dreq->ctx, pagevec[i],
+ req = nfs_page_create_from_page(dreq->ctx, pagevec[i], false,
pgbase, pos, req_len);
if (IS_ERR(req)) {
result = PTR_ERR(req);
@@ -898,7 +898,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,
struct nfs_page *req;
unsigned int req_len = min_t(size_t, bytes, PAGE_SIZE - pgbase);

- req = nfs_page_create_from_page(dreq->ctx, pagevec[i],
+ req = nfs_page_create_from_page(dreq->ctx, pagevec[i], false,
pgbase, pos, req_len);
if (IS_ERR(req)) {
result = PTR_ERR(req);
diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c
index a9373de891c9..72d3da0fb654 100644
--- a/fs/nfs/pagelist.c
+++ b/fs/nfs/pagelist.c
@@ -413,11 +413,14 @@ static void nfs_page_assign_folio(struct nfs_page *req, struct folio *folio)
}
}

-static void nfs_page_assign_page(struct nfs_page *req, struct page *page)
+static void nfs_page_assign_page(struct nfs_page *req, struct page *page, bool pinned)
{
if (page != NULL) {
req->wb_page = page;
- get_page(page);
+ if (pinned)
+ set_bit(PG_PINNED, &req->wb_flags);
+ else
+ get_page(page);
}
}

@@ -435,6 +438,7 @@ static void nfs_page_assign_page(struct nfs_page *req, struct page *page)
*/
struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx,
struct page *page,
+ bool pinned,
unsigned int pgbase, loff_t offset,
unsigned int count)
{
@@ -446,7 +450,7 @@ struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx,
ret = nfs_page_create(l_ctx, pgbase, offset >> PAGE_SHIFT,
offset_in_page(offset), count);
if (!IS_ERR(ret)) {
- nfs_page_assign_page(ret, page);
+ nfs_page_assign_page(ret, page, pinned);
nfs_page_group_init(ret, NULL);
}
nfs_put_lock_context(l_ctx);
@@ -500,7 +504,8 @@ nfs_create_subreq(struct nfs_page *req,
if (folio)
nfs_page_assign_folio(ret, folio);
else
- nfs_page_assign_page(ret, page);
+ nfs_page_assign_page(ret, page,
+ test_bit(PG_PINNED, &req->wb_flags));
/* find the last request */
for (last = req->wb_head;
last->wb_this_page != req->wb_head;
@@ -556,7 +561,10 @@ static void nfs_clear_request(struct nfs_page *req)
req->wb_folio = NULL;
clear_bit(PG_FOLIO, &req->wb_flags);
} else if (page != NULL) {
- put_page(page);
+ if (test_and_clear_bit(PG_PINNED, &req->wb_flags))
+ unpin_user_page(page);
+ else
+ put_page(page);
req->wb_page = NULL;
}
if (l_ctx != NULL) {
diff --git a/include/linux/nfs_page.h b/include/linux/nfs_page.h
index afe1d8f09d89..cae67540a2ae 100644
--- a/include/linux/nfs_page.h
+++ b/include/linux/nfs_page.h
@@ -37,6 +37,7 @@ enum {
PG_REMOVE, /* page group sync bit in write path */
PG_CONTENDED1, /* Is someone waiting for a lock? */
PG_CONTENDED2, /* Is someone waiting for a lock? */
+ PG_PINNED, /* page is pinned by GUP */
};

struct nfs_inode;
@@ -124,6 +125,7 @@ struct nfs_pageio_descriptor {

extern struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx,
struct page *page,
+ bool pinned,
unsigned int pgbase,
loff_t offset,
unsigned int count);
--
2.53.0.1185.g05d4b7b318-goog