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