blob: f7d57247197b09897d0861803e17c56faa73dcd0 [file] [edit]
// 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;
}