xref: /dragonfly/sys/dev/drm/linux_scatterlist.c (revision f0bba3d1)
1 /*
2  * Copyright (c) 2010 Isilon Systems, Inc.
3  * Copyright (c) 2010 iX Systems, Inc.
4  * Copyright (c) 2010 Panasas, Inc.
5  * Copyright (c) 2013, 2014 Mellanox Technologies, Ltd.
6  * Copyright (c) 2015 Matthew Dillon <dillon@backplane.com>
7  * Copyright (c) 2016 Matt Macy <mmacy@nextbsd.org>
8  * Copyright (c) 2017-2018 François Tigeot <ftigeot@wolfpond.org>
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice unmodified, this list of conditions, and the following
16  *    disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <linux/slab.h>
34 #include <linux/scatterlist.h>
35 #include <linux/highmem.h>
36 
37 /**
38  * __sg_alloc_table - Allocate and initialize an sg table with given allocator
39  * @table:      The sg table header to use
40  * @nents:      Number of entries in sg list
41  * @max_ents:   The maximum number of entries the allocator returns per call
42  * @gfp_mask:   GFP allocation mask
43  *
44  * Description:
45  *   This function returns a @table @nents long. The allocator is
46  *   defined to return scatterlist chunks of maximum size @max_ents.
47  *   Thus if @nents is bigger than @max_ents, the scatterlists will be
48  *   chained in units of @max_ents.
49  *
50  * Notes:
51  *   If this function returns non-0 (eg failure), the caller must call
52  *   __sg_free_table() to cleanup any leftover allocations.
53  *
54  **/
55 int
__sg_alloc_table(struct sg_table * table,unsigned int nents,unsigned int max_ents,gfp_t gfp_mask)56 __sg_alloc_table(struct sg_table *table, unsigned int nents,
57 		unsigned int max_ents, gfp_t gfp_mask)
58 {
59 	struct scatterlist *sg, *prv;
60 	unsigned int left;
61 
62 	memset(table, 0, sizeof(*table));
63 
64 	if (nents == 0)
65 		return -EINVAL;
66 	left = nents;
67 	prv = NULL;
68 	do {
69 		unsigned int sg_size, alloc_size = left;
70 
71 		if (alloc_size > max_ents) {
72 			alloc_size = max_ents;
73 			sg_size = alloc_size - 1;
74 		} else
75 			sg_size = alloc_size;
76 
77 		left -= sg_size;
78 
79 		sg = kmalloc(alloc_size * sizeof(struct scatterlist), M_DRM, gfp_mask);
80 		if (unlikely(!sg)) {
81 		/*
82 		 * Adjust entry count to reflect that the last
83 		 * entry of the previous table won't be used for
84 		 * linkage.  Without this, sg_kfree() may get
85 		 * confused.
86 		 */
87 			if (prv)
88 				table->nents = ++table->orig_nents;
89 
90 			return -ENOMEM;
91 		}
92 
93 		sg_init_table(sg, alloc_size);
94 		table->nents = table->orig_nents += sg_size;
95 
96 		/*
97 		 * If this is the first mapping, assign the sg table header.
98 		 * If this is not the first mapping, chain previous part.
99 		 */
100 		if (prv)
101 			sg_chain(prv, max_ents, sg);
102 		else
103 			table->sgl = sg;
104 
105 		/*
106 		* If no more entries after this one, mark the end
107 		*/
108 		if (!left)
109 			sg_mark_end(&sg[sg_size - 1]);
110 
111 		prv = sg;
112 	} while (left);
113 
114 	return 0;
115 }
116 
117 void
__sg_free_table(struct sg_table * table,unsigned int max_ents)118 __sg_free_table(struct sg_table *table, unsigned int max_ents)
119 {
120 	struct scatterlist *sgl, *next;
121 
122 	if (unlikely(!table->sgl))
123 		return;
124 
125 	sgl = table->sgl;
126 	while (table->orig_nents) {
127 		unsigned int alloc_size = table->orig_nents;
128 		unsigned int sg_size;
129 
130 		/*
131 		 * If we have more than max_ents segments left,
132 		 * then assign 'next' to the sg table after the current one.
133 		 * sg_size is then one less than alloc size, since the last
134 		 * element is the chain pointer.
135 		 */
136 		if (alloc_size > max_ents) {
137 			next = sgl[max_ents - 1].sl_un.sg;
138 			alloc_size = max_ents;
139 			sg_size = alloc_size - 1;
140 		} else {
141 			sg_size = alloc_size;
142 			next = NULL;
143 		}
144 
145 		table->orig_nents -= sg_size;
146 		kfree(sgl);
147 		sgl = next;
148 	}
149 
150 	table->sgl = NULL;
151 }
152 
153 size_t
sg_pcopy_from_buffer(struct scatterlist * sgl,unsigned int nents,const void * buf,size_t buflen,off_t skip)154 sg_pcopy_from_buffer(struct scatterlist *sgl, unsigned int nents,
155 		     const void *buf, size_t buflen, off_t skip)
156 {
157 	off_t off;
158 	int len, curlen, curoff;
159 	struct sg_page_iter iter;
160 	struct scatterlist *sg;
161 	struct page *page;
162 	char *vaddr;
163 
164 	off = 0;
165 	for_each_sg_page(sgl, &iter, nents, 0) {
166 		sg = iter.sg;
167 		curlen = sg->length;
168 		curoff = sg->offset;
169 		if (skip && curlen >= skip) {
170 			skip -= curlen;
171 			continue;
172 		}
173 		if (skip) {
174 			curlen -= skip;
175 			curoff += skip;
176 			skip = 0;
177 		}
178 		len = min(curlen, buflen - off);
179 		page = sg_page_iter_page(&iter);
180 		vaddr = (char *)kmap(page) + sg->offset;
181 		memcpy(vaddr, (const char *)buf + off, len);
182 		off += len;
183 		kunmap(page);
184 	}
185 
186 	return (off);
187 }
188 
189 size_t
sg_pcopy_to_buffer(struct scatterlist * sgl,unsigned int nents,void * buf,size_t buflen,off_t skip)190 sg_pcopy_to_buffer(struct scatterlist *sgl, unsigned int nents,
191 		   void *buf, size_t buflen, off_t skip)
192 {
193 	off_t off;
194 	int len, curlen, curoff;
195 	struct sg_page_iter iter;
196 	struct scatterlist *sg;
197 	struct page *page;
198 	char *vaddr;
199 
200 	off = 0;
201 	for_each_sg_page(sgl, &iter, nents, 0) {
202 		sg = iter.sg;
203 		curlen = sg->length;
204 		curoff = sg->offset;
205 		if (skip && curlen >= skip) {
206 			skip -= curlen;
207 			continue;
208 		}
209 		if (skip) {
210 			curlen -= skip;
211 			curoff += skip;
212 			skip = 0;
213 		}
214 		len = min(curlen, buflen - off);
215 		page = sg_page_iter_page(&iter);
216 		vaddr = (char *)kmap(page) + sg->offset;
217 		memcpy((char *)buf + off, vaddr, len);
218 		off += len;
219 		kunmap(page);
220 	}
221 
222 	return (off);
223 }
224