summaryrefslogtreecommitdiffstats
path: root/fs/netfs/direct_write.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-23 13:28:57 -0700
commit4352b8aee98005853aa63f57d6377282de17a33f (patch)
tree27614d7a9ef05c32d089004e104364ce42324837 /fs/netfs/direct_write.c
downloadlinux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz
linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ...
Diffstat (limited to 'fs/netfs/direct_write.c')
-rw-r--r--fs/netfs/direct_write.c387
1 files changed, 387 insertions, 0 deletions
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
new file mode 100644
index 000000000..c16fbad28
--- /dev/null
+++ b/fs/netfs/direct_write.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Unbuffered and direct write support.
+ *
+ * Copyright (C) 2023 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/export.h>
+#include <linux/uio.h>
+#include "internal.h"
+
+/*
+ * Perform the cleanup rituals after an unbuffered write is complete.
+ */
+static void netfs_unbuffered_write_done(struct netfs_io_request *wreq)
+{
+ struct netfs_inode *ictx = netfs_inode(wreq->inode);
+
+ _enter("R=%x", wreq->debug_id);
+
+ /* Okay, declare that all I/O is complete. */
+ trace_netfs_rreq(wreq, netfs_rreq_trace_write_done);
+
+ if (!wreq->error)
+ netfs_update_i_size(ictx, &ictx->inode, wreq->start, wreq->transferred);
+
+ if (wreq->origin == NETFS_DIO_WRITE &&
+ wreq->mapping->nrpages) {
+ /* mmap may have got underfoot and we may now have folios
+ * locally covering the region we just wrote. Attempt to
+ * discard the folios, but leave in place any modified locally.
+ * ->write_iter() is prevented from interfering by the DIO
+ * counter.
+ */
+ pgoff_t first = wreq->start >> PAGE_SHIFT;
+ pgoff_t last = (wreq->start + wreq->transferred - 1) >> PAGE_SHIFT;
+
+ invalidate_inode_pages2_range(wreq->mapping, first, last);
+ }
+
+ if (wreq->origin == NETFS_DIO_WRITE)
+ inode_dio_end(wreq->inode);
+
+ _debug("finished");
+ netfs_wake_rreq_flag(wreq, NETFS_RREQ_IN_PROGRESS, netfs_rreq_trace_wake_ip);
+ /* As we cleared NETFS_RREQ_IN_PROGRESS, we acquired its ref. */
+
+ if (wreq->iocb) {
+ size_t written = umin(wreq->transferred, wreq->len);
+
+ wreq->iocb->ki_pos += written;
+ if (wreq->iocb->ki_complete) {
+ trace_netfs_rreq(wreq, netfs_rreq_trace_ki_complete);
+ wreq->iocb->ki_complete(wreq->iocb, wreq->error ?: written);
+ }
+ wreq->iocb = VFS_PTR_POISON;
+ }
+
+ netfs_clear_subrequests(wreq);
+}
+
+/*
+ * Collect the subrequest results of unbuffered write subrequests.
+ */
+static void netfs_unbuffered_write_collect(struct netfs_io_request *wreq,
+ struct netfs_io_stream *stream,
+ struct netfs_io_subrequest *subreq)
+{
+ trace_netfs_collect_sreq(wreq, subreq);
+
+ spin_lock(&wreq->lock);
+ list_del_init(&subreq->rreq_link);
+ spin_unlock(&wreq->lock);
+
+ wreq->transferred += subreq->transferred;
+ iov_iter_advance(&wreq->buffer.iter, subreq->transferred);
+
+ stream->collected_to = subreq->start + subreq->transferred;
+ wreq->collected_to = stream->collected_to;
+ netfs_put_subrequest(subreq, netfs_sreq_trace_put_done);
+
+ trace_netfs_collect_stream(wreq, stream);
+ trace_netfs_collect_state(wreq, wreq->collected_to, 0);
+}
+
+/*
+ * Write data to the server without going through the pagecache and without
+ * writing it to the local cache. We dispatch the subrequests serially and
+ * wait for each to complete before dispatching the next, lest we leave a gap
+ * in the data written due to a failure such as ENOSPC. We could, however
+ * attempt to do preparation such as content encryption for the next subreq
+ * whilst the current is in progress.
+ */
+static int netfs_unbuffered_write(struct netfs_io_request *wreq)
+{
+ struct netfs_io_subrequest *subreq = NULL;
+ struct netfs_io_stream *stream = &wreq->io_streams[0];
+ int ret;
+
+ _enter("%llx", wreq->len);
+
+ if (wreq->origin == NETFS_DIO_WRITE)
+ inode_dio_begin(wreq->inode);
+
+ stream->collected_to = wreq->start;
+
+ for (;;) {
+ bool retry = false;
+
+ if (!subreq) {
+ netfs_prepare_write(wreq, stream, wreq->start + wreq->transferred);
+ subreq = stream->construct;
+ stream->construct = NULL;
+ }
+
+ /* Check if (re-)preparation failed. */
+ if (unlikely(test_bit(NETFS_SREQ_FAILED, &subreq->flags))) {
+ netfs_write_subrequest_terminated(subreq, subreq->error);
+ wreq->error = subreq->error;
+ break;
+ }
+
+ iov_iter_truncate(&subreq->io_iter, wreq->len - wreq->transferred);
+ if (!iov_iter_count(&subreq->io_iter))
+ break;
+
+ subreq->len = 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 = subreq->len;
+
+ 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)) {
+ retry = true;
+ } else if (test_bit(NETFS_SREQ_FAILED, &subreq->flags)) {
+ ret = subreq->error;
+ wreq->error = ret;
+ netfs_see_subrequest(subreq, netfs_sreq_trace_see_failed);
+ subreq = NULL;
+ break;
+ }
+ ret = 0;
+
+ if (!retry) {
+ netfs_unbuffered_write_collect(wreq, stream, subreq);
+ subreq = NULL;
+ if (wreq->transferred >= wreq->len)
+ break;
+ if (!wreq->iocb && signal_pending(current)) {
+ ret = wreq->transferred ? -EINTR : -ERESTARTSYS;
+ trace_netfs_rreq(wreq, netfs_rreq_trace_intr);
+ break;
+ }
+ continue;
+ }
+
+ /* We need to retry the last subrequest, so first reset the
+ * iterator, taking into account what, if anything, we managed
+ * to transfer.
+ */
+ subreq->error = -EAGAIN;
+ trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
+ if (subreq->transferred > 0) {
+ iov_iter_advance(&wreq->buffer.iter, subreq->transferred);
+ wreq->transferred += subreq->transferred;
+ }
+
+ if (stream->source == NETFS_UPLOAD_TO_SERVER &&
+ wreq->netfs_ops->retry_request)
+ wreq->netfs_ops->retry_request(wreq, stream);
+
+ __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->io_iter = wreq->buffer.iter;
+ subreq->start = wreq->start + wreq->transferred;
+ subreq->len = wreq->len - wreq->transferred;
+ subreq->transferred = 0;
+ subreq->retry_count += 1;
+ stream->sreq_max_len = UINT_MAX;
+ stream->sreq_max_segs = INT_MAX;
+
+ netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
+
+ if (stream->prepare_write)
+ stream->prepare_write(subreq);
+ __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
+ netfs_stat(&netfs_n_wh_retry_write_subreq);
+ }
+
+ netfs_unbuffered_write_done(wreq);
+ _leave(" = %d", ret);
+ return ret;
+}
+
+static void netfs_unbuffered_write_async(struct work_struct *work)
+{
+ struct netfs_io_request *wreq = container_of(work, struct netfs_io_request, work);
+
+ netfs_unbuffered_write(wreq);
+ netfs_put_request(wreq, netfs_rreq_trace_put_complete);
+}
+
+/*
+ * Perform an unbuffered write where we may have to do an RMW operation on an
+ * 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_io_request *wreq;
+ unsigned long long start = iocb->ki_pos;
+ unsigned long long end = start + iov_iter_count(iter);
+ ssize_t ret, n;
+ size_t len = iov_iter_count(iter);
+ bool async = !is_sync_kiocb(iocb);
+
+ _enter("");
+
+ /* We're going to need a bounce buffer if what we transmit is going to
+ * be different in some way to the source buffer, e.g. because it gets
+ * encrypted/compressed or because it needs expanding to a block size.
+ */
+ // TODO
+
+ _debug("uw %llx-%llx", start, end);
+
+ wreq = netfs_create_write_req(iocb->ki_filp->f_mapping, iocb->ki_filp, start,
+ iocb->ki_flags & IOCB_DIRECT ?
+ NETFS_DIO_WRITE : NETFS_UNBUFFERED_WRITE);
+ if (IS_ERR(wreq))
+ return PTR_ERR(wreq);
+
+ wreq->io_streams[0].avail = true;
+ trace_netfs_write(wreq, (iocb->ki_flags & IOCB_DIRECT ?
+ netfs_write_trace_dio_write :
+ netfs_write_trace_unbuffered_write));
+
+ {
+ /* If this is an async op and we're not using a bounce buffer,
+ * 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.
+ */
+ if (user_backed_iter(iter)) {
+ n = netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0);
+ if (n < 0) {
+ ret = n;
+ goto error_put;
+ }
+ wreq->direct_bv = (struct bio_vec *)wreq->buffer.iter.bvec;
+ wreq->direct_bv_count = n;
+ wreq->direct_bv_unpin = 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 = *iter;
+ }
+
+ wreq->len = iov_iter_count(&wreq->buffer.iter);
+ }
+
+ __set_bit(NETFS_RREQ_USE_IO_ITER, &wreq->flags);
+
+ /* Copy the data into the bounce buffer and encrypt it. */
+ // TODO
+
+ /* Dispatch the write. */
+ __set_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags);
+
+ if (async) {
+ INIT_WORK(&wreq->work, netfs_unbuffered_write_async);
+ wreq->iocb = iocb;
+ queue_work(system_dfl_wq, &wreq->work);
+ ret = -EIOCBQUEUED;
+ } else {
+ ret = netfs_unbuffered_write(wreq);
+ if (ret < 0) {
+ _debug("begin = %zd", ret);
+ } else {
+ iocb->ki_pos += wreq->transferred;
+ ret = wreq->transferred ?: wreq->error;
+ }
+
+ netfs_put_request(wreq, netfs_rreq_trace_put_complete);
+ }
+
+ netfs_put_request(wreq, netfs_rreq_trace_put_return);
+ return ret;
+
+error_put:
+ netfs_put_failed_request(wreq);
+ return ret;
+}
+EXPORT_SYMBOL(netfs_unbuffered_write_iter_locked);
+
+/**
+ * netfs_unbuffered_write_iter - Unbuffered write to a file
+ * @iocb: IO state structure
+ * @from: iov_iter with data to write
+ *
+ * Do an unbuffered write to a file, writing the data directly to the server
+ * and not lodging the data in the pagecache.
+ *
+ * Return:
+ * * Negative error code if no data has been written at all of
+ * vfs_fsync_range() failed for a synchronous write
+ * * Number of bytes written, even for truncated writes
+ */
+ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+ struct file *file = iocb->ki_filp;
+ struct address_space *mapping = file->f_mapping;
+ struct inode *inode = mapping->host;
+ struct netfs_inode *ictx = netfs_inode(inode);
+ ssize_t ret;
+ loff_t pos = iocb->ki_pos;
+ unsigned long long end = pos + iov_iter_count(from) - 1;
+
+ _enter("%llx,%zx,%llx", pos, iov_iter_count(from), i_size_read(inode));
+
+ if (!iov_iter_count(from))
+ return 0;
+
+ trace_netfs_write_iter(iocb, from);
+ netfs_stat(&netfs_n_wh_dio_write);
+
+ ret = netfs_start_io_direct(inode);
+ if (ret < 0)
+ return ret;
+ ret = generic_write_checks(iocb, from);
+ if (ret <= 0)
+ goto out;
+ ret = file_remove_privs(file);
+ if (ret < 0)
+ goto out;
+ ret = file_update_time(file);
+ if (ret < 0)
+ goto out;
+ if (iocb->ki_flags & IOCB_NOWAIT) {
+ /* We could block if there are any pages in the range. */
+ ret = -EAGAIN;
+ if (filemap_range_has_page(mapping, pos, end))
+ if (filemap_invalidate_inode(inode, true, pos, end))
+ goto out;
+ } else {
+ ret = filemap_write_and_wait_range(mapping, pos, end);
+ if (ret < 0)
+ goto out;
+ }
+
+ /*
+ * After a write we want buffered reads to be sure to go to disk to get
+ * the new data. We invalidate clean cached page from the region we're
+ * about to write. We do this *before* the write so that we can return
+ * without clobbering -EIOCBQUEUED from ->direct_IO().
+ */
+ ret = filemap_invalidate_inode(inode, true, pos, end);
+ if (ret < 0)
+ goto out;
+ end = iocb->ki_pos + iov_iter_count(from);
+ spin_lock(&inode->i_lock);
+ if (end > ictx->_zero_point)
+ netfs_write_zero_point(inode, end);
+ spin_unlock(&inode->i_lock);
+
+ fscache_invalidate(netfs_i_cookie(ictx), NULL, i_size_read(inode),
+ FSCACHE_INVAL_DIO_WRITE);
+ ret = netfs_unbuffered_write_iter_locked(iocb, from, NULL);
+out:
+ netfs_end_io_direct(inode);
+ return ret;
+}
+EXPORT_SYMBOL(netfs_unbuffered_write_iter);