The Allwinner sun8i Crypto Engine includes hardware hash methods. Add a
UCLASS_HASH child for the sun8i-ce parent so FIT hash verification can
use the accelerator from U-Boot proper and SPL.

Support MD5, SHA1, SHA256, SHA384 and SHA512. Build the final hash padding
in the driver. Stream directly addressable input through bounded 64 MiB
zero-copy tasks and word-unaligned input through a fixed 128 KiB repack
buffer. Feed each intermediate state back through hash input-IV mode,
keeping heap use independent of input size while word-aligned cacheline
offsets remain zero-copy.

Map input, padding, optional state and a cacheline-rounded private output
through the parent's DMA objects. Keep the descriptor output length limited
to the algorithm state, then submit hash work on the dedicated completion
channel through the shared task-session API. Keep one session across every
continuation chunk while retiring each task before releasing its mappings.
Release mappings in reverse preparation order after the session retires.

H6 and H616 use bit-sized hash task lengths, so keep each submitted chunk
within the 32-bit task descriptor field. Feed the watchdog from the shared
CE polling path while waiting for hardware completion.

In SPL, only advertise hash algorithms selected for that phase so
SRAM-constrained builds do not accept wider algorithms unless requested.

Reviewed-by: Simon Glass <[email protected]>
Signed-off-by: James Hilliard <[email protected]>
---
Changes v4 -> v5:
  - Use the parent's explicit close-on-error blocking helper to populate
    descriptor transport fields and abort failed chains before callers
    release their DMA mappings
  - Replace the algorithm switch and separate size lookups with one table
  - Derive bounded padding without adding near the input-size limit, and
    validate input ranges and descriptor-length arithmetic
  - Replace the payload-sized word-unaligned bounce with a fixed 128 KiB
    streaming repack while keeping word-aligned cacheline offsets zero-copy
  - Split large direct input into bounded 64 MiB tasks and use one
    continuation loop and one CE session for direct and repacked input
  - Carry intermediate state through hash input-IV mode
  - Use shared cacheline-safe DMA mappings and round private result storage
    to complete cachelines

Changes v3 -> v4:
  - Consolidate algorithm selection and invariant task sizing
  - Use the dedicated hash channel and shared task-session path
  - Return -EOPNOTSUPP for unavailable algorithms

Changes v1 -> v2:
  - Reject oversized bit-length hash tasks  (suggested by Simon Glass)
  - Feed the watchdog during CE polling  (suggested by Simon Glass)
  - Drop the post-operation schedule() call  (suggested by Simon Glass)
  - Bounce only word-unaligned input
---
 drivers/crypto/allwinner/sun8i-ce/Kconfig         |  26 ++
 drivers/crypto/allwinner/sun8i-ce/Makefile        |   1 +
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c |   6 +
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c | 343 ++++++++++++++++++++++
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h      |   5 +
 5 files changed, 381 insertions(+)

diff --git a/drivers/crypto/allwinner/sun8i-ce/Kconfig 
b/drivers/crypto/allwinner/sun8i-ce/Kconfig
index 49686d07aa3..c53c38cf5d6 100644
--- a/drivers/crypto/allwinner/sun8i-ce/Kconfig
+++ b/drivers/crypto/allwinner/sun8i-ce/Kconfig
@@ -40,6 +40,32 @@ config SPL_SUNXI_CE_AES
          Engine found in Allwinner H6 and H616 compatible SoCs. This can be
          used to decrypt FIT images before loading U-Boot proper.
 
+config SUNXI_CE_HASH
+       bool "Allwinner sunxi CE hash"
+       depends on ARCH_SUNXI
+       depends on DM_HASH
+       depends on CLK && DM_RESET
+       select SUNXI_CE
+       help
+         Select this option to enable hash calculation using the Crypto Engine
+         found in Allwinner sunxi SoCs. The driver supports MD5, SHA1,
+         SHA256, SHA384 and SHA512.
+
+config SPL_SUNXI_CE_HASH
+       bool "Allwinner sunxi CE hash in SPL"
+       depends on ARCH_SUNXI
+       depends on MACH_SUN50I_H6 || MACH_SUN50I_H616
+       depends on SPL_DM
+       depends on SPL_OF_CONTROL
+       select SPL_DM_HASH
+       select SPL_CRYPTO
+       select SPL_SUNXI_CE
+       help
+         Select this option to enable hash calculation in SPL using the Crypto
+         Engine found in Allwinner H6 and H616 compatible SoCs. FIT image
+         hashes can then be calculated by the hardware accelerator before
+         U-Boot proper is loaded.
+
 config SUNXI_CE_ECDSA
        bool "Allwinner sunxi CE ECDSA verifier"
        depends on ARCH_SUNXI
diff --git a/drivers/crypto/allwinner/sun8i-ce/Makefile 
b/drivers/crypto/allwinner/sun8i-ce/Makefile
index 753ea827a0d..5baf65e40ec 100644
--- a/drivers/crypto/allwinner/sun8i-ce/Makefile
+++ b/drivers/crypto/allwinner/sun8i-ce/Makefile
@@ -2,4 +2,5 @@
 
 obj-$(CONFIG_$(PHASE_)SUNXI_CE) += sun8i-ce-core.o
 obj-$(CONFIG_$(PHASE_)SUNXI_CE_AES) += sun8i-ce-aes.o
+obj-$(CONFIG_$(PHASE_)SUNXI_CE_HASH) += sun8i-ce-hash.o
 obj-$(CONFIG_$(PHASE_)SUNXI_CE_ECDSA) += sun8i-ce-ecdsa.o
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c 
b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
index 4ce1ee70c14..201e9203bf1 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
@@ -679,6 +679,12 @@ static int sunxi_ce_bind(struct udevice *dev)
                        return ret;
        }
 
+       if (CONFIG_IS_ENABLED(SUNXI_CE_HASH)) {
+               ret = sunxi_ce_bind_child(dev, "sun8i-ce-hash");
+               if (ret)
+                       return ret;
+       }
+
        return 0;
 }
 
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c 
b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c
new file mode 100644
index 00000000000..583d2e5de20
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#define LOG_CATEGORY UCLASS_HASH
+
+#include <dm.h>
+#include <limits.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <u-boot/hash.h>
+#include <u-boot/md5.h>
+#include <u-boot/sha1.h>
+#include <u-boot/sha256.h>
+#include <u-boot/sha512.h>
+#include <linux/kernel.h>
+#include "sun8i-ce.h"
+
+#define SUNXI_CE_HASH_MAX_BLOCK_SIZE   SHA512_BLOCK_SIZE
+#define SUNXI_CE_HASH_MAX_DIGEST_SIZE  SHA512_SUM_LEN
+#define SUNXI_CE_HASH_RESULT_SIZE      ALIGN(SUNXI_CE_HASH_MAX_DIGEST_SIZE, \
+                                             ARCH_DMA_MINALIGN)
+#define SUNXI_CE_HASH_MAX_PAD_SIZE     (2 * SUNXI_CE_HASH_MAX_BLOCK_SIZE)
+#define SUNXI_CE_HASH_REPACK_SIZE      (128 * 1024)
+#define SUNXI_CE_HASH_DIRECT_SIZE      (64 * 1024 * 1024)
+#define SUNXI_CE_HASH_IV_INPUT         BIT(16)
+#define SUNXI_CE_HASH_BLOCK_SIZE               64
+
+static_assert(IS_ALIGNED(SUNXI_CE_HASH_REPACK_SIZE,
+                        SUNXI_CE_HASH_MAX_BLOCK_SIZE));
+static_assert(IS_ALIGNED(SUNXI_CE_HASH_DIRECT_SIZE,
+                        SUNXI_CE_HASH_MAX_BLOCK_SIZE));
+
+struct sunxi_hash_job {
+       struct sunxi_ce_task task __aligned(ARCH_DMA_MINALIGN);
+       u8 pad[SUNXI_CE_HASH_MAX_PAD_SIZE] __aligned(ARCH_DMA_MINALIGN);
+       u8 result[SUNXI_CE_HASH_RESULT_SIZE] __aligned(ARCH_DMA_MINALIGN);
+       u8 state[SUNXI_CE_HASH_MAX_DIGEST_SIZE] __aligned(ARCH_DMA_MINALIGN);
+};
+
+struct sunxi_hash_alg {
+       u8 method;
+       u8 block_size;
+       u8 digest_size;
+       u8 state_size;
+       bool little_endian_len;
+       bool available;
+};
+
+static const struct sunxi_hash_alg sunxi_hash_algs[HASH_ALGO_NUM] = {
+       [HASH_ALGO_MD5] = {
+               .method = SUNXI_CE_METHOD_MD5,
+               .block_size = SUNXI_CE_HASH_BLOCK_SIZE,
+               .digest_size = MD5_SUM_LEN,
+               .state_size = MD5_SUM_LEN,
+               .little_endian_len = true,
+               .available = !IS_ENABLED(CONFIG_XPL_BUILD) ||
+                            CONFIG_IS_ENABLED(MD5),
+       },
+       [HASH_ALGO_SHA1] = {
+               .method = SUNXI_CE_METHOD_SHA1,
+               .block_size = SUNXI_CE_HASH_BLOCK_SIZE,
+               .digest_size = SHA1_SUM_LEN,
+               .state_size = SHA1_SUM_LEN,
+               .available = !IS_ENABLED(CONFIG_XPL_BUILD) ||
+                            CONFIG_IS_ENABLED(SHA1),
+       },
+       [HASH_ALGO_SHA256] = {
+               .method = SUNXI_CE_METHOD_SHA256,
+               .block_size = SUNXI_CE_HASH_BLOCK_SIZE,
+               .digest_size = SHA256_SUM_LEN,
+               .state_size = SHA256_SUM_LEN,
+               .available = !IS_ENABLED(CONFIG_XPL_BUILD) ||
+                            CONFIG_IS_ENABLED(SHA256),
+       },
+       [HASH_ALGO_SHA384] = {
+               .method = SUNXI_CE_METHOD_SHA384,
+               .block_size = SHA512_BLOCK_SIZE,
+               .digest_size = SHA384_SUM_LEN,
+               .state_size = SHA512_SUM_LEN,
+               .available = !IS_ENABLED(CONFIG_XPL_BUILD) ||
+                            CONFIG_IS_ENABLED(SHA384),
+       },
+       [HASH_ALGO_SHA512] = {
+               .method = SUNXI_CE_METHOD_SHA512,
+               .block_size = SHA512_BLOCK_SIZE,
+               .digest_size = SHA512_SUM_LEN,
+               .state_size = SHA512_SUM_LEN,
+               .available = !IS_ENABLED(CONFIG_XPL_BUILD) ||
+                            CONFIG_IS_ENABLED(SHA512),
+       },
+};
+
+static const struct sunxi_hash_alg *sunxi_hash_get_alg(enum HASH_ALGO algo)
+{
+       if ((u32)algo >= ARRAY_SIZE(sunxi_hash_algs) ||
+           !sunxi_hash_algs[algo].available)
+               return NULL;
+
+       return &sunxi_hash_algs[algo];
+}
+
+static size_t sunxi_hash_pad(const struct sunxi_hash_alg *alg, u8 *pad,
+                            const u8 *tail, size_t tail_len, size_t len)
+{
+       size_t block_size = alg->block_size;
+       size_t rem = len % block_size;
+       size_t len_size = block_size == SHA512_BLOCK_SIZE ? 16 : 8;
+       size_t pad_len, len_off;
+       u64 bits;
+
+       pad_len = tail_len + (rem < block_size - len_size ?
+                              block_size - rem : 2 * block_size - rem);
+
+       memset(pad, 0, pad_len);
+       if (tail_len)
+               memcpy(pad, tail, tail_len);
+       pad[tail_len] = 0x80;
+
+       bits = (u64)len << 3;
+       len_off = pad_len - 8;
+       if (alg->little_endian_len) {
+               bits = cpu_to_le64(bits);
+               memcpy(pad + len_off, &bits, sizeof(bits));
+       } else {
+               bits = cpu_to_be64(bits);
+               memcpy(pad + len_off, &bits, sizeof(bits));
+       }
+
+       return pad_len;
+}
+
+static void sunxi_hash_fill_task(struct sunxi_ce_priv *ce,
+                                struct sunxi_hash_job *job,
+                                dma_addr_t iv,
+                                dma_addr_t src, size_t src_len,
+                                dma_addr_t pad, size_t pad_len,
+                                dma_addr_t result, size_t state_len)
+{
+       struct sunxi_ce_task *task = &job->task;
+       u32 total_len = src_len + pad_len;
+       u32 sg = 0;
+
+       memset(task, 0, sizeof(*task));
+
+       task->t_common_ctl = iv ? SUNXI_CE_HASH_IV_INPUT : 0;
+       if (iv)
+               task->t_iv = sunxi_ce_desc_dma_addr(ce, iv);
+       task->t_dlen = total_len * 8;
+
+       if (src_len) {
+               task->t_src[sg].addr = sunxi_ce_desc_dma_addr(ce, src);
+               task->t_src[sg].len = src_len / sizeof(u32);
+               sg++;
+       }
+       if (pad_len) {
+               task->t_src[sg].addr = sunxi_ce_desc_dma_addr(ce, pad);
+               task->t_src[sg].len = pad_len / sizeof(u32);
+       }
+
+       task->t_dst[0].addr = sunxi_ce_desc_dma_addr(ce, result);
+       task->t_dst[0].len = state_len / sizeof(u32);
+}
+
+static int sunxi_hash_run_chunk(struct sunxi_ce_priv *ce,
+                               struct sunxi_ce_session *session,
+                               struct sunxi_hash_job *job,
+                               u32 method,
+                               const void *src, size_t src_len, size_t pad_len,
+                               const void *iv, size_t state_len)
+{
+       struct sunxi_ce_dma_buf result_dma = { };
+       struct sunxi_ce_dma_buf src_dma = { };
+       struct sunxi_ce_dma_buf pad_dma = { };
+       struct sunxi_ce_dma_buf iv_dma = { };
+       int ret;
+
+       if (!IS_ALIGNED(src_len, sizeof(u32)) ||
+           !IS_ALIGNED(pad_len, sizeof(u32)) ||
+           src_len > U32_MAX / 8 || pad_len > U32_MAX / 8 - src_len)
+               return -EINVAL;
+
+       ret = sunxi_ce_dma_map(ce, &src_dma, (void *)src, src_len,
+                              DMA_TO_DEVICE);
+       if (ret)
+               goto out_unmap;
+       ret = sunxi_ce_dma_map(ce, &pad_dma, job->pad, pad_len,
+                              DMA_TO_DEVICE);
+       if (ret)
+               goto out_unmap;
+       ret = sunxi_ce_dma_map(ce, &iv_dma, (void *)iv, iv ? state_len : 0,
+                              DMA_TO_DEVICE);
+       if (ret)
+               goto out_unmap;
+       ret = sunxi_ce_dma_map(ce, &result_dma, job->result,
+                              sizeof(job->result),
+                              DMA_FROM_DEVICE);
+       if (ret)
+               goto out_unmap;
+
+       sunxi_hash_fill_task(ce, job, iv_dma.dma, src_dma.dma,
+                            src_len, pad_dma.dma, pad_len, result_dma.dma,
+                            state_len);
+
+       ret = sunxi_ce_session_run_chain_or_close(session,
+                                                 SUNXI_CE_CHANNEL_HASH, method,
+                                                 &job->task, 1);
+
+out_unmap:
+       sunxi_ce_dma_unmap(&result_dma);
+       sunxi_ce_dma_unmap(&iv_dma);
+       sunxi_ce_dma_unmap(&pad_dma);
+       sunxi_ce_dma_unmap(&src_dma);
+
+       return ret;
+}
+
+static int sunxi_hash_run_stream(struct sunxi_ce_priv *ce,
+                                struct sunxi_hash_job *job,
+                                u32 method,
+                                const u8 *src, size_t src_len,
+                                size_t pad_len, size_t state_len)
+{
+       struct sunxi_ce_session session;
+       const void *iv = NULL;
+       const void *chunk;
+       size_t chunk_size;
+       u8 *repack = NULL;
+       int ret;
+
+       if (src_len && !IS_ALIGNED((uintptr_t)src, sizeof(u32))) {
+               /* CE scatter-gather addresses require word-aligned chunks. */
+               chunk_size = SUNXI_CE_HASH_REPACK_SIZE;
+               repack = memalign(ARCH_DMA_MINALIGN, chunk_size);
+               if (!repack)
+                       return -ENOMEM;
+       } else {
+               chunk_size = SUNXI_CE_HASH_DIRECT_SIZE;
+       }
+
+       ret = sunxi_ce_session_begin(ce,
+                                    SUNXI_CE_CHAN_MASK(SUNXI_CE_CHANNEL_HASH),
+                                    &session);
+       if (ret)
+               goto out;
+
+       while (src_len > chunk_size) {
+               chunk = src;
+               if (repack) {
+                       memcpy(repack, src, chunk_size);
+                       chunk = repack;
+               }
+               ret = sunxi_hash_run_chunk(ce, &session, job, method, chunk,
+                                          chunk_size, 0, iv, state_len);
+               if (ret)
+                       goto out_close;
+
+               memcpy(job->state, job->result, state_len);
+               iv = job->state;
+               src += chunk_size;
+               src_len -= chunk_size;
+       }
+
+       chunk = src;
+       if (repack) {
+               memcpy(repack, src, src_len);
+               chunk = repack;
+       }
+       ret = sunxi_hash_run_chunk(ce, &session, job, method, chunk, src_len,
+                                  pad_len, iv, state_len);
+out_close:
+       ret = sunxi_ce_session_close(&session, ret);
+out:
+       free(repack);
+
+       return ret;
+}
+
+static int sunxi_hash_digest(struct udevice *dev, enum HASH_ALGO hash_algo,
+                            const void *ibuf, const uint32_t ilen, void *obuf)
+{
+       struct sunxi_ce_priv *ce = dev_get_priv(dev_get_parent(dev));
+       const struct sunxi_hash_alg *alg;
+       size_t src_len = ALIGN_DOWN(ilen, sizeof(u32));
+       size_t tail_len = ilen - src_len;
+       struct sunxi_hash_job *job;
+       const u8 *tail = ibuf;
+       size_t pad_len;
+       u8 digest_size, state_size;
+       u32 method;
+       int ret;
+
+       alg = sunxi_hash_get_alg(hash_algo);
+       if (!alg)
+               return -EOPNOTSUPP;
+       if ((!ibuf && ilen) || !obuf)
+               return -EINVAL;
+       if (ilen > UINTPTR_MAX - (uintptr_t)ibuf)
+               return -EOVERFLOW;
+       method = alg->method;
+       digest_size = alg->digest_size;
+       state_size = alg->state_size;
+
+       job = malloc_cache_aligned(sizeof(*job));
+       if (!job)
+               return -ENOMEM;
+
+       if (tail_len)
+               tail += src_len;
+
+       pad_len = sunxi_hash_pad(alg, job->pad, tail, tail_len, ilen);
+       ret = sunxi_hash_run_stream(ce, job, method, ibuf, src_len, pad_len,
+                                   state_size);
+       if (ret)
+               goto out;
+
+       memcpy(obuf, job->result, digest_size);
+
+out:
+       free(job);
+
+       return ret;
+}
+
+static int sunxi_hash_digest_wd(struct udevice *dev, enum HASH_ALGO algo,
+                               const void *ibuf, const uint32_t ilen,
+                               void *obuf, uint32_t chunk_sz)
+{
+       return sunxi_hash_digest(dev, algo, ibuf, ilen, obuf);
+}
+
+static const struct hash_ops sunxi_hash_ops = {
+       .hash_digest = sunxi_hash_digest,
+       .hash_digest_wd = sunxi_hash_digest_wd,
+};
+
+U_BOOT_DRIVER(sun8i_ce_hash) = {
+       .name = "sun8i-ce-hash",
+       .id = UCLASS_HASH,
+       .ops = &sunxi_hash_ops,
+       .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h 
b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
index d5b7edd357d..b7bb899c5f0 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
@@ -21,6 +21,11 @@
 #define SUNXI_CE_COMM_INT              BIT(31)
 #define SUNXI_CE_METHOD_AES            0
 #define SUNXI_CE_METHOD_RAES           0x30
+#define SUNXI_CE_METHOD_MD5            16
+#define SUNXI_CE_METHOD_SHA1           17
+#define SUNXI_CE_METHOD_SHA256         19
+#define SUNXI_CE_METHOD_SHA384         20
+#define SUNXI_CE_METHOD_SHA512         21
 #define SUNXI_CE_METHOD_ECC            33
 #define SUNXI_CE_ECC_OP_VERIFY         7
 #define SUNXI_CE_ECC_OP_SHIFT          16

-- 
2.53.0

Reply via email to