xref: /openbmc/linux/arch/parisc/lib/checksum.c (revision bb3982b4)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * INET		An implementation of the TCP/IP protocol suite for the LINUX
4  *		operating system.  INET is implemented using the  BSD Socket
5  *		interface as the means of communication with the user level.
6  *
7  *		MIPS specific IP/TCP/UDP checksumming routines
8  *
9  * Authors:	Ralf Baechle, <ralf@waldorf-gmbh.de>
10  *		Lots of code moved from tcp.c and ip.c; see those files
11  *		for more names.
12  */
13 #include <linux/module.h>
14 #include <linux/types.h>
15 
16 #include <net/checksum.h>
17 #include <asm/byteorder.h>
18 #include <asm/string.h>
19 #include <linux/uaccess.h>
20 
21 #define addc(_t,_r)                     \
22 	__asm__ __volatile__ (          \
23 "       add             %0, %1, %0\n"   \
24 "       addc            %0, %%r0, %0\n" \
25 	: "=r"(_t)                      \
26 	: "r"(_r), "0"(_t));
27 
28 static inline unsigned short from32to16(unsigned int x)
29 {
30 	/* 32 bits --> 16 bits + carry */
31 	x = (x & 0xffff) + (x >> 16);
32 	/* 16 bits + carry --> 16 bits including carry */
33 	x = (x & 0xffff) + (x >> 16);
34 	return (unsigned short)x;
35 }
36 
37 static inline unsigned int do_csum(const unsigned char * buff, int len)
38 {
39 	int odd, count;
40 	unsigned int result = 0;
41 
42 	if (len <= 0)
43 		goto out;
44 	odd = 1 & (unsigned long) buff;
45 	if (odd) {
46 		result = be16_to_cpu(*buff);
47 		len--;
48 		buff++;
49 	}
50 	count = len >> 1;		/* nr of 16-bit words.. */
51 	if (count) {
52 		if (2 & (unsigned long) buff) {
53 			result += *(unsigned short *) buff;
54 			count--;
55 			len -= 2;
56 			buff += 2;
57 		}
58 		count >>= 1;		/* nr of 32-bit words.. */
59 		if (count) {
60 			while (count >= 4) {
61 				unsigned int r1, r2, r3, r4;
62 				r1 = *(unsigned int *)(buff + 0);
63 				r2 = *(unsigned int *)(buff + 4);
64 				r3 = *(unsigned int *)(buff + 8);
65 				r4 = *(unsigned int *)(buff + 12);
66 				addc(result, r1);
67 				addc(result, r2);
68 				addc(result, r3);
69 				addc(result, r4);
70 				count -= 4;
71 				buff += 16;
72 			}
73 			while (count) {
74 				unsigned int w = *(unsigned int *) buff;
75 				count--;
76 				buff += 4;
77 				addc(result, w);
78 			}
79 			result = (result & 0xffff) + (result >> 16);
80 		}
81 		if (len & 2) {
82 			result += *(unsigned short *) buff;
83 			buff += 2;
84 		}
85 	}
86 	if (len & 1)
87 		result += le16_to_cpu(*buff);
88 	result = from32to16(result);
89 	if (odd)
90 		result = swab16(result);
91 out:
92 	return result;
93 }
94 
95 /*
96  * computes a partial checksum, e.g. for TCP/UDP fragments
97  */
98 /*
99  * why bother folding?
100  */
101 __wsum csum_partial(const void *buff, int len, __wsum sum)
102 {
103 	unsigned int result = do_csum(buff, len);
104 	addc(result, sum);
105 	return (__force __wsum)from32to16(result);
106 }
107 
108 EXPORT_SYMBOL(csum_partial);
109 
110 /*
111  * copy while checksumming, otherwise like csum_partial
112  */
113 __wsum csum_partial_copy_nocheck(const void *src, void *dst,
114 				       int len, __wsum sum)
115 {
116 	/*
117 	 * It's 2:30 am and I don't feel like doing it real ...
118 	 * This is lots slower than the real thing (tm)
119 	 */
120 	sum = csum_partial(src, len, sum);
121 	memcpy(dst, src, len);
122 
123 	return sum;
124 }
125 EXPORT_SYMBOL(csum_partial_copy_nocheck);
126 
127 /*
128  * Copy from userspace and compute checksum.  If we catch an exception
129  * then zero the rest of the buffer.
130  */
131 __wsum csum_partial_copy_from_user(const void __user *src,
132 					void *dst, int len,
133 					__wsum sum, int *err_ptr)
134 {
135 	int missing;
136 
137 	missing = copy_from_user(dst, src, len);
138 	if (missing) {
139 		memset(dst + len - missing, 0, missing);
140 		*err_ptr = -EFAULT;
141 	}
142 
143 	return csum_partial(dst, len, sum);
144 }
145 EXPORT_SYMBOL(csum_partial_copy_from_user);
146