162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * csum_partial_copy - do IP checksumming and copy 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (C) Copyright 1996 Linus Torvalds 662306a36Sopenharmony_ci * accelerated versions (and 21264 assembly versions ) contributed by 762306a36Sopenharmony_ci * Rick Gorton <rick.gorton@alpha-processor.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Don't look at this too closely - you'll go mad. The things 1062306a36Sopenharmony_ci * we do for performance.. 1162306a36Sopenharmony_ci */ 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include <linux/types.h> 1462306a36Sopenharmony_ci#include <linux/string.h> 1562306a36Sopenharmony_ci#include <linux/uaccess.h> 1662306a36Sopenharmony_ci#include <net/checksum.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#define ldq_u(x,y) \ 2062306a36Sopenharmony_ci__asm__ __volatile__("ldq_u %0,%1":"=r" (x):"m" (*(const unsigned long *)(y))) 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define stq_u(x,y) \ 2362306a36Sopenharmony_ci__asm__ __volatile__("stq_u %1,%0":"=m" (*(unsigned long *)(y)):"r" (x)) 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#define extql(x,y,z) \ 2662306a36Sopenharmony_ci__asm__ __volatile__("extql %1,%2,%0":"=r" (z):"r" (x),"r" (y)) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define extqh(x,y,z) \ 2962306a36Sopenharmony_ci__asm__ __volatile__("extqh %1,%2,%0":"=r" (z):"r" (x),"r" (y)) 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define mskql(x,y,z) \ 3262306a36Sopenharmony_ci__asm__ __volatile__("mskql %1,%2,%0":"=r" (z):"r" (x),"r" (y)) 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define mskqh(x,y,z) \ 3562306a36Sopenharmony_ci__asm__ __volatile__("mskqh %1,%2,%0":"=r" (z):"r" (x),"r" (y)) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define insql(x,y,z) \ 3862306a36Sopenharmony_ci__asm__ __volatile__("insql %1,%2,%0":"=r" (z):"r" (x),"r" (y)) 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci#define insqh(x,y,z) \ 4162306a36Sopenharmony_ci__asm__ __volatile__("insqh %1,%2,%0":"=r" (z):"r" (x),"r" (y)) 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define __get_word(insn,x,ptr) \ 4462306a36Sopenharmony_ci({ \ 4562306a36Sopenharmony_ci long __guu_err; \ 4662306a36Sopenharmony_ci __asm__ __volatile__( \ 4762306a36Sopenharmony_ci "1: "#insn" %0,%2\n" \ 4862306a36Sopenharmony_ci "2:\n" \ 4962306a36Sopenharmony_ci EXC(1b,2b,%0,%1) \ 5062306a36Sopenharmony_ci : "=r"(x), "=r"(__guu_err) \ 5162306a36Sopenharmony_ci : "m"(__m(ptr)), "1"(0)); \ 5262306a36Sopenharmony_ci __guu_err; \ 5362306a36Sopenharmony_ci}) 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic inline unsigned short from64to16(unsigned long x) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci /* Using extract instructions is a bit more efficient 5862306a36Sopenharmony_ci than the original shift/bitmask version. */ 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci union { 6162306a36Sopenharmony_ci unsigned long ul; 6262306a36Sopenharmony_ci unsigned int ui[2]; 6362306a36Sopenharmony_ci unsigned short us[4]; 6462306a36Sopenharmony_ci } in_v, tmp_v, out_v; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci in_v.ul = x; 6762306a36Sopenharmony_ci tmp_v.ul = (unsigned long) in_v.ui[0] + (unsigned long) in_v.ui[1]; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* Since the bits of tmp_v.sh[3] are going to always be zero, 7062306a36Sopenharmony_ci we don't have to bother to add that in. */ 7162306a36Sopenharmony_ci out_v.ul = (unsigned long) tmp_v.us[0] + (unsigned long) tmp_v.us[1] 7262306a36Sopenharmony_ci + (unsigned long) tmp_v.us[2]; 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci /* Similarly, out_v.us[2] is always zero for the final add. */ 7562306a36Sopenharmony_ci return out_v.us[0] + out_v.us[1]; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci/* 8162306a36Sopenharmony_ci * Ok. This isn't fun, but this is the EASY case. 8262306a36Sopenharmony_ci */ 8362306a36Sopenharmony_cistatic inline unsigned long 8462306a36Sopenharmony_cicsum_partial_cfu_aligned(const unsigned long __user *src, unsigned long *dst, 8562306a36Sopenharmony_ci long len) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci unsigned long checksum = ~0U; 8862306a36Sopenharmony_ci unsigned long carry = 0; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci while (len >= 0) { 9162306a36Sopenharmony_ci unsigned long word; 9262306a36Sopenharmony_ci if (__get_word(ldq, word, src)) 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci checksum += carry; 9562306a36Sopenharmony_ci src++; 9662306a36Sopenharmony_ci checksum += word; 9762306a36Sopenharmony_ci len -= 8; 9862306a36Sopenharmony_ci carry = checksum < word; 9962306a36Sopenharmony_ci *dst = word; 10062306a36Sopenharmony_ci dst++; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci len += 8; 10362306a36Sopenharmony_ci checksum += carry; 10462306a36Sopenharmony_ci if (len) { 10562306a36Sopenharmony_ci unsigned long word, tmp; 10662306a36Sopenharmony_ci if (__get_word(ldq, word, src)) 10762306a36Sopenharmony_ci return 0; 10862306a36Sopenharmony_ci tmp = *dst; 10962306a36Sopenharmony_ci mskql(word, len, word); 11062306a36Sopenharmony_ci checksum += word; 11162306a36Sopenharmony_ci mskqh(tmp, len, tmp); 11262306a36Sopenharmony_ci carry = checksum < word; 11362306a36Sopenharmony_ci *dst = word | tmp; 11462306a36Sopenharmony_ci checksum += carry; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci return checksum; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci/* 12062306a36Sopenharmony_ci * This is even less fun, but this is still reasonably 12162306a36Sopenharmony_ci * easy. 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_cistatic inline unsigned long 12462306a36Sopenharmony_cicsum_partial_cfu_dest_aligned(const unsigned long __user *src, 12562306a36Sopenharmony_ci unsigned long *dst, 12662306a36Sopenharmony_ci unsigned long soff, 12762306a36Sopenharmony_ci long len) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci unsigned long first; 13062306a36Sopenharmony_ci unsigned long word, carry; 13162306a36Sopenharmony_ci unsigned long lastsrc = 7+len+(unsigned long)src; 13262306a36Sopenharmony_ci unsigned long checksum = ~0U; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci if (__get_word(ldq_u, first,src)) 13562306a36Sopenharmony_ci return 0; 13662306a36Sopenharmony_ci carry = 0; 13762306a36Sopenharmony_ci while (len >= 0) { 13862306a36Sopenharmony_ci unsigned long second; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (__get_word(ldq_u, second, src+1)) 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci extql(first, soff, word); 14362306a36Sopenharmony_ci len -= 8; 14462306a36Sopenharmony_ci src++; 14562306a36Sopenharmony_ci extqh(second, soff, first); 14662306a36Sopenharmony_ci checksum += carry; 14762306a36Sopenharmony_ci word |= first; 14862306a36Sopenharmony_ci first = second; 14962306a36Sopenharmony_ci checksum += word; 15062306a36Sopenharmony_ci *dst = word; 15162306a36Sopenharmony_ci dst++; 15262306a36Sopenharmony_ci carry = checksum < word; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci len += 8; 15562306a36Sopenharmony_ci checksum += carry; 15662306a36Sopenharmony_ci if (len) { 15762306a36Sopenharmony_ci unsigned long tmp; 15862306a36Sopenharmony_ci unsigned long second; 15962306a36Sopenharmony_ci if (__get_word(ldq_u, second, lastsrc)) 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci tmp = *dst; 16262306a36Sopenharmony_ci extql(first, soff, word); 16362306a36Sopenharmony_ci extqh(second, soff, first); 16462306a36Sopenharmony_ci word |= first; 16562306a36Sopenharmony_ci mskql(word, len, word); 16662306a36Sopenharmony_ci checksum += word; 16762306a36Sopenharmony_ci mskqh(tmp, len, tmp); 16862306a36Sopenharmony_ci carry = checksum < word; 16962306a36Sopenharmony_ci *dst = word | tmp; 17062306a36Sopenharmony_ci checksum += carry; 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci return checksum; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci/* 17662306a36Sopenharmony_ci * This is slightly less fun than the above.. 17762306a36Sopenharmony_ci */ 17862306a36Sopenharmony_cistatic inline unsigned long 17962306a36Sopenharmony_cicsum_partial_cfu_src_aligned(const unsigned long __user *src, 18062306a36Sopenharmony_ci unsigned long *dst, 18162306a36Sopenharmony_ci unsigned long doff, 18262306a36Sopenharmony_ci long len, 18362306a36Sopenharmony_ci unsigned long partial_dest) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci unsigned long carry = 0; 18662306a36Sopenharmony_ci unsigned long word; 18762306a36Sopenharmony_ci unsigned long second_dest; 18862306a36Sopenharmony_ci unsigned long checksum = ~0U; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci mskql(partial_dest, doff, partial_dest); 19162306a36Sopenharmony_ci while (len >= 0) { 19262306a36Sopenharmony_ci if (__get_word(ldq, word, src)) 19362306a36Sopenharmony_ci return 0; 19462306a36Sopenharmony_ci len -= 8; 19562306a36Sopenharmony_ci insql(word, doff, second_dest); 19662306a36Sopenharmony_ci checksum += carry; 19762306a36Sopenharmony_ci stq_u(partial_dest | second_dest, dst); 19862306a36Sopenharmony_ci src++; 19962306a36Sopenharmony_ci checksum += word; 20062306a36Sopenharmony_ci insqh(word, doff, partial_dest); 20162306a36Sopenharmony_ci carry = checksum < word; 20262306a36Sopenharmony_ci dst++; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci len += 8; 20562306a36Sopenharmony_ci if (len) { 20662306a36Sopenharmony_ci checksum += carry; 20762306a36Sopenharmony_ci if (__get_word(ldq, word, src)) 20862306a36Sopenharmony_ci return 0; 20962306a36Sopenharmony_ci mskql(word, len, word); 21062306a36Sopenharmony_ci len -= 8; 21162306a36Sopenharmony_ci checksum += word; 21262306a36Sopenharmony_ci insql(word, doff, second_dest); 21362306a36Sopenharmony_ci len += doff; 21462306a36Sopenharmony_ci carry = checksum < word; 21562306a36Sopenharmony_ci partial_dest |= second_dest; 21662306a36Sopenharmony_ci if (len >= 0) { 21762306a36Sopenharmony_ci stq_u(partial_dest, dst); 21862306a36Sopenharmony_ci if (!len) goto out; 21962306a36Sopenharmony_ci dst++; 22062306a36Sopenharmony_ci insqh(word, doff, partial_dest); 22162306a36Sopenharmony_ci } 22262306a36Sopenharmony_ci doff = len; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci ldq_u(second_dest, dst); 22562306a36Sopenharmony_ci mskqh(second_dest, doff, second_dest); 22662306a36Sopenharmony_ci stq_u(partial_dest | second_dest, dst); 22762306a36Sopenharmony_ciout: 22862306a36Sopenharmony_ci checksum += carry; 22962306a36Sopenharmony_ci return checksum; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci/* 23362306a36Sopenharmony_ci * This is so totally un-fun that it's frightening. Don't 23462306a36Sopenharmony_ci * look at this too closely, you'll go blind. 23562306a36Sopenharmony_ci */ 23662306a36Sopenharmony_cistatic inline unsigned long 23762306a36Sopenharmony_cicsum_partial_cfu_unaligned(const unsigned long __user * src, 23862306a36Sopenharmony_ci unsigned long * dst, 23962306a36Sopenharmony_ci unsigned long soff, unsigned long doff, 24062306a36Sopenharmony_ci long len, unsigned long partial_dest) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci unsigned long carry = 0; 24362306a36Sopenharmony_ci unsigned long first; 24462306a36Sopenharmony_ci unsigned long lastsrc; 24562306a36Sopenharmony_ci unsigned long checksum = ~0U; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (__get_word(ldq_u, first, src)) 24862306a36Sopenharmony_ci return 0; 24962306a36Sopenharmony_ci lastsrc = 7+len+(unsigned long)src; 25062306a36Sopenharmony_ci mskql(partial_dest, doff, partial_dest); 25162306a36Sopenharmony_ci while (len >= 0) { 25262306a36Sopenharmony_ci unsigned long second, word; 25362306a36Sopenharmony_ci unsigned long second_dest; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (__get_word(ldq_u, second, src+1)) 25662306a36Sopenharmony_ci return 0; 25762306a36Sopenharmony_ci extql(first, soff, word); 25862306a36Sopenharmony_ci checksum += carry; 25962306a36Sopenharmony_ci len -= 8; 26062306a36Sopenharmony_ci extqh(second, soff, first); 26162306a36Sopenharmony_ci src++; 26262306a36Sopenharmony_ci word |= first; 26362306a36Sopenharmony_ci first = second; 26462306a36Sopenharmony_ci insql(word, doff, second_dest); 26562306a36Sopenharmony_ci checksum += word; 26662306a36Sopenharmony_ci stq_u(partial_dest | second_dest, dst); 26762306a36Sopenharmony_ci carry = checksum < word; 26862306a36Sopenharmony_ci insqh(word, doff, partial_dest); 26962306a36Sopenharmony_ci dst++; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci len += doff; 27262306a36Sopenharmony_ci checksum += carry; 27362306a36Sopenharmony_ci if (len >= 0) { 27462306a36Sopenharmony_ci unsigned long second, word; 27562306a36Sopenharmony_ci unsigned long second_dest; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (__get_word(ldq_u, second, lastsrc)) 27862306a36Sopenharmony_ci return 0; 27962306a36Sopenharmony_ci extql(first, soff, word); 28062306a36Sopenharmony_ci extqh(second, soff, first); 28162306a36Sopenharmony_ci word |= first; 28262306a36Sopenharmony_ci first = second; 28362306a36Sopenharmony_ci mskql(word, len-doff, word); 28462306a36Sopenharmony_ci checksum += word; 28562306a36Sopenharmony_ci insql(word, doff, second_dest); 28662306a36Sopenharmony_ci carry = checksum < word; 28762306a36Sopenharmony_ci stq_u(partial_dest | second_dest, dst); 28862306a36Sopenharmony_ci if (len) { 28962306a36Sopenharmony_ci ldq_u(second_dest, dst+1); 29062306a36Sopenharmony_ci insqh(word, doff, partial_dest); 29162306a36Sopenharmony_ci mskqh(second_dest, len, second_dest); 29262306a36Sopenharmony_ci stq_u(partial_dest | second_dest, dst+1); 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci checksum += carry; 29562306a36Sopenharmony_ci } else { 29662306a36Sopenharmony_ci unsigned long second, word; 29762306a36Sopenharmony_ci unsigned long second_dest; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci if (__get_word(ldq_u, second, lastsrc)) 30062306a36Sopenharmony_ci return 0; 30162306a36Sopenharmony_ci extql(first, soff, word); 30262306a36Sopenharmony_ci extqh(second, soff, first); 30362306a36Sopenharmony_ci word |= first; 30462306a36Sopenharmony_ci ldq_u(second_dest, dst); 30562306a36Sopenharmony_ci mskql(word, len-doff, word); 30662306a36Sopenharmony_ci checksum += word; 30762306a36Sopenharmony_ci mskqh(second_dest, len, second_dest); 30862306a36Sopenharmony_ci carry = checksum < word; 30962306a36Sopenharmony_ci insql(word, doff, word); 31062306a36Sopenharmony_ci stq_u(partial_dest | word | second_dest, dst); 31162306a36Sopenharmony_ci checksum += carry; 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci return checksum; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_cistatic __wsum __csum_and_copy(const void __user *src, void *dst, int len) 31762306a36Sopenharmony_ci{ 31862306a36Sopenharmony_ci unsigned long soff = 7 & (unsigned long) src; 31962306a36Sopenharmony_ci unsigned long doff = 7 & (unsigned long) dst; 32062306a36Sopenharmony_ci unsigned long checksum; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (!doff) { 32362306a36Sopenharmony_ci if (!soff) 32462306a36Sopenharmony_ci checksum = csum_partial_cfu_aligned( 32562306a36Sopenharmony_ci (const unsigned long __user *) src, 32662306a36Sopenharmony_ci (unsigned long *) dst, len-8); 32762306a36Sopenharmony_ci else 32862306a36Sopenharmony_ci checksum = csum_partial_cfu_dest_aligned( 32962306a36Sopenharmony_ci (const unsigned long __user *) src, 33062306a36Sopenharmony_ci (unsigned long *) dst, 33162306a36Sopenharmony_ci soff, len-8); 33262306a36Sopenharmony_ci } else { 33362306a36Sopenharmony_ci unsigned long partial_dest; 33462306a36Sopenharmony_ci ldq_u(partial_dest, dst); 33562306a36Sopenharmony_ci if (!soff) 33662306a36Sopenharmony_ci checksum = csum_partial_cfu_src_aligned( 33762306a36Sopenharmony_ci (const unsigned long __user *) src, 33862306a36Sopenharmony_ci (unsigned long *) dst, 33962306a36Sopenharmony_ci doff, len-8, partial_dest); 34062306a36Sopenharmony_ci else 34162306a36Sopenharmony_ci checksum = csum_partial_cfu_unaligned( 34262306a36Sopenharmony_ci (const unsigned long __user *) src, 34362306a36Sopenharmony_ci (unsigned long *) dst, 34462306a36Sopenharmony_ci soff, doff, len-8, partial_dest); 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci return (__force __wsum)from64to16 (checksum); 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci__wsum 35062306a36Sopenharmony_cicsum_and_copy_from_user(const void __user *src, void *dst, int len) 35162306a36Sopenharmony_ci{ 35262306a36Sopenharmony_ci if (!access_ok(src, len)) 35362306a36Sopenharmony_ci return 0; 35462306a36Sopenharmony_ci return __csum_and_copy(src, dst, len); 35562306a36Sopenharmony_ci} 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci__wsum 35862306a36Sopenharmony_cicsum_partial_copy_nocheck(const void *src, void *dst, int len) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci return __csum_and_copy((__force const void __user *)src, 36162306a36Sopenharmony_ci dst, len); 36262306a36Sopenharmony_ci} 36362306a36Sopenharmony_ciEXPORT_SYMBOL(csum_partial_copy_nocheck); 364