* [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto
@ 2026-07-28 17:41 James Hilliard
2026-07-28 17:41 ` [PATCH v6 01/13] cmd: aes: fix DM operation handling James Hilliard
` (12 more replies)
0 siblings, 13 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
This series enables Allwinner Crypto Engine backed FIT decryption and FIT
signature/hash validation for secure-boot flows on H6/H616-class boards,
covering both SPL and U-Boot proper.
The purpose is to use CE-backed AES, hash and ECDSA operations on H6/H616
instead of relying only on software crypto paths. Word-aligned ECB and CBC
decryption requests of at least 256 KiB use both the AES and RAES engines
in SPL and U-Boot proper. The AES child uses the same fixed-memory
scheduler for one or two engines. Each engine has two bounded descriptor
banks, so software can refill a retired bank while its peer remains in
flight. One fixed transfer object owns all scheduler state, and the parent
polls and retires each completion snapshot as one operation.
On the H616 test board with the CE at 300 MHz, target-timed and fully
compared 64 MiB AES-256 decrypts reached 561.4 to 566.4 MiB/s for ECB
and CBC with aligned and word-offset buffers. SHA-256 over a 64 MiB
word-offset buffer reached 190.4 MiB/s. P-256 verification took 0.015 to
0.016 seconds.
At a high level this adds:
- SPL driver-model crypto plumbing for AES and hash providers.
- FIT decrypt-to-buffer support and SPL FIT cipher support, so SPL can
decrypt an encrypted U-Boot proper FIT without allocating another full
payload buffer.
- A shared sun8i-ce parent driver with one exclusive task-session
lifecycle, a cacheline-safe DMA mapping API, and one completion
poll/retire path, plus AES, hash and ECDSA children for H6/H616.
- Parallel AES/RAES processing for word-aligned ECB and CBC decryption
requests of at least 256 KiB, using two fixed banks of ten task
descriptors per engine and requesting completion only from each chain
tail.
- An H6/H616 ECDSA child for CE-backed FIT signature validation, plus
common ECDSA curve-size and key-encoding fixes for secp224r1,
prime256v1, secp384r1 and secp521r1.
- Driver-model AES and FIT hash-provider dispatch with software fallback
for unsupported operations and hard-error propagation.
The AES child supports software-provided AES-128/192/256 keys in ECB and
CBC modes, including exact in-place operation. Word-aligned payload
middles use direct DMA regardless of cacheline alignment, while fixed
per-bank edge buffers isolate partial cachelines. Byte-unaligned payloads
use a fixed 64 KiB repack buffer. The hash child supports the CE one-shot
MD5/SHA1/SHA256/SHA384/SHA512 methods used by FIT verification and uses a
fixed 128 KiB repack buffer when needed. No allocation scales with payload
size. The ECDSA child exposes the H6/H616 CE ECC verifier through
UCLASS_ECDSA.
Tested flows include:
- SPL loading an encrypted and signed U-Boot proper FIT with CE-backed
AES decryption, hash verification and ECDSA verification.
- U-Boot proper loading an encrypted and signed FIT with CE-backed AES
decryption, hash verification and ECDSA signature verification.
- U-Boot proper AES command paths using the DM AES provider.
- Target-timed 64 MiB AES-256-ECB encrypt and decrypt at aligned and
word-offset addresses with full-buffer and edge-sentinel comparisons.
- Target-timed 64 MiB AES-256-CBC decrypt out of place and in place at
aligned and word-offset addresses, with repeated full-buffer
comparisons.
- Target-timed 8 MiB AES-128/192/256 ECB and CBC operations with
full-buffer comparisons.
- AES-256 boundary and tail cases at aligned, word-offset and byte-offset
addresses, with cacheline sentinels, exact in-place operation and
partial-overlap rejection.
- Target-timed 64 MiB SHA-256 hashing at aligned, word-offset and
byte-offset addresses with repeated digest comparisons.
- Sandbox provider fallback, hard-error propagation, AES decrypt input
validation and all supported ECDSA curve sizes.
- U-Boot proper CE-backed FIT signature checks with secp224r1,
prime256v1, secp384r1 and secp521r1.
This series depends on the separately submitted
"crypto: hash: use DM providers from hash command" patch.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v5 -> v6:
- Drop stale AES-128 wording and return -EINVAL when the
software AES provider has no selected key
(suggested by Simon Glass)
- Leave signed FIT metadata unchanged and require/verify a payload hash
before unverified in-place decryption, so repeated loads fail before
AES processes plaintext (suggested by Simon Glass)
- Return -ENOSYS from the allocating host decrypt stub
(suggested by Simon Glass)
- Add Reviewed-by tags from Simon Glass for hash-provider
selection, software AES dispatch, and CE ECDSA/hash support
- Send hash-command DM provider selection as a standalone prerequisite
(suggested by Tom Rini)
- Drop unrelated hash_digest_wd() indentation changes from that
prerequisite
(suggested by Tom Rini)
- Add Reviewed-by from Tom Rini to the SPL DM AES patch
- Document that no in-tree configuration relies on the old SPL hash
object key
(suggested by Tom Rini)
- Preserve the existing fit_image_decrypt_data() declaration wrapping
- Link to v5: https://patch.msgid.link/20260719-submit-ce-series-v2-v5-0-3c41f66d4522@gmail.com
Changes v4 -> v5:
- Rebase on U-Boot main
- Replace independently claimed channel and engine sessions with one
exclusive parent session that tracks per-channel in-flight state,
deadlines, completion and abort cleanup
- Add one cacheline-safe DMA mapping API for AES, hash and ECDSA, with
descriptor-address validation and complete-cacheline output ownership
- Replace the separate one-shot, bounce and dual-CBC AES paths with one
fixed-memory one/two-lane scheduler using two ten-task banks per lane
- Use AES and RAES in parallel for word-aligned ECB and CBC decryption
requests of at least 256 KiB, with fair refill from a shared cursor
- Direct-map word-aligned cacheline-offset AES buffers using private
per-bank edge cachelines, and use a fixed 64 KiB repack only for
byte-unaligned buffers
- Replace the payload-sized word-unaligned hash bounce with one
continuation stream using 64 MiB direct chunks or a fixed 128 KiB
repack buffer
- Use the normal DM clock and reset lifecycle in SPL and U-Boot proper,
select the required SPL dependencies and retain the pre-RAM DT nodes
- Harden ECDSA input validation and use a cacheline-rounded
private result mapping
- Validate secure boot, every AES key size, AES-256 alignment and overlap
cases, bounded hash streaming and every supported ECDSA curve on H616
- Link to v4: https://patch.msgid.link/20260713-submit-ce-series-v2-v4-0-ff7edc705b8a@gmail.com
Changes v3 -> v4:
- Enable CE ECDSA in SPL and U-Boot proper on both H6 and H616.
- Validate FIT cipher metadata before accessing key parameters.
- Rebase on U-Boot master.
- Try all registered AES and hash providers, preserving hard provider
errors and using software fallback only when no provider supports the
operation.
- Treat -EINVAL as a hard provider error and reserve fallback for
explicitly unsupported operations.
- Require SPL_OF_CONTROL for SPL FIT decryption and fix disabled AES
stubs.
- Correct AES-192/256 handling in the software DM provider and add
provider, decrypt-input and in-place-decrypt sandbox tests.
- Map SPL FIT destinations after post-processing determines the final
size and size decompression mappings for the maximum output.
- Exercise all supported ECDSA curve sizes in the host FIT signing test.
- Fold per-channel task sessions and engine ownership into the CE parent.
- Trim redundant CE scheduler state and checks, and derive each NIST
curve's a = p - 3 parameter instead of storing duplicate constants.
- Run CBC decrypts of at least 256 KiB across AES and RAES in SPL and
U-Boot proper with double-buffered ten-descriptor chains and tail-only
completion.
- Support exact in-place and aligned-offset dual-engine CBC decrypt.
- Submit ECDSA and hash work on their dedicated completion channels.
- Add target-timed 64 MiB AES-256-CBC and SHA-256 hardware results.
- Link to v3: https://patch.msgid.link/20260709-submit-ce-series-v2-v3-0-5017da8c9bef@gmail.com
Changes v2 -> v3:
- Rebase on U-Boot master.
- Add review tags from Svyatoslav Ryhel and Simon Glass.
- Simplify the AES decrypt helper guard and validation flow.
- Document the AES provider in-place CBC decrypt contract.
- Tighten SPL encrypted-FIT buffer handling and error reporting.
- Clean up the SPL DM hash fallback and Kconfig help text.
- Link to v2: https://patch.msgid.link/20260702-submit-ce-series-v2-v2-0-ffda5bed58af@gmail.com
To: Ion Agorria <ion@agorria.com>
To: Svyatoslav Ryhel <clamor95@gmail.com>
To: u-boot@lists.u-boot-project.org
Cc: Tom Rini <trini@konsulko.com>
Cc: James Hilliard <james.hilliard1@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
Cc: Daniel Golle <daniel@makrotopia.org>
Cc: Aristo Chen <aristo.chen@canonical.com>
Cc: Thierry Reding <treding@nvidia.com>
Cc: Peng Fan <peng.fan@nxp.com>
Cc: Neil Armstrong <neil.armstrong@linaro.org>
Cc: Vincent Jardin <vjardin@free.fr>
Cc: Johan Jonker <jbx6244@gmail.com>
Cc: Francesco Valla <francesco@valla.it>
Cc: Andre Przywara <andre.przywara@arm.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Richard Genoud <richard.genoud@bootlin.com>
Cc: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Cc: Quentin Schulz <u-boot@0leil.net>
Cc: Marek Vasut <marek.vasut+renesas@mailbox.org>
Cc: Randolph Sapp <rs@ti.com>
Cc: Alexey Charkov <alchark@gmail.com>
Cc: Michael Trimarchi <michael@amarulasolutions.com>
Cc: Quentin Schulz <quentin.schulz@cherry.de>
---
James Hilliard (13):
cmd: aes: fix DM operation handling
crypto: aes: allow DM AES in SPL
crypto: hash: allow DM hash in SPL
boot: image: try all DM hash providers
crypto: aes: fix software key-size handling
crypto: aes: add software-key provider dispatch
boot: image: add FIT decrypt-to-buffer helper
spl: fit: support encrypted payloads
clk: sunxi: add H6/H616 CE gates and reset
lib: ecdsa: support additional curve sizes
crypto: allwinner: add sun8i-ce AES driver
crypto: allwinner: add sun8i-ce ECDSA verifier
crypto: allwinner: add sun8i-ce hash driver
MAINTAINERS | 1 +
arch/arm/dts/sunxi-u-boot.dtsi | 15 +
boot/Kconfig | 9 +
boot/image-cipher.c | 47 +-
boot/image-fit.c | 116 ++-
cmd/aes.c | 8 +-
common/spl/spl_fit.c | 89 ++-
doc/mkimage.1 | 3 +
doc/usage/fit/signature.rst | 9 +-
drivers/clk/sunxi/clk_h6.c | 5 +
drivers/clk/sunxi/clk_h616.c | 5 +
drivers/crypto/Kconfig | 2 +
drivers/crypto/Makefile | 1 +
drivers/crypto/aes/Kconfig | 8 +
drivers/crypto/aes/aes-sw.c | 51 +-
drivers/crypto/aes/aes-uclass.c | 73 ++
drivers/crypto/allwinner/Kconfig | 3 +
drivers/crypto/allwinner/Makefile | 3 +
drivers/crypto/allwinner/sun8i-ce/Kconfig | 161 ++++
drivers/crypto/allwinner/sun8i-ce/Makefile | 6 +
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c | 809 +++++++++++++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c | 791 ++++++++++++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-ecdsa.c | 382 ++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-hash.c | 343 +++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h | 128 ++++
drivers/crypto/hash/Kconfig | 13 +
drivers/crypto/hash/Makefile | 2 +-
drivers/crypto/tegra/tegra_aes.c | 7 +
include/image.h | 39 +
include/u-boot/aes.h | 27 +-
include/u-boot/ecdsa.h | 22 +
include/u-boot/fdt-libcrypto.h | 6 +-
include/uboot_aes.h | 90 ++-
lib/Makefile | 2 +-
lib/aes/aes-decrypt.c | 91 ++-
lib/ecdsa/Kconfig | 2 +-
lib/ecdsa/ecdsa-libcrypto.c | 141 ++--
lib/ecdsa/ecdsa-verify.c | 39 +-
lib/fdt-libcrypto.c | 60 +-
test/dm/aes.c | 258 +++++++
test/lib/Makefile | 3 +
test/lib/test_aes_decrypt.c | 89 +++
test/py/tests/test_fit_ecdsa.py | 18 +-
tools/image-sig-host.c | 7 +
44 files changed, 3742 insertions(+), 242 deletions(-)
---
base-commit: e354b34a6ab4b1887fd451bea8ceb7be146070a8
change-id: 20260702-submit-ce-series-v2-4a77b170c68c
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v6 01/13] cmd: aes: fix DM operation handling
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 02/13] crypto: aes: allow DM AES in SPL James Hilliard
` (11 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
The DM AES command path takes operation-specific arguments after the
common arguments. argv[1] holds the mode, "ecb" or "cbc", while argv[2]
holds the requested operation, "enc" or "dec".
The DM path checked argv[1] for the operation, so the ecb and cbc
commands always returned CMD_RET_USAGE before running. Parse argv[2]
instead.
Fixes: b01444aa14cf ("cmd: aes: Add support for DM AES drivers")
Reviewed-by: Svyatoslav Ryhel <clamor95@gmail.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v1 -> v2:
- Limit the patch to the argv parsing fix (suggested by Simon Glass)
- State that the commands always returned usage
(suggested by Simon Glass)
- Add a Fixes tag (suggested by Simon Glass)
---
cmd/aes.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/cmd/aes.c b/cmd/aes.c
index 3fd83013ffe..edcd87cf11b 100644
--- a/cmd/aes.c
+++ b/cmd/aes.c
@@ -175,9 +175,9 @@ int cmd_aes_ecb(int argc, char *const argv[], u32 key_len)
if (ret)
return ret;
- if (!strncmp(argv[1], "enc", 3))
+ if (!strncmp(argv[2], "enc", 3))
enc = 1;
- else if (!strncmp(argv[1], "dec", 3))
+ else if (!strncmp(argv[2], "dec", 3))
enc = 0;
else
return CMD_RET_USAGE;
@@ -223,9 +223,9 @@ int cmd_aes_cbc(int argc, char *const argv[], u32 key_len)
if (ret)
return ret;
- if (!strncmp(argv[1], "enc", 3))
+ if (!strncmp(argv[2], "enc", 3))
enc = 1;
- else if (!strncmp(argv[1], "dec", 3))
+ else if (!strncmp(argv[2], "dec", 3))
enc = 0;
else
return CMD_RET_USAGE;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 02/13] crypto: aes: allow DM AES in SPL
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
2026-07-28 17:41 ` [PATCH v6 01/13] cmd: aes: fix DM operation handling James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 03/13] crypto: hash: allow DM hash " James Hilliard
` (10 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
The AES uclass Makefile is already keyed by CONFIG_$(PHASE_)DM_AES,
but there is no SPL_DM_AES symbol to build it for SPL.
Add the SPL variant so SPL code can use UCLASS_AES providers, for
example when decrypting FIT images before loading U-Boot proper. Select
SPL_CRYPTO from SPL_DM_AES as well, since drivers/Makefile only descends
into drivers/crypto/ for SPL when SPL_CRYPTO is enabled.
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Tom Rini <trini@konsulko.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v1 -> v2:
- Select SPL_CRYPTO from SPL_DM_AES (suggested by Simon Glass)
---
drivers/crypto/aes/Kconfig | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/crypto/aes/Kconfig b/drivers/crypto/aes/Kconfig
index 7e1b1b2875d..254cb598568 100644
--- a/drivers/crypto/aes/Kconfig
+++ b/drivers/crypto/aes/Kconfig
@@ -4,6 +4,14 @@ config DM_AES
help
If you want to use driver model for AES crypto operations, say Y.
+config SPL_DM_AES
+ bool "Enable Driver Model for AES crypto operations in SPL"
+ depends on SPL_DM
+ select SPL_CRYPTO
+ help
+ If you want to use driver model for AES crypto operations in SPL,
+ say Y.
+
config AES_SOFTWARE
bool "Enable driver for AES in software"
depends on DM_AES && AES
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 03/13] crypto: hash: allow DM hash in SPL
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
2026-07-28 17:41 ` [PATCH v6 01/13] cmd: aes: fix DM operation handling James Hilliard
2026-07-28 17:41 ` [PATCH v6 02/13] crypto: aes: allow DM AES in SPL James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:56 ` Tom Rini
2026-07-28 17:41 ` [PATCH v6 04/13] boot: image: try all DM hash providers James Hilliard
` (9 subsequent siblings)
12 siblings, 1 reply; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
The hash uclass is currently keyed only by CONFIG_DM_HASH, so SPL cannot
enable UCLASS_HASH independently. Any SPL code using hash_digest*() has to
rely on U-Boot proper also enabling DM_HASH, and the FIT hash path selects
the driver-model implementation with a non-phase-aware preprocessor check.
Add SPL_DM_HASH, build the hash uclass from CONFIG_$(PHASE_)DM_HASH and use
CONFIG_IS_ENABLED(DM_HASH) when selecting the FIT hash implementation. This
lets SPL FIT verification use a UCLASS_HASH provider without requiring the
U-Boot proper hash uclass.
No in-tree configuration enables both DM_HASH and SPL_CRYPTO, so the
Makefile object key switch does not remove hash-uclass.o from an existing
SPL build.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v5 -> v6:
- Document that no in-tree configuration relies on the old SPL object key
(suggested by Tom Rini)
Changes v2 -> v3:
- Remove the bare software-hash fallback scope
(suggested by Simon Glass)
- Document that SPL_DM_HASH needs a hardware provider unless a
phase-aware software hash provider is added
(suggested by Simon Glass)
---
boot/image-fit.c | 50 +++++++++++++++++++++++---------------------
drivers/crypto/hash/Kconfig | 13 ++++++++++++
drivers/crypto/hash/Makefile | 2 +-
3 files changed, 40 insertions(+), 25 deletions(-)
diff --git a/boot/image-fit.c b/boot/image-fit.c
index ef90c5abd18..d7d7c70fe76 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -32,10 +32,8 @@ extern void *aligned_alloc(size_t alignment, size_t size);
#include <malloc.h>
#include <memalign.h>
#include <asm/global_data.h>
-#ifdef CONFIG_DM_HASH
#include <dm.h>
#include <u-boot/hash.h>
-#endif
#define aligned_alloc(a, s) memalign((a), (s))
DECLARE_GLOBAL_DATA_PTR;
@@ -1318,43 +1316,47 @@ int fit_set_timestamp(void *fit, int noffset, time_t timestamp)
int calculate_hash(const void *data, int data_len, const char *name,
uint8_t *value, int *value_len)
{
-#if !defined(USE_HOSTCC) && defined(CONFIG_DM_HASH)
+ struct hash_algo *algo;
+ int ret;
+
+#ifndef USE_HOSTCC
int rc;
enum HASH_ALGO hash_algo;
struct udevice *dev;
- rc = uclass_get_device(UCLASS_HASH, 0, &dev);
- if (rc) {
- debug("failed to get hash device, rc=%d\n", rc);
- return -1;
- }
+ if (CONFIG_IS_ENABLED(DM_HASH)) {
+ rc = uclass_get_device(UCLASS_HASH, 0, &dev);
+ if (rc) {
+ debug("failed to get hash device, rc=%d\n", rc);
+ return -1;
+ }
- hash_algo = hash_algo_lookup_by_name(name);
- if (hash_algo == HASH_ALGO_INVALID) {
- debug("Unsupported hash algorithm\n");
- return -1;
- };
+ hash_algo = hash_algo_lookup_by_name(name);
+ if (hash_algo == HASH_ALGO_INVALID) {
+ debug("Unsupported hash algorithm\n");
+ return -1;
+ }
- rc = hash_digest_wd(dev, hash_algo, data, data_len, value, CHUNKSZ);
- if (rc) {
- debug("failed to get hash value, rc=%d\n", rc);
- return -1;
- }
+ rc = hash_digest_wd(dev, hash_algo, data, data_len, value,
+ CHUNKSZ);
+ if (rc) {
+ debug("failed to get hash value, rc=%d\n", rc);
+ return -1;
+ }
- *value_len = hash_algo_digest_size(hash_algo);
-#else
- struct hash_algo *algo;
- int ret;
+ *value_len = hash_algo_digest_size(hash_algo);
+ return 0;
+ }
+#endif
ret = hash_lookup_algo(name, &algo);
if (ret < 0) {
- debug("Unsupported hash alogrithm\n");
+ debug("Unsupported hash algorithm\n");
return -1;
}
algo->hash_func_ws(data, data_len, value, algo->chunk_size);
*value_len = algo->digest_size;
-#endif
return 0;
}
diff --git a/drivers/crypto/hash/Kconfig b/drivers/crypto/hash/Kconfig
index 72b955ac791..272af7bce18 100644
--- a/drivers/crypto/hash/Kconfig
+++ b/drivers/crypto/hash/Kconfig
@@ -4,6 +4,19 @@ config DM_HASH
help
If you want to use driver model for Hash, say Y.
+config SPL_DM_HASH
+ bool "Enable Driver Model for Hash in SPL"
+ depends on SPL_DM
+ select SPL_CRYPTO
+ help
+ Enable the hash uclass in SPL so SPL code can bind and use
+ UCLASS_HASH providers through the driver model. This is useful for
+ FIT verification paths that want to calculate image hashes through a
+ hardware hash accelerator before U-Boot proper is loaded.
+ HASH_SOFTWARE depends on DM_HASH, so SPL_DM_HASH alone does not
+ provide a software hash device. Enable a hardware hash provider for
+ SPL when selecting this option.
+
config HASH_SOFTWARE
bool "Enable driver for Hash in software"
depends on DM_HASH
diff --git a/drivers/crypto/hash/Makefile b/drivers/crypto/hash/Makefile
index 33d88161ed4..9f0d30f9be3 100644
--- a/drivers/crypto/hash/Makefile
+++ b/drivers/crypto/hash/Makefile
@@ -2,5 +2,5 @@
#
# Copyright (c) 2021 ASPEED Technology Inc.
-obj-$(CONFIG_DM_HASH) += hash-uclass.o
+obj-$(CONFIG_$(PHASE_)DM_HASH) += hash-uclass.o
obj-$(CONFIG_HASH_SOFTWARE) += hash_sw.o
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 04/13] boot: image: try all DM hash providers
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (2 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 03/13] crypto: hash: allow DM hash " James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 05/13] crypto: aes: fix software key-size handling James Hilliard
` (8 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
FIT hash calculation currently selects UCLASS_HASH device zero directly,
so a provider which does not implement the requested algorithm prevents a
later capable provider from being used. It also turns every provider error
into the same generic FIT hash failure.
Use the common hash provider-selection helper. Fall back to the configured
software implementation only when no DM provider supports the algorithm,
and preserve probe and digest failures from providers which accept it.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v3 -> v4:
- New patch
---
boot/image-fit.c | 26 +++++++++-----------------
1 file changed, 9 insertions(+), 17 deletions(-)
diff --git a/boot/image-fit.c b/boot/image-fit.c
index d7d7c70fe76..9b39696de2d 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -1322,30 +1322,22 @@ int calculate_hash(const void *data, int data_len, const char *name,
#ifndef USE_HOSTCC
int rc;
enum HASH_ALGO hash_algo;
- struct udevice *dev;
if (CONFIG_IS_ENABLED(DM_HASH)) {
- rc = uclass_get_device(UCLASS_HASH, 0, &dev);
- if (rc) {
- debug("failed to get hash device, rc=%d\n", rc);
- return -1;
- }
-
hash_algo = hash_algo_lookup_by_name(name);
- if (hash_algo == HASH_ALGO_INVALID) {
- debug("Unsupported hash algorithm\n");
- return -1;
+ if (hash_algo != HASH_ALGO_INVALID)
+ rc = hash_digest_wd_lookup(hash_algo, data, data_len,
+ value, CHUNKSZ);
+ else
+ rc = -EOPNOTSUPP;
+ if (!rc) {
+ *value_len = hash_algo_digest_size(hash_algo);
+ return 0;
}
-
- rc = hash_digest_wd(dev, hash_algo, data, data_len, value,
- CHUNKSZ);
- if (rc) {
+ if (rc != -ENODEV && rc != -EOPNOTSUPP) {
debug("failed to get hash value, rc=%d\n", rc);
return -1;
}
-
- *value_len = hash_algo_digest_size(hash_algo);
- return 0;
}
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 05/13] crypto: aes: fix software key-size handling
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (3 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 04/13] boot: image: try all DM hash providers James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 06/13] crypto: aes: add software-key provider dispatch James Hilliard
` (7 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
The AES uclass API expresses key sizes in bits, while the common software
AES primitives take key lengths in bytes. The software provider passes the
uclass value through unchanged, so AES-192 and AES-256 select the AES-128
round count and key schedule shape. Key expansion also copies the bit count
as a byte count for every key size.
Validate the uclass key size, convert it to bytes once and retain that byte
length for the software operations. Correct the primitive API documentation
and add NIST ECB and CBC vectors for AES-128, AES-192 and AES-256.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v5 -> v6:
- Drop stale AES-128 wording from the generic primitive docs
(suggested by Simon Glass)
- Return -EINVAL when no software key has been selected
(suggested by Simon Glass)
Changes v3 -> v4:
- New patch
---
drivers/crypto/aes/aes-sw.c | 45 ++++++++++++------
include/uboot_aes.h | 24 +++++-----
test/dm/aes.c | 109 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 153 insertions(+), 25 deletions(-)
diff --git a/drivers/crypto/aes/aes-sw.c b/drivers/crypto/aes/aes-sw.c
index a65200fb79b..4ec9484c608 100644
--- a/drivers/crypto/aes/aes-sw.c
+++ b/drivers/crypto/aes/aes-sw.c
@@ -12,15 +12,25 @@ struct sw_aes_priv {
u8 key_slots[SW_KEY_SLOTS][AES256_KEY_LENGTH];
u8 key_schedule[AES256_EXPAND_KEY_LENGTH];
u8 selected_slot;
- u32 selected_key_size;
+ u8 selected_key_len;
bool key_expanded;
};
+static int sw_aes_key_len(u32 key_size)
+{
+ if (key_size == AES128_KEY_LENGTH * 8 ||
+ key_size == AES192_KEY_LENGTH * 8 ||
+ key_size == AES256_KEY_LENGTH * 8)
+ return key_size / 8;
+
+ return -EINVAL;
+}
+
static int prepare_aes(struct sw_aes_priv *priv)
{
- if (!priv->selected_key_size) {
+ if (!priv->selected_key_len) {
log_debug("%s: AES key size not set, setup a slot first\n", __func__);
- return 1;
+ return -EINVAL;
}
if (priv->key_expanded)
@@ -28,7 +38,8 @@ static int prepare_aes(struct sw_aes_priv *priv)
priv->key_expanded = 1;
- aes_expand_key(priv->key_slots[priv->selected_slot], priv->selected_key_size,
+ aes_expand_key(priv->key_slots[priv->selected_slot],
+ priv->selected_key_len,
priv->key_schedule);
return 0;
@@ -42,12 +53,16 @@ static int sw_aes_ops_available_key_slots(struct udevice *dev)
static int sw_aes_ops_select_key_slot(struct udevice *dev, u32 key_size, u8 slot)
{
struct sw_aes_priv *priv = dev_get_priv(dev);
+ int key_len;
if (slot >= SW_KEY_SLOTS)
- return 1;
+ return -EINVAL;
+ key_len = sw_aes_key_len(key_size);
+ if (key_len < 0)
+ return key_len;
priv->selected_slot = slot;
- priv->selected_key_size = key_size;
+ priv->selected_key_len = key_len;
priv->key_expanded = 0;
return 0;
@@ -57,14 +72,18 @@ static int sw_aes_ops_set_key_for_key_slot(struct udevice *dev, u32 key_size,
u8 *key, u8 slot)
{
struct sw_aes_priv *priv = dev_get_priv(dev);
+ int key_len;
if (slot >= SW_KEY_SLOTS)
- return 1;
+ return -EINVAL;
+ key_len = sw_aes_key_len(key_size);
+ if (key_len < 0)
+ return key_len;
- memcpy(priv->key_slots[slot], key, key_size / 8);
+ memcpy(priv->key_slots[slot], key, key_len);
if (priv->selected_slot == slot)
- priv->selected_key_size = key_size;
+ priv->selected_key_len = key_len;
priv->key_expanded = 0;
@@ -82,7 +101,7 @@ static int sw_aes_ops_aes_ecb_encrypt(struct udevice *dev, u8 *src, u8 *dst,
return ret;
while (num_aes_blocks > 0) {
- aes_encrypt(priv->selected_key_size, src, priv->key_schedule, dst);
+ aes_encrypt(priv->selected_key_len, src, priv->key_schedule, dst);
num_aes_blocks -= 1;
src += AES_BLOCK_LENGTH;
dst += AES_BLOCK_LENGTH;
@@ -102,7 +121,7 @@ static int sw_aes_ops_aes_ecb_decrypt(struct udevice *dev, u8 *src, u8 *dst,
return ret;
while (num_aes_blocks > 0) {
- aes_decrypt(priv->selected_key_size, src, priv->key_schedule, dst);
+ aes_decrypt(priv->selected_key_len, src, priv->key_schedule, dst);
num_aes_blocks -= 1;
src += AES_BLOCK_LENGTH;
dst += AES_BLOCK_LENGTH;
@@ -121,7 +140,7 @@ static int sw_aes_ops_aes_cbc_encrypt(struct udevice *dev, u8 *iv, u8 *src,
if (ret)
return ret;
- aes_cbc_encrypt_blocks(priv->selected_key_size, priv->key_schedule, iv,
+ aes_cbc_encrypt_blocks(priv->selected_key_len, priv->key_schedule, iv,
src, dst, num_aes_blocks);
return 0;
@@ -137,7 +156,7 @@ static int sw_aes_ops_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src,
if (ret)
return ret;
- aes_cbc_decrypt_blocks(priv->selected_key_size, priv->key_schedule,
+ aes_cbc_decrypt_blocks(priv->selected_key_len, priv->key_schedule,
iv, src, dst, num_aes_blocks);
return 0;
diff --git a/include/uboot_aes.h b/include/uboot_aes.h
index 592b7dbee43..e463d5b0c42 100644
--- a/include/uboot_aes.h
+++ b/include/uboot_aes.h
@@ -47,30 +47,30 @@ enum {
* operations.
*
* @key Key
- * @key_size Size of the key (in bits)
+ * @key_len Size of the key in bytes
* @expkey Buffer to place expanded key, AES_EXPAND_KEY_LENGTH
*/
-void aes_expand_key(u8 *key, u32 key_size, u8 *expkey);
+void aes_expand_key(u8 *key, u32 key_len, u8 *expkey);
/**
- * aes_encrypt() - Encrypt single block of data with AES 128
+ * aes_encrypt() - Encrypt a single block of data with AES
*
- * @key_size Size of the aes key (in bits)
+ * @key_len Size of the AES key in bytes
* @in Input data
* @expkey Expanded key to use for encryption (from aes_expand_key())
* @out Output data
*/
-void aes_encrypt(u32 key_size, u8 *in, u8 *expkey, u8 *out);
+void aes_encrypt(u32 key_len, u8 *in, u8 *expkey, u8 *out);
/**
- * aes_decrypt() - Decrypt single block of data with AES 128
+ * aes_decrypt() - Decrypt a single block of data with AES
*
- * @key_size Size of the aes key (in bits)
+ * @key_len Size of the AES key in bytes
* @in Input data
* @expkey Expanded key to use for decryption (from aes_expand_key())
* @out Output data
*/
-void aes_decrypt(u32 key_size, u8 *in, u8 *expkey, u8 *out);
+void aes_decrypt(u32 key_len, u8 *in, u8 *expkey, u8 *out);
/**
* Apply chain data to the destination using EOR
@@ -86,27 +86,27 @@ void aes_apply_cbc_chain_data(u8 *cbc_chain_data, u8 *src, u8 *dst);
/**
* aes_cbc_encrypt_blocks() - Encrypt multiple blocks of data with AES CBC.
*
- * @key_size Size of the aes key (in bits)
+ * @key_len Size of the AES key in bytes
* @key_exp Expanded key to use
* @iv Initialization vector
* @src Source data to encrypt
* @dst Destination buffer
* @num_aes_blocks Number of AES blocks to encrypt
*/
-void aes_cbc_encrypt_blocks(u32 key_size, u8 *key_exp, u8 *iv, u8 *src, u8 *dst,
+void aes_cbc_encrypt_blocks(u32 key_len, u8 *key_exp, u8 *iv, u8 *src, u8 *dst,
u32 num_aes_blocks);
/**
* Decrypt multiple blocks of data with AES CBC.
*
- * @key_size Size of the aes key (in bits)
+ * @key_len Size of the AES key in bytes
* @key_exp Expanded key to use
* @iv Initialization vector
* @src Source data to decrypt
* @dst Destination buffer
* @num_aes_blocks Number of AES blocks to decrypt
*/
-void aes_cbc_decrypt_blocks(u32 key_size, u8 *key_exp, u8 *iv, u8 *src, u8 *dst,
+void aes_cbc_decrypt_blocks(u32 key_len, u8 *key_exp, u8 *iv, u8 *src, u8 *dst,
u32 num_aes_blocks);
/* An AES block filled with zeros */
diff --git a/test/dm/aes.c b/test/dm/aes.c
index 702e4db2b35..c58a5e257f5 100644
--- a/test/dm/aes.c
+++ b/test/dm/aes.c
@@ -29,6 +29,8 @@ static int dm_test_aes(struct unit_test_state *uts)
u8 test_output[AES_BLOCK_LENGTH];
ut_assertok(uclass_first_device_err(UCLASS_AES, &dev));
+ ut_asserteq(-EINVAL,
+ dm_aes_ecb_encrypt(dev, test_input, test_output, 1));
/* software AES exposes 2 key slots */
ut_asserteq(2, dm_aes_get_available_key_slots(dev));
@@ -55,3 +57,110 @@ static int dm_test_aes(struct unit_test_state *uts)
}
DM_TEST(dm_test_aes, UTF_SCAN_FDT);
+
+struct aes_test_vector {
+ u32 key_size;
+ u8 key[AES256_KEY_LENGTH];
+ u8 ecb[AES_BLOCK_LENGTH];
+ u8 cbc[AES_BLOCK_LENGTH];
+};
+
+static const u8 aes_test_plaintext[AES_BLOCK_LENGTH] = {
+ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+ 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a,
+};
+
+static const u8 aes_test_iv[AES_BLOCK_LENGTH] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+};
+
+static const struct aes_test_vector aes_test_vectors[] = {
+ {
+ .key_size = 128,
+ .key = {
+ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+ 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,
+ },
+ .ecb = {
+ 0x3a, 0xd7, 0x7b, 0xb4, 0x0d, 0x7a, 0x36, 0x60,
+ 0xa8, 0x9e, 0xca, 0xf3, 0x24, 0x66, 0xef, 0x97,
+ },
+ .cbc = {
+ 0x76, 0x49, 0xab, 0xac, 0x81, 0x19, 0xb2, 0x46,
+ 0xce, 0xe9, 0x8e, 0x9b, 0x12, 0xe9, 0x19, 0x7d,
+ },
+ }, {
+ .key_size = 192,
+ .key = {
+ 0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52,
+ 0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5,
+ 0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b,
+ },
+ .ecb = {
+ 0xbd, 0x33, 0x4f, 0x1d, 0x6e, 0x45, 0xf2, 0x5f,
+ 0xf7, 0x12, 0xa2, 0x14, 0x57, 0x1f, 0xa5, 0xcc,
+ },
+ .cbc = {
+ 0x4f, 0x02, 0x1d, 0xb2, 0x43, 0xbc, 0x63, 0x3d,
+ 0x71, 0x78, 0x18, 0x3a, 0x9f, 0xa0, 0x71, 0xe8,
+ },
+ }, {
+ .key_size = 256,
+ .key = {
+ 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe,
+ 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
+ 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7,
+ 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4,
+ },
+ .ecb = {
+ 0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c,
+ 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1, 0x81, 0xf8,
+ },
+ .cbc = {
+ 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba,
+ 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6,
+ },
+ },
+};
+
+static int dm_test_aes_key_sizes(struct unit_test_state *uts)
+{
+ struct udevice *dev;
+ u8 key[AES256_KEY_LENGTH];
+ u8 input[AES_BLOCK_LENGTH];
+ u8 iv[AES_BLOCK_LENGTH];
+ u8 buf[AES_BLOCK_LENGTH];
+ int i, ret;
+
+ ut_assertok(uclass_first_device_err(UCLASS_AES, &dev));
+
+ for (i = 0; i < ARRAY_SIZE(aes_test_vectors); i++) {
+ const struct aes_test_vector *vector = &aes_test_vectors[i];
+
+ memcpy(key, vector->key, vector->key_size / 8);
+ memcpy(input, aes_test_plaintext, sizeof(input));
+ memcpy(iv, aes_test_iv, sizeof(iv));
+ ut_assertok(dm_aes_select_key_slot(dev, vector->key_size, 0));
+ ret = dm_aes_set_key_for_key_slot(dev, vector->key_size, key, 0);
+ ut_assertok(ret);
+
+ ut_assertok(dm_aes_ecb_encrypt(dev, input, buf, 1));
+ ut_asserteq_mem(vector->ecb, buf, sizeof(buf));
+ ut_assertok(dm_aes_ecb_decrypt(dev, buf, buf, 1));
+ ut_asserteq_mem(aes_test_plaintext, buf, sizeof(buf));
+
+ ut_assertok(dm_aes_cbc_encrypt(dev, iv, input, buf, 1));
+ ut_asserteq_mem(vector->cbc, buf, sizeof(buf));
+ ut_assertok(dm_aes_cbc_decrypt(dev, iv, buf, buf, 1));
+ ut_asserteq_mem(aes_test_plaintext, buf, sizeof(buf));
+ }
+
+ ut_asserteq(-EINVAL, dm_aes_select_key_slot(dev, 64, 0));
+ ret = dm_aes_set_key_for_key_slot(dev, 64, key, 0);
+ ut_asserteq(-EINVAL, ret);
+
+ return 0;
+}
+
+DM_TEST(dm_test_aes_key_sizes, UTF_SCAN_FDT);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 06/13] crypto: aes: add software-key provider dispatch
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (4 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 05/13] crypto: aes: fix software key-size handling James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 07/13] boot: image: add FIT decrypt-to-buffer helper James Hilliard
` (6 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
Generic callers which supply an AES key need a provider-owned slot policy,
since hardware may reserve or preload particular key slots. Selecting only
UCLASS_AES device zero also prevents a later provider from handling an
operation unsupported by the first device.
Add a callback through which each provider exposes a slot for
software-provided keys. Add a CBC decrypt helper which probes providers in
order, continuing only when a provider explicitly reports an unsupported
operation and preserving hard probe, setup and decrypt failures.
Use slot zero for the software provider and avoid Tegra's preloaded SBK
slot. Add sandbox coverage for provider fallback and hard-error
propagation.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v3 -> v4:
- New patch
---
drivers/crypto/aes/aes-sw.c | 6 ++
drivers/crypto/aes/aes-uclass.c | 73 +++++++++++++++++++
drivers/crypto/tegra/tegra_aes.c | 7 ++
include/uboot_aes.h | 66 +++++++++++++++--
test/dm/aes.c | 149 +++++++++++++++++++++++++++++++++++++++
5 files changed, 297 insertions(+), 4 deletions(-)
diff --git a/drivers/crypto/aes/aes-sw.c b/drivers/crypto/aes/aes-sw.c
index 4ec9484c608..cd5c10cd2d5 100644
--- a/drivers/crypto/aes/aes-sw.c
+++ b/drivers/crypto/aes/aes-sw.c
@@ -50,6 +50,11 @@ static int sw_aes_ops_available_key_slots(struct udevice *dev)
return SW_KEY_SLOTS;
}
+static int sw_aes_ops_get_software_key_slot(struct udevice *dev)
+{
+ return 0;
+}
+
static int sw_aes_ops_select_key_slot(struct udevice *dev, u32 key_size, u8 slot)
{
struct sw_aes_priv *priv = dev_get_priv(dev);
@@ -164,6 +169,7 @@ static int sw_aes_ops_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src,
static const struct aes_ops aes_ops_sw = {
.available_key_slots = sw_aes_ops_available_key_slots,
+ .get_software_key_slot = sw_aes_ops_get_software_key_slot,
.select_key_slot = sw_aes_ops_select_key_slot,
.set_key_for_key_slot = sw_aes_ops_set_key_for_key_slot,
.aes_ecb_encrypt = sw_aes_ops_aes_ecb_encrypt,
diff --git a/drivers/crypto/aes/aes-uclass.c b/drivers/crypto/aes/aes-uclass.c
index 5bdd3d736c4..ec1ea075367 100644
--- a/drivers/crypto/aes/aes-uclass.c
+++ b/drivers/crypto/aes/aes-uclass.c
@@ -23,6 +23,21 @@ int dm_aes_get_available_key_slots(struct udevice *dev)
return ops->available_key_slots(dev);
}
+int dm_aes_get_software_key_slot(struct udevice *dev)
+{
+ const struct aes_ops *ops;
+
+ if (!dev)
+ return -ENODEV;
+
+ ops = aes_get_ops(dev);
+
+ if (!ops->get_software_key_slot)
+ return -ENOSYS;
+
+ return ops->get_software_key_slot(dev);
+}
+
int dm_aes_select_key_slot(struct udevice *dev, u32 key_size, u8 slot)
{
const struct aes_ops *ops;
@@ -113,6 +128,64 @@ int dm_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src, u8 *dst, u32 num_ae
return ops->aes_cbc_decrypt(dev, iv, src, dst, num_aes_blocks);
}
+static bool aes_op_unsupported(int ret)
+{
+ return ret == -ENOSYS || ret == -EOPNOTSUPP;
+}
+
+int dm_aes_cbc_decrypt_with_key(u32 key_size, u8 *key, u8 *iv, u8 *src,
+ u8 *dst, u32 num_aes_blocks)
+{
+ struct udevice *dev;
+ int first_probe_err = 0;
+ bool found = false;
+ int ret;
+
+ for (ret = uclass_first_device_check(UCLASS_AES, &dev); dev;
+ ret = uclass_next_device_check(&dev)) {
+ u8 slot;
+
+ found = true;
+ if (ret) {
+ if (!first_probe_err)
+ first_probe_err = ret;
+ continue;
+ }
+
+ ret = dm_aes_get_software_key_slot(dev);
+ if (aes_op_unsupported(ret))
+ continue;
+ if (ret < 0)
+ return ret;
+ if (ret > U8_MAX)
+ return -ERANGE;
+ slot = ret;
+
+ ret = dm_aes_set_key_for_key_slot(dev, key_size, key, slot);
+ if (aes_op_unsupported(ret))
+ continue;
+ if (ret)
+ return ret;
+
+ ret = dm_aes_select_key_slot(dev, key_size, slot);
+ if (aes_op_unsupported(ret))
+ continue;
+ if (ret)
+ return ret;
+
+ ret = dm_aes_cbc_decrypt(dev, iv, src, dst, num_aes_blocks);
+ if (!ret)
+ return 0;
+ if (!aes_op_unsupported(ret))
+ return ret;
+ }
+
+ if (first_probe_err)
+ return first_probe_err;
+
+ return found ? -EOPNOTSUPP : -ENODEV;
+}
+
static void left_shift_vector(u8 *in, u8 *out, int size)
{
int carry = 0;
diff --git a/drivers/crypto/tegra/tegra_aes.c b/drivers/crypto/tegra/tegra_aes.c
index 55a4cec525b..348115ac5a7 100644
--- a/drivers/crypto/tegra/tegra_aes.c
+++ b/drivers/crypto/tegra/tegra_aes.c
@@ -321,6 +321,12 @@ static int tegra_aes_ops_available_key_slots(struct udevice *dev)
return 4; /* 4 slots in Tegra20 and Tegra30 */
}
+static int tegra_aes_ops_get_software_key_slot(struct udevice *dev)
+{
+ /* Avoid SBK slot 0, which may hold a preloaded key. */
+ return TEGRA_AES_SLOT_SBK + 1;
+}
+
static int tegra_aes_ops_select_key_slot(struct udevice *dev, u32 key_size, u8 slot)
{
struct tegra_aes_priv *priv = dev_get_priv(dev);
@@ -565,6 +571,7 @@ static int tegra_aes_probe(struct udevice *dev)
static const struct aes_ops tegra_aes_ops = {
.available_key_slots = tegra_aes_ops_available_key_slots,
+ .get_software_key_slot = tegra_aes_ops_get_software_key_slot,
.select_key_slot = tegra_aes_ops_select_key_slot,
.set_key_for_key_slot = tegra_aes_ops_set_key_for_key_slot,
.aes_ecb_encrypt = tegra_aes_ops_aes_ecb_encrypt,
diff --git a/include/uboot_aes.h b/include/uboot_aes.h
index e463d5b0c42..0c1756cef38 100644
--- a/include/uboot_aes.h
+++ b/include/uboot_aes.h
@@ -128,11 +128,17 @@ struct udevice;
* Note that some devices like Tegra AES engine may contain preloaded keys by bootrom,
* thus in those cases the set_key_for_key_slot() may be skipped.
*
- * Sequence for a series of AES CBC encryption, one decryption and a CMAC hash example
- * with 128bits key at slot 0 would be as follow:
+ * Generic callers which load a software-provided key should first ask the
+ * driver for a suitable software key slot. This lets hardware drivers avoid
+ * reserved or preloaded slots while keeping the slot policy local to the
+ * provider.
*
- * set_key_for_key_slot(DEV, 128, KEY, 0);
- * select_key_slot(DEV, 128, 0);
+ * Sequence for a series of AES CBC encryption, one decryption and a CMAC hash
+ * example with a 128-bit software-provided key would be as follows:
+ *
+ * slot = get_software_key_slot(DEV);
+ * set_key_for_key_slot(DEV, 128, KEY, slot);
+ * select_key_slot(DEV, 128, slot);
* aes_cbc_encrypt(DEV, IV1, SRC1, DST1, LEN1);
* aes_cbc_encrypt(DEV, IV2, SRC2, DST2, LEN2);
* aes_cbc_decrypt(DEV, IV3, SRC3, DST3, LEN3);
@@ -146,6 +152,16 @@ struct aes_ops {
*/
int (*available_key_slots)(struct udevice *dev);
+ /**
+ * get_software_key_slot() - Get a slot for software-provided keys
+ *
+ * @dev The AES udevice
+ * @return Key slot to use for a software-provided key,
+ * 0 or positive on success, negative value on
+ * failure
+ */
+ int (*get_software_key_slot)(struct udevice *dev);
+
/**
* select_key_slot() - Selects the AES key slot to use for following operations
*
@@ -210,6 +226,7 @@ struct aes_ops {
* @iv Initialization vector
* @src Source data of length 'num_aes_blocks' blocks
* @dst Destination data of length 'num_aes_blocks' blocks
+ * Must support dst == src for in-place decrypt
* @num_aes_blocks Number of AES blocks to encrypt/decrypt
* @return 0 on success, negative value on failure
*/
@@ -229,6 +246,15 @@ struct aes_ops {
*/
int dm_aes_get_available_key_slots(struct udevice *dev);
+/**
+ * dm_aes_get_software_key_slot - Get a slot for software-provided keys
+ *
+ * @dev The AES udevice
+ * Return: Key slot to use for a software-provided key,
+ * 0 or positive on success, -ve on failure
+ */
+int dm_aes_get_software_key_slot(struct udevice *dev);
+
/**
* dm_aes_select_key_slot - Selects the AES key slot to use for following operations
*
@@ -291,11 +317,31 @@ int dm_aes_cbc_encrypt(struct udevice *dev, u8 *iv, u8 *src, u8 *dst, u32 num_ae
* @iv Initialization vector
* @src Source data of length 'num_aes_blocks' blocks
* @dst Destination data of length 'num_aes_blocks' blocks
+ * Must support dst == src for in-place decrypt
* @num_aes_blocks Number of AES blocks to encrypt/decrypt
* Return: 0 on success, negative value on failure
*/
int dm_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src, u8 *dst, u32 num_aes_blocks);
+/**
+ * dm_aes_cbc_decrypt_with_key() - Decrypt using a software-provided key
+ *
+ * Probe AES providers in order and use the first one which accepts the key
+ * and CBC operation. Hard failures from an accepting provider are returned
+ * without trying another provider.
+ *
+ * @key_size: AES key size in bits
+ * @key: AES key
+ * @iv: Initialization vector
+ * @src: Ciphertext input
+ * @dst: Plaintext output, which may be the same buffer as @src
+ * @num_aes_blocks: Number of AES blocks to decrypt
+ * Return: 0 on success, -ENODEV if there are no providers, -EOPNOTSUPP if no
+ * provider supports the operation, or another error from a provider
+ */
+int dm_aes_cbc_decrypt_with_key(u32 key_size, u8 *key, u8 *iv, u8 *src,
+ u8 *dst, u32 num_aes_blocks);
+
/**
* dm_aes_cmac - Hashes the input data with AES-CMAC, putting the result into dst.
* The key slot must be selected already.
@@ -316,6 +362,11 @@ static inline int dm_aes_get_available_key_slots(struct udevice *dev)
return -ENOSYS;
}
+static inline int dm_aes_get_software_key_slot(struct udevice *dev)
+{
+ return -ENOSYS;
+}
+
static inline int dm_aes_select_key_slot(struct udevice *dev, u32 key_size, u8 slot)
{
return -ENOSYS;
@@ -351,6 +402,13 @@ static inline int dm_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src,
return -ENOSYS;
}
+static inline int dm_aes_cbc_decrypt_with_key(u32 key_size, u8 *key, u8 *iv,
+ u8 *src, u8 *dst,
+ u32 num_aes_blocks)
+{
+ return -ENOSYS;
+}
+
static inline int dm_aes_cmac(struct udevice *dev, u8 *src, u8 *dst, u32 num_aes_blocks)
{
return -ENOSYS;
diff --git a/test/dm/aes.c b/test/dm/aes.c
index c58a5e257f5..1ec80291926 100644
--- a/test/dm/aes.c
+++ b/test/dm/aes.c
@@ -6,7 +6,10 @@
*/
#include <dm.h>
+#include <dm/device-internal.h>
+#include <dm/root.h>
#include <dm/test.h>
+#include <dm/uclass-internal.h>
#include <uboot_aes.h>
#include <test/test.h>
#include <test/ut.h>
@@ -164,3 +167,149 @@ static int dm_test_aes_key_sizes(struct unit_test_state *uts)
}
DM_TEST(dm_test_aes_key_sizes, UTF_SCAN_FDT);
+
+static int unsupported_calls;
+static int success_calls;
+static int hard_error_calls;
+
+static int aes_test_get_slot(struct udevice *dev)
+{
+ return 0;
+}
+
+static int aes_test_set_key(struct udevice *dev, u32 key_size, u8 *key,
+ u8 slot)
+{
+ return 0;
+}
+
+static int aes_test_select_key(struct udevice *dev, u32 key_size, u8 slot)
+{
+ return 0;
+}
+
+static int aes_test_unsupported_slot(struct udevice *dev)
+{
+ unsupported_calls++;
+
+ return -ENOSYS;
+}
+
+static int aes_test_success_decrypt(struct udevice *dev, u8 *iv, u8 *src,
+ u8 *dst, u32 num_aes_blocks)
+{
+ success_calls++;
+ memcpy(dst, src, num_aes_blocks * AES_BLOCK_LENGTH);
+
+ return 0;
+}
+
+static int aes_test_hard_error_decrypt(struct udevice *dev, u8 *iv, u8 *src,
+ u8 *dst, u32 num_aes_blocks)
+{
+ hard_error_calls++;
+
+ return -EINVAL;
+}
+
+static const struct aes_ops aes_test_unsupported_ops = {
+ .get_software_key_slot = aes_test_unsupported_slot,
+};
+
+static const struct aes_ops aes_test_success_ops = {
+ .get_software_key_slot = aes_test_get_slot,
+ .set_key_for_key_slot = aes_test_set_key,
+ .select_key_slot = aes_test_select_key,
+ .aes_cbc_decrypt = aes_test_success_decrypt,
+};
+
+static const struct aes_ops aes_test_hard_error_ops = {
+ .get_software_key_slot = aes_test_get_slot,
+ .set_key_for_key_slot = aes_test_set_key,
+ .select_key_slot = aes_test_select_key,
+ .aes_cbc_decrypt = aes_test_hard_error_decrypt,
+};
+
+U_BOOT_DRIVER(aes_test_unsupported_drv) = {
+ .name = "aes_test_unsupported",
+ .id = UCLASS_AES,
+ .ops = &aes_test_unsupported_ops,
+};
+
+U_BOOT_DRIVER(aes_test_success_drv) = {
+ .name = "aes_test_success",
+ .id = UCLASS_AES,
+ .ops = &aes_test_success_ops,
+};
+
+U_BOOT_DRIVER(aes_test_hard_error_drv) = {
+ .name = "aes_test_hard_error",
+ .id = UCLASS_AES,
+ .ops = &aes_test_hard_error_ops,
+};
+
+static int aes_test_unbind_all(void)
+{
+ struct udevice *dev;
+ int ret;
+
+ for (;;) {
+ ret = uclass_find_first_device(UCLASS_AES, &dev);
+ if (ret || !dev)
+ return ret;
+ if (device_active(dev)) {
+ ret = device_remove(dev, DM_REMOVE_NORMAL);
+ if (ret)
+ return ret;
+ }
+ ret = device_unbind(dev);
+ if (ret)
+ return ret;
+ }
+}
+
+static int aes_test_bind(const struct driver *drv, const char *name)
+{
+ struct udevice *dev;
+
+ return device_bind(dm_root(), drv, name, 0, ofnode_null(), &dev);
+}
+
+static int dm_test_aes_provider_selection(struct unit_test_state *uts)
+{
+ u8 key[AES128_KEY_LENGTH] = { };
+ u8 iv[AES_BLOCK_LENGTH] = { };
+ u8 src[AES_BLOCK_LENGTH] = { 0x5a };
+ u8 dst[AES_BLOCK_LENGTH] = { };
+ int ret;
+
+ ut_assertok(aes_test_unbind_all());
+ ut_assertok(aes_test_bind(DM_DRIVER_GET(aes_test_unsupported_drv),
+ "aes-unsupported"));
+ ut_assertok(aes_test_bind(DM_DRIVER_GET(aes_test_success_drv),
+ "aes-success"));
+
+ unsupported_calls = 0;
+ success_calls = 0;
+ ut_assertok(dm_aes_cbc_decrypt_with_key(128, key, iv, src, dst, 1));
+ ut_asserteq(1, unsupported_calls);
+ ut_asserteq(1, success_calls);
+ ut_asserteq_mem(src, dst, sizeof(src));
+
+ ut_assertok(aes_test_unbind_all());
+ ut_assertok(aes_test_bind(DM_DRIVER_GET(aes_test_hard_error_drv),
+ "aes-hard-error"));
+ ut_assertok(aes_test_bind(DM_DRIVER_GET(aes_test_success_drv),
+ "aes-success"));
+
+ hard_error_calls = 0;
+ success_calls = 0;
+ ret = dm_aes_cbc_decrypt_with_key(128, key, iv, src, dst, 1);
+ ut_asserteq(-EINVAL, ret);
+ ut_asserteq(1, hard_error_calls);
+ ut_asserteq(0, success_calls);
+
+ return 0;
+}
+
+DM_TEST(dm_test_aes_provider_selection, UTF_SCAN_FDT);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 07/13] boot: image: add FIT decrypt-to-buffer helper
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (5 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 06/13] crypto: aes: add software-key provider dispatch James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 08/13] spl: fit: support encrypted payloads James Hilliard
` (5 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
FIT cipher support currently allocates the output buffer inside the AES
helper. SPL often needs to decrypt directly into a caller-selected
buffer, for example a load buffer or a scratch buffer used before
decompression.
Add a decrypt_to callback to the FIT cipher algorithm and wire it up for
AES. The existing allocating decrypt path becomes a wrapper around the
new helper.
Validate the FIT cipher key length, IV length and unciphered-size
property while preparing decryption, and build lib/aes/ by phase when
FIT_CIPHER is enabled so the target-side decrypt helper is available to
SPL builds. Use the DM AES provider helper when enabled, retaining the
software implementation only when no provider supports the operation.
For U-Boot proper, use decrypt_to for in-place decryption when the FIT
payload is already in writable RAM and has a payload hash. The encrypted
data is no longer needed after hash verification, and this avoids a
full-size allocation for encrypted payloads loaded into DRAM.
When normal image verification is disabled, verify the payload hash
before overwriting the ciphertext. A repeated load then fails the hash
check before AES sees the plaintext. Keep using the allocating path for
hashless payloads so their ciphertext remains reusable, and leave signed
FIT metadata unchanged.
On host builds, make both decrypt helpers return -ENOSYS instead of
reporting success without producing output.
Add sandbox coverage for out-of-place and in-place AES-256 decrypt and
malformed key, IV and size inputs.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v5 -> v6:
- Keep signed FIT metadata unchanged after in-place decryption
- Require a payload hash for in-place decryption and verify it when
normal verification is disabled, so repeated loads fail before AES
processes plaintext (suggested by Simon Glass)
- Return -ENOSYS from the allocating host decrypt stub
(suggested by Simon Glass)
- Preserve the existing fit_image_decrypt_data() declaration wrapping
Changes v3 -> v4:
- Validate cipher metadata before reading the key length
- Preserve hard provider failures and fall back only when unsupported
- Treat -EINVAL as a hard provider error
- Fix disabled-feature declarations and use the public test prototype
- Add in-place decrypt and malformed-input tests
- Simplify the legacy allocating decrypt wrapper
Changes v2 -> v3:
- Flip the image_aes_decrypt_to() host-tool guard
(suggested by Simon Glass)
- Let image_aes_decrypt_to() be the single length-validation path
(suggested by Simon Glass)
- Document that AES CBC decrypt providers must support in-place decrypt
(suggested by Simon Glass)
Changes v1 -> v2:
- Explain FIT cipher validation (suggested by Simon Glass)
- Explain phase-keyed lib/aes builds (suggested by Simon Glass)
- Return -ENOSYS without decrypt support (suggested by Simon Glass)
- Use decrypt_to for U-Boot proper in-place decrypt
---
boot/image-cipher.c | 47 ++++++++++++++++++-----
boot/image-fit.c | 66 +++++++++++++++++++++++++++++---
include/image.h | 39 +++++++++++++++++++
include/u-boot/aes.h | 27 ++++++++++----
lib/Makefile | 2 +-
lib/aes/aes-decrypt.c | 91 +++++++++++++++++++++++++++++++++++++--------
test/lib/Makefile | 3 ++
test/lib/test_aes_decrypt.c | 89 ++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 325 insertions(+), 39 deletions(-)
diff --git a/boot/image-cipher.c b/boot/image-cipher.c
index 9d389f26cea..e7ef72987cc 100644
--- a/boot/image-cipher.c
+++ b/boot/image-cipher.c
@@ -25,6 +25,7 @@ struct cipher_algo cipher_algos[] = {
#endif
.encrypt = image_aes_encrypt,
.decrypt = image_aes_decrypt,
+ .decrypt_to = image_aes_decrypt_to,
.add_cipher_data = image_aes_add_cipher_data
},
{
@@ -36,6 +37,7 @@ struct cipher_algo cipher_algos[] = {
#endif
.encrypt = image_aes_encrypt,
.decrypt = image_aes_decrypt,
+ .decrypt_to = image_aes_decrypt_to,
.add_cipher_data = image_aes_add_cipher_data
},
{
@@ -47,6 +49,7 @@ struct cipher_algo cipher_algos[] = {
#endif
.encrypt = image_aes_encrypt,
.decrypt = image_aes_decrypt,
+ .decrypt_to = image_aes_decrypt_to,
.add_cipher_data = image_aes_add_cipher_data
}
};
@@ -70,6 +73,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info,
int cipher_noffset)
{
const void *fdt = gd_fdt_blob();
+ int key_len, iv_len;
const char *node_name;
char node_path[128];
int noffset;
@@ -94,7 +98,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info,
return -1;
}
- info->iv = fdt_getprop(fit, cipher_noffset, "iv", NULL);
+ info->iv = fdt_getprop(fit, cipher_noffset, "iv", &iv_len);
info->ivname = fdt_getprop(fit, cipher_noffset, "iv-name-hint", NULL);
if (!info->iv && !info->ivname) {
@@ -115,7 +119,7 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info,
&info->size_unciphered);
if (ret) {
printf("Can't get size of unciphered data\n");
- return -1;
+ return ret;
}
/*
@@ -136,20 +140,28 @@ static int fit_image_setup_decrypt(struct image_cipher_info *info,
}
/* read key */
- info->key = fdt_getprop(fdt, noffset, "key", NULL);
+ info->key = fdt_getprop(fdt, noffset, "key", &key_len);
if (!info->key) {
printf("Can't get key in cipher node '%s'\n", node_path);
return -1;
}
+ if (key_len != info->cipher->key_len) {
+ printf("Bad key length in cipher node '%s'\n", node_path);
+ return -1;
+ }
/* read iv */
if (!info->iv) {
- info->iv = fdt_getprop(fdt, noffset, "iv", NULL);
+ info->iv = fdt_getprop(fdt, noffset, "iv", &iv_len);
if (!info->iv) {
printf("Can't get IV in cipher node '%s'\n", node_path);
return -1;
}
}
+ if (iv_len != info->cipher->iv_len) {
+ printf("Bad IV length for cipher in image '%s'\n", node_name);
+ return -1;
+ }
return 0;
}
@@ -165,11 +177,28 @@ int fit_image_decrypt_data(const void *fit,
ret = fit_image_setup_decrypt(&info, fit, image_noffset,
cipher_noffset);
if (ret < 0)
- goto out;
+ return ret;
+
+ return info.cipher->decrypt(&info, data_ciphered, size_ciphered,
+ data_unciphered, size_unciphered);
+}
+
+int fit_image_decrypt_data_to(const void *fit,
+ int image_noffset, int cipher_noffset,
+ const void *data_ciphered, size_t size_ciphered,
+ void *data_unciphered, size_t *size_unciphered)
+{
+ struct image_cipher_info info;
+ int ret;
+
+ ret = fit_image_setup_decrypt(&info, fit, image_noffset,
+ cipher_noffset);
+ if (ret < 0)
+ return ret;
- ret = info.cipher->decrypt(&info, data_ciphered, size_ciphered,
- data_unciphered, size_unciphered);
+ if (!info.cipher->decrypt_to)
+ return -ENOSYS;
- out:
- return ret;
+ return info.cipher->decrypt_to(&info, data_ciphered, size_ciphered,
+ data_unciphered, size_unciphered);
}
diff --git a/boot/image-fit.c b/boot/image-fit.c
index 9b39696de2d..bc964510624 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -1028,7 +1028,7 @@ int fit_image_get_data_size(const void *fit, int noffset, int *data_size)
*
* @fit: pointer to the FIT image header
* @noffset: component image node offset
- * @data_size: holds the data-size property
+ * @data_size: holds the data-size-unciphered property
*
* returns:
* 0, on success
@@ -1038,10 +1038,13 @@ int fit_image_get_data_size_unciphered(const void *fit, int noffset,
size_t *data_size)
{
const fdt32_t *val;
+ int len;
- val = fdt_getprop(fit, noffset, "data-size-unciphered", NULL);
+ val = fdt_getprop(fit, noffset, FIT_DATA_SIZE_UNCIPHERED_PROP, &len);
if (!val)
return -ENOENT;
+ if (len != sizeof(*val))
+ return -EINVAL;
*data_size = (size_t)fdt32_to_cpu(*val);
@@ -1550,8 +1553,33 @@ int fit_all_image_verify(const void *fit)
return 1;
}
+#ifndef USE_HOSTCC
+static bool fit_image_has_hash(const void *fit, int image_noffset)
+{
+ int noffset;
+
+ fdt_for_each_subnode(noffset, fit, image_noffset) {
+ const char *name = fit_get_name(fit, noffset, NULL);
+
+ if (!strncmp(name, FIT_HASH_NODENAME,
+ strlen(FIT_HASH_NODENAME)))
+ return true;
+ }
+
+ return false;
+}
+
+static bool fit_range_in_writable_ram(const void *data, size_t size)
+{
+ ulong start = map_to_sysmem(data);
+
+ return start >= gd->ram_base && start <= gd->ram_top &&
+ size <= gd->ram_top - start;
+}
+#endif
+
static int fit_image_uncipher(const void *fit, int image_noffset,
- void **data, size_t *size)
+ bool verified, void **data, size_t *size)
{
int cipher_noffset, ret;
void *dst;
@@ -1562,15 +1590,40 @@ static int fit_image_uncipher(const void *fit, int image_noffset,
if (cipher_noffset < 0)
return 0;
+#ifndef USE_HOSTCC
+ if (!tools_build()) {
+ /*
+ * Avoid a full-size allocation when the FIT payload is already
+ * in writable DRAM. Require a payload hash so an unverified
+ * repeated attempt detects the consumed ciphertext before
+ * decrypting it again.
+ */
+ if (fit_range_in_writable_ram(*data, *size) &&
+ fit_image_has_hash(fit, image_noffset)) {
+ if (!verified && !fit_image_verify(fit, image_noffset)) {
+ ret = -EACCES;
+ goto out;
+ }
+ ret = fit_image_decrypt_data_to(fit, image_noffset,
+ cipher_noffset,
+ *data, *size, *data,
+ &size_dst);
+ if (ret != -ENOSYS)
+ goto out;
+ }
+ }
+#endif
+
ret = fit_image_decrypt_data(fit, image_noffset, cipher_noffset,
*data, *size, &dst, &size_dst);
if (ret)
goto out;
*data = dst;
- *size = size_dst;
+out:
+ if (!ret)
+ *size = size_dst;
- out:
return ret;
}
@@ -2299,7 +2352,8 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
/* Decrypt data before uncompress/move */
if (IS_ENABLED(CONFIG_FIT_CIPHER) && IMAGE_ENABLE_DECRYPT) {
puts(" Decrypting Data ... ");
- if (fit_image_uncipher(fit, noffset, &buf, &size)) {
+ if (fit_image_uncipher(fit, noffset, images->verify,
+ &buf, &size)) {
puts("Error\n");
return -EACCES;
}
diff --git a/include/image.h b/include/image.h
index 4149ebbcce9..879e58e2241 100644
--- a/include/image.h
+++ b/include/image.h
@@ -1179,6 +1179,7 @@ int booti_setup(ulong image, ulong *relocated_addr, ulong *size,
#define FIT_DATA_POSITION_PROP "data-position"
#define FIT_DATA_OFFSET_PROP "data-offset"
#define FIT_DATA_SIZE_PROP "data-size"
+#define FIT_DATA_SIZE_UNCIPHERED_PROP "data-size-unciphered"
#define FIT_TIMESTAMP_PROP "timestamp"
#define FIT_DESC_PROP "description"
#define FIT_ARCH_PROP "arch"
@@ -1875,11 +1876,45 @@ int fit_image_check_sig(const void *fit, int noffset, const void *data,
size_t size, const void *key_blob, int required_keynode,
char **err_msgp);
+/**
+ * fit_image_decrypt_data() - Decrypt a FIT image payload
+ *
+ * @fit: FIT image
+ * @image_noffset: Offset of the image node to decrypt
+ * @cipher_noffset: Offset of the cipher node for the image
+ * @data: Encrypted image payload
+ * @size: Size of encrypted image payload
+ * @data_unciphered: Returns allocated decrypted payload
+ * @size_unciphered: Returns size of decrypted payload
+ * Return: 0 on success, <0 on error
+ */
int fit_image_decrypt_data(const void *fit,
int image_noffset, int cipher_noffset,
const void *data, size_t size,
void **data_unciphered, size_t *size_unciphered);
+/**
+ * fit_image_decrypt_data_to() - Decrypt a FIT image payload to a buffer
+ *
+ * @fit: FIT image
+ * @image_noffset: Offset of the image node to decrypt
+ * @cipher_noffset: Offset of the cipher node for the image
+ * @data: Encrypted image payload
+ * @size: Size of encrypted image payload
+ * @data_unciphered: Destination buffer for decrypted payload. The caller
+ * must provide at least @size bytes.
+ * @size_unciphered: Returns size of decrypted payload
+ *
+ * If @data and @data_unciphered are the same buffer, successful decryption
+ * overwrites the encrypted payload.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fit_image_decrypt_data_to(const void *fit,
+ int image_noffset, int cipher_noffset,
+ const void *data, size_t size,
+ void *data_unciphered, size_t *size_unciphered);
+
/**
* fit_region_make_list() - Make a list of regions to hash
*
@@ -1973,6 +2008,10 @@ struct cipher_algo {
int (*decrypt)(struct image_cipher_info *info,
const void *cipher, size_t cipher_len,
void **data, size_t *data_len);
+
+ int (*decrypt_to)(struct image_cipher_info *info,
+ const void *cipher, size_t cipher_len,
+ void *data, size_t *data_len);
};
int fit_image_cipher_get_algo(const void *fit, int noffset, char **algo);
diff --git a/include/u-boot/aes.h b/include/u-boot/aes.h
index acbc50b9e6f..8fd43f02adc 100644
--- a/include/u-boot/aes.h
+++ b/include/u-boot/aes.h
@@ -16,15 +16,16 @@ int image_aes_encrypt(struct image_cipher_info *info,
int image_aes_add_cipher_data(struct image_cipher_info *info, void *keydest,
void *fit, int node_noffset);
#else
-int image_aes_encrypt(struct image_cipher_info *info,
- const unsigned char *data, int size,
- unsigned char **cipher, int *cipher_len)
+static inline int image_aes_encrypt(struct image_cipher_info *info,
+ const unsigned char *data, int size,
+ unsigned char **cipher, int *cipher_len)
{
return -ENXIO;
}
-int image_aes_add_cipher_data(struct image_cipher_info *info, void *keydest,
- void *fit, int node_noffset)
+static inline int image_aes_add_cipher_data(struct image_cipher_info *info,
+ void *keydest, void *fit,
+ int node_noffset)
{
return -ENXIO;
}
@@ -34,10 +35,20 @@ int image_aes_add_cipher_data(struct image_cipher_info *info, void *keydest,
int image_aes_decrypt(struct image_cipher_info *info,
const void *cipher, size_t cipher_len,
void **data, size_t *size);
+int image_aes_decrypt_to(struct image_cipher_info *info,
+ const void *cipher, size_t cipher_len,
+ void *data, size_t *size);
#else
-int image_aes_decrypt(struct image_cipher_info *info,
- const void *cipher, size_t cipher_len,
- void **data, size_t *size)
+static inline int image_aes_decrypt(struct image_cipher_info *info,
+ const void *cipher, size_t cipher_len,
+ void **data, size_t *size)
+{
+ return -ENXIO;
+}
+
+static inline int image_aes_decrypt_to(struct image_cipher_info *info,
+ const void *cipher, size_t cipher_len,
+ void *data, size_t *size)
{
return -ENXIO;
}
diff --git a/lib/Makefile b/lib/Makefile
index 222378a8531..e8ec4660b38 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -19,7 +19,6 @@ obj-$(CONFIG_ARCH_AT91) += at91/
obj-$(CONFIG_OPTEE_LIB) += optee/
obj-$(CONFIG_AES) += aes.o
-obj-$(CONFIG_AES) += aes/
obj-$(CONFIG_$(PHASE_)BINMAN_FDT) += binman.o
obj-$(CONFIG_FW_LOADER) += fw_loader.o
@@ -89,6 +88,7 @@ obj-$(CONFIG_$(PHASE_)ASN1_DECODER_LEGACY) += asn1_decoder.o
obj-$(CONFIG_$(PHASE_)ZLIB) += zlib/
obj-$(CONFIG_$(PHASE_)ZSTD) += zstd/
+obj-$(CONFIG_$(PHASE_)FIT_CIPHER) += aes/
obj-$(CONFIG_$(PHASE_)GZIP) += gunzip.o
obj-$(CONFIG_$(PHASE_)LZO) += lzo/
obj-$(CONFIG_$(PHASE_)LZMA) += lzma/
diff --git a/lib/aes/aes-decrypt.c b/lib/aes/aes-decrypt.c
index 741102a4723..8a9b5f01efb 100644
--- a/lib/aes/aes-decrypt.c
+++ b/lib/aes/aes-decrypt.c
@@ -4,37 +4,98 @@
*/
#ifndef USE_HOSTCC
+#include <dm.h>
#include <malloc.h>
#endif
#include <image.h>
#include <uboot_aes.h>
+#ifndef USE_HOSTCC
+static int image_aes_validate(struct image_cipher_info *info,
+ const void *cipher, size_t cipher_len,
+ void *data, size_t *size)
+{
+ if (!info || !info->cipher || !info->key || !info->iv || !cipher ||
+ !data || !size)
+ return -EINVAL;
+ if (info->cipher->iv_len != AES_BLOCK_LENGTH ||
+ (info->cipher->key_len != AES128_KEY_LENGTH &&
+ info->cipher->key_len != AES192_KEY_LENGTH &&
+ info->cipher->key_len != AES256_KEY_LENGTH))
+ return -EINVAL;
+ if (!cipher_len || cipher_len % AES_BLOCK_LENGTH ||
+ info->size_unciphered > cipher_len)
+ return -EINVAL;
+
+ return 0;
+}
+#endif
+
+int image_aes_decrypt_to(struct image_cipher_info *info,
+ const void *cipher, size_t cipher_len,
+ void *data, size_t *size)
+{
+#ifdef USE_HOSTCC
+ return -ENOSYS;
+#else
+ unsigned int aes_blocks, key_len;
+ int ret;
+
+ ret = image_aes_validate(info, cipher, cipher_len, data, size);
+ if (ret)
+ return ret;
+ key_len = info->cipher->key_len;
+ aes_blocks = cipher_len / AES_BLOCK_LENGTH;
+
+ if (CONFIG_IS_ENABLED(DM_AES)) {
+ ret = dm_aes_cbc_decrypt_with_key(key_len * 8, (u8 *)info->key,
+ (u8 *)info->iv, (u8 *)cipher,
+ data, aes_blocks);
+ if (!ret) {
+ *size = info->size_unciphered;
+ return 0;
+ }
+ if (ret != -ENODEV && ret != -EOPNOTSUPP)
+ return ret;
+ }
+
+ if (!IS_ENABLED(CONFIG_XPL_BUILD)) {
+ unsigned char key_exp[AES256_EXPAND_KEY_LENGTH];
+
+ /* First we expand the key. */
+ aes_expand_key((u8 *)info->key, key_len, key_exp);
+
+ aes_cbc_decrypt_blocks(key_len, key_exp, (u8 *)info->iv,
+ (u8 *)cipher, data, aes_blocks);
+ *size = info->size_unciphered;
+ return 0;
+ }
+
+ return -ENOSYS;
+#endif
+}
+
int image_aes_decrypt(struct image_cipher_info *info,
const void *cipher, size_t cipher_len,
void **data, size_t *size)
{
-#ifndef USE_HOSTCC
- unsigned char key_exp[AES256_EXPAND_KEY_LENGTH];
- unsigned int aes_blocks, key_len = info->cipher->key_len;
+#ifdef USE_HOSTCC
+ return -ENOSYS;
+#else
+ int ret;
*data = malloc(cipher_len);
if (!*data) {
printf("Can't allocate memory to decrypt\n");
return -ENOMEM;
}
- *size = info->size_unciphered;
-
- memcpy(&key_exp[0], info->key, key_len);
-
- /* First we expand the key. */
- aes_expand_key((u8 *)info->key, key_len, key_exp);
- /* Calculate the number of AES blocks to encrypt. */
- aes_blocks = DIV_ROUND_UP(cipher_len, AES_BLOCK_LENGTH);
+ ret = image_aes_decrypt_to(info, cipher, cipher_len, *data, size);
+ if (ret) {
+ free(*data);
+ *data = NULL;
+ }
- aes_cbc_decrypt_blocks(key_len, key_exp, (u8 *)info->iv,
- (u8 *)cipher, *data, aes_blocks);
+ return ret;
#endif
-
- return 0;
}
diff --git a/test/lib/Makefile b/test/lib/Makefile
index f25383a40e5..721d1470185 100644
--- a/test/lib/Makefile
+++ b/test/lib/Makefile
@@ -29,6 +29,9 @@ obj-$(CONFIG_ERRNO_STR) += test_errno_str.o
obj-$(CONFIG_UT_LIB_ASN1) += asn1.o
obj-$(CONFIG_UT_LIB_RSA) += rsa.o
obj-$(CONFIG_AES) += test_aes.o
+ifeq ($(CONFIG_FIT_CIPHER)$(CONFIG_DM_AES),yy)
+obj-y += test_aes_decrypt.o
+endif
obj-$(CONFIG_SHA256) += test_sha256_hmac.o
obj-$(CONFIG_HKDF_MBEDTLS) += test_sha256_hkdf.o
obj-$(CONFIG_GETOPT) += getopt.o
diff --git a/test/lib/test_aes_decrypt.c b/test/lib/test_aes_decrypt.c
new file mode 100644
index 00000000000..3b498e23e4b
--- /dev/null
+++ b/test/lib/test_aes_decrypt.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Tests for target-side FIT AES decryption
+ *
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#include <image.h>
+#include <u-boot/aes.h>
+#include <uboot_aes.h>
+#include <test/lib.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+static int lib_test_image_aes_decrypt(struct unit_test_state *uts)
+{
+ u8 key[AES256_KEY_LENGTH] = { };
+ u8 key_exp[AES256_EXPAND_KEY_LENGTH];
+ u8 iv[AES_BLOCK_LENGTH] = { };
+ u8 plain[2 * AES_BLOCK_LENGTH];
+ u8 cipher[sizeof(plain)];
+ u8 output[sizeof(plain)];
+ struct cipher_algo algo = {
+ .name = "aes256",
+ .key_len = sizeof(key),
+ .iv_len = sizeof(iv),
+ };
+ struct image_cipher_info info = {
+ .cipher = &algo,
+ .key = key,
+ .iv = iv,
+ .size_unciphered = sizeof(plain),
+ };
+ size_t size;
+ int i, ret;
+
+ for (i = 0; i < sizeof(key); i++)
+ key[i] = i;
+ for (i = 0; i < sizeof(iv); i++)
+ iv[i] = 0x80 + i;
+ for (i = 0; i < sizeof(plain); i++)
+ plain[i] = 0x40 + i;
+
+ aes_expand_key(key, sizeof(key), key_exp);
+ aes_cbc_encrypt_blocks(sizeof(key), key_exp, iv, plain, cipher,
+ ARRAY_SIZE(cipher) / AES_BLOCK_LENGTH);
+
+ size = 0;
+ ut_assertok(image_aes_decrypt_to(&info, cipher, sizeof(cipher), output,
+ &size));
+ ut_asserteq(sizeof(plain), size);
+ ut_asserteq_mem(plain, output, sizeof(plain));
+
+ memcpy(output, cipher, sizeof(cipher));
+ size = 0;
+ ut_assertok(image_aes_decrypt_to(&info, output, sizeof(output), output,
+ &size));
+ ut_asserteq(sizeof(plain), size);
+ ut_asserteq_mem(plain, output, sizeof(plain));
+
+ size = 0x55;
+ ret = image_aes_decrypt_to(&info, cipher, sizeof(cipher) - 1, output,
+ &size);
+ ut_asserteq(-EINVAL, ret);
+ ut_asserteq(0x55, size);
+
+ info.size_unciphered = sizeof(cipher) + 1;
+ ret = image_aes_decrypt_to(&info, cipher, sizeof(cipher), output, &size);
+ ut_asserteq(-EINVAL, ret);
+ info.size_unciphered = sizeof(plain);
+
+ info.key = NULL;
+ ret = image_aes_decrypt_to(&info, cipher, sizeof(cipher), output, &size);
+ ut_asserteq(-EINVAL, ret);
+ info.key = key;
+ info.iv = NULL;
+ ret = image_aes_decrypt_to(&info, cipher, sizeof(cipher), output, &size);
+ ut_asserteq(-EINVAL, ret);
+ info.iv = iv;
+ info.cipher = NULL;
+ ret = image_aes_decrypt_to(&info, cipher, sizeof(cipher), output, &size);
+ ut_asserteq(-EINVAL, ret);
+ ret = image_aes_decrypt_to(NULL, cipher, sizeof(cipher), output, &size);
+ ut_asserteq(-EINVAL, ret);
+
+ return 0;
+}
+
+LIB_TEST(lib_test_image_aes_decrypt, 0);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 08/13] spl: fit: support encrypted payloads
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (6 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 07/13] boot: image: add FIT decrypt-to-buffer helper James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 09/13] clk: sunxi: add H6/H616 CE gates and reset James Hilliard
` (4 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
Add SPL_FIT_CIPHER and decrypt FIT image data before post-processing,
decompression or moving it to the final load address.
SPL cannot always allocate a new output buffer while loading FIT images,
so use the caller-provided decrypt-to-buffer helper. External encrypted
images are read into scratch memory first, then decrypted in place before
the existing copy or decompression path consumes them. Embedded encrypted
images decrypt into the final load buffer, or into scratch memory when
decompression is still required.
Defer mapping the final destination until the board post-processing hook
has finalized the source and length. The direct embedded-decrypt path maps
early because the hardware needs its destination, but tracks and extends
that mapping if post-processing grows the payload. Map decompression output
for CONFIG_SYS_BOOTM_LEN rather than the compressed input length.
Use IMAGE_ENABLE_DECRYPT in the common FIT image-load path so FIT cipher
support is selected by phase. Keep that path disabled for host tools,
since the target-side decrypt helper depends on the U-Boot control FDT
and runtime crypto providers.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v3 -> v4:
- Map the final destination after board post-processing determines size
- Size decompression mappings for the maximum output
- Require SPL_OF_CONTROL and clarify the SPL_FIT_CIPHER help text
Changes v2 -> v3:
- Use a shared helper for SPL decompression buffer decisions
(suggested by Simon Glass)
- Reject encrypted SPL FIT payloads when SPL_FIT_CIPHER is disabled
(suggested by Simon Glass)
- Flatten decrypt buffer selection (suggested by Simon Glass)
- Comment the no-copy path after direct decrypt
(suggested by Simon Glass)
Changes v1 -> v2:
- Drop redundant SPL_FIT select (suggested by Simon Glass)
- Explain the IMAGE_ENABLE_DECRYPT change (suggested by Simon Glass)
- Explain the host tools decrypt behavior (suggested by Simon Glass)
- Decrypt external encrypted payloads in place
(suggested by Simon Glass)
- Skip self-memmove after direct decrypt (suggested by Simon Glass)
---
boot/Kconfig | 9 ++++++
boot/image-fit.c | 2 +-
common/spl/spl_fit.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 91 insertions(+), 9 deletions(-)
diff --git a/boot/Kconfig b/boot/Kconfig
index c67dc0ba493..11b7c7cf017 100644
--- a/boot/Kconfig
+++ b/boot/Kconfig
@@ -155,6 +155,15 @@ config FIT_CIPHER
Enable the feature of data ciphering/unciphering in the tool mkimage
and in the u-boot support of the FIT image.
+config SPL_FIT_CIPHER
+ bool "Enable decrypting data in SPL FIT images"
+ depends on SPL_LOAD_FIT
+ depends on SPL_DM_AES
+ depends on SPL_OF_CONTROL
+ help
+ Enable decrypting FIT image data in SPL. This allows SPL to
+ decrypt an encrypted U-Boot proper FIT image through an AES driver.
+
config FIT_VERITY
bool "dm-verity boot parameter generation from FIT metadata"
depends on FIT && OF_LIBFDT
diff --git a/boot/image-fit.c b/boot/image-fit.c
index bc964510624..8b75c628056 100644
--- a/boot/image-fit.c
+++ b/boot/image-fit.c
@@ -2350,7 +2350,7 @@ int fit_image_load(struct bootm_headers *images, ulong addr,
}
/* Decrypt data before uncompress/move */
- if (IS_ENABLED(CONFIG_FIT_CIPHER) && IMAGE_ENABLE_DECRYPT) {
+ if (!tools_build() && IMAGE_ENABLE_DECRYPT) {
puts(" Decrypting Data ... ");
if (fit_image_uncipher(fit, noffset, images->verify,
&buf, &size)) {
diff --git a/common/spl/spl_fit.c b/common/spl/spl_fit.c
index 18bff7b8d4a..553b81299b7 100644
--- a/common/spl/spl_fit.c
+++ b/common/spl/spl_fit.c
@@ -193,6 +193,34 @@ static int get_aligned_image_size(struct spl_load_info *info, int data_size,
return ALIGN(data_size, spl_get_bl_len(info));
}
+static int spl_fit_image_decrypt(const void *fit, int node, int cipher_node,
+ void **data, size_t *size, void *dst)
+{
+ size_t dst_size;
+ int ret;
+
+ puts(" Decrypting Data ... ");
+ ret = fit_image_decrypt_data_to(fit, node, cipher_node, *data, *size,
+ dst, &dst_size);
+ if (ret) {
+ puts("Error\n");
+ return ret;
+ }
+
+ *data = dst;
+ *size = dst_size;
+
+ puts("OK\n");
+
+ return 0;
+}
+
+static bool spl_image_needs_decomp(uint8_t image_comp)
+{
+ return (IS_ENABLED(CONFIG_SPL_GZIP) && image_comp == IH_COMP_GZIP) ||
+ (IS_ENABLED(CONFIG_SPL_LZMA) && image_comp == IH_COMP_LZMA);
+}
+
/**
* load_simple_fit(): load the image described in a certain FIT node
* @info: points to information about the device to load data from
@@ -221,19 +249,23 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
int len;
ulong size;
ulong load_addr;
- void *load_ptr;
+ void *load_ptr = NULL;
+ size_t load_map_len = 0;
void *src;
ulong overhead;
uint8_t image_comp = -1, type = -1;
const void *data;
const void *fit = ctx->fit;
bool external_data = false;
+ bool encrypted;
+ bool needs_decomp = false;
+ int cipher_node = -ENOENT;
+ int ret;
log_debug("starting\n");
if (CONFIG_IS_ENABLED(BOOTMETH_VBE) &&
xpl_get_phase(info) != IH_PHASE_NONE) {
enum image_phase_t phase;
- int ret;
ret = fit_image_get_phase(fit, node, &phase);
/* if the image is for any phase, let's use it */
@@ -259,6 +291,7 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
if (spl_decompression_enabled()) {
fit_image_get_comp(fit, node, &image_comp);
debug("%s ", genimg_get_comp_name(image_comp));
+ needs_decomp = spl_image_needs_decomp(image_comp);
}
if (fit_image_get_load(fit, node, &load_addr)) {
@@ -270,6 +303,14 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
load_addr = image_info->load_addr;
}
+ cipher_node = fdt_subnode_offset(fit, node, FIT_CIPHER_NODENAME);
+ if (cipher_node >= 0 && !CONFIG_IS_ENABLED(FIT_CIPHER)) {
+ printf("Can't load %s: encrypted image without SPL_FIT_CIPHER\n",
+ fit_get_name(fit, node, NULL));
+ return -ENOSYS;
+ }
+ encrypted = cipher_node >= 0;
+
if (!fit_image_get_data_position(fit, node, &offset)) {
external_data = true;
} else if (!fit_image_get_data_offset(fit, node, &offset)) {
@@ -311,11 +352,12 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
if ((ulong)len > max_size)
goto too_big;
- if (spl_decompression_enabled() &&
- (image_comp == IH_COMP_GZIP || image_comp == IH_COMP_LZMA))
- src_ptr = map_sysmem(ALIGN(CONFIG_SYS_LOAD_ADDR, ARCH_DMA_MINALIGN), len);
+ if (needs_decomp || encrypted)
+ src_ptr = map_sysmem(ALIGN(CONFIG_SYS_LOAD_ADDR,
+ ARCH_DMA_MINALIGN), len);
else
- src_ptr = map_sysmem(ALIGN(load_addr, ARCH_DMA_MINALIGN), len);
+ src_ptr = map_sysmem(ALIGN(load_addr, ARCH_DMA_MINALIGN),
+ len);
length = len;
overhead = get_aligned_image_overhead(info, offset);
@@ -360,10 +402,40 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
puts("OK\n");
}
+ if (encrypted) {
+ void *decrypt_ptr;
+
+ if (external_data) {
+ decrypt_ptr = src;
+ } else if (needs_decomp) {
+ decrypt_ptr = map_sysmem(ALIGN(CONFIG_SYS_LOAD_ADDR,
+ ARCH_DMA_MINALIGN),
+ length);
+ } else {
+ load_map_len = length;
+ load_ptr = map_sysmem(load_addr, load_map_len);
+ decrypt_ptr = load_ptr;
+ }
+
+ ret = spl_fit_image_decrypt(fit, node, cipher_node, &src, &length,
+ decrypt_ptr);
+ if (ret)
+ return ret;
+ }
+
if (CONFIG_IS_ENABLED(FIT_IMAGE_POST_PROCESS))
board_fit_image_post_process(fit, node, &src, &length);
- load_ptr = map_sysmem(load_addr, length);
+ size = needs_decomp ? CONFIG_SYS_BOOTM_LEN : length;
+ if (!load_ptr || size > load_map_len) {
+ void *old_load_ptr = load_ptr;
+
+ load_ptr = map_sysmem(load_addr, size);
+ load_map_len = size;
+ if (src == old_load_ptr)
+ src = load_ptr;
+ }
+
if (IS_ENABLED(CONFIG_SPL_GZIP) && image_comp == IH_COMP_GZIP) {
size = length;
if (gunzip(load_ptr, CONFIG_SYS_BOOTM_LEN, src, &size)) {
@@ -381,7 +453,8 @@ static int load_simple_fit(struct spl_load_info *info, ulong fit_offset,
return -EIO;
}
length = loadEnd - CONFIG_SYS_LOAD_ADDR;
- } else {
+ } else if (src != load_ptr) {
+ /* Direct decrypt of an embedded image can already be in place. */
memmove(load_ptr, src, length);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 09/13] clk: sunxi: add H6/H616 CE gates and reset
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (7 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 08/13] spl: fit: support encrypted payloads James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 10/13] lib: ecdsa: support additional curve sizes James Hilliard
` (3 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
The H6 and H616 devicetrees describe the Crypto Engine with module, bus
and MBUS clocks plus a bus reset. Add the corresponding gate and reset
entries so drivers can enable the CE through the normal clock and reset
uclasses in U-Boot proper.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v1 -> v2:
- Add Simon's Reviewed-by tag
---
drivers/clk/sunxi/clk_h6.c | 5 +++++
drivers/clk/sunxi/clk_h616.c | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/drivers/clk/sunxi/clk_h6.c b/drivers/clk/sunxi/clk_h6.c
index 81deb5728e5..27ed5705a14 100644
--- a/drivers/clk/sunxi/clk_h6.c
+++ b/drivers/clk/sunxi/clk_h6.c
@@ -20,6 +20,10 @@ static struct ccu_clk_gate h6_gates[] = {
[CLK_DE] = GATE(0x600, BIT(31)),
[CLK_BUS_DE] = GATE(0x60c, BIT(0)),
+ [CLK_CE] = GATE(0x680, BIT(31)),
+ [CLK_BUS_CE] = GATE(0x68c, BIT(0)),
+
+ [CLK_MBUS_CE] = GATE(0x804, BIT(2)),
[CLK_MBUS_NAND] = GATE(0x804, BIT(5)),
[CLK_NAND0] = GATE(0x810, BIT(31)),
@@ -77,6 +81,7 @@ static struct ccu_clk_gate h6_gates[] = {
static struct ccu_reset h6_resets[] = {
[RST_BUS_DE] = RESET(0x60c, BIT(16)),
+ [RST_BUS_CE] = RESET(0x68c, BIT(16)),
[RST_BUS_NAND] = RESET(0x82c, BIT(16)),
[RST_BUS_MMC0] = RESET(0x84c, BIT(16)),
diff --git a/drivers/clk/sunxi/clk_h616.c b/drivers/clk/sunxi/clk_h616.c
index 3e7eea25bfe..d5b659d0bb2 100644
--- a/drivers/clk/sunxi/clk_h616.c
+++ b/drivers/clk/sunxi/clk_h616.c
@@ -19,6 +19,10 @@ static struct ccu_clk_gate h616_gates[] = {
[CLK_DE] = GATE(0x600, BIT(31)),
[CLK_BUS_DE] = GATE(0x60c, BIT(0)),
+ [CLK_CE] = GATE(0x680, BIT(31)),
+ [CLK_BUS_CE] = GATE(0x68c, BIT(0)),
+
+ [CLK_MBUS_CE] = GATE(0x804, BIT(2)),
[CLK_MBUS_NAND] = GATE(0x804, BIT(5)),
[CLK_NAND0] = GATE(0x810, BIT(31)),
@@ -86,6 +90,7 @@ static struct ccu_clk_gate h616_gates[] = {
static struct ccu_reset h616_resets[] = {
[RST_BUS_DE] = RESET(0x60c, BIT(16)),
+ [RST_BUS_CE] = RESET(0x68c, BIT(16)),
[RST_BUS_NAND] = RESET(0x82c, BIT(16)),
[RST_BUS_MMC0] = RESET(0x84c, BIT(16)),
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 10/13] lib: ecdsa: support additional curve sizes
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (8 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 09/13] clk: sunxi: add H6/H616 CE gates and reset James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 11/13] crypto: allwinner: add sun8i-ce AES driver James Hilliard
` (2 subsequent siblings)
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
U-Boot's ECDSA FIT code is tied closely to prime256v1 and secp384r1.
Hardware verifiers may support a wider set of curves, and FIT public key
properties need to preserve the fixed coordinate width of those curves.
Add common curve-size handling for secp224r1, prime256v1, secp384r1 and
secp521r1. Use fixed-width big-endian byte arrays when writing libcrypto
BIGNUM values into the control FDT so non-32-bit-aligned coordinates such
as secp521r1 are encoded correctly. RSA key sizes are already multiples
of 32 bits, so their generated byte encoding is unchanged.
Register the corresponding host and target ECDSA algorithm names and
clean up the libcrypto signing context so raw signatures are owned and
freed consistently on error paths. Exercise all four curves in the host
FIT signing test.
SPL ECDSA verification uses UCLASS_ECDSA, so make SPL_ECDSA_VERIFY
depend on SPL_DM. Keep fdt_add_bignum() in the libfdt error namespace so
RSA and ECDSA callers continue to handle FDT growth and other errors
consistently.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v3 -> v4:
- Exercise secp224r1, prime256v1, secp384r1 and secp521r1 signing
Changes v1 -> v2:
- Explain the RSA impact of fdt_add_bignum() (suggested by Simon Glass)
- Document ECDSA public key byte-array widths (suggested by Simon Glass)
- Document ecdsa_curve_size() (suggested by Simon Glass)
- Keep fdt_add_bignum() errors in libfdt space
(suggested by Simon Glass)
- Explain the SPL_DM dependency (suggested by Simon Glass)
---
doc/mkimage.1 | 3 +
doc/usage/fit/signature.rst | 9 ++-
include/u-boot/ecdsa.h | 22 +++++++
include/u-boot/fdt-libcrypto.h | 6 +-
lib/ecdsa/Kconfig | 2 +-
lib/ecdsa/ecdsa-libcrypto.c | 141 +++++++++++++++++++++++++---------------
lib/ecdsa/ecdsa-verify.c | 39 +++++------
lib/fdt-libcrypto.c | 60 ++++-------------
test/py/tests/test_fit_ecdsa.py | 18 +++--
tools/image-sig-host.c | 7 ++
10 files changed, 174 insertions(+), 133 deletions(-)
diff --git a/doc/mkimage.1 b/doc/mkimage.1
index 9a2d07cee75..b6ba88dd314 100644
--- a/doc/mkimage.1
+++ b/doc/mkimage.1
@@ -459,7 +459,10 @@ lb.
rsa2048
rsa3072
rsa4096
+ecdsa224
ecdsa256
+ecdsa384
+secp521r1
.TE
.RE
.
diff --git a/doc/usage/fit/signature.rst b/doc/usage/fit/signature.rst
index da08cc75c3a..a6aacd691d3 100644
--- a/doc/usage/fit/signature.rst
+++ b/doc/usage/fit/signature.rst
@@ -132,13 +132,16 @@ rsa,n0-inverse
For ECDSA the following are mandatory:
ecdsa,curve
- Name of ECDSA curve (e.g. "prime256v1")
+ Name of ECDSA curve, such as "secp224r1", "prime256v1",
+ "secp384r1", or "secp521r1"
ecdsa,x-point
- Public key X coordinate as a big-endian multi-word integer
+ Public key X coordinate as a fixed-width big-endian byte array. The width
+ is the curve size rounded up to whole bytes.
ecdsa,y-point
- Public key Y coordinate as a big-endian multi-word integer
+ Public key Y coordinate as a fixed-width big-endian byte array. The width
+ is the curve size rounded up to whole bytes.
These parameters can be added to a binary device tree using parameter -K of the
mkimage command::
diff --git a/include/u-boot/ecdsa.h b/include/u-boot/ecdsa.h
index f0ac0f327e9..3d70b138026 100644
--- a/include/u-boot/ecdsa.h
+++ b/include/u-boot/ecdsa.h
@@ -8,6 +8,7 @@
#include <errno.h>
#include <image.h>
+#include <string.h>
/**
* crypto_algo API impementation for ECDSA;
@@ -64,8 +65,29 @@ int ecdsa_verify(struct image_sign_info *info,
uint8_t *sig, uint sig_len);
/** @} */
+#define ECDSA224_BYTES (224 / 8)
#define ECDSA256_BYTES (256 / 8)
#define ECDSA384_BYTES (384 / 8)
#define ECDSA521_BYTES ((521 + 7) / 8)
+/**
+ * ecdsa_curve_size() - Get the size in bits for a named ECDSA curve
+ *
+ * @curve_name: OpenSSL short name for the curve
+ * Return: curve size in bits, or 0 if @curve_name is not supported
+ */
+static inline unsigned int ecdsa_curve_size(const char *curve_name)
+{
+ if (!strcmp(curve_name, "secp224r1"))
+ return 224;
+ else if (!strcmp(curve_name, "prime256v1"))
+ return 256;
+ else if (!strcmp(curve_name, "secp384r1"))
+ return 384;
+ else if (!strcmp(curve_name, "secp521r1"))
+ return 521;
+
+ return 0;
+}
+
#endif
diff --git a/include/u-boot/fdt-libcrypto.h b/include/u-boot/fdt-libcrypto.h
index b15d8a1eaf4..3164f2891aa 100644
--- a/include/u-boot/fdt-libcrypto.h
+++ b/include/u-boot/fdt-libcrypto.h
@@ -12,16 +12,16 @@
/**
* fdt_add_bignum() - Write a libcrypto BIGNUM as an FDT property
*
- * Convert a libcrypto BIGNUM * into a big endian array of integers.
+ * Convert a libcrypto BIGNUM * into a fixed-width big endian byte array.
*
* @blob: FDT blob to modify
* @noffset: Offset of the FDT node
* @prop_name: What to call the property in the FDT
* @num: pointer to a libcrypto big number
* @num_bits: How big is 'num' in bits?
- * Return: 0 if all good all working, -ve on horror
+ * Return: 0 on success, negative libfdt error on failure
*/
int fdt_add_bignum(void *blob, int noffset, const char *prop_name,
- BIGNUM *num, int num_bits);
+ const BIGNUM *num, int num_bits);
#endif /* _FDT_LIBCRYPTO_H */
diff --git a/lib/ecdsa/Kconfig b/lib/ecdsa/Kconfig
index ca13b6bfa1f..f0c56278fb6 100644
--- a/lib/ecdsa/Kconfig
+++ b/lib/ecdsa/Kconfig
@@ -17,7 +17,7 @@ config ECDSA_VERIFY
config SPL_ECDSA_VERIFY
bool "Enable ECDSA verification support in SPL"
- depends on SPL
+ depends on SPL && SPL_DM
help
Allow ECDSA signatures to be recognized and verified in SPL.
diff --git a/lib/ecdsa/ecdsa-libcrypto.c b/lib/ecdsa/ecdsa-libcrypto.c
index c4bfb2cec61..378d53ea242 100644
--- a/lib/ecdsa/ecdsa-libcrypto.c
+++ b/lib/ecdsa/ecdsa-libcrypto.c
@@ -31,7 +31,7 @@ struct signer {
EVP_PKEY *evp_key; /* Pointer to EVP_PKEY object */
EC_KEY *ecdsa_key; /* Pointer to EC_KEY object */
void *hash; /* Pointer to hash used for verification */
- void *signature; /* Pointer to output signature. Do not free()!*/
+ void *signature; /* Pointer to raw signature buffer */
};
struct ecdsa_public_key {
@@ -50,11 +50,8 @@ static int fdt_get_key(struct ecdsa_public_key *key, const void *fdt, int node)
if (!key->curve_name)
return -ENOMSG;
- if (!strcmp(key->curve_name, "prime256v1"))
- key->size_bits = 256;
- else if (!strcmp(key->curve_name, "secp384r1"))
- key->size_bits = 384;
- else
+ key->size_bits = ecdsa_curve_size(key->curve_name);
+ if (!key->size_bits)
return -EINVAL;
key->x = fdt_getprop(fdt, node, "ecdsa,x-point", &x_len);
@@ -63,7 +60,8 @@ static int fdt_get_key(struct ecdsa_public_key *key, const void *fdt, int node)
if (!key->x || !key->y)
return -EINVAL;
- if (x_len != key->size_bits / 8 || y_len != key->size_bits / 8)
+ if (x_len != (key->size_bits + 7) / 8 ||
+ y_len != (key->size_bits + 7) / 8)
return -EINVAL;
return 0;
@@ -85,18 +83,12 @@ static int read_key_from_fdt(struct signer *ctx, const void *fdt, int node)
return ret;
}
- if (!strcmp(pubkey.curve_name, "prime256v1")) {
- nid = NID_X9_62_prime256v1;
- } else if (!strcmp(pubkey.curve_name, "secp384r1")) {
- nid = NID_secp384r1;
- } else {
+ nid = OBJ_sn2nid(pubkey.curve_name);
+ if (nid == NID_undef) {
fprintf(stderr, "Unsupported curve name: '%s'\n", pubkey.curve_name);
return -EINVAL;
}
- fprintf(stderr, "Loading ECDSA key: curve=%s, bits=%d\n", pubkey.curve_name,
- pubkey.size_bits);
-
ec_key = EC_KEY_new_by_curve_name(nid);
if (!ec_key) {
fprintf(stderr, "Failed to allocate EC_KEY for curve %s\n", pubkey.curve_name);
@@ -111,7 +103,7 @@ static int read_key_from_fdt(struct signer *ctx, const void *fdt, int node)
return -ENOMEM;
}
- len = pubkey.size_bits / 8;
+ len = (pubkey.size_bits + 7) / 8;
uint8_t buf[1 + len * 2];
@@ -133,14 +125,13 @@ static int read_key_from_fdt(struct signer *ctx, const void *fdt, int node)
return -EINVAL;
}
- fprintf(stderr, "Successfully loaded ECDSA key from FDT node %d\n", node);
EC_POINT_free(point);
ctx->ecdsa_key = ec_key;
return 0;
}
-static int alloc_ctx(struct signer *ctx, const struct image_sign_info *info)
+static int init_ctx(struct signer *ctx)
{
memset(ctx, 0, sizeof(*ctx));
@@ -149,11 +140,21 @@ static int alloc_ctx(struct signer *ctx, const struct image_sign_info *info)
return -1;
}
+ return 0;
+}
+
+static int alloc_sig_ctx(struct signer *ctx, const struct image_sign_info *info)
+{
ctx->hash = malloc(info->checksum->checksum_len);
ctx->signature = malloc(info->crypto->key_len * 2);
- if (!ctx->hash || !ctx->signature)
+ if (!ctx->hash || !ctx->signature) {
+ free(ctx->hash);
+ free(ctx->signature);
+ ctx->hash = NULL;
+ ctx->signature = NULL;
return -ENOMEM;
+ }
return 0;
}
@@ -166,8 +167,8 @@ static void free_ctx(struct signer *ctx)
if (ctx->evp_key)
EVP_PKEY_free(ctx->evp_key);
- if (ctx->hash)
- free(ctx->hash);
+ free(ctx->hash);
+ free(ctx->signature);
}
/*
@@ -203,7 +204,12 @@ static ECDSA_SIG *ecdsa_sig_from_raw(void *buf, size_t order)
s_buf = (uintptr_t)buf + point_bytes;
r = BN_bin2bn(buf, point_bytes, NULL);
s = BN_bin2bn((void *)s_buf, point_bytes, NULL);
- ECDSA_SIG_set0(sig, r, s);
+ if (!r || !s || !ECDSA_SIG_set0(sig, r, s)) {
+ BN_free(r);
+ BN_free(s);
+ ECDSA_SIG_free(sig);
+ return NULL;
+ }
return sig;
}
@@ -271,10 +277,6 @@ static int load_key_from_fdt(struct signer *ctx, const struct image_sign_info *i
if (!fdt)
return -EINVAL;
- ret = alloc_ctx(ctx, info);
- if (ret)
- return ret;
-
sig_node = fdt_subnode_offset(fdt, 0, FIT_SIG_NODENAME);
if (sig_node < 0) {
fprintf(stderr, "No /signature node found\n");
@@ -331,7 +333,9 @@ static int prepare_ctx(struct signer *ctx, const struct image_sign_info *info)
int key_len_bytes, ret;
char kname[1024];
- memset(ctx, 0, sizeof(*ctx));
+ ret = init_ctx(ctx);
+ if (ret)
+ return ret;
if (info->fdt_blob) {
return load_key_from_fdt(ctx, info);
@@ -345,10 +349,6 @@ static int prepare_ctx(struct signer *ctx, const struct image_sign_info *info)
return -EINVAL;
}
- ret = alloc_ctx(ctx, info);
- if (ret)
- return ret;
-
ret = read_key(ctx, kname);
if (ret)
return ret;
@@ -368,11 +368,18 @@ static int do_sign(struct signer *ctx, struct image_sign_info *info,
{
const struct checksum_algo *algo = info->checksum;
ECDSA_SIG *sig;
+ int ret;
+
+ ret = algo->calculate(algo->name, region, region_count, ctx->hash);
+ if (ret)
+ return ret;
- algo->calculate(algo->name, region, region_count, ctx->hash);
sig = ECDSA_do_sign(ctx->hash, algo->checksum_len, ctx->ecdsa_key);
+ if (!sig)
+ return -EIO;
ecdsa_sig_encode_raw(ctx->signature, sig, info->crypto->key_len);
+ ECDSA_SIG_free(sig);
return 0;
}
@@ -389,10 +396,16 @@ static int ecdsa_check_signature(struct signer *ctx, struct image_sign_info *inf
okay = ECDSA_do_verify(ctx->hash, info->checksum->checksum_len,
sig, ctx->ecdsa_key);
if (!okay)
- fprintf(stderr, "WARNING: Signature is fake news!\n");
+ fprintf(stderr, "WARNING: ECDSA signature verification failed\n");
+ else if (okay < 0)
+ fprintf(stderr, "ERROR: ECDSA signature verification failed\n");
ECDSA_SIG_free(sig);
- return !okay;
+
+ if (okay == 1)
+ return 0;
+
+ return okay < 0 ? -EIO : -EPERM;
}
static int do_verify(struct signer *ctx, struct image_sign_info *info,
@@ -400,6 +413,7 @@ static int do_verify(struct signer *ctx, struct image_sign_info *info,
uint8_t *raw_sig, uint sig_len)
{
const struct checksum_algo *algo = info->checksum;
+ int ret;
if (sig_len != info->crypto->key_len * 2) {
fprintf(stderr, "Signature has wrong length\n");
@@ -407,7 +421,9 @@ static int do_verify(struct signer *ctx, struct image_sign_info *info,
}
memcpy(ctx->signature, raw_sig, sig_len);
- algo->calculate(algo->name, region, region_count, ctx->hash);
+ ret = algo->calculate(algo->name, region, region_count, ctx->hash);
+ if (ret)
+ return ret;
return ecdsa_check_signature(ctx, info);
}
@@ -420,11 +436,16 @@ int ecdsa_sign(struct image_sign_info *info, const struct image_region region[],
ret = prepare_ctx(&ctx, info);
if (ret >= 0) {
- do_sign(&ctx, info, region, region_count);
- *sigp = ctx.signature;
- *sig_len = info->crypto->key_len * 2;
-
- ret = ecdsa_check_signature(&ctx, info);
+ ret = alloc_sig_ctx(&ctx, info);
+ if (!ret)
+ ret = do_sign(&ctx, info, region, region_count);
+ if (!ret)
+ ret = ecdsa_check_signature(&ctx, info);
+ if (!ret) {
+ *sigp = ctx.signature;
+ *sig_len = info->crypto->key_len * 2;
+ ctx.signature = NULL;
+ }
}
free_ctx(&ctx);
@@ -439,8 +460,12 @@ int ecdsa_verify(struct image_sign_info *info,
int ret;
ret = prepare_ctx(&ctx, info);
- if (ret >= 0)
- ret = do_verify(&ctx, info, region, region_count, sig, sig_len);
+ if (ret >= 0) {
+ ret = alloc_sig_ctx(&ctx, info);
+ if (!ret)
+ ret = do_verify(&ctx, info, region, region_count,
+ sig, sig_len);
+ }
free_ctx(&ctx);
return ret;
@@ -453,7 +478,7 @@ static int do_add(struct signer *ctx, void *fdt, const char *key_node_name,
const char *curve_name;
const EC_GROUP *group;
const EC_POINT *point;
- BIGNUM *x, *y;
+ BIGNUM *x = NULL, *y = NULL;
signature_node = fdt_subnode_offset(fdt, 0, FIT_SIG_NODENAME);
if (signature_node == -FDT_ERR_NOTFOUND) {
@@ -491,42 +516,54 @@ static int do_add(struct signer *ctx, void *fdt, const char *key_node_name,
group = EC_KEY_get0_group(ctx->ecdsa_key);
key_bits = EC_GROUP_order_bits(group);
curve_name = OBJ_nid2sn(EC_GROUP_get_curve_name(group));
- /* Let 'x' and 'y' memory leak by not BN_free()'ing them. */
x = BN_new();
y = BN_new();
+ if (!x || !y) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
point = EC_KEY_get0_public_key(ctx->ecdsa_key);
- EC_POINT_get_affine_coordinates(group, point, x, y, NULL);
+ if (!EC_POINT_get_affine_coordinates(group, point, x, y, NULL)) {
+ ret = -EINVAL;
+ goto out;
+ }
ret = fdt_setprop_string(fdt, key_node, FIT_KEY_HINT,
info->keyname);
if (ret < 0)
- return ret;
+ goto out;
ret = fdt_setprop_string(fdt, key_node, "ecdsa,curve", curve_name);
if (ret < 0)
- return ret;
+ goto out;
ret = fdt_add_bignum(fdt, key_node, "ecdsa,x-point", x, key_bits);
if (ret < 0)
- return ret;
+ goto out;
ret = fdt_add_bignum(fdt, key_node, "ecdsa,y-point", y, key_bits);
if (ret < 0)
- return ret;
+ goto out;
ret = fdt_setprop_string(fdt, key_node, FIT_ALGO_PROP,
info->name);
if (ret < 0)
- return ret;
+ goto out;
if (info->require_keys) {
ret = fdt_setprop_string(fdt, key_node, FIT_KEY_REQUIRED,
info->require_keys);
if (ret < 0)
- return ret;
+ goto out;
}
- return key_node;
+ ret = key_node;
+
+out:
+ BN_free(x);
+ BN_free(y);
+ return ret;
}
int ecdsa_add_verify_data(struct image_sign_info *info, void *fdt)
diff --git a/lib/ecdsa/ecdsa-verify.c b/lib/ecdsa/ecdsa-verify.c
index 629b662cf6c..8570d35028f 100644
--- a/lib/ecdsa/ecdsa-verify.c
+++ b/lib/ecdsa/ecdsa-verify.c
@@ -12,22 +12,6 @@
#include <dm/uclass.h>
#include <u-boot/ecdsa.h>
-/*
- * Derive size of an ECDSA key from the curve name
- *
- * While it's possible to extract the key size by using string manipulation,
- * use a list of known curves for the time being.
- */
-static int ecdsa_key_size(const char *curve_name)
-{
- if (!strcmp(curve_name, "prime256v1"))
- return 256;
- else if (!strcmp(curve_name, "secp384r1"))
- return 384;
-
- return 0;
-}
-
static int fdt_get_key(struct ecdsa_public_key *key, const void *fdt, int node)
{
int x_len, y_len;
@@ -38,9 +22,9 @@ static int fdt_get_key(struct ecdsa_public_key *key, const void *fdt, int node)
return -ENOMSG;
}
- key->size_bits = ecdsa_key_size(key->curve_name);
+ key->size_bits = ecdsa_curve_size(key->curve_name);
if (key->size_bits == 0) {
- debug("Unknown ECDSA curve '%s'", key->curve_name);
+ debug("Unknown ECDSA curve '%s'\n", key->curve_name);
return -EINVAL;
}
@@ -50,9 +34,10 @@ static int fdt_get_key(struct ecdsa_public_key *key, const void *fdt, int node)
if (!key->x || !key->y)
return -EINVAL;
- if (x_len != (key->size_bits / 8) || y_len != (key->size_bits / 8)) {
- printf("%s: node=%d, curve@%p x@%p+%i y@%p+%i\n", __func__,
- node, key->curve_name, key->x, x_len, key->y, y_len);
+ if (x_len != (key->size_bits + 7) / 8 ||
+ y_len != (key->size_bits + 7) / 8) {
+ debug("%s: node=%d, curve@%p x@%p+%i y@%p+%i\n", __func__,
+ node, key->curve_name, key->x, x_len, key->y, y_len);
return -EINVAL;
}
@@ -123,6 +108,12 @@ int ecdsa_verify(struct image_sign_info *info,
return ecdsa_verify_hash(dev, info, hash, sig, sig_len);
}
+U_BOOT_CRYPTO_ALGO(ecdsa224) = {
+ .name = "ecdsa224",
+ .key_len = ECDSA224_BYTES,
+ .verify = ecdsa_verify,
+};
+
U_BOOT_CRYPTO_ALGO(ecdsa256) = {
.name = "ecdsa256",
.key_len = ECDSA256_BYTES,
@@ -135,6 +126,12 @@ U_BOOT_CRYPTO_ALGO(ecdsa384) = {
.verify = ecdsa_verify,
};
+U_BOOT_CRYPTO_ALGO(secp521r1) = {
+ .name = "secp521r1",
+ .key_len = ECDSA521_BYTES,
+ .verify = ecdsa_verify,
+};
+
/*
* uclass definition for ECDSA API
*
diff --git a/lib/fdt-libcrypto.c b/lib/fdt-libcrypto.c
index ecb0344c8f6..8c5a7282ce9 100644
--- a/lib/fdt-libcrypto.c
+++ b/lib/fdt-libcrypto.c
@@ -5,68 +5,34 @@
*/
#include <libfdt.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <u-boot/fdt-libcrypto.h>
int fdt_add_bignum(void *blob, int noffset, const char *prop_name,
- BIGNUM *num, int num_bits)
+ const BIGNUM *num, int num_bits)
{
- int nwords = num_bits / 32;
- int size;
- uint32_t *buf, *ptr;
- BIGNUM *tmp, *big2, *big32, *big2_32;
- BN_CTX *ctx;
+ int size = (num_bits + 7) / 8;
+ unsigned char *buf;
int ret;
- tmp = BN_new();
- big2 = BN_new();
- big32 = BN_new();
- big2_32 = BN_new();
+ if (size <= 0)
+ return -FDT_ERR_BADVALUE;
- /*
- * Note: This code assumes that all of the above succeed, or all fail.
- * In practice memory allocations generally do not fail (unless the
- * process is killed), so it does not seem worth handling each of these
- * as a separate case. Technicaly this could leak memory on failure,
- * but a) it won't happen in practice, and b) it doesn't matter as we
- * will immediately exit with a failure code.
- */
- if (!tmp || !big2 || !big32 || !big2_32) {
- fprintf(stderr, "Out of memory (bignum)\n");
- return -ENOMEM;
- }
- ctx = BN_CTX_new();
- if (!ctx) {
- fprintf(stderr, "Out of memory (bignum context)\n");
- return -ENOMEM;
- }
- BN_set_word(big2, 2L);
- BN_set_word(big32, 32L);
- BN_exp(big2_32, big2, big32, ctx); /* B = 2^32 */
-
- size = nwords * sizeof(uint32_t);
buf = malloc(size);
if (!buf) {
fprintf(stderr, "Out of memory (%d bytes)\n", size);
- return -ENOMEM;
+ return -FDT_ERR_NOSPACE;
}
- /* Write out modulus as big endian array of integers */
- for (ptr = buf + nwords - 1; ptr >= buf; ptr--) {
- BN_mod(tmp, num, big2_32, ctx); /* n = N mod B */
- *ptr = cpu_to_fdt32(BN_get_word(tmp));
- BN_rshift(num, num, 32); /* N = N/B */
+ if (BN_bn2binpad(num, buf, size) != size) {
+ free(buf);
+ return -FDT_ERR_BADVALUE;
}
- /*
- * We try signing with successively increasing size values, so this
- * might fail several times
- */
+ /* Callers may retry with a larger FDT if the property does not fit. */
ret = fdt_setprop(blob, noffset, prop_name, buf, size);
free(buf);
- BN_free(tmp);
- BN_free(big2);
- BN_free(big32);
- BN_free(big2_32);
- return ret ? -FDT_ERR_NOSPACE : 0;
+ return ret;
}
diff --git a/test/py/tests/test_fit_ecdsa.py b/test/py/tests/test_fit_ecdsa.py
index 3e816d68eb6..ae26169b9bd 100644
--- a/test/py/tests/test_fit_ecdsa.py
+++ b/test/py/tests/test_fit_ecdsa.py
@@ -50,9 +50,9 @@ class SignableFitImage(object):
return self.signable_nodes
- def change_signature_algo_to_ecdsa(self):
+ def change_signature_algo_to_ecdsa(self, algo):
for image in self.signable_nodes:
- self.__fdt_set(f'{image}/signature', algo='sha256,ecdsa256')
+ self.__fdt_set(f'{image}/signature', algo=f'sha256,{algo}')
def sign(self, mkimage, key_file):
utils.run_and_log(self.ubman, [mkimage, '-F', self.fit, f'-G{key_file}'])
@@ -71,10 +71,16 @@ class SignableFitImage(object):
@pytest.mark.requiredtool('dtc')
@pytest.mark.requiredtool('fdtget')
@pytest.mark.requiredtool('fdtput')
-def test_fit_ecdsa(ubman):
+@pytest.mark.parametrize('curve, algo', (
+ ('secp224r1', 'ecdsa224'),
+ ('prime256v1', 'ecdsa256'),
+ ('secp384r1', 'ecdsa384'),
+ ('secp521r1', 'secp521r1'),
+))
+def test_fit_ecdsa(ubman, curve, algo):
""" Test that signatures generated by mkimage are legible. """
def generate_ecdsa_key():
- return ECC.generate(curve='prime256v1')
+ return ECC.generate(curve=curve)
def assemble_fit_image(dest_fit, its, destdir):
dtc_args = f'-I dts -O dtb -i {destdir}'
@@ -86,7 +92,7 @@ def test_fit_ecdsa(ubman):
mkimage = ubman.config.build_dir + '/tools/mkimage'
datadir = ubman.config.source_dir + '/test/py/tests/vboot/'
- tempdir = os.path.join(ubman.config.result_dir, 'ecdsa')
+ tempdir = os.path.join(ubman.config.result_dir, f'ecdsa-{curve}')
os.makedirs(tempdir, exist_ok=True)
key_file = f'{tempdir}/ecdsa-test-key.pem'
fit_file = f'{tempdir}/test.fit'
@@ -109,6 +115,6 @@ def test_fit_ecdsa(ubman):
if len(nodes) == 0:
raise ValueError('FIT image has no "/image" nodes with "signature"')
- fit.change_signature_algo_to_ecdsa()
+ fit.change_signature_algo_to_ecdsa(algo)
fit.sign(mkimage, key_file)
fit.check_signatures(key)
diff --git a/tools/image-sig-host.c b/tools/image-sig-host.c
index 5285263c616..aeca83bd440 100644
--- a/tools/image-sig-host.c
+++ b/tools/image-sig-host.c
@@ -69,6 +69,13 @@ struct crypto_algo crypto_algos[] = {
.add_verify_data = rsa_add_verify_data,
.verify = rsa_verify,
},
+ {
+ .name = "ecdsa224",
+ .key_len = ECDSA224_BYTES,
+ .sign = ecdsa_sign,
+ .add_verify_data = ecdsa_add_verify_data,
+ .verify = ecdsa_verify,
+ },
{
.name = "ecdsa256",
.key_len = ECDSA256_BYTES,
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 11/13] crypto: allwinner: add sun8i-ce AES driver
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (9 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 10/13] lib: ecdsa: support additional curve sizes James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 12/13] crypto: allwinner: add sun8i-ce ECDSA verifier James Hilliard
2026-07-28 17:41 ` [PATCH v6 13/13] crypto: allwinner: add sun8i-ce hash driver James Hilliard
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
Add an Allwinner sun8i Crypto Engine driver using the same directory and
driver naming style as the Linux sun8i-ce driver. The parent device owns
the shared CE registers, clocks and resets. It also provides an exclusive
task-session API which centralizes descriptor submission, per-channel
in-flight accounting, completion, error handling and reset on abort. The
AES child exposes the standard UCLASS_AES interface.
Support AES-128, AES-192 and AES-256 in ECB and CBC modes with
software-provided keys. Use one bounded scheduler for both one-engine and
two-engine operations by selecting the lane count and deriving serial or
parallel behavior from the task controls. Each lane owns two fixed banks
of ten descriptors, so memory use is independent of payload size.
For word-aligned ECB and CBC decryption requests of at least 256 KiB, run
the AES and RAES engines in parallel in SPL and U-Boot proper. Each lane
has two banks and one active-bank index. A bank's descriptor count records
whether it is prepared. Process each completion snapshot as one epoch:
retire all completed channels, submit each prepared peer bank, release
the completed mappings, then refill free banks from a shared cursor. This
keeps both engines fed without assuming a completion order. Keep a rolling
next-IV value and snapshot one IV for each queued task before DMA can
overwrite in-place ciphertext.
The H616 descriptor format uses word addresses. Map each word-aligned
bank independently, stage only its partial cacheline edges in private
aligned buffers, and map the cacheline-aligned middle directly. Build
each task's source and destination lists from those logical segments.
This isolates neighboring cachelines while preparation of the peer bank
overlaps DMA on the active bank, retaining aligned throughput for
cacheline-misaligned payloads. Support exact in-place operation, preserve
dirty bytes outside out-of-place DMA ranges, reject partial overlaps, and
use a fixed 64 KiB repack buffer only for byte addresses the descriptor
format cannot represent. Keep one CE session for the complete request,
including every bounded repack chunk.
Require DMA output mappings to cover complete cachelines so invalidation
cannot discard adjacent dirty data. Retire a channel only after its
completion and that channel's DMA current source and destination
registers are idle. Validate mapped buffers and descriptor chains before
encoding their hardware addresses, and assert the hardware descriptor
layout at build time. Use one cleanup path for normal and forced session
teardown. Track per-channel deadlines independently so activity on one
engine cannot extend another engine's timeout.
Model the H6 and H616 compatibles after the Linux sun8i-ce variant data.
Both variants use byte-sized cipher task lengths and provide the second
RAES engine; H616 additionally needs word-addressed descriptors. Program
the H616 module clock divider for 300 MHz from PLL_PERI0(2X). Select the
required SPL crypto, clock and reset support from SPL_SUNXI_CE, retain the
CE and clock-provider nodes in the pre-RAM device tree, and use the same
bulk clock/reset lifecycle in SPL and U-Boot proper.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v4 -> v5:
- Make the blocking chain helper close-on-error contract explicit and
avoid a redundant close in the one-shot wrapper
- Store the module-clock register value in variant data and share one
H6/H616 clock-programming path
- Reorder fixed bank bookkeeping before aligned DMA storage to remove
avoidable alignment holes
- Replace channel and engine claims with one exclusive parent session
tracking in-flight channels, independent deadlines and abort cleanup
- Move channel, method, tail interrupt and next-pointer setup into shared
submission, validate descriptor DMA ranges and wait for DMA idle
- Use the normal bulk clock and reset lifecycle in SPL and U-Boot proper,
select its SPL dependencies and retain the required pre-RAM DT nodes
- Replace the separate one-shot, bounce and dual-CBC paths with one
bounded one/two-lane scheduler, extending dual-engine scheduling to
word-aligned ECB requests of at least 256 KiB
- Drive each lane with two fixed banks of ten tasks, use fair refill from
a shared cursor, and submit prepared peers before CPU cache work
- Keep one session and rolling CBC IV across the complete operation,
with one compact IV snapshot for each queued task
- Direct-map word-aligned cacheline-offset buffers using private head and
tail cachelines plus SG middles, including exact in-place operation
- Use a fixed 64 KiB repack for byte-unaligned buffers, reject partial
overlap and validate length and address overflow
- Map only the configured key bytes and require output DMA mappings to
own complete cachelines
- Assert the hardware descriptor layout and reset active hardware before
releasing mappings after an error
Changes v3 -> v4:
- Drop invariant variant fields and redundant chain mapping state
- Add per-channel task sessions and engine ownership
- Use AES and RAES in parallel for CBC decrypts of at least 256 KiB
in SPL and U-Boot proper
- Double-buffer ten-descriptor chains with tail-only completion
- Support exact in-place decrypt and aligned-offset input buffers
- Map scheduler indices directly to AES/RAES channels and engine bits
- Drop redundant internal bounds checks and session initialization
- Reuse the scheduled CE register-wait helper and return status
separately
Changes v2 -> v3:
- Run the H616 CE module clock at 300 MHz
Changes v1 -> v2:
- Expose slot 0 as the software-provided AES key slot
---
MAINTAINERS | 1 +
arch/arm/dts/sunxi-u-boot.dtsi | 15 +
drivers/crypto/Kconfig | 2 +
drivers/crypto/Makefile | 1 +
drivers/crypto/allwinner/Kconfig | 3 +
drivers/crypto/allwinner/Makefile | 3 +
drivers/crypto/allwinner/sun8i-ce/Kconfig | 41 ++
drivers/crypto/allwinner/sun8i-ce/Makefile | 4 +
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c | 809 ++++++++++++++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c | 779 +++++++++++++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h | 120 ++++
11 files changed, 1778 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 289a45f47bb..2078e744340 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -718,6 +718,7 @@ F: arch/arm/include/asm/arch-sunxi/
F: arch/arm/mach-sunxi/
F: board/sunxi/
F: drivers/clk/sunxi/
+F: drivers/crypto/allwinner/
F: drivers/phy/allwinner/
F: drivers/pinctrl/sunxi/
F: drivers/video/sunxi/
diff --git a/arch/arm/dts/sunxi-u-boot.dtsi b/arch/arm/dts/sunxi-u-boot.dtsi
index e1a9a7f5d4c..c1dc7404bf8 100644
--- a/arch/arm/dts/sunxi-u-boot.dtsi
+++ b/arch/arm/dts/sunxi-u-boot.dtsi
@@ -19,6 +19,21 @@
};
};
+#ifdef CONFIG_SPL_CLK
+&ccu {
+ bootph-pre-ram;
+};
+
+&rtc {
+ bootph-pre-ram;
+};
+#endif
+
+#ifdef CONFIG_SPL_SUNXI_CE
+&crypto {
+ bootph-pre-ram;
+};
+#endif
/* Let U-Boot be the firmware layer that controls the watchdog. */
#ifdef CONFIG_MACH_SUN8I_R528
&wdt {
diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig
index 0d58e3910fe..d15a59f87ee 100644
--- a/drivers/crypto/Kconfig
+++ b/drivers/crypto/Kconfig
@@ -6,6 +6,8 @@ source "drivers/crypto/aes/Kconfig"
source "drivers/crypto/fsl/Kconfig"
+source "drivers/crypto/allwinner/Kconfig"
+
source "drivers/crypto/aspeed/Kconfig"
source "drivers/crypto/nuvoton/Kconfig"
diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile
index e4a4482b7f3..cbbd5dc1dbe 100644
--- a/drivers/crypto/Makefile
+++ b/drivers/crypto/Makefile
@@ -8,6 +8,7 @@ obj-y += aes/
obj-y += rsa_mod_exp/
obj-y += fsl/
obj-y += hash/
+obj-y += allwinner/
obj-y += aspeed/
obj-y += nuvoton/
obj-y += tegra/
diff --git a/drivers/crypto/allwinner/Kconfig b/drivers/crypto/allwinner/Kconfig
new file mode 100644
index 00000000000..9765b089e25
--- /dev/null
+++ b/drivers/crypto/allwinner/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+source "drivers/crypto/allwinner/sun8i-ce/Kconfig"
diff --git a/drivers/crypto/allwinner/Makefile b/drivers/crypto/allwinner/Makefile
new file mode 100644
index 00000000000..2dcae98bac9
--- /dev/null
+++ b/drivers/crypto/allwinner/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-y += sun8i-ce/
diff --git a/drivers/crypto/allwinner/sun8i-ce/Kconfig b/drivers/crypto/allwinner/sun8i-ce/Kconfig
new file mode 100644
index 00000000000..973c4f3af21
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/Kconfig
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+config SUNXI_CE
+ bool
+ depends on ARCH_SUNXI
+ depends on CLK && DM_RESET
+
+config SPL_SUNXI_CE
+ bool
+ depends on ARCH_SUNXI
+ depends on MACH_SUN50I_H6 || MACH_SUN50I_H616
+ depends on SPL_DM
+ depends on SPL_OF_CONTROL
+ select SPL_CLK
+ select SPL_CRYPTO
+ select SPL_DM_RESET
+
+config SUNXI_CE_AES
+ bool "Allwinner sunxi CE AES"
+ depends on ARCH_SUNXI
+ depends on DM_AES
+ depends on CLK && DM_RESET
+ select AES
+ select SUNXI_CE
+ help
+ Select this option to enable AES encryption and decryption using
+ the Crypto Engine found in Allwinner sunxi SoCs. The driver
+ supports software-provided AES-128, AES-192 and AES-256 keys.
+
+config SPL_SUNXI_CE_AES
+ bool "Allwinner sunxi CE AES in SPL"
+ depends on ARCH_SUNXI
+ depends on MACH_SUN50I_H6 || MACH_SUN50I_H616
+ depends on SPL_DM_AES
+ depends on SPL_OF_CONTROL
+ select SPL_CRYPTO
+ select SPL_SUNXI_CE
+ help
+ Select this option to enable AES decryption in SPL using the Crypto
+ Engine found in Allwinner H6 and H616 compatible SoCs. This can be
+ used to decrypt FIT images before loading U-Boot proper.
diff --git a/drivers/crypto/allwinner/sun8i-ce/Makefile b/drivers/crypto/allwinner/sun8i-ce/Makefile
new file mode 100644
index 00000000000..2a8778065b1
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-$(CONFIG_$(PHASE_)SUNXI_CE) += sun8i-ce-core.o
+obj-$(CONFIG_$(PHASE_)SUNXI_CE_AES) += sun8i-ce-aes.o
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c
new file mode 100644
index 00000000000..8dde4863642
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-aes.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#define LOG_CATEGORY UCLASS_AES
+
+#include <dm.h>
+#include <limits.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <u-boot/schedule.h>
+#include <uboot_aes.h>
+#include <asm/cache.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include "sun8i-ce.h"
+
+#define SUNXI_CE_ENCRYPTION 0
+#define SUNXI_CE_DECRYPTION BIT(8)
+
+#define SUNXI_CE_OP_ECB 0
+#define SUNXI_CE_OP_CBC BIT(8)
+
+#define SUNXI_CE_AES_KEY_128BIT 0
+#define SUNXI_CE_AES_KEY_192BIT 1
+#define SUNXI_CE_AES_KEY_256BIT 2
+
+#define SUNXI_CE_AES_TASK_SIZE (128 * 1024)
+#define SUNXI_CE_AES_TASK_BLOCKS \
+ (SUNXI_CE_AES_TASK_SIZE / AES_BLOCK_LENGTH)
+#define SUNXI_CE_AES_CHAIN_DEPTH 10
+#define SUNXI_CE_AES_MAX_LANES 2
+#define SUNXI_CE_AES_BANK_COUNT 2
+#define SUNXI_CE_AES_DUAL_MIN_BLOCKS \
+ (SUNXI_CE_AES_MAX_LANES * SUNXI_CE_AES_TASK_BLOCKS)
+#define SUNXI_CE_AES_REPACK_SIZE (64 * 1024)
+#define SUNXI_CE_AES_REPACK_BLOCKS \
+ (SUNXI_CE_AES_REPACK_SIZE / AES_BLOCK_LENGTH)
+#define SUNXI_CE_AES_NO_BANK SUNXI_CE_AES_BANK_COUNT
+
+static_assert(SUNXI_CE_AES_BANK_COUNT == 2);
+
+enum sunxi_aes_segment_type {
+ SUNXI_AES_SEGMENT_HEAD,
+ SUNXI_AES_SEGMENT_MIDDLE,
+ SUNXI_AES_SEGMENT_TAIL,
+ SUNXI_AES_SEGMENT_COUNT,
+};
+
+struct sunxi_aes_priv {
+ u8 key[AES256_KEY_LENGTH] __aligned(sizeof(u32));
+ u8 key_len;
+ u8 ce_key_size;
+};
+
+struct sunxi_aes_segment {
+ dma_addr_t dma;
+ u32 len;
+};
+
+struct sunxi_aes_bank_map {
+ u8 src_edges[2][ARCH_DMA_MINALIGN] __aligned(ARCH_DMA_MINALIGN);
+ u8 dst_edges[2][ARCH_DMA_MINALIGN] __aligned(ARCH_DMA_MINALIGN);
+ struct sunxi_ce_dma_buf src_edges_dma;
+ struct sunxi_ce_dma_buf src_middle_dma;
+ struct sunxi_ce_dma_buf dst_edges_dma;
+ struct sunxi_ce_dma_buf dst_middle_dma;
+ struct sunxi_aes_segment src_segments[SUNXI_AES_SEGMENT_COUNT];
+ struct sunxi_aes_segment dst_segments[SUNXI_AES_SEGMENT_COUNT];
+ u32 dst_head;
+ u32 dst_tail;
+};
+
+struct sunxi_aes_bank {
+ struct sunxi_ce_task tasks[SUNXI_CE_AES_CHAIN_DEPTH]
+ __aligned(ARCH_DMA_MINALIGN);
+ size_t offset;
+ u32 len;
+ /* Zero means free; otherwise every DMA mapping remains owned here. */
+ u32 task_count;
+ u8 ivs[SUNXI_CE_AES_CHAIN_DEPTH][AES_BLOCK_LENGTH]
+ __aligned(ARCH_DMA_MINALIGN);
+ struct sunxi_ce_dma_buf iv_dma;
+ struct sunxi_aes_bank_map data;
+};
+
+struct sunxi_aes_lane {
+ struct sunxi_aes_bank banks[SUNXI_CE_AES_BANK_COUNT];
+ /* Submitted bank, or SUNXI_CE_AES_NO_BANK while the lane is idle. */
+ u8 active_bank;
+};
+
+struct sunxi_aes_xfer {
+ struct sunxi_ce_session session;
+ dma_addr_t key_dma;
+ u8 *src;
+ u8 *dst;
+ size_t cursor;
+ u32 remaining;
+ u32 comm_ctl;
+ u32 sym_ctl;
+ u8 lane_count;
+ u8 next_iv[AES_BLOCK_LENGTH];
+ struct sunxi_aes_lane lanes[SUNXI_CE_AES_MAX_LANES];
+};
+
+static const u8 sunxi_aes_methods[SUNXI_CE_AES_MAX_LANES] = {
+ SUNXI_CE_METHOD_AES,
+ SUNXI_CE_METHOD_RAES,
+};
+
+static bool sunxi_aes_is_cbc(const struct sunxi_aes_xfer *xfer)
+{
+ return xfer->sym_ctl & SUNXI_CE_OP_CBC;
+}
+
+static bool sunxi_aes_is_decrypt(const struct sunxi_aes_xfer *xfer)
+{
+ return xfer->comm_ctl & SUNXI_CE_DECRYPTION;
+}
+
+static bool sunxi_aes_is_serial(const struct sunxi_aes_xfer *xfer)
+{
+ return sunxi_aes_is_cbc(xfer) && !sunxi_aes_is_decrypt(xfer);
+}
+
+static u32 sunxi_aes_other_bank(u32 bank_index)
+{
+ return bank_index ^ 1;
+}
+
+static int sunxi_ce_key_size(u32 key_bits)
+{
+ switch (key_bits) {
+ case AES128_KEY_LENGTH * 8:
+ return SUNXI_CE_AES_KEY_128BIT;
+ case AES192_KEY_LENGTH * 8:
+ return SUNXI_CE_AES_KEY_192BIT;
+ case AES256_KEY_LENGTH * 8:
+ return SUNXI_CE_AES_KEY_256BIT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static bool sunxi_aes_ranges_overlap(const u8 *src, const u8 *dst, size_t len)
+{
+ uintptr_t src_start = (uintptr_t)src;
+ uintptr_t dst_start = (uintptr_t)dst;
+
+ if (src_start < dst_start)
+ return dst_start - src_start < len;
+
+ return src_start - dst_start < len;
+}
+
+static u8 sunxi_aes_lane_count(struct sunxi_ce_priv *ce, bool cbc,
+ bool decrypt, u32 num_blocks)
+{
+ if (ce->variant->aes_engine_count > 1 && (!cbc || decrypt) &&
+ num_blocks >= SUNXI_CE_AES_DUAL_MIN_BLOCKS)
+ return SUNXI_CE_AES_MAX_LANES;
+
+ return 1;
+}
+
+static void sunxi_aes_edge_lengths(const u8 *buf, u32 len, u32 *head,
+ u32 *middle, u32 *tail)
+{
+ *head = (ARCH_DMA_MINALIGN -
+ ((uintptr_t)buf & (ARCH_DMA_MINALIGN - 1))) &
+ (ARCH_DMA_MINALIGN - 1);
+ *head = min(*head, len);
+ *middle = ALIGN_DOWN(len - *head, ARCH_DMA_MINALIGN);
+ *tail = len - *head - *middle;
+}
+
+static void sunxi_aes_release_bank_data(struct sunxi_aes_bank_map *map,
+ u8 *dst, u32 len,
+ bool commit_output)
+{
+ sunxi_ce_dma_unmap(&map->dst_middle_dma);
+ sunxi_ce_dma_unmap(&map->dst_edges_dma);
+ sunxi_ce_dma_unmap(&map->src_middle_dma);
+ sunxi_ce_dma_unmap(&map->src_edges_dma);
+
+ if (commit_output) {
+ if (map->dst_head)
+ memcpy(dst, map->dst_edges[0], map->dst_head);
+ if (map->dst_tail)
+ memcpy(dst + len - map->dst_tail,
+ map->dst_edges[1], map->dst_tail);
+ }
+
+ memset(map, 0, sizeof(*map));
+}
+
+static int sunxi_aes_map_bank_data(struct sunxi_ce_priv *ce,
+ struct sunxi_aes_bank_map *map,
+ u8 *src, u8 *dst, u32 len)
+{
+ u32 src_head, src_middle, src_tail;
+ u32 dst_head, dst_middle, dst_tail;
+ enum dma_data_direction src_dir;
+ bool in_place = src == dst;
+ int ret;
+
+ if (!IS_ALIGNED((uintptr_t)src, sizeof(u32)) ||
+ !IS_ALIGNED((uintptr_t)dst, sizeof(u32)) ||
+ !IS_ALIGNED(len, sizeof(u32)))
+ return -EINVAL;
+
+ memset(map, 0, sizeof(*map));
+ src_dir = in_place ? DMA_BIDIRECTIONAL : DMA_TO_DEVICE;
+
+ /* Independently refilled banks must not share DMA cache envelopes. */
+ sunxi_aes_edge_lengths(src, len, &src_head, &src_middle, &src_tail);
+ sunxi_aes_edge_lengths(dst, len, &dst_head, &dst_middle, &dst_tail);
+ map->dst_head = dst_head;
+ map->dst_tail = dst_tail;
+
+ if (src_head)
+ memcpy(map->src_edges[0], src, src_head);
+ if (src_tail)
+ memcpy(map->src_edges[1], src + len - src_tail, src_tail);
+ if (src_head || src_tail) {
+ ret = sunxi_ce_dma_map(ce, &map->src_edges_dma, map->src_edges,
+ sizeof(map->src_edges), DMA_TO_DEVICE);
+ if (ret)
+ return ret;
+ }
+
+ if (src_middle) {
+ ret = sunxi_ce_dma_map(ce, &map->src_middle_dma, src + src_head,
+ src_middle, src_dir);
+ if (ret)
+ return ret;
+ }
+
+ if (dst_head || dst_tail) {
+ ret = sunxi_ce_dma_map(ce, &map->dst_edges_dma, map->dst_edges,
+ sizeof(map->dst_edges), DMA_FROM_DEVICE);
+ if (ret)
+ return ret;
+ }
+
+ if (!in_place && dst_middle) {
+ ret = sunxi_ce_dma_map(ce, &map->dst_middle_dma, dst + dst_head,
+ dst_middle, DMA_FROM_DEVICE);
+ if (ret)
+ return ret;
+ }
+
+ map->src_segments[SUNXI_AES_SEGMENT_HEAD].dma = map->src_edges_dma.dma;
+ map->src_segments[SUNXI_AES_SEGMENT_HEAD].len = src_head;
+ map->src_segments[SUNXI_AES_SEGMENT_MIDDLE].dma = map->src_middle_dma.dma;
+ map->src_segments[SUNXI_AES_SEGMENT_MIDDLE].len = src_middle;
+ map->src_segments[SUNXI_AES_SEGMENT_TAIL].dma =
+ map->src_edges_dma.dma + ARCH_DMA_MINALIGN;
+ map->src_segments[SUNXI_AES_SEGMENT_TAIL].len = src_tail;
+
+ map->dst_segments[SUNXI_AES_SEGMENT_HEAD].dma = map->dst_edges_dma.dma;
+ map->dst_segments[SUNXI_AES_SEGMENT_HEAD].len = dst_head;
+ map->dst_segments[SUNXI_AES_SEGMENT_MIDDLE].dma =
+ in_place ? map->src_middle_dma.dma : map->dst_middle_dma.dma;
+ map->dst_segments[SUNXI_AES_SEGMENT_MIDDLE].len = dst_middle;
+ map->dst_segments[SUNXI_AES_SEGMENT_TAIL].dma =
+ map->dst_edges_dma.dma + ARCH_DMA_MINALIGN;
+ map->dst_segments[SUNXI_AES_SEGMENT_TAIL].len = dst_tail;
+
+ return 0;
+}
+
+static int sunxi_aes_fill_sg(struct sunxi_ce_priv *ce,
+ struct sunxi_ce_sginfo *sg,
+ const struct sunxi_aes_segment *segments,
+ u32 offset, u32 len)
+{
+ u32 end = offset + len;
+ u32 covered = 0;
+ u32 segment_offset = 0;
+ u8 i, sg_count = 0;
+
+ for (i = 0; i < SUNXI_AES_SEGMENT_COUNT; i++) {
+ u32 segment_end = segment_offset + segments[i].len;
+ u32 start = max(offset, segment_offset);
+ u32 stop = min(end, segment_end);
+ u32 part_len;
+ dma_addr_t dma;
+
+ if (start >= stop) {
+ segment_offset = segment_end;
+ continue;
+ }
+ if (sg_count >= SUNXI_CE_MAX_SG)
+ return -EINVAL;
+
+ part_len = stop - start;
+ if (!IS_ALIGNED(part_len, sizeof(u32)))
+ return -EINVAL;
+ dma = segments[i].dma + start - segment_offset;
+ sg[sg_count].addr = sunxi_ce_desc_dma_addr(ce, dma);
+ sg[sg_count].len = part_len / sizeof(u32);
+ covered += part_len;
+ sg_count++;
+ segment_offset = segment_end;
+ }
+
+ return covered == len ? 0 : -EINVAL;
+}
+
+static void sunxi_aes_init_task(struct sunxi_ce_priv *ce,
+ struct sunxi_ce_task *task,
+ dma_addr_t key, dma_addr_t iv,
+ u32 len, u32 comm_ctl, u32 sym_ctl)
+{
+ memset(task, 0, sizeof(*task));
+
+ task->t_common_ctl = comm_ctl;
+ task->t_sym_ctl = sym_ctl;
+ task->t_key = sunxi_ce_desc_dma_addr(ce, key);
+ if (iv)
+ task->t_iv = sunxi_ce_desc_dma_addr(ce, iv);
+ task->t_dlen = len;
+}
+
+static u32 sunxi_aes_fair_blocks(struct sunxi_aes_xfer *xfer,
+ u32 lanes_left)
+{
+ if (!xfer->remaining)
+ return 0;
+
+ return 1 + (xfer->remaining - 1) / lanes_left;
+}
+
+static void sunxi_aes_release_bank(struct sunxi_aes_xfer *xfer,
+ struct sunxi_aes_bank *bank,
+ bool commit_output)
+{
+ u8 *dst = xfer->dst + bank->offset;
+ u32 len = bank->len;
+
+ sunxi_aes_release_bank_data(&bank->data, dst, len, commit_output);
+ sunxi_ce_dma_unmap(&bank->iv_dma);
+
+ if (commit_output && sunxi_aes_is_serial(xfer))
+ memcpy(xfer->next_iv, dst + len - AES_BLOCK_LENGTH,
+ AES_BLOCK_LENGTH);
+
+ bank->offset = 0;
+ bank->len = 0;
+ bank->task_count = 0;
+}
+
+static void sunxi_aes_release_all_banks(struct sunxi_aes_xfer *xfer)
+{
+ u32 bank_index, lane_index;
+
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++) {
+ struct sunxi_aes_lane *lane = &xfer->lanes[lane_index];
+
+ for (bank_index = 0; bank_index < SUNXI_CE_AES_BANK_COUNT;
+ bank_index++)
+ sunxi_aes_release_bank(xfer, &lane->banks[bank_index],
+ false);
+ lane->active_bank = SUNXI_CE_AES_NO_BANK;
+ }
+}
+
+static int sunxi_aes_prepare_bank(struct sunxi_aes_xfer *xfer, u32 lane_index,
+ u32 bank_index, u32 max_blocks)
+{
+ struct sunxi_aes_lane *lane = &xfer->lanes[lane_index];
+ struct sunxi_aes_bank *bank = &lane->banks[bank_index];
+ struct sunxi_ce_priv *ce = xfer->session.ce;
+ u32 chain_depth = sunxi_aes_is_serial(xfer) ? 1 :
+ SUNXI_CE_AES_CHAIN_DEPTH;
+ u32 blocks, offset = 0;
+ u32 task_index;
+ int ret;
+
+ if (!xfer->remaining)
+ return 0;
+ if (lane->active_bank == bank_index || bank->task_count)
+ return -EBUSY;
+
+ blocks = min(xfer->remaining, max_blocks);
+ blocks = min_t(u32, blocks,
+ chain_depth * SUNXI_CE_AES_TASK_BLOCKS);
+ bank->offset = xfer->cursor;
+ bank->len = blocks * AES_BLOCK_LENGTH;
+ bank->task_count = DIV_ROUND_UP(blocks, SUNXI_CE_AES_TASK_BLOCKS);
+
+ if (sunxi_aes_is_cbc(xfer)) {
+ for (task_index = 0; task_index < bank->task_count; task_index++) {
+ u32 task_len = min_t(u32, bank->len - offset,
+ SUNXI_CE_AES_TASK_SIZE);
+
+ /* Snapshot before an in-place task can overwrite ciphertext. */
+ memcpy(bank->ivs[task_index], xfer->next_iv,
+ AES_BLOCK_LENGTH);
+ if (sunxi_aes_is_decrypt(xfer))
+ memcpy(xfer->next_iv,
+ xfer->src + bank->offset + offset + task_len -
+ AES_BLOCK_LENGTH, AES_BLOCK_LENGTH);
+ offset += task_len;
+ }
+
+ ret = sunxi_ce_dma_map(ce, &bank->iv_dma, bank->ivs,
+ bank->task_count * AES_BLOCK_LENGTH,
+ DMA_TO_DEVICE);
+ if (ret)
+ goto out_release;
+ }
+
+ ret = sunxi_aes_map_bank_data(ce, &bank->data,
+ xfer->src + bank->offset,
+ xfer->dst + bank->offset, bank->len);
+ if (ret)
+ goto out_release;
+
+ for (task_index = 0, offset = 0; task_index < bank->task_count;
+ task_index++) {
+ struct sunxi_ce_task *task = &bank->tasks[task_index];
+ u32 task_len = min_t(u32, bank->len - offset,
+ SUNXI_CE_AES_TASK_SIZE);
+ dma_addr_t iv = sunxi_aes_is_cbc(xfer) ? bank->iv_dma.dma +
+ task_index * AES_BLOCK_LENGTH : 0;
+
+ sunxi_aes_init_task(ce, task, xfer->key_dma, iv,
+ task_len, xfer->comm_ctl, xfer->sym_ctl);
+ ret = sunxi_aes_fill_sg(ce, task->t_src,
+ bank->data.src_segments,
+ offset, task_len);
+ if (ret)
+ goto out_release;
+ ret = sunxi_aes_fill_sg(ce, task->t_dst,
+ bank->data.dst_segments,
+ offset, task_len);
+ if (ret)
+ goto out_release;
+
+ offset += task_len;
+ }
+ xfer->cursor += bank->len;
+ xfer->remaining -= blocks;
+
+ return 0;
+
+out_release:
+ sunxi_aes_release_bank(xfer, bank, false);
+
+ return ret;
+}
+
+static int sunxi_aes_submit_bank(struct sunxi_aes_xfer *xfer, u32 lane_index,
+ u32 bank_index)
+{
+ struct sunxi_aes_lane *lane = &xfer->lanes[lane_index];
+ struct sunxi_aes_bank *bank = &lane->banks[bank_index];
+ int ret;
+
+ if (!bank->task_count)
+ return 0;
+ if (lane->active_bank != SUNXI_CE_AES_NO_BANK)
+ return -EINVAL;
+
+ ret = sunxi_ce_session_submit_chain(&xfer->session, lane_index,
+ sunxi_aes_methods[lane_index],
+ bank->tasks, bank->task_count);
+ if (!ret)
+ lane->active_bank = bank_index;
+
+ return ret;
+}
+
+static int sunxi_aes_service(struct sunxi_aes_xfer *xfer, u32 completed_mask)
+{
+ u8 completed_banks[SUNXI_CE_AES_MAX_LANES] = {
+ SUNXI_CE_AES_NO_BANK,
+ SUNXI_CE_AES_NO_BANK,
+ };
+ u32 lane_index, refills_left = 0;
+ int ret;
+
+ /* Snapshot completions before changing or releasing any bank state. */
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++) {
+ struct sunxi_aes_lane *lane = &xfer->lanes[lane_index];
+
+ if (!(completed_mask & SUNXI_CE_CHAN_MASK(lane_index)))
+ continue;
+ if (lane->active_bank == SUNXI_CE_AES_NO_BANK)
+ return -EINVAL;
+
+ completed_banks[lane_index] = lane->active_bank;
+ lane->active_bank = SUNXI_CE_AES_NO_BANK;
+ refills_left++;
+ }
+
+ /* Submit every prepared peer before doing CPU-side continuation work. */
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++) {
+ u32 peer_bank;
+
+ if (completed_banks[lane_index] == SUNXI_CE_AES_NO_BANK)
+ continue;
+ peer_bank = sunxi_aes_other_bank(completed_banks[lane_index]);
+ ret = sunxi_aes_submit_bank(xfer, lane_index, peer_bank);
+ if (ret)
+ return ret;
+ }
+
+ /* Retire completed mappings and commit their edge cache lines. */
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++) {
+ struct sunxi_aes_bank *bank;
+
+ if (completed_banks[lane_index] == SUNXI_CE_AES_NO_BANK)
+ continue;
+ bank = &xfer->lanes[lane_index].banks[completed_banks[lane_index]];
+ sunxi_aes_release_bank(xfer, bank, true);
+ }
+
+ /* Refill each newly free bank, then submit it if its lane is idle. */
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++) {
+ struct sunxi_aes_lane *lane = &xfer->lanes[lane_index];
+ u32 completed_bank = completed_banks[lane_index];
+
+ if (completed_bank == SUNXI_CE_AES_NO_BANK)
+ continue;
+ if (xfer->remaining) {
+ u32 max_blocks = sunxi_aes_fair_blocks(xfer,
+ refills_left);
+
+ ret = sunxi_aes_prepare_bank(xfer, lane_index,
+ completed_bank,
+ max_blocks);
+ if (ret)
+ return ret;
+ }
+ refills_left--;
+ if (lane->active_bank == SUNXI_CE_AES_NO_BANK) {
+ ret = sunxi_aes_submit_bank(xfer, lane_index,
+ completed_bank);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int sunxi_aes_run_scheduler(struct sunxi_aes_xfer *xfer, u8 *src,
+ u8 *dst, u32 num_blocks)
+{
+ u32 bank_index, lane_index;
+ int completed, ret;
+
+ xfer->src = src;
+ xfer->dst = dst;
+ xfer->cursor = 0;
+ xfer->remaining = num_blocks;
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++)
+ xfer->lanes[lane_index].active_bank = SUNXI_CE_AES_NO_BANK;
+
+ /*
+ * Prime bank 0 and submit it, then prepare bank 1 as its peer:
+ *
+ * active_bank --> submitted chain
+ * other bank --> prepared chain, or free when task_count is zero
+ */
+ for (bank_index = 0;
+ bank_index < (sunxi_aes_is_serial(xfer) ? 1 :
+ SUNXI_CE_AES_BANK_COUNT);
+ bank_index++) {
+ for (lane_index = 0; lane_index < xfer->lane_count; lane_index++) {
+ u32 lanes_left = xfer->lane_count - lane_index;
+ u32 max_blocks;
+
+ if (!xfer->remaining)
+ break;
+ max_blocks = sunxi_aes_fair_blocks(xfer, lanes_left);
+ ret = sunxi_aes_prepare_bank(xfer, lane_index, bank_index,
+ max_blocks);
+ if (ret)
+ return ret;
+ if (!bank_index) {
+ ret = sunxi_aes_submit_bank(xfer, lane_index,
+ bank_index);
+ if (ret)
+ return ret;
+ }
+ }
+ }
+
+ while (sunxi_ce_session_busy(&xfer->session)) {
+ completed = sunxi_ce_session_poll(&xfer->session);
+ if (completed < 0)
+ return completed;
+ if (!completed) {
+ schedule();
+ continue;
+ }
+
+ ret = sunxi_aes_service(xfer, completed);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sunxi_aes_run(struct udevice *dev, u8 *iv, u8 *src, u8 *dst,
+ u32 num_blocks, u32 aes_mode, bool decrypt)
+{
+ struct sunxi_aes_priv *priv = dev_get_priv(dev);
+ struct sunxi_ce_priv *ce = dev_get_priv(dev_get_parent(dev));
+ struct sunxi_ce_dma_buf key_dma = { };
+ struct sunxi_aes_xfer *xfer;
+ u8 *repack = NULL;
+ u8 lane_count;
+ u32 comm_ctl, sym_ctl;
+ bool cbc = aes_mode == SUNXI_CE_OP_CBC;
+ bool word_addressable;
+ size_t total_len;
+ int ret;
+
+ if (!priv->key_len)
+ return -EINVAL;
+ if (!num_blocks)
+ return 0;
+ if (!src || !dst)
+ return -EINVAL;
+ if (cbc && !iv)
+ return -EINVAL;
+
+ if (num_blocks > SIZE_MAX / AES_BLOCK_LENGTH)
+ return -EOVERFLOW;
+ total_len = (size_t)num_blocks * AES_BLOCK_LENGTH;
+ if (total_len > UINTPTR_MAX - (uintptr_t)src ||
+ total_len > UINTPTR_MAX - (uintptr_t)dst)
+ return -EOVERFLOW;
+ if (src != dst && sunxi_aes_ranges_overlap(src, dst, total_len))
+ return -EINVAL;
+
+ xfer = malloc_cache_aligned(sizeof(*xfer));
+ if (!xfer)
+ return -ENOMEM;
+ memset(xfer, 0, sizeof(*xfer));
+ if (cbc)
+ memcpy(xfer->next_iv, iv, AES_BLOCK_LENGTH);
+
+ ret = sunxi_ce_dma_map(ce, &key_dma, priv->key, priv->key_len,
+ DMA_TO_DEVICE);
+ if (ret)
+ goto out_free;
+
+ comm_ctl = decrypt ? SUNXI_CE_DECRYPTION : SUNXI_CE_ENCRYPTION;
+ sym_ctl = priv->ce_key_size | aes_mode;
+ word_addressable = IS_ALIGNED((uintptr_t)src, sizeof(u32)) &&
+ IS_ALIGNED((uintptr_t)dst, sizeof(u32));
+ lane_count = word_addressable ?
+ sunxi_aes_lane_count(ce, cbc, decrypt, num_blocks) : 1;
+ xfer->key_dma = key_dma.dma;
+ xfer->comm_ctl = comm_ctl;
+ xfer->sym_ctl = sym_ctl;
+ xfer->lane_count = lane_count;
+
+ if (!word_addressable) {
+ repack = memalign(ARCH_DMA_MINALIGN, SUNXI_CE_AES_REPACK_SIZE);
+ if (!repack) {
+ ret = -ENOMEM;
+ goto out_unmap_key;
+ }
+ }
+
+ ret = sunxi_ce_session_begin(ce, GENMASK(lane_count - 1, 0),
+ &xfer->session);
+ if (ret)
+ goto out_unmap_key;
+
+ if (word_addressable) {
+ ret = sunxi_aes_run_scheduler(xfer, src, dst, num_blocks);
+ goto out_close;
+ }
+
+ while (num_blocks) {
+ u32 blocks = min_t(u32, num_blocks,
+ SUNXI_CE_AES_REPACK_BLOCKS);
+ u32 len = blocks * AES_BLOCK_LENGTH;
+
+ memcpy(repack, src, len);
+ ret = sunxi_aes_run_scheduler(xfer, repack, repack, blocks);
+ if (ret)
+ goto out_close;
+ memcpy(dst, repack, len);
+
+ num_blocks -= blocks;
+ src += len;
+ dst += len;
+ }
+
+ ret = 0;
+
+out_close:
+ /* Stop every lane before releasing any DMA-owned memory. */
+ ret = sunxi_ce_session_close(&xfer->session, ret);
+ sunxi_aes_release_all_banks(xfer);
+out_unmap_key:
+ sunxi_ce_dma_unmap(&key_dma);
+out_free:
+ free(repack);
+ free(xfer);
+
+ return ret;
+}
+
+static int sunxi_aes_available_key_slots(struct udevice *dev)
+{
+ return 1;
+}
+
+static int sunxi_aes_get_software_key_slot(struct udevice *dev)
+{
+ return 0;
+}
+
+static int sunxi_aes_select_key_slot(struct udevice *dev, u32 key_size,
+ u8 slot)
+{
+ struct sunxi_aes_priv *priv = dev_get_priv(dev);
+ int ce_key_size;
+
+ if (slot)
+ return -EINVAL;
+
+ ce_key_size = sunxi_ce_key_size(key_size);
+ if (ce_key_size < 0)
+ return ce_key_size;
+
+ priv->key_len = key_size / 8;
+ priv->ce_key_size = ce_key_size;
+
+ return 0;
+}
+
+static int sunxi_aes_set_key_for_key_slot(struct udevice *dev, u32 key_size,
+ u8 *key, u8 slot)
+{
+ struct sunxi_aes_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (!key)
+ return -EINVAL;
+
+ ret = sunxi_aes_select_key_slot(dev, key_size, slot);
+ if (ret)
+ return ret;
+
+ memcpy(priv->key, key, key_size / 8);
+
+ return 0;
+}
+
+static int sunxi_aes_ecb_encrypt(struct udevice *dev, u8 *src, u8 *dst,
+ u32 num_blocks)
+{
+ return sunxi_aes_run(dev, NULL, src, dst, num_blocks,
+ SUNXI_CE_OP_ECB, false);
+}
+
+static int sunxi_aes_ecb_decrypt(struct udevice *dev, u8 *src, u8 *dst,
+ u32 num_blocks)
+{
+ return sunxi_aes_run(dev, NULL, src, dst, num_blocks,
+ SUNXI_CE_OP_ECB, true);
+}
+
+static int sunxi_aes_cbc_encrypt(struct udevice *dev, u8 *iv, u8 *src,
+ u8 *dst, u32 num_blocks)
+{
+ return sunxi_aes_run(dev, iv, src, dst, num_blocks,
+ SUNXI_CE_OP_CBC, false);
+}
+
+static int sunxi_aes_cbc_decrypt(struct udevice *dev, u8 *iv, u8 *src,
+ u8 *dst, u32 num_blocks)
+{
+ return sunxi_aes_run(dev, iv, src, dst, num_blocks,
+ SUNXI_CE_OP_CBC, true);
+}
+
+static const struct aes_ops sunxi_aes_ops = {
+ .available_key_slots = sunxi_aes_available_key_slots,
+ .get_software_key_slot = sunxi_aes_get_software_key_slot,
+ .select_key_slot = sunxi_aes_select_key_slot,
+ .set_key_for_key_slot = sunxi_aes_set_key_for_key_slot,
+ .aes_ecb_encrypt = sunxi_aes_ecb_encrypt,
+ .aes_ecb_decrypt = sunxi_aes_ecb_decrypt,
+ .aes_cbc_encrypt = sunxi_aes_cbc_encrypt,
+ .aes_cbc_decrypt = sunxi_aes_cbc_decrypt,
+};
+
+U_BOOT_DRIVER(sun8i_ce_aes) = {
+ .name = "sun8i-ce-aes",
+ .id = UCLASS_AES,
+ .ops = &sunxi_aes_ops,
+ .priv_auto = sizeof(struct sunxi_aes_priv),
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
new file mode 100644
index 00000000000..f22d534caea
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#include <cpu_func.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <dm/lists.h>
+#include <errno.h>
+#include <time.h>
+#include <u-boot/schedule.h>
+#include <vsprintf.h>
+#include <asm/arch/cpu.h>
+#include <asm/cache.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/string.h>
+#include "sun8i-ce.h"
+
+#define SUNXI_CE_TDQ 0x00
+#define SUNXI_CE_ICR 0x08
+#define SUNXI_CE_ISR 0x0c
+#define SUNXI_CE_TLR 0x10
+#define SUNXI_CE_ESR 0x18
+#define SUNXI_CE_SCSA 0x24
+#define SUNXI_CE_SCDA 0x28
+#define SUNXI_CE_HCSA 0x34
+#define SUNXI_CE_HCDA 0x38
+#define SUNXI_CE_ACSA 0x44
+#define SUNXI_CE_ACDA 0x48
+#define SUNXI_CE_XCSA 0x54
+#define SUNXI_CE_XCDA 0x58
+
+#define SUNXI_CE_ERR_ALGO_NOTSUP BIT(0)
+#define SUNXI_CE_ERR_DATALEN BIT(1)
+#define SUNXI_CE_ERR_KEYSRAM BIT(2)
+#define SUNXI_CE_ERR_ADDR_INVALID BIT(5)
+#define SUNXI_CE_ERR_KEYLADDER BIT(6)
+#define SUNXI_CE_TASK_START BIT(0)
+#define SUNXI_CE_METHOD_MASK GENMASK(6, 0)
+#define SUNXI_CE_TLR_METHOD_SHIFT 8
+#define SUNXI_CE_WORD_SHIFT 2
+#define SUNXI_CE_TIMEOUT_US 3000000
+
+#define SUN50I_H6_CCU_CE_CLK 0x680
+#define SUN50I_H6_CCU_CE_CLK_SRC_MASK BIT(24)
+#define SUN50I_H6_CCU_CE_CLK_N_MASK GENMASK(9, 8)
+#define SUN50I_H6_CCU_CE_CLK_M_MASK GENMASK(3, 0)
+#define SUN50I_H6_CCU_CE_CLK_GATE BIT(31)
+#define SUN50I_H6_CCU_CE_CLK_MASK (SUN50I_H6_CCU_CE_CLK_GATE | \
+ SUN50I_H6_CCU_CE_CLK_SRC_MASK | \
+ SUN50I_H6_CCU_CE_CLK_N_MASK | \
+ SUN50I_H6_CCU_CE_CLK_M_MASK)
+/* PLL_PERI0(2X) / 4 = 300 MHz */
+#define SUN50I_H616_CCU_CE_CLK_M 3
+
+static int sunxi_ce_reset(struct sunxi_ce_priv *priv);
+
+struct sunxi_ce_channel_route {
+ u16 src_reg;
+ u16 dst_reg;
+};
+
+static const struct sunxi_ce_channel_route
+sunxi_ce_channel_routes[SUNXI_CE_MAX_CHANS] = {
+ [SUNXI_CE_CHANNEL_AES] = {
+ .src_reg = SUNXI_CE_SCSA,
+ .dst_reg = SUNXI_CE_SCDA,
+ },
+ [SUNXI_CE_CHANNEL_RAES] = {
+ .src_reg = SUNXI_CE_XCSA,
+ .dst_reg = SUNXI_CE_XCDA,
+ },
+ [SUNXI_CE_CHANNEL_HASH] = {
+ .src_reg = SUNXI_CE_HCSA,
+ .dst_reg = SUNXI_CE_HCDA,
+ },
+ [SUNXI_CE_CHANNEL_ASYM] = {
+ .src_reg = SUNXI_CE_ACSA,
+ .dst_reg = SUNXI_CE_ACDA,
+ },
+};
+
+u32 sunxi_ce_desc_dma_addr(struct sunxi_ce_priv *priv, dma_addr_t addr)
+{
+ if (priv->variant->needs_word_addresses)
+ addr >>= SUNXI_CE_WORD_SHIFT;
+
+ return (u32)addr;
+}
+
+static void sunxi_ce_flush(void *buf, size_t len)
+{
+ ulong start = ALIGN_DOWN((ulong)buf, ARCH_DMA_MINALIGN);
+ ulong end = ALIGN((ulong)buf + len, ARCH_DMA_MINALIGN);
+
+ flush_dcache_range(start, end);
+}
+
+static int sunxi_ce_validate_dma_range(struct sunxi_ce_priv *priv,
+ dma_addr_t addr, size_t len)
+{
+ dma_addr_t last;
+
+ if (!IS_ALIGNED(addr, sizeof(u32)))
+ return -EINVAL;
+ if (!len)
+ return 0;
+ if (len - 1 > (dma_addr_t)-1 - addr)
+ return -EOVERFLOW;
+
+ last = addr + len - 1;
+ if (priv->variant->needs_word_addresses)
+ last >>= SUNXI_CE_WORD_SHIFT;
+
+ return last > U32_MAX ? -ERANGE : 0;
+}
+
+int sunxi_ce_dma_map(struct sunxi_ce_priv *priv,
+ struct sunxi_ce_dma_buf *map, void *buf, size_t len,
+ enum dma_data_direction dir)
+{
+ ulong addr, map_start, map_end;
+ dma_addr_t base;
+ int ret;
+
+ if (!priv || !map || (!buf && len) || map->map_len)
+ return -EINVAL;
+ if (dir != DMA_TO_DEVICE && dir != DMA_FROM_DEVICE &&
+ dir != DMA_BIDIRECTIONAL)
+ return -EINVAL;
+ if (!len)
+ return 0;
+ if (dir != DMA_TO_DEVICE &&
+ (!IS_ALIGNED((ulong)buf, ARCH_DMA_MINALIGN) ||
+ !IS_ALIGNED(len, ARCH_DMA_MINALIGN)))
+ return -EINVAL;
+
+ addr = (ulong)buf;
+ if (len > ULONG_MAX - addr)
+ return -EOVERFLOW;
+ if (addr + len > ULONG_MAX - (ARCH_DMA_MINALIGN - 1))
+ return -EOVERFLOW;
+ map_start = ALIGN_DOWN(addr, ARCH_DMA_MINALIGN);
+ map_end = ALIGN(addr + len, ARCH_DMA_MINALIGN);
+
+ base = dma_map_single((void *)map_start, map_end - map_start, dir);
+ if (dma_mapping_error(NULL, base))
+ return -EIO;
+
+ map->base = base;
+ map->dma = base + addr - map_start;
+ map->map_len = map_end - map_start;
+ map->dir = dir;
+ ret = sunxi_ce_validate_dma_range(priv, map->dma, len);
+ if (ret) {
+ sunxi_ce_dma_unmap(map);
+ return ret;
+ }
+
+ return 0;
+}
+
+void sunxi_ce_dma_unmap(struct sunxi_ce_dma_buf *map)
+{
+ if (!map || !map->map_len)
+ return;
+
+ dma_unmap_single(map->base, map->map_len, map->dir);
+ memset(map, 0, sizeof(*map));
+}
+
+static void sunxi_ce_print_error(u32 err)
+{
+ printf("CE ERROR: %#x\n", err);
+ if (err & SUNXI_CE_ERR_ALGO_NOTSUP)
+ printf("CE ERROR: algorithm not supported\n");
+ if (err & SUNXI_CE_ERR_DATALEN)
+ printf("CE ERROR: data length error\n");
+ if (err & SUNXI_CE_ERR_KEYSRAM)
+ printf("CE ERROR: keysram access error for AES\n");
+ if (err & SUNXI_CE_ERR_ADDR_INVALID)
+ printf("CE ERROR: address invalid\n");
+ if (err & SUNXI_CE_ERR_KEYLADDER)
+ 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;
+}
+
+static u32 sunxi_ce_error_mask(u32 channel_mask)
+{
+ u32 error_mask = 0;
+ u32 chan;
+
+ for (chan = 0; chan < SUNXI_CE_MAX_CHANS; chan++) {
+ if (channel_mask & SUNXI_CE_CHAN_MASK(chan))
+ error_mask |= SUNXI_CE_CHAN_ERR_MASK(chan);
+ }
+
+ return error_mask;
+}
+
+static int sunxi_ce_prepare_channels(struct sunxi_ce_priv *priv,
+ u32 channel_mask)
+{
+ u32 error_mask, val;
+ int ret;
+
+ error_mask = sunxi_ce_error_mask(channel_mask);
+
+ val = readl(priv->base + SUNXI_CE_ICR);
+ writel(val | channel_mask, priv->base + SUNXI_CE_ICR);
+ writel(channel_mask, priv->base + SUNXI_CE_ISR);
+ writel(error_mask, priv->base + SUNXI_CE_ESR);
+ ret = sunxi_ce_wait(priv->base + SUNXI_CE_ISR, channel_mask, 0);
+ if (ret) {
+ printf("%s: timeout waiting for stale interrupt\n", __func__);
+ clrbits_le32(priv->base + SUNXI_CE_ICR, channel_mask);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sunxi_ce_submit_task(struct sunxi_ce_priv *priv,
+ dma_addr_t task_dma, u32 method)
+{
+ u32 load = (method << SUNXI_CE_TLR_METHOD_SHIFT) |
+ SUNXI_CE_TASK_START;
+ int ret;
+
+ ret = sunxi_ce_wait(priv->base + SUNXI_CE_TLR,
+ SUNXI_CE_TASK_START, 0);
+ if (ret) {
+ printf("%s: timeout waiting for task launcher\n", __func__);
+ return ret;
+ }
+
+ writel(sunxi_ce_desc_dma_addr(priv, task_dma),
+ priv->base + SUNXI_CE_TDQ);
+ /* Be sure all data is written before enabling the task. */
+ wmb();
+ writel(load, priv->base + SUNXI_CE_TLR);
+ ret = sunxi_ce_wait(priv->base + SUNXI_CE_TLR,
+ SUNXI_CE_TASK_START, 0);
+ if (ret)
+ printf("%s: timeout registering task\n", __func__);
+
+ return ret;
+}
+
+static int sunxi_ce_ack_channel(struct sunxi_ce_priv *priv, u32 chan)
+{
+ u32 channel_mask, error_mask, err;
+
+ channel_mask = SUNXI_CE_CHAN_MASK(chan);
+ writel(channel_mask, priv->base + SUNXI_CE_ISR);
+ error_mask = SUNXI_CE_CHAN_ERR_MASK(chan);
+ err = readl(priv->base + SUNXI_CE_ESR) & error_mask;
+ writel(error_mask, priv->base + SUNXI_CE_ESR);
+ if (err) {
+ sunxi_ce_print_error(err >> (chan * 8));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sunxi_ce_wait_channel_idle(struct sunxi_ce_priv *priv, u32 chan)
+{
+ const struct sunxi_ce_channel_route *route;
+ int ret;
+
+ if (chan >= SUNXI_CE_MAX_CHANS)
+ return -EINVAL;
+ route = &sunxi_ce_channel_routes[chan];
+
+ ret = sunxi_ce_wait(priv->base + route->src_reg, ~0U, 0);
+ if (!ret)
+ ret = sunxi_ce_wait(priv->base + route->dst_reg, ~0U, 0);
+ if (ret)
+ printf("%s: timeout waiting for channel %u DMA idle\n",
+ __func__, chan);
+
+ return ret;
+}
+
+static int sunxi_ce_wait_channels_idle(struct sunxi_ce_priv *priv,
+ u32 channel_mask)
+{
+ u32 chan;
+ int cleanup_ret, ret = 0;
+
+ for (chan = 0; chan < SUNXI_CE_MAX_CHANS; chan++) {
+ if (!(channel_mask & SUNXI_CE_CHAN_MASK(chan)))
+ continue;
+
+ cleanup_ret = sunxi_ce_wait_channel_idle(priv, chan);
+ if (cleanup_ret && !ret)
+ ret = cleanup_ret;
+ }
+
+ return ret;
+}
+
+static int sunxi_ce_clear_errors(struct sunxi_ce_priv *priv, u32 channel_mask)
+{
+ u32 error_mask, err;
+
+ error_mask = sunxi_ce_error_mask(channel_mask);
+ err = readl(priv->base + SUNXI_CE_ESR) & error_mask;
+ writel(error_mask, priv->base + SUNXI_CE_ESR);
+
+ if (err) {
+ u32 chan;
+
+ for (chan = 0; chan < SUNXI_CE_MAX_CHANS; chan++) {
+ u32 chan_err = (err >> (chan * 8)) & 0xff;
+
+ if (chan_err)
+ sunxi_ce_print_error(chan_err);
+ }
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sunxi_ce_session_check(struct sunxi_ce_session *session)
+{
+ if (!session || !session->ce)
+ return -EINVAL;
+ if (session->ce->active_session != session)
+ return -ECANCELED;
+
+ return 0;
+}
+
+int sunxi_ce_session_begin(struct sunxi_ce_priv *priv, u32 channel_mask,
+ struct sunxi_ce_session *session)
+{
+ u32 valid_channels = GENMASK(SUNXI_CE_MAX_CHANS - 1, 0);
+ int ret;
+
+ if (!priv || !session || !channel_mask ||
+ channel_mask & ~valid_channels)
+ return -EINVAL;
+ if (priv->active_session)
+ return -EBUSY;
+
+ memset(session, 0, sizeof(*session));
+ session->ce = priv;
+ session->channel_mask = channel_mask;
+ priv->active_session = session;
+
+ ret = sunxi_ce_prepare_channels(priv, channel_mask);
+ if (ret) {
+ priv->active_session = NULL;
+ session->ce = NULL;
+ }
+
+ return ret;
+}
+
+int sunxi_ce_session_submit_chain(struct sunxi_ce_session *session,
+ u32 channel, u32 method,
+ struct sunxi_ce_task *tasks, u32 task_count)
+{
+ dma_addr_t tasks_dma;
+ size_t tasks_len;
+ u32 channel_mask;
+ u32 i;
+ int ret;
+
+ ret = sunxi_ce_session_check(session);
+ if (ret)
+ return ret;
+ if (channel >= SUNXI_CE_MAX_CHANS || method & ~SUNXI_CE_METHOD_MASK)
+ return -EINVAL;
+ channel_mask = SUNXI_CE_CHAN_MASK(channel);
+ if (!(session->channel_mask & channel_mask))
+ return -EINVAL;
+ if (session->inflight_mask & channel_mask)
+ return -EBUSY;
+ if (!tasks || !task_count)
+ return -EINVAL;
+ if (task_count > SIZE_MAX / sizeof(*tasks))
+ return -EOVERFLOW;
+ tasks_len = task_count * sizeof(*tasks);
+ tasks_dma = virt_to_phys(tasks);
+ ret = sunxi_ce_validate_dma_range(session->ce, tasks_dma, tasks_len);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < task_count; i++) {
+ tasks[i].t_id = channel;
+ tasks[i].t_common_ctl &= ~(SUNXI_CE_METHOD_MASK |
+ SUNXI_CE_COMM_INT);
+ tasks[i].t_common_ctl |= method;
+ if (i + 1 == task_count)
+ tasks[i].t_common_ctl |= SUNXI_CE_COMM_INT;
+ tasks[i].next = i + 1 < task_count ?
+ sunxi_ce_desc_dma_addr(session->ce,
+ tasks_dma +
+ (i + 1) * sizeof(*tasks)) : 0;
+ }
+
+ sunxi_ce_flush(tasks, tasks_len);
+
+ ret = sunxi_ce_submit_task(session->ce, tasks_dma, method);
+ if (!ret) {
+ session->inflight_mask |= channel_mask;
+ session->deadline[channel] = timer_get_us() + SUNXI_CE_TIMEOUT_US;
+ }
+
+ return ret;
+}
+
+int sunxi_ce_session_poll(struct sunxi_ce_session *session)
+{
+ unsigned long now;
+ u32 channel_mask, completed, pending;
+ unsigned int chan;
+ int ret;
+
+ ret = sunxi_ce_session_check(session);
+ if (ret)
+ return ret;
+
+ completed = readl(session->ce->base + SUNXI_CE_ISR) &
+ session->inflight_mask;
+ pending = completed;
+ while (pending) {
+ chan = __ffs(pending);
+ channel_mask = SUNXI_CE_CHAN_MASK(chan);
+ pending &= ~channel_mask;
+
+ ret = sunxi_ce_ack_channel(session->ce, chan);
+ if (ret)
+ return ret;
+ ret = sunxi_ce_wait_channel_idle(session->ce, chan);
+ if (ret)
+ return ret;
+ session->inflight_mask &= ~channel_mask;
+ session->deadline[chan] = 0;
+ }
+
+ if (completed) {
+ /* A chain-tail completion orders every preceding output DMA. */
+ rmb();
+ }
+
+ now = timer_get_us();
+ pending = session->inflight_mask;
+ while (pending) {
+ chan = __ffs(pending);
+ channel_mask = SUNXI_CE_CHAN_MASK(chan);
+ pending &= ~channel_mask;
+ if (!time_after(now, session->deadline[chan]))
+ continue;
+
+ /* Do not report a timeout for a completion racing this snapshot. */
+ if (readl(session->ce->base + SUNXI_CE_ISR) & channel_mask)
+ continue;
+
+ printf("%s: DMA timeout on channel %u\n", __func__, chan);
+ return -ETIMEDOUT;
+ }
+
+ return completed;
+}
+
+static int sunxi_ce_session_wait(struct sunxi_ce_session *session)
+{
+ int completed, ret;
+
+ ret = sunxi_ce_session_check(session);
+ if (ret)
+ return ret;
+
+ while (session->inflight_mask) {
+ completed = sunxi_ce_session_poll(session);
+ if (completed < 0)
+ return completed;
+ if (!completed)
+ schedule();
+ }
+
+ return 0;
+}
+
+int sunxi_ce_session_run_chain_or_close(struct sunxi_ce_session *session,
+ u32 channel, u32 method,
+ struct sunxi_ce_task *tasks,
+ u32 task_count)
+{
+ int ret;
+
+ ret = sunxi_ce_session_submit_chain(session, channel, method, tasks,
+ task_count);
+ if (!ret)
+ ret = sunxi_ce_session_wait(session);
+ if (ret)
+ return sunxi_ce_session_close(session, ret);
+
+ return 0;
+}
+
+static void sunxi_ce_session_release(struct sunxi_ce_session *session)
+{
+ struct sunxi_ce_priv *priv = session->ce;
+
+ priv->active_session = NULL;
+ memset(session, 0, sizeof(*session));
+}
+
+static int sunxi_ce_session_finish(struct sunxi_ce_session *session, bool abort)
+{
+ struct sunxi_ce_priv *priv;
+ u32 channel_mask, valid_channels;
+ int cleanup_ret, idle_ret, ret;
+
+ ret = sunxi_ce_session_check(session);
+ if (ret)
+ return ret;
+
+ priv = session->ce;
+ channel_mask = session->channel_mask;
+ valid_channels = GENMASK(SUNXI_CE_MAX_CHANS - 1, 0);
+ ret = 0;
+
+ if (!abort && session->inflight_mask) {
+ ret = -EBUSY;
+ abort = true;
+ }
+
+ if (!abort) {
+ clrbits_le32(priv->base + SUNXI_CE_ICR, channel_mask);
+ writel(channel_mask, priv->base + SUNXI_CE_ISR);
+ ret = sunxi_ce_clear_errors(priv, channel_mask);
+
+ writel(0, priv->base + SUNXI_CE_TLR);
+ writel(0, priv->base + SUNXI_CE_TDQ);
+ cleanup_ret = sunxi_ce_wait(priv->base + SUNXI_CE_TLR,
+ SUNXI_CE_TASK_START, 0);
+ readl(priv->base + SUNXI_CE_TDQ);
+ if (cleanup_ret) {
+ printf("%s: timeout clearing task launcher\n", __func__);
+ if (!ret)
+ ret = cleanup_ret;
+ abort = true;
+ }
+ }
+
+ if (abort) {
+ cleanup_ret = sunxi_ce_reset(priv);
+ if (cleanup_ret) {
+ idle_ret = sunxi_ce_wait_channels_idle(priv, channel_mask);
+ if (idle_ret)
+ panic_str("CE: failed to stop DMA");
+ if (!ret)
+ ret = cleanup_ret;
+ }
+
+ /* Reset or observed idle DMA makes releasing mapped memory safe. */
+ rmb();
+ clrbits_le32(priv->base + SUNXI_CE_ICR, valid_channels);
+ writel(valid_channels, priv->base + SUNXI_CE_ISR);
+ writel(sunxi_ce_error_mask(valid_channels),
+ priv->base + SUNXI_CE_ESR);
+ writel(0, priv->base + SUNXI_CE_TLR);
+ writel(0, priv->base + SUNXI_CE_TDQ);
+ }
+
+ sunxi_ce_session_release(session);
+
+ return ret;
+}
+
+int sunxi_ce_session_close(struct sunxi_ce_session *session, int status)
+{
+ int cleanup_ret;
+
+ if (!session || !session->ce)
+ return status ? status : -EINVAL;
+
+ cleanup_ret = sunxi_ce_session_finish(session, status != 0);
+
+ return status ? status : cleanup_ret;
+}
+
+int sunxi_ce_run_task(struct sunxi_ce_priv *priv, u32 channel, u32 method,
+ struct sunxi_ce_task *task)
+{
+ struct sunxi_ce_session session;
+ u32 channel_mask;
+ int ret;
+
+ if (channel >= SUNXI_CE_MAX_CHANS)
+ return -EINVAL;
+ channel_mask = SUNXI_CE_CHAN_MASK(channel);
+
+ ret = sunxi_ce_session_begin(priv, channel_mask, &session);
+ if (ret)
+ return ret;
+
+ ret = sunxi_ce_session_run_chain_or_close(&session, channel, method,
+ task, 1);
+ if (ret)
+ return ret;
+
+ return sunxi_ce_session_close(&session, 0);
+}
+
+static int sunxi_ce_reset(struct sunxi_ce_priv *priv)
+{
+ int ret;
+
+ ret = reset_assert_bulk(&priv->resets);
+ if (ret)
+ return ret;
+
+ udelay(1);
+
+ ret = reset_deassert_bulk(&priv->resets);
+ if (ret)
+ return ret;
+
+ udelay(10);
+
+ return 0;
+}
+
+static void sunxi_ce_setup_mod_clock(const struct sunxi_ce_variant *variant)
+{
+ void __iomem *ccu = (void __iomem *)SUNXI_CCM_BASE;
+
+ clrsetbits_le32(ccu + SUN50I_H6_CCU_CE_CLK,
+ SUN50I_H6_CCU_CE_CLK_MASK, variant->mod_clk_cfg);
+}
+
+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 = sunxi_ce_bind_child(dev, "sun8i-ce-aes");
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sunxi_ce_probe(struct udevice *dev)
+{
+ struct sunxi_ce_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ priv->variant = (const struct sunxi_ce_variant *)
+ dev_get_driver_data(dev);
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -EINVAL;
+
+ ret = reset_get_bulk(dev, &priv->resets);
+ if (ret) {
+ dev_err(dev, "failed to get resets: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_get_bulk(dev, &priv->clks);
+ if (ret) {
+ dev_err(dev, "failed to get clocks: %d\n", ret);
+ goto err_release_resets;
+ }
+
+ sunxi_ce_setup_mod_clock(priv->variant);
+
+ ret = reset_deassert_bulk(&priv->resets);
+ if (ret) {
+ dev_err(dev, "failed to deassert resets: %d\n", ret);
+ goto err_release_clks;
+ }
+
+ ret = clk_enable_bulk(&priv->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks: %d\n", ret);
+ goto err_assert_resets;
+ }
+
+ ret = sunxi_ce_reset(priv);
+ if (ret) {
+ dev_err(dev, "failed to reset CE: %d\n", ret);
+ goto err_disable_clks;
+ }
+
+ return 0;
+
+err_disable_clks:
+ clk_disable_bulk(&priv->clks);
+err_assert_resets:
+ reset_assert_bulk(&priv->resets);
+err_release_clks:
+ clk_release_bulk(&priv->clks);
+err_release_resets:
+ reset_release_bulk(&priv->resets);
+
+ return ret;
+}
+
+static int sunxi_ce_remove(struct udevice *dev)
+{
+ struct sunxi_ce_priv *priv = dev_get_priv(dev);
+
+ clk_disable_bulk(&priv->clks);
+ clk_release_bulk(&priv->clks);
+ reset_assert_bulk(&priv->resets);
+ reset_release_bulk(&priv->resets);
+
+ return 0;
+}
+
+static const struct sunxi_ce_variant sun50i_h6_variant = {
+ .aes_engine_count = 2,
+};
+
+static const struct sunxi_ce_variant sun50i_h616_variant = {
+ .needs_word_addresses = true,
+ .aes_engine_count = 2,
+ .mod_clk_cfg = SUN50I_H6_CCU_CE_CLK_SRC_MASK |
+ SUN50I_H616_CCU_CE_CLK_M,
+};
+
+static const struct udevice_id sunxi_ce_ids[] = {
+ {
+ .compatible = "allwinner,sun50i-h6-crypto",
+ .data = (ulong)&sun50i_h6_variant,
+ }, {
+ .compatible = "allwinner,sun50i-h616-crypto",
+ .data = (ulong)&sun50i_h616_variant,
+ },
+ { }
+};
+
+U_BOOT_DRIVER(sun8i_ce) = {
+ .name = "sun8i-ce",
+ .id = UCLASS_NOP,
+ .of_match = sunxi_ce_ids,
+ .bind = sunxi_ce_bind,
+ .probe = sunxi_ce_probe,
+ .remove = sunxi_ce_remove,
+ .priv_auto = sizeof(struct sunxi_ce_priv),
+ .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
new file mode 100644
index 00000000000..d0035af559a
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
@@ -0,0 +1,120 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#ifndef __SUN8I_CE_H
+#define __SUN8I_CE_H
+
+#include <stddef.h>
+#include <clk.h>
+#include <reset.h>
+#include <linux/bitops.h>
+#include <linux/dma-direction.h>
+#include <linux/types.h>
+
+#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_MAX_SG 8
+#define SUNXI_CE_MAX_CHANS 4
+#define SUNXI_CE_CHAN_ERR_MASK(x) (0xffU << ((x) * 8))
+
+struct sunxi_ce_sginfo {
+ u32 addr;
+ u32 len;
+};
+
+struct sunxi_ce_task {
+ u32 t_id;
+ u32 t_common_ctl;
+ u32 t_sym_ctl;
+ u32 t_asym_ctl;
+ u32 t_key;
+ u32 t_iv;
+ u32 t_ctr;
+ u32 t_dlen;
+ struct sunxi_ce_sginfo t_src[SUNXI_CE_MAX_SG];
+ struct sunxi_ce_sginfo t_dst[SUNXI_CE_MAX_SG];
+ u32 next;
+ u32 reserved[3];
+};
+
+static_assert(sizeof(struct sunxi_ce_sginfo) == 8);
+static_assert(offsetof(struct sunxi_ce_task, next) == 160);
+static_assert(sizeof(struct sunxi_ce_task) == 176);
+
+struct sunxi_ce_variant {
+ bool needs_word_addresses;
+ u8 aes_engine_count;
+ u32 mod_clk_cfg;
+};
+
+struct sunxi_ce_session;
+
+struct sunxi_ce_priv {
+ void __iomem *base;
+ const struct sunxi_ce_variant *variant;
+ struct clk_bulk clks;
+ struct reset_ctl_bulk resets;
+ struct sunxi_ce_session *active_session;
+};
+
+struct sunxi_ce_session {
+ struct sunxi_ce_priv *ce;
+ u32 channel_mask;
+ u32 inflight_mask;
+ unsigned long deadline[SUNXI_CE_MAX_CHANS];
+};
+
+static inline bool sunxi_ce_session_busy(const struct sunxi_ce_session *session)
+{
+ return session && session->inflight_mask;
+}
+
+/*
+ * DMA_FROM_DEVICE and DMA_BIDIRECTIONAL buffers must cover complete cache
+ * lines. DMA_TO_DEVICE buffers may use a rounded cache envelope. Keep every
+ * mapping alive until its chain completes or the owning session is closed.
+ */
+struct sunxi_ce_dma_buf {
+ dma_addr_t base;
+ dma_addr_t dma;
+ size_t map_len;
+ enum dma_data_direction dir;
+};
+
+u32 sunxi_ce_desc_dma_addr(struct sunxi_ce_priv *priv, dma_addr_t addr);
+int sunxi_ce_dma_map(struct sunxi_ce_priv *priv,
+ struct sunxi_ce_dma_buf *map, void *buf, size_t len,
+ enum dma_data_direction dir);
+void sunxi_ce_dma_unmap(struct sunxi_ce_dma_buf *map);
+/*
+ * A session owns at most one descriptor chain per selected channel. Submission
+ * supplies the descriptor transport fields, including the tail interrupt.
+ * Polling returns a completed-channel mask (or a negative error) after output
+ * DMA is idle and ordered. A nonzero close status aborts all active channels
+ * before callers release their DMA mappings. A blocking run also closes the
+ * session on error so its caller can immediately unwind local DMA mappings.
+ */
+int sunxi_ce_session_begin(struct sunxi_ce_priv *priv, u32 channel_mask,
+ struct sunxi_ce_session *session);
+int sunxi_ce_session_submit_chain(struct sunxi_ce_session *session,
+ u32 channel, u32 method,
+ struct sunxi_ce_task *tasks,
+ u32 task_count);
+int sunxi_ce_session_poll(struct sunxi_ce_session *session);
+int sunxi_ce_session_run_chain_or_close(struct sunxi_ce_session *session,
+ u32 channel, u32 method,
+ struct sunxi_ce_task *tasks,
+ u32 task_count);
+int sunxi_ce_session_close(struct sunxi_ce_session *session, int status);
+int sunxi_ce_run_task(struct sunxi_ce_priv *priv, u32 channel, u32 method,
+ struct sunxi_ce_task *task);
+
+#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 12/13] crypto: allwinner: add sun8i-ce ECDSA verifier
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (10 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 11/13] crypto: allwinner: add sun8i-ce AES driver James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
2026-07-28 17:41 ` [PATCH v6 13/13] crypto: allwinner: add sun8i-ce hash driver James Hilliard
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
The H6 and H616 Crypto Engines include an ECC engine which can verify
ECDSA signatures. Add a UCLASS_ECDSA child for the sun8i-ce parent so FIT
signature verification can use the hardware block from U-Boot proper and
SPL.
The CE takes explicit curve parameters for each operation. Provide tables
for the curves accepted by U-Boot's FIT ECDSA parser: secp224r1,
prime256v1, secp384r1 and secp521r1. Derive each curve's a = p - 3
parameter from p instead of storing duplicate constants. Make each curve
independently selectable for U-Boot proper and SPL. SRAM-constrained
builds can keep only the curves they need. The task input layout follows
the ECC verify buffer order used by Allwinner's CE implementation.
The CE input buffer uses fixed-width curve fields. Reuse the parameter
packing logic for the message digest as well, so wider digests are
truncated to the leftmost curve-width bytes according to ECDSA rules.
Map the private input and result through the parent's cacheline-safe DMA
objects. Round result storage to complete cachelines while keeping the
descriptor output curve-sized. Submit ECC work on the dedicated asymmetric
completion channel. After retirement, unmap the result and input exactly
once before inspecting the result.
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes v4 -> v5:
- Let parent submission populate the descriptor transport fields
- Name fields in the fixed ECC input sequence and inline the trivial DM
verifier wrapper
- Reject missing key coordinates, digest or signature before curve lookup
- Use shared cacheline-safe DMA mappings and round private result storage
to complete cachelines
- Unmap the result and input before inspecting the verification result
Changes v3 -> v4:
- Enable CE ECDSA for both H6 and H616
- Use the dedicated asymmetric channel and shared task-session path
- Derive the NIST a = p - 3 parameter instead of storing duplicate arrays
Changes v1 -> v2:
- Document the CE input-buffer layout (suggested by Simon Glass)
- Document ECC byte-sized task length (suggested by Simon Glass)
- Make CE ECDSA curves independently selectable
(suggested by Simon Glass)
- Use neutral wording in comments and commit log
---
drivers/crypto/allwinner/sun8i-ce/Kconfig | 94 +++++
drivers/crypto/allwinner/sun8i-ce/Makefile | 1 +
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c | 6 +
drivers/crypto/allwinner/sun8i-ce/sun8i-ce-ecdsa.c | 382 +++++++++++++++++++++
drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h | 3 +
5 files changed, 486 insertions(+)
diff --git a/drivers/crypto/allwinner/sun8i-ce/Kconfig b/drivers/crypto/allwinner/sun8i-ce/Kconfig
index 973c4f3af21..49686d07aa3 100644
--- a/drivers/crypto/allwinner/sun8i-ce/Kconfig
+++ b/drivers/crypto/allwinner/sun8i-ce/Kconfig
@@ -39,3 +39,97 @@ config SPL_SUNXI_CE_AES
Select this option to enable AES decryption in SPL using the Crypto
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_ECDSA
+ bool "Allwinner sunxi CE ECDSA verifier"
+ depends on ARCH_SUNXI
+ depends on ECDSA_VERIFY
+ depends on CLK && DM_RESET
+ select SUNXI_CE
+ help
+ Select this option to enable ECDSA signature verification using
+ the Crypto Engine found in Allwinner sunxi SoCs. FIT image
+ signatures can then be checked by the hardware accelerator in
+ U-Boot proper. Digests wider than the selected curve are
+ truncated according to ECDSA rules.
+
+config SPL_SUNXI_CE_ECDSA
+ bool "Allwinner sunxi CE ECDSA verifier in SPL"
+ depends on ARCH_SUNXI
+ depends on MACH_SUN50I_H6 || MACH_SUN50I_H616
+ depends on SPL_DM
+ depends on SPL_OF_CONTROL
+ depends on SPL_ECDSA_VERIFY
+ select SPL_CRYPTO
+ select SPL_SUNXI_CE
+ help
+ Select this option to enable ECDSA signature verification in SPL
+ using the Crypto Engine found in Allwinner H6 and H616 compatible
+ SoCs.
+ This allows SPL FIT image signatures to be checked by the hardware
+ accelerator before U-Boot proper is loaded. Digests wider than the
+ selected curve are truncated according to ECDSA rules.
+
+if SUNXI_CE_ECDSA
+
+config SUNXI_CE_ECDSA_SECP224R1
+ bool "Support secp224r1"
+ default y
+ help
+ Enable the secp224r1 curve parameters for the sunxi CE ECDSA
+ verifier.
+
+config SUNXI_CE_ECDSA_PRIME256V1
+ bool "Support prime256v1"
+ default y
+ help
+ Enable the prime256v1 curve parameters for the sunxi CE ECDSA
+ verifier.
+
+config SUNXI_CE_ECDSA_SECP384R1
+ bool "Support secp384r1"
+ default y
+ help
+ Enable the secp384r1 curve parameters for the sunxi CE ECDSA
+ verifier.
+
+config SUNXI_CE_ECDSA_SECP521R1
+ bool "Support secp521r1"
+ default y
+ help
+ Enable the secp521r1 curve parameters for the sunxi CE ECDSA
+ verifier.
+
+endif
+
+if SPL_SUNXI_CE_ECDSA
+
+config SPL_SUNXI_CE_ECDSA_SECP224R1
+ bool "Support secp224r1 in SPL"
+ default y
+ help
+ Enable the secp224r1 curve parameters for the sunxi CE ECDSA
+ verifier in SPL.
+
+config SPL_SUNXI_CE_ECDSA_PRIME256V1
+ bool "Support prime256v1 in SPL"
+ default y
+ help
+ Enable the prime256v1 curve parameters for the sunxi CE ECDSA
+ verifier in SPL.
+
+config SPL_SUNXI_CE_ECDSA_SECP384R1
+ bool "Support secp384r1 in SPL"
+ default y
+ help
+ Enable the secp384r1 curve parameters for the sunxi CE ECDSA
+ verifier in SPL.
+
+config SPL_SUNXI_CE_ECDSA_SECP521R1
+ bool "Support secp521r1 in SPL"
+ default y
+ help
+ Enable the secp521r1 curve parameters for the sunxi CE ECDSA
+ verifier in SPL.
+
+endif
diff --git a/drivers/crypto/allwinner/sun8i-ce/Makefile b/drivers/crypto/allwinner/sun8i-ce/Makefile
index 2a8778065b1..753ea827a0d 100644
--- a/drivers/crypto/allwinner/sun8i-ce/Makefile
+++ b/drivers/crypto/allwinner/sun8i-ce/Makefile
@@ -2,3 +2,4 @@
obj-$(CONFIG_$(PHASE_)SUNXI_CE) += sun8i-ce-core.o
obj-$(CONFIG_$(PHASE_)SUNXI_CE_AES) += sun8i-ce-aes.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 f22d534caea..4ce1ee70c14 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-core.c
@@ -673,6 +673,12 @@ static int sunxi_ce_bind(struct udevice *dev)
return ret;
}
+ if (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA)) {
+ ret = sunxi_ce_bind_child(dev, "sun8i-ce-ecdsa");
+ if (ret)
+ return ret;
+ }
+
return 0;
}
diff --git a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-ecdsa.c b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-ecdsa.c
new file mode 100644
index 00000000000..f9b36347e4f
--- /dev/null
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce-ecdsa.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 James Hilliard
+ */
+
+#include <crypto/ecdsa-uclass.h>
+#include <dm.h>
+#include <malloc.h>
+#include <memalign.h>
+#include <string.h>
+#include <linux/kernel.h>
+#include "sun8i-ce.h"
+
+enum sunxi_ecdsa_field {
+ SUNXI_ECDSA_FIELD_N,
+ SUNXI_ECDSA_FIELD_S,
+ SUNXI_ECDSA_FIELD_E,
+ SUNXI_ECDSA_FIELD_R,
+ SUNXI_ECDSA_FIELD_P,
+ SUNXI_ECDSA_FIELD_A,
+ SUNXI_ECDSA_FIELD_GX,
+ SUNXI_ECDSA_FIELD_GY,
+ SUNXI_ECDSA_FIELD_QX,
+ SUNXI_ECDSA_FIELD_QY,
+ SUNXI_ECDSA_FIELD_N2,
+ SUNXI_ECDSA_FIELD_R2,
+ SUNXI_ECDSA_FIELD_COUNT,
+};
+
+#define SUNXI_ECDSA_MAX_WORDS \
+ (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_SECP521R1) ? 17 : \
+ CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_SECP384R1) ? 12 : \
+ CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_PRIME256V1) ? 8 : \
+ CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_SECP224R1) ? 7 : 1)
+#define SUNXI_ECDSA_MAX_BYTES (SUNXI_ECDSA_MAX_WORDS * sizeof(u32))
+#define SUNXI_ECDSA_MAX_SRC_BYTES \
+ (SUNXI_ECDSA_FIELD_COUNT * SUNXI_ECDSA_MAX_BYTES)
+#define SUNXI_ECDSA_MAX_DST_BYTES ALIGN(SUNXI_ECDSA_MAX_BYTES, \
+ ARCH_DMA_MINALIGN)
+
+struct sunxi_ecdsa_job {
+ struct sunxi_ce_task task __aligned(ARCH_DMA_MINALIGN);
+ u8 src[SUNXI_ECDSA_MAX_SRC_BYTES] __aligned(ARCH_DMA_MINALIGN);
+ u8 dst[SUNXI_ECDSA_MAX_DST_BYTES] __aligned(ARCH_DMA_MINALIGN);
+};
+
+struct sunxi_ecdsa_curve {
+ /* p, Gx, Gy and n as consecutive big-endian curve-width values. */
+ const u8 *params;
+ u16 bits;
+ u8 bytes;
+ u8 words;
+};
+
+static const u8 ecdsa_p224_params[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ /* Gx */
+ 0xb7, 0x0e, 0x0c, 0xbd, 0x6b, 0xb4, 0xbf, 0x7f,
+ 0x32, 0x13, 0x90, 0xb9, 0x4a, 0x03, 0xc1, 0xd3,
+ 0x56, 0xc2, 0x11, 0x22, 0x34, 0x32, 0x80, 0xd6,
+ 0x11, 0x5c, 0x1d, 0x21,
+ /* Gy */
+ 0xbd, 0x37, 0x63, 0x88, 0xb5, 0xf7, 0x23, 0xfb,
+ 0x4c, 0x22, 0xdf, 0xe6, 0xcd, 0x43, 0x75, 0xa0,
+ 0x5a, 0x07, 0x47, 0x64, 0x44, 0xd5, 0x81, 0x99,
+ 0x85, 0x00, 0x7e, 0x34,
+ /* n */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
+ 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
+ 0x5c, 0x5c, 0x2a, 0x3d,
+};
+
+static const u8 ecdsa_p256_params[] = {
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ /* Gx */
+ 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47,
+ 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2,
+ 0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0,
+ 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96,
+ /* Gy */
+ 0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b,
+ 0x8e, 0xe7, 0xeb, 0x4a, 0x7c, 0x0f, 0x9e, 0x16,
+ 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce,
+ 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5,
+ /* n */
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84,
+ 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51,
+};
+
+static const u8 ecdsa_p384_params[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ /* Gx */
+ 0xaa, 0x87, 0xca, 0x22, 0xbe, 0x8b, 0x05, 0x37,
+ 0x8e, 0xb1, 0xc7, 0x1e, 0xf3, 0x20, 0xad, 0x74,
+ 0x6e, 0x1d, 0x3b, 0x62, 0x8b, 0xa7, 0x9b, 0x98,
+ 0x59, 0xf7, 0x41, 0xe0, 0x82, 0x54, 0x2a, 0x38,
+ 0x55, 0x02, 0xf2, 0x5d, 0xbf, 0x55, 0x29, 0x6c,
+ 0x3a, 0x54, 0x5e, 0x38, 0x72, 0x76, 0x0a, 0xb7,
+ /* Gy */
+ 0x36, 0x17, 0xde, 0x4a, 0x96, 0x26, 0x2c, 0x6f,
+ 0x5d, 0x9e, 0x98, 0xbf, 0x92, 0x92, 0xdc, 0x29,
+ 0xf8, 0xf4, 0x1d, 0xbd, 0x28, 0x9a, 0x14, 0x7c,
+ 0xe9, 0xda, 0x31, 0x13, 0xb5, 0xf0, 0xb8, 0xc0,
+ 0x0a, 0x60, 0xb1, 0xce, 0x1d, 0x7e, 0x81, 0x9d,
+ 0x7a, 0x43, 0x1d, 0x7c, 0x90, 0xea, 0x0e, 0x5f,
+ /* n */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf,
+ 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a,
+ 0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73,
+};
+
+static const u8 ecdsa_p521_params[] = {
+ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff,
+ /* Gx */
+ 0x00, 0xc6, 0x85, 0x8e, 0x06, 0xb7, 0x04, 0x04,
+ 0xe9, 0xcd, 0x9e, 0x3e, 0xcb, 0x66, 0x23, 0x95,
+ 0xb4, 0x42, 0x9c, 0x64, 0x81, 0x39, 0x05, 0x3f,
+ 0xb5, 0x21, 0xf8, 0x28, 0xaf, 0x60, 0x6b, 0x4d,
+ 0x3d, 0xba, 0xa1, 0x4b, 0x5e, 0x77, 0xef, 0xe7,
+ 0x59, 0x28, 0xfe, 0x1d, 0xc1, 0x27, 0xa2, 0xff,
+ 0xa8, 0xde, 0x33, 0x48, 0xb3, 0xc1, 0x85, 0x6a,
+ 0x42, 0x9b, 0xf9, 0x7e, 0x7e, 0x31, 0xc2, 0xe5,
+ 0xbd, 0x66,
+ /* Gy */
+ 0x01, 0x18, 0x39, 0x29, 0x6a, 0x78, 0x9a, 0x3b,
+ 0xc0, 0x04, 0x5c, 0x8a, 0x5f, 0xb4, 0x2c, 0x7d,
+ 0x1b, 0xd9, 0x98, 0xf5, 0x44, 0x49, 0x57, 0x9b,
+ 0x44, 0x68, 0x17, 0xaf, 0xbd, 0x17, 0x27, 0x3e,
+ 0x66, 0x2c, 0x97, 0xee, 0x72, 0x99, 0x5e, 0xf4,
+ 0x26, 0x40, 0xc5, 0x50, 0xb9, 0x01, 0x3f, 0xad,
+ 0x07, 0x61, 0x35, 0x3c, 0x70, 0x86, 0xa2, 0x72,
+ 0xc2, 0x40, 0x88, 0xbe, 0x94, 0x76, 0x9f, 0xd1,
+ 0x66, 0x50,
+ /* n */
+ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xfa, 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f,
+ 0x96, 0x6b, 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09,
+ 0xa5, 0xd0, 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c,
+ 0x47, 0xae, 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38,
+ 0x64, 0x09,
+};
+
+static const struct sunxi_ecdsa_curve sunxi_ecdsa_secp224r1 = {
+ .bits = 224,
+ .bytes = 28,
+ .words = 7,
+ .params = ecdsa_p224_params,
+};
+
+static const struct sunxi_ecdsa_curve sunxi_ecdsa_prime256v1 = {
+ .bits = 256,
+ .bytes = 32,
+ .words = 8,
+ .params = ecdsa_p256_params,
+};
+
+static const struct sunxi_ecdsa_curve sunxi_ecdsa_secp384r1 = {
+ .bits = 384,
+ .bytes = 48,
+ .words = 12,
+ .params = ecdsa_p384_params,
+};
+
+static const struct sunxi_ecdsa_curve sunxi_ecdsa_secp521r1 = {
+ .bits = 521,
+ .bytes = 66,
+ .words = 17,
+ .params = ecdsa_p521_params,
+};
+
+static const struct sunxi_ecdsa_curve *
+sunxi_ecdsa_find_curve(const struct ecdsa_public_key *pubkey)
+{
+ if (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_SECP224R1) &&
+ !strcmp(pubkey->curve_name, "secp224r1"))
+ return &sunxi_ecdsa_secp224r1;
+ if (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_PRIME256V1) &&
+ !strcmp(pubkey->curve_name, "prime256v1"))
+ return &sunxi_ecdsa_prime256v1;
+ if (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_SECP384R1) &&
+ !strcmp(pubkey->curve_name, "secp384r1"))
+ return &sunxi_ecdsa_secp384r1;
+ if (CONFIG_IS_ENABLED(SUNXI_CE_ECDSA_SECP521R1) &&
+ !strcmp(pubkey->curve_name, "secp521r1"))
+ return &sunxi_ecdsa_secp521r1;
+
+ return NULL;
+}
+
+static u8 *sunxi_ecdsa_copy_le_param(u8 *dst,
+ const struct sunxi_ecdsa_curve *curve,
+ const void *src, size_t len)
+{
+ const u8 *p = src;
+ size_t copy_len;
+ int i;
+
+ copy_len = min_t(size_t, len, curve->bytes);
+ for (i = 0; i < copy_len; i++)
+ dst[i] = p[copy_len - 1 - i];
+
+ return dst + curve->words * sizeof(u32);
+}
+
+static void sunxi_ecdsa_fill_src(struct sunxi_ecdsa_job *job,
+ const struct sunxi_ecdsa_curve *curve,
+ const struct ecdsa_public_key *pubkey,
+ const void *hash, size_t hash_len,
+ const void *signature)
+{
+ const u8 *r = signature;
+ const u8 *s = r + curve->bytes;
+ const u8 *p = curve->params;
+ const u8 *gx = p + curve->bytes;
+ const u8 *gy = gx + curve->bytes;
+ const u8 *n = gy + curve->bytes;
+ const void *params[SUNXI_ECDSA_FIELD_COUNT] = {
+ [SUNXI_ECDSA_FIELD_N] = n,
+ [SUNXI_ECDSA_FIELD_S] = s,
+ [SUNXI_ECDSA_FIELD_E] = hash,
+ [SUNXI_ECDSA_FIELD_R] = r,
+ [SUNXI_ECDSA_FIELD_P] = p,
+ [SUNXI_ECDSA_FIELD_A] = p,
+ [SUNXI_ECDSA_FIELD_GX] = gx,
+ [SUNXI_ECDSA_FIELD_GY] = gy,
+ [SUNXI_ECDSA_FIELD_QX] = pubkey->x,
+ [SUNXI_ECDSA_FIELD_QY] = pubkey->y,
+ [SUNXI_ECDSA_FIELD_N2] = n,
+ [SUNXI_ECDSA_FIELD_R2] = r,
+ };
+ u8 *dst = job->src;
+ u32 i;
+
+ /*
+ * The CE manual specifies this fixed sequence of little-endian
+ * curve-width fields for ECC signature verification:
+ *
+ * n, s, e, r, p, a, Gx, Gy, Qx, Qy, n, r
+ */
+ for (i = 0; i < ARRAY_SIZE(params); i++) {
+ u8 *field = dst;
+ size_t len = i == SUNXI_ECDSA_FIELD_E ? hash_len : curve->bytes;
+
+ dst = sunxi_ecdsa_copy_le_param(dst, curve, params[i], len);
+ if (i == SUNXI_ECDSA_FIELD_A) {
+ u32 borrow;
+
+ /* All supported NIST curves use a = p - 3. */
+ for (borrow = 3; borrow; field++) {
+ u8 val = *field;
+
+ *field = val - borrow;
+ borrow = val < borrow;
+ }
+ }
+ }
+}
+
+static void sunxi_ecdsa_fill_task(struct sunxi_ce_priv *priv,
+ struct sunxi_ecdsa_job *job,
+ const struct sunxi_ecdsa_curve *curve,
+ dma_addr_t src, dma_addr_t dst)
+{
+ struct sunxi_ce_task *task = &job->task;
+ u32 bytes = curve->words * sizeof(u32);
+
+ task->t_asym_ctl = curve->words |
+ (SUNXI_CE_ECC_OP_VERIFY << SUNXI_CE_ECC_OP_SHIFT);
+ /* The ECC engine uses a byte-sized task length. */
+ task->t_dlen = SUNXI_ECDSA_FIELD_COUNT * bytes;
+ task->t_src[0].addr = sunxi_ce_desc_dma_addr(priv, src);
+ task->t_src[0].len = task->t_dlen / sizeof(u32);
+ task->t_dst[0].addr = sunxi_ce_desc_dma_addr(priv, dst);
+ task->t_dst[0].len = curve->words;
+}
+
+static int sunxi_ecdsa_run(struct sunxi_ce_priv *priv,
+ struct sunxi_ecdsa_job *job,
+ const struct sunxi_ecdsa_curve *curve)
+{
+ size_t dst_len = curve->words * sizeof(u32);
+ size_t src_len = SUNXI_ECDSA_FIELD_COUNT * dst_len;
+ struct sunxi_ce_dma_buf src_dma = { };
+ struct sunxi_ce_dma_buf dst_dma = { };
+ u32 result;
+ int ret;
+
+ ret = sunxi_ce_dma_map(priv, &src_dma, job->src, src_len,
+ DMA_TO_DEVICE);
+ if (ret)
+ goto out;
+ ret = sunxi_ce_dma_map(priv, &dst_dma, job->dst, sizeof(job->dst),
+ DMA_FROM_DEVICE);
+ if (ret)
+ goto out;
+
+ sunxi_ecdsa_fill_task(priv, job, curve, src_dma.dma, dst_dma.dma);
+
+ ret = sunxi_ce_run_task(priv, SUNXI_CE_CHANNEL_ASYM,
+ SUNXI_CE_METHOD_ECC, &job->task);
+
+out:
+ sunxi_ce_dma_unmap(&dst_dma);
+ sunxi_ce_dma_unmap(&src_dma);
+ if (ret)
+ return ret;
+
+ result = *(u32 *)job->dst;
+
+ return result == 1 ? 0 : -EPERM;
+}
+
+static int sunxi_ecdsa_verify_dm(struct udevice *dev,
+ const struct ecdsa_public_key *pubkey,
+ const void *hash, size_t hash_len,
+ const void *signature, size_t sig_len)
+{
+ struct sunxi_ce_priv *priv = dev_get_priv(dev_get_parent(dev));
+ const struct sunxi_ecdsa_curve *curve;
+ struct sunxi_ecdsa_job *job;
+ int ret;
+
+ if (!priv || !pubkey || !pubkey->curve_name || !pubkey->x ||
+ !pubkey->y || !hash || !hash_len || !signature)
+ return -EINVAL;
+
+ curve = sunxi_ecdsa_find_curve(pubkey);
+ if (!curve || pubkey->size_bits != curve->bits ||
+ sig_len != curve->bytes * 2)
+ return -EINVAL;
+
+ job = malloc_cache_aligned(sizeof(*job));
+ if (!job)
+ return -ENOMEM;
+
+ memset(job, 0, sizeof(*job));
+ sunxi_ecdsa_fill_src(job, curve, pubkey, hash, hash_len, signature);
+
+ ret = sunxi_ecdsa_run(priv, job, curve);
+ free(job);
+
+ return ret;
+}
+
+static const struct ecdsa_ops sunxi_ecdsa_ops = {
+ .verify = sunxi_ecdsa_verify_dm,
+};
+
+U_BOOT_DRIVER(sun8i_ce_ecdsa) = {
+ .name = "sun8i-ce-ecdsa",
+ .id = UCLASS_ECDSA,
+ .ops = &sunxi_ecdsa_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 d0035af559a..d5b7edd357d 100644
--- a/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
+++ b/drivers/crypto/allwinner/sun8i-ce/sun8i-ce.h
@@ -21,6 +21,9 @@
#define SUNXI_CE_COMM_INT BIT(31)
#define SUNXI_CE_METHOD_AES 0
#define SUNXI_CE_METHOD_RAES 0x30
+#define SUNXI_CE_METHOD_ECC 33
+#define SUNXI_CE_ECC_OP_VERIFY 7
+#define SUNXI_CE_ECC_OP_SHIFT 16
#define SUNXI_CE_MAX_SG 8
#define SUNXI_CE_MAX_CHANS 4
#define SUNXI_CE_CHAN_ERR_MASK(x) (0xffU << ((x) * 8))
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v6 13/13] crypto: allwinner: add sun8i-ce hash driver
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
` (11 preceding siblings ...)
2026-07-28 17:41 ` [PATCH v6 12/13] crypto: allwinner: add sun8i-ce ECDSA verifier James Hilliard
@ 2026-07-28 17:41 ` James Hilliard
12 siblings, 0 replies; 15+ messages in thread
From: James Hilliard @ 2026-07-28 17:41 UTC (permalink / raw)
To: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot
Cc: Chen-Yu Tsai, Samuel Holland, Tom Rini, Simon Glass,
James Hilliard, Thierry Reding, Quentin Schulz, Quentin Schulz,
Marek Vasut, Marek Vasut, Rasmus Villemoes, Rasmus Villemoes,
Aristo Chen, Anton Ivanov, Daniel Golle, Francois Berder,
Peng Fan, Neil Armstrong, Randolph Sapp, Jonas Karlman,
Wolfgang Wallner, Alexey Charkov, Ilias Apalodimas,
Heiko Schocher, Kory Maincent (TI.com), Anshul Dalal,
Johan Jonker, Francesco Valla, Heinrich Schuchardt, Michael Walle,
Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
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 <sjg@chromium.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
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
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v6 03/13] crypto: hash: allow DM hash in SPL
2026-07-28 17:41 ` [PATCH v6 03/13] crypto: hash: allow DM hash " James Hilliard
@ 2026-07-28 17:56 ` Tom Rini
0 siblings, 0 replies; 15+ messages in thread
From: Tom Rini @ 2026-07-28 17:56 UTC (permalink / raw)
To: James Hilliard
Cc: Svyatoslav Ryhel, Ion Agorria, Aspeed BMC SW team, Joel Stanley,
u-boot, Chen-Yu Tsai, Samuel Holland, Simon Glass, Thierry Reding,
Quentin Schulz, Quentin Schulz, Marek Vasut, Marek Vasut,
Rasmus Villemoes, Rasmus Villemoes, Aristo Chen, Anton Ivanov,
Daniel Golle, Francois Berder, Peng Fan, Neil Armstrong,
Randolph Sapp, Jonas Karlman, Wolfgang Wallner, Alexey Charkov,
Ilias Apalodimas, Heiko Schocher, Kory Maincent (TI.com),
Anshul Dalal, Johan Jonker, Francesco Valla, Heinrich Schuchardt,
Michael Walle, Andre Przywara, Lukasz Majewski, Richard Genoud,
Michael Trimarchi, E Shattow, Enric Balletbo i Serra,
Mattijs Korpershoek, Lucas Dietrich, David Lechner,
Julien Stephan, Kuan-Wei Chiu, Bastien Curutchet, Raymond Mao,
Ryan Chen, Chia-Wei Wang, Lucien.Jheng, Mateusz Furdyna,
Dinesh Maniyam, Heiko Stuebner, Vincent Jardin
[-- Attachment #1: Type: text/plain, Size: 984 bytes --]
On Tue, Jul 28, 2026 at 11:41:46AM -0600, James Hilliard wrote:
> The hash uclass is currently keyed only by CONFIG_DM_HASH, so SPL cannot
> enable UCLASS_HASH independently. Any SPL code using hash_digest*() has to
> rely on U-Boot proper also enabling DM_HASH, and the FIT hash path selects
> the driver-model implementation with a non-phase-aware preprocessor check.
>
> Add SPL_DM_HASH, build the hash uclass from CONFIG_$(PHASE_)DM_HASH and use
> CONFIG_IS_ENABLED(DM_HASH) when selecting the FIT hash implementation. This
> lets SPL FIT verification use a UCLASS_HASH provider without requiring the
> U-Boot proper hash uclass.
>
> No in-tree configuration enables both DM_HASH and SPL_CRYPTO, so the
> Makefile object key switch does not remove hash-uclass.o from an existing
> SPL build.
>
> Reviewed-by: Simon Glass <sjg@chromium.org>
> Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
Reviewed-by: Tom Rini <trini@konsulko.com>
--
Tom
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-29 4:56 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-28 17:41 [PATCH v6 00/13] crypto: allwinner: enable sun8i-ce FIT crypto James Hilliard
2026-07-28 17:41 ` [PATCH v6 01/13] cmd: aes: fix DM operation handling James Hilliard
2026-07-28 17:41 ` [PATCH v6 02/13] crypto: aes: allow DM AES in SPL James Hilliard
2026-07-28 17:41 ` [PATCH v6 03/13] crypto: hash: allow DM hash " James Hilliard
2026-07-28 17:56 ` Tom Rini
2026-07-28 17:41 ` [PATCH v6 04/13] boot: image: try all DM hash providers James Hilliard
2026-07-28 17:41 ` [PATCH v6 05/13] crypto: aes: fix software key-size handling James Hilliard
2026-07-28 17:41 ` [PATCH v6 06/13] crypto: aes: add software-key provider dispatch James Hilliard
2026-07-28 17:41 ` [PATCH v6 07/13] boot: image: add FIT decrypt-to-buffer helper James Hilliard
2026-07-28 17:41 ` [PATCH v6 08/13] spl: fit: support encrypted payloads James Hilliard
2026-07-28 17:41 ` [PATCH v6 09/13] clk: sunxi: add H6/H616 CE gates and reset James Hilliard
2026-07-28 17:41 ` [PATCH v6 10/13] lib: ecdsa: support additional curve sizes James Hilliard
2026-07-28 17:41 ` [PATCH v6 11/13] crypto: allwinner: add sun8i-ce AES driver James Hilliard
2026-07-28 17:41 ` [PATCH v6 12/13] crypto: allwinner: add sun8i-ce ECDSA verifier James Hilliard
2026-07-28 17:41 ` [PATCH v6 13/13] crypto: allwinner: add sun8i-ce hash driver James Hilliard
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.