From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2250B43BDA0 for ; Mon, 24 Aug 2026 14:42:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582523; cv=none; b=ifwCatgv/N7L23KGVXwuyo9aBdQ9HPZTzydx+61aIrVAsafnH2Z21ihQA+fhOS5ijXd17uPMzErzimrA+Ji1KWKsCKdkiB36hkn1t9YNWCH3xzoDSl9AkSZixpLDhUOhZuRqc8sfi7FjxM2CLJt2mexh3kZ0AB8oI8j3SkCOvEg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582523; c=relaxed/simple; bh=gZJj56dXZ3m5mpuWg5Y32mLuivOuA0n2jQSSKK4Lf/g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ARx2u+5wGKVQpTKzDw5peTThThyq1P35Ndv7FF4zlEqDrwLxcQOwuturwhDyJyWMMvO0ci3OJfNVwKMS48GjA68itxwKC8In+wD7GGBzMRwXt8MLZ5nCr6CGJb9YAbcC+bCmrCHBNeGG6NQRyEF49NEL6uAO0DLaITJzBVvtB9A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=P4CJ9KBl; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="P4CJ9KBl" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582521; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=b4i/vAlF7TVRLwxgQcCoqCNsYOQic8KZ0Txwd6Khtt8=; b=P4CJ9KBlAoTo3242Gp/2tWdsQLI9tv5Q5mjyAjQJ0fitGUkLMUaUv7ex+h+Gsop1LukwQr u8vigH9uCKjwaCt5hnj97LP9R+aTVhlUG6AO172nDXk4ILko9W0EqPdhJp3HTcEC3fCjYo 8jzgAbH55TK1GxG1Sigo5YQGOgJ7YPo= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-660-BkkMkSVBPOmbFAgBkBZ02Q-1; Mon, 24 Aug 2026 10:41:56 -0400 X-MC-Unique: BkkMkSVBPOmbFAgBkBZ02Q-1 X-Mimecast-MFC-AGG-ID: BkkMkSVBPOmbFAgBkBZ02Q_1787582513 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 61DE919541B5; Mon, 24 Aug 2026 14:41:52 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 40B6A1800605; Mon, 24 Aug 2026 14:41:44 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Karl Mehltretter , stable@vger.kernel.org Subject: [PATCH v10 01/35] netfs: Fix uninitialized return value in netfs_unbuffered_write() Date: Mon, 24 Aug 2026 15:40:54 +0100 Message-ID: <20260824144130.759997-2-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Content-Type: text/plain; charset="utf-8" From: Karl Mehltretter If preparation of the first subrequest fails, netfs_unbuffered_write() exits its loop before ret is initialized. The empty-iterator check can do the same. For synchronous writes, netfs_unbuffered_write_iter_locked() may then return an unrelated error instead of wreq->error. This is reachable through CIFS if cifs_prepare_write() fails to reopen the file or obtain credits. Initialize ret to 0 so the caller returns wreq->error if no data was written, or the number of bytes already written otherwise. Found with Clang's -Wconditional-uninitialized. Fixes: a0b4c7a49137e ("netfs: Fix unbuffered/DIO writes to dispatch subrequ= ests in strict sequence") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Signed-off-by: David Howells --- fs/netfs/direct_write.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index c16fbad286a1..b04019097ab8 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -95,7 +95,7 @@ static int netfs_unbuffered_write(struct netfs_io_request= *wreq) { struct netfs_io_subrequest *subreq =3D NULL; struct netfs_io_stream *stream =3D &wreq->io_streams[0]; - int ret; + int ret =3D 0; =20 _enter("%llx", wreq->len); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CEEB7363C5F for ; Mon, 24 Aug 2026 14:42:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582536; cv=none; b=ptpEVNiJac4ybPKdFV/Q5fFwVWJbT2GjTZFuCenYzd5UZ6alOLbaF20lN8b49X510aHzv+1YX+nFjK5WhxXiZf3BrNIjSUcElpDJykicvcnugpER8aoykAkTcJYDPAP+YjYzbruH8GXYRVlQtUcn1veJUnImFM+4jB1LqcAvunE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582536; c=relaxed/simple; bh=beVFn8xzbeETPCJ2w7SjPsrKiM8S/E1+kc/72GLG3dg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dOrph45XJKK3Pwc1aZA3Opu46/FzbKP0WQdJ1/+o+JBwdsxUwR3+IAxCIrPYSdIhCM67omRLvgt8c8U9IJtstBjUGcEvCEYHWxg0DiE4hAl2W1fZVsSKYw4OlVr80yGi2A/vnLdVtEvJv2m/N7RExqw9SB62M9uQShXcojd8v3I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=I0l5duAn; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="I0l5duAn" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582531; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=H0ERCBm2RCS8Ugmv+lE/K1zDrz8BlcNp0WLETLwOqHA=; b=I0l5duAnFqzRz4neD8WMMdHFWyNQPFJaWGARnS8ItB7xvAkuGKQg7C90gFOT5Kjc8u2ABa gaC65l1wgxe/Bn3Ya3YbOY4CJ/FOogCjbcbJ8nz9z2K0YwAhoPRnzcQJr4FVn9dc6YjnXV Tphik09co356ZB/cSnZt8Qa1PTMGu/o= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-83-vSgrQxF3OFCKM5VWwndmlA-1; Mon, 24 Aug 2026 10:42:07 -0400 X-MC-Unique: vSgrQxF3OFCKM5VWwndmlA-1 X-Mimecast-MFC-AGG-ID: vSgrQxF3OFCKM5VWwndmlA_1787582523 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B8F691955D61; Mon, 24 Aug 2026 14:42:01 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 16F1C1800605; Mon, 24 Aug 2026 14:41:53 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 02/35] netfs: Fix read progress reporting Date: Mon, 24 Aug 2026 15:40:55 +0100 Message-ID: <20260824144130.759997-3-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Content-Type: text/plain; charset="utf-8" For really big read RPC ops that span multiple folios, netfslib allows the filesystem to give progress notifications to wake up the collector thread to do a collection of folios that have now been fetched, even if the RPC is still ongoing, thereby allowing the application to make progress. This works by taking the current rreq->cleaned_to value (which indicates which folios have been unlocked) and adding the stashed size of the next folio to it. cleaned_to, however, is subject to 64-bit tearing on a 32-bit arch. Fix this by stashing the next progress notification point as a size_t (which won't tear) to be added to rreq->start (which won't change), with the collector thread calculating that from cleaned_to plus the next folio size. Further, however, if the folios are small, the collector thread gets constantly woken up - which has a negative performance impact on the system. Fix that too by setting a minimum trigger of 256KiB or the size of the folio at the front of the queue, whichever is larger. Also, make sure rreq->cleaned_to is initialised up front, along with rreq->collected_to and stream->collected_to. Fixes: e2d46f2ec332 ("netfs: Change the read result collector to only use o= ne work item") Link: https://sashiko.dev/#/patchset/20260804100224.2748935-1-dhowells%40re= dhat.com Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/buffered_read.c | 4 +++ fs/netfs/internal.h | 1 + fs/netfs/objects.c | 32 ++++++++++++-------- fs/netfs/read_collect.c | 58 +++++++++++++++++++++++++++--------- fs/netfs/read_single.c | 2 ++ include/linux/netfs.h | 2 +- include/trace/events/netfs.h | 21 +++++++++++++ 7 files changed, 93 insertions(+), 27 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 7fdfa4f27e34..3c32ef41a27f 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -106,6 +106,9 @@ static ssize_t netfs_prepare_read_iterator(struct netfs= _io_subrequest *subreq, folio_batch_release(&put_batch); return added; } + + if (!rreq->progress_at) + netfs_read_set_unlock_at(rreq); rreq->submitted +=3D added; } folio_batch_release(&put_batch); @@ -387,6 +390,7 @@ static int netfs_create_singular_buffer(struct netfs_io= _request *rreq, struct fo if (added < 0) return added; rreq->submitted =3D rreq->start + added; + rreq->progress_at =3D added; return 0; } =20 diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 420ee7b26580..f92281a611de 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -109,6 +109,7 @@ static inline void netfs_see_subrequest(struct netfs_io= _subrequest *subreq, /* * read_collect.c */ +void netfs_read_set_unlock_at(struct netfs_io_request *rreq); bool netfs_read_collection(struct netfs_io_request *rreq); void netfs_read_collection_worker(struct work_struct *work); void netfs_cancel_read(struct netfs_io_subrequest *subreq, int error); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 01461a74642d..7f6a3e912602 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -41,24 +41,32 @@ struct netfs_io_request *netfs_alloc_request(struct add= ress_space *mapping, =20 memset(rreq, 0, kmem_cache_size(cache)); INIT_WORK(&rreq->cleanup_work, netfs_free_request); - rreq->gfp =3D gfp; - rreq->start =3D start; - rreq->len =3D len; - rreq->origin =3D origin; - rreq->netfs_ops =3D ctx->ops; - rreq->mapping =3D mapping; - rreq->inode =3D inode; - rreq->i_size =3D i_size_read(inode); - rreq->debug_id =3D atomic_inc_return(&debug_ids); - rreq->wsize =3D INT_MAX; + rreq->gfp =3D gfp; + rreq->start =3D start; + rreq->collected_to =3D start; + rreq->cleaned_to =3D start; + rreq->len =3D len; + rreq->progress_at =3D 0; + rreq->origin =3D origin; + rreq->netfs_ops =3D ctx->ops; + rreq->mapping =3D mapping; + rreq->inode =3D inode; + rreq->i_size =3D i_size_read(inode); + rreq->debug_id =3D atomic_inc_return(&debug_ids); + rreq->wsize =3D INT_MAX; rreq->io_streams[0].sreq_max_len =3D ULONG_MAX; rreq->io_streams[0].sreq_max_segs =3D 0; spin_lock_init(&rreq->lock); - INIT_LIST_HEAD(&rreq->io_streams[0].subrequests); - INIT_LIST_HEAD(&rreq->io_streams[1].subrequests); init_waitqueue_head(&rreq->waitq); refcount_set(&rreq->ref, 2); =20 + for (int s =3D 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_stream *stream =3D &rreq->io_streams[s]; + + INIT_LIST_HEAD(&stream->subrequests); + stream->collected_to =3D rreq->start; + } + if (origin =3D=3D NETFS_READAHEAD || origin =3D=3D NETFS_READPAGE || origin =3D=3D NETFS_READ_GAPS || diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 23660a590124..723b479ef606 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -94,6 +94,35 @@ static void netfs_unlock_read_folio(struct netfs_io_requ= est *rreq, folioq_clear(folioq, slot); } =20 +/* + * Determine how much to gather before unlocking more folios. + */ +void netfs_read_set_unlock_at(struct netfs_io_request *rreq) +{ + struct folio_queue *folioq =3D rreq->buffer.tail; + unsigned int slot =3D rreq->buffer.first_tail_slot; + size_t cleaned_to =3D rreq->cleaned_to - rreq->start; + size_t progress_at =3D cleaned_to; + size_t minimum =3D 256 * 1024; + + while (progress_at < rreq->len) { + if (slot >=3D folioq_nr_slots(folioq)) { + folioq =3D folioq->next; + if (!folioq) + break; + slot =3D 0; + } + + progress_at +=3D folioq_folio_size(folioq, slot); + if (progress_at - cleaned_to >=3D minimum) + break; + slot++; + } + + WRITE_ONCE(rreq->progress_at, progress_at); + trace_netfs_read_progress_at(rreq); +} + /* * Unlock any folios we've finished with. */ @@ -112,7 +141,7 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, if (slot >=3D folioq_nr_slots(folioq)) { folioq =3D rolling_buffer_delete_spent(&rreq->buffer); if (!folioq) { - rreq->front_folio_order =3D 0; + WRITE_ONCE(rreq->progress_at, ULONG_MAX); return; } slot =3D 0; @@ -120,8 +149,7 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, =20 for (;;) { struct folio *folio; - unsigned long long fpos, fend; - unsigned int order; + unsigned long long fpos =3D rreq->cleaned_to, fend; size_t fsize; =20 if (*notes & COPY_TO_CACHE) @@ -133,9 +161,7 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, rreq->debug_id, folio->index)) trace_netfs_folio(folio, netfs_folio_trace_not_locked); =20 - order =3D folioq_folio_order(folioq, slot); - rreq->front_folio_order =3D order; - fsize =3D PAGE_SIZE << order; + fsize =3D folioq_folio_size(folioq, slot); fpos =3D folio_pos(folio); fend =3D fpos + fsize; =20 @@ -146,7 +172,7 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, break; =20 netfs_unlock_read_folio(rreq, folioq, slot); - WRITE_ONCE(rreq->cleaned_to, fpos + fsize); + WRITE_ONCE(rreq->cleaned_to, fend); *notes |=3D MADE_PROGRESS; =20 clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); @@ -172,6 +198,8 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, rreq->buffer.tail =3D folioq; done: rreq->buffer.first_tail_slot =3D slot; + + netfs_read_set_unlock_at(rreq); } =20 /* @@ -232,7 +260,7 @@ static void netfs_collect_read_results(struct netfs_io_= request *rreq) * subreqs. */ if (notes & BUFFERED) { - size_t fsize =3D PAGE_SIZE << rreq->front_folio_order; + uoff_t unlock_at =3D rreq->start + rreq->progress_at; =20 /* Clear the tail of a short read. */ if (!(notes & HIT_PENDING) && @@ -257,7 +285,7 @@ static void netfs_collect_read_results(struct netfs_io_= request *rreq) transferred =3D front->len; trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon); } - if (front->start + transferred >=3D rreq->cleaned_to + fsize || + if (front->start + transferred >=3D unlock_at || test_bit(NETFS_SREQ_HIT_EOF, &front->flags)) netfs_read_unlock_folios(rreq, ¬es); } else { @@ -477,20 +505,22 @@ void netfs_read_collection_worker(struct work_struct = *work) void netfs_read_subreq_progress(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq =3D subreq->rreq; - struct netfs_io_stream *stream =3D &rreq->io_streams[0]; - size_t fsize =3D PAGE_SIZE << rreq->front_folio_order; - - trace_netfs_sreq(subreq, netfs_sreq_trace_progress); + struct netfs_io_stream *stream =3D &rreq->io_streams[subreq->stream_nr]; + size_t progress_at =3D READ_ONCE(rreq->progress_at); + uoff_t update_at =3D rreq->start + progress_at; + uoff_t transferred_to =3D subreq->start + subreq->transferred; =20 /* If we are at the head of the queue, wake up the collector, * getting a ref to it if we were the ones to do so. */ - if (subreq->start + subreq->transferred > rreq->cleaned_to + fsize && + if (progress_at !=3D ULONG_MAX && + transferred_to >=3D update_at && (rreq->origin =3D=3D NETFS_READAHEAD || rreq->origin =3D=3D NETFS_READPAGE || rreq->origin =3D=3D NETFS_READ_FOR_WRITE) && list_is_first(&subreq->rreq_link, &stream->subrequests) ) { + trace_netfs_sreq(subreq, netfs_sreq_trace_progress); __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); netfs_wake_collector(rreq); } diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index 8833550d2eb6..de67ac41548d 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -170,6 +170,8 @@ ssize_t netfs_read_single(struct inode *inode, struct f= ile *file, struct iov_ite if (IS_ERR(rreq)) return PTR_ERR(rreq); =20 + rreq->progress_at =3D rreq->len; + ret =3D netfs_single_begin_cache_read(rreq, ictx); if (ret =3D=3D -ENOMEM || ret =3D=3D -EINTR || ret =3D=3D -ERESTARTSYS) goto cleanup_free; diff --git a/include/linux/netfs.h b/include/linux/netfs.h index d0b62d53eea9..cc8d6500d059 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -247,6 +247,7 @@ struct netfs_io_request { unsigned long long submitted; /* Amount submitted for I/O so far */ unsigned long long len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ + size_t progress_at; /* Report read progress when hit this much read */ long error; /* 0 or error that occurred */ unsigned long long i_size; /* Size of the file */ unsigned long long start; /* Start position */ @@ -263,7 +264,6 @@ struct netfs_io_request { atomic_t subreq_counter; /* Next subreq->debug_index */ unsigned int nr_group_rel; /* Number of refs to release on ->group */ spinlock_t lock; /* Lock for queuing subreqs */ - unsigned char front_folio_order; /* Order (size) of front folio */ enum netfs_io_origin origin; /* Origin of the request */ bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */ refcount_t ref; diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 082cb03c6131..8ec10c076875 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -786,6 +786,27 @@ TRACE_EVENT(netfs_folioq, __print_symbolic(__entry->trace, netfs_folioq_traces)) ); =20 +TRACE_EVENT(netfs_read_progress_at, + TP_PROTO(const struct netfs_io_request *rreq), + + TP_ARGS(rreq), + + TP_STRUCT__entry( + __field(unsigned int, rreq) + __field(size_t, progress_at) + __field(size_t, cleaned_to) + ), + + TP_fast_assign( + __entry->rreq =3D rreq->debug_id; + __entry->cleaned_to =3D rreq->cleaned_to - rreq->start; + __entry->progress_at =3D rreq->progress_at; + ), + + TP_printk("R=3D%08x cln=3D%zx prg=3D%zx", + __entry->rreq, __entry->cleaned_to, __entry->progress_at) + ); + #undef EM #undef E_ #endif /* _TRACE_NETFS_H */ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 68CF73F326F for ; Mon, 24 Aug 2026 14:42:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582550; cv=none; b=n8uKbWe0zTlGEQXkgulK7WWoClENIGo7TKh4YzLCAAvdWjlAg602rCU9shK4mz4QzuGK7aX2xESvIlZlyE7rvS4P5QxJwh+D+1m1/u+zXEQvNwyg6/DidftuXNlh9oToJuJ+mq7NNcEkpgosJjMYhnCi4EJCZbyqre1kOltdVSI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582550; c=relaxed/simple; bh=apaEe1KKvi9hUsfGqQaTcMaK6cm1pCzXkI7g35TIUCs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ctE6zy2M+gJppK/fTXFhadty+7S1s7FeZ4hHFYWKoVeT2BUrvP8d6C+YFRU1kxzkBkfm+hCP04IcDLChQM9WTalFmY1UMgVSnV04ISz/egT93YHzmlk6Us0w1ZRx+P33qXEyhSBUGSn7HmRzCmjtzWuyYoppjhBGQnxb4DlPXnA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=eRCQMRss; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="eRCQMRss" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582544; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Rlx5VnIGmiAUrjbdMcmXagLWYOUIziHjNSnG9MzSIqk=; b=eRCQMRssrDH+seSddjXFLbSWCl2z4KzlPq5R/d1WYKDaN3vVMMRWAJdp8CMj8Vk58TCf5Y 0hfzEwN4UaoYdKTmy3Vpx3LlQRnkotHX5PmFNQPGlZWqXKUZebYZHE+VnMJjaHhTrPGLg/ hVFeeeJ5Vd1EOaivF+tyE1p5ESzsYHE= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-160-OyAkK77hND-PTy9pUe7Scg-1; Mon, 24 Aug 2026 10:42:17 -0400 X-MC-Unique: OyAkK77hND-PTy9pUe7Scg-1 X-Mimecast-MFC-AGG-ID: OyAkK77hND-PTy9pUe7Scg_1787582532 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 54F411955E85; Mon, 24 Aug 2026 14:42:11 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 69A6B1955F09; Mon, 24 Aug 2026 14:42:03 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Gao Xiang , bpf@vger.kernel.org Subject: [PATCH v10 03/35] cachefiles,netfs: sunset ondemand mode Date: Mon, 24 Aug 2026 15:40:56 +0100 Message-ID: <20260824144130.759997-4-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" From: Gao Xiang It was an effort to enhance fscache as a kernel cache for lazy pulling (at least according to previous Incremental FS discussion [1]) and EROFS over fscache was the in-tree user of this mode. fscache has since evolved to be netfslib-oriented, serving network filesystem inodes via the netfs library, but EROFS never acts as a network filesystem and we need to cache golden filesystem images rather than individual EROFS inodes. Since EROFS over fscache is now removed, clean up netfs/fscache/ cachefiles upstream too. [1] https://lore.kernel.org/r/CAOQ4uxi4dzxArY24YO=3D+kBCK2gGoq3Ptb8WkzCqSog= PgU_R3dQ@mail.gmail.com [dh] Fixed up comments on: https://sashiko.dev/#/patchset/20260716103030.3065561-1-dhowells%40redhat.c= om https://sashiko.dev/#/patchset/20260722130218.78958-1-dhowells%40redhat.com Signed-off-by: Gao Xiang Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-erofs@lists.ozlabs.org cc: bpf@vger.kernel.org cc: linux-fsdevel@vger.kernel.org --- .../filesystems/caching/cachefiles.rst | 179 ---- fs/cachefiles/Kconfig | 12 - fs/cachefiles/Makefile | 1 - fs/cachefiles/daemon.c | 96 +-- fs/cachefiles/interface.c | 8 - fs/cachefiles/internal.h | 134 --- fs/cachefiles/io.c | 26 +- fs/cachefiles/namei.c | 8 - fs/cachefiles/ondemand.c | 761 ------------------ include/linux/netfs.h | 9 - include/trace/events/cachefiles.h | 184 +---- include/uapi/linux/cachefiles.h | 68 -- kernel/bpf/btf.c | 7 - 13 files changed, 9 insertions(+), 1484 deletions(-) delete mode 100644 fs/cachefiles/ondemand.c delete mode 100644 include/uapi/linux/cachefiles.h diff --git a/Documentation/filesystems/caching/cachefiles.rst b/Documentati= on/filesystems/caching/cachefiles.rst index b3ccc782cb3b..adfb7d079027 100644 --- a/Documentation/filesystems/caching/cachefiles.rst +++ b/Documentation/filesystems/caching/cachefiles.rst @@ -28,8 +28,6 @@ Cache on Already Mounted Filesystem =20 (*) Debugging. =20 - (*) On-demand Read. - =20 Overview =3D=3D=3D=3D=3D=3D=3D=3D @@ -483,180 +481,3 @@ the control file. For example:: echo $((1|4|8)) >/sys/module/cachefiles/parameters/debug =20 will turn on all function entry debugging. - - -On-demand Read -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -When working in its original mode, CacheFiles serves as a local cache for a -remote networking fs - while in on-demand read mode, CacheFiles can boost = the -scenario where on-demand read semantics are needed, e.g. container image -distribution. - -The essential difference between these two modes is seen when a cache miss -occurs: In the original mode, the netfs will fetch the data from the remote -server and then write it to the cache file; in on-demand read mode, fetchi= ng -the data and writing it into the cache is delegated to a user daemon. - -``CONFIG_CACHEFILES_ONDEMAND`` should be enabled to support on-demand read= mode. - - -Protocol Communication ----------------------- - -The on-demand read mode uses a simple protocol for communication between k= ernel -and user daemon. The protocol can be modeled as:: - - kernel --[request]--> user daemon --[reply]--> kernel - -CacheFiles will send requests to the user daemon when needed. The user da= emon -should poll the devnode ('/dev/cachefiles') to check if there's a pending -request to be processed. A POLLIN event will be returned when there's a p= ending -request. - -The user daemon then reads the devnode to fetch a request to process. It = should -be noted that each read only gets one request. When it has finished proces= sing -the request, the user daemon should write the reply to the devnode. - -Each request starts with a message header of the form:: - - struct cachefiles_msg { - __u32 msg_id; - __u32 opcode; - __u32 len; - __u32 object_id; - __u8 data[]; - }; - -where: - - * ``msg_id`` is a unique ID identifying this request among all pending - requests. - - * ``opcode`` indicates the type of this request. - - * ``object_id`` is a unique ID identifying the cache file operated on. - - * ``data`` indicates the payload of this request. - - * ``len`` indicates the whole length of this request, including the - header and following type-specific payload. - - -Turning on On-demand Mode -------------------------- - -An optional parameter becomes available to the "bind" command:: - - bind [ondemand] - -When the "bind" command is given no argument, it defaults to the original = mode. -When it is given the "ondemand" argument, i.e. "bind ondemand", on-demand = read -mode will be enabled. - - -The OPEN Request ----------------- - -When the netfs opens a cache file for the first time, a request with the -CACHEFILES_OP_OPEN opcode, a.k.a an OPEN request will be sent to the user -daemon. The payload format is of the form:: - - struct cachefiles_open { - __u32 volume_key_size; - __u32 cookie_key_size; - __u32 fd; - __u32 flags; - __u8 data[]; - }; - -where: - - * ``data`` contains the volume_key followed directly by the cookie_key. - The volume key is a NUL-terminated string; the cookie key is binary - data. - - * ``volume_key_size`` indicates the size of the volume key in bytes. - - * ``cookie_key_size`` indicates the size of the cookie key in bytes. - - * ``fd`` indicates an anonymous fd referring to the cache file, through - which the user daemon can perform write/llseek file operations on the - cache file. - - -The user daemon can use the given (volume_key, cookie_key) pair to disting= uish -the requested cache file. With the given anonymous fd, the user daemon can -fetch the data and write it to the cache file in the background, even when -kernel has not triggered a cache miss yet. - -Be noted that each cache file has a unique object_id, while it may have mu= ltiple -anonymous fds. The user daemon may duplicate anonymous fds from the initi= al -anonymous fd indicated by the @fd field through dup(). Thus each object_i= d can -be mapped to multiple anonymous fds, while the usr daemon itself needs to -maintain the mapping. - -When implementing a user daemon, please be careful of RLIMIT_NOFILE, -``/proc/sys/fs/nr_open`` and ``/proc/sys/fs/file-max``. Typically these n= eedn't -be huge since they're related to the number of open device blobs rather th= an -open files of each individual filesystem. - -The user daemon should reply the OPEN request by issuing a "copen" (comple= te -open) command on the devnode:: - - copen , - -where: - - * ``msg_id`` must match the msg_id field of the OPEN request. - - * When >=3D 0, ``cache_size`` indicates the size of the cache file; - when < 0, ``cache_size`` indicates any error code encountered by the - user daemon. - - -The CLOSE Request ------------------ - -When a cookie withdrawn, a CLOSE request (opcode CACHEFILES_OP_CLOSE) will= be -sent to the user daemon. This tells the user daemon to close all anonymou= s fds -associated with the given object_id. The CLOSE request has no extra paylo= ad, -and shouldn't be replied. - - -The READ Request ----------------- - -When a cache miss is encountered in on-demand read mode, CacheFiles will s= end a -READ request (opcode CACHEFILES_OP_READ) to the user daemon. This tells th= e user -daemon to fetch the contents of the requested file range. The payload is = of the -form:: - - struct cachefiles_read { - __u64 off; - __u64 len; - }; - -where: - - * ``off`` indicates the starting offset of the requested file range. - - * ``len`` indicates the length of the requested file range. - - -When it receives a READ request, the user daemon should fetch the requeste= d data -and write it to the cache file identified by object_id. - -When it has finished processing the READ request, the user daemon should r= eply -by using the CACHEFILES_IOC_READ_COMPLETE ioctl on one of the anonymous fds -associated with the object_id given in the READ request. The ioctl is of = the -form:: - - ioctl(fd, CACHEFILES_IOC_READ_COMPLETE, msg_id); - -where: - - * ``fd`` is one of the anonymous fds associated with the object_id - given. - - * ``msg_id`` must match the msg_id field of the READ request. diff --git a/fs/cachefiles/Kconfig b/fs/cachefiles/Kconfig index c5a070550ee3..afb25b6af5aa 100644 --- a/fs/cachefiles/Kconfig +++ b/fs/cachefiles/Kconfig @@ -26,15 +26,3 @@ config CACHEFILES_ERROR_INJECTION help This permits error injection to be enabled in cachefiles whilst a cache is in service. - -config CACHEFILES_ONDEMAND - bool "Support for on-demand read" - depends on CACHEFILES - default n - help - This permits userspace to enable the cachefiles on-demand read mode. - In this mode, when a cache miss occurs, responsibility for fetching - the data lies with the cachefiles backend instead of with the netfs - and is delegated to userspace. - - If unsure, say N. diff --git a/fs/cachefiles/Makefile b/fs/cachefiles/Makefile index c37a7a9af10b..16d811f1a2fa 100644 --- a/fs/cachefiles/Makefile +++ b/fs/cachefiles/Makefile @@ -16,6 +16,5 @@ cachefiles-y :=3D \ xattr.o =20 cachefiles-$(CONFIG_CACHEFILES_ERROR_INJECTION) +=3D error_inject.o -cachefiles-$(CONFIG_CACHEFILES_ONDEMAND) +=3D ondemand.o =20 obj-$(CONFIG_CACHEFILES) :=3D cachefiles.o diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c index 4117b145ac94..1a66e0af2837 100644 --- a/fs/cachefiles/daemon.c +++ b/fs/cachefiles/daemon.c @@ -76,10 +76,6 @@ static const struct cachefiles_daemon_cmd cachefiles_dae= mon_cmds[] =3D { { "inuse", cachefiles_daemon_inuse }, { "secctx", cachefiles_daemon_secctx }, { "tag", cachefiles_daemon_tag }, -#ifdef CONFIG_CACHEFILES_ONDEMAND - { "copen", cachefiles_ondemand_copen }, - { "restore", cachefiles_ondemand_restore }, -#endif { "", NULL } }; =20 @@ -113,9 +109,6 @@ static int cachefiles_daemon_open(struct inode *inode, = struct file *file) INIT_LIST_HEAD(&cache->volumes); INIT_LIST_HEAD(&cache->object_list); spin_lock_init(&cache->object_list_lock); - refcount_set(&cache->unbind_pincount, 1); - xa_init_flags(&cache->reqs, XA_FLAGS_ALLOC); - xa_init_flags(&cache->ondemand_ids, XA_FLAGS_ALLOC1); =20 /* set default caching limits * - limit at 1% free space and/or free files @@ -134,54 +127,6 @@ static int cachefiles_daemon_open(struct inode *inode,= struct file *file) return 0; } =20 -void cachefiles_flush_reqs(struct cachefiles_cache *cache) -{ - struct xarray *xa =3D &cache->reqs; - struct cachefiles_req *req; - unsigned long index; - - /* - * Make sure the following two operations won't be reordered. - * 1) set CACHEFILES_DEAD bit - * 2) flush requests in the xarray - * Otherwise the request may be enqueued after xarray has been - * flushed, leaving the orphan request never being completed. - * - * CPU 1 CPU 2 - * =3D=3D=3D=3D=3D =3D=3D=3D=3D=3D - * flush requests in the xarray - * test CACHEFILES_DEAD bit - * enqueue the request - * set CACHEFILES_DEAD bit - */ - smp_mb(); - - xa_lock(xa); - xa_for_each(xa, index, req) { - req->error =3D -EIO; - complete(&req->done); - __xa_erase(xa, index); - } - xa_unlock(xa); - - xa_destroy(&cache->reqs); - xa_destroy(&cache->ondemand_ids); -} - -void cachefiles_put_unbind_pincount(struct cachefiles_cache *cache) -{ - if (refcount_dec_and_test(&cache->unbind_pincount)) { - cachefiles_daemon_unbind(cache); - cachefiles_open =3D 0; - kfree(cache); - } -} - -void cachefiles_get_unbind_pincount(struct cachefiles_cache *cache) -{ - refcount_inc(&cache->unbind_pincount); -} - /* * Release a cache. */ @@ -194,16 +139,14 @@ static int cachefiles_daemon_release(struct inode *in= ode, struct file *file) ASSERT(cache); =20 set_bit(CACHEFILES_DEAD, &cache->flags); - - if (cachefiles_in_ondemand_mode(cache)) - cachefiles_flush_reqs(cache); + cachefiles_daemon_unbind(cache); =20 /* clean up the control file interface */ cache->cachefilesd =3D NULL; file->private_data =3D NULL; + cachefiles_open =3D 0; =20 - cachefiles_put_unbind_pincount(cache); - + kfree(cache); _leave(""); return 0; } @@ -266,10 +209,7 @@ static ssize_t cachefiles_daemon_read(struct file *fil= e, char __user *_buffer, if (!test_bit(CACHEFILES_READY, &cache->flags)) return 0; =20 - if (cachefiles_in_ondemand_mode(cache)) - return cachefiles_ondemand_daemon_read(cache, _buffer, buflen); - else - return cachefiles_do_daemon_read(cache, _buffer, buflen); + return cachefiles_do_daemon_read(cache, _buffer, buflen); } =20 /* @@ -358,28 +298,13 @@ static __poll_t cachefiles_daemon_poll(struct file *f= ile, struct poll_table_struct *poll) { struct cachefiles_cache *cache =3D file->private_data; - XA_STATE(xas, &cache->reqs, 0); - struct cachefiles_req *req; __poll_t mask; =20 poll_wait(file, &cache->daemon_pollwq, poll); mask =3D 0; =20 - if (cachefiles_in_ondemand_mode(cache)) { - if (!xa_empty(&cache->reqs)) { - xas_lock(&xas); - xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) { - if (!cachefiles_ondemand_is_reopening_read(req)) { - mask |=3D EPOLLIN; - break; - } - } - xas_unlock(&xas); - } - } else { - if (test_bit(CACHEFILES_STATE_CHANGED, &cache->flags)) - mask |=3D EPOLLIN; - } + if (test_bit(CACHEFILES_STATE_CHANGED, &cache->flags)) + mask |=3D EPOLLIN; =20 if (test_bit(CACHEFILES_CULLING, &cache->flags)) mask |=3D EPOLLOUT; @@ -779,14 +704,7 @@ static int cachefiles_daemon_bind(struct cachefiles_ca= che *cache, char *args) return -EBUSY; } =20 - if (IS_ENABLED(CONFIG_CACHEFILES_ONDEMAND)) { - if (!strcmp(args, "ondemand")) { - set_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags); - } else if (*args) { - pr_err("Invalid argument to the 'bind' command\n"); - return -EINVAL; - } - } else if (*args) { + if (*args) { pr_err("'bind' command doesn't take an argument\n"); return -EINVAL; } diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index a08250d244ea..50a000310a8c 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -32,11 +32,6 @@ struct cachefiles_object *cachefiles_alloc_object(struct= fscache_cookie *cookie) if (!object) return NULL; =20 - if (cachefiles_ondemand_init_obj_info(object, volume)) { - kmem_cache_free(cachefiles_object_jar, object); - return NULL; - } - refcount_set(&object->ref, 1); =20 spin_lock_init(&object->lock); @@ -94,7 +89,6 @@ void cachefiles_put_object(struct cachefiles_object *obje= ct, ASSERTCMP(object->file, =3D=3D, NULL); =20 kfree(object->d_name); - cachefiles_ondemand_deinit_obj_info(object); cache =3D object->volume->cache->cache; fscache_put_cookie(object->cookie, fscache_cookie_put_object); object->cookie =3D NULL; @@ -374,8 +368,6 @@ static void cachefiles_withdraw_cookie(struct fscache_c= ookie *cookie) spin_unlock(&cache->object_list_lock); } =20 - cachefiles_ondemand_clean_object(object); - if (object->file) { cachefiles_begin_secure(cache, &saved_cred); cachefiles_clean_up_object(object, cache); diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index b62cd3e9a18e..c93324e0f98c 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -15,8 +15,6 @@ #include #include #include -#include -#include =20 #define CACHEFILES_DIO_BLOCK_SIZE 4096 =20 @@ -44,21 +42,6 @@ struct cachefiles_volume { struct dentry *fanout[256]; /* Fanout subdirs */ }; =20 -enum cachefiles_object_state { - CACHEFILES_ONDEMAND_OBJSTATE_CLOSE, /* Anonymous fd closed by daemon or i= nitial state */ - CACHEFILES_ONDEMAND_OBJSTATE_OPEN, /* Anonymous fd associated with object= is available */ - CACHEFILES_ONDEMAND_OBJSTATE_REOPENING, /* Object that was closed and is = being reopened. */ - CACHEFILES_ONDEMAND_OBJSTATE_DROPPING, /* Object is being dropped. */ -}; - -struct cachefiles_ondemand_info { - struct work_struct ondemand_work; - int ondemand_id; - enum cachefiles_object_state state; - struct cachefiles_object *object; - spinlock_t lock; -}; - /* * Backing file state. */ @@ -74,13 +57,8 @@ struct cachefiles_object { enum cachefiles_content content_info:8; /* Info about content presence */ unsigned long flags; #define CACHEFILES_OBJECT_USING_TMPFILE 0 /* Have an unlinked tmpfile */ -#ifdef CONFIG_CACHEFILES_ONDEMAND - struct cachefiles_ondemand_info *ondemand; -#endif }; =20 -#define CACHEFILES_ONDEMAND_ID_CLOSED -1 - /* * Cache files cache definition */ @@ -119,35 +97,12 @@ struct cachefiles_cache { #define CACHEFILES_DEAD 1 /* T if cache dead */ #define CACHEFILES_CULLING 2 /* T if cull engaged */ #define CACHEFILES_STATE_CHANGED 3 /* T if state changed (poll trigger) */ -#define CACHEFILES_ONDEMAND_MODE 4 /* T if in on-demand read mode */ char *rootdirname; /* name of cache root directory */ char *tag; /* cache binding tag */ - refcount_t unbind_pincount;/* refcount to do daemon unbind */ - struct xarray reqs; /* xarray of pending on-demand requests */ - unsigned long req_id_next; - struct xarray ondemand_ids; /* xarray for ondemand_id allocation */ - u32 ondemand_id_next; - u32 msg_id_next; u32 secid; /* LSM security id */ bool have_secid; /* whether "secid" was set */ }; =20 -static inline bool cachefiles_in_ondemand_mode(struct cachefiles_cache *ca= che) -{ - return IS_ENABLED(CONFIG_CACHEFILES_ONDEMAND) && - test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags); -} - -struct cachefiles_req { - struct cachefiles_object *object; - struct completion done; - refcount_t ref; - int error; - struct cachefiles_msg msg; -}; - -#define CACHEFILES_REQ_NEW XA_MARK_1 - #include =20 static inline @@ -190,9 +145,6 @@ extern int cachefiles_has_space(struct cachefiles_cache= *cache, * daemon.c */ extern const struct file_operations cachefiles_daemon_fops; -extern void cachefiles_flush_reqs(struct cachefiles_cache *cache); -extern void cachefiles_get_unbind_pincount(struct cachefiles_cache *cache); -extern void cachefiles_put_unbind_pincount(struct cachefiles_cache *cache); =20 /* * error_inject.c @@ -298,90 +250,6 @@ extern struct file *cachefiles_create_tmpfile(struct c= achefiles_object *object); extern bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache, struct cachefiles_object *object); =20 -/* - * ondemand.c - */ -#ifdef CONFIG_CACHEFILES_ONDEMAND -extern ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *ca= che, - char __user *_buffer, size_t buflen); - -extern int cachefiles_ondemand_copen(struct cachefiles_cache *cache, - char *args); - -extern int cachefiles_ondemand_restore(struct cachefiles_cache *cache, - char *args); - -extern int cachefiles_ondemand_init_object(struct cachefiles_object *objec= t); -extern void cachefiles_ondemand_clean_object(struct cachefiles_object *obj= ect); - -extern int cachefiles_ondemand_read(struct cachefiles_object *object, - loff_t pos, size_t len); - -extern int cachefiles_ondemand_init_obj_info(struct cachefiles_object *obj, - struct cachefiles_volume *volume); -extern void cachefiles_ondemand_deinit_obj_info(struct cachefiles_object *= obj); - -#define CACHEFILES_OBJECT_STATE_FUNCS(_state, _STATE) \ -static inline bool \ -cachefiles_ondemand_object_is_##_state(const struct cachefiles_object *obj= ect) \ -{ \ - return object->ondemand->state =3D=3D CACHEFILES_ONDEMAND_OBJSTATE_##_STA= TE; \ -} \ - \ -static inline void \ -cachefiles_ondemand_set_object_##_state(struct cachefiles_object *object) \ -{ \ - object->ondemand->state =3D CACHEFILES_ONDEMAND_OBJSTATE_##_STATE; \ -} - -CACHEFILES_OBJECT_STATE_FUNCS(open, OPEN); -CACHEFILES_OBJECT_STATE_FUNCS(close, CLOSE); -CACHEFILES_OBJECT_STATE_FUNCS(reopening, REOPENING); -CACHEFILES_OBJECT_STATE_FUNCS(dropping, DROPPING); - -static inline bool cachefiles_ondemand_is_reopening_read(struct cachefiles= _req *req) -{ - return cachefiles_ondemand_object_is_reopening(req->object) && - req->msg.opcode =3D=3D CACHEFILES_OP_READ; -} - -#else -static inline ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_ca= che *cache, - char __user *_buffer, size_t buflen) -{ - return -EOPNOTSUPP; -} - -static inline int cachefiles_ondemand_init_object(struct cachefiles_object= *object) -{ - return 0; -} - -static inline void cachefiles_ondemand_clean_object(struct cachefiles_obje= ct *object) -{ -} - -static inline int cachefiles_ondemand_read(struct cachefiles_object *objec= t, - loff_t pos, size_t len) -{ - return -EOPNOTSUPP; -} - -static inline int cachefiles_ondemand_init_obj_info(struct cachefiles_obje= ct *obj, - struct cachefiles_volume *volume) -{ - return 0; -} -static inline void cachefiles_ondemand_deinit_obj_info(struct cachefiles_o= bject *obj) -{ -} - -static inline bool cachefiles_ondemand_is_reopening_read(struct cachefiles= _req *req) -{ - return false; -} -#endif - /* * security.c */ @@ -430,8 +298,6 @@ do { \ pr_err("I/O Error: " FMT"\n", ##__VA_ARGS__); \ fscache_io_error((___cache)->cache); \ set_bit(CACHEFILES_DEAD, &(___cache)->flags); \ - if (cachefiles_in_ondemand_mode(___cache)) \ - cachefiles_flush_reqs(___cache); \ } while (0) =20 #define cachefiles_io_error_obj(object, FMT, ...) \ diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index d879b80a0bed..9540ec25b3cb 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -390,7 +390,6 @@ cachefiles_do_prepare_read(struct netfs_cache_resources= *cres, size_t len =3D *_len; loff_t off, to; ino_t ino =3D file ? file_inode(file)->i_ino : 0; - int rc; =20 _enter("%zx @%llx/%llx", len, start, i_size); =20 @@ -403,8 +402,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources= *cres, if (test_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags)) { __set_bit(NETFS_SREQ_COPY_TO_CACHE, _flags); why =3D cachefiles_trace_read_no_data; - if (!test_bit(NETFS_SREQ_ONDEMAND, _flags)) - goto out_no_object; + goto out_no_object; } =20 /* The object and the file may be being created in the background. */ @@ -421,7 +419,6 @@ cachefiles_do_prepare_read(struct netfs_cache_resources= *cres, object =3D cachefiles_cres_object(cres); cache =3D object->volume->cache; cachefiles_begin_secure(cache, &saved_cred); -retry: off =3D cachefiles_inject_read_error(); if (off =3D=3D 0) off =3D vfs_llseek(file, start, SEEK_DATA); @@ -474,14 +471,6 @@ cachefiles_do_prepare_read(struct netfs_cache_resource= s *cres, =20 download_and_store: __set_bit(NETFS_SREQ_COPY_TO_CACHE, _flags); - if (test_bit(NETFS_SREQ_ONDEMAND, _flags)) { - rc =3D cachefiles_ondemand_read(object, start, len); - if (!rc) { - __clear_bit(NETFS_SREQ_ONDEMAND, _flags); - goto retry; - } - ret =3D NETFS_INVALID_READ; - } out: cachefiles_end_secure(cache, saved_cred); out_no_object: @@ -501,18 +490,6 @@ static enum netfs_io_source cachefiles_prepare_read(st= ruct netfs_io_subrequest * &subreq->flags, subreq->rreq->inode->i_ino); } =20 -/* - * Prepare an on-demand read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ -static enum netfs_io_source -cachefiles_prepare_ondemand_read(struct netfs_cache_resources *cres, - loff_t start, size_t *_len, loff_t i_size, - unsigned long *_flags, ino_t ino) -{ - return cachefiles_do_prepare_read(cres, start, _len, i_size, _flags, ino); -} - /* * Prepare for a write to occur. */ @@ -731,7 +708,6 @@ static const struct netfs_cache_ops cachefiles_netfs_ca= che_ops =3D { .prepare_read =3D cachefiles_prepare_read, .prepare_write =3D cachefiles_prepare_write, .prepare_write_subreq =3D cachefiles_prepare_write_subreq, - .prepare_ondemand_read =3D cachefiles_prepare_ondemand_read, .query_occupancy =3D cachefiles_query_occupancy, }; =20 diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 8a9f6be15828..88955249a1a6 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -442,10 +442,6 @@ struct file *cachefiles_create_tmpfile(struct cachefil= es_object *object) if (!cachefiles_mark_inode_in_use(object, file_inode(file))) WARN_ON(1); =20 - ret =3D cachefiles_ondemand_init_object(object); - if (ret < 0) - goto err_unuse; - ni_size =3D object->cookie->object_size; ni_size =3D round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); =20 @@ -546,10 +542,6 @@ static bool cachefiles_open_file(struct cachefiles_obj= ect *object, } _debug("file -> %pd positive", dentry); =20 - ret =3D cachefiles_ondemand_init_object(object); - if (ret < 0) - goto error_fput; - ret =3D cachefiles_check_auxdata(object, file); if (ret < 0) goto check_failed; diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c deleted file mode 100644 index 0849eaf583cd..000000000000 --- a/fs/cachefiles/ondemand.c +++ /dev/null @@ -1,761 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "internal.h" - -struct ondemand_anon_file { - struct file *file; - int fd; -}; - -static inline void cachefiles_req_put(struct cachefiles_req *req) -{ - if (refcount_dec_and_test(&req->ref)) - kfree(req); -} - -static int cachefiles_ondemand_fd_release(struct inode *inode, - struct file *file) -{ - struct cachefiles_object *object =3D file->private_data; - struct cachefiles_cache *cache; - struct cachefiles_ondemand_info *info; - int object_id; - struct cachefiles_req *req; - XA_STATE(xas, NULL, 0); - - if (!object) - return 0; - - info =3D object->ondemand; - cache =3D object->volume->cache; - xas.xa =3D &cache->reqs; - - xa_lock(&cache->reqs); - spin_lock(&info->lock); - object_id =3D info->ondemand_id; - info->ondemand_id =3D CACHEFILES_ONDEMAND_ID_CLOSED; - cachefiles_ondemand_set_object_close(object); - spin_unlock(&info->lock); - - /* Only flush CACHEFILES_REQ_NEW marked req to avoid race with daemon_rea= d */ - xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) { - if (req->msg.object_id =3D=3D object_id && - req->msg.opcode =3D=3D CACHEFILES_OP_CLOSE) { - complete(&req->done); - xas_store(&xas, NULL); - } - } - xa_unlock(&cache->reqs); - - xa_erase(&cache->ondemand_ids, object_id); - trace_cachefiles_ondemand_fd_release(object, object_id); - cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd); - cachefiles_put_unbind_pincount(cache); - return 0; -} - -static ssize_t cachefiles_ondemand_fd_write_iter(struct kiocb *kiocb, - struct iov_iter *iter) -{ - struct cachefiles_object *object =3D kiocb->ki_filp->private_data; - struct cachefiles_cache *cache =3D object->volume->cache; - struct file *file; - size_t len =3D iter->count, aligned_len =3D len; - loff_t pos =3D kiocb->ki_pos; - const struct cred *saved_cred; - int ret; - - spin_lock(&object->lock); - file =3D object->file; - if (!file) { - spin_unlock(&object->lock); - return -ENOBUFS; - } - get_file(file); - spin_unlock(&object->lock); - - cachefiles_begin_secure(cache, &saved_cred); - ret =3D __cachefiles_prepare_write(object, file, &pos, &aligned_len, len,= true); - cachefiles_end_secure(cache, saved_cred); - if (ret < 0) - goto out; - - trace_cachefiles_ondemand_fd_write(object, file_inode(file), pos, len); - ret =3D __cachefiles_write(object, file, pos, iter, NULL, NULL); - if (ret > 0) - kiocb->ki_pos +=3D ret; - -out: - fput(file); - return ret; -} - -static loff_t cachefiles_ondemand_fd_llseek(struct file *filp, loff_t pos, - int whence) -{ - struct cachefiles_object *object =3D filp->private_data; - struct file *file; - loff_t ret; - - spin_lock(&object->lock); - file =3D object->file; - if (!file) { - spin_unlock(&object->lock); - return -ENOBUFS; - } - get_file(file); - spin_unlock(&object->lock); - - ret =3D vfs_llseek(file, pos, whence); - fput(file); - - return ret; -} - -static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int i= octl, - unsigned long id) -{ - struct cachefiles_object *object =3D filp->private_data; - struct cachefiles_cache *cache =3D object->volume->cache; - struct cachefiles_req *req; - XA_STATE(xas, &cache->reqs, id); - - if (ioctl !=3D CACHEFILES_IOC_READ_COMPLETE) - return -EINVAL; - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return -EOPNOTSUPP; - - xa_lock(&cache->reqs); - req =3D xas_load(&xas); - if (!req || req->msg.opcode !=3D CACHEFILES_OP_READ || - req->object !=3D object) { - xa_unlock(&cache->reqs); - return -EINVAL; - } - xas_store(&xas, NULL); - xa_unlock(&cache->reqs); - - trace_cachefiles_ondemand_cread(object, id); - complete(&req->done); - return 0; -} - -static const struct file_operations cachefiles_ondemand_fd_fops =3D { - .owner =3D THIS_MODULE, - .release =3D cachefiles_ondemand_fd_release, - .write_iter =3D cachefiles_ondemand_fd_write_iter, - .llseek =3D cachefiles_ondemand_fd_llseek, - .unlocked_ioctl =3D cachefiles_ondemand_fd_ioctl, -}; - -/* - * OPEN request Completion (copen) - * - command: "copen ," - * indicates the object size if >=3D0, error code if negati= ve - */ -int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args) -{ - struct cachefiles_req *req; - struct fscache_cookie *cookie; - struct cachefiles_ondemand_info *info; - char *pid, *psize; - unsigned long id; - long size; - int ret; - XA_STATE(xas, &cache->reqs, 0); - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return -EOPNOTSUPP; - - if (!*args) { - pr_err("Empty id specified\n"); - return -EINVAL; - } - - pid =3D args; - psize =3D strchr(args, ','); - if (!psize) { - pr_err("Cache size is not specified\n"); - return -EINVAL; - } - - *psize =3D 0; - psize++; - - ret =3D kstrtoul(pid, 0, &id); - if (ret) - return ret; - - xa_lock(&cache->reqs); - xas.xa_index =3D id; - req =3D xas_load(&xas); - if (!req || req->msg.opcode !=3D CACHEFILES_OP_OPEN || - !req->object->ondemand->ondemand_id) { - xa_unlock(&cache->reqs); - return -EINVAL; - } - xas_store(&xas, NULL); - xa_unlock(&cache->reqs); - - info =3D req->object->ondemand; - /* fail OPEN request if copen format is invalid */ - ret =3D kstrtol(psize, 0, &size); - if (ret) { - req->error =3D ret; - goto out; - } - - /* fail OPEN request if daemon reports an error */ - if (size < 0) { - if (!IS_ERR_VALUE(size)) { - req->error =3D -EINVAL; - ret =3D -EINVAL; - } else { - req->error =3D size; - ret =3D 0; - } - goto out; - } - - spin_lock(&info->lock); - /* - * The anonymous fd was closed before copen ? Fail the request. - * - * t1 | t2 - * --------------------------------------------------------- - * cachefiles_ondemand_copen - * req =3D xa_erase(&cache->reqs, id) - * // Anon fd is maliciously closed. - * cachefiles_ondemand_fd_release - * xa_lock(&cache->reqs) - * cachefiles_ondemand_set_object_close(object) - * xa_unlock(&cache->reqs) - * cachefiles_ondemand_set_object_open - * // No one will ever close it again. - * cachefiles_ondemand_daemon_read - * cachefiles_ondemand_select_req - * - * Get a read req but its fd is already closed. The daemon can't - * issue a cread ioctl with an closed fd, then hung. - */ - if (info->ondemand_id =3D=3D CACHEFILES_ONDEMAND_ID_CLOSED) { - spin_unlock(&info->lock); - req->error =3D -EBADFD; - goto out; - } - cookie =3D req->object->cookie; - cookie->object_size =3D size; - if (size) - clear_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags); - else - set_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags); - trace_cachefiles_ondemand_copen(req->object, id, size); - - cachefiles_ondemand_set_object_open(req->object); - spin_unlock(&info->lock); - wake_up_all(&cache->daemon_pollwq); - -out: - spin_lock(&info->lock); - /* Need to set object close to avoid reopen status continuing */ - if (info->ondemand_id =3D=3D CACHEFILES_ONDEMAND_ID_CLOSED) - cachefiles_ondemand_set_object_close(req->object); - spin_unlock(&info->lock); - complete(&req->done); - return ret; -} - -int cachefiles_ondemand_restore(struct cachefiles_cache *cache, char *args) -{ - struct cachefiles_req *req; - - XA_STATE(xas, &cache->reqs, 0); - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return -EOPNOTSUPP; - - /* - * Reset the requests to CACHEFILES_REQ_NEW state, so that the - * requests have been processed halfway before the crash of the - * user daemon could be reprocessed after the recovery. - */ - xas_lock(&xas); - xas_for_each(&xas, req, ULONG_MAX) - xas_set_mark(&xas, CACHEFILES_REQ_NEW); - xas_unlock(&xas); - - wake_up_all(&cache->daemon_pollwq); - return 0; -} - -static int cachefiles_ondemand_get_fd(struct cachefiles_req *req, - struct ondemand_anon_file *anon_file) -{ - struct cachefiles_object *object; - struct cachefiles_cache *cache; - struct cachefiles_open *load; - u32 object_id; - int ret; - - object =3D cachefiles_grab_object(req->object, - cachefiles_obj_get_ondemand_fd); - cache =3D object->volume->cache; - - ret =3D xa_alloc_cyclic(&cache->ondemand_ids, &object_id, NULL, - XA_LIMIT(1, INT_MAX), - &cache->ondemand_id_next, GFP_KERNEL); - if (ret < 0) - goto err; - - anon_file->fd =3D get_unused_fd_flags(O_WRONLY); - if (anon_file->fd < 0) { - ret =3D anon_file->fd; - goto err_free_id; - } - - anon_file->file =3D anon_inode_getfile_fmode("[cachefiles]", - &cachefiles_ondemand_fd_fops, object, - O_WRONLY, FMODE_PWRITE | FMODE_LSEEK); - if (IS_ERR(anon_file->file)) { - ret =3D PTR_ERR(anon_file->file); - goto err_put_fd; - } - - spin_lock(&object->ondemand->lock); - if (object->ondemand->ondemand_id > 0) { - spin_unlock(&object->ondemand->lock); - /* Pair with check in cachefiles_ondemand_fd_release(). */ - anon_file->file->private_data =3D NULL; - ret =3D -EEXIST; - goto err_put_file; - } - - load =3D (void *)req->msg.data; - load->fd =3D anon_file->fd; - object->ondemand->ondemand_id =3D object_id; - spin_unlock(&object->ondemand->lock); - - cachefiles_get_unbind_pincount(cache); - trace_cachefiles_ondemand_open(object, &req->msg, load); - return 0; - -err_put_file: - fput(anon_file->file); - anon_file->file =3D NULL; -err_put_fd: - put_unused_fd(anon_file->fd); - anon_file->fd =3D ret; -err_free_id: - xa_erase(&cache->ondemand_ids, object_id); -err: - spin_lock(&object->ondemand->lock); - /* Avoid marking an opened object as closed. */ - if (object->ondemand->ondemand_id <=3D 0) - cachefiles_ondemand_set_object_close(object); - spin_unlock(&object->ondemand->lock); - cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd); - return ret; -} - -static void ondemand_object_worker(struct work_struct *work) -{ - struct cachefiles_ondemand_info *info =3D - container_of(work, struct cachefiles_ondemand_info, ondemand_work); - - cachefiles_ondemand_init_object(info->object); -} - -/* - * If there are any inflight or subsequent READ requests on the - * closed object, reopen it. - * Skip read requests whose related object is reopening. - */ -static struct cachefiles_req *cachefiles_ondemand_select_req(struct xa_sta= te *xas, - unsigned long xa_max) -{ - struct cachefiles_req *req; - struct cachefiles_object *object; - struct cachefiles_ondemand_info *info; - - xas_for_each_marked(xas, req, xa_max, CACHEFILES_REQ_NEW) { - if (req->msg.opcode !=3D CACHEFILES_OP_READ) - return req; - object =3D req->object; - info =3D object->ondemand; - if (cachefiles_ondemand_object_is_close(object)) { - cachefiles_ondemand_set_object_reopening(object); - queue_work(fscache_wq, &info->ondemand_work); - continue; - } - if (cachefiles_ondemand_object_is_reopening(object)) - continue; - return req; - } - return NULL; -} - -static inline bool cachefiles_ondemand_finish_req(struct cachefiles_req *r= eq, - struct xa_state *xas, int err) -{ - if (unlikely(!xas || !req)) - return false; - - if (xa_cmpxchg(xas->xa, xas->xa_index, req, NULL, 0) !=3D req) - return false; - - req->error =3D err; - complete(&req->done); - return true; -} - -ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache, - char __user *_buffer, size_t buflen) -{ - struct cachefiles_req *req; - struct cachefiles_msg *msg; - size_t n; - int ret =3D 0; - struct ondemand_anon_file anon_file; - XA_STATE(xas, &cache->reqs, cache->req_id_next); - - xa_lock(&cache->reqs); - /* - * Cyclically search for a request that has not ever been processed, - * to prevent requests from being processed repeatedly, and make - * request distribution fair. - */ - req =3D cachefiles_ondemand_select_req(&xas, ULONG_MAX); - if (!req && cache->req_id_next > 0) { - xas_set(&xas, 0); - req =3D cachefiles_ondemand_select_req(&xas, cache->req_id_next - 1); - } - if (!req) { - xa_unlock(&cache->reqs); - return 0; - } - - msg =3D &req->msg; - n =3D msg->len; - - if (n > buflen) { - xa_unlock(&cache->reqs); - return -EMSGSIZE; - } - - xas_clear_mark(&xas, CACHEFILES_REQ_NEW); - cache->req_id_next =3D xas.xa_index + 1; - refcount_inc(&req->ref); - cachefiles_grab_object(req->object, cachefiles_obj_get_read_req); - xa_unlock(&cache->reqs); - - if (msg->opcode =3D=3D CACHEFILES_OP_OPEN) { - ret =3D cachefiles_ondemand_get_fd(req, &anon_file); - if (ret) - goto out; - } - - msg->msg_id =3D xas.xa_index; - msg->object_id =3D req->object->ondemand->ondemand_id; - - if (copy_to_user(_buffer, msg, n) !=3D 0) - ret =3D -EFAULT; - - if (msg->opcode =3D=3D CACHEFILES_OP_OPEN) { - if (ret < 0) { - fput(anon_file.file); - put_unused_fd(anon_file.fd); - goto out; - } - fd_install(anon_file.fd, anon_file.file); - } -out: - cachefiles_put_object(req->object, cachefiles_obj_put_read_req); - /* Remove error request and CLOSE request has no reply */ - if (ret || msg->opcode =3D=3D CACHEFILES_OP_CLOSE) - cachefiles_ondemand_finish_req(req, &xas, ret); - cachefiles_req_put(req); - return ret ? ret : n; -} - -typedef int (*init_req_fn)(struct cachefiles_req *req, void *private); - -static int cachefiles_ondemand_send_req(struct cachefiles_object *object, - enum cachefiles_opcode opcode, - size_t data_len, - init_req_fn init_req, - void *private) -{ - struct cachefiles_cache *cache =3D object->volume->cache; - struct cachefiles_req *req =3D NULL; - XA_STATE(xas, &cache->reqs, 0); - int ret; - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return 0; - - if (test_bit(CACHEFILES_DEAD, &cache->flags)) { - ret =3D -EIO; - goto out; - } - - req =3D kzalloc(sizeof(*req) + data_len, GFP_KERNEL); - if (!req) { - ret =3D -ENOMEM; - goto out; - } - - refcount_set(&req->ref, 1); - req->object =3D object; - init_completion(&req->done); - req->msg.opcode =3D opcode; - req->msg.len =3D sizeof(struct cachefiles_msg) + data_len; - - ret =3D init_req(req, private); - if (ret) - goto out; - - do { - /* - * Stop enqueuing the request when daemon is dying. The - * following two operations need to be atomic as a whole. - * 1) check cache state, and - * 2) enqueue request if cache is alive. - * Otherwise the request may be enqueued after xarray has been - * flushed, leaving the orphan request never being completed. - * - * CPU 1 CPU 2 - * =3D=3D=3D=3D=3D =3D=3D=3D=3D=3D - * test CACHEFILES_DEAD bit - * set CACHEFILES_DEAD bit - * flush requests in the xarray - * enqueue the request - */ - xas_lock(&xas); - - if (test_bit(CACHEFILES_DEAD, &cache->flags) || - cachefiles_ondemand_object_is_dropping(object)) { - xas_unlock(&xas); - ret =3D -EIO; - goto out; - } - - /* coupled with the barrier in cachefiles_flush_reqs() */ - smp_mb(); - - if (opcode =3D=3D CACHEFILES_OP_CLOSE && - !cachefiles_ondemand_object_is_open(object)) { - WARN_ON_ONCE(object->ondemand->ondemand_id =3D=3D 0); - xas_unlock(&xas); - ret =3D -EIO; - goto out; - } - - /* - * Cyclically find a free xas to avoid msg_id reuse that would - * cause the daemon to successfully copen a stale msg_id. - */ - xas.xa_index =3D cache->msg_id_next; - xas_find_marked(&xas, UINT_MAX, XA_FREE_MARK); - if (xas.xa_node =3D=3D XAS_RESTART) { - xas.xa_index =3D 0; - xas_find_marked(&xas, cache->msg_id_next - 1, XA_FREE_MARK); - } - if (xas.xa_node =3D=3D XAS_RESTART) - xas_set_err(&xas, -EBUSY); - - xas_store(&xas, req); - if (xas_valid(&xas)) { - cache->msg_id_next =3D xas.xa_index + 1; - xas_clear_mark(&xas, XA_FREE_MARK); - xas_set_mark(&xas, CACHEFILES_REQ_NEW); - } - xas_unlock(&xas); - } while (xas_nomem(&xas, GFP_KERNEL)); - - ret =3D xas_error(&xas); - if (ret) - goto out; - - wake_up_all(&cache->daemon_pollwq); -wait: - ret =3D wait_for_completion_killable(&req->done); - if (!ret) { - ret =3D req->error; - } else { - ret =3D -EINTR; - if (!cachefiles_ondemand_finish_req(req, &xas, ret)) { - /* Someone will complete it soon. */ - cpu_relax(); - goto wait; - } - } - cachefiles_req_put(req); - return ret; -out: - /* Reset the object to close state in error handling path. - * If error occurs after creating the anonymous fd, - * cachefiles_ondemand_fd_release() will set object to close. - */ - if (opcode =3D=3D CACHEFILES_OP_OPEN && - !cachefiles_ondemand_object_is_dropping(object)) - cachefiles_ondemand_set_object_close(object); - kfree(req); - return ret; -} - -static int cachefiles_ondemand_init_open_req(struct cachefiles_req *req, - void *private) -{ - struct cachefiles_object *object =3D req->object; - struct fscache_cookie *cookie =3D object->cookie; - struct fscache_volume *volume =3D object->volume->vcookie; - struct cachefiles_open *load =3D (void *)req->msg.data; - size_t volume_key_size, cookie_key_size; - void *volume_key, *cookie_key; - - /* - * Volume key is a NUL-terminated string. key[0] stores strlen() of the - * string, followed by the content of the string (excluding '\0'). - */ - volume_key_size =3D volume->key[0] + 1; - volume_key =3D volume->key + 1; - - /* Cookie key is binary data, which is netfs specific. */ - cookie_key_size =3D cookie->key_len; - cookie_key =3D fscache_get_key(cookie); - - if (!(object->cookie->advice & FSCACHE_ADV_WANT_CACHE_SIZE)) { - pr_err("WANT_CACHE_SIZE is needed for on-demand mode\n"); - return -EINVAL; - } - - load->volume_key_size =3D volume_key_size; - load->cookie_key_size =3D cookie_key_size; - memcpy(load->data, volume_key, volume_key_size); - memcpy(load->data + volume_key_size, cookie_key, cookie_key_size); - - return 0; -} - -static int cachefiles_ondemand_init_close_req(struct cachefiles_req *req, - void *private) -{ - struct cachefiles_object *object =3D req->object; - - if (!cachefiles_ondemand_object_is_open(object)) - return -ENOENT; - - trace_cachefiles_ondemand_close(object, &req->msg); - return 0; -} - -struct cachefiles_read_ctx { - loff_t off; - size_t len; -}; - -static int cachefiles_ondemand_init_read_req(struct cachefiles_req *req, - void *private) -{ - struct cachefiles_object *object =3D req->object; - struct cachefiles_read *load =3D (void *)req->msg.data; - struct cachefiles_read_ctx *read_ctx =3D private; - - load->off =3D read_ctx->off; - load->len =3D read_ctx->len; - trace_cachefiles_ondemand_read(object, &req->msg, load); - return 0; -} - -int cachefiles_ondemand_init_object(struct cachefiles_object *object) -{ - struct fscache_cookie *cookie =3D object->cookie; - struct fscache_volume *volume =3D object->volume->vcookie; - size_t volume_key_size, cookie_key_size, data_len; - - if (!object->ondemand) - return 0; - - /* - * CacheFiles will firstly check the cache file under the root cache - * directory. If the coherency check failed, it will fallback to - * creating a new tmpfile as the cache file. Reuse the previously - * allocated object ID if any. - */ - if (cachefiles_ondemand_object_is_open(object)) - return 0; - - volume_key_size =3D volume->key[0] + 1; - cookie_key_size =3D cookie->key_len; - data_len =3D sizeof(struct cachefiles_open) + - volume_key_size + cookie_key_size; - - return cachefiles_ondemand_send_req(object, CACHEFILES_OP_OPEN, - data_len, cachefiles_ondemand_init_open_req, NULL); -} - -void cachefiles_ondemand_clean_object(struct cachefiles_object *object) -{ - unsigned long index; - struct cachefiles_req *req; - struct cachefiles_cache *cache; - - if (!object->ondemand) - return; - - cachefiles_ondemand_send_req(object, CACHEFILES_OP_CLOSE, 0, - cachefiles_ondemand_init_close_req, NULL); - - if (!object->ondemand->ondemand_id) - return; - - /* Cancel all requests for the object that is being dropped. */ - cache =3D object->volume->cache; - xa_lock(&cache->reqs); - cachefiles_ondemand_set_object_dropping(object); - xa_for_each(&cache->reqs, index, req) { - if (req->object =3D=3D object) { - req->error =3D -EIO; - complete(&req->done); - __xa_erase(&cache->reqs, index); - } - } - xa_unlock(&cache->reqs); - - /* Wait for ondemand_object_worker() to finish to avoid UAF. */ - cancel_work_sync(&object->ondemand->ondemand_work); -} - -int cachefiles_ondemand_init_obj_info(struct cachefiles_object *object, - struct cachefiles_volume *volume) -{ - if (!cachefiles_in_ondemand_mode(volume->cache)) - return 0; - - object->ondemand =3D kzalloc_obj(struct cachefiles_ondemand_info); - if (!object->ondemand) - return -ENOMEM; - - object->ondemand->object =3D object; - spin_lock_init(&object->ondemand->lock); - INIT_WORK(&object->ondemand->ondemand_work, ondemand_object_worker); - return 0; -} - -void cachefiles_ondemand_deinit_obj_info(struct cachefiles_object *object) -{ - kfree(object->ondemand); - object->ondemand =3D NULL; -} - -int cachefiles_ondemand_read(struct cachefiles_object *object, - loff_t pos, size_t len) -{ - struct cachefiles_read_ctx read_ctx =3D {pos, len}; - - return cachefiles_ondemand_send_req(object, CACHEFILES_OP_READ, - sizeof(struct cachefiles_read), - cachefiles_ondemand_init_read_req, &read_ctx); -} diff --git a/include/linux/netfs.h b/include/linux/netfs.h index cc8d6500d059..a8702bd52d3a 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -191,7 +191,6 @@ struct netfs_io_subrequest { #define NETFS_SREQ_COPY_TO_CACHE 0 /* Set if should copy the data to the c= ache */ #define NETFS_SREQ_CLEAR_TAIL 1 /* Set if the rest of the read should be = cleared */ #define NETFS_SREQ_MADE_PROGRESS 4 /* Set if we transferred at least some = data */ -#define NETFS_SREQ_ONDEMAND 5 /* Set if it's from on-demand read mode */ #define NETFS_SREQ_BOUNDARY 6 /* Set if ends on hard boundary (eg. ceph o= bject) */ #define NETFS_SREQ_HIT_EOF 7 /* Set if short due to EOF */ #define NETFS_SREQ_IN_PROGRESS 8 /* Unlocked when the subrequest complete= s */ @@ -374,14 +373,6 @@ struct netfs_cache_ops { loff_t *_start, size_t *_len, size_t upper_len, loff_t i_size, bool no_space_allocated_yet); =20 - /* Prepare an on-demand read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ - enum netfs_io_source (*prepare_ondemand_read)(struct netfs_cache_resource= s *cres, - loff_t start, size_t *_len, - loff_t i_size, - unsigned long *_flags, ino_t ino); - /* Query the occupancy of the cache in a region, returning where the * next chunk of data starts and how long it is. */ diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cache= files.h index 6e3b1424eea4..9259bc71049e 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -31,10 +31,6 @@ enum cachefiles_obj_ref_trace { cachefiles_obj_see_lookup_failed, cachefiles_obj_see_withdraw_cookie, cachefiles_obj_see_withdrawal, - cachefiles_obj_get_ondemand_fd, - cachefiles_obj_put_ondemand_fd, - cachefiles_obj_get_read_req, - cachefiles_obj_put_read_req, }; =20 enum fscache_why_object_killed { @@ -129,11 +125,7 @@ enum cachefiles_error_trace { EM(cachefiles_obj_see_lookup_cookie, "SEE lookup_cookie") \ EM(cachefiles_obj_see_lookup_failed, "SEE lookup_failed") \ EM(cachefiles_obj_see_withdraw_cookie, "SEE withdraw_cookie") \ - EM(cachefiles_obj_see_withdrawal, "SEE withdrawal") \ - EM(cachefiles_obj_get_ondemand_fd, "GET ondemand_fd") \ - EM(cachefiles_obj_put_ondemand_fd, "PUT ondemand_fd") \ - EM(cachefiles_obj_get_read_req, "GET read_req") \ - E_(cachefiles_obj_put_read_req, "PUT read_req") + E_(cachefiles_obj_see_withdrawal, "SEE withdrawal") =20 #define cachefiles_coherency_traces \ EM(cachefiles_coherency_check_aux, "BAD aux ") \ @@ -687,180 +679,6 @@ TRACE_EVENT(cachefiles_io_error, __entry->error) ); =20 -TRACE_EVENT(cachefiles_ondemand_open, - TP_PROTO(struct cachefiles_object *obj, struct cachefiles_msg *msg, - struct cachefiles_open *load), - - TP_ARGS(obj, msg, load), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(unsigned int, object_id) - __field(unsigned int, fd) - __field(unsigned int, flags) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->msg_id =3D msg->msg_id; - __entry->object_id =3D msg->object_id; - __entry->fd =3D load->fd; - __entry->flags =3D load->flags; - ), - - TP_printk("o=3D%08x mid=3D%x oid=3D%x fd=3D%d f=3D%x", - __entry->obj, - __entry->msg_id, - __entry->object_id, - __entry->fd, - __entry->flags) - ); - -TRACE_EVENT(cachefiles_ondemand_copen, - TP_PROTO(struct cachefiles_object *obj, unsigned int msg_id, - long len), - - TP_ARGS(obj, msg_id, len), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(long, len) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->msg_id =3D msg_id; - __entry->len =3D len; - ), - - TP_printk("o=3D%08x mid=3D%x l=3D%lx", - __entry->obj, - __entry->msg_id, - __entry->len) - ); - -TRACE_EVENT(cachefiles_ondemand_close, - TP_PROTO(struct cachefiles_object *obj, struct cachefiles_msg *msg), - - TP_ARGS(obj, msg), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(unsigned int, object_id) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->msg_id =3D msg->msg_id; - __entry->object_id =3D msg->object_id; - ), - - TP_printk("o=3D%08x mid=3D%x oid=3D%x", - __entry->obj, - __entry->msg_id, - __entry->object_id) - ); - -TRACE_EVENT(cachefiles_ondemand_read, - TP_PROTO(struct cachefiles_object *obj, struct cachefiles_msg *msg, - struct cachefiles_read *load), - - TP_ARGS(obj, msg, load), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(unsigned int, object_id) - __field(loff_t, start) - __field(size_t, len) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->msg_id =3D msg->msg_id; - __entry->object_id =3D msg->object_id; - __entry->start =3D load->off; - __entry->len =3D load->len; - ), - - TP_printk("o=3D%08x mid=3D%x oid=3D%x s=3D%llx l=3D%zx", - __entry->obj, - __entry->msg_id, - __entry->object_id, - __entry->start, - __entry->len) - ); - -TRACE_EVENT(cachefiles_ondemand_cread, - TP_PROTO(struct cachefiles_object *obj, unsigned int msg_id), - - TP_ARGS(obj, msg_id), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->msg_id =3D msg_id; - ), - - TP_printk("o=3D%08x mid=3D%x", - __entry->obj, - __entry->msg_id) - ); - -TRACE_EVENT(cachefiles_ondemand_fd_write, - TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t start, size_t len), - - TP_ARGS(obj, backer, start, len), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, backer) - __field(loff_t, start) - __field(size_t, len) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->backer =3D backer->i_ino; - __entry->start =3D start; - __entry->len =3D len; - ), - - TP_printk("o=3D%08x iB=3D%x s=3D%llx l=3D%zx", - __entry->obj, - __entry->backer, - __entry->start, - __entry->len) - ); - -TRACE_EVENT(cachefiles_ondemand_fd_release, - TP_PROTO(struct cachefiles_object *obj, int object_id), - - TP_ARGS(obj, object_id), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, object_id) - ), - - TP_fast_assign( - __entry->obj =3D obj ? obj->debug_id : 0; - __entry->object_id =3D object_id; - ), - - TP_printk("o=3D%08x oid=3D%x", - __entry->obj, - __entry->object_id) - ); - #endif /* _TRACE_CACHEFILES_H */ =20 /* This part must be outside protection */ diff --git a/include/uapi/linux/cachefiles.h b/include/uapi/linux/cachefile= s.h deleted file mode 100644 index 78caa73e5343..000000000000 --- a/include/uapi/linux/cachefiles.h +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#ifndef _LINUX_CACHEFILES_H -#define _LINUX_CACHEFILES_H - -#include -#include - -/* - * Fscache ensures that the maximum length of cookie key is 255. The volum= e key - * is controlled by netfs, and generally no bigger than 255. - */ -#define CACHEFILES_MSG_MAX_SIZE 1024 - -enum cachefiles_opcode { - CACHEFILES_OP_OPEN, - CACHEFILES_OP_CLOSE, - CACHEFILES_OP_READ, -}; - -/* - * Message Header - * - * @msg_id a unique ID identifying this message - * @opcode message type, CACHEFILE_OP_* - * @len message length, including message header and following data - * @object_id a unique ID identifying a cache file - * @data message type specific payload - */ -struct cachefiles_msg { - __u32 msg_id; - __u32 opcode; - __u32 len; - __u32 object_id; - __u8 data[]; -}; - -/* - * @data contains the volume_key followed directly by the cookie_key. volu= me_key - * is a NUL-terminated string; @volume_key_size indicates the size of the = volume - * key in bytes. cookie_key is binary data, which is netfs specific; - * @cookie_key_size indicates the size of the cookie key in bytes. - * - * @fd identifies an anon_fd referring to the cache file. - */ -struct cachefiles_open { - __u32 volume_key_size; - __u32 cookie_key_size; - __u32 fd; - __u32 flags; - __u8 data[]; -}; - -/* - * @off indicates the starting offset of the requested file range - * @len indicates the length of the requested file range - */ -struct cachefiles_read { - __u64 off; - __u64 len; -}; - -/* - * Reply for READ request - * @arg for this ioctl is the @id field of READ request. - */ -#define CACHEFILES_IOC_READ_COMPLETE _IOW(0x98, 1, int) - -#endif diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index c4673a54c4ba..7e25051dc1ea 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6676,13 +6676,6 @@ static const struct bpf_raw_tp_null_args raw_tp_null= _args[] =3D { { "cachefiles_mark_inactive", 0x1 }, { "cachefiles_vfs_error", 0x1 }, { "cachefiles_io_error", 0x1 }, - { "cachefiles_ondemand_open", 0x1 }, - { "cachefiles_ondemand_copen", 0x1 }, - { "cachefiles_ondemand_close", 0x1 }, - { "cachefiles_ondemand_read", 0x1 }, - { "cachefiles_ondemand_cread", 0x1 }, - { "cachefiles_ondemand_fd_write", 0x1 }, - { "cachefiles_ondemand_fd_release", 0x1 }, /* ext4, from ext4__mballoc event class */ { "ext4_mballoc_discard", 0x10 }, { "ext4_mballoc_free", 0x10 }, From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 52F8F43B6EB for ; Mon, 24 Aug 2026 14:42:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582550; cv=none; b=nB603LzdZ3eTrUOaiTvBDny0moNQ0DGduBq94YQo5x221JbeBIFqKPI3g5zzQ4Lx8ZQtGm04U3x0dLY6zkdBStki0ByhFJr8qOyZh0vAAG2SLs7NKdQksqcBnMWv4sMV7yMsaUbVSMLlFfi+xlE1NNgox7wdUnZBhZ5IFXGBbpQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582550; c=relaxed/simple; bh=QrVxkC1qUai+LkpCyw0Tz6tntuP4Wpe1tMlKeqdSWwU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aBqMhRsQ3GdCHpFy3ZFr/8DlU/5ymo64z9YGoq7jo4BZ8uJE/wDZ5zeV+FpZYynILaG282EnWnp6cE5L5d+//PhQn1Kta3VTB6wd1yDFgtnVyirKZOkRhfib+2s1RgzUZQOEFYoB+vSuSBYuWyTLtneyFIa+y5JCsmG/7jUeeM0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Ky1CH5/S; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Ky1CH5/S" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582547; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=hoJL241RSoxtycntHxJVxwYZyJBhwGJRmdZEHBFJJr0=; b=Ky1CH5/SsjP935RYwPMJao3bHXe5GfFCN6n5uSNAJMO6eOrFZGDWek82W//OwKLDtRiQ1Y jAli0PACIgtxWTY+OAAo46+EQ8wBB16yAqxcE2JymqZ4Xi/ZAlZlYYdtfa71E5t1RdNSbb K15okeFjHjPJjlbHu4+pwkXxhAN3kZE= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-586-wqArccvqNnWhMmpYhyITZg-1; Mon, 24 Aug 2026 10:42:23 -0400 X-MC-Unique: wqArccvqNnWhMmpYhyITZg-1 X-Mimecast-MFC-AGG-ID: wqArccvqNnWhMmpYhyITZg_1787582539 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 69BE31977535; Mon, 24 Aug 2026 14:42:19 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 09DC330002ED; Mon, 24 Aug 2026 14:42:12 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 04/35] cachefiles: Fix potential UAF/KASAN warning Date: Mon, 24 Aug 2026 15:40:57 +0100 Message-ID: <20260824144130.759997-5-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Currently, trace_cachefiles_coherency() is being passed a pointer to a __be64 lain over the coherency data in struct cachefiles_xattr so that it can display the first 8 bytes. However, the data is of variable length and could even be 0 bytes. This could lead to a UAF or KASAN warning. Fix this by making sure the buffer has room for at least 8 bytes and that those 8 bytes are pre-cleared. Further, those bytes are not 8-byte aligned, so fix the tracepoint to extract the data as four 2-byte words (they are 2-byte aligned) and reassemble the __be64. The compiler will convert this into a single 8-byte load where the CPU supports it. Fixes: 229105e5cfd9 ("cachefiles: Add auxiliary data trace") Link: https://sashiko.dev/#/patchset/20260810144746.574036-1-dhowells%40red= hat.com Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/cachefiles/xattr.c | 16 ++++++++-------- include/trace/events/cachefiles.h | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c index f8ae78b3f7b6..92990cfdcca7 100644 --- a/fs/cachefiles/xattr.c +++ b/fs/cachefiles/xattr.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "internal.h" =20 #define CACHEFILES_COOKIE_TYPE_DATA 1 @@ -50,7 +51,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) =20 _enter("%x,#%d", object->debug_id, len); =20 - buf =3D kmalloc(sizeof(struct cachefiles_xattr) + len, GFP_KERNEL); + buf =3D kmalloc(sizeof(struct cachefiles_xattr) + min(len, sizeof(__be64)= ), GFP_KERNEL); if (!buf) return -ENOMEM; =20 @@ -60,6 +61,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) buf->content =3D object->content_info; if (test_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags)) buf->content =3D CACHEFILES_CONTENT_DIRTY; + put_unaligned_be64(0, (__be64 *)buf->data); if (len > 0) memcpy(buf->data, fscache_get_aux(object->cookie), len); =20 @@ -77,8 +79,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) trace_cachefiles_vfs_error(object, file_inode(file), ret, cachefiles_trace_setxattr_error); trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, + buf->data, buf->content, cachefiles_coherency_set_fail); if (ret !=3D -ENOMEM) cachefiles_io_error_obj( @@ -86,8 +87,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) "Failed to set xattr with error %d", ret); } else { trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, + buf->data, buf->content, cachefiles_coherency_set_ok); } =20 @@ -110,9 +110,10 @@ int cachefiles_check_auxdata(struct cachefiles_object = *object, struct file *file int ret =3D -ESTALE; =20 tlen =3D sizeof(struct cachefiles_xattr) + len; - buf =3D kmalloc(tlen, GFP_KERNEL); + buf =3D kmalloc(sizeof(struct cachefiles_xattr) + min(len, sizeof(__be64)= ), GFP_KERNEL); if (!buf) return -ENOMEM; + put_unaligned_be64(0, (__be64 *)buf->data); =20 xlen =3D cachefiles_inject_read_error(); if (xlen =3D=3D 0) @@ -148,8 +149,7 @@ int cachefiles_check_auxdata(struct cachefiles_object *= object, struct file *file =20 out: trace_cachefiles_coherency(object, file_inode(file)->i_ino, - be64_to_cpup((__be64 *)buf->data), - buf->content, why); + buf->data, buf->content, why); kfree(buf); return ret; } diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cache= files.h index 9259bc71049e..927338f8fe85 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -372,7 +372,7 @@ TRACE_EVENT(cachefiles_rename, TRACE_EVENT(cachefiles_coherency, TP_PROTO(struct cachefiles_object *obj, ino_t ino, - u64 disk_aux, + const void *disk_aux, enum cachefiles_content content, enum cachefiles_coherency_trace why), =20 @@ -389,12 +389,27 @@ TRACE_EVENT(cachefiles_coherency, ), =20 TP_fast_assign( + union { + __be16 s[4]; + __be64 ll; + } x; + __entry->obj =3D obj->debug_id; __entry->why =3D why; __entry->content =3D content; __entry->ino =3D ino; __entry->aux =3D be64_to_cpup((__be64 *)obj->cookie->inline_aux); - __entry->disk_aux =3D disk_aux; + + /* cachefiles_xattr::data is not 64-byte aligned. */ + if (disk_aux) { + x.s[0] =3D ((__be16 *)disk_aux)[0]; + x.s[1] =3D ((__be16 *)disk_aux)[1]; + x.s[2] =3D ((__be16 *)disk_aux)[2]; + x.s[3] =3D ((__be16 *)disk_aux)[3]; + __entry->disk_aux =3D be64_to_cpu(x.ll); + } else { + __entry->disk_aux =3D 0; + } ), =20 TP_printk("o=3D%08x %s B=3D%llx c=3D%u aux=3D%llx dsk=3D%llx", From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BC92743BDA0 for ; Mon, 24 Aug 2026 14:42:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582564; cv=none; b=shuBW0I/TQq6lZl5GXBuZsjh6y80Liq06NBFaYtkcxm5Yo8Ehh6rpO+tAbD8fs2sa8+08uYn3Pfw4AWC2FJ0BUfiCZ/zBJoThg1W+N6yy9Kvw2XJqnEy4z7oMgxPqiA878f3J27VTKEO4FA+d8E/LtqhaAKhry1SmY2njJkYfMA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582564; c=relaxed/simple; bh=IoCOVwvaVi7H2FOBtgmH7faEMiEZHzm7roB7mQtntF0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FKHr5jJ2y1RNr4KuGcPnnhzYZ+jY6fJfBDvgJUdvVA5xgtYRh80NiHKIvXAz15uv4Zr2czmOpvUM732pYyNed/2SSF32mkw/Wi6WFMcCpbm6bgomBfH8TMHB2n0HvOGSnQ3iQCDv4ADmPMgBljq8EsulflWFwaQi/D7lfAJM1t0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=ROtXksJL; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="ROtXksJL" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582555; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=6Ewi4qSsSL5b4R0l90oASgZKuTI8wz/A21/TicXS79Y=; b=ROtXksJLMsEyC+FNUNJgbLckG38jYjZEPAAWFuYjo8P8EW2NlHVxI1774m3XJDDI7ZpNKh 0J30uMx+7NBlPEFjK2dcI0jimEH7ud2cJNcKnvIwyuoz48fM3boOA2jfzhZ3BUAfRZxd7w 6K1yQobpW+v+dhEQHVnQBotpBbmUtlk= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-115-1b2yRGiCNy6p8i-FUIM80A-1; Mon, 24 Aug 2026 10:42:30 -0400 X-MC-Unique: 1b2yRGiCNy6p8i-FUIM80A-1 X-Mimecast-MFC-AGG-ID: 1b2yRGiCNy6p8i-FUIM80A_1787582548 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 137941977535; Mon, 24 Aug 2026 14:42:28 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2D2D01955F09; Mon, 24 Aug 2026 14:42:20 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 05/35] netfs: Use uoff_t instead of unsigned long long and loff_t Date: Mon, 24 Aug 2026 15:40:58 +0100 Message-ID: <20260824144130.759997-6-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" Use uoff_t instead of unsigned long long and loff_t for file positions that can't be negative. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/afs/file.c | 4 +- fs/cachefiles/interface.c | 10 ++-- fs/cachefiles/internal.h | 4 +- fs/cachefiles/io.c | 24 +++++----- fs/ceph/addr.c | 4 +- fs/netfs/buffered_read.c | 24 +++++----- fs/netfs/buffered_write.c | 10 ++-- fs/netfs/direct_read.c | 2 +- fs/netfs/direct_write.c | 8 ++-- fs/netfs/fscache_cookie.c | 8 ++-- fs/netfs/fscache_io.c | 8 ++-- fs/netfs/internal.h | 10 ++-- fs/netfs/iterator.c | 2 +- fs/netfs/misc.c | 8 ++-- fs/netfs/objects.c | 2 +- fs/netfs/read_collect.c | 4 +- fs/netfs/read_pgpriv2.c | 8 ++-- fs/netfs/read_retry.c | 2 +- fs/netfs/write_collect.c | 10 ++-- fs/netfs/write_issue.c | 10 ++-- fs/netfs/write_retry.c | 2 +- include/linux/fscache-cache.h | 2 +- include/linux/fscache.h | 36 +++++++-------- include/linux/netfs.h | 74 ++++++++++++++--------------- include/trace/events/cachefiles.h | 30 ++++++------ include/trace/events/fscache.h | 10 ++-- include/trace/events/netfs.h | 77 +++++++++++++++---------------- 27 files changed, 196 insertions(+), 197 deletions(-) diff --git a/fs/afs/file.c b/fs/afs/file.c index 0467742bfeee..3380a0d20c0f 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -413,7 +413,7 @@ static int afs_init_request(struct netfs_io_request *rr= eq, struct file *file) return 0; } =20 -static int afs_check_write_begin(struct file *file, loff_t pos, unsigned l= en, +static int afs_check_write_begin(struct file *file, uoff_t pos, unsigned l= en, struct folio **foliop, void **_fsdata) { struct afs_vnode *vnode =3D AFS_FS_I(file_inode(file)); @@ -451,7 +451,7 @@ void afs_set_i_size(struct afs_vnode *vnode, loff_t new= _i_size) fscache_update_cookie(afs_vnode_cache(vnode), NULL, &new_i_size); } =20 -static void afs_update_i_size(struct inode *inode, loff_t new_i_size) +static void afs_update_i_size(struct inode *inode, uoff_t new_i_size) { afs_set_i_size(AFS_FS_I(inode), new_i_size); } diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index 50a000310a8c..a160d5c3e74c 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -111,7 +111,7 @@ static int cachefiles_adjust_size(struct cachefiles_obj= ect *object) struct iattr newattrs; struct file *file =3D object->file; uint64_t ni_size; - loff_t oi_size; + uoff_t oi_size; int ret; =20 ni_size =3D object->cookie->object_size; @@ -225,11 +225,11 @@ static bool cachefiles_lookup_cookie(struct fscache_c= ookie *cookie) * any unused granules. */ static bool cachefiles_shorten_object(struct cachefiles_object *object, - struct file *file, loff_t new_size) + struct file *file, uoff_t new_size) { struct cachefiles_cache *cache =3D object->volume->cache; struct inode *inode =3D file_inode(file); - loff_t i_size, dio_size; + uoff_t i_size, dio_size; int ret; =20 dio_size =3D round_up(new_size, CACHEFILES_DIO_BLOCK_SIZE); @@ -271,14 +271,14 @@ static bool cachefiles_shorten_object(struct cachefil= es_object *object, * Resize the backing object. */ static void cachefiles_resize_cookie(struct netfs_cache_resources *cres, - loff_t new_size) + uoff_t new_size) { struct cachefiles_object *object =3D cachefiles_cres_object(cres); struct cachefiles_cache *cache =3D object->volume->cache; struct fscache_cookie *cookie =3D object->cookie; const struct cred *saved_cred; struct file *file =3D cachefiles_cres_file(cres); - loff_t old_size =3D cookie->object_size; + uoff_t old_size =3D cookie->object_size; =20 _enter("%llu->%llu", old_size, new_size); =20 diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index c93324e0f98c..60bd801ada04 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -203,11 +203,11 @@ extern bool cachefiles_begin_operation(struct netfs_c= ache_resources *cres, enum fscache_want_state want_state); extern int __cachefiles_prepare_write(struct cachefiles_object *object, struct file *file, - loff_t *_start, size_t *_len, size_t upper_len, + uoff_t *_start, size_t *_len, size_t upper_len, bool no_space_allocated_yet); extern int __cachefiles_write(struct cachefiles_object *object, struct file *file, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv); diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 9540ec25b3cb..7de8069d15b6 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -19,7 +19,7 @@ struct cachefiles_kiocb { struct kiocb iocb; refcount_t ki_refcnt; - loff_t start; + uoff_t start; union { size_t skipped; size_t len; @@ -73,7 +73,7 @@ static void cachefiles_read_complete(struct kiocb *iocb, = long ret) * Initiate a read from the cache. */ static int cachefiles_read(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, @@ -197,8 +197,8 @@ static int cachefiles_read(struct netfs_cache_resources= *cres, * of data starts and how long it is. */ static int cachefiles_query_occupancy(struct netfs_cache_resources *cres, - loff_t start, size_t len, size_t granularity, - loff_t *_data_start, size_t *_data_len) + uoff_t start, size_t len, size_t granularity, + uoff_t *_data_start, size_t *_data_len) { struct cachefiles_object *object; struct file *file; @@ -280,7 +280,7 @@ static void cachefiles_write_complete(struct kiocb *ioc= b, long ret) */ int __cachefiles_write(struct cachefiles_object *object, struct file *file, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv) @@ -357,7 +357,7 @@ int __cachefiles_write(struct cachefiles_object *object, } =20 static int cachefiles_write(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv) @@ -377,7 +377,7 @@ static int cachefiles_write(struct netfs_cache_resource= s *cres, =20 static inline enum netfs_io_source cachefiles_do_prepare_read(struct netfs_cache_resources *cres, - loff_t start, size_t *_len, loff_t i_size, + uoff_t start, size_t *_len, loff_t i_size, unsigned long *_flags, ino_t netfs_ino) { enum cachefiles_prepare_read_trace why; @@ -483,7 +483,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources= *cres, * boundary as appropriate. */ static enum netfs_io_source cachefiles_prepare_read(struct netfs_io_subreq= uest *subreq, - unsigned long long i_size) + uoff_t i_size) { return cachefiles_do_prepare_read(&subreq->rreq->cache_resources, subreq->start, &subreq->len, i_size, @@ -495,7 +495,7 @@ static enum netfs_io_source cachefiles_prepare_read(str= uct netfs_io_subrequest * */ int __cachefiles_prepare_write(struct cachefiles_object *object, struct file *file, - loff_t *_start, size_t *_len, size_t upper_len, + uoff_t *_start, size_t *_len, size_t upper_len, bool no_space_allocated_yet) { struct cachefiles_cache *cache =3D object->volume->cache; @@ -577,8 +577,8 @@ int __cachefiles_prepare_write(struct cachefiles_object= *object, } =20 static int cachefiles_prepare_write(struct netfs_cache_resources *cres, - loff_t *_start, size_t *_len, size_t upper_len, - loff_t i_size, bool no_space_allocated_yet) + uoff_t *_start, size_t *_len, size_t upper_len, + uoff_t i_size, bool no_space_allocated_yet) { struct cachefiles_object *object =3D cachefiles_cres_object(cres); struct cachefiles_cache *cache =3D object->volume->cache; @@ -628,7 +628,7 @@ static void cachefiles_issue_write(struct netfs_io_subr= equest *subreq) struct netfs_io_stream *stream =3D &wreq->io_streams[subreq->stream_nr]; const struct cred *saved_cred; size_t off, pre, post, len =3D subreq->len; - loff_t start =3D subreq->start; + uoff_t start =3D subreq->start; int ret; =20 _enter("W=3D%x[%x] %llx-%llx", diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index ecf33b66610c..6586f6c1dc73 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -65,7 +65,7 @@ (CONGESTION_ON_THRESH(congestion_kb) - \ (CONGESTION_ON_THRESH(congestion_kb) >> 2)) =20 -static int ceph_netfs_check_write_begin(struct file *file, loff_t pos, uns= igned int len, +static int ceph_netfs_check_write_begin(struct file *file, uoff_t pos, uns= igned int len, struct folio **foliop, void **_fsdata); =20 static inline struct ceph_snap_context *page_snap_context(struct page *pag= e) @@ -1854,7 +1854,7 @@ ceph_find_incompatible(struct folio *folio) return NULL; } =20 -static int ceph_netfs_check_write_begin(struct file *file, loff_t pos, uns= igned int len, +static int ceph_netfs_check_write_begin(struct file *file, uoff_t pos, uns= igned int len, struct folio **foliop, void **_fsdata) { struct inode *inode =3D file_inode(file); diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 3c32ef41a27f..7e58aec6cdff 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -10,9 +10,9 @@ #include "internal.h" =20 static void netfs_cache_expand_readahead(struct netfs_io_request *rreq, - unsigned long long *_start, - unsigned long long *_len, - unsigned long long i_size) + uoff_t *_start, + uoff_t *_len, + uoff_t i_size) { struct netfs_cache_resources *cres =3D &rreq->cache_resources; =20 @@ -134,7 +134,7 @@ static ssize_t netfs_prepare_read_iterator(struct netfs= _io_subrequest *subreq, =20 static enum netfs_io_source netfs_cache_prepare_read(struct netfs_io_reque= st *rreq, struct netfs_io_subrequest *subreq, - loff_t i_size) + uoff_t i_size) { struct netfs_cache_resources *cres =3D &rreq->cache_resources; enum netfs_io_source source; @@ -214,8 +214,8 @@ static void netfs_issue_read(struct netfs_io_request *r= req, static void netfs_read_to_pagecache(struct netfs_io_request *rreq, struct readahead_control *ractl) { - unsigned long long start =3D rreq->start; ssize_t size =3D rreq->len; + uoff_t start =3D rreq->start; int ret =3D 0; =20 do { @@ -237,8 +237,8 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq, source =3D netfs_cache_prepare_read(rreq, subreq, rreq->i_size); subreq->source =3D source; if (source =3D=3D NETFS_DOWNLOAD_FROM_SERVER) { - unsigned long long zero_point =3D netfs_read_zero_point(rreq->inode); - unsigned long long zp =3D umin(zero_point, rreq->i_size); + uoff_t zero_point =3D netfs_read_zero_point(rreq->inode); + uoff_t zp =3D umin(zero_point, rreq->i_size); size_t len =3D subreq->len; =20 if (unlikely(rreq->origin =3D=3D NETFS_READ_SINGLE)) @@ -342,7 +342,7 @@ void netfs_readahead(struct readahead_control *ractl) { struct netfs_io_request *rreq; struct netfs_inode *ictx =3D netfs_inode(ractl->mapping->host); - unsigned long long start =3D readahead_pos(ractl); + uoff_t start =3D readahead_pos(ractl); size_t size =3D readahead_length(ractl); int ret; =20 @@ -564,11 +564,11 @@ EXPORT_SYMBOL(netfs_read_folio); * If any of these criteria are met, then zero out the unwritten parts * of the folio and return true. Otherwise, return false. */ -static bool netfs_skip_folio_read(struct folio *folio, loff_t pos, size_t = len, +static bool netfs_skip_folio_read(struct folio *folio, uoff_t pos, size_t = len, bool always_fill) { struct inode *inode =3D folio_inode(folio); - loff_t i_size =3D i_size_read(inode); + uoff_t i_size =3D i_size_read(inode); size_t offset =3D offset_in_folio(folio, pos); size_t plen =3D folio_size(folio); =20 @@ -633,7 +633,7 @@ static bool netfs_skip_folio_read(struct folio *folio, = loff_t pos, size_t len, */ int netfs_write_begin(struct netfs_inode *ctx, struct file *file, struct address_space *mapping, - loff_t pos, unsigned int len, struct folio **_folio, + uoff_t pos, unsigned int len, struct folio **_folio, void **_fsdata) { struct netfs_io_request *rreq; @@ -729,7 +729,7 @@ int netfs_prefetch_for_write(struct file *file, struct = folio *folio, struct netfs_io_request *rreq; struct address_space *mapping =3D folio->mapping; struct netfs_inode *ctx =3D netfs_inode(mapping->host); - unsigned long long start =3D folio_pos(folio); + uoff_t start =3D folio_pos(folio); size_t flen =3D folio_size(folio); int ret; =20 diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index 2cdb68e6b16f..df496873e4f4 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -17,7 +17,7 @@ * as possible to hold as much of the remaining length as possible in one = go. */ static struct folio *netfs_grab_folio_for_write(struct address_space *mapp= ing, - loff_t pos, size_t part) + uoff_t pos, size_t part) { pgoff_t index =3D pos / PAGE_SIZE; fgf_t fgp_flags =3D FGP_WRITEBEGIN; @@ -35,9 +35,9 @@ static struct folio *netfs_grab_folio_for_write(struct ad= dress_space *mapping, * the values actually are. */ void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode, - loff_t pos, size_t copied) + uoff_t pos, size_t copied) { - loff_t i_size, end =3D pos + copied; + uoff_t i_size, end =3D pos + copied; blkcnt_t add; size_t gap; =20 @@ -102,7 +102,7 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct = iov_iter *iter, struct folio *folio =3D NULL, *writethrough =3D NULL; unsigned int bdp_flags =3D (iocb->ki_flags & IOCB_NOWAIT) ? BDP_ASYNC : 0; ssize_t written =3D 0, ret, ret2; - loff_t pos =3D iocb->ki_pos; + uoff_t pos =3D iocb->ki_pos; size_t max_chunk =3D mapping_max_folio_size(mapping); bool maybe_trouble =3D false; =20 @@ -134,7 +134,7 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct = iov_iter *iter, enum netfs_folio_trace trace; struct netfs_folio *finfo; struct netfs_group *group; - unsigned long long fpos; + uoff_t fpos; size_t flen; size_t offset; /* Offset into pagecache folio */ size_t part; /* Bytes to write to folio */ diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index 6a8fb0d55e04..aa10af5171a8 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -47,8 +47,8 @@ static void netfs_prepare_dio_read_iterator(struct netfs_= io_subrequest *subreq) */ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) { - unsigned long long start =3D rreq->start; ssize_t size =3D rreq->len; + uoff_t start =3D rreq->start; int ret; =20 do { diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index b04019097ab8..47ec47e3a95b 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -216,9 +216,9 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb= *iocb, struct iov_iter * struct netfs_group *netfs_group) { struct netfs_io_request *wreq; - unsigned long long start =3D iocb->ki_pos; - unsigned long long end =3D start + iov_iter_count(iter); ssize_t ret, n; + uoff_t start =3D iocb->ki_pos; + uoff_t end =3D start + iov_iter_count(iter); size_t len =3D iov_iter_count(iter); bool async =3D !is_sync_kiocb(iocb); =20 @@ -327,8 +327,8 @@ ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb,= struct iov_iter *from) struct inode *inode =3D mapping->host; struct netfs_inode *ictx =3D netfs_inode(inode); ssize_t ret; - loff_t pos =3D iocb->ki_pos; - unsigned long long end =3D pos + iov_iter_count(from) - 1; + uoff_t pos =3D iocb->ki_pos; + uoff_t end =3D pos + iov_iter_count(from) - 1; =20 _enter("%llx,%zx,%llx", pos, iov_iter_count(from), i_size_read(inode)); =20 diff --git a/fs/netfs/fscache_cookie.c b/fs/netfs/fscache_cookie.c index 3d56fc73435f..5a226f9cbdea 100644 --- a/fs/netfs/fscache_cookie.c +++ b/fs/netfs/fscache_cookie.c @@ -327,7 +327,7 @@ static struct fscache_cookie *fscache_alloc_cookie( u8 advice, const void *index_key, size_t index_key_len, const void *aux_data, size_t aux_data_len, - loff_t object_size) + uoff_t object_size) { struct fscache_cookie *cookie; =20 @@ -452,7 +452,7 @@ struct fscache_cookie *__fscache_acquire_cookie( u8 advice, const void *index_key, size_t index_key_len, const void *aux_data, size_t aux_data_len, - loff_t object_size) + uoff_t object_size) { struct fscache_cookie *cookie; =20 @@ -663,7 +663,7 @@ static void fscache_unuse_cookie_locked(struct fscache_= cookie *cookie) * Stop using the cookie for I/O. */ void __fscache_unuse_cookie(struct fscache_cookie *cookie, - const void *aux_data, const loff_t *object_size) + const void *aux_data, const uoff_t *object_size) { unsigned int debug_id =3D cookie->debug_id; unsigned int r =3D refcount_read(&cookie->ref); @@ -1049,7 +1049,7 @@ static void fscache_perform_invalidation(struct fscac= he_cookie *cookie) * Invalidate an object. */ void __fscache_invalidate(struct fscache_cookie *cookie, - const void *aux_data, loff_t new_size, + const void *aux_data, uoff_t new_size, unsigned int flags) { bool is_caching; diff --git a/fs/netfs/fscache_io.c b/fs/netfs/fscache_io.c index 37f05b4d3469..8bca63721eeb 100644 --- a/fs/netfs/fscache_io.c +++ b/fs/netfs/fscache_io.c @@ -162,7 +162,7 @@ EXPORT_SYMBOL(__fscache_begin_write_operation); struct fscache_write_request { struct netfs_cache_resources cache_resources; struct address_space *mapping; - loff_t start; + uoff_t start; size_t len; bool set_bits; bool using_pgpriv2; @@ -171,7 +171,7 @@ struct fscache_write_request { }; =20 void __fscache_clear_page_bits(struct address_space *mapping, - loff_t start, size_t len) + uoff_t start, size_t len) { pgoff_t first =3D start / PAGE_SIZE; pgoff_t last =3D (start + len - 1) / PAGE_SIZE; @@ -208,7 +208,7 @@ static void fscache_wreq_done(void *priv, ssize_t trans= ferred_or_error) =20 void __fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, - loff_t start, size_t len, loff_t i_size, + uoff_t start, size_t len, uoff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool cond) @@ -267,7 +267,7 @@ EXPORT_SYMBOL(__fscache_write_to_cache); /* * Change the size of a backing object. */ -void __fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_siz= e) +void __fscache_resize_cookie(struct fscache_cookie *cookie, uoff_t new_siz= e) { struct netfs_cache_resources cres; =20 diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index f92281a611de..4ea3ef8b3bb0 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -33,7 +33,7 @@ int netfs_prefetch_for_write(struct file *file, struct fo= lio *folio, * buffered_write.c */ void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode, - loff_t pos, size_t copied); + uoff_t pos, size_t copied); =20 /* * main.c @@ -85,7 +85,7 @@ void netfs_wait_for_paused_write(struct netfs_io_request = *rreq); */ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, struct file *file, - loff_t start, size_t len, + uoff_t start, size_t len, enum netfs_io_origin origin); void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_= trace what); void netfs_clear_subrequests(struct netfs_io_request *rreq); @@ -201,11 +201,11 @@ void netfs_write_collection_worker(struct work_struct= *work); */ struct netfs_io_request *netfs_create_write_req(struct address_space *mapp= ing, struct file *file, - loff_t start, + uoff_t start, enum netfs_io_origin origin); void netfs_prepare_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start); + uoff_t start); void netfs_reissue_write(struct netfs_io_stream *stream, struct netfs_io_subrequest *subreq, struct iov_iter *source); @@ -213,7 +213,7 @@ void netfs_issue_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream); size_t netfs_advance_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start, size_t len, bool to_eof); + uoff_t start, size_t len, bool to_eof); struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size= _t len); int netfs_advance_writethrough(struct netfs_io_request *wreq, struct write= back_control *wbc, struct folio *folio, size_t copied, bool to_page_end, diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c index b375567e0520..eb1efb17f53a 100644 --- a/fs/netfs/iterator.c +++ b/fs/netfs/iterator.c @@ -209,7 +209,7 @@ static size_t netfs_limit_xarray(const struct iov_iter = *iter, size_t start_offse { struct folio *folio; unsigned int nsegs =3D 0; - loff_t pos =3D iter->xarray_start + iter->iov_offset; + uoff_t pos =3D iter->xarray_start + iter->iov_offset; pgoff_t index =3D pos / PAGE_SIZE; size_t span =3D 0, n =3D iter->count; =20 diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index 5d554512ed23..f58a0d45e614 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -193,7 +193,7 @@ void netfs_clear_inode_writeback(struct inode *inode, c= onst void *aux) struct fscache_cookie *cookie =3D netfs_i_cookie(netfs_inode(inode)); =20 if (inode_state_read_once(inode) & I_PINNING_NETFS_WB) { - loff_t i_size =3D i_size_read(inode); + uoff_t i_size =3D i_size_read(inode); fscache_unuse_cookie(cookie, aux, &i_size); } } @@ -218,8 +218,8 @@ void netfs_invalidate_folio(struct folio *folio, size_t= offset, size_t length) _enter("{%lx},%zx,%zx", folio->index, offset, length); =20 if (offset =3D=3D 0 && length =3D=3D flen) { - unsigned long long i_size, remote_i_size, zero_point; - unsigned long long fpos =3D folio_pos(folio), end; + uoff_t i_size, remote_i_size, zero_point; + uoff_t fpos =3D folio_pos(folio), end; =20 netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point); end =3D umin(fpos + flen, i_size); @@ -305,7 +305,7 @@ bool netfs_release_folio(struct folio *folio, gfp_t gfp) { struct inode *inode =3D folio_inode(folio); struct netfs_inode *ctx =3D netfs_inode(inode); - unsigned long long i_size, remote_i_size, zero_point, end; + uoff_t i_size, remote_i_size, zero_point, end; =20 if (folio_test_dirty(folio)) return false; diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 7f6a3e912602..3460aa1c4af1 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -16,7 +16,7 @@ static void netfs_free_request(struct work_struct *work); */ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, struct file *file, - loff_t start, size_t len, + uoff_t start, size_t len, enum netfs_io_origin origin) { static atomic_t debug_ids; diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 723b479ef606..a29b694ce1e8 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -130,8 +130,8 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, unsigned int *notes) { struct folio_queue *folioq =3D rreq->buffer.tail; - unsigned long long collected_to =3D rreq->collected_to; unsigned int slot =3D rreq->buffer.first_tail_slot; + uoff_t collected_to =3D rreq->collected_to; =20 if (rreq->cleaned_to >=3D rreq->collected_to) return; @@ -149,7 +149,7 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, =20 for (;;) { struct folio *folio; - unsigned long long fpos =3D rreq->cleaned_to, fend; + uoff_t fpos =3D rreq->cleaned_to, fend; size_t fsize; =20 if (*notes & COPY_TO_CACHE) diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index c31190993b76..ac16bb8210d1 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -20,7 +20,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_requ= est *creq, struct folio { struct netfs_io_stream *cache =3D &creq->io_streams[1]; size_t fsize =3D folio_size(folio), flen =3D fsize; - loff_t fpos =3D folio_pos(folio), i_size; + uoff_t fpos =3D folio_pos(folio), i_size; bool to_eof =3D false; =20 _enter(""); @@ -172,8 +172,8 @@ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_re= quest *rreq) bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) { struct folio_queue *folioq =3D creq->buffer.tail; - unsigned long long collected_to =3D creq->collected_to; unsigned int slot =3D creq->buffer.first_tail_slot; + uoff_t collected_to =3D creq->collected_to; bool made_progress =3D false; =20 if (slot >=3D folioq_nr_slots(folioq)) { @@ -183,7 +183,7 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io= _request *creq) =20 for (;;) { struct folio *folio; - unsigned long long fpos, fend; + uoff_t fpos, fend; size_t fsize, flen; =20 folio =3D folioq_folio(folioq, slot); @@ -196,7 +196,7 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io= _request *creq) fsize =3D folio_size(folio); flen =3D fsize; =20 - fend =3D min_t(unsigned long long, fpos + flen, creq->i_size); + fend =3D min_t(uoff_t, fpos + flen, creq->i_size); =20 trace_netfs_collect_folio(creq, folio, fend, collected_to); =20 diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 2b42758e01ec..f5c5ed12dd02 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -75,7 +75,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_= request *rreq) do { struct netfs_io_subrequest *from, *to, *tmp; struct iov_iter source; - unsigned long long start, len; + uoff_t start, len; size_t part; bool boundary =3D false, subreq_superfluous =3D false; =20 diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 210eb8f3958d..100a5038c61e 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -67,7 +67,7 @@ int netfs_folio_written_back(struct folio *folio) /* Streaming writes cannot be redirtied whilst under writeback, * so discard the streaming record. */ - unsigned long long fend; + uoff_t fend; =20 fend =3D folio_pos(folio) + finfo->dirty_offset + finfo->dirty_len; spin_lock(&ictx->inode.i_lock); @@ -115,8 +115,8 @@ static void netfs_writeback_unlock_folios(struct netfs_= io_request *wreq, unsigned int *notes) { struct folio_queue *folioq =3D wreq->buffer.tail; - unsigned long long collected_to =3D wreq->collected_to; unsigned int slot =3D wreq->buffer.first_tail_slot; + uoff_t collected_to =3D wreq->collected_to; =20 if (WARN_ON_ONCE(!folioq)) { pr_err("[!] Writeback unlock found empty rolling buffer!\n"); @@ -140,7 +140,7 @@ static void netfs_writeback_unlock_folios(struct netfs_= io_request *wreq, for (;;) { struct folio *folio; struct netfs_folio *finfo; - unsigned long long fpos, fend; + uoff_t fpos, fend; size_t fsize, flen; =20 folio =3D folioq_folio(folioq, slot); @@ -154,7 +154,7 @@ static void netfs_writeback_unlock_folios(struct netfs_= io_request *wreq, finfo =3D netfs_folio_info(folio); flen =3D finfo ? finfo->dirty_offset + finfo->dirty_len : fsize; =20 - fend =3D min_t(unsigned long long, fpos + flen, wreq->i_size); + fend =3D min_t(uoff_t, fpos + flen, wreq->i_size); =20 trace_netfs_collect_folio(wreq, folio, fend, collected_to); =20 @@ -201,8 +201,8 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) { struct netfs_io_subrequest *front, *remove; struct netfs_io_stream *stream; - unsigned long long collected_to, issued_to; unsigned int notes; + uoff_t collected_to, issued_to; int s; =20 _enter("%llx-%llx", wreq->start, wreq->start + wreq->len); diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 2d9cfcd43658..1a1f4efcaefd 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -89,7 +89,7 @@ static void netfs_kill_dirty_pages(struct address_space *= mapping, */ struct netfs_io_request *netfs_create_write_req(struct address_space *mapp= ing, struct file *file, - loff_t start, + uoff_t start, enum netfs_io_origin origin) { struct netfs_io_request *wreq; @@ -156,7 +156,7 @@ EXPORT_SYMBOL(netfs_prepare_write_failed); */ void netfs_prepare_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start) + uoff_t start) { struct netfs_io_subrequest *subreq; struct iov_iter *wreq_iter =3D &wreq->buffer.iter; @@ -277,7 +277,7 @@ void netfs_issue_write(struct netfs_io_request *wreq, */ size_t netfs_advance_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start, size_t len, bool to_eof) + uoff_t start, size_t len, bool to_eof) { struct netfs_io_subrequest *subreq =3D stream->construct; size_t part; @@ -328,7 +328,7 @@ static int netfs_write_folio(struct netfs_io_request *w= req, struct netfs_folio *finfo; size_t iter_off =3D 0; size_t fsize =3D folio_size(folio), flen =3D fsize, foff =3D 0; - loff_t fpos =3D folio_pos(folio), i_size; + uoff_t fpos =3D folio_pos(folio), i_size; bool to_eof =3D false, streamw =3D false; bool debug =3D false; =20 @@ -719,7 +719,7 @@ static int netfs_write_folio_single(struct netfs_io_req= uest *wreq, struct netfs_io_stream *stream; size_t iter_off =3D 0; size_t fsize =3D folio_size(folio), flen; - loff_t fpos =3D folio_pos(folio); + uoff_t fpos =3D folio_pos(folio); ssize_t ret; bool to_eof =3D false; bool no_debug =3D false; diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index 058bc7a166a5..6cd584242af2 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -55,7 +55,7 @@ static void netfs_retry_write_stream(struct netfs_io_requ= est *wreq, do { struct netfs_io_subrequest *subreq =3D NULL, *from, *to, *tmp; struct iov_iter source; - unsigned long long start, len; + uoff_t start, len; size_t part; bool boundary =3D false; =20 diff --git a/include/linux/fscache-cache.h b/include/linux/fscache-cache.h index 4c91a019972b..ee524c863fa9 100644 --- a/include/linux/fscache-cache.h +++ b/include/linux/fscache-cache.h @@ -67,7 +67,7 @@ struct fscache_cache_ops { =20 /* Change the size of a data object */ void (*resize_cookie)(struct netfs_cache_resources *cres, - loff_t new_size); + uoff_t new_size); =20 /* Invalidate an object */ bool (*invalidate_cookie)(struct fscache_cookie *cookie); diff --git a/include/linux/fscache.h b/include/linux/fscache.h index 58fdb9605425..e19fca38382b 100644 --- a/include/linux/fscache.h +++ b/include/linux/fscache.h @@ -112,7 +112,7 @@ struct fscache_cookie { struct list_head proc_link; /* Link in proc list */ struct list_head commit_link; /* Link in commit queue */ struct work_struct work; /* Commit/relinq/withdraw work */ - loff_t object_size; /* Size of the netfs object */ + uoff_t object_size; /* Size of the netfs object */ unsigned long unused_at; /* Time at which unused (jiffies) */ unsigned long flags; #define FSCACHE_COOKIE_RELINQUISHED 0 /* T if cookie has been relinquishe= d */ @@ -163,22 +163,22 @@ extern struct fscache_cookie *__fscache_acquire_cooki= e( u8, const void *, size_t, const void *, size_t, - loff_t); + uoff_t); extern void __fscache_use_cookie(struct fscache_cookie *, bool); -extern void __fscache_unuse_cookie(struct fscache_cookie *, const void *, = const loff_t *); +extern void __fscache_unuse_cookie(struct fscache_cookie *, const void *, = const uoff_t *); extern void __fscache_relinquish_cookie(struct fscache_cookie *, bool); -extern void __fscache_resize_cookie(struct fscache_cookie *, loff_t); -extern void __fscache_invalidate(struct fscache_cookie *, const void *, lo= ff_t, unsigned int); +extern void __fscache_resize_cookie(struct fscache_cookie *, uoff_t); +extern void __fscache_invalidate(struct fscache_cookie *, const void *, uo= ff_t, unsigned int); extern int __fscache_begin_read_operation(struct netfs_cache_resources *, = struct fscache_cookie *); extern int __fscache_begin_write_operation(struct netfs_cache_resources *,= struct fscache_cookie *); =20 void __fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, - loff_t start, size_t len, loff_t i_size, + uoff_t start, size_t len, uoff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool cond); -extern void __fscache_clear_page_bits(struct address_space *, loff_t, size= _t); +extern void __fscache_clear_page_bits(struct address_space *, uoff_t, size= _t); =20 /** * fscache_acquire_volume - Register a volume as desiring caching services @@ -249,7 +249,7 @@ struct fscache_cookie *fscache_acquire_cookie(struct fs= cache_volume *volume, size_t index_key_len, const void *aux_data, size_t aux_data_len, - loff_t object_size) + uoff_t object_size) { if (!fscache_volume_valid(volume)) return NULL; @@ -286,7 +286,7 @@ static inline void fscache_use_cookie(struct fscache_co= okie *cookie, */ static inline void fscache_unuse_cookie(struct fscache_cookie *cookie, const void *aux_data, - const loff_t *object_size) + const uoff_t *object_size) { if (fscache_cookie_valid(cookie)) __fscache_unuse_cookie(cookie, aux_data, object_size); @@ -327,7 +327,7 @@ static inline void *fscache_get_aux(struct fscache_cook= ie *cookie) */ static inline void fscache_update_aux(struct fscache_cookie *cookie, - const void *aux_data, const loff_t *object_size) + const void *aux_data, const uoff_t *object_size) { void *p =3D fscache_get_aux(cookie); =20 @@ -343,7 +343,7 @@ extern atomic_t fscache_n_updates; =20 static inline void __fscache_update_cookie(struct fscache_cookie *cookie, const void *au= x_data, - const loff_t *object_size) + const uoff_t *object_size) { #ifdef CONFIG_FSCACHE_STATS atomic_inc(&fscache_n_updates); @@ -369,7 +369,7 @@ void __fscache_update_cookie(struct fscache_cookie *coo= kie, const void *aux_data */ static inline void fscache_update_cookie(struct fscache_cookie *cookie, const void *aux_= data, - const loff_t *object_size) + const uoff_t *object_size) { if (fscache_cookie_enabled(cookie)) __fscache_update_cookie(cookie, aux_data, object_size); @@ -386,7 +386,7 @@ void fscache_update_cookie(struct fscache_cookie *cooki= e, const void *aux_data, * description. */ static inline -void fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size) +void fscache_resize_cookie(struct fscache_cookie *cookie, uoff_t new_size) { if (fscache_cookie_enabled(cookie)) __fscache_resize_cookie(cookie, new_size); @@ -413,7 +413,7 @@ void fscache_resize_cookie(struct fscache_cookie *cooki= e, loff_t new_size) */ static inline void fscache_invalidate(struct fscache_cookie *cookie, - const void *aux_data, loff_t size, unsigned int flags) + const void *aux_data, uoff_t size, unsigned int flags) { if (fscache_cookie_enabled(cookie)) __fscache_invalidate(cookie, aux_data, size, flags); @@ -502,7 +502,7 @@ static inline void fscache_end_operation(struct netfs_c= ache_resources *cres) */ static inline int fscache_read(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, @@ -561,7 +561,7 @@ int fscache_begin_write_operation(struct netfs_cache_re= sources *cres, */ static inline int fscache_write(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv) @@ -581,7 +581,7 @@ int fscache_write(struct netfs_cache_resources *cres, * waiting. */ static inline void fscache_clear_page_bits(struct address_space *mapping, - loff_t start, size_t len, + uoff_t start, size_t len, bool caching) { if (caching) @@ -615,7 +615,7 @@ static inline void fscache_clear_page_bits(struct addre= ss_space *mapping, */ static inline void fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, - loff_t start, size_t len, loff_t i_size, + uoff_t start, size_t len, uoff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool caching) diff --git a/include/linux/netfs.h b/include/linux/netfs.h index a8702bd52d3a..8c78c91fefd9 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -62,8 +62,8 @@ struct netfs_inode { struct fscache_cookie *cache; #endif struct list_head wb_queue; /* Queue of processes wanting to do writeback = */ - loff_t _remote_i_size; /* Size of the remote file */ - loff_t _zero_point; /* Size after which we assume there's no data + uoff_t _remote_i_size; /* Size of the remote file */ + uoff_t _zero_point; /* Size after which we assume there's no data * on the server */ spinlock_t lock; /* Lock covering wb_queue */ atomic_t io_count; /* Number of outstanding reqs */ @@ -142,7 +142,7 @@ struct netfs_io_stream { void (*issue_write)(struct netfs_io_subrequest *subreq); /* Collection tracking */ struct list_head subrequests; /* Contributory I/O operations */ - unsigned long long collected_to; /* Position we've collected results to */ + uoff_t collected_to; /* Position we've collected results to */ size_t transferred; /* The amount transferred from this stream */ unsigned short error; /* Aggregate error for the stream */ enum netfs_io_source source; /* Where to read from/write to */ @@ -177,7 +177,7 @@ struct netfs_io_subrequest { struct work_struct work; struct list_head rreq_link; /* Link in rreq->subrequests */ struct iov_iter io_iter; /* Iterator for this subrequest */ - unsigned long long start; /* Where to start the I/O */ + uoff_t start; /* Where to start the I/O */ size_t len; /* Size of the I/O */ size_t transferred; /* Amount of data transferred */ refcount_t ref; @@ -243,17 +243,17 @@ struct netfs_io_request { void *netfs_priv; /* Private data for the netfs */ void *netfs_priv2; /* Private data for the netfs */ struct bio_vec *direct_bv; /* DIO buffer list (when handling iovec-iter)= */ - unsigned long long submitted; /* Amount submitted for I/O so far */ - unsigned long long len; /* Length of the request */ + uoff_t submitted; /* Amount submitted for I/O so far */ + uoff_t len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ size_t progress_at; /* Report read progress when hit this much read */ long error; /* 0 or error that occurred */ - unsigned long long i_size; /* Size of the file */ - unsigned long long start; /* Start position */ + uoff_t i_size; /* Size of the file */ + uoff_t start; /* Start position */ atomic64_t issued_to; /* Write issuer folio cursor */ - unsigned long long collected_to; /* Point we've collected to */ - unsigned long long cleaned_to; /* Position we've cleaned folios to */ - unsigned long long abandon_to; /* Position to abandon folios to */ + uoff_t collected_to; /* Point we've collected to */ + uoff_t cleaned_to; /* Position we've cleaned folios to */ + uoff_t abandon_to; /* Position to abandon folios to */ const struct folio *no_unlock_folio; /* Don't unlock this folio after rea= d */ gfp_t gfp; /* GFP flags to use */ unsigned int direct_bv_count; /* Number of elements in direct_bv[] */ @@ -298,12 +298,12 @@ struct netfs_request_ops { int (*prepare_read)(struct netfs_io_subrequest *subreq); void (*issue_read)(struct netfs_io_subrequest *subreq); bool (*is_still_valid)(struct netfs_io_request *rreq); - int (*check_write_begin)(struct file *file, loff_t pos, unsigned len, + int (*check_write_begin)(struct file *file, uoff_t pos, unsigned len, struct folio **foliop, void **_fsdata); void (*done)(struct netfs_io_request *rreq); =20 /* Modification handling */ - void (*update_i_size)(struct inode *inode, loff_t i_size); + void (*update_i_size)(struct inode *inode, uoff_t i_size); void (*post_modify)(struct inode *inode); =20 /* Write request handling */ @@ -331,7 +331,7 @@ struct netfs_cache_ops { =20 /* Read data from the cache */ int (*read)(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, @@ -339,7 +339,7 @@ struct netfs_cache_ops { =20 /* Write data to the cache */ int (*write)(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv); @@ -349,15 +349,15 @@ struct netfs_cache_ops { =20 /* Expand readahead request */ void (*expand_readahead)(struct netfs_cache_resources *cres, - unsigned long long *_start, - unsigned long long *_len, - unsigned long long i_size); + uoff_t *_start, + uoff_t *_len, + uoff_t i_size); =20 /* Prepare a read operation, shortening it to a cached/uncached * boundary as appropriate. */ enum netfs_io_source (*prepare_read)(struct netfs_io_subrequest *subreq, - unsigned long long i_size); + uoff_t i_size); =20 /* Prepare a write subrequest, working out if we're allowed to do it * and finding out the maximum amount of data to gather before @@ -370,15 +370,15 @@ struct netfs_cache_ops { * actually do. */ int (*prepare_write)(struct netfs_cache_resources *cres, - loff_t *_start, size_t *_len, size_t upper_len, - loff_t i_size, bool no_space_allocated_yet); + uoff_t *_start, size_t *_len, size_t upper_len, + uoff_t i_size, bool no_space_allocated_yet); =20 /* Query the occupancy of the cache in a region, returning where the * next chunk of data starts and how long it is. */ int (*query_occupancy)(struct netfs_cache_resources *cres, - loff_t start, size_t len, size_t granularity, - loff_t *_data_start, size_t *_data_len); + uoff_t start, size_t len, size_t granularity, + uoff_t *_data_start, size_t *_data_len); }; =20 /* High-level read API. */ @@ -409,7 +409,7 @@ struct readahead_control; void netfs_readahead(struct readahead_control *); int netfs_read_folio(struct file *, struct folio *); int netfs_write_begin(struct netfs_inode *, struct file *, - struct address_space *, loff_t pos, unsigned int len, + struct address_space *, uoff_t pos, unsigned int len, struct folio **, void **fsdata); int netfs_writepages(struct address_space *mapping, struct writeback_control *wbc); @@ -487,10 +487,10 @@ static inline struct netfs_inode *netfs_inode(struct = inode *inode) * cmpxchg8b without the need of the lock prefix). For SMP compiles and 6= 4bit * archs it makes no difference if preempt is enabled or not. */ -static inline unsigned long long netfs_read_remote_i_size(const struct ino= de *inode) +static inline uoff_t netfs_read_remote_i_size(const struct inode *inode) { const struct netfs_inode *ictx =3D container_of(inode, struct netfs_inode= , inode); - unsigned long long remote_i_size; + uoff_t remote_i_size; =20 #if BITS_PER_LONG=3D=3D32 && defined(CONFIG_SMP) unsigned int seq; @@ -525,7 +525,7 @@ static inline unsigned long long netfs_read_remote_i_si= ze(const struct inode *in * spinning forever. */ static inline void netfs_write_remote_i_size(struct inode *inode, - unsigned long long remote_i_size) + uoff_t remote_i_size) { struct netfs_inode *ictx =3D netfs_inode(inode); =20 @@ -562,10 +562,10 @@ static inline void netfs_write_remote_i_size(struct i= node *inode, * cmpxchg8b without the need of the lock prefix). For SMP compiles and 6= 4bit * archs it makes no difference if preempt is enabled or not. */ -static inline unsigned long long netfs_read_zero_point(const struct inode = *inode) +static inline uoff_t netfs_read_zero_point(const struct inode *inode) { struct netfs_inode *ictx =3D container_of(inode, struct netfs_inode, inod= e); - unsigned long long zero_point; + uoff_t zero_point; =20 #if BITS_PER_LONG=3D=3D32 && defined(CONFIG_SMP) unsigned int seq; @@ -600,7 +600,7 @@ static inline unsigned long long netfs_read_zero_point(= const struct inode *inode * forever. */ static inline void netfs_write_zero_point(struct inode *inode, - unsigned long long zero_point) + uoff_t zero_point) { struct netfs_inode *ictx =3D netfs_inode(inode); =20 @@ -641,9 +641,9 @@ static inline void netfs_write_zero_point(struct inode = *inode, * archs it makes no difference if preempt is enabled or not. */ static inline void netfs_read_sizes(const struct inode *inode, - unsigned long long *i_size, - unsigned long long *remote_i_size, - unsigned long long *zero_point) + uoff_t *i_size, + uoff_t *remote_i_size, + uoff_t *zero_point) { const struct netfs_inode *ictx =3D container_of(inode, struct netfs_inode= , inode); #if BITS_PER_LONG=3D=3D32 && defined(CONFIG_SMP) @@ -689,9 +689,9 @@ static inline void netfs_read_sizes(const struct inode = *inode, * forever. */ static inline void netfs_write_sizes(struct inode *inode, - unsigned long long i_size, - unsigned long long remote_i_size, - unsigned long long zero_point) + uoff_t i_size, + uoff_t remote_i_size, + uoff_t zero_point) { struct netfs_inode *ictx =3D netfs_inode(inode); =20 @@ -759,7 +759,7 @@ static inline void netfs_inode_init(struct netfs_inode = *ctx, * Inform the netfs lib that a file got resized so that it can adjust its = state. */ static inline void netfs_resize_file(struct netfs_inode *ictx, - unsigned long long new_i_size, + uoff_t new_i_size, bool changed_on_server) { #if BITS_PER_LONG=3D=3D32 && defined(CONFIG_SMP) diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cache= files.h index 927338f8fe85..4412d8344182 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -449,7 +449,7 @@ TRACE_EVENT(cachefiles_vol_coherency, =20 TRACE_EVENT(cachefiles_prep_read, TP_PROTO(struct cachefiles_object *obj, - loff_t start, + uoff_t start, size_t len, unsigned short flags, enum netfs_io_source source, @@ -464,7 +464,7 @@ TRACE_EVENT(cachefiles_prep_read, __field(enum netfs_io_source, source) __field(enum cachefiles_prepare_read_trace, why) __field(size_t, len) - __field(loff_t, start) + __field(uoff_t, start) __field(unsigned int, netfs_inode) __field(unsigned int, cache_inode) ), @@ -492,16 +492,16 @@ TRACE_EVENT(cachefiles_prep_read, TRACE_EVENT(cachefiles_read, TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t start, + uoff_t start, size_t len), =20 TP_ARGS(obj, backer, start, len), =20 TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, backer) - __field(size_t, len) - __field(loff_t, start) + __field(unsigned int, obj) + __field(unsigned int, backer) + __field(size_t, len) + __field(uoff_t, start) ), =20 TP_fast_assign( @@ -521,16 +521,16 @@ TRACE_EVENT(cachefiles_read, TRACE_EVENT(cachefiles_write, TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t start, + uoff_t start, size_t len), =20 TP_ARGS(obj, backer, start, len), =20 TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, backer) - __field(size_t, len) - __field(loff_t, start) + __field(unsigned int, obj) + __field(unsigned int, backer) + __field(size_t, len) + __field(uoff_t, start) ), =20 TP_fast_assign( @@ -549,7 +549,7 @@ TRACE_EVENT(cachefiles_write, =20 TRACE_EVENT(cachefiles_trunc, TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t from, loff_t to, enum cachefiles_trunc_trace why), + uoff_t from, uoff_t to, enum cachefiles_trunc_trace why), =20 TP_ARGS(obj, backer, from, to, why), =20 @@ -557,8 +557,8 @@ TRACE_EVENT(cachefiles_trunc, __field(unsigned int, obj) __field(unsigned int, backer) __field(enum cachefiles_trunc_trace, why) - __field(loff_t, from) - __field(loff_t, to) + __field(uoff_t, from) + __field(uoff_t, to) ), =20 TP_fast_assign( diff --git a/include/trace/events/fscache.h b/include/trace/events/fscache.h index f1a73aa83fbb..8735d428ebd9 100644 --- a/include/trace/events/fscache.h +++ b/include/trace/events/fscache.h @@ -460,13 +460,13 @@ TRACE_EVENT(fscache_relinquish, ); =20 TRACE_EVENT(fscache_invalidate, - TP_PROTO(struct fscache_cookie *cookie, loff_t new_size), + TP_PROTO(struct fscache_cookie *cookie, uoff_t new_size), =20 TP_ARGS(cookie, new_size), =20 TP_STRUCT__entry( __field(unsigned int, cookie ) - __field(loff_t, new_size ) + __field(uoff_t, new_size ) ), =20 TP_fast_assign( @@ -479,14 +479,14 @@ TRACE_EVENT(fscache_invalidate, ); =20 TRACE_EVENT(fscache_resize, - TP_PROTO(struct fscache_cookie *cookie, loff_t new_size), + TP_PROTO(struct fscache_cookie *cookie, uoff_t new_size), =20 TP_ARGS(cookie, new_size), =20 TP_STRUCT__entry( __field(unsigned int, cookie ) - __field(loff_t, old_size ) - __field(loff_t, new_size ) + __field(uoff_t, old_size ) + __field(uoff_t, new_size ) ), =20 TP_fast_assign( diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 8ec10c076875..4f5a447d92b1 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -296,7 +296,7 @@ netfs_folioq_traces; =20 TRACE_EVENT(netfs_read, TP_PROTO(struct netfs_io_request *rreq, - loff_t start, size_t len, + uoff_t start, size_t len, enum netfs_read_trace what), =20 TP_ARGS(rreq, start, len, what), @@ -304,8 +304,8 @@ TRACE_EVENT(netfs_read, TP_STRUCT__entry( __field(unsigned int, rreq) __field(unsigned int, cookie) - __field(loff_t, i_size) - __field(loff_t, start) + __field(uoff_t, i_size) + __field(uoff_t, start) __field(size_t, len) __field(enum netfs_read_trace, what) __field(u64, netfs_inode) @@ -372,7 +372,7 @@ TRACE_EVENT(netfs_sreq, __field(u8, slot) __field(size_t, len) __field(size_t, transferred) - __field(loff_t, start) + __field(uoff_t, start) ), =20 TP_fast_assign( @@ -413,7 +413,7 @@ TRACE_EVENT(netfs_failure, __field(enum netfs_failure, what) __field(size_t, len) __field(size_t, transferred) - __field(loff_t, start) + __field(uoff_t, start) ), =20 TP_fast_assign( @@ -519,10 +519,10 @@ TRACE_EVENT(netfs_write_iter, TP_ARGS(iocb, from), =20 TP_STRUCT__entry( - __field(unsigned long long, start) - __field(size_t, len) - __field(unsigned int, flags) - __field(unsigned int, ino) + __field(uoff_t, start) + __field(size_t, len) + __field(unsigned int, flags) + __field(unsigned int, ino) ), =20 TP_fast_assign( @@ -547,8 +547,8 @@ TRACE_EVENT(netfs_write, __field(unsigned int, cookie) __field(unsigned int, ino) __field(enum netfs_write_trace, what) - __field(unsigned long long, start) - __field(unsigned long long, len) + __field(uoff_t, start) + __field(uoff_t, len) ), =20 TP_fast_assign( @@ -577,10 +577,10 @@ TRACE_EVENT(netfs_copy2cache, TP_ARGS(rreq, creq), =20 TP_STRUCT__entry( - __field(unsigned int, rreq) - __field(unsigned int, creq) - __field(unsigned int, cookie) - __field(unsigned int, ino) + __field(unsigned int, rreq) + __field(unsigned int, creq) + __field(unsigned int, cookie) + __field(unsigned int, ino) ), =20 TP_fast_assign( @@ -605,10 +605,10 @@ TRACE_EVENT(netfs_collect, TP_ARGS(wreq), =20 TP_STRUCT__entry( - __field(unsigned int, wreq) - __field(unsigned int, len) - __field(unsigned long long, transferred) - __field(unsigned long long, start) + __field(unsigned int, wreq) + __field(unsigned int, len) + __field(uoff_t, transferred) + __field(uoff_t, start) ), =20 TP_fast_assign( @@ -631,12 +631,12 @@ TRACE_EVENT(netfs_collect_sreq, TP_ARGS(wreq, subreq), =20 TP_STRUCT__entry( - __field(unsigned int, wreq) - __field(unsigned int, subreq) - __field(unsigned int, stream) - __field(unsigned int, len) - __field(unsigned int, transferred) - __field(unsigned long long, start) + __field(unsigned int, wreq) + __field(unsigned int, subreq) + __field(unsigned int, stream) + __field(unsigned int, len) + __field(unsigned int, transferred) + __field(uoff_t, start) ), =20 TP_fast_assign( @@ -656,17 +656,16 @@ TRACE_EVENT(netfs_collect_sreq, TRACE_EVENT(netfs_collect_folio, TP_PROTO(const struct netfs_io_request *wreq, const struct folio *folio, - unsigned long long fend, - unsigned long long collected_to), + uoff_t fend, uoff_t collected_to), =20 TP_ARGS(wreq, folio, fend, collected_to), =20 TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned long, index) - __field(unsigned long long, fend) - __field(unsigned long long, cleaned_to) - __field(unsigned long long, collected_to) + __field(uoff_t, fend) + __field(uoff_t, cleaned_to) + __field(uoff_t, collected_to) ), =20 TP_fast_assign( @@ -679,13 +678,13 @@ TRACE_EVENT(netfs_collect_folio, =20 TP_printk("R=3D%08x ix=3D%05lx r=3D%llx-%llx t=3D%llx/%llx", __entry->wreq, __entry->index, - (unsigned long long)__entry->index * PAGE_SIZE, __entry->fend, + (uoff_t)__entry->index * PAGE_SIZE, __entry->fend, __entry->cleaned_to, __entry->collected_to) ); =20 TRACE_EVENT(netfs_collect_state, TP_PROTO(const struct netfs_io_request *wreq, - unsigned long long collected_to, + uoff_t collected_to, unsigned int notes), =20 TP_ARGS(wreq, collected_to, notes), @@ -693,8 +692,8 @@ TRACE_EVENT(netfs_collect_state, TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned int, notes) - __field(unsigned long long, collected_to) - __field(unsigned long long, cleaned_to) + __field(uoff_t, collected_to) + __field(uoff_t, cleaned_to) ), =20 TP_fast_assign( @@ -713,7 +712,7 @@ TRACE_EVENT(netfs_collect_state, TRACE_EVENT(netfs_collect_gap, TP_PROTO(const struct netfs_io_request *wreq, const struct netfs_io_stream *stream, - unsigned long long jump_to, char type), + uoff_t jump_to, char type), =20 TP_ARGS(wreq, stream, jump_to, type), =20 @@ -721,8 +720,8 @@ TRACE_EVENT(netfs_collect_gap, __field(unsigned int, wreq) __field(unsigned char, stream) __field(unsigned char, type) - __field(unsigned long long, from) - __field(unsigned long long, to) + __field(uoff_t, from) + __field(uoff_t, to) ), =20 TP_fast_assign( @@ -747,8 +746,8 @@ TRACE_EVENT(netfs_collect_stream, TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned char, stream) - __field(unsigned long long, collected_to) - __field(unsigned long long, issued_to) + __field(uoff_t, collected_to) + __field(uoff_t, issued_to) ), =20 TP_fast_assign( From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AC42C43B6EB for ; Mon, 24 Aug 2026 14:42:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582573; cv=none; b=KxBfYCSEtOLH8Fh0sTtiaupUkzUMke/Sqjo04hJjMIYEFRLwHO2iB0QVdMmNfOgL6xD+Jlp5x0NWFTbpbd295KtM8CHUJt0iayXZ2wW8vJIm0klXqPqNSj/Qb4PKu2Bad/fMr+dKTyFtySATNHrRJqjrxICl2n0QMrsp0x8rubE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582573; c=relaxed/simple; bh=WnUrIoFsPW78MzYAVx0FrQKBJh5ob5aqpScAtxwFRGA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NnnZnDWE73enuFeL4Og8DY0oy8aGUEQ6FEggkd5YYTIqmXEjsnBfGjo8Zk0u+14X8siHtoTTWlD1JMC1LnfO0TYVRfbPhT/r5/IKYMwmKEZVXJSW0BU2p5JgemvQiGS5AxFy2/0GUAJKNiPZcG3GmFPe3NOrhqMUsHAiHGicR/I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Q5lTPiLN; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Q5lTPiLN" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582565; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oAh3PBs47lZUHcwbeGXM5gS5ZzePV60tR0gQfqSs8Ac=; b=Q5lTPiLNrAxhSbvCBwpkTkt4i+qFsNGEYlVetEwe35Op2PmASd084kTQ4AvfYA22DYmTBT hGXHlQ0Xic8XBfrifgOU3+E6O5y9Q+3vInep+zRD8bmtDijXSheCZUhwrNCDVOt5tTKU2G Lh66qCcVfVCHbqF/daLV2C/W4BvGc+I= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-554-fezJf3dqOA29Pdmv6Zl6fA-1; Mon, 24 Aug 2026 10:42:40 -0400 X-MC-Unique: fezJf3dqOA29Pdmv6Zl6fA-1 X-Mimecast-MFC-AGG-ID: fezJf3dqOA29Pdmv6Zl6fA_1787582557 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4A7F818601C5; Mon, 24 Aug 2026 14:42:37 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id BCA1D1800767; Mon, 24 Aug 2026 14:42:29 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [PATCH v10 06/35] mm: Make readahead store folio count in readahead_control Date: Mon, 24 Aug 2026 15:40:59 +0100 Message-ID: <20260824144130.759997-7-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Content-Type: text/plain; charset="utf-8" Make readahead store folio count in readahead_control so that the filesystem can know in advance how many folios it needs to keep track of. This is cleared by read_pages() in case it is called from a loop. The count is accessed by the filesystem with readahead_folio_count(). Signed-off-by: David Howells cc: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-mm@kvack.org cc: linux-fsdevel@vger.kernel.org --- include/linux/pagemap.h | 10 ++++++++++ mm/readahead.c | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index 2c3718d592d6..e1e51bace388 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -1348,6 +1348,7 @@ struct readahead_control { struct file_ra_state *ra; /* private: use the readahead_* accessors instead */ pgoff_t _index; + unsigned int _nr_folios; unsigned int _nr_pages; unsigned int _batch_count; bool dropbehind; @@ -1527,6 +1528,15 @@ static inline size_t readahead_batch_length(const st= ruct readahead_control *rac) return rac->_batch_count * PAGE_SIZE; } =20 +/** + * readahead_folio_count - Get the number of folios in this readahead requ= est. + * @rac: The readahead request. + */ +static inline unsigned int readahead_folio_count(const struct readahead_co= ntrol *rac) +{ + return rac->_nr_folios; +} + static inline unsigned long dir_pages(const struct inode *inode) { return (unsigned long)(inode->i_size + PAGE_SIZE - 1) >> diff --git a/mm/readahead.c b/mm/readahead.c index 558c92957518..069ded56fd80 100644 --- a/mm/readahead.c +++ b/mm/readahead.c @@ -188,6 +188,7 @@ static void read_pages(struct readahead_control *rac) if (unlikely(rac->_workingset)) psi_memstall_leave(&rac->_pflags); rac->_workingset =3D false; + rac->_nr_folios =3D 0; =20 BUG_ON(readahead_count(rac)); } @@ -303,6 +304,7 @@ void page_cache_ra_unbounded(struct readahead_control *= ractl, if (i =3D=3D mark) folio_set_readahead(folio); ractl->_workingset |=3D folio_test_workingset(folio); + ractl->_nr_folios++; ractl->_nr_pages +=3D min_nrpages; i +=3D min_nrpages; } @@ -473,6 +475,7 @@ static inline int ra_alloc_folio(struct readahead_contr= ol *ractl, pgoff_t index, return err; } =20 + ractl->_nr_folios++; ractl->_nr_pages +=3D 1UL << order; ractl->_workingset |=3D folio_test_workingset(folio); return 0; @@ -822,6 +825,7 @@ void readahead_expand(struct readahead_control *ractl, ractl->_workingset =3D true; psi_memstall_enter(&ractl->_pflags); } + ractl->_nr_folios++; ractl->_nr_pages +=3D min_nrpages; ractl->_index =3D folio->index; } @@ -851,6 +855,7 @@ void readahead_expand(struct readahead_control *ractl, ractl->_workingset =3D true; psi_memstall_enter(&ractl->_pflags); } + ractl->_nr_folios++; ractl->_nr_pages +=3D min_nrpages; if (ra) { ra->size +=3D min_nrpages; From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1EE1643E9FE for ; Mon, 24 Aug 2026 14:42:56 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582583; cv=none; b=NpErOaBSu1T9QwPRI9AgpYd8e/3l8q0OrVXmAlKcdWczV0E+dALJileU411dmYGilzGKzq0iHhqzPWLPhQw01wcaZrBLfHoDxcK8Ex8xfeRCrkLhDxbgJRQU9QwCidu13REkRXf7Ol3m6o9dtRu7dTpQBwT7Z9zImqO91jAmSFI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582583; c=relaxed/simple; bh=p0vbe70Yj59SVi9liwbEFATvWMMs9N0JzO2Z8iwGP1s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=d++S/7p7F35MTDKs1j1eonndrsnBZb2RqApiatTTgnmEkMKvmRTS83fssv5e2OfUL4LxMjz1ya61/ckSR2/xYup+wxJ1CMGIPn5BiXiV8kzZEqtEuplw2zOubte3rhFn9RmryklvkQ/lV7I8qBJnVM3cB+lBcYihUmxZTtABAls= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=NrVLWR0j; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="NrVLWR0j" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582574; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gRNZmNiLLHfpFB28y7PTJWtB/r0o/0KMXHwmtUXX9FU=; b=NrVLWR0jagHtHE+j9387qkyey/CAzy0p0M8oAD85ZTb8ddZqKYn86/9Fxao0teK6JbB0DA Sn2O0ghU2laG9dTWV501PGVGQHaByu1r+95IR1xy3Vt/ntazoZB8Ju2c8h4aLNtfe8pIzB SnMJm/fVWKFpCuLy3VKqWHhx4k6r5XY= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-373-aImLARBcPzGnjMDOa9oV5Q-1; Mon, 24 Aug 2026 10:42:49 -0400 X-MC-Unique: aImLARBcPzGnjMDOa9oV5Q-1 X-Mimecast-MFC-AGG-ID: aImLARBcPzGnjMDOa9oV5Q_1787582566 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1B9D5195FCFC; Mon, 24 Aug 2026 14:42:46 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id DEA8C1955DAA; Mon, 24 Aug 2026 14:42:38 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-mm@kvack.org Subject: [PATCH v10 07/35] netfs: Bulk load the readahead-provided folios up front Date: Mon, 24 Aug 2026 15:41:00 +0100 Message-ID: <20260824144130.759997-8-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" Load all the folios by the VM for readahead up front into the folio queue. With the number of folios provided by the VM, the folio queue can be fully allocated first and then the loading happen in one go inside the RCU read lock. The folio refs acquired from readahead are dropped in bulk once the first subrequest is dispatched as it's quite a slow operation. The collector waits for NETFS_RREQ_NEED_PUT_RA_REFS to be cleared so that it doesn't unlock folios before the xarray has been scanned for them. This simplifies the buffer handling later and isn't noticeably slower as the xarray doesn't need to be modified and the folios are all already pre-locked. Signed-off-by: David Howells Reviewed-by: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-mm@kvack.org cc: linux-fsdevel@vger.kernel.org --- fs/netfs/buffered_read.c | 103 +++++++++++++++++++-------------- fs/netfs/internal.h | 1 + fs/netfs/misc.c | 19 ++++++ fs/netfs/read_collect.c | 7 +++ fs/netfs/read_retry.c | 7 +++ fs/netfs/rolling_buffer.c | 75 ++++++++++++++++++++++++ include/linux/netfs.h | 1 + include/linux/rolling_buffer.h | 3 + include/trace/events/netfs.h | 3 + 9 files changed, 177 insertions(+), 42 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 7e58aec6cdff..77f7dfc816a8 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -54,6 +54,42 @@ static void netfs_rreq_expand(struct netfs_io_request *r= req, } } =20 +/* + * Drop the folio refs acquired from the readahead API. + */ +static void netfs_bulk_drop_ra_refs(struct netfs_io_request *rreq) +{ + struct folio_batch fbatch; + struct folio *folio; + pgoff_t nr_pages =3D DIV_ROUND_UP(rreq->len, PAGE_SIZE); + pgoff_t first =3D rreq->start / PAGE_SIZE; + XA_STATE(xas, &rreq->mapping->i_pages, first); + + folio_batch_init(&fbatch); + + rcu_read_lock(); + + xas_for_each(&xas, folio, first + nr_pages - 1) { + if (xas_retry(&xas, folio)) + continue; + + if (!folio_batch_add(&fbatch, folio)) + folio_batch_release(&fbatch); + } + + rcu_read_unlock(); + folio_batch_release(&fbatch); + trace_netfs_rreq(rreq, netfs_rreq_trace_ra_put_ref); + clear_bit_unlock(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); + wake_up(&rreq->waitq); +} + +static void netfs_maybe_bulk_drop_ra_refs(struct netfs_io_request *rreq) +{ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_bulk_drop_ra_refs(rreq); +} + /* * Begin an operation, and fetch the stored zero point value from the cook= ie if * available. @@ -74,12 +110,8 @@ static int netfs_begin_cache_read(struct netfs_io_reque= st *rreq, struct netfs_in * * Returns the limited size if successful and -ENOMEM if insufficient memo= ry * available. - * - * [!] NOTE: This must be run in the same thread as ->issue_read() was cal= led - * in as we access the readahead_control struct. */ -static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *sub= req, - struct readahead_control *ractl) +static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *sub= req) { struct netfs_io_request *rreq =3D subreq->rreq; size_t rsize =3D subreq->len; @@ -87,33 +119,6 @@ static ssize_t netfs_prepare_read_iterator(struct netfs= _io_subrequest *subreq, if (subreq->source =3D=3D NETFS_DOWNLOAD_FROM_SERVER) rsize =3D umin(rsize, rreq->io_streams[0].sreq_max_len); =20 - if (ractl) { - /* If we don't have sufficient folios in the rolling buffer, - * extract a folioq's worth from the readahead region at a time - * into the buffer. Note that this acquires a ref on each page - * that we will need to release later - but we don't want to do - * that until after we've started the I/O. - */ - struct folio_batch put_batch; - - folio_batch_init(&put_batch); - while (rreq->submitted < subreq->start + rsize) { - ssize_t added; - - added =3D rolling_buffer_load_from_ra(&rreq->buffer, ractl, - &put_batch); - if (added < 0) { - folio_batch_release(&put_batch); - return added; - } - - if (!rreq->progress_at) - netfs_read_set_unlock_at(rreq); - rreq->submitted +=3D added; - } - folio_batch_release(&put_batch); - } - subreq->len =3D rsize; if (unlikely(rreq->io_streams[0].sreq_max_segs)) { size_t limit =3D netfs_limit_iter(&rreq->buffer.iter, 0, rsize, @@ -211,8 +216,7 @@ static void netfs_issue_read(struct netfs_io_request *r= req, * slicing up the region to be read according to available cache blocks and * network rsize. */ -static void netfs_read_to_pagecache(struct netfs_io_request *rreq, - struct readahead_control *ractl) +static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { ssize_t size =3D rreq->len; uoff_t start =3D rreq->start; @@ -291,7 +295,7 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq, break; =20 issue: - slice =3D netfs_prepare_read_iterator(subreq, ractl); + slice =3D netfs_prepare_read_iterator(subreq); if (slice < 0) { ret =3D slice; netfs_cancel_read(subreq, ret); @@ -305,6 +309,7 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq, } =20 netfs_issue_read(rreq, subreq); + netfs_maybe_bulk_drop_ra_refs(rreq); =20 if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags)) netfs_wait_for_paused_read(rreq); @@ -342,6 +347,7 @@ void netfs_readahead(struct readahead_control *ractl) { struct netfs_io_request *rreq; struct netfs_inode *ictx =3D netfs_inode(ractl->mapping->host); + ssize_t added; uoff_t start =3D readahead_pos(ractl); size_t size =3D readahead_length(ractl); int ret; @@ -363,11 +369,24 @@ void netfs_readahead(struct readahead_control *ractl) =20 netfs_rreq_expand(rreq, ractl); =20 - rreq->submitted =3D rreq->start; - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->g= fp) < 0) + /* Load the folios to be read into a bvecq chain. Note that this + * acquires a ref on each folio that we will need to release later - + * but we don't want to do that until after we've started the I/O. + */ + added =3D rolling_buffer_bulk_load_from_ra(&rreq->buffer, ractl, + rreq->debug_id, rreq->gfp); + if (added < 0) { + ret =3D added; goto cleanup_free; - netfs_read_to_pagecache(rreq, ractl); + } + __set_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); + + rreq->submitted =3D rreq->start + added; + rreq->cleaned_to =3D rreq->start; + netfs_read_set_unlock_at(rreq); =20 + netfs_read_to_pagecache(rreq); + netfs_maybe_bulk_drop_ra_refs(rreq); return netfs_put_request(rreq, netfs_rreq_trace_put_return); =20 cleanup_free: @@ -461,7 +480,7 @@ static int netfs_read_gaps(struct file *file, struct fo= lio *folio) iov_iter_bvec(&rreq->buffer.iter, ITER_DEST, bvec, i, rreq->len); rreq->submitted =3D rreq->start + flen; =20 - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); =20 ret =3D netfs_wait_for_read(rreq); if (ret >=3D 0) { @@ -536,7 +555,7 @@ int netfs_read_folio(struct file *file, struct folio *f= olio) if (ret < 0) goto discard; =20 - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret =3D netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; @@ -693,7 +712,7 @@ int netfs_write_begin(struct netfs_inode *ctx, if (ret < 0) goto error_put; =20 - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret =3D netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); if (ret < 0) @@ -758,7 +777,7 @@ int netfs_prefetch_for_write(struct file *file, struct = folio *folio, if (ret < 0) goto error_put; =20 - netfs_read_to_pagecache(rreq, NULL); + netfs_read_to_pagecache(rreq); ret =3D netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 4ea3ef8b3bb0..5680e37fc70d 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -79,6 +79,7 @@ ssize_t netfs_wait_for_read(struct netfs_io_request *rreq= ); ssize_t netfs_wait_for_write(struct netfs_io_request *rreq); void netfs_wait_for_paused_read(struct netfs_io_request *rreq); void netfs_wait_for_paused_write(struct netfs_io_request *rreq); +void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq); =20 /* * objects.c diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index f58a0d45e614..eafc4edae6a0 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -563,3 +563,22 @@ void netfs_wait_for_paused_write(struct netfs_io_reque= st *rreq) { return netfs_wait_for_pause(rreq, netfs_write_collection); } + +/* + * Wait for the readahead-acquired refs to be put. + */ +void netfs_wait_for_put_ra_refs(struct netfs_io_request *rreq) +{ + DEFINE_WAIT(myself); + + for (;;) { + trace_netfs_rreq(rreq, netfs_rreq_trace_wait_put_ra_refs); + prepare_to_wait(&rreq->waitq, &myself, TASK_UNINTERRUPTIBLE); + if (!test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + break; + schedule(); + } + + trace_netfs_rreq(rreq, netfs_rreq_trace_waited_put_ra_refs); + finish_wait(&rreq->waitq, &myself); +} diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index a29b694ce1e8..72afc17ecbb3 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -147,6 +147,13 @@ static void netfs_read_unlock_folios(struct netfs_io_r= equest *rreq, slot =3D 0; } =20 + /* We have to wait for readahead refs to have been released before we + * can unlock any folios as the ref-dropper walks i_pages and the only + * thing preventing these folios from being removed is the folio lock. + */ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_wait_for_put_ra_refs(rreq); + for (;;) { struct folio *folio; uoff_t fpos =3D rreq->cleaned_to, fend; diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index f5c5ed12dd02..61dbd644d6bb 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -292,6 +292,13 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io= _request *rreq) { struct folio_queue *p; =20 + /* We have to wait for readahead refs to have been released before we + * can unlock any folios as the ref-dropper walks i_pages and the only + * thing preventing these folios from being removed is the folio lock. + */ + if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) + netfs_wait_for_put_ra_refs(rreq); + for (p =3D rreq->buffer.tail; p; p =3D p->next) { for (int slot =3D 0; slot < folioq_count(p); slot++) { struct folio *folio =3D folioq_folio(p, slot); diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c index 8c0026836f9c..ff6c7ee23f4d 100644 --- a/fs/netfs/rolling_buffer.c +++ b/fs/netfs/rolling_buffer.c @@ -153,6 +153,81 @@ ssize_t rolling_buffer_load_from_ra(struct rolling_buf= fer *roll, return size; } =20 +/* + * Decant the entire list of folios to read into a rolling buffer. + */ +ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, + struct readahead_control *ractl, + unsigned int rreq_id, gfp_t gfp) +{ + XA_STATE(xas, &ractl->mapping->i_pages, ractl->_index); + struct folio_queue *fq; + struct folio *folio; + ssize_t loaded =3D 0; + int nr, slot =3D 0, npages =3D 0; + + /* First allocate all the folioqs we're going to need to avoid having + * to deal with ENOMEM later. + */ + nr =3D ractl->_nr_folios; + do { + fq =3D netfs_folioq_alloc(rreq_id, gfp, + netfs_trace_folioq_make_space); + if (!fq) { + rolling_buffer_clear(roll); + return -ENOMEM; + } + fq->prev =3D roll->head; + if (!roll->tail) + roll->tail =3D fq; + else + roll->head->next =3D fq; + roll->head =3D fq; + + nr -=3D folioq_nr_slots(fq); + } while (nr > 0); + + rcu_read_lock(); + + fq =3D roll->tail; + xas_for_each(&xas, folio, ractl->_index + ractl->_nr_pages - 1) { + unsigned int order; + + if (xas_retry(&xas, folio)) + continue; + VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); + + order =3D folio_order(folio); + fq->orders[slot] =3D order; + fq->vec.folios[slot] =3D folio; + loaded +=3D PAGE_SIZE << order; + npages +=3D 1 << order; + trace_netfs_folio(folio, netfs_folio_trace_read); + + slot++; + if (slot >=3D folioq_nr_slots(fq)) { + fq->vec.nr =3D slot; + fq =3D fq->next; + if (!fq) { + WARN_ON_ONCE(npages < readahead_count(ractl)); + break; + } + slot =3D 0; + } + } + + rcu_read_unlock(); + + if (fq) + fq->vec.nr =3D slot; + + WRITE_ONCE(roll->iter.count, loaded); + iov_iter_folio_queue(&roll->iter, ITER_DEST, roll->tail, 0, 0, loaded); + ractl->_index +=3D npages; + ractl->_nr_pages -=3D npages; + return loaded; +} + /* * Append a folio to the rolling buffer. */ diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 8c78c91fefd9..16cc0858c613 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -278,6 +278,7 @@ struct netfs_io_request { #define NETFS_RREQ_FOLIO_COPY_TO_CACHE 10 /* Copy current folio to cache f= rom read */ #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages = */ +#define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA ga= ve us */ #define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark * write to cache on read */ const struct netfs_request_ops *netfs_ops; diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h index 9e5dad29669c..761735bdf3ab 100644 --- a/include/linux/rolling_buffer.h +++ b/include/linux/rolling_buffer.h @@ -48,6 +48,9 @@ int rolling_buffer_make_space(struct rolling_buffer *roll= , gfp_t gfp); ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, struct readahead_control *ractl, struct folio_batch *put_batch); +ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, + struct readahead_control *ractl, + unsigned int rreq_id, gfp_t gfp); ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *f= olio, unsigned int flags, gfp_t gfp); struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *rol= l); diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 4f5a447d92b1..0202ba4ccbd0 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -59,6 +59,7 @@ EM(netfs_rreq_trace_free, "FREE ") \ EM(netfs_rreq_trace_intr, "INTR ") \ EM(netfs_rreq_trace_ki_complete, "KI-CMPL") \ + EM(netfs_rreq_trace_ra_put_ref, "RA-PUT ") \ EM(netfs_rreq_trace_recollect, "RECLLCT") \ EM(netfs_rreq_trace_redirty, "REDIRTY") \ EM(netfs_rreq_trace_resubmit, "RESUBMT") \ @@ -70,9 +71,11 @@ EM(netfs_rreq_trace_unpause, "UNPAUSE") \ EM(netfs_rreq_trace_wait_ip, "WAIT-IP") \ EM(netfs_rreq_trace_wait_pause, "--PAUSED--") \ + EM(netfs_rreq_trace_wait_put_ra_refs, "WAIT-P-RA") \ EM(netfs_rreq_trace_wait_quiesce, "WAIT-QUIESCE") \ EM(netfs_rreq_trace_waited_ip, "DONE-IP") \ EM(netfs_rreq_trace_waited_pause, "--UNPAUSED--") \ + EM(netfs_rreq_trace_waited_put_ra_refs, "DONE-P-RA") \ EM(netfs_rreq_trace_waited_quiesce, "DONE-QUIESCE") \ EM(netfs_rreq_trace_wake_ip, "WAKE-IP") \ EM(netfs_rreq_trace_wake_queue, "WAKE-Q ") \ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1082443F8CF for ; Mon, 24 Aug 2026 14:43:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582592; cv=none; b=Xt5J9mTP27S6xbtXHVQ5uz2W+iTna2PDBCyg5YNVAfOFswM++svseA0FGIQuOOV6HKElDTykBElo1ED+gMi7Pe/611zFBIFU/05xSTNHHgcwnmSamenWflkiLKkwRa/nv+EAmc2pxaa8tuBpXbFawUkbJGnvvNlbR/wFoz58qlg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582592; c=relaxed/simple; bh=W2r34cI+XaaQUdqCi3aOgKHvzlTyAT8XuHNk4YjX+HU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HfqQp+I006qhcNfz4ct5jadTM2EeKLCfKWkPwEQL3vqI1G8VhUJUIF9T/Yu8KXWoz77wWzBaX2OxIhOQVOuujDWzagBiQjCH8qJ27LjMzSr0NWwcOLE9jukf07csgJ28TWEOS63E8+f8JIb8FJmOf/Tmti9hR40iniueS3y2FH4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Juf99mSC; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Juf99mSC" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582585; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=forfFVNHXdcKUrAO6tYWFvQxC3xkWQUr9XOJVlaCNVg=; b=Juf99mSCRXEB4ZSvTMxhB1J6lZhuAQzQiGQBsl2Q17DMvJQW12WXiicNlS6hHg9fMVfiMu xOfJeJrmH43PSHGB7UhurQy6Mm5Ai3SslknhEKUotVuMTx7RPdhGja4hllhJ0RmMXu4PxQ jUQLIIef80n15l3ZSpBLzQiCkoSRztA= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-427-bXO84dgtNhKoDkREXaCNxg-1; Mon, 24 Aug 2026 10:42:59 -0400 X-MC-Unique: bXO84dgtNhKoDkREXaCNxg-1 X-Mimecast-MFC-AGG-ID: bXO84dgtNhKoDkREXaCNxg_1787582574 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 7E653180AE92; Mon, 24 Aug 2026 14:42:54 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id C62F3195422E; Mon, 24 Aug 2026 14:42:47 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-block@vger.kernel.org Subject: [PATCH v10 08/35] Add a function to kmap one page of a multipage bio_vec Date: Mon, 24 Aug 2026 15:41:01 +0100 Message-ID: <20260824144130.759997-9-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" Add a function to kmap one page of a multipage bio_vec by offset (which is added to the offset in the bio_vec internally). The caller is responsible for calculating how much of the page is then available. Signed-off-by: David Howells Acked-by: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: Christoph Hellwig cc: Jens Axboe cc: linux-block@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- include/linux/bvec.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/linux/bvec.h b/include/linux/bvec.h index 92837e2743f1..53cf36e73967 100644 --- a/include/linux/bvec.h +++ b/include/linux/bvec.h @@ -343,4 +343,22 @@ static inline phys_addr_t bvec_phys(const struct bio_v= ec *bvec) return page_to_phys(bvec->bv_page) + bvec->bv_offset; } =20 +/** + * bvec_kmap_partial - Map part of a bvec into the kernel virtual address = space + * @bvec: bvec to map + * @offset: Offset into bvec + * + * Map the page containing the byte at @offset into the kernel virtual add= ress + * space. The caller is responsible for making sure this doesn't overrun. + * + * Call kunmap_local on the returned address to unmap. + */ +static inline void *bvec_kmap_partial(struct bio_vec *bvec, size_t offset) +{ + offset +=3D bvec->bv_offset; + + return kmap_local_page(bvec->bv_page + (offset >> PAGE_SHIFT)) + + (offset & ~PAGE_MASK); +} + #endif /* __LINUX_BVEC_H */ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3F90E43B49D for ; Mon, 24 Aug 2026 14:43:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582604; cv=none; b=I7A5f4ysaFfy2WuI6vBOr4hB8qqfWpVgEmMGSWnqFc7+yl5NJu9db+SSQjOeiNKifslS5KdgdfUGzlgRS98th/g5+A0jM0RZvDp1vrRQGTO5wjWJmzExBSz7JFHpR3meMmqKL198Lh4EDZ2vFn9KjBDjJ1eofmkvNucgvxf7un8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582604; c=relaxed/simple; bh=TtEk6nyhfRNWHCT6pcrAPHeKJnJE4K7tL1ALTNLEfas=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=mJfrG8O8mP9cWqz95ySktx6FMWQP4x2x+C2P8RcrQyr6+RQaqCzH+UwlbX5jp5kcudP1hN79fQ+lvqK30iqYCIkdYSZlPaxhpl9Fzf47Dz4U7vr8Ttl7lHcVdz1wpKn5z/RkYiqs1GFVjqZJ7NGrSQBJJ2lr0fy5JowEIfOxgnk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=CJryMz5k; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="CJryMz5k" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582592; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=HO4OLbP/FWnSz/NPberlvhYzFjgbzDJ1WK0iNKs0+Tc=; b=CJryMz5k9gjvH+9COuhkQHUgUsBsBu9dqDs8hXMxiwtqfB3UP7xV5kIlwKDlrYZnchSAam BJtblDG8ZTK0ilIdluuGk4MThn4UxOs0n3iOotTiPgyGhxKy+B3H9shuEu5t0G9B3hM0Ja uehchZJLNc4Lc7jkmvwoyAGuTcDA+mU= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-34-8_DUO4sUMj2SsodbKPtBAg-1; Mon, 24 Aug 2026 10:43:07 -0400 X-MC-Unique: 8_DUO4sUMj2SsodbKPtBAg-1 X-Mimecast-MFC-AGG-ID: 8_DUO4sUMj2SsodbKPtBAg_1787582583 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id CACEA195FCE6; Mon, 24 Aug 2026 14:43:02 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 39F391800348; Mon, 24 Aug 2026 14:42:55 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-block@vger.kernel.org Subject: [PATCH v10 09/35] iov_iter: Make iov_iter_get_pages*() wrap iov_iter_extract_pages() Date: Mon, 24 Aug 2026 15:41:02 +0100 Message-ID: <20260824144130.759997-10-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" Make iov_iter_get_pages*() wrap iov_iter_extract_pages() for kernel iterator types (e.g. ITER_BVEC, ITER_FOLIOQ, ITER_XARRAY). The pages obtained have their refcounts incremented afterwards if they're not slab pages. ITER_KVEC is left returning -EFAULT. Signed-off-by: David Howells Reviewed-by: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: Christoph Hellwig cc: Jens Axboe cc: linux-block@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- lib/iov_iter.c | 164 ++++++------------------------------------------- 1 file changed, 19 insertions(+), 145 deletions(-) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index c2484551a4e8..15a50d538c65 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -910,118 +910,34 @@ static int want_pages_array(struct page ***res, size= _t size, return count; } =20 -static ssize_t iter_folioq_get_pages(struct iov_iter *iter, +/* + * Wrap iov_iter_extract_pages() and then pin the non-slab pages we got ba= ck. + * This only works for non-user iterator types as get_pages uses get_user_= pages + * not pin_user_pages. + */ +static ssize_t iter_get_kernel_pages(struct iov_iter *iter, struct page ***ppages, size_t maxsize, unsigned maxpages, size_t *_start_offset) { - const struct folio_queue *folioq =3D iter->folioq; struct page **pages; - unsigned int slot =3D iter->folioq_slot; - size_t extracted =3D 0, count =3D iter->count, iov_offset =3D iter->iov_o= ffset; + ssize_t ret, done; =20 - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - slot =3D 0; - if (WARN_ON(iov_offset !=3D 0)) - return -EIO; - } + ret =3D iov_iter_extract_pages(iter, ppages, maxsize, maxpages, + 0, _start_offset); + if (ret <=3D 0) + return ret; =20 - maxpages =3D want_pages_array(ppages, maxsize, iov_offset & ~PAGE_MASK, m= axpages); - if (!maxpages) - return -ENOMEM; - *_start_offset =3D iov_offset & ~PAGE_MASK; pages =3D *ppages; + for (done =3D ret + *_start_offset; done > 0; done -=3D PAGE_SIZE) { + struct folio *folio =3D page_folio(*pages); =20 - for (;;) { - struct folio *folio =3D folioq_folio(folioq, slot); - size_t offset =3D iov_offset, fsize =3D folioq_folio_size(folioq, slot); - size_t part =3D PAGE_SIZE - offset % PAGE_SIZE; - - if (offset < fsize) { - part =3D umin(part, umin(maxsize - extracted, fsize - offset)); - count -=3D part; - iov_offset +=3D part; - extracted +=3D part; - - *pages =3D folio_page(folio, offset / PAGE_SIZE); - get_page(*pages); - pages++; - maxpages--; - } - - if (maxpages =3D=3D 0 || extracted >=3D maxsize) - break; - - if (iov_offset >=3D fsize) { - iov_offset =3D 0; - slot++; - if (slot =3D=3D folioq_nr_slots(folioq) && folioq->next) { - folioq =3D folioq->next; - slot =3D 0; - } - } - } - - iter->count =3D count; - iter->iov_offset =3D iov_offset; - iter->folioq =3D folioq; - iter->folioq_slot =3D slot; - return extracted; -} - -static ssize_t iter_xarray_populate_pages(struct page **pages, struct xarr= ay *xa, - pgoff_t index, unsigned int nr_pages) -{ - XA_STATE(xas, xa, index); - struct folio *folio; - unsigned int ret =3D 0; - - rcu_read_lock(); - for (folio =3D xas_load(&xas); folio; folio =3D xas_next(&xas)) { - if (xas_retry(&xas, folio)) - continue; - - /* Has the folio moved or been split? */ - if (unlikely(folio !=3D xas_reload(&xas))) { - xas_reset(&xas); - continue; - } - - pages[ret] =3D folio_file_page(folio, xas.xa_index); - folio_get(folio); - if (++ret =3D=3D nr_pages) - break; + if (!folio_test_slab(folio) && !folio_test_large_kmalloc(folio)) + folio_get(folio); + pages++; } - rcu_read_unlock(); return ret; } =20 -static ssize_t iter_xarray_get_pages(struct iov_iter *i, - struct page ***pages, size_t maxsize, - unsigned maxpages, size_t *_start_offset) -{ - unsigned nr, offset, count; - pgoff_t index; - loff_t pos; - - pos =3D i->xarray_start + i->iov_offset; - index =3D pos >> PAGE_SHIFT; - offset =3D pos & ~PAGE_MASK; - *_start_offset =3D offset; - - count =3D want_pages_array(pages, maxsize, offset, maxpages); - if (!count) - return -ENOMEM; - nr =3D iter_xarray_populate_pages(*pages, i->xarray, index, count); - if (nr =3D=3D 0) - return 0; - - maxsize =3D min_t(size_t, nr * PAGE_SIZE - offset, maxsize); - i->iov_offset +=3D maxsize; - i->count -=3D maxsize; - return maxsize; -} - /* must be done on non-empty ITER_UBUF or ITER_IOVEC one */ static unsigned long first_iovec_segment(const struct iov_iter *i, size_t = *size) { @@ -1044,22 +960,6 @@ static unsigned long first_iovec_segment(const struct= iov_iter *i, size_t *size) BUG(); // if it had been empty, we wouldn't get called } =20 -/* must be done on non-empty ITER_BVEC one */ -static struct page *first_bvec_segment(const struct iov_iter *i, - size_t *size, size_t *start) -{ - struct page *page; - size_t skip =3D i->iov_offset, len; - - len =3D i->bvec->bv_len - skip; - if (*size > len) - *size =3D len; - skip +=3D i->bvec->bv_offset; - page =3D i->bvec->bv_page + skip / PAGE_SIZE; - *start =3D skip % PAGE_SIZE; - return page; -} - static ssize_t __iov_iter_get_pages_alloc(struct iov_iter *i, struct page ***pages, size_t maxsize, unsigned int maxpages, size_t *start) @@ -1095,36 +995,10 @@ static ssize_t __iov_iter_get_pages_alloc(struct iov= _iter *i, iov_iter_advance(i, maxsize); return maxsize; } - if (iov_iter_is_bvec(i)) { - struct page **p; - struct page *page; =20 - page =3D first_bvec_segment(i, &maxsize, start); - n =3D want_pages_array(pages, maxsize, *start, maxpages); - if (!n) - return -ENOMEM; - p =3D *pages; - for (int k =3D 0; k < n; k++) { - struct folio *folio =3D page_folio(page + k); - p[k] =3D page + k; - if (!folio_test_slab(folio)) - folio_get(folio); - } - maxsize =3D min_t(size_t, maxsize, n * PAGE_SIZE - *start); - i->count -=3D maxsize; - i->iov_offset +=3D maxsize; - if (i->iov_offset =3D=3D i->bvec->bv_len) { - i->iov_offset =3D 0; - i->bvec++; - i->nr_segs--; - } - return maxsize; - } - if (iov_iter_is_folioq(i)) - return iter_folioq_get_pages(i, pages, maxsize, maxpages, start); - if (iov_iter_is_xarray(i)) - return iter_xarray_get_pages(i, pages, maxsize, maxpages, start); - return -EFAULT; + if (iov_iter_is_kvec(i)) + return -EFAULT; + return iter_get_kernel_pages(i, pages, maxsize, maxpages, start); } =20 ssize_t iov_iter_get_pages2(struct iov_iter *i, struct page **pages, From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E9F4F442B3E for ; Mon, 24 Aug 2026 14:43:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582620; cv=none; b=bhxwjQetaCQifgWZ4yjzJTQX6njMq7cPxO4khqIVddoJ3l/ofwSpfYRRw8d0iUbDO56Ae9SxZf92A/HfTLGX8Aab9802FCd6QDuZx42lDdJXnRbFVdfh1ZyvKRR0DmryY0YK4vKJtqEOo7yhu9KGybDJjGz2teSeFOCSc4m+Caw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582620; c=relaxed/simple; bh=vvZWvi1fw/z6F35zKHwfApSdkAefvWofpeP9/cQeQqg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eXMJ0rH6dC7HvWzm4/wXJwOPVA52ru6FYUgsZwlxxonFbpXwCq0EZy9KoX5ZaYgd9idP1cVt2BaYXRN6R8iPmY2I5rVTLIEYnj7al3s3pdPjesKJwvk3wgH0tcQoivGKadDFYH//y/S8ZCLee6eUmi/BPT218kfi6F6HUoJLle4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=WsMOCt5a; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="WsMOCt5a" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582605; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=P0ohCpej8YfjY9+IgfE9GwugkhS746eg2PFK1N+H3OE=; b=WsMOCt5a4wQHaQtmmwTXdo9xKHXQu2wN9xmuxWoibEVYTz0vp4XIBfeRnUdehMzWRXcasV 3YCC8WHdBZnpsmPLxDE0b2C2jS0DHrVk70PvtdG1GMDygxaK4UVxx2qwezl9qLYwW9tCX5 VN6fA+5Eom7YG/Yf91nSd3HLH9S/5mY= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-553-OKuguzOFPn6NkrlLZ_uD8Q-1; Mon, 24 Aug 2026 10:43:18 -0400 X-MC-Unique: OKuguzOFPn6NkrlLZ_uD8Q-1 X-Mimecast-MFC-AGG-ID: OKuguzOFPn6NkrlLZ_uD8Q_1787582592 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 266841944EBF; Mon, 24 Aug 2026 14:43:11 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 8587C1800643; Mon, 24 Aug 2026 14:43:04 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, linux-block@vger.kernel.org Subject: [PATCH v10 10/35] iov_iter: Add a segmented queue of bio_vec[] Date: Mon, 24 Aug 2026 15:41:03 +0100 Message-ID: <20260824144130.759997-11-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Content-Type: text/plain; charset="utf-8" Add the concept of a segmented queue of bio_vec[] arrays. This allows an indefinite quantity of elements to be handled and allows things like network filesystems and crypto drivers to glue bits on the ends without having to reallocate the array. The bvecq struct that defines each segment also carries capacity/usage information along with flags indicating whether the constituent memory regions need freeing or unpinning. The bvecq structs are refcounted to allow a queue to be extracted in batches and split between a number of subrequests. The bvecq can have the bio_vec[] it manages allocated in with it, but this is not required. A flag is provided for if this is the case as comparing ->bv to ->__bv is not sufficient to detect this case. Add an iterator type ITER_BVECQ for it. This is intended to replace ITER_FOLIOQ (and ITER_XARRAY). Note that the prev pointer is only really needed for iov_iter_revert() and could be dispensed with if struct iov_iter contained the head information as well as the current point. Signed-off-by: David Howells Reviewed-by: Paulo Alcantara (Red Hat) cc: Matthew Wilcox cc: Christoph Hellwig cc: Jens Axboe cc: linux-block@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- include/linux/bvecq.h | 50 ++++++ include/linux/iov_iter.h | 74 ++++++++- include/linux/uio.h | 11 ++ lib/iov_iter.c | 333 ++++++++++++++++++++++++++++++++++++- lib/scatterlist.c | 67 +++++++- lib/tests/kunit_iov_iter.c | 260 +++++++++++++++++++++++++++++ 6 files changed, 789 insertions(+), 6 deletions(-) create mode 100644 include/linux/bvecq.h diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h new file mode 100644 index 000000000000..77fd07852c33 --- /dev/null +++ b/include/linux/bvecq.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Implementation of a segmented queue of bio_vec[]. + * + * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#ifndef _LINUX_BVECQ_H +#define _LINUX_BVECQ_H + +#include + +/* + * The type of memory retention used by the elements in bvecq->bv[] and ho= w to + * clean it up. + */ +enum bvecq_mem { + BVECQ_MEM_EXTERNAL, /* Externally retained memory - no freeing */ + BVECQ_MEM_PAGECACHE, /* Ref'd pagecache pages - must put */ + BVECQ_MEM_GUP, /* Pinned memory from get_user_pages() - unpin */ + BVECQ_MEM_ALLOCED, /* Memory alloc'd by bvecq - can be freed/pooled */ +} __mode(byte); + +/* + * Segmented bio_vec queue. + * + * These can be linked together to form messages of indefinite length and + * iterated over with an ITER_BVECQ iterator. The list is non-circular; n= ext + * and prev are NULL at the ends. + * + * The bv pointer points to the bio_vec array; this may be __bv if allocat= ed + * together. The caller is responsible for determining whether or not thi= s is + * the case as the array pointed to by bv may be follow on directly from t= he + * bvecq by accident of allocation (ie. ->bv =3D=3D ->__bv is *not* suffic= ient to + * determine this). + */ +struct bvecq { + struct bvecq *next; /* Next bvec in the list or NULL */ + struct bvecq *prev; /* Prev bvec in the list or NULL */ + refcount_t ref; + u32 priv; /* Private data */ + u16 nr_slots; /* Number of elements in bv[] used */ + u16 max_slots; /* Number of elements allocated in bv[] */ + enum bvecq_mem mem_type:3; /* What sort of memory and how to free it */ + bool inline_bv:1; /* T if __bv[] is being used */ + struct bio_vec *bv; /* Pointer to array of page fragments */ + struct bio_vec __bv[]; /* Default array (if ->inline_bv) */ +}; + +#endif /* _LINUX_BVECQ_H */ diff --git a/include/linux/iov_iter.h b/include/linux/iov_iter.h index f9a17fbbd398..ec0aa2893689 100644 --- a/include/linux/iov_iter.h +++ b/include/linux/iov_iter.h @@ -10,6 +10,7 @@ =20 #include #include +#include #include =20 typedef size_t (*iov_step_f)(void *iter_base, size_t progress, size_t len, @@ -141,6 +142,71 @@ size_t iterate_bvec(struct iov_iter *iter, size_t len,= void *priv, void *priv2, return progress; } =20 +/* + * Handle ITER_BVECQ. + */ +static __always_inline +size_t iterate_bvecq(struct iov_iter *iter, size_t len, void *priv, void *= priv2, + iov_step_f step) +{ + const struct bvecq *bq =3D iter->bvecq; + unsigned int slot =3D iter->bvecq_slot; + size_t progress =3D 0, skip =3D iter->iov_offset; + + do { + const struct bio_vec *bvec; + struct page *page; + size_t poff, plen; + void *base; + + if (slot >=3D bq->nr_slots) { + if (!bq->next) + break; + bq =3D bq->next; + slot =3D 0; + continue; + } + + bvec =3D &bq->bv[slot]; + /* + * The caller must ensure that a slot with bv_len>0 has a valid + * bv_page. + */ + page =3D bvec->bv_page + (bvec->bv_offset + skip) / PAGE_SIZE; + poff =3D (bvec->bv_offset + skip) % PAGE_SIZE; + plen =3D min(bvec->bv_len - skip, len); + + while (plen > 0) { + size_t part, remain, consumed; + + part =3D min(plen, PAGE_SIZE - poff); + base =3D kmap_local_page(page) + poff; + remain =3D step(base, progress, part, priv, priv2); + kunmap_local(base); + + consumed =3D part - remain; + progress +=3D consumed; + skip +=3D consumed; + len -=3D consumed; + if (!len || remain) + goto stop; + page++; + poff =3D 0; + plen -=3D consumed; + } + + skip =3D 0; + slot++; + } while (len); + +stop: + iter->bvecq_slot =3D slot; + iter->bvecq =3D bq; + iter->iov_offset =3D skip; + iter->count -=3D progress; + return progress; +} + /* * Handle ITER_FOLIOQ. */ @@ -306,6 +372,8 @@ size_t iterate_and_advance2(struct iov_iter *iter, size= _t len, void *priv, return iterate_bvec(iter, len, priv, priv2, step); if (iov_iter_is_kvec(iter)) return iterate_kvec(iter, len, priv, priv2, step); + if (iov_iter_is_bvecq(iter)) + return iterate_bvecq(iter, len, priv, priv2, step); if (iov_iter_is_folioq(iter)) return iterate_folioq(iter, len, priv, priv2, step); if (iov_iter_is_xarray(iter)) @@ -342,8 +410,8 @@ size_t iterate_and_advance(struct iov_iter *iter, size_= t len, void *priv, * buffer is presented in segments, which for kernel iteration are broken = up by * physical pages and mapped, with the mapped address being presented. * - * [!] Note This will only handle BVEC, KVEC, FOLIOQ, XARRAY and DISCARD-t= ype - * iterators; it will not handle UBUF or IOVEC-type iterators. + * [!] Note This will only handle BVEC, KVEC, BVECQ, FOLIOQ, XARRAY and + * DISCARD-type iterators; it will not handle UBUF or IOVEC-type iterators. * * A step functions, @step, must be provided, one for handling mapped kern= el * addresses and the other is given user addresses which have the potentia= l to @@ -370,6 +438,8 @@ size_t iterate_and_advance_kernel(struct iov_iter *iter= , size_t len, void *priv, return iterate_bvec(iter, len, priv, priv2, step); if (iov_iter_is_kvec(iter)) return iterate_kvec(iter, len, priv, priv2, step); + if (iov_iter_is_bvecq(iter)) + return iterate_bvecq(iter, len, priv, priv2, step); if (iov_iter_is_folioq(iter)) return iterate_folioq(iter, len, priv, priv2, step); if (iov_iter_is_xarray(iter)) diff --git a/include/linux/uio.h b/include/linux/uio.h index a9bc5b3067e3..f7cfa6ea8213 100644 --- a/include/linux/uio.h +++ b/include/linux/uio.h @@ -26,6 +26,7 @@ enum iter_type { ITER_IOVEC, ITER_BVEC, ITER_KVEC, + ITER_BVECQ, ITER_FOLIOQ, ITER_XARRAY, ITER_DISCARD, @@ -68,6 +69,7 @@ struct iov_iter { const struct iovec *__iov; const struct kvec *kvec; const struct bio_vec *bvec; + const struct bvecq *bvecq; const struct folio_queue *folioq; struct xarray *xarray; void __user *ubuf; @@ -77,6 +79,7 @@ struct iov_iter { }; union { unsigned long nr_segs; + u16 bvecq_slot; u8 folioq_slot; loff_t xarray_start; }; @@ -145,6 +148,11 @@ static inline bool iov_iter_is_discard(const struct io= v_iter *i) return iov_iter_type(i) =3D=3D ITER_DISCARD; } =20 +static inline bool iov_iter_is_bvecq(const struct iov_iter *i) +{ + return iov_iter_type(i) =3D=3D ITER_BVECQ; +} + static inline bool iov_iter_is_folioq(const struct iov_iter *i) { return iov_iter_type(i) =3D=3D ITER_FOLIOQ; @@ -295,6 +303,9 @@ void iov_iter_kvec(struct iov_iter *i, unsigned int dir= ection, const struct kvec void iov_iter_bvec(struct iov_iter *i, unsigned int direction, const struc= t bio_vec *bvec, unsigned long nr_segs, size_t count); void iov_iter_discard(struct iov_iter *i, unsigned int direction, size_t c= ount); +void iov_iter_bvec_queue(struct iov_iter *i, unsigned int direction, + const struct bvecq *bvecq, + unsigned int first_slot, unsigned int offset, size_t count); void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction, const struct folio_queue *folioq, unsigned int first_slot, unsigned int offset, size_t count); diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 15a50d538c65..c7f7ff9a9b37 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -538,6 +538,40 @@ static void iov_iter_iovec_advance(struct iov_iter *i,= size_t size) i->__iov =3D iov; } =20 +static void iov_iter_bvecq_advance(struct iov_iter *i, size_t by) +{ + const struct bvecq *bq =3D i->bvecq; + unsigned int slot =3D i->bvecq_slot; + + if (!i->count) + return; + i->count -=3D by; + + by +=3D i->iov_offset; /* From beginning of current segment. */ + do { + size_t len; + + if (slot >=3D bq->nr_slots) { + if (!bq->next) + break; + bq =3D bq->next; + slot =3D 0; + continue; + } + + len =3D bq->bv[slot].bv_len; + + if (likely(by < len)) + break; + by -=3D len; + slot++; + } while (by); + + i->iov_offset =3D by; + i->bvecq_slot =3D slot; + i->bvecq =3D bq; +} + static void iov_iter_folioq_advance(struct iov_iter *i, size_t size) { const struct folio_queue *folioq =3D i->folioq; @@ -583,6 +617,8 @@ void iov_iter_advance(struct iov_iter *i, size_t size) iov_iter_iovec_advance(i, size); } else if (iov_iter_is_bvec(i)) { iov_iter_bvec_advance(i, size); + } else if (iov_iter_is_bvecq(i)) { + iov_iter_bvecq_advance(i, size); } else if (iov_iter_is_folioq(i)) { iov_iter_folioq_advance(i, size); } else if (iov_iter_is_discard(i)) { @@ -591,6 +627,33 @@ void iov_iter_advance(struct iov_iter *i, size_t size) } EXPORT_SYMBOL(iov_iter_advance); =20 +static void iov_iter_bvecq_revert(struct iov_iter *i, size_t unroll) +{ + const struct bvecq *bq =3D i->bvecq; + unsigned int slot =3D i->bvecq_slot; + + for (;;) { + size_t len; + + if (slot =3D=3D 0) { + bq =3D bq->prev; + slot =3D bq->nr_slots; + continue; + } + slot--; + + len =3D bq->bv[slot].bv_len; + if (unroll <=3D len) { + i->iov_offset =3D len - unroll; + break; + } + unroll -=3D len; + } + + i->bvecq_slot =3D slot; + i->bvecq =3D bq; +} + static void iov_iter_folioq_revert(struct iov_iter *i, size_t unroll) { const struct folio_queue *folioq =3D i->folioq; @@ -648,6 +711,9 @@ void iov_iter_revert(struct iov_iter *i, size_t unroll) } unroll -=3D n; } + } else if (iov_iter_is_bvecq(i)) { + i->iov_offset =3D 0; + iov_iter_bvecq_revert(i, unroll); } else if (iov_iter_is_folioq(i)) { i->iov_offset =3D 0; iov_iter_folioq_revert(i, unroll); @@ -678,9 +744,30 @@ size_t iov_iter_single_seg_count(const struct iov_iter= *i) if (iov_iter_is_bvec(i)) return min(i->count, i->bvec->bv_len - i->iov_offset); } + if (!i->count) + return 0; + if (unlikely(iov_iter_is_bvecq(i))) { + const struct bvecq *bq =3D i->bvecq; + unsigned int slot =3D i->bvecq_slot; + size_t offset =3D i->iov_offset; + + for (;;) { + while (slot >=3D bq->nr_slots) { + bq =3D bq->next; + if (!bq) + return 0; + slot =3D 0; + offset =3D 0; + } + if (bq->bv[slot].bv_len > offset) + break; + slot++; + offset =3D 0; + } + return min(i->count, bq->bv[slot].bv_len - offset); + } if (unlikely(iov_iter_is_folioq(i))) - return !i->count ? 0 : - umin(folioq_folio_size(i->folioq, i->folioq_slot), i->count); + return umin(folioq_folio_size(i->folioq, i->folioq_slot), i->count); return i->count; } EXPORT_SYMBOL(iov_iter_single_seg_count); @@ -717,6 +804,35 @@ void iov_iter_bvec(struct iov_iter *i, unsigned int di= rection, } EXPORT_SYMBOL(iov_iter_bvec); =20 +/** + * iov_iter_bvec_queue - Initialise an I/O iterator to use a segmented bve= c queue + * @i: The iterator to initialise. + * @direction: The direction of the transfer. + * @bvecq: The starting point in the bvec queue. + * @first_slot: The first slot in the bvec queue to use + * @offset: The offset into the bvec in the first slot to start at + * @count: The size of the I/O buffer in bytes. + * + * Set up an I/O iterator to either draw data out of the buffers attached = to an + * inode or to inject data into those buffers. The pages *must* be preven= ted + * from evaporation, either by the caller. + */ +void iov_iter_bvec_queue(struct iov_iter *i, unsigned int direction, + const struct bvecq *bvecq, unsigned int first_slot, + unsigned int offset, size_t count) +{ + WARN_ON(direction & ~(READ | WRITE)); + *i =3D (struct iov_iter) { + .iter_type =3D ITER_BVECQ, + .data_source =3D direction, + .bvecq =3D bvecq, + .bvecq_slot =3D first_slot, + .count =3D count, + .iov_offset =3D offset, + }; +} +EXPORT_SYMBOL(iov_iter_bvec_queue); + /** * iov_iter_folio_queue - Initialise an I/O iterator to use the folios in = a folio queue * @i: The iterator to initialise. @@ -839,6 +955,39 @@ static unsigned long iov_iter_alignment_bvec(const str= uct iov_iter *i) return res; } =20 +static unsigned long iov_iter_alignment_bvecq(const struct iov_iter *iter) +{ + const struct bvecq *bq; + unsigned long res =3D 0; + unsigned int slot =3D iter->bvecq_slot; + size_t skip =3D iter->iov_offset; + size_t size =3D iter->count; + + if (!size) + return res; + + for (bq =3D iter->bvecq; bq; bq =3D bq->next) { + for (; slot < bq->nr_slots; slot++) { + const struct bio_vec *bvec =3D &bq->bv[slot]; + size_t part =3D min(bvec->bv_len - skip, size); + + if (part) { + res |=3D bvec->bv_offset + skip; + res |=3D part; + } + + size -=3D part; + if (size =3D=3D 0) + return res; + skip =3D 0; + } + + slot =3D 0; + } + + return res; +} + unsigned long iov_iter_alignment(const struct iov_iter *i) { if (likely(iter_is_ubuf(i))) { @@ -854,6 +1003,8 @@ unsigned long iov_iter_alignment(const struct iov_iter= *i) =20 if (iov_iter_is_bvec(i)) return iov_iter_alignment_bvec(i); + if (iov_iter_is_bvecq(i)) + return iov_iter_alignment_bvecq(i); =20 /* With both xarray and folioq types, we're dealing with whole folios. */ if (iov_iter_is_folioq(i)) @@ -1066,6 +1217,38 @@ static int bvec_npages(const struct iov_iter *i, int= maxpages) return npages; } =20 +static size_t iov_npages_bvecq(const struct iov_iter *iter, size_t maxpage= s) +{ + const struct bvecq *bq; + unsigned int slot =3D iter->bvecq_slot; + size_t npages =3D 0; + size_t skip =3D iter->iov_offset; + size_t size =3D iter->count; + + for (bq =3D iter->bvecq; bq; bq =3D bq->next) { + for (; slot < bq->nr_slots; slot++) { + const struct bio_vec *bvec =3D &bq->bv[slot]; + size_t offs =3D (bvec->bv_offset + skip) % PAGE_SIZE; + size_t part =3D min(bvec->bv_len - skip, size); + + if (part) { + npages +=3D DIV_ROUND_UP(offs + part, PAGE_SIZE); + if (npages >=3D maxpages) + goto out; + } + + size -=3D part; + if (!size) + goto out; + skip =3D 0; + } + + slot =3D 0; + } +out: + return umin(npages, maxpages); +} + int iov_iter_npages(const struct iov_iter *i, int maxpages) { if (unlikely(!i->count)) @@ -1080,6 +1263,8 @@ int iov_iter_npages(const struct iov_iter *i, int max= pages) return iov_npages(i, maxpages); if (iov_iter_is_bvec(i)) return bvec_npages(i, maxpages); + if (iov_iter_is_bvecq(i)) + return iov_npages_bvecq(i, maxpages); if (iov_iter_is_folioq(i)) { unsigned offset =3D i->iov_offset % PAGE_SIZE; int npages =3D DIV_ROUND_UP(offset + i->count, PAGE_SIZE); @@ -1366,6 +1551,146 @@ void iov_iter_restore(struct iov_iter *i, struct io= v_iter_state *state) i->nr_segs =3D state->nr_segs; } =20 +/* + * Count the number of virtually contiguous pages coming up next in an + * ITER_BVECQ iterator, up to the specified maxima. + */ +static unsigned int iter_count_bvecq_pages(const struct iov_iter *iter, + size_t maxsize, + unsigned int maxpages) +{ + const struct bvecq *bvecq =3D iter->bvecq; + unsigned int slot =3D iter->bvecq_slot; + ssize_t remain =3D umin(maxsize, iter->count); + size_t count =3D 0, offset =3D iter->iov_offset; + + do { + const struct bio_vec *bv; + size_t boff, blen; + + if (slot >=3D bvecq->nr_slots) { + if (!bvecq->next) { + WARN_ON_ONCE(remain > 0); + break; + } + bvecq =3D bvecq->next; + slot =3D 0; + offset =3D 0; + continue; + } + + bv =3D &bvecq->bv[slot++]; + boff =3D bv->bv_offset; + blen =3D bv->bv_len; + + /* bv_page is not allowed to be NULL unless bv_len =3D=3D 0. */ + if (WARN_ON_ONCE(!bv->bv_page && blen > 0)) + break; + if (!PAGE_ALIGNED(boff) && count > 0) + break; + + boff +=3D offset; + blen -=3D offset; + offset =3D 0; + if (!blen) + continue; + + blen =3D umin(blen, remain); + remain -=3D blen; + blen +=3D offset_in_page(boff); + count +=3D DIV_ROUND_UP(blen, PAGE_SIZE); + + if (!PAGE_ALIGNED(blen)) + break; + } while (remain > 0 && count < maxpages); + + return umin(count, maxpages); +} + +/* + * Extract a list of virtually contiguous pages from an ITER_BVECQ iterato= r. + * This does not get references on the pages, nor does it get a pin on the= m. + */ +static ssize_t iov_iter_extract_bvecq_pages(struct iov_iter *iter, + struct page ***pages, size_t maxsize, + unsigned int maxpages, + iov_iter_extraction_t extraction_flags, + size_t *offset0) +{ + const struct bvecq *bvecq; + struct page **p; + unsigned int slot, nr =3D 0; + size_t extracted =3D 0, offset; + + /* Count the next run of virtually contiguous pages. */ + maxpages =3D iter_count_bvecq_pages(iter, maxsize, maxpages); + if (!maxpages) + return 0; + + if (!*pages) { + *pages =3D kvmalloc_array(maxpages, sizeof(struct page *), GFP_KERNEL); + if (!*pages) + return -ENOMEM; + } + + p =3D *pages; + + /* Now transcribe the page pointers. */ + extracted =3D 0; + bvecq =3D iter->bvecq; + offset =3D iter->iov_offset; + slot =3D iter->bvecq_slot; + + do { + const struct bio_vec *bv; + size_t boff, blen; + + if (slot >=3D bvecq->nr_slots) { + if (!bvecq->next) { + WARN_ON_ONCE(extracted < iter->count); + break; + } + bvecq =3D bvecq->next; + slot =3D 0; + offset =3D 0; + continue; + } + + bv =3D &bvecq->bv[slot]; + boff =3D bv->bv_offset; + blen =3D bv->bv_len; + + /* bv_page is not allowed to be NULL unless bv_len =3D=3D 0. */ + + if (offset < blen) { + size_t part =3D umin(maxsize - extracted, blen - offset); + size_t poff =3D (boff + offset) % PAGE_SIZE; + size_t pix =3D (boff + offset) / PAGE_SIZE; + + if (poff + part > PAGE_SIZE) + part =3D PAGE_SIZE - poff; + + if (!extracted) + *offset0 =3D poff; + + p[nr++] =3D bv->bv_page + pix; + offset +=3D part; + extracted +=3D part; + } + + if (offset >=3D blen) { + offset =3D 0; + slot++; + } + } while (nr < maxpages && extracted < maxsize); + + iter->bvecq =3D bvecq; + iter->bvecq_slot =3D slot; + iter->iov_offset =3D offset; + iter->count -=3D extracted; + return extracted; +} + /* * Extract a list of contiguous pages from an ITER_FOLIOQ iterator. This = does * not get references on the pages, nor does it get a pin on them. @@ -1726,6 +2051,10 @@ ssize_t iov_iter_extract_pages(struct iov_iter *i, return iov_iter_extract_bvec_pages(i, pages, maxsize, maxpages, extraction_flags, offset0); + if (iov_iter_is_bvecq(i)) + return iov_iter_extract_bvecq_pages(i, pages, maxsize, + maxpages, extraction_flags, + offset0); if (iov_iter_is_folioq(i)) return iov_iter_extract_folioq_pages(i, pages, maxsize, maxpages, extraction_flags, diff --git a/lib/scatterlist.c b/lib/scatterlist.c index 6ea40d2e6247..23e5a180103b 100644 --- a/lib/scatterlist.c +++ b/lib/scatterlist.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include =20 @@ -1267,6 +1268,65 @@ static ssize_t extract_kvec_to_sg(struct iov_iter *i= ter, return ret; } =20 +/* + * Extract up to sg_max folios from an BVECQ-type iterator and add them to + * the scatterlist. The pages are not pinned. + */ +static ssize_t extract_bvecq_to_sg(struct iov_iter *iter, + ssize_t maxsize, + struct sg_table *sgtable, + unsigned int sg_max, + iov_iter_extraction_t extraction_flags) +{ + const struct bvecq *bvecq =3D iter->bvecq; + struct scatterlist *sg =3D sgtable->sgl + sgtable->nents; + unsigned int slot =3D iter->bvecq_slot; + ssize_t ret =3D 0; + size_t offset =3D iter->iov_offset; + + maxsize =3D umin(maxsize, iov_iter_count(iter)); + + while (sg_max > 0 && ret < maxsize) { + const struct bio_vec *bv; + size_t blen, part; + + if (slot >=3D bvecq->nr_slots) { + if (!bvecq->next) { + WARN_ON_ONCE(ret < iter->count); + break; + } + bvecq =3D bvecq->next; + slot =3D 0; + offset =3D 0; + continue; + } + + bv =3D &bvecq->bv[slot]; + blen =3D bv->bv_len; + + if (offset >=3D blen) { + offset =3D 0; + slot++; + continue; + } + + part =3D umin(maxsize - ret, blen - offset); + + sg_set_page(sg, bv->bv_page, part, bv->bv_offset + offset); + sgtable->nents++; + sg++; + sg_max--; + offset +=3D part; + ret +=3D part; + } + + iter->bvecq =3D bvecq; + iter->bvecq_slot =3D slot; + iter->iov_offset =3D offset; + iter->count -=3D ret; + return ret; +} + /* * Extract up to sg_max folios from an FOLIOQ-type iterator and add them to * the scatterlist. The pages are not pinned. @@ -1391,8 +1451,8 @@ static ssize_t extract_xarray_to_sg(struct iov_iter *= iter, * addition of @sg_max elements. * * The pages referred to by UBUF- and IOVEC-type iterators are extracted a= nd - * pinned; BVEC-, KVEC-, FOLIOQ- and XARRAY-type are extracted but aren't - * pinned; DISCARD-type is not supported. + * pinned; BVEC-, BVECQ-, KVEC-, FOLIOQ- and XARRAY-type are extracted but + * aren't pinned; DISCARD-type is not supported. * * No end mark is placed on the scatterlist; that's left to the caller. * @@ -1424,6 +1484,9 @@ ssize_t extract_iter_to_sg(struct iov_iter *iter, siz= e_t maxsize, case ITER_KVEC: return extract_kvec_to_sg(iter, maxsize, sgtable, sg_max, extraction_flags); + case ITER_BVECQ: + return extract_bvecq_to_sg(iter, maxsize, sgtable, sg_max, + extraction_flags); case ITER_FOLIOQ: return extract_folioq_to_sg(iter, maxsize, sgtable, sg_max, extraction_flags); diff --git a/lib/tests/kunit_iov_iter.c b/lib/tests/kunit_iov_iter.c index d9690ba1db88..f6413e6b925a 100644 --- a/lib/tests/kunit_iov_iter.c +++ b/lib/tests/kunit_iov_iter.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -552,6 +553,183 @@ static void __init iov_kunit_copy_from_folioq(struct = kunit *test) KUNIT_SUCCEED(test); } =20 +static void iov_kunit_destroy_bvecq(void *data) +{ + struct bvecq *bq, *next; + + for (bq =3D data; bq; bq =3D next) { + next =3D bq->next; + /* The pages are freed by vmap with VM_MAP_PUT_PAGES. */ + kfree(bq); + } +} + +static struct bvecq *iov_kunit_alloc_bvecq(struct kunit *test, unsigned in= t max_slots) +{ + struct bvecq *bq; + + bq =3D kzalloc(struct_size(bq, __bv, max_slots), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bq); + bq->max_slots =3D max_slots; + bq->bv =3D bq->__bv; + bq->inline_bv =3D true; + return bq; +} + +static struct bvecq *iov_kunit_create_bvecq(struct kunit *test, unsigned i= nt max_slots) +{ + struct bvecq *bq; + + bq =3D iov_kunit_alloc_bvecq(test, max_slots); + kunit_add_action_or_reset(test, iov_kunit_destroy_bvecq, bq); + return bq; +} + +static void __init iov_kunit_load_bvecq(struct kunit *test, + struct iov_iter *iter, int dir, + struct bvecq *bq_head, + struct page **pages, size_t npages) +{ + struct bvecq *bq =3D bq_head; + size_t size =3D 0; + + for (int i =3D 0; i < npages; i++) { + if (bq->nr_slots >=3D bq->max_slots) { + bq->next =3D iov_kunit_alloc_bvecq(test, 13); + bq->next->prev =3D bq; + bq =3D bq->next; + } + bvec_set_page(&bq->bv[bq->nr_slots], pages[i], PAGE_SIZE, 0); + bq->nr_slots++; + size +=3D PAGE_SIZE; + } + iov_iter_bvec_queue(iter, dir, bq_head, 0, 0, size); +} + +/* + * Test copying to a ITER_BVECQ-type iterator. + */ +static void __init iov_kunit_copy_to_bvecq(struct kunit *test) +{ + const struct kvec_test_range *pr; + struct iov_iter iter; + struct bvecq *bq; + struct page **spages, **bpages; + u8 *scratch, *buffer; + size_t bufsize, npages, size, copied; + int i, patt; + + bufsize =3D 0x100000; + npages =3D bufsize / PAGE_SIZE; + + bq =3D iov_kunit_create_bvecq(test, 13); + + scratch =3D iov_kunit_create_buffer(test, &spages, npages); + for (i =3D 0; i < bufsize; i++) + scratch[i] =3D pattern(i); + + buffer =3D iov_kunit_create_buffer(test, &bpages, npages); + memset(buffer, 0, bufsize); + + iov_kunit_load_bvecq(test, &iter, READ, bq, bpages, npages); + + i =3D 0; + for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { + size =3D pr->to - pr->from; + KUNIT_ASSERT_LE(test, pr->to, bufsize); + + iov_iter_bvec_queue(&iter, READ, bq, 0, 0, pr->to); + iov_iter_advance(&iter, pr->from); + copied =3D copy_to_iter(scratch + i, size, &iter); + + KUNIT_EXPECT_EQ(test, copied, size); + KUNIT_EXPECT_EQ(test, iter.count, 0); + i +=3D size; + if (test->status =3D=3D KUNIT_FAILURE) + goto stop; + } + + /* Build the expected image in the scratch buffer. */ + patt =3D 0; + memset(scratch, 0, bufsize); + for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) + for (i =3D pr->from; i < pr->to; i++) + scratch[i] =3D pattern(patt++); + + /* Compare the images */ + for (i =3D 0; i < bufsize; i++) { + KUNIT_EXPECT_EQ_MSG(test, buffer[i], scratch[i], "at i=3D%x", i); + if (buffer[i] !=3D scratch[i]) + return; + } + +stop: + KUNIT_SUCCEED(test); +} + +/* + * Test copying from a ITER_BVECQ-type iterator. + */ +static void __init iov_kunit_copy_from_bvecq(struct kunit *test) +{ + const struct kvec_test_range *pr; + struct iov_iter iter; + struct bvecq *bq; + struct page **spages, **bpages; + u8 *scratch, *buffer; + size_t bufsize, npages, size, copied; + int i, j; + + bufsize =3D 0x100000; + npages =3D bufsize / PAGE_SIZE; + + bq =3D iov_kunit_create_bvecq(test, 13); + + buffer =3D iov_kunit_create_buffer(test, &bpages, npages); + for (i =3D 0; i < bufsize; i++) + buffer[i] =3D pattern(i); + + scratch =3D iov_kunit_create_buffer(test, &spages, npages); + memset(scratch, 0, bufsize); + + iov_kunit_load_bvecq(test, &iter, READ, bq, bpages, npages); + + i =3D 0; + for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { + size =3D pr->to - pr->from; + KUNIT_ASSERT_LE(test, pr->to, bufsize); + + iov_iter_bvec_queue(&iter, WRITE, bq, 0, 0, pr->to); + iov_iter_advance(&iter, pr->from); + copied =3D copy_from_iter(scratch + i, size, &iter); + + KUNIT_EXPECT_EQ(test, copied, size); + KUNIT_EXPECT_EQ(test, iter.count, 0); + i +=3D size; + } + + /* Build the expected image in the main buffer. */ + i =3D 0; + memset(buffer, 0, bufsize); + for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { + for (j =3D pr->from; j < pr->to; j++) { + buffer[i++] =3D pattern(j); + if (i >=3D bufsize) + goto stop; + } + } +stop: + + /* Compare the images */ + for (i =3D 0; i < bufsize; i++) { + KUNIT_EXPECT_EQ_MSG(test, scratch[i], buffer[i], "at i=3D%x", i); + if (scratch[i] !=3D buffer[i]) + return; + } + + KUNIT_SUCCEED(test); +} + static void iov_kunit_destroy_xarray(void *data) { struct xarray *xarray =3D data; @@ -867,6 +1045,85 @@ static void __init iov_kunit_extract_pages_bvec(struc= t kunit *test) KUNIT_SUCCEED(test); } =20 +/* + * Test the extraction of ITER_BVECQ-type iterators. + */ +static void __init iov_kunit_extract_pages_bvecq(struct kunit *test) +{ + const struct kvec_test_range *pr; + struct iov_iter iter; + struct bvecq *bq; + struct page **bpages, *pagelist[8], **pages =3D pagelist; + ssize_t len; + size_t bufsize, size =3D 0, npages; + int i, from; + + bufsize =3D 0x100000; + npages =3D bufsize / PAGE_SIZE; + + bq =3D iov_kunit_create_bvecq(test, 13); + + iov_kunit_create_buffer(test, &bpages, npages); + iov_kunit_load_bvecq(test, &iter, READ, bq, bpages, npages); + + for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { + from =3D pr->from; + size =3D pr->to - from; + KUNIT_ASSERT_LE(test, pr->to, bufsize); + + iov_iter_bvec_queue(&iter, WRITE, bq, 0, 0, pr->to); + iov_iter_advance(&iter, from); + + do { + size_t offset0 =3D LONG_MAX; + + for (i =3D 0; i < ARRAY_SIZE(pagelist); i++) + pagelist[i] =3D (void *)(unsigned long)0xaa55aa55aa55aa55ULL; + + len =3D iov_iter_extract_pages(&iter, &pages, 100 * 1024, + ARRAY_SIZE(pagelist), 0, &offset0); + KUNIT_EXPECT_GE(test, len, 0); + if (len < 0) + break; + KUNIT_EXPECT_LE(test, len, size); + KUNIT_EXPECT_EQ(test, iter.count, size - len); + if (len =3D=3D 0) + break; + size -=3D len; + KUNIT_EXPECT_GE(test, (ssize_t)offset0, 0); + KUNIT_EXPECT_LT(test, offset0, PAGE_SIZE); + + for (i =3D 0; i < ARRAY_SIZE(pagelist); i++) { + struct page *p; + ssize_t part =3D min_t(ssize_t, len, PAGE_SIZE - offset0); + int ix; + + KUNIT_ASSERT_GE(test, part, 0); + ix =3D from / PAGE_SIZE; + KUNIT_ASSERT_LT(test, ix, npages); + p =3D bpages[ix]; + KUNIT_EXPECT_PTR_EQ(test, pagelist[i], p); + KUNIT_EXPECT_EQ(test, offset0, from % PAGE_SIZE); + from +=3D part; + len -=3D part; + KUNIT_ASSERT_GE(test, len, 0); + if (len =3D=3D 0) + break; + offset0 =3D 0; + } + + if (test->status =3D=3D KUNIT_FAILURE) + goto stop; + } while (iov_iter_count(&iter) > 0); + + KUNIT_EXPECT_EQ(test, size, 0); + KUNIT_EXPECT_EQ(test, iter.count, 0); + } + +stop: + KUNIT_SUCCEED(test); +} + /* * Test the extraction of ITER_FOLIOQ-type iterators. */ @@ -1226,12 +1483,15 @@ static struct kunit_case __refdata iov_kunit_cases[= ] =3D { KUNIT_CASE(iov_kunit_copy_from_kvec), KUNIT_CASE(iov_kunit_copy_to_bvec), KUNIT_CASE(iov_kunit_copy_from_bvec), + KUNIT_CASE(iov_kunit_copy_to_bvecq), + KUNIT_CASE(iov_kunit_copy_from_bvecq), KUNIT_CASE(iov_kunit_copy_to_folioq), KUNIT_CASE(iov_kunit_copy_from_folioq), KUNIT_CASE(iov_kunit_copy_to_xarray), KUNIT_CASE(iov_kunit_copy_from_xarray), KUNIT_CASE(iov_kunit_extract_pages_kvec), KUNIT_CASE(iov_kunit_extract_pages_bvec), + KUNIT_CASE(iov_kunit_extract_pages_bvecq), KUNIT_CASE(iov_kunit_extract_pages_folioq), KUNIT_CASE(iov_kunit_extract_pages_xarray), KUNIT_CASE(iov_kunit_iter_to_sg_kvec), From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DC819400DE2 for ; Mon, 24 Aug 2026 14:43:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582629; cv=none; b=Bgs//WkH66mYs/Jit44z48Jy8+gED5AQhYhMV5eK2qGZBfihV8Ar7ctq5sh7K9eFH/+0UThDCT9ELM+zTHmBx3S5U/H20s1U8dZnOko2R7B+OppSbxDXAV1A6kiP6ETrslx/O+amkZxZRS8dn109QU+w88wSGsekf8ZdbevuBIk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582629; c=relaxed/simple; bh=MKkyzCFZmDRHrbCHMejanqjzuroAFLKERorDD2qxh3I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kJmKA7c03X0O98ArtBaF0Lw9bss3gOo9S8wGJdac0nKvGpmAlCm8XTVxDOhKkmxF4CoMLkO/8S/kzPCMWPNHCuWwTcqc+G3O3QhhvifoAstNGfOVYAeJXnGNSiDkP05CvReGVzeu0CXeur9zH1YxohVSWxia+2u7i/Wy9+KVFQw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Vw2x5U2O; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Vw2x5U2O" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582606; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=TgrIrRAz8HB5htgg3CO9jEbp7XYngKIDrPcN2SBDHlU=; b=Vw2x5U2O+rh0sOup7RWXybpCBmUnicvMqU1AyftnPmPIjxSRChwcCtRPqX0RBV8ojChnbc 0sK4xXPu9g2bkvYYmn6dF+2SE/E8ek0mKq4rvuNWbvVUz8KPGrliswkG+6EX7s1sC7lvnK ssPiTsj1pRva1pUC36aH4dJ9t6cunkI= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-94-3Y3pxtAtMceAW2SlMjoNag-1; Mon, 24 Aug 2026 10:43:23 -0400 X-MC-Unique: 3Y3pxtAtMceAW2SlMjoNag-1 X-Mimecast-MFC-AGG-ID: 3Y3pxtAtMceAW2SlMjoNag_1787582599 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3CDF11944E43; Mon, 24 Aug 2026 14:43:19 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id BB95A30002ED; Mon, 24 Aug 2026 14:43:12 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 11/35] netfs: Add some tools for managing bvecq chains Date: Mon, 24 Aug 2026 15:41:04 +0100 Message-ID: <20260824144130.759997-12-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Provide a selection of tools for managing bvec queue chains. This includes: (1) Allocation, prepopulation, expansion, shortening and refcounting of bvecqs and bvecq chains. This can be used to do things like creating an encryption buffer in cifs or a directory content buffer in afs. The memory segments will be appropriate disposed off according to the flags on the bvecq. (2) Management of a bvecq chain as a rolling buffer and the management of positions within it. (3) Loading folios, slicing chains and clearing content. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/Makefile | 1 + fs/netfs/bvecq.c | 830 +++++++++++++++++++++++++++++++++++ fs/netfs/internal.h | 1 + fs/netfs/stats.c | 4 +- include/linux/bvecq.h | 308 +++++++++++++ include/linux/netfs.h | 1 + include/trace/events/netfs.h | 24 + 7 files changed, 1168 insertions(+), 1 deletion(-) create mode 100644 fs/netfs/bvecq.c diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile index b43188d64bd8..e1f12ecb5abf 100644 --- a/fs/netfs/Makefile +++ b/fs/netfs/Makefile @@ -3,6 +3,7 @@ netfs-y :=3D \ buffered_read.o \ buffered_write.o \ + bvecq.o \ direct_read.o \ direct_write.o \ iterator.o \ diff --git a/fs/netfs/bvecq.c b/fs/netfs/bvecq.c new file mode 100644 index 000000000000..6905c84ea351 --- /dev/null +++ b/fs/netfs/bvecq.c @@ -0,0 +1,830 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Buffering helpers for bvec queues + * + * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + */ + +#include +#include "internal.h" + +void bvecq_dump(const struct bvecq *bq) +{ + int b =3D 0; + + for (; bq; bq =3D bvecq_next(bq), b++) { + int skipz =3D 0; + + pr_notice("BQ[%u] %u/%u\n", b, bq->nr_slots, bq->max_slots); + for (int s =3D 0; s < bq->nr_slots; s++) { + const struct bio_vec *bv =3D &bq->bv[s]; + + if (!bv->bv_page && !bv->bv_len && skipz < 2) { + skipz =3D 1; + continue; + } + if (skipz =3D=3D 1) + pr_notice("BQ[%u:00-%02u] ...\n", b, s - 1); + skipz =3D 2; + pr_notice("BQ[%u:%02u] %10lx %04x %04x %u\n", + b, s, + bv->bv_page ? page_to_pfn(bv->bv_page) : 0, + bv->bv_offset, bv->bv_len, + bv->bv_page ? page_count(bv->bv_page) : 0); + } + } +} +EXPORT_SYMBOL(bvecq_dump); + +/** + * bvecq_alloc_one - Allocate a single bvecq node with unpopulated slots + * @nr_slots: Number of slots to allocate + * @gfp: The allocation constraints. + * @for_writeback: True if allocating for writeback + * + * Allocate a single bvecq node and initialise the header. A number of in= line + * slots are also allocated, rounded up to fit after the header in a power= -of-2 + * slab object of up to 512 bytes (up to 29 slots on a 64-bit cpu). The s= lot + * array is not initialised. + * + * Return: The node pointer or NULL on allocation failure. + */ +struct bvecq *bvecq_alloc_one(size_t nr_slots, gfp_t gfp, bool for_writeba= ck) +{ + struct bvecq *bq; + const size_t max_size =3D 512; + const size_t max_slots =3D (max_size - sizeof(*bq)) / sizeof(bq->__bv[0]); + size_t part =3D min(nr_slots, max_slots); + size_t size =3D roundup_pow_of_two(struct_size(bq, __bv, part)); + + bq =3D kmalloc(size, gfp & ~GFP_ZONEMASK); + if (!bq) + return bq; + + *bq =3D (struct bvecq) { + .ref =3D REFCOUNT_INIT(1), + .bv =3D bq->__bv, + .inline_bv =3D true, + .max_slots =3D (size - sizeof(*bq)) / sizeof(bq->__bv[0]), + }; + netfs_stat(&netfs_n_bvecq); + return bq; +} +EXPORT_SYMBOL(bvecq_alloc_one); + +/** + * bvecq_alloc_chain - Allocate an unpopulated bvecq chain + * @nr_slots: Number of slots to allocate + * @gfp: The allocation constraints. + * @for_writeback: True if allocating for writeback + * + * Allocate a chain of bvecq nodes providing at least the requested cumula= tive + * number of slots. + * + * Return: The first node pointer or NULL on allocation failure. + */ +struct bvecq *bvecq_alloc_chain(size_t nr_slots, gfp_t gfp, bool for_write= back) +{ + struct bvecq *head =3D NULL, *tail =3D NULL; + + _enter("%zu", nr_slots); + + for (;;) { + struct bvecq *bq; + + bq =3D bvecq_alloc_one(nr_slots, gfp, for_writeback); + if (!bq) + goto oom; + + if (tail) + bvecq_append(tail, bq); + else + head =3D bq; + tail =3D bq; + if (tail->max_slots >=3D nr_slots) + break; + nr_slots -=3D tail->max_slots; + } + + return head; +oom: + bvecq_put(head); + return NULL; +} +EXPORT_SYMBOL(bvecq_alloc_chain); + +/** + * bvecq_alloc_buffer2 - Allocate a bvecq chain and populate with buffers + * @size: Target size of the buffer (can be 0 for an empty buffer) + * @pre_slots: Number of preamble slots to set aside + * @gfp: The allocation constraints. + * @for_writeback: True if allocating for writeback + * + * Allocate a chain of bvecq nodes and populate the slots with sufficient = pages + * to provide at least the requested amount of space, leaving the first + * @pre_slots slots unset. The pre-slots must all fit into the the first + * bvecq. + * + * The pages allocated may be compound pages larger than PAGE_SIZE and thus + * occupy fewer slots. The pages have their refcounts set to 1 and can be + * passed to MSG_SPLICE_PAGES. + * + * Return: The first node pointer or NULL on allocation failure. + */ +struct bvecq *bvecq_alloc_buffer2(size_t size, unsigned int pre_slots, gfp= _t gfp, + bool for_writeback) +{ + struct bvecq *head =3D NULL, *p =3D NULL; + size_t nr_per_bq =3D BVECQ_STD_SLOTS; + size_t count =3D pre_slots + DIV_ROUND_UP(size, PAGE_SIZE); + + _enter("%zx,%zx,%u", size, count, pre_slots); + + if (WARN_ON_ONCE(pre_slots > nr_per_bq)) + return NULL; + + head =3D bvecq_alloc_chain(count, gfp, for_writeback); + if (!head) + return NULL; + + p =3D head; + do { + struct page **pages; + size_t unused, want, got, slot; + + if (!count) + break; + if (WARN_ON_ONCE(!p)) + goto oom; + + if (p->nr_slots =3D=3D 0) { + /* Need to clear pre slots and pages[], so just clear all. */ + memset(p->bv, 0, p->max_slots * sizeof(p->bv[0])); + p->mem_type =3D BVECQ_MEM_ALLOCED; + p->nr_slots =3D pre_slots; + count -=3D pre_slots; + pre_slots =3D 0; + if (!count) + break; + } + + if (p->nr_slots >=3D p->max_slots) { + p =3D p->next; + continue; + } + unused =3D p->max_slots - p->nr_slots; + + pages =3D (struct page **)&p->bv[p->max_slots]; + pages -=3D unused; + + want =3D min(count, unused); + got =3D alloc_pages_bulk(gfp, want, pages); + if (!got) + goto oom; + + slot =3D p->nr_slots; + for (int i =3D 0; i < got; i++) { + set_page_count(pages[i], 1); + bvec_set_page(&p->bv[slot++], pages[i], PAGE_SIZE, 0); + } + + bvecq_filled_to(p, slot); + count -=3D got; + } while (count > 0); + + return head; +oom: + bvecq_put(head); + return NULL; +} +EXPORT_SYMBOL(bvecq_alloc_buffer2); + +/* + * Free the page pointed to by a slot as necessary. + */ +static void bvecq_free_slot(struct bvecq *bq, unsigned int slot) +{ + struct page *page =3D bq->bv[slot].bv_page; + + if (!page) + return; + + switch (bq->mem_type) { + case BVECQ_MEM_EXTERNAL: + break; + case BVECQ_MEM_PAGECACHE: + put_page(page); + break; + case BVECQ_MEM_GUP: + unpin_user_page(page); + break; + case BVECQ_MEM_ALLOCED: + __free_pages(page, compound_order(page)); + break; + default: + WARN_ON_ONCE(1); + break; + } +} + +/** + * bvecq_put - Put a ref on a bvec queue + * @bq: The start of the folio queue to free + * + * Put the ref(s) on the nodes in a bvec queue, freeing up the node and the + * page fragments it points to as the refcounts become zero. + */ +void bvecq_put(struct bvecq *bq) +{ + struct bvecq *next; + + for (; bq; bq =3D next) { + if (!refcount_dec_and_test(&bq->ref)) + break; + for (int slot =3D 0; slot < bq->nr_slots; slot++) + bvecq_free_slot(bq, slot); + next =3D bq->next; + netfs_stat_d(&netfs_n_bvecq); + kfree(bq); + } +} +EXPORT_SYMBOL(bvecq_put); + +/** + * bvecq_expand_buffer - Allocate buffer space into a bvec queue + * @_buffer: Pointer to the bvecq chain to expand (may point to a NULL; up= dated). + * @_cur_size: Current size of the buffer (updated). + * @size: Target size of the buffer. + * @gfp: The allocation constraints. + * + * Append extra pages to a buffer to increase its capacity to the @size + * specified. If the current tail has space, but is not of the + * BVECQ_MEM_ALLOCED memory type, a separate bvecq will be allocated to ho= ld + * the new memory. + */ +int bvecq_expand_buffer(struct bvecq **_buffer, size_t *_cur_size, size_t = size, gfp_t gfp) +{ + struct bvecq *tail =3D *_buffer; + + size =3D round_up(size, PAGE_SIZE); + if (tail) + while (tail->next) + tail =3D tail->next; + + while (*_cur_size < size) { + struct page *page; + size_t need =3D size - *_cur_size; + int order =3D 0; + + if (!tail || bvecq_is_full(tail) || tail->mem_type !=3D BVECQ_MEM_ALLOCE= D) { + struct bvecq *p; + + p =3D bvecq_alloc_one(BVECQ_STD_SLOTS, gfp, false); + if (!p) + return -ENOMEM; + if (tail) + bvecq_append(tail, p); + else + *_buffer =3D p; + tail =3D p; + p->mem_type =3D BVECQ_MEM_ALLOCED; + } + + if (need > PAGE_SIZE) + order =3D umin(ilog2(need) - PAGE_SHIFT, MAX_PAGECACHE_ORDER); + + page =3D alloc_pages(gfp | __GFP_COMP, order); + if (!page && order > 0) { + page =3D alloc_pages(gfp | __GFP_COMP, 0); + order =3D 0; + } + if (!page) + return -ENOMEM; + + bvec_set_page(&tail->bv[tail->nr_slots], page, PAGE_SIZE << order, 0); + *_cur_size +=3D PAGE_SIZE << order; + bvecq_filled_to(tail, tail->nr_slots + 1); + } + + return 0; +} +EXPORT_SYMBOL(bvecq_expand_buffer); + +/** + * bvecq_shorten_buffer - Shorten a bvec queue buffer + * @bq: The start of the buffer to shorten + * @slot: The slot to start from + * @size: The size to retain + * + * Shorten the content of a bvec queue down to the minimum number of slots, + * starting at the specified slot, to retain the specified size. + * + * Return: 0 if successful; -EMSGSIZE if there is insufficient content. + */ +int bvecq_shorten_buffer(struct bvecq *bq, unsigned int slot, size_t size) +{ + struct bvecq *next; + + /* Skip through the segments we want to keep. */ + for (; bq; bq =3D bq->next) { + for (; slot < bq->nr_slots; slot++) { + if (size < bq->bv[slot].bv_len) + goto found; + size -=3D bq->bv[slot].bv_len; + } + slot =3D 0; + } + if (WARN_ON_ONCE(size > 0)) + return -EMSGSIZE; + return 0; + +found: + /* Shorten any partial entry and clean the rest of this bvecq. */ + if (size > 0) { + bq->bv[slot].bv_len =3D size; + slot++; + } + for (int i =3D slot; i < bq->nr_slots; i++) + bvecq_free_slot(bq, i); + bq->nr_slots =3D slot; + + /* Free the queue tail. */ + next =3D bq->next; + bq->next =3D NULL; + bvecq_put(next); + return 0; +} +EXPORT_SYMBOL(bvecq_shorten_buffer); + +/** + * bvecq_buffer_init - Initialise a buffer and set position + * @pos: The position to point at the new buffer. + * @gfp: The allocation constraints. + * @for_writeback: True if allocating for writeback + * + * Initialise a rolling buffer. We allocate an unpopulated bvecq node to = so + * that the pointers can be independently driven by the producer and the + * consumer. + * + * Return 0 if successful; -ENOMEM on allocation failure. + */ +int bvecq_buffer_init(struct bvecq_pos *pos, gfp_t gfp, bool for_writeback) +{ + struct bvecq *bq; + + bq =3D bvecq_alloc_one(BVECQ_STD_SLOTS, gfp, for_writeback); + if (!bq) + return -ENOMEM; + + pos->bvecq =3D bq; /* Comes with a ref. */ + pos->slot =3D 0; + pos->offset =3D 0; + return 0; +} + +/** + * bvecq_buffer_append - Append a new bvecq node to a buffer + * @pos: The position of the last node. + * @bq: The buffer to add. + * + * Add a new node on to the buffer chain at the specified position, either + * because the previous one is full or because we have a discontiguity to + * contend with, and update @pos to point to it. + */ +void bvecq_buffer_append(struct bvecq_pos *pos, struct bvecq *bq) +{ + struct bvecq *head =3D pos->bvecq; + + pos->bvecq =3D bvecq_get(bq); + pos->slot =3D 0; + pos->offset =3D 0; + + /* [!] NOTE: After we set head->next, the consumer is at liberty to + * immediately delete the old head. + */ + bvecq_append(head, bq); + bvecq_put(head); +} + +/** + * bvecq_pos_advance - Advance a bvecq position + * @pos: The position to advance. + * @amount: The amount of bytes to advance by. + * + * Advance the specified bvecq position by @amount bytes. @pos is updated= and + * bvecq ref counts may have been manipulated. If the position hits the e= nd of + * the queue, then it is left pointing beyond the last slot of the last bv= ecq + * so that it doesn't break the chain. + */ +void bvecq_pos_advance(struct bvecq_pos *pos, size_t amount) +{ + struct bvecq *bq =3D pos->bvecq, *next; + unsigned int slot =3D pos->slot; + size_t offset =3D pos->offset; + + while (amount) { + size_t part; + + if (!bvecq_acquire_slot(bq, slot)) { + next =3D bvecq_next(bq); + if (!next) { + WARN_ON_ONCE(amount > 0); + break; + } + if (bvecq_acquire_slot(bq, slot)) + continue; /* More slots got added. */ + bq =3D next; + slot =3D 0; + offset =3D 0; + continue; + } + + part =3D bq->bv[slot].bv_len - offset; + + if (part > amount) { + offset +=3D amount; + break; + } + amount -=3D part; + offset =3D 0; + slot++; + } + + pos->slot =3D slot; + pos->offset =3D offset; + bvecq_pos_move(pos, bq); +} + +/* + * Clear part of the memory pointed to by a bio_vec. + */ +static void bvec_zero(const struct bio_vec *bv, size_t offset, size_t len) +{ + struct page *page =3D bv->bv_page; + + offset +=3D bv->bv_offset; + + page +=3D offset / PAGE_SIZE; + offset =3D offset % PAGE_SIZE; + + while (len) { + size_t part =3D min(len, PAGE_SIZE - offset); + char *p =3D kmap_local_page(page); + + memset(p + offset, 0, part); + kunmap_local(p); + + len -=3D part; + offset =3D 0; + page++; + } +} + +/** + * bvecq_zero - Clear memory starting at the bvecq position. + * @pos: The position in the bvecq chain to start clearing. + * @amount: The number of bytes to clear. + * + * Clear memory fragments pointed to by a bvec queue. @pos is updated and + * bvecq ref counts may have been manipulated. If the position hits the e= nd of + * the queue, then it is left pointing beyond the last slot of the last bv= ecq + * so that it doesn't break the chain. + * + * Return: The number of bytes cleared. + */ +ssize_t bvecq_zero(struct bvecq_pos *pos, size_t amount) +{ + struct bvecq *bq =3D pos->bvecq, *next; + unsigned int slot =3D pos->slot; + ssize_t cleared =3D 0; + size_t offset =3D pos->offset; + + while (amount) { + const struct bio_vec *bv; + size_t part; + + if (!bvecq_acquire_slot(bq, slot)) { + next =3D bvecq_next(bq); + if (!next) { + WARN_ON_ONCE(amount > 0); + break; + } + if (bvecq_acquire_slot(bq, slot)) + continue; /* More slots got added. */ + bq =3D next; + slot =3D 0; + offset =3D 0; + continue; + } + + bv =3D &bq->bv[slot]; + if (offset >=3D bv->bv_len) { + slot++; + offset =3D 0; + continue; + } + + part =3D min(bv->bv_len - offset, amount); + bvec_zero(bv, offset, part); + cleared +=3D part; + offset +=3D part; + amount -=3D part; + } + + pos->slot =3D slot; + pos->offset =3D offset; + bvecq_pos_move(pos, bq); + return cleared; +} + +/** + * bvecq_slice - Find a slice of a bvecq queue + * @pos: The position to start at. + * @max_size: The maximum size of the slice (or ULONG_MAX). + * @max_slots: The maximum number of slots in the slice (or INT_MAX). + * @_nr_slots: Where to put the number of slots (updated). + * + * Determine the size and number of slots that can be obtained the next sl= ice + * of bvec queue up to the maximum size and slot count specified. + * + * @pos is updated to the end of the slice. If the position hits the end = of + * the queue, then it is left pointing beyond the last slot of the last bv= ecq + * so that it doesn't break the chain. + * + * Return: The number of bytes in the slice. + */ +size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size, + unsigned int max_slots, unsigned int *_nr_slots) +{ + struct bvecq *bq, *next; + unsigned int slot =3D pos->slot, nslots =3D 0; + size_t size =3D 0, offset =3D pos->offset; + + bq =3D pos->bvecq; + for (;;) { + for (; slot < bvecq_nr_slots_acquire(bq); slot++) { + const struct bio_vec *bvec =3D &bq->bv[slot]; + + if (offset < bvec->bv_len && bvec->bv_page) { + size_t part =3D min(bvec->bv_len - offset, max_size); + + size +=3D part; + offset +=3D part; + max_size -=3D part; + nslots++; + if (!max_size || nslots >=3D max_slots) + goto out; + } + offset =3D 0; + } + + /* pos->bvecq isn't allowed to go NULL as the queue may get + * extended and we would lose our place. + */ + next =3D bvecq_next(bq); + if (!next) + break; + if (bvecq_acquire_slot(bq, slot)) + continue; /* More slots got added. */ + slot =3D 0; + bq =3D next; + } + +out: + *_nr_slots =3D nslots; + if (slot =3D=3D bvecq_nr_slots_acquire(bq)) { + next =3D bvecq_next(bq); + if (next) { + bq =3D next; + slot =3D 0; + offset =3D 0; + } + } + bvecq_pos_move(pos, bq); + pos->slot =3D slot; + pos->offset =3D offset; + return size; +} + +/** + * bvecq_extract - Extract a slice of a bvecq queue into a new bvecq queue + * @pos: The position to start at. + * @max_size: The maximum size of the slice (or ULONG_MAX). + * @max_slots: The maximum number of slots in the slice (or INT_MAX). + * @to: Where to put the extraction bvecq chain head (updated). + * @for_writeback: True if allocating for writeback + * + * Allocate a new bvecq and extract into it memory fragments from a slice = of + * bvec queue, starting at @pos. No refs are taken on the page. + * + * @pos is updated to the end of the slice. If the position hits the end = of + * the queue, then it is left pointing beyond the last slot of the last bv= ecq + * so that it doesn't break the chain. + * + * If successful, *@to is set to point to the head of the newly allocated = chain + * and the caller inherits a ref to it. + * + * Return: The number of bytes extracted; -ENOMEM on allocation failure or= -EIO + * if no slots were available to extract. + */ +ssize_t bvecq_extract(struct bvecq_pos *pos, size_t max_size, unsigned int= max_slots, + struct bvecq **to, bool for_writeback) +{ + struct bvecq_pos tmp_pos; + struct bvecq *src, *dst =3D NULL, *next; + unsigned int slot =3D pos->slot, dslot =3D 0, nslots; + ssize_t extracted =3D 0; + size_t offset =3D pos->offset, amount; + + *to =3D NULL; + if (WARN_ON_ONCE(!max_slots)) + max_slots =3D INT_MAX; + + bvecq_pos_set(&tmp_pos, pos); + amount =3D bvecq_slice(&tmp_pos, max_size, max_slots, &nslots); + bvecq_pos_unset(&tmp_pos); + if (nslots =3D=3D 0) + return -EIO; + + dst =3D bvecq_alloc_chain(nslots, GFP_NOFS, for_writeback); + if (!dst) + return -ENOMEM; + *to =3D dst; + max_slots =3D nslots; + nslots =3D 0; + + /* Transcribe the slots */ + src =3D pos->bvecq; + for (;;) { + for (; slot < bvecq_nr_slots_acquire(src); slot++) { + const struct bio_vec *sv =3D &src->bv[slot]; + struct bio_vec *dv =3D &dst->bv[dslot]; + + _debug("EXTR BQ=3D%x[%x] off=3D%zx am=3D%zx p=3D%lx", + src->priv, slot, offset, amount, page_to_pfn(sv->bv_page)); + + if (offset < sv->bv_len && sv->bv_page) { + size_t part =3D min(sv->bv_len - offset, amount); + + bvec_set_page(dv, sv->bv_page, part, + sv->bv_offset + offset); + extracted +=3D part; + amount -=3D part; + offset +=3D part; + trace_netfs_bv_slot(dst, dslot); + dslot++; + nslots++; + if (dslot >=3D dst->max_slots) { + bvecq_filled_to(dst, dslot); + dst =3D dst->next; + dslot =3D 0; + } + if (nslots >=3D max_slots) + goto out; + if (amount =3D=3D 0) + goto out; + } + offset =3D 0; + } + + /* pos->bvecq isn't allowed to go NULL as the queue may get + * extended and we would lose our place. + */ + next =3D bvecq_next(src); + if (!next) + break; + if (bvecq_acquire_slot(src, slot)) + continue; /* More slots got added. */ + slot =3D 0; + src =3D next; + if (extracted > 0) + break; + } + +out: + if (dst) + bvecq_filled_to(dst, dslot); + if (slot =3D=3D bvecq_nr_slots_acquire(src)) { + next =3D bvecq_next(src); + if (next) { + src =3D next; + slot =3D 0; + offset =3D 0; + } + } + bvecq_pos_move(pos, src); + pos->slot =3D slot; + pos->offset =3D offset; + return extracted; +} + +/** + * bvecq_load_from_ra - Allocate a bvecq chain and load from readahead + * @pos: Blank position object to attach the new chain to. + * @ractl: The readahead control context. + * + * Decant the set of folios to be read from the readahead context into a b= vecq + * chain. Each folio occupies one bio_vec element. + * + * Return: Amount of data loaded or -ENOMEM on allocation failure. + */ +ssize_t bvecq_load_from_ra(struct bvecq_pos *pos, struct readahead_control= *ractl) +{ + XA_STATE(xas, &ractl->mapping->i_pages, ractl->_index); + struct folio *folio; + struct bvecq *bq; + unsigned int slot =3D 0; + size_t loaded =3D 0; + + bq =3D bvecq_alloc_chain(ractl->_nr_folios, GFP_KERNEL, false); + if (!bq) + return -ENOMEM; + + pos->bvecq =3D bq; + pos->slot =3D 0; + pos->offset =3D 0; + + rcu_read_lock(); + + xas_for_each(&xas, folio, ractl->_index + ractl->_nr_pages - 1) { + size_t len; + + if (xas_retry(&xas, folio)) + continue; + VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); + + len =3D folio_size(folio); + bvec_set_folio(&bq->bv[slot], folio, len, 0); + loaded +=3D len; + slot++; + trace_netfs_folio(folio, netfs_folio_trace_read); + + if (slot >=3D bq->max_slots) { + bvecq_filled_to(bq, slot); + bq =3D bq->next; + if (!bq) + break; + slot =3D 0; + } + } + + rcu_read_unlock(); + + if (bq) + bvecq_filled_to(bq, slot); + + ractl->_index +=3D ractl->_nr_pages; + ractl->_nr_pages =3D 0; + return loaded; +} + +/* + * Add space to a buffer. + */ +static int bvecq_buffer_make_space(struct bvecq_pos *pos, gfp_t gfp, bool = for_writeback) +{ + struct bvecq *bq; + + bq =3D bvecq_alloc_one(BVECQ_STD_SLOTS, gfp, for_writeback); + if (!bq) + return -ENOMEM; + + bvecq_buffer_append(pos, bq); + return 0; +} + +/** + * bvecq_append_page - Add part of a page to a buffer and advance to it + * @pos: The current position in the buffer + * @page: The page to add + * @offset: The offset of the page part to include + * @len: The length of the page part to include + * @gfp: The allocation flags + * @for_writeback: True if allocating for writeback + * + * Add part of a page to a buffer, extending the buffer if necessary. The + * position in the buffer is updated on return. + * + * Return: 0 or -ENOMEM on allocation failure. + */ +int bvecq_append_page(struct bvecq_pos *pos, struct page *page, + size_t offset, size_t len, gfp_t gfp, bool for_writeback) +{ + struct bvecq *bq =3D pos->bvecq; + int slot =3D pos->slot; + + WARN_ON_ONCE(slot !=3D bq->nr_slots); + + if (slot >=3D bq->max_slots) { + if (bvecq_buffer_make_space(pos, gfp, for_writeback) < 0) + return -ENOMEM; + bq =3D pos->bvecq; + slot =3D pos->slot; + } + + bvec_set_page(&bq->bv[slot++], page, len, offset); + bvecq_filled_to(bq, slot); + pos->slot =3D slot; + return 0; +} +EXPORT_SYMBOL(bvecq_append_page); diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 5680e37fc70d..8db2c82004a4 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -172,6 +172,7 @@ extern atomic_t netfs_n_wh_retry_write_subreq; extern atomic_t netfs_n_wb_lock_skip; extern atomic_t netfs_n_wb_lock_wait; extern atomic_t netfs_n_folioq; +extern atomic_t netfs_n_bvecq; =20 int netfs_stats_show(struct seq_file *m, void *v); =20 diff --git a/fs/netfs/stats.c b/fs/netfs/stats.c index ab6b916addc4..84c2a4bcc762 100644 --- a/fs/netfs/stats.c +++ b/fs/netfs/stats.c @@ -48,6 +48,7 @@ atomic_t netfs_n_wh_retry_write_subreq; atomic_t netfs_n_wb_lock_skip; atomic_t netfs_n_wb_lock_wait; atomic_t netfs_n_folioq; +atomic_t netfs_n_bvecq; =20 int netfs_stats_show(struct seq_file *m, void *v) { @@ -90,9 +91,10 @@ int netfs_stats_show(struct seq_file *m, void *v) atomic_read(&netfs_n_rh_retry_read_subreq), atomic_read(&netfs_n_wh_retry_write_req), atomic_read(&netfs_n_wh_retry_write_subreq)); - seq_printf(m, "Objs : rr=3D%u sr=3D%u foq=3D%u wsc=3D%u\n", + seq_printf(m, "Objs : rr=3D%u sr=3D%u bq=3D%u foq=3D%u wsc=3D%u\n", atomic_read(&netfs_n_rh_rreq), atomic_read(&netfs_n_rh_sreq), + atomic_read(&netfs_n_bvecq), atomic_read(&netfs_n_folioq), atomic_read(&netfs_n_wh_wstream_conflict)); seq_printf(m, "WbLock : skip=3D%u wait=3D%u\n", diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h index 77fd07852c33..22fc7995f3ee 100644 --- a/include/linux/bvecq.h +++ b/include/linux/bvecq.h @@ -47,4 +47,312 @@ struct bvecq { struct bio_vec __bv[]; /* Default array (if ->inline_bv) */ }; =20 +#if BITS_PER_LONG =3D=3D 64 +/* Number of slots in __bv[] for a bvecq in a 512-byte kmalloc block. */ +#define BVECQ_STD_SLOTS 29 /* 2 words/slot; 32 slots; bvecq is 6 words (3= slots) */ +#elif BITS_PER_LONG =3D=3D 32 +/* Number of slots in __bv[] for a bvecq in a 256-byte kmalloc block. */ +#define BVECQ_STD_SLOTS 18 /* 3 words/slot; 21 slots; bvecq is 9 words (3= slots) */ +#else +#error BVECQ_STD_SLOTS undetermined +#endif + +/* + * Position in a bio_vec queue. The bvecq holds a ref on the queue segmen= t it + * points to. + */ +struct bvecq_pos { + struct bvecq *bvecq; /* The first bvecq */ + unsigned int offset; /* The offset within the starting slot */ + u16 slot; /* The starting slot */ +}; + +void bvecq_dump(const struct bvecq *bq); +struct bvecq *bvecq_alloc_one(size_t nr_slots, gfp_t gfp, bool for_writeba= ck); +struct bvecq *bvecq_alloc_chain(size_t nr_slots, gfp_t gfp, bool for_write= back); +struct bvecq *bvecq_alloc_buffer2(size_t size, unsigned int pre_slots, gfp= _t gfp, + bool for_writeback); +void bvecq_put(struct bvecq *bq); +int bvecq_expand_buffer(struct bvecq **_buffer, size_t *_cur_size, size_t = size, gfp_t gfp); +int bvecq_shorten_buffer(struct bvecq *bq, unsigned int slot, size_t size); +int bvecq_buffer_init(struct bvecq_pos *pos, gfp_t gfp, bool for_writeback= ); +void bvecq_buffer_append(struct bvecq_pos *pos, struct bvecq *bq); +void bvecq_pos_advance(struct bvecq_pos *pos, size_t amount); +ssize_t bvecq_zero(struct bvecq_pos *pos, size_t amount); +size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size, + unsigned int max_slots, unsigned int *_nr_slots); +ssize_t bvecq_extract(struct bvecq_pos *pos, size_t max_size, unsigned int= max_slots, + struct bvecq **to, bool for_writeback); +ssize_t bvecq_load_from_ra(struct bvecq_pos *pos, struct readahead_control= *ractl); +int bvecq_append_page(struct bvecq_pos *pos, struct page *page, + size_t offset, size_t len, gfp_t gfp, bool for_writeback); + +/** + * bvecq_alloc_buffer - Allocate a bvecq chain and populate with buffers + * @size: Target size of the buffer (can be 0 for an empty buffer) + * @gfp: The allocation constraints. + * @for_writeback: True if allocating for writeback + * + * Wrapper around %bvecq_alloc_buffer2(). + */ +static inline struct bvecq *bvecq_alloc_buffer(size_t size, gfp_t gfp, boo= l for_writeback) +{ + return bvecq_alloc_buffer2(size, 0, gfp, for_writeback); +} + +/** + * bvecq_get - Get a ref on a bvecq + * @bq: The bvecq to get a ref on + */ +static inline struct bvecq *bvecq_get(struct bvecq *bq) +{ + refcount_inc(&bq->ref); + return bq; +} + +/** + * bvecq_is_full - Determine if a bvecq is full + * @bvecq: The object to query + * + * Return: true if full; false if not. + */ +static inline bool bvecq_is_full(const struct bvecq *bvecq) +{ + return bvecq->nr_slots >=3D bvecq->max_slots; +} + +/** + * bvecq_filled_to - Release filled slots with release barrier + * @bvecq: The object modified + * @to: The latest slot filled + 1 + */ +static inline void bvecq_filled_to(struct bvecq *bvecq, unsigned int to) +{ + /* Set the slot counter after filling the slot */ + smp_store_release(&bvecq->nr_slots, to); +} + +/** + * bvecq_nr_slots_acquire - Get the number of filled slots with acquire ba= rrier + * @bvecq: The object to query + * + * Return: The number of filled slots + */ +static inline unsigned int bvecq_nr_slots_acquire(const struct bvecq *bvec= q) +{ + /* Read the slot counter before looking at the slot */ + return smp_load_acquire(&bvecq->nr_slots); +} + +/** + * bvecq_acquire_slot - Determine if a slot is valid with acquire barrier + * @bvecq: The object to query + * @slot: The next slot + * + * Return: true if valid; false if might not be valid + */ +static inline bool bvecq_acquire_slot(const struct bvecq *bvecq, unsigned = int slot) +{ + /* Read the slot counter before looking at the slot */ + return slot < bvecq_nr_slots_acquire(bvecq); +} + +/** + * bvecq_append - Get the next bvecq with appropriate barrier + * @to: The bvecq to append to + * @add: The bvecq to append + * + * Attach a new bvecq to a chain using an appropriate barrier to protect t= he + * write. + * + * [!] Note that this function transfers the caller's ref to the chain. + */ +static inline void bvecq_append(struct bvecq *to, struct bvecq *add) +{ + add->prev =3D to; + + /* Make sure the initialisation is stored before the next pointer. */ + smp_store_release(&to->next, add); +} + +/** + * bvecq_next - Get the next bvecq with appropriate barrier + * @bq: The bvecq to start from + * + * Return the next bvecq in a chain, using an appropriate barrier to prote= ct + * the access. + */ +static inline struct bvecq *bvecq_next(const struct bvecq *bq) +{ + /* Read the contents of the next node after the pointer to it. */ + return smp_load_acquire(&bq->next); +} + +/** + * bvecq_pos_set - Set one position to be the same as another + * @pos: The position object to set + * @at: The source position. + * + * Set @pos to have the same position as @at. This may take a ref on the + * bvecq pointed to. + */ +static inline void bvecq_pos_set(struct bvecq_pos *pos, const struct bvecq= _pos *at) +{ + *pos =3D *at; + bvecq_get(pos->bvecq); +} + +/** + * bvecq_pos_unset - Unset a position + * @pos: The position object to unset + * + * Unset @pos. This does any needed ref cleanup. + */ +static inline void bvecq_pos_unset(struct bvecq_pos *pos) +{ + bvecq_put(pos->bvecq); + pos->bvecq =3D NULL; + pos->slot =3D 0; + pos->offset =3D 0; +} + +/** + * bvecq_pos_transfer - Transfer one position to another, clearing the fir= st + * @pos: The position object to set + * @from: The source position to clear. + * + * Set @pos to have the same position as @from and then clear @from. This= may + * transfer a ref on the bvecq pointed to. + */ +static inline void bvecq_pos_transfer(struct bvecq_pos *pos, struct bvecq_= pos *from) +{ + *pos =3D *from; + from->bvecq =3D NULL; + from->slot =3D 0; + from->offset =3D 0; +} + +/** + * bvecq_pos_move - Update a position to a new bvecq + * @pos: The position object to update. + * @to: The new bvecq to point at. + * + * Update @pos to point to @to if it doesn't already do so. This may + * manipulate refs on the bvecqs pointed to. + */ +static inline void bvecq_pos_move(struct bvecq_pos *pos, struct bvecq *to) +{ + struct bvecq *old =3D pos->bvecq; + + if (old !=3D to) { + pos->bvecq =3D bvecq_get(to); + bvecq_put(old); + } +} + +/** + * bvecq_pos_nudge - Nudge a position onto the next segment if current use= d up + * @pos: The position object to nudge. + * + * Update @pos to point to the next segment in the chain if we've used up = the + * current segment. This may manipulate refs on the bvecqs pointed to. + * + * Return: true if found a new segment, false if hit the end. + */ +static inline bool bvecq_pos_nudge(struct bvecq_pos *pos) +{ + struct bvecq *bq =3D pos->bvecq; + + for (;;) { + if (!bvecq_acquire_slot(bq, pos->slot)) { + bq =3D bvecq_next(bq); + if (!bq) + return false; + if (bvecq_acquire_slot(bq, pos->slot)) + continue; /* More slots got added. */ + bvecq_pos_move(pos, bq); + pos->slot =3D 0; + pos->offset =3D 0; + continue; + } + if (pos->offset >=3D bq->bv[pos->slot].bv_len) { + pos->slot++; + pos->offset =3D 0; + continue; + } + return true; + } +} + +/** + * bvecq_pos_step - Step a position to the next slot if possible + * @pos: The position object to step. + * + * Update @pos to point to the next slot in the queue if not at the end. = This + * may manipulate refs on the bvecqs pointed to. + * + * Return: true if successful, false if was at the end. + */ +static inline bool bvecq_pos_step(struct bvecq_pos *pos) +{ + struct bvecq *bq =3D pos->bvecq, *next; + + pos->slot++; + pos->offset =3D 0; + if (bvecq_acquire_slot(bq, pos->slot)) + return true; + next =3D bvecq_next(bq); + if (!next) + return false; + if (bvecq_acquire_slot(bq, pos->slot)) + return true; + bvecq_pos_move(pos, next); + pos->slot =3D 0; + return true; +} + +/** + * bvecq_delete_spent - Delete the bvecq at the front if possible + * @pos: The position object to update. + * + * Delete the used up bvecq at the front of the queue that @pos points to = if it + * is not the last node in the queue; if it is the last node in the queue,= it + * is kept so that the queue doesn't become detached from the other end. = This + * may manipulate refs on the bvecqs pointed to. It is also possible that= the + * producer will fill more slots in the current bvecq. + * + * Also, we have to be very careful: the consumer can catch the producer, = which + * could lead to us having nothing left in the queue, causing the front and + * back pointers to end up on different tracks. To avoid this, we must al= ways + * keep at least one segment in the queue. + * + * The caller must reload from @pos after calling this. + * + * Return: true if there's more available; false if not. + */ +static inline bool bvecq_delete_spent(struct bvecq_pos *pos) +{ + struct bvecq *spent =3D pos->bvecq; + struct bvecq *next; + unsigned int slot =3D pos->slot; + +again: + /* Read the contents of the queue node after the pointer to it. */ + next =3D bvecq_next(spent); + if (!next) + return false; /* Nothing more to consume at the moment. */ + if (slot < bvecq_nr_slots_acquire(spent)) + return true; /* The producer added more. */ + next->prev =3D NULL; + bvecq_pos_move(pos, next); + pos->slot =3D 0; + pos->offset =3D 0; + if (!bvecq_acquire_slot(next, 0)) { + spent =3D next; + slot =3D 0; + goto again; + } + return true; +} + #endif /* _LINUX_BVECQ_H */ diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 16cc0858c613..c0eb80e49f5c 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include =20 diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 0202ba4ccbd0..111c02817bea 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -809,6 +809,30 @@ TRACE_EVENT(netfs_read_progress_at, __entry->rreq, __entry->cleaned_to, __entry->progress_at) ); =20 +TRACE_EVENT(netfs_bv_slot, + TP_PROTO(const struct bvecq *bq, int slot), + + TP_ARGS(bq, slot), + + TP_STRUCT__entry( + __field(unsigned long, pfn) + __field(unsigned int, offset) + __field(unsigned int, len) + __field(unsigned int, slot) + ), + + TP_fast_assign( + __entry->slot =3D slot; + __entry->pfn =3D page_to_pfn(bq->bv[slot].bv_page); + __entry->offset =3D bq->bv[slot].bv_offset; + __entry->len =3D bq->bv[slot].bv_len; + ), + + TP_printk("bq[%x] p=3D%lx %x-%x", + __entry->slot, + __entry->pfn, __entry->offset, __entry->offset + __entry->len) + ); + #undef EM #undef E_ #endif /* _TRACE_NETFS_H */ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A1C2C43E06C for ; Mon, 24 Aug 2026 14:43:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582627; cv=none; b=faUREFZcaTlQlP4bdSVtxMQ4e5DywRD9P10mdR8F+VvLVov2MlTAQbG7zWAUQhUtpZp3E3kllzwI613SNM2Gk6DjXNy81DZA4d22T6YC6w5TPy19ZaN2mxCehIqk2Qq5vOu8r/cebMIPVen2APZWgT+5P33jo8rmrSDzu++xbz4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582627; c=relaxed/simple; bh=Te/GAbQS51/6gQqRKCrP2cga70qQouyzEnJGX1O9MYg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PtLNUmD9nFX0ELOUyS6PgeOyObK/s8XjRkmrUsuniadL9REebi9/G4asL/2JwughqOXpoPHaAvQ6EVPFQx0QPyRv0H7vXgQPWWwFoZy/dE8HwvnrWJPRIB0Cg9wBVpG/ylqtVS/4DiVN0C9c/6k+sCiOrHtBys9BuG5hcl0HE/U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Cu1A2U7/; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Cu1A2U7/" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582614; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=mAcMtWnodpY2/7YJsnesasXtJNGD58hwio/z27wR72E=; b=Cu1A2U7/vRtSjw4qJ4PsVpABwELL8P6601PiDQqsjnj0IIHWmwG4AW1KIdp/l8AMOrJEsg QRUL/6VW7EkEYhmTbT2V6fvONAvZiKXd4xFDt5rXkCGHXTUS8TDPqfpiuzdtRFKhk01TMn hUCI5/Ko8whVHqfZAJolubm9J13W7vQ= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-610-MYj2p4IGMJyVwFx8awMGow-1; Mon, 24 Aug 2026 10:43:30 -0400 X-MC-Unique: MYj2p4IGMJyVwFx8awMGow-1 X-Mimecast-MFC-AGG-ID: MYj2p4IGMJyVwFx8awMGow_1787582607 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F04E2185FE77; Mon, 24 Aug 2026 14:43:26 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id D2B681955D6D; Mon, 24 Aug 2026 14:43:20 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 12/35] netfs: Make mempool available for bvecq Date: Mon, 24 Aug 2026 15:41:05 +0100 Message-ID: <20260824144130.759997-13-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" Make a mempool available for allocating bvecq structs. Use it automatically if anything other than GFP_KERNEL (with GFP_ZONEMASK flags masked off) is specified. Reading from a file can use GFP_KERNEL as the failure mode is straightforward and the same for DIO reads and writes. When it comes to writeback, however, the writeback_iter() API does not permit temporary failure, including ENOMEM, if WB_SYNC_ALL is set and the caller must process all folios to completion. (I'm not sure if EINTR constitutes an acceptable failure). Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/bvecq.c | 39 +++++++++++++++++++++++++++++++++------ fs/netfs/internal.h | 1 + fs/netfs/main.c | 7 +++++++ include/linux/bvecq.h | 3 +++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/fs/netfs/bvecq.c b/fs/netfs/bvecq.c index 6905c84ea351..7b0aedafeda9 100644 --- a/fs/netfs/bvecq.c +++ b/fs/netfs/bvecq.c @@ -44,8 +44,9 @@ EXPORT_SYMBOL(bvecq_dump); * * Allocate a single bvecq node and initialise the header. A number of in= line * slots are also allocated, rounded up to fit after the header in a power= -of-2 - * slab object of up to 512 bytes (up to 29 slots on a 64-bit cpu). The s= lot - * array is not initialised. + * slab object of up to 512 bytes (up to 29 slots on a 64-bit cpu). The c= aller + * should be aware that the number of slots allocated may be more or less = than + * the number requested. The slot array is not initialised. * * Return: The node pointer or NULL on allocation failure. */ @@ -56,16 +57,39 @@ struct bvecq *bvecq_alloc_one(size_t nr_slots, gfp_t gf= p, bool for_writeback) const size_t max_slots =3D (max_size - sizeof(*bq)) / sizeof(bq->__bv[0]); size_t part =3D min(nr_slots, max_slots); size_t size =3D roundup_pow_of_two(struct_size(bq, __bv, part)); + bool from_pool =3D false; =20 - bq =3D kmalloc(size, gfp & ~GFP_ZONEMASK); - if (!bq) - return bq; + gfp &=3D ~(GFP_ZONEMASK | __GFP_THISNODE); + + if (for_writeback) { + if (size !=3D BVECQ_STD_SIZE) { + gfp_t gfp_temp =3D gfp; =20 + gfp_temp |=3D __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN; + gfp_temp &=3D ~(__GFP_DIRECT_RECLAIM | __GFP_IO); + bq =3D kmalloc(size, gfp_temp); + if (bq) + goto success; + } + + bq =3D mempool_alloc(&netfs_bvecq_pool, gfp); + if (!bq) + return bq; + from_pool =3D true; + size =3D BVECQ_STD_SIZE; + } else { + bq =3D kmalloc(size, gfp); + if (!bq) + return bq; + } + +success: *bq =3D (struct bvecq) { .ref =3D REFCOUNT_INIT(1), .bv =3D bq->__bv, .inline_bv =3D true, .max_slots =3D (size - sizeof(*bq)) / sizeof(bq->__bv[0]), + .from_pool =3D from_pool, }; netfs_stat(&netfs_n_bvecq); return bq; @@ -245,7 +269,10 @@ void bvecq_put(struct bvecq *bq) bvecq_free_slot(bq, slot); next =3D bq->next; netfs_stat_d(&netfs_n_bvecq); - kfree(bq); + if (bq->from_pool) + mempool_free(bq, &netfs_bvecq_pool); + else + kfree(bq); } } EXPORT_SYMBOL(bvecq_put); diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 8db2c82004a4..431ec77b1305 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -43,6 +43,7 @@ extern struct list_head netfs_io_requests; extern spinlock_t netfs_proc_lock; extern mempool_t netfs_request_pool; extern mempool_t netfs_subrequest_pool; +extern mempool_t netfs_bvecq_pool; extern mempool_t netfs_folioq_pool; =20 #ifdef CONFIG_PROC_FS diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 927badf3989d..9f72e5054aff 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -28,6 +28,7 @@ static struct kmem_cache *netfs_request_slab; static struct kmem_cache *netfs_subrequest_slab; mempool_t netfs_request_pool; mempool_t netfs_subrequest_pool; +mempool_t netfs_bvecq_pool; mempool_t netfs_folioq_pool; =20 #ifdef CONFIG_PROC_FS @@ -112,6 +113,9 @@ static int __init netfs_init(void) if (mempool_init_kmalloc_pool(&netfs_folioq_pool, 100, sizeof(struct foli= o_queue)) < 0) goto error_folioq_pool; =20 + if (mempool_init_kmalloc_pool(&netfs_bvecq_pool, 100, BVECQ_STD_SIZE) < 0) + goto error_bvecq_pool; + netfs_request_slab =3D kmem_cache_create("netfs_request", sizeof(struct netfs_io_request), 0, SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT, @@ -164,6 +168,8 @@ static int __init netfs_init(void) error_reqpool: kmem_cache_destroy(netfs_request_slab); error_req: + mempool_exit(&netfs_bvecq_pool); +error_bvecq_pool: mempool_exit(&netfs_folioq_pool); error_folioq_pool: return ret; @@ -178,6 +184,7 @@ static void __exit netfs_exit(void) kmem_cache_destroy(netfs_subrequest_slab); mempool_exit(&netfs_request_pool); kmem_cache_destroy(netfs_request_slab); + mempool_exit(&netfs_bvecq_pool); mempool_exit(&netfs_folioq_pool); } module_exit(netfs_exit); diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h index 22fc7995f3ee..8adfdd43b865 100644 --- a/include/linux/bvecq.h +++ b/include/linux/bvecq.h @@ -43,15 +43,18 @@ struct bvecq { u16 max_slots; /* Number of elements allocated in bv[] */ enum bvecq_mem mem_type:3; /* What sort of memory and how to free it */ bool inline_bv:1; /* T if __bv[] is being used */ + bool from_pool:1; /* T if bvecq from mempool */ struct bio_vec *bv; /* Pointer to array of page fragments */ struct bio_vec __bv[]; /* Default array (if ->inline_bv) */ }; =20 #if BITS_PER_LONG =3D=3D 64 /* Number of slots in __bv[] for a bvecq in a 512-byte kmalloc block. */ +#define BVECQ_STD_SIZE 512 #define BVECQ_STD_SLOTS 29 /* 2 words/slot; 32 slots; bvecq is 6 words (3= slots) */ #elif BITS_PER_LONG =3D=3D 32 /* Number of slots in __bv[] for a bvecq in a 256-byte kmalloc block. */ +#define BVECQ_STD_SIZE 256 #define BVECQ_STD_SLOTS 18 /* 3 words/slot; 21 slots; bvecq is 9 words (3= slots) */ #else #error BVECQ_STD_SLOTS undetermined From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E338C444717 for ; Mon, 24 Aug 2026 14:43:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582632; cv=none; b=WTsGBecgnE+U7M91KuNaEJCjV0Sup78sDHVVn4DQSOKQ/ZpRu/tK+VWqMKsw4xatjlJGaFqt58uT8cVHhndkEgWxZ7ptAlr9YOQ4NYVsLymKMIM4ysz2lHWz3sUUXVuhqCSssepepjXBhV5IbkMYBMh+0bJz3+svIyC4f5Pr+2U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582632; c=relaxed/simple; bh=6xHWjsY1qDBoefXHrGBW8eWcOhqLBMGbASXLWlznntE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=J3KtxC7bqAsLrh0KDV62/PW7EgMz99S487Sf+JPnMewLexBtQaWZO7eZmfc3xMun1IHFU2vMjbaxx4bax/hOe80Ig/nQU8d6EHbBdl7oxTIjxNo+laqur3quxH1bKlCa+kTbHKcN9EcYN2k8Bll8BwUA4/5vxm+YViVGudQnFMU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=XZx8fdiA; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="XZx8fdiA" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582624; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=5t4/4PShxGf/yJwbsS+IUPK4M6t7DCNTTfTNINUpLpc=; b=XZx8fdiAkus/9c+SEsnMIKPVPECuvkLkfcNkI/li1fm45z6YYnBtAfxSwaRJUbUXjmQxNl B4ZnuR5XI8Q5LxlZraFDkoqXCCY+1NgmLTfPvHm4wOr29yrzJ6Zk1fheoxSaUBiT+4J+U6 IbXiZNPI6CrLQnLyUE70mHmNPnMSods= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-26-t_D96OY9MSi7wj8RCirzKA-1; Mon, 24 Aug 2026 10:43:38 -0400 X-MC-Unique: t_D96OY9MSi7wj8RCirzKA-1 X-Mimecast-MFC-AGG-ID: t_D96OY9MSi7wj8RCirzKA_1787582614 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C6A4E18303C5; Mon, 24 Aug 2026 14:43:34 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id A2D341955DAA; Mon, 24 Aug 2026 14:43:28 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 13/35] netfs: Add a function to extract from an iter into a bvecq Date: Mon, 24 Aug 2026 15:41:06 +0100 Message-ID: <20260824144130.759997-14-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" Add a function to extract a slice of data from an iterator of any type into a bvec queue chain. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/iterator.c | 143 ++++++++++++++++++++++++++++++++++++++++++ include/linux/netfs.h | 3 + 2 files changed, 146 insertions(+) diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c index eb1efb17f53a..e66c589ca3b0 100644 --- a/fs/netfs/iterator.c +++ b/fs/netfs/iterator.c @@ -13,6 +13,149 @@ #include #include "internal.h" =20 +/** + * netfs_extract_iter - Extract virtually contiguous pages from an iterato= r into a bvecq + * @orig: The original iterator + * @max_len: Maximum number of bytes to extract + * @max_pages: Maximum number of pages to extract + * @_bvecq_head: Where to cache the bvec queue + * @extraction_flags: Flags to qualify the request + * @gfp: Allocation mode for bvecq structs. + * + * Extract virtually contiguous page fragments from the source iterator up= to + * the given maxima and build bvec queue that refers to all of those bits. + * This allows the original iterator to disposed of. + * + * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-pee= r DMA be + * allowed on the pages extracted. + * + * On success or partial success, the amount of data in the bvec is return= ed, + * the original iterator will have been advanced by the amount extracted. + * + * If an error occurs and no pages are extracted, an error will be returne= d and + * any allocated bvecq will be freed. If there is no data to be extracted= (or + * @max_len or @max_pages are zero), a single empty bvecq will be returned. + * + * The bvecq segments are marked with indications on how to get clean up t= he + * extracted fragments. + */ +ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t m= ax_pages, + struct bvecq **_bvecq_head, + iov_iter_extraction_t extraction_flags, gfp_t gfp) +{ + struct bvecq *bq_tail =3D NULL, *bq; + ssize_t ret =3D 0; + size_t extracted =3D 0; + + _enter("{%u,%zx},%zx", orig->iter_type, orig->count, max_len); + + *_bvecq_head =3D NULL; + if (max_len > orig->count) + max_len =3D orig->count; + if (!max_len || !max_pages) + goto alloc_empty; + if (WARN_ON_ONCE(max_pages > INT_MAX)) + max_pages =3D INT_MAX; /* Protect iov_iter_npages(). */ + + max_pages =3D iov_iter_npages(orig, max_pages); + if (!max_pages) + goto alloc_empty; + + do { + bq =3D bvecq_alloc_one(max_pages, gfp, false); + if (!bq) { + ret =3D -ENOMEM; + break; + } + if (user_backed_iter(orig)) + bq->mem_type =3D iov_iter_extract_will_pin(orig) ? + BVECQ_MEM_GUP : BVECQ_MEM_PAGECACHE; + + if (bq_tail) + bvecq_append(bq_tail, bq); + else + *_bvecq_head =3D bq; + bq_tail =3D bq; + + if (max_len =3D=3D 0) + break; + + struct bio_vec *bv =3D bq->bv; + unsigned int slot =3D 0; + do { + struct page **pages; + ssize_t got; + size_t offset; + size_t space =3D bq->max_slots - slot; + size_t bv_size =3D array_size(bq->max_slots, sizeof(*bv)); + size_t pg_size =3D array_size(space, sizeof(*pages)); + + /* Put the page list at the end of the bvec list + * storage. bvec elements are larger than page + * pointers, so as long as we work 0->last, we should + * be fine. + */ + pages =3D (void *)bv + bv_size - pg_size; + + got =3D iov_iter_extract_pages(orig, &pages, max_len, + min(space, max_pages), + extraction_flags, &offset); + if (got < 0) { + ret =3D got; + goto out; + } + + if (got =3D=3D 0) { + pr_err("extract_pages gave nothing from %zx, %zx\n", + extracted, max_len); + ret =3D -EIO; + goto out; + } + + if (WARN(got > max_len, + "%s: extract_pages overrun %zx > %zx bytes\n", + __func__, got, max_len)) { + ret =3D -EIO; + goto out; + } + + extracted +=3D got; + max_len -=3D got; + + do { + size_t len =3D umin(got, PAGE_SIZE - offset); + + BUG_ON(slot >=3D bq->max_slots); + + bvec_set_page(&bq->bv[slot], *pages++, len, offset); + slot++; + max_pages--; + got -=3D len; + offset =3D 0; + } while (got > 0); + + bvecq_filled_to(bq, slot); + } while (max_len > 0 && max_pages > 0 && !bvecq_is_full(bq)); + + } while (max_len > 0 && max_pages > 0); + +out: + if (extracted || ret =3D=3D 0) + return extracted; + bvecq_put(*_bvecq_head); + *_bvecq_head =3D NULL; + return ret; + +alloc_empty: + bq =3D bvecq_alloc_one(1, gfp, false); + if (!bq) + return -ENOMEM; + *_bvecq_head =3D bq; + return 0; + +} +EXPORT_SYMBOL_GPL(netfs_extract_iter); + /** * netfs_extract_user_iter - Extract the pages from a user iterator into a= bvec * @orig: The original iterator diff --git a/include/linux/netfs.h b/include/linux/netfs.h index c0eb80e49f5c..14a24bb80af5 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -431,6 +431,9 @@ void netfs_get_subrequest(struct netfs_io_subrequest *s= ubreq, enum netfs_sreq_ref_trace what); void netfs_put_subrequest(struct netfs_io_subrequest *subreq, enum netfs_sreq_ref_trace what); +ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t m= ax_pages, + struct bvecq **_bvecq_head, + iov_iter_extraction_t extraction_flags, gfp_t gfp); ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len, struct iov_iter *new, iov_iter_extraction_t extraction_flags); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1E36F43F084 for ; Mon, 24 Aug 2026 14:43:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582647; cv=none; b=TmJIep+Pv6TR3tosCChM7+tYuLJOFnBHjXRO8ZakcazLTT+8ntxsDXd47PUunKkLnvHgZJrfm2WpnBhhbWY2LpZwO0C82gAOztgCpp26uHHfmRV7zwUisSFRGH46VWAo9qtkpaq+LCD2pCA62ny382MeyIHSap1RnKkg5i4Y2cw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582647; c=relaxed/simple; bh=H34odt7B4p0KPHEqVN5x7uupLXLLPBr4y4lM5E/qvMg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=luQhBUsW+pYfg4SvQ1+hW1IiP6LOwkVNCTkLuXPNM6LMBmeTB/7Q6qPEWTqbYoy4GFsyFlKF/JpS+v9jJU619d536qFDDNhGytTlb7DVLWiL/n/SpTcGaR+YlG+6oKpo3JlIMIh8awUjYCcDYY7mbouxvkqgPEdoRw7w7XCp2lc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=D3nZbblN; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="D3nZbblN" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582633; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=0dDrAC3aDIFeWk8EdwLQ01eWxbOmeq651U6qLheHER0=; b=D3nZbblNnpTECloDoxXh5ksfCTM0Bgk1cfLGrDmgPTyyaQ4dJ+oY9Kbr8e8ZJmiBOGjeEi pghhgTDy94eVX5fR4kFzdh+TAGUtm6A/MlKGfii03C3cF/hwuM983ZjyLndtp71xMhZ9HX mrkkSPRpbbyDAA+nEX5QrMqcRMsBIGM= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-298--V87FTobOWmr111UmNXocw-1; Mon, 24 Aug 2026 10:43:49 -0400 X-MC-Unique: -V87FTobOWmr111UmNXocw-1 X-Mimecast-MFC-AGG-ID: -V87FTobOWmr111UmNXocw_1787582623 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id CD5691955BDB; Mon, 24 Aug 2026 14:43:42 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 6A1E230002ED; Mon, 24 Aug 2026 14:43:36 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 14/35] afs: Use a bvecq to hold dir content rather than folioq Date: Mon, 24 Aug 2026 15:41:07 +0100 Message-ID: <20260824144130.759997-15-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Use a bvecq to hold the contents of a directory rather than the folioq so that the latter can be phased out. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: Marc Dionne cc: linux-afs@lists.infradead.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/afs/dir.c | 35 +++++---- fs/afs/dir_edit.c | 42 +++++------ fs/afs/dir_search.c | 33 ++++----- fs/afs/inode.c | 2 +- fs/afs/internal.h | 6 +- fs/afs/symlink.c | 34 ++++----- fs/netfs/write_issue.c | 163 ++++++----------------------------------- 7 files changed, 93 insertions(+), 222 deletions(-) diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 6df56fe9163f..1dff50323b56 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -140,9 +140,9 @@ static void afs_dir_dump(struct afs_vnode *dvnode) pr_warn("DIR %llx:%llx is=3D%llx\n", dvnode->fid.vid, dvnode->fid.vnode, i_size); =20 - iov_iter_folio_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, i_size); - iterate_folioq(&iter, iov_iter_count(&iter), NULL, NULL, - afs_dir_dump_step); + iov_iter_bvec_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, i_size); + iterate_bvecq(&iter, iov_iter_count(&iter), NULL, NULL, + afs_dir_dump_step); } =20 /* @@ -203,9 +203,9 @@ static int afs_dir_check(struct afs_vnode *dvnode) if (unlikely(!i_size)) return 0; =20 - iov_iter_folio_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, i_size); - checked =3D iterate_folioq(&iter, iov_iter_count(&iter), dvnode, NULL, - afs_dir_check_step); + iov_iter_bvec_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, i_size); + checked =3D iterate_bvecq(&iter, iov_iter_count(&iter), dvnode, NULL, + afs_dir_check_step); if (checked !=3D i_size) { afs_dir_dump(dvnode); return -EIO; @@ -250,15 +250,14 @@ static ssize_t afs_do_read_single(struct afs_vnode *d= vnode, struct file *file) if (dvnode->directory_size < i_size) { size_t cur_size =3D dvnode->directory_size; =20 - ret =3D netfs_alloc_folioq_buffer(NULL, - &dvnode->directory, &cur_size, i_size, - mapping_gfp_mask(dvnode->netfs.inode.i_mapping)); + ret =3D bvecq_expand_buffer(&dvnode->directory, &cur_size, + round_up(i_size, PAGE_SIZE), GFP_KERNEL); dvnode->directory_size =3D cur_size; if (ret < 0) return ret; } =20 - iov_iter_folio_queue(&iter, ITER_DEST, dvnode->directory, 0, 0, dvnode->d= irectory_size); + iov_iter_bvec_queue(&iter, ITER_DEST, dvnode->directory, 0, 0, dvnode->di= rectory_size); =20 /* AFS requires us to perform the read of a directory synchronously as * a single unit to avoid issues with the directory contents being @@ -294,8 +293,8 @@ static ssize_t afs_read_single(struct afs_vnode *dvnode= , struct file *file) } =20 /* - * Read the directory into a folio_queue buffer in one go, scrubbing the - * previous contents. We return -ESTALE if the caller needs to call us ag= ain. + * Read the directory into the buffer in one go, scrubbing the previous + * contents. We return -ESTALE if the caller needs to call us again. */ ssize_t afs_read_dir(struct afs_vnode *dvnode, struct file *file) __acquires(&dvnode->validate_lock) @@ -483,7 +482,7 @@ static size_t afs_dir_iterate_step(void *iter_base, siz= e_t progress, size_t len, } =20 /* - * Iterate through the directory folios. + * Iterate through the directory content. */ static int afs_dir_iterate_contents(struct inode *dir, struct dir_context = *dir_ctx) { @@ -498,11 +497,11 @@ static int afs_dir_iterate_contents(struct inode *dir= , struct dir_context *dir_c if (i_size <=3D 0 || dir_ctx->pos >=3D i_size) return 0; =20 - iov_iter_folio_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, i_size); + iov_iter_bvec_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, i_size); iov_iter_advance(&iter, round_down(dir_ctx->pos, AFS_DIR_BLOCK_SIZE)); =20 - iterate_folioq(&iter, iov_iter_count(&iter), dvnode, &ctx, - afs_dir_iterate_step); + iterate_bvecq(&iter, iov_iter_count(&iter), dvnode, &ctx, + afs_dir_iterate_step); =20 if (ctx.error =3D=3D -ESTALE) afs_invalidate_dir(dvnode, afs_dir_invalid_iter_stale); @@ -2228,8 +2227,8 @@ static int afs_dir_writepages(struct address_space *m= apping, } =20 if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) { - iov_iter_folio_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, - i_size_read(&dvnode->netfs.inode)); + iov_iter_bvec_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, + i_size_read(&dvnode->netfs.inode)); ret =3D netfs_writeback_single(mapping, wbc, &iter); if (ret =3D=3D 1) ret =3D 0; /* Skipped write due to lock conflict. */ diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c index 3ead36a07048..01c6265e8865 100644 --- a/fs/afs/dir_edit.c +++ b/fs/afs/dir_edit.c @@ -110,9 +110,8 @@ static void afs_clear_contig_bits(union afs_xdr_dir_blo= ck *block, */ static union afs_xdr_dir_block *afs_dir_get_block(struct afs_dir_iter *ite= r, size_t block) { - struct folio_queue *fq; struct afs_vnode *dvnode =3D iter->dvnode; - struct folio *folio; + struct bvecq *bq; size_t blpos =3D block * AFS_DIR_BLOCK_SIZE; size_t blend =3D (block + 1) * AFS_DIR_BLOCK_SIZE, fpos =3D iter->fpos; int ret; @@ -120,41 +119,38 @@ static union afs_xdr_dir_block *afs_dir_get_block(str= uct afs_dir_iter *iter, siz if (dvnode->directory_size < blend) { size_t cur_size =3D dvnode->directory_size; =20 - ret =3D netfs_alloc_folioq_buffer( - NULL, &dvnode->directory, &cur_size, blend, - mapping_gfp_mask(dvnode->netfs.inode.i_mapping)); + ret =3D bvecq_expand_buffer(&dvnode->directory, &cur_size, blend, + GFP_KERNEL); dvnode->directory_size =3D cur_size; if (ret < 0) goto fail; } =20 - fq =3D iter->fq; - if (!fq) - fq =3D dvnode->directory; + bq =3D iter->bq; + if (!bq) + bq =3D dvnode->directory; =20 - /* Search the folio queue for the folio containing the block... */ - for (; fq; fq =3D fq->next) { - for (int s =3D iter->fq_slot; s < folioq_count(fq); s++) { - size_t fsize =3D folioq_folio_size(fq, s); + /* Search the contents for the region containing the block... */ + for (; bq; bq =3D bq->next) { + for (int s =3D iter->bq_slot; s < bq->nr_slots; s++) { + struct bio_vec *bv =3D &bq->bv[s]; + size_t bsize =3D bv->bv_len; =20 - if (blend <=3D fpos + fsize) { + if (blend <=3D fpos + bsize) { /* ... and then return the mapped block. */ - folio =3D folioq_folio(fq, s); - if (WARN_ON_ONCE(folio_pos(folio) !=3D fpos)) - goto fail; - iter->fq =3D fq; - iter->fq_slot =3D s; + iter->bq =3D bq; + iter->bq_slot =3D s; iter->fpos =3D fpos; - return kmap_local_folio(folio, blpos - fpos); + return bvec_kmap_partial(bv, blpos - fpos); } - fpos +=3D fsize; + fpos +=3D bsize; } - iter->fq_slot =3D 0; + iter->bq_slot =3D 0; } =20 fail: - iter->fq =3D NULL; - iter->fq_slot =3D 0; + iter->bq =3D NULL; + iter->bq_slot =3D 0; afs_invalidate_dir(dvnode, afs_dir_invalid_edit_get_block); return NULL; } diff --git a/fs/afs/dir_search.c b/fs/afs/dir_search.c index 104411c0692f..01751c680115 100644 --- a/fs/afs/dir_search.c +++ b/fs/afs/dir_search.c @@ -66,12 +66,11 @@ bool afs_dir_init_iter(struct afs_dir_iter *iter, const= struct qstr *name) */ union afs_xdr_dir_block *afs_dir_find_block(struct afs_dir_iter *iter, siz= e_t block) { - struct folio_queue *fq =3D iter->fq; struct afs_vnode *dvnode =3D iter->dvnode; - struct folio *folio; + struct bvecq *bq =3D iter->bq; size_t blpos =3D block * AFS_DIR_BLOCK_SIZE; size_t blend =3D (block + 1) * AFS_DIR_BLOCK_SIZE, fpos =3D iter->fpos; - int slot =3D iter->fq_slot; + int slot =3D iter->bq_slot; =20 _enter("%zx,%d", block, slot); =20 @@ -83,36 +82,34 @@ union afs_xdr_dir_block *afs_dir_find_block(struct afs_= dir_iter *iter, size_t bl if (dvnode->directory_size < blend) goto fail; =20 - if (!fq || blpos < fpos) { - fq =3D dvnode->directory; + if (!bq || blpos < fpos) { + bq =3D dvnode->directory; slot =3D 0; fpos =3D 0; } =20 /* Search the folio queue for the folio containing the block... */ - for (; fq; fq =3D fq->next) { - for (; slot < folioq_count(fq); slot++) { - size_t fsize =3D folioq_folio_size(fq, slot); + for (; bq; bq =3D bq->next) { + for (; slot < bq->nr_slots; slot++) { + struct bio_vec *bv =3D &bq->bv[slot]; + size_t bsize =3D bv->bv_len; =20 - if (blend <=3D fpos + fsize) { + if (blend <=3D fpos + bsize) { /* ... and then return the mapped block. */ - folio =3D folioq_folio(fq, slot); - if (WARN_ON_ONCE(folio_pos(folio) !=3D fpos)) - goto fail; - iter->fq =3D fq; - iter->fq_slot =3D slot; + iter->bq =3D bq; + iter->bq_slot =3D slot; iter->fpos =3D fpos; - iter->block =3D kmap_local_folio(folio, blpos - fpos); + iter->block =3D bvec_kmap_partial(bv, blpos - fpos); return iter->block; } - fpos +=3D fsize; + fpos +=3D bsize; } slot =3D 0; } =20 fail: - iter->fq =3D NULL; - iter->fq_slot =3D 0; + iter->bq =3D NULL; + iter->bq_slot =3D 0; afs_invalidate_dir(dvnode, afs_dir_invalid_edit_get_block); return NULL; } diff --git a/fs/afs/inode.c b/fs/afs/inode.c index 14f39a9bea6c..634fbf8eb212 100644 --- a/fs/afs/inode.c +++ b/fs/afs/inode.c @@ -683,7 +683,7 @@ void afs_evict_inode(struct inode *inode) flush_delayed_work(&vnode->lock_work); netfs_wait_for_outstanding_io(inode); truncate_inode_pages_final(&inode->i_data); - netfs_free_folioq_buffer(vnode->directory); + bvecq_put(vnode->directory); if (vnode->symlink) afs_evict_symlink(vnode); =20 diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 290873bac89b..556e7e7777f2 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -710,7 +710,7 @@ struct afs_vnode { #define AFS_VNODE_MODIFYING 10 /* Set if we're performing a modification = op */ #define AFS_VNODE_DIR_READ 11 /* Set if we've read a dir's contents */ =20 - struct folio_queue *directory; /* Directory contents */ + struct bvecq *directory; /* Directory contents */ struct afs_symlink __rcu *symlink; /* Symlink content */ struct list_head wb_keys; /* List of keys available for writeback */ struct list_head pending_locks; /* locks waiting to be granted */ @@ -991,9 +991,9 @@ static inline void afs_invalidate_cache(struct afs_vnod= e *vnode, unsigned int fl struct afs_dir_iter { struct afs_vnode *dvnode; union afs_xdr_dir_block *block; - struct folio_queue *fq; + struct bvecq *bq; unsigned int fpos; - int fq_slot; + int bq_slot; unsigned int loop_check; u8 nr_slots; u8 bucket; diff --git a/fs/afs/symlink.c b/fs/afs/symlink.c index 16b4823cb7b7..7fd4f985c441 100644 --- a/fs/afs/symlink.c +++ b/fs/afs/symlink.c @@ -56,7 +56,6 @@ void afs_evict_symlink(struct afs_vnode *vnode) void afs_init_new_symlink(struct afs_vnode *vnode, struct afs_operation *o= p) { struct afs_symlink *symlink =3D op->create.symlink; - size_t dsize =3D 0; size_t size =3D strlen(symlink->content) + 1; char *p; =20 @@ -66,13 +65,15 @@ void afs_init_new_symlink(struct afs_vnode *vnode, stru= ct afs_operation *op) if (!fscache_cookie_enabled(netfs_i_cookie(&vnode->netfs))) return; =20 - if (netfs_alloc_folioq_buffer(NULL, &vnode->directory, &dsize, size, - mapping_gfp_mask(vnode->netfs.inode.i_mapping)) < 0) + vnode->directory =3D bvecq_alloc_buffer(PAGE_SIZE, GFP_KERNEL, false); + if (!vnode->directory) return; =20 - vnode->directory_size =3D dsize; - p =3D kmap_local_folio(folioq_folio(vnode->directory, 0), 0); + vnode->directory_size =3D size; + p =3D bvec_kmap_partial(&vnode->directory->bv[0], 0); memcpy(p, symlink->content, size); + if (size < PAGE_SIZE) + memset(p + size, 0, PAGE_SIZE - size); kunmap_local(p); netfs_single_mark_inode_dirty(&vnode->netfs.inode); } @@ -94,17 +95,12 @@ static ssize_t afs_do_read_symlink(struct afs_vnode *vn= ode) } =20 if (!vnode->directory) { - size_t cur_size =3D 0; - - ret =3D netfs_alloc_folioq_buffer(NULL, - &vnode->directory, &cur_size, PAGE_SIZE, - mapping_gfp_mask(vnode->netfs.inode.i_mapping)); - vnode->directory_size =3D PAGE_SIZE - 1; - if (ret < 0) - return ret; + vnode->directory =3D bvecq_alloc_buffer(PAGE_SIZE, GFP_KERNEL, false); + if (!vnode->directory) + return -ENOMEM; } =20 - iov_iter_folio_queue(&iter, ITER_DEST, vnode->directory, 0, 0, PAGE_SIZE); + iov_iter_bvec_queue(&iter, ITER_DEST, vnode->directory, 0, 0, PAGE_SIZE); =20 /* AFS requires us to perform the read of a symlink as a single unit to * avoid issues with the content being changed between reads. @@ -127,7 +123,7 @@ static ssize_t afs_do_read_symlink(struct afs_vnode *vn= ode) refcount_set(&symlink->ref, 1); symlink->content[i_size] =3D 0; =20 - const char *s =3D kmap_local_folio(folioq_folio(vnode->directory, 0), 0); + const char *s =3D bvec_kmap_partial(&vnode->directory->bv[0], 0); =20 memcpy(symlink->content, s, i_size); kunmap_local(s); @@ -136,7 +132,7 @@ static ssize_t afs_do_read_symlink(struct afs_vnode *vn= ode) } =20 if (!fscache_cookie_enabled(netfs_i_cookie(&vnode->netfs))) { - netfs_free_folioq_buffer(vnode->directory); + bvecq_put(vnode->directory); vnode->directory =3D NULL; vnode->directory_size =3D 0; } @@ -249,14 +245,14 @@ int afs_symlink_writepages(struct address_space *mapp= ing, =20 if (vnode->directory && atomic64_read(&vnode->cb_expires_at) !=3D AFS_NO_CB_PROMISE) { - iov_iter_folio_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0, - i_size_read(&vnode->netfs.inode)); + iov_iter_bvec_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0, + i_size_read(&vnode->netfs.inode)); ret =3D netfs_writeback_single(mapping, wbc, &iter); } =20 if (ret =3D=3D 0) { netfs_wb_begin(&vnode->netfs, false); - netfs_free_folioq_buffer(vnode->directory); + bvecq_put(vnode->directory); vnode->directory =3D NULL; vnode->directory_size =3D 0; netfs_wb_end(&vnode->netfs); diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 1a1f4efcaefd..d68d130d6da0 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -708,129 +708,11 @@ ssize_t netfs_end_writethrough(struct netfs_io_reque= st *wreq, struct writeback_c return ret; } =20 -/* - * Write some of a pending folio data back to the server and/or the cache. - */ -static int netfs_write_folio_single(struct netfs_io_request *wreq, - struct folio *folio) -{ - struct netfs_io_stream *upload =3D &wreq->io_streams[0]; - struct netfs_io_stream *cache =3D &wreq->io_streams[1]; - struct netfs_io_stream *stream; - size_t iter_off =3D 0; - size_t fsize =3D folio_size(folio), flen; - uoff_t fpos =3D folio_pos(folio); - ssize_t ret; - bool to_eof =3D false; - bool no_debug =3D false; - - _enter(""); - - flen =3D folio_size(folio); - if (flen > wreq->i_size - fpos) { - flen =3D wreq->i_size - fpos; - folio_zero_segment(folio, flen, fsize); - to_eof =3D true; - } else if (flen =3D=3D wreq->i_size - fpos) { - to_eof =3D true; - } - - _debug("folio %zx/%zx", flen, fsize); - - if (!upload->avail && !cache->avail) { - trace_netfs_folio(folio, netfs_folio_trace_cancel_store); - return 0; - } - - if (!upload->construct) - trace_netfs_folio(folio, netfs_folio_trace_store); - else - trace_netfs_folio(folio, netfs_folio_trace_store_plus); - - /* Attach the folio to the rolling buffer. */ - folio_get(folio); - ret =3D rolling_buffer_append(&wreq->buffer, folio, NETFS_ROLLBUF_PUT_MAR= K, wreq->gfp); - if (ret < 0) { - folio_put(folio); - return ret; - } - - /* Move the submission point forward to allow for write-streaming data - * not starting at the front of the page. We don't do write-streaming - * with the cache as the cache requires DIO alignment. - * - * Also skip uploading for data that's been read and just needs copying - * to the cache. - */ - for (int s =3D 0; s < NR_IO_STREAMS; s++) { - stream =3D &wreq->io_streams[s]; - stream->submit_off =3D 0; - stream->submit_len =3D flen; - if (!stream->avail) { - stream->submit_off =3D UINT_MAX; - stream->submit_len =3D 0; - } - } - - /* Attach the folio to one or more subrequests. For a big folio, we - * could end up with thousands of subrequests if the wsize is small - - * but we might need to wait during the creation of subrequests for - * network resources (eg. SMB credits). - */ - for (;;) { - ssize_t part; - size_t lowest_off =3D ULONG_MAX; - int choose_s =3D -1; - - /* Always add to the lowest-submitted stream first. */ - for (int s =3D 0; s < NR_IO_STREAMS; s++) { - stream =3D &wreq->io_streams[s]; - if (stream->submit_len > 0 && - stream->submit_off < lowest_off) { - lowest_off =3D stream->submit_off; - choose_s =3D s; - } - } - - if (choose_s < 0) - break; - stream =3D &wreq->io_streams[choose_s]; - - /* Advance the iterator(s). */ - if (stream->submit_off > iter_off) { - rolling_buffer_advance(&wreq->buffer, stream->submit_off - iter_off); - iter_off =3D stream->submit_off; - } - - atomic64_set(&wreq->issued_to, fpos + stream->submit_off); - stream->submit_extendable_to =3D fsize - stream->submit_off; - part =3D netfs_advance_write(wreq, stream, fpos + stream->submit_off, - stream->submit_len, to_eof); - stream->submit_off +=3D part; - if (part > stream->submit_len) - stream->submit_len =3D 0; - else - stream->submit_len -=3D part; - if (part > 0) - no_debug =3D true; - } - - wreq->buffer.iter.iov_offset =3D 0; - if (fsize > iter_off) - rolling_buffer_advance(&wreq->buffer, fsize - iter_off); - atomic64_set(&wreq->issued_to, fpos + fsize); - - if (!no_debug) - kdebug("R=3D%x: No submit", wreq->debug_id); - _leave(" =3D 0"); - return 0; -} - /** * netfs_writeback_single - Write back a monolithic payload * @mapping: The mapping to write from * @wbc: Hints from the VM - * @iter: Data to write, must be ITER_FOLIOQ. + * @iter: Data to write. * * Write a monolithic, non-pagecache object back to the server and/or * the cache. @@ -844,13 +726,8 @@ int netfs_writeback_single(struct address_space *mappi= ng, { struct netfs_io_request *wreq; struct netfs_inode *ictx =3D netfs_inode(mapping->host); - struct folio_queue *fq; - size_t size =3D iov_iter_count(iter); int ret; =20 - if (WARN_ON_ONCE(!iov_iter_is_folioq(iter))) - return -EIO; - if (!netfs_wb_begin(ictx, wbc->sync_mode =3D=3D WB_SYNC_NONE)) { /* The VFS will have undirtied the inode. */ netfs_single_mark_inode_dirty(&ictx->inode); @@ -863,37 +740,43 @@ int netfs_writeback_single(struct address_space *mapp= ing, goto couldnt_start; } =20 + wreq->buffer.iter =3D *iter; + wreq->len =3D iov_iter_count(iter); + __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); trace_netfs_write(wreq, netfs_write_trace_writeback_single); netfs_stat(&netfs_n_wh_writepages); =20 - if (__test_and_set_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags)) + if (test_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags)) wreq->netfs_ops->begin_writeback(wreq); =20 - for (fq =3D (struct folio_queue *)iter->folioq; fq; fq =3D fq->next) { - for (int slot =3D 0; slot < folioq_count(fq); slot++) { - struct folio *folio =3D folioq_folio(fq, slot); - size_t part =3D umin(folioq_folio_size(fq, slot), size); + for (int s =3D 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_subrequest *subreq; + struct netfs_io_stream *stream =3D &wreq->io_streams[s]; =20 - _debug("wbiter %lx %llx", folio->index, atomic64_read(&wreq->issued_to)= ); + if (!stream->avail) + continue; =20 - ret =3D netfs_write_folio_single(wreq, folio); - if (ret < 0) - goto stop; - size -=3D part; - if (size <=3D 0) - goto stop; - } + netfs_prepare_write(wreq, stream, 0); + + subreq =3D stream->construct; + subreq->len =3D wreq->len; + stream->submit_len =3D subreq->len; + stream->submit_extendable_to =3D round_up(wreq->len, PAGE_SIZE); + + netfs_issue_write(wreq, stream); } =20 -stop: - for (int s =3D 0; s < NR_IO_STREAMS; s++) - netfs_issue_write(wreq, &wreq->io_streams[s]); smp_wmb(); /* Write lists before ALL_QUEUED. */ set_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags); =20 netfs_wake_collector(wreq); =20 + /* TODO: Might want to be async here if WB_SYNC_NONE, but then need to + * wait before modifying. + */ + ret =3D netfs_wait_for_write(wreq); + netfs_put_request(wreq, netfs_rreq_trace_put_return); _leave(" =3D %d", ret); return ret; From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D76DC446BF6 for ; Mon, 24 Aug 2026 14:43:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582650; cv=none; b=gfJdtpK86/uAqfvR//FmRn9FAWQzwXb7CYSZVENAH0NvtInt43KoTufbOwbs0VtatATYOEiAAI+eg5EfW9ydgmj4IQYD2m7JzVYti+3qW+Z+KnsFzLHDF7rB0ITmhv26ec+HS3nx84XmTBjZ5wOTSpOOpXBVzVAjR6V3VISKwdg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582650; c=relaxed/simple; bh=gsraYPI5C7tMpVTMWThQNRCN+1v1Lwt8iNbkMhdYxQA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ir7qq7MrubOepUYnFTBin7nM3XBOZhvjL0YJtLqNISUZgB8zp6rixr5s4i1fkWsDnrSPAWbD9Yt7qTTe8cvGyPW1FH1NENhPuGj2oHhN6xxHOsLGe+IUlSF0hemGa8poJcF8nCvDcLdDD2st3l8bAYYhgjILZzUlqoG09Cxm+uE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=LJp+q65t; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="LJp+q65t" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582637; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=IzsTQKC5QvjxL4Q4aRJhCPzbX6SXFpRWfdA05sM6DVQ=; b=LJp+q65tuk5G7waw3i9Za5Ttjw2IlFVapEC3SL8RvWXDqcv8kHVaJ72DJYit1pls272TFZ Z3asYb4h73aqsqxzX4eFmm2JIqIqRtvOX9r20dTKIix27OAKWphePTc3nmZt1bAxZ3tK5w agc74PwyuJVQBhXURZnq8++/oN7EwtI= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-564-6joHOwpVNYuLninAKGIq9A-1; Mon, 24 Aug 2026 10:43:54 -0400 X-MC-Unique: 6joHOwpVNYuLninAKGIq9A-1 X-Mimecast-MFC-AGG-ID: 6joHOwpVNYuLninAKGIq9A_1787582630 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 80AF0195FCF5; Mon, 24 Aug 2026 14:43:50 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 699ED1955F09; Mon, 24 Aug 2026 14:43:44 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 15/35] cifs: Use a bvecq for buffering instead of a folioq Date: Mon, 24 Aug 2026 15:41:08 +0100 Message-ID: <20260824144130.759997-16-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" Use a bvecq for internal buffering for crypto purposes instead of a folioq so that the latter can be phased out. Signed-off-by: David Howells cc: Paulo Alcantara cc: Namjae Jeon cc: Matthew Wilcox cc: Christoph Hellwig cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/smb/client/cifsglob.h | 2 +- fs/smb/client/smb2ops.c | 76 +++++++++++++++++++--------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 79e4e84f8985..c8b1c23ef494 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -289,7 +289,7 @@ struct smb_rqst { struct kvec *rq_iov; /* array of kvecs */ unsigned int rq_nvec; /* number of kvecs in array */ struct iov_iter rq_iter; /* Data iterator */ - struct folio_queue *rq_buffer; /* Buffer for encryption */ + struct bvecq *rq_buffer; /* Buffer for encryption */ }; =20 struct mid_q_entry; diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 192649fec25d..885f0cc861ed 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4683,19 +4683,18 @@ crypt_message(struct TCP_Server_Info *server, int n= um_rqst, } =20 /* - * Copy data from an iterator to the folios in a folio queue buffer. + * Copy data from an iterator to the pages in a bvec queue buffer. */ -static bool cifs_copy_iter_to_folioq(struct iov_iter *iter, size_t size, - struct folio_queue *buffer) +static bool cifs_copy_iter_to_bvecq(struct iov_iter *iter, size_t size, + struct bvecq *buffer) { for (; buffer; buffer =3D buffer->next) { - for (int s =3D 0; s < folioq_count(buffer); s++) { - struct folio *folio =3D folioq_folio(buffer, s); - size_t part =3D folioq_folio_size(buffer, s); + for (int s =3D 0; s < buffer->nr_slots; s++) { + struct bio_vec *bv =3D &buffer->bv[s]; + size_t part =3D umin(bv->bv_len, size); =20 - part =3D umin(part, size); - - if (copy_folio_from_iter(folio, 0, part, iter) !=3D part) + if (copy_page_from_iter(bv->bv_page, bv->bv_offset, + part, iter) !=3D part) return false; size -=3D part; } @@ -4707,7 +4706,7 @@ void smb3_free_compound_rqst(int num_rqst, struct smb_rqst *rqst) { for (int i =3D 0; i < num_rqst; i++) - netfs_free_folioq_buffer(rqst[i].rq_buffer); + bvecq_put(rqst[i].rq_buffer); } =20 /* @@ -4734,7 +4733,7 @@ smb3_init_transform_rq(struct TCP_Server_Info *server= , int num_rqst, for (int i =3D 1; i < num_rqst; i++) { struct smb_rqst *old =3D &old_rq[i - 1]; struct smb_rqst *new =3D &new_rq[i]; - struct folio_queue *buffer =3D NULL; + struct bvecq *buffer =3D NULL; size_t size =3D iov_iter_count(&old->rq_iter); =20 orig_len +=3D smb_rqst_len(server, old); @@ -4742,17 +4741,16 @@ smb3_init_transform_rq(struct TCP_Server_Info *serv= er, int num_rqst, new->rq_nvec =3D old->rq_nvec; =20 if (size > 0) { - size_t cur_size =3D 0; - rc =3D netfs_alloc_folioq_buffer(NULL, &buffer, &cur_size, - size, GFP_NOFS); - new->rq_buffer =3D buffer; - if (rc < 0) + rc =3D -ENOMEM; + buffer =3D bvecq_alloc_buffer(size, GFP_NOFS, true); + if (!buffer) goto err_free; =20 - iov_iter_folio_queue(&new->rq_iter, ITER_SOURCE, - buffer, 0, 0, size); + new->rq_buffer =3D buffer; + iov_iter_bvec_queue(&new->rq_iter, ITER_SOURCE, + buffer, 0, 0, size); =20 - if (!cifs_copy_iter_to_folioq(&old->rq_iter, size, buffer)) { + if (!cifs_copy_iter_to_bvecq(&old->rq_iter, size, buffer)) { rc =3D smb_EIO1(smb_eio_trace_tx_copy_iter_to_buf, size); goto err_free; } @@ -4842,22 +4840,20 @@ decrypt_raw_data(struct TCP_Server_Info *server, ch= ar *buf, } =20 static int -cifs_copy_folioq_to_iter(struct folio_queue *folioq, size_t data_size, - size_t skip, struct iov_iter *iter) +cifs_copy_bvecq_to_iter(struct bvecq *bq, size_t data_size, + size_t skip, struct iov_iter *iter) { - for (; folioq; folioq =3D folioq->next) { - for (int s =3D 0; s < folioq_count(folioq); s++) { - struct folio *folio; - size_t fsize, n, len; + for (; bq; bq =3D bq->next) { + for (int s =3D 0; s < bq->nr_slots; s++) { + struct bio_vec *bv =3D &bq->bv[s]; + size_t n, len; =20 if (data_size =3D=3D 0) return 0; =20 - folio =3D folioq_folio(folioq, s); - fsize =3D folio_size(folio); - len =3D umin(fsize - skip, data_size); + len =3D umin(bv->bv_len - skip, data_size); =20 - n =3D copy_folio_to_iter(folio, skip, len, iter); + n =3D copy_page_to_iter(bv->bv_page, bv->bv_offset + skip, len, iter); if (n !=3D len) { cifs_dbg(VFS, "%s: something went wrong\n", __func__); return smb_EIO2(smb_eio_trace_rx_copy_to_iter, @@ -4879,7 +4875,7 @@ cifs_copy_folioq_to_iter(struct folio_queue *folioq, = size_t data_size, =20 static int handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid, - char *buf, unsigned int buf_len, struct folio_queue *buffer, + char *buf, unsigned int buf_len, struct bvecq *buffer, unsigned int buffer_len, bool is_offloaded) { unsigned int data_offset; @@ -4989,8 +4985,8 @@ handle_read_data(struct TCP_Server_Info *server, stru= ct mid_q_entry *mid, } =20 /* Copy the data to the output I/O iterator. */ - rdata->result =3D cifs_copy_folioq_to_iter(buffer, data_len, - cur_off, &rdata->subreq.io_iter); + rdata->result =3D cifs_copy_bvecq_to_iter(buffer, data_len, + cur_off, &rdata->subreq.io_iter); if (rdata->result !=3D 0) { if (is_offloaded) mid->mid_state =3D MID_RESPONSE_MALFORMED; @@ -5029,7 +5025,7 @@ handle_read_data(struct TCP_Server_Info *server, stru= ct mid_q_entry *mid, struct smb2_decrypt_work { struct work_struct decrypt; struct TCP_Server_Info *server; - struct folio_queue *buffer; + struct bvecq *buffer; char *buf; unsigned int len; }; @@ -5043,7 +5039,7 @@ static void smb2_decrypt_offload(struct work_struct *= work) struct mid_q_entry *mid; struct iov_iter iter; =20 - iov_iter_folio_queue(&iter, ITER_DEST, dw->buffer, 0, 0, dw->len); + iov_iter_bvec_queue(&iter, ITER_DEST, dw->buffer, 0, 0, dw->len); rc =3D decrypt_raw_data(dw->server, dw->buf, dw->server->vals->read_rsp_s= ize, &iter, true); if (rc) { @@ -5092,7 +5088,7 @@ static void smb2_decrypt_offload(struct work_struct *= work) } =20 free_pages: - netfs_free_folioq_buffer(dw->buffer); + bvecq_put(dw->buffer); cifs_small_buf_release(dw->buf); kfree(dw); } @@ -5138,12 +5134,12 @@ receive_encrypted_read(struct TCP_Server_Info *serv= er, struct mid_q_entry **mid, dw->len =3D len; len =3D round_up(dw->len, PAGE_SIZE); =20 - size_t cur_size =3D 0; - rc =3D netfs_alloc_folioq_buffer(NULL, &dw->buffer, &cur_size, len, GFP_N= OFS); - if (rc < 0) + rc =3D -ENOMEM; + dw->buffer =3D bvecq_alloc_buffer(len, GFP_NOFS, false); + if (!dw->buffer) goto discard_data; =20 - iov_iter_folio_queue(&iter, ITER_DEST, dw->buffer, 0, 0, len); + iov_iter_bvec_queue(&iter, ITER_DEST, dw->buffer, 0, 0, len); =20 /* Read the data into the buffer and clear excess bufferage. */ rc =3D cifs_read_iter_from_socket(server, &iter, dw->len); @@ -5201,7 +5197,7 @@ receive_encrypted_read(struct TCP_Server_Info *server= , struct mid_q_entry **mid, } =20 free_pages: - netfs_free_folioq_buffer(dw->buffer); + bvecq_put(dw->buffer); free_dw: kfree(dw); return rc; From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B2AA843E49B for ; Mon, 24 Aug 2026 14:44:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582660; cv=none; b=AjM7t30KS1aXomf0gPFNcLPyhHlNgyHxq3i8zV8VH9OvX3nuY9qwoGtHOj42D/oWaX2c31L3C4D9U03D7Ck3S7SpgC9PPMaAYMbppRiTeY5+r3Glcb1LH1u+K1qDtyQFCMOInPJr28F3XrVQxI5S9h8HvFFQVEtdAEU7tFSqIoM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582660; c=relaxed/simple; bh=hxt8zbjOTFGnpBBiqQfW+N1tChvqxp9WhUCVjYHuO9Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nViWd6pB3/UcK862HbiFlUPZqLlKJoM2JUMDxbEWeC7uF7y+WEbkMnQef3+D1eB1GuEPLXqGk6S1CSRca50rQd8WgdwKa7HXNtadzGJp8GFSs+yGI8PPkbsNgXGIaHv6SI7w5pEiPC4xqySW0k33l7Rti8DAfaxokLwai7SffPg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=h3cT7egi; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="h3cT7egi" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582650; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=GsV/bFz2jJW8aQ9QDHq6eP6PICyJaBwOdY5nB5MwmFQ=; b=h3cT7egis20qmQPlikOUhoh5sMLcgDrgKfJhi2GzttXjKGHMpUdozmnII1DNAbjHyEXxKp GlD6v+Yy4blUmq5+YFDuOwNpBzT2h8Wt5zSSExg48wYNqGfMTMmBC4rbzXEWM3yS+7kWaM wBesCM0g0QJZKvl6xZ0AWVlWFMr4uYk= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-157-il2AEd3LOgO99_tafAfVnQ-1; Mon, 24 Aug 2026 10:44:07 -0400 X-MC-Unique: il2AEd3LOgO99_tafAfVnQ-1 X-Mimecast-MFC-AGG-ID: il2AEd3LOgO99_tafAfVnQ_1787582639 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id DEA9C19540E4; Mon, 24 Aug 2026 14:43:58 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 373967D9; Mon, 24 Aug 2026 14:43:51 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Shyam Prasad N , Tom Talpey Subject: [PATCH v10 16/35] smbdirect: Support ITER_BVECQ in smbdirect_map_sges_from_iter() Date: Mon, 24 Aug 2026 15:41:09 +0100 Message-ID: <20260824144130.759997-17-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Content-Type: text/plain; charset="utf-8" Add support for ITER_BVECQ to smbdirect_map_sges_from_iter(). Signed-off-by: David Howells Acked-by: Stefan Metzmacher cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: Namjae Jeon cc: Shyam Prasad N cc: Tom Talpey cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/smb/smbdirect/connection.c | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index 8adf58097534..f5e7e75dd809 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -5,6 +5,7 @@ */ =20 #include "internal.h" +#include #include =20 struct smbdirect_map_sges { @@ -2006,6 +2007,69 @@ static ssize_t smbdirect_map_sges_from_bvec(struct i= ov_iter *iter, return ret; } =20 +/* + * Extract memory fragments from a BVECQ-class iterator and add them to an= RDMA + * list. The fragments are not pinned. + */ +static ssize_t smbdirect_map_sges_from_bvecq(struct iov_iter *iter, + struct smbdirect_map_sges *state, + ssize_t maxsize) +{ + const struct bvecq *bq =3D iter->bvecq, *next; + unsigned int slot =3D iter->bvecq_slot; + ssize_t extracted =3D 0; + size_t offset =3D iter->iov_offset; + + maxsize =3D umin(maxsize, iov_iter_count(iter)); + + do { + struct bio_vec *bv; + size_t bsize; + + while (slot >=3D bq->nr_slots) { + next =3D bvecq_next(bq); + if (!next) { + if (WARN_ON_ONCE(maxsize > 0)) + return -EIO; + goto out; + } + bq =3D next; + slot =3D 0; + } + + bv =3D &bq->bv[slot]; + bsize =3D bv->bv_len; + + if (offset < bsize) { + size_t part =3D umin(maxsize, bsize - offset); + bool ok; + + ok =3D smbdirect_map_sges_single_page(state, + bv->bv_page, + bv->bv_offset + offset, + part); + if (!ok) + return -EIO; + + offset +=3D part; + extracted +=3D part; + maxsize -=3D part; + } + + if (offset >=3D bsize) { + offset =3D 0; + slot++; + } + } while (state->num_sge < state->max_sge && maxsize > 0); + +out: + iter->bvecq =3D bq; + iter->bvecq_slot =3D slot; + iter->iov_offset =3D offset; + iter->count -=3D extracted; + return extracted; +} + /* * Extract fragments from a KVEC-class iterator and add them to an ib_sge = list. * This can deal with vmalloc'd buffers as well as kmalloc'd or static buf= fers. @@ -2155,6 +2219,9 @@ static ssize_t smbdirect_map_sges_from_iter(struct io= v_iter *iter, size_t len, case ITER_BVEC: ret =3D smbdirect_map_sges_from_bvec(iter, state, len); break; + case ITER_BVECQ: + ret =3D smbdirect_map_sges_from_bvecq(iter, state, len); + break; case ITER_KVEC: ret =3D smbdirect_map_sges_from_kvec(iter, state, len); break; From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF94344683F for ; Mon, 24 Aug 2026 14:44:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582663; cv=none; b=a46S3OCrPkrB7SP8M96dFrBPPCR703x+fyeeGHh4YEXCb5TADEZWrAgUnRdVzn9vGo2YN/xKjXsiD6FDKQsvmXhDvxhUxK4lOrOcv7n7KgBb+9gAT/stnXnHnbsJA4v0iQ82UEsoPrwcFbGY5oGcZu4s4g1O1M7uY+/8e71yxIE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582663; c=relaxed/simple; bh=Ku4YHS7rhuOu/Tuh9PGxlIL1utiUWT+vZus72fCPXHQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oFyoX8ObgyOkbn4u8ICwP0B1AkHJd63EI5sO+pumyZVod1TzV+swHt1Tw+wp+KeCbRGSO35yFd2mcju/4gEr55iB6ubnj/vD7AQ3/h5+vCqS2OuQPQpQx7zw7CBsYGIv7Ggglsr5dgyzYs7Iqr9CroeIMIRW7GBz8EUnUMoLtyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=DutZYAB5; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="DutZYAB5" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582654; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=9rm9NpPcIh/jFryu1IF6Xb6qZ4y4/kpSxxSi4Ji3UH4=; b=DutZYAB57tqCY88wW9nOElS0vcnQyvjTq7h0FNUtphDrlCfEaoCZl13tONQhqJVnZIcWaL 9nLnDFGyCAIFbCT2jxto1rSwUlGk5futTdIlG2qavPf+OmTjtmNfz2rOpqZ86ytEwoW+J5 kX6DkmINnaqxqjLBSBHIZ874CNL02Ak= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-392-lfEsx3z8P2WhCtc69nCW9Q-1; Mon, 24 Aug 2026 10:44:10 -0400 X-MC-Unique: lfEsx3z8P2WhCtc69nCW9Q-1 X-Mimecast-MFC-AGG-ID: lfEsx3z8P2WhCtc69nCW9Q_1787582646 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 95E4A1944DE3; Mon, 24 Aug 2026 14:44:06 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 7DD2D26C; Mon, 24 Aug 2026 14:44:00 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 17/35] netfs: Remove the writethrough code Date: Mon, 24 Aug 2026 15:41:10 +0100 Message-ID: <20260824144130.759997-18-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Content-Type: text/plain; charset="utf-8" Remove the netfs writethrough code as it's very tricky to get the locking right and it will probably deadlock if used in conjunction with Ceph snapshots because it excludes writeback for the duration, but to flush out old snapshots, it does a synchronous flush that invokes writeback. Instead, O_SYNC writes do a flush after performing the write - which is already there as the callers of netfs_perform_write() all call generic_write_sync(). Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/9p/vfs_addr.c | 1 - fs/afs/file.c | 1 - fs/netfs/buffered_write.c | 56 ++----------------- fs/netfs/internal.h | 7 --- fs/netfs/main.c | 1 - fs/netfs/stats.c | 4 +- fs/netfs/write_collect.c | 2 - fs/netfs/write_issue.c | 104 +---------------------------------- include/linux/netfs.h | 1 - include/trace/events/netfs.h | 8 +-- 10 files changed, 9 insertions(+), 176 deletions(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 1ac0b3dcc077..2129fcb0f65c 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -124,7 +124,6 @@ static int v9fs_init_request(struct netfs_io_request *r= req, struct file *file) struct p9_fid *fid; struct dentry *dentry; bool writing =3D (rreq->origin =3D=3D NETFS_READ_FOR_WRITE || - rreq->origin =3D=3D NETFS_WRITETHROUGH || rreq->origin =3D=3D NETFS_UNBUFFERED_WRITE || rreq->origin =3D=3D NETFS_DIO_WRITE); =20 diff --git a/fs/afs/file.c b/fs/afs/file.c index 3380a0d20c0f..99987f6bbc36 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -400,7 +400,6 @@ static int afs_init_request(struct netfs_io_request *rr= eq, struct file *file) } break; case NETFS_WRITEBACK: - case NETFS_WRITETHROUGH: case NETFS_UNBUFFERED_WRITE: case NETFS_DIO_WRITE: if (S_ISREG(rreq->inode->i_mode)) diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index df496873e4f4..ead22980075f 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -91,44 +91,14 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct = iov_iter *iter, struct inode *inode =3D file_inode(file); struct address_space *mapping =3D inode->i_mapping; struct netfs_inode *ctx =3D netfs_inode(inode); - struct writeback_control wbc =3D { - .sync_mode =3D WB_SYNC_NONE, - .for_sync =3D true, - .nr_to_write =3D LONG_MAX, - .range_start =3D iocb->ki_pos, - .range_end =3D iocb->ki_pos + iter->count, - }; - struct netfs_io_request *wreq =3D NULL; - struct folio *folio =3D NULL, *writethrough =3D NULL; + struct folio *folio =3D NULL; unsigned int bdp_flags =3D (iocb->ki_flags & IOCB_NOWAIT) ? BDP_ASYNC : 0; - ssize_t written =3D 0, ret, ret2; + ssize_t written =3D 0, ret; uoff_t pos =3D iocb->ki_pos; size_t max_chunk =3D mapping_max_folio_size(mapping); bool maybe_trouble =3D false; =20 - if (unlikely(iocb->ki_flags & (IOCB_DSYNC | IOCB_SYNC)) - ) { - wbc_attach_fdatawrite_inode(&wbc, mapping->host); - - ret =3D filemap_write_and_wait_range(mapping, pos, pos + iter->count); - if (ret < 0) { - wbc_detach_inode(&wbc); - goto out; - } - - wreq =3D netfs_begin_writethrough(iocb, iter->count); - if (IS_ERR(wreq)) { - wbc_detach_inode(&wbc); - ret =3D PTR_ERR(wreq); - wreq =3D NULL; - goto out; - } - if (!is_sync_kiocb(iocb)) - wreq->iocb =3D iocb; - netfs_stat(&netfs_n_wh_writethrough); - } else { - netfs_stat(&netfs_n_wh_buffered_write); - } + netfs_stat(&netfs_n_wh_buffered_write); =20 do { enum netfs_folio_trace trace; @@ -390,15 +360,8 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct= iov_iter *iter, pos +=3D copied; written +=3D copied; =20 - if (likely(!wreq)) { - folio_mark_dirty(folio); - folio_unlock(folio); - } else { - netfs_advance_writethrough(wreq, &wbc, folio, copied, - offset + copied =3D=3D flen, - &writethrough); - /* Folio unlocked */ - } + folio_mark_dirty(folio); + folio_unlock(folio); retry: folio_put(folio); folio =3D NULL; @@ -420,15 +383,6 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct= iov_iter *iter, ctx->ops->post_modify(inode); } =20 - if (unlikely(wreq)) { - ret2 =3D netfs_end_writethrough(wreq, &wbc, writethrough); - wbc_detach_inode(&wbc); - if (ret2 =3D=3D -EIOCBQUEUED) - return ret2; - if (ret =3D=3D 0 && ret2 < 0) - ret =3D ret2; - } - iocb->ki_pos +=3D written; _leave(" =3D %zd [%zd]", written, ret); return written ? written : ret; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 431ec77b1305..09e86183b26b 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -157,7 +157,6 @@ extern atomic_t netfs_n_rh_write_zskip; extern atomic_t netfs_n_rh_retry_read_req; extern atomic_t netfs_n_rh_retry_read_subreq; extern atomic_t netfs_n_wh_buffered_write; -extern atomic_t netfs_n_wh_writethrough; extern atomic_t netfs_n_wh_dio_write; extern atomic_t netfs_n_wh_writepages; extern atomic_t netfs_n_wh_copy_to_cache; @@ -217,12 +216,6 @@ void netfs_issue_write(struct netfs_io_request *wreq, size_t netfs_advance_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, uoff_t start, size_t len, bool to_eof); -struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size= _t len); -int netfs_advance_writethrough(struct netfs_io_request *wreq, struct write= back_control *wbc, - struct folio *folio, size_t copied, bool to_page_end, - struct folio **writethrough_cache); -ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct write= back_control *wbc, - struct folio *writethrough_cache); =20 /* * write_retry.c diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 9f72e5054aff..3908ea2422c8 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -45,7 +45,6 @@ static const char *netfs_origins[nr__netfs_io_origin] =3D= { [NETFS_DIO_READ] =3D "DR", [NETFS_WRITEBACK] =3D "WB", [NETFS_WRITEBACK_SINGLE] =3D "W1", - [NETFS_WRITETHROUGH] =3D "WT", [NETFS_UNBUFFERED_WRITE] =3D "UW", [NETFS_DIO_WRITE] =3D "DW", [NETFS_PGPRIV2_COPY_TO_CACHE] =3D "2C", diff --git a/fs/netfs/stats.c b/fs/netfs/stats.c index 84c2a4bcc762..a10d34f88597 100644 --- a/fs/netfs/stats.c +++ b/fs/netfs/stats.c @@ -32,7 +32,6 @@ atomic_t netfs_n_rh_write_zskip; atomic_t netfs_n_rh_retry_read_req; atomic_t netfs_n_rh_retry_read_subreq; atomic_t netfs_n_wh_buffered_write; -atomic_t netfs_n_wh_writethrough; atomic_t netfs_n_wh_dio_write; atomic_t netfs_n_wh_writepages; atomic_t netfs_n_wh_copy_to_cache; @@ -59,9 +58,8 @@ int netfs_stats_show(struct seq_file *m, void *v) atomic_read(&netfs_n_rh_read_single), atomic_read(&netfs_n_rh_write_begin), atomic_read(&netfs_n_rh_write_zskip)); - seq_printf(m, "Writes : BW=3D%u WT=3D%u DW=3D%u WP=3D%u 2C=3D%u\n", + seq_printf(m, "Writes : BW=3D%u DW=3D%u WP=3D%u 2C=3D%u\n", atomic_read(&netfs_n_wh_buffered_write), - atomic_read(&netfs_n_wh_writethrough), atomic_read(&netfs_n_wh_dio_write), atomic_read(&netfs_n_wh_writepages), atomic_read(&netfs_n_wh_copy_to_cache)); diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 100a5038c61e..244a68e04624 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -214,7 +214,6 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) smp_rmb(); collected_to =3D ULLONG_MAX; if (wreq->origin =3D=3D NETFS_WRITEBACK || - wreq->origin =3D=3D NETFS_WRITETHROUGH || wreq->origin =3D=3D NETFS_PGPRIV2_COPY_TO_CACHE) notes =3D NEED_UNLOCK; else @@ -411,7 +410,6 @@ bool netfs_write_collection(struct netfs_io_request *wr= eq) switch (wreq->origin) { case NETFS_WRITEBACK: case NETFS_WRITEBACK_SINGLE: - case NETFS_WRITETHROUGH: netfs_wb_end(ictx); break; default: diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index d68d130d6da0..6d0f72797351 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -96,7 +96,6 @@ struct netfs_io_request *netfs_create_write_req(struct ad= dress_space *mapping, struct netfs_inode *ictx; bool is_cacheable =3D (origin =3D=3D NETFS_WRITEBACK || origin =3D=3D NETFS_WRITEBACK_SINGLE || - origin =3D=3D NETFS_WRITETHROUGH || origin =3D=3D NETFS_PGPRIV2_COPY_TO_CACHE); =20 wreq =3D netfs_alloc_request(mapping, file, start, 0, origin); @@ -365,11 +364,7 @@ static int netfs_write_folio(struct netfs_io_request *= wreq, streamw =3D true; } =20 - if (wreq->origin =3D=3D NETFS_WRITETHROUGH) { - to_eof =3D false; - if (flen > i_size - fpos) - flen =3D i_size - fpos; - } else if (flen > i_size - fpos) { + if (flen > i_size - fpos) { flen =3D i_size - fpos; if (!streamw) folio_zero_segment(folio, flen, fsize); @@ -611,103 +606,6 @@ int netfs_writepages(struct address_space *mapping, } EXPORT_SYMBOL(netfs_writepages); =20 -/* - * Begin a write operation for writing through the pagecache. - */ -struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size= _t len) -{ - struct netfs_io_request *wreq =3D NULL; - struct netfs_inode *ictx =3D netfs_inode(file_inode(iocb->ki_filp)); - - netfs_wb_begin(ictx, false); - - wreq =3D netfs_create_write_req(iocb->ki_filp->f_mapping, iocb->ki_filp, - iocb->ki_pos, NETFS_WRITETHROUGH); - if (IS_ERR(wreq)) { - netfs_wb_end(ictx); - return wreq; - } - - wreq->io_streams[0].avail =3D true; - __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); - trace_netfs_write(wreq, netfs_write_trace_writethrough); - return wreq; -} - -/* - * Advance the state of the write operation used when writing through the - * pagecache. Data has been copied into the pagecache that we need to app= end - * to the request. If we've added more than wsize then we need to create = a new - * subrequest. - */ -int netfs_advance_writethrough(struct netfs_io_request *wreq, struct write= back_control *wbc, - struct folio *folio, size_t copied, bool to_page_end, - struct folio **writethrough_cache) -{ - int ret; - - _enter("R=3D%x ic=3D%zu ws=3D%u cp=3D%zu tp=3D%u", - wreq->debug_id, wreq->buffer.iter.count, wreq->wsize, copied, to_p= age_end); - - /* The folio is locked. */ - - if (*writethrough_cache !=3D folio) { - if (*writethrough_cache) { - /* Did the folio get moved? */ - folio_put(*writethrough_cache); - *writethrough_cache =3D NULL; - } - /* We can make multiple writes to the folio... */ - if (wreq->len =3D=3D 0) - trace_netfs_folio(folio, netfs_folio_trace_wthru); - else - trace_netfs_folio(folio, netfs_folio_trace_wthru_plus); - *writethrough_cache =3D folio; - folio_get(folio); - } - - wreq->len +=3D copied; - - if (!to_page_end) { - folio_mark_dirty(folio); - folio_unlock(folio); - return 0; - } - - ret =3D netfs_write_folio(wreq, wbc, folio); - folio_put(*writethrough_cache); - *writethrough_cache =3D NULL; - wreq->submitted =3D wreq->len; - return ret; -} - -/* - * End a write operation used when writing through the pagecache. - */ -ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct write= back_control *wbc, - struct folio *writethrough_cache) -{ - ssize_t ret; - - _enter("R=3D%x", wreq->debug_id); - - if (writethrough_cache) { - folio_lock(writethrough_cache); - netfs_write_folio(wreq, wbc, writethrough_cache); - folio_put(writethrough_cache); - wreq->submitted =3D wreq->len; - } - - netfs_end_issue_write(wreq); - - if (wreq->iocb) - ret =3D -EIOCBQUEUED; - else - ret =3D netfs_wait_for_write(wreq); - netfs_put_request(wreq, netfs_rreq_trace_put_return); - return ret; -} - /** * netfs_writeback_single - Write back a monolithic payload * @mapping: The mapping to write from diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 14a24bb80af5..b6e5f6dc54e2 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -209,7 +209,6 @@ enum netfs_io_origin { NETFS_DIO_READ, /* This is a direct I/O read */ NETFS_WRITEBACK, /* This write was triggered by writepages */ NETFS_WRITEBACK_SINGLE, /* This monolithic write was triggered by writep= ages */ - NETFS_WRITETHROUGH, /* This write was made by netfs_perform_write() */ NETFS_UNBUFFERED_WRITE, /* This is an unbuffered write */ NETFS_DIO_WRITE, /* This is a direct I/O write */ NETFS_PGPRIV2_COPY_TO_CACHE, /* [DEPRECATED] This is writing read data to= the cache */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 111c02817bea..ccb9f3c30432 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -30,8 +30,7 @@ EM(netfs_write_trace_dio_write, "DIO-WRITE") \ EM(netfs_write_trace_unbuffered_write, "UNB-WRITE") \ EM(netfs_write_trace_writeback, "WRITEBACK") \ - EM(netfs_write_trace_writeback_single, "WB-SINGLE") \ - E_(netfs_write_trace_writethrough, "WRITETHRU") + E_(netfs_write_trace_writeback_single, "WB-SINGLE") =20 #define netfs_rreq_origins \ EM(NETFS_READAHEAD, "RA") \ @@ -43,7 +42,6 @@ EM(NETFS_DIO_READ, "DR") \ EM(NETFS_WRITEBACK, "WB") \ EM(NETFS_WRITEBACK_SINGLE, "W1") \ - EM(NETFS_WRITETHROUGH, "WT") \ EM(NETFS_UNBUFFERED_WRITE, "UW") \ EM(NETFS_DIO_WRITE, "DW") \ E_(NETFS_PGPRIV2_COPY_TO_CACHE, "2C") @@ -221,9 +219,7 @@ EM(netfs_folio_trace_redirtied, "redirtied") \ EM(netfs_folio_trace_store, "store") \ EM(netfs_folio_trace_store_copy, "store-copy") \ - EM(netfs_folio_trace_store_plus, "store+") \ - EM(netfs_folio_trace_wthru, "wthru") \ - E_(netfs_folio_trace_wthru_plus, "wthru+") + E_(netfs_folio_trace_store_plus, "store+") =20 #define netfs_collect_contig_traces \ EM(netfs_contig_trace_collect, "Collect") \ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6CADB357D1E for ; Mon, 24 Aug 2026 14:45:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582730; cv=none; b=AOzll3fqft7Y9POGIIqlBgwwK6g9Drbtqojm+6Zjf43Aw5IzkSDGd4q5cPWbSWCYf3pRLYo+49Wr+fj4UFi8BQk05yOyMtBCcdkUAfQ5tLk09tcAewfseXQHP7pUxiPXscNku9hpTXgqFT/wIHfGL0muJUiunPhsRJck1s60j4o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582730; c=relaxed/simple; bh=RCruthYQd8BvOfrOYrsw+ORB2lSKq8tHxKnn+NpbngA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=IFHItlppB6A/iX31UxahnzCYTs+wWmwxkxlhMCWaPs1yqKMi0f88gsJmMLqmpDUHLyWMSOniWk8/vSG+da0TdxGbxsldT8tQu/aYOD+pH/Rbk3NO1O8PSBpzVxGqID6+yVOf+kCIjHxZp5PoZjhn0XiyljriO3pn8clRPPQtzX0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=UXKPJYsn; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="UXKPJYsn" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582727; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=b7baYKQZt0ba2opPjrSbjLhJKMY6G3aHST2hZ7+P0Gc=; b=UXKPJYsnyE2Gw5Rfu64PcAR1R+c7stNlNp61gugD7EDFkhrNcPjfgdKrzsFCQZVGxQ8rLX 1C1CXDS+m+0/1AcRVxds5X0zjJ6kj76MbnheU7JB7eL46uzwQiibJj4WmpYjxPt61p2guV h8UPMhA2mNokIwGoWSm9s9W+sdLScXg= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-1-BPFk5QKYNyq37mSpxfkupA-1; Mon, 24 Aug 2026 10:44:17 -0400 X-MC-Unique: BPFk5QKYNyq37mSpxfkupA-1 X-Mimecast-MFC-AGG-ID: BPFk5QKYNyq37mSpxfkupA_1787582654 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6D892180AE34; Mon, 24 Aug 2026 14:44:14 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 347D230002ED; Mon, 24 Aug 2026 14:44:07 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 18/35] netfs: trace: Change the "clear" folio traces to "endwb" Date: Mon, 24 Aug 2026 15:41:11 +0100 Message-ID: <20260824144130.759997-19-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Change the "clear" folio traces to "endwb" as it's more obvious what it means. Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/write_collect.c | 8 ++++---- include/trace/events/netfs.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 244a68e04624..6114bdf27ce0 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -56,7 +56,7 @@ static void netfs_dump_request(const struct netfs_io_requ= est *rreq) */ int netfs_folio_written_back(struct folio *folio) { - enum netfs_folio_trace why =3D netfs_folio_trace_clear; + enum netfs_folio_trace why =3D netfs_folio_trace_endwb; struct inode *inode =3D folio_inode(folio); struct netfs_inode *ictx =3D netfs_inode(inode); struct netfs_folio *finfo; @@ -79,13 +79,13 @@ int netfs_folio_written_back(struct folio *folio) group =3D finfo->netfs_group; gcount++; kfree(finfo); - why =3D netfs_folio_trace_clear_s; + why =3D netfs_folio_trace_endwb_s; goto end_wb; } =20 if ((group =3D netfs_folio_group(folio))) { if (group =3D=3D NETFS_FOLIO_COPY_TO_CACHE) { - why =3D netfs_folio_trace_clear_cc; + why =3D netfs_folio_trace_endwb_cc; folio_detach_private(folio); goto end_wb; } @@ -98,7 +98,7 @@ int netfs_folio_written_back(struct folio *folio) if (!folio_test_dirty(folio)) { folio_detach_private(folio); gcount++; - why =3D netfs_folio_trace_clear_g; + why =3D netfs_folio_trace_endwb_g; } } =20 diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index ccb9f3c30432..e23300c56d00 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -192,12 +192,12 @@ EM(netfs_folio_trace_alloc_buffer, "alloc-buf") \ EM(netfs_folio_trace_cancel_copy, "cancel-copy") \ EM(netfs_folio_trace_cancel_store, "cancel-store") \ - EM(netfs_folio_trace_clear, "clear") \ - EM(netfs_folio_trace_clear_cc, "clear-cc") \ - EM(netfs_folio_trace_clear_g, "clear-g") \ - EM(netfs_folio_trace_clear_s, "clear-s") \ EM(netfs_folio_trace_copy_to_cache, "mark-copy") \ EM(netfs_folio_trace_end_copy, "end-copy") \ + EM(netfs_folio_trace_endwb, "endwb") \ + EM(netfs_folio_trace_endwb_cc, "endwb-cc") \ + EM(netfs_folio_trace_endwb_g, "endwb-g") \ + EM(netfs_folio_trace_endwb_s, "endwb-s") \ EM(netfs_folio_trace_filled_gaps, "filled-gaps") \ EM(netfs_folio_trace_invalidate_all, "inval-all") \ EM(netfs_folio_trace_invalidate_front, "inval-front") \ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A8E143E06D for ; Mon, 24 Aug 2026 14:44:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582671; cv=none; b=QptWcgrh2OnPxEPWM14rAjmep/ldfxCDPY6vA2kIHbGSpIiTeKJn9gWjQOn6j0ChyIKJU6iksEIFsZC4wBk7CHf7Q4jcOF6BxhFQHPI2y4nO8iGzG6CDjWo8unGhwxZVCUd1ARaVId9Hg3YVRw1NB+dYz6pGmZ8j89E2icaD7GY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582671; c=relaxed/simple; bh=GsFeamdGY59OQx/+NMLkn2PhTXuKbikCKWmedWt40jM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=f5+Jtlvs+Yf+SmVBKzvbsdHuxuRozwL5QPyulMPgnOohylMugiKdMAurFYYl7K1xbL2k788sZlTrlMhslhFkJ2ZYQY95ne7dQdn26SmGBVVWVPfT01Ir96ECcJSVlnXZj8VOCh7KBULGyOympEVxEohEReucz7rcQIK/WCXT6AY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=AQgJXSVN; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="AQgJXSVN" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582669; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=na3Caw1f/gh59s0Uf9hdmse2XsohuJJX+14MfsyaNfA=; b=AQgJXSVNsMFCPCM3MAOU6YYOvh9i+5kb7cyV/8rD3OGdie7PX5frkszJyf5F4XNK7Mu1rN 9MN1g4hbmZCcHA9YBY1urKGpNZBNf+um9DzfHE1998Emau84fy32LTaccjKgChUW7gsfDX Kt9jAkHlkY9mREtlOBXkYIRJY9FKk0c= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-395-MI7FYOHcNm2fc5zylT1LyA-1; Mon, 24 Aug 2026 10:44:26 -0400 X-MC-Unique: MI7FYOHcNm2fc5zylT1LyA-1 X-Mimecast-MFC-AGG-ID: MI7FYOHcNm2fc5zylT1LyA_1787582662 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 39FD5186057F; Mon, 24 Aug 2026 14:44:22 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 24E081800348; Mon, 24 Aug 2026 14:44:15 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 19/35] netfs: trace: Rejig a couple of the tracepoints Date: Mon, 24 Aug 2026 15:41:12 +0100 Message-ID: <20260824144130.759997-20-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" Rejig the following tracepoints: (1) Change netfs_folio to show the pfn. (2) Change netfs_collect_folio to show a folio index range rather than file position range and don't show the cleaned_to or collected_to points. Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/read_collect.c | 2 +- fs/netfs/read_pgpriv2.c | 2 +- fs/netfs/write_collect.c | 2 +- include/trace/events/netfs.h | 23 ++++++++++------------- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 72afc17ecbb3..94e180ec6e41 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -172,7 +172,7 @@ static void netfs_read_unlock_folios(struct netfs_io_re= quest *rreq, fpos =3D folio_pos(folio); fend =3D fpos + fsize; =20 - trace_netfs_collect_folio(rreq, folio, fend, collected_to); + trace_netfs_collect_folio(rreq, folio); =20 /* Unlock any folio we've transferred all of. */ if (collected_to < fend) diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index ac16bb8210d1..883843270699 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -198,7 +198,7 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io= _request *creq) =20 fend =3D min_t(uoff_t, fpos + flen, creq->i_size); =20 - trace_netfs_collect_folio(creq, folio, fend, collected_to); + trace_netfs_collect_folio(creq, folio); =20 /* Unlock any folio we've transferred all of. */ if (collected_to < fend) diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 6114bdf27ce0..7194182b975c 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -156,7 +156,7 @@ static void netfs_writeback_unlock_folios(struct netfs_= io_request *wreq, =20 fend =3D min_t(uoff_t, fpos + flen, wreq->i_size); =20 - trace_netfs_collect_folio(wreq, folio, fend, collected_to); + trace_netfs_collect_folio(wreq, folio); =20 /* Unlock any folio we've transferred all of. */ if (collected_to < fend) diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index e23300c56d00..6439210d0705 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -495,6 +495,7 @@ TRACE_EVENT(netfs_folio, TP_STRUCT__entry( __field(u64, ino) __field(pgoff_t, index) + __field(unsigned long, pfn) __field(unsigned int, nr) __field(enum netfs_folio_trace, why) ), @@ -505,9 +506,11 @@ TRACE_EVENT(netfs_folio, __entry->why =3D why; __entry->index =3D folio->index; __entry->nr =3D folio_nr_pages(folio); + __entry->pfn =3D folio_pfn(folio); ), =20 - TP_printk("i=3D%05llx ix=3D%05lx-%05lx %s", + TP_printk("p=3D%lx i=3D%05llx ix=3D%05lx-%05lx %s", + __entry->pfn, __entry->ino, __entry->index, __entry->index + __entry->nr - 1, __print_symbolic(__entry->why, netfs_folio_traces)) ); @@ -654,31 +657,25 @@ TRACE_EVENT(netfs_collect_sreq, =20 TRACE_EVENT(netfs_collect_folio, TP_PROTO(const struct netfs_io_request *wreq, - const struct folio *folio, - uoff_t fend, uoff_t collected_to), + const struct folio *folio), =20 - TP_ARGS(wreq, folio, fend, collected_to), + TP_ARGS(wreq, folio), =20 TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned long, index) - __field(uoff_t, fend) - __field(uoff_t, cleaned_to) - __field(uoff_t, collected_to) + __field(unsigned int, nr) ), =20 TP_fast_assign( __entry->wreq =3D wreq->debug_id; __entry->index =3D folio->index; - __entry->fend =3D fend; - __entry->cleaned_to =3D wreq->cleaned_to; - __entry->collected_to =3D collected_to; + __entry->nr =3D folio_nr_pages(folio); ), =20 - TP_printk("R=3D%08x ix=3D%05lx r=3D%llx-%llx t=3D%llx/%llx", + TP_printk("R=3D%08x ix=3D%05lx-%05lx", __entry->wreq, __entry->index, - (uoff_t)__entry->index * PAGE_SIZE, __entry->fend, - __entry->cleaned_to, __entry->collected_to) + __entry->index + __entry->nr - 1) ); =20 TRACE_EVENT(netfs_collect_state, From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C5CF84418F0 for ; Mon, 24 Aug 2026 14:44:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582681; cv=none; b=F6FfikaknyEShprsxHkIh1qqghrjd2up6+TdfqtrhO5n4hYGxUY2qGqhd6DHE30Fos75v09pXkbftBrSZdpfum1b8rioRK9h6U/0dFQSZ1l6kiXE5QLt9C0HPK1u2NoT5l2JbZCFmnoUl7TDXedIdRs/NsYoqCkGkyiwYYJe4vU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582681; c=relaxed/simple; bh=LL6inTQGGT5VX0HtdM1RUN0MpaPVvc5v0nzLrUviRQI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=CcI4V/b7YUgLBcwx/1fXulx0KH9MQl0/Gx5xybcg/0E7bFioHE7JnyNZhTuyUXwcjszJWmx30y06Y/aLyQdJA7VPL7yeIr/furrHbIuEyvSnjffQ8YeTOraUNVQ/sk5fpON9AioC+LySgccnSl9ZJ45vcxq19QgN+Lex4McL6Co= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=OKgA9dL0; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="OKgA9dL0" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582678; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=gyAlcOnJzmfzASmgtzD8YShFZnNf+3YpRDZZvlK08ds=; b=OKgA9dL0WlfPG6NeAX8+NFTDDs+7VMBTJpst3VubVm4BCNnTM7MKnzLW+rd+JRw+727tEM gQSWKQGNjRNYx/TEf5fwcGVmpQAd5gUDb2umwdRBNPoAAcyL31MTgdZz0emfUfbtr1lbhA VeomuEmvmmi3kqbFHJ47mUWwyQ01a7Q= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-610-aH7RqJJcN92veFWctk50sw-1; Mon, 24 Aug 2026 10:44:33 -0400 X-MC-Unique: aH7RqJJcN92veFWctk50sw-1 X-Mimecast-MFC-AGG-ID: aH7RqJJcN92veFWctk50sw_1787582670 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EEC0519775A6; Mon, 24 Aug 2026 14:44:29 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id D2AA230002ED; Mon, 24 Aug 2026 14:44:23 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 20/35] netfs: Add some functions to wrap the all-queued handling Date: Mon, 24 Aug 2026 15:41:13 +0100 Message-ID: <20260824144130.759997-21-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Add some helper functions to wrap the handling of the NETFS_RREQ_ALL_QUEUED flag and to insert the appropriate barriers. Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/buffered_read.c | 9 +++------ fs/netfs/direct_read.c | 9 +++------ fs/netfs/internal.h | 19 +++++++++++++++++++ fs/netfs/misc.c | 2 +- fs/netfs/read_collect.c | 4 +--- fs/netfs/read_pgpriv2.c | 3 +-- fs/netfs/read_single.c | 9 +++------ fs/netfs/write_collect.c | 3 +-- fs/netfs/write_issue.c | 7 ++----- include/trace/events/netfs.h | 1 + 10 files changed, 35 insertions(+), 31 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 77f7dfc816a8..03e095060162 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -303,10 +303,8 @@ static void netfs_read_to_pagecache(struct netfs_io_re= quest *rreq) } start +=3D slice; size -=3D slice; - if (size <=3D 0) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); - } + if (size <=3D 0) + netfs_all_subreqs_queued(rreq); =20 netfs_issue_read(rreq, subreq); netfs_maybe_bulk_drop_ra_refs(rreq); @@ -319,8 +317,7 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq) } while (size > 0); =20 if (unlikely(size > 0)) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } =20 diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index aa10af5171a8..5405e108b7a3 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -84,10 +84,8 @@ static void netfs_dispatch_unbuffered_reads(struct netfs= _io_request *rreq) size -=3D slice; start +=3D slice; rreq->submitted +=3D slice; - if (size <=3D 0) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); - } + if (size <=3D 0) + netfs_all_subreqs_queued(rreq); =20 rreq->netfs_ops->issue_read(subreq); =20 @@ -99,8 +97,7 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_= io_request *rreq) } while (size > 0); =20 if (unlikely(size > 0)) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } } diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 09e86183b26b..df04ee82e34b 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -314,6 +314,25 @@ static inline bool netfs_check_subreq_in_progress(cons= t struct netfs_io_subreque return test_bit_acquire(NETFS_SREQ_IN_PROGRESS, &subreq->flags); } =20 +/* + * Indicate that we've generated and queued all the subrequests we're goin= g to. + */ +static inline void netfs_all_subreqs_queued(struct netfs_io_request *rreq) +{ + smp_wmb(); /* Write lists before ALL_QUEUED. */ + set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + trace_netfs_rreq(rreq, netfs_rreq_trace_all_queued); +} + +/* + * Query if all subrequests are queued. + */ +static inline bool netfs_are_all_subreqs_queued(const struct netfs_io_requ= est *rreq) +{ + /* Read lists after ALL_QUEUED. */ + return test_bit_acquire(NETFS_RREQ_ALL_QUEUED, &rreq->flags); +} + /* * fscache-cache.c */ diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index eafc4edae6a0..a3cd76d584b8 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -424,7 +424,7 @@ static int netfs_collect_in_app(struct netfs_io_request= *rreq, need_collect =3D true; break; } - if (subreq || !test_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags)) + if (subreq || !netfs_are_all_subreqs_queued(rreq)) done =3D false; } =20 diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 94e180ec6e41..f758ab0aaf5d 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -447,10 +447,8 @@ bool netfs_read_collection(struct netfs_io_request *rr= eq) /* We're done when the app thread has finished posting subreqs and the * queue is empty. */ - if (!test_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags)) + if (!netfs_are_all_subreqs_queued(rreq)) return false; - smp_rmb(); /* Read ALL_QUEUED before subreq lists. */ - if (!list_empty(&stream->subrequests)) return false; =20 diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index 883843270699..aab156d00031 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -155,8 +155,7 @@ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_re= quest *rreq) return; =20 netfs_issue_write(creq, &creq->io_streams[1]); - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &creq->flags); + netfs_all_subreqs_queued(creq); trace_netfs_rreq(rreq, netfs_rreq_trace_end_copy_to_cache); if (list_empty_careful(&creq->io_streams[1].subrequests)) netfs_wake_collector(creq); diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index de67ac41548d..ccb5fc809d99 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -113,14 +113,12 @@ static int netfs_single_dispatch_read(struct netfs_io= _request *rreq) goto cancel; } =20 - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); rreq->netfs_ops->issue_read(subreq); rreq->submitted +=3D subreq->len; break; case NETFS_READ_FROM_CACHE: - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); trace_netfs_sreq(subreq, netfs_sreq_trace_submit); netfs_single_read_cache(rreq, subreq); rreq->submitted +=3D subreq->len; @@ -136,8 +134,7 @@ static int netfs_single_dispatch_read(struct netfs_io_r= equest *rreq) return ret; cancel: netfs_cancel_read(subreq, ret); - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); return ret; } diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 7194182b975c..6d99d4a6f780 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -371,9 +371,8 @@ bool netfs_write_collection(struct netfs_io_request *wr= eq) /* We're done when the app thread has finished posting subreqs and all * the queues in all the streams are empty. */ - if (!test_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags)) + if (!netfs_are_all_subreqs_queued(wreq)) return false; - smp_rmb(); /* Read ALL_QUEUED before lists. */ =20 transferred =3D LONG_MAX; for (s =3D 0; s < NR_IO_STREAMS; s++) { diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 6d0f72797351..4ed50ac7c1f8 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -518,8 +518,7 @@ static void netfs_end_issue_write(struct netfs_io_reque= st *wreq) { bool needs_poke =3D true; =20 - smp_wmb(); /* Write subreq lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags); + netfs_all_subreqs_queued(wreq); =20 for (int s =3D 0; s < NR_IO_STREAMS; s++) { struct netfs_io_stream *stream =3D &wreq->io_streams[s]; @@ -665,9 +664,7 @@ int netfs_writeback_single(struct address_space *mappin= g, netfs_issue_write(wreq, stream); } =20 - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags); - + netfs_all_subreqs_queued(wreq); netfs_wake_collector(wreq); =20 /* TODO: Might want to be async here if WB_SYNC_NONE, but then need to diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 6439210d0705..1687b974900c 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -47,6 +47,7 @@ E_(NETFS_PGPRIV2_COPY_TO_CACHE, "2C") =20 #define netfs_rreq_traces \ + EM(netfs_rreq_trace_all_queued, "ALL-Q ") \ EM(netfs_rreq_trace_assess, "ASSESS ") \ EM(netfs_rreq_trace_collect, "COLLECT") \ EM(netfs_rreq_trace_complete, "COMPLET") \ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 399B44418F0 for ; Mon, 24 Aug 2026 14:44:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582690; cv=none; b=V++Ag8/QeZkkDjQ19zlwwJiKvG7A6eNPKX/Fk9KTjAs4SMa+HqGfQbaEPerB8zGnjCpadaq5IHDzm96gwaEGoQlyClL4iAyaTToT7iyiIwhd6LZ07hp8h1mc7tHzb+ZMl2MOy72eyJ3VUN6fiQ0f1Bm8FduGo5WtZ7NNxBp1s7E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582690; c=relaxed/simple; bh=wgYu6gI5V2Eao2dPlRPwB8T+Tu60WhH38AcZaIiCaoA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Lnqw+pEJB+CSoSqRn+cWzp7qMW77anGhA6ZjBIt7y2TP5f/vO2fWndDQtL8KhWl7ciLt5tRtEm6tWHKEzMV0v9yXBFd8iposTvprlDPU/vvO5W1yN8jTDr9H8DvhUg0yH9hxVcmCrRo8ybCjqwCrQAw2conxy96g3Lr4HHl16Gk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Rj009vFP; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Rj009vFP" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582688; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=+g0CT/3xbEYkbuzN70YZ1k411vm20BLM3/RDiJeUpoQ=; b=Rj009vFPMoTxTu+xoOKng2qlPbGEnO8dnNJ9CiNI6rPw8mBgnEW9ZX/WhdZtWnxWq3J4of EFZhu4sTp5wuRnDGtRLOYjz3bRaH2j/s1raTQNXAjq7huaO/kkOVtFCNJx00BVY9Tn93e9 lAbOraQVyg0kd0Z6NDQfd7UIs6HOsP8= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-664-6DFV8RnON6uyFerSV8GGUg-1; Mon, 24 Aug 2026 10:44:43 -0400 X-MC-Unique: 6DFV8RnON6uyFerSV8GGUg-1 X-Mimecast-MFC-AGG-ID: 6DFV8RnON6uyFerSV8GGUg_1787582679 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3B5F41834590; Mon, 24 Aug 2026 14:44:39 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id B193B30002ED; Mon, 24 Aug 2026 14:44:31 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Trond Myklebust , Anna Schumaker , Alex Markuze , Viacheslav Dubeyko Subject: [PATCH v10 21/35] netfs: Make deprecated PG_private_2 support optional Date: Mon, 24 Aug 2026 15:41:14 +0100 Message-ID: <20260824144130.759997-22-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Make the deprecated PG_private_2 support optional, requiring it to be selected by the filesystems that might want to use it. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: Trond Myklebust cc: Anna Schumaker cc: Ilya Dryomov cc: Alex Markuze cc: Viacheslav Dubeyko cc: netfs@lists.linux.dev cc: linux-nfs@vger.kernel.org cc: ceph-devel@vger.kernel.org cc: linux-fsdevel@vger.kernel.org --- fs/ceph/Kconfig | 1 + fs/netfs/Kconfig | 3 +++ fs/netfs/Makefile | 2 +- fs/netfs/internal.h | 28 ++++++++++++++++++++++++++++ fs/netfs/read_collect.c | 2 +- fs/nfs/Kconfig | 1 + include/linux/netfs.h | 2 ++ 7 files changed, 37 insertions(+), 2 deletions(-) diff --git a/fs/ceph/Kconfig b/fs/ceph/Kconfig index 3d64a316ca31..aa6ccd7794d2 100644 --- a/fs/ceph/Kconfig +++ b/fs/ceph/Kconfig @@ -4,6 +4,7 @@ config CEPH_FS depends on INET select CEPH_LIB select NETFS_SUPPORT + select NETFS_PGPRIV2 select FS_ENCRYPTION_ALGS if FS_ENCRYPTION default n help diff --git a/fs/netfs/Kconfig b/fs/netfs/Kconfig index 7701c037c328..d0e7b0971fa3 100644 --- a/fs/netfs/Kconfig +++ b/fs/netfs/Kconfig @@ -22,6 +22,9 @@ config NETFS_STATS between CPUs. On the other hand, the stats are very useful for debugging purposes. Saying 'Y' here is recommended. =20 +config NETFS_PGPRIV2 + bool + config NETFS_DEBUG bool "Enable dynamic debugging netfslib and FS-Cache" depends on NETFS_SUPPORT diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile index e1f12ecb5abf..b1ea4439c1bb 100644 --- a/fs/netfs/Makefile +++ b/fs/netfs/Makefile @@ -12,7 +12,6 @@ netfs-y :=3D \ misc.o \ objects.o \ read_collect.o \ - read_pgpriv2.o \ read_retry.o \ read_single.o \ rolling_buffer.o \ @@ -20,6 +19,7 @@ netfs-y :=3D \ write_issue.o \ write_retry.o =20 +netfs-$(CONFIG_NETFS_PGPRIV2) +=3D read_pgpriv2.o netfs-$(CONFIG_NETFS_STATS) +=3D stats.o =20 netfs-$(CONFIG_FSCACHE) +=3D \ diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index df04ee82e34b..c50750b72262 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -120,9 +120,37 @@ void netfs_cache_read_terminated(void *priv, ssize_t t= ransferred_or_error); /* * read_pgpriv2.c */ +#ifdef CONFIG_NETFS_PGPRIV2 +int netfs_prepare_pgpriv2_write_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs); void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct fol= io *folio); void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq); bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *wreq); +static inline bool netfs_using_pgpriv2(const struct netfs_io_request *rreq) +{ + return test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags); +} +#else +static inline int netfs_prepare_pgpriv2_write_buffer(struct netfs_io_subre= quest *subreq, + unsigned int max_segs) +{ + return -EIO; +} +static inline void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rr= eq, struct folio *folio) +{ +} +static inline void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request= *rreq) +{ +} +static inline bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_requ= est *wreq) +{ + return true; +} +static inline bool netfs_using_pgpriv2(const struct netfs_io_request *rreq) +{ + return false; +} +#endif =20 /* * read_retry.c diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index f758ab0aaf5d..f773579692ab 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -54,7 +54,7 @@ static void netfs_unlock_read_folio(struct netfs_io_reque= st *rreq, flush_dcache_folio(folio); folio_mark_uptodate(folio); =20 - if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (!netfs_using_pgpriv2(rreq)) { finfo =3D netfs_folio_info(folio); if (finfo) { trace_netfs_folio(folio, netfs_folio_trace_filled_gaps); diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index 6bb30543eff0..e7862f35b72c 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -174,6 +174,7 @@ config NFS_FSCACHE bool "Provide NFS client caching support" depends on NFS_FS select NETFS_SUPPORT + select NETFS_PGPRIV2 select FSCACHE help Say Y here if you want NFS data to be cached locally on disc through diff --git a/include/linux/netfs.h b/include/linux/netfs.h index b6e5f6dc54e2..7cdb14a130d9 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -279,8 +279,10 @@ struct netfs_io_request { #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages = */ #define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA ga= ve us */ +#ifdef CONFIG_NETFS_PGPRIV2 #define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark * write to cache on read */ +#endif const struct netfs_request_ops *netfs_ops; }; From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2CCAE4582DC for ; Mon, 24 Aug 2026 14:44:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582703; cv=none; b=vD1zEFNik4UYGhEOxrlWSTCV7/XAqZUm0x0iCPvnHLnt2eVwcRHmwVFjf1fANny+ZEqBy21gJ0hPCYxJ9Um0eKdet6qcgbMQcWyQVSfcw+PpjR8XwIpoVGtPLjJyvQn7H+8KVjTypI85mbLXBpQgzhFYgKej8smY8uT2UYwM9Og= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582703; c=relaxed/simple; bh=RfbCsK1oMOOWAvpl8pZKIhEKS6Z7/ePn4EIdPZMhiy8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=eG9m9xBi5PdGwW5ulpzhgF4uh9uVpudFSV0vYqH4ha/OpHvyHHLtvxvhNu+z3yEO3roD+S2G8jLI+No76M4RaREve/ucl5b4XQ/ZwHw3L7crb6ZUJxpVIDfd5skFCWG0CZP8rmTsQh29EYsemDhNQzQe4+etYZa3hzsgGPoxbJ8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=CbWHjaCe; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="CbWHjaCe" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582697; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=o576o2DrtYHdEAbhCoC/rkgbk501codFN5a22eWx6hk=; b=CbWHjaCec3EGg/U8v1x86RRI88l8KGXr+zeDVy+Duow42v5DZVHowB3L0SSGajGiaHWc9c r5y7PQKVUjUkdCwZJBDeATfNkuWY2pgFVJ6Gp4Kv8+ATvAUVGt4em17ybIfgcDpIPwdX5l xWKbBFs3sYGvYnfCL3fMSPNkUOeyL6s= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-529-XTFi0h2-P6SoqRRJyWot1w-1; Mon, 24 Aug 2026 10:44:51 -0400 X-MC-Unique: XTFi0h2-P6SoqRRJyWot1w-1 X-Mimecast-MFC-AGG-ID: XTFi0h2-P6SoqRRJyWot1w_1787582687 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 877F11834590; Mon, 24 Aug 2026 14:44:47 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E6D3D30002ED; Mon, 24 Aug 2026 14:44:40 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 22/35] cachefiles: Don't rely on backing fs storage map for most use cases Date: Mon, 24 Aug 2026 15:41:15 +0100 Message-ID: <20260824144130.759997-23-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Cachefiles currently uses the backing filesystem's idea of what data is held in a backing file and queries this by means of SEEK_DATA and SEEK_HOLE. However, this means it does two seek operations on the backing file for each individual read call it wants to prepare (unless the first returns -ENXIO). Worse, the backing filesystem is at liberty to insert or remove blocks of zeros in order to optimise its layout which may cause false positives and false negatives. The problem is that keeping track of what is dirty is tricky (if storing info in xattrs, which may have limited capacity and must be read and written as one piece) and expensive (in terms of diskspace at least) and is basically duplicating what a filesystem does. However, the most common write case, in which the application does { open(O_TRUNC); write(); write(); ... write(); close(); } where each write follows directly on from the previous and leaves no gaps in the file is reasonably easy to detect and can be noted in the primary xattr as CACHEFILES_CONTENT_ALL, indicating we have everything up to the object size stored. In this specific case, given that it is known that there are no holes in the file, there's no need to call SEEK_DATA/HOLE or use any other mechanism to track the contents. That speeds things up enormously. Even when it is necessary to use SEEK_DATA/HOLE, it may not be necessary to call it for each cache read subrequest generated. Implement this by adding support for the CACHEFILES_CONTENT_ALL content type (which is defined, but currently unused), which requires a slight adjustment in how backing files are managed. Specifically, the driver needs to know how much of the tail block is data and whether storing more data will create a hole. To this end, the way that the size of a backing file is managed is changed. Currently, the backing file is expanded to strictly match the size of the network file, but this can be changed to carry more useful information. This makes two pieces of metadata available: xattr.object_size and the backing file's i_size. Apply the following schema: (a) i_size is always a multiple of the DIO block size. (b) i_size is only updated to the end of the highest write stored. This is used to work out if we are following on without leaving a hole. (c) xattr.object_size is the size of the network filesystem file cached in this backing file. (d) xattr.object_size must point after the start of the last block (unless both are 0). (e) If xattr.object_size is at or after the block at the current end of the backing file (ie. i_size), then we have all the contents of the block (if xattr.content =3D=3D CACHEFILES_CONTENT_ALL). (f) If xattr.object_size is somewhere in the middle of the last block, then the data following it is invalid and must be ignored. (g) If data is added to the last block, then that block must be fetched, modified and rewritten (it must be a buffered write through the pagecache and not DIO). (h) Writes to cache are rounded out to blocks on both sides and the folios used as sources must contain data for any lower gap and must have been cleared for any upper gap, and so will rewrite any non-data area in the tail block. To implement this, the following changes are made: (1) cookie->object_size is no longer updated when writes are copied into the pagecache, but rather only updated when a write request completes. This prevents object size miscomparison when checking the xattr causing the backing file to be invalidated (opening and marking the backing file and modifying the pagecache run in parallel). (2) The cache's current idea of the amount of data that should be stored in the backing file is kept track of in object->object_size. Possibly this is redundant with cookie->object_size, but the latter gets updated in some addition circumstances. (3) The size of the backing file at the start of a request is now tracked in struct netfs_cache_resources so that the partial EOF block can be located and cleaned. (4) The cache block size is now used consistently rather than using CACHEFILES_DIO_BLOCK_SIZE (4096). (5) The backing file size is no longer adjusted when looking up an object. (6) When shortening a file, if the new size is not block aligned, the part beyond the new size is cleared. If the file is truncated to zero, the content_info gets reset to CACHEFILES_CONTENT_NO_DATA. (7) A new struct, fscache_occupancy, is instituted to track the region being read. Netfslib allocates it and fills in the start and end of the region to be read then calls the ->query_occupancy() method to find and fill in the extents. It also indicates whether a recorded extent contains data or just contains a region that's all zeros (FSCACHE_EXTENT_DATA or FSCACHE_EXTENT_ZERO). (8) The ->prepare_read() cache method is changed such that, if given, it just limits the amount that can be read from the cache in one go. It no longer indicates what source of read should be done; that information is now obtained from ->query_occupancy(). (9) A new cache method, ->collect_write(), is added that is called when a contiguous series of writes have completed and a discontiguity or the end of the request has been hit. It it supplied with the start and length of the write made to the backing file and can use this information to update the cache metadata. (10) cachefiles_query_occupancy() is altered to find the next two "extents" of data stored in the backing file by doing SEEK_DATA/HOLE between the bounds set - unless it is known that there are no holes, in which case a whole-file first extent can be set. (11) cachefiles_collect_write() is implemented to take the collated write completion information and use this to update the cache metadata, in particular working out whether there's now a hole in the backing file requiring future use of SEEK_DATA/HOLE instead of just assuming the data is all present. It also uses fallocate(FALLOC_FL_ZERO_RANGE) to clean the part of a partial block that extended beyond the old object size. It might be better to perform a synchronous DIO write for this purpose, but that would mandate an RMW cycle. Ideally, it should be all zeros anyway, but, unfortunately, shared-writable mmap can interfere. (12) cachefiles_begin_operation() is updated to note the current backing file size and the cache DIO size. (13) cachefiles_create_tmpfile() no longer expands the backing file when it creates it. (14) cachefiles_set_object_xattr() is changed to use object->object_size rather than cookie->object_size. (15) cachefiles_check_auxdata() is altered to actually store the content type and to also set object->object_size. The cachefiles_coherency tracepoint is also modified to display xattr.object_size. (16) netfs_read_to_pagecache() is reworked. The cache ->prepare_read() method is replaced with ->query_occupancy() as the arbiter of what region of the file is read from where, and that retrieves up to two occupied extents of the backing file at once. The cache ->prepare_read() method is now repurposed to be the same as the equivalent network filesystem method and allows the cache to limit the size of the read before the iterator is prepared. netfs_single_dispatch_read() is similarly modified. (17) netfs_update_i_size() and afs_update_i_size() no longer call fscache_update_cookie() to update cookie->object_size. (18) Write collection now collates contiguous sequences of writes to the cache and calls the cache ->collect_write() method. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/afs/file.c | 1 - fs/cachefiles/interface.c | 90 ++------ fs/cachefiles/internal.h | 13 +- fs/cachefiles/io.c | 341 ++++++++++++++++++++++++------ fs/cachefiles/namei.c | 19 +- fs/cachefiles/xattr.c | 23 +- fs/netfs/buffered_read.c | 176 +++++++++------ fs/netfs/buffered_write.c | 3 - fs/netfs/internal.h | 2 + fs/netfs/objects.c | 1 + fs/netfs/read_retry.c | 2 + fs/netfs/read_single.c | 39 ++-- fs/netfs/write_collect.c | 132 ++++++++++-- fs/netfs/write_issue.c | 18 ++ fs/netfs/write_retry.c | 3 + include/linux/fscache.h | 17 ++ include/linux/netfs.h | 40 +++- include/trace/events/cachefiles.h | 21 +- include/trace/events/netfs.h | 9 +- 19 files changed, 662 insertions(+), 288 deletions(-) diff --git a/fs/afs/file.c b/fs/afs/file.c index 99987f6bbc36..11695be8c4a5 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -447,7 +447,6 @@ void afs_set_i_size(struct afs_vnode *vnode, loff_t new= _i_size) } spin_unlock(&inode->i_lock); write_sequnlock(&vnode->cb_lock); - fscache_update_cookie(afs_vnode_cache(vnode), NULL, &new_i_size); } =20 static void afs_update_i_size(struct inode *inode, uoff_t new_i_size) diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index a160d5c3e74c..789ff6abe926 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -99,73 +99,6 @@ void cachefiles_put_object(struct cachefiles_object *obj= ect, _leave(""); } =20 -/* - * Adjust the size of a cache file if necessary to match the DIO size. We= keep - * the EOF marker a multiple of DIO blocks so that we don't fall back to d= oing - * non-DIO for a partial block straddling the EOF, but we also have to be - * careful of someone expanding the file and accidentally accreting the - * padding. - */ -static int cachefiles_adjust_size(struct cachefiles_object *object) -{ - struct iattr newattrs; - struct file *file =3D object->file; - uint64_t ni_size; - uoff_t oi_size; - int ret; - - ni_size =3D object->cookie->object_size; - ni_size =3D round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); - - _enter("{OBJ%x},[%llu]", - object->debug_id, (unsigned long long) ni_size); - - if (!file) - return -ENOBUFS; - - oi_size =3D i_size_read(file_inode(file)); - if (oi_size =3D=3D ni_size) - return 0; - - inode_lock(file_inode(file)); - - /* if there's an extension to a partial page at the end of the backing - * file, we need to discard the partial page so that we pick up new - * data after it */ - if (oi_size & ~PAGE_MASK && ni_size > oi_size) { - _debug("discard tail %llx", oi_size); - newattrs.ia_valid =3D ATTR_SIZE; - newattrs.ia_size =3D oi_size & PAGE_MASK; - ret =3D cachefiles_inject_remove_error(); - if (ret =3D=3D 0) - ret =3D notify_change(&nop_mnt_idmap, file->f_path.dentry, - &newattrs, NULL); - if (ret < 0) - goto truncate_failed; - } - - newattrs.ia_valid =3D ATTR_SIZE; - newattrs.ia_size =3D ni_size; - ret =3D cachefiles_inject_write_error(); - if (ret =3D=3D 0) - ret =3D notify_change(&nop_mnt_idmap, file->f_path.dentry, - &newattrs, NULL); - -truncate_failed: - inode_unlock(file_inode(file)); - - if (ret < 0) - trace_cachefiles_io_error(NULL, file_inode(file), ret, - cachefiles_trace_notify_change_error); - if (ret =3D=3D -EIO) { - cachefiles_io_error_obj(object, "Size set failed"); - ret =3D -ENOBUFS; - } - - _leave(" =3D %d", ret); - return ret; -} - /* * Attempt to look up the nominated node in this cache */ @@ -198,7 +131,6 @@ static bool cachefiles_lookup_cookie(struct fscache_coo= kie *cookie) spin_lock(&cache->object_list_lock); list_add(&object->cache_link, &cache->object_list); spin_unlock(&cache->object_list_lock); - cachefiles_adjust_size(object); =20 cachefiles_end_secure(cache, saved_cred); _leave(" =3D t"); @@ -232,7 +164,7 @@ static bool cachefiles_shorten_object(struct cachefiles= _object *object, uoff_t i_size, dio_size; int ret; =20 - dio_size =3D round_up(new_size, CACHEFILES_DIO_BLOCK_SIZE); + dio_size =3D round_up(new_size, cache->bsize); i_size =3D i_size_read(inode); =20 trace_cachefiles_trunc(object, inode, i_size, dio_size, @@ -264,6 +196,7 @@ static bool cachefiles_shorten_object(struct cachefiles= _object *object, } } =20 + object->object_size =3D new_size; return true; } =20 @@ -278,22 +211,31 @@ static void cachefiles_resize_cookie(struct netfs_cac= he_resources *cres, struct fscache_cookie *cookie =3D object->cookie; const struct cred *saved_cred; struct file *file =3D cachefiles_cres_file(cres); - uoff_t old_size =3D cookie->object_size; + uoff_t i_size =3D i_size_read(file_inode(file)); =20 - _enter("%llu->%llu", old_size, new_size); + _enter("%llu->%llu", object->object_size, new_size); =20 - if (new_size < old_size) { + /* If the file is being shrunk, we need to downsize the backing file + * and clear the end of the final block. + */ + if (new_size < object->object_size) { + if (new_size >=3D i_size) + goto out; cachefiles_begin_secure(cache, &saved_cred); cachefiles_shorten_object(object, file, new_size); cachefiles_end_secure(cache, saved_cred); object->cookie->object_size =3D new_size; + if (new_size =3D=3D 0) + object->content_info =3D CACHEFILES_CONTENT_NO_DATA; return; } =20 /* The file is being expanded. We don't need to do anything - * particularly. cookie->initial_size doesn't change and so the point - * at which we have to download before doesn't change. + * particularly. The tail of the last block should have been cleared + * both when it is written and when it is shrunk. */ +out: + object->object_size =3D new_size; cookie->object_size =3D new_size; } =20 diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index 60bd801ada04..b2605111fd56 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -16,8 +16,6 @@ #include #include =20 -#define CACHEFILES_DIO_BLOCK_SIZE 4096 - struct cachefiles_cache; struct cachefiles_object; =20 @@ -51,12 +49,17 @@ struct cachefiles_object { struct list_head cache_link; /* Link in cache->*_list */ struct file *file; /* The file representing this object */ char *d_name; /* Backing file name */ + unsigned long flags; +#define CACHEFILES_OBJECT_USING_TMPFILE 0 /* Have an unlinked tmpfile */ + uoff_t object_size; /* Size of the object stored + * (independent of cookie->object_size for + * coherency reasons) + */ + atomic64_t read_limit; /* Point beyond which uncommitted writes */ int debug_id; spinlock_t lock; refcount_t ref; - enum cachefiles_content content_info:8; /* Info about content presence */ - unsigned long flags; -#define CACHEFILES_OBJECT_USING_TMPFILE 0 /* Have an unlinked tmpfile */ + enum cachefiles_content content_info; /* Info about content presence */ }; =20 /* diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 7de8069d15b6..b33ad1d21d44 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -32,6 +32,8 @@ struct cachefiles_kiocb { u64 b_writing; }; =20 +#define IS_ERR_VALUE_LL(x) unlikely((x) >=3D (unsigned long long)-MAX_ERRN= O) + static inline void cachefiles_put_kiocb(struct cachefiles_kiocb *ki) { if (refcount_dec_and_test(&ki->ki_refcnt)) { @@ -193,60 +195,81 @@ static int cachefiles_read(struct netfs_cache_resourc= es *cres, } =20 /* - * Query the occupancy of the cache in a region, returning where the next = chunk - * of data starts and how long it is. + * Query the occupancy of the cache in a region, returning the extent of t= he + * next two chunks of cached data and the next hole. */ static int cachefiles_query_occupancy(struct netfs_cache_resources *cres, - uoff_t start, size_t len, size_t granularity, - uoff_t *_data_start, size_t *_data_len) + struct fscache_occupancy *occ) { struct cachefiles_object *object; + struct inode *inode; struct file *file; - loff_t off, off2; - - *_data_start =3D -1; - *_data_len =3D 0; + uoff_t read_limit; + loff_t ret; + int i; =20 if (!fscache_wait_for_operation(cres, FSCACHE_WANT_READ)) return -ENOBUFS; =20 object =3D cachefiles_cres_object(cres); file =3D cachefiles_cres_file(cres); - granularity =3D max_t(size_t, object->volume->cache->bsize, granularity); + inode =3D file_inode(file); + occ->granularity =3D object->volume->cache->bsize; + /* Read read_limit before content_info. */ + read_limit =3D atomic64_read_acquire(&object->read_limit); + + _enter("%pD,%llu,%llx-%llx/%llx", + file, inode->i_ino, occ->query_from, occ->query_to, read_limit); + + if (read_limit =3D=3D 0) + goto done; + + switch (READ_ONCE(object->content_info)) { + case CACHEFILES_CONTENT_ALL: + case CACHEFILES_CONTENT_SINGLE: + if (read_limit > occ->query_from) { + occ->cached_from[0] =3D 0; + occ->cached_to[0] =3D read_limit; + occ->cached_type[0] =3D FSCACHE_EXTENT_DATA; + occ->query_from =3D ULLONG_MAX; + } + goto done; + default: + break; + } =20 - _enter("%pD,%llu,%llx,%zx/%llx", - file, file_inode(file)->i_ino, start, len, - i_size_read(file_inode(file))); + for (i =3D 0; i < ARRAY_SIZE(occ->cached_from); i++) { + ret =3D cachefiles_inject_read_error(); + if (ret =3D=3D 0) + ret =3D vfs_llseek(file, occ->query_from, SEEK_DATA); + if (IS_ERR_VALUE_LL(ret)) { + if (ret !=3D -ENXIO) + return ret; + occ->query_from =3D ULLONG_MAX; + goto done; + } + occ->cached_type[i] =3D FSCACHE_EXTENT_DATA; + occ->cached_from[i] =3D ret; + occ->query_from =3D ret; + + ret =3D cachefiles_inject_read_error(); + if (ret =3D=3D 0) + ret =3D vfs_llseek(file, occ->query_from, SEEK_HOLE); + if (IS_ERR_VALUE_LL(ret)) { + if (ret !=3D -ENXIO) + return ret; + occ->query_from =3D ULLONG_MAX; + goto done; + } + occ->cached_to[i] =3D ret; + occ->query_from =3D ret; + if (occ->query_from >=3D occ->query_to) + break; + } =20 - off =3D cachefiles_inject_read_error(); - if (off =3D=3D 0) - off =3D vfs_llseek(file, start, SEEK_DATA); - if (off =3D=3D -ENXIO) - return -ENODATA; /* Beyond EOF */ - if (off < 0 && off >=3D (loff_t)-MAX_ERRNO) - return -ENOBUFS; /* Error. */ - if (round_up(off, granularity) >=3D start + len) - return -ENODATA; /* No data in range */ - - off2 =3D cachefiles_inject_read_error(); - if (off2 =3D=3D 0) - off2 =3D vfs_llseek(file, off, SEEK_HOLE); - if (off2 =3D=3D -ENXIO) - return -ENODATA; /* Beyond EOF */ - if (off2 < 0 && off2 >=3D (loff_t)-MAX_ERRNO) - return -ENOBUFS; /* Error. */ - - /* Round away partial blocks */ - off =3D round_up(off, granularity); - off2 =3D round_down(off2, granularity); - if (off2 <=3D off) - return -ENODATA; - - *_data_start =3D off; - if (off2 > start + len) - *_data_len =3D len; - else - *_data_len =3D off2 - off; +done: + _debug("query[0] %llx-%llx", occ->cached_from[0], occ->cached_to[0]); + _debug("query[1] %llx-%llx", occ->cached_from[1], occ->cached_to[1]); return 0; } =20 @@ -377,7 +400,7 @@ static int cachefiles_write(struct netfs_cache_resource= s *cres, =20 static inline enum netfs_io_source cachefiles_do_prepare_read(struct netfs_cache_resources *cres, - uoff_t start, size_t *_len, loff_t i_size, + uoff_t start, size_t *_len, uoff_t i_size, unsigned long *_flags, ino_t netfs_ino) { enum cachefiles_prepare_read_trace why; @@ -478,18 +501,6 @@ cachefiles_do_prepare_read(struct netfs_cache_resource= s *cres, return ret; } =20 -/* - * Prepare a read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ -static enum netfs_io_source cachefiles_prepare_read(struct netfs_io_subreq= uest *subreq, - uoff_t i_size) -{ - return cachefiles_do_prepare_read(&subreq->rreq->cache_resources, - subreq->start, &subreq->len, i_size, - &subreq->flags, subreq->rreq->inode->i_ino); -} - /* * Prepare for a write to occur. */ @@ -504,7 +515,7 @@ int __cachefiles_prepare_write(struct cachefiles_object= *object, int ret; =20 /* Round to DIO size */ - start =3D round_down(*_start, PAGE_SIZE); + start =3D round_down(*_start, cache->bsize); if (start !=3D *_start || *_len > upper_len) { /* Probably asked to cache a streaming write written into the * pagecache when the cookie was temporarily out of service to @@ -514,7 +525,7 @@ int __cachefiles_prepare_write(struct cachefiles_object= *object, return -ENOBUFS; } =20 - *_len =3D round_up(len, PAGE_SIZE); + *_len =3D round_up(len, cache->bsize); =20 /* We need to work out whether there's sufficient disk space to perform * the write - but we can skip that check if we have space already @@ -540,7 +551,7 @@ int __cachefiles_prepare_write(struct cachefiles_object= *object, * space, we need to see if it's fully allocated. If it's not, we may * want to cull it. */ - if (cachefiles_has_space(cache, 0, *_len / PAGE_SIZE, + if (cachefiles_has_space(cache, 0, *_len / cache->bsize, cachefiles_has_space_check) =3D=3D 0) return 0; /* Enough space to simply overwrite the whole block */ =20 @@ -572,7 +583,7 @@ int __cachefiles_prepare_write(struct cachefiles_object= *object, return ret; =20 check_space: - return cachefiles_has_space(cache, 0, *_len / PAGE_SIZE, + return cachefiles_has_space(cache, 0, *_len / cache->bsize, cachefiles_has_space_for_write); } =20 @@ -635,9 +646,9 @@ static void cachefiles_issue_write(struct netfs_io_subr= equest *subreq) wreq->debug_id, subreq->debug_index, start, start + len - 1); =20 /* We need to start on the cache granularity boundary */ - off =3D start & (CACHEFILES_DIO_BLOCK_SIZE - 1); + off =3D start & (cache->bsize - 1); if (off) { - pre =3D CACHEFILES_DIO_BLOCK_SIZE - off; + pre =3D cache->bsize - off; if (pre >=3D len) { fscache_count_dio_misfit(); netfs_write_subrequest_terminated(subreq, len); @@ -651,8 +662,8 @@ static void cachefiles_issue_write(struct netfs_io_subr= equest *subreq) =20 /* We also need to end on the cache granularity boundary */ if (start + len =3D=3D wreq->i_size) { - size_t part =3D len % CACHEFILES_DIO_BLOCK_SIZE; - size_t need =3D CACHEFILES_DIO_BLOCK_SIZE - part; + size_t part =3D len & (cache->bsize - 1); + size_t need =3D cache->bsize - part; =20 if (part && stream->submit_extendable_to >=3D need) { len +=3D need; @@ -661,7 +672,7 @@ static void cachefiles_issue_write(struct netfs_io_subr= equest *subreq) } } =20 - post =3D len & (CACHEFILES_DIO_BLOCK_SIZE - 1); + post =3D len & (cache->bsize - 1); if (post) { len -=3D post; if (len =3D=3D 0) { @@ -688,6 +699,198 @@ static void cachefiles_issue_write(struct netfs_io_su= brequest *subreq) netfs_write_subrequest_terminated, subreq); } =20 +/* + * Collect the result of buffered writeback to the cache. This includes + * copying a read to the cache. Netfslib collates the results, which might + * occur out of order, and delivers them to the cache so that it can updat= e its + * content record. + * + * block_type is one of: + * - NETFS_CACHE_COLLECT_WRITE_DATA for a contiguous block of data + * - NETFS_CACHE_COLLECT_WRITE_GAP if a discontiguity was skipped + * - NETFS_CACHE_COLLECT_WRITE_CANCEL for a hole due to a failed/cancelled= write + * + * The writes we made are all rounded out at both sides to the nearest DIO + * block boundary, so if the final block contains the EOF in the middle of= it + * (rather than at the end), padding will have been written to the file. = The + * backing file's filesize will have been updated if the write extended the + * file; the filesize may still change due to outstanding subreqs. + * + * The metadata in the cache file xattr records the size of the object we = have + * stored, but the cache file EOF only goes up to where we've cached data = to + * and, furthermore, is rounded up to the nearest DIO block boundary. + * + * Concurrent updates should be protected against by the caller. Netfslib + * holds NETFS_ICTX_WB_LOCK as a lock on writeback requests. DIO writes + * invalidate the cookie and caching is kept disabled until all users have + * unused the cookie. + */ +static void cachefiles_collect_write(struct netfs_io_request *wreq, + uoff_t start, size_t len, + enum netfs_cache_collect block_type) +{ + struct netfs_cache_resources *cres =3D &wreq->cache_resources; + struct cachefiles_object *object =3D cachefiles_cres_object(cres); + struct cachefiles_cache *cache =3D object->volume->cache; + struct inode *inode; + struct file *file =3D cachefiles_cres_file(cres); + uoff_t read_limit; + uoff_t old_size =3D cres->cache_i_size; + uoff_t new_size; + uoff_t data_to =3D object->object_size; + uoff_t end =3D start + len; + int ret; + + if (!file) + return; + + inode =3D file_inode(file); + new_size =3D i_size_read(inode); + + _enter("%llx,%zx,%x", start, len, cache->bsize); + + if (WARN_ON(old_size & (cache->bsize - 1)) || + WARN_ON(new_size & (cache->bsize - 1)) || + WARN_ON(start & (cache->bsize - 1)) || + WARN_ON(len & (cache->bsize - 1))) { + trace_cachefiles_io_error(object, inode, -EIO, + cachefiles_trace_alignment_error); + cachefiles_remove_object_xattr(cache, object, file->f_path.dentry); + return; + } + + /* If this is recording a gap, due to discontiguous writes or lack of + * cache space, then a hole may have been introduced into the backing + * file. Treat it as a zero-length data block. + */ + if (block_type =3D=3D NETFS_CACHE_COLLECT_WRITE_GAP || + block_type =3D=3D NETFS_CACHE_COLLECT_WRITE_CANCEL) { + start =3D end; + len =3D 0; + } + + /* Zeroth case: Single monolithic files are handled specially. + */ + if (wreq->origin =3D=3D NETFS_WRITEBACK_SINGLE) { + if (block_type =3D=3D NETFS_CACHE_COLLECT_WRITE_GAP || + block_type =3D=3D NETFS_CACHE_COLLECT_WRITE_CANCEL) { + trace_cachefiles_trunc(object, inode, data_to, 0, + cachefiles_trunc_zap); + ret =3D cachefiles_inject_remove_error(); + if (ret =3D=3D 0) + ret =3D vfs_truncate(&file->f_path, 0); + if (ret < 0) { + trace_cachefiles_io_error(object, inode, ret, + cachefiles_trace_trunc_error); + cachefiles_io_error_obj(object, "truncate failed %d", ret); + cachefiles_remove_object_xattr(cache, object, file->f_path.dentry); + return; + } + + object->content_info =3D CACHEFILES_CONTENT_NO_DATA; + read_limit =3D 0; + } else { + object->content_info =3D CACHEFILES_CONTENT_SINGLE; + read_limit =3D len; + } + goto update_sizes_2; + } + + /* First case: The backing file was empty. */ + if (old_size =3D=3D 0) { + if (start =3D=3D 0) + object->content_info =3D CACHEFILES_CONTENT_ALL; + else + object->content_info =3D CACHEFILES_CONTENT_BACKFS_MAP; + goto update_sizes; + } + + /* Second case: The backing file is entirely within the old object size + * and thus there can be no partial tail block to deal with in the + * cache file. + */ + if (old_size <=3D data_to) { + if (start > old_size) + goto discontiguous; + goto update_sizes; + } + + /* Third case: The write happened entirely within the bounds of the + * current cache file's size. + */ + if (end <=3D old_size) + goto update_sizes; + + /* Fourth case: The write overwrote the partial tail block and extended + * the file. We only need to update the object size because netfslib + * rounds out/pads cache writes to whole disk blocks. + */ + if (start < old_size) + goto update_sizes; + + /* Fifth case: The write started from the end of the whole tail block + * and extended the file. Just extend our notion of the filesize. + */ + if (start =3D=3D old_size && old_size =3D=3D data_to) + goto update_sizes; + + /* Sixth case: The write continued on from the partial tail block and + * extended the file. Need to clear the gap. + */ + if (start =3D=3D old_size && old_size > data_to) + goto clear_gap; + +discontiguous: + /* Seventh case: The write was beyond the EOF on the cache file, so now + * there's a hole in the file and we can no longer say in the metadata + * that we can assume we have it all. We may also need to clear the + * end of the partial tail block. + */ + /* TODO: For the moment, we will have to use SEEK_HOLE/SEEK_DATA. */ + if (object->content_info !=3D CACHEFILES_CONTENT_BACKFS_MAP) { + object->content_info =3D CACHEFILES_CONTENT_BACKFS_MAP; + trace_cachefiles_coherency(object, inode->i_ino, data_to, NULL, + CACHEFILES_CONTENT_BACKFS_MAP, + cachefiles_coherency_discontiguous); + } + +clear_gap: + /* We need to clear any partial padding that got jumped over. It + * *should* be all zeros, but shared-writable mmap exists... + */ + if (old_size > data_to) { + trace_cachefiles_trunc(object, inode, data_to, old_size, + cachefiles_trunc_clear_padding); + ret =3D cachefiles_inject_write_error(); + if (ret =3D=3D 0) + ret =3D vfs_fallocate(file, FALLOC_FL_ZERO_RANGE, + data_to, old_size - data_to); + if (ret < 0) { + trace_cachefiles_io_error(object, inode, ret, + cachefiles_trace_fallocate_error); + cachefiles_io_error_obj(object, "fallocate zero pad failed %d", ret); + cachefiles_remove_object_xattr(cache, object, file->f_path.dentry); + return; + } + } + +update_sizes: + read_limit =3D umax(old_size, end); +update_sizes_2: + cres->cache_i_size =3D read_limit; + + /* We need to be careful setting the object_size: we may have written + * more to the cache than to the server (due to cache DIO rounding) and + * the i_size set on the netfs inode may include unwritten data that + * the server doesn't know about yet. + */ + object->object_size =3D umin(read_limit, wreq->i_size); + + /* Raise the limit at which reads can access the file. */ + /* Update read_limit after content_info */ + atomic64_set_release(&object->read_limit, read_limit); +} + /* * Clean up an operation. */ @@ -705,10 +908,10 @@ static const struct netfs_cache_ops cachefiles_netfs_= cache_ops =3D { .read =3D cachefiles_read, .write =3D cachefiles_write, .issue_write =3D cachefiles_issue_write, - .prepare_read =3D cachefiles_prepare_read, .prepare_write =3D cachefiles_prepare_write, .prepare_write_subreq =3D cachefiles_prepare_write_subreq, .query_occupancy =3D cachefiles_query_occupancy, + .collect_write =3D cachefiles_collect_write, }; =20 /* @@ -718,13 +921,19 @@ bool cachefiles_begin_operation(struct netfs_cache_re= sources *cres, enum fscache_want_state want_state) { struct cachefiles_object *object =3D cachefiles_cres_object(cres); + struct file *file; + + cres->dio_size =3D object->volume->cache->bsize; =20 if (!cachefiles_cres_file(cres)) { cres->ops =3D &cachefiles_netfs_cache_ops; if (object->file) { spin_lock(&object->lock); - if (!cres->cache_priv2 && object->file) - cres->cache_priv2 =3D get_file(object->file); + file =3D object->file; + if (!cres->cache_priv2 && file) { + cres->cache_priv2 =3D get_file(file); + cres->cache_i_size =3D i_size_read(file_inode(file)); + } spin_unlock(&object->lock); } } diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 88955249a1a6..a30df0f91e5e 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -414,7 +414,6 @@ struct file *cachefiles_create_tmpfile(struct cachefile= s_object *object) struct dentry *fan =3D volume->fanout[(u8)object->cookie->key_hash]; struct file *file; const struct path parentpath =3D { .mnt =3D cache->mnt, .dentry =3D fan }; - uint64_t ni_size; long ret; =20 =20 @@ -442,23 +441,6 @@ struct file *cachefiles_create_tmpfile(struct cachefil= es_object *object) if (!cachefiles_mark_inode_in_use(object, file_inode(file))) WARN_ON(1); =20 - ni_size =3D object->cookie->object_size; - ni_size =3D round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); - - if (ni_size > 0) { - trace_cachefiles_trunc(object, file_inode(file), 0, ni_size, - cachefiles_trunc_expand_tmpfile); - ret =3D cachefiles_inject_write_error(); - if (ret =3D=3D 0) - ret =3D vfs_truncate(&file->f_path, ni_size); - if (ret < 0) { - trace_cachefiles_vfs_error( - object, file_inode(file), ret, - cachefiles_trace_trunc_error); - goto err_unuse; - } - } - ret =3D -EINVAL; if (unlikely(!file->f_op->read_iter) || unlikely(!file->f_op->write_iter)) { @@ -467,6 +449,7 @@ struct file *cachefiles_create_tmpfile(struct cachefile= s_object *object) } out: cachefiles_end_secure(cache, saved_cred); + object->content_info =3D CACHEFILES_CONTENT_ALL; return file; =20 err_unuse: diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c index 92990cfdcca7..5c1794beca93 100644 --- a/fs/cachefiles/xattr.c +++ b/fs/cachefiles/xattr.c @@ -43,6 +43,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) struct dentry *dentry; struct file *file =3D object->file; unsigned int len =3D object->cookie->aux_len; + uoff_t object_size =3D object->cookie->object_size; int ret; =20 if (!file) @@ -55,7 +56,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) if (!buf) return -ENOMEM; =20 - buf->object_size =3D cpu_to_be64(object->cookie->object_size); + buf->object_size =3D cpu_to_be64(object_size); buf->zero_point =3D 0; buf->type =3D CACHEFILES_COOKIE_TYPE_DATA; buf->content =3D object->content_info; @@ -79,7 +80,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) trace_cachefiles_vfs_error(object, file_inode(file), ret, cachefiles_trace_setxattr_error); trace_cachefiles_coherency(object, file_inode(file)->i_ino, - buf->data, buf->content, + object_size, buf->data, buf->content, cachefiles_coherency_set_fail); if (ret !=3D -ENOMEM) cachefiles_io_error_obj( @@ -87,7 +88,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object = *object) "Failed to set xattr with error %d", ret); } else { trace_cachefiles_coherency(object, file_inode(file)->i_ino, - buf->data, buf->content, + object_size, buf->data, buf->content, cachefiles_coherency_set_ok); } =20 @@ -103,10 +104,12 @@ int cachefiles_check_auxdata(struct cachefiles_object= *object, struct file *file { struct cachefiles_xattr *buf; struct dentry *dentry =3D file->f_path.dentry; + struct inode *inode =3D file_inode(file); unsigned int len =3D object->cookie->aux_len, tlen; const void *p =3D fscache_get_aux(object->cookie); enum cachefiles_coherency_trace why; ssize_t xlen; + uoff_t obj_size; int ret =3D -ESTALE; =20 tlen =3D sizeof(struct cachefiles_xattr) + len; @@ -121,34 +124,39 @@ int cachefiles_check_auxdata(struct cachefiles_object= *object, struct file *file if (xlen !=3D tlen) { if (xlen < 0) { ret =3D xlen; - trace_cachefiles_vfs_error(object, file_inode(file), xlen, + trace_cachefiles_vfs_error(object, inode, xlen, cachefiles_trace_getxattr_error); } if (xlen =3D=3D -EIO) cachefiles_io_error_obj( object, "Failed to read aux with error %zd", xlen); + obj_size =3D 0; why =3D cachefiles_coherency_check_xattr; goto out; } =20 + obj_size =3D be64_to_cpu(buf->object_size); if (buf->type !=3D CACHEFILES_COOKIE_TYPE_DATA) { why =3D cachefiles_coherency_check_type; } else if (memcmp(buf->data, p, len) !=3D 0) { why =3D cachefiles_coherency_check_aux; - } else if (be64_to_cpu(buf->object_size) !=3D object->cookie->object_size= ) { + } else if (obj_size !=3D object->cookie->object_size) { why =3D cachefiles_coherency_check_objsize; } else if (buf->content =3D=3D CACHEFILES_CONTENT_DIRTY) { // TODO: Begin conflict resolution pr_warn("Dirty object in cache\n"); why =3D cachefiles_coherency_check_dirty; } else { + object->content_info =3D buf->content; + object->object_size =3D obj_size; + atomic64_set(&object->read_limit, i_size_read(inode)); why =3D cachefiles_coherency_check_ok; ret =3D 0; } =20 out: - trace_cachefiles_coherency(object, file_inode(file)->i_ino, + trace_cachefiles_coherency(object, inode->i_ino, obj_size, buf->data, buf->content, why); kfree(buf); return ret; @@ -163,6 +171,9 @@ int cachefiles_remove_object_xattr(struct cachefiles_ca= che *cache, { int ret; =20 + trace_cachefiles_coherency(object, d_inode(dentry)->i_ino, 0, NULL, 0, + cachefiles_coherency_remove); + ret =3D cachefiles_inject_remove_error(); if (ret =3D=3D 0) { ret =3D mnt_want_write(cache->mnt); diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 03e095060162..5af849b2f283 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -137,21 +137,6 @@ static ssize_t netfs_prepare_read_iterator(struct netf= s_io_subrequest *subreq) return subreq->len; } =20 -static enum netfs_io_source netfs_cache_prepare_read(struct netfs_io_reque= st *rreq, - struct netfs_io_subrequest *subreq, - uoff_t i_size) -{ - struct netfs_cache_resources *cres =3D &rreq->cache_resources; - enum netfs_io_source source; - - if (!cres->ops) - return NETFS_DOWNLOAD_FROM_SERVER; - source =3D cres->ops->prepare_read(subreq, i_size); - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - return source; - -} - /* * Issue a read against the cache. * - Eats the caller's ref on subreq. @@ -166,6 +151,19 @@ static void netfs_read_cache_to_pagecache(struct netfs= _io_request *rreq, netfs_cache_read_terminated, subreq); } =20 +int netfs_read_query_cache(struct netfs_io_request *rreq, struct fscache_o= ccupancy *occ) +{ + struct netfs_cache_resources *cres =3D &rreq->cache_resources; + + occ->granularity =3D PAGE_SIZE; + if (occ->query_from >=3D occ->query_to) + return 0; + if (!cres->ops) + return 0; + occ->query_from =3D round_up(occ->query_from, occ->granularity); + return cres->ops->query_occupancy(cres, occ); +} + void netfs_queue_read(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq) { @@ -218,14 +216,53 @@ static void netfs_issue_read(struct netfs_io_request = *rreq, */ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { + struct fscache_occupancy _occ =3D { + .query_from =3D rreq->start, + .query_to =3D rreq->start + rreq->len, + .cached_from[0] =3D 0, + .cached_to[0] =3D 0, + .cached_from[1] =3D ULLONG_MAX, + .cached_to[1] =3D ULLONG_MAX, + }; + struct fscache_occupancy *occ =3D &_occ; ssize_t size =3D rreq->len; uoff_t start =3D rreq->start; int ret =3D 0; =20 do { + int (*prepare_read)(struct netfs_io_subrequest *subreq) =3D NULL; struct netfs_io_subrequest *subreq; - enum netfs_io_source source =3D NETFS_SOURCE_UNKNOWN; ssize_t slice; + uoff_t hole_to, cache_to; + + /* If we don't have any, find out the next couple of data + * extents from the cache, containing of following the + * specified start offset. Holes have to be fetched from the + * server; data regions from the cache. + */ + hole_to =3D occ->cached_from[0]; + cache_to =3D occ->cached_to[0]; + if (start >=3D cache_to) { + /* Extent exhausted; shuffle down. */ + int i; + + for (i =3D 0; i < ARRAY_SIZE(occ->cached_from) - 1; i++) { + occ->cached_from[i] =3D occ->cached_from[i + 1]; + occ->cached_to[i] =3D occ->cached_to[i + 1]; + occ->cached_type[i] =3D occ->cached_type[i + 1]; + } + occ->cached_from[i] =3D ULLONG_MAX; + occ->cached_to[i] =3D ULLONG_MAX; + + if (occ->cached_from[0] !=3D ULLONG_MAX) + continue; + + /* Get new extents */ + ret =3D netfs_read_query_cache(rreq, occ); + if (ret < 0) + break; + continue; + } =20 subreq =3D netfs_alloc_subrequest(rreq); if (!subreq) { @@ -238,63 +275,75 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) =20 netfs_queue_read(rreq, subreq); =20 - source =3D netfs_cache_prepare_read(rreq, subreq, rreq->i_size); - subreq->source =3D source; - if (source =3D=3D NETFS_DOWNLOAD_FROM_SERVER) { - uoff_t zero_point =3D netfs_read_zero_point(rreq->inode); - uoff_t zp =3D umin(zero_point, rreq->i_size); - size_t len =3D subreq->len; - - if (unlikely(rreq->origin =3D=3D NETFS_READ_SINGLE)) - zp =3D rreq->i_size; - if (subreq->start >=3D zp) { - subreq->source =3D source =3D NETFS_FILL_WITH_ZEROES; - goto fill_with_zeroes; + uoff_t zero_point =3D netfs_read_zero_point(rreq->inode); + uoff_t zlimit =3D umin(zero_point, rreq->i_size); + + _debug("rsub %llx %llx-%llx", subreq->start, hole_to, cache_to); + + if (start >=3D hole_to && start < cache_to) { + /* Overlap with a cached region, where the cache may + * record a block of zeroes. + */ + _debug("cached s=3D%llx c=3D%llx l=3D%zx", start, cache_to, size); + subreq->len =3D umin(cache_to - start, size); + subreq->len =3D round_up(subreq->len, occ->granularity); + if (occ->cached_type[0] =3D=3D FSCACHE_EXTENT_ZERO) { + subreq->source =3D NETFS_FILL_WITH_ZEROES; + netfs_stat(&netfs_n_rh_zero); + } else { + subreq->source =3D NETFS_READ_FROM_CACHE; + prepare_read =3D rreq->cache_resources.ops->prepare_read; } =20 - if (len > zp - subreq->start) - len =3D zp - subreq->start; - if (len =3D=3D 0) { - pr_err("ZERO-LEN READ: R=3D%08x[%x] l=3D%zx/%zx s=3D%llx z=3D%llx i=3D= %llx", - rreq->debug_id, subreq->debug_index, - subreq->len, size, - subreq->start, zero_point, rreq->i_size); - netfs_cancel_read(subreq, ret); - break; - } - subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); =20 - netfs_stat(&netfs_n_rh_download); - if (rreq->netfs_ops->prepare_read) { - ret =3D rreq->netfs_ops->prepare_read(subreq); - if (ret < 0) { - netfs_cancel_read(subreq, ret); - break; - } - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - } - goto issue; - } - - fill_with_zeroes: - if (source =3D=3D NETFS_FILL_WITH_ZEROES) { + } else if (subreq->start >=3D zlimit && size > 0) { + /* If this range lies beyond the zero-point, that part + * can just be cleared locally. + */ + _debug("zero %llx-%llx", start, start + size); + subreq->len =3D size; subreq->source =3D NETFS_FILL_WITH_ZEROES; - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); + if (rreq->cache_resources.ops) + __set_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); netfs_stat(&netfs_n_rh_zero); - goto issue; + } else { + /* Read a cache hole from the server. If any part of + * this range lies beyond the zero-point or the EOF, + * that part can just be cleared locally. + */ + uoff_t limit =3D min3(zlimit, start + size, hole_to); + + _debug("limit %llx %llx", rreq->i_size, zero_point); + _debug("download %llx-%llx", start, start + size); + subreq->len =3D umin(limit - subreq->start, ULONG_MAX); + subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; + if (rreq->cache_resources.ops) + __set_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); + netfs_stat(&netfs_n_rh_download); } =20 - if (source =3D=3D NETFS_READ_FROM_CACHE) { - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); - goto issue; + if (size =3D=3D 0) { + pr_err("ZERO-LEN READ: R=3D%08x[%x] l=3D%zx/%zx s=3D%llx z=3D%llx i=3D%= llx", + rreq->debug_id, subreq->debug_index, + subreq->len, size, + subreq->start, zero_point, rreq->i_size); + netfs_cancel_read(subreq, ret); + break; } =20 - pr_err("Unexpected read source %u\n", source); - WARN_ON_ONCE(1); - netfs_cancel_read(subreq, ret); - break; + rreq->io_streams[0].sreq_max_len =3D MAX_RW_COUNT; + rreq->io_streams[0].sreq_max_segs =3D INT_MAX; + + if (prepare_read) { + ret =3D prepare_read(subreq); + if (ret < 0) { + netfs_cancel_read(subreq, ret); + break; + } + trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); + } =20 - issue: slice =3D netfs_prepare_read_iterator(subreq); if (slice < 0) { ret =3D slice; @@ -306,6 +355,7 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq) if (size <=3D 0) netfs_all_subreqs_queued(rreq); =20 + trace_netfs_sreq(subreq, netfs_sreq_trace_submit); netfs_issue_read(rreq, subreq); netfs_maybe_bulk_drop_ra_refs(rreq); =20 diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index ead22980075f..49b47252f675 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -54,9 +54,6 @@ void netfs_update_i_size(struct netfs_inode *ctx, struct = inode *inode, i_size =3D i_size_read(inode); if (end > i_size) { i_size_write(inode, end); -#if IS_ENABLED(CONFIG_FSCACHE) - fscache_update_cookie(ctx->cache, NULL, &end); -#endif =20 gap =3D SECTOR_SIZE - (i_size & (SECTOR_SIZE - 1)); if (copied > gap) { diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index c50750b72262..5c05b0dcb143 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -23,6 +23,8 @@ /* * buffered_read.c */ +int netfs_read_query_cache(struct netfs_io_request *rreq, + struct fscache_occupancy *occ); void netfs_queue_read(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq); void netfs_cache_read_terminated(void *priv, ssize_t transferred_or_error); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 3460aa1c4af1..13816a02a798 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -44,6 +44,7 @@ struct netfs_io_request *netfs_alloc_request(struct addre= ss_space *mapping, rreq->gfp =3D gfp; rreq->start =3D start; rreq->collected_to =3D start; + rreq->cache_coll_to =3D start; rreq->cleaned_to =3D start; rreq->len =3D len; rreq->progress_at =3D 0; diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 61dbd644d6bb..96223f62516a 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -272,6 +272,7 @@ void netfs_retry_reads(struct netfs_io_request *rreq) struct netfs_io_stream *stream =3D &rreq->io_streams[0]; =20 netfs_stat(&netfs_n_rh_retry_read_req); + trace_netfs_rreq(rreq, netfs_rreq_trace_retry_begin); =20 /* Wait for all outstanding I/O to quiesce before performing retries as * we may need to renegotiate the I/O sizes. @@ -282,6 +283,7 @@ void netfs_retry_reads(struct netfs_io_request *rreq) =20 trace_netfs_rreq(rreq, netfs_rreq_trace_resubmit); netfs_retry_read_subrequests(rreq); + trace_netfs_rreq(rreq, netfs_rreq_trace_retry_end); } =20 /* diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index ccb5fc809d99..b370a8534324 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -58,20 +58,6 @@ static int netfs_single_begin_cache_read(struct netfs_io= _request *rreq, struct n return fscache_begin_read_operation(&rreq->cache_resources, netfs_i_cooki= e(ctx)); } =20 -static void netfs_single_cache_prepare_read(struct netfs_io_request *rreq, - struct netfs_io_subrequest *subreq) -{ - struct netfs_cache_resources *cres =3D &rreq->cache_resources; - - if (!cres->ops) { - subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; - return; - } - subreq->source =3D cres->ops->prepare_read(subreq, rreq->i_size); - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - -} - static void netfs_single_read_cache(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq) { @@ -89,6 +75,14 @@ static void netfs_single_read_cache(struct netfs_io_requ= est *rreq, */ static int netfs_single_dispatch_read(struct netfs_io_request *rreq) { + struct fscache_occupancy occ =3D { + .query_from =3D 0, + .query_to =3D rreq->len, + .cached_from[0] =3D ULLONG_MAX, + .cached_to[0] =3D ULLONG_MAX, + .cached_from[1] =3D ULLONG_MAX, + .cached_to[1] =3D ULLONG_MAX, + }; struct netfs_io_subrequest *subreq; int ret =3D 0; =20 @@ -96,14 +90,21 @@ static int netfs_single_dispatch_read(struct netfs_io_r= equest *rreq) if (!subreq) return -ENOMEM; =20 - subreq->source =3D NETFS_SOURCE_UNKNOWN; + subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; subreq->start =3D 0; subreq->len =3D rreq->len; subreq->io_iter =3D rreq->buffer.iter; =20 netfs_queue_read(rreq, subreq); =20 - netfs_single_cache_prepare_read(rreq, subreq); + /* Try to use the cache if the cache content matches the size of the + * remote file. + */ + netfs_read_query_cache(rreq, &occ); + if (occ.cached_from[0] =3D=3D 0 && + occ.cached_to[0] >=3D rreq->len) + subreq->source =3D NETFS_READ_FROM_CACHE; + switch (subreq->source) { case NETFS_DOWNLOAD_FROM_SERVER: netfs_stat(&netfs_n_rh_download); @@ -118,6 +119,12 @@ static int netfs_single_dispatch_read(struct netfs_io_= request *rreq) rreq->submitted +=3D subreq->len; break; case NETFS_READ_FROM_CACHE: + if (rreq->cache_resources.ops->prepare_read) { + ret =3D rreq->cache_resources.ops->prepare_read(subreq); + if (ret < 0) + goto cancel; + } + netfs_all_subreqs_queued(rreq); trace_netfs_sreq(subreq, netfs_sreq_trace_submit); netfs_single_read_cache(rreq, subreq); diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 6d99d4a6f780..6e8ea534230d 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -188,6 +188,26 @@ static void netfs_writeback_unlock_folios(struct netfs= _io_request *wreq, wreq->buffer.first_tail_slot =3D slot; } =20 +/* + * Collect cache results. + */ +static void netfs_cache_collect(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + enum netfs_cache_collect block_type) +{ + struct netfs_cache_resources *cres =3D &wreq->cache_resources; + + if (stream->source !=3D NETFS_WRITE_TO_CACHE || + wreq->cache_coll_to >=3D stream->collected_to) + return; + + if (cres->ops && cres->ops->collect_write) + cres->ops->collect_write(wreq, wreq->cache_coll_to, + stream->collected_to - wreq->cache_coll_to, + block_type); + wreq->cache_coll_to =3D stream->collected_to; +} + /* * Collect and assess the results of various write subrequests. We may ne= ed to * retry some of the results - or even do an RMW cycle for content crypto. @@ -235,13 +255,19 @@ static void netfs_collect_write_results(struct netfs_= io_request *wreq) /* Read first subreq pointer before IN_PROGRESS flag. */ =20 while (front) { + enum netfs_cache_collect cache_collect; + trace_netfs_collect_sreq(wreq, front); //_debug("sreq [%x] %llx %zx/%zx", // front->debug_index, front->start, front->transferred, front->l= en); =20 if (stream->collected_to < front->start) { trace_netfs_collect_gap(wreq, stream, issued_to, 'F'); + if (stream->cache_collect !=3D NETFS_CACHE_COLLECT_WRITE_GAP) + netfs_cache_collect(wreq, stream, stream->cache_collect); stream->collected_to =3D front->start; + netfs_cache_collect(wreq, stream, NETFS_CACHE_COLLECT_WRITE_GAP); + stream->cache_collect =3D NETFS_CACHE_COLLECT_WRITE_GAP; } =20 /* Stall if the front is still undergoing I/O. */ @@ -249,7 +275,6 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) notes |=3D HIT_PENDING; break; } - smp_rmb(); /* Read counters after I-P flag. */ =20 if (stream->failed) { stream->collected_to =3D front->start + front->len; @@ -262,15 +287,44 @@ static void netfs_collect_write_results(struct netfs_= io_request *wreq) stream->transferred_valid =3D true; notes |=3D MADE_PROGRESS; } - if (test_bit(NETFS_SREQ_FAILED, &front->flags)) { - stream->failed =3D true; - stream->error =3D front->error; - if (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER) - mapping_set_error(wreq->mapping, front->error); - notes |=3D NEED_REASSESS | SAW_FAILURE; + + /* Handle failed or cancelled subreqs. Failure of + * cache writes are handled differently to upload + * failures. Cache writes aren't fatal, provided we're + * not doing disconnected operation, and so we can kind + * of treat them as if they had succeeded - except that + * we need to log any holes they cause. + */ + switch (stream->source) { + case NETFS_UPLOAD_TO_SERVER: + if (test_bit(NETFS_SREQ_FAILED, &front->flags)) { + if (!stream->failed) { + stream->failed =3D true; + stream->error =3D front->error; + mapping_set_error(wreq->mapping, front->error); + break; + } + notes |=3D NEED_REASSESS | SAW_FAILURE; + } + break; + + case NETFS_WRITE_TO_CACHE: + cache_collect =3D test_bit(NETFS_SREQ_CANCELLED, &front->flags) ? + NETFS_CACHE_COLLECT_WRITE_CANCEL : + NETFS_CACHE_COLLECT_WRITE_DATA; + if (cache_collect !=3D stream->cache_collect && + stream->cache_collect !=3D NETFS_CACHE_COLLECT_WRITE_GAP) { + trace_netfs_rreq(wreq, netfs_rreq_trace_cache_fail_collect); + netfs_cache_collect(wreq, stream, stream->cache_collect); + } + stream->cache_collect =3D cache_collect; + break; + + default: + WARN_ON(1); break; } - if (front->transferred < front->len) { + if (test_bit(NETFS_SREQ_NEED_RETRY, &front->flags)) { stream->need_retry =3D true; notes |=3D NEED_RETRY | MADE_PROGRESS; break; @@ -359,6 +413,7 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) */ bool netfs_write_collection(struct netfs_io_request *wreq) { + struct netfs_io_stream *cstream =3D &wreq->io_streams[1]; struct netfs_inode *ictx =3D netfs_inode(wreq->inode); size_t transferred; bool transferred_valid =3D false; @@ -393,13 +448,19 @@ bool netfs_write_collection(struct netfs_io_request *= wreq) wreq->transferred =3D transferred; trace_netfs_rreq(wreq, netfs_rreq_trace_write_done); =20 - if (wreq->io_streams[1].active && - wreq->io_streams[1].failed && - ictx->ops->invalidate_cache) { - /* Cache write failure doesn't prevent writeback completion - * unless we're in disconnected mode. - */ - ictx->ops->invalidate_cache(wreq); + if (cstream->active) { + if (test_bit(NETFS_RREQ_CACHE_ERROR, &wreq->flags)) { + if (ictx->ops->invalidate_cache) { + /* Cache write failure doesn't prevent + * writeback completion unless we're in + * disconnected mode. + */ + trace_netfs_rreq(wreq, netfs_rreq_trace_inval_cache); + ictx->ops->invalidate_cache(wreq); + } + } else if (!cstream->failed) { + netfs_cache_collect(wreq, cstream, cstream->cache_collect); + } } =20 _debug("finished"); @@ -483,24 +544,51 @@ void netfs_write_subrequest_terminated(void *_op, ssi= ze_t transferred_or_error) =20 if (IS_ERR_VALUE(transferred_or_error)) { subreq->error =3D transferred_or_error; - /* if need retry is set, error should not matter */ - if (!test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { - set_bit(NETFS_SREQ_FAILED, &subreq->flags); - trace_netfs_failure(wreq, subreq, transferred_or_error, netfs_fail_writ= e); - } =20 switch (subreq->source) { case NETFS_WRITE_TO_CACHE: + /* We don't mark a cache-write subreq as failed. + * Instead we tell the issuer to produce dummy subreqs + * instead and make a note if we need to invalidate the + * cache at the end. We also don't pause the loop that + * grabs pages and launches upload subreqs. + * + * Note that we need to distinguish between -ENOBUFS + * (no space available in the cache) and other errors. + * In the former case, we can keep the data we have, + * though we might have to change the way the on-disk + * data is tracked. + */ netfs_stat(&netfs_n_wh_write_failed); + if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) + break; + + trace_netfs_failure(wreq, subreq, transferred_or_error, netfs_fail_writ= e); + __set_bit(NETFS_SREQ_CANCELLED, &subreq->flags); + set_bit(NETFS_RREQ_CACHE_STOP, &wreq->flags); + if (transferred_or_error =3D=3D -ENOBUFS) + trace_netfs_rreq(wreq, netfs_rreq_trace_cache_no_space); + else if (!test_and_set_bit(NETFS_RREQ_CACHE_ERROR, &wreq->flags)) + trace_netfs_rreq(wreq, netfs_rreq_trace_cache_failed); + subreq->transferred =3D subreq->len; break; + case NETFS_UPLOAD_TO_SERVER: + /* If need_retry is set, error should not matter */ + if (!test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { + set_bit(NETFS_SREQ_FAILED, &subreq->flags); + trace_netfs_failure(wreq, subreq, transferred_or_error, + netfs_fail_upload); + } + + set_bit(NETFS_RREQ_PAUSE, &wreq->flags); + trace_netfs_rreq(wreq, netfs_rreq_trace_set_pause); netfs_stat(&netfs_n_wh_upload_failed); break; + default: break; } - trace_netfs_rreq(wreq, netfs_rreq_trace_set_pause); - set_bit(NETFS_RREQ_PAUSE, &wreq->flags); } else { if (WARN(transferred_or_error > subreq->len - subreq->transferred, "Subreq excess write: R=3D%x[%x] %zd > %zu - %zu", diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 4ed50ac7c1f8..638f0122315c 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -111,6 +111,8 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, goto nomem; =20 wreq->cleaned_to =3D wreq->start; + if (wreq->cache_resources.dio_size > 1) + wreq->cache_coll_to =3D round_down(wreq->start, wreq->cache_resources.di= o_size); =20 wreq->io_streams[0].stream_nr =3D 0; wreq->io_streams[0].source =3D NETFS_UPLOAD_TO_SERVER; @@ -230,6 +232,21 @@ static void netfs_do_issue_write(struct netfs_io_strea= m *stream, =20 _enter("R=3D%x[%x],%zx", wreq->debug_id, subreq->debug_index, subreq->len= ); =20 + if (stream->source =3D=3D NETFS_WRITE_TO_CACHE && + unlikely(test_bit(NETFS_RREQ_CACHE_STOP, &wreq->flags))) { + size_t dio_size =3D wreq->cache_resources.dio_size; + size_t len, disp; + + disp =3D subreq->start & (dio_size - 1); + len =3D round_up(subreq->len + disp, dio_size); + + subreq->start -=3D disp; + subreq->len =3D len; + + __set_bit(NETFS_SREQ_CANCELLED, &subreq->flags); + return netfs_write_subrequest_terminated(subreq, subreq->len); + } + if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) return netfs_write_subrequest_terminated(subreq, subreq->error); =20 @@ -263,6 +280,7 @@ void netfs_issue_write(struct netfs_io_request *wreq, =20 if (!subreq) return; + stream->construct =3D NULL; subreq->io_iter.count =3D subreq->len; netfs_do_issue_write(stream, subreq); diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index 6cd584242af2..747d57467b4f 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -211,6 +211,7 @@ void netfs_retry_writes(struct netfs_io_request *wreq) int s; =20 netfs_stat(&netfs_n_wh_retry_write_req); + trace_netfs_rreq(wreq, netfs_rreq_trace_retry_begin); =20 /* Wait for all outstanding I/O to quiesce before performing retries as * we may need to renegotiate the I/O sizes. @@ -235,4 +236,6 @@ void netfs_retry_writes(struct netfs_io_request *wreq) netfs_retry_write_stream(wreq, stream); } } + + trace_netfs_rreq(wreq, netfs_rreq_trace_retry_end); } diff --git a/include/linux/fscache.h b/include/linux/fscache.h index e19fca38382b..f2d958bd1f48 100644 --- a/include/linux/fscache.h +++ b/include/linux/fscache.h @@ -147,6 +147,23 @@ struct fscache_cookie { }; }; =20 +enum fscache_extent_type { + FSCACHE_EXTENT_DATA, + FSCACHE_EXTENT_ZERO, +} __mode(byte); + +/* + * Cache occupancy information. + */ +struct fscache_occupancy { + unsigned long long query_from; /* Point to query from */ + unsigned long long query_to; /* Point to query to */ + unsigned long long cached_from[2]; /* Point at which cache extents start = */ + unsigned long long cached_to[2]; /* Point at which cache extents end */ + unsigned int granularity; /* Granularity desired */ + enum fscache_extent_type cached_type[2]; /* Type of cache extent */ +}; + /* * slow-path functions for when there is actually caching available, and t= he * netfs does actually have a valid token diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 7cdb14a130d9..3882b6c7aaf0 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -23,6 +23,7 @@ =20 enum netfs_sreq_ref_trace; typedef struct mempool mempool_t; +struct fscache_occupancy; struct folio_queue; =20 /** @@ -126,6 +127,12 @@ static inline struct netfs_group *netfs_folio_group(st= ruct folio *folio) return priv; } =20 +enum netfs_cache_collect { + NETFS_CACHE_COLLECT_WRITE_GAP, /* Gap in collection, no state either way= */ + NETFS_CACHE_COLLECT_WRITE_DATA, /* Currently collecting good writes */ + NETFS_CACHE_COLLECT_WRITE_CANCEL, /* Currently collecting cancelled write= s */ +}; + /* * Stream of I/O subrequests going to a particular destination, such as the * server or the local cache. This is mainly intended for writing where w= e may @@ -153,6 +160,7 @@ struct netfs_io_stream { bool need_retry; /* T if this stream needs retrying */ bool failed; /* T if this stream failed */ bool transferred_valid; /* T is ->transferred is valid */ + enum netfs_cache_collect cache_collect; /* Current writeback cache collec= t state */ }; =20 /* @@ -162,8 +170,10 @@ struct netfs_cache_resources { const struct netfs_cache_ops *ops; void *cache_priv; void *cache_priv2; + uoff_t cache_i_size; /* Initial size of cache file */ unsigned int debug_id; /* Cookie debug ID */ unsigned int inval_counter; /* object->inval_counter at begin_op */ + unsigned int dio_size; /* DIO block size */ }; =20 /* @@ -197,6 +207,7 @@ struct netfs_io_subrequest { #define NETFS_SREQ_IN_PROGRESS 8 /* Unlocked when the subrequest complete= s */ #define NETFS_SREQ_NEED_RETRY 9 /* Set if the filesystem requests a retry= */ #define NETFS_SREQ_FAILED 10 /* Set if the subreq failed unretryably */ +#define NETFS_SREQ_CANCELLED 11 /* Set if the subreq was cancelled by net= fslib */ }; =20 enum netfs_io_origin { @@ -252,6 +263,7 @@ struct netfs_io_request { uoff_t start; /* Start position */ atomic64_t issued_to; /* Write issuer folio cursor */ uoff_t collected_to; /* Point we've collected to */ + uoff_t cache_coll_to; /* Point the cache has collected to */ uoff_t cleaned_to; /* Position we've cleaned folios to */ uoff_t abandon_to; /* Position to abandon folios to */ const struct folio *no_unlock_folio; /* Don't unlock this folio after rea= d */ @@ -273,11 +285,13 @@ struct netfs_io_request { #define NETFS_RREQ_FAILED 3 /* The request failed */ #define NETFS_RREQ_RETRYING 4 /* Set if we're in the retry path */ #define NETFS_RREQ_SHORT_TRANSFER 5 /* Set if we have a short transfer */ -#define NETFS_RREQ_OFFLOAD_COLLECTION 8 /* Offload collection to workqueue= */ -#define NETFS_RREQ_NO_UNLOCK_FOLIO 9 /* Don't unlock no_unlock_folio on co= mpletion */ -#define NETFS_RREQ_FOLIO_COPY_TO_CACHE 10 /* Copy current folio to cache f= rom read */ -#define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ -#define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages = */ +#define NETFS_RREQ_CACHE_STOP 8 /* Set to stop caching (ENOBUFS or error)= */ +#define NETFS_RREQ_CACHE_ERROR 9 /* Set if we got an error from the cache= */ +#define NETFS_RREQ_OFFLOAD_COLLECTION 12 /* Offload collection to workqueu= e */ +#define NETFS_RREQ_NO_UNLOCK_FOLIO 13 /* Don't unlock no_unlock_folio on c= ompletion */ +#define NETFS_RREQ_FOLIO_COPY_TO_CACHE 14 /* Copy current folio to cache f= rom read */ +#define NETFS_RREQ_UPLOAD_TO_SERVER 15 /* Need to write to the server */ +#define NETFS_RREQ_USE_IO_ITER 16 /* Use ->io_iter rather than ->i_pages = */ #define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA ga= ve us */ #ifdef CONFIG_NETFS_PGPRIV2 #define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark @@ -359,8 +373,7 @@ struct netfs_cache_ops { /* Prepare a read operation, shortening it to a cached/uncached * boundary as appropriate. */ - enum netfs_io_source (*prepare_read)(struct netfs_io_subrequest *subreq, - uoff_t i_size); + int (*prepare_read)(struct netfs_io_subrequest *subreq); =20 /* Prepare a write subrequest, working out if we're allowed to do it * and finding out the maximum amount of data to gather before @@ -380,8 +393,17 @@ struct netfs_cache_ops { * next chunk of data starts and how long it is. */ int (*query_occupancy)(struct netfs_cache_resources *cres, - uoff_t start, size_t len, size_t granularity, - uoff_t *_data_start, size_t *_data_len); + struct fscache_occupancy *occ); + + /* Collect the result of buffered writeback to the cache. This + * includes copying a read to the cache. block_type is one of: + * - NETFS_CACHE_COLLECT_WRITE_DATA for a block of data + * - NETFS_CACHE_COLLECT_WRITE_GAP if a discontiguity was skipped + * - NETFS_CACHE_COLLECT_WRITE_CANCEL for a cancellation gap + */ + void (*collect_write)(struct netfs_io_request *wreq, + uoff_t start, size_t len, + enum netfs_cache_collect block_type); }; =20 /* High-level read API. */ diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cache= files.h index 4412d8344182..6146483300c2 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -52,6 +52,8 @@ enum cachefiles_coherency_trace { cachefiles_coherency_check_ok, cachefiles_coherency_check_type, cachefiles_coherency_check_xattr, + cachefiles_coherency_discontiguous, + cachefiles_coherency_remove, cachefiles_coherency_set_fail, cachefiles_coherency_set_ok, cachefiles_coherency_vol_check_cmp, @@ -63,9 +65,11 @@ enum cachefiles_coherency_trace { }; =20 enum cachefiles_trunc_trace { + cachefiles_trunc_clear_padding, cachefiles_trunc_dio_adjust, cachefiles_trunc_expand_tmpfile, cachefiles_trunc_shrink, + cachefiles_trunc_zap, }; =20 enum cachefiles_prepare_read_trace { @@ -80,6 +84,7 @@ enum cachefiles_prepare_read_trace { }; =20 enum cachefiles_error_trace { + cachefiles_trace_alignment_error, cachefiles_trace_fallocate_error, cachefiles_trace_getxattr_error, cachefiles_trace_link_error, @@ -136,6 +141,8 @@ enum cachefiles_error_trace { EM(cachefiles_coherency_check_ok, "OK ") \ EM(cachefiles_coherency_check_type, "BAD type") \ EM(cachefiles_coherency_check_xattr, "BAD xatt") \ + EM(cachefiles_coherency_discontiguous, "--- gap ") \ + EM(cachefiles_coherency_remove, "REMOVE ") \ EM(cachefiles_coherency_set_fail, "SET fail") \ EM(cachefiles_coherency_set_ok, "SET ok ") \ EM(cachefiles_coherency_vol_check_cmp, "VOL BAD cmp ") \ @@ -146,9 +153,11 @@ enum cachefiles_error_trace { E_(cachefiles_coherency_vol_set_ok, "VOL SET ok ") =20 #define cachefiles_trunc_traces \ + EM(cachefiles_trunc_clear_padding, "CLRPAD") \ EM(cachefiles_trunc_dio_adjust, "DIOADJ") \ EM(cachefiles_trunc_expand_tmpfile, "EXPTMP") \ - E_(cachefiles_trunc_shrink, "SHRINK") + EM(cachefiles_trunc_shrink, "SHRINK") \ + E_(cachefiles_trunc_zap, "ZAP ") =20 #define cachefiles_prepare_read_traces \ EM(cachefiles_trace_read_after_eof, "after-eof ") \ @@ -161,6 +170,7 @@ enum cachefiles_error_trace { E_(cachefiles_trace_read_seek_nxio, "seek-enxio") =20 #define cachefiles_error_traces \ + EM(cachefiles_trace_alignment_error, "align") \ EM(cachefiles_trace_fallocate_error, "fallocate") \ EM(cachefiles_trace_getxattr_error, "getxattr") \ EM(cachefiles_trace_link_error, "link") \ @@ -371,12 +381,12 @@ TRACE_EVENT(cachefiles_rename, =20 TRACE_EVENT(cachefiles_coherency, TP_PROTO(struct cachefiles_object *obj, - ino_t ino, + ino_t ino, uoff_t obj_size, const void *disk_aux, enum cachefiles_content content, enum cachefiles_coherency_trace why), =20 - TP_ARGS(obj, ino, disk_aux, content, why), + TP_ARGS(obj, ino, obj_size, disk_aux, content, why), =20 /* Note that obj may be NULL */ TP_STRUCT__entry( @@ -384,6 +394,7 @@ TRACE_EVENT(cachefiles_coherency, __field(enum cachefiles_coherency_trace, why) __field(enum cachefiles_content, content) __field(u64, ino) + __field(u64, obj_size) __field(u64, aux) __field(u64, disk_aux) ), @@ -398,6 +409,7 @@ TRACE_EVENT(cachefiles_coherency, __entry->why =3D why; __entry->content =3D content; __entry->ino =3D ino; + __entry->obj_size =3D obj_size; __entry->aux =3D be64_to_cpup((__be64 *)obj->cookie->inline_aux); =20 /* cachefiles_xattr::data is not 64-byte aligned. */ @@ -412,10 +424,11 @@ TRACE_EVENT(cachefiles_coherency, } ), =20 - TP_printk("o=3D%08x %s B=3D%llx c=3D%u aux=3D%llx dsk=3D%llx", + TP_printk("o=3D%08x %s B=3D%llx oz=3D%llx c=3D%u aux=3D%llx dsk=3D%ll= x", __entry->obj, __print_symbolic(__entry->why, cachefiles_coherency_traces), __entry->ino, + __entry->obj_size, __entry->content, __entry->aux, __entry->disk_aux) diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 1687b974900c..c395d3f5ed61 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -49,6 +49,10 @@ #define netfs_rreq_traces \ EM(netfs_rreq_trace_all_queued, "ALL-Q ") \ EM(netfs_rreq_trace_assess, "ASSESS ") \ + EM(netfs_rreq_trace_cache_cancelled, "CA-CNCL") \ + EM(netfs_rreq_trace_cache_failed, "CA-FAIL") \ + EM(netfs_rreq_trace_cache_fail_collect, "CA-F-CO") \ + EM(netfs_rreq_trace_cache_no_space, "CA-NOSP") \ EM(netfs_rreq_trace_collect, "COLLECT") \ EM(netfs_rreq_trace_complete, "COMPLET") \ EM(netfs_rreq_trace_copy, "COPY ") \ @@ -57,11 +61,14 @@ EM(netfs_rreq_trace_end_copy_to_cache, "END-C2C") \ EM(netfs_rreq_trace_free, "FREE ") \ EM(netfs_rreq_trace_intr, "INTR ") \ + EM(netfs_rreq_trace_inval_cache, "INVL-CA") \ EM(netfs_rreq_trace_ki_complete, "KI-CMPL") \ EM(netfs_rreq_trace_ra_put_ref, "RA-PUT ") \ EM(netfs_rreq_trace_recollect, "RECLLCT") \ EM(netfs_rreq_trace_redirty, "REDIRTY") \ EM(netfs_rreq_trace_resubmit, "RESUBMT") \ + EM(netfs_rreq_trace_retry_begin, "RETRY-BEGIN") \ + EM(netfs_rreq_trace_retry_end, "RETRY-END") \ EM(netfs_rreq_trace_set_abandon, "S-ABNDN") \ EM(netfs_rreq_trace_set_pause, "PAUSE ") \ EM(netfs_rreq_trace_unlock, "UNLOCK ") \ @@ -133,12 +140,12 @@ =20 #define netfs_failures \ EM(netfs_fail_check_write_begin, "check-write-begin") \ - EM(netfs_fail_copy_to_cache, "copy-to-cache") \ EM(netfs_fail_dio_read_short, "dio-read-short") \ EM(netfs_fail_dio_read_zero, "dio-read-zero") \ EM(netfs_fail_read, "read") \ EM(netfs_fail_short_read, "short-read") \ EM(netfs_fail_prepare_write, "prep-write") \ + EM(netfs_fail_upload, "upload") \ E_(netfs_fail_write, "write") =20 #define netfs_rreq_ref_traces \ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B9101443E43 for ; Mon, 24 Aug 2026 14:45:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582706; cv=none; b=aMbRGWGoEMvcEmLKhfQKJYCgCBykZp+AfFMNKRMp4YoTLHqxm1Dx6LZ/y7/uuwXLAczzAMao26AtkddpUf/UChXB7djv752yEFQrowEtVT5xcks5no+7seU0rIH2BLJtP10Bu30rKcgDg6PJQLcOFghHHE6kAfSZSCHyJNqgFhQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582706; c=relaxed/simple; bh=UNrYDMf6eAxJ0FTwzE2bwebqez3ZBmvT7ji7dFtMFXc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Owf/mhvM3Lr4qGl36Qn9dRBpKZ+Ohrj+ppS5zlZoS7JqmTdKO+erG1g8DyJ0HKNQsIvWDKew2EC9Vz7y29pQjJFz7+wgq9990h8CDMqBpqqfJqpkIvlzzEtoprSvXOfuDrXuHIBEkAU1GAXlWec/+FUzykmC2dWDH5ZYUJzQNvs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=RmQbdEvL; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="RmQbdEvL" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582703; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Iq2/sdFHEfHQRYqyMFYXhuI1Z114QvIQc+DxaEQ93hs=; b=RmQbdEvLXJ4A1hCNyVjGHtxoWwydOlY68vIgnKYVa2aFlrPvz+Se6bn2Dei/mMqdjkX72h R5b7/aQrcBoixN79YboEOAfU8T94pcSlSOjMwcfPNabKH16yV+PHFlcscMpK/LAL2XRAaK mw2cntSogAUL1VWs9xtyeA4QEMHxh7o= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-370-gg_it8jhPX6GzkTumDMaMA-1; Mon, 24 Aug 2026 10:44:59 -0400 X-MC-Unique: gg_it8jhPX6GzkTumDMaMA-1 X-Mimecast-MFC-AGG-ID: gg_it8jhPX6GzkTumDMaMA_1787582695 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 571F01955F3C; Mon, 24 Aug 2026 14:44:55 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 3EE751800348; Mon, 24 Aug 2026 14:44:49 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 23/35] netfs: Add the cache object ID to netfs_read/write tracepoints Date: Mon, 24 Aug 2026 15:41:16 +0100 Message-ID: <20260824144130.759997-24-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" Add the cache object debug ID to netfs_read/write tracepoints to make debugging easier as there's now a direct cross-reference with the cachefiles tracepoints that only log that debug ID. Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/cachefiles/io.c | 1 + fs/netfs/fscache_io.c | 2 +- include/linux/netfs.h | 3 ++- include/trace/events/netfs.h | 27 +++++++++++++++------------ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index b33ad1d21d44..a43fe168127a 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -927,6 +927,7 @@ bool cachefiles_begin_operation(struct netfs_cache_reso= urces *cres, =20 if (!cachefiles_cres_file(cres)) { cres->ops =3D &cachefiles_netfs_cache_ops; + cres->object_id =3D object->debug_id; if (object->file) { spin_lock(&object->lock); file =3D object->file; diff --git a/fs/netfs/fscache_io.c b/fs/netfs/fscache_io.c index 8bca63721eeb..056a2bae5d99 100644 --- a/fs/netfs/fscache_io.c +++ b/fs/netfs/fscache_io.c @@ -79,7 +79,7 @@ static int fscache_begin_operation(struct netfs_cache_res= ources *cres, cres->ops =3D NULL; cres->cache_priv =3D cookie; cres->cache_priv2 =3D NULL; - cres->debug_id =3D cookie->debug_id; + cres->cookie_id =3D cookie->debug_id; cres->inval_counter =3D cookie->inval_counter; =20 if (!fscache_begin_cookie_access(cookie, why)) { diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 3882b6c7aaf0..ef1068a274a8 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -171,7 +171,8 @@ struct netfs_cache_resources { void *cache_priv; void *cache_priv2; uoff_t cache_i_size; /* Initial size of cache file */ - unsigned int debug_id; /* Cookie debug ID */ + unsigned int cookie_id; /* Cache cookie debug ID */ + unsigned int object_id; /* Cache object debug ID */ unsigned int inval_counter; /* object->inval_counter at begin_op */ unsigned int dio_size; /* DIO block size */ }; diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index c395d3f5ed61..927287a7290c 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -311,6 +311,7 @@ TRACE_EVENT(netfs_read, TP_STRUCT__entry( __field(unsigned int, rreq) __field(unsigned int, cookie) + __field(unsigned int, object) __field(uoff_t, i_size) __field(uoff_t, start) __field(size_t, len) @@ -320,7 +321,8 @@ TRACE_EVENT(netfs_read, =20 TP_fast_assign( __entry->rreq =3D rreq->debug_id; - __entry->cookie =3D rreq->cache_resources.debug_id; + __entry->cookie =3D rreq->cache_resources.cookie_id; + __entry->object =3D rreq->cache_resources.object_id; __entry->i_size =3D rreq->i_size; __entry->start =3D start; __entry->len =3D len; @@ -328,10 +330,10 @@ TRACE_EVENT(netfs_read, __entry->netfs_inode =3D rreq->inode->i_ino; ), =20 - TP_printk("R=3D%08x %s c=3D%08x ni=3D%llx s=3D%llx l=3D%zx sz=3D%llx", + TP_printk("R=3D%08x %s c=3D%08x o=3D%08x ni=3D%llx s=3D%llx l=3D%zx s= z=3D%llx", __entry->rreq, __print_symbolic(__entry->what, netfs_read_traces), - __entry->cookie, + __entry->cookie, __entry->object, __entry->netfs_inode, __entry->start, __entry->len, __entry->i_size) ); @@ -555,6 +557,7 @@ TRACE_EVENT(netfs_write, TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned int, cookie) + __field(unsigned int, object) __field(unsigned int, ino) __field(enum netfs_write_trace, what) __field(uoff_t, start) @@ -562,20 +565,19 @@ TRACE_EVENT(netfs_write, ), =20 TP_fast_assign( - struct netfs_inode *__ctx =3D netfs_inode(wreq->inode); - struct fscache_cookie *__cookie =3D netfs_i_cookie(__ctx); __entry->wreq =3D wreq->debug_id; - __entry->cookie =3D __cookie ? __cookie->debug_id : 0; + __entry->cookie =3D wreq->cache_resources.cookie_id; + __entry->object =3D wreq->cache_resources.object_id; __entry->ino =3D wreq->inode->i_ino; __entry->what =3D what; __entry->start =3D wreq->start; __entry->len =3D wreq->len; ), =20 - TP_printk("R=3D%08x %s c=3D%08x i=3D%x by=3D%llx-%llx", + TP_printk("R=3D%08x %s c=3D%08x o=3D%08x i=3D%x by=3D%llx-%llx", __entry->wreq, __print_symbolic(__entry->what, netfs_write_traces), - __entry->cookie, + __entry->cookie, __entry->object, __entry->ino, __entry->start, __entry->start + __entry->len - 1) ); @@ -590,22 +592,23 @@ TRACE_EVENT(netfs_copy2cache, __field(unsigned int, rreq) __field(unsigned int, creq) __field(unsigned int, cookie) + __field(unsigned int, object) __field(unsigned int, ino) ), =20 TP_fast_assign( - struct netfs_inode *__ctx =3D netfs_inode(rreq->inode); - struct fscache_cookie *__cookie =3D netfs_i_cookie(__ctx); __entry->rreq =3D rreq->debug_id; __entry->creq =3D creq->debug_id; - __entry->cookie =3D __cookie ? __cookie->debug_id : 0; + __entry->cookie =3D rreq->cache_resources.cookie_id; + __entry->object =3D rreq->cache_resources.object_id; __entry->ino =3D rreq->inode->i_ino; ), =20 - TP_printk("R=3D%08x CR=3D%08x c=3D%08x i=3D%x ", + TP_printk("R=3D%08x CR=3D%08x c=3D%08x o=3D%08x i=3D%x ", __entry->rreq, __entry->creq, __entry->cookie, + __entry->object, __entry->ino) ); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BE25B3515CC for ; Mon, 24 Aug 2026 14:45:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582720; cv=none; b=IYKaljfH5OVz2AEirLrFdPKizFSvwewrzzskbEd5l6NvNuPm3l1fpKJJu301cKivUjXgOTNx1wZyL8VktyXcIWIwx60H+3gGRatIC7mXjjj4MLRX9hQlUgKBpApDbZJYLn9Gi8gGhPSnIQgyDmfXkPtRd3qAVkOTAwrtMZpOuyY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582720; c=relaxed/simple; bh=uxGicCKUp4L9Q4cXMZwHIssV/cZPQSbdjYxowJjzhHk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GUAiX7jL2ssAOo3P7BxotXznyiKjseN/Z3xxQEUdcIWdudVVj55Rh6GEcHvODFJjHlYsCqsHi/q4/3WSEoItm5PT81CJvesrYDIkMJeQlZqAuQZMpEQ45vdj8NQDYQ4aATPM0sm8XpaYO6YbzT35FenT8Ekr+F9jOs4FZpOtIaM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=ZOFwQU/a; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="ZOFwQU/a" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582714; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ewRjjJtQSsQHPDzITGXupEXzwZctJOCmrWbevwVivH8=; b=ZOFwQU/a37PzIcpc4gUFKaxwex7KwlyfR6s3ZFXhIMTWuBF9EixZ3WtjH1FelR48fV5REd 1doofVXFfFhyMJO+Br2oym6zQXyFs4eJhAwzy9ROx16XZMfs4d+p2F4XtITO3SlDex4VlM IfmrrPJvD7V94kcsmGEoAcGtUObqxcA= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-140-4ylE14V5MPmPHugzkfmKkQ-1; Mon, 24 Aug 2026 10:45:11 -0400 X-MC-Unique: 4ylE14V5MPmPHugzkfmKkQ-1 X-Mimecast-MFC-AGG-ID: 4ylE14V5MPmPHugzkfmKkQ_1787582704 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 84894195F16C; Mon, 24 Aug 2026 14:45:04 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1148730002ED; Mon, 24 Aug 2026 14:44:56 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Shyam Prasad N , Tom Talpey Subject: [PATCH v10 24/35] netfs: Switch to using bvecq rather than folio_queue and rolling_buffer Date: Mon, 24 Aug 2026 15:41:17 +0100 Message-ID: <20260824144130.759997-25-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Switch netfslib to using bvecq, a segmented bio_vec[] queue, instead of the folio_queue and rolling_buffer constructs, to keep track of the regions of memory it is performing I/O upon. For unbuffered/direct I/O, the iterator is extracted into the queue up front. For buffered I/O, the folios are added to the queue as the operation proceeds, much as it does now with folio_queues. For buffered reads and writes through the pagecache, bv_len is set to the length of the entire folio and then slices of the relevant parts are passed to subrequests. The bvecq structs are marked with information as to how the regions contained therein should be disposed of (unlock-only, free, unpin). When setting up a subrequest, netfslib will furnish it with a slice of the main buffer queue as a pointer to starting bvecq, slot and offset and, for the moment, an ITER_BVECQ iterator is set to cover the slice in subreq->io_iter. Notes on the implementation: (1) This patch uses the concept of a 'bvecq position', which is a tuple of { bvecq, slot, offset }. This is lighter weight than using a full iov_iter, though that would also suffice. If not NULL, the position also holds a reference on the bvecq it is pointing to. This is probably overkill as only the hindmost position (that of collection) needs to hold a reference. (2) There are three positions on the netfs_io_request struct. Not all are used by every request type. Firstly, there's ->load_cursor, which is used by buffered read and write to point to the next slot to have a folio inserted into it (either loaded from the readahead_control or from writeback_iter()). Secondly, there's ->dispatch_cursor, which is used to provide the position in the buffer from which we start dispatching a subrequest. Thirdly, there's the ->collect_cursor, which is used by the collection routines to point to the next memory region to be cleaned up. (3) There are two positions on the netfs_io_subrequest struct. Firstly, there's ->dispatch_pos, which indicates the position from which a subrequest's buffer begins. This is used as the base of the position from which to retry (advanced by ->transfer). Secondly, there's ->content, which is normally the same as ->dispatch_pos but if the bvecq chain got duplicated or the content got copied, then this will point to that and will that will be disposed of on retry. The filesystem may modify ->content, but not ->dispatch_pos. (4) Maintenance of the position structs is done with helper functions, such as bvecq_pos_attach() to hide the refcounting. (5) When sending a write to the cache, the ends of the slice will be rounded up/down to the backing file's DIO block alignment. (6) bvecq_slice() is used to select a slice of the source buffer and assign it to a subrequest. The source buffer position is advanced. (7) netfs_extract_iter() is used by unbuffered/direct I/O API functions to decant a chunk of the iov_iter supplied by the VFS into a bvecq chain - and to label the bvecqs with appropriate disposal information (e.g. unpin, free, nothing). There are further options that can be explored in the future: (1) Allow the provision of a duplicated bvecq chain for a specific slice so that the filesystem can add bits on either end (such as adding protocol headers and trailers and gluing several things together into a compound operation). (2) If a filesystem supports vectored/sparse read and write ops, it can be given a chain with discontiguities in it to perform in a single op (Ceph, for example, can do this). (3) Make MSG_SPLICE_PAGES handling read the disposal info in the bvecq and use that to indicate how it should get rid of the stuff it pasted into a sk_buff. (4) If a bounce buffer is needed (encryption, for example), the bounce buffer can be held in a bvecq and sliced up instead of the main buffer queue. (5) Get rid of subreq->io_iter and move the iov_iter stuff down into the filesystem. The I/O iterators are normally only needed transitorily, and the one currently in netfs_io_subrequest is unnecessary most of the time. folio_queue and rolling_buffer will be removed in a follow up patch. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: Namjae Jeon cc: Shyam Prasad N cc: Tom Talpey cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/afs/dir.c | 5 +- fs/afs/symlink.c | 6 +- fs/cachefiles/io.c | 12 --- fs/netfs/Makefile | 1 - fs/netfs/buffered_read.c | 123 +++++++++++++++----------- fs/netfs/direct_read.c | 78 +++++++---------- fs/netfs/direct_write.c | 69 ++++++++------- fs/netfs/internal.h | 10 +-- fs/netfs/iterator.c | 2 + fs/netfs/main.c | 6 -- fs/netfs/misc.c | 20 +---- fs/netfs/objects.c | 17 ++-- fs/netfs/read_collect.c | 116 +++++++++++++------------ fs/netfs/read_pgpriv2.c | 91 +++++++++++++------- fs/netfs/read_retry.c | 102 +++++++++++++--------- fs/netfs/read_single.c | 17 +++- fs/netfs/stats.c | 4 +- fs/netfs/write_collect.c | 60 ++++++------- fs/netfs/write_issue.c | 162 ++++++++++++++++++++++++----------- fs/netfs/write_retry.c | 62 ++++++++------ include/linux/netfs.h | 27 +++--- include/trace/events/netfs.h | 46 +++++----- 22 files changed, 575 insertions(+), 461 deletions(-) diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 1dff50323b56..b2eb5bce3e9f 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -2228,8 +2228,9 @@ static int afs_dir_writepages(struct address_space *m= apping, =20 if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) { iov_iter_bvec_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0, - i_size_read(&dvnode->netfs.inode)); - ret =3D netfs_writeback_single(mapping, wbc, &iter); + dvnode->directory_size); + ret =3D netfs_writeback_single(mapping, wbc, &iter, + i_size_read(&dvnode->netfs.inode)); if (ret =3D=3D 1) ret =3D 0; /* Skipped write due to lock conflict. */ } diff --git a/fs/afs/symlink.c b/fs/afs/symlink.c index 7fd4f985c441..71de59173ea5 100644 --- a/fs/afs/symlink.c +++ b/fs/afs/symlink.c @@ -245,9 +245,9 @@ int afs_symlink_writepages(struct address_space *mappin= g, =20 if (vnode->directory && atomic64_read(&vnode->cb_expires_at) !=3D AFS_NO_CB_PROMISE) { - iov_iter_bvec_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0, - i_size_read(&vnode->netfs.inode)); - ret =3D netfs_writeback_single(mapping, wbc, &iter); + iov_iter_bvec_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0, PAGE_SIZ= E); + ret =3D netfs_writeback_single(mapping, wbc, &iter, + i_size_read(&vnode->netfs.inode)); } =20 if (ret =3D=3D 0) { diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index a43fe168127a..23007f450ef4 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -636,7 +636,6 @@ static void cachefiles_issue_write(struct netfs_io_subr= equest *subreq) struct netfs_cache_resources *cres =3D &wreq->cache_resources; struct cachefiles_object *object =3D cachefiles_cres_object(cres); struct cachefiles_cache *cache =3D object->volume->cache; - struct netfs_io_stream *stream =3D &wreq->io_streams[subreq->stream_nr]; const struct cred *saved_cred; size_t off, pre, post, len =3D subreq->len; uoff_t start =3D subreq->start; @@ -661,17 +660,6 @@ static void cachefiles_issue_write(struct netfs_io_sub= request *subreq) } =20 /* We also need to end on the cache granularity boundary */ - if (start + len =3D=3D wreq->i_size) { - size_t part =3D len & (cache->bsize - 1); - size_t need =3D cache->bsize - part; - - if (part && stream->submit_extendable_to >=3D need) { - len +=3D need; - subreq->len +=3D need; - subreq->io_iter.count +=3D need; - } - } - post =3D len & (cache->bsize - 1); if (post) { len -=3D post; diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile index b1ea4439c1bb..421dd0be413b 100644 --- a/fs/netfs/Makefile +++ b/fs/netfs/Makefile @@ -14,7 +14,6 @@ netfs-y :=3D \ read_collect.o \ read_retry.o \ read_single.o \ - rolling_buffer.o \ write_collect.o \ write_issue.o \ write_retry.o diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 5af849b2f283..8a843822d47b 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -114,26 +114,21 @@ static int netfs_begin_cache_read(struct netfs_io_req= uest *rreq, struct netfs_in static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *sub= req) { struct netfs_io_request *rreq =3D subreq->rreq; + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; + ssize_t extracted; size_t rsize =3D subreq->len; =20 if (subreq->source =3D=3D NETFS_DOWNLOAD_FROM_SERVER) - rsize =3D umin(rsize, rreq->io_streams[0].sreq_max_len); - - subreq->len =3D rsize; - if (unlikely(rreq->io_streams[0].sreq_max_segs)) { - size_t limit =3D netfs_limit_iter(&rreq->buffer.iter, 0, rsize, - rreq->io_streams[0].sreq_max_segs); - - if (limit < rsize) { - subreq->len =3D limit; - trace_netfs_sreq(subreq, netfs_sreq_trace_limited); - } + rsize =3D umin(rsize, stream->sreq_max_len); + + bvecq_pos_set(&subreq->dispatch_pos, &rreq->dispatch_cursor); + extracted =3D bvecq_slice(&rreq->dispatch_cursor, rsize, + stream->sreq_max_segs, &subreq->nr_segs); + if (extracted < rsize) { + subreq->len =3D extracted; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); } =20 - subreq->io_iter =3D rreq->buffer.iter; - - iov_iter_truncate(&subreq->io_iter, subreq->len); - rolling_buffer_advance(&rreq->buffer, subreq->len); return subreq->len; } =20 @@ -192,6 +187,10 @@ void netfs_queue_read(struct netfs_io_request *rreq, static void netfs_issue_read(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq) { + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + switch (subreq->source) { case NETFS_DOWNLOAD_FROM_SERVER: rreq->netfs_ops->issue_read(subreq); @@ -200,7 +199,8 @@ static void netfs_issue_read(struct netfs_io_request *r= req, netfs_read_cache_to_pagecache(rreq, subreq); break; default: - __set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags); + bvecq_zero(&subreq->content, subreq->len); + subreq->transferred =3D subreq->len; subreq->error =3D 0; iov_iter_zero(subreq->len, &subreq->io_iter); subreq->transferred =3D subreq->len; @@ -229,6 +229,10 @@ static void netfs_read_to_pagecache(struct netfs_io_re= quest *rreq) uoff_t start =3D rreq->start; int ret =3D 0; =20 + _enter("R=3D%08x", rreq->debug_id); + + bvecq_pos_set(&rreq->dispatch_cursor, &rreq->load_cursor); + do { int (*prepare_read)(struct netfs_io_subrequest *subreq) =3D NULL; struct netfs_io_subrequest *subreq; @@ -373,6 +377,9 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq) =20 /* Defer error return as we may need to wait for outstanding I/O. */ cmpxchg(&rreq->error, 0, ret); + + bvecq_pos_unset(&rreq->load_cursor); + bvecq_pos_unset(&rreq->dispatch_cursor); } =20 /** @@ -420,8 +427,7 @@ void netfs_readahead(struct readahead_control *ractl) * acquires a ref on each folio that we will need to release later - * but we don't want to do that until after we've started the I/O. */ - added =3D rolling_buffer_bulk_load_from_ra(&rreq->buffer, ractl, - rreq->debug_id, rreq->gfp); + added =3D bvecq_load_from_ra(&rreq->load_cursor, ractl); if (added < 0) { ret =3D added; goto cleanup_free; @@ -430,6 +436,7 @@ void netfs_readahead(struct readahead_control *ractl) =20 rreq->submitted =3D rreq->start + added; rreq->cleaned_to =3D rreq->start; + bvecq_pos_set(&rreq->collect_cursor, &rreq->load_cursor); netfs_read_set_unlock_at(rreq); =20 netfs_read_to_pagecache(rreq); @@ -442,21 +449,21 @@ void netfs_readahead(struct readahead_control *ractl) EXPORT_SYMBOL(netfs_readahead); =20 /* - * Create a rolling buffer with a single occupying folio. + * Create a buffer queue with a single occupying folio. */ -static int netfs_create_singular_buffer(struct netfs_io_request *rreq, str= uct folio *folio, - unsigned int rollbuf_flags) +static int netfs_create_singular_buffer(struct netfs_io_request *rreq, str= uct folio *folio) { - ssize_t added; + struct bvecq *bq; + size_t fsize =3D folio_size(folio); =20 - if (rolling_buffer_init(&rreq->buffer, rreq->debug_id, ITER_DEST, rreq->g= fp) < 0) + if (bvecq_buffer_init(&rreq->load_cursor, rreq->gfp, false) < 0) return -ENOMEM; =20 - added =3D rolling_buffer_append(&rreq->buffer, folio, rollbuf_flags, rreq= ->gfp); - if (added < 0) - return added; - rreq->submitted =3D rreq->start + added; - rreq->progress_at =3D added; + bq =3D rreq->load_cursor.bvecq; + bvec_set_folio(&bq->bv[0], folio, fsize, 0); + bvecq_filled_to(bq, 1); + rreq->submitted =3D rreq->start + fsize; + rreq->progress_at =3D fsize; return 0; } =20 @@ -470,11 +477,11 @@ static int netfs_read_gaps(struct file *file, struct = folio *folio) struct netfs_group *group =3D netfs_folio_group(folio); struct netfs_folio *finfo =3D netfs_folio_info(folio); struct netfs_inode *ctx =3D netfs_inode(mapping->host); - struct folio *sink =3D NULL; - struct bio_vec *bvec; + struct bvecq *bq =3D NULL; + struct page *sink =3D NULL; unsigned int from =3D finfo->dirty_offset; unsigned int to =3D from + finfo->dirty_len; - unsigned int off =3D 0, i =3D 0; + unsigned int off =3D 0, slot =3D 0; size_t flen =3D folio_size(folio); size_t nr_bvec =3D flen / PAGE_SIZE + 2; size_t part; @@ -499,32 +506,47 @@ static int netfs_read_gaps(struct file *file, struct = folio *folio) * end get copied to, but the middle is discarded. */ ret =3D -ENOMEM; - bvec =3D kmalloc_objs(*bvec, nr_bvec); - if (!bvec) + bq =3D bvecq_alloc_chain(nr_bvec, rreq->gfp, false); + if (!bq) goto discard; + rreq->load_cursor.bvecq =3D bq; =20 - sink =3D folio_alloc(GFP_KERNEL, 0); - if (!sink) { - kfree(bvec); + sink =3D alloc_page(rreq->gfp); + if (!sink) goto discard; - } =20 trace_netfs_folio(folio, netfs_folio_trace_read_gaps); =20 - rreq->direct_bv =3D bvec; - rreq->direct_bv_count =3D nr_bvec; + for (struct bvecq *p =3D bq; p; p =3D p->next) + p->mem_type =3D BVECQ_MEM_PAGECACHE; + if (from > 0) { - bvec_set_folio(&bvec[i++], folio, from, 0); + folio_get(folio); + bvec_set_folio(&bq->bv[slot++], folio, from, 0); off =3D from; } while (off < to) { - part =3D min_t(size_t, to - off, PAGE_SIZE); - bvec_set_folio(&bvec[i++], sink, part, 0); + if (slot >=3D bq->max_slots) { + bvecq_filled_to(bq, slot); + bq =3D bq->next; + slot =3D 0; + } + part =3D min(to - off, PAGE_SIZE); + get_page(sink); + bvec_set_page(&bq->bv[slot++], sink, part, 0); off +=3D part; } - if (to < flen) - bvec_set_folio(&bvec[i++], folio, flen - to, to); - iov_iter_bvec(&rreq->buffer.iter, ITER_DEST, bvec, i, rreq->len); + if (to < flen) { + if (slot >=3D bq->max_slots) { + bvecq_filled_to(bq, slot); + bq =3D bq->next; + slot =3D 0; + } + folio_get(folio); + bvec_set_folio(&bq->bv[slot++], folio, flen - to, to); + } + bvecq_filled_to(bq, slot); + rreq->submitted =3D rreq->start + flen; =20 netfs_read_to_pagecache(rreq); @@ -541,13 +563,14 @@ static int netfs_read_gaps(struct file *file, struct = folio *folio) folio_mark_uptodate(folio); } =20 - if (sink) - folio_put(sink); + put_page(sink); folio_unlock(folio); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; =20 discard: + if (sink) + put_page(sink); netfs_put_failed_request(rreq); alloc_error: folio_unlock(folio); @@ -598,7 +621,7 @@ int netfs_read_folio(struct file *file, struct folio *f= olio) trace_netfs_read(rreq, rreq->start, rreq->len, netfs_read_trace_readpage); =20 /* Set up the output buffer */ - ret =3D netfs_create_singular_buffer(rreq, folio, 0); + ret =3D netfs_create_singular_buffer(rreq, folio); if (ret < 0) goto discard; =20 @@ -755,7 +778,7 @@ int netfs_write_begin(struct netfs_inode *ctx, trace_netfs_read(rreq, pos, len, netfs_read_trace_write_begin); =20 /* Set up the output buffer */ - ret =3D netfs_create_singular_buffer(rreq, folio, 0); + ret =3D netfs_create_singular_buffer(rreq, folio); if (ret < 0) goto error_put; =20 @@ -820,7 +843,7 @@ int netfs_prefetch_for_write(struct file *file, struct = folio *folio, trace_netfs_read(rreq, start, flen, netfs_read_trace_prefetch_for_write); =20 /* Set up the output buffer */ - ret =3D netfs_create_singular_buffer(rreq, folio, NETFS_ROLLBUF_PAGECACHE= _MARK); + ret =3D netfs_create_singular_buffer(rreq, folio); if (ret < 0) goto error_put; =20 diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index 5405e108b7a3..058cc6bb7124 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -16,44 +16,22 @@ #include #include "internal.h" =20 -static void netfs_prepare_dio_read_iterator(struct netfs_io_subrequest *su= breq) -{ - struct netfs_io_request *rreq =3D subreq->rreq; - size_t rsize; - - rsize =3D umin(subreq->len, rreq->io_streams[0].sreq_max_len); - subreq->len =3D rsize; - - if (unlikely(rreq->io_streams[0].sreq_max_segs)) { - size_t limit =3D netfs_limit_iter(&rreq->buffer.iter, 0, rsize, - rreq->io_streams[0].sreq_max_segs); - - if (limit < rsize) { - subreq->len =3D limit; - trace_netfs_sreq(subreq, netfs_sreq_trace_limited); - } - } - - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - - subreq->io_iter =3D rreq->buffer.iter; - iov_iter_truncate(&subreq->io_iter, subreq->len); - iov_iter_advance(&rreq->buffer.iter, subreq->len); -} - /* * Perform a read to a buffer from the server, slicing up the region to be= read * according to the network rsize. */ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) { + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; ssize_t size =3D rreq->len; uoff_t start =3D rreq->start; int ret; =20 + bvecq_pos_set(&rreq->dispatch_cursor, &rreq->load_cursor); + bvecq_pos_transfer(&rreq->collect_cursor, &rreq->load_cursor); + do { struct netfs_io_subrequest *subreq; - ssize_t slice; =20 subreq =3D netfs_alloc_subrequest(rreq); if (!subreq) { @@ -79,14 +57,22 @@ static void netfs_dispatch_unbuffered_reads(struct netf= s_io_request *rreq) } } =20 - netfs_prepare_dio_read_iterator(subreq); - slice =3D subreq->len; - size -=3D slice; - start +=3D slice; - rreq->submitted +=3D slice; + bvecq_pos_set(&subreq->dispatch_pos, &rreq->dispatch_cursor); + bvecq_pos_set(&subreq->content, &rreq->dispatch_cursor); + subreq->len =3D bvecq_slice(&rreq->dispatch_cursor, + umin(size, stream->sreq_max_len), + stream->sreq_max_segs, + &subreq->nr_segs); + + size -=3D subreq->len; + start +=3D subreq->len; + rreq->submitted +=3D subreq->len; if (size <=3D 0) netfs_all_subreqs_queued(rreq); =20 + iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + rreq->netfs_ops->issue_read(subreq); =20 if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags)) @@ -100,6 +86,8 @@ static void netfs_dispatch_unbuffered_reads(struct netfs= _io_request *rreq) netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } + + bvecq_pos_unset(&rreq->dispatch_cursor); } =20 /* @@ -178,25 +166,17 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kioc= b *iocb, struct iov_iter *i * buffer for ourselves as the caller's iterator will be trashed when * we return. * - * In such a case, extract an iterator to represent as much of the the - * output buffer as we can manage. Note that the extraction might not - * be able to allocate a sufficiently large bvec array and may shorten - * the request. + * Extract a buffer queue to represent as much of the output buffer as + * we can manage. The fragments are extracted into a bvecq which will + * have sufficient nodes allocated to hold all the data, though this + * may end up truncated if ENOMEM is encountered. */ - if (user_backed_iter(iter)) { - ret =3D netfs_extract_user_iter(iter, rreq->len, &rreq->buffer.iter, 0); - if (ret < 0) - goto error_put; - rreq->direct_bv =3D (struct bio_vec *)rreq->buffer.iter.bvec; - rreq->direct_bv_count =3D ret; - rreq->direct_bv_unpin =3D iov_iter_extract_will_pin(iter); - rreq->len =3D iov_iter_count(&rreq->buffer.iter); - } else { - rreq->buffer.iter =3D *iter; - rreq->len =3D orig_count; - rreq->direct_bv_unpin =3D false; - iov_iter_advance(iter, orig_count); - } + ret =3D netfs_extract_iter(iter, rreq->len, INT_MAX, + &rreq->load_cursor.bvecq, 0, rreq->gfp); + if (ret < 0) + goto error_put; + + rreq->len =3D ret; =20 // TODO: Set up bounce buffer if needed =20 diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 47ec47e3a95b..5bc3ec261f6c 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -73,7 +73,11 @@ static void netfs_unbuffered_write_collect(struct netfs_= io_request *wreq, spin_unlock(&wreq->lock); =20 wreq->transferred +=3D subreq->transferred; - iov_iter_advance(&wreq->buffer.iter, subreq->transferred); + if (subreq->transferred < subreq->len) { + bvecq_pos_unset(&wreq->dispatch_cursor); + bvecq_pos_transfer(&wreq->dispatch_cursor, &subreq->dispatch_pos); + bvecq_pos_advance(&wreq->dispatch_cursor, subreq->transferred); + } =20 stream->collected_to =3D subreq->start + subreq->transferred; wreq->collected_to =3D stream->collected_to; @@ -99,6 +103,9 @@ static int netfs_unbuffered_write(struct netfs_io_reques= t *wreq) =20 _enter("%llx", wreq->len); =20 + bvecq_pos_set(&wreq->dispatch_cursor, &wreq->load_cursor); + bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor); + if (wreq->origin =3D=3D NETFS_DIO_WRITE) inode_dio_begin(wreq->inode); =20 @@ -111,6 +118,8 @@ static int netfs_unbuffered_write(struct netfs_io_reque= st *wreq) netfs_prepare_write(wreq, stream, wreq->start + wreq->transferred); subreq =3D stream->construct; stream->construct =3D NULL; + } else { + bvecq_pos_set(&subreq->dispatch_pos, &wreq->dispatch_cursor); } =20 /* Check if (re-)preparation failed. */ @@ -120,16 +129,18 @@ static int netfs_unbuffered_write(struct netfs_io_req= uest *wreq) break; } =20 - iov_iter_truncate(&subreq->io_iter, wreq->len - wreq->transferred); + subreq->len =3D bvecq_slice(&wreq->dispatch_cursor, stream->sreq_max_len, + stream->sreq_max_segs, &subreq->nr_segs); + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + + iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, + subreq->content.bvecq, subreq->content.slot, + subreq->content.offset, + subreq->len); + if (!iov_iter_count(&subreq->io_iter)) break; =20 - subreq->len =3D netfs_limit_iter(&subreq->io_iter, 0, - stream->sreq_max_len, - stream->sreq_max_segs); - iov_iter_truncate(&subreq->io_iter, subreq->len); - stream->submit_extendable_to =3D subreq->len; - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); stream->issue_write(subreq); =20 @@ -166,9 +177,14 @@ static int netfs_unbuffered_write(struct netfs_io_requ= est *wreq) */ subreq->error =3D -EAGAIN; trace_netfs_sreq(subreq, netfs_sreq_trace_retry); + + bvecq_pos_unset(&subreq->content); + bvecq_pos_unset(&wreq->dispatch_cursor); + bvecq_pos_transfer(&wreq->dispatch_cursor, &subreq->dispatch_pos); + if (subreq->transferred > 0) { - iov_iter_advance(&wreq->buffer.iter, subreq->transferred); wreq->transferred +=3D subreq->transferred; + bvecq_pos_advance(&wreq->dispatch_cursor, subreq->transferred); } =20 if (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER && @@ -179,7 +195,6 @@ static int netfs_unbuffered_write(struct netfs_io_reque= st *wreq) __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); __clear_bit(NETFS_SREQ_BOUNDARY, &subreq->flags); __clear_bit(NETFS_SREQ_FAILED, &subreq->flags); - subreq->io_iter =3D wreq->buffer.iter; subreq->start =3D wreq->start + wreq->transferred; subreq->len =3D wreq->len - wreq->transferred; subreq->transferred =3D 0; @@ -195,6 +210,8 @@ static int netfs_unbuffered_write(struct netfs_io_reque= st *wreq) netfs_stat(&netfs_n_wh_retry_write_subreq); } =20 + bvecq_pos_unset(&wreq->dispatch_cursor); + bvecq_pos_unset(&wreq->load_cursor); netfs_unbuffered_write_done(wreq); _leave(" =3D %d", ret); return ret; @@ -213,10 +230,10 @@ static void netfs_unbuffered_write_async(struct work_= struct *work) * encrypted file. This can also be used for direct I/O writes. */ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_= iter *iter, - struct netfs_group *netfs_group) + struct netfs_group *netfs_group) { struct netfs_io_request *wreq; - ssize_t ret, n; + ssize_t ret; uoff_t start =3D iocb->ki_pos; uoff_t end =3D start + iov_iter_count(iter); size_t len =3D iov_iter_count(iter); @@ -252,25 +269,17 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kio= cb *iocb, struct iov_iter * * allocate a sufficiently large bvec array and may shorten the * request. */ - if (user_backed_iter(iter)) { - n =3D netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0); - if (n < 0) { - ret =3D n; - goto error_put; - } - wreq->direct_bv =3D (struct bio_vec *)wreq->buffer.iter.bvec; - wreq->direct_bv_count =3D n; - wreq->direct_bv_unpin =3D iov_iter_extract_will_pin(iter); - } else { - /* If this is a kernel-generated async DIO request, - * assume that any resources the iterator points to - * (eg. a bio_vec array) will persist till the end of - * the op. - */ - wreq->buffer.iter =3D *iter; - } + ssize_t n =3D netfs_extract_iter(iter, len, INT_MAX, + &wreq->load_cursor.bvecq, 0, wreq->gfp); =20 - wreq->len =3D iov_iter_count(&wreq->buffer.iter); + if (n < 0) { + ret =3D n; + goto error_put; + } + wreq->len =3D n; + _debug("dio-write %zx/%zx %u/%u", + n, len, wreq->load_cursor.bvecq->nr_slots, + wreq->load_cursor.bvecq->max_slots); } =20 __set_bit(NETFS_RREQ_USE_IO_ITER, &wreq->flags); diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 5c05b0dcb143..b402b504a370 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -7,7 +7,6 @@ =20 #include #include -#include #include #include #include @@ -71,9 +70,8 @@ static inline void netfs_proc_del_rreq(struct netfs_io_re= quest *rreq) {} /* * misc.c */ -struct folio_queue *netfs_buffer_make_space(struct netfs_io_request *rreq, - enum netfs_folioq_trace trace); -void netfs_reset_iter(struct netfs_io_subrequest *subreq); +struct bvecq *netfs_buffer_make_space(struct netfs_io_request *rreq, + enum netfs_bvecq_trace trace); void netfs_wake_collector(struct netfs_io_request *rreq); void netfs_subreq_clear_in_progress(struct netfs_io_subrequest *subreq); void netfs_wait_for_in_progress_stream(struct netfs_io_request *rreq, @@ -201,7 +199,6 @@ extern atomic_t netfs_n_wh_retry_write_req; extern atomic_t netfs_n_wh_retry_write_subreq; extern atomic_t netfs_n_wb_lock_skip; extern atomic_t netfs_n_wb_lock_wait; -extern atomic_t netfs_n_folioq; extern atomic_t netfs_n_bvecq; =20 int netfs_stats_show(struct seq_file *m, void *v); @@ -239,8 +236,7 @@ void netfs_prepare_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, uoff_t start); void netfs_reissue_write(struct netfs_io_stream *stream, - struct netfs_io_subrequest *subreq, - struct iov_iter *source); + struct netfs_io_subrequest *subreq); void netfs_issue_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream); size_t netfs_advance_write(struct netfs_io_request *wreq, diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c index e66c589ca3b0..c207eacd5c5d 100644 --- a/fs/netfs/iterator.c +++ b/fs/netfs/iterator.c @@ -156,6 +156,7 @@ ssize_t netfs_extract_iter(struct iov_iter *orig, size_= t max_len, size_t max_pag } EXPORT_SYMBOL_GPL(netfs_extract_iter); =20 +#if 0 /** * netfs_extract_user_iter - Extract the pages from a user iterator into a= bvec * @orig: The original iterator @@ -449,3 +450,4 @@ size_t netfs_limit_iter(const struct iov_iter *iter, si= ze_t start_offset, BUG(); } EXPORT_SYMBOL(netfs_limit_iter); +#endif diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 3908ea2422c8..041810707eb4 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -109,9 +109,6 @@ static int __init netfs_init(void) { int ret =3D -ENOMEM; =20 - if (mempool_init_kmalloc_pool(&netfs_folioq_pool, 100, sizeof(struct foli= o_queue)) < 0) - goto error_folioq_pool; - if (mempool_init_kmalloc_pool(&netfs_bvecq_pool, 100, BVECQ_STD_SIZE) < 0) goto error_bvecq_pool; =20 @@ -169,8 +166,6 @@ static int __init netfs_init(void) error_req: mempool_exit(&netfs_bvecq_pool); error_bvecq_pool: - mempool_exit(&netfs_folioq_pool); -error_folioq_pool: return ret; } fs_initcall(netfs_init); @@ -184,6 +179,5 @@ static void __exit netfs_exit(void) mempool_exit(&netfs_request_pool); kmem_cache_destroy(netfs_request_slab); mempool_exit(&netfs_bvecq_pool); - mempool_exit(&netfs_folioq_pool); } module_exit(netfs_exit); diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index a3cd76d584b8..a2a2aeb1bb0d 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -8,6 +8,7 @@ #include #include "internal.h" =20 +#if 0 /** * netfs_alloc_folioq_buffer - Allocate buffer space into a folio queue * @mapping: Address space to set on the folio (or NULL). @@ -103,24 +104,7 @@ void netfs_free_folioq_buffer(struct folio_queue *fq) folio_batch_release(&fbatch); } EXPORT_SYMBOL(netfs_free_folioq_buffer); - -/* - * Reset the subrequest iterator to refer just to the region remaining to = be - * read. The iterator may or may not have been advanced by socket ops or - * extraction ops to an extent that may or may not match the amount actual= ly - * read. - */ -void netfs_reset_iter(struct netfs_io_subrequest *subreq) -{ - struct iov_iter *io_iter =3D &subreq->io_iter; - size_t remain =3D subreq->len - subreq->transferred; - - if (io_iter->count > remain) - iov_iter_advance(io_iter, io_iter->count - remain); - else if (io_iter->count < remain) - iov_iter_revert(io_iter, remain - io_iter->count); - iov_iter_truncate(&subreq->io_iter, remain); -} +#endif =20 /** * netfs_dirty_folio - Mark folio dirty and pin a cache object for writeba= ck diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 13816a02a798..740971955198 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -133,7 +133,6 @@ static void netfs_free_request_rcu(struct rcu_head *rcu) static void netfs_deinit_request(struct netfs_io_request *rreq) { struct netfs_inode *ictx =3D netfs_inode(rreq->inode); - unsigned int i; =20 trace_netfs_rreq(rreq, netfs_rreq_trace_free); =20 @@ -148,16 +147,10 @@ static void netfs_deinit_request(struct netfs_io_requ= est *rreq) rreq->netfs_ops->free_request(rreq); if (rreq->cache_resources.ops) rreq->cache_resources.ops->end_operation(&rreq->cache_resources); - if (rreq->direct_bv) { - for (i =3D 0; i < rreq->direct_bv_count; i++) { - if (rreq->direct_bv[i].bv_page) { - if (rreq->direct_bv_unpin) - unpin_user_page(rreq->direct_bv[i].bv_page); - } - } - kvfree(rreq->direct_bv); - } - rolling_buffer_clear(&rreq->buffer); + bvecq_pos_unset(&rreq->load_cursor); + bvecq_pos_unset(&rreq->dispatch_cursor); + bvecq_pos_unset(&rreq->collect_cursor); + bvecq_put(rreq->spare); =20 if (atomic_dec_and_test(&ictx->io_count)) wake_up_var(&ictx->io_count); @@ -249,6 +242,8 @@ static void netfs_free_subrequest(struct netfs_io_subre= quest *subreq) trace_netfs_sreq(subreq, netfs_sreq_trace_free); if (rreq->netfs_ops->free_subrequest) rreq->netfs_ops->free_subrequest(subreq); + bvecq_pos_unset(&subreq->dispatch_pos); + bvecq_pos_unset(&subreq->content); mempool_free(subreq, rreq->netfs_ops->subrequest_pool ?: &netfs_subreques= t_pool); netfs_stat_d(&netfs_n_rh_sreq); netfs_put_request(rreq, netfs_rreq_trace_put_subreq); diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index f773579692ab..ea01b2f1f1f8 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -27,9 +27,13 @@ */ static void netfs_clear_unread(struct netfs_io_subrequest *subreq) { - netfs_reset_iter(subreq); - WARN_ON_ONCE(subreq->len - subreq->transferred !=3D iov_iter_count(&subre= q->io_iter)); - iov_iter_zero(iov_iter_count(&subreq->io_iter), &subreq->io_iter); + struct iov_iter iter; + + iov_iter_bvec_queue(&iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + iov_iter_advance(&iter, subreq->transferred); + iov_iter_zero(subreq->len, &iter); + if (subreq->start + subreq->transferred >=3D subreq->rreq->i_size) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); } @@ -40,11 +44,11 @@ static void netfs_clear_unread(struct netfs_io_subreque= st *subreq) * dirty and let writeback handle it. */ static void netfs_unlock_read_folio(struct netfs_io_request *rreq, - struct folio_queue *folioq, + struct bvecq *bvecq, int slot) { struct netfs_folio *finfo; - struct folio *folio =3D folioq_folio(folioq, slot); + struct folio *folio =3D page_folio(bvecq->bv[slot].bv_page); =20 if (unlikely(folio_pos(folio) < rreq->abandon_to)) { trace_netfs_folio(folio, netfs_folio_trace_abandon); @@ -75,7 +79,7 @@ static void netfs_unlock_read_folio(struct netfs_io_reque= st *rreq, trace_netfs_folio(folio, netfs_folio_trace_read_done); } =20 - folioq_clear(folioq, slot); + bvecq->bv[slot].bv_page =3D NULL; } else { // TODO: Use of PG_private_2 is deprecated. if (test_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags)) @@ -91,7 +95,7 @@ static void netfs_unlock_read_folio(struct netfs_io_reque= st *rreq, folio_unlock(folio); } =20 - folioq_clear(folioq, slot); + bvecq->bv[slot].bv_page =3D NULL; } =20 /* @@ -99,21 +103,25 @@ static void netfs_unlock_read_folio(struct netfs_io_re= quest *rreq, */ void netfs_read_set_unlock_at(struct netfs_io_request *rreq) { - struct folio_queue *folioq =3D rreq->buffer.tail; - unsigned int slot =3D rreq->buffer.first_tail_slot; + const struct bvecq *bq =3D rreq->collect_cursor.bvecq; + unsigned int slot =3D rreq->collect_cursor.slot; size_t cleaned_to =3D rreq->cleaned_to - rreq->start; size_t progress_at =3D cleaned_to; size_t minimum =3D 256 * 1024; =20 while (progress_at < rreq->len) { - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - if (!folioq) + if (!bvecq_acquire_slot(bq, slot)) { + const struct bvecq *next =3D bvecq_next(bq); + + if (!next) break; + if (bvecq_acquire_slot(bq, slot)) + continue; /* More slots got added. */ + bq =3D next; slot =3D 0; } =20 - progress_at +=3D folioq_folio_size(folioq, slot); + progress_at +=3D bq->bv[slot].bv_len; if (progress_at - cleaned_to >=3D minimum) break; slot++; @@ -129,8 +137,8 @@ void netfs_read_set_unlock_at(struct netfs_io_request *= rreq) static void netfs_read_unlock_folios(struct netfs_io_request *rreq, unsigned int *notes) { - struct folio_queue *folioq =3D rreq->buffer.tail; - unsigned int slot =3D rreq->buffer.first_tail_slot; + struct bvecq *bvecq =3D rreq->collect_cursor.bvecq; + unsigned int slot =3D rreq->collect_cursor.slot; uoff_t collected_to =3D rreq->collected_to; =20 if (rreq->cleaned_to >=3D rreq->collected_to) @@ -138,15 +146,6 @@ static void netfs_read_unlock_folios(struct netfs_io_r= equest *rreq, =20 // TODO: Begin decryption =20 - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D rolling_buffer_delete_spent(&rreq->buffer); - if (!folioq) { - WRITE_ONCE(rreq->progress_at, ULONG_MAX); - return; - } - slot =3D 0; - } - /* We have to wait for readahead refs to have been released before we * can unlock any folios as the ref-dropper walks i_pages and the only * thing preventing these folios from being removed is the folio lock. @@ -159,17 +158,30 @@ static void netfs_read_unlock_folios(struct netfs_io_= request *rreq, uoff_t fpos =3D rreq->cleaned_to, fend; size_t fsize; =20 + /* Clean up the head bvecq segment. If we clear an entire + * segment, then we can get rid of it provided it's not also + * the tail segment being filled by the issuer. + */ + if (!bvecq_acquire_slot(bvecq, slot)) { + rreq->collect_cursor.slot =3D slot; + if (!bvecq_delete_spent(&rreq->collect_cursor)) { + WRITE_ONCE(rreq->progress_at, ULONG_MAX); + return; + } + bvecq =3D rreq->collect_cursor.bvecq; + slot =3D rreq->collect_cursor.slot; + } + if (*notes & COPY_TO_CACHE) set_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); =20 - folio =3D folioq_folio(folioq, slot); + folio =3D page_folio(bvecq->bv[slot].bv_page); if (WARN_ONCE(!folio_test_locked(folio), "R=3D%08x: folio %lx is not locked\n", rreq->debug_id, folio->index)) trace_netfs_folio(folio, netfs_folio_trace_not_locked); =20 - fsize =3D folioq_folio_size(folioq, slot); - fpos =3D folio_pos(folio); + fsize =3D bvecq->bv[slot].bv_len; fend =3D fpos + fsize; =20 trace_netfs_collect_folio(rreq, folio); @@ -178,34 +190,19 @@ static void netfs_read_unlock_folios(struct netfs_io_= request *rreq, if (collected_to < fend) break; =20 - netfs_unlock_read_folio(rreq, folioq, slot); + netfs_unlock_read_folio(rreq, bvecq, slot); + slot++; WRITE_ONCE(rreq->cleaned_to, fend); *notes |=3D MADE_PROGRESS; =20 clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); =20 - /* Clean up the head folioq. If we clear an entire folioq, then - * we can get rid of it provided it's not also the tail folioq - * being filled by the issuer. - */ - folioq_clear(folioq, slot); - slot++; - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D rolling_buffer_delete_spent(&rreq->buffer); - if (!folioq) - goto done; - slot =3D 0; - trace_netfs_folioq(folioq, netfs_trace_folioq_read_progress); - } - if (fpos + fsize >=3D collected_to) break; } =20 - rreq->buffer.tail =3D folioq; -done: - rreq->buffer.first_tail_slot =3D slot; - + bvecq_pos_move(&rreq->collect_cursor, bvecq); + rreq->collect_cursor.slot =3D slot; netfs_read_set_unlock_at(rreq); } =20 @@ -383,12 +380,17 @@ static void netfs_rreq_assess_dio(struct netfs_io_req= uest *rreq) =20 if (rreq->origin =3D=3D NETFS_UNBUFFERED_READ || rreq->origin =3D=3D NETFS_DIO_READ) { - for (i =3D 0; i < rreq->direct_bv_count; i++) { - flush_dcache_page(rreq->direct_bv[i].bv_page); - // TODO: cifs marks pages in the destination buffer - // dirty under some circumstances after a read. Do we - // need to do that too? - set_page_dirty(rreq->direct_bv[i].bv_page); + for (struct bvecq *bq =3D rreq->collect_cursor.bvecq; bq; bq =3D bvecq_n= ext(bq)) { + unsigned int nr_slots =3D bvecq_nr_slots_acquire(bq); + /* Read the slot count before the slots. */ + + for (i =3D 0; i < nr_slots; i++) { + flush_dcache_page(bq->bv[i].bv_page); + // TODO: cifs marks pages in the destination buffer + // dirty under some circumstances after a read. Do we + // need to do that too? + set_page_dirty(bq->bv[i].bv_page); + } } } =20 @@ -477,7 +479,15 @@ bool netfs_read_collection(struct netfs_io_request *rr= eq) =20 trace_netfs_rreq(rreq, netfs_rreq_trace_done); netfs_clear_subrequests(rreq); - netfs_unlock_abandoned_read_pages(rreq); + switch (rreq->origin) { + case NETFS_READAHEAD: + case NETFS_READPAGE: + case NETFS_READ_FOR_WRITE: + netfs_unlock_abandoned_read_pages(rreq); + break; + default: + break; + } if (unlikely(rreq->copy_to_cache)) netfs_pgpriv2_end_copy_to_cache(rreq); return true; diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index aab156d00031..2e4eab1e327f 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -19,6 +19,9 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct= folio *folio) { struct netfs_io_stream *cache =3D &creq->io_streams[1]; + struct bvecq *queue; + unsigned int slot; + size_t dio_size =3D PAGE_SIZE; size_t fsize =3D folio_size(folio), flen =3D fsize; uoff_t fpos =3D folio_pos(folio), i_size; bool to_eof =3D false; @@ -48,18 +51,37 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_re= quest *creq, struct folio to_eof =3D true; } =20 + flen =3D round_up(flen, dio_size); + _debug("folio %zx %zx", flen, fsize); =20 trace_netfs_folio(folio, netfs_folio_trace_store_copy); =20 - /* Attach the folio to the rolling buffer. */ - if (rolling_buffer_append(&creq->buffer, folio, 0, creq->gfp) < 0) { - folio_end_private_2(folio); - clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); - return; + /* Institute a new bvec queue segment if the current one is full or if + * we encounter a discontiguity. The discontiguity break is important + * when it comes to bulk unlocking folios by file range. + */ + queue =3D creq->load_cursor.bvecq; + if (bvecq_is_full(queue) || + (fpos !=3D creq->last_end && creq->last_end > 0 && queue->nr_slots > = 0)) { + bvecq_buffer_append(&creq->load_cursor, creq->spare); + creq->spare =3D NULL; + + queue =3D creq->load_cursor.bvecq; } =20 - cache->submit_extendable_to =3D fsize; + /* Attach the folio to the rolling buffer. */ + slot =3D queue->nr_slots; + bvec_set_folio(&queue->bv[slot], folio, fsize, 0); + trace_netfs_bv_slot(queue, slot); + slot++; + bvecq_filled_to(queue, slot); + creq->load_cursor.slot =3D slot; + creq->load_cursor.offset =3D 0; + creq->last_end =3D fpos + flen; + + bvecq_pos_nudge(&creq->dispatch_cursor); +=09 cache->submit_off =3D 0; cache->submit_len =3D flen; =20 @@ -71,10 +93,9 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_req= uest *creq, struct folio do { ssize_t part; =20 - creq->buffer.iter.iov_offset =3D cache->submit_off; + creq->dispatch_cursor.offset =3D cache->submit_off; =20 atomic64_set(&creq->issued_to, fpos + cache->submit_off); - cache->submit_extendable_to =3D fsize - cache->submit_off; part =3D netfs_advance_write(creq, cache, fpos + cache->submit_off, cache->submit_len, to_eof); cache->submit_off +=3D part; @@ -84,8 +105,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_req= uest *creq, struct folio cache->submit_len -=3D part; } while (cache->submit_len > 0); =20 - creq->buffer.iter.iov_offset =3D 0; - rolling_buffer_advance(&creq->buffer, fsize); + bvecq_pos_step(&creq->dispatch_cursor); atomic64_set(&creq->issued_to, fpos + fsize); =20 if (flen < fsize) @@ -111,6 +131,11 @@ static struct netfs_io_request *netfs_pgpriv2_begin_co= py_to_cache( if (!creq->io_streams[1].avail) goto cancel_put; =20 + if (bvecq_buffer_init(&creq->load_cursor, creq->gfp, false) < 0) + goto cancel_put; + bvecq_pos_set(&creq->dispatch_cursor, &creq->load_cursor); + bvecq_pos_set(&creq->collect_cursor, &creq->dispatch_cursor); + __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &creq->flags); trace_netfs_copy2cache(rreq, creq); trace_netfs_write(creq, netfs_write_trace_copy_to_cache); @@ -139,6 +164,14 @@ void netfs_pgpriv2_copy_to_cache(struct netfs_io_reque= st *rreq, struct folio *fo if (IS_ERR(creq)) return; =20 + if (!creq->spare) { + creq->spare =3D bvecq_alloc_one(BVECQ_STD_SLOTS, creq->gfp, false); + if (!creq->spare) { + clear_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &creq->flags); + return; + } + } + trace_netfs_folio(folio, netfs_folio_trace_copy_to_cache); folio_start_private_2(folio); netfs_pgpriv2_copy_folio(creq, folio); @@ -170,22 +203,27 @@ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_= request *rreq) */ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) { - struct folio_queue *folioq =3D creq->buffer.tail; - unsigned int slot =3D creq->buffer.first_tail_slot; + struct bvecq *bq =3D creq->collect_cursor.bvecq; + unsigned int slot; uoff_t collected_to =3D creq->collected_to; bool made_progress =3D false; =20 - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D rolling_buffer_delete_spent(&creq->buffer); - slot =3D 0; - } + slot =3D creq->collect_cursor.slot; =20 for (;;) { struct folio *folio; uoff_t fpos, fend; size_t fsize, flen; =20 - folio =3D folioq_folio(folioq, slot); + if (!bvecq_acquire_slot(bq, slot)) { + creq->collect_cursor.slot =3D slot; + if (!bvecq_delete_spent(&creq->collect_cursor)) + goto out; + bq =3D creq->collect_cursor.bvecq; + slot =3D creq->collect_cursor.slot; + } + + folio =3D page_folio(bq->bv[slot].bv_page); if (WARN_ONCE(!folio_test_private_2(folio), "R=3D%08x: folio %lx is not marked private_2\n", creq->debug_id, folio->index)) @@ -208,25 +246,18 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_= io_request *creq) creq->cleaned_to =3D fpos + fsize; made_progress =3D true; =20 - /* Clean up the head folioq. If we clear an entire folioq, then - * we can get rid of it provided it's not also the tail folioq - * being filled by the issuer. + /* Clean up the head segment. If we clear an entire segment, + * then we can get rid of it provided it's not also the tail + * segment being filled by the issuer. */ - folioq_clear(folioq, slot); + bq->bv[slot].bv_page =3D NULL; slot++; - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D rolling_buffer_delete_spent(&creq->buffer); - if (!folioq) - goto done; - slot =3D 0; - } =20 if (fpos + fsize >=3D collected_to) break; } =20 - creq->buffer.tail =3D folioq; -done: - creq->buffer.first_tail_slot =3D slot; + creq->collect_cursor.slot =3D slot; +out: return made_progress; } diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 96223f62516a..733d9ff452c7 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -12,6 +12,12 @@ static void netfs_reissue_read(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq) { + bvecq_pos_unset(&subreq->content); + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + iov_iter_advance(&subreq->io_iter, subreq->transferred); + subreq->error =3D 0; __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); @@ -27,6 +33,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_= request *rreq) { struct netfs_io_subrequest *subreq; struct netfs_io_stream *stream =3D &rreq->io_streams[0]; + struct bvecq_pos dispatch_cursor =3D {}; struct list_head *next; =20 _enter("R=3D%x", rreq->debug_id); @@ -46,9 +53,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_= request *rreq) if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) break; if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { - __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); subreq->retry_count++; - netfs_reset_iter(subreq); netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); netfs_reissue_read(rreq, subreq); } @@ -74,11 +79,12 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) =20 do { struct netfs_io_subrequest *from, *to, *tmp; - struct iov_iter source; uoff_t start, len; size_t part; bool boundary =3D false, subreq_superfluous =3D false; =20 + bvecq_pos_unset(&dispatch_cursor); + /* Go through the subreqs and find the next span of contiguous * buffer that we then rejig (cifs, for example, needs the * rsize renegotiating) and reissue. @@ -105,7 +111,8 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) break; =20 subreq =3D list_entry(next, struct netfs_io_subrequest, rreq_link); - if (subreq->start + subreq->transferred !=3D start + len || + if (subreq->start !=3D start + len || + subreq->transferred > 0 || test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) || !test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) break; @@ -118,11 +125,14 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) /* Determine the set of buffers we're going to use. Each * subreq gets a subset of a single overall contiguous buffer. */ - netfs_reset_iter(from); - source =3D from->io_iter; - source.count =3D len; + bvecq_pos_transfer(&dispatch_cursor, &from->dispatch_pos); + bvecq_pos_advance(&dispatch_cursor, from->transferred); + from->transferred =3D 0; =20 - /* Work through the sublist. */ + /* Work through the sublist. The chain of buffers we're going + * to fill is attached to dispatch_cursor and we need to read + * 'len' amount of data from 'start'. + */ subreq =3D from; list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) { if (!len) { @@ -130,16 +140,22 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) break; } subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; - subreq->start =3D start - subreq->transferred; - subreq->len =3D len + subreq->transferred; + subreq->start =3D start; + subreq->len =3D len; __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); subreq->retry_count++; + subreq->transferred =3D 0; + + bvecq_pos_unset(&subreq->content); + bvecq_pos_unset(&subreq->dispatch_pos); + bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); =20 trace_netfs_sreq(subreq, netfs_sreq_trace_retry); =20 /* Renegotiate max_len (rsize) */ - stream->sreq_max_len =3D subreq->len; + stream->sreq_max_len =3D len; + stream->sreq_max_segs =3D INT_MAX; if (rreq->netfs_ops->prepare_read && rreq->netfs_ops->prepare_read(subreq) < 0) { trace_netfs_sreq(subreq, netfs_sreq_trace_reprep_failed); @@ -147,13 +163,12 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) goto abandon; } =20 - part =3D umin(len, stream->sreq_max_len); - if (unlikely(stream->sreq_max_segs)) - part =3D netfs_limit_iter(&source, 0, part, stream->sreq_max_segs); - subreq->len =3D subreq->transferred + part; - subreq->io_iter =3D source; - iov_iter_truncate(&subreq->io_iter, part); - iov_iter_advance(&source, part); + part =3D bvecq_slice(&dispatch_cursor, + umin(len, stream->sreq_max_len), + stream->sreq_max_segs, + &subreq->nr_segs); + subreq->len =3D part; + len -=3D part; start +=3D part; if (!len) { @@ -217,9 +232,7 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) trace_netfs_sreq(subreq, netfs_sreq_trace_retry); =20 stream->sreq_max_len =3D umin(len, rreq->rsize); - stream->sreq_max_segs =3D 0; - if (unlikely(stream->sreq_max_segs)) - part =3D netfs_limit_iter(&source, 0, part, stream->sreq_max_segs); + stream->sreq_max_segs =3D INT_MAX; =20 netfs_stat(&netfs_n_rh_download); if (rreq->netfs_ops->prepare_read(subreq) < 0) { @@ -228,11 +241,12 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) goto abandon; } =20 - part =3D umin(len, stream->sreq_max_len); - subreq->len =3D subreq->transferred + part; - subreq->io_iter =3D source; - iov_iter_truncate(&subreq->io_iter, part); - iov_iter_advance(&source, part); + bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); + part =3D bvecq_slice(&dispatch_cursor, + umin(len, stream->sreq_max_len), + stream->sreq_max_segs, + &subreq->nr_segs); + subreq->len =3D part; =20 len -=3D part; start +=3D part; @@ -246,12 +260,14 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) =20 } while (!list_is_head(next, &stream->subrequests)); =20 +out: + bvecq_pos_unset(&dispatch_cursor); return; =20 /* If we hit an error, fail all remaining incomplete subrequests */ abandon_after: if (list_is_last(&subreq->rreq_link, &stream->subrequests)) - return; + goto out; subreq =3D list_next_entry(subreq, rreq_link); abandon: list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) { @@ -262,6 +278,7 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) __set_bit(NETFS_SREQ_FAILED, &subreq->flags); __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); } + goto out; } =20 /* @@ -292,7 +309,7 @@ void netfs_retry_reads(struct netfs_io_request *rreq) */ void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq) { - struct folio_queue *p; + struct bvecq *p; =20 /* We have to wait for readahead refs to have been released before we * can unlock any folios as the ref-dropper walks i_pages and the only @@ -301,21 +318,22 @@ void netfs_unlock_abandoned_read_pages(struct netfs_i= o_request *rreq) if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags)) netfs_wait_for_put_ra_refs(rreq); =20 - for (p =3D rreq->buffer.tail; p; p =3D p->next) { - for (int slot =3D 0; slot < folioq_count(p); slot++) { - struct folio *folio =3D folioq_folio(p, slot); - - if (folio && !folioq_is_marked2(p, slot)) { - if (folio =3D=3D rreq->no_unlock_folio && - test_bit(NETFS_RREQ_NO_UNLOCK_FOLIO, - &rreq->flags)) { - _debug("no unlock"); - } else { - trace_netfs_folio(folio, - netfs_folio_trace_abandon); - folio_unlock(folio); - } + for (p =3D rreq->collect_cursor.bvecq; p; p =3D bvecq_next(p)) { + unsigned int nr_slots =3D bvecq_nr_slots_acquire(p); + + for (int slot =3D 0; slot < nr_slots; slot++) { + if (!p->bv[slot].bv_page) + continue; + + struct folio *folio =3D page_folio(p->bv[slot].bv_page); + + if (folio =3D=3D rreq->no_unlock_folio && + test_bit(NETFS_RREQ_NO_UNLOCK_FOLIO, &rreq->flags)) { + _debug("no unlock"); + continue; } + trace_netfs_folio(folio, netfs_folio_trace_abandon); + folio_unlock(folio); } } } diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index b370a8534324..9fd07dbb08b5 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -93,7 +93,12 @@ static int netfs_single_dispatch_read(struct netfs_io_re= quest *rreq) subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; subreq->start =3D 0; subreq->len =3D rreq->len; - subreq->io_iter =3D rreq->buffer.iter; + + bvecq_pos_set(&subreq->dispatch_pos, &rreq->dispatch_cursor); + bvecq_pos_set(&subreq->content, &rreq->dispatch_cursor); + + iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); =20 netfs_queue_read(rreq, subreq); =20 @@ -174,6 +179,15 @@ ssize_t netfs_read_single(struct inode *inode, struct = file *file, struct iov_ite if (IS_ERR(rreq)) return PTR_ERR(rreq); =20 + ret =3D netfs_extract_iter(iter, rreq->len, INT_MAX, &rreq->dispatch_curs= or.bvecq, + 0, rreq->gfp); + if (ret < 0) + goto cleanup_free; + if (ret < rreq->len) { + ret =3D -EIO; + goto cleanup_free; + } + rreq->progress_at =3D rreq->len; =20 ret =3D netfs_single_begin_cache_read(rreq, ictx); @@ -183,7 +197,6 @@ ssize_t netfs_read_single(struct inode *inode, struct f= ile *file, struct iov_ite netfs_stat(&netfs_n_rh_read_single); trace_netfs_read(rreq, 0, rreq->len, netfs_read_trace_read_single); =20 - rreq->buffer.iter =3D *iter; netfs_single_dispatch_read(rreq); =20 ret =3D netfs_wait_for_read(rreq); diff --git a/fs/netfs/stats.c b/fs/netfs/stats.c index a10d34f88597..0ba6ce9295b2 100644 --- a/fs/netfs/stats.c +++ b/fs/netfs/stats.c @@ -46,7 +46,6 @@ atomic_t netfs_n_wh_retry_write_req; atomic_t netfs_n_wh_retry_write_subreq; atomic_t netfs_n_wb_lock_skip; atomic_t netfs_n_wb_lock_wait; -atomic_t netfs_n_folioq; atomic_t netfs_n_bvecq; =20 int netfs_stats_show(struct seq_file *m, void *v) @@ -89,11 +88,10 @@ int netfs_stats_show(struct seq_file *m, void *v) atomic_read(&netfs_n_rh_retry_read_subreq), atomic_read(&netfs_n_wh_retry_write_req), atomic_read(&netfs_n_wh_retry_write_subreq)); - seq_printf(m, "Objs : rr=3D%u sr=3D%u bq=3D%u foq=3D%u wsc=3D%u\n", + seq_printf(m, "Objs : rr=3D%u sr=3D%u bq=3D%u wsc=3D%u\n", atomic_read(&netfs_n_rh_rreq), atomic_read(&netfs_n_rh_sreq), atomic_read(&netfs_n_bvecq), - atomic_read(&netfs_n_folioq), atomic_read(&netfs_n_wh_wstream_conflict)); seq_printf(m, "WbLock : skip=3D%u wait=3D%u\n", atomic_read(&netfs_n_wb_lock_skip), diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 6e8ea534230d..b980a65441dc 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -114,12 +114,12 @@ int netfs_folio_written_back(struct folio *folio) static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, unsigned int *notes) { - struct folio_queue *folioq =3D wreq->buffer.tail; - unsigned int slot =3D wreq->buffer.first_tail_slot; + struct bvecq *bvecq =3D wreq->collect_cursor.bvecq; + unsigned int slot =3D wreq->collect_cursor.slot; uoff_t collected_to =3D wreq->collected_to; =20 - if (WARN_ON_ONCE(!folioq)) { - pr_err("[!] Writeback unlock found empty rolling buffer!\n"); + if (WARN_ON_ONCE(!bvecq)) { + pr_err("[!] Writeback unlock found empty buffer!\n"); netfs_dump_request(wreq); return; } @@ -130,20 +130,28 @@ static void netfs_writeback_unlock_folios(struct netf= s_io_request *wreq, return; } =20 - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D rolling_buffer_delete_spent(&wreq->buffer); - if (!folioq) - return; - slot =3D 0; - } - for (;;) { struct folio *folio; struct netfs_folio *finfo; uoff_t fpos, fend; size_t fsize, flen; =20 - folio =3D folioq_folio(folioq, slot); + /* Try to clean up the head of the queue if it appears to be + * used up, but we need to be very careful - the cleanup can + * catch the dispatcher, which could lead to us having nothing + * left in the queue, causing the front and back pointers to + * end up on different tracks. To avoid this, we must always + * keep at least one segment in the queue. + */ + if (!bvecq_acquire_slot(bvecq, slot)) { + wreq->collect_cursor.slot =3D slot; + if (!bvecq_delete_spent(&wreq->collect_cursor)) + return; + bvecq =3D wreq->collect_cursor.bvecq; + slot =3D wreq->collect_cursor.slot; + } + + folio =3D page_folio(bvecq->bv[slot].bv_page); if (WARN_ONCE(!folio_test_writeback(folio), "R=3D%08x: folio %lx is not under writeback\n", wreq->debug_id, folio->index)) @@ -166,26 +174,13 @@ static void netfs_writeback_unlock_folios(struct netf= s_io_request *wreq, wreq->cleaned_to =3D fpos + fsize; *notes |=3D MADE_PROGRESS; =20 - /* Clean up the head folioq. If we clear an entire folioq, then - * we can get rid of it provided it's not also the tail folioq - * being filled by the issuer. - */ - folioq_clear(folioq, slot); + bvecq->bv[slot].bv_page =3D NULL; slot++; - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D rolling_buffer_delete_spent(&wreq->buffer); - if (!folioq) - goto done; - slot =3D 0; - } - if (fpos + fsize >=3D collected_to) break; } =20 - wreq->buffer.tail =3D folioq; -done: - wreq->buffer.first_tail_slot =3D slot; + wreq->collect_cursor.slot =3D slot; } =20 /* @@ -230,7 +225,8 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) trace_netfs_rreq(wreq, netfs_rreq_trace_collect); =20 reassess_streams: - issued_to =3D atomic64_read(&wreq->issued_to); + /* Order reading the issued_to point before reading the queue it refers t= o. */ + issued_to =3D atomic64_read_acquire(&wreq->issued_to); smp_rmb(); collected_to =3D ULLONG_MAX; if (wreq->origin =3D=3D NETFS_WRITEBACK || @@ -560,8 +556,12 @@ void netfs_write_subrequest_terminated(void *_op, ssiz= e_t transferred_or_error) * data is tracked. */ netfs_stat(&netfs_n_wh_write_failed); - if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) - break; + if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { + /* We don't retry failed cache writes. */ + __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); + if (!subreq->error) + subreq->error =3D -ENOBUFS; + } =20 trace_netfs_failure(wreq, subreq, transferred_or_error, netfs_fail_writ= e); __set_bit(NETFS_SREQ_CANCELLED, &subreq->flags); diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 638f0122315c..a5c5ba5deb43 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -107,8 +107,6 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, ictx =3D netfs_inode(wreq->inode); if (is_cacheable) fscache_begin_write_operation(&wreq->cache_resources, netfs_i_cookie(ict= x)); - if (rolling_buffer_init(&wreq->buffer, wreq->debug_id, ITER_SOURCE, wreq-= >gfp) < 0) - goto nomem; =20 wreq->cleaned_to =3D wreq->start; if (wreq->cache_resources.dio_size > 1) @@ -133,9 +131,6 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, } =20 return wreq; -nomem: - netfs_put_failed_request(wreq); - return ERR_PTR(-ENOMEM); } =20 /** @@ -160,21 +155,13 @@ void netfs_prepare_write(struct netfs_io_request *wre= q, uoff_t start) { struct netfs_io_subrequest *subreq; - struct iov_iter *wreq_iter =3D &wreq->buffer.iter; - - /* Make sure we don't point the iterator at a used-up folio_queue - * struct being used as a placeholder to prevent the queue from - * collapsing. In such a case, extend the queue. - */ - if (iov_iter_is_folioq(wreq_iter) && - wreq_iter->folioq_slot >=3D folioq_nr_slots(wreq_iter->folioq)) - rolling_buffer_make_space(&wreq->buffer, wreq->gfp); =20 subreq =3D netfs_alloc_subrequest(wreq); subreq->source =3D stream->source; subreq->start =3D start; subreq->stream_nr =3D stream->stream_nr; - subreq->io_iter =3D *wreq_iter; + + bvecq_pos_set(&subreq->dispatch_pos, &wreq->dispatch_cursor); =20 _enter("R=3D%x[%x]", wreq->debug_id, subreq->debug_index); =20 @@ -255,15 +242,16 @@ static void netfs_do_issue_write(struct netfs_io_stre= am *stream, } =20 void netfs_reissue_write(struct netfs_io_stream *stream, - struct netfs_io_subrequest *subreq, - struct iov_iter *source) + struct netfs_io_subrequest *subreq) { - size_t size =3D subreq->len - subreq->transferred; - // TODO: Use encrypted buffer - subreq->io_iter =3D *source; - iov_iter_advance(source, size); - iov_iter_truncate(&subreq->io_iter, size); + bvecq_pos_unset(&subreq->content); + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, + subreq->content.bvecq, subreq->content.slot, + subreq->content.offset, + subreq->len); + iov_iter_advance(&subreq->io_iter, subreq->transferred); =20 subreq->retry_count++; subreq->error =3D 0; @@ -281,8 +269,13 @@ void netfs_issue_write(struct netfs_io_request *wreq, if (!subreq) return; =20 + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, + subreq->content.bvecq, subreq->content.slot, + subreq->content.offset, + subreq->len); + stream->construct =3D NULL; - subreq->io_iter.count =3D subreq->len; netfs_do_issue_write(stream, subreq); } =20 @@ -319,7 +312,6 @@ size_t netfs_advance_write(struct netfs_io_request *wre= q, _debug("part %zx/%zx %zx/%zx", subreq->len, stream->sreq_max_len, part, l= en); subreq->len +=3D part; subreq->nr_segs++; - stream->submit_extendable_to -=3D part; =20 if (subreq->len >=3D stream->sreq_max_len || subreq->nr_segs >=3D stream->sreq_max_segs || @@ -343,7 +335,8 @@ static int netfs_write_folio(struct netfs_io_request *w= req, struct netfs_io_stream *stream; struct netfs_group *fgroup; /* TODO: Use this with ceph */ struct netfs_folio *finfo; - size_t iter_off =3D 0; + struct bvecq *queue =3D wreq->load_cursor.bvecq; + unsigned int slot; size_t fsize =3D folio_size(folio), flen =3D fsize, foff =3D 0; uoff_t fpos =3D folio_pos(folio), i_size; bool to_eof =3D false, streamw =3D false; @@ -351,12 +344,20 @@ static int netfs_write_folio(struct netfs_io_request = *wreq, =20 _enter(""); =20 - if (rolling_buffer_make_space(&wreq->buffer, wreq->gfp) < 0) - return -ENOMEM; + if (!wreq->spare) { + wreq->spare =3D bvecq_alloc_one(BVECQ_STD_SLOTS, wreq->gfp, true); + if (!wreq->spare) + return -ENOMEM; + } =20 - /* netfs_perform_write() may shift i_size around the page or from out - * of the page to beyond it, but cannot move i_size into or through the - * page since we have it locked. + /* netfs_perform_write() may shift i_size around the folio or from out + * of the folio to beyond it, but cannot move i_size into or through + * the folio since we have it locked. + * + * Truncate could in theory move i_size into or before the folio, but + * it should take steps to prevent writeback from happening + * concurrently and should wait for any in-progress writebacks before + * proceeding. */ i_size =3D i_size_read(wreq->inode); =20 @@ -448,8 +449,29 @@ static int netfs_write_folio(struct netfs_io_request *= wreq, trace_netfs_folio(folio, netfs_folio_trace_store_plus); } =20 + /* Institute a new bvec queue segment if the current one is full or if + * we encounter a discontiguity. The discontiguity break is important + * when it comes to bulk unlocking folios by file range. + */ + if (bvecq_is_full(queue) || + (fpos !=3D wreq->last_end && wreq->last_end > 0)) { + bvecq_buffer_append(&wreq->load_cursor, wreq->spare); + wreq->spare =3D NULL; + + queue =3D wreq->load_cursor.bvecq; + bvecq_pos_move(&wreq->dispatch_cursor, queue); + wreq->dispatch_cursor.slot =3D 0; + } + /* Attach the folio to the rolling buffer. */ - rolling_buffer_append(&wreq->buffer, folio, 0, wreq->gfp); + slot =3D queue->nr_slots; + bvec_set_folio(&queue->bv[slot], folio, fsize, 0); + trace_netfs_bv_slot(queue, slot); + slot++; + bvecq_filled_to(queue, slot); + wreq->load_cursor.slot =3D slot; + wreq->load_cursor.offset =3D 0; + wreq->last_end =3D fpos + fsize; =20 /* Move the submission point forward to allow for write-streaming data * not starting at the front of the page. We don't do write-streaming @@ -458,10 +480,18 @@ static int netfs_write_folio(struct netfs_io_request = *wreq, * Also skip uploading for data that's been read and just needs copying * to the cache. */ + bvecq_pos_nudge(&wreq->dispatch_cursor); +=09 for (int s =3D 0; s < NR_IO_STREAMS; s++) { + size_t soff =3D foff, slen =3D flen, alignment =3D 1; + + if (stream->source =3D=3D NETFS_WRITE_TO_CACHE) + alignment =3D wreq->cache_resources.dio_size; stream =3D &wreq->io_streams[s]; - stream->submit_off =3D foff; - stream->submit_len =3D flen; + stream->submit_off =3D round_down(soff, alignment); + slen +=3D foff - stream->submit_off; + stream->submit_len =3D round_up(slen, alignment); + if (!stream->avail || (stream->source =3D=3D NETFS_WRITE_TO_CACHE && streamw) || (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER && @@ -495,14 +525,10 @@ static int netfs_write_folio(struct netfs_io_request = *wreq, break; stream =3D &wreq->io_streams[choose_s]; =20 - /* Advance the iterator(s). */ - if (stream->submit_off > iter_off) { - rolling_buffer_advance(&wreq->buffer, stream->submit_off - iter_off); - iter_off =3D stream->submit_off; - } + /* Advance the cursor. */ + wreq->dispatch_cursor.offset =3D stream->submit_off; =20 atomic64_set(&wreq->issued_to, fpos + stream->submit_off); - stream->submit_extendable_to =3D fsize - stream->submit_off; part =3D netfs_advance_write(wreq, stream, fpos + stream->submit_off, stream->submit_len, to_eof); stream->submit_off +=3D part; @@ -514,9 +540,9 @@ static int netfs_write_folio(struct netfs_io_request *w= req, debug =3D true; } =20 - if (fsize > iter_off) - rolling_buffer_advance(&wreq->buffer, fsize - iter_off); - atomic64_set(&wreq->issued_to, fpos + fsize); + bvecq_pos_step(&wreq->dispatch_cursor); + /* Order loading the queue before updating the issue_to point */ + atomic64_set_release(&wreq->issued_to, fpos + fsize); =20 if (!debug) kdebug("R=3D%x: No submit", wreq->debug_id); @@ -577,6 +603,11 @@ int netfs_writepages(struct address_space *mapping, goto couldnt_start; } =20 + if (bvecq_buffer_init(&wreq->load_cursor, wreq->gfp, true) < 0) + goto nomem; + bvecq_pos_set(&wreq->dispatch_cursor, &wreq->load_cursor); + bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor); + __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); trace_netfs_write(wreq, netfs_write_trace_writeback); netfs_stat(&netfs_n_wh_writepages); @@ -601,12 +632,17 @@ int netfs_writepages(struct address_space *mapping, } while ((folio =3D writeback_iter(mapping, wbc, folio, &error))); =20 netfs_end_issue_write(wreq); + bvecq_pos_unset(&wreq->load_cursor); + bvecq_pos_unset(&wreq->dispatch_cursor); netfs_wake_collector(wreq); =20 netfs_put_request(wreq, netfs_rreq_trace_put_return); _leave(" =3D %d", error); return error; =20 +nomem: + error =3D -ENOMEM; + netfs_put_failed_request(wreq); couldnt_start: if (error =3D=3D -ENOMEM) { folio_redirty_for_writepage(wbc, folio); @@ -627,20 +663,24 @@ EXPORT_SYMBOL(netfs_writepages); * netfs_writeback_single - Write back a monolithic payload * @mapping: The mapping to write from * @wbc: Hints from the VM - * @iter: Data to write. + * @iter: Buffer to write from + * @len: Amount to write from buffer * - * Write a monolithic, non-pagecache object back to the server and/or - * the cache. + * Write a monolithic, non-pagecache object back to the server and/or the + * cache. There's a maximum of one subrequest per stream. The buffer sho= uld + * be rounded out sufficiently that it can accommodate cache DIO rounding. * * Return: 0 if successful; 1 if skipped due to lock conflict and WB_SYNC_= NONE; * or a negative error code. + * the cache. There's a maximum of one subrequest per stream. */ int netfs_writeback_single(struct address_space *mapping, struct writeback_control *wbc, - struct iov_iter *iter) + struct iov_iter *iter, size_t len) { struct netfs_io_request *wreq; struct netfs_inode *ictx =3D netfs_inode(mapping->host); + size_t clen; int ret; =20 if (!netfs_wb_begin(ictx, wbc->sync_mode =3D=3D WB_SYNC_NONE)) { @@ -654,9 +694,27 @@ int netfs_writeback_single(struct address_space *mappi= ng, ret =3D PTR_ERR(wreq); goto couldnt_start; } + wreq->len =3D len; + clen =3D len; + + if (wreq->cache_resources.dio_size > 1) { + clen =3D round_up(len, wreq->cache_resources.dio_size); + if (clen > iov_iter_count(iter)) { + ret =3D -EIO; + goto cleanup_free; + } + } =20 - wreq->buffer.iter =3D *iter; - wreq->len =3D iov_iter_count(iter); + ret =3D netfs_extract_iter(iter, clen, INT_MAX, &wreq->dispatch_cursor.bv= ecq, + 0, wreq->gfp); + if (ret < 0) + goto cleanup_free; + if (ret < clen) { + ret =3D -EIO; + goto cleanup_free; + } + + bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor); =20 __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); trace_netfs_write(wreq, netfs_write_trace_writeback_single); @@ -676,12 +734,14 @@ int netfs_writeback_single(struct address_space *mapp= ing, =20 subreq =3D stream->construct; subreq->len =3D wreq->len; + if (stream->source =3D=3D NETFS_WRITE_TO_CACHE) + subreq->len =3D clen; stream->submit_len =3D subreq->len; - stream->submit_extendable_to =3D round_up(wreq->len, PAGE_SIZE); =20 netfs_issue_write(wreq, stream); } =20 + wreq->submitted =3D wreq->len; netfs_all_subreqs_queued(wreq); netfs_wake_collector(wreq); =20 @@ -689,11 +749,15 @@ int netfs_writeback_single(struct address_space *mapp= ing, * wait before modifying. */ ret =3D netfs_wait_for_write(wreq); + if (ret > 0) + ret =3D 0; =20 netfs_put_request(wreq, netfs_rreq_trace_put_return); _leave(" =3D %d", ret); return ret; =20 +cleanup_free: + netfs_put_failed_request(wreq); couldnt_start: netfs_wb_end(ictx); _leave(" =3D %d", ret); diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index 747d57467b4f..c61bed687244 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -17,15 +17,17 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq, struct netfs_io_stream *stream) { + struct bvecq_pos dispatch_cursor =3D {}; struct list_head *next; =20 _enter("R=3D%x[%x:]", wreq->debug_id, stream->stream_nr); =20 if (list_empty(&stream->subrequests)) return; + if (WARN_ON_ONCE(stream->source !=3D NETFS_UPLOAD_TO_SERVER)) + return; /* Shouldn't be retrying cache writes. */ =20 - if (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER && - wreq->netfs_ops->retry_request) + if (wreq->netfs_ops->retry_request) wreq->netfs_ops->retry_request(wreq, stream); =20 if (unlikely(stream->failed)) @@ -39,12 +41,8 @@ static void netfs_retry_write_stream(struct netfs_io_req= uest *wreq, if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) break; if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { - struct iov_iter source; - - netfs_reset_iter(subreq); - source =3D subreq->io_iter; netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); - netfs_reissue_write(stream, subreq, &source); + netfs_reissue_write(stream, subreq); } } return; @@ -54,11 +52,12 @@ static void netfs_retry_write_stream(struct netfs_io_re= quest *wreq, =20 do { struct netfs_io_subrequest *subreq =3D NULL, *from, *to, *tmp; - struct iov_iter source; uoff_t start, len; size_t part; bool boundary =3D false; =20 + bvecq_pos_unset(&dispatch_cursor); + /* Go through the stream and find the next span of contiguous * data that we then rejig (cifs, for example, needs the wsize * renegotiating) and reissue. @@ -70,7 +69,7 @@ static void netfs_retry_write_stream(struct netfs_io_requ= est *wreq, =20 if (test_bit(NETFS_SREQ_FAILED, &from->flags) || !test_bit(NETFS_SREQ_NEED_RETRY, &from->flags)) - return; + goto out; =20 for (;;) { /* Read pointer to subreq before reading subreq state. */ @@ -79,7 +78,8 @@ static void netfs_retry_write_stream(struct netfs_io_requ= est *wreq, break; =20 subreq =3D list_entry(next, struct netfs_io_subrequest, rreq_link); - if (subreq->start + subreq->transferred !=3D start + len || + if (subreq->start !=3D start + len || + subreq->transferred > 0 || test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) || !test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) break; @@ -90,11 +90,13 @@ static void netfs_retry_write_stream(struct netfs_io_re= quest *wreq, /* Determine the set of buffers we're going to use. Each * subreq gets a subset of a single overall contiguous buffer. */ - netfs_reset_iter(from); - source =3D from->io_iter; - source.count =3D len; + bvecq_pos_transfer(&dispatch_cursor, &from->dispatch_pos); + bvecq_pos_advance(&dispatch_cursor, from->transferred); =20 - /* Work through the sublist. */ + /* Work through the sublist. The chain of buffers we're going + * to fill is attached to dispatch_cursor and we need to read + * 'len' amount of data from 'start'. + */ subreq =3D from; list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) { if (!len) @@ -104,16 +106,23 @@ static void netfs_retry_write_stream(struct netfs_io_= request *wreq, subreq->len =3D len; __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); trace_netfs_sreq(subreq, netfs_sreq_trace_retry); + subreq->transferred =3D 0; + + bvecq_pos_unset(&subreq->content); + bvecq_pos_unset(&subreq->dispatch_pos); =20 /* Renegotiate max_len (wsize) */ stream->sreq_max_len =3D len; + stream->sreq_max_segs =3D INT_MAX; stream->prepare_write(subreq); =20 - part =3D umin(len, stream->sreq_max_len); - if (unlikely(stream->sreq_max_segs)) - part =3D netfs_limit_iter(&source, 0, part, stream->sreq_max_segs); + bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); + part =3D bvecq_slice(&dispatch_cursor, + umin(len, stream->sreq_max_len), + stream->sreq_max_segs, + &subreq->nr_segs); subreq->len =3D part; - subreq->transferred =3D 0; + len -=3D part; start +=3D part; if (len && subreq =3D=3D to && @@ -121,7 +130,7 @@ static void netfs_retry_write_stream(struct netfs_io_re= quest *wreq, boundary =3D true; =20 netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); - netfs_reissue_write(stream, subreq, &source); + netfs_reissue_write(stream, subreq); if (subreq =3D=3D to) break; } @@ -173,17 +182,19 @@ static void netfs_retry_write_stream(struct netfs_io_= request *wreq, netfs_stat(&netfs_n_wh_upload); stream->sreq_max_len =3D umin(len, wreq->wsize); break; - case NETFS_WRITE_TO_CACHE: - netfs_stat(&netfs_n_wh_write); - break; default: WARN_ON_ONCE(1); } =20 stream->prepare_write(subreq); =20 - part =3D umin(len, stream->sreq_max_len); + bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); + part =3D bvecq_slice(&dispatch_cursor, + umin(len, stream->sreq_max_len), + stream->sreq_max_segs, + &subreq->nr_segs); subreq->len =3D subreq->transferred + part; + len -=3D part; start +=3D part; if (!len && boundary) { @@ -191,13 +202,16 @@ static void netfs_retry_write_stream(struct netfs_io_= request *wreq, boundary =3D false; } =20 - netfs_reissue_write(stream, subreq, &source); + netfs_reissue_write(stream, subreq); if (!len) break; =20 } while (len); =20 } while (!list_is_head(next, &stream->subrequests)); + +out: + bvecq_pos_unset(&dispatch_cursor); } =20 /* diff --git a/include/linux/netfs.h b/include/linux/netfs.h index ef1068a274a8..a58d2b6914ea 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -19,12 +19,13 @@ #include #include #include -#include =20 enum netfs_sreq_ref_trace; typedef struct mempool mempool_t; +struct readahead_control; +struct netfs_io_request; +struct netfs_io_subrequest; struct fscache_occupancy; -struct folio_queue; =20 /** * folio_start_private_2 - Start an fscache write on a folio. [DEPRECATED] @@ -145,7 +146,6 @@ struct netfs_io_stream { unsigned int sreq_max_segs; /* 0 or max number of segments in an iterato= r */ unsigned int submit_off; /* Folio offset we're submitting from */ unsigned int submit_len; /* Amount of data left to submit */ - unsigned int submit_extendable_to; /* Amount I/O can be rounded up to */ void (*prepare_write)(struct netfs_io_subrequest *subreq); void (*issue_write)(struct netfs_io_subrequest *subreq); /* Collection tracking */ @@ -188,6 +188,8 @@ struct netfs_io_subrequest { struct netfs_io_request *rreq; /* Supervising I/O request */ struct work_struct work; struct list_head rreq_link; /* Link in rreq->subrequests */ + struct bvecq_pos dispatch_pos; /* Bookmark in the combined queue of the s= tart */ + struct bvecq_pos content; /* The (copied) content of the subrequest */ struct iov_iter io_iter; /* Iterator for this subrequest */ uoff_t start; /* Where to start the I/O */ size_t len; /* Size of the I/O */ @@ -248,13 +250,14 @@ struct netfs_io_request { struct netfs_io_stream io_streams[2]; /* Streams of parallel I/O operatio= ns */ #define NR_IO_STREAMS 2 //wreq->nr_io_streams struct netfs_group *group; /* Writeback group being written back */ - struct rolling_buffer buffer; /* Unencrypted buffer */ -#define NETFS_ROLLBUF_PUT_MARK ROLLBUF_MARK_1 -#define NETFS_ROLLBUF_PAGECACHE_MARK ROLLBUF_MARK_2 + struct bvecq *spare; /* Advance allocation of bvecq */ + struct bvecq_pos load_cursor; /* Point at which new folios are loaded in = */ + struct bvecq_pos dispatch_cursor; /* Point from which buffers are dispatc= hed */ + struct bvecq_pos collect_cursor; /* Clear-up point of I/O buffer */ wait_queue_head_t waitq; /* Processor waiter */ void *netfs_priv; /* Private data for the netfs */ void *netfs_priv2; /* Private data for the netfs */ - struct bio_vec *direct_bv; /* DIO buffer list (when handling iovec-iter)= */ + uoff_t last_end; /* End pos of last folio submitted */ uoff_t submitted; /* Amount submitted for I/O so far */ uoff_t len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ @@ -269,7 +272,6 @@ struct netfs_io_request { uoff_t abandon_to; /* Position to abandon folios to */ const struct folio *no_unlock_folio; /* Don't unlock this folio after rea= d */ gfp_t gfp; /* GFP flags to use */ - unsigned int direct_bv_count; /* Number of elements in direct_bv[] */ unsigned int debug_id; unsigned int rsize; /* Maximum read size (0 for none) */ unsigned int wsize; /* Maximum write size (0 for none) */ @@ -277,7 +279,6 @@ struct netfs_io_request { unsigned int nr_group_rel; /* Number of refs to release on ->group */ spinlock_t lock; /* Lock for queuing subreqs */ enum netfs_io_origin origin; /* Origin of the request */ - bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */ refcount_t ref; unsigned long flags; #define NETFS_RREQ_IN_PROGRESS 0 /* Unlocked when the request completes (= has ref) */ @@ -428,7 +429,7 @@ void netfs_single_mark_inode_dirty(struct inode *inode); ssize_t netfs_read_single(struct inode *inode, struct file *file, struct i= ov_iter *iter); int netfs_writeback_single(struct address_space *mapping, struct writeback_control *wbc, - struct iov_iter *iter); + struct iov_iter *iter, size_t len); =20 /* Address operations API */ struct readahead_control; @@ -473,12 +474,6 @@ void netfs_end_io_write(struct inode *inode); int netfs_start_io_direct(struct inode *inode); void netfs_end_io_direct(struct inode *inode); =20 -/* Miscellaneous APIs. */ -struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, - unsigned int trace /*enum netfs_folioq_trace*/); -void netfs_folioq_free(struct folio_queue *folioq, - unsigned int trace /*enum netfs_trace_folioq*/); - /* Buffer wrangling helpers API. */ int netfs_alloc_folioq_buffer(struct address_space *mapping, struct folio_queue **_buffer, diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 927287a7290c..4b2ffd14a1ca 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -227,7 +227,9 @@ EM(netfs_folio_trace_redirtied, "redirtied") \ EM(netfs_folio_trace_store, "store") \ EM(netfs_folio_trace_store_copy, "store-copy") \ - E_(netfs_folio_trace_store_plus, "store+") + EM(netfs_folio_trace_store_plus, "store+") \ + EM(netfs_folio_trace_zero, "zero") \ + E_(netfs_folio_trace_zero_ra, "zero-ra") =20 #define netfs_collect_contig_traces \ EM(netfs_contig_trace_collect, "Collect") \ @@ -240,13 +242,13 @@ EM(netfs_trace_donate_to_next, "to-next") \ E_(netfs_trace_donate_to_deferred_next, "defer-next") =20 -#define netfs_folioq_traces \ - EM(netfs_trace_folioq_alloc_buffer, "alloc-buf") \ - EM(netfs_trace_folioq_clear, "clear") \ - EM(netfs_trace_folioq_delete, "delete") \ - EM(netfs_trace_folioq_make_space, "make-space") \ - EM(netfs_trace_folioq_rollbuf_init, "roll-init") \ - E_(netfs_trace_folioq_read_progress, "r-progress") +#define netfs_bvecq_traces \ + EM(netfs_trace_bvecq_alloc_buffer, "alloc-buf") \ + EM(netfs_trace_bvecq_clear, "clear") \ + EM(netfs_trace_bvecq_delete, "delete") \ + EM(netfs_trace_bvecq_make_space, "make-space") \ + EM(netfs_trace_bvecq_rollbuf_init, "roll-init") \ + E_(netfs_trace_bvecq_read_progress, "r-progress") =20 #ifndef __NETFS_DECLARE_TRACE_ENUMS_ONCE_ONLY #define __NETFS_DECLARE_TRACE_ENUMS_ONCE_ONLY @@ -266,7 +268,7 @@ enum netfs_sreq_ref_trace { netfs_sreq_ref_traces } __m= ode(byte); enum netfs_folio_trace { netfs_folio_traces } __mode(byte); enum netfs_collect_contig_trace { netfs_collect_contig_traces } __mode(byt= e); enum netfs_donate_trace { netfs_donate_traces } __mode(byte); -enum netfs_folioq_trace { netfs_folioq_traces } __mode(byte); +enum netfs_bvecq_trace { netfs_bvecq_traces } __mode(byte); =20 #endif =20 @@ -290,7 +292,7 @@ netfs_sreq_ref_traces; netfs_folio_traces; netfs_collect_contig_traces; netfs_donate_traces; -netfs_folioq_traces; +netfs_bvecq_traces; =20 /* * Now redefine the EM() and E_() macros to map the enums to the strings t= hat @@ -394,10 +396,10 @@ TRACE_EVENT(netfs_sreq, __entry->len =3D sreq->len; __entry->transferred =3D sreq->transferred; __entry->start =3D sreq->start; - __entry->slot =3D sreq->io_iter.folioq_slot; + __entry->slot =3D sreq->content.slot; ), =20 - TP_printk("R=3D%08x[%x] %s %s f=3D%03x s=3D%llx %zx/%zx s=3D%u e=3D%d= ", + TP_printk("R=3D%08x[%x] %s %s f=3D%03x s=3D%llx %zx/%zx bv=3D%u e=3D%= d", __entry->rreq, __entry->index, __print_symbolic(__entry->source, netfs_sreq_sources), __print_symbolic(__entry->what, netfs_sreq_traces), @@ -769,27 +771,25 @@ TRACE_EVENT(netfs_collect_stream, __entry->collected_to, __entry->issued_to) ); =20 -TRACE_EVENT(netfs_folioq, - TP_PROTO(const struct folio_queue *fq, - enum netfs_folioq_trace trace), +TRACE_EVENT(netfs_bvecq, + TP_PROTO(const struct bvecq *bq, + enum netfs_bvecq_trace trace), =20 - TP_ARGS(fq, trace), + TP_ARGS(bq, trace), =20 TP_STRUCT__entry( - __field(unsigned int, rreq) __field(unsigned int, id) - __field(enum netfs_folioq_trace, trace) + __field(enum netfs_bvecq_trace, trace) ), =20 TP_fast_assign( - __entry->rreq =3D fq ? fq->rreq_id : 0; - __entry->id =3D fq ? fq->debug_id : 0; + __entry->id =3D bq ? bq->priv : 0; __entry->trace =3D trace; ), =20 - TP_printk("R=3D%08x fq=3D%x %s", - __entry->rreq, __entry->id, - __print_symbolic(__entry->trace, netfs_folioq_traces)) + TP_printk("bq=3D%x %s", + __entry->id, + __print_symbolic(__entry->trace, netfs_bvecq_traces)) ); =20 TRACE_EVENT(netfs_read_progress_at, From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8AF8C351C1E for ; Mon, 24 Aug 2026 14:45:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582724; cv=none; b=VritSY0npbgZPrTAqbIKRLFOR7rSbuvkbDc4ilUDVOfJY3c6k0PaYVCEpRKdSDXYoTzUx3ZXAbe7K9jwJrEySNC8U04W2/JthylSH3ywPSAdkn8FSRqJxSlf1WymBvb7opoc8XRjtXdnVntN5nJNL4oGaPLIKHIOqFPoZUnmPTU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582724; c=relaxed/simple; bh=5zSMz6OLTUHrQTCIhBj7aLAr9qMlQqWCFMxBwsTGFEU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Zg3+HeHYK7j1cBl8RbkiD2se6RSXR32dQJhQUs/B5xlVWAghjLCnUFfLfrspcodygdYsJD7LWddNV9jmjvOaKGGcnvp6+xL85vzJbwDE0xaTUqvHJP7mOAPyGenkWSOWaIX0E5qxgTaJeb0E75vDuovua+oYxw5eu7VFQ0WbjqU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=gLxL+k2S; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="gLxL+k2S" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582720; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=uz94seJkld4/VYmPIzePLWpyWumrSoaTR2JUNJSPhWI=; b=gLxL+k2SKHBo6xmIjwz5hfbeKHDZFI+foaXttoWVPu8KB+PuUD/PFt2dwMDi5za+aIAj2R LHrgdx0ohvoyv56tnX5BsrHrdO3Jo2F915j22CPXJbMkGL7uQ3+Y5ZVfCsH3mhiMbtSgsv rJLD/mENOjN4cCFwSJ+G57guJkvI7J0= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-103-mDOs-YwvM82jIQKWZb6chA-1; Mon, 24 Aug 2026 10:45:16 -0400 X-MC-Unique: mDOs-YwvM82jIQKWZb6chA-1 X-Mimecast-MFC-AGG-ID: mDOs-YwvM82jIQKWZb6chA_1787582713 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D3E731944B30; Mon, 24 Aug 2026 14:45:12 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2B9EF30002ED; Mon, 24 Aug 2026 14:45:05 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, Shyam Prasad N , Tom Talpey Subject: [PATCH v10 25/35] smbdirect: Remove support for ITER_FOLIOQ from smbdirect_map_sges_from_iter() Date: Mon, 24 Aug 2026 15:41:18 +0100 Message-ID: <20260824144130.759997-26-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" netfslib now only presents an bvecq queue and an associated ITER_BVECQ iterator to the filesystem, so it isn't going to see the ITER_FOLIOQ iterator. So remove that code. Netfslib also won't supply ITER_BVEC/KVEC iterators, though smbdirect might; further in future, it won't supply iterators at all, but rather a bvecq slice (that can be used to construct an iterator). Signed-off-by: David Howells Acked-by: Stefan Metzmacher cc: Paulo Alcantara cc: Namjae Jeon cc: Stefan Metzmacher cc: Shyam Prasad N cc: Tom Talpey cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/smb/smbdirect/connection.c | 68 ----------------------------------- 1 file changed, 68 deletions(-) diff --git a/fs/smb/smbdirect/connection.c b/fs/smb/smbdirect/connection.c index f5e7e75dd809..f217cf127cf9 100644 --- a/fs/smb/smbdirect/connection.c +++ b/fs/smb/smbdirect/connection.c @@ -6,7 +6,6 @@ =20 #include "internal.h" #include -#include =20 struct smbdirect_map_sges { struct ib_sge *sge; @@ -2131,70 +2130,6 @@ static ssize_t smbdirect_map_sges_from_kvec(struct i= ov_iter *iter, return ret; } =20 -/* - * Extract folio fragments from a FOLIOQ-class iterator and add them to an - * ib_sge list. The folios are not pinned. - */ -static ssize_t smbdirect_map_sges_from_folioq(struct iov_iter *iter, - struct smbdirect_map_sges *state, - ssize_t maxsize) -{ - const struct folio_queue *folioq =3D iter->folioq; - unsigned int slot =3D iter->folioq_slot; - ssize_t ret =3D 0; - size_t offset =3D iter->iov_offset; - - if (WARN_ON_ONCE(!folioq)) - return -EIO; - - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - if (WARN_ON_ONCE(!folioq)) - return -EIO; - slot =3D 0; - } - - do { - struct folio *folio =3D folioq_folio(folioq, slot); - size_t fsize =3D folioq_folio_size(folioq, slot); - - if (offset < fsize) { - size_t part =3D umin(maxsize, fsize - offset); - bool ok; - - ok =3D smbdirect_map_sges_single_page(state, - folio_page(folio, 0), - offset, - part); - if (!ok) - return -EIO; - - offset +=3D part; - ret +=3D part; - maxsize -=3D part; - } - - if (offset >=3D fsize) { - offset =3D 0; - slot++; - if (slot >=3D folioq_nr_slots(folioq)) { - if (!folioq->next) { - WARN_ON_ONCE(ret < iter->count); - break; - } - folioq =3D folioq->next; - slot =3D 0; - } - } - } while (state->num_sge < state->max_sge && maxsize > 0); - - iter->folioq =3D folioq; - iter->folioq_slot =3D slot; - iter->iov_offset =3D offset; - iter->count -=3D ret; - return ret; -} - /* * Extract page fragments from up to the given amount of the source iterat= or * and build up an ib_sge list that refers to all of those bits. The ib_s= ge list @@ -2225,9 +2160,6 @@ static ssize_t smbdirect_map_sges_from_iter(struct io= v_iter *iter, size_t len, case ITER_KVEC: ret =3D smbdirect_map_sges_from_kvec(iter, state, len); break; - case ITER_FOLIOQ: - ret =3D smbdirect_map_sges_from_folioq(iter, state, len); - break; default: WARN_ONCE(1, "iov_iter_type[%u]\n", iov_iter_type(iter)); return -EIO; From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3946A357D00 for ; Mon, 24 Aug 2026 14:45:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582729; cv=none; b=g7lgr3Q4UzSrVgPHpT93zjYqQzTwgONtglbxPjgp5IdYbjZoq0xV/dYudhjFZXAkHssk6qz8IwJrq3oXGA0mOMuY1yNOzCD9YCL/C2aU8I7oowcYVwguZjQjRkZ04UEG/u6VobkqNR9sxZHVDLQevXvelck9nWbhUkSjgxvLm/Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582729; c=relaxed/simple; bh=jHTN9fIV0m8rEl6p/PwSILd+jkIW+fv35VL0vlLuC1w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=cUDM40QMeASJxhSQSEhFjLgTMpo8t1tv+O3TYvpDdWanJMwJ4x87x7uu8X2+v+jaSBaNUGNEgwhvhiNTQDNBaVBqLUKSCjvv6Xfo3bQUyhIe2ISHemJfo5LZM25kmj8Dx/UkLHV9F26fJUx/1kaE1of1xXayeLIzJq8VZmYD39g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=bUd2/Mea; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="bUd2/Mea" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582727; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=55F89iKzGp4okT73Es5bUJSwx7jU3VZ6IoRoXIUEMpE=; b=bUd2/MeaceGZYZBscPz2Sgto6ntl5xjWWHzNohkapEkzP6raXF+9nRLlr242Keo2TYNzO3 1S+i0QuunPv4gvL2Hs9bKbVPxJZIvL6r2Do3sjnlK+ZOCVdXjXuLFtM0Y5i/9G8OAU/eJ8 JCYNHCjAuJTyH4u2c3GpiwXOj5pbrmo= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-685-ojRkqQFOOIO7PUaJW2pavA-1; Mon, 24 Aug 2026 10:45:23 -0400 X-MC-Unique: ojRkqQFOOIO7PUaJW2pavA-1 X-Mimecast-MFC-AGG-ID: ojRkqQFOOIO7PUaJW2pavA_1787582720 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id AF33A19541B1; Mon, 24 Aug 2026 14:45:20 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 94C0618005AD; Mon, 24 Aug 2026 14:45:14 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 26/35] netfs: Remove netfs_alloc/free_folioq_buffer() Date: Mon, 24 Aug 2026 15:41:19 +0100 Message-ID: <20260824144130.759997-27-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 Content-Type: text/plain; charset="utf-8" Remove netfs_alloc/free_folioq_buffer() as bvecq_alloc_*() and bvecq_put() should now be used instead. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: Namjae Jeon cc: linux-cifs@vger.kernel.org cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/afs/dir_edit.c | 1 - fs/netfs/misc.c | 98 ----------------------------------------- fs/smb/client/smb2ops.c | 1 - include/linux/netfs.h | 6 --- 4 files changed, 106 deletions(-) diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c index 01c6265e8865..31fdef23c6b6 100644 --- a/fs/afs/dir_edit.c +++ b/fs/afs/dir_edit.c @@ -10,7 +10,6 @@ #include #include #include -#include #include "internal.h" #include "xdr_fs.h" =20 diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index a2a2aeb1bb0d..4fa09560fb4b 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -8,104 +8,6 @@ #include #include "internal.h" =20 -#if 0 -/** - * netfs_alloc_folioq_buffer - Allocate buffer space into a folio queue - * @mapping: Address space to set on the folio (or NULL). - * @_buffer: Pointer to the folio queue to add to (may point to a NULL; up= dated). - * @_cur_size: Current size of the buffer (updated). - * @size: Target size of the buffer. - * @gfp: The allocation constraints. - */ -int netfs_alloc_folioq_buffer(struct address_space *mapping, - struct folio_queue **_buffer, - size_t *_cur_size, ssize_t size, gfp_t gfp) -{ - struct folio_queue *tail =3D *_buffer, *p; - - size =3D round_up(size, PAGE_SIZE); - if (*_cur_size >=3D size) - return 0; - - if (tail) - while (tail->next) - tail =3D tail->next; - - do { - struct folio *folio; - int order =3D 0, slot; - - if (!tail || folioq_full(tail)) { - p =3D netfs_folioq_alloc(0, GFP_NOFS, netfs_trace_folioq_alloc_buffer); - if (!p) - return -ENOMEM; - if (tail) { - tail->next =3D p; - p->prev =3D tail; - } else { - *_buffer =3D p; - } - tail =3D p; - } - - if (size - *_cur_size > PAGE_SIZE) - order =3D umin(ilog2(size - *_cur_size) - PAGE_SHIFT, - MAX_PAGECACHE_ORDER); - - folio =3D folio_alloc(gfp, order); - if (!folio && order > 0) - folio =3D folio_alloc(gfp, 0); - if (!folio) - return -ENOMEM; - - folio->mapping =3D mapping; - folio->index =3D *_cur_size / PAGE_SIZE; - trace_netfs_folio(folio, netfs_folio_trace_alloc_buffer); - slot =3D folioq_append_mark(tail, folio); - *_cur_size +=3D folioq_folio_size(tail, slot); - } while (*_cur_size < size); - - return 0; -} -EXPORT_SYMBOL(netfs_alloc_folioq_buffer); - -/** - * netfs_free_folioq_buffer - Free a folio queue. - * @fq: The start of the folio queue to free - * - * Free up a chain of folio_queues and, if marked, the marked folios they = point - * to. - */ -void netfs_free_folioq_buffer(struct folio_queue *fq) -{ - struct folio_queue *next; - struct folio_batch fbatch; - - folio_batch_init(&fbatch); - - for (; fq; fq =3D next) { - for (int slot =3D 0; slot < folioq_count(fq); slot++) { - struct folio *folio =3D folioq_folio(fq, slot); - - if (!folio || - !folioq_is_marked(fq, slot)) - continue; - - trace_netfs_folio(folio, netfs_folio_trace_put); - if (folio_batch_add(&fbatch, folio)) - folio_batch_release(&fbatch); - } - - netfs_stat_d(&netfs_n_folioq); - next =3D fq->next; - kfree(fq); - } - - folio_batch_release(&fbatch); -} -EXPORT_SYMBOL(netfs_free_folioq_buffer); -#endif - /** * netfs_dirty_folio - Mark folio dirty and pin a cache object for writeba= ck * @mapping: The mapping the folio belongs to. diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 885f0cc861ed..97823bcf198c 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include "cifsfs.h" #include "cifsglob.h" diff --git a/include/linux/netfs.h b/include/linux/netfs.h index a58d2b6914ea..926c3c6f311b 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -474,12 +474,6 @@ void netfs_end_io_write(struct inode *inode); int netfs_start_io_direct(struct inode *inode); void netfs_end_io_direct(struct inode *inode); =20 -/* Buffer wrangling helpers API. */ -int netfs_alloc_folioq_buffer(struct address_space *mapping, - struct folio_queue **_buffer, - size_t *_cur_size, ssize_t size, gfp_t gfp); -void netfs_free_folioq_buffer(struct folio_queue *fq); - /* Writeback exclusion API. */ bool netfs_wb_begin(struct netfs_inode *ictx, bool nowait); void netfs_wb_end(struct netfs_inode *ictx); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 52B51361978 for ; Mon, 24 Aug 2026 14:45:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582737; cv=none; b=ew/w2diMk0TVZSE/xVezqU64FPvdZE5gekGaypEhY8A+z3cBj+Ycu5hcNVllrl5NQaBT8oJQ1UCw8ZqTIbCY8VH2/+X8jkoUKXBMU/LF9QAizwDLHwXPbEr8L0EfmlRLn6W9NKjNnJzxEAe5MSBz79F7o+yIaXUiFqjFgNWY8Qs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582737; c=relaxed/simple; bh=xhG1xuKXon8FpvfH+l1xI2dc1namucrVO3PpJA4Zha4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JFP1Gn1SCP6AJw7hpfLLfRWEAEkW9b7IWi1aeDolDpX7U0No9ta+uxRyM4FJSCKWkpL9wrDNA+Cz703YfCeq7ktktWL4Eag/NSw9pP0EUvxSgNbYlm4bRQoVJU7cg6zLhmQEeNYrTukfrYSONR3RKZuKdXHGLmUFIcaCUeQktvE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=fqvnqAqi; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="fqvnqAqi" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582735; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=AoZPI18aCpm9EVwf/WssgeTToyeGup35nFlfN3aqYBQ=; b=fqvnqAqiM8tbUM0mNiPgXIDGwnrPMO6XW2TL5wflbfTlNQUr5ETj7BaH1eorF2eo3hk4hI dtY3YEp1QDFoXFSNac397fiz86NMBcPe4tvHjnS3fCNHWFO4OUE5ywMpPZK1W18jU1kkSj P+pMqeAtFlxEQDNn2yy+Hfsl5dV7Wes= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-544-ophg8iASOd2UFBYfsED8TQ-1; Mon, 24 Aug 2026 10:45:31 -0400 X-MC-Unique: ophg8iASOd2UFBYfsED8TQ-1 X-Mimecast-MFC-AGG-ID: ophg8iASOd2UFBYfsED8TQ_1787582728 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8C5261955D62; Mon, 24 Aug 2026 14:45:28 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 6BD471955F0F; Mon, 24 Aug 2026 14:45:22 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 27/35] netfs: Remove netfs_extract_user_iter() Date: Mon, 24 Aug 2026 15:41:20 +0100 Message-ID: <20260824144130.759997-28-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" Remove netfs_extract_user_iter() as it has been replaced with netfs_extract_iter(). Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/iterator.c | 104 ------------------------------------------ include/linux/netfs.h | 3 -- 2 files changed, 107 deletions(-) diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c index c207eacd5c5d..65164a1761e3 100644 --- a/fs/netfs/iterator.c +++ b/fs/netfs/iterator.c @@ -157,110 +157,6 @@ ssize_t netfs_extract_iter(struct iov_iter *orig, siz= e_t max_len, size_t max_pag EXPORT_SYMBOL_GPL(netfs_extract_iter); =20 #if 0 -/** - * netfs_extract_user_iter - Extract the pages from a user iterator into a= bvec - * @orig: The original iterator - * @orig_len: The amount of iterator to copy - * @new: The iterator to be set up - * @extraction_flags: Flags to qualify the request - * - * Extract the page fragments from the given amount of the source iterator= and - * build up a second iterator that refers to all of those bits. This allo= ws - * the original iterator to be disposed of. - * - * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-pee= r DMA be - * allowed on the pages extracted. - * - * On success, the number of elements in the bvec is returned, the original - * iterator will have been advanced by the amount extracted. - * - * The iov_iter_extract_mode() function should be used to query how cleanup - * should be performed. - */ -ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len, - struct iov_iter *new, - iov_iter_extraction_t extraction_flags) -{ - struct bio_vec *bv =3D NULL; - struct page **pages; - unsigned int cur_npages; - unsigned int max_pages; - unsigned int npages =3D 0; - unsigned int i; - ssize_t ret =3D 0; - size_t count =3D orig_len, offset, len; - size_t bv_size, pg_size; - - if (WARN_ON_ONCE(!iter_is_ubuf(orig) && !iter_is_iovec(orig))) - return -EIO; - - max_pages =3D iov_iter_npages(orig, INT_MAX); - bv_size =3D array_size(max_pages, sizeof(*bv)); - bv =3D kvmalloc(bv_size, GFP_KERNEL); - if (!bv) - return -ENOMEM; - - /* Put the page list at the end of the bvec list storage. bvec - * elements are larger than page pointers, so as long as we work - * 0->last, we should be fine. - */ - pg_size =3D array_size(max_pages, sizeof(*pages)); - pages =3D (void *)bv + bv_size - pg_size; - - while (count && npages < max_pages) { - ret =3D iov_iter_extract_pages(orig, &pages, count, - max_pages - npages, extraction_flags, - &offset); - if (unlikely(ret <=3D 0)) { - ret =3D ret ?: -EIO; - break; - } - - if (WARN(ret > count, - "%s: extract_pages overrun %zd > %zu bytes\n", - __func__, ret, count)) { - ret =3D -EIO; - break; - } - - cur_npages =3D DIV_ROUND_UP(offset + ret, PAGE_SIZE); - if (WARN(cur_npages > max_pages - npages, - "%s: extract_pages overrun %u > %u pages\n", - __func__, npages + cur_npages, max_pages)) { - ret =3D -EIO; - break; - } - - count -=3D ret; - ret +=3D offset; - - for (i =3D 0; i < cur_npages; i++) { - len =3D ret > PAGE_SIZE ? PAGE_SIZE : ret; - bvec_set_page(bv + npages + i, *pages++, len - offset, offset); - ret -=3D len; - offset =3D 0; - } - - npages +=3D cur_npages; - } - - /* Note: Don't try to clean up after EIO. Either we got no pages, so - * nothing to clean up, or we got a buffer overrun, memory corruption - * and can't trust the stuff in the buffer (a WARN was emitted). - */ - - if (ret < 0 && (ret =3D=3D -ENOMEM || npages =3D=3D 0)) { - for (i =3D 0; i < npages; i++) - unpin_user_page(bv[i].bv_page); - kvfree(bv); - return ret; - } - - iov_iter_bvec(new, orig->data_source, bv, npages, orig_len - count); - return npages; -} -EXPORT_SYMBOL_GPL(netfs_extract_user_iter); - /* * Select the span of a bvec iterator we're going to use. Limit it by bot= h maximum * size and maximum number of segments. Returns the size of the span in b= ytes. diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 926c3c6f311b..8de9ce35f70d 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -459,9 +459,6 @@ void netfs_put_subrequest(struct netfs_io_subrequest *s= ubreq, ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t m= ax_pages, struct bvecq **_bvecq_head, iov_iter_extraction_t extraction_flags, gfp_t gfp); -ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len, - struct iov_iter *new, - iov_iter_extraction_t extraction_flags); size_t netfs_limit_iter(const struct iov_iter *iter, size_t start_offset, size_t max_size, size_t max_segs); void netfs_prepare_write_failed(struct netfs_io_subrequest *subreq); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2EAE4367F58 for ; Mon, 24 Aug 2026 14:45:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582747; cv=none; b=SiiJcGBSpjva+18qWk1bVOO0O1U5vb9c9FbgdnhKRHGRnUddZ6HJ/uDiVNixu05Won649uOG2IsA1u81NWiThofBHqOwOclBve4IPXubF/mI5M/uIUtXid2oZD4tzJN9B7lie95ENnO57UAlRbfAtxgXvVzmmtI3/rnd3cRjoLQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582747; c=relaxed/simple; bh=dbYdtltEzR99Nv8eGbv08ilia0OTvq4p/0cvb8fEnMY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BeC7JkC4WBNLTU2tKJefxgajA2kyUkyuCBUbYwtp3Gucy21gjuYY6jXWmSeVuClrBsG9nMJz3jt1DpbaIHu18xdtJmcC9mirtZzMP5U7QZ7joScGn3fqz0Ik5kY8IC5nUwixAO9BO1ZUGLx91Q4piDQ0qB8RvCXZdw5/bLJF2l0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Owhn8tgs; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Owhn8tgs" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582744; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=d7ghCZI4bd99o29JF3qKSAAw57gS5MneY4nJOQZ7iXY=; b=Owhn8tgs3ygEktM+ZLB0iDzIYtO0vTCzlqkU0My1KWbhcEL9xBD8MLlsYKWyTfm+xkmxza K8GCADyLyvGj+4maEgxr09tttwAydoe4yqhLQ/XbIkQx8gU6FJ3WbQjcHNx0bHMscvM7QX VZEKCga6djdDy7oNAUVEOiT1tflZmFY= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-527-Jup2YZU-PVyoMtOfpKVHag-1; Mon, 24 Aug 2026 10:45:40 -0400 X-MC-Unique: Jup2YZU-PVyoMtOfpKVHag-1 X-Mimecast-MFC-AGG-ID: Jup2YZU-PVyoMtOfpKVHag_1787582736 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 83FE11955F3C; Mon, 24 Aug 2026 14:45:36 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2DE2830002ED; Mon, 24 Aug 2026 14:45:29 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 28/35] iov_iter: Remove ITER_FOLIOQ Date: Mon, 24 Aug 2026 15:41:21 +0100 Message-ID: <20260824144130.759997-29-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Remove ITER_FOLIOQ as it's no longer used. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- include/linux/iov_iter.h | 65 +-------- include/linux/uio.h | 12 -- lib/iov_iter.c | 175 +----------------------- lib/scatterlist.c | 69 +--------- lib/tests/kunit_iov_iter.c | 271 ------------------------------------- 5 files changed, 7 insertions(+), 585 deletions(-) diff --git a/include/linux/iov_iter.h b/include/linux/iov_iter.h index ec0aa2893689..39ffd5878f50 100644 --- a/include/linux/iov_iter.h +++ b/include/linux/iov_iter.h @@ -11,7 +11,6 @@ #include #include #include -#include =20 typedef size_t (*iov_step_f)(void *iter_base, size_t progress, size_t len, void *priv, void *priv2); @@ -207,62 +206,6 @@ size_t iterate_bvecq(struct iov_iter *iter, size_t len= , void *priv, void *priv2, return progress; } =20 -/* - * Handle ITER_FOLIOQ. - */ -static __always_inline -size_t iterate_folioq(struct iov_iter *iter, size_t len, void *priv, void = *priv2, - iov_step_f step) -{ - const struct folio_queue *folioq =3D iter->folioq; - unsigned int slot =3D iter->folioq_slot; - size_t progress =3D 0, skip =3D iter->iov_offset; - - if (slot =3D=3D folioq_nr_slots(folioq)) { - /* The iterator may have been extended. */ - folioq =3D folioq->next; - slot =3D 0; - } - - do { - struct folio *folio =3D folioq_folio(folioq, slot); - size_t part, remain =3D 0, consumed; - size_t fsize; - void *base; - - if (!folio) - break; - - fsize =3D folioq_folio_size(folioq, slot); - if (skip < fsize) { - base =3D kmap_local_folio(folio, skip); - part =3D umin(len, PAGE_SIZE - skip % PAGE_SIZE); - remain =3D step(base, progress, part, priv, priv2); - kunmap_local(base); - consumed =3D part - remain; - len -=3D consumed; - progress +=3D consumed; - skip +=3D consumed; - } - if (skip >=3D fsize) { - skip =3D 0; - slot++; - if (slot =3D=3D folioq_nr_slots(folioq) && folioq->next) { - folioq =3D folioq->next; - slot =3D 0; - } - } - if (remain) - break; - } while (len); - - iter->folioq_slot =3D slot; - iter->folioq =3D folioq; - iter->iov_offset =3D skip; - iter->count -=3D progress; - return progress; -} - /* * Handle ITER_XARRAY. */ @@ -374,8 +317,6 @@ size_t iterate_and_advance2(struct iov_iter *iter, size= _t len, void *priv, return iterate_kvec(iter, len, priv, priv2, step); if (iov_iter_is_bvecq(iter)) return iterate_bvecq(iter, len, priv, priv2, step); - if (iov_iter_is_folioq(iter)) - return iterate_folioq(iter, len, priv, priv2, step); if (iov_iter_is_xarray(iter)) return iterate_xarray(iter, len, priv, priv2, step); return iterate_discard(iter, len, priv, priv2, step); @@ -410,8 +351,8 @@ size_t iterate_and_advance(struct iov_iter *iter, size_= t len, void *priv, * buffer is presented in segments, which for kernel iteration are broken = up by * physical pages and mapped, with the mapped address being presented. * - * [!] Note This will only handle BVEC, KVEC, BVECQ, FOLIOQ, XARRAY and - * DISCARD-type iterators; it will not handle UBUF or IOVEC-type iterators. + * [!] Note This will only handle BVEC, KVEC, BVECQ, XARRAY and DISCARD-ty= pe + * iterators; it will not handle UBUF or IOVEC-type iterators. * * A step functions, @step, must be provided, one for handling mapped kern= el * addresses and the other is given user addresses which have the potentia= l to @@ -440,8 +381,6 @@ size_t iterate_and_advance_kernel(struct iov_iter *iter= , size_t len, void *priv, return iterate_kvec(iter, len, priv, priv2, step); if (iov_iter_is_bvecq(iter)) return iterate_bvecq(iter, len, priv, priv2, step); - if (iov_iter_is_folioq(iter)) - return iterate_folioq(iter, len, priv, priv2, step); if (iov_iter_is_xarray(iter)) return iterate_xarray(iter, len, priv, priv2, step); return iterate_discard(iter, len, priv, priv2, step); diff --git a/include/linux/uio.h b/include/linux/uio.h index f7cfa6ea8213..e84a0c4f28c6 100644 --- a/include/linux/uio.h +++ b/include/linux/uio.h @@ -11,7 +11,6 @@ #include =20 struct page; -struct folio_queue; =20 typedef unsigned int __bitwise iov_iter_extraction_t; =20 @@ -27,7 +26,6 @@ enum iter_type { ITER_BVEC, ITER_KVEC, ITER_BVECQ, - ITER_FOLIOQ, ITER_XARRAY, ITER_DISCARD, }; @@ -70,7 +68,6 @@ struct iov_iter { const struct kvec *kvec; const struct bio_vec *bvec; const struct bvecq *bvecq; - const struct folio_queue *folioq; struct xarray *xarray; void __user *ubuf; }; @@ -80,7 +77,6 @@ struct iov_iter { union { unsigned long nr_segs; u16 bvecq_slot; - u8 folioq_slot; loff_t xarray_start; }; }; @@ -153,11 +149,6 @@ static inline bool iov_iter_is_bvecq(const struct iov_= iter *i) return iov_iter_type(i) =3D=3D ITER_BVECQ; } =20 -static inline bool iov_iter_is_folioq(const struct iov_iter *i) -{ - return iov_iter_type(i) =3D=3D ITER_FOLIOQ; -} - static inline bool iov_iter_is_xarray(const struct iov_iter *i) { return iov_iter_type(i) =3D=3D ITER_XARRAY; @@ -306,9 +297,6 @@ void iov_iter_discard(struct iov_iter *i, unsigned int = direction, size_t count); void iov_iter_bvec_queue(struct iov_iter *i, unsigned int direction, const struct bvecq *bvecq, unsigned int first_slot, unsigned int offset, size_t count); -void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction, - const struct folio_queue *folioq, - unsigned int first_slot, unsigned int offset, size_t count); void iov_iter_xarray(struct iov_iter *i, unsigned int direction, struct xa= rray *xarray, loff_t start, size_t count); ssize_t iov_iter_get_pages2(struct iov_iter *i, struct page **pages, diff --git a/lib/iov_iter.c b/lib/iov_iter.c index c7f7ff9a9b37..fdb51396157d 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -572,39 +572,6 @@ static void iov_iter_bvecq_advance(struct iov_iter *i,= size_t by) i->bvecq =3D bq; } =20 -static void iov_iter_folioq_advance(struct iov_iter *i, size_t size) -{ - const struct folio_queue *folioq =3D i->folioq; - unsigned int slot =3D i->folioq_slot; - - if (!i->count) - return; - i->count -=3D size; - - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - slot =3D 0; - } - - size +=3D i->iov_offset; /* From beginning of current segment. */ - do { - size_t fsize =3D folioq_folio_size(folioq, slot); - - if (likely(size < fsize)) - break; - size -=3D fsize; - slot++; - if (slot >=3D folioq_nr_slots(folioq) && folioq->next) { - folioq =3D folioq->next; - slot =3D 0; - } - } while (size); - - i->iov_offset =3D size; - i->folioq_slot =3D slot; - i->folioq =3D folioq; -} - void iov_iter_advance(struct iov_iter *i, size_t size) { if (unlikely(i->count < size)) @@ -619,8 +586,6 @@ void iov_iter_advance(struct iov_iter *i, size_t size) iov_iter_bvec_advance(i, size); } else if (iov_iter_is_bvecq(i)) { iov_iter_bvecq_advance(i, size); - } else if (iov_iter_is_folioq(i)) { - iov_iter_folioq_advance(i, size); } else if (iov_iter_is_discard(i)) { i->count -=3D size; } @@ -654,32 +619,6 @@ static void iov_iter_bvecq_revert(struct iov_iter *i, = size_t unroll) i->bvecq =3D bq; } =20 -static void iov_iter_folioq_revert(struct iov_iter *i, size_t unroll) -{ - const struct folio_queue *folioq =3D i->folioq; - unsigned int slot =3D i->folioq_slot; - - for (;;) { - size_t fsize; - - if (slot =3D=3D 0) { - folioq =3D folioq->prev; - slot =3D folioq_nr_slots(folioq); - } - slot--; - - fsize =3D folioq_folio_size(folioq, slot); - if (unroll <=3D fsize) { - i->iov_offset =3D fsize - unroll; - break; - } - unroll -=3D fsize; - } - - i->folioq_slot =3D slot; - i->folioq =3D folioq; -} - void iov_iter_revert(struct iov_iter *i, size_t unroll) { if (!unroll) @@ -714,9 +653,6 @@ void iov_iter_revert(struct iov_iter *i, size_t unroll) } else if (iov_iter_is_bvecq(i)) { i->iov_offset =3D 0; iov_iter_bvecq_revert(i, unroll); - } else if (iov_iter_is_folioq(i)) { - i->iov_offset =3D 0; - iov_iter_folioq_revert(i, unroll); } else { /* same logics for iovec and kvec */ const struct iovec *iov =3D iter_iov(i); while (1) { @@ -766,8 +702,6 @@ size_t iov_iter_single_seg_count(const struct iov_iter = *i) } return min(i->count, bq->bv[slot].bv_len - offset); } - if (unlikely(iov_iter_is_folioq(i))) - return umin(folioq_folio_size(i->folioq, i->folioq_slot), i->count); return i->count; } EXPORT_SYMBOL(iov_iter_single_seg_count); @@ -833,36 +767,6 @@ void iov_iter_bvec_queue(struct iov_iter *i, unsigned = int direction, } EXPORT_SYMBOL(iov_iter_bvec_queue); =20 -/** - * iov_iter_folio_queue - Initialise an I/O iterator to use the folios in = a folio queue - * @i: The iterator to initialise. - * @direction: The direction of the transfer. - * @folioq: The starting point in the folio queue. - * @first_slot: The first slot in the folio queue to use - * @offset: The offset into the folio in the first slot to start at - * @count: The size of the I/O buffer in bytes. - * - * Set up an I/O iterator to either draw data out of the pages attached to= an - * inode or to inject data into those pages. The pages *must* be prevented - * from evaporation, either by taking a ref on them or locking them by the - * caller. - */ -void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction, - const struct folio_queue *folioq, unsigned int first_slot, - unsigned int offset, size_t count) -{ - BUG_ON(direction & ~1); - *i =3D (struct iov_iter) { - .iter_type =3D ITER_FOLIOQ, - .data_source =3D direction, - .folioq =3D folioq, - .folioq_slot =3D first_slot, - .count =3D count, - .iov_offset =3D offset, - }; -} -EXPORT_SYMBOL(iov_iter_folio_queue); - /** * iov_iter_xarray - Initialise an I/O iterator to use the pages in an xar= ray * @i: The iterator to initialise. @@ -1006,9 +910,7 @@ unsigned long iov_iter_alignment(const struct iov_iter= *i) if (iov_iter_is_bvecq(i)) return iov_iter_alignment_bvecq(i); =20 - /* With both xarray and folioq types, we're dealing with whole folios. */ - if (iov_iter_is_folioq(i)) - return i->iov_offset | i->count; + /* With the xarray type, we're dealing with whole folios. */ if (iov_iter_is_xarray(i)) return (i->xarray_start + i->iov_offset) | i->count; =20 @@ -1265,11 +1167,6 @@ int iov_iter_npages(const struct iov_iter *i, int ma= xpages) return bvec_npages(i, maxpages); if (iov_iter_is_bvecq(i)) return iov_npages_bvecq(i, maxpages); - if (iov_iter_is_folioq(i)) { - unsigned offset =3D i->iov_offset % PAGE_SIZE; - int npages =3D DIV_ROUND_UP(offset + i->count, PAGE_SIZE); - return min(npages, maxpages); - } if (iov_iter_is_xarray(i)) { unsigned offset =3D (i->xarray_start + i->iov_offset) % PAGE_SIZE; int npages =3D DIV_ROUND_UP(offset + i->count, PAGE_SIZE); @@ -1691,68 +1588,6 @@ static ssize_t iov_iter_extract_bvecq_pages(struct i= ov_iter *iter, return extracted; } =20 -/* - * Extract a list of contiguous pages from an ITER_FOLIOQ iterator. This = does - * not get references on the pages, nor does it get a pin on them. - */ -static ssize_t iov_iter_extract_folioq_pages(struct iov_iter *i, - struct page ***pages, size_t maxsize, - unsigned int maxpages, - iov_iter_extraction_t extraction_flags, - size_t *offset0) -{ - const struct folio_queue *folioq =3D i->folioq; - struct page **p; - unsigned int nr =3D 0; - size_t extracted =3D 0, offset, slot =3D i->folioq_slot; - - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - slot =3D 0; - if (WARN_ON(i->iov_offset !=3D 0)) - return -EIO; - } - - offset =3D i->iov_offset & ~PAGE_MASK; - *offset0 =3D offset; - - maxpages =3D want_pages_array(pages, maxsize, offset, maxpages); - if (!maxpages) - return -ENOMEM; - p =3D *pages; - - for (;;) { - struct folio *folio =3D folioq_folio(folioq, slot); - size_t offset =3D i->iov_offset, fsize =3D folioq_folio_size(folioq, slo= t); - size_t part =3D PAGE_SIZE - offset % PAGE_SIZE; - - if (offset < fsize) { - part =3D umin(part, umin(maxsize - extracted, fsize - offset)); - i->count -=3D part; - i->iov_offset +=3D part; - extracted +=3D part; - - p[nr++] =3D folio_page(folio, offset / PAGE_SIZE); - } - - if (nr >=3D maxpages || extracted >=3D maxsize) - break; - - if (i->iov_offset >=3D fsize) { - i->iov_offset =3D 0; - slot++; - if (slot =3D=3D folioq_nr_slots(folioq) && folioq->next) { - folioq =3D folioq->next; - slot =3D 0; - } - } - } - - i->folioq =3D folioq; - i->folioq_slot =3D slot; - return extracted; -} - /* * Extract a list of contiguous pages from an ITER_XARRAY iterator. This = does not * get references on the pages, nor does it get a pin on them. @@ -2015,8 +1850,8 @@ static ssize_t iov_iter_extract_user_pages(struct iov= _iter *i, * added to the pages, but refs will not be taken. * iov_iter_extract_will_pin() will return true. * - * (*) If the iterator is ITER_KVEC, ITER_BVEC, ITER_FOLIOQ or ITER_XARRA= Y, the - * pages are merely listed; no extra refs or pins are obtained. + * (*) If the iterator is ITER_KVEC, ITER_BVEC, ITER_XARRAY, the pages are + * merely listed; no extra refs or pins are obtained. * iov_iter_extract_will_pin() will return 0. * * Note also: @@ -2055,10 +1890,6 @@ ssize_t iov_iter_extract_pages(struct iov_iter *i, return iov_iter_extract_bvecq_pages(i, pages, maxsize, maxpages, extraction_flags, offset0); - if (iov_iter_is_folioq(i)) - return iov_iter_extract_folioq_pages(i, pages, maxsize, - maxpages, extraction_flags, - offset0); if (iov_iter_is_xarray(i)) return iov_iter_extract_xarray_pages(i, pages, maxsize, maxpages, extraction_flags, diff --git a/lib/scatterlist.c b/lib/scatterlist.c index 23e5a180103b..b9a7298306d9 100644 --- a/lib/scatterlist.c +++ b/lib/scatterlist.c @@ -12,7 +12,6 @@ #include #include #include -#include =20 /** * sg_nents - return total count of entries in scatterlist @@ -1327,67 +1326,6 @@ static ssize_t extract_bvecq_to_sg(struct iov_iter *= iter, return ret; } =20 -/* - * Extract up to sg_max folios from an FOLIOQ-type iterator and add them to - * the scatterlist. The pages are not pinned. - */ -static ssize_t extract_folioq_to_sg(struct iov_iter *iter, - ssize_t maxsize, - struct sg_table *sgtable, - unsigned int sg_max, - iov_iter_extraction_t extraction_flags) -{ - const struct folio_queue *folioq =3D iter->folioq; - struct scatterlist *sg =3D sgtable->sgl + sgtable->nents; - unsigned int slot =3D iter->folioq_slot; - ssize_t ret =3D 0; - size_t offset =3D iter->iov_offset; - - BUG_ON(!folioq); - - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - if (WARN_ON_ONCE(!folioq)) - return 0; - slot =3D 0; - } - - do { - struct folio *folio =3D folioq_folio(folioq, slot); - size_t fsize =3D folioq_folio_size(folioq, slot); - - if (offset < fsize) { - size_t part =3D umin(maxsize - ret, fsize - offset); - - sg_set_page(sg, folio_page(folio, 0), part, offset); - sgtable->nents++; - sg++; - sg_max--; - offset +=3D part; - ret +=3D part; - } - - if (offset >=3D fsize) { - offset =3D 0; - slot++; - if (slot >=3D folioq_nr_slots(folioq)) { - if (!folioq->next) { - WARN_ON_ONCE(ret < iter->count); - break; - } - folioq =3D folioq->next; - slot =3D 0; - } - } - } while (sg_max > 0 && ret < maxsize); - - iter->folioq =3D folioq; - iter->folioq_slot =3D slot; - iter->iov_offset =3D offset; - iter->count -=3D ret; - return ret; -} - /* * Extract up to sg_max folios from an XARRAY-type iterator and add them to * the scatterlist. The pages are not pinned. @@ -1451,8 +1389,8 @@ static ssize_t extract_xarray_to_sg(struct iov_iter *= iter, * addition of @sg_max elements. * * The pages referred to by UBUF- and IOVEC-type iterators are extracted a= nd - * pinned; BVEC-, BVECQ-, KVEC-, FOLIOQ- and XARRAY-type are extracted but - * aren't pinned; DISCARD-type is not supported. + * pinned; BVEC-, BVECQ-, KVEC-, XARRAY-type are extracted but aren't pinn= ed; + * DISCARD-type is not supported. * * No end mark is placed on the scatterlist; that's left to the caller. * @@ -1487,9 +1425,6 @@ ssize_t extract_iter_to_sg(struct iov_iter *iter, siz= e_t maxsize, case ITER_BVECQ: return extract_bvecq_to_sg(iter, maxsize, sgtable, sg_max, extraction_flags); - case ITER_FOLIOQ: - return extract_folioq_to_sg(iter, maxsize, sgtable, sg_max, - extraction_flags); case ITER_XARRAY: return extract_xarray_to_sg(iter, maxsize, sgtable, sg_max, extraction_flags); diff --git a/lib/tests/kunit_iov_iter.c b/lib/tests/kunit_iov_iter.c index f6413e6b925a..1c7085234006 100644 --- a/lib/tests/kunit_iov_iter.c +++ b/lib/tests/kunit_iov_iter.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -383,176 +382,6 @@ static void __init iov_kunit_copy_from_bvec(struct ku= nit *test) KUNIT_SUCCEED(test); } =20 -static void iov_kunit_destroy_folioq(void *data) -{ - struct folio_queue *folioq, *next; - - for (folioq =3D data; folioq; folioq =3D next) { - next =3D folioq->next; - kfree(folioq); - } -} - -static void __init iov_kunit_load_folioq(struct kunit *test, - struct iov_iter *iter, int dir, - struct folio_queue *folioq, - struct page **pages, size_t npages) -{ - struct folio_queue *p =3D folioq; - size_t size =3D 0; - int i; - - for (i =3D 0; i < npages; i++) { - if (folioq_full(p)) { - p->next =3D kzalloc_obj(struct folio_queue); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p->next); - folioq_init(p->next, 0); - p->next->prev =3D p; - p =3D p->next; - } - folioq_append(p, page_folio(pages[i])); - size +=3D PAGE_SIZE; - } - iov_iter_folio_queue(iter, dir, folioq, 0, 0, size); -} - -static struct folio_queue *iov_kunit_create_folioq(struct kunit *test) -{ - struct folio_queue *folioq; - - folioq =3D kzalloc_obj(struct folio_queue); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, folioq); - kunit_add_action_or_reset(test, iov_kunit_destroy_folioq, folioq); - folioq_init(folioq, 0); - return folioq; -} - -/* - * Test copying to a ITER_FOLIOQ-type iterator. - */ -static void __init iov_kunit_copy_to_folioq(struct kunit *test) -{ - const struct kvec_test_range *pr; - struct iov_iter iter; - struct folio_queue *folioq; - struct page **spages, **bpages; - u8 *scratch, *buffer; - size_t bufsize, npages, size, copied; - int i, patt; - - bufsize =3D 0x100000; - npages =3D bufsize / PAGE_SIZE; - - folioq =3D iov_kunit_create_folioq(test); - - scratch =3D iov_kunit_create_buffer(test, &spages, npages); - for (i =3D 0; i < bufsize; i++) - scratch[i] =3D pattern(i); - - buffer =3D iov_kunit_create_buffer(test, &bpages, npages); - memset(buffer, 0, bufsize); - - iov_kunit_load_folioq(test, &iter, READ, folioq, bpages, npages); - - i =3D 0; - for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { - size =3D pr->to - pr->from; - KUNIT_ASSERT_LE(test, pr->to, bufsize); - - iov_iter_folio_queue(&iter, READ, folioq, 0, 0, pr->to); - iov_iter_advance(&iter, pr->from); - copied =3D copy_to_iter(scratch + i, size, &iter); - - KUNIT_EXPECT_EQ(test, copied, size); - KUNIT_EXPECT_EQ(test, iter.count, 0); - KUNIT_EXPECT_EQ(test, iter.iov_offset, pr->to % PAGE_SIZE); - i +=3D size; - if (test->status =3D=3D KUNIT_FAILURE) - goto stop; - } - - /* Build the expected image in the scratch buffer. */ - patt =3D 0; - memset(scratch, 0, bufsize); - for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) - for (i =3D pr->from; i < pr->to; i++) - scratch[i] =3D pattern(patt++); - - /* Compare the images */ - for (i =3D 0; i < bufsize; i++) { - KUNIT_EXPECT_EQ_MSG(test, buffer[i], scratch[i], "at i=3D%x", i); - if (buffer[i] !=3D scratch[i]) - return; - } - -stop: - KUNIT_SUCCEED(test); -} - -/* - * Test copying from a ITER_FOLIOQ-type iterator. - */ -static void __init iov_kunit_copy_from_folioq(struct kunit *test) -{ - const struct kvec_test_range *pr; - struct iov_iter iter; - struct folio_queue *folioq; - struct page **spages, **bpages; - u8 *scratch, *buffer; - size_t bufsize, npages, size, copied; - int i, j; - - bufsize =3D 0x100000; - npages =3D bufsize / PAGE_SIZE; - - folioq =3D iov_kunit_create_folioq(test); - - buffer =3D iov_kunit_create_buffer(test, &bpages, npages); - for (i =3D 0; i < bufsize; i++) - buffer[i] =3D pattern(i); - - scratch =3D iov_kunit_create_buffer(test, &spages, npages); - memset(scratch, 0, bufsize); - - iov_kunit_load_folioq(test, &iter, READ, folioq, bpages, npages); - - i =3D 0; - for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { - size =3D pr->to - pr->from; - KUNIT_ASSERT_LE(test, pr->to, bufsize); - - iov_iter_folio_queue(&iter, WRITE, folioq, 0, 0, pr->to); - iov_iter_advance(&iter, pr->from); - copied =3D copy_from_iter(scratch + i, size, &iter); - - KUNIT_EXPECT_EQ(test, copied, size); - KUNIT_EXPECT_EQ(test, iter.count, 0); - KUNIT_EXPECT_EQ(test, iter.iov_offset, pr->to % PAGE_SIZE); - i +=3D size; - } - - /* Build the expected image in the main buffer. */ - i =3D 0; - memset(buffer, 0, bufsize); - for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { - for (j =3D pr->from; j < pr->to; j++) { - buffer[i++] =3D pattern(j); - if (i >=3D bufsize) - goto stop; - } - } -stop: - - /* Compare the images */ - for (i =3D 0; i < bufsize; i++) { - KUNIT_EXPECT_EQ_MSG(test, scratch[i], buffer[i], "at i=3D%x", i); - if (scratch[i] !=3D buffer[i]) - return; - } - - KUNIT_SUCCEED(test); -} - static void iov_kunit_destroy_bvecq(void *data) { struct bvecq *bq, *next; @@ -1124,85 +953,6 @@ static void __init iov_kunit_extract_pages_bvecq(stru= ct kunit *test) KUNIT_SUCCEED(test); } =20 -/* - * Test the extraction of ITER_FOLIOQ-type iterators. - */ -static void __init iov_kunit_extract_pages_folioq(struct kunit *test) -{ - const struct kvec_test_range *pr; - struct folio_queue *folioq; - struct iov_iter iter; - struct page **bpages, *pagelist[8], **pages =3D pagelist; - ssize_t len; - size_t bufsize, size =3D 0, npages; - int i, from; - - bufsize =3D 0x100000; - npages =3D bufsize / PAGE_SIZE; - - folioq =3D iov_kunit_create_folioq(test); - - iov_kunit_create_buffer(test, &bpages, npages); - iov_kunit_load_folioq(test, &iter, READ, folioq, bpages, npages); - - for (pr =3D kvec_test_ranges; pr->from >=3D 0; pr++) { - from =3D pr->from; - size =3D pr->to - from; - KUNIT_ASSERT_LE(test, pr->to, bufsize); - - iov_iter_folio_queue(&iter, WRITE, folioq, 0, 0, pr->to); - iov_iter_advance(&iter, from); - - do { - size_t offset0 =3D LONG_MAX; - - for (i =3D 0; i < ARRAY_SIZE(pagelist); i++) - pagelist[i] =3D (void *)(unsigned long)0xaa55aa55aa55aa55ULL; - - len =3D iov_iter_extract_pages(&iter, &pages, 100 * 1024, - ARRAY_SIZE(pagelist), 0, &offset0); - KUNIT_EXPECT_GE(test, len, 0); - if (len < 0) - break; - KUNIT_EXPECT_LE(test, len, size); - KUNIT_EXPECT_EQ(test, iter.count, size - len); - if (len =3D=3D 0) - break; - size -=3D len; - KUNIT_EXPECT_GE(test, (ssize_t)offset0, 0); - KUNIT_EXPECT_LT(test, offset0, PAGE_SIZE); - - for (i =3D 0; i < ARRAY_SIZE(pagelist); i++) { - struct page *p; - ssize_t part =3D min_t(ssize_t, len, PAGE_SIZE - offset0); - int ix; - - KUNIT_ASSERT_GE(test, part, 0); - ix =3D from / PAGE_SIZE; - KUNIT_ASSERT_LT(test, ix, npages); - p =3D bpages[ix]; - KUNIT_EXPECT_PTR_EQ(test, pagelist[i], p); - KUNIT_EXPECT_EQ(test, offset0, from % PAGE_SIZE); - from +=3D part; - len -=3D part; - KUNIT_ASSERT_GE(test, len, 0); - if (len =3D=3D 0) - break; - offset0 =3D 0; - } - - if (test->status =3D=3D KUNIT_FAILURE) - goto stop; - } while (iov_iter_count(&iter) > 0); - - KUNIT_EXPECT_EQ(test, size, 0); - KUNIT_EXPECT_EQ(test, iter.count, 0); - } - -stop: - KUNIT_SUCCEED(test); -} - /* * Test the extraction of ITER_XARRAY-type iterators. */ @@ -1430,23 +1180,6 @@ static void __init iov_kunit_iter_to_sg_bvec(struct = kunit *test) iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data); } =20 -static void __init iov_kunit_iter_to_sg_folioq(struct kunit *test) -{ - struct iov_kunit_iter_to_sg_data data; - struct folio_queue *folioq; - struct iov_iter iter; - size_t bufsize; - - bufsize =3D 0x200000; - iov_kunit_iter_to_sg_init(test, bufsize, false, &data); - - folioq =3D iov_kunit_create_folioq(test); - iov_kunit_load_folioq(test, &iter, READ, folioq, data.pages, - data.npages); - - iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data); -} - static void __init iov_kunit_iter_to_sg_xarray(struct kunit *test) { struct iov_kunit_iter_to_sg_data data; @@ -1485,18 +1218,14 @@ static struct kunit_case __refdata iov_kunit_cases[= ] =3D { KUNIT_CASE(iov_kunit_copy_from_bvec), KUNIT_CASE(iov_kunit_copy_to_bvecq), KUNIT_CASE(iov_kunit_copy_from_bvecq), - KUNIT_CASE(iov_kunit_copy_to_folioq), - KUNIT_CASE(iov_kunit_copy_from_folioq), KUNIT_CASE(iov_kunit_copy_to_xarray), KUNIT_CASE(iov_kunit_copy_from_xarray), KUNIT_CASE(iov_kunit_extract_pages_kvec), KUNIT_CASE(iov_kunit_extract_pages_bvec), KUNIT_CASE(iov_kunit_extract_pages_bvecq), - KUNIT_CASE(iov_kunit_extract_pages_folioq), KUNIT_CASE(iov_kunit_extract_pages_xarray), KUNIT_CASE(iov_kunit_iter_to_sg_kvec), KUNIT_CASE(iov_kunit_iter_to_sg_bvec), - KUNIT_CASE(iov_kunit_iter_to_sg_folioq), KUNIT_CASE(iov_kunit_iter_to_sg_xarray), KUNIT_CASE(iov_kunit_iter_to_sg_ubuf), {} From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 093DA36B92F for ; Mon, 24 Aug 2026 14:45:57 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582761; cv=none; b=IRglaV2qyg3JQTrV3AuZKU1xYcsKN/71i9+vQxdr0DovO0xFGD9rnVcgJ/6Ty9D5zI6f7BzRwqkjjapaf2Q7kXrOWyIIffUDa6epBRoRJfvsKVXRylFVPguqCP2yi3Gj9cESjH2rGsOU1UeQxEnCIYK//nNjedLy5VJhN834Gh0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582761; c=relaxed/simple; bh=deUSSaYRIINmGtEMAZJBFS7OJiK25oQztUxwgJ8ECkk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TYxdfKJywRRjfeXXHT9LrF6RkeolOWLvAT63vQCuR//pt9PNnN1syYK9JToRA0XFPMFaq5uCFRh/wg+3m8EnFjFOKmdhIm6c8cIxlS0axz6TSWqIukPWuDmXd5CE42kPjNF1tqQH+bxKhpzNXAMDym6ugGEaHjrwm/xjOhMGQeo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=JcTxmrxv; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="JcTxmrxv" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582757; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bdCzaF5f/J+r0ba5wvYUU0jDp563ebgQvVUCX11+NvY=; b=JcTxmrxvnkEPQ7Ugq79Z/MBAe7hScDKJdxP6pDPennxguT5QrrRtUpgjQxru0m/QUGrdcp AtcWnfwSyFNCqKpVCq/7oADUxGpW3/OxxQLfrGFN08cpit+ILfa+QJLvGD2I8GTm7cf1q9 eO8xhqlzaEaJBLQtxmrc+a1aX6pS3Rg= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-156-vFhkIegYOKSHl0nwfOv0aA-1; Mon, 24 Aug 2026 10:45:52 -0400 X-MC-Unique: vFhkIegYOKSHl0nwfOv0aA-1 X-Mimecast-MFC-AGG-ID: vFhkIegYOKSHl0nwfOv0aA_1787582745 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 4DBDB18345A5; Mon, 24 Aug 2026 14:45:45 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 34D4930002ED; Mon, 24 Aug 2026 14:45:37 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 29/35] netfs: Remove folio_queue and rolling_buffer Date: Mon, 24 Aug 2026 15:41:22 +0100 Message-ID: <20260824144130.759997-30-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Remove folio_queue and rolling_buffer as they're no longer used. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- Documentation/core-api/folio_queue.rst | 209 -------------- Documentation/core-api/index.rst | 1 - Documentation/filesystems/netfs_library.rst | 2 +- fs/netfs/internal.h | 1 - fs/netfs/iterator.c | 192 ------------- fs/netfs/main.c | 1 - fs/netfs/rolling_buffer.c | 301 -------------------- include/linux/folio_queue.h | 282 ------------------ include/linux/netfs.h | 2 - include/linux/rolling_buffer.h | 64 ----- kernel/bpf/btf.c | 2 - 11 files changed, 1 insertion(+), 1056 deletions(-) delete mode 100644 Documentation/core-api/folio_queue.rst delete mode 100644 fs/netfs/rolling_buffer.c delete mode 100644 include/linux/folio_queue.h delete mode 100644 include/linux/rolling_buffer.h diff --git a/Documentation/core-api/folio_queue.rst b/Documentation/core-ap= i/folio_queue.rst deleted file mode 100644 index b7628896d2b6..000000000000 --- a/Documentation/core-api/folio_queue.rst +++ /dev/null @@ -1,209 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0+ - -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D -Folio Queue -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -:Author: David Howells - -.. Contents: - - * Overview - * Initialisation - * Adding and removing folios - * Querying information about a folio - * Querying information about a folio_queue - * Folio queue iteration - * Folio marks - * Lockless simultaneous production/consumption issues - - -Overview -=3D=3D=3D=3D=3D=3D=3D=3D - -The folio_queue struct forms a single segment in a segmented list of folios -that can be used to form an I/O buffer. As such, the list can be iterated= over -using the ITER_FOLIOQ iov_iter type. - -The publicly accessible members of the structure are:: - - struct folio_queue { - struct folio_queue *next; - struct folio_queue *prev; - ... - }; - -A pair of pointers are provided, ``next`` and ``prev``, that point to the -segments on either side of the segment being accessed. Whilst this is a -doubly-linked list, it is intentionally not a circular list; the outward -sibling pointers in terminal segments should be NULL. - -Each segment in the list also stores: - - * an ordered sequence of folio pointers, - * the size of each folio and - * three 1-bit marks per folio, - -but these should not be accessed directly as the underlying data structure= may -change, but rather the access functions outlined below should be used. - -The facility can be made accessible by:: - - #include - -and to use the iterator:: - - #include - - -Initialisation -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -A segment should be initialised by calling:: - - void folioq_init(struct folio_queue *folioq); - -with a pointer to the segment to be initialised. Note that this will not -necessarily initialise all the folio pointers, so care must be taken to ch= eck -the number of folios added. - - -Adding and removing folios -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D - -Folios can be set in the next unused slot in a segment struct by calling o= ne -of:: - - unsigned int folioq_append(struct folio_queue *folioq, - struct folio *folio); - - unsigned int folioq_append_mark(struct folio_queue *folioq, - struct folio *folio); - -Both functions update the stored folio count, store the folio and note its -size. The second function also sets the first mark for the folio added. = Both -functions return the number of the slot used. [!] Note that no attempt is= made -to check that the capacity wasn't overrun and the list will not be extended -automatically. - -A folio can be excised by calling:: - - void folioq_clear(struct folio_queue *folioq, unsigned int slot); - -This clears the slot in the array and also clears all the marks for that f= olio, -but doesn't change the folio count - so future accesses of that slot must = check -if the slot is occupied. - - -Querying information about a folio -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Information about the folio in a particular slot may be queried by the -following function:: - - struct folio *folioq_folio(const struct folio_queue *folioq, - unsigned int slot); - -If a folio has not yet been set in that slot, this may yield an undefined -pointer. The size of the folio in a slot may be queried with either of:: - - unsigned int folioq_folio_order(const struct folio_queue *folioq, - unsigned int slot); - - size_t folioq_folio_size(const struct folio_queue *folioq, - unsigned int slot); - -The first function returns the size as an order and the second as a number= of -bytes. - - -Querying information about a folio_queue -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Information may be retrieved about a particular segment with the following -functions:: - - unsigned int folioq_nr_slots(const struct folio_queue *folioq); - - unsigned int folioq_count(struct folio_queue *folioq); - - bool folioq_full(struct folio_queue *folioq); - -The first function returns the maximum capacity of a segment. It must not= be -assumed that this won't vary between segments. The second returns the num= ber -of folios added to a segments and the third is a shorthand to indicate if = the -segment has been filled to capacity. - -Not that the count and fullness are not affected by clearing folios from t= he -segment. These are more about indicating how many slots in the array have= been -initialised, and it assumed that slots won't get reused, but rather the se= gment -will get discarded as the queue is consumed. - - -Folio marks -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -Folios within a queue can also have marks assigned to them. These marks c= an be -used to note information such as if a folio needs folio_put() calling upon= it. -There are three marks available to be set for each folio. - -The marks can be set by:: - - void folioq_mark(struct folio_queue *folioq, unsigned int slot); - void folioq_mark2(struct folio_queue *folioq, unsigned int slot); - -Cleared by:: - - void folioq_unmark(struct folio_queue *folioq, unsigned int slot); - void folioq_unmark2(struct folio_queue *folioq, unsigned int slot); - -And the marks can be queried by:: - - bool folioq_is_marked(const struct folio_queue *folioq, unsigned int slot= ); - bool folioq_is_marked2(const struct folio_queue *folioq, unsigned int slo= t); - -The marks can be used for any purpose and are not interpreted by this API. - - -Folio queue iteration -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -A list of segments may be iterated over using the I/O iterator facility us= ing -an ``iov_iter`` iterator of ``ITER_FOLIOQ`` type. The iterator may be -initialised with:: - - void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction, - const struct folio_queue *folioq, - unsigned int first_slot, unsigned int offset, - size_t count); - -This may be told to start at a particular segment, slot and offset within a -queue. The iov iterator functions will follow the next pointers when adva= ncing -and prev pointers when reverting when needed. - - -Lockless simultaneous production/consumption issues -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D - -If properly managed, the list can be extended by the producer at the head = end -and shortened by the consumer at the tail end simultaneously without the n= eed -to take locks. The ITER_FOLIOQ iterator inserts appropriate barriers to a= id -with this. - -Care must be taken when simultaneously producing and consuming a list. If= the -last segment is reached and the folios it refers to are entirely consumed = by -the IOV iterators, an iov_iter struct will be left pointing to the last se= gment -with a slot number equal to the capacity of that segment. The iterator wi= ll -try to continue on from this if there's another segment available when it = is -used again, but care must be taken lest the segment got removed and freed = by -the consumer before the iterator was advanced. - -It is recommended that the queue always contain at least one segment, even= if -that segment has never been filled or is entirely spent. This prevents the -head and tail pointers from collapsing. - - -API Function Reference -=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D - -.. kernel-doc:: include/linux/folio_queue.h diff --git a/Documentation/core-api/index.rst b/Documentation/core-api/inde= x.rst index 13769d5c40bf..16c529a33ac4 100644 --- a/Documentation/core-api/index.rst +++ b/Documentation/core-api/index.rst @@ -39,7 +39,6 @@ Library functionality that is used throughout the kernel. kref cleanup assoc_array - folio_queue xarray maple_tree idr diff --git a/Documentation/filesystems/netfs_library.rst b/Documentation/fi= lesystems/netfs_library.rst index ddd799df6ce3..18e3c3aae57c 100644 --- a/Documentation/filesystems/netfs_library.rst +++ b/Documentation/filesystems/netfs_library.rst @@ -449,7 +449,7 @@ be called from the writeback code to write the data to = the cache, if there is one. =20 The inode should be marked ``NETFS_ICTX_SINGLE_NO_UPLOAD`` if this API is = to be -used. The writeback function requires the buffer to be of ITER_FOLIOQ typ= e. +used. =20 High-Level VM API =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index b402b504a370..79260a13682b 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -45,7 +45,6 @@ extern spinlock_t netfs_proc_lock; extern mempool_t netfs_request_pool; extern mempool_t netfs_subrequest_pool; extern mempool_t netfs_bvecq_pool; -extern mempool_t netfs_folioq_pool; =20 #ifdef CONFIG_PROC_FS static inline void netfs_proc_add_rreq(struct netfs_io_request *rreq) diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c index 65164a1761e3..a557cd3dbb73 100644 --- a/fs/netfs/iterator.c +++ b/fs/netfs/iterator.c @@ -155,195 +155,3 @@ ssize_t netfs_extract_iter(struct iov_iter *orig, siz= e_t max_len, size_t max_pag =20 } EXPORT_SYMBOL_GPL(netfs_extract_iter); - -#if 0 -/* - * Select the span of a bvec iterator we're going to use. Limit it by bot= h maximum - * size and maximum number of segments. Returns the size of the span in b= ytes. - */ -static size_t netfs_limit_bvec(const struct iov_iter *iter, size_t start_o= ffset, - size_t max_size, size_t max_segs) -{ - const struct bio_vec *bvecs =3D iter->bvec; - unsigned int nbv =3D iter->nr_segs, ix =3D 0, nsegs =3D 0; - size_t len, span =3D 0, n =3D iter->count; - size_t skip =3D iter->iov_offset + start_offset; - - if (WARN_ON(!iov_iter_is_bvec(iter)) || - WARN_ON(start_offset > n) || - n =3D=3D 0) - return 0; - - while (n && ix < nbv && skip) { - len =3D bvecs[ix].bv_len; - if (skip < len) - break; - skip -=3D len; - n -=3D len; - ix++; - } - - while (n && ix < nbv) { - len =3D min3(n, bvecs[ix].bv_len - skip, max_size); - span +=3D len; - nsegs++; - ix++; - if (span >=3D max_size || nsegs >=3D max_segs) - break; - skip =3D 0; - n -=3D len; - } - - return min(span, max_size); -} - -/* - * Select the span of a kvec iterator we're going to use. Limit it by both - * maximum size and maximum number of segments. Returns the size of the s= pan - * in bytes. - */ -static size_t netfs_limit_kvec(const struct iov_iter *iter, size_t start_o= ffset, - size_t max_size, size_t max_segs) -{ - const struct kvec *kvecs =3D iter->kvec; - unsigned int nkv =3D iter->nr_segs, ix =3D 0, nsegs =3D 0; - size_t len, span =3D 0, n =3D iter->count; - size_t skip =3D iter->iov_offset + start_offset; - - if (WARN_ON(!iov_iter_is_kvec(iter)) || - WARN_ON(start_offset > n) || - n =3D=3D 0) - return 0; - - while (n && ix < nkv && skip) { - len =3D kvecs[ix].iov_len; - if (skip < len) - break; - skip -=3D len; - n -=3D len; - ix++; - } - - while (n && ix < nkv) { - len =3D min3(n, kvecs[ix].iov_len - skip, max_size); - span +=3D len; - nsegs++; - ix++; - if (span >=3D max_size || nsegs >=3D max_segs) - break; - skip =3D 0; - n -=3D len; - } - - return min(span, max_size); -} - -/* - * Select the span of an xarray iterator we're going to use. Limit it by = both - * maximum size and maximum number of segments. It is assumed that segmen= ts - * can be larger than a page in size, provided they're physically contiguo= us. - * Returns the size of the span in bytes. - */ -static size_t netfs_limit_xarray(const struct iov_iter *iter, size_t start= _offset, - size_t max_size, size_t max_segs) -{ - struct folio *folio; - unsigned int nsegs =3D 0; - uoff_t pos =3D iter->xarray_start + iter->iov_offset; - pgoff_t index =3D pos / PAGE_SIZE; - size_t span =3D 0, n =3D iter->count; - - XA_STATE(xas, iter->xarray, index); - - if (WARN_ON(!iov_iter_is_xarray(iter)) || - WARN_ON(start_offset > n) || - n =3D=3D 0) - return 0; - max_size =3D min(max_size, n - start_offset); - - rcu_read_lock(); - xas_for_each(&xas, folio, ULONG_MAX) { - size_t offset, flen, len; - if (xas_retry(&xas, folio)) - continue; - if (WARN_ON(xa_is_value(folio))) - break; - if (WARN_ON(folio_test_hugetlb(folio))) - break; - - flen =3D folio_size(folio); - offset =3D offset_in_folio(folio, pos); - len =3D min(max_size, flen - offset); - span +=3D len; - nsegs++; - if (span >=3D max_size || nsegs >=3D max_segs) - break; - } - - rcu_read_unlock(); - return min(span, max_size); -} - -/* - * Select the span of a folio queue iterator we're going to use. Limit it= by - * both maximum size and maximum number of segments. Returns the size of = the - * span in bytes. - */ -static size_t netfs_limit_folioq(const struct iov_iter *iter, size_t start= _offset, - size_t max_size, size_t max_segs) -{ - const struct folio_queue *folioq =3D iter->folioq; - unsigned int nsegs =3D 0; - unsigned int slot =3D iter->folioq_slot; - size_t span =3D 0, n =3D iter->count; - - if (WARN_ON(!iov_iter_is_folioq(iter)) || - WARN_ON(start_offset > n) || - n =3D=3D 0) - return 0; - max_size =3D umin(max_size, n - start_offset); - - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - slot =3D 0; - } - - start_offset +=3D iter->iov_offset; - do { - size_t flen =3D folioq_folio_size(folioq, slot); - - if (start_offset < flen) { - span +=3D flen - start_offset; - nsegs++; - start_offset =3D 0; - } else { - start_offset -=3D flen; - } - if (span >=3D max_size || nsegs >=3D max_segs) - break; - - slot++; - if (slot >=3D folioq_nr_slots(folioq)) { - folioq =3D folioq->next; - slot =3D 0; - } - } while (folioq); - - return umin(span, max_size); -} - -size_t netfs_limit_iter(const struct iov_iter *iter, size_t start_offset, - size_t max_size, size_t max_segs) -{ - if (iov_iter_is_folioq(iter)) - return netfs_limit_folioq(iter, start_offset, max_size, max_segs); - if (iov_iter_is_bvec(iter)) - return netfs_limit_bvec(iter, start_offset, max_size, max_segs); - if (iov_iter_is_xarray(iter)) - return netfs_limit_xarray(iter, start_offset, max_size, max_segs); - if (iov_iter_is_kvec(iter)) - return netfs_limit_kvec(iter, start_offset, max_size, max_segs); - BUG(); -} -EXPORT_SYMBOL(netfs_limit_iter); -#endif diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 041810707eb4..5d8b87f71888 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -29,7 +29,6 @@ static struct kmem_cache *netfs_subrequest_slab; mempool_t netfs_request_pool; mempool_t netfs_subrequest_pool; mempool_t netfs_bvecq_pool; -mempool_t netfs_folioq_pool; =20 #ifdef CONFIG_PROC_FS LIST_HEAD(netfs_io_requests); diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c deleted file mode 100644 index ff6c7ee23f4d..000000000000 --- a/fs/netfs/rolling_buffer.c +++ /dev/null @@ -1,301 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* Rolling buffer helpers - * - * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved. - * Written by David Howells (dhowells@redhat.com) - */ - -#include -#include -#include -#include -#include -#include "internal.h" - -static atomic_t debug_ids; - -/** - * netfs_folioq_alloc - Allocate a folio_queue struct - * @rreq_id: Associated debugging ID for tracing purposes - * @gfp: Allocation constraints - * @trace: Trace tag to indicate the purpose of the allocation - * - * Allocate, initialise and account the folio_queue struct and log a trace= line - * to mark the allocation. - */ -struct folio_queue *netfs_folioq_alloc(unsigned int rreq_id, gfp_t gfp, - unsigned int /*enum netfs_folioq_trace*/ trace) -{ - struct folio_queue *fq; - - if (gfp =3D=3D GFP_KERNEL) - fq =3D netfs_folioq_pool.alloc(gfp, netfs_folioq_pool.pool_data); - else - fq =3D mempool_alloc(&netfs_folioq_pool, gfp); - if (fq) { - netfs_stat(&netfs_n_folioq); - folioq_init(fq, rreq_id); - fq->debug_id =3D atomic_inc_return(&debug_ids); - trace_netfs_folioq(fq, trace); - } - return fq; -} -EXPORT_SYMBOL(netfs_folioq_alloc); - -/** - * netfs_folioq_free - Free a folio_queue struct - * @folioq: The object to free - * @trace: Trace tag to indicate which free - * - * Free and unaccount the folio_queue struct. - */ -void netfs_folioq_free(struct folio_queue *folioq, - unsigned int /*enum netfs_trace_folioq*/ trace) -{ - trace_netfs_folioq(folioq, trace); - netfs_stat_d(&netfs_n_folioq); - mempool_free(folioq, &netfs_folioq_pool); -} -EXPORT_SYMBOL(netfs_folioq_free); - -/* - * Initialise a rolling buffer. We allocate an empty folio queue struct t= o so - * that the pointers can be independently driven by the producer and the - * consumer. - */ -int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, - unsigned int direction, gfp_t gfp) -{ - struct folio_queue *fq; - - fq =3D netfs_folioq_alloc(rreq_id, gfp, netfs_trace_folioq_rollbuf_init); - if (!fq) - return -ENOMEM; - - roll->head =3D fq; - roll->tail =3D fq; - iov_iter_folio_queue(&roll->iter, direction, fq, 0, 0, 0); - return 0; -} - -/* - * Add another folio_queue to a rolling buffer if there's no space left. - */ -int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp) -{ - struct folio_queue *fq, *head =3D roll->head; - - if (!folioq_full(head)) - return 0; - - fq =3D netfs_folioq_alloc(head->rreq_id, gfp, netfs_trace_folioq_make_spa= ce); - if (!fq) - return -ENOMEM; - fq->prev =3D head; - - roll->head =3D fq; - if (folioq_full(head)) { - /* Make sure we don't leave the master iterator pointing to a - * block that might get immediately consumed. - */ - if (roll->iter.folioq =3D=3D head && - roll->iter.folioq_slot =3D=3D folioq_nr_slots(head)) { - roll->iter.folioq =3D fq; - roll->iter.folioq_slot =3D 0; - } - } - - /* Make sure the initialisation is stored before the next pointer. - * - * [!] NOTE: After we set head->next, the consumer is at liberty to - * immediately delete the old head. - */ - smp_store_release(&head->next, fq); - return 0; -} - -/* - * Decant the list of folios to read into a rolling buffer. - */ -ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - struct folio_batch *put_batch) -{ - struct folio_queue *fq; - struct page **vec; - int nr, ix, to; - ssize_t size =3D 0; - - if (rolling_buffer_make_space(roll, GFP_KERNEL) < 0) - return -ENOMEM; - - fq =3D roll->head; - vec =3D (struct page **)fq->vec.folios; - nr =3D __readahead_batch(ractl, vec + folio_batch_count(&fq->vec), - folio_batch_space(&fq->vec)); - ix =3D fq->vec.nr; - to =3D ix + nr; - fq->vec.nr =3D to; - for (; ix < to; ix++) { - struct folio *folio =3D folioq_folio(fq, ix); - unsigned int order =3D folio_order(folio); - - fq->orders[ix] =3D order; - size +=3D PAGE_SIZE << order; - trace_netfs_folio(folio, netfs_folio_trace_read); - if (!folio_batch_add(put_batch, folio)) - folio_batch_release(put_batch); - } - WRITE_ONCE(roll->iter.count, roll->iter.count + size); - - /* Store the counter after setting the slot. */ - smp_store_release(&roll->next_head_slot, to); - return size; -} - -/* - * Decant the entire list of folios to read into a rolling buffer. - */ -ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - unsigned int rreq_id, gfp_t gfp) -{ - XA_STATE(xas, &ractl->mapping->i_pages, ractl->_index); - struct folio_queue *fq; - struct folio *folio; - ssize_t loaded =3D 0; - int nr, slot =3D 0, npages =3D 0; - - /* First allocate all the folioqs we're going to need to avoid having - * to deal with ENOMEM later. - */ - nr =3D ractl->_nr_folios; - do { - fq =3D netfs_folioq_alloc(rreq_id, gfp, - netfs_trace_folioq_make_space); - if (!fq) { - rolling_buffer_clear(roll); - return -ENOMEM; - } - fq->prev =3D roll->head; - if (!roll->tail) - roll->tail =3D fq; - else - roll->head->next =3D fq; - roll->head =3D fq; - - nr -=3D folioq_nr_slots(fq); - } while (nr > 0); - - rcu_read_lock(); - - fq =3D roll->tail; - xas_for_each(&xas, folio, ractl->_index + ractl->_nr_pages - 1) { - unsigned int order; - - if (xas_retry(&xas, folio)) - continue; - VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio); - - order =3D folio_order(folio); - fq->orders[slot] =3D order; - fq->vec.folios[slot] =3D folio; - loaded +=3D PAGE_SIZE << order; - npages +=3D 1 << order; - trace_netfs_folio(folio, netfs_folio_trace_read); - - slot++; - if (slot >=3D folioq_nr_slots(fq)) { - fq->vec.nr =3D slot; - fq =3D fq->next; - if (!fq) { - WARN_ON_ONCE(npages < readahead_count(ractl)); - break; - } - slot =3D 0; - } - } - - rcu_read_unlock(); - - if (fq) - fq->vec.nr =3D slot; - - WRITE_ONCE(roll->iter.count, loaded); - iov_iter_folio_queue(&roll->iter, ITER_DEST, roll->tail, 0, 0, loaded); - ractl->_index +=3D npages; - ractl->_nr_pages -=3D npages; - return loaded; -} - -/* - * Append a folio to the rolling buffer. - */ -ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *f= olio, - unsigned int flags, gfp_t gfp) -{ - ssize_t size =3D folio_size(folio); - int slot; - - if (rolling_buffer_make_space(roll, gfp) < 0) - return -ENOMEM; - - slot =3D folioq_append(roll->head, folio); - if (flags & ROLLBUF_MARK_1) - folioq_mark(roll->head, slot); - if (flags & ROLLBUF_MARK_2) - folioq_mark2(roll->head, slot); - - WRITE_ONCE(roll->iter.count, roll->iter.count + size); - - /* Store the counter after setting the slot. */ - smp_store_release(&roll->next_head_slot, slot); - return size; -} - -/* - * Delete a spent buffer from a rolling queue and return the next in line.= We - * don't return the last buffer to keep the pointers independent, but retu= rn - * NULL instead. - */ -struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *rol= l) -{ - struct folio_queue *spent =3D roll->tail, *next =3D READ_ONCE(spent->next= ); - - if (!next) - return NULL; - next->prev =3D NULL; - netfs_folioq_free(spent, netfs_trace_folioq_delete); - roll->tail =3D next; - return next; -} - -/* - * Clear out a rolling queue. Folios that have mark 1 set are put. - */ -void rolling_buffer_clear(struct rolling_buffer *roll) -{ - struct folio_batch fbatch; - struct folio_queue *p; - - folio_batch_init(&fbatch); - - while ((p =3D roll->tail)) { - roll->tail =3D p->next; - for (int slot =3D 0; slot < folioq_count(p); slot++) { - struct folio *folio =3D folioq_folio(p, slot); - - if (!folio) - continue; - if (folioq_is_marked(p, slot)) { - trace_netfs_folio(folio, netfs_folio_trace_put); - if (!folio_batch_add(&fbatch, folio)) - folio_batch_release(&fbatch); - } - } - - netfs_folioq_free(p, netfs_trace_folioq_clear); - } - - folio_batch_release(&fbatch); -} diff --git a/include/linux/folio_queue.h b/include/linux/folio_queue.h deleted file mode 100644 index f6d5f1f127c9..000000000000 --- a/include/linux/folio_queue.h +++ /dev/null @@ -1,282 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Queue of folios definitions - * - * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved. - * Written by David Howells (dhowells@redhat.com) - * - * See: - * - * Documentation/core-api/folio_queue.rst - * - * for a description of the API. - */ - -#ifndef _LINUX_FOLIO_QUEUE_H -#define _LINUX_FOLIO_QUEUE_H - -#include -#include - -/* - * Segment in a queue of running buffers. Each segment can hold a number = of - * folios and a portion of the queue can be referenced with the ITER_FOLIOQ - * iterator. The possibility exists of inserting non-folio elements into = the - * queue (such as gaps). - * - * Explicit prev and next pointers are used instead of a list_head to make= it - * easier to add segments to tail and remove them from the head without the - * need for a lock. - */ -struct folio_queue { - struct folio_batch vec; /* Folios in the queue segment */ - u8 orders[FOLIO_BATCH_SIZE]; /* Order of each folio */ - struct folio_queue *next; /* Next queue segment or NULL */ - struct folio_queue *prev; /* Previous queue segment of NULL */ - unsigned long marks; /* 1-bit mark per folio */ - unsigned long marks2; /* Second 1-bit mark per folio */ -#if FOLIO_BATCH_SIZE > BITS_PER_LONG -#error marks is not big enough -#endif - unsigned int rreq_id; - unsigned int debug_id; -}; - -/** - * folioq_init - Initialise a folio queue segment - * @folioq: The segment to initialise - * @rreq_id: The request identifier to use in tracelines. - * - * Initialise a folio queue segment and set an identifier to be used in tr= aces. - * - * Note that the folio pointers are left uninitialised. - */ -static inline void folioq_init(struct folio_queue *folioq, unsigned int rr= eq_id) -{ - folio_batch_init(&folioq->vec); - folioq->next =3D NULL; - folioq->prev =3D NULL; - folioq->marks =3D 0; - folioq->marks2 =3D 0; - folioq->rreq_id =3D rreq_id; - folioq->debug_id =3D 0; -} - -/** - * folioq_nr_slots: Query the capacity of a folio queue segment - * @folioq: The segment to query - * - * Query the number of folios that a particular folio queue segment might = hold. - * [!] NOTE: This must not be assumed to be the same for every segment! - */ -static inline unsigned int folioq_nr_slots(const struct folio_queue *folio= q) -{ - return FOLIO_BATCH_SIZE; -} - -/** - * folioq_count: Query the occupancy of a folio queue segment - * @folioq: The segment to query - * - * Query the number of folios that have been added to a folio queue segmen= t. - * Note that this is not decreased as folios are removed from a segment. - */ -static inline unsigned int folioq_count(struct folio_queue *folioq) -{ - return folio_batch_count(&folioq->vec); -} - -/** - * folioq_full: Query if a folio queue segment is full - * @folioq: The segment to query - * - * Query if a folio queue segment is fully occupied. Note that this does = not - * change if folios are removed from a segment. - */ -static inline bool folioq_full(struct folio_queue *folioq) -{ - //return !folio_batch_space(&folioq->vec); - return folioq_count(folioq) >=3D folioq_nr_slots(folioq); -} - -/** - * folioq_is_marked: Check first folio mark in a folio queue segment - * @folioq: The segment to query - * @slot: The slot number of the folio to query - * - * Determine if the first mark is set for the folio in the specified slot = in a - * folio queue segment. - */ -static inline bool folioq_is_marked(const struct folio_queue *folioq, unsi= gned int slot) -{ - return test_bit(slot, &folioq->marks); -} - -/** - * folioq_mark: Set the first mark on a folio in a folio queue segment - * @folioq: The segment to modify - * @slot: The slot number of the folio to modify - * - * Set the first mark for the folio in the specified slot in a folio queue - * segment. - */ -static inline void folioq_mark(struct folio_queue *folioq, unsigned int sl= ot) -{ - set_bit(slot, &folioq->marks); -} - -/** - * folioq_unmark: Clear the first mark on a folio in a folio queue segment - * @folioq: The segment to modify - * @slot: The slot number of the folio to modify - * - * Clear the first mark for the folio in the specified slot in a folio que= ue - * segment. - */ -static inline void folioq_unmark(struct folio_queue *folioq, unsigned int = slot) -{ - clear_bit(slot, &folioq->marks); -} - -/** - * folioq_is_marked2: Check second folio mark in a folio queue segment - * @folioq: The segment to query - * @slot: The slot number of the folio to query - * - * Determine if the second mark is set for the folio in the specified slot= in a - * folio queue segment. - */ -static inline bool folioq_is_marked2(const struct folio_queue *folioq, uns= igned int slot) -{ - return test_bit(slot, &folioq->marks2); -} - -/** - * folioq_mark2: Set the second mark on a folio in a folio queue segment - * @folioq: The segment to modify - * @slot: The slot number of the folio to modify - * - * Set the second mark for the folio in the specified slot in a folio queue - * segment. - */ -static inline void folioq_mark2(struct folio_queue *folioq, unsigned int s= lot) -{ - set_bit(slot, &folioq->marks2); -} - -/** - * folioq_unmark2: Clear the second mark on a folio in a folio queue segme= nt - * @folioq: The segment to modify - * @slot: The slot number of the folio to modify - * - * Clear the second mark for the folio in the specified slot in a folio qu= eue - * segment. - */ -static inline void folioq_unmark2(struct folio_queue *folioq, unsigned int= slot) -{ - clear_bit(slot, &folioq->marks2); -} - -/** - * folioq_append: Add a folio to a folio queue segment - * @folioq: The segment to add to - * @folio: The folio to add - * - * Add a folio to the tail of the sequence in a folio queue segment, incre= asing - * the occupancy count and returning the slot number for the folio just ad= ded. - * The folio size is extracted and stored in the queue and the marks are l= eft - * unmodified. - * - * Note that it's left up to the caller to check that the segment capacity= will - * not be exceeded and to extend the queue. - */ -static inline unsigned int folioq_append(struct folio_queue *folioq, struc= t folio *folio) -{ - unsigned int slot =3D folioq->vec.nr++; - - folioq->vec.folios[slot] =3D folio; - folioq->orders[slot] =3D folio_order(folio); - return slot; -} - -/** - * folioq_append_mark: Add a folio to a folio queue segment - * @folioq: The segment to add to - * @folio: The folio to add - * - * Add a folio to the tail of the sequence in a folio queue segment, incre= asing - * the occupancy count and returning the slot number for the folio just ad= ded. - * The folio size is extracted and stored in the queue, the first mark is = set - * and and the second and third marks are left unmodified. - * - * Note that it's left up to the caller to check that the segment capacity= will - * not be exceeded and to extend the queue. - */ -static inline unsigned int folioq_append_mark(struct folio_queue *folioq, = struct folio *folio) -{ - unsigned int slot =3D folioq->vec.nr++; - - folioq->vec.folios[slot] =3D folio; - folioq->orders[slot] =3D folio_order(folio); - folioq_mark(folioq, slot); - return slot; -} - -/** - * folioq_folio: Get a folio from a folio queue segment - * @folioq: The segment to access - * @slot: The folio slot to access - * - * Retrieve the folio in the specified slot from a folio queue segment. N= ote - * that no bounds check is made and if the slot hasn't been added into yet= , the - * pointer will be undefined. If the slot has been cleared, NULL will be - * returned. - */ -static inline struct folio *folioq_folio(const struct folio_queue *folioq,= unsigned int slot) -{ - return folioq->vec.folios[slot]; -} - -/** - * folioq_folio_order: Get the order of a folio from a folio queue segment - * @folioq: The segment to access - * @slot: The folio slot to access - * - * Retrieve the order of the folio in the specified slot from a folio queue - * segment. Note that no bounds check is made and if the slot hasn't been - * added into yet, the order returned will be 0. - */ -static inline unsigned int folioq_folio_order(const struct folio_queue *fo= lioq, unsigned int slot) -{ - return folioq->orders[slot]; -} - -/** - * folioq_folio_size: Get the size of a folio from a folio queue segment - * @folioq: The segment to access - * @slot: The folio slot to access - * - * Retrieve the size of the folio in the specified slot from a folio queue - * segment. Note that no bounds check is made and if the slot hasn't been - * added into yet, the size returned will be PAGE_SIZE. - */ -static inline size_t folioq_folio_size(const struct folio_queue *folioq, u= nsigned int slot) -{ - return PAGE_SIZE << folioq_folio_order(folioq, slot); -} - -/** - * folioq_clear: Clear a folio from a folio queue segment - * @folioq: The segment to clear - * @slot: The folio slot to clear - * - * Clear a folio from a sequence in a folio queue segment and clear its ma= rks. - * The occupancy count is left unchanged. - */ -static inline void folioq_clear(struct folio_queue *folioq, unsigned int s= lot) -{ - folioq->vec.folios[slot] =3D NULL; - folioq_unmark(folioq, slot); - folioq_unmark2(folioq, slot); -} - -#endif /* _LINUX_FOLIO_QUEUE_H */ diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 8de9ce35f70d..af111c589a50 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -459,8 +459,6 @@ void netfs_put_subrequest(struct netfs_io_subrequest *s= ubreq, ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t m= ax_pages, struct bvecq **_bvecq_head, iov_iter_extraction_t extraction_flags, gfp_t gfp); -size_t netfs_limit_iter(const struct iov_iter *iter, size_t start_offset, - size_t max_size, size_t max_segs); void netfs_prepare_write_failed(struct netfs_io_subrequest *subreq); void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_e= rror); =20 diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h deleted file mode 100644 index 761735bdf3ab..000000000000 --- a/include/linux/rolling_buffer.h +++ /dev/null @@ -1,64 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* Rolling buffer of folios - * - * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved. - * Written by David Howells (dhowells@redhat.com) - */ - -#ifndef _ROLLING_BUFFER_H -#define _ROLLING_BUFFER_H - -#include -#include - -/* - * Rolling buffer. Whilst the buffer is live and in use, folios and folio - * queue segments can be added to one end by one thread and removed from t= he - * other end by another thread. The buffer isn't allowed to be empty; it = must - * always have at least one folio_queue in it so that neither side has to - * modify both queue pointers. - * - * The iterator in the buffer is extended as buffers are inserted. It can= be - * snapshotted to use a segment of the buffer. - */ -struct rolling_buffer { - struct folio_queue *head; /* Producer's insertion point */ - struct folio_queue *tail; /* Consumer's removal point */ - struct iov_iter iter; /* Iterator tracking what's left in the buffer */ - u8 next_head_slot; /* Next slot in ->head */ - u8 first_tail_slot; /* First slot in ->tail */ -}; - -/* - * Snapshot of a rolling buffer. - */ -struct rolling_buffer_snapshot { - struct folio_queue *curr_folioq; /* Queue segment in which current folio = resides */ - unsigned char curr_slot; /* Folio currently being read */ - unsigned char curr_order; /* Order of folio */ -}; - -/* Marks to store per-folio in the internal folio_queue structs. */ -#define ROLLBUF_MARK_1 BIT(0) -#define ROLLBUF_MARK_2 BIT(1) - -int rolling_buffer_init(struct rolling_buffer *roll, unsigned int rreq_id, - unsigned int direction, gfp_t gfp); -int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp); -ssize_t rolling_buffer_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - struct folio_batch *put_batch); -ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll, - struct readahead_control *ractl, - unsigned int rreq_id, gfp_t gfp); -ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *f= olio, - unsigned int flags, gfp_t gfp); -struct folio_queue *rolling_buffer_delete_spent(struct rolling_buffer *rol= l); -void rolling_buffer_clear(struct rolling_buffer *roll); - -static inline void rolling_buffer_advance(struct rolling_buffer *roll, siz= e_t amount) -{ - iov_iter_advance(&roll->iter, amount); -} - -#endif /* _ROLLING_BUFFER_H */ diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 7e25051dc1ea..b9558a0c2c1e 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6742,8 +6742,6 @@ static const struct bpf_raw_tp_null_args raw_tp_null_= args[] =3D { /* amdgpu */ { "amdgpu_vm_bo_map", 0x1 }, { "amdgpu_vm_bo_unmap", 0x1 }, - /* netfs */ - { "netfs_folioq", 0x1 }, /* xfs from xfs_defer_pending_class */ { "xfs_defer_create_intent", 0x1 }, { "xfs_defer_cancel_list", 0x1 }, From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3397D36D500 for ; Mon, 24 Aug 2026 14:46:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582767; cv=none; b=SEUfyKGR007Cf50reQ4ra2ha56m55UuYqB1OSdf0o6IHbID2NZtVCOHwbdgPnXrfgnSpty+eJLTp8iy0gt1zIIl5+cKSEEMqKpOhq52td7zbC/T2Hix65qjreQKZeTDZ5jwvosimuptmdzzXPz74zlOsUGXy4JVYOtTYSCt5z9s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582767; c=relaxed/simple; bh=YisZaEoV6zpAmHCOlBFBPfQJokSHOmvVWlOQAb/Q/88=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GbqaHKkRgBxltmC5uGpT4GyJdc+aryX41ZfIBhh+OTSAmTr7CBz1R0l4kjv/hGHpHvJdv0PCLGe8F886E/pE/j9wKasObjJpGTdJ5hLFA/wjtxB5FA2YwBTkwtdfkk5cAeUrSSmJv6mb1IJ32R+d8vVr5hqqv5Sr4jH0tmizl3g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=eH1f7xds; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="eH1f7xds" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582764; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=t9lwWMx4C7bZ4MEL10hI90d5dhBX9oTb/3fE80c80G4=; b=eH1f7xdsWmDnYETvgWxDvko+UFNd4YqKlVpDkFi9GphqgGH7DVD3yPNrs1FGOp4Ztbw3ia MWIzyLQX6k4gBgfGNrdhV5H5SqVSUMzdBFQmBi+KEnhlDEbi77B8LZ/Vtz4e4wqkmy5+C4 Qe6OID8HZEMoc/HoNxNMMj+2l2rRFUY= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-226-mkigj0z8OOKRn8w-RNedvw-1; Mon, 24 Aug 2026 10:45:57 -0400 X-MC-Unique: mkigj0z8OOKRn8w-RNedvw-1 X-Mimecast-MFC-AGG-ID: mkigj0z8OOKRn8w-RNedvw_1787582753 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 319371977529; Mon, 24 Aug 2026 14:45:53 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0E4171955F09; Mon, 24 Aug 2026 14:45:46 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 30/35] netfs: Simplify read abandonment Date: Mon, 24 Aug 2026 15:41:23 +0100 Message-ID: <20260824144130.759997-31-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" Currently, if one or more of the subrequests in a read request fails, the read collection algorithm will attempt to salvage folios that are fully downloaded but that span multiple subreqs, provided all of the contributory subreqs succeeded, but this makes the algorithm quite complicated as a subreq can contribute to multiple folios and a folio can be contributed to by multiple subreqs. Simplify this by just abandoning the rest of a read request once a permanently failed subreq is hit. This is what read_folio, DIO/unbuffered read, gap filling, RMW and write preload all need to do; readahead is the odd one out as it doesn't have any return other than unlocking folios. With this change, even for readahead, the rest of the read is simply abandoned; read() and suchlike will call ->read_folio() on each non-uptodate folio to retry and retrieve the error. Note that retryable failures still get retried by netfslib as part of the request as before. Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/read_collect.c | 190 ++++++++++++++++++++++++++++------------ include/linux/netfs.h | 1 + 2 files changed, 137 insertions(+), 54 deletions(-) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index ea01b2f1f1f8..a648e404ed04 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -19,8 +19,8 @@ #define MADE_PROGRESS 0x04 /* Made progress cleaning up a stream or the fo= lio set */ #define BUFFERED 0x08 /* The pagecache needs cleaning up */ #define NEED_RETRY 0x10 /* A front op requests retrying */ -#define COPY_TO_CACHE 0x40 /* Need to copy subrequest to cache */ -#define ABANDON_SREQ 0x80 /* Need to abandon untransferred part of subrequ= est */ +#define COPY_TO_CACHE 0x20 /* Need to copy subrequest to cache */ +#define ABANDON_RREQ 0x40 /* Need to abandon the rest of a request */ =20 /* * Clear the unread part of an I/O request. @@ -206,6 +206,78 @@ static void netfs_read_unlock_folios(struct netfs_io_r= equest *rreq, netfs_read_set_unlock_at(rreq); } =20 +/* + * Abandon all remaining read results. Once we've hit a permanent failure= , we + * assume that the file is probably unusable. In the event of readahead, = in + * theory we might manage to read some data later in the file, that we cou= ld + * still unlock, but ->read_folio() will be called again. + */ +static void netfs_abandon_read_results(struct netfs_io_request *rreq) +{ + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; + unsigned int notes =3D ABANDON_RREQ; + + _enter("%llx-%llx", rreq->start, rreq->start + rreq->len); + trace_netfs_rreq(rreq, netfs_rreq_trace_collect); + trace_netfs_collect(rreq); + + if (rreq->origin =3D=3D NETFS_READAHEAD || + rreq->origin =3D=3D NETFS_READPAGE || + rreq->origin =3D=3D NETFS_READ_FOR_WRITE) + notes |=3D BUFFERED; + + /* Remove completed subrequests from the front of the stream and + * advance the completion point. We stop when we hit something that's + * in progress. The issuer thread may be adding stuff to the tail + * whilst we're doing this. + */ + for (;;) { + struct netfs_io_subrequest *front; + unsigned long front_flags; + + front =3D list_first_entry_or_null_acquire(&stream->subrequests, + struct netfs_io_subrequest, rreq_link); + /* Read first subreq pointer before IN_PROGRESS flag. */ + if (!front) + break; + + front_flags =3D smp_load_acquire(&front->flags); + /* Order read of flags before read of anything else, such as error. */ + + /* Wait for each subreq to complete. */ + if (test_bit(NETFS_SREQ_IN_PROGRESS, &front_flags)) { + notes |=3D HIT_PENDING; + break; + } + + /* The subreq now belongs to us. */ + if (!stream->failed) { + stream->failed =3D true; + stream->error =3D front->error; + rreq->error =3D front->error; + trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon); + } + + stream->collected_to =3D front->start + front->len; + trace_netfs_sreq(front, netfs_sreq_trace_abandoned); + + spin_lock(&rreq->lock); + list_del_init(&front->rreq_link); + spin_unlock(&rreq->lock); + netfs_put_subrequest(front, netfs_sreq_trace_put_abandon); + } + + rreq->collected_to =3D stream->collected_to; + rreq->abandon_to =3D rreq->collected_to; + if (notes & BUFFERED) + netfs_read_unlock_folios(rreq, ¬es); + else + rreq->cleaned_to =3D rreq->collected_to; + + trace_netfs_collect_stream(rreq, stream); + trace_netfs_collect_state(rreq, rreq->collected_to, notes); +} + /* * Collect and assess the results of various read subrequests. We may nee= d to * retry some of the results. @@ -225,6 +297,9 @@ static void netfs_collect_read_results(struct netfs_io_= request *rreq) trace_netfs_collect(rreq); =20 reassess: + if (test_bit(NETFS_RREQ_ABANDON_REQ, &rreq->flags)) + goto abandon_request; + if (rreq->origin =3D=3D NETFS_READAHEAD || rreq->origin =3D=3D NETFS_READPAGE || rreq->origin =3D=3D NETFS_READ_FOR_WRITE) @@ -242,7 +317,9 @@ static void netfs_collect_read_results(struct netfs_io_= request *rreq) /* Read first subreq pointer before IN_PROGRESS flag. */ =20 while (front) { + unsigned long front_flags; size_t transferred; + uoff_t unlock_at =3D rreq->start + rreq->progress_at; =20 trace_netfs_collect_sreq(rreq, front); _debug("sreq [%x] %llx %zx/%zx", @@ -253,25 +330,63 @@ static void netfs_collect_read_results(struct netfs_i= o_request *rreq) stream->collected_to =3D front->start; } =20 - if (netfs_check_subreq_in_progress(front)) + front_flags =3D smp_load_acquire(&front->flags); + /* Order read of flags before read of anything else, such as error. */ + + if (test_bit(NETFS_SREQ_FAILED, &front_flags)) + goto abandon_request; + if (test_bit(NETFS_SREQ_IN_PROGRESS, &front_flags)) notes |=3D HIT_PENDING; - smp_rmb(); /* Read counters after IN_PROGRESS flag. */ + transferred =3D READ_ONCE(front->transferred); =20 + /* If we can collect the next folio from a pending op, do so, + * but we should only do it if we don't otherwise need to wait + * for completion. + */ + if ((notes & HIT_PENDING) && + (notes & BUFFERED) && + !test_bit(NETFS_SREQ_HIT_EOF, &front_flags) && + front->error =3D=3D 0 && + transferred < front->len + ) { + stream->collected_to =3D front->start + transferred; + rreq->collected_to =3D stream->collected_to; + + if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front_flags)) + notes |=3D COPY_TO_CACHE; + if (front->start + transferred >=3D unlock_at) + netfs_read_unlock_folios(rreq, ¬es); + } + + /* Stall if the front is still undergoing I/O. */ + if (notes & HIT_PENDING) + break; + + if (test_bit(NETFS_SREQ_NEED_RETRY, &front_flags)) { + stream->need_retry =3D true; + notes |=3D NEED_RETRY | MADE_PROGRESS; + break; + } else if (test_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags)) { + notes |=3D MADE_PROGRESS; + } else { + stream->transferred +=3D transferred; + stream->transferred_valid =3D true; + if (front->transferred < front->len) + set_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags); + notes |=3D MADE_PROGRESS; + } + /* If we can now collect the next folio, do so. We don't want * to defer this as we have to decide whether we need to copy * to the cache or not, and that may differ between adjacent * subreqs. */ if (notes & BUFFERED) { - uoff_t unlock_at =3D rreq->start + rreq->progress_at; - /* Clear the tail of a short read. */ - if (!(notes & HIT_PENDING) && - front->error =3D=3D 0 && - transferred < front->len && - (test_bit(NETFS_SREQ_HIT_EOF, &front->flags) || - test_bit(NETFS_SREQ_CLEAR_TAIL, &front->flags))) { + if (transferred < front->len && + (test_bit(NETFS_SREQ_HIT_EOF, &front_flags) || + test_bit(NETFS_SREQ_CLEAR_TAIL, &front_flags))) { netfs_clear_unread(front); transferred =3D front->transferred =3D front->len; trace_netfs_sreq(front, netfs_sreq_trace_clear); @@ -280,67 +395,28 @@ static void netfs_collect_read_results(struct netfs_i= o_request *rreq) stream->collected_to =3D front->start + transferred; rreq->collected_to =3D stream->collected_to; =20 - if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front->flags)) + if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front_flags)) notes |=3D COPY_TO_CACHE; =20 - if (test_bit(NETFS_SREQ_FAILED, &front->flags)) { - rreq->abandon_to =3D front->start + front->len; - front->transferred =3D front->len; - transferred =3D front->len; - trace_netfs_rreq(rreq, netfs_rreq_trace_set_abandon); - } if (front->start + transferred >=3D unlock_at || - test_bit(NETFS_SREQ_HIT_EOF, &front->flags)) + test_bit(NETFS_SREQ_HIT_EOF, &front_flags)) netfs_read_unlock_folios(rreq, ¬es); } else { stream->collected_to =3D front->start + transferred; rreq->collected_to =3D stream->collected_to; } =20 - /* Stall if the front is still undergoing I/O. */ - if (notes & HIT_PENDING) - break; - - if (test_bit(NETFS_SREQ_FAILED, &front->flags)) { - if (!stream->failed) { - stream->error =3D front->error; - rreq->error =3D front->error; - set_bit(NETFS_RREQ_FAILED, &rreq->flags); - stream->failed =3D true; - } - notes |=3D MADE_PROGRESS | ABANDON_SREQ; - } else if (test_bit(NETFS_SREQ_NEED_RETRY, &front->flags)) { - stream->need_retry =3D true; - notes |=3D NEED_RETRY | MADE_PROGRESS; - break; - } else if (test_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags)) { - notes |=3D MADE_PROGRESS; - } else { - if (!stream->failed) { - stream->transferred +=3D transferred; - stream->transferred_valid =3D true; - } - if (front->transferred < front->len) - set_bit(NETFS_RREQ_SHORT_TRANSFER, &rreq->flags); - notes |=3D MADE_PROGRESS; - } - /* Remove if completely consumed. */ stream->source =3D front->source; spin_lock(&rreq->lock); =20 remove =3D front; - trace_netfs_sreq(front, - notes & ABANDON_SREQ ? - netfs_sreq_trace_abandoned : netfs_sreq_trace_consumed); + trace_netfs_sreq(front, netfs_sreq_trace_consumed); list_del_init(&front->rreq_link); front =3D list_first_entry_or_null(&stream->subrequests, struct netfs_io_subrequest, rreq_link); spin_unlock(&rreq->lock); - netfs_put_subrequest(remove, - notes & ABANDON_SREQ ? - netfs_sreq_trace_put_abandon : - netfs_sreq_trace_put_done); + netfs_put_subrequest(remove, netfs_sreq_trace_put_done); } =20 trace_netfs_collect_stream(rreq, stream); @@ -369,6 +445,12 @@ static void netfs_collect_read_results(struct netfs_io= _request *rreq) _debug("retry"); netfs_retry_reads(rreq); goto out; + +abandon_request: + set_bit(NETFS_RREQ_FAILED, &rreq->flags); + set_bit(NETFS_RREQ_ABANDON_REQ, &rreq->flags); + netfs_wake_rreq_flag(rreq, NETFS_RREQ_PAUSE, netfs_rreq_trace_unpause); + return netfs_abandon_read_results(rreq); } =20 /* diff --git a/include/linux/netfs.h b/include/linux/netfs.h index af111c589a50..6ee3891845e9 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -287,6 +287,7 @@ struct netfs_io_request { #define NETFS_RREQ_FAILED 3 /* The request failed */ #define NETFS_RREQ_RETRYING 4 /* Set if we're in the retry path */ #define NETFS_RREQ_SHORT_TRANSFER 5 /* Set if we have a short transfer */ +#define NETFS_RREQ_ABANDON_REQ 6 /* Set if the request is to be abandoned= */ #define NETFS_RREQ_CACHE_STOP 8 /* Set to stop caching (ENOBUFS or error)= */ #define NETFS_RREQ_CACHE_ERROR 9 /* Set if we got an error from the cache= */ #define NETFS_RREQ_OFFLOAD_COLLECTION 12 /* Offload collection to workqueu= e */ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 14A29370AC1 for ; Mon, 24 Aug 2026 14:46:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582772; cv=none; b=IAQIQB0AhxhMvXWWj0ui+hqG/5kEuyTCKIiHaTMMfp9kfTvYIJu8TNMZCrkxipnCbjBQugBh3skphaIIzpMnbxk8KJFURXqrss63Kd7mUBhUDvqg38fsohnv1+tzUJ9SLdY5PlqeUbv16xE37E7gGxbCUTM1zVcb01DcKWpYbu4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582772; c=relaxed/simple; bh=jLZOtodk8nbd9Up582gA9ylmJJboXVq9JuWLiXTb8Z4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Qaeu88a6A2h7TQuLP2Y371sn7Y7rIf/33BdOX6AybJmHGyG7L5sXdpgc3QQ3IAFAiTF9EyFzkVrpjh2DGCYoeQvJq77MPa9nKSmtdApDWeOD1kac0CRJUgk8qQgklNii/BihcXSMM7W8WBkheDxwhd7GJWXXmv7F6xM6DN8gVUE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=XcRdJKMx; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="XcRdJKMx" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582770; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=TDDdhH66HohUGjb0SYEEP34XQgxP3o5V8whXb/26vVw=; b=XcRdJKMxqjBjrWbYIh2123kGtH/OutS5AGyeDB9p2uK3okdD0rLZAoIjQvuVtso35Y2e9/ mcbMkHgQmwMF29Lptd7fcDyPs38MoTnnCQc85Q7Hp06MxoAUcRpPDsTZ8ukrwk2o2wRayX YLcpeZSPHnRnN79VhRXENB1QPddWzzw= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-434-fVFbgLSmOnm4tY4YBQm-VQ-1; Mon, 24 Aug 2026 10:46:04 -0400 X-MC-Unique: fVFbgLSmOnm4tY4YBQm-VQ-1 X-Mimecast-MFC-AGG-ID: fVFbgLSmOnm4tY4YBQm-VQ_1787582761 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 5E2EC19541B1; Mon, 24 Aug 2026 14:46:01 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 2B4B41971E53; Mon, 24 Aug 2026 14:45:54 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 31/35] netfs: Check for too much data being read Date: Mon, 24 Aug 2026 15:41:24 +0100 Message-ID: <20260824144130.759997-32-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" Put in a check in read subreq termination to detect more data being read for a subrequest than was requested. In the event that this happens, abort the rest of the read request on the basis that some of the read buffer may have been corrupted and return -EIO. Signed-off-by: David Howells cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/read_collect.c | 24 ++++++++++++++++++++++++ include/trace/events/netfs.h | 1 + 2 files changed, 25 insertions(+) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index a648e404ed04..e91f7962df04 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -339,6 +339,15 @@ static void netfs_collect_read_results(struct netfs_io= _request *rreq) notes |=3D HIT_PENDING; =20 transferred =3D READ_ONCE(front->transferred); + if (unlikely(transferred > front->len)) { + /* Ugh... A subreq overran its allotted length. It + * may have corrupted the read buffer. + */ + stream->failed =3D true; + stream->error =3D -EIO; + rreq->error =3D -EIO; + goto abandon_request; + } =20 /* If we can collect the next folio from a pending op, do so, * but we should only do it if we don't otherwise need to wait @@ -655,6 +664,21 @@ void netfs_read_subreq_terminated(struct netfs_io_subr= equest *subreq) break; } =20 + /* If the subrequest read more than it was supposed to, abort + * the request with EIO as we may have clobbered other parts + * of the buffer that are already read. + */ + if (subreq->transferred > subreq->len) { + trace_netfs_sreq(subreq, netfs_sreq_trace_too_much); + __set_bit(NETFS_SREQ_FAILED, &subreq->flags); + __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); + subreq->error =3D -EIO; + trace_netfs_failure(rreq, subreq, subreq->error, netfs_fail_read); + trace_netfs_rreq(rreq, netfs_rreq_trace_set_pause); + set_bit(NETFS_RREQ_PAUSE, &rreq->flags); + goto skip_error_checks; + } + /* Deal with retry requests, short reads and errors. If we retry * but don't make progress, we abandon the attempt. */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 4b2ffd14a1ca..312dc2214d92 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -133,6 +133,7 @@ EM(netfs_sreq_trace_submit, "SUBMT") \ EM(netfs_sreq_trace_superfluous, "SPRFL") \ EM(netfs_sreq_trace_terminated, "TERM ") \ + EM(netfs_sreq_trace_too_much, "!TOOM") \ EM(netfs_sreq_trace_wait_for, "_WAIT") \ EM(netfs_sreq_trace_write, "WRITE") \ EM(netfs_sreq_trace_write_skip, "SKIP ") \ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2401343BDAF for ; Mon, 24 Aug 2026 14:46:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582778; cv=none; b=T8KPYMR1/6C6p7iEHDAV/y+TElXn2u6LsYVlLl7/uPFypTpqrGiDSJHMCFxg8srIzfziYQwOXi+JNpWJkvE9NtuNQoRF0ip/AYet0O8YG25NvKIabqzkScEQgbLwxw5piZpjeMWKUDspm+gCMVFbcq2Q/vclz1tYvx4BVSxWO3A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582778; c=relaxed/simple; bh=NK/vt55ATgBFX0JsjLVJlNfG+2hQn7dcB6piAdLfrFU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jxZfwRad4GKyAIrLg49LUclnt8bR1KsrckRUq+Q5Nt2hbUrRFnyl5b3VipiLmTe8Y5hvMpR+LY9ZVGV733+2XF8u1NE8vrRG9ftKujDGWrFTF/49Dgz3tDR1RkHd9d0zCITpvE3Mfwih7a1ogJEGzzpHQJ3hRfnRQgI54MWoxTY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=aa5S51yQ; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="aa5S51yQ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582775; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=GoClngj4Ddh9NN9Yes/xcG/KivXCKdaPGjH/7gZPero=; b=aa5S51yQQ/ZFDgs+8R25rv5WPp2qkBPxPXn+2OLI75R24fenjDR8N6AEvks8mMKBMixBGs kNbTKAYE/EkCqD/tUhgP1rPuRWIYoYYq/zrnZyDKQHY7dIk0aYD6oN/SJpJpxRj4NmV/l7 qiRuRaNKYsZBxeWRfa6z53cyA1AxoFQ= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-385-v3g5PefYO7CcJHuGWl16Gg-1; Mon, 24 Aug 2026 10:46:11 -0400 X-MC-Unique: v3g5PefYO7CcJHuGWl16Gg-1 X-Mimecast-MFC-AGG-ID: v3g5PefYO7CcJHuGWl16Gg_1787582769 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3339A183458E; Mon, 24 Aug 2026 14:46:09 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 1699818001D0; Mon, 24 Aug 2026 14:46:02 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 32/35] netfs: Add a method to get an estimate of the amount that can be written Date: Mon, 24 Aug 2026 15:41:25 +0100 Message-ID: <20260824144130.759997-33-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Content-Type: text/plain; charset="utf-8" Add a method, both to the netfs cache API and the netfs filesystem API, that allows netfslib to query how much can be written to a stream in one go. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/9p/vfs_addr.c | 17 +++++++++++++++++ fs/afs/file.c | 2 +- fs/afs/internal.h | 3 +++ fs/afs/write.c | 17 +++++++++++++++++ fs/cachefiles/io.c | 15 ++++++++++++++- fs/netfs/write_issue.c | 2 ++ fs/smb/client/file.c | 14 ++++++++++++++ include/linux/netfs.h | 22 ++++++++++++++++++++++ 8 files changed, 90 insertions(+), 2 deletions(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 2129fcb0f65c..9b163a13ec3f 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -48,6 +48,22 @@ static void v9fs_begin_writeback(struct netfs_io_request= *wreq) wreq->io_streams[0].avail =3D true; } =20 +/* + * Estimate how much data should be accumulated before we start issuing + * write subrequests. + */ +static int v9fs_estimate_write(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate) +{ + struct p9_fid *fid =3D wreq->netfs_priv; + unsigned long long limit =3D ULLONG_MAX - stream->issue_from; + unsigned long long max_len =3D fid->clnt->msize - P9_IOHDRSZ; + + estimate->issue_at =3D stream->issue_from + umin(max_len, limit); + return 0; +} + /* * Issue a subrequest to write to the server. */ @@ -185,6 +201,7 @@ const struct netfs_request_ops v9fs_req_ops =3D { .free_request =3D v9fs_free_request, .issue_read =3D v9fs_issue_read, .begin_writeback =3D v9fs_begin_writeback, + .estimate_write =3D v9fs_estimate_write, .issue_write =3D v9fs_issue_write, }; =20 diff --git a/fs/afs/file.c b/fs/afs/file.c index 11695be8c4a5..4a412e0472ec 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -469,7 +469,7 @@ const struct netfs_request_ops afs_req_ops =3D { .update_i_size =3D afs_update_i_size, .invalidate_cache =3D afs_netfs_invalidate_cache, .begin_writeback =3D afs_begin_writeback, - .prepare_write =3D afs_prepare_write, + .estimate_write =3D afs_estimate_write, .issue_write =3D afs_issue_write, .retry_request =3D afs_retry_request, }; diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 556e7e7777f2..ae2bc699bfde 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1698,6 +1698,9 @@ extern int afs_check_volume_status(struct afs_volume = *, struct afs_operation *); /* * write.c */ +int afs_estimate_write(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate); void afs_prepare_write(struct netfs_io_subrequest *subreq); void afs_issue_write(struct netfs_io_subrequest *subreq); void afs_begin_writeback(struct netfs_io_request *wreq); diff --git a/fs/afs/write.c b/fs/afs/write.c index 7f34b939706a..a0c9916f594b 100644 --- a/fs/afs/write.c +++ b/fs/afs/write.c @@ -82,6 +82,23 @@ static const struct afs_operation_ops afs_store_data_ope= ration =3D { .success =3D afs_store_data_success, }; =20 +/* + * Estimate the maximum size of a write we can send to the server. + */ +int afs_estimate_write(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate) +{ + unsigned long long limit =3D ULLONG_MAX - stream->issue_from; + unsigned long long max_len =3D 256 * 1024 * 1024; + + //if (test_bit(NETFS_SREQ_RETRYING, &subreq->flags)) + // max_len =3D 512 * 1024; + + estimate->issue_at =3D stream->issue_from + umin(max_len, limit); + return 0; +} + /* * Prepare a subrequest to write to the server. This sets the max_len * parameter. diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 23007f450ef4..91b69be78c6b 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -26,7 +26,10 @@ struct cachefiles_kiocb { }; struct cachefiles_object *object; netfs_io_terminated_t term_func; - void *term_func_priv; + union { + struct netfs_io_subrequest *subreq; + void *term_func_priv; + }; bool was_async; unsigned int inval_counter; /* Copy of cookie->inval_counter */ u64 b_writing; @@ -611,6 +614,15 @@ static int cachefiles_prepare_write(struct netfs_cache= _resources *cres, return ret; } =20 +static int cachefiles_estimate_write(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate) +{ + estimate->issue_at =3D stream->issue_from + MAX_RW_COUNT; + estimate->max_segs =3D BIO_MAX_VECS; + return 0; +} + static void cachefiles_prepare_write_subreq(struct netfs_io_subrequest *su= breq) { struct netfs_io_request *wreq =3D subreq->rreq; @@ -898,6 +910,7 @@ static const struct netfs_cache_ops cachefiles_netfs_ca= che_ops =3D { .issue_write =3D cachefiles_issue_write, .prepare_write =3D cachefiles_prepare_write, .prepare_write_subreq =3D cachefiles_prepare_write_subreq, + .estimate_write =3D cachefiles_estimate_write, .query_occupancy =3D cachefiles_query_occupancy, .collect_write =3D cachefiles_collect_write, }; diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index a5c5ba5deb43..025ea5763fda 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -114,6 +114,7 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, =20 wreq->io_streams[0].stream_nr =3D 0; wreq->io_streams[0].source =3D NETFS_UPLOAD_TO_SERVER; + wreq->io_streams[0].estimate_write =3D ictx->ops->estimate_write; wreq->io_streams[0].prepare_write =3D ictx->ops->prepare_write; wreq->io_streams[0].issue_write =3D ictx->ops->issue_write; wreq->io_streams[0].collected_to =3D start; @@ -126,6 +127,7 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, if (fscache_resources_valid(&wreq->cache_resources)) { wreq->io_streams[1].avail =3D true; wreq->io_streams[1].active =3D true; + wreq->io_streams[1].estimate_write =3D wreq->cache_resources.ops->estima= te_write; wreq->io_streams[1].prepare_write =3D wreq->cache_resources.ops->prepare= _write_subreq; wreq->io_streams[1].issue_write =3D wreq->cache_resources.ops->issue_wri= te; } diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index ac89c1ba56b1..7ad464782439 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -39,6 +39,19 @@ =20 static int cifs_reopen_file(struct cifsFileInfo *cfile, bool can_flush); =20 +/* + * Estimate the amount of data that can be written in one RPC op. + */ +static int cifs_estimate_write(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate) +{ + struct cifs_sb_info *cifs_sb =3D CIFS_SB(wreq->inode->i_sb); + + estimate->issue_at =3D stream->issue_from + cifs_sb->ctx->wsize; + return 0; +} + /* * Prepare a subrequest to upload to the server. We need to allocate cred= its * so that we know the maximum amount of data that we can include in it. @@ -362,6 +375,7 @@ const struct netfs_request_ops cifs_req_ops =3D { .issue_read =3D cifs_issue_read, .done =3D cifs_rreq_done, .begin_writeback =3D cifs_begin_writeback, + .estimate_write =3D cifs_estimate_write, .prepare_write =3D cifs_prepare_write, .issue_write =3D cifs_issue_write, .invalidate_cache =3D cifs_netfs_invalidate_cache, diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 6ee3891845e9..656f4f3f80b6 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -134,6 +134,16 @@ enum netfs_cache_collect { NETFS_CACHE_COLLECT_WRITE_CANCEL, /* Currently collecting cancelled write= s */ }; =20 +/* + * Estimate of maximum write subrequest for writeback. The filesystem is + * responsible for filling this in when called from ->estimate_write(), th= ough + * netfslib will preset infinite defaults. + */ +struct netfs_write_estimate { + uoff_t issue_at; /* Point at which we must submit */ + int max_segs; /* Max number of segments in a single RPC */ +}; + /* * Stream of I/O subrequests going to a particular destination, such as the * server or the local cache. This is mainly intended for writing where w= e may @@ -142,10 +152,14 @@ enum netfs_cache_collect { struct netfs_io_stream { /* Submission tracking */ struct netfs_io_subrequest *construct; /* Op being constructed */ + uoff_t issue_from; /* Current issue point */ size_t sreq_max_len; /* Maximum size of a subrequest */ unsigned int sreq_max_segs; /* 0 or max number of segments in an iterato= r */ unsigned int submit_off; /* Folio offset we're submitting from */ unsigned int submit_len; /* Amount of data left to submit */ + int (*estimate_write)(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate); void (*prepare_write)(struct netfs_io_subrequest *subreq); void (*issue_write)(struct netfs_io_subrequest *subreq); /* Collection tracking */ @@ -328,6 +342,9 @@ struct netfs_request_ops { =20 /* Write request handling */ void (*begin_writeback)(struct netfs_io_request *wreq); + int (*estimate_write)(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate); void (*prepare_write)(struct netfs_io_subrequest *subreq); void (*issue_write)(struct netfs_io_subrequest *subreq); void (*retry_request)(struct netfs_io_request *wreq, struct netfs_io_stre= am *stream); @@ -364,6 +381,11 @@ struct netfs_cache_ops { netfs_io_terminated_t term_func, void *term_func_priv); =20 + /* Estimate the amount of data that can be written in an op. */ + int (*estimate_write)(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_write_estimate *estimate); + /* Write data to the cache from a netfs subrequest. */ void (*issue_write)(struct netfs_io_subrequest *subreq); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1B1E8440635 for ; Mon, 24 Aug 2026 14:46:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582795; cv=none; b=nLb92qkB3xu8T5SvmObhBGmNODf3G8rhhlNItJBlDYmB7Fm5nlHHwAIFod6xYKs7GzsisHODfP84/PNeMx1/JcK/tFVI64t0FclzaL+dVGMU67wU0vlytqvlscmTvljuegko13kcPt8zHKEXcVMFBg50PhGBVGjpuS+nskRc8Cc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582795; c=relaxed/simple; bh=oli6bvvtZa857DP+f9ew7hGjO55p/IYL3kKHqt6ZNTk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=q4+z2TgYI6sh/nf8DDqsE0JpyB7ZSuomeKMaDyY3E71IO/tRcvmuAAdFX0fqkntLgFjX4BF9sMjhgAVB9qVdB3XkB+LLkjo+fKlDy+fi/jq7Q1btPdttutXobklKisQ4zJcgHyWVzFYo/LGQLqV9k2eD0cwlOQTkA9gT/cNXdeM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=ggCsBuPL; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="ggCsBuPL" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582791; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=+VO+wLECGASPvZQ3rfRizQ8mKl5AmDD0OAjI6NaXH00=; b=ggCsBuPLXYv1DwZDEMMLYuJmWF36Cn9dXWVQEnbhdNsQJe3XYq2XGKIfkgc4x9NvABIJV2 BAJPglKO7BBxoX257EAcVTPTvxwX9Yvn+pN4AlBmR/CRBr0lQLz9v2Bik6neM6N2UjUNp+ svYEgDRkO3uZS4uEa0S7SW26zXLyVN0= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-401-ks-7-uoUN7i1r3GCSy6-qg-1; Mon, 24 Aug 2026 10:46:22 -0400 X-MC-Unique: ks-7-uoUN7i1r3GCSy6-qg-1 X-Mimecast-MFC-AGG-ID: ks-7-uoUN7i1r3GCSy6-qg_1787582777 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 77DA6197752E; Mon, 24 Aug 2026 14:46:17 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id DC19318001D0; Mon, 24 Aug 2026 14:46:10 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 33/35] netfs: Rework writeback to use a separate list of regions to be unlocked Date: Mon, 24 Aug 2026 15:41:26 +0100 Message-ID: <20260824144130.759997-34-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 Content-Type: text/plain; charset="utf-8" Currently, the netfslib buffered writeback algorithm walks the list of folios, using that to determine the folios that need to be unlocked. This is tricky, however, as different streams really want different folios or different parts of folios (e.g. data that's read from the server will be written to the cache, but not written back to the server, and a small region that can be written to the server may need to be rounded out for DIO write to the cache). This also may require the collector thread to be walking the folio list at the same time that the application thread is filling it - and at the same time as things are doing I/O to or from it. Also, it requires very careful cleanup during collection, such that there's always at least one link in the chain so that the consumer never gets disconnected from the consumer. Instead, rework the buffered writeback collector such that the list of regions to be unlocked is kept separate from the bvecq chain that is used to pass list of folio or other memory fragments to the filesystem with each subrequest. With this, the collector walks the xarray to find the folios to unlock rather than scanning the list of folios and then deriving a folio pointer from the bio_vec. This has the future potential to be combined with folio_end_writeback() as both need to do the xarray lookup, and there are also stats jugging and suchlike that can be done in bulk for several folios. The way that folios are passed to subrequests is also modified so that a piece of a folio at a time is attached to the bvecq in such a way that should bounce buffering be needed (e.g. for content encryption), the individual crypto blocks can be passed instead of having to assemble a large folio's worth at a time (which might be a problem under memory pressure). Note that this can also be applied to buffered read collection, but not unbuffered/DIO collection as in the latter case, the list of fragments is the only stable reference available to perform the GUP cleanup. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/buffered_read.c | 1 + fs/netfs/internal.h | 5 +- fs/netfs/main.c | 7 + fs/netfs/objects.c | 11 + fs/netfs/read_pgpriv2.c | 4 +- fs/netfs/write_collect.c | 196 +++++++---- fs/netfs/write_issue.c | 641 +++++++++++++++++++++++++---------- include/linux/netfs.h | 30 +- include/trace/events/netfs.h | 32 +- 9 files changed, 675 insertions(+), 252 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 8a843822d47b..88e5404080e8 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -464,6 +464,7 @@ static int netfs_create_singular_buffer(struct netfs_io= _request *rreq, struct fo bvecq_filled_to(bq, 1); rreq->submitted =3D rreq->start + fsize; rreq->progress_at =3D fsize; + bvecq_pos_set(&rreq->collect_cursor, &rreq->load_cursor); return 0; } =20 diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 79260a13682b..3aa9c7ef85de 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -44,6 +44,7 @@ extern struct list_head netfs_io_requests; extern spinlock_t netfs_proc_lock; extern mempool_t netfs_request_pool; extern mempool_t netfs_subrequest_pool; +extern mempool_t netfs_writeback_pool; extern mempool_t netfs_bvecq_pool; =20 #ifdef CONFIG_PROC_FS @@ -220,7 +221,7 @@ static inline void netfs_stat_d(atomic_t *stat) /* * write_collect.c */ -int netfs_folio_written_back(struct folio *folio); +void netfs_folio_written_back(struct folio *folio, void *cleaner_priv); bool netfs_write_collection(struct netfs_io_request *wreq); void netfs_write_collection_worker(struct work_struct *work); =20 @@ -231,6 +232,8 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, struct file *file, uoff_t start, enum netfs_io_origin origin); +struct netfs_io_subrequest *netfs_alloc_write_subreq(struct netfs_io_reque= st *wreq, + struct netfs_io_stream *stream); void netfs_prepare_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, uoff_t start); diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 5d8b87f71888..b8da5e85cc67 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -28,6 +28,7 @@ static struct kmem_cache *netfs_request_slab; static struct kmem_cache *netfs_subrequest_slab; mempool_t netfs_request_pool; mempool_t netfs_subrequest_pool; +mempool_t netfs_writeback_pool; mempool_t netfs_bvecq_pool; =20 #ifdef CONFIG_PROC_FS @@ -110,6 +111,9 @@ static int __init netfs_init(void) =20 if (mempool_init_kmalloc_pool(&netfs_bvecq_pool, 100, BVECQ_STD_SIZE) < 0) goto error_bvecq_pool; + if (mempool_init_kmalloc_pool(&netfs_writeback_pool, 100, + sizeof(struct netfs_writeback)) < 0) + goto error_writeback_pool; =20 netfs_request_slab =3D kmem_cache_create("netfs_request", sizeof(struct netfs_io_request), 0, @@ -163,6 +167,8 @@ static int __init netfs_init(void) error_reqpool: kmem_cache_destroy(netfs_request_slab); error_req: + mempool_exit(&netfs_writeback_pool); +error_writeback_pool: mempool_exit(&netfs_bvecq_pool); error_bvecq_pool: return ret; @@ -177,6 +183,7 @@ static void __exit netfs_exit(void) kmem_cache_destroy(netfs_subrequest_slab); mempool_exit(&netfs_request_pool); kmem_cache_destroy(netfs_request_slab); + mempool_exit(&netfs_writeback_pool); mempool_exit(&netfs_bvecq_pool); } module_exit(netfs_exit); diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index 740971955198..a1d0de65484e 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -66,6 +66,8 @@ struct netfs_io_request *netfs_alloc_request(struct addre= ss_space *mapping, =20 INIT_LIST_HEAD(&stream->subrequests); stream->collected_to =3D rreq->start; + stream->issue_from =3D rreq->start; + stream->alignment =3D 1; } =20 if (origin =3D=3D NETFS_READAHEAD || @@ -151,6 +153,15 @@ static void netfs_deinit_request(struct netfs_io_reque= st *rreq) bvecq_pos_unset(&rreq->dispatch_cursor); bvecq_pos_unset(&rreq->collect_cursor); bvecq_put(rreq->spare); + while (rreq->writebacks) { + struct netfs_writeback *wback =3D rreq->writebacks; + + rreq->writebacks =3D wback->next; + mempool_free(wback, &netfs_bvecq_pool); + =09 + } + for (int i =3D 0; i < NR_IO_STREAMS; i++) + bvecq_pos_unset(&rreq->io_streams[i].dispatch_cursor); =20 if (atomic_dec_and_test(&ictx->io_count)) wake_up_var(&ictx->io_count); diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index 2e4eab1e327f..14fe90751b5b 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -95,7 +95,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_requ= est *creq, struct folio =20 creq->dispatch_cursor.offset =3D cache->submit_off; =20 - atomic64_set(&creq->issued_to, fpos + cache->submit_off); + atomic64_set(&cache->issued_to, fpos + cache->submit_off); part =3D netfs_advance_write(creq, cache, fpos + cache->submit_off, cache->submit_len, to_eof); cache->submit_off +=3D part; @@ -106,7 +106,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_re= quest *creq, struct folio } while (cache->submit_len > 0); =20 bvecq_pos_step(&creq->dispatch_cursor); - atomic64_set(&creq->issued_to, fpos + fsize); + atomic64_set(&cache->issued_to, fpos + fsize); =20 if (flen < fsize) netfs_issue_write(creq, cache); diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index b980a65441dc..ccc9fa5d1883 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -21,6 +21,51 @@ #define NEED_RETRY 0x10 /* A front op requests retrying */ #define SAW_FAILURE 0x20 /* One stream or hit a permanent failure */ =20 +/** + * folio_end_writeback_range - End writeback for the folios within the ran= ge + * @mapping: The pagecache to modify + * @from: Pointer to the starting position (updated) + * @to: The end position (exclusive) + * @cleaner_func: Function to clean up the folios in the range + * @cleaner_priv: Private data for the cleaner func + * + * Unlock folios that are entirely within in the given range, where @from = is + * included in the range, but @to is excluded from the range. + * + * Return: True if at least one folio got cleaned, false otherwise. @from= will + * be updated to point past the last folio cleaned. + */ +static inline +bool folio_end_writeback_range(struct address_space *mapping, + uoff_t *from, uoff_t to, + void (*cleaner_func)(struct folio *folio, + void *cleaner_priv), + void *cleaner_priv) +{ + struct folio *folio; + XA_STATE(xas, &mapping->i_pages, *from / PAGE_SIZE); + bool cleaned =3D false; + + rcu_read_lock(); + xas_for_each(&xas, folio, (to - 1) / PAGE_SIZE) { + uoff_t fend; + + if (xas_retry(&xas, folio)) + continue; + + fend =3D folio_next_pos(folio); + if (fend > to) + break; + + cleaner_func(folio, cleaner_priv); + folio_end_writeback(folio); + *from =3D fend; + cleaned =3D true; + } + rcu_read_unlock(); + return cleaned; +} + static void netfs_dump_request(const struct netfs_io_request *rreq) { pr_err("Request R=3D%08x r=3D%d fl=3D%lx or=3D%x e=3D%ld\n", @@ -28,8 +73,8 @@ static void netfs_dump_request(const struct netfs_io_requ= est *rreq) rreq->origin, rreq->error); pr_err(" st=3D%llx tsl=3D%zx/%llx/%llx\n", rreq->start, rreq->transferred, rreq->submitted, rreq->len); - pr_err(" cci=3D%llx/%llx/%llx\n", - rreq->cleaned_to, rreq->collected_to, atomic64_read(&rreq->issued_= to)); + pr_err(" cci=3D%llx/%llx\n", + rreq->cleaned_to, rreq->collected_to); pr_err(" iw=3D%pSR\n", rreq->netfs_ops->issue_write); for (int i =3D 0; i < NR_IO_STREAMS; i++) { const struct netfs_io_subrequest *sreq; @@ -38,8 +83,9 @@ static void netfs_dump_request(const struct netfs_io_requ= est *rreq) pr_err(" str[%x] s=3D%x e=3D%d acnf=3D%u,%u,%u,%u\n", s->stream_nr, s->source, s->error, s->avail, s->active, s->need_retry, s->failed); - pr_err(" str[%x] ct=3D%llx t=3D%zx\n", - s->stream_nr, s->collected_to, s->transferred); + pr_err(" str[%x] it=3D%llx ct=3D%llx t=3D%zx\n", + s->stream_nr, atomic64_read(&s->issued_to), + s->collected_to, s->transferred); list_for_each_entry(sreq, &s->subrequests, rreq_link) { pr_err(" sreq[%x:%x] sc=3D%u s=3D%llx t=3D%zx/%zx r=3D%d f=3D%lx\n", sreq->stream_nr, sreq->debug_index, sreq->source, @@ -54,14 +100,23 @@ static void netfs_dump_request(const struct netfs_io_r= equest *rreq) * that we are not allowed to lock the folio here on pain of deadlocking w= ith * truncate. */ -int netfs_folio_written_back(struct folio *folio) +void netfs_folio_written_back(struct folio *folio, void *cleaner_priv) { enum netfs_folio_trace why =3D netfs_folio_trace_endwb; + struct netfs_io_request *wreq =3D cleaner_priv; struct inode *inode =3D folio_inode(folio); struct netfs_inode *ictx =3D netfs_inode(inode); struct netfs_folio *finfo; struct netfs_group *group =3D NULL; - int gcount =3D 0; + + if (WARN_ONCE(!folio_test_writeback(folio), + "R=3D%08x: folio %lx is not under writeback\n", + wreq->debug_id, folio->index)) { + trace_netfs_folio(folio, netfs_folio_trace_not_under_wback); + netfs_dump_request(wreq); + } + + trace_netfs_collect_folio(wreq, folio); =20 if ((finfo =3D netfs_folio_info(folio))) { /* Streaming writes cannot be redirtied whilst under writeback, @@ -77,7 +132,7 @@ int netfs_folio_written_back(struct folio *folio) =20 folio_detach_private(folio); group =3D finfo->netfs_group; - gcount++; + wreq->nr_group_rel++; kfree(finfo); why =3D netfs_folio_trace_endwb_s; goto end_wb; @@ -97,15 +152,13 @@ int netfs_folio_written_back(struct folio *folio) why =3D netfs_folio_trace_redirtied; if (!folio_test_dirty(folio)) { folio_detach_private(folio); - gcount++; + wreq->nr_group_rel++; why =3D netfs_folio_trace_endwb_g; } } =20 end_wb: trace_netfs_folio(folio, why); - folio_end_writeback(folio); - return gcount; } =20 /* @@ -114,15 +167,7 @@ int netfs_folio_written_back(struct folio *folio) static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, unsigned int *notes) { - struct bvecq *bvecq =3D wreq->collect_cursor.bvecq; - unsigned int slot =3D wreq->collect_cursor.slot; - uoff_t collected_to =3D wreq->collected_to; - - if (WARN_ON_ONCE(!bvecq)) { - pr_err("[!] Writeback unlock found empty buffer!\n"); - netfs_dump_request(wreq); - return; - } + struct netfs_writeback *wback, *next; =20 if (wreq->origin =3D=3D NETFS_PGPRIV2_COPY_TO_CACHE) { if (netfs_pgpriv2_unlock_copied_folios(wreq)) @@ -130,57 +175,46 @@ static void netfs_writeback_unlock_folios(struct netf= s_io_request *wreq, return; } =20 + wback =3D wreq->writebacks; + for (;;) { - struct folio *folio; - struct netfs_folio *finfo; - uoff_t fpos, fend; - size_t fsize, flen; - - /* Try to clean up the head of the queue if it appears to be - * used up, but we need to be very careful - the cleanup can - * catch the dispatcher, which could lead to us having nothing - * left in the queue, causing the front and back pointers to - * end up on different tracks. To avoid this, we must always - * keep at least one segment in the queue. - */ - if (!bvecq_acquire_slot(bvecq, slot)) { - wreq->collect_cursor.slot =3D slot; - if (!bvecq_delete_spent(&wreq->collect_cursor)) - return; - bvecq =3D wreq->collect_cursor.bvecq; - slot =3D wreq->collect_cursor.slot; - } + uoff_t stop_at; + size_t len; =20 - folio =3D page_folio(bvecq->bv[slot].bv_page); - if (WARN_ONCE(!folio_test_writeback(folio), - "R=3D%08x: folio %lx is not under writeback\n", - wreq->debug_id, folio->index)) - trace_netfs_folio(folio, netfs_folio_trace_not_under_wback); + /* Jump over discontiguities. */ + if (wreq->cleaned_to < wback->start) + wreq->cleaned_to =3D wback->start; =20 - fpos =3D folio_pos(folio); - fsize =3D folio_size(folio); - finfo =3D netfs_folio_info(folio); - flen =3D finfo ? finfo->dirty_offset + finfo->dirty_len : fsize; + if (wreq->collected_to <=3D wreq->cleaned_to) + break; =20 - fend =3D min_t(uoff_t, fpos + flen, wreq->i_size); + /* Order read of region length before reading folios. */ + len =3D smp_load_acquire(&wback->len); =20 - trace_netfs_collect_folio(wreq, folio); + if (wreq->cleaned_to >=3D wback->start + len) { + /* Order read of next before recheck length. */ + next =3D smp_load_acquire(&wback->next); + if (!next) + break; /* We don't remove the tail writeback. */ =20 - /* Unlock any folio we've transferred all of. */ - if (collected_to < fend) - break; + /* Order read of region length before reading folios. */ + if (len !=3D smp_load_acquire(&wback->len)) + continue; /* len/next update race. */ =20 - wreq->nr_group_rel +=3D netfs_folio_written_back(folio); - wreq->cleaned_to =3D fpos + fsize; - *notes |=3D MADE_PROGRESS; + mempool_free(wback, &netfs_bvecq_pool); + wreq->writebacks =3D next; + wback =3D next; + continue; + } =20 - bvecq->bv[slot].bv_page =3D NULL; - slot++; - if (fpos + fsize >=3D collected_to) + stop_at =3D min(wreq->collected_to, wback->start + len); + + trace_netfs_collect_folios(wreq, wback->start, len); + if (!folio_end_writeback_range(wreq->mapping, &wreq->cleaned_to, stop_at, + netfs_folio_written_back, wreq)) break; + *notes |=3D MADE_PROGRESS; } - - wreq->collect_cursor.slot =3D slot; } =20 /* @@ -225,9 +259,7 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) trace_netfs_rreq(wreq, netfs_rreq_trace_collect); =20 reassess_streams: - /* Order reading the issued_to point before reading the queue it refers t= o. */ - issued_to =3D atomic64_read_acquire(&wreq->issued_to); - smp_rmb(); + issued_to =3D ULLONG_MAX; collected_to =3D ULLONG_MAX; if (wreq->origin =3D=3D NETFS_WRITEBACK || wreq->origin =3D=3D NETFS_PGPRIV2_COPY_TO_CACHE) @@ -241,19 +273,34 @@ static void netfs_collect_write_results(struct netfs_= io_request *wreq) * to the tail whilst we're doing this. */ for (s =3D 0; s < NR_IO_STREAMS; s++) { + uoff_t s_issued_to; + stream =3D &wreq->io_streams[s]; - /* Read active flag before list pointers */ + /* Read active flag before issued_to */ if (!smp_load_acquire(&stream->active)) continue; =20 - front =3D list_first_entry_or_null_acquire(&stream->subrequests, - struct netfs_io_subrequest, rreq_link); - /* Read first subreq pointer before IN_PROGRESS flag. */ - - while (front) { + for (;;) { enum netfs_cache_collect cache_collect; =20 - trace_netfs_collect_sreq(wreq, front); + /* Order reading the issued_to point before reading the + * queue it refers to. + */ + s_issued_to =3D atomic64_read_acquire(&stream->issued_to); + if (s_issued_to < issued_to) + issued_to =3D s_issued_to; + + front =3D list_first_entry_or_null_acquire(&stream->subrequests, + struct netfs_io_subrequest, + rreq_link); + /* Read first subreq pointer before IN_PROGRESS flag. */ + if (!front) { + if (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER && + test_bit(NETFS_RREQ_PAUSE, &wreq->flags)) + notes |=3D MADE_PROGRESS; + break; + } + //_debug("sreq [%x] %llx %zx/%zx", // front->debug_index, front->start, front->transferred, front->l= en); =20 @@ -272,13 +319,17 @@ static void netfs_collect_write_results(struct netfs_= io_request *wreq) break; } =20 + trace_netfs_collect_sreq(wreq, front); + if (stream->failed) { - stream->collected_to =3D front->start + front->len; + stream->collected_to =3D front->start + front->len + front->post_gap; notes |=3D MADE_PROGRESS | SAW_FAILURE; goto cancel; } if (front->start + front->transferred > stream->collected_to) { stream->collected_to =3D front->start + front->transferred; + if (front->transferred =3D=3D front->len) + stream->collected_to +=3D front->post_gap; stream->transferred =3D stream->collected_to - wreq->start; stream->transferred_valid =3D true; notes |=3D MADE_PROGRESS; @@ -328,6 +379,7 @@ static void netfs_collect_write_results(struct netfs_io= _request *wreq) =20 cancel: /* Remove if completely consumed. */ + stream->collected_to =3D front->start + front->len + front->post_gap; spin_lock(&wreq->lock); =20 remove =3D front; @@ -540,6 +592,8 @@ void netfs_write_subrequest_terminated(void *_op, ssize= _t transferred_or_error) =20 if (IS_ERR_VALUE(transferred_or_error)) { subreq->error =3D transferred_or_error; + if (transferred_or_error =3D=3D -ENOMEM) + set_bit(NETFS_RREQ_SAW_ENOMEM, &wreq->flags); =20 switch (subreq->source) { case NETFS_WRITE_TO_CACHE: diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 025ea5763fda..0c71f8c151c2 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -36,6 +36,38 @@ #include #include "internal.h" =20 +#define NOTE_UPLOAD_AVAIL 0x001 /* Upload is available */ +#define NOTE_CACHE_AVAIL 0x002 /* Local cache is available */ +#define NOTE_CACHE_COPY 0x004 /* Copy folio to cache */ +#define NOTE_UPLOAD 0x008 /* Upload folio to server */ +#define NOTE_UPLOAD_STARTED 0x010 /* Upload started */ +#define NOTE_STREAMW 0x020 /* Folio is from a streaming write */ +#define NOTE_FLUSH_ANYWAY 0x040 /* Flush data, even if not hit estimated l= imit */ + +#define NOTES__KEEP_MASK (NOTE_UPLOAD_AVAIL | NOTE_CACHE_AVAIL | NOTE_UPLO= AD_STARTED) + +struct netfs_wb_params { + uoff_t fpos; + unsigned int notes; /* Notes on applicability */ + + /* When we're using a bounce buffer, the outer data window is all of + * the data we encrypted, rounded out to the largest alignment; the + * inner data window is all the data that got changed, rounded out to + * the smallest alignment. + * + * We have two alignments at play: the size of chunk which we encrypt + * in one go (typically 4KiB) and the local cache DIO size. + */ + unsigned int inner_align; /* Smallest alignment */ + unsigned int inner_off; /* Start of inner data window */ + unsigned int inner_end; /* End of inner data window */ + unsigned int outer_align; /* Largest alignment */ + unsigned int outer_off; /* Start of outer data window */ + unsigned int outer_end; /* End of outer data window */ + + struct netfs_write_estimate estimates[NR_IO_STREAMS]; +}; + /* * Kill all dirty folios in the event of an unrecoverable error, starting = with * a locked folio we've already obtained from writeback_iter(). @@ -114,6 +146,7 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, =20 wreq->io_streams[0].stream_nr =3D 0; wreq->io_streams[0].source =3D NETFS_UPLOAD_TO_SERVER; + wreq->io_streams[0].applicable =3D NOTE_UPLOAD; wreq->io_streams[0].estimate_write =3D ictx->ops->estimate_write; wreq->io_streams[0].prepare_write =3D ictx->ops->prepare_write; wreq->io_streams[0].issue_write =3D ictx->ops->issue_write; @@ -122,6 +155,7 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, =20 wreq->io_streams[1].stream_nr =3D 1; wreq->io_streams[1].source =3D NETFS_WRITE_TO_CACHE; + wreq->io_streams[1].applicable =3D NOTE_CACHE_COPY; wreq->io_streams[1].collected_to =3D start; wreq->io_streams[1].transferred =3D 0; if (fscache_resources_valid(&wreq->cache_resources)) { @@ -130,6 +164,7 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, wreq->io_streams[1].estimate_write =3D wreq->cache_resources.ops->estima= te_write; wreq->io_streams[1].prepare_write =3D wreq->cache_resources.ops->prepare= _write_subreq; wreq->io_streams[1].issue_write =3D wreq->cache_resources.ops->issue_wri= te; + wreq->io_streams[1].alignment =3D wreq->cache_resources.dio_size; } =20 return wreq; @@ -148,6 +183,58 @@ void netfs_prepare_write_failed(struct netfs_io_subreq= uest *subreq) } EXPORT_SYMBOL(netfs_prepare_write_failed); =20 +/* + * Allocate and prepare a write subrequest. Will only return NULL if not + * performing writeback; if performing writeback, mempools may be accessed= and + * the allocator may wait forever. + */ +struct netfs_io_subrequest *netfs_alloc_write_subreq(struct netfs_io_reque= st *wreq, + struct netfs_io_stream *stream) +{ + struct netfs_io_subrequest *subreq; + + subreq =3D netfs_alloc_subrequest(wreq); + if (!subreq) + return subreq; + + subreq->source =3D stream->source; + subreq->start =3D stream->issue_from; + subreq->len =3D stream->buffered; + subreq->stream_nr =3D stream->stream_nr; + + _enter("R=3D%x[%x]", wreq->debug_id, subreq->debug_index); + + trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); + + switch (stream->source) { + case NETFS_UPLOAD_TO_SERVER: + netfs_stat(&netfs_n_wh_upload); + break; + case NETFS_WRITE_TO_CACHE: + netfs_stat(&netfs_n_wh_write); + break; + default: + WARN_ON_ONCE(1); + break; + } + + __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); + + /* We add to the end of the list whilst the collector may be walking + * the list. The collector only goes nextwards and uses the lock to + * remove entries off of the front. + */ + spin_lock(&wreq->lock); + /* Write IN_PROGRESS before pointer to new subreq */ + list_add_tail_release(&subreq->rreq_link, &stream->subrequests); + if (list_is_first(&subreq->rreq_link, &stream->subrequests) && + stream->collected_to =3D=3D 0) + stream->collected_to =3D subreq->start; + + spin_unlock(&wreq->lock); + return subreq; +} + /* * Prepare a write subrequest. We need to allocate a new subrequest * if we don't have one. @@ -209,6 +296,51 @@ void netfs_prepare_write(struct netfs_io_request *wreq, stream->construct =3D subreq; } =20 +/* + * Advance the state of the amount of data buffered on a stream. + */ +static void netfs_advance_stream(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_io_subrequest *subreq) +{ + stream->issue_from +=3D subreq->len; + stream->buffered -=3D subreq->len; + if (stream->buffered =3D=3D 0) { + subreq->post_gap =3D stream->post_gap; + stream->post_gap =3D 0; + stream->buffering =3D false; + bvecq_pos_unset(&stream->dispatch_cursor); + } + /* Order loading the queue before updating the issue_to point */ + atomic64_set_release(&stream->issued_to, stream->issue_from); +} + +/* + * Prepare the buffer for a buffered write. + */ +static int netfs_prepare_buffered_write_buffer(struct netfs_io_subrequest = *subreq, + unsigned int max_segs) +{ + struct netfs_io_request *wreq =3D subreq->rreq; + struct netfs_io_stream *stream =3D &wreq->io_streams[subreq->stream_nr]; + ssize_t len; + + _enter("%zx,{,%u,%u},%u", + subreq->len, stream->dispatch_cursor.slot, stream->dispatch_cursor= .offset, max_segs); + + bvecq_pos_set(&subreq->dispatch_pos, &stream->dispatch_cursor); + bvecq_pos_set(&subreq->content, &stream->dispatch_cursor); + + len =3D bvecq_slice(&stream->dispatch_cursor, subreq->len, max_segs, &sub= req->nr_segs); + if (len < subreq->len) { + subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); + } + + netfs_advance_stream(wreq, stream, subreq); + return 0; +} + /* * Set the I/O iterator for the filesystem/cache to use and dispatch the I= /O * operation. The operation may be asynchronous and should call @@ -326,31 +458,242 @@ size_t netfs_advance_write(struct netfs_io_request *= wreq, } =20 /* - * Write some of a pending folio data back to the server. + * Prepare and issue a subrequest. + * TODO: Replace with combined ->prepare/->issue call(). + */ +static int netfs_prep_and_issue_subreq(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_io_subrequest *subreq) +{ + stream->sreq_max_len =3D UINT_MAX; + stream->sreq_max_segs =3D INT_MAX; + switch (stream->source) { + case NETFS_UPLOAD_TO_SERVER: + netfs_stat(&netfs_n_wh_upload); + stream->sreq_max_len =3D wreq->wsize; + break; + case NETFS_WRITE_TO_CACHE: + netfs_stat(&netfs_n_wh_write); + break; + default: + WARN_ON_ONCE(1); + break; + } + + if (stream->prepare_write) + stream->prepare_write(subreq); + netfs_prepare_buffered_write_buffer(subreq, stream->sreq_max_segs); + iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, + subreq->content.bvecq, subreq->content.slot, + subreq->content.offset, + subreq->len); + trace_netfs_sreq(subreq, netfs_sreq_trace_submit); + stream->issue_write(subreq); + return 0; +} + +/* + * Issue writes for a stream. */ -static int netfs_write_folio(struct netfs_io_request *wreq, - struct writeback_control *wbc, - struct folio *folio) +static void netfs_writeback_flush(struct netfs_io_request *wreq, + struct netfs_io_stream *stream, + struct netfs_wb_params *params) { - struct netfs_io_stream *upload =3D &wreq->io_streams[0]; - struct netfs_io_stream *cache =3D &wreq->io_streams[1]; - struct netfs_io_stream *stream; + struct netfs_write_estimate *estimate =3D ¶ms->estimates[stream->stre= am_nr]; + + for (;;) { + struct netfs_io_subrequest *subreq; + int ret; + + if (test_bit(NETFS_RREQ_PAUSE, &wreq->flags)) + netfs_wait_for_paused_write(wreq); + + subreq =3D netfs_alloc_write_subreq(wreq, stream); + /* subreq allocation in a writeback is backed by a mempool and + * will wait for an new one to come available. + */ + + if (stream->source =3D=3D NETFS_WRITE_TO_CACHE && + unlikely(test_bit(NETFS_RREQ_CACHE_STOP, &wreq->flags))) { + estimate->issue_at =3D ULLONG_MAX; + estimate->max_segs =3D INT_MAX; + __set_bit(NETFS_SREQ_CANCELLED, &subreq->flags); + netfs_advance_stream(wreq, stream, subreq); + netfs_write_subrequest_terminated(subreq, subreq->len); + return; + } + + ret =3D netfs_prep_and_issue_subreq(wreq, stream, subreq); + if (ret < 0) { + /* Ownership of subreq was returned to us. */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + bvecq_pos_advance(&stream->dispatch_cursor, subreq->len); + netfs_advance_stream(wreq, stream, subreq); + netfs_write_subrequest_terminated(subreq, ret); + } + /* We no longer own subreq. */ + + if (stream->buffered =3D=3D 0) { + if (stream->stream_nr =3D=3D 0) + params->notes &=3D ~NOTE_UPLOAD_STARTED; + return; + } + + if (!(params->notes & NOTE_FLUSH_ANYWAY)) { + estimate->issue_at =3D ULLONG_MAX; + estimate->max_segs =3D INT_MAX; + stream->estimate_write(wreq, stream, estimate); + if (stream->issue_from + stream->buffered < estimate->issue_at && + estimate->max_segs > 0) + return; + } + } +} + +/* + * End the issuing of writes, let the collector know we're done. + */ +static void netfs_writeback_end(struct netfs_io_request *wreq, + struct netfs_wb_params *params) +{ + bool needs_poke =3D true; + + params->notes |=3D NOTE_FLUSH_ANYWAY; + + for (int s =3D 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_stream *stream =3D &wreq->io_streams[s]; + + if (stream->buffering) { + netfs_writeback_flush(wreq, stream, params); + stream->buffering =3D false; + } + } + + netfs_all_subreqs_queued(wreq); + + for (int s =3D 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_stream *stream =3D &wreq->io_streams[s]; + + if (!stream->active) + continue; + if (!list_empty(&stream->subrequests)) + needs_poke =3D false; + } + + if (needs_poke) + netfs_wake_collector(wreq); +} + +/* + * Add a single, physically contiguous segment of data to a writeback stre= am + * and dispatch subrequests when we hit a discontiguity or have accumulated + * sufficient data to hit the estimated dispatch point. + */ +static void netfs_writeback_add_seg_to_stream(struct netfs_io_request *wre= q, + struct netfs_io_stream *stream, + struct netfs_wb_params *params, + uoff_t start, size_t len) +{ + struct netfs_write_estimate *estimate =3D ¶ms->estimates[stream->stre= am_nr]; + + _enter("%llx,%zx", start, len); + + params->notes &=3D ~NOTE_FLUSH_ANYWAY; + + /* Flush if not contiguous with the previous slice. */ + if (stream->buffering && start !=3D stream->last_end) { + params->notes |=3D NOTE_FLUSH_ANYWAY; + netfs_writeback_flush(wreq, stream, params); + params->notes &=3D ~NOTE_FLUSH_ANYWAY; + } + + /* Begin the assembly of a slice and get an estimate of how much we can + * accumulate before we have to flush. + */ + if (!stream->buffering) { + stream->issue_from =3D start; + bvecq_pos_set(&stream->dispatch_cursor, &wreq->load_cursor); + stream->buffering =3D true; + stream->buffered =3D 0; + estimate->issue_at =3D ULLONG_MAX; + estimate->max_segs =3D INT_MAX; + stream->estimate_write(wreq, stream, estimate); + } + + stream->buffered +=3D len; + stream->last_end =3D start + len; + estimate->max_segs--; + + _debug("[%u] %llx + %zx >=3D %llx, %u %x", + stream->stream_nr, stream->issue_from, stream->buffered, + estimate->issue_at, estimate->max_segs, params->notes); + + if (stream->issue_from + stream->buffered >=3D estimate->issue_at || + estimate->max_segs <=3D 0) + netfs_writeback_flush(wreq, stream, params); +} + +/* + * Add a folio directly to the writeback streams and dispatch subrequests = as + * needed. + */ +static void netfs_writeback_add_folio_to_stream(struct netfs_io_request *w= req, + struct netfs_wb_params *params, + struct folio *folio) +{ + size_t fsize =3D folio_size(folio); + uoff_t fpos =3D params->fpos; + + /* Attach the folio to the rolling buffer. */ + bvecq_append_page(&wreq->load_cursor, &folio->page, 0, fsize, wreq->gfp, = true); + wreq->load_cursor.slot--; + + trace_netfs_bv_slot(wreq->load_cursor.bvecq, wreq->load_cursor.slot - 1); + + for (int s =3D 0; s < NR_IO_STREAMS; s++) { + struct netfs_io_stream *stream =3D &wreq->io_streams[s]; + size_t off, end; + + if (!stream->active || !(params->notes & stream->applicable)) + continue; + + /* Select the appropriately sized chunk. */ + if (stream->source =3D=3D NETFS_WRITE_TO_CACHE) { + off =3D params->outer_off; + end =3D params->outer_end; + } else { + off =3D params->inner_off; + end =3D params->inner_end; + } + + if (end < fsize) + stream->post_gap =3D fsize - end; + + wreq->load_cursor.offset =3D off; + netfs_writeback_add_seg_to_stream(wreq, stream, params, fpos + off, end = - off); + } + + + /* Advance the load cursor after copying to the dispatch cursor. */ + wreq->load_cursor.slot++; + wreq->load_cursor.offset =3D 0; +} + +/* + * Queue a folio for writeback. + */ +static void netfs_writeback_folio(struct netfs_io_request *wreq, + struct writeback_control *wbc, + struct folio *folio, + struct netfs_wb_params *params) +{ + struct netfs_writeback *wback; struct netfs_group *fgroup; /* TODO: Use this with ceph */ struct netfs_folio *finfo; - struct bvecq *queue =3D wreq->load_cursor.bvecq; - unsigned int slot; - size_t fsize =3D folio_size(folio), flen =3D fsize, foff =3D 0; + size_t fsize =3D folio_size(folio), fend =3D fsize, foff =3D 0; uoff_t fpos =3D folio_pos(folio), i_size; - bool to_eof =3D false, streamw =3D false; - bool debug =3D false; =20 - _enter(""); - - if (!wreq->spare) { - wreq->spare =3D bvecq_alloc_one(BVECQ_STD_SLOTS, wreq->gfp, true); - if (!wreq->spare) - return -ENOMEM; - } + _enter("%x", params->notes); =20 /* netfs_perform_write() may shift i_size around the folio or from out * of the folio to beyond it, but cannot move i_size into or through @@ -363,15 +706,16 @@ static int netfs_write_folio(struct netfs_io_request = *wreq, */ i_size =3D i_size_read(wreq->inode); =20 + params->fpos =3D fpos; if (fpos >=3D i_size) { /* mmap beyond eof. */ _debug("beyond eof"); folio_start_writeback(folio); folio_unlock(folio); - wreq->nr_group_rel +=3D netfs_folio_written_back(folio); + netfs_folio_written_back(folio, wreq); netfs_put_group_many(wreq->group, wreq->nr_group_rel); wreq->nr_group_rel =3D 0; - return 0; + return; } =20 if (fpos + fsize > wreq->i_size) @@ -381,21 +725,23 @@ static int netfs_write_folio(struct netfs_io_request = *wreq, finfo =3D netfs_folio_info(folio); if (finfo) { foff =3D finfo->dirty_offset; - flen =3D foff + finfo->dirty_len; - streamw =3D true; + fend =3D foff + finfo->dirty_len; + params->notes |=3D NOTE_STREAMW; } =20 - if (flen > i_size - fpos) { - flen =3D i_size - fpos; - if (!streamw) - folio_zero_segment(folio, flen, fsize); - to_eof =3D true; - } else if (flen =3D=3D i_size - fpos) { - to_eof =3D true; + if (fend > i_size - fpos) { + fend =3D i_size - fpos; + if (!(params->notes & NOTE_STREAMW)) + folio_zero_segment(folio, fend, fsize); } - flen -=3D foff; =20 - _debug("folio %zx %zx %zx", foff, flen, fsize); + /* Account for cache and crypto alignments. */ + params->inner_off =3D round_down(foff, params->inner_align); + params->inner_end =3D round_up (fend, params->inner_align); + params->outer_off =3D round_down(foff, params->outer_align); + params->outer_end =3D round_up (fend, params->outer_align); + + _debug("folio %zx %zx %zx", foff, fend - foff, fsize); =20 /* Deal with discontinuities in the stream of dirty pages. These can * arise from a number of sources: @@ -414,149 +760,93 @@ static int netfs_write_folio(struct netfs_io_request= *wreq, * write-back group. */ if (fgroup =3D=3D NETFS_FOLIO_COPY_TO_CACHE) { - netfs_issue_write(wreq, upload); + if (!(params->notes & NOTE_CACHE_AVAIL)) { + trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); + goto cancel_folio; + } + params->notes |=3D NOTE_CACHE_COPY; + trace_netfs_folio(folio, netfs_folio_trace_store_copy); } else if (fgroup !=3D wreq->group) { /* We can't write this page to the server yet. */ kdebug("wrong group"); - folio_redirty_for_writepage(wbc, folio); - folio_unlock(folio); - netfs_issue_write(wreq, upload); - netfs_issue_write(wreq, cache); - return 0; + goto skip_folio; + } else if (!(params->notes & (NOTE_UPLOAD_AVAIL | NOTE_CACHE_AVAIL))) { + trace_netfs_folio(folio, netfs_folio_trace_cancel_store); + goto cancel_folio_discard; + } else { + if (params->notes & NOTE_UPLOAD_STARTED) { + params->notes |=3D NOTE_UPLOAD; + trace_netfs_folio(folio, netfs_folio_trace_store_plus); + } else { + params->notes |=3D NOTE_UPLOAD | NOTE_UPLOAD_STARTED; + trace_netfs_folio(folio, netfs_folio_trace_store); + } + if ((params->notes & NOTE_CACHE_AVAIL) && + !(params->notes & NOTE_STREAMW)) + params->notes |=3D NOTE_CACHE_COPY; } =20 - if (foff > 0) - netfs_issue_write(wreq, upload); - if (streamw) - netfs_issue_write(wreq, cache); - folio_start_writeback(folio); folio_unlock(folio); =20 - if (fgroup =3D=3D NETFS_FOLIO_COPY_TO_CACHE) { - if (!cache->avail) { - trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); - netfs_issue_write(wreq, upload); - netfs_folio_written_back(folio); - return 0; - } - trace_netfs_folio(folio, netfs_folio_trace_store_copy); - } else if (!upload->avail && !cache->avail) { - trace_netfs_folio(folio, netfs_folio_trace_cancel_store); - netfs_folio_written_back(folio); - return 0; - } else if (!upload->construct) { - trace_netfs_folio(folio, netfs_folio_trace_store); + /* Keep track of what we will need to unlock. */ + wback =3D wreq->writebacks_tail; + if (!wback || fpos !=3D wback->start + wback->len || wback->len > LONG_MA= X) { + wback =3D mempool_alloc(&netfs_writeback_pool, wreq->gfp); + wback->next =3D NULL; + wback->start =3D fpos; + wback->len =3D fsize; + + if (wreq->writebacks) + /* Order write of next after last write of len in old tail. */ + smp_store_release(&wreq->writebacks_tail->next, wback); + else + wreq->writebacks =3D wback; + wreq->writebacks_tail =3D wback; } else { - trace_netfs_folio(folio, netfs_folio_trace_store_plus); - } - - /* Institute a new bvec queue segment if the current one is full or if - * we encounter a discontiguity. The discontiguity break is important - * when it comes to bulk unlocking folios by file range. - */ - if (bvecq_is_full(queue) || - (fpos !=3D wreq->last_end && wreq->last_end > 0)) { - bvecq_buffer_append(&wreq->load_cursor, wreq->spare); - wreq->spare =3D NULL; - - queue =3D wreq->load_cursor.bvecq; - bvecq_pos_move(&wreq->dispatch_cursor, queue); - wreq->dispatch_cursor.slot =3D 0; + /* Order update of len after setting pointer. */ + smp_store_release(&wback->len, wback->len + fsize); } =20 - /* Attach the folio to the rolling buffer. */ - slot =3D queue->nr_slots; - bvec_set_folio(&queue->bv[slot], folio, fsize, 0); - trace_netfs_bv_slot(queue, slot); - slot++; - bvecq_filled_to(queue, slot); - wreq->load_cursor.slot =3D slot; - wreq->load_cursor.offset =3D 0; - wreq->last_end =3D fpos + fsize; - - /* Move the submission point forward to allow for write-streaming data - * not starting at the front of the page. We don't do write-streaming - * with the cache as the cache requires DIO alignment. - * - * Also skip uploading for data that's been read and just needs copying - * to the cache. - */ - bvecq_pos_nudge(&wreq->dispatch_cursor); -=09 + /* Flush any streams not being used for this folio. */ for (int s =3D 0; s < NR_IO_STREAMS; s++) { - size_t soff =3D foff, slen =3D flen, alignment =3D 1; - - if (stream->source =3D=3D NETFS_WRITE_TO_CACHE) - alignment =3D wreq->cache_resources.dio_size; - stream =3D &wreq->io_streams[s]; - stream->submit_off =3D round_down(soff, alignment); - slen +=3D foff - stream->submit_off; - stream->submit_len =3D round_up(slen, alignment); - - if (!stream->avail || - (stream->source =3D=3D NETFS_WRITE_TO_CACHE && streamw) || - (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER && - fgroup =3D=3D NETFS_FOLIO_COPY_TO_CACHE)) { - stream->submit_off =3D UINT_MAX; - stream->submit_len =3D 0; - } - } + struct netfs_io_stream *stream =3D &wreq->io_streams[s]; =20 - /* Attach the folio to one or more subrequests. For a big folio, we - * could end up with thousands of subrequests if the wsize is small - - * but we might need to wait during the creation of subrequests for - * network resources (eg. SMB credits). - */ - for (;;) { - ssize_t part; - size_t lowest_off =3D ULONG_MAX; - int choose_s =3D -1; - - /* Always add to the lowest-submitted stream first. */ - for (int s =3D 0; s < NR_IO_STREAMS; s++) { - stream =3D &wreq->io_streams[s]; - if (stream->submit_len > 0 && - stream->submit_off < lowest_off) { - lowest_off =3D stream->submit_off; - choose_s =3D s; + if (!stream->active || !(params->notes & stream->applicable)) { + if (stream->buffering) { + params->notes |=3D NOTE_FLUSH_ANYWAY; + netfs_writeback_flush(wreq, stream, params); } + atomic64_set_release(&stream->issued_to, fpos + params->outer_end); } - - if (choose_s < 0) - break; - stream =3D &wreq->io_streams[choose_s]; - - /* Advance the cursor. */ - wreq->dispatch_cursor.offset =3D stream->submit_off; - - atomic64_set(&wreq->issued_to, fpos + stream->submit_off); - part =3D netfs_advance_write(wreq, stream, fpos + stream->submit_off, - stream->submit_len, to_eof); - stream->submit_off +=3D part; - if (part > stream->submit_len) - stream->submit_len =3D 0; - else - stream->submit_len -=3D part; - if (part > 0) - debug =3D true; } =20 - bvecq_pos_step(&wreq->dispatch_cursor); - /* Order loading the queue before updating the issue_to point */ - atomic64_set_release(&wreq->issued_to, fpos + fsize); - - if (!debug) - kdebug("R=3D%x: No submit", wreq->debug_id); + /* Initiate or extend the dispatch of each selected stream. At this + * point we may need to copy the data to a bounce buffer and push the + * bounce bits instead. + */ + // TODO: Do bouncing if selected. + netfs_writeback_add_folio_to_stream(wreq, params, folio); =20 - if (foff + flen < fsize) - for (int s =3D 0; s < NR_IO_STREAMS; s++) - netfs_issue_write(wreq, &wreq->io_streams[s]); +out: + _leave(" =3D %x", params->notes); + return; =20 - _leave(" =3D 0"); - return 0; +skip_folio: + folio_redirty_for_writepage(wbc, folio); + folio_unlock(folio); + goto out; +cancel_folio_discard: + netfs_put_group(fgroup); +cancel_folio: + folio_detach_private(folio); + kfree(finfo); + folio_unlock(folio); + folio_cancel_dirty(folio); + goto out; } =20 +#if 0 // TODO: Remove /* * End the issuing of writes, letting the collector know we're done. */ @@ -579,6 +869,7 @@ static void netfs_end_issue_write(struct netfs_io_reque= st *wreq) if (needs_poke) netfs_wake_collector(wreq); } +#endif =20 /* * Write some of the pending data back to the server @@ -588,6 +879,7 @@ int netfs_writepages(struct address_space *mapping, { struct netfs_inode *ictx =3D netfs_inode(mapping->host); struct netfs_io_request *wreq =3D NULL; + struct netfs_wb_params params =3D {}; struct folio *folio; int error =3D 0; =20 @@ -605,46 +897,50 @@ int netfs_writepages(struct address_space *mapping, goto couldnt_start; } =20 - if (bvecq_buffer_init(&wreq->load_cursor, wreq->gfp, true) < 0) - goto nomem; - bvecq_pos_set(&wreq->dispatch_cursor, &wreq->load_cursor); - bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor); + bvecq_buffer_init(&wreq->load_cursor, GFP_NOFS, true); =20 __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); trace_netfs_write(wreq, netfs_write_trace_writeback); netfs_stat(&netfs_n_wh_writepages); =20 - do { - _debug("wbiter %lx %llx", folio->index, atomic64_read(&wreq->issued_to)); + params.inner_align =3D 1; + params.outer_align =3D 1; =20 - /* It appears we don't have to handle cyclic writeback wrapping. */ - WARN_ON_ONCE(wreq && folio_pos(folio) < atomic64_read(&wreq->issued_to)); + if (wreq->io_streams[1].avail) { + params.notes |=3D NOTE_CACHE_AVAIL; + params.outer_align =3D wreq->cache_resources.dio_size; + } + // TODO: Adjust alignments for crypto + + do { + _debug("wbiter %lx", folio->index); =20 if (netfs_folio_group(folio) !=3D NETFS_FOLIO_COPY_TO_CACHE && unlikely(!test_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags))) { set_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags); wreq->netfs_ops->begin_writeback(wreq); + if (wreq->io_streams[0].avail) { + params.notes |=3D NOTE_UPLOAD_AVAIL; + /* Order setting the active flag after other fields. */ + smp_store_release(&wreq->io_streams[0].active, true); + } } =20 - error =3D netfs_write_folio(wreq, wbc, folio); - if (error =3D=3D -ENOMEM) { - folio_redirty_for_writepage(wbc, folio); - folio_unlock(folio); - } + params.notes &=3D NOTES__KEEP_MASK; + netfs_writeback_folio(wreq, wbc, folio, ¶ms); } while ((folio =3D writeback_iter(mapping, wbc, folio, &error))); =20 - netfs_end_issue_write(wreq); + netfs_writeback_end(wreq, ¶ms); + bvecq_pos_unset(&wreq->load_cursor); - bvecq_pos_unset(&wreq->dispatch_cursor); + for (int i =3D 0; i < NR_IO_STREAMS; i++) + bvecq_pos_unset(&wreq->io_streams[i].dispatch_cursor); netfs_wake_collector(wreq); =20 netfs_put_request(wreq, netfs_rreq_trace_put_return); _leave(" =3D %d", error); return error; =20 -nomem: - error =3D -ENOMEM; - netfs_put_failed_request(wreq); couldnt_start: if (error =3D=3D -ENOMEM) { folio_redirty_for_writepage(wbc, folio); @@ -738,7 +1034,6 @@ int netfs_writeback_single(struct address_space *mappi= ng, subreq->len =3D wreq->len; if (stream->source =3D=3D NETFS_WRITE_TO_CACHE) subreq->len =3D clen; - stream->submit_len =3D subreq->len; =20 netfs_issue_write(wreq, stream); } diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 656f4f3f80b6..2c5b7f3a48b7 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -134,6 +134,16 @@ enum netfs_cache_collect { NETFS_CACHE_COLLECT_WRITE_CANCEL, /* Currently collecting cancelled write= s */ }; =20 +/* + * Record of a contiguous region undergoing writeback. The tail region (i= e. if + * next is NULL) may be extended dynamically. + */ +struct netfs_writeback { + struct netfs_writeback *next; /* Next extent in list */ + uoff_t start; /* Start position */ + size_t len; /* Total size (can increase) */ +}; + /* * Estimate of maximum write subrequest for writeback. The filesystem is * responsible for filling this in when called from ->estimate_write(), th= ough @@ -150,23 +160,32 @@ struct netfs_write_estimate { * have to write to multiple destinations concurrently. */ struct netfs_io_stream { - /* Submission tracking */ + /* Submission tracking (main dispatch only; not retry) */ + struct bvecq_pos dispatch_cursor; /* Point from which buffers are dispatc= hed */ struct netfs_io_subrequest *construct; /* Op being constructed */ uoff_t issue_from; /* Current issue point */ + uoff_t last_end; /* End file pos of last folio added */ + size_t buffered; /* Amount in buffer */ + size_t post_gap; /* Length of partial folio tail */ size_t sreq_max_len; /* Maximum size of a subrequest */ unsigned int sreq_max_segs; /* 0 or max number of segments in an iterato= r */ unsigned int submit_off; /* Folio offset we're submitting from */ unsigned int submit_len; /* Amount of data left to submit */ + unsigned int alignment; /* Required alignment */ + u8 applicable; /* What sources are applicable (NOTE_* mask) */ + bool buffering; /* T if buffering on this stream */ int (*estimate_write)(struct netfs_io_request *wreq, struct netfs_io_stream *stream, struct netfs_write_estimate *estimate); void (*prepare_write)(struct netfs_io_subrequest *subreq); void (*issue_write)(struct netfs_io_subrequest *subreq); + atomic64_t issued_to; /* Point to which can be considered issued */ + /* Collection tracking */ struct list_head subrequests; /* Contributory I/O operations */ uoff_t collected_to; /* Position we've collected results to */ size_t transferred; /* The amount transferred from this stream */ - unsigned short error; /* Aggregate error for the stream */ + short error; /* Aggregate error for the stream */ enum netfs_io_source source; /* Where to read from/write to */ unsigned char stream_nr; /* Index of stream in parent table */ bool avail; /* T if stream is available */ @@ -207,11 +226,12 @@ struct netfs_io_subrequest { struct iov_iter io_iter; /* Iterator for this subrequest */ uoff_t start; /* Where to start the I/O */ size_t len; /* Size of the I/O */ + size_t post_gap; /* Length of partial folio tail */ size_t transferred; /* Amount of data transferred */ refcount_t ref; short error; /* 0 or error that occurred */ unsigned short debug_index; /* Index in list (for debugging output) */ - unsigned int nr_segs; /* Number of segs in io_iter */ + unsigned int nr_segs; /* Number of segments in content */ u8 retry_count; /* The number of retries (0 on initial pass) */ enum netfs_io_source source; /* Where to read from/write to */ unsigned char stream_nr; /* I/O stream this belongs to */ @@ -263,6 +283,8 @@ struct netfs_io_request { #endif struct netfs_io_stream io_streams[2]; /* Streams of parallel I/O operatio= ns */ #define NR_IO_STREAMS 2 //wreq->nr_io_streams + struct netfs_writeback *writebacks; /* List of regions undergoing writeba= ck */ + struct netfs_writeback *writebacks_tail; /* Tail of region list */ struct netfs_group *group; /* Writeback group being written back */ struct bvecq *spare; /* Advance allocation of bvecq */ struct bvecq_pos load_cursor; /* Point at which new folios are loaded in = */ @@ -279,7 +301,6 @@ struct netfs_io_request { long error; /* 0 or error that occurred */ uoff_t i_size; /* Size of the file */ uoff_t start; /* Start position */ - atomic64_t issued_to; /* Write issuer folio cursor */ uoff_t collected_to; /* Point we've collected to */ uoff_t cache_coll_to; /* Point the cache has collected to */ uoff_t cleaned_to; /* Position we've cleaned folios to */ @@ -302,6 +323,7 @@ struct netfs_io_request { #define NETFS_RREQ_RETRYING 4 /* Set if we're in the retry path */ #define NETFS_RREQ_SHORT_TRANSFER 5 /* Set if we have a short transfer */ #define NETFS_RREQ_ABANDON_REQ 6 /* Set if the request is to be abandoned= */ +#define NETFS_RREQ_SAW_ENOMEM 7 /* Set if we encounted ENOMEM */ #define NETFS_RREQ_CACHE_STOP 8 /* Set to stop caching (ENOBUFS or error)= */ #define NETFS_RREQ_CACHE_ERROR 9 /* Set if we got an error from the cache= */ #define NETFS_RREQ_OFFLOAD_COLLECTION 12 /* Offload collection to workqueu= e */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 312dc2214d92..6ccf9de42858 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -764,7 +764,7 @@ TRACE_EVENT(netfs_collect_stream, __entry->wreq =3D wreq->debug_id; __entry->stream =3D stream->stream_nr; __entry->collected_to =3D stream->collected_to; - __entry->issued_to =3D atomic64_read(&wreq->issued_to); + __entry->issued_to =3D atomic64_read(&stream->issued_to); ), =20 TP_printk("R=3D%08x[%x:] cto=3D%llx ito=3D%llx", @@ -772,6 +772,36 @@ TRACE_EVENT(netfs_collect_stream, __entry->collected_to, __entry->issued_to) ); =20 +TRACE_EVENT(netfs_collect_folios, + TP_PROTO(const struct netfs_io_request *wreq, + uoff_t range_start, size_t range_len), + + TP_ARGS(wreq, range_start, range_len), + + TP_STRUCT__entry( + __field(unsigned int, wreq) + __field(size_t, range_len) + __field(uoff_t, range_start) + __field(uoff_t, cleaned_to) + __field(uoff_t, collected_to) + ), + + TP_fast_assign( + __entry->wreq =3D wreq->debug_id; + __entry->range_len =3D range_len; + __entry->range_start =3D range_start; + __entry->cleaned_to =3D wreq->cleaned_to; + __entry->collected_to =3D wreq->collected_to; + ), + + TP_printk("R=3D%08x r=3D%llx-%llx cln=3D%llx col=3D%llx", + __entry->wreq, + __entry->range_start, + __entry->range_start + __entry->range_len, + __entry->cleaned_to, + __entry->collected_to) + ); + TRACE_EVENT(netfs_bvecq, TP_PROTO(const struct bvecq *bq, enum netfs_bvecq_trace trace), From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 966CC43BDDF for ; Mon, 24 Aug 2026 14:46:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582805; cv=none; b=ThPNeMQDXRcm53aAd4ZaIZoZcWTzqbNBcWVm9N3tkgToVyZDuFzAxbCWYbUTioAEx8bUqcEhdupy0JtZYycX1m3LMcFaGlTRjKvmbuyDaMVnjKYixKbxSjbUr13LYY9jCbYjl5fY33yCOFE1kdc/XgaWT9Hv0LqXePqTi5ZpZ+k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582805; c=relaxed/simple; bh=HU1tfCE1BbIJ4Q6HYg7HPr2CZmr/GmMVOOgHhWOFZ30=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NoHP1fpTqWVQR8FI3S+09oOGPZmWfEVbuR1wVDl+YvERHXxi6yvZBQnCpEdux1jGb+VotGdulSt/aCCzB/UkdEVzVYIpA15tjh+7R0TJnO/7nBfQPpkJHBBGDdzZqe1DsIXE0eDdWRM6RqvMFH/D+BuL7ElNUWx3sVyKLyUUW4k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=dPNDH27Z; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="dPNDH27Z" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582793; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Gpgt+6O35ek7x1kuD6AC9QWes4eKNtIpqZX1FAGx/p8=; b=dPNDH27ZWb79xGbxZL5Mcq+Ikvz9D/PpZPBvupIseAXNwrH8ppLO5SlwUyJwn3qWsHwik8 WEgUp/crWYN5TEmKiE8CQyvohy+rys1UameDO1FyCxty4jaAjLpO8No/bznX6HoPxsATCo IVYkFMx5YhJIwv0a/dmrdSfVqaf4etE= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-86-_ukP0tMHOHqUgSbpDQngcA-1; Mon, 24 Aug 2026 10:46:29 -0400 X-MC-Unique: _ukP0tMHOHqUgSbpDQngcA-1 X-Mimecast-MFC-AGG-ID: _ukP0tMHOHqUgSbpDQngcA_1787582786 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2DF2D183047D; Mon, 24 Aug 2026 14:46:26 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 4C9B030002ED; Mon, 24 Aug 2026 14:46:19 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 34/35] netfs: Combine prepare and issue ops and grab the buffers on request Date: Mon, 24 Aug 2026 15:41:27 +0100 Message-ID: <20260824144130.759997-35-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" Modify the way subrequests are generated in netfslib to try and simplify the code. The issue, primarily, is in writeback: the code has to create multiple streams of write requests to disparate targets with different properties (e.g. server and fscache), where not every folio needs to go to every target (e.g. data just read from the server may only need writing to the cache). The current model in writeback, at least, is to go carefully through every folio, preparing a subrequest for each stream when it was detected that part of the current folio needed to go to that stream, and repeating this within and across contiguous folios; then to issue subrequests as they become full or hit boundaries after first setting up the buffer. However, this is quite difficult to follow - and makes it tricky to handle discontiguous folios in a request. This is changed such that netfs now accumulates buffers and attaches them to each stream when they become valid for that stream, then flushes the stream when a limit or a boundary is hit. The issuing code in netfs then loops around creating and issuing subrequests without calling a separate prepare stage (though a function is provided to get an estimate of when flushing should occur). The filesystem (or cache) then gets to take a slice of the master bvec chain as its I/O buffer for each subrequest, including discontiguities if it can support a sparse/vectored RPC (as Ceph can). Similar-ish changes also apply to buffered read and unbuffered read and write, though in each of those cases there is only a single contiguous stream. Though for buffered read this consists of interwoven requests from multiple sources (server or cache). To this end, netfslib is changed in the following ways: (1) ->prepare_xxx(), buffer selection and ->issue_xxx() are now collapsed together such that one ->issue_xxx() call is made with the subrequest defined to the maximum extent; the filesystem/cache then reduces the length of the subrequest and calls back to netfslib to grab a slice of the buffer, which may reduce the subrequest further if a maximum segment limit is set. The filesystem/cache then dispatches the operation. (2) Retry buffer tracking is added to the netfs_io_request struct. This is then selected by the subrequest retry counter being non-zero. (3) The use of iov_iter is pushed down to the filesystem. Netfslib now provides the filesystem with a bvecq holding the buffer rather than an iov_iter. The bvecq can be duplicated and headers/trailers attached to hold protocol and several duplicated bvecqs can be linked together to create a compound operation. (4) If the ->issue_xxx() functions terminate with -ENOMEM, a flag is set on the request to abort further subrequest generation/retrying. If it fails early with any other error, generally the subreq will be extended as much as possible and failed with the error returned. (5) During writeback, netfslib now builds up an accumulation of buffered data before issuing writes on each stream (one server, one cache). It asks each stream for an estimate of how much data to accumulate before it next generates subrequests on the stream. The filesystem or cache is not required to use up all the data accumulated on a stream at that time unless the end of the pagecache is hit. (6) During read-gaps, in which there are two gaps on either end of a dirty streaming write page that need to be filled, a buffer is constructed consisting of the two ends plus a sink page repeated to cover the middle portion. This is passed to the server as a single write. For something like Ceph, this should probably be done either as a vectored/sparse read or as two separate reads (if different Ceph objects are involved). (7) During unbuffered/DIO read/write, there is a single contiguous file region to be read or written as a single stream. The dispatching function just creates subrequests and calls ->issue_xxx() repeatedly to eat through the bufferage. (8) At the start of buffered read, the entire set of folios allocated by VM readahead is loaded into a bvecq chain, rather than trying to do it piecemeal as-needed. As the pages were already added and locked by the VM, this is slightly more efficient than loading piecemeal as only a single iteration of the xarray is required. (9) During buffered read, there is a single contiguous file region, to read as a single stream - however, this stream may be stitched together from subrequests to multiple sources. Which sources are used where is now determined by querying the cache to find the next couple of extents in which it has data; netfslib uses this to direct the subrequests towards the appropriate sources. Each subrequest is given the maximum length in the current extent and then ->issue_read() is called. The filesystem then limits the size and slices off a piece of the buffer for that extent. (10) Cachefiles now provides an estimation function that indicates the standard maxima for doing DIO (MAX_RW_COUNT and BIO_MAX_VECS). Note that sparse cachefiles still rely on the backing filesystem for content mapping. That will need to be addressed in a future patch and is not trivial to fix. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/9p/vfs_addr.c | 43 ++++-- fs/afs/file.c | 27 ++-- fs/afs/fsclient.c | 8 +- fs/afs/internal.h | 5 +- fs/afs/write.c | 33 ++--- fs/afs/yfsclient.c | 6 +- fs/cachefiles/io.c | 230 ++++++++++++++++++++++--------- fs/ceph/addr.c | 123 ++++++++++------- fs/netfs/buffered_read.c | 257 +++++++++++++++++++---------------- fs/netfs/direct_read.c | 84 +++++++----- fs/netfs/direct_write.c | 141 ++++++++++--------- fs/netfs/fscache_io.c | 2 +- fs/netfs/internal.h | 43 ++++-- fs/netfs/main.c | 1 + fs/netfs/misc.c | 33 ++++- fs/netfs/objects.c | 4 +- fs/netfs/read_collect.c | 38 ++---- fs/netfs/read_pgpriv2.c | 125 +++++++++++------ fs/netfs/read_retry.c | 208 +++++++++++++--------------- fs/netfs/read_single.c | 155 +++++++++++++-------- fs/netfs/write_issue.c | 157 +++++++++++++-------- fs/netfs/write_retry.c | 168 ++++++++++++----------- fs/nfs/fscache.c | 24 +++- fs/smb/client/cifssmb.c | 13 +- fs/smb/client/file.c | 134 ++++++++---------- fs/smb/client/smb2ops.c | 9 +- fs/smb/client/smb2pdu.c | 28 ++-- fs/smb/client/transport.c | 15 +- include/linux/netfs.h | 54 +++----- include/trace/events/netfs.h | 30 +++- net/9p/client.c | 8 +- 31 files changed, 1301 insertions(+), 905 deletions(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 9b163a13ec3f..fc5b9c677986 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -67,28 +67,51 @@ static int v9fs_estimate_write(struct netfs_io_request = *wreq, /* * Issue a subrequest to write to the server. */ -static void v9fs_issue_write(struct netfs_io_subrequest *subreq) +static int v9fs_issue_write(struct netfs_io_subrequest *subreq) { + struct iov_iter iter; struct p9_fid *fid =3D subreq->rreq->netfs_priv; - int err, len; + int err, len =3D 0; =20 - len =3D p9_client_write(fid, subreq->start, &subreq->io_iter, &err); + subreq->len =3D umin(subreq->len, fid->clnt->msize - P9_IOHDRSZ); + + err =3D netfs_prepare_write_buffer(subreq, INT_MAX); + if (err < 0) + return err; + /* After this point, must fail by termination. */ + + iov_iter_bvec_queue(&iter, ITER_SOURCE, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + + len =3D p9_client_write(fid, subreq->start, &iter, &err); if (len > 0) __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + netfs_write_subrequest_terminated(subreq, len ?: err); + return 0; } =20 /** * v9fs_issue_read - Issue a read from 9P * @subreq: The read to make */ -static void v9fs_issue_read(struct netfs_io_subrequest *subreq) +static int v9fs_issue_read(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq =3D subreq->rreq; + struct iov_iter iter; struct p9_fid *fid =3D rreq->netfs_priv; char *target; unsigned long long pos =3D subreq->start + subreq->transferred; - int total =3D 0, err, len, n; + size_t len; + int total =3D 0, err; + + err =3D netfs_prepare_read_buffer(subreq, INT_MAX); + if (err < 0) + return err; + /* After this point, must fail by termination. */ + + iov_iter_bvec_queue(&iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); =20 if (S_ISLNK(rreq->inode->i_mode)) { /* p9_client_readlink() must not be called for legacy protocols @@ -105,12 +128,13 @@ static void v9fs_issue_read(struct netfs_io_subreques= t *subreq) err =3D p9_client_readlink(fid, &target); if (err !=3D 0) goto fill_subreq; - len =3D strlen(target); - n =3D copy_to_iter(target, len, &subreq->io_iter); + len =3D min(strlen(target), subreq->len); + total =3D copy_to_iter(target, len, &iter); kfree(target); - total =3D n; } else { - total =3D p9_client_read(fid, pos, &subreq->io_iter, &err); + trace_netfs_sreq(subreq, netfs_sreq_trace_submit); + + total =3D p9_client_read(fid, pos, &iter, &err); } =20 fill_subreq: @@ -128,6 +152,7 @@ static void v9fs_issue_read(struct netfs_io_subrequest = *subreq) =20 subreq->error =3D err; netfs_read_subreq_terminated(subreq); + return 0; } =20 /** diff --git a/fs/afs/file.c b/fs/afs/file.c index 4a412e0472ec..a6b61d609f02 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -332,11 +332,12 @@ void afs_fetch_data_immediate_cancel(struct afs_call = *call) /* * Fetch file data from the volume. */ -static void afs_issue_read(struct netfs_io_subrequest *subreq) +static int afs_issue_read(struct netfs_io_subrequest *subreq) { struct afs_operation *op; struct afs_vnode *vnode =3D AFS_FS_I(subreq->rreq->inode); struct key *key =3D subreq->rreq->netfs_priv; + int ret; =20 _enter("%s{%llx:%llu.%u},%x,,,", vnode->volume->name, @@ -345,11 +346,15 @@ static void afs_issue_read(struct netfs_io_subrequest= *subreq) vnode->fid.unique, key_serial(key)); =20 + ret =3D netfs_prepare_read_buffer(subreq, INT_MAX); + if (ret < 0) + return ret; + /* After this point, must fail by termination. */ + op =3D afs_alloc_operation(key, vnode->volume); if (IS_ERR(op)) { - subreq->error =3D PTR_ERR(op); - netfs_read_subreq_terminated(subreq); - return; + ret =3D PTR_ERR(op); + goto failed; } =20 afs_op_set_vnode(op, 0, vnode); @@ -364,20 +369,24 @@ static void afs_issue_read(struct netfs_io_subrequest= *subreq) op->flags |=3D AFS_OPERATION_ASYNC; =20 if (!afs_begin_vnode_operation(op)) { - subreq->error =3D afs_put_operation(op); - netfs_read_subreq_terminated(subreq); - return; + ret =3D afs_put_operation(op); + goto failed; } =20 if (!afs_select_fileserver(op)) { - afs_end_read(op); - return; + afs_end_read(op); /* Error recorded here. */ + return 0; } =20 afs_issue_read_call(op); } else { afs_do_sync_operation(op); } + return 0; +failed: + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); + return 0; } =20 static int afs_init_request(struct netfs_io_request *rreq, struct file *fi= le) diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c index 1a3f186a6a11..9713551dd842 100644 --- a/fs/afs/fsclient.c +++ b/fs/afs/fsclient.c @@ -339,7 +339,9 @@ static int afs_deliver_fs_fetch_data(struct afs_call *c= all) if (call->remaining =3D=3D 0) goto no_more_data; =20 - call->iter =3D &subreq->io_iter; + iov_iter_bvec_queue(&call->def_iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + call->iov_len =3D umin(call->remaining, subreq->len - subreq->transferre= d); call->unmarshall++; fallthrough; @@ -1088,7 +1090,7 @@ static void afs_fs_store_data64(struct afs_operation = *op) if (!call) return afs_op_nomem(op); =20 - call->write_iter =3D op->store.write_iter; + call->write_iter =3D &op->store.write_iter; =20 /* marshall the parameters */ bp =3D call->request; @@ -1142,7 +1144,7 @@ void afs_fs_store_data(struct afs_operation *op) if (!call) return afs_op_nomem(op); =20 - call->write_iter =3D op->store.write_iter; + call->write_iter =3D &op->store.write_iter; =20 /* marshall the parameters */ bp =3D call->request; diff --git a/fs/afs/internal.h b/fs/afs/internal.h index ae2bc699bfde..5c536cc7b53c 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -914,7 +914,7 @@ struct afs_operation { afs_lock_type_t type; } lock; struct { - struct iov_iter *write_iter; + struct iov_iter write_iter; loff_t pos; loff_t size; loff_t i_size; @@ -1701,8 +1701,7 @@ extern int afs_check_volume_status(struct afs_volume = *, struct afs_operation *); int afs_estimate_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, struct netfs_write_estimate *estimate); -void afs_prepare_write(struct netfs_io_subrequest *subreq); -void afs_issue_write(struct netfs_io_subrequest *subreq); +int afs_issue_write(struct netfs_io_subrequest *subreq); void afs_begin_writeback(struct netfs_io_request *wreq); void afs_retry_request(struct netfs_io_request *wreq, struct netfs_io_stre= am *stream); extern int afs_writepages(struct address_space *, struct writeback_control= *); diff --git a/fs/afs/write.c b/fs/afs/write.c index a0c9916f594b..46f81d9e7d7f 100644 --- a/fs/afs/write.c +++ b/fs/afs/write.c @@ -99,20 +99,6 @@ int afs_estimate_write(struct netfs_io_request *wreq, return 0; } =20 -/* - * Prepare a subrequest to write to the server. This sets the max_len - * parameter. - */ -void afs_prepare_write(struct netfs_io_subrequest *subreq) -{ - struct netfs_io_stream *stream =3D &subreq->rreq->io_streams[subreq->stre= am_nr]; - - //if (test_bit(NETFS_SREQ_RETRYING, &subreq->flags)) - // subreq->max_len =3D 512 * 1024; - //else - stream->sreq_max_len =3D 256 * 1024 * 1024; -} - /* * Issue a subrequest to write to the server. */ @@ -156,12 +142,15 @@ static void afs_issue_write_worker(struct work_struct= *work) op->flags |=3D AFS_OPERATION_UNINTR; op->ops =3D &afs_store_data_operation; =20 + trace_netfs_sreq(subreq, netfs_sreq_trace_submit); afs_begin_vnode_operation(op); =20 - op->store.write_iter =3D &subreq->io_iter; op->store.i_size =3D umax(pos + len, netfs_read_remote_i_size(&vnode->net= fs.inode)); op->mtime =3D inode_get_mtime(&vnode->netfs.inode); =20 + iov_iter_bvec_queue(&op->store.write_iter, ITER_SOURCE, subreq->content.b= vecq, + subreq->content.slot, subreq->content.offset, subreq->len); + afs_wait_for_operation(op); ret =3D afs_put_operation(op); switch (ret) { @@ -185,11 +174,21 @@ static void afs_issue_write_worker(struct work_struct= *work) netfs_write_subrequest_terminated(subreq, ret < 0 ? ret : subreq->len); } =20 -void afs_issue_write(struct netfs_io_subrequest *subreq) +int afs_issue_write(struct netfs_io_subrequest *subreq) { + int ret; + + if (subreq->len > 256 * 1024 * 1024) + subreq->len =3D 256 * 1024 * 1024; + ret =3D netfs_prepare_write_buffer(subreq, INT_MAX); + if (ret < 0) + return ret; + /* After this point, must fail by termination. */ + subreq->work.func =3D afs_issue_write_worker; if (!queue_work(system_dfl_wq, &subreq->work)) WARN_ON_ONCE(1); + return 0; } =20 /* @@ -200,6 +199,8 @@ void afs_begin_writeback(struct netfs_io_request *wreq) { if (S_ISREG(wreq->inode->i_mode)) afs_get_writeback_key(wreq); + + wreq->io_streams[0].avail =3D true; } =20 /* diff --git a/fs/afs/yfsclient.c b/fs/afs/yfsclient.c index d941179730a9..52c588092050 100644 --- a/fs/afs/yfsclient.c +++ b/fs/afs/yfsclient.c @@ -385,7 +385,9 @@ static int yfs_deliver_fs_fetch_data64(struct afs_call = *call) if (call->remaining =3D=3D 0) goto no_more_data; =20 - call->iter =3D &subreq->io_iter; + iov_iter_bvec_queue(&call->def_iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + call->iov_len =3D min(call->remaining, subreq->len - subreq->transferred= ); call->unmarshall++; fallthrough; @@ -1357,7 +1359,7 @@ void yfs_fs_store_data(struct afs_operation *op) if (!call) return afs_op_nomem(op); =20 - call->write_iter =3D op->store.write_iter; + call->write_iter =3D &op->store.write_iter; =20 /* marshall the parameters */ bp =3D call->request; diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 91b69be78c6b..a1580385d5fb 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -197,12 +197,131 @@ static int cachefiles_read(struct netfs_cache_resour= ces *cres, return ret; } =20 +/* + * Handle completion of a read from the cache issued by netfslib. + */ +static void cachefiles_issue_read_complete(struct kiocb *iocb, long ret) +{ + struct cachefiles_kiocb *ki =3D container_of(iocb, struct cachefiles_kioc= b, iocb); + struct netfs_io_subrequest *subreq =3D ki->subreq; + struct inode *inode =3D file_inode(ki->iocb.ki_filp); + + _enter("%ld", ret); + + if (ret < 0) { + subreq->error =3D -ESTALE; + trace_cachefiles_io_error(ki->object, inode, ret, + cachefiles_trace_read_error); + } + + if (ret >=3D 0) { + if (ki->object->cookie->inval_counter =3D=3D ki->inval_counter) { + subreq->error =3D 0; + if (ret > 0) { + subreq->transferred +=3D ret; + __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + } + } else { + subreq->error =3D -ESTALE; + } + } + + netfs_read_subreq_terminated(subreq); + cachefiles_put_kiocb(ki); +} + +/* + * Issue a read operation to the cache. We assume that the cache is reada= ble + * as the caller should have called ->query_occupancy() first. + */ +static int cachefiles_issue_read(struct netfs_io_subrequest *subreq) +{ + struct netfs_cache_resources *cres =3D &subreq->rreq->cache_resources; + struct cachefiles_object *object =3D cachefiles_cres_object(cres); + struct cachefiles_kiocb *ki; + struct iov_iter iter; + struct file *file =3D cachefiles_cres_file(cres); + unsigned int old_nofs; + ssize_t ret; + + fscache_count_read(); + + _enter("%pD,%lli,%llx,%zx/%llx", + file, file_inode(file)->i_ino, subreq->start, subreq->len, + i_size_read(file_inode(file))); + + if (subreq->len > MAX_RW_COUNT) + subreq->len =3D MAX_RW_COUNT; + + ret =3D netfs_prepare_read_buffer(subreq, BIO_MAX_VECS); + if (ret < 0) + return ret; + /* After this point, must fail by termination. */ + + iov_iter_bvec_queue(&iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + + ret =3D -ENOMEM; + ki =3D kzalloc_obj(struct cachefiles_kiocb); + if (!ki) + goto failed; + + refcount_set(&ki->ki_refcnt, 2); + ki->iocb.ki_filp =3D file; + ki->iocb.ki_pos =3D subreq->start; + ki->iocb.ki_flags =3D IOCB_DIRECT; + ki->iocb.ki_ioprio =3D get_current_ioprio(); + ki->iocb.ki_complete =3D cachefiles_issue_read_complete; + ki->object =3D object; + ki->inval_counter =3D cres->inval_counter; + ki->subreq =3D subreq; + ki->was_async =3D true; + + get_file(ki->iocb.ki_filp); + cachefiles_grab_object(object, cachefiles_obj_get_ioreq); + + trace_cachefiles_read(object, file_inode(file), ki->iocb.ki_pos, subreq->= len); + old_nofs =3D memalloc_nofs_save(); + ret =3D cachefiles_inject_read_error(); + if (ret =3D=3D 0) + ret =3D vfs_iocb_iter_read(file, &ki->iocb, &iter); + memalloc_nofs_restore(old_nofs); + + switch (ret) { + case -EIOCBQUEUED: + break; + + case -ERESTARTSYS: + case -ERESTARTNOINTR: + case -ERESTARTNOHAND: + case -ERESTART_RESTARTBLOCK: + /* There's no easy way to restart the syscall since other AIO's + * may be already running. Just fail this IO with EINTR. + */ + ret =3D -EINTR; + fallthrough; + default: + ki->was_async =3D false; + cachefiles_issue_read_complete(&ki->iocb, ret); + break; + } + + cachefiles_put_kiocb(ki); + _leave(" =3D %zd", ret); + return 0; +failed: + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); + return 0; +} + /* * Query the occupancy of the cache in a region, returning the extent of t= he - * next two chunks of cached data and the next hole. + * next two chunks of cached data and the next hole. The occupancy map is + * preloaded to show just one giant hole. */ -static int cachefiles_query_occupancy(struct netfs_cache_resources *cres, - struct fscache_occupancy *occ) +static void cachefiles_query_occupancy(struct netfs_cache_resources *cres, + struct fscache_occupancy *occ) { struct cachefiles_object *object; struct inode *inode; @@ -212,7 +331,7 @@ static int cachefiles_query_occupancy(struct netfs_cach= e_resources *cres, int i; =20 if (!fscache_wait_for_operation(cres, FSCACHE_WANT_READ)) - return -ENOBUFS; + return; =20 object =3D cachefiles_cres_object(cres); file =3D cachefiles_cres_file(cres); @@ -247,7 +366,7 @@ static int cachefiles_query_occupancy(struct netfs_cach= e_resources *cres, ret =3D vfs_llseek(file, occ->query_from, SEEK_DATA); if (IS_ERR_VALUE_LL(ret)) { if (ret !=3D -ENXIO) - return ret; + goto done; occ->query_from =3D ULLONG_MAX; goto done; } @@ -260,7 +379,7 @@ static int cachefiles_query_occupancy(struct netfs_cach= e_resources *cres, ret =3D vfs_llseek(file, occ->query_from, SEEK_HOLE); if (IS_ERR_VALUE_LL(ret)) { if (ret !=3D -ENXIO) - return ret; + goto done; occ->query_from =3D ULLONG_MAX; goto done; } @@ -273,7 +392,6 @@ static int cachefiles_query_occupancy(struct netfs_cach= e_resources *cres, done: _debug("query[0] %llx-%llx", occ->cached_from[0], occ->cached_to[0]); _debug("query[1] %llx-%llx", occ->cached_from[1], occ->cached_to[1]); - return 0; } =20 /* @@ -590,9 +708,9 @@ int __cachefiles_prepare_write(struct cachefiles_object= *object, cachefiles_has_space_for_write); } =20 -static int cachefiles_prepare_write(struct netfs_cache_resources *cres, - uoff_t *_start, size_t *_len, size_t upper_len, - uoff_t i_size, bool no_space_allocated_yet) +static int cachefiles_prepare_write_old(struct netfs_cache_resources *cres, + uoff_t *_start, size_t *_len, size_t upper_len, + uoff_t i_size, bool no_space_allocated_yet) { struct cachefiles_object *object =3D cachefiles_cres_object(cres); struct cachefiles_cache *cache =3D object->volume->cache; @@ -623,80 +741,63 @@ static int cachefiles_estimate_write(struct netfs_io_= request *wreq, return 0; } =20 -static void cachefiles_prepare_write_subreq(struct netfs_io_subrequest *su= breq) +static int cachefiles_issue_write(struct netfs_io_subrequest *subreq) { struct netfs_io_request *wreq =3D subreq->rreq; struct netfs_cache_resources *cres =3D &wreq->cache_resources; - struct netfs_io_stream *stream =3D &wreq->io_streams[subreq->stream_nr]; - - _enter("W=3D%x[%x] %llx", wreq->debug_id, subreq->debug_index, subreq->st= art); + struct cachefiles_object *object =3D cachefiles_cres_object(cres); + struct cachefiles_cache *cache =3D object->volume->cache; + struct iov_iter iter; + const struct cred *saved_cred; + uoff_t start =3D subreq->start; + size_t len =3D subreq->len; + int ret =3D -EINVAL; =20 - stream->sreq_max_len =3D MAX_RW_COUNT; - stream->sreq_max_segs =3D BIO_MAX_VECS; + _enter("W=3D%x[%x] %llx-%llx", + wreq->debug_id, subreq->debug_index, start, start + len - 1); =20 if (!cachefiles_cres_file(cres)) { if (!fscache_wait_for_operation(cres, FSCACHE_WANT_WRITE)) - return netfs_prepare_write_failed(subreq); + return -ENOBUFS; if (!cachefiles_cres_file(cres)) - return netfs_prepare_write_failed(subreq); + return -ENOBUFS; } -} =20 -static void cachefiles_issue_write(struct netfs_io_subrequest *subreq) -{ - struct netfs_io_request *wreq =3D subreq->rreq; - struct netfs_cache_resources *cres =3D &wreq->cache_resources; - struct cachefiles_object *object =3D cachefiles_cres_object(cres); - struct cachefiles_cache *cache =3D object->volume->cache; - const struct cred *saved_cred; - size_t off, pre, post, len =3D subreq->len; - uoff_t start =3D subreq->start; - int ret; + ret =3D netfs_prepare_write_buffer(subreq, BIO_MAX_VECS); + if (ret < 0) + return ret; + /* After this point, must fail by termination. */ =20 - _enter("W=3D%x[%x] %llx-%llx", - wreq->debug_id, subreq->debug_index, start, start + len - 1); + /* The buffer extraction func may round out start and end. */ + start =3D subreq->start; + len =3D subreq->len; =20 - /* We need to start on the cache granularity boundary */ - off =3D start & (cache->bsize - 1); - if (off) { - pre =3D cache->bsize - off; - if (pre >=3D len) { - fscache_count_dio_misfit(); - netfs_write_subrequest_terminated(subreq, len); - return; - } - subreq->transferred +=3D pre; - start +=3D pre; - len -=3D pre; - iov_iter_advance(&subreq->io_iter, pre); - } - - /* We also need to end on the cache granularity boundary */ - post =3D len & (cache->bsize - 1); - if (post) { - len -=3D post; - if (len =3D=3D 0) { - fscache_count_dio_misfit(); - netfs_write_subrequest_terminated(subreq, post); - return; - } - iov_iter_truncate(&subreq->io_iter, len); + /* We need to start and end on cache granularity boundaries. */ + if (WARN_ON_ONCE(start & (cache->bsize - 1)) || + WARN_ON_ONCE(len & (cache->bsize - 1))) { + fscache_count_dio_misfit(); + ret =3D -EIO; + goto failed; } =20 + iov_iter_bvec_queue(&iter, ITER_SOURCE, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, len); + trace_netfs_sreq(subreq, netfs_sreq_trace_cache_prepare); cachefiles_begin_secure(cache, &saved_cred); ret =3D __cachefiles_prepare_write(object, cachefiles_cres_file(cres), &start, &len, len, true); cachefiles_end_secure(cache, saved_cred); - if (ret < 0) { - netfs_write_subrequest_terminated(subreq, ret); - return; - } + if (ret < 0) + goto failed; =20 trace_netfs_sreq(subreq, netfs_sreq_trace_cache_write); - cachefiles_write(&subreq->rreq->cache_resources, - subreq->start, &subreq->io_iter, + cachefiles_write(&subreq->rreq->cache_resources, subreq->start, &iter, netfs_write_subrequest_terminated, subreq); + return 0; +failed: + netfs_write_subrequest_terminated(subreq, ret); + return 0; } =20 /* @@ -753,6 +854,7 @@ static void cachefiles_collect_write(struct netfs_io_re= quest *wreq, WARN_ON(new_size & (cache->bsize - 1)) || WARN_ON(start & (cache->bsize - 1)) || WARN_ON(len & (cache->bsize - 1))) { + trace_netfs_rreq(wreq, netfs_rreq_trace_cache_align_error); trace_cachefiles_io_error(object, inode, -EIO, cachefiles_trace_alignment_error); cachefiles_remove_object_xattr(cache, object, file->f_path.dentry); @@ -907,10 +1009,10 @@ static const struct netfs_cache_ops cachefiles_netfs= _cache_ops =3D { .end_operation =3D cachefiles_end_operation, .read =3D cachefiles_read, .write =3D cachefiles_write, + .issue_read =3D cachefiles_issue_read, .issue_write =3D cachefiles_issue_write, - .prepare_write =3D cachefiles_prepare_write, - .prepare_write_subreq =3D cachefiles_prepare_write_subreq, .estimate_write =3D cachefiles_estimate_write, + .prepare_write_old =3D cachefiles_prepare_write_old, .query_occupancy =3D cachefiles_query_occupancy, .collect_write =3D cachefiles_collect_write, }; diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index 6586f6c1dc73..b13c071bd574 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -274,7 +274,7 @@ static void finish_netfs_read(struct ceph_osd_request *= req) ceph_dec_osd_stopping_blocker(fsc->mdsc); } =20 -static bool ceph_netfs_issue_op_inline(struct netfs_io_subrequest *subreq) +static int ceph_netfs_issue_op_inline(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq =3D subreq->rreq; struct inode *inode =3D rreq->inode; @@ -283,7 +283,8 @@ static bool ceph_netfs_issue_op_inline(struct netfs_io_= subrequest *subreq) struct ceph_mds_request *req; struct ceph_mds_client *mdsc =3D ceph_sb_to_mdsc(inode->i_sb); struct ceph_inode_info *ci =3D ceph_inode(inode); - ssize_t err =3D 0; + struct iov_iter iter; + ssize_t err; size_t len; int mode; =20 @@ -292,8 +293,20 @@ static bool ceph_netfs_issue_op_inline(struct netfs_io= _subrequest *subreq) __set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags); __clear_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); =20 - if (subreq->start >=3D inode->i_size) + err =3D netfs_prepare_read_buffer(subreq, INT_MAX); + if (err < 0) + return err; + /* After this point, must fail by termination. */ + + if (subreq->start >=3D inode->i_size) { + __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); + err =3D 0; goto out; + } + + iov_iter_bvec_queue(&iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, + subreq->len); =20 /* We need to fetch the inline data. */ mode =3D ceph_try_to_choose_auth_mds(inode, CEPH_STAT_CAP_INLINE_DATA); @@ -302,11 +315,13 @@ static bool ceph_netfs_issue_op_inline(struct netfs_i= o_subrequest *subreq) err =3D PTR_ERR(req); goto out; } + req->r_ino1 =3D ci->i_vino; req->r_args.getattr.mask =3D cpu_to_le32(CEPH_STAT_CAP_INLINE_DATA); req->r_num_caps =3D 2; =20 trace_netfs_sreq(subreq, netfs_sreq_trace_submit); + err =3D ceph_mdsc_do_request(mdsc, NULL, req); if (err < 0) goto out; @@ -316,11 +331,13 @@ static bool ceph_netfs_issue_op_inline(struct netfs_i= o_subrequest *subreq) if (iinfo->inline_version =3D=3D CEPH_INLINE_NONE) { /* The data got uninlined */ ceph_mdsc_put_request(req); - return false; + __set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); + err =3D -EAGAIN; + goto out; } =20 len =3D min_t(size_t, iinfo->inline_len - subreq->start, subreq->len); - err =3D copy_to_iter(iinfo->inline_data + subreq->start, len, &subreq->io= _iter); + err =3D copy_to_iter(iinfo->inline_data + subreq->start, len, &iter); if (err =3D=3D 0) { err =3D -EFAULT; } else { @@ -333,26 +350,10 @@ static bool ceph_netfs_issue_op_inline(struct netfs_i= o_subrequest *subreq) subreq->error =3D err; trace_netfs_sreq(subreq, netfs_sreq_trace_io_progress); netfs_read_subreq_terminated(subreq); - return true; -} - -static int ceph_netfs_prepare_read(struct netfs_io_subrequest *subreq) -{ - struct netfs_io_request *rreq =3D subreq->rreq; - struct inode *inode =3D rreq->inode; - struct ceph_inode_info *ci =3D ceph_inode(inode); - struct ceph_fs_client *fsc =3D ceph_inode_to_fs_client(inode); - u64 objno, objoff; - u32 xlen; - - /* Truncate the extent at the end of the current block */ - ceph_calc_file_object_mapping(&ci->i_layout, subreq->start, subreq->len, - &objno, &objoff, &xlen); - rreq->io_streams[0].sreq_max_len =3D umin(xlen, fsc->mount_options->rsize= ); return 0; } =20 -static void ceph_netfs_issue_read(struct netfs_io_subrequest *subreq) +static int ceph_netfs_issue_read(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq =3D subreq->rreq; struct inode *inode =3D rreq->inode; @@ -361,19 +362,18 @@ static void ceph_netfs_issue_read(struct netfs_io_sub= request *subreq) struct ceph_client *cl =3D fsc->client; struct ceph_osd_request *req =3D NULL; struct ceph_vino vino =3D ceph_vino(inode); - int err; - u64 len; + struct iov_iter iter; + u64 objno, objoff, len, off =3D subreq->start; + u32 maxlen; + int err =3D -EIO; bool sparse =3D IS_ENCRYPTED(inode) || ceph_test_mount_opt(fsc, SPARSEREA= D); - u64 off =3D subreq->start; int extent_cnt; =20 - if (ceph_inode_is_shutdown(inode)) { - err =3D -EIO; - goto out; - } + if (ceph_inode_is_shutdown(inode)) + return -EIO; =20 - if (ceph_has_inline_data(ci) && ceph_netfs_issue_op_inline(subreq)) - return; + if (ceph_has_inline_data(ci)) + return ceph_netfs_issue_op_inline(subreq); =20 // TODO: This rounding here is slightly dodgy. It *should* work, for // now, as the cache only deals in blocks that are a multiple of @@ -383,26 +383,48 @@ static void ceph_netfs_issue_read(struct netfs_io_sub= request *subreq) len =3D subreq->len; ceph_fscrypt_adjust_off_and_len(inode, &off, &len); =20 + /* Truncate the extent at the end of the current block */ + ceph_calc_file_object_mapping(&ci->i_layout, subreq->start, len, + &objno, &objoff, &maxlen); + maxlen =3D min(maxlen, fsc->mount_options->rsize); + len =3D min(len, maxlen); + subreq->len =3D len; + + /* Grab a slice of read buffer. This may shrink the subreq. */ + err =3D netfs_prepare_read_buffer(subreq, INT_MAX); + if (err < 0) + return err; + /* After this point, must fail by termination. */ + + /* Create a request. In theory, this may shrink the request again, but + * it shouldn't since we calculated the object size above and already + * shrank to that, but if it does, we'll just end up doing a short read + * and retrying to get the rest. + */ + len =3D subreq->len; req =3D ceph_osdc_new_request(&fsc->client->osdc, &ci->i_layout, vino, off, &len, 0, 1, sparse ? CEPH_OSD_OP_SPARSE_READ : CEPH_OSD_OP_READ, CEPH_OSD_FLAG_READ, NULL, ci->i_truncate_seq, ci->i_truncate_size, false); if (IS_ERR(req)) { err =3D PTR_ERR(req); - req =3D NULL; - goto out; + goto failed_noput; } =20 if (sparse) { extent_cnt =3D __ceph_sparse_read_ext_count(inode, len); err =3D ceph_alloc_sparse_ext_map(&req->r_ops[0], extent_cnt); if (err) - goto out; + goto failed; } =20 doutc(cl, "%llx.%llx pos=3D%llu orig_len=3D%zu len=3D%llu\n", ceph_vinop(inode), subreq->start, subreq->len, len); =20 + iov_iter_bvec_queue(&iter, ITER_DEST, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, + subreq->len); + /* * FIXME: For now, use CEPH_OSD_DATA_TYPE_PAGES instead of _ITER for * encrypted inodes. We'd need infrastructure that handles an iov_iter @@ -421,13 +443,11 @@ static void ceph_netfs_issue_read(struct netfs_io_sub= request *subreq) * ceph_msg_data_cursor_init() triggers BUG_ON() in the case * if msg->sparse_read_total > msg->data_length. */ - subreq->io_iter.count =3D len; - - err =3D iov_iter_get_pages_alloc2(&subreq->io_iter, &pages, len, &page_o= ff); + err =3D iov_iter_get_pages_alloc2(&iter, &pages, len, &page_off); if (err < 0) { doutc(cl, "%llx.%llx failed to allocate pages, %d\n", ceph_vinop(inode), err); - goto out; + goto eio; } =20 /* should always give us a page-aligned read */ @@ -438,12 +458,10 @@ static void ceph_netfs_issue_read(struct netfs_io_sub= request *subreq) osd_req_op_extent_osd_data_pages(req, 0, pages, len, 0, false, false); } else { - osd_req_op_extent_osd_iter(req, 0, &subreq->io_iter); - } - if (!ceph_inc_osd_stopping_blocker(fsc->mdsc)) { - err =3D -EIO; - goto out; + osd_req_op_extent_osd_iter(req, 0, &iter); } + if (!ceph_inc_osd_stopping_blocker(fsc->mdsc)) + goto eio; req->r_callback =3D finish_netfs_read; req->r_priv =3D subreq; req->r_inode =3D inode; @@ -451,19 +469,22 @@ static void ceph_netfs_issue_read(struct netfs_io_sub= request *subreq) =20 trace_netfs_sreq(subreq, netfs_sreq_trace_submit); ceph_osdc_start_request(req->r_osdc, req); -out: ceph_osdc_put_request(req); - if (err) { - subreq->error =3D err; - netfs_read_subreq_terminated(subreq); - } - doutc(cl, "%llx.%llx result %d\n", ceph_vinop(inode), err); + doutc(cl, "%llx.%llx result -EIOCBQUEUED\n", ceph_vinop(inode)); + return 0; +eio: + err =3D -EIO; +failed: + ceph_osdc_put_request(req); +failed_noput: + subreq->error =3D err; + netfs_read_subreq_terminated(subreq); + return 0; } =20 static int ceph_init_request(struct netfs_io_request *rreq, struct file *f= ile) { struct inode *inode =3D rreq->inode; - struct ceph_fs_client *fsc =3D ceph_inode_to_fs_client(inode); struct ceph_client *cl =3D ceph_inode_to_client(inode); int got =3D 0, want =3D CEPH_CAP_FILE_CACHE; struct ceph_netfs_request_data *priv; @@ -515,7 +536,6 @@ static int ceph_init_request(struct netfs_io_request *r= req, struct file *file) =20 priv->caps =3D got; rreq->netfs_priv =3D priv; - rreq->io_streams[0].sreq_max_len =3D fsc->mount_options->rsize; =20 out: if (ret < 0) { @@ -543,7 +563,6 @@ static void ceph_netfs_free_request(struct netfs_io_req= uest *rreq) const struct netfs_request_ops ceph_netfs_ops =3D { .init_request =3D ceph_init_request, .free_request =3D ceph_netfs_free_request, - .prepare_read =3D ceph_netfs_prepare_read, .issue_read =3D ceph_netfs_issue_read, .expand_readahead =3D ceph_netfs_expand_readahead, .check_write_begin =3D ceph_netfs_check_write_begin, diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 88e5404080e8..fbb0c312b723 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -100,70 +100,98 @@ static int netfs_begin_cache_read(struct netfs_io_req= uest *rreq, struct netfs_in } =20 /* - * netfs_prepare_read_iterator - Prepare the subreq iterator for I/O - * @subreq: The subrequest to be set up - * - * Prepare the I/O iterator representing the read buffer on a subrequest f= or - * the filesystem to use for I/O (it can be passed directly to a socket). = This - * is intended to be called from the ->issue_read() method once the filesy= stem - * has trimmed the request to the size it wants. - * - * Returns the limited size if successful and -ENOMEM if insufficient memo= ry - * available. + * Prepare the I/O buffer on a buffered read subrequest for the filesystem= to + * use as a bvec queue. */ -static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *sub= req) +static int netfs_prepare_buffered_read_buffer(struct netfs_io_subrequest *= subreq, + unsigned int max_segs) { struct netfs_io_request *rreq =3D subreq->rreq; struct netfs_io_stream *stream =3D &rreq->io_streams[0]; ssize_t extracted; - size_t rsize =3D subreq->len; =20 - if (subreq->source =3D=3D NETFS_DOWNLOAD_FROM_SERVER) - rsize =3D umin(rsize, stream->sreq_max_len); + _enter("R=3D%08x[%x] l=3D%zx s=3D%u", + rreq->debug_id, subreq->debug_index, subreq->len, max_segs); =20 - bvecq_pos_set(&subreq->dispatch_pos, &rreq->dispatch_cursor); - extracted =3D bvecq_slice(&rreq->dispatch_cursor, rsize, - stream->sreq_max_segs, &subreq->nr_segs); - if (extracted < rsize) { + bvecq_pos_set(&subreq->dispatch_pos, &stream->dispatch_cursor); + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + extracted =3D bvecq_slice(&stream->dispatch_cursor, subreq->len, + max_segs, &subreq->nr_segs); + + if (extracted < subreq->len) { subreq->len =3D extracted; trace_netfs_sreq(subreq, netfs_sreq_trace_limited); } + stream->buffered -=3D extracted; + stream->issue_from =3D subreq->start + subreq->len; + rreq->submitted =3D stream->issue_from; =20 - return subreq->len; + if (!stream->buffered) + netfs_all_subreqs_queued(rreq); + return 0; } =20 -/* - * Issue a read against the cache. - * - Eats the caller's ref on subreq. +/** + * netfs_prepare_read_buffer - Get the buffer for a subrequest + * @subreq: The subrequest to get the buffer for + * @max_segs: Maximum number of segments in buffer (or INT_MAX) + * + * Extract a slice of buffer from the stream and attach it to the subreque= st as + * a bio_vec queue. The maximum amount of data attached is set by + * @subreq->len, but this may be shortened if @max_segs would be exceeded. + * + * [!] NOTE: This must be run in the same thread as ->issue_read() was cal= led + * in as we access the readahead_control struct if there is one. */ -static void netfs_read_cache_to_pagecache(struct netfs_io_request *rreq, - struct netfs_io_subrequest *subreq) +int netfs_prepare_read_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs) { - struct netfs_cache_resources *cres =3D &rreq->cache_resources; - - netfs_stat(&netfs_n_rh_read); - cres->ops->read(cres, subreq->start, &subreq->io_iter, NETFS_READ_HOLE_IG= NORE, - netfs_cache_read_terminated, subreq); + switch (subreq->rreq->origin) { + case NETFS_READAHEAD: + case NETFS_READPAGE: + case NETFS_READ_FOR_WRITE: + if (subreq->retry_count) + return netfs_prepare_buffered_read_retry_buffer(subreq, max_segs); + return netfs_prepare_buffered_read_buffer(subreq, max_segs); + + case NETFS_UNBUFFERED_READ: + case NETFS_DIO_READ: + case NETFS_READ_GAPS: + return netfs_prepare_unbuffered_read_buffer(subreq, max_segs); + case NETFS_READ_SINGLE: + return netfs_prepare_read_single_buffer(subreq, max_segs); + default: + WARN_ON_ONCE(1); + return -EIO; + } } +EXPORT_SYMBOL(netfs_prepare_read_buffer); =20 -int netfs_read_query_cache(struct netfs_io_request *rreq, struct fscache_o= ccupancy *occ) +void netfs_read_query_cache(struct netfs_io_request *rreq, struct fscache_= occupancy *occ) { struct netfs_cache_resources *cres =3D &rreq->cache_resources; =20 occ->granularity =3D PAGE_SIZE; if (occ->query_from >=3D occ->query_to) - return 0; + return; if (!cres->ops) - return 0; + return; occ->query_from =3D round_up(occ->query_from, occ->granularity); - return cres->ops->query_occupancy(cres, occ); + cres->ops->query_occupancy(cres, occ); } =20 -void netfs_queue_read(struct netfs_io_request *rreq, - struct netfs_io_subrequest *subreq) +/* + * Allocate and prepare a read subrequest. + */ +struct netfs_io_subrequest *netfs_alloc_read_subrequest(struct netfs_io_re= quest *rreq) { + struct netfs_io_subrequest *subreq; struct netfs_io_stream *stream =3D &rreq->io_streams[0]; =20 + subreq =3D netfs_alloc_subrequest(rreq); + if (!subreq) + return subreq; + __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); =20 /* We add to the end of the list whilst the collector may be walking @@ -173,40 +201,56 @@ void netfs_queue_read(struct netfs_io_request *rreq, spin_lock(&rreq->lock); /* Write IN_PROGRESS before pointer to new subreq */ list_add_tail_release(&subreq->rreq_link, &stream->subrequests); - if (list_is_first(&subreq->rreq_link, &stream->subrequests)) { - if (!stream->active) { - stream->collected_to =3D subreq->start; - /* Store list pointers before active flag */ - smp_store_release(&stream->active, true); - } - } + if (!stream->active) + /* Store list pointers before active flag */ + smp_store_release(&stream->active, true); =20 spin_unlock(&rreq->lock); + return subreq; } =20 static void netfs_issue_read(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq) { - bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); - iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, - subreq->content.slot, subreq->content.offset, subreq->len); + struct netfs_cache_resources *cres =3D &rreq->cache_resources; + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; + int ret; + + _enter("R=3D%08x[%x]", rreq->debug_id, subreq->debug_index); =20 switch (subreq->source) { case NETFS_DOWNLOAD_FROM_SERVER: - rreq->netfs_ops->issue_read(subreq); - break; + ret =3D rreq->netfs_ops->issue_read(subreq); + if (ret < 0) + goto fail; + return; case NETFS_READ_FROM_CACHE: - netfs_read_cache_to_pagecache(rreq, subreq); - break; + netfs_stat(&netfs_n_rh_read); + ret =3D cres->ops->issue_read(subreq); + if (ret < 0) + goto fail; + return; default: - bvecq_zero(&subreq->content, subreq->len); + WARN_ON_ONCE(1); + fallthrough; + case NETFS_FILL_WITH_ZEROES: + stream->issue_from =3D subreq->start + subreq->len; + stream->buffered -=3D subreq->len; + trace_netfs_sreq(subreq, netfs_sreq_trace_submit); + if (!stream->buffered) + netfs_all_subreqs_queued(rreq); + bvecq_zero(&stream->dispatch_cursor, subreq->len); subreq->transferred =3D subreq->len; subreq->error =3D 0; - iov_iter_zero(subreq->len, &subreq->io_iter); - subreq->transferred =3D subreq->len; - netfs_read_subreq_terminated(subreq); - break; + return netfs_read_subreq_terminated(subreq); } + +fail: + /* Ownership of subreq was returned to us. */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + stream->buffered -=3D subreq->len; + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); } =20 /* @@ -225,19 +269,16 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) .cached_to[1] =3D ULLONG_MAX, }; struct fscache_occupancy *occ =3D &_occ; - ssize_t size =3D rreq->len; - uoff_t start =3D rreq->start; + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; int ret =3D 0; =20 _enter("R=3D%08x", rreq->debug_id); =20 - bvecq_pos_set(&rreq->dispatch_cursor, &rreq->load_cursor); + bvecq_pos_set(&stream->dispatch_cursor, &rreq->load_cursor); =20 do { - int (*prepare_read)(struct netfs_io_subrequest *subreq) =3D NULL; struct netfs_io_subrequest *subreq; - ssize_t slice; - uoff_t hole_to, cache_to; + uoff_t hole_to, cache_to, stop; =20 /* If we don't have any, find out the next couple of data * extents from the cache, containing of following the @@ -246,7 +287,7 @@ static void netfs_read_to_pagecache(struct netfs_io_req= uest *rreq) */ hole_to =3D occ->cached_from[0]; cache_to =3D occ->cached_to[0]; - if (start >=3D cache_to) { + if (stream->issue_from >=3D cache_to) { /* Extent exhausted; shuffle down. */ int i; =20 @@ -261,52 +302,50 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) if (occ->cached_from[0] !=3D ULLONG_MAX) continue; =20 - /* Get new extents */ - ret =3D netfs_read_query_cache(rreq, occ); - if (ret < 0) - break; + /* Wait for the cache to enter the readable state and + * get more extents. If the cache doesn't become + * readable, this leaves the default negative extent in + * place. + */ + netfs_read_query_cache(rreq, occ); continue; } =20 - subreq =3D netfs_alloc_subrequest(rreq); + subreq =3D netfs_alloc_read_subrequest(rreq); if (!subreq) { ret =3D -ENOMEM; break; } =20 - subreq->start =3D start; - subreq->len =3D size; - - netfs_queue_read(rreq, subreq); + subreq->start =3D stream->issue_from; + stop =3D stream->issue_from + stream->buffered; =20 uoff_t zero_point =3D netfs_read_zero_point(rreq->inode); uoff_t zlimit =3D umin(zero_point, rreq->i_size); =20 _debug("rsub %llx %llx-%llx", subreq->start, hole_to, cache_to); =20 - if (start >=3D hole_to && start < cache_to) { + if (stream->issue_from >=3D hole_to && stream->issue_from < cache_to) { /* Overlap with a cached region, where the cache may * record a block of zeroes. */ - _debug("cached s=3D%llx c=3D%llx l=3D%zx", start, cache_to, size); - subreq->len =3D umin(cache_to - start, size); - subreq->len =3D round_up(subreq->len, occ->granularity); + _debug("cached s=3D%llx c=3D%llx l=3D%zx", + stream->issue_from, cache_to, stream->buffered); + subreq->len =3D umin(cache_to - stream->issue_from, stream->buffered); if (occ->cached_type[0] =3D=3D FSCACHE_EXTENT_ZERO) { subreq->source =3D NETFS_FILL_WITH_ZEROES; netfs_stat(&netfs_n_rh_zero); } else { + subreq->len =3D round_up(subreq->len, occ->granularity); subreq->source =3D NETFS_READ_FROM_CACHE; - prepare_read =3D rreq->cache_resources.ops->prepare_read; } - - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - - } else if (subreq->start >=3D zlimit && size > 0) { + } else if (subreq->start >=3D zlimit && + subreq->start < stop) { /* If this range lies beyond the zero-point, that part * can just be cleared locally. */ - _debug("zero %llx-%llx", start, start + size); - subreq->len =3D size; + _debug("zero %llx-%llx", subreq->start, stop); + subreq->len =3D stream->buffered; subreq->source =3D NETFS_FILL_WITH_ZEROES; if (rreq->cache_resources.ops) __set_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); @@ -316,10 +355,10 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) * this range lies beyond the zero-point or the EOF, * that part can just be cleared locally. */ - uoff_t limit =3D min3(zlimit, start + size, hole_to); + uoff_t limit =3D min3(zlimit, stop, hole_to); =20 _debug("limit %llx %llx", rreq->i_size, zero_point); - _debug("download %llx-%llx", start, start + size); + _debug("download %llx-%llx", subreq->start, stop); subreq->len =3D umin(limit - subreq->start, ULONG_MAX); subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; if (rreq->cache_resources.ops) @@ -327,39 +366,15 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) netfs_stat(&netfs_n_rh_download); } =20 - if (size =3D=3D 0) { + if (subreq->len =3D=3D 0) { pr_err("ZERO-LEN READ: R=3D%08x[%x] l=3D%zx/%zx s=3D%llx z=3D%llx i=3D%= llx", rreq->debug_id, subreq->debug_index, - subreq->len, size, + subreq->len, stream->buffered, subreq->start, zero_point, rreq->i_size); netfs_cancel_read(subreq, ret); break; } =20 - rreq->io_streams[0].sreq_max_len =3D MAX_RW_COUNT; - rreq->io_streams[0].sreq_max_segs =3D INT_MAX; - - if (prepare_read) { - ret =3D prepare_read(subreq); - if (ret < 0) { - netfs_cancel_read(subreq, ret); - break; - } - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - } - - slice =3D netfs_prepare_read_iterator(subreq); - if (slice < 0) { - ret =3D slice; - netfs_cancel_read(subreq, ret); - break; - } - start +=3D slice; - size -=3D slice; - if (size <=3D 0) - netfs_all_subreqs_queued(rreq); - - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); netfs_issue_read(rreq, subreq); netfs_maybe_bulk_drop_ra_refs(rreq); =20 @@ -368,18 +383,19 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) if (test_bit(NETFS_RREQ_FAILED, &rreq->flags)) break; cond_resched(); - } while (size > 0); + } while (stream->buffered > 0); =20 - if (unlikely(size > 0)) { + if (unlikely(!netfs_are_all_subreqs_queued(rreq))) { netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } =20 /* Defer error return as we may need to wait for outstanding I/O. */ - cmpxchg(&rreq->error, 0, ret); + if (ret < 0) + cmpxchg(&rreq->error, 0, ret); =20 bvecq_pos_unset(&rreq->load_cursor); - bvecq_pos_unset(&rreq->dispatch_cursor); + bvecq_pos_unset(&stream->dispatch_cursor); } =20 /** @@ -400,17 +416,22 @@ static void netfs_read_to_pagecache(struct netfs_io_r= equest *rreq) void netfs_readahead(struct readahead_control *ractl) { struct netfs_io_request *rreq; + struct netfs_io_stream *stream; struct netfs_inode *ictx =3D netfs_inode(ractl->mapping->host); ssize_t added; uoff_t start =3D readahead_pos(ractl); size_t size =3D readahead_length(ractl); int ret; =20 + _enter(""); + rreq =3D netfs_alloc_request(ractl->mapping, ractl->file, start, size, NETFS_READAHEAD); if (IS_ERR(rreq)) return; =20 + stream =3D &rreq->io_streams[0]; + __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &rreq->flags); =20 ret =3D netfs_begin_cache_read(rreq, ictx); @@ -435,9 +456,9 @@ void netfs_readahead(struct readahead_control *ractl) __set_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags); =20 rreq->submitted =3D rreq->start + added; - rreq->cleaned_to =3D rreq->start; bvecq_pos_set(&rreq->collect_cursor, &rreq->load_cursor); netfs_read_set_unlock_at(rreq); + stream->buffered =3D added; =20 netfs_read_to_pagecache(rreq); netfs_maybe_bulk_drop_ra_refs(rreq); @@ -453,18 +474,20 @@ EXPORT_SYMBOL(netfs_readahead); */ static int netfs_create_singular_buffer(struct netfs_io_request *rreq, str= uct folio *folio) { + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; struct bvecq *bq; size_t fsize =3D folio_size(folio); =20 if (bvecq_buffer_init(&rreq->load_cursor, rreq->gfp, false) < 0) return -ENOMEM; + bvecq_pos_set(&rreq->collect_cursor, &rreq->load_cursor); =20 bq =3D rreq->load_cursor.bvecq; bvec_set_folio(&bq->bv[0], folio, fsize, 0); bvecq_filled_to(bq, 1); rreq->submitted =3D rreq->start + fsize; rreq->progress_at =3D fsize; - bvecq_pos_set(&rreq->collect_cursor, &rreq->load_cursor); + stream->buffered =3D fsize; return 0; } =20 @@ -474,6 +497,7 @@ static int netfs_create_singular_buffer(struct netfs_io= _request *rreq, struct fo static int netfs_read_gaps(struct file *file, struct folio *folio) { struct netfs_io_request *rreq; + struct netfs_io_stream *stream; struct address_space *mapping =3D folio->mapping; struct netfs_group *group =3D netfs_folio_group(folio); struct netfs_folio *finfo =3D netfs_folio_info(folio); @@ -495,6 +519,7 @@ static int netfs_read_gaps(struct file *file, struct fo= lio *folio) ret =3D PTR_ERR(rreq); goto alloc_error; } + stream =3D &rreq->io_streams[0]; =20 ret =3D netfs_begin_cache_read(rreq, ctx); if (ret =3D=3D -ENOMEM || ret =3D=3D -EINTR || ret =3D=3D -ERESTARTSYS) @@ -549,6 +574,7 @@ static int netfs_read_gaps(struct file *file, struct fo= lio *folio) bvecq_filled_to(bq, slot); =20 rreq->submitted =3D rreq->start + flen; + stream->buffered =3D flen; =20 netfs_read_to_pagecache(rreq); =20 @@ -627,6 +653,7 @@ int netfs_read_folio(struct file *file, struct folio *f= olio) goto discard; =20 netfs_read_to_pagecache(rreq); + ret =3D netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret < 0 ? ret : 0; diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index 058cc6bb7124..8ded0197af91 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -16,6 +16,32 @@ #include #include "internal.h" =20 +int netfs_prepare_unbuffered_read_buffer(struct netfs_io_subrequest *subre= q, + unsigned int max_segs) +{ + struct netfs_io_request *rreq =3D subreq->rreq; + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; + size_t len; + + bvecq_pos_set(&subreq->dispatch_pos, &stream->dispatch_cursor); + bvecq_pos_set(&subreq->content, &stream->dispatch_cursor); + len =3D bvecq_slice(&stream->dispatch_cursor, subreq->len, max_segs, + &subreq->nr_segs); + + if (len < subreq->len) { + subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); + } + + stream->buffered -=3D subreq->len; + stream->issue_from +=3D subreq->len; + rreq->submitted =3D stream->issue_from; + + if (stream->buffered =3D=3D 0) + netfs_all_subreqs_queued(rreq); + return 0; +} + /* * Perform a read to a buffer from the server, slicing up the region to be= read * according to the network rsize. @@ -23,17 +49,15 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) { struct netfs_io_stream *stream =3D &rreq->io_streams[0]; - ssize_t size =3D rreq->len; - uoff_t start =3D rreq->start; - int ret; =20 - bvecq_pos_set(&rreq->dispatch_cursor, &rreq->load_cursor); - bvecq_pos_transfer(&rreq->collect_cursor, &rreq->load_cursor); + bvecq_pos_set(&rreq->collect_cursor, &rreq->load_cursor); + bvecq_pos_transfer(&stream->dispatch_cursor, &rreq->load_cursor); =20 do { struct netfs_io_subrequest *subreq; + int ret; =20 - subreq =3D netfs_alloc_subrequest(rreq); + subreq =3D netfs_alloc_read_subrequest(rreq); if (!subreq) { /* Stash the error in the request if there's not * already an error set. @@ -43,51 +67,32 @@ static void netfs_dispatch_unbuffered_reads(struct netf= s_io_request *rreq) } =20 subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; - subreq->start =3D start; - subreq->len =3D size; - - netfs_queue_read(rreq, subreq); + subreq->start =3D stream->issue_from; + subreq->len =3D stream->buffered; =20 netfs_stat(&netfs_n_rh_download); - if (rreq->netfs_ops->prepare_read) { - ret =3D rreq->netfs_ops->prepare_read(subreq); - if (ret < 0) { - netfs_cancel_read(subreq, ret); - break; - } - } - - bvecq_pos_set(&subreq->dispatch_pos, &rreq->dispatch_cursor); - bvecq_pos_set(&subreq->content, &rreq->dispatch_cursor); - subreq->len =3D bvecq_slice(&rreq->dispatch_cursor, - umin(size, stream->sreq_max_len), - stream->sreq_max_segs, - &subreq->nr_segs); - - size -=3D subreq->len; - start +=3D subreq->len; - rreq->submitted +=3D subreq->len; - if (size <=3D 0) - netfs_all_subreqs_queued(rreq); =20 - iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, - subreq->content.slot, subreq->content.offset, subreq->len); - - rreq->netfs_ops->issue_read(subreq); + ret =3D rreq->netfs_ops->issue_read(subreq); + if (ret < 0) { + stream->buffered -=3D subreq->len; + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); + break; + } =20 if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags)) netfs_wait_for_paused_read(rreq); if (test_bit(NETFS_RREQ_FAILED, &rreq->flags)) break; cond_resched(); - } while (size > 0); + } while (stream->buffered > 0); =20 - if (unlikely(size > 0)) { + if (unlikely(stream->buffered > 0)) { netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } =20 - bvecq_pos_unset(&rreq->dispatch_cursor); + bvecq_pos_unset(&stream->dispatch_cursor); } =20 /* @@ -138,6 +143,7 @@ static ssize_t netfs_unbuffered_read(struct netfs_io_re= quest *rreq, bool sync) ssize_t netfs_unbuffered_read_iter_locked(struct kiocb *iocb, struct iov_i= ter *iter) { struct netfs_io_request *rreq; + struct netfs_io_stream *stream; ssize_t ret; size_t orig_count =3D iov_iter_count(iter); bool sync =3D is_sync_kiocb(iocb); @@ -162,6 +168,8 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kiocb = *iocb, struct iov_iter *i netfs_stat(&netfs_n_rh_dio_read); trace_netfs_read(rreq, rreq->start, rreq->len, netfs_read_trace_dio_read); =20 + stream =3D &rreq->io_streams[0]; + /* If this is an async op, we have to keep track of the destination * buffer for ourselves as the caller's iterator will be trashed when * we return. @@ -177,6 +185,8 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kiocb = *iocb, struct iov_iter *i goto error_put; =20 rreq->len =3D ret; + stream->buffered =3D ret; + stream->issue_from =3D rreq->start; =20 // TODO: Set up bounce buffer if needed =20 diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 5bc3ec261f6c..40a2d34789ad 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -9,6 +9,34 @@ #include #include "internal.h" =20 +/* + * Prepare the buffer for an unbuffered/DIO write. + */ +int netfs_prepare_unbuffered_write_buffer(struct netfs_io_subrequest *subr= eq, + unsigned int max_segs) +{ + struct netfs_io_stream *stream =3D &subreq->rreq->io_streams[subreq->stre= am_nr]; + size_t len; + + bvecq_pos_set(&subreq->dispatch_pos, &stream->dispatch_cursor); + bvecq_pos_set(&subreq->content, &stream->dispatch_cursor); + len =3D bvecq_slice(&stream->dispatch_cursor, subreq->len, max_segs, + &subreq->nr_segs); + + if (len < subreq->len) { + subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); + } + + // TODO: Wait here for completion of prev subreq + + stream->issue_from +=3D subreq->len; + stream->buffered -=3D subreq->len; + if (stream->buffered =3D=3D 0) + netfs_all_subreqs_queued(subreq->rreq); + return 0; +} + /* * Perform the cleanup rituals after an unbuffered write is complete. */ @@ -74,9 +102,9 @@ static void netfs_unbuffered_write_collect(struct netfs_= io_request *wreq, =20 wreq->transferred +=3D subreq->transferred; if (subreq->transferred < subreq->len) { - bvecq_pos_unset(&wreq->dispatch_cursor); - bvecq_pos_transfer(&wreq->dispatch_cursor, &subreq->dispatch_pos); - bvecq_pos_advance(&wreq->dispatch_cursor, subreq->transferred); + bvecq_pos_unset(&stream->dispatch_cursor); + bvecq_pos_transfer(&stream->dispatch_cursor, &subreq->dispatch_pos); + bvecq_pos_advance(&stream->dispatch_cursor, subreq->transferred); } =20 stream->collected_to =3D subreq->start + subreq->transferred; @@ -85,6 +113,7 @@ static void netfs_unbuffered_write_collect(struct netfs_= io_request *wreq, =20 trace_netfs_collect_stream(wreq, stream); trace_netfs_collect_state(wreq, wreq->collected_to, 0); + /* TODO: Progressively clean up wreq->direct_bq */ } =20 /* @@ -103,60 +132,50 @@ static int netfs_unbuffered_write(struct netfs_io_req= uest *wreq) =20 _enter("%llx", wreq->len); =20 - bvecq_pos_set(&wreq->dispatch_cursor, &wreq->load_cursor); - bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor); + stream->issue_from =3D wreq->start; + stream->buffered =3D wreq->len; + bvecq_pos_set(&stream->dispatch_cursor, &wreq->load_cursor); =20 if (wreq->origin =3D=3D NETFS_DIO_WRITE) inode_dio_begin(wreq->inode); =20 - stream->collected_to =3D wreq->start; - for (;;) { bool retry =3D false; =20 if (!subreq) { - netfs_prepare_write(wreq, stream, wreq->start + wreq->transferred); - subreq =3D stream->construct; - stream->construct =3D NULL; - } else { - bvecq_pos_set(&subreq->dispatch_pos, &wreq->dispatch_cursor); + subreq =3D netfs_alloc_write_subreq(wreq, stream); + if (!subreq) { + ret =3D -ENOMEM; + goto failed; + } } =20 - /* Check if (re-)preparation failed. */ - if (unlikely(test_bit(NETFS_SREQ_FAILED, &subreq->flags))) { - netfs_write_subrequest_terminated(subreq, subreq->error); - wreq->error =3D subreq->error; - break; + ret =3D stream->issue_write(subreq); + if (ret < 0) { + /* Ownership of subreq was returned to us. Note that + * ->dispatch_pos may or may not be initialised. + */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + netfs_write_subrequest_terminated(subreq, ret); + list_del_init(&subreq->rreq_link); + netfs_put_subrequest(subreq, netfs_sreq_trace_put_failed); + subreq =3D NULL; + goto failed; } =20 - subreq->len =3D bvecq_slice(&wreq->dispatch_cursor, stream->sreq_max_len, - stream->sreq_max_segs, &subreq->nr_segs); - bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); - - iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, - subreq->content.bvecq, subreq->content.slot, - subreq->content.offset, - subreq->len); - - if (!iov_iter_count(&subreq->io_iter)) - break; - - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); - stream->issue_write(subreq); - - /* Async, need to wait. */ - netfs_wait_for_in_progress_stream(wreq, stream); - - if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { + ret =3D netfs_wait_for_in_progress_subreq(wreq, subreq); + if (ret < 0) { + if (ret !=3D -EAGAIN) { + /* Don't need to lock here as the collection is + * done in this thread. + */ + list_del_init(&subreq->rreq_link); + netfs_put_subrequest(subreq, netfs_sreq_trace_put_failed); + subreq =3D NULL; + goto failed; + } retry =3D true; - } else if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) { - ret =3D subreq->error; - wreq->error =3D ret; - netfs_see_subrequest(subreq, netfs_sreq_trace_see_failed); - subreq =3D NULL; - break; } - ret =3D 0; =20 if (!retry) { netfs_unbuffered_write_collect(wreq, stream, subreq); @@ -171,20 +190,21 @@ static int netfs_unbuffered_write(struct netfs_io_req= uest *wreq) continue; } =20 - /* We need to retry the last subrequest, so first reset the - * iterator, taking into account what, if anything, we managed - * to transfer. + /* We need to retry the last subrequest, so first wind back the + * buffer position. */ subreq->error =3D -EAGAIN; trace_netfs_sreq(subreq, netfs_sreq_trace_retry); =20 bvecq_pos_unset(&subreq->content); - bvecq_pos_unset(&wreq->dispatch_cursor); - bvecq_pos_transfer(&wreq->dispatch_cursor, &subreq->dispatch_pos); + bvecq_pos_unset(&stream->dispatch_cursor); + bvecq_pos_transfer(&stream->dispatch_cursor, &subreq->dispatch_pos); =20 + stream->issue_from -=3D subreq->len - subreq->transferred; + stream->buffered +=3D subreq->len - subreq->transferred; if (subreq->transferred > 0) { - wreq->transferred +=3D subreq->transferred; - bvecq_pos_advance(&wreq->dispatch_cursor, subreq->transferred); + wreq->transferred +=3D subreq->transferred; + bvecq_pos_advance(&stream->dispatch_cursor, subreq->transferred); } =20 if (stream->source =3D=3D NETFS_UPLOAD_TO_SERVER && @@ -193,25 +213,21 @@ static int netfs_unbuffered_write(struct netfs_io_req= uest *wreq) =20 __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); - __clear_bit(NETFS_SREQ_BOUNDARY, &subreq->flags); __clear_bit(NETFS_SREQ_FAILED, &subreq->flags); - subreq->start =3D wreq->start + wreq->transferred; - subreq->len =3D wreq->len - wreq->transferred; + __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + subreq->start =3D stream->issue_from; + subreq->len =3D stream->buffered; subreq->transferred =3D 0; subreq->retry_count +=3D 1; - stream->sreq_max_len =3D UINT_MAX; - stream->sreq_max_segs =3D INT_MAX; =20 netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); =20 - if (stream->prepare_write) - stream->prepare_write(subreq); __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); netfs_stat(&netfs_n_wh_retry_write_subreq); } =20 - bvecq_pos_unset(&wreq->dispatch_cursor); - bvecq_pos_unset(&wreq->load_cursor); +failed: + bvecq_pos_unset(&stream->dispatch_cursor); netfs_unbuffered_write_done(wreq); _leave(" =3D %d", ret); return ret; @@ -255,6 +271,7 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb= *iocb, struct iov_iter * if (IS_ERR(wreq)) return PTR_ERR(wreq); =20 + wreq->len =3D iov_iter_count(iter); wreq->io_streams[0].avail =3D true; trace_netfs_write(wreq, (iocb->ki_flags & IOCB_DIRECT ? netfs_write_trace_dio_write : @@ -265,9 +282,7 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb= *iocb, struct iov_iter * * we have to save the source buffer as the iterator is only * good until we return. In such a case, extract an iterator * to represent as much of the the output buffer as we can - * manage. Note that the extraction might not be able to - * allocate a sufficiently large bvec array and may shorten the - * request. + * manage. Note that the extraction may shorten the request. */ ssize_t n =3D netfs_extract_iter(iter, len, INT_MAX, &wreq->load_cursor.bvecq, 0, wreq->gfp); @@ -282,8 +297,6 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb= *iocb, struct iov_iter * wreq->load_cursor.bvecq->max_slots); } =20 - __set_bit(NETFS_RREQ_USE_IO_ITER, &wreq->flags); - /* Copy the data into the bounce buffer and encrypt it. */ // TODO =20 diff --git a/fs/netfs/fscache_io.c b/fs/netfs/fscache_io.c index 056a2bae5d99..0b85bd33dfe5 100644 --- a/fs/netfs/fscache_io.c +++ b/fs/netfs/fscache_io.c @@ -239,7 +239,7 @@ void __fscache_write_to_cache(struct fscache_cookie *co= okie, fscache_access_io_write) < 0) goto abandon_free; =20 - ret =3D cres->ops->prepare_write(cres, &start, &len, len, i_size, false); + ret =3D cres->ops->prepare_write_old(cres, &start, &len, len, i_size, fal= se); if (ret < 0) goto abandon_end; =20 diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 3aa9c7ef85de..3d345c9d6d5a 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -22,10 +22,9 @@ /* * buffered_read.c */ -int netfs_read_query_cache(struct netfs_io_request *rreq, - struct fscache_occupancy *occ); -void netfs_queue_read(struct netfs_io_request *rreq, - struct netfs_io_subrequest *subreq); +void netfs_read_query_cache(struct netfs_io_request *rreq, + struct fscache_occupancy *occ); +struct netfs_io_subrequest *netfs_alloc_read_subrequest(struct netfs_io_re= quest *rreq); void netfs_cache_read_terminated(void *priv, ssize_t transferred_or_error); int netfs_prefetch_for_write(struct file *file, struct folio *folio, size_t offset, size_t len); @@ -36,6 +35,18 @@ int netfs_prefetch_for_write(struct file *file, struct f= olio *folio, void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode, uoff_t pos, size_t copied); =20 +/* + * direct_read.c + */ +int netfs_prepare_unbuffered_read_buffer(struct netfs_io_subrequest *subre= q, + unsigned int max_segs); + +/* + * direct_write.c + */ +int netfs_prepare_unbuffered_write_buffer(struct netfs_io_subrequest *subr= eq, + unsigned int max_segs); + /* * main.c */ @@ -74,6 +85,8 @@ struct bvecq *netfs_buffer_make_space(struct netfs_io_req= uest *rreq, enum netfs_bvecq_trace trace); void netfs_wake_collector(struct netfs_io_request *rreq); void netfs_subreq_clear_in_progress(struct netfs_io_subrequest *subreq); +int netfs_wait_for_in_progress_subreq(struct netfs_io_request *rreq, + struct netfs_io_subrequest *subreq); void netfs_wait_for_in_progress_stream(struct netfs_io_request *rreq, struct netfs_io_stream *stream); ssize_t netfs_wait_for_read(struct netfs_io_request *rreq); @@ -155,9 +168,18 @@ static inline bool netfs_using_pgpriv2(const struct ne= tfs_io_request *rreq) /* * read_retry.c */ +int netfs_prepare_buffered_read_retry_buffer(struct netfs_io_subrequest *s= ubreq, + unsigned int max_segs); +int netfs_reset_for_read_retry(struct netfs_io_subrequest *subreq); void netfs_retry_reads(struct netfs_io_request *rreq); void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq); =20 +/* + * read_single.c + */ +int netfs_prepare_read_single_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs); + /* * stats.c */ @@ -228,26 +250,19 @@ void netfs_write_collection_worker(struct work_struct= *work); /* * write_issue.c */ +struct netfs_writethrough; struct netfs_io_request *netfs_create_write_req(struct address_space *mapp= ing, struct file *file, uoff_t start, enum netfs_io_origin origin); struct netfs_io_subrequest *netfs_alloc_write_subreq(struct netfs_io_reque= st *wreq, struct netfs_io_stream *stream); -void netfs_prepare_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream, - uoff_t start); -void netfs_reissue_write(struct netfs_io_stream *stream, - struct netfs_io_subrequest *subreq); -void netfs_issue_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream); -size_t netfs_advance_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream, - uoff_t start, size_t len, bool to_eof); =20 /* * write_retry.c */ +int netfs_prepare_write_retry_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs); void netfs_retry_writes(struct netfs_io_request *wreq); =20 /* diff --git a/fs/netfs/main.c b/fs/netfs/main.c index b8da5e85cc67..3736a2a7662d 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -18,6 +18,7 @@ MODULE_DESCRIPTION("Network fs support"); MODULE_AUTHOR("Red Hat, Inc."); MODULE_LICENSE("GPL"); =20 +EXPORT_TRACEPOINT_SYMBOL(netfs_rreq); EXPORT_TRACEPOINT_SYMBOL(netfs_sreq); =20 unsigned netfs_debug; diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index 4fa09560fb4b..a729e51a8ab2 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -250,6 +250,37 @@ void netfs_subreq_clear_in_progress(struct netfs_io_su= brequest *subreq) netfs_wake_collector(rreq); } =20 +/* + * Wait for a subrequest to come to completion. + */ +int netfs_wait_for_in_progress_subreq(struct netfs_io_request *rreq, + struct netfs_io_subrequest *subreq) +{ + if (netfs_check_subreq_in_progress(subreq)) { + DEFINE_WAIT(myself); + + trace_netfs_rreq(rreq, netfs_rreq_trace_wait_quiesce); + for (;;) { + prepare_to_wait(&rreq->waitq, &myself, TASK_UNINTERRUPTIBLE); + + if (!netfs_check_subreq_in_progress(subreq)) + break; + + trace_netfs_sreq(subreq, netfs_sreq_trace_wait_for); + schedule(); + } + + trace_netfs_rreq(rreq, netfs_rreq_trace_waited_quiesce); + finish_wait(&rreq->waitq, &myself); + } + + if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) + return -EAGAIN; + if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) + return subreq->error; + return 0; +} + /* * Wait for all outstanding I/O in a stream to quiesce. */ @@ -380,7 +411,7 @@ static ssize_t netfs_wait_for_in_progress(struct netfs_= io_request *rreq, case NETFS_UNBUFFERED_WRITE: break; default: - if (rreq->submitted < rreq->len) { + if (rreq->transferred < rreq->len) { trace_netfs_failure(rreq, NULL, ret, netfs_fail_short_read); ret =3D -EIO; } diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index a1d0de65484e..3b22748474b9 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -55,8 +55,6 @@ struct netfs_io_request *netfs_alloc_request(struct addre= ss_space *mapping, rreq->i_size =3D i_size_read(inode); rreq->debug_id =3D atomic_inc_return(&debug_ids); rreq->wsize =3D INT_MAX; - rreq->io_streams[0].sreq_max_len =3D ULONG_MAX; - rreq->io_streams[0].sreq_max_segs =3D 0; spin_lock_init(&rreq->lock); init_waitqueue_head(&rreq->waitq); refcount_set(&rreq->ref, 2); @@ -150,8 +148,8 @@ static void netfs_deinit_request(struct netfs_io_reques= t *rreq) if (rreq->cache_resources.ops) rreq->cache_resources.ops->end_operation(&rreq->cache_resources); bvecq_pos_unset(&rreq->load_cursor); - bvecq_pos_unset(&rreq->dispatch_cursor); bvecq_pos_unset(&rreq->collect_cursor); + bvecq_pos_unset(&rreq->retry_cursor); bvecq_put(rreq->spare); while (rreq->writebacks) { struct netfs_writeback *wback =3D rreq->writebacks; diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index e91f7962df04..9e50759591a9 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -36,6 +36,7 @@ static void netfs_clear_unread(struct netfs_io_subrequest= *subreq) =20 if (subreq->start + subreq->transferred >=3D subreq->rreq->i_size) __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); + trace_netfs_rreq(subreq->rreq, netfs_rreq_trace_zero_unread); } =20 /* @@ -407,8 +408,7 @@ static void netfs_collect_read_results(struct netfs_io_= request *rreq) if (test_bit(NETFS_SREQ_COPY_TO_CACHE, &front_flags)) notes |=3D COPY_TO_CACHE; =20 - if (front->start + transferred >=3D unlock_at || - test_bit(NETFS_SREQ_HIT_EOF, &front_flags)) + if (front->start + transferred >=3D unlock_at) netfs_read_unlock_folios(rreq, ¬es); } else { stream->collected_to =3D front->start + transferred; @@ -500,31 +500,6 @@ static void netfs_rreq_assess_dio(struct netfs_io_requ= est *rreq) inode_dio_end(rreq->inode); } =20 -/* - * Do processing after reading a monolithic single object. - */ -static void netfs_rreq_assess_single(struct netfs_io_request *rreq) -{ - struct netfs_io_stream *stream =3D &rreq->io_streams[0]; - - if (!rreq->error && stream->source =3D=3D NETFS_DOWNLOAD_FROM_SERVER && - fscache_resources_valid(&rreq->cache_resources)) { - trace_netfs_rreq(rreq, netfs_rreq_trace_dirty); - netfs_single_mark_inode_dirty(rreq->inode); - } - - if (rreq->iocb) { - rreq->iocb->ki_pos +=3D rreq->transferred; - if (rreq->iocb->ki_complete) { - trace_netfs_rreq(rreq, netfs_rreq_trace_ki_complete); - rreq->iocb->ki_complete( - rreq->iocb, rreq->error ? rreq->error : rreq->transferred); - } - } - if (rreq->netfs_ops->done) - rreq->netfs_ops->done(rreq); -} - /* * Perform the collection of subrequests and folios. * @@ -558,7 +533,7 @@ bool netfs_read_collection(struct netfs_io_request *rre= q) netfs_rreq_assess_dio(rreq); break; case NETFS_READ_SINGLE: - netfs_rreq_assess_single(rreq); + WARN_ON_ONCE(1); break; default: break; @@ -692,6 +667,11 @@ void netfs_read_subreq_terminated(struct netfs_io_subr= equest *subreq) } else if (test_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags)) { __set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); trace_netfs_sreq(subreq, netfs_sreq_trace_partial_read); + } else if (subreq->source =3D=3D NETFS_READ_FROM_CACHE) { + netfs_stat(&netfs_n_rh_read_failed); + __set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); + subreq->error =3D -ENODATA; + trace_netfs_sreq(subreq, netfs_sreq_trace_short); } else { __set_bit(NETFS_SREQ_FAILED, &subreq->flags); subreq->error =3D -ENODATA; @@ -710,6 +690,8 @@ void netfs_read_subreq_terminated(struct netfs_io_subre= quest *subreq) =20 if (unlikely(subreq->error < 0)) { trace_netfs_failure(rreq, subreq, subreq->error, netfs_fail_read); + if (subreq->error =3D=3D -ENOMEM) + set_bit(NETFS_RREQ_SAW_ENOMEM, &rreq->flags); if (subreq->source =3D=3D NETFS_READ_FROM_CACHE) { netfs_stat(&netfs_n_rh_read_failed); __set_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index 14fe90751b5b..3c239682bbaf 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -13,8 +13,37 @@ #include #include "internal.h" =20 +int netfs_prepare_pgpriv2_write_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs) +{ + struct netfs_io_request *creq =3D subreq->rreq; + struct netfs_io_stream *stream =3D &creq->io_streams[1]; + size_t len; + + bvecq_pos_set(&subreq->dispatch_pos, &stream->dispatch_cursor); + bvecq_pos_set(&subreq->content, &stream->dispatch_cursor); + len =3D bvecq_slice(&stream->dispatch_cursor, subreq->len, max_segs, + &subreq->nr_segs); + + if (len < subreq->len) { + subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); + } + + // TODO: Wait here for completion of prev subreq + + stream->issue_from +=3D subreq->len; + stream->buffered -=3D subreq->len; + if (stream->buffered =3D=3D 0) + netfs_all_subreqs_queued(creq); + return 0; +} + /* - * [DEPRECATED] Copy a folio to the cache with PG_private_2 set. + * [DEPRECATED] Copy a folio to the cache with PG_private_2 set. Note tha= t the + * folio won't necessarily be contiguous with the previous one as there mi= ght + * be a mixture of folios read from the cache and downloaded from the serv= er + * (or just zeroed). */ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct= folio *folio) { @@ -24,7 +53,6 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_requ= est *creq, struct folio size_t dio_size =3D PAGE_SIZE; size_t fsize =3D folio_size(folio), flen =3D fsize; uoff_t fpos =3D folio_pos(folio), i_size; - bool to_eof =3D false; =20 _enter(""); =20 @@ -44,12 +72,8 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_req= uest *creq, struct folio if (fpos + fsize > creq->i_size) creq->i_size =3D i_size; =20 - if (flen > i_size - fpos) { + if (flen > i_size - fpos) flen =3D i_size - fpos; - to_eof =3D true; - } else if (flen =3D=3D i_size - fpos) { - to_eof =3D true; - } =20 flen =3D round_up(flen, dio_size); =20 @@ -63,7 +87,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_requ= est *creq, struct folio */ queue =3D creq->load_cursor.bvecq; if (bvecq_is_full(queue) || - (fpos !=3D creq->last_end && creq->last_end > 0 && queue->nr_slots > = 0)) { + (fpos !=3D cache->last_end && cache->last_end > 0 && queue->nr_slots = > 0)) { bvecq_buffer_append(&creq->load_cursor, creq->spare); creq->spare =3D NULL; =20 @@ -78,38 +102,10 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_r= equest *creq, struct folio bvecq_filled_to(queue, slot); creq->load_cursor.slot =3D slot; creq->load_cursor.offset =3D 0; - creq->last_end =3D fpos + flen; - - bvecq_pos_nudge(&creq->dispatch_cursor); -=09 - cache->submit_off =3D 0; - cache->submit_len =3D flen; + cache->last_end =3D fpos + flen; + trace_netfs_wback(creq, folio, 0); =20 - /* Attach the folio to one or more subrequests. For a big folio, we - * could end up with thousands of subrequests if the wsize is small - - * but we might need to wait during the creation of subrequests for - * network resources (eg. SMB credits). - */ - do { - ssize_t part; - - creq->dispatch_cursor.offset =3D cache->submit_off; - - atomic64_set(&cache->issued_to, fpos + cache->submit_off); - part =3D netfs_advance_write(creq, cache, fpos + cache->submit_off, - cache->submit_len, to_eof); - cache->submit_off +=3D part; - if (part > cache->submit_len) - cache->submit_len =3D 0; - else - cache->submit_len -=3D part; - } while (cache->submit_len > 0); - - bvecq_pos_step(&creq->dispatch_cursor); - atomic64_set(&cache->issued_to, fpos + fsize); - - if (flen < fsize) - netfs_issue_write(creq, cache); + cache->buffered +=3D flen; } =20 /* @@ -119,6 +115,7 @@ static struct netfs_io_request *netfs_pgpriv2_begin_cop= y_to_cache( struct netfs_io_request *rreq, struct folio *folio) { struct netfs_io_request *creq; + struct netfs_io_stream *cache; =20 if (!fscache_resources_valid(&rreq->cache_resources)) goto cancel; @@ -128,13 +125,14 @@ static struct netfs_io_request *netfs_pgpriv2_begin_c= opy_to_cache( if (IS_ERR(creq)) goto cancel; =20 - if (!creq->io_streams[1].avail) + cache =3D &creq->io_streams[1]; + if (!cache->avail) goto cancel_put; =20 if (bvecq_buffer_init(&creq->load_cursor, creq->gfp, false) < 0) goto cancel_put; - bvecq_pos_set(&creq->dispatch_cursor, &creq->load_cursor); - bvecq_pos_set(&creq->collect_cursor, &creq->dispatch_cursor); + bvecq_pos_set(&cache->dispatch_cursor, &creq->load_cursor); + bvecq_pos_set(&creq->collect_cursor, &creq->load_cursor); =20 __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &creq->flags); trace_netfs_copy2cache(rreq, creq); @@ -177,24 +175,61 @@ void netfs_pgpriv2_copy_to_cache(struct netfs_io_requ= est *rreq, struct folio *fo netfs_pgpriv2_copy_folio(creq, folio); } =20 +/* + * Issue all pending writes on the cache stream. + */ +static void netfs_pgpriv2_issue_stream(struct netfs_io_request *wreq, + struct netfs_io_stream *stream) +{ + atomic64_set_release(&stream->issued_to, wreq->start); + + do { + struct netfs_io_subrequest *subreq; + int ret; + + subreq =3D netfs_alloc_write_subreq(wreq, stream); + if (!subreq) + break; + + ret =3D stream->issue_write(subreq); + if (ret < 0) { + /* Ownership of subreq was returned to us. Punt the + * error to the collector and stop the issuance of new + * subreqs. + */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + stream->buffered -=3D subreq->len; + netfs_write_subrequest_terminated(subreq, ret); + break; + } + /* We no longer own subreq. */ + + if (test_bit(NETFS_RREQ_SAW_ENOMEM, &wreq->flags)) + break; + + } while (stream->buffered > 0); + + netfs_all_subreqs_queued(wreq); +} + /* * [DEPRECATED] End writing to the cache, flushing out any outstanding wri= tes. */ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq) { struct netfs_io_request *creq =3D rreq->copy_to_cache; + struct netfs_io_stream *stream =3D &creq->io_streams[1]; =20 if (IS_ERR_OR_NULL(creq)) return; =20 - netfs_issue_write(creq, &creq->io_streams[1]); - netfs_all_subreqs_queued(creq); + netfs_pgpriv2_issue_stream(creq, stream); trace_netfs_rreq(rreq, netfs_rreq_trace_end_copy_to_cache); if (list_empty_careful(&creq->io_streams[1].subrequests)) netfs_wake_collector(creq); =20 netfs_put_request(creq, netfs_rreq_trace_put_return); - creq->copy_to_cache =3D NULL; + rreq->copy_to_cache =3D NULL; } =20 /* diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 733d9ff452c7..51ee780b11ed 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -9,20 +9,55 @@ #include #include "internal.h" =20 -static void netfs_reissue_read(struct netfs_io_request *rreq, - struct netfs_io_subrequest *subreq) +/* + * Prepare the I/O buffer on a buffered read subrequest for the filesystem= to + * use as a bvec queue. + */ +int netfs_prepare_buffered_read_retry_buffer(struct netfs_io_subrequest *s= ubreq, + unsigned int max_segs) { - bvecq_pos_unset(&subreq->content); + struct netfs_io_request *rreq =3D subreq->rreq; + size_t len; + + bvecq_pos_set(&subreq->dispatch_pos, &rreq->retry_cursor); bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); - iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, - subreq->content.slot, subreq->content.offset, subreq->len); - iov_iter_advance(&subreq->io_iter, subreq->transferred); + len =3D bvecq_slice(&rreq->retry_cursor, subreq->len, max_segs, + &subreq->nr_segs); + if (len < subreq->len) { + subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); + } + rreq->retry_buffered -=3D subreq->len; + rreq->retry_start +=3D subreq->len; + return 0; +} =20 - subreq->error =3D 0; +/* + * Reset the state of the subrequest and discard any buffering so that we = can + * retry (where this may include sending it to the server instead of the + * cache). + */ +int netfs_reset_for_read_retry(struct netfs_io_subrequest *subreq) +{ + trace_netfs_sreq(subreq, netfs_sreq_trace_retry); + + if (subreq->retry_count > 3) { + trace_netfs_sreq(subreq, netfs_sreq_trace_too_many_retries); + return subreq->error; + } + + subreq->retry_count++; __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); + __clear_bit(NETFS_SREQ_FAILED, &subreq->flags); __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); - netfs_stat(&netfs_n_rh_retry_read_subreq); - subreq->rreq->netfs_ops->issue_read(subreq); + bvecq_pos_unset(&subreq->content); + bvecq_pos_unset(&subreq->dispatch_pos); + subreq->error =3D 0; + subreq->transferred =3D 0; + netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); + netfs_stat(&netfs_n_wh_retry_write_subreq); + return 0; } =20 /* @@ -33,8 +68,8 @@ static void netfs_retry_read_subrequests(struct netfs_io_= request *rreq) { struct netfs_io_subrequest *subreq; struct netfs_io_stream *stream =3D &rreq->io_streams[0]; - struct bvecq_pos dispatch_cursor =3D {}; struct list_head *next; + int ret; =20 _enter("R=3D%x", rreq->debug_id); =20 @@ -44,46 +79,19 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) if (rreq->netfs_ops->retry_request) rreq->netfs_ops->retry_request(rreq, NULL); =20 - /* If there's no renegotiation to do, just resend each retryable subreq - * up to the first permanently failed one. - */ - if (!rreq->netfs_ops->prepare_read && - !rreq->cache_resources.ops) { - list_for_each_entry(subreq, &stream->subrequests, rreq_link) { - if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) - break; - if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { - subreq->retry_count++; - netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); - netfs_reissue_read(rreq, subreq); - } - } - return; - } + /* Read pointer to subreq before reading subreq state. */ + next =3D smp_load_acquire(&stream->subrequests.next); =20 - /* Okay, we need to renegotiate all the download requests and flip any - * failed cache reads over to being download requests and negotiate - * those also. All fully successful subreqs have been removed from the - * list and any spare data from those has been donated. - * - * What we do is decant the list and rebuild it one subreq at a time so - * that we don't end up with donations jumping over a gap we're busy - * populating with smaller subrequests. In the event that the subreq - * we just launched finishes before we insert the next subreq, it'll - * fill in rreq->prev_donated instead. - * - * Note: Alternatively, we could split the tail subrequest right before - * we reissue it and fix up the donations under lock. + /* Renegotiate all the download requests and flip any failed cache + * reads over to being download requests and negotiate those also. */ - next =3D stream->subrequests.next; - do { struct netfs_io_subrequest *from, *to, *tmp; - uoff_t start, len; - size_t part; - bool boundary =3D false, subreq_superfluous =3D false; + uoff_t start; + size_t len; + bool subreq_superfluous =3D false; =20 - bvecq_pos_unset(&dispatch_cursor); + bvecq_pos_unset(&rreq->retry_cursor); =20 /* Go through the subreqs and find the next span of contiguous * buffer that we then rejig (cifs, for example, needs the @@ -98,8 +106,7 @@ static void netfs_retry_read_subrequests(struct netfs_io= _request *rreq) rreq->debug_id, from->debug_index, from->start, from->transferred, from->len); =20 - if (test_bit(NETFS_SREQ_FAILED, &from->flags) || - !test_bit(NETFS_SREQ_NEED_RETRY, &from->flags)) { + if (!test_bit(NETFS_SREQ_NEED_RETRY, &from->flags)) { subreq =3D from; goto abandon; } @@ -113,20 +120,21 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) subreq =3D list_entry(next, struct netfs_io_subrequest, rreq_link); if (subreq->start !=3D start + len || subreq->transferred > 0 || - test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) || !test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) break; to =3D subreq; len +=3D to->len; } =20 - _debug(" - range: %llx-%llx %llx", start, start + len - 1, len); + _debug(" - range: %llx-%llx %zx", start, start + len - 1, len); =20 /* Determine the set of buffers we're going to use. Each - * subreq gets a subset of a single overall contiguous buffer. + * subreq takes a subset of a single overall contiguous buffer. */ - bvecq_pos_transfer(&dispatch_cursor, &from->dispatch_pos); - bvecq_pos_advance(&dispatch_cursor, from->transferred); + bvecq_pos_transfer(&rreq->retry_cursor, &from->dispatch_pos); + bvecq_pos_advance(&rreq->retry_cursor, from->transferred); + rreq->retry_start =3D start; + rreq->retry_buffered =3D len; from->transferred =3D 0; =20 /* Work through the sublist. The chain of buffers we're going @@ -135,51 +143,30 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) */ subreq =3D from; list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) { - if (!len) { + if (rreq->retry_buffered =3D=3D 0) { subreq_superfluous =3D true; break; } subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; - subreq->start =3D start; - subreq->len =3D len; - __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); - __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); - subreq->retry_count++; - subreq->transferred =3D 0; + subreq->start =3D rreq->retry_start; + subreq->len =3D rreq->retry_buffered; =20 - bvecq_pos_unset(&subreq->content); - bvecq_pos_unset(&subreq->dispatch_pos); - bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); - - trace_netfs_sreq(subreq, netfs_sreq_trace_retry); - - /* Renegotiate max_len (rsize) */ - stream->sreq_max_len =3D len; - stream->sreq_max_segs =3D INT_MAX; - if (rreq->netfs_ops->prepare_read && - rreq->netfs_ops->prepare_read(subreq) < 0) { - trace_netfs_sreq(subreq, netfs_sreq_trace_reprep_failed); + ret =3D netfs_reset_for_read_retry(subreq); + if (ret < 0) { __set_bit(NETFS_SREQ_FAILED, &subreq->flags); + rreq->error =3D ret; goto abandon; } =20 - part =3D bvecq_slice(&dispatch_cursor, - umin(len, stream->sreq_max_len), - stream->sreq_max_segs, - &subreq->nr_segs); - subreq->len =3D part; - - len -=3D part; - start +=3D part; - if (!len) { - if (boundary) - __set_bit(NETFS_SREQ_BOUNDARY, &subreq->flags); - } else { - __clear_bit(NETFS_SREQ_BOUNDARY, &subreq->flags); + netfs_stat(&netfs_n_rh_download); + ret =3D rreq->netfs_ops->issue_read(subreq); + if (ret < 0) { + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); + goto abandon_after; } - - netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); - netfs_reissue_read(rreq, subreq); + if (test_bit(NETFS_RREQ_SAW_ENOMEM, &rreq->flags)) + goto abandon_after; if (subreq =3D=3D to) { subreq_superfluous =3D false; break; @@ -189,7 +176,7 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) /* If we managed to use fewer subreqs, we can discard the * excess; if we used the same number, then we're done. */ - if (!len) { + if (rreq->retry_buffered =3D=3D 0) { if (!subreq_superfluous) continue; list_for_each_entry_safe_from(subreq, tmp, @@ -207,7 +194,8 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) } =20 /* We ran out of subrequests, so we need to allocate some more - * and insert them after. + * and insert them after. They must start with being marked + * for retry to switch to the retry cursor. */ do { subreq =3D netfs_alloc_subrequest(rreq); @@ -216,8 +204,8 @@ static void netfs_retry_read_subrequests(struct netfs_i= o_request *rreq) goto abandon_after; } subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; - subreq->start =3D start; - subreq->len =3D len; + subreq->start =3D rreq->retry_start; + subreq->len =3D rreq->retry_buffered; subreq->stream_nr =3D stream->stream_nr; subreq->retry_count =3D 1; =20 @@ -225,43 +213,32 @@ static void netfs_retry_read_subrequests(struct netfs= _io_request *rreq) refcount_read(&subreq->ref), netfs_sreq_trace_new); =20 + __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); + spin_lock(&rreq->lock); + /* Write IN_PROGRESS before pointer to new subreq */ + smp_wmb(); list_add(&subreq->rreq_link, &to->rreq_link); spin_unlock(&rreq->lock); to =3D subreq; trace_netfs_sreq(subreq, netfs_sreq_trace_retry); =20 - stream->sreq_max_len =3D umin(len, rreq->rsize); - stream->sreq_max_segs =3D INT_MAX; - netfs_stat(&netfs_n_rh_download); - if (rreq->netfs_ops->prepare_read(subreq) < 0) { - trace_netfs_sreq(subreq, netfs_sreq_trace_reprep_failed); - __set_bit(NETFS_SREQ_FAILED, &subreq->flags); - goto abandon; - } - - bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); - part =3D bvecq_slice(&dispatch_cursor, - umin(len, stream->sreq_max_len), - stream->sreq_max_segs, - &subreq->nr_segs); - subreq->len =3D part; - - len -=3D part; - start +=3D part; - if (!len && boundary) { - __set_bit(NETFS_SREQ_BOUNDARY, &to->flags); - boundary =3D false; + ret =3D rreq->netfs_ops->issue_read(subreq); + if (ret < 0) { + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); + goto abandon_after; } + if (test_bit(NETFS_RREQ_SAW_ENOMEM, &rreq->flags)) + goto abandon_after; =20 - netfs_reissue_read(rreq, subreq); - } while (len); + } while (rreq->retry_buffered > 0); =20 } while (!list_is_head(next, &stream->subrequests)); =20 out: - bvecq_pos_unset(&dispatch_cursor); + bvecq_pos_unset(&rreq->retry_cursor); return; =20 /* If we hit an error, fail all remaining incomplete subrequests */ @@ -334,6 +311,7 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io_= request *rreq) } trace_netfs_folio(folio, netfs_folio_trace_abandon); folio_unlock(folio); + p->bv[slot].bv_page =3D NULL; } } } diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index 9fd07dbb08b5..4d387db98668 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -16,6 +16,22 @@ #include #include "internal.h" =20 +int netfs_prepare_read_single_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs) +{ + struct netfs_io_request *rreq =3D subreq->rreq; + struct netfs_io_stream *stream =3D &rreq->io_streams[0]; + + bvecq_pos_set(&subreq->dispatch_pos, &rreq->load_cursor); + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + + stream->buffered =3D 0; + stream->issue_from +=3D subreq->len; + rreq->submitted =3D stream->issue_from; + netfs_all_subreqs_queued(rreq); + return 0; +} + /** * netfs_single_mark_inode_dirty - Mark a single, monolithic object inode = dirty * @inode: The inode to mark @@ -58,17 +74,6 @@ static int netfs_single_begin_cache_read(struct netfs_io= _request *rreq, struct n return fscache_begin_read_operation(&rreq->cache_resources, netfs_i_cooki= e(ctx)); } =20 -static void netfs_single_read_cache(struct netfs_io_request *rreq, - struct netfs_io_subrequest *subreq) -{ - struct netfs_cache_resources *cres =3D &rreq->cache_resources; - - _enter("R=3D%08x[%x]", rreq->debug_id, subreq->debug_index); - netfs_stat(&netfs_n_rh_read); - cres->ops->read(cres, subreq->start, &subreq->io_iter, NETFS_READ_HOLE_FA= IL, - netfs_cache_read_terminated, subreq); -} - /* * Perform a read to a buffer from the cache or the server. Only a single * subreq is permitted as the object must be fetched in a single transacti= on. @@ -84,70 +89,84 @@ static int netfs_single_dispatch_read(struct netfs_io_r= equest *rreq) .cached_to[1] =3D ULLONG_MAX, }; struct netfs_io_subrequest *subreq; - int ret =3D 0; + int ret; + + netfs_read_query_cache(rreq, &occ); =20 - subreq =3D netfs_alloc_subrequest(rreq); + subreq =3D netfs_alloc_read_subrequest(rreq); if (!subreq) return -ENOMEM; =20 - subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; subreq->start =3D 0; subreq->len =3D rreq->len; =20 - bvecq_pos_set(&subreq->dispatch_pos, &rreq->dispatch_cursor); - bvecq_pos_set(&subreq->content, &rreq->dispatch_cursor); - - iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->content.bvecq, - subreq->content.slot, subreq->content.offset, subreq->len); - - netfs_queue_read(rreq, subreq); + trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); =20 /* Try to use the cache if the cache content matches the size of the * remote file. */ - netfs_read_query_cache(rreq, &occ); if (occ.cached_from[0] =3D=3D 0 && - occ.cached_to[0] >=3D rreq->len) - subreq->source =3D NETFS_READ_FROM_CACHE; + occ.cached_to[0] >=3D rreq->len) { + struct netfs_cache_resources *cres =3D &rreq->cache_resources; =20 - switch (subreq->source) { - case NETFS_DOWNLOAD_FROM_SERVER: - netfs_stat(&netfs_n_rh_download); - if (rreq->netfs_ops->prepare_read) { - ret =3D rreq->netfs_ops->prepare_read(subreq); - if (ret < 0) - goto cancel; + subreq->source =3D NETFS_READ_FROM_CACHE; + netfs_stat(&netfs_n_rh_read); + ret =3D cres->ops->issue_read(subreq); + if (ret < 0) { + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); } =20 - netfs_all_subreqs_queued(rreq); - rreq->netfs_ops->issue_read(subreq); - rreq->submitted +=3D subreq->len; - break; - case NETFS_READ_FROM_CACHE: - if (rreq->cache_resources.ops->prepare_read) { - ret =3D rreq->cache_resources.ops->prepare_read(subreq); - if (ret < 0) - goto cancel; + ret =3D netfs_wait_for_in_progress_subreq(rreq, subreq); + if (ret =3D=3D 0) + goto success; + if (ret =3D=3D -ENOMEM) + goto cancel; + + /* Didn't manage to retrieve from the cache, so toss it to the + * server instead. + */ + if (netfs_reset_for_read_retry(subreq) < 0) + goto cancel; + } + + __set_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags); + + /* Try to send it to the cache. */ + for (;;) { + subreq->source =3D NETFS_DOWNLOAD_FROM_SERVER; + netfs_stat(&netfs_n_rh_download); + ret =3D rreq->netfs_ops->issue_read(subreq); + if (ret < 0) { + subreq->error =3D ret; + netfs_read_subreq_terminated(subreq); } =20 - netfs_all_subreqs_queued(rreq); - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); - netfs_single_read_cache(rreq, subreq); - rreq->submitted +=3D subreq->len; - ret =3D 0; - break; - default: - pr_warn("Unexpected single-read source %u\n", subreq->source); - WARN_ON_ONCE(true); - ret =3D -EIO; - goto cancel; + ret =3D netfs_wait_for_in_progress_subreq(rreq, subreq); + if (ret =3D=3D 0) + goto success; + if (ret =3D=3D -ENOMEM) + goto cancel; + if (ret !=3D -EAGAIN) + goto failed; + if (netfs_reset_for_read_retry(subreq) < 0) + goto cancel; } =20 - return ret; +success: + rreq->transferred =3D subreq->transferred; + list_del_init(&subreq->rreq_link); + netfs_put_subrequest(subreq, netfs_sreq_trace_put_consumed); + return 0; cancel: - netfs_cancel_read(subreq, ret); - netfs_all_subreqs_queued(rreq); - netfs_wake_collector(rreq); + rreq->error =3D ret; + list_del_init(&subreq->rreq_link); + netfs_put_subrequest(subreq, netfs_sreq_trace_put_cancel); + return ret; +failed: + rreq->error =3D ret; + list_del_init(&subreq->rreq_link); + netfs_put_subrequest(subreq, netfs_sreq_trace_put_failed); return ret; } =20 @@ -179,7 +198,7 @@ ssize_t netfs_read_single(struct inode *inode, struct f= ile *file, struct iov_ite if (IS_ERR(rreq)) return PTR_ERR(rreq); =20 - ret =3D netfs_extract_iter(iter, rreq->len, INT_MAX, &rreq->dispatch_curs= or.bvecq, + ret =3D netfs_extract_iter(iter, rreq->len, INT_MAX, &rreq->load_cursor.b= vecq, 0, rreq->gfp); if (ret < 0) goto cleanup_free; @@ -197,9 +216,29 @@ ssize_t netfs_read_single(struct inode *inode, struct = file *file, struct iov_ite netfs_stat(&netfs_n_rh_read_single); trace_netfs_read(rreq, 0, rreq->len, netfs_read_trace_read_single); =20 - netfs_single_dispatch_read(rreq); + ret =3D netfs_single_dispatch_read(rreq); + + trace_netfs_rreq(rreq, netfs_rreq_trace_complete); + if (ret =3D=3D 0) { + task_io_account_read(rreq->transferred); + + if (test_bit(NETFS_RREQ_FOLIO_COPY_TO_CACHE, &rreq->flags) && + fscache_resources_valid(&rreq->cache_resources)) { + trace_netfs_rreq(rreq, netfs_rreq_trace_dirty); + netfs_single_mark_inode_dirty(rreq->inode); + } + ret =3D rreq->transferred; + } + + if (rreq->netfs_ops->done) + rreq->netfs_ops->done(rreq); + + netfs_wake_rreq_flag(rreq, NETFS_RREQ_IN_PROGRESS, netfs_rreq_trace_wake_= ip); + /* As we cleared NETFS_RREQ_IN_PROGRESS, we acquired its ref. */ + netfs_put_request(rreq, netfs_rreq_trace_put_work_ip); + + trace_netfs_rreq(rreq, netfs_rreq_trace_done); =20 - ret =3D netfs_wait_for_read(rreq); netfs_put_request(rreq, netfs_rreq_trace_put_return); return ret; =20 diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 0c71f8c151c2..e0d14d77b751 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -68,6 +68,9 @@ struct netfs_wb_params { struct netfs_write_estimate estimates[NR_IO_STREAMS]; }; =20 +static int netfs_prepare_write_single_buffer(struct netfs_io_subrequest *s= ubreq, + unsigned int max_segs); + /* * Kill all dirty folios in the event of an unrecoverable error, starting = with * a locked folio we've already obtained from writeback_iter(). @@ -148,7 +151,6 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, wreq->io_streams[0].source =3D NETFS_UPLOAD_TO_SERVER; wreq->io_streams[0].applicable =3D NOTE_UPLOAD; wreq->io_streams[0].estimate_write =3D ictx->ops->estimate_write; - wreq->io_streams[0].prepare_write =3D ictx->ops->prepare_write; wreq->io_streams[0].issue_write =3D ictx->ops->issue_write; wreq->io_streams[0].collected_to =3D start; wreq->io_streams[0].transferred =3D 0; @@ -162,7 +164,6 @@ struct netfs_io_request *netfs_create_write_req(struct = address_space *mapping, wreq->io_streams[1].avail =3D true; wreq->io_streams[1].active =3D true; wreq->io_streams[1].estimate_write =3D wreq->cache_resources.ops->estima= te_write; - wreq->io_streams[1].prepare_write =3D wreq->cache_resources.ops->prepare= _write_subreq; wreq->io_streams[1].issue_write =3D wreq->cache_resources.ops->issue_wri= te; wreq->io_streams[1].alignment =3D wreq->cache_resources.dio_size; } @@ -170,19 +171,6 @@ struct netfs_io_request *netfs_create_write_req(struct= address_space *mapping, return wreq; } =20 -/** - * netfs_prepare_write_failed - Note write preparation failed - * @subreq: The subrequest to mark - * - * Mark a subrequest to note that preparation for write failed. - */ -void netfs_prepare_write_failed(struct netfs_io_subrequest *subreq) -{ - __set_bit(NETFS_SREQ_FAILED, &subreq->flags); - trace_netfs_sreq(subreq, netfs_sreq_trace_prep_failed); -} -EXPORT_SYMBOL(netfs_prepare_write_failed); - /* * Allocate and prepare a write subrequest. Will only return NULL if not * performing writeback; if performing writeback, mempools may be accessed= and @@ -235,6 +223,7 @@ struct netfs_io_subrequest *netfs_alloc_write_subreq(st= ruct netfs_io_request *wr return subreq; } =20 +#if 0 // TODO: Remove old stuff /* * Prepare a write subrequest. We need to allocate a new subrequest * if we don't have one. @@ -295,6 +284,7 @@ void netfs_prepare_write(struct netfs_io_request *wreq, =20 stream->construct =3D subreq; } +#endif =20 /* * Advance the state of the amount of data buffered on a stream. @@ -341,6 +331,44 @@ static int netfs_prepare_buffered_write_buffer(struct = netfs_io_subrequest *subre return 0; } =20 +/** + * netfs_prepare_write_buffer - Get the buffer for a subrequest + * @subreq: The subrequest to get the buffer for + * @max_segs: Maximum number of segments in buffer (or INT_MAX) + * + * Extract a slice of buffer from the stream and attach it to the subreque= st as + * a bio_vec queue. The maximum amount of data attached is set by + * @subreq->len, but this may be shortened if @max_segs would be exceeded. + */ +int netfs_prepare_write_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs) +{ + struct netfs_io_request *rreq =3D subreq->rreq; + + switch (rreq->origin) { + case NETFS_WRITEBACK: + if (test_bit(NETFS_RREQ_RETRYING, &rreq->flags)) + return netfs_prepare_write_retry_buffer(subreq, max_segs); + return netfs_prepare_buffered_write_buffer(subreq, max_segs); + + case NETFS_UNBUFFERED_WRITE: + case NETFS_DIO_WRITE: + return netfs_prepare_unbuffered_write_buffer(subreq, max_segs); + + case NETFS_WRITEBACK_SINGLE: + return netfs_prepare_write_single_buffer(subreq, max_segs); + + case NETFS_PGPRIV2_COPY_TO_CACHE: + return netfs_prepare_pgpriv2_write_buffer(subreq, max_segs); + + default: + WARN_ON_ONCE(1); + return -EIO; + } +} +EXPORT_SYMBOL(netfs_prepare_write_buffer); + +#if 0 // TODO: Remove old stuff /* * Set the I/O iterator for the filesystem/cache to use and dispatch the I= /O * operation. The operation may be asynchronous and should call @@ -395,8 +423,8 @@ void netfs_reissue_write(struct netfs_io_stream *stream, netfs_do_issue_write(stream, subreq); } =20 -void netfs_issue_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream) +static void netfs_issue_write(struct netfs_io_request *wreq, + struct netfs_io_stream *stream) { struct netfs_io_subrequest *subreq =3D stream->construct; =20 @@ -491,6 +519,7 @@ static int netfs_prep_and_issue_subreq(struct netfs_io_= request *wreq, stream->issue_write(subreq); return 0; } +#endif =20 /* * Issue writes for a stream. @@ -523,7 +552,7 @@ static void netfs_writeback_flush(struct netfs_io_reque= st *wreq, return; } =20 - ret =3D netfs_prep_and_issue_subreq(wreq, stream, subreq); + ret =3D stream->issue_write(subreq); if (ret < 0) { /* Ownership of subreq was returned to us. */ trace_netfs_sreq(subreq, netfs_sreq_trace_fail); @@ -649,6 +678,7 @@ static void netfs_writeback_add_folio_to_stream(struct = netfs_io_request *wreq, wreq->load_cursor.slot--; =20 trace_netfs_bv_slot(wreq->load_cursor.bvecq, wreq->load_cursor.slot - 1); + trace_netfs_wback(wreq, folio, params->notes); =20 for (int s =3D 0; s < NR_IO_STREAMS; s++) { struct netfs_io_stream *stream =3D &wreq->io_streams[s]; @@ -846,31 +876,6 @@ static void netfs_writeback_folio(struct netfs_io_requ= est *wreq, goto out; } =20 -#if 0 // TODO: Remove -/* - * End the issuing of writes, letting the collector know we're done. - */ -static void netfs_end_issue_write(struct netfs_io_request *wreq) -{ - bool needs_poke =3D true; - - netfs_all_subreqs_queued(wreq); - - for (int s =3D 0; s < NR_IO_STREAMS; s++) { - struct netfs_io_stream *stream =3D &wreq->io_streams[s]; - - if (!stream->active) - continue; - if (!list_empty(&stream->subrequests)) - needs_poke =3D false; - netfs_issue_write(wreq, stream); - } - - if (needs_poke) - netfs_wake_collector(wreq); -} -#endif - /* * Write some of the pending data back to the server */ @@ -957,6 +962,25 @@ int netfs_writepages(struct address_space *mapping, } EXPORT_SYMBOL(netfs_writepages); =20 +/* + * Prepare a buffer for a single monolithic write. + */ +static int netfs_prepare_write_single_buffer(struct netfs_io_subrequest *s= ubreq, + unsigned int max_segs) +{ + struct netfs_io_request *wreq =3D subreq->rreq; + struct netfs_io_stream *stream =3D &wreq->io_streams[subreq->stream_nr]; + + bvecq_pos_set(&subreq->dispatch_pos, &stream->dispatch_cursor); + bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); + + stream->buffered =3D 0; + stream->issue_from =3D subreq->len; + wreq->submitted =3D subreq->len; + netfs_all_subreqs_queued(wreq); + return 0; +} + /** * netfs_writeback_single - Write back a monolithic payload * @mapping: The mapping to write from @@ -968,6 +992,11 @@ EXPORT_SYMBOL(netfs_writepages); * cache. There's a maximum of one subrequest per stream. The buffer sho= uld * be rounded out sufficiently that it can accommodate cache DIO rounding. * + * This is normally only used to write to the cache (for AFS directories a= nd + * symlinks); it doesn't normally write to the server as well. The filesy= stem + * can override that by setting NETFS_RREQ_UPLOAD_TO_SERVER when the reque= st is + * initialised. + * * Return: 0 if successful; 1 if skipped due to lock conflict and WB_SYNC_= NONE; * or a negative error code. * the cache. There's a maximum of one subrequest per stream. @@ -981,6 +1010,11 @@ int netfs_writeback_single(struct address_space *mapp= ing, size_t clen; int ret; =20 + _enter("%zx,%zx", iov_iter_count(iter), len); + + if (!len) + return 0; + if (!netfs_wb_begin(ictx, wbc->sync_mode =3D=3D WB_SYNC_NONE)) { /* The VFS will have undirtied the inode. */ netfs_single_mark_inode_dirty(&ictx->inode); @@ -992,9 +1026,9 @@ int netfs_writeback_single(struct address_space *mappi= ng, ret =3D PTR_ERR(wreq); goto couldnt_start; } + wreq->len =3D len; clen =3D len; - if (wreq->cache_resources.dio_size > 1) { clen =3D round_up(len, wreq->cache_resources.dio_size); if (clen > iov_iter_count(iter)) { @@ -1003,7 +1037,7 @@ int netfs_writeback_single(struct address_space *mapp= ing, } } =20 - ret =3D netfs_extract_iter(iter, clen, INT_MAX, &wreq->dispatch_cursor.bv= ecq, + ret =3D netfs_extract_iter(iter, clen, INT_MAX, &wreq->load_cursor.bvecq, 0, wreq->gfp); if (ret < 0) goto cleanup_free; @@ -1012,12 +1046,14 @@ int netfs_writeback_single(struct address_space *ma= pping, goto cleanup_free; } =20 - bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor); - __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); trace_netfs_write(wreq, netfs_write_trace_writeback_single); netfs_stat(&netfs_n_wh_writepages); =20 + /* This normally just writes to the cache; if the filesystem wants to + * write to the server too, it must set UPLOAD_TO_SERVER in + * ->init_request(). + */ if (test_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags)) wreq->netfs_ops->begin_writeback(wreq); =20 @@ -1028,14 +1064,29 @@ int netfs_writeback_single(struct address_space *ma= pping, if (!stream->avail) continue; =20 - netfs_prepare_write(wreq, stream, 0); - - subreq =3D stream->construct; - subreq->len =3D wreq->len; + stream->issue_from =3D 0; + stream->buffered =3D len; if (stream->source =3D=3D NETFS_WRITE_TO_CACHE) - subreq->len =3D clen; + stream->buffered =3D clen; =20 - netfs_issue_write(wreq, stream); + subreq =3D netfs_alloc_write_subreq(wreq, stream); + if (!subreq) { + ret =3D -ENOMEM; + break; + } + + bvecq_pos_set(&stream->dispatch_cursor, &wreq->load_cursor); + + ret =3D stream->issue_write(subreq); + if (ret < 0) { + /* Ownership of subreq was returned to us. */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + stream->buffered -=3D subreq->len; + netfs_write_subrequest_terminated(subreq, ret); + /* Punt the error to the collector. */ + } + + bvecq_pos_unset(&stream->dispatch_cursor); } =20 wreq->submitted =3D wreq->len; diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index c61bed687244..153c180f4fed 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -12,13 +12,44 @@ #include "internal.h" =20 /* - * Perform retries on the streams that need it. + * Prepare the write buffer for a retry. We can't necessarily reuse the w= rite + * buffer from the previous run of a subrequest because the filesystem is + * permitted to modify it (add headers/trailers, encrypt it). Further, the + * subrequest may now be a different size (e.g. cifs has to negotiate for + * maximum transfer size). Also, we can't look at *stream as that may sti= ll + * refer to the source material being broken up into original subrequests. + */ +int netfs_prepare_write_retry_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs) +{ + struct netfs_io_request *wreq =3D subreq->rreq; + size_t len; + + bvecq_pos_set(&subreq->dispatch_pos, &wreq->retry_cursor); + bvecq_pos_set(&subreq->content, &wreq->retry_cursor); + len =3D bvecq_slice(&wreq->retry_cursor, subreq->len, max_segs, &subreq->= nr_segs); + + if (len < subreq->len) { + subreq->len =3D len; + trace_netfs_sreq(subreq, netfs_sreq_trace_limited); + } + + wreq->retry_start +=3D len; + wreq->retry_buffered -=3D len; + if (wreq->retry_buffered =3D=3D 0) + bvecq_pos_unset(&wreq->retry_cursor); + return 0; +} + +/* + * Perform retries on the streams that need it. This only has to deal with + * buffered writes; unbuffered write retry is handled in direct_write.c. */ static void netfs_retry_write_stream(struct netfs_io_request *wreq, struct netfs_io_stream *stream) { - struct bvecq_pos dispatch_cursor =3D {}; struct list_head *next; + int ret; =20 _enter("R=3D%x[%x:]", wreq->debug_id, stream->stream_nr); =20 @@ -33,30 +64,15 @@ static void netfs_retry_write_stream(struct netfs_io_re= quest *wreq, if (unlikely(stream->failed)) return; =20 - /* If there's no renegotiation to do, just resend each failed subreq. */ - if (!stream->prepare_write) { - struct netfs_io_subrequest *subreq; - - list_for_each_entry(subreq, &stream->subrequests, rreq_link) { - if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) - break; - if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) { - netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); - netfs_reissue_write(stream, subreq); - } - } - return; - } - - next =3D stream->subrequests.next; + /* Read pointer to subreq before reading subreq state. */ + next =3D smp_load_acquire(&stream->subrequests.next); =20 do { struct netfs_io_subrequest *subreq =3D NULL, *from, *to, *tmp; uoff_t start, len; - size_t part; - bool boundary =3D false; + bool subreq_superfluous =3D false; =20 - bvecq_pos_unset(&dispatch_cursor); + bvecq_pos_unset(&wreq->retry_cursor); =20 /* Go through the stream and find the next span of contiguous * data that we then rejig (cifs, for example, needs the wsize @@ -80,7 +96,6 @@ static void netfs_retry_write_stream(struct netfs_io_requ= est *wreq, subreq =3D list_entry(next, struct netfs_io_subrequest, rreq_link); if (subreq->start !=3D start + len || subreq->transferred > 0 || - test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) || !test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) break; to =3D subreq; @@ -90,8 +105,10 @@ static void netfs_retry_write_stream(struct netfs_io_re= quest *wreq, /* Determine the set of buffers we're going to use. Each * subreq gets a subset of a single overall contiguous buffer. */ - bvecq_pos_transfer(&dispatch_cursor, &from->dispatch_pos); - bvecq_pos_advance(&dispatch_cursor, from->transferred); + bvecq_pos_transfer(&wreq->retry_cursor, &from->dispatch_pos); + bvecq_pos_advance(&wreq->retry_cursor, from->transferred); + wreq->retry_start =3D start; + wreq->retry_buffered =3D len; =20 /* Work through the sublist. The chain of buffers we're going * to fill is attached to dispatch_cursor and we need to read @@ -99,47 +116,50 @@ static void netfs_retry_write_stream(struct netfs_io_r= equest *wreq, */ subreq =3D from; list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) { - if (!len) + if (!wreq->retry_buffered) { + subreq_superfluous =3D true; break; - - subreq->start =3D start; - subreq->len =3D len; - __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); - trace_netfs_sreq(subreq, netfs_sreq_trace_retry); - subreq->transferred =3D 0; + } =20 bvecq_pos_unset(&subreq->content); bvecq_pos_unset(&subreq->dispatch_pos); =20 - /* Renegotiate max_len (wsize) */ - stream->sreq_max_len =3D len; - stream->sreq_max_segs =3D INT_MAX; - stream->prepare_write(subreq); - - bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); - part =3D bvecq_slice(&dispatch_cursor, - umin(len, stream->sreq_max_len), - stream->sreq_max_segs, - &subreq->nr_segs); - subreq->len =3D part; - - len -=3D part; - start +=3D part; - if (len && subreq =3D=3D to && - __test_and_clear_bit(NETFS_SREQ_BOUNDARY, &to->flags)) - boundary =3D true; - + __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags); + __clear_bit(NETFS_SREQ_FAILED, &subreq->flags); + __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); + subreq->start =3D wreq->retry_start; + subreq->len =3D wreq->retry_buffered; + subreq->transferred =3D 0; + subreq->retry_count +=3D 1; + subreq->error =3D 0; + + netfs_stat(&netfs_n_wh_retry_write_subreq); + trace_netfs_sreq(subreq, netfs_sreq_trace_retry); netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit); - netfs_reissue_write(stream, subreq); - if (subreq =3D=3D to) + ret =3D stream->issue_write(subreq); + if (ret < 0) { + /* Ownership of subreq was returned to us. + * Expand the subreq to consume the entire + * remaining amount to be retried and fail it. + */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + wreq->retry_buffered -=3D subreq->len; + netfs_write_subrequest_terminated(subreq, ret); + break; + } + + if (subreq =3D=3D to) { + subreq_superfluous =3D false; break; + } } =20 /* If we managed to use fewer subreqs, we can discard the * excess; if we used the same number, then we're done. */ - if (!len) { - if (subreq =3D=3D to) + if (!wreq->retry_buffered) { + if (!subreq_superfluous) continue; list_for_each_entry_safe_from(subreq, tmp, &stream->subrequests, rreq_link) { @@ -161,6 +181,7 @@ static void netfs_retry_write_stream(struct netfs_io_re= quest *wreq, subreq =3D netfs_alloc_subrequest(wreq); subreq->source =3D to->source; subreq->start =3D start; + subreq->len =3D wreq->retry_buffered; subreq->stream_nr =3D to->stream_nr; subreq->retry_count =3D 1; =20 @@ -169,49 +190,40 @@ static void netfs_retry_write_stream(struct netfs_io_= request *wreq, netfs_sreq_trace_new); trace_netfs_sreq(subreq, netfs_sreq_trace_split); =20 + __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); spin_lock(&wreq->lock); + /* Write IN_PROGRESS before pointer to new subreq */ + smp_wmb(); list_add(&subreq->rreq_link, &to->rreq_link); spin_unlock(&wreq->lock); to =3D subreq; - trace_netfs_sreq(subreq, netfs_sreq_trace_retry); =20 - stream->sreq_max_len =3D len; - stream->sreq_max_segs =3D INT_MAX; switch (stream->source) { case NETFS_UPLOAD_TO_SERVER: netfs_stat(&netfs_n_wh_upload); - stream->sreq_max_len =3D umin(len, wreq->wsize); break; default: WARN_ON_ONCE(1); } =20 - stream->prepare_write(subreq); - - bvecq_pos_set(&subreq->dispatch_pos, &dispatch_cursor); - part =3D bvecq_slice(&dispatch_cursor, - umin(len, stream->sreq_max_len), - stream->sreq_max_segs, - &subreq->nr_segs); - subreq->len =3D subreq->transferred + part; - - len -=3D part; - start +=3D part; - if (!len && boundary) { - __set_bit(NETFS_SREQ_BOUNDARY, &to->flags); - boundary =3D false; - } - - netfs_reissue_write(stream, subreq); - if (!len) + trace_netfs_sreq(subreq, netfs_sreq_trace_retry); + ret =3D stream->issue_write(subreq); + if (ret < 0) { + /* Ownership of subreq was returned to us. + * Expand the subreq to consume the entire + * remaining amount to be retried and fail it. + */ + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); + wreq->retry_buffered -=3D subreq->len; + netfs_write_subrequest_terminated(subreq, ret); break; - - } while (len); + } + } while (wreq->retry_buffered > 0); =20 } while (!list_is_head(next, &stream->subrequests)); =20 out: - bvecq_pos_unset(&dispatch_cursor); + bvecq_pos_unset(&wreq->retry_cursor); } =20 /* diff --git a/fs/nfs/fscache.c b/fs/nfs/fscache.c index 9b7fdad4a920..fb1441f88661 100644 --- a/fs/nfs/fscache.c +++ b/fs/nfs/fscache.c @@ -23,6 +23,7 @@ #include "iostat.h" #include "fscache.h" #include "nfstrace.h" +#include =20 #define NFS_MAX_KEY_LEN 1000 =20 @@ -273,8 +274,6 @@ static int nfs_netfs_init_request(struct netfs_io_reque= st *rreq, struct file *fi rreq->debug_id =3D atomic_inc_return(&nfs_netfs_debug_id); /* [DEPRECATED] Use PG_private_2 to mark folio being written to the cache= . */ __set_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags); - rreq->io_streams[0].sreq_max_len =3D NFS_SB(rreq->inode->i_sb)->rsize; - return 0; } =20 @@ -296,8 +295,9 @@ static struct nfs_netfs_io_data *nfs_netfs_alloc(struct= netfs_io_subrequest *sre return netfs; } =20 -static void nfs_netfs_issue_read(struct netfs_io_subrequest *sreq) +static int nfs_netfs_issue_read(struct netfs_io_subrequest *sreq) { + struct netfs_io_request *rreq =3D sreq->rreq; struct nfs_netfs_io_data *netfs; struct nfs_pageio_descriptor pgio; struct inode *inode =3D sreq->rreq->inode; @@ -307,6 +307,16 @@ static void nfs_netfs_issue_read(struct netfs_io_subre= quest *sreq) pgoff_t start, last; int err; =20 + if (sreq->len > NFS_SB(rreq->inode->i_sb)->rsize) + sreq->len =3D NFS_SB(rreq->inode->i_sb)->rsize; + + err =3D netfs_prepare_read_buffer(sreq, INT_MAX); + if (err < 0) { + sreq->error =3D err; + return err; + } + /* After this point, must fail by termination. */ + start =3D (sreq->start + sreq->transferred) >> PAGE_SHIFT; last =3D ((sreq->start + sreq->len - sreq->transferred - 1) >> PAGE_SHIFT= ); =20 @@ -316,12 +326,15 @@ static void nfs_netfs_issue_read(struct netfs_io_subr= equest *sreq) netfs =3D nfs_netfs_alloc(sreq); if (!netfs) { sreq->error =3D -ENOMEM; - return netfs_read_subreq_terminated(sreq); + netfs_read_subreq_terminated(sreq); + return 0; } =20 + trace_netfs_sreq(sreq, netfs_sreq_trace_submit); + pgio.pg_netfs =3D netfs; /* used in completion */ =20 - xa_for_each_range(&sreq->rreq->mapping->i_pages, idx, page, start, last) { + xa_for_each_range(&rreq->mapping->i_pages, idx, page, start, last) { /* nfs_read_add_folio() may schedule() due to pNFS layout and other RPCs= */ err =3D nfs_read_add_folio(&pgio, ctx, page_folio(page)); if (err < 0) { @@ -332,6 +345,7 @@ static void nfs_netfs_issue_read(struct netfs_io_subreq= uest *sreq) out: nfs_pageio_complete_read(&pgio); nfs_netfs_put(netfs); + return 0; } =20 void nfs_netfs_initiate_read(struct nfs_pgio_header *hdr) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 1f77512252e7..37d576d38837 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1467,8 +1467,7 @@ cifs_readv_callback(struct TCP_Server_Info *server, s= truct mid_q_entry *mid) struct cifs_tcon *tcon =3D tlink_tcon(rdata->req->cfile->tlink); struct inode *inode =3D &ictx->inode; struct smb_rqst rqst =3D { .rq_iov =3D rdata->iov, - .rq_nvec =3D 1, - .rq_iter =3D rdata->subreq.io_iter }; + .rq_nvec =3D 1}; struct cifs_credits credits =3D { .value =3D 1, .instance =3D 0, @@ -1482,6 +1481,11 @@ cifs_readv_callback(struct TCP_Server_Info *server, = struct mid_q_entry *mid) __func__, mid->mid, mid->mid_state, rdata->result, rdata->subreq.len); =20 + if (rdata->got_bytes) + iov_iter_bvec_queue(&rqst.rq_iter, ITER_DEST, + rdata->subreq.content.bvecq, rdata->subreq.content.slot, + rdata->subreq.content.offset, rdata->subreq.len); + switch (mid->mid_state) { case MID_RESPONSE_RECEIVED: /* result already set, check signature */ @@ -2007,7 +2011,10 @@ cifs_async_writev(struct cifs_io_subrequest *wdata) =20 rqst.rq_iov =3D iov; rqst.rq_nvec =3D 1; - rqst.rq_iter =3D wdata->subreq.io_iter; + + iov_iter_bvec_queue(&rqst.rq_iter, ITER_SOURCE, + wdata->subreq.content.bvecq, wdata->subreq.content.slot, + wdata->subreq.content.offset, wdata->subreq.len); =20 cifs_dbg(FYI, "async write at %llu %zu bytes\n", wdata->subreq.start, wdata->subreq.len); diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 7ad464782439..54db4e3a83fd 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -53,21 +53,23 @@ static int cifs_estimate_write(struct netfs_io_request = *wreq, } =20 /* - * Prepare a subrequest to upload to the server. We need to allocate cred= its - * so that we know the maximum amount of data that we can include in it. + * Issue a subrequest to upload to the server. */ -static void cifs_prepare_write(struct netfs_io_subrequest *subreq) +static int cifs_issue_write(struct netfs_io_subrequest *subreq) { struct cifs_io_subrequest *wdata =3D container_of(subreq, struct cifs_io_subrequest, subreq); struct cifs_io_request *req =3D wdata->req; - struct netfs_io_stream *stream =3D &req->rreq.io_streams[subreq->stream_n= r]; struct TCP_Server_Info *server; struct cifsFileInfo *open_file =3D req->cfile; - struct cifs_sb_info *cifs_sb =3D CIFS_SB(wdata->rreq->inode->i_sb); - size_t wsize =3D req->rreq.wsize; + struct cifs_sb_info *cifs_sb =3D CIFS_SB(subreq->rreq->inode->i_sb); + unsigned int max_segs =3D INT_MAX; + size_t len; int rc; =20 + if (cifs_forced_shutdown(cifs_sb)) + return smb_EIO(smb_eio_trace_forced_shutdown); + if (!wdata->have_xid) { wdata->xid =3D get_xid(); wdata->have_xid =3D true; @@ -86,18 +88,16 @@ static void cifs_prepare_write(struct netfs_io_subreque= st *subreq) if (rc < 0) { if (rc =3D=3D -EAGAIN) goto retry; - subreq->error =3D rc; - return netfs_prepare_write_failed(subreq); + return rc; } } =20 - rc =3D server->ops->wait_mtu_credits(server, wsize, &stream->sreq_max_len, - &wdata->credits); - if (rc < 0) { - subreq->error =3D rc; - return netfs_prepare_write_failed(subreq); - } + len =3D umin(subreq->len, cifs_sb->ctx->wsize); + rc =3D server->ops->wait_mtu_credits(server, len, &len, &wdata->credits); + if (rc < 0) + return rc; =20 + subreq->len =3D len; wdata->credits.rreq_debug_id =3D subreq->rreq->debug_id; wdata->credits.rreq_debug_index =3D subreq->debug_index; wdata->credits.in_flight_check =3D 1; @@ -113,46 +113,34 @@ static void cifs_prepare_write(struct netfs_io_subreq= uest *subreq) const struct smbdirect_socket_parameters *sp =3D smbd_get_parameters(server->smbd_conn); =20 - stream->sreq_max_segs =3D sp->max_frmr_depth; + max_segs =3D sp->max_frmr_depth; } #endif -} =20 -/* - * Issue a subrequest to upload to the server. - */ -static void cifs_issue_write(struct netfs_io_subrequest *subreq) -{ - struct cifs_io_subrequest *wdata =3D - container_of(subreq, struct cifs_io_subrequest, subreq); - struct cifs_sb_info *sbi =3D CIFS_SB(subreq->rreq->inode->i_sb); - int rc; - - if (cifs_forced_shutdown(sbi)) { - rc =3D smb_EIO(smb_eio_trace_forced_shutdown); - goto fail; + rc =3D netfs_prepare_write_buffer(subreq, max_segs); + if (rc < 0) { + add_credits_and_wake_if(wdata->server, &wdata->credits, 0); + return rc; } + /* After this point, must fail by termination. */ =20 - rc =3D adjust_credits(wdata->server, wdata, cifs_trace_rw_credits_issue_w= rite_adjust); + rc =3D adjust_credits(server, wdata, cifs_trace_rw_credits_issue_write_ad= just); if (rc) - goto fail; + goto fail_with_credits; =20 rc =3D -EAGAIN; if (wdata->req->cfile->invalidHandle) - goto fail; + goto fail_with_credits; =20 wdata->server->ops->async_writev(wdata); -out: - return; + return 0; =20 -fail: +fail_with_credits: if (rc =3D=3D -EAGAIN) trace_netfs_sreq(subreq, netfs_sreq_trace_retry); - else - trace_netfs_sreq(subreq, netfs_sreq_trace_fail); add_credits_and_wake_if(wdata->server, &wdata->credits, 0); cifs_write_subrequest_terminated(wdata, rc); - goto out; + return 0; } =20 static void cifs_netfs_invalidate_cache(struct netfs_io_request *wreq) @@ -161,17 +149,25 @@ static void cifs_netfs_invalidate_cache(struct netfs_= io_request *wreq) } =20 /* - * Negotiate the size of a read operation on behalf of the netfs library. + * Issue a read operation on behalf of the netfs helper functions. We're = asked + * to make a read of a certain size at a point in the file. We are permit= ted + * to only read a portion of that, but as long as we read something, the n= etfs + * helper will call us again so that we can issue another read. */ -static int cifs_prepare_read(struct netfs_io_subrequest *subreq) +static int cifs_issue_read(struct netfs_io_subrequest *subreq) { struct netfs_io_request *rreq =3D subreq->rreq; struct cifs_io_subrequest *rdata =3D container_of(subreq, struct cifs_io_= subrequest, subreq); struct cifs_io_request *req =3D container_of(subreq->rreq, struct cifs_io= _request, rreq); - struct TCP_Server_Info *server; + struct TCP_Server_Info *server =3D rdata->server; struct cifs_sb_info *cifs_sb =3D CIFS_SB(rreq->inode->i_sb); - size_t size; - int rc =3D 0; + unsigned int max_segs =3D INT_MAX; + size_t len; + int rc; + + cifs_dbg(FYI, "%s: op=3D%08x[%x] mapping=3D%p len=3D%zu/%zu\n", + __func__, rreq->debug_id, subreq->debug_index, rreq->mapping, + subreq->transferred, subreq->len); =20 if (!rdata->have_xid) { rdata->xid =3D get_xid(); @@ -185,17 +181,15 @@ static int cifs_prepare_read(struct netfs_io_subreque= st *subreq) cifs_negotiate_rsize(server, cifs_sb->ctx, tlink_tcon(req->cfile->tlink)); =20 - rc =3D server->ops->wait_mtu_credits(server, cifs_sb->ctx->rsize, - &size, &rdata->credits); + len =3D umin(subreq->len, cifs_sb->ctx->rsize); + rc =3D server->ops->wait_mtu_credits(server, len, &len, &rdata->credits); if (rc) return rc; =20 - rreq->io_streams[0].sreq_max_len =3D size; - - rdata->credits.in_flight_check =3D 1; + subreq->len =3D len; rdata->credits.rreq_debug_id =3D rreq->debug_id; rdata->credits.rreq_debug_index =3D subreq->debug_index; - + rdata->credits.in_flight_check =3D 1; trace_smb3_rw_credits(rdata->rreq->debug_id, rdata->subreq.debug_index, rdata->credits.value, @@ -207,40 +201,27 @@ static int cifs_prepare_read(struct netfs_io_subreque= st *subreq) const struct smbdirect_socket_parameters *sp =3D smbd_get_parameters(server->smbd_conn); =20 - rreq->io_streams[0].sreq_max_segs =3D sp->max_frmr_depth; + max_segs =3D sp->max_frmr_depth; } #endif - return 0; -} =20 -/* - * Issue a read operation on behalf of the netfs helper functions. We're = asked - * to make a read of a certain size at a point in the file. We are permit= ted - * to only read a portion of that, but as long as we read something, the n= etfs - * helper will call us again so that we can issue another read. - */ -static void cifs_issue_read(struct netfs_io_subrequest *subreq) -{ - struct netfs_io_request *rreq =3D subreq->rreq; - struct cifs_io_subrequest *rdata =3D container_of(subreq, struct cifs_io_= subrequest, subreq); - struct cifs_io_request *req =3D container_of(subreq->rreq, struct cifs_io= _request, rreq); - struct TCP_Server_Info *server =3D rdata->server; - int rc =3D 0; - - cifs_dbg(FYI, "%s: op=3D%08x[%x] mapping=3D%p len=3D%zu/%zu\n", - __func__, rreq->debug_id, subreq->debug_index, rreq->mapping, - subreq->transferred, subreq->len); + rc =3D netfs_prepare_read_buffer(subreq, max_segs); + if (rc < 0) { + add_credits_and_wake_if(rdata->server, &rdata->credits, 0); + return rc; + } + /* After this point, must fail by termination. */ =20 rc =3D adjust_credits(server, rdata, cifs_trace_rw_credits_issue_read_adj= ust); if (rc) - goto failed; + goto fail_with_credits; =20 if (req->cfile->invalidHandle) { do { rc =3D cifs_reopen_file(req->cfile, true); } while (rc =3D=3D -EAGAIN); if (rc) - goto failed; + goto fail_with_credits; } =20 if (subreq->rreq->origin !=3D NETFS_UNBUFFERED_READ && @@ -248,15 +229,22 @@ static void cifs_issue_read(struct netfs_io_subreques= t *subreq) __set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags); =20 trace_netfs_sreq(subreq, netfs_sreq_trace_submit); + rc =3D rdata->server->ops->async_readv(rdata); if (rc) goto failed; - return; + return 0; =20 +fail_with_credits: + if (rc =3D=3D -EAGAIN) + trace_netfs_sreq(subreq, netfs_sreq_trace_retry); + else + trace_netfs_sreq(subreq, netfs_sreq_trace_fail); failed: add_credits_and_wake_if(rdata->server, &rdata->credits, 0); subreq->error =3D rc; netfs_read_subreq_terminated(subreq); + return 0; } =20 /* @@ -371,12 +359,10 @@ const struct netfs_request_ops cifs_req_ops =3D { .init_request =3D cifs_init_request, .free_request =3D cifs_free_request, .free_subrequest =3D cifs_free_subrequest, - .prepare_read =3D cifs_prepare_read, .issue_read =3D cifs_issue_read, .done =3D cifs_rreq_done, .begin_writeback =3D cifs_begin_writeback, .estimate_write =3D cifs_estimate_write, - .prepare_write =3D cifs_prepare_write, .issue_write =3D cifs_issue_write, .invalidate_cache =3D cifs_netfs_invalidate_cache, }; diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 97823bcf198c..1f3266f89cce 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -4884,6 +4884,7 @@ handle_read_data(struct TCP_Server_Info *server, stru= ct mid_q_entry *mid, unsigned int cur_page_idx; unsigned int pad_len; struct cifs_io_subrequest *rdata =3D mid->callback_data; + struct iov_iter iter; struct smb2_hdr *shdr =3D (struct smb2_hdr *)buf; size_t copied; bool use_rdma_mr =3D false; @@ -4956,6 +4957,10 @@ handle_read_data(struct TCP_Server_Info *server, str= uct mid_q_entry *mid, =20 pad_len =3D data_offset - server->vals->read_rsp_size; =20 + iov_iter_bvec_queue(&iter, ITER_DEST, + rdata->subreq.content.bvecq, rdata->subreq.content.slot, + rdata->subreq.content.offset, rdata->subreq.len); + if (buf_len <=3D data_offset) { /* read response payload is in pages */ cur_page_idx =3D pad_len / PAGE_SIZE; @@ -4985,7 +4990,7 @@ handle_read_data(struct TCP_Server_Info *server, stru= ct mid_q_entry *mid, =20 /* Copy the data to the output I/O iterator. */ rdata->result =3D cifs_copy_bvecq_to_iter(buffer, data_len, - cur_off, &rdata->subreq.io_iter); + cur_off, &iter); if (rdata->result !=3D 0) { if (is_offloaded) mid->mid_state =3D MID_RESPONSE_MALFORMED; @@ -4999,7 +5004,7 @@ handle_read_data(struct TCP_Server_Info *server, stru= ct mid_q_entry *mid, buf_len >=3D end_off) { /* read response payload is in buf */ WARN_ONCE(buffer, "read data can be either in buf or in buffer"); - copied =3D copy_to_iter(buf + data_offset, data_len, &rdata->subreq.io_i= ter); + copied =3D copy_to_iter(buf + data_offset, data_len, &iter); if (copied =3D=3D 0) return smb_EIO2(smb_eio_trace_rx_copy_to_iter, copied, data_len); rdata->got_bytes =3D copied; diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 4ce165e40657..085fdc7d8bec 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -4592,9 +4592,13 @@ smb2_new_read_req(void **buf, unsigned int *total_le= n, */ if (rdata && smb3_use_rdma_offload(io_parms)) { struct smbdirect_buffer_descriptor_v1 *v1; + struct iov_iter iter; bool need_invalidate =3D server->dialect =3D=3D SMB30_PROT_ID; =20 - rdata->mr =3D smbd_register_mr(server->smbd_conn, &rdata->subreq.io_iter, + iov_iter_bvec_queue(&iter, ITER_DEST, + rdata->subreq.content.bvecq, rdata->subreq.content.slot, + rdata->subreq.content.offset, rdata->subreq.len); + rdata->mr =3D smbd_register_mr(server->smbd_conn, &iter, true, need_invalidate); if (!rdata->mr) return -EAGAIN; @@ -4658,9 +4662,10 @@ smb2_readv_callback(struct TCP_Server_Info *server, = struct mid_q_entry *mid) unsigned int rreq_debug_id =3D rdata->rreq->debug_id; unsigned int subreq_debug_index =3D rdata->subreq.debug_index; =20 - if (rdata->got_bytes) { - rqst.rq_iter =3D rdata->subreq.io_iter; - } + if (rdata->got_bytes) + iov_iter_bvec_queue(&rqst.rq_iter, ITER_DEST, + rdata->subreq.content.bvecq, rdata->subreq.content.slot, + rdata->subreq.content.offset, rdata->subreq.len); =20 WARN_ONCE(rdata->server !=3D server, "rdata server %p !=3D mid server %p", @@ -5148,7 +5153,9 @@ smb2_async_writev(struct cifs_io_subrequest *wdata) goto out; =20 rqst.rq_iov =3D iov; - rqst.rq_iter =3D wdata->subreq.io_iter; + iov_iter_bvec_queue(&rqst.rq_iter, ITER_SOURCE, + wdata->subreq.content.bvecq, wdata->subreq.content.slot, + wdata->subreq.content.offset, wdata->subreq.len); =20 rqst.rq_iov[0].iov_len =3D total_len - 1; rqst.rq_iov[0].iov_base =3D (char *)req; @@ -5187,9 +5194,14 @@ smb2_async_writev(struct cifs_io_subrequest *wdata) */ if (smb3_use_rdma_offload(io_parms)) { struct smbdirect_buffer_descriptor_v1 *v1; + struct iov_iter iter; bool need_invalidate =3D server->dialect =3D=3D SMB30_PROT_ID; =20 - wdata->mr =3D smbd_register_mr(server->smbd_conn, &wdata->subreq.io_iter, + iov_iter_bvec_queue(&iter, ITER_SOURCE, + wdata->subreq.content.bvecq, wdata->subreq.content.slot, + wdata->subreq.content.offset, wdata->subreq.len); + + wdata->mr =3D smbd_register_mr(server->smbd_conn, &iter, false, need_invalidate); if (!wdata->mr) { rc =3D -EAGAIN; @@ -5226,8 +5238,8 @@ smb2_async_writev(struct cifs_io_subrequest *wdata) smb2_set_replay(server, &rqst); } =20 - cifs_dbg(FYI, "async write at %llu %u bytes iter=3D%zx\n", - io_parms->offset, io_parms->length, iov_iter_count(&wdata->subreq.io_it= er)); + cifs_dbg(FYI, "async write at %llu %u bytes len=3D%zx\n", + io_parms->offset, io_parms->length, wdata->subreq.len); =20 if (wdata->credits.value > 0) { shdr->CreditCharge =3D cpu_to_le16(DIV_ROUND_UP(wdata->subreq.len, diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index fdf4e50c27ce..be2f6b909c34 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -1267,12 +1267,19 @@ cifs_readv_receive(struct TCP_Server_Info *server, = struct mid_q_entry *mid) } =20 #ifdef CONFIG_CIFS_SMB_DIRECT - if (rdata->mr) + if (rdata->mr) { length =3D data_len; /* An RDMA read is already done. */ - else + } else { +#endif + struct iov_iter iter; + + iov_iter_bvec_queue(&iter, ITER_DEST, rdata->subreq.content.bvecq, + rdata->subreq.content.slot, rdata->subreq.content.offset, + data_len); + length =3D cifs_read_iter_from_socket(server, &iter, data_len); +#ifdef CONFIG_CIFS_SMB_DIRECT + } #endif - length =3D cifs_read_iter_from_socket(server, &rdata->subreq.io_iter, - data_len); if (length > 0) rdata->got_bytes +=3D length; server->total_read +=3D length; diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 2c5b7f3a48b7..5eb836086329 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -162,23 +162,17 @@ struct netfs_write_estimate { struct netfs_io_stream { /* Submission tracking (main dispatch only; not retry) */ struct bvecq_pos dispatch_cursor; /* Point from which buffers are dispatc= hed */ - struct netfs_io_subrequest *construct; /* Op being constructed */ uoff_t issue_from; /* Current issue point */ uoff_t last_end; /* End file pos of last folio added */ size_t buffered; /* Amount in buffer */ size_t post_gap; /* Length of partial folio tail */ - size_t sreq_max_len; /* Maximum size of a subrequest */ - unsigned int sreq_max_segs; /* 0 or max number of segments in an iterato= r */ - unsigned int submit_off; /* Folio offset we're submitting from */ - unsigned int submit_len; /* Amount of data left to submit */ unsigned int alignment; /* Required alignment */ u8 applicable; /* What sources are applicable (NOTE_* mask) */ bool buffering; /* T if buffering on this stream */ int (*estimate_write)(struct netfs_io_request *wreq, struct netfs_io_stream *stream, struct netfs_write_estimate *estimate); - void (*prepare_write)(struct netfs_io_subrequest *subreq); - void (*issue_write)(struct netfs_io_subrequest *subreq); + int (*issue_write)(struct netfs_io_subrequest *subreq); atomic64_t issued_to; /* Point to which can be considered issued */ =20 /* Collection tracking */ @@ -223,15 +217,14 @@ struct netfs_io_subrequest { struct list_head rreq_link; /* Link in rreq->subrequests */ struct bvecq_pos dispatch_pos; /* Bookmark in the combined queue of the s= tart */ struct bvecq_pos content; /* The (copied) content of the subrequest */ - struct iov_iter io_iter; /* Iterator for this subrequest */ uoff_t start; /* Where to start the I/O */ size_t len; /* Size of the I/O */ size_t post_gap; /* Length of partial folio tail */ size_t transferred; /* Amount of data transferred */ + unsigned int nr_segs; /* Number of segments in content */ refcount_t ref; short error; /* 0 or error that occurred */ unsigned short debug_index; /* Index in list (for debugging output) */ - unsigned int nr_segs; /* Number of segments in content */ u8 retry_count; /* The number of retries (0 on initial pass) */ enum netfs_io_source source; /* Where to read from/write to */ unsigned char stream_nr; /* I/O stream this belongs to */ @@ -239,7 +232,6 @@ struct netfs_io_subrequest { #define NETFS_SREQ_COPY_TO_CACHE 0 /* Set if should copy the data to the c= ache */ #define NETFS_SREQ_CLEAR_TAIL 1 /* Set if the rest of the read should be = cleared */ #define NETFS_SREQ_MADE_PROGRESS 4 /* Set if we transferred at least some = data */ -#define NETFS_SREQ_BOUNDARY 6 /* Set if ends on hard boundary (eg. ceph o= bject) */ #define NETFS_SREQ_HIT_EOF 7 /* Set if short due to EOF */ #define NETFS_SREQ_IN_PROGRESS 8 /* Unlocked when the subrequest complete= s */ #define NETFS_SREQ_NEED_RETRY 9 /* Set if the filesystem requests a retry= */ @@ -288,12 +280,11 @@ struct netfs_io_request { struct netfs_group *group; /* Writeback group being written back */ struct bvecq *spare; /* Advance allocation of bvecq */ struct bvecq_pos load_cursor; /* Point at which new folios are loaded in = */ - struct bvecq_pos dispatch_cursor; /* Point from which buffers are dispatc= hed */ struct bvecq_pos collect_cursor; /* Clear-up point of I/O buffer */ + struct bvecq_pos retry_cursor; /* Point from which retries are dispatched= */ wait_queue_head_t waitq; /* Processor waiter */ void *netfs_priv; /* Private data for the netfs */ void *netfs_priv2; /* Private data for the netfs */ - uoff_t last_end; /* End pos of last folio submitted */ uoff_t submitted; /* Amount submitted for I/O so far */ uoff_t len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ @@ -305,6 +296,8 @@ struct netfs_io_request { uoff_t cache_coll_to; /* Point the cache has collected to */ uoff_t cleaned_to; /* Position we've cleaned folios to */ uoff_t abandon_to; /* Position to abandon folios to */ + uoff_t retry_start; /* Position to retry from */ + size_t retry_buffered; /* Amount of data to retry */ const struct folio *no_unlock_folio; /* Don't unlock this folio after rea= d */ gfp_t gfp; /* GFP flags to use */ unsigned int debug_id; @@ -351,8 +344,7 @@ struct netfs_request_ops { =20 /* Read request handling */ void (*expand_readahead)(struct netfs_io_request *rreq); - int (*prepare_read)(struct netfs_io_subrequest *subreq); - void (*issue_read)(struct netfs_io_subrequest *subreq); + int (*issue_read)(struct netfs_io_subrequest *subreq); bool (*is_still_valid)(struct netfs_io_request *rreq); int (*check_write_begin)(struct file *file, uoff_t pos, unsigned len, struct folio **foliop, void **_fsdata); @@ -367,8 +359,7 @@ struct netfs_request_ops { int (*estimate_write)(struct netfs_io_request *wreq, struct netfs_io_stream *stream, struct netfs_write_estimate *estimate); - void (*prepare_write)(struct netfs_io_subrequest *subreq); - void (*issue_write)(struct netfs_io_subrequest *subreq); + int (*issue_write)(struct netfs_io_subrequest *subreq); void (*retry_request)(struct netfs_io_request *wreq, struct netfs_io_stre= am *stream); void (*invalidate_cache)(struct netfs_io_request *wreq); }; @@ -408,8 +399,11 @@ struct netfs_cache_ops { struct netfs_io_stream *stream, struct netfs_write_estimate *estimate); =20 + /* Read data from the cache for a netfs subrequest. */ + int (*issue_read)(struct netfs_io_subrequest *subreq); + /* Write data to the cache from a netfs subrequest. */ - void (*issue_write)(struct netfs_io_subrequest *subreq); + int (*issue_write)(struct netfs_io_subrequest *subreq); =20 /* Expand readahead request */ void (*expand_readahead)(struct netfs_cache_resources *cres, @@ -417,30 +411,18 @@ struct netfs_cache_ops { uoff_t *_len, uoff_t i_size); =20 - /* Prepare a read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ - int (*prepare_read)(struct netfs_io_subrequest *subreq); - - /* Prepare a write subrequest, working out if we're allowed to do it - * and finding out the maximum amount of data to gather before - * attempting to submit. If we're not permitted to do it, the - * subrequest should be marked failed. - */ - void (*prepare_write_subreq)(struct netfs_io_subrequest *subreq); - /* Prepare a write operation, working out what part of the write we can * actually do. */ - int (*prepare_write)(struct netfs_cache_resources *cres, - uoff_t *_start, size_t *_len, size_t upper_len, - uoff_t i_size, bool no_space_allocated_yet); + int (*prepare_write_old)(struct netfs_cache_resources *cres, + uoff_t *_start, size_t *_len, size_t upper_len, + uoff_t i_size, bool no_space_allocated_yet); =20 /* Query the occupancy of the cache in a region, returning where the * next chunk of data starts and how long it is. */ - int (*query_occupancy)(struct netfs_cache_resources *cres, - struct fscache_occupancy *occ); + void (*query_occupancy)(struct netfs_cache_resources *cres, + struct fscache_occupancy *occ); =20 /* Collect the result of buffered writeback to the cache. This * includes copying a read to the cache. block_type is one of: @@ -477,7 +459,6 @@ int netfs_writeback_single(struct address_space *mappin= g, struct iov_iter *iter, size_t len); =20 /* Address operations API */ -struct readahead_control; void netfs_readahead(struct readahead_control *); int netfs_read_folio(struct file *, struct folio *); int netfs_write_begin(struct netfs_inode *, struct file *, @@ -504,7 +485,8 @@ void netfs_put_subrequest(struct netfs_io_subrequest *s= ubreq, ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t m= ax_pages, struct bvecq **_bvecq_head, iov_iter_extraction_t extraction_flags, gfp_t gfp); -void netfs_prepare_write_failed(struct netfs_io_subrequest *subreq); +int netfs_prepare_read_buffer(struct netfs_io_subrequest *subreq, unsigned= int max_segs); +int netfs_prepare_write_buffer(struct netfs_io_subrequest *subreq, unsigne= d int max_segs); void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_e= rror); =20 int netfs_start_io_read(struct inode *inode); diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 6ccf9de42858..dfe08492d1ad 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -49,6 +49,7 @@ #define netfs_rreq_traces \ EM(netfs_rreq_trace_all_queued, "ALL-Q ") \ EM(netfs_rreq_trace_assess, "ASSESS ") \ + EM(netfs_rreq_trace_cache_align_error, "CA-ALN!") \ EM(netfs_rreq_trace_cache_cancelled, "CA-CNCL") \ EM(netfs_rreq_trace_cache_failed, "CA-FAIL") \ EM(netfs_rreq_trace_cache_fail_collect, "CA-F-CO") \ @@ -85,7 +86,8 @@ EM(netfs_rreq_trace_waited_quiesce, "DONE-QUIESCE") \ EM(netfs_rreq_trace_wake_ip, "WAKE-IP") \ EM(netfs_rreq_trace_wake_queue, "WAKE-Q ") \ - E_(netfs_rreq_trace_write_done, "WR-DONE") + EM(netfs_rreq_trace_write_done, "WR-DONE") \ + E_(netfs_rreq_trace_zero_unread, "ZERO-UR") =20 #define netfs_sreq_sources \ EM(NETFS_SOURCE_UNKNOWN, "----") \ @@ -134,6 +136,7 @@ EM(netfs_sreq_trace_superfluous, "SPRFL") \ EM(netfs_sreq_trace_terminated, "TERM ") \ EM(netfs_sreq_trace_too_much, "!TOOM") \ + EM(netfs_sreq_trace_too_many_retries, "!RETR") \ EM(netfs_sreq_trace_wait_for, "_WAIT") \ EM(netfs_sreq_trace_write, "WRITE") \ EM(netfs_sreq_trace_write_skip, "SKIP ") \ @@ -528,6 +531,31 @@ TRACE_EVENT(netfs_folio, __print_symbolic(__entry->why, netfs_folio_traces)) ); =20 +TRACE_EVENT(netfs_wback, + TP_PROTO(struct netfs_io_request *wreq, struct folio *folio, unsigned= int notes), + + TP_ARGS(wreq, folio, notes), + + TP_STRUCT__entry( + __field(pgoff_t, index) + __field(unsigned int, wreq) + __field(unsigned int, nr) + __field(unsigned int, notes) + ), + + TP_fast_assign( + __entry->wreq =3D wreq->debug_id; + __entry->notes =3D notes; + __entry->index =3D folio->index; + __entry->nr =3D folio_nr_pages(folio); + ), + + TP_printk("R=3D%08x ix=3D%05lx-%05lx n=3D%02x", + __entry->wreq, + __entry->index, __entry->index + __entry->nr - 1, + __entry->notes) + ); + TRACE_EVENT(netfs_write_iter, TP_PROTO(const struct kiocb *iocb, const struct iov_iter *from), =20 diff --git a/net/9p/client.c b/net/9p/client.c index ef64546c6d52..68d82f677a7a 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -1564,6 +1564,7 @@ void p9_client_write_subreq(struct netfs_io_subrequest *subreq) { struct netfs_io_request *wreq =3D subreq->rreq; + struct iov_iter iter; struct p9_fid *fid =3D wreq->netfs_priv; struct p9_client *clnt =3D fid->clnt; struct p9_req_t *req; @@ -1574,14 +1575,17 @@ p9_client_write_subreq(struct netfs_io_subrequest *= subreq) p9_debug(P9_DEBUG_9P, ">>> TWRITE fid %d offset %llu len %d\n", fid->fid, start, len); =20 + iov_iter_bvec_queue(&iter, ITER_SOURCE, subreq->content.bvecq, + subreq->content.slot, subreq->content.offset, subreq->len); + /* Don't bother zerocopy for small IO (< 1024) */ if (clnt->trans_mod->zc_request && len > 1024) { - req =3D p9_client_zc_rpc(clnt, P9_TWRITE, NULL, &subreq->io_iter, + req =3D p9_client_zc_rpc(clnt, P9_TWRITE, NULL, &iter, 0, wreq->len, P9_ZC_HDR_SZ, "dqd", fid->fid, start, len); } else { req =3D p9_client_rpc(clnt, P9_TWRITE, "dqV", fid->fid, - start, len, &subreq->io_iter); + start, len, &iter); } if (IS_ERR(req)) { netfs_write_subrequest_terminated(subreq, PTR_ERR(req)); From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 078BA446066 for ; Mon, 24 Aug 2026 14:46:42 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582806; cv=none; b=QIStxgu540pl8e7N7e+OzGI8NMTYbGY8mbnAjEvl4fwvL3hIts28JX3FegDRHOmkgIp+KrdVgwjpra5U4j2umC83S2GzeYutEyqRJwuj0sv4iGl7i2aiuuBjrNfVxpE7vM00fseEt63hMx8bL2O4v0C5lsumcCWVFNgbnxbWbU0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787582806; c=relaxed/simple; bh=btTXYx6Hp6Bwj+KUkWowA9NM3l4ByBTx2uap/qUaSxY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UpGl6VloBpzuSzomLfUnusL5/R9iZBRRl27At+Lv5hsvD5X1szust1xIr5joNvSITVgXAIWF3LpDsGnC+SgtRSiEZ+66FR8GTPrWJCObIKuuLT2UWXhy6K3sWhcyFjHBVveXqBx4NxT3kS2l/Gg0/ALoYC9VzF90OGzanAsJJKI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Xl4l7WUn; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Xl4l7WUn" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787582801; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=wZ8TKj4kMlT2oxl+NTWw+AKG13lHeNsePdMSIyuFA3c=; b=Xl4l7WUnUvJTpt1TmYufw0aTdV879e3f0raFH4hMxDoj3dryV2h9mhNtrKs/qnI0jPSerf estZhuV/X3ewrn0C6Ii0O9430DHViyL7KoiGq7be/ccmmSmInVZgDlb3RGc2RsZq5btPRY RHEXEkmlON5kyC8EStB2wkEB8wp+AGM= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-685-MNfmIiBKMheIBG1E3aCMSA-1; Mon, 24 Aug 2026 10:46:36 -0400 X-MC-Unique: MNfmIiBKMheIBG1E3aCMSA-1 X-Mimecast-MFC-AGG-ID: MNfmIiBKMheIBG1E3aCMSA_1787582794 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 1D9781834594; Mon, 24 Aug 2026 14:46:34 +0000 (UTC) Received: from warthog.com (unknown [10.44.32.15]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id DB00A1955DAA; Mon, 24 Aug 2026 14:46:27 +0000 (UTC) From: David Howells To: Paulo Alcantara Cc: David Howells , Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 35/35] netfs: Clean up now-unused code Date: Mon, 24 Aug 2026 15:41:28 +0100 Message-ID: <20260824144130.759997-36-dhowells@redhat.com> In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" Delete now-unused code from netfslib, given the changes to the writeback dispatch code and the combination of ->prepare_xxx() and ->issue_xxx() methods. Signed-off-by: David Howells cc: Paulo Alcantara cc: Matthew Wilcox cc: Christoph Hellwig cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/netfs/write_issue.c | 216 ----------------------------------------- 1 file changed, 216 deletions(-) diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index e0d14d77b751..961949689303 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -223,69 +223,6 @@ struct netfs_io_subrequest *netfs_alloc_write_subreq(s= truct netfs_io_request *wr return subreq; } =20 -#if 0 // TODO: Remove old stuff -/* - * Prepare a write subrequest. We need to allocate a new subrequest - * if we don't have one. - */ -void netfs_prepare_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream, - uoff_t start) -{ - struct netfs_io_subrequest *subreq; - - subreq =3D netfs_alloc_subrequest(wreq); - subreq->source =3D stream->source; - subreq->start =3D start; - subreq->stream_nr =3D stream->stream_nr; - - bvecq_pos_set(&subreq->dispatch_pos, &wreq->dispatch_cursor); - - _enter("R=3D%x[%x]", wreq->debug_id, subreq->debug_index); - - trace_netfs_sreq(subreq, netfs_sreq_trace_prepare); - - stream->sreq_max_len =3D UINT_MAX; - stream->sreq_max_segs =3D INT_MAX; - switch (stream->source) { - case NETFS_UPLOAD_TO_SERVER: - netfs_stat(&netfs_n_wh_upload); - stream->sreq_max_len =3D wreq->wsize; - break; - case NETFS_WRITE_TO_CACHE: - netfs_stat(&netfs_n_wh_write); - break; - default: - WARN_ON_ONCE(1); - break; - } - - if (stream->prepare_write) - stream->prepare_write(subreq); - - __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); - - /* We add to the end of the list whilst the collector may be walking - * the list. The collector only goes nextwards and uses the lock to - * remove entries off of the front. - */ - spin_lock(&wreq->lock); - /* Write IN_PROGRESS before pointer to new subreq */ - list_add_tail_release(&subreq->rreq_link, &stream->subrequests); - if (list_is_first(&subreq->rreq_link, &stream->subrequests)) { - if (!stream->active) { - stream->collected_to =3D subreq->start; - /* Write list pointers before active flag */ - smp_store_release(&stream->active, true); - } - } - - spin_unlock(&wreq->lock); - - stream->construct =3D subreq; -} -#endif - /* * Advance the state of the amount of data buffered on a stream. */ @@ -368,159 +305,6 @@ int netfs_prepare_write_buffer(struct netfs_io_subreq= uest *subreq, } EXPORT_SYMBOL(netfs_prepare_write_buffer); =20 -#if 0 // TODO: Remove old stuff -/* - * Set the I/O iterator for the filesystem/cache to use and dispatch the I= /O - * operation. The operation may be asynchronous and should call - * netfs_write_subrequest_terminated() when complete. - */ -static void netfs_do_issue_write(struct netfs_io_stream *stream, - struct netfs_io_subrequest *subreq) -{ - struct netfs_io_request *wreq =3D subreq->rreq; - - _enter("R=3D%x[%x],%zx", wreq->debug_id, subreq->debug_index, subreq->len= ); - - if (stream->source =3D=3D NETFS_WRITE_TO_CACHE && - unlikely(test_bit(NETFS_RREQ_CACHE_STOP, &wreq->flags))) { - size_t dio_size =3D wreq->cache_resources.dio_size; - size_t len, disp; - - disp =3D subreq->start & (dio_size - 1); - len =3D round_up(subreq->len + disp, dio_size); - - subreq->start -=3D disp; - subreq->len =3D len; - - __set_bit(NETFS_SREQ_CANCELLED, &subreq->flags); - return netfs_write_subrequest_terminated(subreq, subreq->len); - } - - if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) - return netfs_write_subrequest_terminated(subreq, subreq->error); - - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); - stream->issue_write(subreq); -} - -void netfs_reissue_write(struct netfs_io_stream *stream, - struct netfs_io_subrequest *subreq) -{ - // TODO: Use encrypted buffer - bvecq_pos_unset(&subreq->content); - bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); - iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, - subreq->content.bvecq, subreq->content.slot, - subreq->content.offset, - subreq->len); - iov_iter_advance(&subreq->io_iter, subreq->transferred); - - subreq->retry_count++; - subreq->error =3D 0; - __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); - __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags); - netfs_stat(&netfs_n_wh_retry_write_subreq); - netfs_do_issue_write(stream, subreq); -} - -static void netfs_issue_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream) -{ - struct netfs_io_subrequest *subreq =3D stream->construct; - - if (!subreq) - return; - - bvecq_pos_set(&subreq->content, &subreq->dispatch_pos); - iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, - subreq->content.bvecq, subreq->content.slot, - subreq->content.offset, - subreq->len); - - stream->construct =3D NULL; - netfs_do_issue_write(stream, subreq); -} - -/* - * Add data to the write subrequest, dispatching each as we fill it up or = if it - * is discontiguous with the previous. We only fill one part at a time so= that - * we can avoid overrunning the credits obtained (cifs) and try to paralle= lise - * content-crypto preparation with network writes. - */ -size_t netfs_advance_write(struct netfs_io_request *wreq, - struct netfs_io_stream *stream, - uoff_t start, size_t len, bool to_eof) -{ - struct netfs_io_subrequest *subreq =3D stream->construct; - size_t part; - - if (!stream->avail) { - _leave("no write"); - return len; - } - - _enter("R=3D%x[%x]", wreq->debug_id, subreq ? subreq->debug_index : 0); - - if (subreq && start !=3D subreq->start + subreq->len) { - netfs_issue_write(wreq, stream); - subreq =3D NULL; - } - - if (!stream->construct) - netfs_prepare_write(wreq, stream, start); - subreq =3D stream->construct; - - part =3D umin(stream->sreq_max_len - subreq->len, len); - _debug("part %zx/%zx %zx/%zx", subreq->len, stream->sreq_max_len, part, l= en); - subreq->len +=3D part; - subreq->nr_segs++; - - if (subreq->len >=3D stream->sreq_max_len || - subreq->nr_segs >=3D stream->sreq_max_segs || - to_eof) { - netfs_issue_write(wreq, stream); - subreq =3D NULL; - } - - return part; -} - -/* - * Prepare and issue a subrequest. - * TODO: Replace with combined ->prepare/->issue call(). - */ -static int netfs_prep_and_issue_subreq(struct netfs_io_request *wreq, - struct netfs_io_stream *stream, - struct netfs_io_subrequest *subreq) -{ - stream->sreq_max_len =3D UINT_MAX; - stream->sreq_max_segs =3D INT_MAX; - switch (stream->source) { - case NETFS_UPLOAD_TO_SERVER: - netfs_stat(&netfs_n_wh_upload); - stream->sreq_max_len =3D wreq->wsize; - break; - case NETFS_WRITE_TO_CACHE: - netfs_stat(&netfs_n_wh_write); - break; - default: - WARN_ON_ONCE(1); - break; - } - - if (stream->prepare_write) - stream->prepare_write(subreq); - netfs_prepare_buffered_write_buffer(subreq, stream->sreq_max_segs); - iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE, - subreq->content.bvecq, subreq->content.slot, - subreq->content.offset, - subreq->len); - trace_netfs_sreq(subreq, netfs_sreq_trace_submit); - stream->issue_write(subreq); - return 0; -} -#endif - /* * Issue writes for a stream. */ From nobody Mon Sep 28 08:01:21 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 293EF3624AB for ; Mon, 24 Aug 2026 15:02:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787583727; cv=none; b=CRE4qPKIxnEV+WDKNVkWWDdMPby+INXLL6mmLUgYaaPM48pCcC65nSUP8Q9WixKyQGMAApK4o5fGu6als0TaRbpDL4+FmY79xRTDbUUHxAfbJSoBvTYOCuWtKJqKLXGkApHN57DWq1ya3f00+vhBbgpylJaYMECfYxIWyhe48go= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787583727; c=relaxed/simple; bh=N0G8QjNCNGIBQnyNuPE6mZGl7LBf74gy5UdDDCMl9hw=; h=From:In-Reply-To:References:To:Cc:Subject:MIME-Version: Content-Type:Date:Message-ID; b=HkJsgB6VXfZYCCliD0SYTXxJ2K3Td/Vh/3CM2KsUTdU7UoaaowHbKZb1hfIVj8+Vl6wtzM5a0Jgcqnj2az2g8ggQoOBYIMJniGogFO8rPKGxyFy+S3f8YTB2c2QUZhy2A07tPq2vMQZKpJDPzYETIuyLe165C+twHfy4KT76Jpc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=In0Hpv7T; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="In0Hpv7T" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787583722; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=nO//czvvztOuNSNWZRG8kBY0zaU5xWtjhN7ZKW5YMbI=; b=In0Hpv7THhHRHwZCIWpX09DzILyF+UfsphOpE3lI8ggk3tnN5uTgJSAJWKMdKtOzxBavyg 4qDIwdR2CfoEJ7r9oQ0LCOp9DPvugO5b8s3q/jYdmeHPB2yK/VZSrMI5GyqlqgjyKOOr3U /FIHAxGtW8HSGIyiqfwlkerFJjQqP+A= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-690-qRaPtRW7NxmtGTjHvtDIvQ-1; Mon, 24 Aug 2026 11:01:58 -0400 X-MC-Unique: qRaPtRW7NxmtGTjHvtDIvQ-1 X-Mimecast-MFC-AGG-ID: qRaPtRW7NxmtGTjHvtDIvQ_1787583713 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 84A4A197753E; Mon, 24 Aug 2026 15:01:52 +0000 (UTC) Received: from warthog.procyon.org.uk (unknown [10.44.32.15]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 68B031955F0F; Mon, 24 Aug 2026 15:01:46 +0000 (UTC) Organization: Red Hat UK Ltd. Registered Address: Red Hat UK Ltd, Amberley Place, 107-111 Peascod Street, Windsor, Berkshire, SI4 1TE, United Kingdom. Registered in England and Wales under Company Registration No. 3798903 From: David Howells In-Reply-To: <20260824144130.759997-1-dhowells@redhat.com> References: <20260824144130.759997-1-dhowells@redhat.com> To: Paulo Alcantara Cc: dhowells@redhat.com, Christian Brauner , Matthew Wilcox , Christoph Hellwig , Jens Axboe , Leon Romanovsky , Namjae Jeon , ChenXiaoSong , Marc Dionne , Stefan Metzmacher , Eric Van Hensbergen , Dominique Martinet , Ilya Dryomov , netfs@lists.linux.dev, linux-afs@lists.infradead.org, linux-cifs@vger.kernel.org, linux-nfs@vger.kernel.org, ceph-devel@vger.kernel.org, v9fs@lists.linux.dev, linux-erofs@lists.ozlabs.org, linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v10 36/35] cachefiles: Preset the state xattr when creating a new file Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-ID: <761011.1787583705.1@warthog.procyon.org.uk> Content-Transfer-Encoding: quoted-printable Date: Mon, 24 Aug 2026 16:01:45 +0100 Message-ID: <761012.1787583705@warthog.procyon.org.uk> X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" =20 With a really small cache, cachefiles is likely to see a lot of writes hitting ENOSPC - and this can include setxattr that sets the state xattr on a cachefile - but we don't really want to successfully fill a cache file only to have to scrap it because we can't set the xattr. Instead, preset the xattr when we create the tmpfile we're going to use, and scrap the file at that point if we get ENOSPC. Only if setxattr succeeds do we allow data to be written to the file. Note that there is a potential performance loss in that writes to the cache have to be delayed until this is completed - but we do the tmpfile/setxattr in parallel, starting when the file is opened and only have to wait once writeback occurs. Further, a new tracepoint, cachefiles_no_space, has been added to log ENOSPC events, along with a couple of subreq traces to log failure detection in netfslib. Signed-off-by: David Howells cc: Marc Dionne cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org --- fs/cachefiles/internal.h | 1=20 fs/cachefiles/io.c | 23 ++++++++++---- fs/cachefiles/namei.c | 15 ++++++++- fs/cachefiles/xattr.c | 59 +++++++++++++++++++++++++++++++++= ++++- include/trace/events/cachefiles.h | 30 ++++++++++++++++++- include/trace/events/netfs.h | 2 + 6 files changed, 120 insertions(+), 10 deletions(-) diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index b2605111fd56..664be64ab538 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -283,6 +283,7 @@ void cachefiles_withdraw_volume(struct cachefiles_volum= e *volume); /* * xattr.c */ +int cachefiles_preset_object_xattr(struct cachefiles_object *object, struc= t file *file); extern int cachefiles_set_object_xattr(struct cachefiles_object *object); extern int cachefiles_check_auxdata(struct cachefiles_object *object, struct file *file); diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index a1580385d5fb..73c8ba01a9e2 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -672,10 +672,14 @@ int __cachefiles_prepare_write(struct cachefiles_obje= ct *object, * space, we need to see if it's fully allocated. If it's not, we may * want to cull it. */ - if (cachefiles_has_space(cache, 0, *_len / cache->bsize, - cachefiles_has_space_check) =3D=3D 0) + ret =3D cachefiles_has_space(cache, 0, *_len / cache->bsize, + cachefiles_has_space_check); + if (ret =3D=3D 0) return 0; /* Enough space to simply overwrite the whole block */ =20 + if (ret =3D=3D -ENOBUFS) + trace_cachefiles_no_space(object, cachefiles_trace_write_nospace_2); + pos =3D cachefiles_inject_read_error(); if (pos =3D=3D 0) pos =3D vfs_llseek(file, start, SEEK_HOLE); @@ -704,8 +708,11 @@ int __cachefiles_prepare_write(struct cachefiles_objec= t *object, return ret; =20 check_space: - return cachefiles_has_space(cache, 0, *_len / cache->bsize, - cachefiles_has_space_for_write); + ret =3D cachefiles_has_space(cache, 0, *_len / cache->bsize, + cachefiles_has_space_for_write); + if (ret =3D=3D -ENOBUFS) + trace_cachefiles_no_space(object, cachefiles_trace_write_nospace); + return ret; } =20 static int cachefiles_prepare_write_old(struct netfs_cache_resources *cres, @@ -757,10 +764,14 @@ static int cachefiles_issue_write(struct netfs_io_sub= request *subreq) wreq->debug_id, subreq->debug_index, start, start + len - 1); =20 if (!cachefiles_cres_file(cres)) { - if (!fscache_wait_for_operation(cres, FSCACHE_WANT_WRITE)) + if (!fscache_wait_for_operation(cres, FSCACHE_WANT_WRITE)) { + trace_netfs_sreq(subreq, netfs_sreq_trace_cache_waitfail); return -ENOBUFS; - if (!cachefiles_cres_file(cres)) + } + if (!cachefiles_cres_file(cres)) { + trace_netfs_sreq(subreq, netfs_sreq_trace_cache_nofile); return -ENOBUFS; + } } =20 ret =3D netfs_prepare_write_buffer(subreq, BIO_MAX_VECS); diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index a30df0f91e5e..ef656a319ede 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -117,8 +117,11 @@ struct dentry *cachefiles_get_directory(struct cachefi= les_cache *cache, if (d_is_negative(subdir)) { ret =3D cachefiles_has_space(cache, 1, 0, cachefiles_has_space_for_create); - if (ret < 0) + if (ret < 0) { + if (ret =3D=3D -ENOBUFS) + trace_cachefiles_no_space(NULL, cachefiles_trace_mkdir_nospace); goto mkdir_error; + } =20 _debug("attempt mkdir"); =20 @@ -447,6 +450,11 @@ struct file *cachefiles_create_tmpfile(struct cachefil= es_object *object) pr_notice("Cache does not support read_iter and write_iter\n"); goto err_unuse; } + + /* Preallocate space for the xattr. */ + ret =3D cachefiles_preset_object_xattr(object, file); + if (ret < 0) + goto err_unuse; out: cachefiles_end_secure(cache, saved_cred); object->content_info =3D CACHEFILES_CONTENT_ALL; @@ -470,8 +478,11 @@ static bool cachefiles_create_file(struct cachefiles_o= bject *object) =20 ret =3D cachefiles_has_space(object->volume->cache, 1, 0, cachefiles_has_space_for_create); - if (ret < 0) + if (ret < 0) { + if (ret =3D=3D -ENOBUFS) + trace_cachefiles_no_space(object, cachefiles_trace_create_nospace); return false; + } =20 file =3D cachefiles_create_tmpfile(object); if (IS_ERR(file)) diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c index 5c1794beca93..9f78710bfe4d 100644 --- a/fs/cachefiles/xattr.c +++ b/fs/cachefiles/xattr.c @@ -34,6 +34,57 @@ struct cachefiles_vol_xattr { __u8 data[]; /* netfs volume coherency data */ } __packed; =20 +/* + * Preset the state xattr on a cache file to allocate space for it. + */ +int cachefiles_preset_object_xattr(struct cachefiles_object *object, struc= t file *file) +{ + struct cachefiles_xattr *buf; + struct dentry *dentry =3D file->f_path.dentry; + unsigned int len =3D object->cookie->aux_len; + int ret; + + buf =3D kzalloc(sizeof(struct cachefiles_xattr) + min(len, sizeof(__be64)= ), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf->type =3D CACHEFILES_COOKIE_TYPE_DATA; + buf->content =3D CACHEFILES_CONTENT_DIRTY; + + ret =3D cachefiles_inject_write_error(); + if (ret =3D=3D 0) { + ret =3D mnt_want_write_file(file); + if (ret =3D=3D 0) { + ret =3D vfs_setxattr(&nop_mnt_idmap, dentry, + cachefiles_xattr_cache, buf, + sizeof(struct cachefiles_xattr) + len, 0); + mnt_drop_write_file(file); + } + } + if (ret < 0) { + trace_cachefiles_vfs_error(object, file_inode(file), ret, + cachefiles_trace_setxattr_error); + trace_cachefiles_coherency(object, file_inode(file)->i_ino, + object->object_size, + buf->data, buf->content, + cachefiles_coherency_set_fail); + switch (ret) { + case -ENOMEM: + case -ENOSPC: + break; + default: + cachefiles_io_error_obj( + object, + "Failed to set xattr with error %d", ret); + break; + } + } + + kfree(buf); + _leave(" =3D %d", ret); + return ret; +} + /* * set the state xattr on a cache file */ @@ -82,10 +133,16 @@ int cachefiles_set_object_xattr(struct cachefiles_obje= ct *object) trace_cachefiles_coherency(object, file_inode(file)->i_ino, object_size, buf->data, buf->content, cachefiles_coherency_set_fail); - if (ret !=3D -ENOMEM) + switch (ret) { + case -ENOMEM: + break; + case -ENOSPC: + default: cachefiles_io_error_obj( object, "Failed to set xattr with error %d", ret); + break; + } } else { trace_cachefiles_coherency(object, file_inode(file)->i_ino, object_size, buf->data, buf->content, diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cache= files.h index 6146483300c2..3dca69126586 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -85,11 +85,13 @@ enum cachefiles_prepare_read_trace { =20 enum cachefiles_error_trace { cachefiles_trace_alignment_error, + cachefiles_trace_create_nospace, cachefiles_trace_fallocate_error, cachefiles_trace_getxattr_error, cachefiles_trace_link_error, cachefiles_trace_lookup_error, cachefiles_trace_mkdir_error, + cachefiles_trace_mkdir_nospace, cachefiles_trace_notify_change_error, cachefiles_trace_open_error, cachefiles_trace_read_error, @@ -102,6 +104,8 @@ enum cachefiles_error_trace { cachefiles_trace_trunc_error, cachefiles_trace_unlink_error, cachefiles_trace_write_error, + cachefiles_trace_write_nospace, + cachefiles_trace_write_nospace_2, }; =20 #endif @@ -171,11 +175,13 @@ enum cachefiles_error_trace { =20 #define cachefiles_error_traces \ EM(cachefiles_trace_alignment_error, "align") \ + EM(cachefiles_trace_create_nospace, "create-nospace") \ EM(cachefiles_trace_fallocate_error, "fallocate") \ EM(cachefiles_trace_getxattr_error, "getxattr") \ EM(cachefiles_trace_link_error, "link") \ EM(cachefiles_trace_lookup_error, "lookup") \ EM(cachefiles_trace_mkdir_error, "mkdir") \ + EM(cachefiles_trace_mkdir_nospace, "mkdir-nospace") \ EM(cachefiles_trace_notify_change_error, "notify_change") \ EM(cachefiles_trace_open_error, "open") \ EM(cachefiles_trace_read_error, "read") \ @@ -187,7 +193,9 @@ enum cachefiles_error_trace { EM(cachefiles_trace_tmpfile_error, "tmpfile") \ EM(cachefiles_trace_trunc_error, "trunc") \ EM(cachefiles_trace_unlink_error, "unlink") \ - E_(cachefiles_trace_write_error, "write") + EM(cachefiles_trace_write_error, "write") \ + EM(cachefiles_trace_write_nospace, "write-nospace") \ + E_(cachefiles_trace_write_nospace_2, "write-nospace-2") =20 =20 /* @@ -707,6 +715,26 @@ TRACE_EVENT(cachefiles_io_error, __entry->error) ); =20 +TRACE_EVENT(cachefiles_no_space, + TP_PROTO(struct cachefiles_object *obj, enum cachefiles_error_trace t= race), + + TP_ARGS(obj, trace), + + TP_STRUCT__entry( + __field(unsigned int, obj) + __field(enum cachefiles_error_trace, trace) + ), + + TP_fast_assign( + __entry->obj =3D obj ? obj->debug_id : 0; + __entry->trace =3D trace; + ), + + TP_printk("o=3D%08x %s", + __entry->obj, + __print_symbolic(__entry->trace, cachefiles_error_traces)) + ); + #endif /* _TRACE_CACHEFILES_H */ =20 /* This part must be outside protection */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index dfe08492d1ad..54e90694eda7 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -102,8 +102,10 @@ EM(netfs_sreq_trace_abandoned, "ABNDN") \ EM(netfs_sreq_trace_add_donations, "+DON ") \ EM(netfs_sreq_trace_added, "ADD ") \ + EM(netfs_sreq_trace_cache_nofile, "CA-!F") \ EM(netfs_sreq_trace_cache_nowrite, "CA-NW") \ EM(netfs_sreq_trace_cache_prepare, "CA-PR") \ + EM(netfs_sreq_trace_cache_waitfail, "CA-!W") \ EM(netfs_sreq_trace_cache_write, "CA-WR") \ EM(netfs_sreq_trace_cancel, "CANCL") \ EM(netfs_sreq_trace_clear, "CLEAR") \