The AST2600, AST1030 and later crypto engines add AES/DES/3DES CTR mode
(HACE10[6:4] = 0b100) on top of the ECB/CBC modes shared with the
AST2500. Decode the CTR selection, round the working buffers up to a
whole block so the stream-like final block is still processed a block at
a time, and write the counter advanced by the number of blocks consumed
back to the context buffer so the driver can continue across requests.

Signed-off-by: Jamin Lin <[email protected]>
---
 hw/misc/aspeed_hace.c | 50 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 45 insertions(+), 5 deletions(-)

diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index 3d3f1fc8dd..09d0515271 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -42,6 +42,7 @@
 #define  CRYPT_CMD_OP_MODE_MASK     (0x7 << 4)
 #define  CRYPT_CMD_ECB              (0x0 << 4)
 #define  CRYPT_CMD_CBC              (0x1 << 4)
+#define  CRYPT_CMD_CTR              (0x4 << 4)
 /* AES key length HACE10[3:2] */
 #define  CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
 #define  CRYPT_CMD_AES256           (0x2 << 2)
@@ -589,6 +590,9 @@ static bool crypt_decode_cmd(uint32_t cmd, 
QCryptoCipherAlgo *alg,
     case CRYPT_CMD_CBC:
         *mode = QCRYPTO_CIPHER_MODE_CBC;
         break;
+    case CRYPT_CMD_CTR:
+        *mode = QCRYPTO_CIPHER_MODE_CTR;
+        break;
     default:
         return false;
     }
@@ -652,6 +656,22 @@ static bool crypt_prepare_sg(AspeedHACEState *s, uint64_t 
addr,
     return copied == len;
 }
 
+/*
+ * Add @add to the big-endian counter block @ctr (@len bytes) in place, so the
+ * CTR mode counter can be advanced by the number of blocks just consumed.
+ */
+static void crypt_be_add(uint8_t *ctr, size_t len, uint64_t add)
+{
+    size_t i = len;
+
+    while (i > 0 && add) {
+        i--;
+        add += ctr[i];
+        ctr[i] = add & 0xff;
+        add >>= 8;
+    }
+}
+
 /*
  * Perform an AES/DES/3DES ECB/CBC operation. The source and destination are
  * either single contiguous buffers (direct access mode) or scatter-gather
@@ -677,6 +697,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t 
cmd)
     uint64_t dst_addr;
     size_t iv_offset;
     size_t blocklen;
+    size_t buf_len;
     size_t keylen;
     bool status;
 
@@ -729,8 +750,14 @@ static void do_crypt_operation(AspeedHACEState *s, 
uint32_t cmd)
         return;
     }
 
-    src_buf = g_malloc0(len);
-    dst_buf = g_malloc0(len);
+    /*
+     * 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.
+     */
+    buf_len = QEMU_ALIGN_UP(len, blocklen);
+    src_buf = g_malloc0(buf_len);
+    dst_buf = g_malloc0(buf_len);
 
     /* Gather the source into the bounce buffer, per the selected mode. */
     src_addr = s->regs[R_CRYPT_SRC];
@@ -751,7 +778,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t 
cmd)
     }
 
     if (encrypt) {
-        if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, len,
+        if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, buf_len,
                                    &local_err) < 0) {
             qemu_log_mask(LOG_GUEST_ERROR, "%s: encrypt failed: %s\n",
                           __func__, error_get_pretty(local_err));
@@ -759,7 +786,7 @@ static void do_crypt_operation(AspeedHACEState *s, uint32_t 
cmd)
             return;
         }
     } else {
-        if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, len,
+        if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, buf_len,
                                    &local_err) < 0) {
             qemu_log_mask(LOG_GUEST_ERROR, "%s: decrypt failed: %s\n",
                           __func__, error_get_pretty(local_err));
@@ -792,13 +819,26 @@ static void do_crypt_operation(AspeedHACEState *s, 
uint32_t cmd)
          * 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;
+        next_iv = (encrypt ? dst_buf : src_buf) + 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);
         }
+    } else if (mode == QCRYPTO_CIPHER_MODE_CTR) {
+        /*
+         * CTR chains on the counter, which advances by one per block. Add the
+         * number of blocks processed (buf_len / blocklen) and write it back.
+         */
+        crypt_be_add(ctx + iv_offset, blocklen, buf_len / blocklen);
+        if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
+                                MEMTXATTRS_UNSPECIFIED, ctx + iv_offset,
+                                blocklen)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, ctx_addr + iv_offset);
+        }
     }
 }
 
-- 
2.43.0

Reply via email to