Commit 7a1a61b7ac for qemu.org
commit 7a1a61b7ace9a4f7dad5cc3d4d69459029360961
Author: Jamin Lin <jamin_lin@aspeedtech.com>
Date: Tue Aug 11 06:01:35 2026 +0000
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>
Reviewed-by: Kane Chen <kane_chen@aspeedtech.com>
Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-15-jamin_lin@aspeedtech.com
Signed-off-by: Cédric Le Goater <clg@redhat.com>
diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index 0a6e2fa829..23dacc9be8 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)
@@ -42,6 +45,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)
@@ -57,10 +61,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)
@@ -596,6 +605,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;
}
@@ -689,11 +701,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)
{
@@ -703,6 +716,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;
@@ -711,10 +725,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) {
@@ -734,6 +751,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];
+ 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,
@@ -758,7 +789,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));
@@ -769,9 +800,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);
@@ -855,6 +888,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);
+ }
}
}
@@ -899,9 +950,11 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
case R_CRYPT_SRC:
case R_CRYPT_DEST:
case R_CRYPT_CONTEXT:
+ case R_CRYPT_GCM_TAG:
data &= ahc->src_mask;
break;
case R_CRYPT_DATA_LEN:
+ case R_CRYPT_GCM_ADD_LEN:
data &= CRYPT_DATA_LEN_MASK;
break;
case R_HASH_SRC:
@@ -980,6 +1033,7 @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
data &= ahc->src_hi_mask;
break;
case R_CRYPT_DEST_HI:
+ case R_CRYPT_GCM_TAG_HI:
data &= ahc->dest_hi_mask;
break;
case R_CRYPT_CONTEXT_HI: