| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * csum_partial_copy - do IP checksumming and copy |
| * |
| * (C) Copyright 1996 Linus Torvalds |
| * |
| * 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> |
| #include <asm/checksum.h> |
| |
| |
| #define ldl_u(x, y) \ |
| __asm__ __volatile__("ldl_u %0, %1":"=r" (x):"m" (*(const unsigned long *)(y))) |
| |
| #define stl_u(x, y) \ |
| __asm__ __volatile__("stl_u %1, %0":"=m" (*(unsigned long *)(y)):"r" (x)) |
| |
| static inline void stll_u(unsigned long data, unsigned long *dst) |
| { |
| int i = 0; |
| unsigned long doff = (unsigned long)dst & 7; |
| |
| for (; doff < 8; i++, doff++) |
| *((char *)dst + i) = *((char *)&data + i); |
| } |
| |
| static inline void sthl_u(unsigned long data, unsigned long *dst) |
| { |
| int i = 0; |
| unsigned long doff = (unsigned long)dst & 7; |
| |
| for (; i < doff; i++) |
| *((char *)dst + 8 - doff + i) = *((char *)&data + 8 - doff + i); |
| } |
| |
| #define __get_word(insn, x, ptr) \ |
| ({ \ |
| long __guu_err; \ |
| __asm__ __volatile__( \ |
| "1: "#insn" %0,%2\n" \ |
| "2:\n" \ |
| ".section __ex_table,\"a\"\n" \ |
| " .long 1b - .\n" \ |
| " ldi %0,2b-1b(%1)\n" \ |
| ".previous" \ |
| : "=r"(x), "=r"(__guu_err) \ |
| : "m"(__m(ptr)), "1"(0)); \ |
| __guu_err; \ |
| }) |
| |
| static inline unsigned long |
| csum_partial_cfu_dest_aligned(const unsigned long __user *src, |
| unsigned long *dst, long len) |
| { |
| unsigned long word; |
| unsigned long checksum = ~0U; |
| int err = 0; |
| |
| err = __copy_from_user(dst, src, len+8); |
| |
| while (len > 0) { |
| word = *dst; |
| checksum += word; |
| checksum += (checksum < word); |
| dst++; |
| len -= 8; |
| } |
| len += 8; |
| word = *dst; |
| |
| if (len != 8) |
| maskll(word, len, word); |
| checksum += word; |
| checksum += (checksum < word); |
| |
| return checksum; |
| } |
| |
| static inline unsigned long |
| csum_partial_cfu_dest_unaligned(const unsigned long __user *src, |
| unsigned long *dst, unsigned long doff, long len) |
| { |
| unsigned long word, patch; |
| unsigned long partial_dest, second_dest; |
| unsigned long checksum = ~0U; |
| int err = 0; |
| |
| err = __copy_from_user(dst, src, len+8); |
| |
| dst = (unsigned long *)((unsigned long)dst & (~7UL)); |
| word = *dst; |
| inshl(word, 8 - doff, partial_dest); |
| dst++; |
| |
| while (len >= 0) { |
| word = *dst; |
| insll(word, 8 - doff, second_dest); |
| patch = partial_dest | second_dest; |
| checksum += patch; |
| checksum += (checksum < patch); |
| inshl(word, 8 - doff, partial_dest); |
| dst++; |
| len -= 8; |
| } |
| |
| len += 8; |
| word = *dst; |
| insll(word, 8 - doff, second_dest); |
| patch = partial_dest | second_dest; |
| maskll(patch, len, patch); |
| checksum += patch; |
| checksum += (checksum < patch); |
| |
| return checksum; |
| } |
| |
| static __wsum __csum_and_copy(const void __user *src, void *dst, int len) |
| { |
| unsigned long checksum; |
| unsigned long doff = 7 & (unsigned long) dst; |
| |
| if (!doff) { |
| checksum = csum_partial_cfu_dest_aligned( |
| (const unsigned long __user *) src, |
| (unsigned long *) dst, len-8); |
| } else { |
| checksum = csum_partial_cfu_dest_unaligned( |
| (const unsigned long __user *) src, |
| (unsigned long *) dst, doff, len-8); |
| } |
| return (__force __wsum)from64to16(checksum); |
| } |
| |
| __wsum |
| csum_and_copy_from_user(const void __user *src, void *dst, int len) |
| { |
| if (!access_ok(src, len)) |
| return 0; |
| return __csum_and_copy(src, dst, len); |
| } |
| EXPORT_SYMBOL(csum_and_copy_from_user); |
| |
| __wsum |
| csum_partial_copy_nocheck(const void *src, void *dst, int len) |
| { |
| return __csum_and_copy((__force const void __user *)src, |
| dst, len); |
| } |
| EXPORT_SYMBOL(csum_partial_copy_nocheck); |