2009-05-14 02:56:38 +04:00
/*
*
* INET An implementation of the TCP / IP protocol suite for the LINUX
* operating system . INET is implemented using the BSD Socket
* interface as the means of communication with the user level .
*
* IP / TCP / UDP checksumming routines
*
* Authors : Jorge Cwik , < jorge @ laser . satlink . net >
* Arnt Gulbrandsen , < agulbra @ nvg . unit . no >
* Tom May , < ftom @ netcom . com >
* Andreas Schwab , < schwab @ issan . informatik . uni - dortmund . de >
* Lots of code moved from tcp . c and ip . c ; see those files
* for more names .
*
* 03 / 02 / 96 Jes Sorensen , Andreas Schwab , Roman Hodek :
* Fixed some nasty bugs , causing some horrible crashes .
* A : At some points , the sum ( % 0 ) was used as
* length - counter instead of the length counter
* ( % 1 ) . Thanks to Roman Hodek for pointing this out .
* B : GCC seems to mess up if one uses too many
* data - registers to hold input values and one tries to
* specify d0 and d1 as scratch registers . Letting gcc
* choose these registers itself solves the problem .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*/
/* Revised by Kenneth Albanowski for m68knommu. Basic problem: unaligned access
kills , so most of the assembly has to go . */
2011-11-17 06:29:17 +04:00
# include <linux/export.h>
2009-05-14 02:56:38 +04:00
# include <net/checksum.h>
# include <asm/byteorder.h>
2009-06-23 23:37:26 +04:00
# ifndef do_csum
2009-06-23 23:22:58 +04:00
static inline unsigned short from32to16 ( unsigned int x )
2009-05-14 02:56:38 +04:00
{
/* add up 16-bit and 16-bit for 16+c bit */
x = ( x & 0xffff ) + ( x > > 16 ) ;
/* add up carry.. */
x = ( x & 0xffff ) + ( x > > 16 ) ;
return x ;
}
static unsigned int do_csum ( const unsigned char * buff , int len )
{
2011-07-07 05:18:49 +04:00
int odd ;
2009-06-23 23:22:58 +04:00
unsigned int result = 0 ;
2009-05-14 02:56:38 +04:00
if ( len < = 0 )
goto out ;
odd = 1 & ( unsigned long ) buff ;
if ( odd ) {
2009-06-19 12:41:19 +04:00
# ifdef __LITTLE_ENDIAN
result + = ( * buff < < 8 ) ;
2009-06-24 00:52:51 +04:00
# else
result = * buff ;
2009-06-19 12:41:19 +04:00
# endif
2009-05-14 02:56:38 +04:00
len - - ;
buff + + ;
}
2011-07-07 05:18:49 +04:00
if ( len > = 2 ) {
2009-05-14 02:56:38 +04:00
if ( 2 & ( unsigned long ) buff ) {
result + = * ( unsigned short * ) buff ;
len - = 2 ;
buff + = 2 ;
}
2011-07-07 05:18:49 +04:00
if ( len > = 4 ) {
const unsigned char * end = buff + ( ( unsigned ) len & ~ 3 ) ;
2009-06-23 23:22:58 +04:00
unsigned int carry = 0 ;
2009-05-14 02:56:38 +04:00
do {
2009-06-23 23:22:58 +04:00
unsigned int w = * ( unsigned int * ) buff ;
2009-05-14 02:56:38 +04:00
buff + = 4 ;
result + = carry ;
result + = w ;
carry = ( w > result ) ;
2011-07-07 05:18:49 +04:00
} while ( buff < end ) ;
2009-05-14 02:56:38 +04:00
result + = carry ;
result = ( result & 0xffff ) + ( result > > 16 ) ;
}
if ( len & 2 ) {
result + = * ( unsigned short * ) buff ;
buff + = 2 ;
}
}
if ( len & 1 )
2009-06-19 12:41:19 +04:00
# ifdef __LITTLE_ENDIAN
result + = * buff ;
# else
2009-05-14 02:56:38 +04:00
result + = ( * buff < < 8 ) ;
2009-06-19 12:41:19 +04:00
# endif
2009-05-14 02:56:38 +04:00
result = from32to16 ( result ) ;
if ( odd )
result = ( ( result > > 8 ) & 0xff ) | ( ( result & 0xff ) < < 8 ) ;
out :
return result ;
}
2009-06-23 23:37:26 +04:00
# endif
2009-05-14 02:56:38 +04:00
2013-01-18 13:42:16 +04:00
# ifndef ip_fast_csum
2009-05-14 02:56:38 +04:00
/*
* This is a version of ip_compute_csum ( ) optimized for IP headers ,
* which always checksum on 4 octet boundaries .
*/
__sum16 ip_fast_csum ( const void * iph , unsigned int ihl )
{
return ( __force __sum16 ) ~ do_csum ( iph , ihl * 4 ) ;
}
EXPORT_SYMBOL ( ip_fast_csum ) ;
2013-01-18 13:42:16 +04:00
# endif
2009-05-14 02:56:38 +04:00
/*
* computes the checksum of a memory block at buff , length len ,
* and adds in " sum " ( 32 - bit )
*
* returns a 32 - bit number suitable for feeding into itself
* or csum_tcpudp_magic
*
* this function must be called with even lengths , except
* for the last fragment , which may be odd
*
* it ' s best to have buff aligned on a 32 - bit boundary
*/
__wsum csum_partial ( const void * buff , int len , __wsum wsum )
{
unsigned int sum = ( __force unsigned int ) wsum ;
unsigned int result = do_csum ( buff , len ) ;
/* add in old sum, and carry.. */
result + = sum ;
if ( sum > result )
result + = 1 ;
return ( __force __wsum ) result ;
}
EXPORT_SYMBOL ( csum_partial ) ;
/*
* this routine is used for miscellaneous IP - like checksums , mainly
* in icmp . c
*/
__sum16 ip_compute_csum ( const void * buff , int len )
{
return ( __force __sum16 ) ~ do_csum ( buff , len ) ;
}
EXPORT_SYMBOL ( ip_compute_csum ) ;
/*
* copy from fs while checksumming , otherwise like csum_partial
*/
__wsum
csum_partial_copy_from_user ( const void __user * src , void * dst , int len ,
__wsum sum , int * csum_err )
{
int missing ;
missing = __copy_from_user ( dst , src , len ) ;
if ( missing ) {
memset ( dst + len - missing , 0 , missing ) ;
* csum_err = - EFAULT ;
} else
* csum_err = 0 ;
return csum_partial ( dst , len , sum ) ;
}
EXPORT_SYMBOL ( csum_partial_copy_from_user ) ;
/*
* copy from ds while checksumming , otherwise like csum_partial
*/
__wsum
csum_partial_copy ( const void * src , void * dst , int len , __wsum sum )
{
memcpy ( dst , src , len ) ;
return csum_partial ( dst , len , sum ) ;
}
EXPORT_SYMBOL ( csum_partial_copy ) ;
# ifndef csum_tcpudp_nofold
__wsum csum_tcpudp_nofold ( __be32 saddr , __be32 daddr ,
unsigned short len ,
unsigned short proto ,
__wsum sum )
{
unsigned long long s = ( __force u32 ) sum ;
s + = ( __force u32 ) saddr ;
s + = ( __force u32 ) daddr ;
# ifdef __BIG_ENDIAN
s + = proto + len ;
# else
s + = ( proto + len ) < < 8 ;
# endif
s + = ( s > > 32 ) ;
return ( __force __wsum ) s ;
}
EXPORT_SYMBOL ( csum_tcpudp_nofold ) ;
# endif