/*
 * This is a SIMD SHA-1 implementation. It requires the Intel(R) Supplemental
 * SSE3 instruction set extensions introduced in Intel Core Microarchitecture
 * processors. CPUs supporting Intel(R) AVX extensions will get an additional
 * boost.
 *
 * This work was inspired by the vectorized implementation of Dean Gaudet.
 * Additional information on it can be found at:
 *    http://www.arctic.org/~dean/crypto/sha1.html
 *
 * It was improved upon with more efficient vectorization of the message
 * scheduling. This implementation has also been optimized for all current and
 * several future generations of Intel CPUs.
 *
 * See this article for more information about the implementation details:
 *   http://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1/
 *
 * Copyright (C) 2010, Intel Corp.
 *   Authors: Maxim Locktyukhin <maxim.locktyukhin@intel.com>
 *            Ronen Zohar <ronen.zohar@intel.com>
 *
 * Converted to AT&T syntax and adapted for inclusion in the Linux kernel:
 *   Author: Mathias Krause <minipli@googlemail.com>
 *
 * 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.
 */

#include <linux/linkage.h>

#define CTX	%rdi	// arg1
#define BUF	%rsi	// arg2
#define CNT	%rdx	// arg3

#define REG_A	%ecx
#define REG_B	%esi
#define REG_C	%edi
#define REG_D	%ebp
#define REG_E	%edx

#define REG_T1	%eax
#define REG_T2	%ebx

#define K_BASE		%r8
#define HASH_PTR	%r9
#define BUFFER_PTR	%r10
#define BUFFER_END	%r11

#define W_TMP1	%xmm0
#define W_TMP2	%xmm9

#define W0	%xmm1
#define W4	%xmm2
#define W8	%xmm3
#define W12	%xmm4
#define W16	%xmm5
#define W20	%xmm6
#define W24	%xmm7
#define W28	%xmm8

#define XMM_SHUFB_BSWAP	%xmm10

/* we keep window of 64 w[i]+K pre-calculated values in a circular buffer */
#define WK(t)	(((t) & 15) * 4)(%rsp)
#define W_PRECALC_AHEAD	16

/*
 * This macro implements the SHA-1 function's body for single 64-byte block
 * param: function's name
 */
.macro SHA1_VECTOR_ASM  name
	ENTRY(\name)

	push	%rbx
	push	%rbp
	push	%r12

	mov	%rsp, %r12
	sub	$64, %rsp		# allocate workspace
	and	$~15, %rsp		# align stack

	mov	CTX, HASH_PTR
	mov	BUF, BUFFER_PTR

	shl	$6, CNT			# multiply by 64
	add	BUF, CNT
	mov	CNT, BUFFER_END

	lea	K_XMM_AR(%rip), K_BASE
	xmm_mov	BSWAP_SHUFB_CTL(%rip), XMM_SHUFB_BSWAP

	SHA1_PIPELINED_MAIN_BODY

	# cleanup workspace
	mov	$8, %ecx
	mov	%rsp, %rdi
	xor	%rax, %rax
	rep stosq

	mov	%r12, %rsp		# deallocate workspace

	pop	%r12
	pop	%rbp
	pop	%rbx
	ret

	ENDPROC(\name)
.endm

/*
 * This macro implements 80 rounds of SHA-1 for one 64-byte block
 */
.macro SHA1_PIPELINED_MAIN_BODY
	INIT_REGALLOC

	mov	  (HASH_PTR), A
	mov	 4(HASH_PTR), B
	mov	 8(HASH_PTR), C
	mov	12(HASH_PTR), D
	mov	16(HASH_PTR), E

  .set i, 0
  .rept W_PRECALC_AHEAD
	W_PRECALC i
    .set i, (i+1)
  .endr

.align 4
1:
	RR F1,A,B,C,D,E,0
	RR F1,D,E,A,B,C,2
	RR F1,B,C,D,E,A,4
	RR F1,E,A,B,C,D,6
	RR F1,C,D,E,A,B,8

	RR F1,A,B,C,D,E,10
	RR F1,D,E,A,B,C,12
	RR F1,B,C,D,E,A,14
	RR F1,E,A,B,C,D,16
	RR F1,C,D,E,A,B,18

	RR F2,A,B,C,D,E,20
	RR F2,D,E,A,B,C,22
	RR F2,B,C,D,E,A,24
	RR F2,E,A,B,C,D,26
	RR F2,C,D,E,A,B,28

	RR F2,A,B,C,D,E,30
	RR F2,D,E,A,B,C,32
	RR F2,B,C,D,E,A,34
	RR F2,E,A,B,C,D,36
	RR F2,C,D,E,A,B,38

	RR F3,A,B,C,D,E,40
	RR F3,D,E,A,B,C,42
	RR F3,B,C,D,E,A,44
	RR F3,E,A,B,C,D,46
	RR F3,C,D,E,A,B,48

	RR F3,A,B,C,D,E,50
	RR F3,D,E,A,B,C,52
	RR F3,B,C,D,E,A,54
	RR F3,E,A,B,C,D,56
	RR F3,C,D,E,A,B,58

	add	$64, BUFFER_PTR		# move to the next 64-byte block
	cmp	BUFFER_END, BUFFER_PTR	# if the current is the last one use
	cmovae	K_BASE, BUFFER_PTR	# dummy source to avoid buffer overrun

	RR F4,A,B,C,D,E,60
	RR F4,D,E,A,B,C,62
	RR F4,B,C,D,E,A,64
	RR F4,E,A,B,C,D,66
	RR F4,C,D,E,A,B,68

	RR F4,A,B,C,D,E,70
	RR F4,D,E,A,B,C,72
	RR F4,B,C,D,E,A,74
	RR F4,E,A,B,C,D,76
	RR F4,C,D,E,A,B,78

	UPDATE_HASH   (HASH_PTR), A
	UPDATE_HASH  4(HASH_PTR), B
	UPDATE_HASH  8(HASH_PTR), C
	UPDATE_HASH 12(HASH_PTR), D
	UPDATE_HASH 16(HASH_PTR), E

	RESTORE_RENAMED_REGS
	cmp	K_BASE, BUFFER_PTR	# K_BASE means, we reached the end
	jne	1b
.endm

.macro INIT_REGALLOC
  .set A, REG_A
  .set B, REG_B
  .set C, REG_C
  .set D, REG_D
  .set E, REG_E
  .set T1, REG_T1
  .set T2, REG_T2
.endm

.macro RESTORE_RENAMED_REGS
	# order is important (REG_C is where it should be)
	mov	B, REG_B
	mov	D, REG_D
	mov	A, REG_A
	mov	E, REG_E
.endm

.macro SWAP_REG_NAMES  a, b
  .set _T, \a
  .set \a, \b
  .set \b, _T
.endm

.macro F1  b, c, d
	mov	\c, T1
	SWAP_REG_NAMES \c, T1
	xor	\d, T1
	and	\b, T1
	xor	\d, T1
.endm

.macro F2  b, c, d
	mov	\d, T1
	SWAP_REG_NAMES \d, T1
	xor	\c, T1
	xor	\b, T1
.endm

.macro F3  b, c ,d
	mov	\c, T1
	SWAP_REG_NAMES \c, T1
	mov	\b, T2
	or	\b, T1
	and	\c, T2
	and	\d, T1
	or	T2, T1
.endm

.macro F4  b, c, d
	F2 \b, \c, \d
.endm

.macro UPDATE_HASH  hash, val
	add	\hash, \val
	mov	\val, \hash
.endm

/*
 * RR does two rounds of SHA-1 back to back with W[] pre-calc
 *   t1 = F(b, c, d);   e += w(i)
 *   e += t1;           b <<= 30;   d  += w(i+1);
 *   t1 = F(a, b, c);
 *   d += t1;           a <<= 5;
 *   e += a;
 *   t1 = e;            a >>= 7;
 *   t1 <<= 5;
 *   d += t1;
 */
.macro RR  F, a, b, c, d, e, round
	add	WK(\round), \e
	\F   \b, \c, \d		# t1 = F(b, c, d);
	W_PRECALC (\round + W_PRECALC_AHEAD)
	rol	$30, \b
	add	T1, \e
	add	WK(\round + 1), \d

	\F   \a, \b, \c
	W_PRECALC (\round + W_PRECALC_AHEAD + 1)
	rol	$5, \a
	add	\a, \e
	add	T1, \d
	ror	$7, \a		# (a <<r 5) >>r 7) => a <<r 30)

	mov	\e, T1
	SWAP_REG_NAMES \e, T1

	rol	$5, T1
	add	T1, \d

	# write:  \a, \b
	# rotate: \a<=\d, \b<=\e, \c<=\a, \d<=\b, \e<=\c
.endm

.macro W_PRECALC  r
  .set i, \r

  .if (i < 20)
    .set K_XMM, 0
  .elseif (i < 40)
    .set K_XMM, 16
  .elseif (i < 60)
    .set K_XMM, 32
  .elseif (i < 80)
    .set K_XMM, 48
  .endif

  .if ((i < 16) || ((i >= 80) && (i < (80 + W_PRECALC_AHEAD))))
    .set i, ((\r) % 80)	    # pre-compute for the next iteration
    .if (i == 0)
	W_PRECALC_RESET
    .endif
	W_PRECALC_00_15
  .elseif (i<32)
	W_PRECALC_16_31
  .elseif (i < 80)   // rounds 32-79
	W_PRECALC_32_79
  .endif
.endm

.macro W_PRECALC_RESET
  .set W,          W0
  .set W_minus_04, W4
  .set W_minus_08, W8
  .set W_minus_12, W12
  .set W_minus_16, W16
  .set W_minus_20, W20
  .set W_minus_24, W24
  .set W_minus_28, W28
  .set W_minus_32, W
.endm

.macro W_PRECALC_ROTATE
  .set W_minus_32, W_minus_28
  .set W_minus_28, W_minus_24
  .set W_minus_24, W_minus_20
  .set W_minus_20, W_minus_16
  .set W_minus_16, W_minus_12
  .set W_minus_12, W_minus_08
  .set W_minus_08, W_minus_04
  .set W_minus_04, W
  .set W,          W_minus_32
.endm

.macro W_PRECALC_SSSE3

.macro W_PRECALC_00_15
	W_PRECALC_00_15_SSSE3
.endm
.macro W_PRECALC_16_31
	W_PRECALC_16_31_SSSE3
.endm
.macro W_PRECALC_32_79
	W_PRECALC_32_79_SSSE3
.endm

/* message scheduling pre-compute for rounds 0-15 */
.macro W_PRECALC_00_15_SSSE3
  .if ((i & 3) == 0)
	movdqu	(i*4)(BUFFER_PTR), W_TMP1
  .elseif ((i & 3) == 1)
	pshufb	XMM_SHUFB_BSWAP, W_TMP1
	movdqa	W_TMP1, W
  .elseif ((i & 3) == 2)
	paddd	(K_BASE), W_TMP1
  .elseif ((i & 3) == 3)
	movdqa  W_TMP1, WK(i&~3)
	W_PRECALC_ROTATE
  .endif
.endm

/* message scheduling pre-compute for rounds 16-31
 *
 * - calculating last 32 w[i] values in 8 XMM registers
 * - pre-calculate K+w[i] values and store to mem, for later load by ALU add
 *   instruction
 *
 * some "heavy-lifting" vectorization for rounds 16-31 due to w[i]->w[i-3]
 * dependency, but improves for 32-79
 */
.macro W_PRECALC_16_31_SSSE3
  # blended scheduling of vector and scalar instruction streams, one 4-wide
  # vector iteration / 4 scalar rounds
  .if ((i & 3) == 0)
	movdqa	W_minus_12, W
	palignr	$8, W_minus_16, W	# w[i-14]
	movdqa	W_minus_04, W_TMP1
	psrldq	$4, W_TMP1		# w[i-3]
	pxor	W_minus_08, W
  .elseif ((i & 3) == 1)
	pxor	W_minus_16, W_TMP1
	pxor	W_TMP1, W
	movdqa	W, W_TMP2
	movdqa	W, W_TMP1
	pslldq	$12, W_TMP2
  .elseif ((i & 3) == 2)
	psrld	$31, W
	pslld	$1, W_TMP1
	por	W, W_TMP1
	movdqa	W_TMP2, W
	psrld	$30, W_TMP2
	pslld	$2, W
  .elseif ((i & 3) == 3)
	pxor	W, W_TMP1
	pxor	W_TMP2, W_TMP1
	movdqa	W_TMP1, W
	paddd	K_XMM(K_BASE), W_TMP1
	movdqa	W_TMP1, WK(i&~3)
	W_PRECALC_ROTATE
  .endif
.endm

/* message scheduling pre-compute for rounds 32-79
 *
 * in SHA-1 specification: w[i] = (w[i-3] ^ w[i-8]  ^ w[i-14] ^ w[i-16]) rol 1
 * instead we do equal:    w[i] = (w[i-6] ^ w[i-16] ^ w[i-28] ^ w[i-32]) rol 2
 * allows more efficient vectorization since w[i]=>w[i-3] dependency is broken
 */
.macro W_PRECALC_32_79_SSSE3
  .if ((i & 3) == 0)
	movdqa	W_minus_04, W_TMP1
	pxor	W_minus_28, W		# W is W_minus_32 before xor
	palignr	$8, W_minus_08, W_TMP1
  .elseif ((i & 3) == 1)
	pxor	W_minus_16, W
	pxor	W_TMP1, W
	movdqa	W, W_TMP1
  .elseif ((i & 3) == 2)
	psrld	$30, W
	pslld	$2, W_TMP1
	por	W, W_TMP1
  .elseif ((i & 3) == 3)
	movdqa	W_TMP1, W
	paddd	K_XMM(K_BASE), W_TMP1
	movdqa	W_TMP1, WK(i&~3)
	W_PRECALC_ROTATE
  .endif
.endm

.endm		// W_PRECALC_SSSE3


#define K1	0x5a827999
#define K2	0x6ed9eba1
#define K3	0x8f1bbcdc
#define K4	0xca62c1d6

.section .rodata
.align 16

K_XMM_AR:
	.long K1, K1, K1, K1
	.long K2, K2, K2, K2
	.long K3, K3, K3, K3
	.long K4, K4, K4, K4

BSWAP_SHUFB_CTL:
	.long 0x00010203
	.long 0x04050607
	.long 0x08090a0b
	.long 0x0c0d0e0f


.section .text

W_PRECALC_SSSE3
.macro xmm_mov a, b
	movdqu	\a,\b
.endm

/* SSSE3 optimized implementation:
 *  extern "C" void sha1_transform_ssse3(u32 *digest, const char *data, u32 *ws,
 *                                       unsigned int rounds);
 */
SHA1_VECTOR_ASM     sha1_transform_ssse3

#ifdef CONFIG_AS_AVX

.macro W_PRECALC_AVX

.purgem W_PRECALC_00_15
.macro  W_PRECALC_00_15
    W_PRECALC_00_15_AVX
.endm
.purgem W_PRECALC_16_31
.macro  W_PRECALC_16_31
    W_PRECALC_16_31_AVX
.endm
.purgem W_PRECALC_32_79
.macro  W_PRECALC_32_79
    W_PRECALC_32_79_AVX
.endm

.macro W_PRECALC_00_15_AVX
  .if ((i & 3) == 0)
	vmovdqu	(i*4)(BUFFER_PTR), W_TMP1
  .elseif ((i & 3) == 1)
	vpshufb	XMM_SHUFB_BSWAP, W_TMP1, W
  .elseif ((i & 3) == 2)
	vpaddd	(K_BASE), W, W_TMP1
  .elseif ((i & 3) == 3)
	vmovdqa	W_TMP1, WK(i&~3)
	W_PRECALC_ROTATE
  .endif
.endm

.macro W_PRECALC_16_31_AVX
  .if ((i & 3) == 0)
	vpalignr $8, W_minus_16, W_minus_12, W	# w[i-14]
	vpsrldq	$4, W_minus_04, W_TMP1		# w[i-3]
	vpxor	W_minus_08, W, W
	vpxor	W_minus_16, W_TMP1, W_TMP1
  .elseif ((i & 3) == 1)
	vpxor	W_TMP1, W, W
	vpslldq	$12, W, W_TMP2
	vpslld	$1, W, W_TMP1
  .elseif ((i & 3) == 2)
	vpsrld	$31, W, W
	vpor	W, W_TMP1, W_TMP1
	vpslld	$2, W_TMP2, W
	vpsrld	$30, W_TMP2, W_TMP2
  .elseif ((i & 3) == 3)
	vpxor	W, W_TMP1, W_TMP1
	vpxor	W_TMP2, W_TMP1, W
	vpaddd	K_XMM(K_BASE), W, W_TMP1
	vmovdqu	W_TMP1, WK(i&~3)
	W_PRECALC_ROTATE
  .endif
.endm

.macro W_PRECALC_32_79_AVX
  .if ((i & 3) == 0)
	vpalignr $8, W_minus_08, W_minus_04, W_TMP1
	vpxor	W_minus_28, W, W		# W is W_minus_32 before xor
  .elseif ((i & 3) == 1)
	vpxor	W_minus_16, W_TMP1, W_TMP1
	vpxor	W_TMP1, W, W
  .elseif ((i & 3) == 2)
	vpslld	$2, W, W_TMP1
	vpsrld	$30, W, W
	vpor	W, W_TMP1, W
  .elseif ((i & 3) == 3)
	vpaddd	K_XMM(K_BASE), W, W_TMP1
	vmovdqu	W_TMP1, WK(i&~3)
	W_PRECALC_ROTATE
  .endif
.endm

.endm    // W_PRECALC_AVX

W_PRECALC_AVX
.purgem xmm_mov
.macro xmm_mov a, b
	vmovdqu	\a,\b
.endm


/* AVX optimized implementation:
 *  extern "C" void sha1_transform_avx(u32 *digest, const char *data, u32 *ws,
 *                                     unsigned int rounds);
 */
SHA1_VECTOR_ASM     sha1_transform_avx

#endif