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 using the CE one-shot task
interface. Build the final hash padding in the driver and bounce
word-unaligned input so FIT data with short or unaligned tails can be
hashed directly.

H6 and H616 use bit-sized hash task lengths, so reject inputs whose
padded length would overflow 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.

Signed-off-by: James Hilliard <[email protected]>
---
Changes v1 -> v2:
  - Reject oversized bit-length hash tasks  (suggested by Simon Glass)
  - Feed the watchdog while polling CE completion  (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 |  55 ++--
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c | 298 ++++++++++++++++++++++
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h      |   7 +
 5 files changed, 372 insertions(+), 15 deletions(-)

diff --git a/drivers/crypto/allwinner/sun8i-ce/Kconfig 
b/drivers/crypto/allwinner/sun8i-ce/Kconfig
index e780638d86f..c9ca8141ba6 100644
--- a/drivers/crypto/allwinner/sun8i-ce/Kconfig
+++ b/drivers/crypto/allwinner/sun8i-ce/Kconfig
@@ -38,6 +38,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 743a93ca3d3..ed0ac8b025f 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
@@ -7,11 +7,13 @@
 #include <dm.h>
 #include <dm/device_compat.h>
 #include <dm/lists.h>
+#include <errno.h>
+#include <time.h>
+#include <u-boot/schedule.h>
 #include <asm/arch/cpu.h>
 #include <asm/cache.h>
 #include <asm/io.h>
 #include <linux/delay.h>
-#include <linux/iopoll.h>
 #include "sun8i-ce.h"
 
 #define SUNXI_CE_TDQ                   0x00
@@ -77,6 +79,25 @@ static void sunxi_ce_print_error(u32 err)
                printf("CE ERROR: key ladder configuration error\n");
 }
 
+static int sunxi_ce_wait(void __iomem *addr, u32 mask, u32 expect)
+{
+       unsigned long timeout = timer_get_us() + SUNXI_CE_TIMEOUT_US;
+       u32 val;
+
+       do {
+               val = readl(addr);
+               if ((val & mask) == expect)
+                       return 0;
+               schedule();
+       } while (!time_after(timer_get_us(), timeout));
+
+       val = readl(addr);
+       if ((val & mask) == expect)
+               return 0;
+
+       return -ETIMEDOUT;
+}
+
 int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct sunxi_ce_task *task)
 {
        u32 method = task->t_common_ctl & SUNXI_CE_METHOD_MASK;
@@ -87,15 +108,12 @@ int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct 
sunxi_ce_task *task)
        writel(val | SUNXI_CE_CHAN_INT, priv->base + SUNXI_CE_ICR);
        writel(SUNXI_CE_CHAN_INT, priv->base + SUNXI_CE_ISR);
        writel(SUNXI_CE_CHAN_ERR_MASK, priv->base + SUNXI_CE_ESR);
-       ret = readl_poll_timeout(priv->base + SUNXI_CE_ISR, val,
-                                !(val & SUNXI_CE_CHAN_INT),
-                                SUNXI_CE_TIMEOUT_US);
+       ret = sunxi_ce_wait(priv->base + SUNXI_CE_ISR, SUNXI_CE_CHAN_INT, 0);
        if (ret) {
                printf("%s: timeout waiting for stale interrupt\n", __func__);
                return ret;
        }
-       ret = readl_poll_timeout(priv->base + SUNXI_CE_TLR, val, !val,
-                                SUNXI_CE_TIMEOUT_US);
+       ret = sunxi_ce_wait(priv->base + SUNXI_CE_TLR, ~0U, 0);
        if (ret) {
                printf("%s: timeout waiting for task launcher\n", __func__);
                return ret;
@@ -108,9 +126,8 @@ int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct 
sunxi_ce_task *task)
        writel((method << SUNXI_CE_TLR_METHOD_SHIFT) | SUNXI_CE_TASK_START,
               priv->base + SUNXI_CE_TLR);
 
-       ret = readl_poll_timeout(priv->base + SUNXI_CE_ISR, val,
-                                val & SUNXI_CE_CHAN_INT,
-                                SUNXI_CE_TIMEOUT_US);
+       ret = sunxi_ce_wait(priv->base + SUNXI_CE_ISR, SUNXI_CE_CHAN_INT,
+                           SUNXI_CE_CHAN_INT);
        if (ret) {
                printf("%s: DMA timeout\n", __func__);
                writel(SUNXI_CE_CHAN_INT, priv->base + SUNXI_CE_ISR);
@@ -118,14 +135,12 @@ int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct 
sunxi_ce_task *task)
                return ret;
        }
 
-       ret = readl_poll_timeout(priv->base + SUNXI_CE_SCSA, val, !val,
-                                SUNXI_CE_TIMEOUT_US);
+       ret = sunxi_ce_wait(priv->base + SUNXI_CE_SCSA, ~0U, 0);
        if (ret) {
                printf("%s: timeout waiting for source DMA idle\n", __func__);
                return ret;
        }
-       ret = readl_poll_timeout(priv->base + SUNXI_CE_SCDA, val, !val,
-                                SUNXI_CE_TIMEOUT_US);
+       ret = sunxi_ce_wait(priv->base + SUNXI_CE_SCDA, ~0U, 0);
        if (ret) {
                printf("%s: timeout waiting for destination DMA idle\n",
                       __func__);
@@ -135,8 +150,7 @@ int sunxi_ce_run_task(struct sunxi_ce_priv *priv, struct 
sunxi_ce_task *task)
        writel(0, priv->base + SUNXI_CE_TLR);
        clrbits_le32(priv->base + SUNXI_CE_ICR, SUNXI_CE_CHAN_INT);
        writel(0, priv->base + SUNXI_CE_TDQ);
-       ret = readl_poll_timeout(priv->base + SUNXI_CE_TLR, val, !val,
-                                SUNXI_CE_TIMEOUT_US);
+       ret = sunxi_ce_wait(priv->base + SUNXI_CE_TLR, ~0U, 0);
        if (ret) {
                printf("%s: timeout clearing task launcher\n", __func__);
                return ret;
@@ -259,6 +273,13 @@ static int sunxi_ce_bind(struct udevice *dev)
                        return ret;
        }
 
+       if (CONFIG_IS_ENABLED(SUNXI_CE_HASH) && variant->has_hash) {
+               ret = device_bind_driver(dev, "sun8i-ce-hash",
+                                        "sun8i-ce-hash", NULL);
+               if (ret)
+                       return ret;
+       }
+
        return 0;
 }
 
@@ -345,7 +366,9 @@ static int sunxi_ce_remove(struct udevice *dev)
 
 static const struct sunxi_ce_variant sun50i_h6_variant = {
        .has_aes = true,
+       .has_hash = true,
        .cipher_t_dlen_in_bytes = true,
+       .hash_t_dlen_in_bits = true,
        .spl_enable_clocks = sun50i_h6_ce_spl_enable_clocks,
        .spl_reset = sun50i_h6_ce_spl_reset,
 };
@@ -353,7 +376,9 @@ static const struct sunxi_ce_variant sun50i_h6_variant = {
 static const struct sunxi_ce_variant sun50i_h616_variant = {
        .has_aes = true,
        .has_ecdsa = true,
+       .has_hash = true,
        .cipher_t_dlen_in_bytes = true,
+       .hash_t_dlen_in_bits = true,
        .needs_word_addresses = true,
        .setup_mod_clock = sun50i_h616_ce_setup_mod_clock,
        .spl_enable_clocks = sun50i_h616_ce_spl_enable_clocks,
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..64c4c2646e0
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#define LOG_CATEGORY UCLASS_HASH
+
+#include <dm.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/dma-mapping.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_MAX_PAD_SIZE     (2 * SUNXI_CE_HASH_MAX_BLOCK_SIZE)
+
+struct sunxi_hash_algo {
+       enum HASH_ALGO algo;
+       u32 method;
+       u32 digest_size;
+       u32 result_size;
+       u32 block_size;
+       bool le_len;
+};
+
+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_MAX_DIGEST_SIZE] __aligned(ARCH_DMA_MINALIGN);
+};
+
+static const struct sunxi_hash_algo sunxi_hash_algos[] = {
+       {
+               .algo = HASH_ALGO_MD5,
+               .method = SUNXI_CE_METHOD_MD5,
+               .digest_size = MD5_SUM_LEN,
+               .result_size = MD5_SUM_LEN,
+               .block_size = 64,
+               .le_len = true,
+       },
+       {
+               .algo = HASH_ALGO_SHA1,
+               .method = SUNXI_CE_METHOD_SHA1,
+               .digest_size = SHA1_SUM_LEN,
+               .result_size = SHA1_SUM_LEN,
+               .block_size = 64,
+       },
+       {
+               .algo = HASH_ALGO_SHA256,
+               .method = SUNXI_CE_METHOD_SHA256,
+               .digest_size = SHA256_SUM_LEN,
+               .result_size = SHA256_SUM_LEN,
+               .block_size = 64,
+       },
+       {
+               .algo = HASH_ALGO_SHA384,
+               .method = SUNXI_CE_METHOD_SHA384,
+               .digest_size = SHA384_SUM_LEN,
+               .result_size = SHA512_SUM_LEN,
+               .block_size = SHA512_BLOCK_SIZE,
+       },
+       {
+               .algo = HASH_ALGO_SHA512,
+               .method = SUNXI_CE_METHOD_SHA512,
+               .digest_size = SHA512_SUM_LEN,
+               .result_size = SHA512_SUM_LEN,
+               .block_size = SHA512_BLOCK_SIZE,
+       },
+};
+
+static bool sunxi_hash_algo_enabled(enum HASH_ALGO algo)
+{
+       if (!IS_ENABLED(CONFIG_XPL_BUILD))
+               return true;
+
+       switch (algo) {
+       case HASH_ALGO_MD5:
+               return CONFIG_IS_ENABLED(MD5);
+       case HASH_ALGO_SHA1:
+               return CONFIG_IS_ENABLED(SHA1);
+       case HASH_ALGO_SHA256:
+               return CONFIG_IS_ENABLED(SHA256);
+       case HASH_ALGO_SHA384:
+               return CONFIG_IS_ENABLED(SHA384);
+       case HASH_ALGO_SHA512:
+               return CONFIG_IS_ENABLED(SHA512);
+       default:
+               return false;
+       }
+}
+
+static const struct sunxi_hash_algo *sunxi_hash_find_algo(enum HASH_ALGO algo)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(sunxi_hash_algos); i++) {
+               if (sunxi_hash_algos[i].algo == algo &&
+                   sunxi_hash_algo_enabled(algo))
+                       return &sunxi_hash_algos[i];
+       }
+
+       return NULL;
+}
+
+static size_t sunxi_hash_pad(const struct sunxi_hash_algo *algo, u8 *pad,
+                            const u8 *tail, size_t tail_len, u32 len)
+{
+       size_t aligned_len = ALIGN_DOWN(len, sizeof(u32));
+       size_t rem = len % algo->block_size;
+       size_t len_size = algo->block_size == SHA512_BLOCK_SIZE ? 16 : 8;
+       size_t total_len, pad_len, len_off;
+       u64 bits;
+
+       total_len = rem < algo->block_size - len_size ?
+                   len + algo->block_size - rem :
+                   len + 2 * algo->block_size - rem;
+       pad_len = total_len - aligned_len;
+       if (pad_len > SUNXI_CE_HASH_MAX_PAD_SIZE)
+               return 0;
+
+       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 (algo->le_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,
+                                const struct sunxi_hash_algo *algo,
+                                dma_addr_t src, size_t src_len,
+                                dma_addr_t pad, size_t pad_len,
+                                dma_addr_t result)
+{
+       struct sunxi_ce_task *task = &job->task;
+       u32 total_len = src_len + pad_len;
+       u32 sg = 0;
+
+       memset(task, 0, sizeof(*task));
+
+       task->t_id = SUNXI_CE_CHAN;
+       task->t_common_ctl = SUNXI_CE_COMM_INT | algo->method;
+       task->t_dlen = ce->variant->hash_t_dlen_in_bits ?
+                      total_len * 8 : total_len / sizeof(u32);
+
+       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++;
+       }
+       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 = algo->result_size / sizeof(u32);
+}
+
+static int sunxi_hash_run(struct sunxi_ce_priv *ce, struct sunxi_hash_job *job,
+                         const struct sunxi_hash_algo *algo,
+                         const void *src, size_t src_len, size_t pad_len,
+                         size_t result_len)
+{
+       dma_addr_t src_dma = 0, pad_dma, result_dma;
+       int ret;
+
+       if (src_len) {
+               src_dma = dma_map_single((void *)src, src_len,
+                                        DMA_TO_DEVICE);
+               if (dma_mapping_error(NULL, src_dma))
+                       return -EIO;
+       }
+
+       pad_dma = dma_map_single(job->pad, pad_len, DMA_TO_DEVICE);
+       if (dma_mapping_error(NULL, pad_dma)) {
+               ret = -EIO;
+               goto out_unmap_src;
+       }
+
+       result_dma = dma_map_single(job->result, result_len, DMA_FROM_DEVICE);
+       if (dma_mapping_error(NULL, result_dma)) {
+               ret = -EIO;
+               goto out_unmap_pad;
+       }
+
+       sunxi_hash_fill_task(ce, job, algo, src_dma, src_len, pad_dma,
+                            pad_len, result_dma);
+       sunxi_ce_flush(&job->task, sizeof(job->task));
+
+       ret = sunxi_ce_run_task(ce, &job->task);
+
+       dma_unmap_single(result_dma, result_len, DMA_FROM_DEVICE);
+out_unmap_pad:
+       dma_unmap_single(pad_dma, pad_len, DMA_TO_DEVICE);
+out_unmap_src:
+       if (src_len)
+               dma_unmap_single(src_dma, src_len, DMA_TO_DEVICE);
+
+       return ret;
+}
+
+static int sunxi_hash_digest(struct udevice *dev, enum HASH_ALGO hash_algo,
+                            const void *ibuf, const uint32_t ilen, void *obuf)
+{
+       const struct sunxi_hash_algo *algo = sunxi_hash_find_algo(hash_algo);
+       struct sunxi_ce_priv *ce = dev_get_priv(dev_get_parent(dev));
+       size_t src_len = ALIGN_DOWN(ilen, sizeof(u32));
+       size_t tail_len = ilen - src_len;
+       struct sunxi_hash_job *job;
+       const void *src = ibuf;
+       u8 *src_buf = NULL;
+       const u8 *tail = ibuf;
+       size_t pad_len;
+       int ret;
+
+       if (!algo || (!ibuf && ilen) || !obuf)
+               return -EINVAL;
+
+       job = malloc_cache_aligned(sizeof(*job));
+       if (!job)
+               return -ENOMEM;
+
+       memset(job, 0, sizeof(*job));
+
+       if (src_len && !IS_ALIGNED((ulong)ibuf, sizeof(u32))) {
+               src_buf = memalign(ARCH_DMA_MINALIGN,
+                                  ALIGN(src_len, ARCH_DMA_MINALIGN));
+               if (!src_buf) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+               memcpy(src_buf, ibuf, src_len);
+               src = src_buf;
+       }
+
+       if (tail_len)
+               tail += src_len;
+
+       pad_len = sunxi_hash_pad(algo, job->pad, tail, tail_len, ilen);
+       if (!pad_len) {
+               ret = -EINVAL;
+               goto out;
+       }
+       if (ce->variant->hash_t_dlen_in_bits &&
+           src_len + pad_len > U32_MAX / 8) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ret = sunxi_hash_run(ce, job, algo, src, src_len, pad_len,
+                            algo->result_size);
+       if (ret)
+               goto out;
+
+       memcpy(obuf, job->result, algo->digest_size);
+
+out:
+       free(src_buf);
+       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 7facd6d89ad..c3d235a43cc 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
@@ -15,6 +15,11 @@
 #define SUNXI_CE_CHAN_INT              BIT(SUNXI_CE_CHAN)
 #define SUNXI_CE_COMM_INT              BIT(31)
 #define SUNXI_CE_METHOD_AES            0
+#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
@@ -43,7 +48,9 @@ struct sunxi_ce_task {
 struct sunxi_ce_variant {
        bool has_aes;
        bool has_ecdsa;
+       bool has_hash;
        bool cipher_t_dlen_in_bytes;
+       bool hash_t_dlen_in_bits;
        bool needs_word_addresses;
        void (*setup_mod_clock)(void);
        void (*spl_enable_clocks)(void);

-- 
2.53.0

Reply via email to