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