summaryrefslogtreecommitdiffstats
path: root/qemu/roms/ipxe/src/arch/x86/core/x86_tcpip.c
blob: 8a4ce5152cde146aa7f0ce8fcfec0f4f7da7e1e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
 * Copyright (C) 2012 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

FILE_LICENCE ( GPL2_OR_LATER );

/** @file
 *
 * TCP/IP checksum
 *
 */

#include <limits.h>
#include <ipxe/tcpip.h>

extern char x86_tcpip_loop_end[];

/**
 * Calculate continued TCP/IP checkum
 *
 * @v partial		Checksum of already-summed data, in network byte order
 * @v data		Data buffer
 * @v len		Length of data buffer
 * @ret cksum		Updated checksum, in network byte order
 */
uint16_t x86_tcpip_continue_chksum ( uint16_t partial,
				     const void *data, size_t len ) {
	unsigned long sum = ( ( ~partial ) & 0xffff );
	unsigned long initial_word_count;
	unsigned long loop_count;
	unsigned long loop_partial_count;
	unsigned long final_word_count;
	unsigned long final_byte;
	unsigned long discard_S;
	unsigned long discard_c;
	unsigned long discard_a;
	unsigned long discard_r1;
	unsigned long discard_r2;

	/* Calculate number of initial 16-bit words required to bring
	 * the main loop into alignment.  (We don't care about the
	 * speed for data aligned to less than 16 bits, since this
	 * situation won't occur in practice.)
	 */
	if ( len >= sizeof ( sum ) ) {
		initial_word_count = ( ( -( ( intptr_t ) data ) &
					 ( sizeof ( sum ) - 1 ) ) >> 1 );
	} else {
		initial_word_count = 0;
	}
	len -= ( initial_word_count * 2 );

	/* Calculate number of iterations of the main loop.  This loop
	 * processes native machine words (32-bit or 64-bit), and is
	 * unrolled 16 times.  We calculate an overall iteration
	 * count, and a starting point for the first iteration.
	 */
	loop_count = ( len / ( sizeof ( sum ) * 16 ) );
	loop_partial_count =
		( ( len % ( sizeof ( sum ) * 16 ) ) / sizeof ( sum ) );

	/* Calculate number of 16-bit words remaining after the main
	 * loop completes.
	 */
	final_word_count = ( ( len % sizeof ( sum ) ) / 2 );

	/* Calculate whether or not a final byte remains at the end */
	final_byte = ( len & 1 );

	/* Calculate the checksum */
	__asm__ ( /* Calculate position at which to jump into the
		   * unrolled loop.
		   */
		  "imul $( -x86_tcpip_loop_step_size ), %4\n\t"
		  "add %5, %4\n\t"

		  /* Clear carry flag before starting checksumming */
		  "clc\n\t"

		  /* Checksum initial words */
		  "jmp 2f\n\t"
		  "\n1:\n\t"
		  "lodsw\n\t"
		  "adcw %w2, %w0\n\t"
		  "\n2:\n\t"
		  "loop 1b\n\t"

		  /* Main "lods;adc" loop, unrolled x16 */
		  "mov %12, %3\n\t"
		  "jmp *%4\n\t"
		  "\nx86_tcpip_loop_start:\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "lods%z2\n\tadc %2, %0\n\t"
		  "\nx86_tcpip_loop_end:\n\t"
		  "loop x86_tcpip_loop_start\n\t"
		  ".equ x86_tcpip_loop_step_size, "
		  "  ( ( x86_tcpip_loop_end - x86_tcpip_loop_start ) >> 4 )\n\t"

		  /* Checksum remaining whole words */
		  "mov %13, %3\n\t"
		  "jmp 2f\n\t"
		  "\n1:\n\t"
		  "lodsw\n\t"
		  "adcw %w2, %w0\n\t"
		  "\n2:\n\t"
		  "loop 1b\n\t"

		  /* Checksum final byte if applicable */
		  "mov %14, %3\n\t"
		  "loop 1f\n\t"
		  "adcb (%1), %b0\n\t"
		  "adcb $0, %h0\n\t"
		  "\n1:\n\t"

		  /* Fold down to a uint16_t */
		  "push %0\n\t"
		  "popw %w0\n\t"
		  "popw %w2\n\t"
		  "adcw %w2, %w0\n\t"
#if ULONG_MAX > 0xffffffffUL /* 64-bit only */
		  "popw %w2\n\t"
		  "adcw %w2, %w0\n\t"
		  "popw %w2\n\t"
		  "adcw %w2, %w0\n\t"
#endif /* 64-bit only */

		  /* Consume CF */
		  "adcw $0, %w0\n\t"
		  "adcw $0, %w0\n\t"

		  : "=&Q" ( sum ), "=&S" ( discard_S ), "=&a" ( discard_a ),
		    "=&c" ( discard_c ), "=&r" ( discard_r1 ),
		    "=&r" ( discard_r2 )
		  : "0" ( sum ), "1" ( data ), "2" ( 0 ),
		    "3" ( initial_word_count + 1 ), "4" ( loop_partial_count ),
		    "5" ( x86_tcpip_loop_end ), "g" ( loop_count + 1 ),
		    "g" ( final_word_count + 1 ), "g" ( final_byte ) );

	return ( ~sum & 0xffff );
}