162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* Iterator helpers.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2022 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/export.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/mm.h>
1162306a36Sopenharmony_ci#include <linux/uio.h>
1262306a36Sopenharmony_ci#include <linux/scatterlist.h>
1362306a36Sopenharmony_ci#include <linux/netfs.h>
1462306a36Sopenharmony_ci#include "internal.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/**
1762306a36Sopenharmony_ci * netfs_extract_user_iter - Extract the pages from a user iterator into a bvec
1862306a36Sopenharmony_ci * @orig: The original iterator
1962306a36Sopenharmony_ci * @orig_len: The amount of iterator to copy
2062306a36Sopenharmony_ci * @new: The iterator to be set up
2162306a36Sopenharmony_ci * @extraction_flags: Flags to qualify the request
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * Extract the page fragments from the given amount of the source iterator and
2462306a36Sopenharmony_ci * build up a second iterator that refers to all of those bits.  This allows
2562306a36Sopenharmony_ci * the original iterator to disposed of.
2662306a36Sopenharmony_ci *
2762306a36Sopenharmony_ci * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-peer DMA be
2862306a36Sopenharmony_ci * allowed on the pages extracted.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * On success, the number of elements in the bvec is returned, the original
3162306a36Sopenharmony_ci * iterator will have been advanced by the amount extracted.
3262306a36Sopenharmony_ci *
3362306a36Sopenharmony_ci * The iov_iter_extract_mode() function should be used to query how cleanup
3462306a36Sopenharmony_ci * should be performed.
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_cissize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
3762306a36Sopenharmony_ci				struct iov_iter *new,
3862306a36Sopenharmony_ci				iov_iter_extraction_t extraction_flags)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct bio_vec *bv = NULL;
4162306a36Sopenharmony_ci	struct page **pages;
4262306a36Sopenharmony_ci	unsigned int cur_npages;
4362306a36Sopenharmony_ci	unsigned int max_pages;
4462306a36Sopenharmony_ci	unsigned int npages = 0;
4562306a36Sopenharmony_ci	unsigned int i;
4662306a36Sopenharmony_ci	ssize_t ret;
4762306a36Sopenharmony_ci	size_t count = orig_len, offset, len;
4862306a36Sopenharmony_ci	size_t bv_size, pg_size;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	if (WARN_ON_ONCE(!iter_is_ubuf(orig) && !iter_is_iovec(orig)))
5162306a36Sopenharmony_ci		return -EIO;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	max_pages = iov_iter_npages(orig, INT_MAX);
5462306a36Sopenharmony_ci	bv_size = array_size(max_pages, sizeof(*bv));
5562306a36Sopenharmony_ci	bv = kvmalloc(bv_size, GFP_KERNEL);
5662306a36Sopenharmony_ci	if (!bv)
5762306a36Sopenharmony_ci		return -ENOMEM;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	/* Put the page list at the end of the bvec list storage.  bvec
6062306a36Sopenharmony_ci	 * elements are larger than page pointers, so as long as we work
6162306a36Sopenharmony_ci	 * 0->last, we should be fine.
6262306a36Sopenharmony_ci	 */
6362306a36Sopenharmony_ci	pg_size = array_size(max_pages, sizeof(*pages));
6462306a36Sopenharmony_ci	pages = (void *)bv + bv_size - pg_size;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	while (count && npages < max_pages) {
6762306a36Sopenharmony_ci		ret = iov_iter_extract_pages(orig, &pages, count,
6862306a36Sopenharmony_ci					     max_pages - npages, extraction_flags,
6962306a36Sopenharmony_ci					     &offset);
7062306a36Sopenharmony_ci		if (ret < 0) {
7162306a36Sopenharmony_ci			pr_err("Couldn't get user pages (rc=%zd)\n", ret);
7262306a36Sopenharmony_ci			break;
7362306a36Sopenharmony_ci		}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci		if (ret > count) {
7662306a36Sopenharmony_ci			pr_err("get_pages rc=%zd more than %zu\n", ret, count);
7762306a36Sopenharmony_ci			break;
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci		count -= ret;
8162306a36Sopenharmony_ci		ret += offset;
8262306a36Sopenharmony_ci		cur_npages = DIV_ROUND_UP(ret, PAGE_SIZE);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci		if (npages + cur_npages > max_pages) {
8562306a36Sopenharmony_ci			pr_err("Out of bvec array capacity (%u vs %u)\n",
8662306a36Sopenharmony_ci			       npages + cur_npages, max_pages);
8762306a36Sopenharmony_ci			break;
8862306a36Sopenharmony_ci		}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		for (i = 0; i < cur_npages; i++) {
9162306a36Sopenharmony_ci			len = ret > PAGE_SIZE ? PAGE_SIZE : ret;
9262306a36Sopenharmony_ci			bvec_set_page(bv + npages + i, *pages++, len - offset, offset);
9362306a36Sopenharmony_ci			ret -= len;
9462306a36Sopenharmony_ci			offset = 0;
9562306a36Sopenharmony_ci		}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci		npages += cur_npages;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	iov_iter_bvec(new, orig->data_source, bv, npages, orig_len - count);
10162306a36Sopenharmony_ci	return npages;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(netfs_extract_user_iter);
104