]>
Commit | Line | Data |
---|---|---|
f8bcbe62 RJ |
1 | /* |
2 | * Copyright (C) 2015 Robert Jarzmik <[email protected]> | |
3 | * | |
4 | * Scatterlist splitting helpers. | |
5 | * | |
6 | * This source code is licensed under the GNU General Public License, | |
7 | * Version 2. See the file COPYING for more details. | |
8 | */ | |
9 | ||
10 | #include <linux/scatterlist.h> | |
11 | #include <linux/slab.h> | |
12 | ||
13 | struct sg_splitter { | |
14 | struct scatterlist *in_sg0; | |
15 | int nents; | |
16 | off_t skip_sg0; | |
17 | unsigned int length_last_sg; | |
18 | ||
19 | struct scatterlist *out_sg; | |
20 | }; | |
21 | ||
22 | static int sg_calculate_split(struct scatterlist *in, int nents, int nb_splits, | |
23 | off_t skip, const size_t *sizes, | |
24 | struct sg_splitter *splitters, bool mapped) | |
25 | { | |
26 | int i; | |
27 | unsigned int sglen; | |
28 | size_t size = sizes[0], len; | |
29 | struct sg_splitter *curr = splitters; | |
30 | struct scatterlist *sg; | |
31 | ||
32 | for (i = 0; i < nb_splits; i++) { | |
33 | splitters[i].in_sg0 = NULL; | |
34 | splitters[i].nents = 0; | |
35 | } | |
36 | ||
37 | for_each_sg(in, sg, nents, i) { | |
38 | sglen = mapped ? sg_dma_len(sg) : sg->length; | |
39 | if (skip > sglen) { | |
40 | skip -= sglen; | |
41 | continue; | |
42 | } | |
43 | ||
44 | len = min_t(size_t, size, sglen - skip); | |
45 | if (!curr->in_sg0) { | |
46 | curr->in_sg0 = sg; | |
47 | curr->skip_sg0 = skip; | |
48 | } | |
49 | size -= len; | |
50 | curr->nents++; | |
51 | curr->length_last_sg = len; | |
52 | ||
53 | while (!size && (skip + len < sglen) && (--nb_splits > 0)) { | |
54 | curr++; | |
55 | size = *(++sizes); | |
56 | skip += len; | |
57 | len = min_t(size_t, size, sglen - skip); | |
58 | ||
59 | curr->in_sg0 = sg; | |
60 | curr->skip_sg0 = skip; | |
61 | curr->nents = 1; | |
62 | curr->length_last_sg = len; | |
63 | size -= len; | |
64 | } | |
65 | skip = 0; | |
66 | ||
67 | if (!size && --nb_splits > 0) { | |
68 | curr++; | |
69 | size = *(++sizes); | |
70 | } | |
71 | ||
72 | if (!nb_splits) | |
73 | break; | |
74 | } | |
75 | ||
76 | return (size || !splitters[0].in_sg0) ? -EINVAL : 0; | |
77 | } | |
78 | ||
79 | static void sg_split_phys(struct sg_splitter *splitters, const int nb_splits) | |
80 | { | |
81 | int i, j; | |
82 | struct scatterlist *in_sg, *out_sg; | |
83 | struct sg_splitter *split; | |
84 | ||
85 | for (i = 0, split = splitters; i < nb_splits; i++, split++) { | |
86 | in_sg = split->in_sg0; | |
87 | out_sg = split->out_sg; | |
88 | for (j = 0; j < split->nents; j++, out_sg++) { | |
89 | *out_sg = *in_sg; | |
90 | if (!j) { | |
91 | out_sg->offset += split->skip_sg0; | |
92 | out_sg->length -= split->skip_sg0; | |
93 | } else { | |
94 | out_sg->offset = 0; | |
95 | } | |
96 | sg_dma_address(out_sg) = 0; | |
97 | sg_dma_len(out_sg) = 0; | |
98 | in_sg = sg_next(in_sg); | |
99 | } | |
100 | out_sg[-1].length = split->length_last_sg; | |
101 | sg_mark_end(out_sg - 1); | |
102 | } | |
103 | } | |
104 | ||
105 | static void sg_split_mapped(struct sg_splitter *splitters, const int nb_splits) | |
106 | { | |
107 | int i, j; | |
108 | struct scatterlist *in_sg, *out_sg; | |
109 | struct sg_splitter *split; | |
110 | ||
111 | for (i = 0, split = splitters; i < nb_splits; i++, split++) { | |
112 | in_sg = split->in_sg0; | |
113 | out_sg = split->out_sg; | |
114 | for (j = 0; j < split->nents; j++, out_sg++) { | |
115 | sg_dma_address(out_sg) = sg_dma_address(in_sg); | |
116 | sg_dma_len(out_sg) = sg_dma_len(in_sg); | |
117 | if (!j) { | |
118 | sg_dma_address(out_sg) += split->skip_sg0; | |
119 | sg_dma_len(out_sg) -= split->skip_sg0; | |
120 | } | |
121 | in_sg = sg_next(in_sg); | |
122 | } | |
123 | sg_dma_len(--out_sg) = split->length_last_sg; | |
124 | } | |
125 | } | |
126 | ||
127 | /** | |
128 | * sg_split - split a scatterlist into several scatterlists | |
129 | * @in: the input sg list | |
130 | * @in_mapped_nents: the result of a dma_map_sg(in, ...), or 0 if not mapped. | |
131 | * @skip: the number of bytes to skip in the input sg list | |
132 | * @nb_splits: the number of desired sg outputs | |
133 | * @split_sizes: the respective size of each output sg list in bytes | |
134 | * @out: an array where to store the allocated output sg lists | |
135 | * @out_mapped_nents: the resulting sg lists mapped number of sg entries. Might | |
136 | * be NULL if sglist not already mapped (in_mapped_nents = 0) | |
137 | * @gfp_mask: the allocation flag | |
138 | * | |
139 | * This function splits the input sg list into nb_splits sg lists, which are | |
140 | * allocated and stored into out. | |
141 | * The @in is split into : | |
142 | * - @out[0], which covers bytes [@skip .. @skip + @split_sizes[0] - 1] of @in | |
143 | * - @out[1], which covers bytes [@skip + split_sizes[0] .. | |
144 | * @skip + @split_sizes[0] + @split_sizes[1] -1] | |
145 | * etc ... | |
146 | * It will be the caller's duty to kfree() out array members. | |
147 | * | |
148 | * Returns 0 upon success, or error code | |
149 | */ | |
150 | int sg_split(struct scatterlist *in, const int in_mapped_nents, | |
151 | const off_t skip, const int nb_splits, | |
152 | const size_t *split_sizes, | |
153 | struct scatterlist **out, int *out_mapped_nents, | |
154 | gfp_t gfp_mask) | |
155 | { | |
156 | int i, ret; | |
157 | struct sg_splitter *splitters; | |
158 | ||
159 | splitters = kcalloc(nb_splits, sizeof(*splitters), gfp_mask); | |
160 | if (!splitters) | |
161 | return -ENOMEM; | |
162 | ||
163 | ret = sg_calculate_split(in, sg_nents(in), nb_splits, skip, split_sizes, | |
164 | splitters, false); | |
165 | if (ret < 0) | |
166 | goto err; | |
167 | ||
168 | ret = -ENOMEM; | |
169 | for (i = 0; i < nb_splits; i++) { | |
170 | splitters[i].out_sg = kmalloc_array(splitters[i].nents, | |
171 | sizeof(struct scatterlist), | |
172 | gfp_mask); | |
173 | if (!splitters[i].out_sg) | |
174 | goto err; | |
175 | } | |
176 | ||
177 | /* | |
178 | * The order of these 3 calls is important and should be kept. | |
179 | */ | |
180 | sg_split_phys(splitters, nb_splits); | |
181 | ret = sg_calculate_split(in, in_mapped_nents, nb_splits, skip, | |
182 | split_sizes, splitters, true); | |
183 | if (ret < 0) | |
184 | goto err; | |
185 | sg_split_mapped(splitters, nb_splits); | |
186 | ||
187 | for (i = 0; i < nb_splits; i++) { | |
188 | out[i] = splitters[i].out_sg; | |
189 | if (out_mapped_nents) | |
190 | out_mapped_nents[i] = splitters[i].nents; | |
191 | } | |
192 | ||
193 | kfree(splitters); | |
194 | return 0; | |
195 | ||
196 | err: | |
197 | for (i = 0; i < nb_splits; i++) | |
198 | kfree(splitters[i].out_sg); | |
199 | kfree(splitters); | |
200 | return ret; | |
201 | } | |
202 | EXPORT_SYMBOL(sg_split); |