| // SPDX-License-Identifier: GPL-2.0-only |
| /* Read with PG_private_2 [DEPRECATED]. |
| * |
| * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved. |
| * Written by David Howells (dhowells@redhat.com) |
| */ |
| |
| #include <linux/export.h> |
| #include <linux/fs.h> |
| #include <linux/mm.h> |
| #include <linux/pagemap.h> |
| #include <linux/slab.h> |
| #include <linux/task_io_accounting_ops.h> |
| #include "internal.h" |
| |
| int netfs_prepare_pgpriv2_write_buffer(struct netfs_io_subrequest *subreq, |
| unsigned int max_segs) |
| { |
| struct netfs_io_request *creq = subreq->rreq; |
| struct netfs_io_stream *stream = &creq->io_streams[1]; |
| size_t len; |
| |
| bvecq_pos_set(&subreq->io_buffer, &stream->dispatch_cursor); |
| len = bvecq_slice(&stream->dispatch_cursor, subreq->len, max_segs, |
| &subreq->nr_segs); |
| |
| if (len < subreq->len) { |
| subreq->len = len; |
| trace_netfs_sreq(subreq, netfs_sreq_trace_limited); |
| } |
| |
| // TODO: Wait here for completion of prev subreq |
| |
| stream->issue_from += subreq->len; |
| stream->buffered -= subreq->len; |
| if (stream->buffered == 0) |
| netfs_all_subreqs_queued(creq); |
| return 0; |
| } |
| |
| /* |
| * [DEPRECATED] Copy a folio to the cache with PG_private_2 set. Note that the |
| * folio won't necessarily be contiguous with the previous one as there might |
| * be a mixture of folios read from the cache and downloaded from the server |
| * (or just zeroed). |
| */ |
| static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio *folio) |
| { |
| struct netfs_io_stream *cache = &creq->io_streams[1]; |
| struct bvecq *queue; |
| unsigned int slot; |
| size_t dio_size = PAGE_SIZE; |
| size_t fsize = folio_size(folio), flen = fsize; |
| uoff_t fpos = folio_pos(folio), i_size; |
| |
| _enter(""); |
| |
| /* 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. |
| */ |
| i_size = i_size_read(creq->inode); |
| |
| if (fpos >= i_size) { |
| /* mmap beyond eof. */ |
| _debug("beyond eof"); |
| folio_end_private_2(folio); |
| return; |
| } |
| |
| if (fpos + fsize > creq->i_size) |
| creq->i_size = i_size; |
| |
| if (flen > i_size - fpos) |
| flen = i_size - fpos; |
| |
| flen = round_up(flen, dio_size); |
| |
| _debug("folio %zx %zx", flen, fsize); |
| |
| trace_netfs_folio(folio, netfs_folio_trace_store_copy); |
| |
| /* 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 = creq->load_cursor.bvecq; |
| if (bvecq_is_full(queue) || |
| (fpos != cache->last_end && cache->last_end > 0 && queue->nr_slots > 0)) { |
| bvecq_buffer_append(&creq->load_cursor, creq->spare); |
| creq->spare = NULL; |
| |
| queue = creq->load_cursor.bvecq; |
| } |
| |
| /* Attach the folio to the rolling buffer. */ |
| slot = 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 = slot; |
| creq->load_cursor.offset = 0; |
| cache->last_end = fpos + flen; |
| trace_netfs_wback(creq, folio, 0); |
| |
| cache->buffered += flen; |
| } |
| |
| /* |
| * [DEPRECATED] Set up copying to the cache. |
| */ |
| static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache( |
| struct netfs_io_request *rreq, struct folio *folio) |
| { |
| struct netfs_io_request *creq; |
| struct netfs_io_stream *cache; |
| |
| if (!fscache_resources_valid(&rreq->cache_resources)) |
| goto cancel; |
| |
| creq = netfs_create_write_req(rreq->mapping, NULL, folio_pos(folio), |
| NETFS_PGPRIV2_COPY_TO_CACHE); |
| if (IS_ERR(creq)) |
| goto cancel; |
| |
| cache = &creq->io_streams[1]; |
| if (!cache->avail) |
| goto cancel_put; |
| |
| if (bvecq_buffer_init(&creq->load_cursor, creq->gfp, false) < 0) |
| goto cancel_put; |
| bvecq_pos_set(&cache->dispatch_cursor, &creq->load_cursor); |
| bvecq_pos_set(&creq->collect_cursor, &creq->load_cursor); |
| |
| __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &creq->flags); |
| trace_netfs_copy2cache(rreq, creq); |
| trace_netfs_write(creq, netfs_write_trace_copy_to_cache); |
| netfs_stat(&netfs_n_wh_copy_to_cache); |
| rreq->copy_to_cache = creq; |
| return creq; |
| |
| cancel_put: |
| netfs_put_failed_request(creq); |
| cancel: |
| rreq->copy_to_cache = ERR_PTR(-ENOBUFS); |
| set_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags); |
| return ERR_PTR(-ENOBUFS); |
| } |
| |
| /* |
| * [DEPRECATED] Mark page as requiring copy-to-cache using PG_private_2 and add |
| * it to the copy write request. PG_private_2 should already be set on the |
| * folio. |
| */ |
| void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) |
| { |
| struct netfs_io_request *creq = rreq->copy_to_cache; |
| |
| if (!creq) |
| creq = netfs_pgpriv2_begin_copy_to_cache(rreq, folio); |
| if (IS_ERR(creq)) { |
| set_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags); |
| netfs_cancel_copy_to_cache(rreq, folio); |
| return; |
| } |
| |
| if (!creq->spare) { |
| creq->spare = bvecq_alloc_one(BVECQ_POOL_SLOTS, creq->gfp, false); |
| if (!creq->spare) { |
| set_bit(NETFS_RREQ_CANCEL_CACHING, &creq->flags); |
| netfs_cancel_copy_to_cache(rreq, folio); |
| return; |
| } |
| } |
| |
| trace_netfs_folio(folio, netfs_folio_trace_pgpriv2_copy); |
| netfs_pgpriv2_copy_folio(creq, folio); |
| } |
| |
| /* |
| * 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 = netfs_alloc_write_subreq(wreq, stream); |
| if (!subreq) |
| break; |
| |
| ret = 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 -= 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 writes. |
| */ |
| void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq) |
| { |
| struct netfs_io_request *creq = rreq->copy_to_cache; |
| struct netfs_io_stream *stream = &creq->io_streams[1]; |
| |
| if (IS_ERR_OR_NULL(creq)) |
| return; |
| |
| 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); |
| |
| netfs_put_request(creq, netfs_rreq_trace_put_return); |
| rreq->copy_to_cache = NULL; |
| } |
| |
| /* |
| * [DEPRECATED] Remove the PG_private_2 mark from any folios we've finished |
| * copying. |
| */ |
| bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) |
| { |
| struct bvecq *bq = creq->collect_cursor.bvecq; |
| unsigned int slot; |
| uoff_t collected_to = creq->collected_to; |
| bool made_progress = false; |
| |
| slot = creq->collect_cursor.slot; |
| while (!bvecq_acquire_slot(bq, slot)) { |
| creq->collect_cursor.slot = slot; |
| if (!bvecq_delete_spent(&creq->collect_cursor)) |
| goto out; |
| bq = creq->collect_cursor.bvecq; |
| slot = creq->collect_cursor.slot; |
| } |
| |
| for (;;) { |
| struct folio *folio; |
| uoff_t fpos, fend; |
| size_t fsize, flen; |
| |
| folio = bvec_folio(&bq->bv[slot]); |
| if (WARN_ONCE(!folio_test_private_2(folio), |
| "R=%08x: folio %lx is not marked private_2\n", |
| creq->debug_id, folio->index)) |
| trace_netfs_folio(folio, netfs_folio_trace_not_under_wback); |
| |
| fpos = folio_pos(folio); |
| fsize = folio_size(folio); |
| flen = fsize; |
| |
| fend = min_t(uoff_t, fpos + flen, creq->i_size); |
| |
| trace_netfs_collect_folio(creq, folio); |
| |
| /* Unlock any folio we've transferred all of. */ |
| if (collected_to < fend) |
| break; |
| |
| trace_netfs_folio(folio, netfs_folio_trace_end_copy); |
| folio_end_private_2(folio); |
| creq->cleaned_to = fpos + fsize; |
| made_progress = true; |
| |
| /* 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. |
| */ |
| bq->bv[slot].bv_page = NULL; |
| slot++; |
| while (!bvecq_acquire_slot(bq, slot)) { |
| creq->collect_cursor.slot = slot; |
| if (!bvecq_delete_spent(&creq->collect_cursor)) |
| goto out; |
| bq = creq->collect_cursor.bvecq; |
| slot = creq->collect_cursor.slot; |
| } |
| |
| if (fpos + fsize >= collected_to) |
| break; |
| } |
| |
| creq->collect_cursor.slot = slot; |
| out: |
| return made_progress; |
| } |