Implement the AES-GCM mode (HACE10[6:4] = 0b101) used by the AST2700
crypto engine: decode the GCM selection, read the 96-bit IV from the
context buffer, operate on the exact data length (GCM handles a partial
final block itself), and write the 128-bit authentication tag to the tag
buffer (HACE18/HACE8C). The hardware GCM path is only used without
associated data (the driver falls back to software otherwise), so AAD is
not modelled and a non-zero HACE14 is reported as unimplemented.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/misc/aspeed_hace.c | 70 ++++++++++++++++++++++++++++++++++++++-----
1 file changed, 62 insertions(+), 8 deletions(-)
diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index a807cc1422..927759b48f 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -31,6 +31,9 @@
/* HACE0C[27:0] holds the crypto data length */
#define CRYPT_DATA_LEN_MASK 0x0FFFFFFF
#define R_CRYPT_CMD (0x10 / 4)
+/* AES-GCM associated data length (HACE14) and tag write buffer (HACE18) */
+#define R_CRYPT_GCM_ADD_LEN (0x14 / 4)
+#define R_CRYPT_GCM_TAG (0x18 / 4)
/* Crypto engine command register (HACE10) bits */
#define CRYPT_CMD_ENCRYPT BIT(7)
#define CRYPT_CMD_ISR_EN BIT(12)
@@ -43,6 +46,7 @@
#define CRYPT_CMD_ECB (0x0 << 4)
#define CRYPT_CMD_CBC (0x1 << 4)
#define CRYPT_CMD_CTR (0x4 << 4)
+#define CRYPT_CMD_GCM (0x5 << 4)
/* AES key length HACE10[3:2] */
#define CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
#define CRYPT_CMD_AES256 (0x2 << 2)
@@ -58,10 +62,15 @@
#define CRYPT_CTX_KEY_OFFSET 0x10
#define CRYPT_CTX_SIZE 0x30
+/* AES-GCM uses a 96-bit IV and a 128-bit authentication tag */
+#define CRYPT_GCM_IV_LEN 12
+#define CRYPT_GCM_TAG_LEN 16
+
/* AST2700 64-bit DMA high address registers for the crypto command */
#define R_CRYPT_SRC_HI (0x80 / 4)
#define R_CRYPT_DEST_HI (0x84 / 4)
#define R_CRYPT_CONTEXT_HI (0x88 / 4)
+#define R_CRYPT_GCM_TAG_HI (0x8c / 4)
#define R_STATUS (0x1c / 4)
#define HASH_IRQ BIT(9)
@@ -598,6 +607,9 @@ static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg,
case CRYPT_CMD_CTR:
*mode = QCRYPTO_CIPHER_MODE_CTR;
break;
+ case CRYPT_CMD_GCM:
+ *mode = QCRYPTO_CIPHER_MODE_GCM;
+ break;
default:
return false;
}
@@ -691,11 +703,12 @@ static uint64_t crypt_get_addr(AspeedHACEState *s, int reg, int reg_hi)
}
/*
- * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are
- * either single contiguous buffers (direct access mode) or scatter-gather
- * lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the IV/key come from
- * the context buffer (HACE08). For CBC the resulting chaining IV is written
- * back to the context buffer so the driver can continue the chain.
+ * Perform an AES/DES/3DES ECB/CBC/CTR or AES-GCM operation. The source and
+ * destination are either single contiguous buffers (direct access mode) or
+ * scatter-gather lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the
+ * IV/key come from the context buffer (HACE08). For CBC and CTR the resulting
+ * chaining state is written back to the context buffer so the driver can
+ * continue; for GCM the authentication tag is written to the tag buffer.
*/
static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
{
@@ -705,6 +718,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
g_autoptr(QCryptoCipher) cipher = NULL;
g_autofree uint8_t *src_buf = NULL;
g_autofree uint8_t *dst_buf = NULL;
+ uint8_t tag[CRYPT_GCM_TAG_LEN];
uint8_t ctx[CRYPT_CTX_SIZE];
Error *local_err = NULL;
QCryptoCipherMode mode;
@@ -713,10 +727,13 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
uint64_t ctx_addr;
uint64_t src_addr;
uint64_t dst_addr;
+ uint64_t tag_addr;
+ uint32_t aad_len;
size_t iv_offset;
size_t blocklen;
size_t buf_len;
size_t keylen;
+ size_t ivlen;
bool status;
if (len == 0) {
@@ -736,6 +753,20 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
return;
}
+ /* GCM uses a 96-bit IV; the block modes use a full-block IV. */
+ ivlen = (mode == QCRYPTO_CIPHER_MODE_GCM) ? CRYPT_GCM_IV_LEN : blocklen;
+
+ /*
+ * The hardware GCM path is only exercised without associated data (the
+ * driver falls back to software when there is any), so AAD is not modelled.
+ */
+ aad_len = s->regs[R_CRYPT_GCM_ADD_LEN] & CRYPT_DATA_LEN_MASK;
+ if (mode == QCRYPTO_CIPHER_MODE_GCM && aad_len != 0) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: GCM associated data is not implemented\n", __func__);
+ return;
+ }
+
/* Fetch the IV and key from the context buffer in DRAM. */
ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT, R_CRYPT_CONTEXT_HI);
if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED,
@@ -760,7 +791,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
}
if (mode != QCRYPTO_CIPHER_MODE_ECB &&
- qcrypto_cipher_setiv(cipher, ctx + iv_offset, blocklen,
+ qcrypto_cipher_setiv(cipher, ctx + iv_offset, ivlen,
&local_err) < 0) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv failed: %s\n",
__func__, error_get_pretty(local_err));
@@ -771,9 +802,11 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
/*
* Round the working buffers up to a whole block. Block modes are already
* block-aligned; the stream-like CTR mode may leave a partial final block
- * that the engine still processes a full block at a time.
+ * that the engine still processes a full block at a time. GCM handles a
+ * partial final block itself, so it operates on the exact length.
*/
- buf_len = QEMU_ALIGN_UP(len, blocklen);
+ buf_len = (mode == QCRYPTO_CIPHER_MODE_GCM) ?
+ len : QEMU_ALIGN_UP(len, blocklen);
src_buf = g_malloc0(buf_len);
dst_buf = g_malloc0(buf_len);
@@ -857,6 +890,24 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
"%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
__func__, ctx_addr + iv_offset);
}
+ } else if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+ /*
+ * GCM authenticates the message and writes the resulting tag to the
+ * dedicated tag buffer (HACE18/HACE8C).
+ */
+ if (qcrypto_cipher_gettag(cipher, tag, sizeof(tag), &local_err) < 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher gettag failed: "
+ "%s\n", __func__, error_get_pretty(local_err));
+ error_free(local_err);
+ return;
+ }
+ tag_addr = crypt_get_addr(s, R_CRYPT_GCM_TAG, R_CRYPT_GCM_TAG_HI);
+ if (address_space_write(&s->dram_as, tag_addr, MEMTXATTRS_UNSPECIFIED,
+ tag, sizeof(tag))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Failed to write tag, addr=0x%" HWADDR_PRIx "\n",
+ __func__, tag_addr);
+ }
}
}
@@ -987,6 +1038,9 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
case R_CRYPT_CONTEXT_HI:
data &= ahc->key_hi_mask;
break;
+ case R_CRYPT_GCM_TAG_HI:
+ data &= ahc->dest_hi_mask;
+ break;
default:
break;
}
--
2.43.0
> -----Original Message-----
> From: Jamin Lin <jamin_lin@aspeedtech.com>
> Sent: Wednesday, July 15, 2026 11:34 AM
> To: Daniel P. Berrangé <berrange@redhat.com>; Cédric Le Goater
> <clg@kaod.org>; Peter Maydell <peter.maydell@linaro.org>; Steven Lee
> <steven_lee@aspeedtech.com>; Troy Lee <leetroy@gmail.com>; Kane Chen
> <kane_chen@aspeedtech.com>; Andrew Jeffery
> <andrew@codeconstruct.com.au>; Joel Stanley <joel@jms.id.au>; Eric Blake
> <eblake@redhat.com>; Markus Armbruster <armbru@redhat.com>; Fabiano
> Rosas <farosas@suse.de>; Laurent Vivier <lvivier@redhat.com>; Paolo Bonzini
> <pbonzini@redhat.com>; open list:All patches CC here
> <qemu-devel@nongnu.org>; open list:ASPEED BMCs
> <qemu-arm@nongnu.org>
> Cc: Jamin Lin <jamin_lin@aspeedtech.com>; Troy Lee
> <troy_lee@aspeedtech.com>
> Subject: [PATCH v2 14/17] hw/misc/aspeed_hace: Support the AES-GCM mode
> for the crypto command
>
> Implement the AES-GCM mode (HACE10[6:4] = 0b101) used by the AST2700
> crypto engine: decode the GCM selection, read the 96-bit IV from the context
> buffer, operate on the exact data length (GCM handles a partial final block
> itself), and write the 128-bit authentication tag to the tag buffer
> (HACE18/HACE8C). The hardware GCM path is only used without associated
> data (the driver falls back to software otherwise), so AAD is not modelled and
> a non-zero HACE14 is reported as unimplemented.
>
> Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
> ---
> hw/misc/aspeed_hace.c | 70
> ++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 62 insertions(+), 8 deletions(-)
>
> diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index
> a807cc1422..927759b48f 100644
> --- a/hw/misc/aspeed_hace.c
> +++ b/hw/misc/aspeed_hace.c
> @@ -31,6 +31,9 @@
> /* HACE0C[27:0] holds the crypto data length */
> #define CRYPT_DATA_LEN_MASK 0x0FFFFFFF
> #define R_CRYPT_CMD (0x10 / 4)
> +/* AES-GCM associated data length (HACE14) and tag write buffer
> +(HACE18) */ #define R_CRYPT_GCM_ADD_LEN (0x14 / 4)
> +#define R_CRYPT_GCM_TAG (0x18 / 4)
> /* Crypto engine command register (HACE10) bits */
> #define CRYPT_CMD_ENCRYPT BIT(7)
> #define CRYPT_CMD_ISR_EN BIT(12)
> @@ -43,6 +46,7 @@
> #define CRYPT_CMD_ECB (0x0 << 4)
> #define CRYPT_CMD_CBC (0x1 << 4)
> #define CRYPT_CMD_CTR (0x4 << 4)
> +#define CRYPT_CMD_GCM (0x5 << 4)
> /* AES key length HACE10[3:2] */
> #define CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
> #define CRYPT_CMD_AES256 (0x2 << 2)
> @@ -58,10 +62,15 @@
> #define CRYPT_CTX_KEY_OFFSET 0x10
> #define CRYPT_CTX_SIZE 0x30
>
> +/* AES-GCM uses a 96-bit IV and a 128-bit authentication tag */
> +#define CRYPT_GCM_IV_LEN 12
> +#define CRYPT_GCM_TAG_LEN 16
> +
> /* AST2700 64-bit DMA high address registers for the crypto command */
> #define R_CRYPT_SRC_HI (0x80 / 4)
> #define R_CRYPT_DEST_HI (0x84 / 4)
> #define R_CRYPT_CONTEXT_HI (0x88 / 4)
> +#define R_CRYPT_GCM_TAG_HI (0x8c / 4)
>
> #define R_STATUS (0x1c / 4)
> #define HASH_IRQ BIT(9)
> @@ -598,6 +607,9 @@ static bool crypt_decode_cmd(uint32_t cmd,
> QCryptoCipherAlgo *alg,
> case CRYPT_CMD_CTR:
> *mode = QCRYPTO_CIPHER_MODE_CTR;
> break;
> + case CRYPT_CMD_GCM:
> + *mode = QCRYPTO_CIPHER_MODE_GCM;
> + break;
> default:
> return false;
> }
> @@ -691,11 +703,12 @@ static uint64_t crypt_get_addr(AspeedHACEState *s,
> int reg, int reg_hi) }
>
> /*
> - * Perform an AES/DES/3DES ECB/CBC operation. The source and destination
> are
> - * either single contiguous buffers (direct access mode) or scatter-gather
> - * lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the IV/key come
> from
> - * the context buffer (HACE08). For CBC the resulting chaining IV is written
> - * back to the context buffer so the driver can continue the chain.
> + * Perform an AES/DES/3DES ECB/CBC/CTR or AES-GCM operation. The
> source
> + and
> + * destination are either single contiguous buffers (direct access
> + mode) or
> + * scatter-gather lists (HACE10[18]/[19]), addressed by HACE00/HACE04;
> + the
> + * IV/key come from the context buffer (HACE08). For CBC and CTR the
> + resulting
> + * chaining state is written back to the context buffer so the driver
> + can
> + * continue; for GCM the authentication tag is written to the tag buffer.
> */
> static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) { @@
> -705,6 +718,7 @@ static void do_crypt_operation(AspeedHACEState *s,
> uint32_t cmd)
> g_autoptr(QCryptoCipher) cipher = NULL;
> g_autofree uint8_t *src_buf = NULL;
> g_autofree uint8_t *dst_buf = NULL;
> + uint8_t tag[CRYPT_GCM_TAG_LEN];
> uint8_t ctx[CRYPT_CTX_SIZE];
> Error *local_err = NULL;
> QCryptoCipherMode mode;
> @@ -713,10 +727,13 @@ static void do_crypt_operation(AspeedHACEState *s,
> uint32_t cmd)
> uint64_t ctx_addr;
> uint64_t src_addr;
> uint64_t dst_addr;
> + uint64_t tag_addr;
> + uint32_t aad_len;
> size_t iv_offset;
> size_t blocklen;
> size_t buf_len;
> size_t keylen;
> + size_t ivlen;
> bool status;
>
> if (len == 0) {
> @@ -736,6 +753,20 @@ static void do_crypt_operation(AspeedHACEState *s,
> uint32_t cmd)
> return;
> }
>
> + /* GCM uses a 96-bit IV; the block modes use a full-block IV. */
> + ivlen = (mode == QCRYPTO_CIPHER_MODE_GCM) ?
> CRYPT_GCM_IV_LEN :
> + blocklen;
> +
> + /*
> + * The hardware GCM path is only exercised without associated data
> (the
> + * driver falls back to software when there is any), so AAD is not
> modelled.
> + */
> + aad_len = s->regs[R_CRYPT_GCM_ADD_LEN] &
> CRYPT_DATA_LEN_MASK;
> + if (mode == QCRYPTO_CIPHER_MODE_GCM && aad_len != 0) {
> + qemu_log_mask(LOG_UNIMP,
> + "%s: GCM associated data is not implemented\n",
> __func__);
> + return;
> + }
> +
> /* Fetch the IV and key from the context buffer in DRAM. */
> ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT,
> R_CRYPT_CONTEXT_HI);
> if (address_space_read(&s->dram_as, ctx_addr,
> MEMTXATTRS_UNSPECIFIED, @@ -760,7 +791,7 @@ static void
> do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
> }
>
> if (mode != QCRYPTO_CIPHER_MODE_ECB &&
> - qcrypto_cipher_setiv(cipher, ctx + iv_offset, blocklen,
> + qcrypto_cipher_setiv(cipher, ctx + iv_offset, ivlen,
> &local_err) < 0) {
> qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv
> failed: %s\n",
> __func__, error_get_pretty(local_err)); @@
> -771,9 +802,11 @@ static void do_crypt_operation(AspeedHACEState *s,
> uint32_t cmd)
> /*
> * Round the working buffers up to a whole block. Block modes are
> already
> * block-aligned; the stream-like CTR mode may leave a partial final
> block
> - * that the engine still processes a full block at a time.
> + * that the engine still processes a full block at a time. GCM handles a
> + * partial final block itself, so it operates on the exact length.
> */
> - buf_len = QEMU_ALIGN_UP(len, blocklen);
> + buf_len = (mode == QCRYPTO_CIPHER_MODE_GCM) ?
> + len : QEMU_ALIGN_UP(len, blocklen);
> src_buf = g_malloc0(buf_len);
> dst_buf = g_malloc0(buf_len);
>
> @@ -857,6 +890,24 @@ static void do_crypt_operation(AspeedHACEState *s,
> uint32_t cmd)
> "%s: Failed to write IV, addr=0x%"
> HWADDR_PRIx "\n",
> __func__, ctx_addr + iv_offset);
> }
> + } else if (mode == QCRYPTO_CIPHER_MODE_GCM) {
> + /*
> + * GCM authenticates the message and writes the resulting tag to
> the
> + * dedicated tag buffer (HACE18/HACE8C).
> + */
> + if (qcrypto_cipher_gettag(cipher, tag, sizeof(tag), &local_err) < 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher
> gettag failed: "
> + "%s\n", __func__,
> error_get_pretty(local_err));
> + error_free(local_err);
> + return;
> + }
> + tag_addr = crypt_get_addr(s, R_CRYPT_GCM_TAG,
> R_CRYPT_GCM_TAG_HI);
> + if (address_space_write(&s->dram_as, tag_addr,
> MEMTXATTRS_UNSPECIFIED,
> + tag, sizeof(tag))) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: Failed to write tag, addr=0x%"
> HWADDR_PRIx "\n",
> + __func__, tag_addr);
> + }
> }
> }
>
> @@ -987,6 +1038,9 @@ static void aspeed_hace_write(void *opaque, hwaddr
> addr, uint64_t data,
> case R_CRYPT_CONTEXT_HI:
> data &= ahc->key_hi_mask;
> break;
> + case R_CRYPT_GCM_TAG_HI:
> + data &= ahc->dest_hi_mask;
> + break;
> default:
> break;
> }
> --
> 2.43.0
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
© 2016 - 2026 Red Hat, Inc.