/* ARMv8 CE accelerated AES
 * Copyright (C) 2016 Jussi Kivilinna <jussi.kivilinna@iki.fi>
 *
 * This file is part of Libgcrypt.
 *
 * Libgcrypt is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * Libgcrypt 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#if defined(HAVE_ARM_ARCH_V6) && defined(__ARMEL__) && \
    defined(HAVE_COMPATIBLE_GCC_ARM_PLATFORM_AS) && \
    defined(HAVE_GCC_INLINE_ASM_AARCH32_CRYPTO)

.syntax unified
.fpu crypto-neon-fp-armv8
.arm

.text

#ifdef __PIC__
#  define GET_DATA_POINTER(reg, name, rtmp) \
		ldr reg, 1f; \
		ldr rtmp, 2f; \
		b 3f; \
	1:	.word _GLOBAL_OFFSET_TABLE_-(3f+8); \
	2:	.word name(GOT); \
	3:	add reg, pc, reg; \
		ldr reg, [reg, rtmp];
#else
#  define GET_DATA_POINTER(reg, name, rtmp) ldr reg, =name
#endif


/* AES macros */

#define aes_preload_keys(keysched, rekeysched) \
        vldmia   keysched!, {q5-q7}; \
        mov      rekeysched, keysched; \
        vldmialo keysched!, {q8-q15}; /* 128-bit */ \
        addeq    keysched, #(2*16); \
        vldmiaeq keysched!, {q10-q15}; /* 192-bit */ \
        addhi    keysched, #(4*16); \
        vldmiahi keysched!, {q12-q15}; /* 256-bit */ \

#define do_aes_one128(ed, mcimc, qo, qb) \
        aes##ed.8    qb, q5; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q6; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q7; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q8; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q9; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q10; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q11; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q12; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q13; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q14; \
        veor         qo, qb, q15;

#define do_aes_one128re(ed, mcimc, qo, qb, keysched, rekeysched) \
        vldm         rekeysched, {q8-q9}; \
        do_aes_one128(ed, mcimc, qo, qb);

#define do_aes_one192(ed, mcimc, qo, qb, keysched, rekeysched) \
        vldm         rekeysched!, {q8}; \
        aes##ed.8    qb, q5; \
        aes##mcimc.8 qb, qb; \
        vldm         rekeysched, {q9}; \
        aes##ed.8    qb, q6; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q7; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q8; \
        aes##mcimc.8 qb, qb; \
        vldmia       keysched!, {q8}; \
        aes##ed.8    qb, q9; \
        aes##mcimc.8 qb, qb; \
        sub          rekeysched, #(1*16); \
        aes##ed.8    qb, q10; \
        aes##mcimc.8 qb, qb; \
        vldm         keysched, {q9}; \
        aes##ed.8    qb, q11; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q12; \
        aes##mcimc.8 qb, qb; \
        sub          keysched, #16; \
        aes##ed.8    qb, q13; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q14; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q15; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q8; \
        veor         qo, qb, q9; \

#define do_aes_one256(ed, mcimc, qo, qb, keysched, rekeysched) \
        vldmia       rekeysched!, {q8}; \
        aes##ed.8    qb, q5; \
        aes##mcimc.8 qb, qb; \
        vldmia       rekeysched!, {q9}; \
        aes##ed.8    qb, q6; \
        aes##mcimc.8 qb, qb; \
        vldmia       rekeysched!, {q10}; \
        aes##ed.8    qb, q7; \
        aes##mcimc.8 qb, qb; \
        vldm         rekeysched, {q11}; \
        aes##ed.8    qb, q8; \
        aes##mcimc.8 qb, qb; \
        vldmia       keysched!, {q8}; \
        aes##ed.8    qb, q9; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q10; \
        aes##mcimc.8 qb, qb; \
        vldmia       keysched!, {q9}; \
        aes##ed.8    qb, q11; \
        aes##mcimc.8 qb, qb; \
        sub          rekeysched, #(3*16); \
        aes##ed.8    qb, q12; \
        aes##mcimc.8 qb, qb; \
        vldmia       keysched!, {q10}; \
        aes##ed.8    qb, q13; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q14; \
        aes##mcimc.8 qb, qb; \
        vldm         keysched, {q11}; \
        aes##ed.8    qb, q15; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q8; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q9; \
        aes##mcimc.8 qb, qb; \
        aes##ed.8    qb, q10; \
        veor         qo, qb, q11; \
        sub          keysched, #(3*16); \

#define aes_round_4(ed, mcimc, b0, b1, b2, b3, key) \
        aes##ed.8    b0, key; \
        aes##mcimc.8 b0, b0; \
          aes##ed.8    b1, key; \
          aes##mcimc.8 b1, b1; \
            aes##ed.8    b2, key; \
            aes##mcimc.8 b2, b2; \
              aes##ed.8    b3, key; \
              aes##mcimc.8 b3, b3;

#define do_aes_4_128(ed, mcimc, b0, b1, b2, b3) \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q5); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q6); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q7); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q8); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q9); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q10); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q11); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q12); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q13); \
        aes##ed.8    b0, q14; \
        veor         b0, b0, q15; \
        aes##ed.8    b1, q14; \
        veor         b1, b1, q15; \
        aes##ed.8    b2, q14; \
        veor         b2, b2, q15; \
        aes##ed.8    b3, q14; \
        veor         b3, b3, q15;

#define do_aes_4_128re(ed, mcimc, b0, b1, b2, b3, keysched, rekeysched) \
        vldm         rekeysched, {q8-q9}; \
        do_aes_4_128(ed, mcimc, b0, b1, b2, b3);

#define do_aes_4_192(ed, mcimc, b0, b1, b2, b3, keysched, rekeysched) \
        vldm         rekeysched!, {q8}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q5); \
        vldm         rekeysched, {q9}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q6); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q7); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q8); \
        vldmia       keysched!, {q8}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q9); \
        sub          rekeysched, #(1*16); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q10); \
        vldm         keysched, {q9}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q11); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q12); \
        sub          keysched, #16; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q13); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q14); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q15); \
        aes##ed.8    b0, q8; \
        veor         b0, b0, q9; \
        aes##ed.8    b1, q8; \
        veor         b1, b1, q9; \
        aes##ed.8    b2, q8; \
        veor         b2, b2, q9; \
        aes##ed.8    b3, q8; \
        veor         b3, b3, q9;

#define do_aes_4_256(ed, mcimc, b0, b1, b2, b3, keysched, rekeysched) \
        vldmia       rekeysched!, {q8}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q5); \
        vldmia       rekeysched!, {q9}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q6); \
        vldmia       rekeysched!, {q10}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q7); \
        vldm         rekeysched, {q11}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q8); \
        vldmia       keysched!, {q8}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q9); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q10); \
        vldmia       keysched!, {q9}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q11); \
        sub          rekeysched, #(3*16); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q12); \
        vldmia       keysched!, {q10}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q13); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q14); \
        vldm         keysched, {q11}; \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q15); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q8); \
        aes_round_4(ed, mcimc, b0, b1, b2, b3, q9); \
        sub          keysched, #(3*16); \
        aes##ed.8    b0, q10; \
        veor         b0, b0, q11; \
        aes##ed.8    b1, q10; \
        veor         b1, b1, q11; \
        aes##ed.8    b2, q10; \
        veor         b2, b2, q11; \
        aes##ed.8    b3, q10; \
        veor         b3, b3, q11;


/* Other functional macros */

#define CLEAR_REG(reg) veor reg, reg;


/*
 * unsigned int _gcry_aes_enc_armv8_ce(void *keysched, byte *dst,
 *                                     const byte *src,
 *                                     unsigned int nrounds);
 */
.align 3
.globl _gcry_aes_enc_armv8_ce
.type  _gcry_aes_enc_armv8_ce,%function;
_gcry_aes_enc_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: dst
   *    r2: src
   *    r3: nrounds
   */

  vldmia r0!, {q1-q3} /* load 3 round keys */

  cmp r3, #12

  vld1.8 {q0}, [r2]

  bhi .Lenc1_256
  beq .Lenc1_192

.Lenc1_128:

.Lenc1_tail:
  vldmia r0, {q8-q15} /* load 8 round keys */

  aese.8  q0, q1
  aesmc.8 q0, q0
  CLEAR_REG(q1)

  aese.8  q0, q2
  aesmc.8 q0, q0
  CLEAR_REG(q2)

  aese.8  q0, q3
  aesmc.8 q0, q0
  CLEAR_REG(q3)

  aese.8  q0, q8
  aesmc.8 q0, q0
  CLEAR_REG(q8)

  aese.8  q0, q9
  aesmc.8 q0, q0
  CLEAR_REG(q9)

  aese.8  q0, q10
  aesmc.8 q0, q0
  CLEAR_REG(q10)

  aese.8  q0, q11
  aesmc.8 q0, q0
  CLEAR_REG(q11)

  aese.8  q0, q12
  aesmc.8 q0, q0
  CLEAR_REG(q12)

  aese.8  q0, q13
  aesmc.8 q0, q0
  CLEAR_REG(q13)

  aese.8  q0, q14
  veor    q0, q15
  CLEAR_REG(q14)
  CLEAR_REG(q15)

  vst1.8 {q0}, [r1]
  CLEAR_REG(q0)

  mov r0, #0
  bx lr

.Lenc1_192:
  aese.8  q0, q1
  aesmc.8 q0, q0
  vmov q1, q3

  aese.8  q0, q2
  aesmc.8 q0, q0
  vldm r0!, {q2-q3} /* load 3 round keys */

  b .Lenc1_tail

.Lenc1_256:
  vldm r0!, {q15}   /* load 1 round key */
  aese.8  q0, q1
  aesmc.8 q0, q0

  aese.8  q0, q2
  aesmc.8 q0, q0

  aese.8  q0, q3
  aesmc.8 q0, q0
  vldm r0!, {q1-q3} /* load 3 round keys */

  aese.8  q0, q15
  aesmc.8 q0, q0

  b .Lenc1_tail
.size _gcry_aes_enc_armv8_ce,.-_gcry_aes_enc_armv8_ce;


/*
 * unsigned int _gcry_aes_dec_armv8_ce(void *keysched, byte *dst,
 *                                     const byte *src,
 *                                     unsigned int nrounds);
 */
.align 3
.globl _gcry_aes_dec_armv8_ce
.type  _gcry_aes_dec_armv8_ce,%function;
_gcry_aes_dec_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: dst
   *    r2: src
   *    r3: nrounds
   */

  vldmia r0!, {q1-q3} /* load 3 round keys */

  cmp r3, #12

  vld1.8 {q0}, [r2]

  bhi .Ldec1_256
  beq .Ldec1_192

.Ldec1_128:

.Ldec1_tail:
  vldmia r0, {q8-q15} /* load 8 round keys */

  aesd.8   q0, q1
  aesimc.8 q0, q0
  CLEAR_REG(q1)

  aesd.8   q0, q2
  aesimc.8 q0, q0
  CLEAR_REG(q2)

  aesd.8   q0, q3
  aesimc.8 q0, q0
  CLEAR_REG(q3)

  aesd.8   q0, q8
  aesimc.8 q0, q0
  CLEAR_REG(q8)

  aesd.8   q0, q9
  aesimc.8 q0, q0
  CLEAR_REG(q9)

  aesd.8   q0, q10
  aesimc.8 q0, q0
  CLEAR_REG(q10)

  aesd.8   q0, q11
  aesimc.8 q0, q0
  CLEAR_REG(q11)

  aesd.8   q0, q12
  aesimc.8 q0, q0
  CLEAR_REG(q12)

  aesd.8   q0, q13
  aesimc.8 q0, q0
  CLEAR_REG(q13)

  aesd.8   q0, q14
  veor     q0, q15
  CLEAR_REG(q14)
  CLEAR_REG(q15)

  vst1.8 {q0}, [r1]
  CLEAR_REG(q0)

  mov r0, #0
  bx lr

.Ldec1_192:
  aesd.8   q0, q1
  aesimc.8 q0, q0
  vmov q1, q3

  aesd.8   q0, q2
  aesimc.8 q0, q0
  vldm r0!, {q2-q3} /* load 3 round keys */

  b .Ldec1_tail

.Ldec1_256:
  vldm r0!, {q15}   /* load 1 round key */
  aesd.8   q0, q1
  aesimc.8 q0, q0

  aesd.8   q0, q2
  aesimc.8 q0, q0

  aesd.8  q0, q3
  aesimc.8 q0, q0
  vldm r0!, {q1-q3} /* load 3 round keys */

  aesd.8   q0, q15
  aesimc.8 q0, q0

  b .Ldec1_tail
.size _gcry_aes_dec_armv8_ce,.-_gcry_aes_dec_armv8_ce;


/*
 * void _gcry_aes_cbc_enc_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *iv, size_t nblocks,
 *                                  int cbc_mac, unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_cbc_enc_armv8_ce
.type  _gcry_aes_cbc_enc_armv8_ce,%function;
_gcry_aes_cbc_enc_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: iv
   *    %st+0: nblocks => r4
   *    %st+4: cbc_mac => r5
   *    %st+8: nrounds => r6
   */

  push {r4-r6,lr} /* 4*4 = 16b */
  ldr r4, [sp, #(16+0)]
  ldr r5, [sp, #(16+4)]
  cmp r4, #0
  ldr r6, [sp, #(16+8)]
  beq .Lcbc_enc_skip
  cmp r5, #0
  vpush {q4-q7}
  moveq r5, #16
  movne r5, #0

  cmp r6, #12
  vld1.8 {q1}, [r3] /* load IV */

  aes_preload_keys(r0, lr);

  beq .Lcbc_enc_loop192
  bhi .Lcbc_enc_loop256

#define CBC_ENC(bits, ...) \
  .Lcbc_enc_loop##bits: \
    vld1.8 {q0}, [r2]!; /* load plaintext */ \
    veor q1, q0, q1; \
    subs r4, r4, #1; \
    \
    do_aes_one##bits(e, mc, q1, q1, ##__VA_ARGS__); \
    \
    vst1.8 {q1}, [r1], r5; /* store ciphertext */ \
    \
    bne .Lcbc_enc_loop##bits; \
    b .Lcbc_enc_done;

  CBC_ENC(128)
  CBC_ENC(192, r0, lr)
  CBC_ENC(256, r0, lr)

#undef CBC_ENC

.Lcbc_enc_done:
  vst1.8 {q1}, [r3] /* store IV */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  vpop {q4-q7}
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

.Lcbc_enc_skip:
  pop {r4-r6,pc}
.size _gcry_aes_cbc_enc_armv8_ce,.-_gcry_aes_cbc_enc_armv8_ce;


/*
 * void _gcry_aes_cbc_dec_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *iv, unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_cbc_dec_armv8_ce
.type  _gcry_aes_cbc_dec_armv8_ce,%function;
_gcry_aes_cbc_dec_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: iv
   *    %st+0: nblocks => r4
   *    %st+4: nrounds => r5
   */

  push {r4-r6,lr} /* 4*4 = 16b */
  ldr r4, [sp, #(16+0)]
  ldr r5, [sp, #(16+4)]
  cmp r4, #0
  beq .Lcbc_dec_skip
  vpush {q4-q7}

  cmp r5, #12
  vld1.8 {q0}, [r3] /* load IV */

  aes_preload_keys(r0, r6);

  beq .Lcbc_dec_entry_192
  bhi .Lcbc_dec_entry_256

#define CBC_DEC(bits, ...) \
  .Lcbc_dec_entry_##bits: \
    cmp r4, #4; \
    blo .Lcbc_dec_loop_##bits; \
    \
  .Lcbc_dec_loop4_##bits: \
    \
    vld1.8 {q1-q2}, [r2]!; /* load ciphertext */ \
    sub r4, r4, #4; \
    vld1.8 {q3-q4}, [r2]; /* load ciphertext */ \
    cmp r4, #4; \
    sub r2, #32; \
    \
    do_aes_4_##bits(d, imc, q1, q2, q3, q4, ##__VA_ARGS__); \
    \
    veor q1, q1, q0; \
    vld1.8 {q0}, [r2]!; /* load next IV */ \
    veor q2, q2, q0; \
    vld1.8 {q0}, [r2]!; /* load next IV */ \
    vst1.8 {q1-q2}, [r1]!; /* store plaintext */ \
    veor q3, q3, q0; \
    vld1.8 {q0}, [r2]!; /* load next IV */ \
    veor q4, q4, q0; \
    vld1.8 {q0}, [r2]!; /* load next IV */ \
    vst1.8 {q3-q4}, [r1]!; /* store plaintext */ \
    \
    bhs .Lcbc_dec_loop4_##bits; \
    cmp r4, #0; \
    beq .Lcbc_dec_done; \
    \
  .Lcbc_dec_loop_##bits: \
    vld1.8 {q1}, [r2]!; /* load ciphertext */ \
    subs r4, r4, #1; \
    vmov q2, q1; \
    \
    do_aes_one##bits(d, imc, q1, q1, ##__VA_ARGS__); \
    \
    veor q1, q1, q0; \
    vmov q0, q2; \
    vst1.8 {q1}, [r1]!; /* store plaintext */ \
    \
    bne .Lcbc_dec_loop_##bits; \
    b .Lcbc_dec_done;

  CBC_DEC(128)
  CBC_DEC(192, r0, r6)
  CBC_DEC(256, r0, r6)

#undef CBC_DEC

.Lcbc_dec_done:
  vst1.8 {q0}, [r3] /* store IV */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  vpop {q4-q7}
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

.Lcbc_dec_skip:
  pop {r4-r6,pc}
.size _gcry_aes_cbc_dec_armv8_ce,.-_gcry_aes_cbc_dec_armv8_ce;


/*
 * void _gcry_aes_cfb_enc_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *iv, unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_cfb_enc_armv8_ce
.type  _gcry_aes_cfb_enc_armv8_ce,%function;
_gcry_aes_cfb_enc_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: iv
   *    %st+0: nblocks => r4
   *    %st+4: nrounds => r5
   */

  push {r4-r6,lr} /* 4*4 = 16b */
  ldr r4, [sp, #(16+0)]
  ldr r5, [sp, #(16+4)]
  cmp r4, #0
  beq .Lcfb_enc_skip
  vpush {q4-q7}

  cmp r5, #12
  vld1.8 {q0}, [r3] /* load IV */

  aes_preload_keys(r0, r6);

  beq .Lcfb_enc_entry_192
  bhi .Lcfb_enc_entry_256

#define CFB_ENC(bits, ...) \
  .Lcfb_enc_entry_##bits: \
  .Lcfb_enc_loop_##bits: \
    vld1.8 {q1}, [r2]!; /* load plaintext */ \
    subs r4, r4, #1; \
    \
    do_aes_one##bits(e, mc, q0, q0, ##__VA_ARGS__); \
    \
    veor q0, q1, q0; \
    vst1.8 {q0}, [r1]!; /* store ciphertext */ \
    \
    bne .Lcfb_enc_loop_##bits; \
    b .Lcfb_enc_done;

  CFB_ENC(128)
  CFB_ENC(192, r0, r6)
  CFB_ENC(256, r0, r6)

#undef CFB_ENC

.Lcfb_enc_done:
  vst1.8 {q0}, [r3] /* store IV */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  vpop {q4-q7}
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

.Lcfb_enc_skip:
  pop {r4-r6,pc}
.size _gcry_aes_cfb_enc_armv8_ce,.-_gcry_aes_cfb_enc_armv8_ce;


/*
 * void _gcry_aes_cfb_dec_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *iv, unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_cfb_dec_armv8_ce
.type  _gcry_aes_cfb_dec_armv8_ce,%function;
_gcry_aes_cfb_dec_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: iv
   *    %st+0: nblocks => r4
   *    %st+4: nrounds => r5
   */

  push {r4-r6,lr} /* 4*4 = 16b */
  ldr r4, [sp, #(16+0)]
  ldr r5, [sp, #(16+4)]
  cmp r4, #0
  beq .Lcfb_dec_skip
  vpush {q4-q7}

  cmp r5, #12
  vld1.8 {q0}, [r3] /* load IV */

  aes_preload_keys(r0, r6);

  beq .Lcfb_dec_entry_192
  bhi .Lcfb_dec_entry_256

#define CFB_DEC(bits, ...) \
  .Lcfb_dec_entry_##bits: \
    cmp r4, #4; \
    blo .Lcfb_dec_loop_##bits; \
    \
  .Lcfb_dec_loop4_##bits: \
    \
    vld1.8 {q2-q3}, [r2]!; /* load ciphertext */ \
    vmov q1, q0; \
    sub r4, r4, #4; \
    vld1.8 {q4}, [r2]; /* load ciphertext */ \
    sub r2, #32; \
    cmp r4, #4; \
    \
    do_aes_4_##bits(e, mc, q1, q2, q3, q4, ##__VA_ARGS__); \
    \
    vld1.8 {q0}, [r2]!; /* load ciphertext */ \
    veor q1, q1, q0; \
    vld1.8 {q0}, [r2]!; /* load ciphertext */ \
    veor q2, q2, q0; \
    vst1.8 {q1-q2}, [r1]!; /* store plaintext */ \
    vld1.8 {q0}, [r2]!; \
    veor q3, q3, q0; \
    vld1.8 {q0}, [r2]!; /* load next IV / ciphertext */ \
    veor q4, q4, q0; \
    vst1.8 {q3-q4}, [r1]!; /* store plaintext */ \
    \
    bhs .Lcfb_dec_loop4_##bits; \
    cmp r4, #0; \
    beq .Lcfb_dec_done; \
    \
  .Lcfb_dec_loop_##bits: \
    \
    vld1.8 {q1}, [r2]!; /* load ciphertext */ \
    \
    subs r4, r4, #1; \
    \
    do_aes_one##bits(e, mc, q0, q0, ##__VA_ARGS__); \
    \
    veor q2, q1, q0; \
    vmov q0, q1; \
    vst1.8 {q2}, [r1]!; /* store plaintext */ \
    \
    bne .Lcfb_dec_loop_##bits; \
    b .Lcfb_dec_done;

  CFB_DEC(128)
  CFB_DEC(192, r0, r6)
  CFB_DEC(256, r0, r6)

#undef CFB_DEC

.Lcfb_dec_done:
  vst1.8 {q0}, [r3] /* store IV */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  vpop {q4-q7}
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

.Lcfb_dec_skip:
  pop {r4-r6,pc}
.size _gcry_aes_cfb_dec_armv8_ce,.-_gcry_aes_cfb_dec_armv8_ce;


/*
 * void _gcry_aes_ctr_enc_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *iv, unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_ctr_enc_armv8_ce
.type  _gcry_aes_ctr_enc_armv8_ce,%function;
_gcry_aes_ctr_enc_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: iv
   *    %st+0: nblocks => r4
   *    %st+4: nrounds => r5
   */

  vpush {q4-q7}
  push {r4-r12,lr} /* 4*16 + 4*10 = 104b */
  ldr r4, [sp, #(104+0)]
  ldr r5, [sp, #(104+4)]
  cmp r4, #0
  beq .Lctr_enc_skip

  cmp r5, #12
  ldm r3, {r7-r10}
  vld1.8 {q0}, [r3] /* load IV */
  rev r7, r7
  rev r8, r8
  rev r9, r9
  rev r10, r10

  aes_preload_keys(r0, r6);

  beq .Lctr_enc_entry_192
  bhi .Lctr_enc_entry_256

#define CTR_ENC(bits, ...) \
  .Lctr_enc_entry_##bits: \
    cmp r4, #4; \
    blo .Lctr_enc_loop_##bits; \
    \
  .Lctr_enc_loop4_##bits: \
    cmp r10, #0xfffffffc; \
    sub r4, r4, #4; \
    blo .Lctr_enc_loop4_##bits##_nocarry; \
    cmp r9, #0xffffffff; \
    bne .Lctr_enc_loop4_##bits##_nocarry; \
    \
    adds r10, #1; \
    vmov q1, q0; \
    blcs .Lctr_overflow_one; \
    rev r11, r10; \
    vmov.32 d1[1], r11; \
    \
    adds r10, #1; \
    vmov q2, q0; \
    blcs .Lctr_overflow_one; \
    rev r11, r10; \
    vmov.32 d1[1], r11; \
    \
    adds r10, #1; \
    vmov q3, q0; \
    blcs .Lctr_overflow_one; \
    rev r11, r10; \
    vmov.32 d1[1], r11; \
    \
    adds r10, #1; \
    vmov q4, q0; \
    blcs .Lctr_overflow_one; \
    rev r11, r10; \
    vmov.32 d1[1], r11; \
    \
    b .Lctr_enc_loop4_##bits##_store_ctr; \
    \
  .Lctr_enc_loop4_##bits##_nocarry: \
    \
    veor q2, q2; \
    vrev64.8 q1, q0; \
    vceq.u32 d5, d5; \
    vadd.u64 q3, q2, q2; \
    vadd.u64 q4, q3, q2; \
    vadd.u64 q0, q3, q3; \
    vsub.u64 q2, q1, q2; \
    vsub.u64 q3, q1, q3; \
    vsub.u64 q4, q1, q4; \
    vsub.u64 q0, q1, q0; \
    vrev64.8 q1, q1; \
    vrev64.8 q2, q2; \
    vrev64.8 q3, q3; \
    vrev64.8 q0, q0; \
    vrev64.8 q4, q4; \
    add r10, #4; \
    \
  .Lctr_enc_loop4_##bits##_store_ctr: \
    \
    vst1.8 {q0}, [r3]; \
    cmp r4, #4; \
    vld1.8 {q0}, [r2]!; /* load ciphertext */ \
    \
    do_aes_4_##bits(e, mc, q1, q2, q3, q4, ##__VA_ARGS__); \
    \
    veor q1, q1, q0; \
    vld1.8 {q0}, [r2]!; /* load ciphertext */ \
    vst1.8 {q1}, [r1]!; /* store plaintext */ \
    vld1.8 {q1}, [r2]!; /* load ciphertext */ \
    veor q2, q2, q0; \
    veor q3, q3, q1; \
    vld1.8 {q0}, [r2]!; /* load ciphertext */ \
    vst1.8 {q2}, [r1]!; /* store plaintext */ \
    veor q4, q4, q0; \
    vld1.8 {q0}, [r3]; /* reload IV */ \
    vst1.8 {q3-q4}, [r1]!; /* store plaintext */ \
    \
    bhs .Lctr_enc_loop4_##bits; \
    cmp r4, #0; \
    beq .Lctr_enc_done; \
    \
  .Lctr_enc_loop_##bits: \
    \
    adds r10, #1; \
    vmov q1, q0; \
    blcs .Lctr_overflow_one; \
    rev r11, r10; \
    subs r4, r4, #1; \
    vld1.8 {q2}, [r2]!; /* load ciphertext */ \
    vmov.32 d1[1], r11; \
    \
    do_aes_one##bits(e, mc, q1, q1, ##__VA_ARGS__); \
    \
    veor q1, q2, q1; \
    vst1.8 {q1}, [r1]!; /* store plaintext */ \
    \
    bne .Lctr_enc_loop_##bits; \
    b .Lctr_enc_done;

  CTR_ENC(128)
  CTR_ENC(192, r0, r6)
  CTR_ENC(256, r0, r6)

#undef CTR_ENC

.Lctr_enc_done:
  vst1.8 {q0}, [r3] /* store IV */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

.Lctr_enc_skip:
  pop {r4-r12,lr}
  vpop {q4-q7}
  bx lr

.Lctr_overflow_one:
  adcs r9, #0
  adcs r8, #0
  adc r7, #0
  rev r11, r9
  rev r12, r8
  vmov.32 d1[0], r11
  rev r11, r7
  vmov.32 d0[1], r12
  vmov.32 d0[0], r11
  bx lr
.size _gcry_aes_ctr_enc_armv8_ce,.-_gcry_aes_ctr_enc_armv8_ce;


/*
 * void _gcry_aes_ocb_enc_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *offset,
 *                                  unsigned char *checksum,
 *                                  void **Ls,
 *                                  size_t nblocks,
 *                                  unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_ocb_enc_armv8_ce
.type  _gcry_aes_ocb_enc_armv8_ce,%function;
_gcry_aes_ocb_enc_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: offset
   *    %st+0: checksum => r4
   *    %st+4: Ls => r5
   *    %st+8: nblocks => r6  (0 < nblocks <= 32)
   *    %st+12: nrounds => r7
   */

  vpush {q4-q7}
  push {r4-r12,lr} /* 4*16 + 4*10 = 104b */
  ldr r7, [sp, #(104+12)]
  ldr r4, [sp, #(104+0)]
  ldr r5, [sp, #(104+4)]
  ldr r6, [sp, #(104+8)]

  cmp r7, #12
  vld1.8 {q0}, [r3] /* load offset */

  aes_preload_keys(r0, r12);

  beq .Locb_enc_entry_192
  bhi .Locb_enc_entry_256

#define OCB_ENC(bits, ...) \
  .Locb_enc_entry_##bits: \
    cmp r6, #4; \
    blo .Locb_enc_loop_##bits; \
    \
  .Locb_enc_loop4_##bits: \
    \
    /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ \
    /* Checksum_i = Checksum_{i-1} xor P_i  */ \
    /* C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i)  */ \
    \
    ldm r5!, {r8, r9, r10, r11}; \
    sub r6, #4; \
    \
    vld1.8 {q9}, [r8];     /* load L_{ntz(i+0)} */ \
    vld1.8 {q1-q2}, [r2]!; /* load P_i+<0-1> */ \
    vld1.8 {q8}, [r4];     /* load Checksum_{i-1} */ \
    veor q0, q0, q9;       /* Offset_i+0 */ \
    vld1.8 {q9}, [r9];     /* load L_{ntz(i+1)} */ \
    veor q8, q8, q1;       /* Checksum_i+0 */ \
    veor q1, q1, q0;       /* P_i+0 xor Offset_i+0 */\
    vld1.8 {q3-q4}, [r2]!; /* load P_i+<2-3> */ \
    vst1.8 {q0}, [r1]!;    /* store Offset_i+0 */\
    veor q0, q0, q9;       /* Offset_i+1 */ \
    vld1.8 {q9}, [r10];    /* load L_{ntz(i+2)} */ \
    veor q8, q8, q2;       /* Checksum_i+1 */ \
    veor q2, q2, q0;       /* P_i+1 xor Offset_i+1 */\
    vst1.8 {q0}, [r1]!;    /* store Offset_i+1 */\
    veor q0, q0, q9;       /* Offset_i+2 */ \
    vld1.8 {q9}, [r11];    /* load L_{ntz(i+3)} */ \
    veor q8, q8, q3;       /* Checksum_i+2 */ \
    veor q3, q3, q0;       /* P_i+2 xor Offset_i+2 */\
    vst1.8 {q0}, [r1]!;    /* store Offset_i+2 */\
    veor q0, q0, q9;       /* Offset_i+3 */ \
    veor q8, q8, q4;       /* Checksum_i+3 */ \
    veor q4, q4, q0;       /* P_i+3 xor Offset_i+3 */\
    vst1.8 {q0}, [r1];     /* store Offset_i+3 */\
    sub r1, #(3*16); \
    vst1.8 {q8}, [r4];     /* store Checksum_i+3 */\
    \
    cmp r6, #4; \
    \
    do_aes_4_##bits(e, mc, q1, q2, q3, q4, ##__VA_ARGS__); \
    \
    mov r8, r1; \
    vld1.8 {q8-q9}, [r1]!; \
    veor q1, q1, q8; \
    veor q2, q2, q9; \
    vld1.8 {q8-q9}, [r1]!; \
    vst1.8 {q1-q2}, [r8]!; \
    veor q3, q3, q8; \
    veor q4, q4, q9; \
    vst1.8 {q3-q4}, [r8]; \
    \
    bhs .Locb_enc_loop4_##bits; \
    cmp r6, #0; \
    beq .Locb_enc_done; \
    \
  .Locb_enc_loop_##bits: \
    \
    /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ \
    /* Checksum_i = Checksum_{i-1} xor P_i  */ \
    /* C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i)  */ \
    \
    ldr r8, [r5], #4; \
    vld1.8 {q1}, [r2]!; /* load plaintext */ \
    vld1.8 {q2}, [r8]; /* load L_{ntz(i)} */ \
    vld1.8 {q3}, [r4]; /* load checksum */ \
    subs r6, #1; \
    veor q0, q0, q2; \
    veor q3, q3, q1; \
    veor q1, q1, q0; \
    vst1.8 {q3}, [r4]; /* store checksum */ \
    \
    do_aes_one##bits(e, mc, q1, q1, ##__VA_ARGS__); \
    \
    veor q1, q1, q0; \
    vst1.8 {q1}, [r1]!; /* store ciphertext */ \
    \
    bne .Locb_enc_loop_##bits; \
    b .Locb_enc_done;

  OCB_ENC(128re, r0, r12)
  OCB_ENC(192, r0, r12)
  OCB_ENC(256, r0, r12)

#undef OCB_ENC

.Locb_enc_done:
  vst1.8 {q0}, [r3] /* store offset */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

  pop {r4-r12,lr}
  vpop {q4-q7}
  bx lr
.size _gcry_aes_ocb_enc_armv8_ce,.-_gcry_aes_ocb_enc_armv8_ce;


/*
 * void _gcry_aes_ocb_dec_armv8_ce (const void *keysched,
 *                                  unsigned char *outbuf,
 *                                  const unsigned char *inbuf,
 *                                  unsigned char *offset,
 *                                  unsigned char *checksum,
 *                                  void **Ls,
 *                                  size_t nblocks,
 *                                  unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_ocb_dec_armv8_ce
.type  _gcry_aes_ocb_dec_armv8_ce,%function;
_gcry_aes_ocb_dec_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: outbuf
   *    r2: inbuf
   *    r3: offset
   *    %st+0: checksum => r4
   *    %st+4: Ls => r5
   *    %st+8: nblocks => r6  (0 < nblocks <= 32)
   *    %st+12: nrounds => r7
   */

  vpush {q4-q7}
  push {r4-r12,lr} /* 4*16 + 4*10 = 104b */
  ldr r7, [sp, #(104+12)]
  ldr r4, [sp, #(104+0)]
  ldr r5, [sp, #(104+4)]
  ldr r6, [sp, #(104+8)]

  cmp r7, #12
  vld1.8 {q0}, [r3] /* load offset */

  aes_preload_keys(r0, r12);

  beq .Locb_dec_entry_192
  bhi .Locb_dec_entry_256

#define OCB_DEC(bits, ...) \
  .Locb_dec_entry_##bits: \
    cmp r6, #4; \
    blo .Locb_dec_loop_##bits; \
    \
  .Locb_dec_loop4_##bits: \
    \
    /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ \
    /* P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i)  */ \
    /* Checksum_i = Checksum_{i-1} xor P_i  */ \
    \
    ldm r5!, {r8, r9, r10, r11}; \
    sub r6, #4; \
    \
    vld1.8 {q9}, [r8];     /* load L_{ntz(i+0)} */ \
    vld1.8 {q1-q2}, [r2]!; /* load P_i+<0-1> */ \
    veor q0, q0, q9;       /* Offset_i+0 */ \
    vld1.8 {q9}, [r9];     /* load L_{ntz(i+1)} */ \
    veor q1, q1, q0;       /* P_i+0 xor Offset_i+0 */\
    vld1.8 {q3-q4}, [r2]!; /* load P_i+<2-3> */ \
    vst1.8 {q0}, [r1]!;    /* store Offset_i+0 */\
    veor q0, q0, q9;       /* Offset_i+1 */ \
    vld1.8 {q9}, [r10];    /* load L_{ntz(i+2)} */ \
    veor q2, q2, q0;       /* P_i+1 xor Offset_i+1 */\
    vst1.8 {q0}, [r1]!;    /* store Offset_i+1 */\
    veor q0, q0, q9;       /* Offset_i+2 */ \
    vld1.8 {q9}, [r11];    /* load L_{ntz(i+3)} */ \
    veor q3, q3, q0;       /* P_i+2 xor Offset_i+2 */\
    vst1.8 {q0}, [r1]!;    /* store Offset_i+2 */\
    veor q0, q0, q9;       /* Offset_i+3 */ \
    veor q4, q4, q0;       /* P_i+3 xor Offset_i+3 */\
    vst1.8 {q0}, [r1];     /* store Offset_i+3 */\
    sub r1, #(3*16); \
    \
    cmp r6, #4; \
    \
    do_aes_4_##bits(d, imc, q1, q2, q3, q4, ##__VA_ARGS__); \
    \
    mov r8, r1; \
    vld1.8 {q8-q9}, [r1]!; \
    veor q1, q1, q8; \
    veor q2, q2, q9; \
    vld1.8 {q8-q9}, [r1]!; \
    vst1.8 {q1-q2}, [r8]!; \
    veor q1, q1, q2; \
    vld1.8 {q2}, [r4];     /* load Checksum_{i-1} */ \
    veor q3, q3, q8; \
    veor q1, q1, q3; \
    veor q4, q4, q9; \
    veor q1, q1, q4; \
    vst1.8 {q3-q4}, [r8]; \
    veor q2, q2, q1; \
    vst1.8 {q2}, [r4];     /* store Checksum_i+3 */ \
    \
    bhs .Locb_dec_loop4_##bits; \
    cmp r6, #0; \
    beq .Locb_dec_done; \
    \
  .Locb_dec_loop_##bits: \
    \
    /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ \
    /* P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i)  */ \
    /* Checksum_i = Checksum_{i-1} xor P_i  */ \
    \
    ldr r8, [r5], #4; \
    vld1.8 {q2}, [r8]; /* load L_{ntz(i)} */ \
    vld1.8 {q1}, [r2]!; /* load ciphertext */ \
    subs r6, #1; \
    veor q0, q0, q2; \
    veor q1, q1, q0; \
    \
    do_aes_one##bits(d, imc, q1, q1, ##__VA_ARGS__) \
    \
    vld1.8 {q2}, [r4]; /* load checksum */ \
    veor q1, q1, q0; \
    vst1.8 {q1}, [r1]!; /* store plaintext */ \
    veor q2, q2, q1; \
    vst1.8 {q2}, [r4]; /* store checksum */ \
    \
    bne .Locb_dec_loop_##bits; \
    b .Locb_dec_done;

  OCB_DEC(128re, r0, r12)
  OCB_DEC(192, r0, r12)
  OCB_DEC(256, r0, r12)

#undef OCB_DEC

.Locb_dec_done:
  vst1.8 {q0}, [r3] /* store offset */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

  pop {r4-r12,lr}
  vpop {q4-q7}
  bx lr
.size _gcry_aes_ocb_dec_armv8_ce,.-_gcry_aes_ocb_dec_armv8_ce;


/*
 * void _gcry_aes_ocb_auth_armv8_ce (const void *keysched,
 *                                   const unsigned char *abuf,
 *                                   unsigned char *offset,
 *                                   unsigned char *checksum,
 *                                   void **Ls,
 *                                   size_t nblocks,
 *                                   unsigned int nrounds);
 */

.align 3
.globl _gcry_aes_ocb_auth_armv8_ce
.type  _gcry_aes_ocb_auth_armv8_ce,%function;
_gcry_aes_ocb_auth_armv8_ce:
  /* input:
   *    r0: keysched
   *    r1: abuf
   *    r2: offset
   *    r3: checksum
   *    %st+0: Ls => r5
   *    %st+4: nblocks => r6  (0 < nblocks <= 32)
   *    %st+8: nrounds => r7
   */

  vpush {q4-q7}
  push {r4-r12,lr} /* 4*16 + 4*10 = 104b */
  ldr r7, [sp, #(104+8)]
  ldr r5, [sp, #(104+0)]
  ldr r6, [sp, #(104+4)]

  cmp r7, #12
  vld1.8 {q0}, [r2] /* load offset */

  aes_preload_keys(r0, r12);

  beq .Locb_auth_entry_192
  bhi .Locb_auth_entry_256

#define OCB_AUTH(bits, ...) \
  .Locb_auth_entry_##bits: \
    cmp r6, #4; \
    blo .Locb_auth_loop_##bits; \
    \
  .Locb_auth_loop4_##bits: \
    \
    /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ \
    /* Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor Offset_i)  */ \
    \
    ldm r5!, {r8, r9, r10, r11}; \
    sub r6, #4; \
    \
    vld1.8 {q9}, [r8];     /* load L_{ntz(i+0)} */ \
    vld1.8 {q1-q2}, [r1]!; /* load A_i+<0-1> */ \
    veor q0, q0, q9;       /* Offset_i+0 */ \
    vld1.8 {q9}, [r9];     /* load L_{ntz(i+1)} */ \
    veor q1, q1, q0;       /* A_i+0 xor Offset_i+0 */\
    vld1.8 {q3-q4}, [r1]!; /* load A_i+<2-3> */ \
    veor q0, q0, q9;       /* Offset_i+1 */ \
    vld1.8 {q9}, [r10];    /* load L_{ntz(i+2)} */ \
    veor q2, q2, q0;       /* A_i+1 xor Offset_i+1 */\
    veor q0, q0, q9;       /* Offset_i+2 */ \
    vld1.8 {q9}, [r11];    /* load L_{ntz(i+3)} */ \
    veor q3, q3, q0;       /* A_i+2 xor Offset_i+2 */\
    veor q0, q0, q9;       /* Offset_i+3 */ \
    veor q4, q4, q0;       /* A_i+3 xor Offset_i+3 */\
    \
    cmp r6, #4; \
    \
    do_aes_4_##bits(e, mc, q1, q2, q3, q4, ##__VA_ARGS__); \
    \
    veor q1, q1, q2; \
    veor q3, q3, q4; \
    vld1.8 {q2}, [r3]; \
    veor q1, q1, q3; \
    veor q2, q2, q1; \
    vst1.8 {q2}, [r3]; \
    \
    bhs .Locb_auth_loop4_##bits; \
    cmp r6, #0; \
    beq .Locb_auth_done; \
    \
  .Locb_auth_loop_##bits: \
    \
    /* Offset_i = Offset_{i-1} xor L_{ntz(i)} */ \
    /* Sum_i = Sum_{i-1} xor ENCIPHER(K, A_i xor Offset_i)  */ \
    \
    ldr r8, [r5], #4; \
    vld1.8 {q2}, [r8]; /* load L_{ntz(i)} */ \
    vld1.8 {q1}, [r1]!; /* load aadtext */ \
    subs r6, #1; \
    veor q0, q0, q2; \
    vld1.8 {q2}, [r3]; /* load checksum */ \
    veor q1, q1, q0; \
    \
    do_aes_one##bits(e, mc, q1, q1, ##__VA_ARGS__) \
    \
    veor q2, q2, q1; \
    vst1.8 {q2}, [r3]; /* store checksum */ \
    \
    bne .Locb_auth_loop_##bits; \
    b .Locb_auth_done;

  OCB_AUTH(128re, r0, r12)
  OCB_AUTH(192, r0, r12)
  OCB_AUTH(256, r0, r12)

#undef OCB_AUTH

.Locb_auth_done:
  vst1.8 {q0}, [r2] /* store offset */

  CLEAR_REG(q0)
  CLEAR_REG(q1)
  CLEAR_REG(q2)
  CLEAR_REG(q3)
  CLEAR_REG(q8)
  CLEAR_REG(q9)
  CLEAR_REG(q10)
  CLEAR_REG(q11)
  CLEAR_REG(q12)
  CLEAR_REG(q13)
  CLEAR_REG(q14)

  pop {r4-r12,lr}
  vpop {q4-q7}
  bx lr
.size _gcry_aes_ocb_auth_armv8_ce,.-_gcry_aes_ocb_auth_armv8_ce;


/*
 * u32 _gcry_aes_sbox4_armv8_ce(u32 in4b);
 */
.align 3
.globl _gcry_aes_sbox4_armv8_ce
.type  _gcry_aes_sbox4_armv8_ce,%function;
_gcry_aes_sbox4_armv8_ce:
  /* See "Gouvêa, C. P. L. & López, J. Implementing GCM on ARMv8. Topics in
   * Cryptology — CT-RSA 2015" for details.
   */
  vmov.i8 q0, #0x52
  vmov.i8 q1, #0
  vmov s0, r0
  aese.8 q0, q1
  veor d0, d1
  vpadd.i32 d0, d0, d1
  vmov r0, s0
  CLEAR_REG(q0)
  bx lr
.size _gcry_aes_sbox4_armv8_ce,.-_gcry_aes_sbox4_armv8_ce;


/*
 * void _gcry_aes_invmixcol_armv8_ce(void *dst, const void *src);
 */
.align 3
.globl _gcry_aes_invmixcol_armv8_ce
.type  _gcry_aes_invmixcol_armv8_ce,%function;
_gcry_aes_invmixcol_armv8_ce:
  vld1.8 {q0}, [r1]
  aesimc.8 q0, q0
  vst1.8 {q0}, [r0]
  CLEAR_REG(q0)
  bx lr
.size _gcry_aes_invmixcol_armv8_ce,.-_gcry_aes_invmixcol_armv8_ce;

#endif
