File: /usr/src/linux/arch/parisc/lib/checksum.c

1     /*
2      * INET		An implementation of the TCP/IP protocol suite for the LINUX
3      *		operating system.  INET is implemented using the  BSD Socket
4      *		interface as the means of communication with the user level.
5      *
6      *		MIPS specific IP/TCP/UDP checksumming routines
7      *
8      * Authors:	Ralf Baechle, <ralf@waldorf-gmbh.de>
9      *		Lots of code moved from tcp.c and ip.c; see those files
10      *		for more names.
11      *
12      *		This program is free software; you can redistribute it and/or
13      *		modify it under the terms of the GNU General Public License
14      *		as published by the Free Software Foundation; either version
15      *		2 of the License, or (at your option) any later version.
16      *
17      * $Id: checksum.c,v 1.3 1997/12/01 17:57:34 ralf Exp $
18      */
19     #include <net/checksum.h>
20     #include <linux/types.h>
21     #include <asm/byteorder.h>
22     #include <asm/string.h>
23     #include <asm/uaccess.h>
24     
25     static inline unsigned short from32to16(unsigned int x)
26     {
27     	/* 32 bits --> 16 bits + carry */
28     	x = (x & 0xffff) + (x >> 16);
29     	/* 16 bits + carry --> 16 bits including carry */
30     	x = (x & 0xffff) + (x >> 16);
31     	return (unsigned short)x;
32     }
33     
34     static inline unsigned int do_csum(const unsigned char * buff, int len)
35     {
36     	int odd, count;
37     	unsigned int result = 0;
38     
39     	if (len <= 0)
40     		goto out;
41     	odd = 1 & (unsigned long) buff;
42     	if (odd) {
43     		result = be16_to_cpu(*buff);
44     		len--;
45     		buff++;
46     	}
47     	count = len >> 1;		/* nr of 16-bit words.. */
48     	if (count) {
49     		if (2 & (unsigned long) buff) {
50     			result += *(unsigned short *) buff;
51     			count--;
52     			len -= 2;
53     			buff += 2;
54     		}
55     		count >>= 1;		/* nr of 32-bit words.. */
56     		if (count) {
57     			unsigned int carry = 0;
58     			do {
59     				unsigned int w = *(unsigned int *) buff;
60     				count--;
61     				buff += 4;
62     				result += carry;
63     				result += w;
64     				carry = (w > result);
65     			} while (count);
66     			result += carry;
67     			result = (result & 0xffff) + (result >> 16);
68     		}
69     		if (len & 2) {
70     			result += *(unsigned short *) buff;
71     			buff += 2;
72     		}
73     	}
74     	if (len & 1)
75     		result += le16_to_cpu(*buff);
76     	result = from32to16(result);
77     	if (odd)
78     		result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
79     out:
80     	return result;
81     }
82     
83     /*
84      * computes a partial checksum, e.g. for TCP/UDP fragments
85      */
86     unsigned int csum_partial(const unsigned char *buff, int len, unsigned int sum)
87     {
88     	unsigned int result = do_csum(buff, len);
89     
90     	/* add in old sum, and carry.. */
91     	result += sum;
92     	if(sum > result)
93     		result += 1;
94     	return result;
95     }
96     
97     /*
98      * copy while checksumming, otherwise like csum_partial
99      */
100     unsigned int csum_partial_copy(const char *src, char *dst, 
101                                    int len, unsigned int sum)
102     {
103     	/*
104     	 * It's 2:30 am and I don't feel like doing it real ...
105     	 * This is lots slower than the real thing (tm)
106     	 */
107     	sum = csum_partial(src, len, sum);
108     	memcpy(dst, src, len);
109     
110     	return sum;
111     }
112     
113     /*
114      * Copy from userspace and compute checksum.  If we catch an exception
115      * then zero the rest of the buffer.
116      */
117     unsigned int csum_partial_copy_from_user (const char *src, char *dst,
118                                               int len, unsigned int sum,
119                                               int *err_ptr)
120     {
121     	int missing;
122     
123     	missing = copy_from_user(dst, src, len);
124     	if (missing) {
125     		memset(dst + len - missing, 0, missing);
126     		*err_ptr = -EFAULT;
127     	}
128     		
129     	return csum_partial(dst, len, sum);
130     }
131