xref: /linux/arch/alpha/lib/csum_partial_copy.c (revision 4be5e8648b0c287aefc6ac3f3a0b12c696054f43)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * csum_partial_copy - do IP checksumming and copy
4  *
5  * (C) Copyright 1996 Linus Torvalds
6  * accelerated versions (and 21264 assembly versions ) contributed by
7  *	Rick Gorton	<rick.gorton@alpha-processor.com>
8  *
9  * Don't look at this too closely - you'll go mad. The things
10  * we do for performance..
11  */
12 
13 #include <linux/types.h>
14 #include <linux/string.h>
15 #include <linux/uaccess.h>
16 
17 
18 #define ldq_u(x,y) \
19 __asm__ __volatile__("ldq_u %0,%1":"=r" (x):"m" (*(const unsigned long *)(y)))
20 
21 #define stq_u(x,y) \
22 __asm__ __volatile__("stq_u %1,%0":"=m" (*(unsigned long *)(y)):"r" (x))
23 
24 #define extql(x,y,z) \
25 __asm__ __volatile__("extql %1,%2,%0":"=r" (z):"r" (x),"r" (y))
26 
27 #define extqh(x,y,z) \
28 __asm__ __volatile__("extqh %1,%2,%0":"=r" (z):"r" (x),"r" (y))
29 
30 #define mskql(x,y,z) \
31 __asm__ __volatile__("mskql %1,%2,%0":"=r" (z):"r" (x),"r" (y))
32 
33 #define mskqh(x,y,z) \
34 __asm__ __volatile__("mskqh %1,%2,%0":"=r" (z):"r" (x),"r" (y))
35 
36 #define insql(x,y,z) \
37 __asm__ __volatile__("insql %1,%2,%0":"=r" (z):"r" (x),"r" (y))
38 
39 #define insqh(x,y,z) \
40 __asm__ __volatile__("insqh %1,%2,%0":"=r" (z):"r" (x),"r" (y))
41 
42 
43 #define __get_user_u(x,ptr)				\
44 ({							\
45 	long __guu_err;					\
46 	__asm__ __volatile__(				\
47 	"1:	ldq_u %0,%2\n"				\
48 	"2:\n"						\
49 	EXC(1b,2b,%0,%1)				\
50 		: "=r"(x), "=r"(__guu_err)		\
51 		: "m"(__m(ptr)), "1"(0));		\
52 	__guu_err;					\
53 })
54 
55 #define __put_user_u(x,ptr)				\
56 ({							\
57 	long __puu_err;					\
58 	__asm__ __volatile__(				\
59 	"1:	stq_u %2,%1\n"				\
60 	"2:\n"						\
61 	EXC(1b,2b,$31,%0)				\
62 		: "=r"(__puu_err)			\
63 		: "m"(__m(addr)), "rJ"(x), "0"(0));	\
64 	__puu_err;					\
65 })
66 
67 
68 static inline unsigned short from64to16(unsigned long x)
69 {
70 	/* Using extract instructions is a bit more efficient
71 	   than the original shift/bitmask version.  */
72 
73 	union {
74 		unsigned long	ul;
75 		unsigned int	ui[2];
76 		unsigned short	us[4];
77 	} in_v, tmp_v, out_v;
78 
79 	in_v.ul = x;
80 	tmp_v.ul = (unsigned long) in_v.ui[0] + (unsigned long) in_v.ui[1];
81 
82 	/* Since the bits of tmp_v.sh[3] are going to always be zero,
83 	   we don't have to bother to add that in.  */
84 	out_v.ul = (unsigned long) tmp_v.us[0] + (unsigned long) tmp_v.us[1]
85 			+ (unsigned long) tmp_v.us[2];
86 
87 	/* Similarly, out_v.us[2] is always zero for the final add.  */
88 	return out_v.us[0] + out_v.us[1];
89 }
90 
91 
92 
93 /*
94  * Ok. This isn't fun, but this is the EASY case.
95  */
96 static inline unsigned long
97 csum_partial_cfu_aligned(const unsigned long __user *src, unsigned long *dst,
98 			 long len, unsigned long checksum,
99 			 int *errp)
100 {
101 	unsigned long carry = 0;
102 	int err = 0;
103 
104 	while (len >= 0) {
105 		unsigned long word;
106 		err |= __get_user(word, src);
107 		checksum += carry;
108 		src++;
109 		checksum += word;
110 		len -= 8;
111 		carry = checksum < word;
112 		*dst = word;
113 		dst++;
114 	}
115 	len += 8;
116 	checksum += carry;
117 	if (len) {
118 		unsigned long word, tmp;
119 		err |= __get_user(word, src);
120 		tmp = *dst;
121 		mskql(word, len, word);
122 		checksum += word;
123 		mskqh(tmp, len, tmp);
124 		carry = checksum < word;
125 		*dst = word | tmp;
126 		checksum += carry;
127 	}
128 	if (err && errp) *errp = err;
129 	return checksum;
130 }
131 
132 /*
133  * This is even less fun, but this is still reasonably
134  * easy.
135  */
136 static inline unsigned long
137 csum_partial_cfu_dest_aligned(const unsigned long __user *src,
138 			      unsigned long *dst,
139 			      unsigned long soff,
140 			      long len, unsigned long checksum,
141 			      int *errp)
142 {
143 	unsigned long first;
144 	unsigned long word, carry;
145 	unsigned long lastsrc = 7+len+(unsigned long)src;
146 	int err = 0;
147 
148 	err |= __get_user_u(first,src);
149 	carry = 0;
150 	while (len >= 0) {
151 		unsigned long second;
152 
153 		err |= __get_user_u(second, src+1);
154 		extql(first, soff, word);
155 		len -= 8;
156 		src++;
157 		extqh(second, soff, first);
158 		checksum += carry;
159 		word |= first;
160 		first = second;
161 		checksum += word;
162 		*dst = word;
163 		dst++;
164 		carry = checksum < word;
165 	}
166 	len += 8;
167 	checksum += carry;
168 	if (len) {
169 		unsigned long tmp;
170 		unsigned long second;
171 		err |= __get_user_u(second, lastsrc);
172 		tmp = *dst;
173 		extql(first, soff, word);
174 		extqh(second, soff, first);
175 		word |= first;
176 		mskql(word, len, word);
177 		checksum += word;
178 		mskqh(tmp, len, tmp);
179 		carry = checksum < word;
180 		*dst = word | tmp;
181 		checksum += carry;
182 	}
183 	if (err && errp) *errp = err;
184 	return checksum;
185 }
186 
187 /*
188  * This is slightly less fun than the above..
189  */
190 static inline unsigned long
191 csum_partial_cfu_src_aligned(const unsigned long __user *src,
192 			     unsigned long *dst,
193 			     unsigned long doff,
194 			     long len, unsigned long checksum,
195 			     unsigned long partial_dest,
196 			     int *errp)
197 {
198 	unsigned long carry = 0;
199 	unsigned long word;
200 	unsigned long second_dest;
201 	int err = 0;
202 
203 	mskql(partial_dest, doff, partial_dest);
204 	while (len >= 0) {
205 		err |= __get_user(word, src);
206 		len -= 8;
207 		insql(word, doff, second_dest);
208 		checksum += carry;
209 		stq_u(partial_dest | second_dest, dst);
210 		src++;
211 		checksum += word;
212 		insqh(word, doff, partial_dest);
213 		carry = checksum < word;
214 		dst++;
215 	}
216 	len += 8;
217 	if (len) {
218 		checksum += carry;
219 		err |= __get_user(word, src);
220 		mskql(word, len, word);
221 		len -= 8;
222 		checksum += word;
223 		insql(word, doff, second_dest);
224 		len += doff;
225 		carry = checksum < word;
226 		partial_dest |= second_dest;
227 		if (len >= 0) {
228 			stq_u(partial_dest, dst);
229 			if (!len) goto out;
230 			dst++;
231 			insqh(word, doff, partial_dest);
232 		}
233 		doff = len;
234 	}
235 	ldq_u(second_dest, dst);
236 	mskqh(second_dest, doff, second_dest);
237 	stq_u(partial_dest | second_dest, dst);
238 out:
239 	checksum += carry;
240 	if (err && errp) *errp = err;
241 	return checksum;
242 }
243 
244 /*
245  * This is so totally un-fun that it's frightening. Don't
246  * look at this too closely, you'll go blind.
247  */
248 static inline unsigned long
249 csum_partial_cfu_unaligned(const unsigned long __user * src,
250 			   unsigned long * dst,
251 			   unsigned long soff, unsigned long doff,
252 			   long len, unsigned long checksum,
253 			   unsigned long partial_dest,
254 			   int *errp)
255 {
256 	unsigned long carry = 0;
257 	unsigned long first;
258 	unsigned long lastsrc;
259 	int err = 0;
260 
261 	err |= __get_user_u(first, src);
262 	lastsrc = 7+len+(unsigned long)src;
263 	mskql(partial_dest, doff, partial_dest);
264 	while (len >= 0) {
265 		unsigned long second, word;
266 		unsigned long second_dest;
267 
268 		err |= __get_user_u(second, src+1);
269 		extql(first, soff, word);
270 		checksum += carry;
271 		len -= 8;
272 		extqh(second, soff, first);
273 		src++;
274 		word |= first;
275 		first = second;
276 		insql(word, doff, second_dest);
277 		checksum += word;
278 		stq_u(partial_dest | second_dest, dst);
279 		carry = checksum < word;
280 		insqh(word, doff, partial_dest);
281 		dst++;
282 	}
283 	len += doff;
284 	checksum += carry;
285 	if (len >= 0) {
286 		unsigned long second, word;
287 		unsigned long second_dest;
288 
289 		err |= __get_user_u(second, lastsrc);
290 		extql(first, soff, word);
291 		extqh(second, soff, first);
292 		word |= first;
293 		first = second;
294 		mskql(word, len-doff, word);
295 		checksum += word;
296 		insql(word, doff, second_dest);
297 		carry = checksum < word;
298 		stq_u(partial_dest | second_dest, dst);
299 		if (len) {
300 			ldq_u(second_dest, dst+1);
301 			insqh(word, doff, partial_dest);
302 			mskqh(second_dest, len, second_dest);
303 			stq_u(partial_dest | second_dest, dst+1);
304 		}
305 		checksum += carry;
306 	} else {
307 		unsigned long second, word;
308 		unsigned long second_dest;
309 
310 		err |= __get_user_u(second, lastsrc);
311 		extql(first, soff, word);
312 		extqh(second, soff, first);
313 		word |= first;
314 		ldq_u(second_dest, dst);
315 		mskql(word, len-doff, word);
316 		checksum += word;
317 		mskqh(second_dest, len, second_dest);
318 		carry = checksum < word;
319 		insql(word, doff, word);
320 		stq_u(partial_dest | word | second_dest, dst);
321 		checksum += carry;
322 	}
323 	if (err && errp) *errp = err;
324 	return checksum;
325 }
326 
327 __wsum
328 csum_partial_copy_from_user(const void __user *src, void *dst, int len,
329 			       __wsum sum, int *errp)
330 {
331 	unsigned long checksum = (__force u32) sum;
332 	unsigned long soff = 7 & (unsigned long) src;
333 	unsigned long doff = 7 & (unsigned long) dst;
334 
335 	if (len) {
336 		if (!access_ok(src, len)) {
337 			if (errp) *errp = -EFAULT;
338 			memset(dst, 0, len);
339 			return sum;
340 		}
341 		if (!doff) {
342 			if (!soff)
343 				checksum = csum_partial_cfu_aligned(
344 					(const unsigned long __user *) src,
345 					(unsigned long *) dst,
346 					len-8, checksum, errp);
347 			else
348 				checksum = csum_partial_cfu_dest_aligned(
349 					(const unsigned long __user *) src,
350 					(unsigned long *) dst,
351 					soff, len-8, checksum, errp);
352 		} else {
353 			unsigned long partial_dest;
354 			ldq_u(partial_dest, dst);
355 			if (!soff)
356 				checksum = csum_partial_cfu_src_aligned(
357 					(const unsigned long __user *) src,
358 					(unsigned long *) dst,
359 					doff, len-8, checksum,
360 					partial_dest, errp);
361 			else
362 				checksum = csum_partial_cfu_unaligned(
363 					(const unsigned long __user *) src,
364 					(unsigned long *) dst,
365 					soff, doff, len-8, checksum,
366 					partial_dest, errp);
367 		}
368 		checksum = from64to16 (checksum);
369 	}
370 	return (__force __wsum)checksum;
371 }
372 EXPORT_SYMBOL(csum_partial_copy_from_user);
373 
374 __wsum
375 csum_partial_copy_nocheck(const void *src, void *dst, int len, __wsum sum)
376 {
377 	__wsum checksum;
378 	mm_segment_t oldfs = get_fs();
379 	set_fs(KERNEL_DS);
380 	checksum = csum_partial_copy_from_user((__force const void __user *)src,
381 						dst, len, sum, NULL);
382 	set_fs(oldfs);
383 	return checksum;
384 }
385 EXPORT_SYMBOL(csum_partial_copy_nocheck);
386