blob: 0ef04ea42b15858bd851d9c296a82e1f8de20399 [file] [log] [blame]
/*
* Copyright (C) 2016 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.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** @file
*
* TCP/IP checksum
*
*/
#include <strings.h>
#include <ipxe/tcpip.h>
/** Alignment used by main checksumming loop */
#define TCPIP_CHKSUM_ALIGN 16
/** Number of steps in each iteration of the unrolled main checksumming loop */
#define TCPIP_CHKSUM_UNROLL 4
/**
* Calculate continued TCP/IP checkum
*
* @v sum Checksum of already-summed data, in network byte order
* @v data Data buffer
* @v len Length of data buffer
* @ret sum Updated checksum, in network byte order
*/
uint16_t tcpip_continue_chksum ( uint16_t sum, const void *data,
size_t len ) {
intptr_t start;
intptr_t end;
intptr_t mid;
unsigned int pre;
unsigned int post;
unsigned int first;
uint64_t discard_low;
uint64_t discard_high;
/* Avoid potentially undefined shift operation */
if ( len == 0 )
return sum;
/* Find maximally-aligned midpoint. For short blocks of data,
* this may be aligned to fewer than 16 bytes.
*/
start = ( ( intptr_t ) data );
end = ( start + len );
mid = ( end &
~( ( ~( 1UL << 63 ) ) >> ( 64 - flsl ( start ^ end ) ) ) );
/* Calculate pre- and post-alignment lengths */
pre = ( ( mid - start ) & ( TCPIP_CHKSUM_ALIGN - 1 ) );
post = ( ( end - mid ) & ( TCPIP_CHKSUM_ALIGN - 1 ) );
/* Calculate number of steps in first iteration of unrolled loop */
first = ( ( ( len - pre - post ) / TCPIP_CHKSUM_ALIGN ) &
( TCPIP_CHKSUM_UNROLL - 1 ) );
/* Calculate checksum */
__asm__ ( /* Invert sum */
"eor %w0, %w0, #0xffff\n\t"
/* Clear carry flag */
"cmn xzr, xzr\n\t"
/* Byteswap and sum pre-alignment byte, if applicable */
"tbz %w4, #0, 1f\n\t"
"ldrb %w2, [%1], #1\n\t"
"rev16 %w0, %w0\n\t"
"rev16 %w2, %w2\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Sum pre-alignment halfword, if applicable */
"tbz %w4, #1, 1f\n\t"
"ldrh %w2, [%1], #2\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Sum pre-alignment word, if applicable */
"tbz %w4, #2, 1f\n\t"
"ldr %w2, [%1], #4\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Sum pre-alignment doubleword, if applicable */
"tbz %w4, #3, 1f\n\t"
"ldr %2, [%1], #8\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Jump into unrolled (x4) main loop */
"adr %2, 2f\n\t"
"sub %2, %2, %5, lsl #3\n\t"
"sub %2, %2, %5, lsl #2\n\t"
"br %2\n\t"
"\n1:\n\t"
"ldp %2, %3, [%1], #16\n\t"
"adcs %0, %0, %2\n\t"
"adcs %0, %0, %3\n\t"
"ldp %2, %3, [%1], #16\n\t"
"adcs %0, %0, %2\n\t"
"adcs %0, %0, %3\n\t"
"ldp %2, %3, [%1], #16\n\t"
"adcs %0, %0, %2\n\t"
"adcs %0, %0, %3\n\t"
"ldp %2, %3, [%1], #16\n\t"
"adcs %0, %0, %2\n\t"
"adcs %0, %0, %3\n\t"
"\n2:\n\t"
"sub %2, %1, %6\n\t"
"cbnz %2, 1b\n\t"
/* Sum post-alignment doubleword, if applicable */
"tbz %w7, #3, 1f\n\t"
"ldr %2, [%1], #8\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Sum post-alignment word, if applicable */
"tbz %w7, #2, 1f\n\t"
"ldr %w2, [%1], #4\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Sum post-alignment halfword, if applicable */
"tbz %w7, #1, 1f\n\t"
"ldrh %w2, [%1], #2\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Sum post-alignment byte, if applicable */
"tbz %w7, #0, 1f\n\t"
"ldrb %w2, [%1], #1\n\t"
"adcs %0, %0, %2\n\t"
"\n1:\n\t"
/* Fold down to a uint32_t plus carry flag */
"lsr %2, %0, #32\n\t"
"adcs %w0, %w0, %w2\n\t"
/* Fold down to a uint16_t plus carry in bit 16 */
"ubfm %2, %0, #0, #15\n\t"
"ubfm %3, %0, #16, #31\n\t"
"adc %w0, %w2, %w3\n\t"
/* Fold down to a uint16_t */
"tbz %w0, #16, 1f\n\t"
"mov %w2, #0xffff\n\t"
"sub %w0, %w0, %w2\n\t"
"tbz %w0, #16, 1f\n\t"
"sub %w0, %w0, %w2\n\t"
"\n1:\n\t"
/* Byteswap back, if applicable */
"tbz %w4, #0, 1f\n\t"
"rev16 %w0, %w0\n\t"
"\n1:\n\t"
/* Invert sum */
"eor %w0, %w0, #0xffff\n\t"
: "+r" ( sum ), "+r" ( data ), "=&r" ( discard_low ),
"=&r" ( discard_high )
: "r" ( pre ), "r" ( first ), "r" ( end - post ),
"r" ( post )
: "cc" );
return sum;
}