xref: /openbmc/linux/fs/netfs/iterator.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
185dd2c8fSDavid Howells // SPDX-License-Identifier: GPL-2.0-or-later
285dd2c8fSDavid Howells /* Iterator helpers.
385dd2c8fSDavid Howells  *
485dd2c8fSDavid Howells  * Copyright (C) 2022 Red Hat, Inc. All Rights Reserved.
585dd2c8fSDavid Howells  * Written by David Howells (dhowells@redhat.com)
685dd2c8fSDavid Howells  */
785dd2c8fSDavid Howells 
885dd2c8fSDavid Howells #include <linux/export.h>
985dd2c8fSDavid Howells #include <linux/slab.h>
1001858469SDavid Howells #include <linux/mm.h>
1185dd2c8fSDavid Howells #include <linux/uio.h>
1201858469SDavid Howells #include <linux/scatterlist.h>
1385dd2c8fSDavid Howells #include <linux/netfs.h>
1485dd2c8fSDavid Howells #include "internal.h"
1585dd2c8fSDavid Howells 
1685dd2c8fSDavid Howells /**
1785dd2c8fSDavid Howells  * netfs_extract_user_iter - Extract the pages from a user iterator into a bvec
1885dd2c8fSDavid Howells  * @orig: The original iterator
1985dd2c8fSDavid Howells  * @orig_len: The amount of iterator to copy
2085dd2c8fSDavid Howells  * @new: The iterator to be set up
2185dd2c8fSDavid Howells  * @extraction_flags: Flags to qualify the request
2285dd2c8fSDavid Howells  *
2385dd2c8fSDavid Howells  * Extract the page fragments from the given amount of the source iterator and
2485dd2c8fSDavid Howells  * build up a second iterator that refers to all of those bits.  This allows
2585dd2c8fSDavid Howells  * the original iterator to disposed of.
2685dd2c8fSDavid Howells  *
2785dd2c8fSDavid Howells  * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-peer DMA be
2885dd2c8fSDavid Howells  * allowed on the pages extracted.
2985dd2c8fSDavid Howells  *
3085dd2c8fSDavid Howells  * On success, the number of elements in the bvec is returned, the original
3185dd2c8fSDavid Howells  * iterator will have been advanced by the amount extracted.
3285dd2c8fSDavid Howells  *
3385dd2c8fSDavid Howells  * The iov_iter_extract_mode() function should be used to query how cleanup
3485dd2c8fSDavid Howells  * should be performed.
3585dd2c8fSDavid Howells  */
netfs_extract_user_iter(struct iov_iter * orig,size_t orig_len,struct iov_iter * new,iov_iter_extraction_t extraction_flags)3685dd2c8fSDavid Howells ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
3785dd2c8fSDavid Howells 				struct iov_iter *new,
3885dd2c8fSDavid Howells 				iov_iter_extraction_t extraction_flags)
3985dd2c8fSDavid Howells {
4085dd2c8fSDavid Howells 	struct bio_vec *bv = NULL;
4185dd2c8fSDavid Howells 	struct page **pages;
4285dd2c8fSDavid Howells 	unsigned int cur_npages;
4385dd2c8fSDavid Howells 	unsigned int max_pages;
4485dd2c8fSDavid Howells 	unsigned int npages = 0;
4585dd2c8fSDavid Howells 	unsigned int i;
4685dd2c8fSDavid Howells 	ssize_t ret;
4785dd2c8fSDavid Howells 	size_t count = orig_len, offset, len;
4885dd2c8fSDavid Howells 	size_t bv_size, pg_size;
4985dd2c8fSDavid Howells 
5085dd2c8fSDavid Howells 	if (WARN_ON_ONCE(!iter_is_ubuf(orig) && !iter_is_iovec(orig)))
5185dd2c8fSDavid Howells 		return -EIO;
5285dd2c8fSDavid Howells 
5385dd2c8fSDavid Howells 	max_pages = iov_iter_npages(orig, INT_MAX);
5485dd2c8fSDavid Howells 	bv_size = array_size(max_pages, sizeof(*bv));
5585dd2c8fSDavid Howells 	bv = kvmalloc(bv_size, GFP_KERNEL);
5685dd2c8fSDavid Howells 	if (!bv)
5785dd2c8fSDavid Howells 		return -ENOMEM;
5885dd2c8fSDavid Howells 
5985dd2c8fSDavid Howells 	/* Put the page list at the end of the bvec list storage.  bvec
6085dd2c8fSDavid Howells 	 * elements are larger than page pointers, so as long as we work
6185dd2c8fSDavid Howells 	 * 0->last, we should be fine.
6285dd2c8fSDavid Howells 	 */
6385dd2c8fSDavid Howells 	pg_size = array_size(max_pages, sizeof(*pages));
6485dd2c8fSDavid Howells 	pages = (void *)bv + bv_size - pg_size;
6585dd2c8fSDavid Howells 
6685dd2c8fSDavid Howells 	while (count && npages < max_pages) {
6785dd2c8fSDavid Howells 		ret = iov_iter_extract_pages(orig, &pages, count,
6885dd2c8fSDavid Howells 					     max_pages - npages, extraction_flags,
6985dd2c8fSDavid Howells 					     &offset);
7085dd2c8fSDavid Howells 		if (ret < 0) {
7185dd2c8fSDavid Howells 			pr_err("Couldn't get user pages (rc=%zd)\n", ret);
7285dd2c8fSDavid Howells 			break;
7385dd2c8fSDavid Howells 		}
7485dd2c8fSDavid Howells 
7585dd2c8fSDavid Howells 		if (ret > count) {
7685dd2c8fSDavid Howells 			pr_err("get_pages rc=%zd more than %zu\n", ret, count);
7785dd2c8fSDavid Howells 			break;
7885dd2c8fSDavid Howells 		}
7985dd2c8fSDavid Howells 
8085dd2c8fSDavid Howells 		count -= ret;
8185dd2c8fSDavid Howells 		ret += offset;
8285dd2c8fSDavid Howells 		cur_npages = DIV_ROUND_UP(ret, PAGE_SIZE);
8385dd2c8fSDavid Howells 
8485dd2c8fSDavid Howells 		if (npages + cur_npages > max_pages) {
8585dd2c8fSDavid Howells 			pr_err("Out of bvec array capacity (%u vs %u)\n",
8685dd2c8fSDavid Howells 			       npages + cur_npages, max_pages);
8785dd2c8fSDavid Howells 			break;
8885dd2c8fSDavid Howells 		}
8985dd2c8fSDavid Howells 
9085dd2c8fSDavid Howells 		for (i = 0; i < cur_npages; i++) {
9185dd2c8fSDavid Howells 			len = ret > PAGE_SIZE ? PAGE_SIZE : ret;
92307e14c0SLinus Torvalds 			bvec_set_page(bv + npages + i, *pages++, len - offset, offset);
9385dd2c8fSDavid Howells 			ret -= len;
9485dd2c8fSDavid Howells 			offset = 0;
9585dd2c8fSDavid Howells 		}
9685dd2c8fSDavid Howells 
9785dd2c8fSDavid Howells 		npages += cur_npages;
9885dd2c8fSDavid Howells 	}
9985dd2c8fSDavid Howells 
10085dd2c8fSDavid Howells 	iov_iter_bvec(new, orig->data_source, bv, npages, orig_len - count);
10185dd2c8fSDavid Howells 	return npages;
10285dd2c8fSDavid Howells }
10385dd2c8fSDavid Howells EXPORT_SYMBOL_GPL(netfs_extract_user_iter);
10401858469SDavid Howells