162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2015 Robert Jarzmik <robert.jarzmik@free.fr> 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Scatterlist splitting helpers. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/scatterlist.h> 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_cistruct sg_splitter { 1262306a36Sopenharmony_ci struct scatterlist *in_sg0; 1362306a36Sopenharmony_ci int nents; 1462306a36Sopenharmony_ci off_t skip_sg0; 1562306a36Sopenharmony_ci unsigned int length_last_sg; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci struct scatterlist *out_sg; 1862306a36Sopenharmony_ci}; 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cistatic int sg_calculate_split(struct scatterlist *in, int nents, int nb_splits, 2162306a36Sopenharmony_ci off_t skip, const size_t *sizes, 2262306a36Sopenharmony_ci struct sg_splitter *splitters, bool mapped) 2362306a36Sopenharmony_ci{ 2462306a36Sopenharmony_ci int i; 2562306a36Sopenharmony_ci unsigned int sglen; 2662306a36Sopenharmony_ci size_t size = sizes[0], len; 2762306a36Sopenharmony_ci struct sg_splitter *curr = splitters; 2862306a36Sopenharmony_ci struct scatterlist *sg; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci for (i = 0; i < nb_splits; i++) { 3162306a36Sopenharmony_ci splitters[i].in_sg0 = NULL; 3262306a36Sopenharmony_ci splitters[i].nents = 0; 3362306a36Sopenharmony_ci } 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci for_each_sg(in, sg, nents, i) { 3662306a36Sopenharmony_ci sglen = mapped ? sg_dma_len(sg) : sg->length; 3762306a36Sopenharmony_ci if (skip > sglen) { 3862306a36Sopenharmony_ci skip -= sglen; 3962306a36Sopenharmony_ci continue; 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci len = min_t(size_t, size, sglen - skip); 4362306a36Sopenharmony_ci if (!curr->in_sg0) { 4462306a36Sopenharmony_ci curr->in_sg0 = sg; 4562306a36Sopenharmony_ci curr->skip_sg0 = skip; 4662306a36Sopenharmony_ci } 4762306a36Sopenharmony_ci size -= len; 4862306a36Sopenharmony_ci curr->nents++; 4962306a36Sopenharmony_ci curr->length_last_sg = len; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci while (!size && (skip + len < sglen) && (--nb_splits > 0)) { 5262306a36Sopenharmony_ci curr++; 5362306a36Sopenharmony_ci size = *(++sizes); 5462306a36Sopenharmony_ci skip += len; 5562306a36Sopenharmony_ci len = min_t(size_t, size, sglen - skip); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci curr->in_sg0 = sg; 5862306a36Sopenharmony_ci curr->skip_sg0 = skip; 5962306a36Sopenharmony_ci curr->nents = 1; 6062306a36Sopenharmony_ci curr->length_last_sg = len; 6162306a36Sopenharmony_ci size -= len; 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci skip = 0; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci if (!size && --nb_splits > 0) { 6662306a36Sopenharmony_ci curr++; 6762306a36Sopenharmony_ci size = *(++sizes); 6862306a36Sopenharmony_ci } 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (!nb_splits) 7162306a36Sopenharmony_ci break; 7262306a36Sopenharmony_ci } 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci return (size || !splitters[0].in_sg0) ? -EINVAL : 0; 7562306a36Sopenharmony_ci} 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic void sg_split_phys(struct sg_splitter *splitters, const int nb_splits) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci int i, j; 8062306a36Sopenharmony_ci struct scatterlist *in_sg, *out_sg; 8162306a36Sopenharmony_ci struct sg_splitter *split; 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci for (i = 0, split = splitters; i < nb_splits; i++, split++) { 8462306a36Sopenharmony_ci in_sg = split->in_sg0; 8562306a36Sopenharmony_ci out_sg = split->out_sg; 8662306a36Sopenharmony_ci for (j = 0; j < split->nents; j++, out_sg++) { 8762306a36Sopenharmony_ci *out_sg = *in_sg; 8862306a36Sopenharmony_ci if (!j) { 8962306a36Sopenharmony_ci out_sg->offset += split->skip_sg0; 9062306a36Sopenharmony_ci out_sg->length -= split->skip_sg0; 9162306a36Sopenharmony_ci } else { 9262306a36Sopenharmony_ci out_sg->offset = 0; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci sg_dma_address(out_sg) = 0; 9562306a36Sopenharmony_ci sg_dma_len(out_sg) = 0; 9662306a36Sopenharmony_ci in_sg = sg_next(in_sg); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci out_sg[-1].length = split->length_last_sg; 9962306a36Sopenharmony_ci sg_mark_end(out_sg - 1); 10062306a36Sopenharmony_ci } 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic void sg_split_mapped(struct sg_splitter *splitters, const int nb_splits) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci int i, j; 10662306a36Sopenharmony_ci struct scatterlist *in_sg, *out_sg; 10762306a36Sopenharmony_ci struct sg_splitter *split; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci for (i = 0, split = splitters; i < nb_splits; i++, split++) { 11062306a36Sopenharmony_ci in_sg = split->in_sg0; 11162306a36Sopenharmony_ci out_sg = split->out_sg; 11262306a36Sopenharmony_ci for (j = 0; j < split->nents; j++, out_sg++) { 11362306a36Sopenharmony_ci sg_dma_address(out_sg) = sg_dma_address(in_sg); 11462306a36Sopenharmony_ci sg_dma_len(out_sg) = sg_dma_len(in_sg); 11562306a36Sopenharmony_ci if (!j) { 11662306a36Sopenharmony_ci sg_dma_address(out_sg) += split->skip_sg0; 11762306a36Sopenharmony_ci sg_dma_len(out_sg) -= split->skip_sg0; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci in_sg = sg_next(in_sg); 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci sg_dma_len(--out_sg) = split->length_last_sg; 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci} 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci/** 12662306a36Sopenharmony_ci * sg_split - split a scatterlist into several scatterlists 12762306a36Sopenharmony_ci * @in: the input sg list 12862306a36Sopenharmony_ci * @in_mapped_nents: the result of a dma_map_sg(in, ...), or 0 if not mapped. 12962306a36Sopenharmony_ci * @skip: the number of bytes to skip in the input sg list 13062306a36Sopenharmony_ci * @nb_splits: the number of desired sg outputs 13162306a36Sopenharmony_ci * @split_sizes: the respective size of each output sg list in bytes 13262306a36Sopenharmony_ci * @out: an array where to store the allocated output sg lists 13362306a36Sopenharmony_ci * @out_mapped_nents: the resulting sg lists mapped number of sg entries. Might 13462306a36Sopenharmony_ci * be NULL if sglist not already mapped (in_mapped_nents = 0) 13562306a36Sopenharmony_ci * @gfp_mask: the allocation flag 13662306a36Sopenharmony_ci * 13762306a36Sopenharmony_ci * This function splits the input sg list into nb_splits sg lists, which are 13862306a36Sopenharmony_ci * allocated and stored into out. 13962306a36Sopenharmony_ci * The @in is split into : 14062306a36Sopenharmony_ci * - @out[0], which covers bytes [@skip .. @skip + @split_sizes[0] - 1] of @in 14162306a36Sopenharmony_ci * - @out[1], which covers bytes [@skip + split_sizes[0] .. 14262306a36Sopenharmony_ci * @skip + @split_sizes[0] + @split_sizes[1] -1] 14362306a36Sopenharmony_ci * etc ... 14462306a36Sopenharmony_ci * It will be the caller's duty to kfree() out array members. 14562306a36Sopenharmony_ci * 14662306a36Sopenharmony_ci * Returns 0 upon success, or error code 14762306a36Sopenharmony_ci */ 14862306a36Sopenharmony_ciint sg_split(struct scatterlist *in, const int in_mapped_nents, 14962306a36Sopenharmony_ci const off_t skip, const int nb_splits, 15062306a36Sopenharmony_ci const size_t *split_sizes, 15162306a36Sopenharmony_ci struct scatterlist **out, int *out_mapped_nents, 15262306a36Sopenharmony_ci gfp_t gfp_mask) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci int i, ret; 15562306a36Sopenharmony_ci struct sg_splitter *splitters; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci splitters = kcalloc(nb_splits, sizeof(*splitters), gfp_mask); 15862306a36Sopenharmony_ci if (!splitters) 15962306a36Sopenharmony_ci return -ENOMEM; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci ret = sg_calculate_split(in, sg_nents(in), nb_splits, skip, split_sizes, 16262306a36Sopenharmony_ci splitters, false); 16362306a36Sopenharmony_ci if (ret < 0) 16462306a36Sopenharmony_ci goto err; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci ret = -ENOMEM; 16762306a36Sopenharmony_ci for (i = 0; i < nb_splits; i++) { 16862306a36Sopenharmony_ci splitters[i].out_sg = kmalloc_array(splitters[i].nents, 16962306a36Sopenharmony_ci sizeof(struct scatterlist), 17062306a36Sopenharmony_ci gfp_mask); 17162306a36Sopenharmony_ci if (!splitters[i].out_sg) 17262306a36Sopenharmony_ci goto err; 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* 17662306a36Sopenharmony_ci * The order of these 3 calls is important and should be kept. 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_ci sg_split_phys(splitters, nb_splits); 17962306a36Sopenharmony_ci if (in_mapped_nents) { 18062306a36Sopenharmony_ci ret = sg_calculate_split(in, in_mapped_nents, nb_splits, skip, 18162306a36Sopenharmony_ci split_sizes, splitters, true); 18262306a36Sopenharmony_ci if (ret < 0) 18362306a36Sopenharmony_ci goto err; 18462306a36Sopenharmony_ci sg_split_mapped(splitters, nb_splits); 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci for (i = 0; i < nb_splits; i++) { 18862306a36Sopenharmony_ci out[i] = splitters[i].out_sg; 18962306a36Sopenharmony_ci if (out_mapped_nents) 19062306a36Sopenharmony_ci out_mapped_nents[i] = splitters[i].nents; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci kfree(splitters); 19462306a36Sopenharmony_ci return 0; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cierr: 19762306a36Sopenharmony_ci for (i = 0; i < nb_splits; i++) 19862306a36Sopenharmony_ci kfree(splitters[i].out_sg); 19962306a36Sopenharmony_ci kfree(splitters); 20062306a36Sopenharmony_ci return ret; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ciEXPORT_SYMBOL(sg_split); 203