This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new 72928d5dec6 arch/arm64: AES using the Armv8 Cryptography Extension.
72928d5dec6 is described below
commit 72928d5dec6849deb7581c05d523a0cffe1fa0f9
Author: Royyan Zahir <[email protected]>
AuthorDate: Sat Sep 19 15:16:06 2026 +0400
arch/arm64: AES using the Armv8 Cryptography Extension.
NuttX emits no AES instruction on any arm64 core. There is no runtime
feature dispatch in arch/arm64, so every AES goes through crypto/rijndael.c
or crypto/aes.c, and the table-driven one indexes memory with key-dependent
values, so its timing follows the cache.
Provide aes_cypher() for ECB, CBC and CTR built on AESE, AESD and the
MixColumns pair, and register it with /dev/crypto as a hardware driver
alongside the existing stm32h7, sam34 and esp32 modules.
ID_AA64ISAR0_EL1.AES is read on every call, which returns -ENOTSUP rather
than trapping on a core without the extension.
Verified against the NIST SP 800-38A appendix F vectors for ECB-128,
ECB-256, CBC-128, CBC-192 and CTR-128, encrypt and decrypt, in place and
out of place.
Signed-off-by: Royyan Zahir <[email protected]>
---
arch/arm64/Kconfig | 13 ++
arch/arm64/src/common/CMakeLists.txt | 8 +
arch/arm64/src/common/Make.defs | 10 +
arch/arm64/src/common/arm64_aes.c | 387 +++++++++++++++++++++++++++++++++++
arch/arm64/src/common/arm64_crypto.c | 138 +++++++++++++
5 files changed, 556 insertions(+)
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 9de3bb3f4c6..fc416bde70e 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -229,6 +229,19 @@ config ARCH_AS_HAS_ARMV8_5
menu "ARMv8.5 architectural features"
depends on ARCH_AS_HAS_ARMV8_5
+config ARM64_CRYPTO_AES
+ bool "AES using the Armv8 Cryptography Extension"
+ select CRYPTO
+ select CRYPTO_AES
+ ---help---
+ Provide aes_cypher() with AESE, AESD and the MixColumns pair.
The
+ table-driven cipher indexes memory with key-dependent values, so
+ its timing follows the cache; these instructions do not.
+
+ The core must implement the extension. ID_AA64ISAR0_EL1 is read
on
+ every call, which returns -ENOTSUP rather than trapping if it
does
+ not.
+
config ARM64_MTE
bool "Memory Tagging Extension support"
select ARCH_HAVE_MEMTAG
diff --git a/arch/arm64/src/common/CMakeLists.txt
b/arch/arm64/src/common/CMakeLists.txt
index 670c9f37a0a..bd53b5aba86 100644
--- a/arch/arm64/src/common/CMakeLists.txt
+++ b/arch/arm64/src/common/CMakeLists.txt
@@ -81,6 +81,14 @@ if(CONFIG_ARCH_HAVE_MMU)
endif()
endif()
+if(CONFIG_ARM64_CRYPTO_AES)
+ list(APPEND SRCS arm64_aes.c)
+endif()
+
+if(CONFIG_ARM64_CRYPTO_AES AND CONFIG_CRYPTO_CRYPTODEV_HARDWARE)
+ list(APPEND SRCS arm64_crypto.c)
+endif()
+
if(CONFIG_ARM64_MTE)
list(APPEND SRCS arm64_mte.c)
endif()
diff --git a/arch/arm64/src/common/Make.defs b/arch/arm64/src/common/Make.defs
index 90d2f89e36e..4bef36a3b1c 100644
--- a/arch/arm64/src/common/Make.defs
+++ b/arch/arm64/src/common/Make.defs
@@ -88,6 +88,16 @@ CMN_CSRCS += arm64_physpgaddr.c
endif
endif
+ifeq ($(CONFIG_ARM64_CRYPTO_AES),y)
+CMN_CSRCS += arm64_aes.c
+endif
+
+ifeq ($(CONFIG_CRYPTO_CRYPTODEV_HARDWARE),y)
+ifeq ($(CONFIG_ARM64_CRYPTO_AES),y)
+CMN_CSRCS += arm64_crypto.c
+endif
+endif
+
ifeq ($(CONFIG_ARM64_MTE),y)
CMN_CSRCS += arm64_mte.c
endif
diff --git a/arch/arm64/src/common/arm64_aes.c
b/arch/arm64/src/common/arm64_aes.c
new file mode 100644
index 00000000000..7d44397b1e1
--- /dev/null
+++ b/arch/arm64/src/common/arm64_aes.c
@@ -0,0 +1,387 @@
+/****************************************************************************
+ * arch/arm64/src/common/arm64_aes.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <nuttx/crypto/crypto.h>
+
+#include "arm64_internal.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* ID_AA64ISAR0_EL1.AES, bits [7:4]. Non-zero means AESE/AESD and the
+ * MixColumns pair are implemented.
+ */
+
+#define ID_AA64ISAR0_AES_SHIFT 4
+#define ID_AA64ISAR0_AES_MASK 0xful
+
+#define AES_BLOCK_SIZE 16
+#define AES_MAX_ROUNDS 14
+#define AES_MAX_WORDS (4 * (AES_MAX_ROUNDS + 1))
+
+/* The counter occupies the last four bytes of the block, as in
+ * crypto/xform.c, so both agree on what a CTR stream looks like.
+ */
+
+#define AES_CTR_OFFSET 12
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct arm64_aes_s
+{
+ uint32_t ek[AES_MAX_WORDS];
+ uint32_t dk[AES_MAX_WORDS];
+ unsigned rounds;
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static bool arm64_aes_present(void)
+{
+ uint64_t isar0;
+
+ __asm__ volatile ("mrs %0, id_aa64isar0_el1" : "=r" (isar0));
+
+ return ((isar0 >> ID_AA64ISAR0_AES_SHIFT) & ID_AA64ISAR0_AES_MASK) != 0;
+}
+
+/* Only SubWord is accelerated; the rest of the schedule is ordinary
+ * arithmetic, so it stays in C.
+ */
+
+static uint32_t arm64_aes_subword(uint32_t in)
+{
+ uint8_t block[AES_BLOCK_SIZE];
+ uint32_t out;
+
+ /* A zero round key leaves AESE doing SubBytes and ShiftRows only, and
+ * ShiftRows cannot disturb a state whose four columns are identical.
+ */
+
+ memcpy(block + 0, &in, 4);
+ memcpy(block + 4, &in, 4);
+ memcpy(block + 8, &in, 4);
+ memcpy(block + 12, &in, 4);
+
+ __asm__ volatile (
+ "ld1 {v0.16b}, [%0] \n"
+ "movi v1.16b, #0 \n"
+ "aese v0.16b, v1.16b \n"
+ "st1 {v0.16b}, [%0] \n"
+ :: "r" (block) : "v0", "v1", "memory");
+
+ memcpy(&out, block, sizeof(out));
+ return out;
+}
+
+static void arm64_aes_encrypt(FAR const struct arm64_aes_s *ctx,
+ FAR const uint8_t *in, FAR uint8_t *out)
+{
+ FAR const uint8_t *rk = (FAR const uint8_t *)ctx->ek;
+ unsigned n = ctx->rounds - 1;
+
+ __asm__ volatile (
+ "ld1 {v0.16b}, [%[in]] \n"
+ "1: \n"
+ "ld1 {v1.16b}, [%[rk]], #16 \n"
+ "aese v0.16b, v1.16b \n"
+ "aesmc v0.16b, v0.16b \n"
+ "subs %w[n], %w[n], #1 \n"
+ "b.ne 1b \n"
+ "ld1 {v1.16b}, [%[rk]], #16 \n"
+ "aese v0.16b, v1.16b \n"
+ "ld1 {v1.16b}, [%[rk]] \n"
+ "eor v0.16b, v0.16b, v1.16b \n"
+ "st1 {v0.16b}, [%[out]] \n"
+ : [rk] "+r" (rk), [n] "+r" (n)
+ : [in] "r" (in), [out] "r" (out)
+ : "v0", "v1", "cc", "memory");
+}
+
+static void arm64_aes_decrypt(FAR const struct arm64_aes_s *ctx,
+ FAR const uint8_t *in, FAR uint8_t *out)
+{
+ FAR const uint8_t *rk = (FAR const uint8_t *)&ctx->dk[4 * ctx->rounds];
+ unsigned n = ctx->rounds - 1;
+
+ __asm__ volatile (
+ "ld1 {v0.16b}, [%[in]] \n"
+ "1: \n"
+ "ld1 {v1.16b}, [%[rk]] \n"
+ "sub %[rk], %[rk], #16 \n"
+ "aesd v0.16b, v1.16b \n"
+ "aesimc v0.16b, v0.16b \n"
+ "subs %w[n], %w[n], #1 \n"
+ "b.ne 1b \n"
+ "ld1 {v1.16b}, [%[rk]] \n"
+ "sub %[rk], %[rk], #16 \n"
+ "aesd v0.16b, v1.16b \n"
+ "ld1 {v1.16b}, [%[rk]] \n"
+ "eor v0.16b, v0.16b, v1.16b \n"
+ "st1 {v0.16b}, [%[out]] \n"
+ : [rk] "+r" (rk), [n] "+r" (n)
+ : [in] "r" (in), [out] "r" (out)
+ : "v0", "v1", "cc", "memory");
+}
+
+static int arm64_aes_setkey(FAR struct arm64_aes_s *ctx,
+ FAR const uint8_t *key, size_t len,
+ bool decrypt)
+{
+ static const uint8_t rcon[] =
+ {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
+ };
+
+ unsigned nk;
+ unsigned words;
+ unsigned i;
+
+ switch (len)
+ {
+ case 16:
+ ctx->rounds = 10;
+ break;
+
+ case 24:
+ ctx->rounds = 12;
+ break;
+
+ case 32:
+ ctx->rounds = 14;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ nk = (unsigned)len / 4;
+ words = 4 * (ctx->rounds + 1);
+
+ memcpy(ctx->ek, key, len);
+
+ for (i = nk; i < words; i++)
+ {
+ uint32_t t = ctx->ek[i - 1];
+
+ if (i % nk == 0)
+ {
+ t = (t >> 8) | (t << 24);
+ t = arm64_aes_subword(t) ^ (uint32_t)rcon[i / nk - 1];
+ }
+ else if (nk > 6 && i % nk == 4)
+ {
+ t = arm64_aes_subword(t);
+ }
+
+ ctx->ek[i] = ctx->ek[i - nk] ^ t;
+ }
+
+ if (!decrypt)
+ {
+ return OK;
+ }
+
+ /* The equivalent inverse cipher wants InvMixColumns applied to every round
+ * key except the first and the last, so decryption is the same shape as
+ * encryption rather than a separate schedule.
+ */
+
+ memcpy(ctx->dk, ctx->ek, (size_t)words * 4);
+
+ for (i = 1; i < ctx->rounds; i++)
+ {
+ FAR uint8_t *rk = (FAR uint8_t *)&ctx->dk[4 * i];
+
+ __asm__ volatile (
+ "ld1 {v0.16b}, [%0] \n"
+ "aesimc v0.16b, v0.16b \n"
+ "st1 {v0.16b}, [%0] \n"
+ :: "r" (rk) : "v0", "memory");
+ }
+
+ return OK;
+}
+
+static void arm64_aes_xor_block(FAR uint8_t *dst, FAR const uint8_t *src)
+{
+ int i;
+
+ for (i = 0; i < AES_BLOCK_SIZE; i++)
+ {
+ dst[i] ^= src[i];
+ }
+}
+
+static void arm64_aes_ctr_inc(FAR uint8_t *block)
+{
+ int i;
+
+ for (i = AES_BLOCK_SIZE - 1; i >= AES_CTR_OFFSET; i--)
+ {
+ if (++block[i] != 0)
+ {
+ break;
+ }
+ }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: aes_cypher
+ *
+ * Description:
+ * Encrypt or decrypt a whole number of AES blocks in ECB, CBC or CTR mode
+ * using the Armv8 Cryptography Extension.
+ *
+ * Returned Value:
+ * OK, -ENOTSUP on a core without the extension, or -EINVAL for a bad mode,
+ * key length or size.
+ *
+ ****************************************************************************/
+
+int aes_cypher(FAR void *out, FAR const void *in, size_t size,
+ FAR const void *iv, FAR const void *key, size_t keysize,
+ int mode, int encrypt)
+{
+ struct arm64_aes_s ctx;
+ uint8_t chain[AES_BLOCK_SIZE];
+ FAR const uint8_t *src = in;
+ FAR uint8_t *dst = out;
+ int ret;
+
+ if (!arm64_aes_present())
+ {
+ return -ENOTSUP;
+ }
+
+ if (size % AES_BLOCK_SIZE != 0)
+ {
+ return -EINVAL;
+ }
+
+ switch (mode & AES_MODE_MASK)
+ {
+ case AES_MODE_ECB:
+ case AES_MODE_CBC:
+ case AES_MODE_CTR:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /* CTR runs the cipher forwards in both directions, so it never needs the
+ * inverse schedule.
+ */
+
+ ret = arm64_aes_setkey(&ctx, key, keysize,
+ !encrypt && (mode & AES_MODE_MASK) != AES_MODE_CTR);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ if (iv != NULL)
+ {
+ memcpy(chain, iv, AES_BLOCK_SIZE);
+ }
+ else
+ {
+ memset(chain, 0, AES_BLOCK_SIZE);
+ }
+
+ while (size != 0)
+ {
+ switch (mode & AES_MODE_MASK)
+ {
+ case AES_MODE_ECB:
+ if (encrypt)
+ {
+ arm64_aes_encrypt(&ctx, src, dst);
+ }
+ else
+ {
+ arm64_aes_decrypt(&ctx, src, dst);
+ }
+ break;
+
+ case AES_MODE_CBC:
+ if (encrypt)
+ {
+ memcpy(dst, src, AES_BLOCK_SIZE);
+ arm64_aes_xor_block(dst, chain);
+ arm64_aes_encrypt(&ctx, dst, dst);
+ memcpy(chain, dst, AES_BLOCK_SIZE);
+ }
+ else
+ {
+ uint8_t prev[AES_BLOCK_SIZE];
+
+ memcpy(prev, src, AES_BLOCK_SIZE);
+ arm64_aes_decrypt(&ctx, src, dst);
+ arm64_aes_xor_block(dst, chain);
+ memcpy(chain, prev, AES_BLOCK_SIZE);
+ }
+ break;
+
+ case AES_MODE_CTR:
+ {
+ uint8_t stream[AES_BLOCK_SIZE];
+
+ arm64_aes_encrypt(&ctx, chain, stream);
+ memcpy(dst, src, AES_BLOCK_SIZE);
+ arm64_aes_xor_block(dst, stream);
+ arm64_aes_ctr_inc(chain);
+ explicit_bzero(stream, sizeof(stream));
+ }
+ break;
+ }
+
+ src += AES_BLOCK_SIZE;
+ dst += AES_BLOCK_SIZE;
+ size -= AES_BLOCK_SIZE;
+ }
+
+ explicit_bzero(&ctx, sizeof(ctx));
+ explicit_bzero(chain, sizeof(chain));
+ return OK;
+}
diff --git a/arch/arm64/src/common/arm64_crypto.c
b/arch/arm64/src/common/arm64_crypto.c
new file mode 100644
index 00000000000..760bb003845
--- /dev/null
+++ b/arch/arm64/src/common/arm64_crypto.c
@@ -0,0 +1,138 @@
+/****************************************************************************
+ * arch/arm64/src/common/arm64_crypto.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <crypto/cryptodev.h>
+#include <crypto/xform.h>
+#include <nuttx/crypto/crypto.h>
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int arm64_crypto_newsession(FAR uint32_t *sid,
+ FAR struct cryptoini *cri)
+{
+ if (sid == NULL || cri == NULL)
+ {
+ return -EINVAL;
+ }
+
+ switch (cri->cri_alg)
+ {
+ case CRYPTO_AES_CBC:
+ break;
+
+ case CRYPTO_AES_CTR:
+
+ /* cri_klen counts the trailing nonce, which is not key material. */
+
+ if ((cri->cri_klen / 8 - AESCTR_NONCESIZE) > 32)
+ {
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return OK;
+}
+
+static int arm64_crypto_freesession(uint64_t tid)
+{
+ return OK;
+}
+
+static int arm64_crypto_process(FAR struct cryptop *crp)
+{
+ FAR struct cryptodesc *crd;
+ uint8_t iv[AESCTR_BLOCKSIZE];
+
+ for (crd = crp->crp_desc; crd != NULL; crd = crd->crd_next)
+ {
+ switch (crd->crd_alg)
+ {
+ case CRYPTO_AES_CBC:
+ return aes_cypher(crp->crp_dst, crp->crp_buf, crd->crd_len,
+ crd->crd_iv, crd->crd_key, crd->crd_klen / 8,
+ AES_MODE_CBC,
+ crd->crd_flags & CRD_F_ENCRYPT);
+
+ case CRYPTO_AES_CTR:
+ memcpy(iv, crd->crd_key + crd->crd_klen / 8 - AESCTR_NONCESIZE,
+ AESCTR_NONCESIZE);
+ memcpy(iv + AESCTR_NONCESIZE, crd->crd_iv, AESCTR_IVSIZE);
+ memset(iv + AESCTR_NONCESIZE + AESCTR_IVSIZE, 0,
+ AESCTR_BLOCKSIZE - AESCTR_NONCESIZE - AESCTR_IVSIZE);
+ return aes_cypher(crp->crp_dst, crp->crp_buf, crd->crd_len,
+ iv, crd->crd_key,
+ crd->crd_klen / 8 - AESCTR_NONCESIZE,
+ AES_MODE_CTR,
+ crd->crd_flags & CRD_F_ENCRYPT);
+
+ default:
+ return -EINVAL;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: hwcr_init
+ *
+ * Description:
+ * Register the Armv8 AES instructions with /dev/crypto.
+ *
+ ****************************************************************************/
+
+void hwcr_init(void)
+{
+ int algs[CRYPTO_ALGORITHM_MAX + 1];
+ int hwcr_id;
+
+ hwcr_id = crypto_get_driverid(0);
+ DEBUGASSERT(hwcr_id >= 0);
+
+ memset(algs, 0, sizeof(algs));
+ algs[CRYPTO_AES_CBC] = CRYPTO_ALG_FLAG_SUPPORTED;
+ algs[CRYPTO_AES_CTR] = CRYPTO_ALG_FLAG_SUPPORTED;
+
+ crypto_register(hwcr_id, algs, arm64_crypto_newsession,
+ arm64_crypto_freesession, arm64_crypto_process);
+}