2005-04-17 02:20:36 +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 . */
# include <net/checksum.h>
2006-01-08 12:01:19 +03:00
# include <linux/module.h>
2005-04-17 02:20:36 +04:00
static inline unsigned short from32to16 ( unsigned long x )
{
/* 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 long do_csum ( const unsigned char * buff , int len )
{
int odd , count ;
unsigned long result = 0 ;
if ( len < = 0 )
goto out ;
odd = 1 & ( unsigned long ) buff ;
if ( odd ) {
result = * buff ;
len - - ;
buff + + ;
}
count = len > > 1 ; /* nr of 16-bit words.. */
if ( count ) {
if ( 2 & ( unsigned long ) buff ) {
result + = * ( unsigned short * ) buff ;
count - - ;
len - = 2 ;
buff + = 2 ;
}
count > > = 1 ; /* nr of 32-bit words.. */
if ( count ) {
unsigned long carry = 0 ;
do {
unsigned long w = * ( unsigned long * ) buff ;
count - - ;
buff + = 4 ;
result + = carry ;
result + = w ;
carry = ( w > result ) ;
} while ( count ) ;
result + = carry ;
result = ( result & 0xffff ) + ( result > > 16 ) ;
}
if ( len & 2 ) {
result + = * ( unsigned short * ) buff ;
buff + = 2 ;
}
}
if ( len & 1 )
result + = ( * buff < < 8 ) ;
result = from32to16 ( result ) ;
if ( odd )
result = ( ( result > > 8 ) & 0xff ) | ( ( result & 0xff ) < < 8 ) ;
out :
return result ;
}
/*
* 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
*/
2006-11-15 08:15:40 +03:00
__wsum csum_partial ( const void * buff , int len , __wsum sum )
2005-04-17 02:20:36 +04:00
{
unsigned int result = do_csum ( buff , len ) ;
/* add in old sum, and carry.. */
2006-11-15 08:15:40 +03:00
result + = ( __force u32 ) sum ;
if ( ( __force u32 ) sum > result )
2005-04-17 02:20:36 +04:00
result + = 1 ;
2006-11-15 08:15:40 +03:00
return ( __force __wsum ) result ;
2005-04-17 02:20:36 +04:00
}
2006-01-08 12:01:19 +03:00
EXPORT_SYMBOL ( csum_partial ) ;
2005-04-17 02:20:36 +04:00
/*
* this routine is used for miscellaneous IP - like checksums , mainly
* in icmp . c
*/
2006-11-15 08:15:40 +03:00
__sum16 ip_compute_csum ( const void * buff , int len )
2005-04-17 02:20:36 +04:00
{
2006-11-15 08:15:40 +03:00
return ( __force __sum16 ) ~ do_csum ( buff , len ) ;
2005-04-17 02:20:36 +04:00
}
2006-01-08 12:01:19 +03:00
EXPORT_SYMBOL ( ip_compute_csum ) ;
2005-04-17 02:20:36 +04:00
/*
* copy from fs while checksumming , otherwise like csum_partial
*/
2006-11-15 08:15:40 +03:00
__wsum
csum_partial_copy_from_user ( const void __user * src , void * dst ,
int len , __wsum sum , int * csum_err )
2005-04-17 02:20:36 +04:00
{
2006-01-08 12:01:19 +03:00
int rem ;
if ( csum_err )
* csum_err = 0 ;
rem = copy_from_user ( dst , src , len ) ;
if ( rem ! = 0 ) {
if ( csum_err )
* csum_err = - EFAULT ;
memset ( dst + len - rem , 0 , rem ) ;
len = rem ;
}
2005-04-17 02:20:36 +04:00
return csum_partial ( dst , len , sum ) ;
}
2006-01-08 12:01:19 +03:00
EXPORT_SYMBOL ( csum_partial_copy_from_user ) ;
2005-04-17 02:20:36 +04:00
/*
* copy from ds while checksumming , otherwise like csum_partial
*/
2006-11-15 08:15:40 +03:00
__wsum
csum_partial_copy_nocheck ( const void * src , void * dst , int len , __wsum sum )
2005-04-17 02:20:36 +04:00
{
memcpy ( dst , src , len ) ;
return csum_partial ( dst , len , sum ) ;
}
2006-01-08 12:01:19 +03:00
2006-11-15 08:15:40 +03:00
EXPORT_SYMBOL ( csum_partial_copy_nocheck ) ;