> -----Original Message-----
> From: Jamin Lin <[email protected]>
> Sent: Wednesday, July 15, 2026 11:33 AM
> To: Daniel P. Berrangé <[email protected]>; Cédric Le Goater
> <[email protected]>; Peter Maydell <[email protected]>; Steven Lee
> <[email protected]>; Troy Lee <[email protected]>; Kane Chen
> <[email protected]>; Andrew Jeffery
> <[email protected]>; Joel Stanley <[email protected]>; Eric Blake
> <[email protected]>; Markus Armbruster <[email protected]>; Fabiano
> Rosas <[email protected]>; Laurent Vivier <[email protected]>; Paolo Bonzini
> <[email protected]>; open list:All patches CC here
> <[email protected]>; open list:ASPEED BMCs
> <[email protected]>
> Cc: Jamin Lin <[email protected]>; Troy Lee
> <[email protected]>
> Subject: [PATCH v2 01/17] hw/misc/aspeed_hace: Support the crypto
> command in direct access mode
>
> The crypt command register was previously stubbed out. Implement it for the
> direct access mode, where HACE00/HACE04 point directly at contiguous source
> and destination buffers. AES-128/192/256, DES and 3DES are supported in ECB
> and CBC modes via the qcrypto cipher API; the IV and key are read from the
> context buffer (HACE08) and, for CBC, the resulting chaining IV is written
> back
> to the context.
>
> The completion interrupt is now raised for every HACE variant as the hardware
> does, which fixes the crypt command hang on the AST2500, AST2600 and
> AST1030. The AST2700 crypto engine still needs 64-bit DMA and AES-GCM,
> which are added later, so it keeps its temporary interrupt-only workaround
> until then.
>
> For debugging, the context, source and destination buffers are dumped through
> the existing aspeed_hace_hexdump trace event (disabled by default). CTR
> mode, scatter-gather mode and AES-GCM are added separately.
>
> Signed-off-by: Jamin Lin <[email protected]>
> ---
> hw/misc/aspeed_hace.c | 283
> +++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 269 insertions(+), 14 deletions(-)
>
> diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c index
> c61efe50c4..de4399f322 100644
> --- a/hw/misc/aspeed_hace.c
> +++ b/hw/misc/aspeed_hace.c
> @@ -18,11 +18,44 @@
> #include "qapi/error.h"
> #include "migration/vmstate.h"
> #include "crypto/hash.h"
> +#include "crypto/cipher.h"
> #include "hw/core/qdev-properties.h"
> #include "hw/core/irq.h"
> #include "trace.h"
>
> -#define R_CRYPT_CMD (0x10 / 4)
> +/* Crypto engine registers */
> +#define R_CRYPT_SRC (0x00 / 4)
> +#define R_CRYPT_DEST (0x04 / 4)
> +#define R_CRYPT_CONTEXT (0x08 / 4)
> +#define R_CRYPT_DATA_LEN (0x0c / 4)
> +/* HACE0C[27:0] holds the crypto data length */
> +#define CRYPT_DATA_LEN_MASK 0x0FFFFFFF
> +#define R_CRYPT_CMD (0x10 / 4)
> +/* Crypto engine command register (HACE10) bits */
> +#define CRYPT_CMD_ENCRYPT BIT(7)
> +#define CRYPT_CMD_ISR_EN BIT(12)
Regarding CRYPT_CMD_ISR_EN, the current code already has similar definitions,
such as CRYPT_IRQ and CRYPT_IRQ_EN. Would it make sense to migrate these
definitions together for consistency?
> +#define CRYPT_CMD_DES_SELECT BIT(16)
> +#define CRYPT_CMD_TRIPLE_DES BIT(17)
> +#define CRYPT_CMD_SRC_SG_CTRL BIT(18)
> +#define CRYPT_CMD_DST_SG_CTRL BIT(19)
CRYPT_CMD_DST_SG_CTRL is currently defined but not used anywhere. Could we
defer adding this definition until it is actually needed?
> +/* Operation mode HACE10[6:4] */
> +#define CRYPT_CMD_OP_MODE_MASK (0x7 << 4)
> +#define CRYPT_CMD_ECB (0x0 << 4)
> +#define CRYPT_CMD_CBC (0x1 << 4)
> +/* AES key length HACE10[3:2] */
> +#define CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
> +#define CRYPT_CMD_AES256 (0x2 << 2)
> +#define CRYPT_CMD_AES192 (0x1 << 2)
> +#define CRYPT_CMD_AES128 (0x0 << 2)
> +
> +/*
> + * Crypto context buffer layout (HACE08). The IV is at the start of the
> +buffer
> + * (DES places its 8 byte IV at offset 8) and the cipher key at offset 0x10.
> + */
> +#define CRYPT_CTX_IV_OFFSET 0x00
> +#define CRYPT_CTX_DES_IV_OFFSET 0x08
> +#define CRYPT_CTX_KEY_OFFSET 0x10
> +#define CRYPT_CTX_SIZE 0x30
>
> #define R_STATUS (0x1c / 4)
> #define HASH_IRQ BIT(9)
> @@ -501,6 +534,216 @@ static void do_hash_operation(AspeedHACEState *s,
> int algo, bool sg_mode,
> }
> }
>
> +static bool crypt_aes_alg(uint32_t cmd, QCryptoCipherAlgo *alg, size_t
> +*keylen) {
> + switch (cmd & CRYPT_CMD_AES_KEY_LEN_MASK) {
> + case CRYPT_CMD_AES128:
> + *alg = QCRYPTO_CIPHER_ALGO_AES_128;
> + *keylen = 16;
> + break;
> + case CRYPT_CMD_AES192:
> + *alg = QCRYPTO_CIPHER_ALGO_AES_192;
> + *keylen = 24;
> + break;
> + case CRYPT_CMD_AES256:
> + *alg = QCRYPTO_CIPHER_ALGO_AES_256;
> + *keylen = 32;
> + break;
> + default:
> + return false;
> + }
> +
> + return true;
> +}
> +
> +/*
> + * Decode the crypto command register into a libqcrypto algorithm/mode
> +pair
> + * and the block/IV geometry. Returns false for unsupported selections.
> + */
> +static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg,
> + QCryptoCipherMode *mode, size_t
> *keylen,
> + size_t *blocklen, size_t *iv_offset) {
> + if (cmd & CRYPT_CMD_DES_SELECT) {
> + *blocklen = 8;
> + *iv_offset = CRYPT_CTX_DES_IV_OFFSET;
> + if (cmd & CRYPT_CMD_TRIPLE_DES) {
> + *alg = QCRYPTO_CIPHER_ALGO_3DES;
> + *keylen = 24;
> + } else {
> + *alg = QCRYPTO_CIPHER_ALGO_DES;
> + *keylen = 8;
> + }
> + } else {
> + *blocklen = 16;
> + *iv_offset = CRYPT_CTX_IV_OFFSET;
> + if (!crypt_aes_alg(cmd, alg, keylen)) {
> + return false;
> + }
> + }
> +
> + switch (cmd & CRYPT_CMD_OP_MODE_MASK) {
> + case CRYPT_CMD_ECB:
> + *mode = QCRYPTO_CIPHER_MODE_ECB;
> + break;
> + case CRYPT_CMD_CBC:
> + *mode = QCRYPTO_CIPHER_MODE_CBC;
> + break;
> + default:
> + return false;
> + }
> +
> + return true;
> +}
> +
> +/*
> + * Direct access mode: the source/destination register (HACE00/HACE04)
> +points
> + * at a single contiguous buffer in DRAM. Copy @len bytes between it
> +and the
> + * bounce buffer @buf; when @to_dram is true @buf is written out,
> +otherwise it
> + * is read in. Returns true on success.
> + */
> +static bool crypt_prepare_direct(AspeedHACEState *s, uint64_t addr,
> + uint8_t *buf, uint32_t len, bool
> +to_dram) {
> + return !address_space_rw(&s->dram_as, addr,
> MEMTXATTRS_UNSPECIFIED,
> + buf, len, to_dram); }
> +
> +/*
> + * Perform an AES/DES/3DES ECB/CBC operation in direct access mode: the
> +source
> + * and destination are single contiguous buffers (HACE00/HACE04) and
> +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.
> + */
> +static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd) {
> + uint32_t len = s->regs[R_CRYPT_DATA_LEN];
> + bool encrypt = cmd & CRYPT_CMD_ENCRYPT;
> + g_autoptr(QCryptoCipher) cipher = NULL;
> + g_autofree uint8_t *src_buf = NULL;
> + g_autofree uint8_t *dst_buf = NULL;
> + uint8_t ctx[CRYPT_CTX_SIZE];
> + Error *local_err = NULL;
> + QCryptoCipherMode mode;
> + QCryptoCipherAlgo alg;
> + const uint8_t *next_iv;
> + uint64_t ctx_addr;
> + uint64_t src_addr;
> + uint64_t dst_addr;
> + size_t iv_offset;
> + size_t blocklen;
> + size_t keylen;
> +
> + if (len == 0) {
> + return;
> + }
> +
> + if (!crypt_decode_cmd(cmd, &alg, &mode, &keylen, &blocklen,
> &iv_offset)) {
> + qemu_log_mask(LOG_UNIMP,
> + "%s: Unsupported crypt command 0x%x\n",
> __func__, cmd);
> + return;
> + }
> +
> + if (!qcrypto_cipher_supports(alg, mode)) {
> + qemu_log_mask(LOG_UNIMP,
> + "%s: cipher mode not supported by the crypto
> backend\n",
> + __func__);
> + return;
> + }
> +
> + /* Fetch the IV and key from the context buffer in DRAM. */
> + ctx_addr = s->regs[R_CRYPT_CONTEXT];
> + if (address_space_read(&s->dram_as, ctx_addr,
> MEMTXATTRS_UNSPECIFIED,
> + ctx, sizeof(ctx))) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: Failed to read context, addr=0x%"
> HWADDR_PRIx "\n",
> + __func__, ctx_addr);
> + return;
> + }
> +
> + if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP))
> {
> + hace_hexdump("context", (char *)ctx, sizeof(ctx));
> + }
> +
> + cipher = qcrypto_cipher_new(alg, mode, ctx + CRYPT_CTX_KEY_OFFSET,
> keylen,
> + &local_err);
> + if (cipher == NULL) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher new
> failed: %s\n",
> + __func__, error_get_pretty(local_err));
> + error_free(local_err);
> + return;
> + }
> +
> + if (mode != QCRYPTO_CIPHER_MODE_ECB &&
> + qcrypto_cipher_setiv(cipher, ctx + iv_offset, blocklen,
> + &local_err) < 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv
> failed: %s\n",
> + __func__, error_get_pretty(local_err));
> + error_free(local_err);
> + return;
> + }
> +
> + src_buf = g_malloc0(len);
> + dst_buf = g_malloc0(len);
> +
> + src_addr = s->regs[R_CRYPT_SRC];
> + if (!crypt_prepare_direct(s, src_addr, src_buf, len, false)) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: Failed to read src, addr=0x%" HWADDR_PRIx
> "\n",
> + __func__, src_addr);
> + return;
> + }
> +
> + if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP))
> {
> + hace_hexdump("src", (char *)src_buf, len);
> + }
> +
> + if (encrypt) {
> + if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, len,
> + &local_err) < 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: encrypt failed:
> %s\n",
> + __func__, error_get_pretty(local_err));
> + error_free(local_err);
> + return;
> + }
> + } else {
> + if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, len,
> + &local_err) < 0) {
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: decrypt failed:
> %s\n",
> + __func__, error_get_pretty(local_err));
> + error_free(local_err);
> + return;
> + }
> + }
> +
> + dst_addr = s->regs[R_CRYPT_DEST];
> + if (!crypt_prepare_direct(s, dst_addr, dst_buf, len, true)) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: Failed to write dst, addr=0x%" HWADDR_PRIx
> "\n",
> + __func__, dst_addr);
> + return;
> + }
> +
> + if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP))
> {
> + hace_hexdump("dst", (char *)dst_buf, len);
> + }
> +
> + if (mode == QCRYPTO_CIPHER_MODE_CBC) {
> + /*
> + * CBC chains on the last ciphertext block: the final block of the
> + * output when encrypting, or of the input when decrypting. Write
> it
> + * back as the IV for the next request.
> + */
> + next_iv = (encrypt ? dst_buf : src_buf) + len - blocklen;
> + if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
> + MEMTXATTRS_UNSPECIFIED, next_iv,
> blocklen)) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: Failed to write IV, addr=0x%"
> HWADDR_PRIx "\n",
> + __func__, ctx_addr + iv_offset);
> + }
> + }
> +}
> +
> static uint64_t aspeed_hace_read(void *opaque, hwaddr addr, unsigned int
> size) {
> AspeedHACEState *s = ASPEED_HACE(opaque); @@ -531,16 +774,22
> @@ static void aspeed_hace_write(void *opaque, hwaddr addr, uint64_t data,
> qemu_irq_lower(s->irq);
> }
> }
> - if (ahc->raise_crypt_interrupt_workaround) {
> - if (data & CRYPT_IRQ) {
> - data &= ~CRYPT_IRQ;
> + if (data & CRYPT_IRQ) {
> + data &= ~CRYPT_IRQ;
>
> - if (s->regs[addr] & CRYPT_IRQ) {
> - qemu_irq_lower(s->irq);
> - }
> + if (s->regs[addr] & CRYPT_IRQ) {
> + qemu_irq_lower(s->irq);
> }
> }
> break;
> + case R_CRYPT_SRC:
> + case R_CRYPT_DEST:
> + case R_CRYPT_CONTEXT:
> + data &= ahc->src_mask;
> + break;
> + case R_CRYPT_DATA_LEN:
> + data &= CRYPT_DATA_LEN_MASK;
> + break;
> case R_HASH_SRC:
> data &= ahc->src_mask;
> break;
> @@ -589,13 +838,19 @@ static void aspeed_hace_write(void *opaque,
> hwaddr addr, uint64_t data,
> break;
> }
> case R_CRYPT_CMD:
> - qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not
> implemented\n",
> - __func__);
> - if (ahc->raise_crypt_interrupt_workaround) {
> - s->regs[R_STATUS] |= CRYPT_IRQ;
> - if (data & CRYPT_IRQ_EN) {
> - qemu_irq_raise(s->irq);
> - }
> + /*
> + * The AST2700 crypto engine needs 64-bit DMA and AES-GCM,
> which are
> + * added later; until then it keeps the temporary workaround of
> only
> + * raising the completion interrupt without running the command.
> + */
> + if (!ahc->raise_crypt_interrupt_workaround) {
> + do_crypt_operation(s, data);
> + }
> +
> + /* Hardware raises the crypt interrupt once the command finishes.
> */
> + s->regs[R_STATUS] |= CRYPT_IRQ;
> + if (data & CRYPT_CMD_ISR_EN) {
> + qemu_irq_raise(s->irq);
> }
> break;
> case R_HASH_SRC_HI:
> --
> 2.43.0