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 <[email protected]>
---
 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 60dedee6a3..b84f83c0d3 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) {
@@ -729,6 +746,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,
@@ -753,7 +784,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));
@@ -764,9 +795,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);
 
@@ -850,6 +883,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);
+        }
     }
 }
 
@@ -980,6 +1031,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

Reply via email to