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_and_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_and_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_and_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