All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
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 01/17] hw/misc/aspeed_hace: Support the crypto command in direct access mode
Date: Wed, 15 Jul 2026 03:33:14 +0000	[thread overview]
Message-ID: <20260715033311.1648424-2-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260715033311.1648424-1-jamin_lin@aspeedtech.com>

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 <jamin_lin@aspeedtech.com>
---
 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)
+#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)
+/* 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


  reply	other threads:[~2026-07-15  3:35 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-15  3:33 [PATCH v2 00/17] Support the ASPEED HACE crypto command Jamin Lin
2026-07-15  3:33 ` Jamin Lin [this message]
2026-07-15  3:33 ` [PATCH v2 02/17] tests/qtest/aspeed-hace: Test the crypto command on the AST2500 Jamin Lin
2026-07-15  3:33 ` [PATCH v2 03/17] hw/misc/aspeed_hace: Support scatter-gather mode for the crypto command Jamin Lin
2026-07-15  3:33 ` [PATCH v2 04/17] hw/misc/aspeed_hace: Support the CTR " Jamin Lin
2026-07-15  3:33 ` [PATCH v2 05/17] tests/qtest/aspeed-hace: Test the crypto command on the AST2600 Jamin Lin
2026-07-15  3:33 ` [PATCH v2 06/17] tests/qtest/aspeed-hace: Test the crypto command on the AST1030 Jamin Lin
2026-07-15  3:33 ` [PATCH v2 07/17] crypto/cipher: Add GCM to QCryptoCipherMode Jamin Lin
2026-07-15  4:51   ` Markus Armbruster
2026-07-15  3:33 ` [PATCH v2 08/17] crypto/cipher: Add setaad/gettag for AEAD modes Jamin Lin
2026-07-15  3:33 ` [PATCH v2 09/17] crypto/cipher-gcrypt: Implement AES-GCM Jamin Lin
2026-07-15  3:33 ` [PATCH v2 10/17] crypto/cipher-nettle: " Jamin Lin
2026-07-15  3:33 ` [PATCH v2 11/17] crypto/cipher-gnutls: " Jamin Lin
2026-07-15  3:33 ` [PATCH v2 12/17] tests/unit/test-crypto-cipher: Test AES-GCM mode Jamin Lin
2026-07-15  3:33 ` [PATCH v2 13/17] hw/misc/aspeed_hace: Support 64-bit DMA for the crypto command Jamin Lin
2026-07-15  3:33 ` [PATCH v2 14/17] hw/misc/aspeed_hace: Support the AES-GCM mode " Jamin Lin
2026-07-15  3:33 ` [PATCH v2 15/17] hw/misc/aspeed_hace: Enable the crypto command on the AST2700 Jamin Lin
2026-07-15  3:33 ` [PATCH v2 16/17] tests/qtest/aspeed-hace: Test " Jamin Lin
2026-07-15  3:33 ` [PATCH v2 17/17] tests/functional/aarch64/test_aspeed_ast2700: Drop the AST2700 crypto self-test workaround Jamin Lin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260715033311.1648424-2-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=andrew@codeconstruct.com.au \
    --cc=armbru@redhat.com \
    --cc=berrange@redhat.com \
    --cc=clg@kaod.org \
    --cc=eblake@redhat.com \
    --cc=farosas@suse.de \
    --cc=joel@jms.id.au \
    --cc=kane_chen@aspeedtech.com \
    --cc=leetroy@gmail.com \
    --cc=lvivier@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=steven_lee@aspeedtech.com \
    --cc=troy_lee@aspeedtech.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.