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>
# include <linux/module.h>
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 ;
}
/*
* This is a version of ip_compute_csum ( ) optimized for IP headers ,
* which always checksum on 4 octet boundaries .
*/
2006-11-15 08:16:07 +03:00
__sum16 ip_fast_csum ( const void * iph , unsigned int ihl )
2005-04-17 02:20:36 +04:00
{
2006-11-15 08:16:07 +03:00
return ( __force __sum16 ) ~ do_csum ( iph , ihl * 4 ) ;
2005-04-17 02:20:36 +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
*/
2006-11-15 08:16:07 +03:00
/*
* Egads . . . That thing apparently assumes that * all * checksums it ever sees will
* be folded . Very likely a bug .
*/
__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:16:07 +03:00
result + = ( __force u32 ) sum ;
2005-04-17 02:20:36 +04:00
/* 16+c bits -> 16 bits */
result = ( result & 0xffff ) + ( result > > 16 ) ;
2006-11-15 08:16:07 +03:00
return ( __force __wsum ) result ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( csum_partial ) ;
/*
* this routine is used for miscellaneous IP - like checksums , mainly
* in icmp . c
*/
2006-11-15 08:16:07 +03:00
__sum16 ip_compute_csum ( const void * buff , int len )
2005-04-17 02:20:36 +04:00
{
2006-11-15 08:16:07 +03:00
return ( __force __sum16 ) ~ do_csum ( buff , len ) ;
2005-04-17 02:20:36 +04:00
}
/*
* copy from fs while checksumming , otherwise like csum_partial
*/
2006-11-15 08:16:07 +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
{
if ( csum_err ) * csum_err = 0 ;
2006-11-15 08:16:07 +03:00
memcpy ( dst , ( __force const void * ) src , len ) ;
2005-04-17 02:20:36 +04:00
return csum_partial ( dst , len , sum ) ;
}
/*
* copy from ds while checksumming , otherwise like csum_partial
*/
2006-11-15 08:16:07 +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 ) ;
}