nfs: Modernize Direct I/O path
Subsystems (4)
v2 cover nfs: Modernize Direct I/O path
Modernize the NFS Direct I/O path as a preparatory step to enable PCI Peer-to-Peer DMA (P2PDMA) support. Following feedback on the initial RFC [1], the modernization and architectural changes are split into this standalone series.
Currently, NFS O_DIRECT relies on the legacy iov_iter_get_pages_alloc2() API which does not support the pinning requirements for P2P memory. The implementation moves NFS to the modern iov_iter_extract_pages() API and migrates NFS direct I/O away from pages to use folios.
Design
======
1. Pin-Awareness
Standard NFS requests use get_page() and put_page() for memory management. However, memory extracted via iov_iter_extract_pages() requires explicit pinning.
Introduce a PG_PINNED flag and a wb_nr_pinned count to struct nfs_page. This allows the request lifecycle to track ownership of physical pins and ensure that unpinning is performed only when the I/O is complete.
2. API Migration
Migrate the Direct I/O path to the modern iov_iter_extract_pages() API. This aligns NFS with the modern extraction model and serves as the foundation for passing ITER_ALLOW_P2PDMA in a follow-up series.
3. Extraction Helper and Folio Support
Introduce a new extraction helper in direct.c to group contiguous pages from the same folio into a single struct nfs_page. This effectively migrates the Direct I/O path from being page-based to being folio-based.
Note: zone_device_pages_have_same_pgmap() checks are intentionally omitted in the extraction helper since P2PDMA enablement will be introduced in a follow-up series.
Bisectability
=============
The series attempts to remain bisectable.
[Patches 1-2] Introduce pin-aware infrastructure and accounting. [Patch 3] Adds a centralized request release helper.
[Patch 4] Migrates the Direct I/O path to iov_iter_extract_pages(). [Patches 5-6] Implement the extraction helper and folio-based grouping. [Patch 7] Removes orphaned page-based helpers.
Testing
=======
This series has been tested with xfstests [2] on RDMA & TCP transports:
./check generic/091 generic/130 generic/139 generic/143 generic/154 \
generic/155 generic/183 generic/188 generic/190 generic/196 \
generic/198 generic/203 generic/214 generic/240 generic/263 \
generic/287 generic/290 generic/292 generic/330 generic/444 \
generic/450 generic/451 generic/586 generic/647 generic/708 \
generic/729 generic/760The following summary was tabulated via a custom script [3] (on github).
python3 display.py results/*/check.log
+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+ | testcase | rdma-sys-3 | rdma-sys-4.0 | rdma-sys-4.1 | rdma-sys-4.2 | tcp-sys-3 | tcp-sys-4.0 | tcp-sys-4.1 | tcp-sys-4.2 | +--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+ | generic/091 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/130 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/139 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/143 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/154 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/155 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/183 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/188 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/190 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/196 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/198 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/203 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/214 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/240 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/263 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/287 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/290 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/292 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/330 | skipped | skipped | skipped | pass | skipped | skipped | skipped | pass | | generic/444 | skipped | skipped | skipped | skipped | skipped | skipped | skipped | skipped | | generic/450 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/451 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/586 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/647 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/708 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/729 | pass | pass | pass | pass | pass | pass | pass | pass | | generic/760 | pass | pass | pass | pass | pass | pass | pass | pass | +--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+--------------+
Thanks,
Praan
[1] https://lore.kernel.org/all/[email protected]/
[2] https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
[v2]
- Fix data corruption in nfs_direct_extract_pages() by correctly calculating intra-page offsets using offset_in_page().
- Fix requested_bytes accounting in direct read/write paths to only increment after successful RPC scheduling.
- Add missing kernel-doc descriptions for the @pinned parameter in nfs_page_create_from_page() and nfs_page_create_from_folio().
- Rebase on fs-next/
[v1] https://lore.kernel.org/all/[email protected]/
Pranjal Shrivastava (7):
nfs: make nfs_page pin-aware
nfs: Track number of pinned pages in nfs_page
nfs: Introduce nfs_release_request_list helper
nfs: migrate direct I/O to iov_iter_extract_pages
nfs: introduce nfs_direct_extract_pages helper
nfs: Optimize direct I/O to use folios for requests
nfs: Cleanup the nfs_page_create_from_page helper
fs/nfs/direct.c | 165 +++++++++++++++++++++++---------------- fs/nfs/pagelist.c | 87 +++++++++++---------- fs/nfs/read.c | 2 +- fs/nfs/write.c | 2 +- include/linux/nfs_page.h | 12 ++- 5 files changed, 150 insertions(+), 118 deletions(-)
base-commit: 389bb4a76905771adfa86d21ee0b865247148e9d
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
PATCH v2 1/7 nfs: make nfs_page pin-aware
Modernizing the NFS Direct I/O path to use iov_iter_extract_pages() 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.
Introduce a new flag, PG_PINNED, to struct nfs_page. Update the creation path (nfs_page_create_from_page and nfs_page_create_from_folio) to accept a pinned bool and set the flag accordingly. If the page is pinned, we skip the existing reference increment (get_page/folio_get) as the pin itself acts as a reference.
Update nfs_clear_request() & nfs_direct_release_pages() to use unpin_user_page() or unpin_user_folio() instead of only refcount decrement (put_page) when PG_PINNED flag is set. Finally, ensure subrequests inherit the pinning status from their parent request.
--- fs/nfs/direct.c | 22 +++++++++++++++------- fs/nfs/pagelist.c | 38 ++++++++++++++++++++++++++++---------- fs/nfs/read.c | 2 +- fs/nfs/write.c | 2 +- include/linux/nfs_page.h | 3 +++ 5 files changed, 48 insertions(+), 19 deletions(-)
fs/nfs/direct.c +15 -7
@@ -165,11 +165,17 @@ int nfs_swap_rw(struct kiocb *iocb, struct iov_iter *iter)return 0 ; } -static void nfs_direct_release_pages (struct page **pages,unsigned int npages) +static void nfs_direct_release_pages (struct page **pages,unsigned int npages, +bool pinned) {unsigned int i; -for (i =0 ; i < npages; i++) -put_page (pages[i]); + +if (pinned) { +unpin_user_pages (pages, npages); + }else { +for (i =0 ; i < npages; i++) +put_page (pages[i]); + } } void nfs_init_cinfo_from_dreq(struct nfs_commit_info *cinfo,
@@ -371,7 +377,8 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,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], - pgbase, pos, req_len); + false, pgbase, pos, + req_len);if (IS_ERR (req)) { result =PTR_ERR (req);break ;
@@ -386,7 +393,7 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq, requested_bytes += req_len; pos += req_len; } -nfs_direct_release_pages (pagevec, npages); +nfs_direct_release_pages (pagevec, npages, false);kvfree (pagevec);if (result <0 )break ;
@@ -907,7 +914,8 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,unsigned int req_len =min_t (size_t, bytes,PAGE_SIZE - pgbase); req =nfs_page_create_from_page (dreq->ctx, pagevec[i], - pgbase, pos, req_len); + false, pgbase, pos, + req_len);if (IS_ERR (req)) { result =PTR_ERR (req);break ;
@@ -950,7 +958,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq, desc.pg_error =0 ; defer = true; } -nfs_direct_release_pages (pagevec, npages); +nfs_direct_release_pages (pagevec, npages, false);kvfree (pagevec);if (result <0 )break ;
fs/nfs/pagelist.c +28 -10
@@ -404,20 +404,26 @@ static struct nfs_page *nfs_page_create(struct nfs_lock_context *l_ctx,return req; } -static void nfs_page_assign_folio (struct nfs_page *req,struct folio *folio) +static void nfs_page_assign_folio (struct nfs_page *req,struct folio *folio,bool pinned) {if (folio !=NULL ) { req->wb_folio = folio; -folio_get (folio); +if (pinned) +set_bit (PG_PINNED , &req->wb_flags); +else +folio_get (folio);set_bit (PG_FOLIO , &req->wb_flags); } } -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); } }
@@ -425,6 +431,7 @@ static void nfs_page_assign_page(struct nfs_page *req, struct page *page)* nfs_page_create_from_page - Create an NFS read/write request. * @ctx: open context to use * @page: page to write +* @pinned: true if page is pinned * @pgbase: starting offset within the page for the write * @offset: file offset for the write * @count: number of bytes to read/write
@@ -435,6 +442,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 +454,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);
@@ -457,6 +465,7 @@ struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx,* nfs_page_create_from_folio - Create an NFS read/write request. * @ctx: open context to use * @folio: folio to write +* @pinned: true if folio is pinned * @offset: starting offset within the folio for the write * @count: number of bytes to read/write *
@@ -466,6 +475,7 @@ struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx,*/ struct nfs_page *nfs_page_create_from_folio (struct nfs_open_context *ctx,struct folio *folio, +bool pinned,unsigned int offset,unsigned int count) {
@@ -476,7 +486,7 @@ struct nfs_page *nfs_page_create_from_folio(struct nfs_open_context *ctx,return ERR_CAST (l_ctx); ret =nfs_page_create (l_ctx, offset, folio->index, offset, count);if (!IS_ERR (ret)) { -nfs_page_assign_folio (ret, folio); +nfs_page_assign_folio (ret, folio, pinned);nfs_page_group_init (ret,NULL ); }nfs_put_lock_context (l_ctx);
@@ -498,9 +508,11 @@ nfs_create_subreq(struct nfs_page *req, offset, count);if (!IS_ERR (ret)) {if (folio) -nfs_page_assign_folio (ret, folio); +nfs_page_assign_folio (ret, folio, +test_bit (PG_PINNED , &req->wb_flags));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;
@@ -552,11 +564,17 @@ static void nfs_clear_request(struct nfs_page *req)struct nfs_open_context *ctx;if (folio !=NULL ) { -folio_put (folio); +if (test_and_clear_bit (PG_PINNED , &req->wb_flags)) +unpin_user_folio (folio,1 ); +else +folio_put (folio); 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 ) {
fs/nfs/read.c +1 -1
@@ -324,7 +324,7 @@ int nfs_read_add_folio(struct nfs_pageio_descriptor *pgio, aligned_len = min_t(unsigned int, ALIGN(len, rsize), fsize); - new =nfs_page_create_from_folio (ctx, folio,0 , aligned_len); + new =nfs_page_create_from_folio (ctx, folio, false,0 , aligned_len);if (IS_ERR (new)) { error =PTR_ERR (new);if (nfs_netfs_folio_unlock (folio))
fs/nfs/write.c +1 -1
@@ -1086,7 +1086,7 @@ static struct nfs_page *nfs_setup_write_request(struct nfs_open_context *ctx, req =nfs_try_to_update_request (folio, offset, bytes);if (req !=NULL ) goto out; - req =nfs_page_create_from_folio (ctx, folio, offset, bytes); + req =nfs_page_create_from_folio (ctx, folio, false, offset, bytes);if (IS_ERR (req)) goto out;nfs_inode_add_request (req);
include/linux/nfs_page.h +3 -0
@@ -38,6 +38,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 ;
@@ -125,11 +126,13 @@ 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);extern struct nfs_page *nfs_page_create_from_folio (struct nfs_open_context *ctx,struct folio *folio, +bool pinned,unsigned int offset,unsigned int count);extern void nfs_release_request (struct nfs_page *);
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
PATCH v2 2/7 nfs: Track number of pinned pages in nfs_page
In reply to Pranjal ShrivastavaTrack the number of pinned pages in nfs_page to handle unpinning correctly, ensuring that only primary requests perform the final unpinning operation, preventing subrequests from incorrectly performing unpinning on behalf of their parent requests.
Add wb_nr_pinned to struct nfs_page to store the count of pinned pages owned by the request. Update request creation and cleanup helpers to initialize and use wb_nr_pinned for primary requests. Use the nfs_page_array_len() helper to calculate the number of pages spanned by a request's offset and length.
--- fs/nfs/pagelist.c | 9 +++++++-- include/linux/nfs_page.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-)
fs/nfs/pagelist.c +7 -2
@@ -455,6 +455,8 @@ struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx, offset_in_page(offset), count);if (!IS_ERR (ret)) {nfs_page_assign_page (ret, page, pinned); +if (pinned) + ret->wb_nr_pinned =1 ;nfs_page_group_init (ret,NULL ); }nfs_put_lock_context (l_ctx);
@@ -487,6 +489,9 @@ struct nfs_page *nfs_page_create_from_folio(struct nfs_open_context *ctx, ret =nfs_page_create (l_ctx, offset, folio->index, offset, count);if (!IS_ERR (ret)) {nfs_page_assign_folio (ret, folio, pinned); +if (pinned) + ret->wb_nr_pinned =nfs_page_array_len (offset_in_page (offset), + count);nfs_page_group_init (ret,NULL ); }nfs_put_lock_context (l_ctx);
@@ -565,14 +570,14 @@ static void nfs_clear_request(struct nfs_page *req)if (folio !=NULL ) {if (test_and_clear_bit (PG_PINNED , &req->wb_flags)) -unpin_user_folio (folio,1 ); +unpin_user_folio (folio, req->wb_nr_pinned);else folio_put (folio); req->wb_folio =NULL ;clear_bit (PG_FOLIO , &req->wb_flags); }else if (page !=NULL ) {if (test_and_clear_bit (PG_PINNED , &req->wb_flags)) -unpin_user_page (page); +unpin_user_pages (&page, req->wb_nr_pinned);else put_page (page); req->wb_page =NULL ;
include/linux/nfs_page.h +1 -0
@@ -59,6 +59,7 @@ struct nfs_page {struct nfs_page *wb_this_page;/* list of reqs for this page */ struct nfs_page *wb_head;/* head pointer for req list */ unsigned short wb_nio;/* Number of I/O attempts */ +unsigned int wb_nr_pinned;/* Number of pinned pages */ };struct nfs_pgio_mirror ;
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
PATCH v2 3/7 nfs: Introduce nfs_release_request_list helper
In reply to Pranjal ShrivastavaIntroduce a centralized helper, nfs_release_request_list, to handle the bulk release of nfs_page requests from a list.
This serves as a preparatory step for two upcoming improvements:
1. Pin-Aware Cleanup: As we migrate to iov_iter_extract_* API, requests will hold pins (GUP) instead of standard references. The helper ensures that the correct unpinning logic gets applied consistently across all requests in a list.
2. Folio Support: In subsequent patches where nfs_page structures will cover multi-page folios, this helper provides a clean infrastructure to unlock these larger units of I/O in bulk during completion, similat to the pattern in bio_release_pages.
Additionally, refactor nfs_read_sync_pgio_error() to utilize this new helper.
--- fs/nfs/direct.c | 8 +------- fs/nfs/pagelist.c | 18 ++++++++++++++++++ include/linux/nfs_page.h | 4 ++-- 3 files changed, 21 insertions(+), 9 deletions(-)
fs/nfs/direct.c +1 -7
@@ -314,13 +314,7 @@ static void nfs_direct_read_completion(struct nfs_pgio_header *hdr)static void nfs_read_sync_pgio_error (struct list_head *head,int error) { -struct nfs_page *req; - -while (!list_empty (head)) { - req =nfs_list_entry (head->next); -nfs_list_remove_request (req); -nfs_release_request (req); - } +nfs_release_request_list (head); }static void nfs_direct_pgio_init (struct nfs_pgio_header *hdr)
fs/nfs/pagelist.c +18 -0
@@ -622,6 +622,24 @@ void nfs_release_request(struct nfs_page *req) }EXPORT_SYMBOL_GPL (nfs_release_request); +/* +* nfs_release_request_list - Release a list of NFS read/write requests +* @head: list of requests to release +* +* Removes each request from the list and drops it's refcount. +*/ +void nfs_release_request_list (struct list_head *head) +{ +struct nfs_page *req; + +while (!list_empty (head)) { + req =nfs_list_entry (head->next); +nfs_list_remove_request (req); +nfs_release_request (req); + } +} +EXPORT_SYMBOL_GPL (nfs_release_request_list); +/* * nfs_generic_pg_test - determine if requests can be coalesced * @desc: pointer to descriptor
include/linux/nfs_page.h +2 -2
@@ -136,8 +136,8 @@ extern struct nfs_page *nfs_page_create_from_folio(struct nfs_open_context *ctx,bool pinned, unsigned int offset, unsigned int count); -extern void nfs_release_request (struct nfs_page *); - +extern void nfs_release_request (struct nfs_page *req); +extern void nfs_release_request_list (struct list_head *head); extern void nfs_pageio_init(struct nfs_pageio_descriptor *desc, struct inode *inode,
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
PATCH v2 4/7 nfs: migrate direct I/O to iov_iter_extract_pages
In reply to Pranjal ShrivastavaMigrate the NFS Direct I/O path away from the legacy iov_iter_get_pages_alloc2() API to the modern iov_iter_extract_pages API. The transition aligns NFS with the modern VFS extraction model and serves as a preparatory step for supporting requirements such as page pinning via GUP for DMA.
The migration fixes a bug in the Direct I/O loop where pages were being unpinned immediately after request creation. With the new extraction model, pins are held until the I/O is complete. Manual release in the loop is correspondingly updated to only clean up failed pages.
--- fs/nfs/direct.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-)
fs/nfs/direct.c +15 -11
@@ -354,16 +354,17 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,inode_dio_begin (inode);while (iov_iter_count (iter)) { -struct page **pagevec; +struct page **pagevec =NULL ;size_t bytes;size_t pgbase;unsigned npages, i; +bool pinned =iov_iter_extract_will_pin (iter); - result =iov_iter_get_pages_alloc2 (iter, &pagevec, - rsize, &pgbase); + result =iov_iter_extract_pages (iter, &pagevec, + rsize, ~0U ,0 , &pgbase);if (result <0 )break ; - + bytes = result; npages = (result + pgbase +PAGE_SIZE -1 ) /PAGE_SIZE ;for (i =0 ; i < npages; i++) {
@@ -371,7 +372,7 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,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], - false, pgbase, pos, + pinned, pgbase, pos, req_len);if (IS_ERR (req)) { result =PTR_ERR (req);
@@ -387,7 +388,8 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq, requested_bytes += req_len; pos += req_len; } -nfs_direct_release_pages (pagevec, npages, false); +if (i < npages) +nfs_direct_release_pages (pagevec + i, npages - i, pinned);kvfree (pagevec);if (result <0 )break ;
@@ -891,13 +893,14 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,NFS_I (inode)->write_io +=iov_iter_count (iter);while (iov_iter_count (iter)) { -struct page **pagevec; +struct page **pagevec =NULL ;size_t bytes;size_t pgbase;unsigned npages, i; +bool pinned =iov_iter_extract_will_pin (iter); - result =iov_iter_get_pages_alloc2 (iter, &pagevec, - wsize, &pgbase); + result =iov_iter_extract_pages (iter, &pagevec, + wsize, ~0U ,0 , &pgbase);if (result <0 )break ;
@@ -908,7 +911,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,unsigned int req_len =min_t (size_t, bytes,PAGE_SIZE - pgbase); req =nfs_page_create_from_page (dreq->ctx, pagevec[i], - false, pgbase, pos, + pinned, pgbase, pos, req_len);if (IS_ERR (req)) { result =PTR_ERR (req);
@@ -952,7 +955,8 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq, desc.pg_error =0 ; defer = true; } -nfs_direct_release_pages (pagevec, npages, false); +if (i < npages) +nfs_direct_release_pages (pagevec + i, npages - i, pinned);kvfree (pagevec);if (result <0 )break ;
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
PATCH v2 5/7 nfs: introduce nfs_direct_extract_pages helper
In reply to Pranjal ShrivastavaIntroduce nfs_direct_extract_pages() in direct.c to centralize page extraction and request creation for the Direct I/O path. The helper manages extraction from the iters and builds a list of nfs_page requests
Refactor nfs_direct_read_schedule_iovec() and nfs_direct_write_schedule_iovec() to utilize the new helper, unifying the extraction logic on both paths.
--- fs/nfs/direct.c | 122 +++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 63 deletions(-)
fs/nfs/direct.c +59 -63
@@ -178,6 +178,50 @@ static void nfs_direct_release_pages(struct page **pages, unsigned int npages, } } +static ssize_t nfs_direct_extract_pages (struct nfs_direct_req *dreq, +struct iov_iter *iter, +size_t size,loff_t *pos, +struct list_head *list) +{ +bool pinned =iov_iter_extract_will_pin (iter); +struct page **pagevec =NULL ; +ssize_t result, bytes =0 ; +unsigned int npages, i; +size_t pgbase; + + result =iov_iter_extract_pages (iter, &pagevec, size, ~0U ,0 , &pgbase); +if (result <=0 ) +return result; + + npages = (result + pgbase +PAGE_SIZE -1 ) >>PAGE_SHIFT ; +for (i =0 ; i < npages; i++) { +struct nfs_page *req; +unsigned int req_len =min_t (size_t, result - bytes,PAGE_SIZE - pgbase); + + req =nfs_page_create_from_page (dreq->ctx, pagevec[i], + pinned, pgbase, *pos, + req_len); +if (IS_ERR (req)) { +if (!bytes) + bytes =PTR_ERR (req); +break ; + } + +list_add_tail (&req->wb_list, list); + pgbase =0 ; + bytes += req_len; + *pos += req_len; + } + +if (i < npages) { +iov_iter_revert (iter, result - bytes); +nfs_direct_release_pages (pagevec + i, npages - i, pinned); + } + +kvfree (pagevec); +return bytes; +} +void nfs_init_cinfo_from_dreq (struct nfs_commit_info *cinfo,struct nfs_direct_req *dreq) {
@@ -346,6 +390,7 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,ssize_t result = -EINVAL ;size_t requested_bytes =0 ;size_t rsize =max_t (size_t,NFS_SERVER (inode)->rsize,PAGE_SIZE ); +LIST_HEAD (nfs_page_list);nfs_pageio_init_read (&desc, dreq->inode, false, &nfs_direct_read_completion_ops);
@@ -354,43 +399,22 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,inode_dio_begin (inode);while (iov_iter_count (iter)) { -struct page **pagevec =NULL ; -size_t bytes; -size_t pgbase; -unsigned npages, i; -bool pinned =iov_iter_extract_will_pin (iter); - - result =iov_iter_extract_pages (iter, &pagevec, - rsize, ~0U ,0 , &pgbase); + result =nfs_direct_extract_pages (dreq, iter, rsize, &pos, &nfs_page_list);if (result <0 )break ; - bytes = result; - npages = (result + pgbase +PAGE_SIZE -1 ) /PAGE_SIZE ; -for (i =0 ; i < npages; i++) { -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], - pinned, pgbase, pos, - req_len); -if (IS_ERR (req)) { - result =PTR_ERR (req); -break ; - } + requested_bytes += result; +while (!list_empty (&nfs_page_list)) { +struct nfs_page *req =nfs_list_entry (nfs_page_list.next); + +nfs_list_remove_request (req);if (!nfs_pageio_add_request (&desc, req)) { result = desc.pg_error;nfs_release_request (req); +nfs_release_request_list (&nfs_page_list);break ; } - pgbase =0 ; - bytes -= req_len; - requested_bytes += req_len; - pos += req_len; } -if (i < npages) -nfs_direct_release_pages (pagevec + i, npages - i, pinned); -kvfree (pagevec);if (result <0 )break ; }
@@ -881,6 +905,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,ssize_t result =0 ;size_t requested_bytes =0 ;size_t wsize =max_t (size_t,NFS_SERVER (inode)->wsize,PAGE_SIZE ); +LIST_HEAD (nfs_page_list);bool defer = false;trace_nfs_direct_write_schedule_iovec (dreq);
@@ -893,42 +918,15 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,NFS_I (inode)->write_io +=iov_iter_count (iter);while (iov_iter_count (iter)) { -struct page **pagevec =NULL ; -size_t bytes; -size_t pgbase; -unsigned npages, i; -bool pinned =iov_iter_extract_will_pin (iter); - - result =iov_iter_extract_pages (iter, &pagevec, - wsize, ~0U ,0 , &pgbase); + result =nfs_direct_extract_pages (dreq, iter, wsize, &pos, &nfs_page_list);if (result <0 )break ; - bytes = result; - npages = (result + pgbase +PAGE_SIZE -1 ) /PAGE_SIZE ; -for (i =0 ; i < npages; i++) { -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], - pinned, pgbase, pos, - req_len); -if (IS_ERR (req)) { - result =PTR_ERR (req); -break ; - } - -if (desc.pg_error <0 ) { -nfs_free_request (req); - result = desc.pg_error; -break ; - } - - pgbase =0 ; - bytes -= req_len; - requested_bytes += req_len; - pos += req_len; + requested_bytes += result; +while (!list_empty (&nfs_page_list)) { +struct nfs_page *req =nfs_list_entry (nfs_page_list.next); +nfs_list_remove_request (req);if (defer) {nfs_mark_request_commit (req,NULL , &cinfo,0 );continue ;
@@ -942,6 +940,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,if (desc.pg_error <0 && desc.pg_error != -EAGAIN ) { result = desc.pg_error;nfs_unlock_and_release_request (req); +nfs_release_request_list (&nfs_page_list);break ; }
@@ -955,9 +954,6 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq, desc.pg_error =0 ; defer = true; } -if (i < npages) -nfs_direct_release_pages (pagevec + i, npages - i, pinned); -kvfree (pagevec);if (result <0 )break ; }
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
PATCH v2 6/7 nfs: Optimize direct I/O to use folios for requests
In reply to Pranjal ShrivastavaOptimize nfs_direct_extract_pages() to group contiguous pages from the same folio into single nfs_page structures. This effectively migrates NFS Direct I/O from being page-based to being folio-based.
Reduce the number of nfs_page allocations and subsequent iterations by utilizing nfs_page_create_from_folio() to create aggregated requests.
--- fs/nfs/direct.c | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-)
fs/nfs/direct.c +37 -10
@@ -194,23 +194,45 @@ static ssize_t nfs_direct_extract_pages(struct nfs_direct_req *dreq,return result; npages = (result + pgbase +PAGE_SIZE -1 ) >>PAGE_SHIFT ; -for (i =0 ; i < npages; i++) { +for (i =0 ; i < npages; ) { +unsigned int chunk_len, folio_offset; +unsigned int nr_to_add =1 ;struct nfs_page *req; -unsigned int req_len =min_t (size_t, result - bytes,PAGE_SIZE - pgbase); +struct folio *folio; - req =nfs_page_create_from_page (dreq->ctx, pagevec[i], - pinned, pgbase, *pos, - req_len); + folio =page_folio (pagevec[i]); + folio_offset = (folio_page_idx (folio, pagevec[i]) <<PAGE_SHIFT ) + pgbase; + chunk_len =min_t (size_t, result - bytes,PAGE_SIZE - pgbase); + +while (i + nr_to_add < npages) { +struct page *next_page = pagevec[i + nr_to_add]; +struct page *prev_page = pagevec[i + nr_to_add -1 ]; + +if (page_folio (next_page) != folio || + next_page != prev_page +1 ) +break ; + + chunk_len +=min_t (size_t, result - bytes - chunk_len,PAGE_SIZE ); + nr_to_add++; + } + + req =nfs_page_create_from_folio (dreq->ctx, folio, + pinned, folio_offset, + chunk_len);if (IS_ERR (req)) {if (!bytes) bytes =PTR_ERR (req);break ; } + req->wb_index = *pos >>PAGE_SHIFT ; + req->wb_offset =offset_in_page (*pos); +list_add_tail (&req->wb_list, list); pgbase =0 ; - bytes += req_len; - *pos += req_len; + bytes += chunk_len; + *pos += chunk_len; + i += nr_to_add; }if (i < npages) {
@@ -403,9 +425,9 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,if (result <0 )break ; - requested_bytes += result;while (!list_empty (&nfs_page_list)) {struct nfs_page *req =nfs_list_entry (nfs_page_list.next); +size_t req_len = req->wb_bytes;nfs_list_remove_request (req);if (!nfs_pageio_add_request (&desc, req)) {
@@ -414,6 +436,7 @@ static ssize_t nfs_direct_read_schedule_iovec(struct nfs_direct_req *dreq,nfs_release_request_list (&nfs_page_list);break ; } + requested_bytes += req_len; }if (result <0 )break ;
@@ -922,19 +945,22 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,if (result <0 )break ; - requested_bytes += result;while (!list_empty (&nfs_page_list)) {struct nfs_page *req =nfs_list_entry (nfs_page_list.next); +size_t req_len = req->wb_bytes;nfs_list_remove_request (req);if (defer) {nfs_mark_request_commit (req,NULL , &cinfo,0 ); + requested_bytes += req_len;continue ; }nfs_lock_request (req); -if (nfs_pageio_add_request (&desc, req)) +if (nfs_pageio_add_request (&desc, req)) { + requested_bytes += req_len;continue ; + }/* Exit on hard errors */ if (desc.pg_error <0 && desc.pg_error != -EAGAIN ) {
@@ -951,6 +977,7 @@ static ssize_t nfs_direct_write_schedule_iovec(struct nfs_direct_req *dreq,spin_unlock (&dreq->lock);nfs_unlock_request (req);nfs_mark_request_commit (req,NULL , &cinfo,0 ); + requested_bytes += req_len; desc.pg_error =0 ; defer = true; }
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
Trond Myklebust
maintainerRe: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 36 lines · Pranjal Shrivastava
Optimize
nfs_direct_extract_pages()to group contiguous pages from the
same folio into singlenfs_pagestructures. This effectively migrates NFS Direct I/O from being page-based to being folio-based.Reduce the number of
nfs_pageallocations and subsequent iterations by utilizingnfs_page_create_from_folio()to create aggregated requests.Signed-off-by Pranjal Shrivastava <[email protected]>---
fs/nfs/direct.c | 47 +++++++++++++++++++++++++++++++++++++----------1 file changed, 37 insertions(+), 10 deletions(-)fs/nfs/direct.c +0
-0@@ -194,23 +194,45 @@ static ssize_t nfs_direct_extract_pages(structnfs_direct_req *dreq, return result;
npages = (result + pgbase +PAGE_SIZE- 1) >>PAGE_SHIFT;fs/nfs/direct.c +3
-1-for (i =0 ; i < npages; i++) { +for (i =0 ; i < npages; ) { +unsigned int chunk_len, folio_offset; +unsigned int nr_to_add =1 ;struct
nfs_page*req;fs/nfs/direct.c +0
-1- unsigned int req_len = min_t(size_t, result - bytes,
PAGE_SIZE- pgbase);fs/nfs/direct.c +1
-0+struct folio *folio;fs/nfs/direct.c +0
-1- req = nfs_page_create_from_page(dreq->ctx,pagevec[i],
fs/nfs/direct.c +0
-1- pinned, pgbase,*pos,
fs/nfs/direct.c static ssize_t nfs_direct_extract_pages(struct nfs_direct_req *dreq, · line 203
Quoted 2 lines
fs/nfs/direct.c +1
-1- req_len); + folio =page_folio (pagevec[i]);
I'm clearly missing something. The memory pointed to by these pages can be any arbitrary user space (or kernel space) memory region. It could be mapped device memory, for instance.
So why can you assume that page_folio() will resolve to a valid folio here?
Signature · 3 lines
Trond Myklebust Linux NFS client maintainer, Hammerspace [email protected], [email protected]
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
On Tue, Jun 16, 2026 at 11:29:13AM -0400, Trond Myklebust wrote:
Hi Trond
Quoted 44 lines · Pranjal Shrivastava
Quoted 36 lines
Optimize
nfs_direct_extract_pages()to group contiguous pages from the
same folio into singlenfs_pagestructures. This effectively migrates NFS Direct I/O from being page-based to being folio-based.Reduce the number of
nfs_pageallocations and subsequent iterations by utilizingnfs_page_create_from_folio()to create aggregated requests.Signed-off-by Pranjal Shrivastava <[email protected]>---
fs/nfs/direct.c | 47 +++++++++++++++++++++++++++++++++++++----------1 file changed, 37 insertions(+), 10 deletions(-)fs/nfs/direct.c +0
-0@@ -194,23 +194,45 @@ static ssize_t nfs_direct_extract_pages(structnfs_direct_req *dreq, return result;
npages = (result + pgbase +PAGE_SIZE- 1) >>PAGE_SHIFT;fs/nfs/direct.c +3
-1-for (i =0 ; i < npages; i++) { +for (i =0 ; i < npages; ) { +unsigned int chunk_len, folio_offset; +unsigned int nr_to_add =1 ;struct
nfs_page*req;fs/nfs/direct.c +0
-1- unsigned int req_len = min_t(size_t, result - bytes,
PAGE_SIZE- pgbase);fs/nfs/direct.c +1
-0+struct folio *folio;fs/nfs/direct.c +0
-1- req = nfs_page_create_from_page(dreq->ctx,pagevec[i],
fs/nfs/direct.c +0
-1- pinned, pgbase,*pos,
fs/nfs/direct.c static ssize_t nfs_direct_extract_pages(struct nfs_direct_req *dreq, · line 203
Quoted 2 lines
fs/nfs/direct.c +1
-1- req_len); + folio =page_folio (pagevec[i]);I'm clearly missing something. The memory pointed to by these pages can be any arbitrary user space (or kernel space) memory region. It could be mapped device memory, for instance.
Quoted 2 lines
So why can you assume that
page_folio()will resolve to a valid folio here?
AFAIU, the MM subsystem explicitly ensures that every valid struct page is part of a folio. The documentation for page_folio() explicitly states [1]:
"Every page is part of a folio. This function cannot be called on a
NULL pointer."
Since iov_iter_extract_pages() only returns pages that are successfully pinned and tracked by the kernel, we are guaranteed that pagevec[i] points to a valid struct page and thus a valid folio.
Regarding device-mapped memory, ZONE_DEVICE pages have also been refactored to support folios recently (e.g. free_zone_device_folio() [2]) If the memory is not part of a large compound page, page_folio() simply returns the struct page pointer cast to a struct folio * [3]. In this case, the folio size is effectively 1, and our extraction loop correctly handles it as a single-page request unless it identifies physical contiguity within the same folio.
The only other thing to take care was folio_split which applies specifically when the caller does not hold a reference on the page. However, in our case (NFS) the iov_iter_extract_pages() has already pinned the folio via GUP by this point which ensures that the folio cannot be split or freed under us, making the page_folio() call and the subsequent aggregation logic safe.
Finally, in cases where device memory is NOT backed by struct page (e.g. dmabuf or PFN-based mappings via remap_pfn_range), the buffers are already unsupported for NFS Direct I/O today. The underlying page pinning (GUP) would fail with -EFAULT in check_vma_flags() [4] even before reaching this point.
Given the above guarantees by the kernel, we can ensure that this resolves to a valid folio at this point in the file-system.
Thanks,
Praan
[1] https://elixir.bootlin.com/linux/v7.1-rc6/source/include/linux/page-flags.h#L291
[2] https://elixir.bootlin.com/linux/v7.1-rc6/source/mm/memremap.c#L416
[3] https://elixir.bootlin.com/linux/v7.1-rc6/source/include/linux/page-flags.h#L234
[4] https://elixir.bootlin.com/linux/v7.1-rc6/source/mm/gup.c#L1208
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 2 lines · Pranjal Shrivastava
AFAIU, the MM subsystem explicitly ensures that every valid struct page is part of a folio.
It is definitively not what the vision for the folio is, although if I'm not mistaken it actually is still true right now. This whole area is a minefield unfortunately, and we also ran into it with iov_iter_extract_bvecs and the earlier block code it was extracted from. Adding the relevant people and lists, but for now your best bet is to stick to what the block code does or even better reuse as much as possible of that code.
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 6 lines · Christoph Hellwig
Quoted 2 lines · Pranjal Shrivastava
AFAIU, the MM subsystem explicitly ensures that every valid struct page is part of a folio.
It is definitively not what the vision for the folio is, although if I'm not mistaken it actually is still true right now.
It's not true, eg, for slab. While there's still a struct page there for slab, there's no refcount and flags like PG_locked have different meanings. You'll get into a lot of trouble trying to treat slabs as folios (and that will include assertions tripping).
Quoted 6 lines
This whole
area is a minefield unfortunately, and we also ran into it withiov_iter_extract_bvecsand the earlier block code it was extracted from. Adding the relevant people and lists, but for now your best bet is to stick to what the block code does or even better reuse as much as possible of that code.
Yes. Fundamentally, it is no business of the filesystem what the iov_iter refers to. We can do direct io to slab memory, vmalloc memory, memory that doesn't have a struct page (eg iomem), or whatever we choose.
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
On Thu, Jun 18, 2026 at 07:20:06PM +0100, Matthew Wilcox wrote:
Hi Matthew, Christoph, Trond,
Quoted 20 lines · Christoph Hellwig
Quoted 6 lines · Pranjal Shrivastava
Quoted 2 lines
AFAIU, the MM subsystem explicitly ensures that every valid struct page is part of a folio.
It is definitively not what the vision for the folio is, although if I'm not mistaken it actually is still true right now.
It's not true, eg, for slab. While there's still a struct page there for slab, there's no refcount and flags like
PG_lockedhave different meanings. You'll get into a lot of trouble trying to treat slabs as folios (and that will include assertions tripping).Quoted 6 lines
This whole
area is a minefield unfortunately, and we also ran into it withiov_iter_extract_bvecsand the earlier block code it was extracted from. Adding the relevant people and lists, but for now your best bet is to stick to what the block code does or even better reuse as much as possible of that code.
Quoted 4 lines
Yes. Fundamentally, it is no business of the filesystem what the
iov_iterrefers to. We can do direct io to slab memory, vmalloc memory, memory that doesn't have a struct page (eg iomem), or whatever we choose.
Thanks for the clarification. I understand the larger vision of keeping filesystems agnostic to the underlying memory represented by the iov_iter
The documentation for page_folio() [1] mentions that "Every page is part of a folio," but it appears there are important nuances regarding slab and other memory types that I was not aware of.
However, I am a bit confused on one point:
Looking at iov_iter_extract_bvecs() [1] it relies on get_contig_folio_len() [2], which calls page_folio() on the pages extracted (via iov_iter_extract_pages()) without additional checks for slab or vmalloc memory.
I am happy to refactor the NFS Direct I/O path to reuse the same helper (get_contig_folio_len()) from the bvec extractor, but I'm a little confused as the bvec extractor seems to suffer from the same risk?
Is the recommendation to keep these details abstracted by the iov_iter lib and eventually hide things like iov_iter_extract_pages() and manual folio conversions from filesystems entirely?
If that's the case, would it help to export get_contig_folio_len() (or introduce new helpers) in the iov_iter lib for NFS and other fs to use?
Thanks,
Praan
[1] https://elixir.bootlin.com/linux/v7.1-rc6/source/include/linux/page-flags.h#L291
[2] https://elixir.bootlin.com/linux/v7.1/source/lib/iov_iter.c#L1849
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
In reply to Matthew Wilcox[sorry, dropped the ball a bit on this due to overload]
Quoted 5 lines · Matthew Wilcox
Quoted 4 lines · Christoph Hellwig
Quoted 2 lines · Pranjal Shrivastava
AFAIU, the MM subsystem explicitly ensures that every valid struct page is part of a folio.
Quoted 7 lines
Quoted 2 lines
It is definitively not what the vision for the folio is, although if I'm not mistaken it actually is still true right now.
It's not true, eg, for slab. While there's still a struct page there for slab, there's no refcount and flags like
PG_lockedhave different meanings. You'll get into a lot of trouble trying to treat slabs as folios (and that will include assertions tripping).
True. But also not relevant for direct I/O user pinning. If we stopped having valid folios for anything mapped into userspace, iov_iter_extract_bvecs would run into problems, and we had the discussion before that at least right now it would be hard to fix.
Also if iov_iter_extract_bvecs was used on kvec or bvec iters we could run into the slab problem. The block usage currently makes sure bvec iters are not handed to iov_iter_extract_bvecs, but there is no such thing for kvec vectors, although no one is using them for direct I/O right now. Not that I'd want to rely on that in the long run.
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 16 lines · Christoph Hellwig
[sorry, dropped the ball a bit on this due to overload]
Quoted 5 lines · Matthew Wilcox
Quoted 4 lines · Christoph Hellwig
Quoted 2 lines · Pranjal Shrivastava
AFAIU, the MM subsystem explicitly ensures that every valid struct page is part of a folio.
Quoted 7 lines
Quoted 2 lines
It is definitively not what the vision for the folio is, although if I'm not mistaken it actually is still true right now.
It's not true, eg, for slab. While there's still a struct page there for slab, there's no refcount and flags like
PG_lockedhave different meanings. You'll get into a lot of trouble trying to treat slabs as folios (and that will include assertions tripping).
Quoted 5 lines
True. But also not relevant for direct I/O user pinning. If we stopped having valid folios for anything mapped into userspace,
iov_iter_extract_bvecswould run into problems, and we had the discussion before that at least right now it would be hard to fix.
+1. I see that extract_bvecs also rely on user memory to have valid folios even if we were to re-use parts of it (get_contig_folio_len) it still relies on page_folio() as detailed in the other reply.
Quoted 6 lines
Also if
iov_iter_extract_bvecswas used on kvec or bvec iters we could run into the slab problem. The block usage currently makes sure bvec iters are not handed toiov_iter_extract_bvecs, but there is no such thing for kvec vectors, although no one is using them for direct I/O right now. Not that I'd want to rely on that in the long run.
Do we have use-cases for a kernel user for direct I/O ? (Just curious to know if there's something on the horizon).
Thanks,
Praan
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 2 lines · Pranjal Shrivastava
Do we have use-cases for a kernel user for direct I/O ? (Just curious to know if there's something on the horizon).
Plenty. Basically any storage on file driver, or storage / file system
server: loop, zloop, nvmet, scsi target and nfsd ar the ones I know off by head.
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 8 lines · Christoph Hellwig
Quoted 2 lines · Pranjal Shrivastava
Do we have use-cases for a kernel user for direct I/O ? (Just curious to know if there's something on the horizon).
Plenty. Basically any storage on file driver, or storage / file system
server: loop, zloop, nvmet, scsi target and nfsd ar the ones I know off by head.
Ack. I see! Thanks!
Regarding the page_folio impasse, how do you suggest we proceed? Should I expose and use get_contig_folio_len() from bvec? Or should I move the NFS helper into the iov_iter lib? (or both).
Also, do you suggest sending the Folio move as a standalone patch if it is blocking the rest of the series or do we prefer keeping these in a single series?
Thanks,
Praan
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 5 lines · Pranjal Shrivastava
Ack. I see! Thanks!
Regarding the
page_folioimpasse, how do you suggest we proceed? Should I expose and useget_contig_folio_len()from bvec? Or should I move the NFS helper into theiov_iterlib? (or both).
Sounds like the best way forward for now.
Quoted 3 lines
Also, do you suggest sending the Folio move as a standalone patch if it is blocking the rest of the series or do we prefer keeping these in a single series?
Sorry if I'm thick, but I'm not sure what "folio" move means. In doubt if you think you can get a series merged quicker without a later part I'd split it.
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 8 lines · Christoph Hellwig
Quoted 5 lines · Pranjal Shrivastava
Ack. I see! Thanks!
Regarding the
page_folioimpasse, how do you suggest we proceed? Should I expose and useget_contig_folio_len()from bvec? Or should I move the NFS helper into theiov_iterlib? (or both).Sounds like the best way forward for now.
Ack. I'll reuse get_contig_folio len and move the nfs extractor to iov_iter for v3.
Quoted 8 lines
Quoted 4 lines
Also, do you suggest sending the Folio move as a standalone patch if it is blocking the rest of the series or do we prefer keeping these in a single series?
Sorry if I'm thick, but I'm not sure what "folio" move means. In doubt if you think you can get a series merged quicker without a later part I'd split it.
Apologies for being unclear. I meant I could post a standalone series that moves NFS Direct I/O to folio, i.e. break out Patches 6 & 7 to another series, if that makes sense?
Thanks,
Praan
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 4 lines · Pranjal Shrivastava
Quoted 3 lines · Christoph Hellwig
Quoted 2 lines · Pranjal Shrivastava
Ack. I see! Thanks!
Quoted 9 lines
Quoted 5 lines
Quoted 3 lines
Regarding the
page_folioimpasse, how do you suggest we proceed? Should I expose and useget_contig_folio_len()from bvec? Or should I move the NFS helper into theiov_iterlib? (or both).Sounds like the best way forward for now.
Ack. I'll reuse
get_contig_foliolen and move the nfs extractor toiov_iterfor v3.
On a second thought, I guess I'd try taking a stab at writing iov_iter_extract_folios and handle the vmalloc / slab cases for kvecs. I believe that would be the right way to go, unless I'm missing something obvious? Can I take a stab at it in the next version?
Thanks,
Praan
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
Quoted 11 lines · Pranjal Shrivastava
Quoted 10 lines · Pranjal Shrivastava
Quoted 8 lines · Christoph Hellwig
Quoted 5 lines · Pranjal Shrivastava
Ack. I see! Thanks!
Regarding the
page_folioimpasse, how do you suggest we proceed? Should I expose and useget_contig_folio_len()from bvec? Or should I move the NFS helper into theiov_iterlib? (or both).Sounds like the best way forward for now.
Quoted 8 lines
Quoted 3 lines
Ack. I'll reuse
get_contig_foliolen and move the nfs extractor toiov_iterfor v3.On a second thought, I guess I'd try taking a stab at writing
iov_iter_extract_foliosand handle the vmalloc / slab cases for kvecs. I believe that would be the right way to go, unless I'm missing something obvious? Can I take a stab at it in the next version?
That does sound good, but it might get you into some longer discussions.
Re: [PATCH v2 6/7] nfs: Optimize direct I/O to use folios for requests
In reply to Pranjal ShrivastavaQuoted 7 lines · Pranjal Shrivastava
Quoted 3 lines
Sorry if I'm thick, but I'm not sure what "folio" move means. In doubt if you think you can get a series merged quicker without a later part I'd split it.
Apologies for being unclear. I meant I could post a standalone series that moves NFS Direct I/O to folio, i.e. break out Patches 6 & 7 to another series, if that makes sense?
Sounds good.
PATCH v2 7/7 nfs: Cleanup the nfs_page_create_from_page helper
In reply to Pranjal ShrivastavaRemove the nfs_page_create_from_page() helper and its public export. Following the migration of the Direct I/O path to folios, this function no longer has any callers in the NFS client.
--- fs/nfs/pagelist.c | 36 ------------------------------------ include/linux/nfs_page.h | 6 ------ 2 files changed, 42 deletions(-)
fs/nfs/pagelist.c +0 -36
@@ -427,42 +427,6 @@ static void nfs_page_assign_page(struct nfs_page *req, struct page *page, bool p } } -/** -* nfs_page_create_from_page - Create an NFS read/write request. -* @ctx: open context to use -* @page: page to write -* @pinned: true if page is pinned -* @pgbase: starting offset within the page for the write -* @offset: file offset for the write -* @count: number of bytes to read/write -* -* The page must be locked by the caller. This makes sure we never -* create two different requests for the same page. -* User should ensure it is safe to sleep in this function. -*/ -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) -{ -struct nfs_lock_context *l_ctx =nfs_get_lock_context (ctx); -struct nfs_page *ret; - -if (IS_ERR (l_ctx)) -return ERR_CAST (l_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, pinned); -if (pinned) - ret->wb_nr_pinned =1 ; -nfs_page_group_init (ret,NULL ); - } -nfs_put_lock_context (l_ctx); -return ret; -} -/** * nfs_page_create_from_folio - Create an NFS read/write request. * @ctx: open context to use
include/linux/nfs_page.h +0 -6
@@ -125,12 +125,6 @@ struct nfs_pageio_descriptor {/* arbitrarily selected limit to number of mirrors */ #define NFS_PAGEIO_DESCRIPTOR_MIRROR_MAX 16 -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); extern struct nfs_page *nfs_page_create_from_folio(struct nfs_open_context *ctx, struct folio *folio, bool pinned,
Signature · 1 line
2.54.0.1136.gdb2ca164c4-goog
Quoted 60 lines · Pranjal Shrivastava
Modernize the NFS Direct I/O path as a preparatory step to enable PCI Peer-to-Peer DMA (P2PDMA) support. Following feedback on the initial RFC [1], the modernization and architectural changes are split into this standalone series.
Currently, NFS
O_DIRECTrelies on the legacyiov_iter_get_pages_alloc2()API which does not support the pinning requirements for P2P memory. The implementation moves NFS to the moderniov_iter_extract_pages()API and migrates NFS direct I/O away from pages to use folios.Design
======1. Pin-Awareness
Standard NFS requests useget_page()andput_page()for memory management. However, memory extracted viaiov_iter_extract_pages()requires explicit pinning.Introduce a
PG_PINNEDflag and awb_nr_pinnedcount to structnfs_page. This allows the request lifecycle to track ownership of physical pins and ensure that unpinning is performed only when the I/O is complete.2. API Migration
Migrate the Direct I/O path to the moderniov_iter_extract_pages()API. This aligns NFS with the modern extraction model and serves as the foundation for passingITER_ALLOW_P2PDMAin a follow-up series.3. Extraction Helper and Folio Support
Introduce a new extraction helper indirect.cto group contiguous pages from the same folio into a single structnfs_page. This effectively migrates the Direct I/O path from being page-based to being folio-based.Note:
zone_device_pages_have_same_pgmap()checks are intentionally omitted in the extraction helper since P2PDMA enablement will be introduced in a follow-up series.Bisectability
=============
The series attempts to remain bisectable.[Patches 1-2] Introduce pin-aware infrastructure and accounting. [Patch 3] Adds a centralized request release helper.
[Patch 4] Migrates the Direct I/O path toiov_iter_extract_pages(). [Patches 5-6] Implement the extraction helper and folio-based grouping. [Patch 7] Removes orphaned page-based helpers.Testing
=======
This series has been tested with xfstests [2] on RDMA & TCP transports:./check generic/091 generic/130 generic/139 generic/143 generic/154 \ generic/155 generic/183 generic/188 generic/190 generic/196 \ generic/198 generic/203 generic/214 generic/240 generic/263 \ generic/287 generic/290 generic/292 generic/330 generic/444 \ generic/450 generic/451 generic/586 generic/647 generic/708 \ generic/729 generic/760The following summary was tabulated via a custom script [3] (on github).
[...]
Quoted 3 lines
[1] https://lore.kernel.org/all/[email protected]/
[2] https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git
Missed [3] https://github.com/pran005/tools/blob/main/display.py
Quoted 10 lines
[v2]
- Fix data corruption innfs_direct_extract_pages()by correctly calculating intra-page offsets usingoffset_in_page().
- Fixrequested_bytesaccounting in direct read/write paths to only increment after successful RPC scheduling.
- Add missing kernel-doc descriptions for the @pinned parameter innfs_page_create_from_page()andnfs_page_create_from_folio().
- Rebase on fs-next/
Thanks,
Praan
Chuck Lever
maintainerHi Pranjal -
Quoted 4 lines · Pranjal Shrivastava
Modernize the NFS Direct I/O path as a preparatory step to enable PCI Peer-to-Peer DMA (P2PDMA) support. Following feedback on the initial RFC [1], the modernization and architectural changes are split into this standalone series.
Good idea! Following is a review process note:
Please add [email protected] and [email protected] when reposting this series for review. The latter is suggested by the MAINTAINERS file; you might know of an even more relevant set of reviewers to include.
Signature · 1 line
Chuck Lever
Quoted 8 lines · Chuck Lever
Hi Pranjal -
Quoted 4 lines · Pranjal Shrivastava
Modernize the NFS Direct I/O path as a preparatory step to enable PCI Peer-to-Peer DMA (P2PDMA) support. Following feedback on the initial RFC [1], the modernization and architectural changes are split into this standalone series.
Quoted 7 lines
Good idea! Following is a review process note:
Please add [email protected] and [email protected] when reposting this series for review. The latter is suggested by the
MAINTAINERSfile; you might know of an even more relevant set of reviewers to include.
Ack. Apologies for missing these out, I just ran get_maintainers on the current (part 1) patches and CC'd. I'll include the lists and relevant people in the next version.
Quoted 2 lines
Signature · 1 line
Chuck Lever
Thanks,
Praan
Quoted 9 lines · Pranjal Shrivastava
=======
This series has been tested with xfstests [2] on RDMA & TCP transports:./check generic/091 generic/130 generic/139 generic/143 generic/154 \ generic/155 generic/183 generic/188 generic/190 generic/196 \ generic/198 generic/203 generic/214 generic/240 generic/263 \ generic/287 generic/290 generic/292 generic/330 generic/444 \ generic/450 generic/451 generic/586 generic/647 generic/708 \ generic/729 generic/760
Did you only run these tests that previously failed for Anna or all of xfstests, which should give you much better coverage?
On Thu, Jun 18, 2026 at 07:11:45AM -0700, Christoph Hellwig wrote:
Hi Christoph,
Quoted 11 lines · Pranjal Shrivastava
Quoted 9 lines
=======
This series has been tested with xfstests [2] on RDMA & TCP transports:./check generic/091 generic/130 generic/139 generic/143 generic/154 \ generic/155 generic/183 generic/188 generic/190 generic/196 \ generic/198 generic/203 generic/214 generic/240 generic/263 \ generic/287 generic/290 generic/292 generic/330 generic/444 \ generic/450 generic/451 generic/586 generic/647 generic/708 \ generic/729 generic/760
Quoted 2 lines
Did you only run these tests that previously failed for Anna or all of xfstests, which should give you much better coverage?
I highlighted those specific tests in the cover letter as they were reported as failures for the NFS direct I/O path.
I've also run the full xfstests quick group on both the unpatched and patched kernels (7.1-rc7) across RDMA and TCP transports. I did not see any regressions between the two runs, the results were identical across the quick suite.
I could report the entire run for a patched and unpatched kernel in the v3 cover letter if you'd like?
Thanks,
Praan