[PATCH v4 14/14] crypto: allwinner: add sun8i-ce hash driver

James Hilliard james.hilliard1 at gmail.com
Mon Jul 13 08:43:11 CEST 2026


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. Submit hash work on its dedicated completion channel
through the parent's task-session API.

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 <james.hilliard1 at gmail.com>
---
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 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 |  23 ++-
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c | 238 ++++++++++++++++++++++
 drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h      |  11 +-
 5 files changed, 292 insertions(+), 7 deletions(-)

diff --git a/drivers/crypto/allwinner/sun8i-ce/Kconfig b/drivers/crypto/allwinner/sun8i-ce/Kconfig
index dedfa835775..b906bc222cc 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 c8c4e1c24bc..4f80334a9a0 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
@@ -123,6 +123,12 @@ static u32 sunxi_ce_method_engine(u32 method)
 		return SUNXI_CE_ENGINE_AES;
 	case SUNXI_CE_METHOD_RAES:
 		return SUNXI_CE_ENGINE_RAES;
+	case SUNXI_CE_METHOD_MD5:
+	case SUNXI_CE_METHOD_SHA1:
+	case SUNXI_CE_METHOD_SHA256:
+	case SUNXI_CE_METHOD_SHA384:
+	case SUNXI_CE_METHOD_SHA512:
+		return SUNXI_CE_ENGINE_HASH;
 	case SUNXI_CE_METHOD_ECC:
 		return SUNXI_CE_ENGINE_ASYM;
 	default:
@@ -586,20 +592,29 @@ static void sun50i_h616_ce_spl_enable_clocks(void)
 	sun50i_h6_ce_spl_reset();
 }
 
+static int sunxi_ce_bind_child(struct udevice *dev, const char *name)
+{
+	return device_bind_driver(dev, name, name, NULL);
+}
+
 static int sunxi_ce_bind(struct udevice *dev)
 {
 	int ret;
 
 	if (CONFIG_IS_ENABLED(SUNXI_CE_AES)) {
-		ret = device_bind_driver(dev, "sun8i-ce-aes",
-					 "sun8i-ce-aes", NULL);
+		ret = sunxi_ce_bind_child(dev, "sun8i-ce-aes");
 		if (ret)
 			return ret;
 	}
 
 	if (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA)) {
-		ret = device_bind_driver(dev, "sun8i-ce-ecdsa",
-					 "sun8i-ce-ecdsa", NULL);
+		ret = sunxi_ce_bind_child(dev, "sun8i-ce-ecdsa");
+		if (ret)
+			return ret;
+	}
+
+	if (CONFIG_IS_ENABLED(SUNXI_CE_HASH)) {
+		ret = sunxi_ce_bind_child(dev, "sun8i-ce-hash");
 		if (ret)
 			return ret;
 	}
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..da725ed0a08
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c
@@ -0,0 +1,238 @@
+// 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/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_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 int sunxi_hash_method(enum HASH_ALGO algo)
+{
+	switch (algo) {
+	case HASH_ALGO_MD5:
+		if (IS_ENABLED(CONFIG_XPL_BUILD) && !CONFIG_IS_ENABLED(MD5))
+			return -EOPNOTSUPP;
+		return SUNXI_CE_METHOD_MD5;
+	case HASH_ALGO_SHA1:
+		if (IS_ENABLED(CONFIG_XPL_BUILD) && !CONFIG_IS_ENABLED(SHA1))
+			return -EOPNOTSUPP;
+		return SUNXI_CE_METHOD_SHA1;
+	case HASH_ALGO_SHA256:
+		if (IS_ENABLED(CONFIG_XPL_BUILD) && !CONFIG_IS_ENABLED(SHA256))
+			return -EOPNOTSUPP;
+		return SUNXI_CE_METHOD_SHA256;
+	case HASH_ALGO_SHA384:
+		if (IS_ENABLED(CONFIG_XPL_BUILD) && !CONFIG_IS_ENABLED(SHA384))
+			return -EOPNOTSUPP;
+		return SUNXI_CE_METHOD_SHA384;
+	case HASH_ALGO_SHA512:
+		if (IS_ENABLED(CONFIG_XPL_BUILD) && !CONFIG_IS_ENABLED(SHA512))
+			return -EOPNOTSUPP;
+		return SUNXI_CE_METHOD_SHA512;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static size_t sunxi_hash_pad(enum HASH_ALGO algo, u8 *pad,
+			     const u8 *tail, size_t tail_len, u32 len)
+{
+	u32 block_size = algo >= HASH_ALGO_SHA384 ?
+			 SHA512_BLOCK_SIZE : 64;
+	size_t aligned_len = ALIGN_DOWN(len, sizeof(u32));
+	size_t rem = len % block_size;
+	size_t len_size = block_size == SHA512_BLOCK_SIZE ? 16 : 8;
+	size_t total_len, pad_len, len_off;
+	u64 bits;
+
+	total_len = rem < block_size - len_size ?
+		    len + block_size - rem : len + 2 * block_size - rem;
+	pad_len = total_len - aligned_len;
+
+	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 == HASH_ALGO_MD5) {
+		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,
+				 u32 method,
+				 dma_addr_t src, size_t src_len,
+				 dma_addr_t pad, size_t pad_len,
+				 dma_addr_t result, size_t result_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_id = SUNXI_CE_CHANNEL_HASH;
+	task->t_common_ctl = SUNXI_CE_COMM_INT | method;
+	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++;
+	}
+	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 = result_len / sizeof(u32);
+}
+
+static int sunxi_hash_run(struct sunxi_ce_priv *ce, struct sunxi_hash_job *job,
+			  u32 method,
+			  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, method, src_dma, src_len, pad_dma,
+			     pad_len, result_dma, result_len);
+
+	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)
+{
+	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;
+	ssize_t digest_size;
+	size_t pad_len, result_size;
+	int method, ret;
+
+	method = sunxi_hash_method(hash_algo);
+	if (method < 0)
+		return method;
+	if ((!ibuf && ilen) || !obuf)
+		return -EINVAL;
+	digest_size = hash_algo_digest_size(hash_algo);
+	if (digest_size < 0)
+		return digest_size;
+	result_size = hash_algo == HASH_ALGO_SHA384 ?
+		      SHA512_SUM_LEN : digest_size;
+
+	job = malloc_cache_aligned(sizeof(*job));
+	if (!job)
+		return -ENOMEM;
+
+	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(hash_algo, job->pad, tail, tail_len, ilen);
+	if (src_len + pad_len > U32_MAX / 8) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = sunxi_hash_run(ce, job, method, src, src_len, pad_len, result_size);
+	if (ret)
+		goto out;
+
+	memcpy(obuf, job->result, 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 04bd962f02f..8926274a1ce 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
@@ -13,11 +13,17 @@
 
 #define SUNXI_CE_CHANNEL_AES		0
 #define SUNXI_CE_CHANNEL_RAES		1
+#define SUNXI_CE_CHANNEL_HASH		2
 #define SUNXI_CE_CHANNEL_ASYM		3
 #define SUNXI_CE_CHAN_MASK(x)		BIT(x)
 #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
@@ -27,10 +33,9 @@
 
 #define SUNXI_CE_ENGINE_AES		BIT(0)
 #define SUNXI_CE_ENGINE_RAES		BIT(1)
+#define SUNXI_CE_ENGINE_HASH		BIT(2)
 #define SUNXI_CE_ENGINE_ASYM		BIT(3)
-#define SUNXI_CE_ENGINE_MASK		(SUNXI_CE_ENGINE_AES | \
-					 SUNXI_CE_ENGINE_RAES | \
-					 SUNXI_CE_ENGINE_ASYM)
+#define SUNXI_CE_ENGINE_MASK		GENMASK(3, 0)
 
 struct sunxi_ce_sginfo {
 	u32 addr;

-- 
2.53.0



More information about the U-Boot mailing list