| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * csum_partial_copy - do IP checksumming and copy | |
| * | |
| * (C) Copyright 1996 Linus Torvalds | |
| * accelerated versions (and 21264 assembly versions ) contributed by | |
| * Rick Gorton <rick.gorton@alpha-processor.com> | |
| * | |
| * Don't look at this too closely - you'll go mad. The things | |
| * we do for performance.. | |
| */ | |
| #include <linux/types.h> | |
| #include <linux/string.h> | |
| #include <linux/uaccess.h> | |
| #define ldq_u(x,y) \ | |
| __asm__ __volatile__("ldq_u %0,%1":"=r" (x):"m" (*(const unsigned long *)(y))) | |
| #define stq_u(x,y) \ | |
| __asm__ __volatile__("stq_u %1,%0":"=m" (*(unsigned long *)(y)):"r" (x)) | |
| #define extql(x,y,z) \ | |
| __asm__ __volatile__("extql %1,%2,%0":"=r" (z):"r" (x),"r" (y)) | |
| #define extqh(x,y,z) \ | |
| __asm__ __volatile__("extqh %1,%2,%0":"=r" (z):"r" (x),"r" (y)) | |
| #define mskql(x,y,z) \ | |
| __asm__ __volatile__("mskql %1,%2,%0":"=r" (z):"r" (x),"r" (y)) | |
| #define mskqh(x,y,z) \ | |
| __asm__ __volatile__("mskqh %1,%2,%0":"=r" (z):"r" (x),"r" (y)) | |
| #define insql(x,y,z) \ | |
| __asm__ __volatile__("insql %1,%2,%0":"=r" (z):"r" (x),"r" (y)) | |
| #define insqh(x,y,z) \ | |
| __asm__ __volatile__("insqh %1,%2,%0":"=r" (z):"r" (x),"r" (y)) | |
| #define __get_user_u(x,ptr) \ | |
| ({ \ | |
| long __guu_err; \ | |
| __asm__ __volatile__( \ | |
| "1: ldq_u %0,%2\n" \ | |
| "2:\n" \ | |
| EXC(1b,2b,%0,%1) \ | |
| : "=r"(x), "=r"(__guu_err) \ | |
| : "m"(__m(ptr)), "1"(0)); \ | |
| __guu_err; \ | |
| }) | |
| #define __put_user_u(x,ptr) \ | |
| ({ \ | |
| long __puu_err; \ | |
| __asm__ __volatile__( \ | |
| "1: stq_u %2,%1\n" \ | |
| "2:\n" \ | |
| EXC(1b,2b,$31,%0) \ | |
| : "=r"(__puu_err) \ | |
| : "m"(__m(addr)), "rJ"(x), "0"(0)); \ | |
| __puu_err; \ | |
| }) | |
| static inline unsigned short from64to16(unsigned long x) | |
| { | |
| /* Using extract instructions is a bit more efficient | |
| than the original shift/bitmask version. */ | |
| union { | |
| unsigned long ul; | |
| unsigned int ui[2]; | |
| unsigned short us[4]; | |
| } in_v, tmp_v, out_v; | |
| in_v.ul = x; | |
| tmp_v.ul = (unsigned long) in_v.ui[0] + (unsigned long) in_v.ui[1]; | |
| /* Since the bits of tmp_v.sh[3] are going to always be zero, | |
| we don't have to bother to add that in. */ | |
| out_v.ul = (unsigned long) tmp_v.us[0] + (unsigned long) tmp_v.us[1] | |
| + (unsigned long) tmp_v.us[2]; | |
| /* Similarly, out_v.us[2] is always zero for the final add. */ | |
| return out_v.us[0] + out_v.us[1]; | |
| } | |
| /* | |
| * Ok. This isn't fun, but this is the EASY case. | |
| */ | |
| static inline unsigned long | |
| csum_partial_cfu_aligned(const unsigned long __user *src, unsigned long *dst, | |
| long len, unsigned long checksum, | |
| int *errp) | |
| { | |
| unsigned long carry = 0; | |
| int err = 0; | |
| while (len >= 0) { | |
| unsigned long word; | |
| err |= __get_user(word, src); | |
| checksum += carry; | |
| src++; | |
| checksum += word; | |
| len -= 8; | |
| carry = checksum < word; | |
| *dst = word; | |
| dst++; | |
| } | |
| len += 8; | |
| checksum += carry; | |
| if (len) { | |
| unsigned long word, tmp; | |
| err |= __get_user(word, src); | |
| tmp = *dst; | |
| mskql(word, len, word); | |
| checksum += word; | |
| mskqh(tmp, len, tmp); | |
| carry = checksum < word; | |
| *dst = word | tmp; | |
| checksum += carry; | |
| } | |
| if (err && errp) *errp = err; | |
| return checksum; | |
| } | |
| /* | |
| * This is even less fun, but this is still reasonably | |
| * easy. | |
| */ | |
| static inline unsigned long | |
| csum_partial_cfu_dest_aligned(const unsigned long __user *src, | |
| unsigned long *dst, | |
| unsigned long soff, | |
| long len, unsigned long checksum, | |
| int *errp) | |
| { | |
| unsigned long first; | |
| unsigned long word, carry; | |
| unsigned long lastsrc = 7+len+(unsigned long)src; | |
| int err = 0; | |
| err |= __get_user_u(first,src); | |
| carry = 0; | |
| while (len >= 0) { | |
| unsigned long second; | |
| err |= __get_user_u(second, src+1); | |
| extql(first, soff, word); | |
| len -= 8; | |
| src++; | |
| extqh(second, soff, first); | |
| checksum += carry; | |
| word |= first; | |
| first = second; | |
| checksum += word; | |
| *dst = word; | |
| dst++; | |
| carry = checksum < word; | |
| } | |
| len += 8; | |
| checksum += carry; | |
| if (len) { | |
| unsigned long tmp; | |
| unsigned long second; | |
| err |= __get_user_u(second, lastsrc); | |
| tmp = *dst; | |
| extql(first, soff, word); | |
| extqh(second, soff, first); | |
| word |= first; | |
| mskql(word, len, word); | |
| checksum += word; | |
| mskqh(tmp, len, tmp); | |
| carry = checksum < word; | |
| *dst = word | tmp; | |
| checksum += carry; | |
| } | |
| if (err && errp) *errp = err; | |
| return checksum; | |
| } | |
| /* | |
| * This is slightly less fun than the above.. | |
| */ | |
| static inline unsigned long | |
| csum_partial_cfu_src_aligned(const unsigned long __user *src, | |
| unsigned long *dst, | |
| unsigned long doff, | |
| long len, unsigned long checksum, | |
| unsigned long partial_dest, | |
| int *errp) | |
| { | |
| unsigned long carry = 0; | |
| unsigned long word; | |
| unsigned long second_dest; | |
| int err = 0; | |
| mskql(partial_dest, doff, partial_dest); | |
| while (len >= 0) { | |
| err |= __get_user(word, src); | |
| len -= 8; | |
| insql(word, doff, second_dest); | |
| checksum += carry; | |
| stq_u(partial_dest | second_dest, dst); | |
| src++; | |
| checksum += word; | |
| insqh(word, doff, partial_dest); | |
| carry = checksum < word; | |
| dst++; | |
| } | |
| len += 8; | |
| if (len) { | |
| checksum += carry; | |
| err |= __get_user(word, src); | |
| mskql(word, len, word); | |
| len -= 8; | |
| checksum += word; | |
| insql(word, doff, second_dest); | |
| len += doff; | |
| carry = checksum < word; | |
| partial_dest |= second_dest; | |
| if (len >= 0) { | |
| stq_u(partial_dest, dst); | |
| if (!len) goto out; | |
| dst++; | |
| insqh(word, doff, partial_dest); | |
| } | |
| doff = len; | |
| } | |
| ldq_u(second_dest, dst); | |
| mskqh(second_dest, doff, second_dest); | |
| stq_u(partial_dest | second_dest, dst); | |
| out: | |
| checksum += carry; | |
| if (err && errp) *errp = err; | |
| return checksum; | |
| } | |
| /* | |
| * This is so totally un-fun that it's frightening. Don't | |
| * look at this too closely, you'll go blind. | |
| */ | |
| static inline unsigned long | |
| csum_partial_cfu_unaligned(const unsigned long __user * src, | |
| unsigned long * dst, | |
| unsigned long soff, unsigned long doff, | |
| long len, unsigned long checksum, | |
| unsigned long partial_dest, | |
| int *errp) | |
| { | |
| unsigned long carry = 0; | |
| unsigned long first; | |
| unsigned long lastsrc; | |
| int err = 0; | |
| err |= __get_user_u(first, src); | |
| lastsrc = 7+len+(unsigned long)src; | |
| mskql(partial_dest, doff, partial_dest); | |
| while (len >= 0) { | |
| unsigned long second, word; | |
| unsigned long second_dest; | |
| err |= __get_user_u(second, src+1); | |
| extql(first, soff, word); | |
| checksum += carry; | |
| len -= 8; | |
| extqh(second, soff, first); | |
| src++; | |
| word |= first; | |
| first = second; | |
| insql(word, doff, second_dest); | |
| checksum += word; | |
| stq_u(partial_dest | second_dest, dst); | |
| carry = checksum < word; | |
| insqh(word, doff, partial_dest); | |
| dst++; | |
| } | |
| len += doff; | |
| checksum += carry; | |
| if (len >= 0) { | |
| unsigned long second, word; | |
| unsigned long second_dest; | |
| err |= __get_user_u(second, lastsrc); | |
| extql(first, soff, word); | |
| extqh(second, soff, first); | |
| word |= first; | |
| first = second; | |
| mskql(word, len-doff, word); | |
| checksum += word; | |
| insql(word, doff, second_dest); | |
| carry = checksum < word; | |
| stq_u(partial_dest | second_dest, dst); | |
| if (len) { | |
| ldq_u(second_dest, dst+1); | |
| insqh(word, doff, partial_dest); | |
| mskqh(second_dest, len, second_dest); | |
| stq_u(partial_dest | second_dest, dst+1); | |
| } | |
| checksum += carry; | |
| } else { | |
| unsigned long second, word; | |
| unsigned long second_dest; | |
| err |= __get_user_u(second, lastsrc); | |
| extql(first, soff, word); | |
| extqh(second, soff, first); | |
| word |= first; | |
| ldq_u(second_dest, dst); | |
| mskql(word, len-doff, word); | |
| checksum += word; | |
| mskqh(second_dest, len, second_dest); | |
| carry = checksum < word; | |
| insql(word, doff, word); | |
| stq_u(partial_dest | word | second_dest, dst); | |
| checksum += carry; | |
| } | |
| if (err && errp) *errp = err; | |
| return checksum; | |
| } | |
| __wsum | |
| csum_partial_copy_from_user(const void __user *src, void *dst, int len, | |
| __wsum sum, int *errp) | |
| { | |
| unsigned long checksum = (__force u32) sum; | |
| unsigned long soff = 7 & (unsigned long) src; | |
| unsigned long doff = 7 & (unsigned long) dst; | |
| if (len) { | |
| if (!access_ok(src, len)) { | |
| if (errp) *errp = -EFAULT; | |
| memset(dst, 0, len); | |
| return sum; | |
| } | |
| if (!doff) { | |
| if (!soff) | |
| checksum = csum_partial_cfu_aligned( | |
| (const unsigned long __user *) src, | |
| (unsigned long *) dst, | |
| len-8, checksum, errp); | |
| else | |
| checksum = csum_partial_cfu_dest_aligned( | |
| (const unsigned long __user *) src, | |
| (unsigned long *) dst, | |
| soff, len-8, checksum, errp); | |
| } else { | |
| unsigned long partial_dest; | |
| ldq_u(partial_dest, dst); | |
| if (!soff) | |
| checksum = csum_partial_cfu_src_aligned( | |
| (const unsigned long __user *) src, | |
| (unsigned long *) dst, | |
| doff, len-8, checksum, | |
| partial_dest, errp); | |
| else | |
| checksum = csum_partial_cfu_unaligned( | |
| (const unsigned long __user *) src, | |
| (unsigned long *) dst, | |
| soff, doff, len-8, checksum, | |
| partial_dest, errp); | |
| } | |
| checksum = from64to16 (checksum); | |
| } | |
| return (__force __wsum)checksum; | |
| } | |
| EXPORT_SYMBOL(csum_partial_copy_from_user); | |
| __wsum | |
| csum_partial_copy_nocheck(const void *src, void *dst, int len, __wsum sum) | |
| { | |
| __wsum checksum; | |
| mm_segment_t oldfs = get_fs(); | |
| set_fs(KERNEL_DS); | |
| checksum = csum_partial_copy_from_user((__force const void __user *)src, | |
| dst, len, sum, NULL); | |
| set_fs(oldfs); | |
| return checksum; | |
| } | |
| EXPORT_SYMBOL(csum_partial_copy_nocheck); |