diff --git a/common/hash.c b/common/hash.c index 71c4bef5826..5cbb4926c1d 100644 --- a/common/hash.c +++ b/common/hash.c @@ -11,6 +11,7 @@ #ifndef USE_HOSTCC #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #else #include "mkimage.h" #include @@ -614,7 +616,26 @@ int hash_command(const char *algo_name, int flags, struct cmd_tbl *cmdtp, return CMD_RET_FAILURE; buf = map_sysmem(addr, len); + if (CONFIG_IS_ENABLED(DM_HASH)) { + enum HASH_ALGO hash_algo; + int ret; + + hash_algo = hash_algo_lookup_by_name(algo_name); + if (hash_algo != HASH_ALGO_INVALID) { + ret = hash_digest_wd_lookup(hash_algo, buf, len, + output, + algo->chunk_size); + if (ret && ret != -ENODEV && ret != -EOPNOTSUPP) { + unmap_sysmem(buf); + free(output); + return CMD_RET_FAILURE; + } + if (!ret) + goto done; + } + } algo->hash_func_ws(buf, len, output, algo->chunk_size); +done: unmap_sysmem(buf); /* Try to avoid code bloat when verify is not needed */ diff --git a/drivers/crypto/aspeed/aspeed_hace.c b/drivers/crypto/aspeed/aspeed_hace.c index 22b5008a296..2469f53472f 100644 --- a/drivers/crypto/aspeed/aspeed_hace.c +++ b/drivers/crypto/aspeed/aspeed_hace.c @@ -160,7 +160,7 @@ static int aspeed_hace_init(struct udevice *dev, enum HASH_ALGO algo, void **ctx free_n_out: free(hace_ctx); - return -EINVAL; + return -EOPNOTSUPP; } static int aspeed_hace_update(struct udevice *dev, void *ctx, const void *ibuf, uint32_t ilen) diff --git a/drivers/crypto/aspeed/cptra_sha.c b/drivers/crypto/aspeed/cptra_sha.c index f57778e160d..0dc00f306f1 100644 --- a/drivers/crypto/aspeed/cptra_sha.c +++ b/drivers/crypto/aspeed/cptra_sha.c @@ -68,7 +68,7 @@ static int cptra_sha_init(struct udevice *dev, enum HASH_ALGO algo, void **ctxp) cs_ctx->dgst_len = 64; break; default: - rc = -EINVAL; + rc = -EOPNOTSUPP; goto free_n_out; }; diff --git a/drivers/crypto/hash/hash-uclass.c b/drivers/crypto/hash/hash-uclass.c index 5d9f1e0d59b..ffca19af2de 100644 --- a/drivers/crypto/hash/hash-uclass.c +++ b/drivers/crypto/hash/hash-uclass.c @@ -84,6 +84,41 @@ int hash_digest_wd(struct udevice *dev, enum HASH_ALGO algo, return ops->hash_digest_wd(dev, algo, ibuf, ilen, obuf, chunk_sz); } +static bool hash_op_unsupported(int ret) +{ + return ret == -ENOSYS || ret == -EOPNOTSUPP; +} + +int hash_digest_wd_lookup(enum HASH_ALGO algo, const void *ibuf, + const u32 ilen, void *obuf, u32 chunk_sz) +{ + struct udevice *dev; + int first_probe_err = 0; + bool found = false; + int ret; + + for (ret = uclass_first_device_check(UCLASS_HASH, &dev); dev; + ret = uclass_next_device_check(&dev)) { + found = true; + if (ret) { + if (!first_probe_err) + first_probe_err = ret; + continue; + } + + ret = hash_digest_wd(dev, algo, ibuf, ilen, obuf, chunk_sz); + if (!ret) + return 0; + if (!hash_op_unsupported(ret)) + return ret; + } + + if (first_probe_err) + return first_probe_err; + + return found ? -EOPNOTSUPP : -ENODEV; +} + int hash_init(struct udevice *dev, enum HASH_ALGO algo, void **ctxp) { struct hash_ops *ops = (struct hash_ops *)device_get_ops(dev); diff --git a/include/u-boot/hash.h b/include/u-boot/hash.h index f9d47a99a77..7dba66047f5 100644 --- a/include/u-boot/hash.h +++ b/include/u-boot/hash.h @@ -19,6 +19,8 @@ enum HASH_ALGO { HASH_ALGO_INVALID = 0xffffffff, }; +struct udevice; + /* general APIs for hash algo information */ enum HASH_ALGO hash_algo_lookup_by_name(const char *name); ssize_t hash_algo_digest_size(enum HASH_ALGO algo); @@ -31,6 +33,24 @@ int hash_digest(struct udevice *dev, enum HASH_ALGO algo, int hash_digest_wd(struct udevice *dev, enum HASH_ALGO algo, const void *ibuf, const uint32_t ilen, void *obuf, uint32_t chunk_sz); +/** + * hash_digest_wd_lookup() - Hash with the first provider supporting an algorithm + * + * Probe each hash device in order and use the first one which supports the + * requested algorithm. Probe failures are remembered while later providers are + * tried. Once a provider accepts an operation, hard failures are returned + * without trying another provider. + * + * @algo: Hash algorithm + * @ibuf: Input buffer + * @ilen: Input buffer length + * @obuf: Output buffer + * @chunk_sz: Watchdog scheduling interval + * Return: 0 on success, -ENODEV if there are no providers, -EOPNOTSUPP if no + * provider supports @algo, or another negative error from a provider + */ +int hash_digest_wd_lookup(enum HASH_ALGO algo, const void *ibuf, + const u32 ilen, void *obuf, u32 chunk_sz); int hash_init(struct udevice *dev, enum HASH_ALGO algo, void **ctxp); int hash_update(struct udevice *dev, void *ctx, const void *ibuf, const uint32_t ilen); int hash_finish(struct udevice *dev, void *ctx, void *obuf); diff --git a/test/dm/Makefile b/test/dm/Makefile index 76aa1fff9ba..fb3e6a7008f 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_DMA) += dma.o obj-$(CONFIG_VIDEO_MIPI_DSI) += dsi_host.o obj-$(CONFIG_DM_DSA) += dsa.o obj-$(CONFIG_ECDSA_VERIFY) += ecdsa.o +obj-$(CONFIG_DM_HASH) += hash.o obj-$(CONFIG_EFI_MEDIA_SANDBOX) += efi_media.o obj-$(CONFIG_DM_ETH) += eth.o obj-$(CONFIG_EXTCON) += extcon.o diff --git a/test/dm/hash.c b/test/dm/hash.c new file mode 100644 index 00000000000..fe949e33de5 --- /dev/null +++ b/test/dm/hash.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Tests for driver-model hash-provider selection + * + * Copyright (C) 2026 James Hilliard + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static int unsupported_calls; +static int success_calls; +static int hard_error_calls; + +static int hash_test_unsupported(struct udevice *dev, enum HASH_ALGO algo, + const void *ibuf, const uint32_t ilen, + void *obuf, uint32_t chunk_sz) +{ + unsupported_calls++; + + return -EOPNOTSUPP; +} + +static int hash_test_success(struct udevice *dev, enum HASH_ALGO algo, + const void *ibuf, const uint32_t ilen, + void *obuf, uint32_t chunk_sz) +{ + success_calls++; + memset(obuf, 0x5a, hash_algo_digest_size(algo)); + + return 0; +} + +static int hash_test_hard_error(struct udevice *dev, enum HASH_ALGO algo, + const void *ibuf, const uint32_t ilen, + void *obuf, uint32_t chunk_sz) +{ + hard_error_calls++; + + return -EINVAL; +} + +static const struct hash_ops hash_test_unsupported_ops = { + .hash_digest_wd = hash_test_unsupported, +}; + +static const struct hash_ops hash_test_success_ops = { + .hash_digest_wd = hash_test_success, +}; + +static const struct hash_ops hash_test_hard_error_ops = { + .hash_digest_wd = hash_test_hard_error, +}; + +U_BOOT_DRIVER(hash_test_unsupported_drv) = { + .name = "hash_test_unsupported", + .id = UCLASS_HASH, + .ops = &hash_test_unsupported_ops, +}; + +U_BOOT_DRIVER(hash_test_success_drv) = { + .name = "hash_test_success", + .id = UCLASS_HASH, + .ops = &hash_test_success_ops, +}; + +U_BOOT_DRIVER(hash_test_hard_error_drv) = { + .name = "hash_test_hard_error", + .id = UCLASS_HASH, + .ops = &hash_test_hard_error_ops, +}; + +static int hash_test_unbind_all(void) +{ + struct udevice *dev; + int ret; + + for (;;) { + ret = uclass_find_first_device(UCLASS_HASH, &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 hash_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_hash_provider_selection(struct unit_test_state *uts) +{ + u8 digest[32]; + int ret; + + ut_assertok(hash_test_unbind_all()); + ut_assertok(hash_test_bind(DM_DRIVER_GET(hash_test_unsupported_drv), + "hash-unsupported")); + ut_assertok(hash_test_bind(DM_DRIVER_GET(hash_test_success_drv), + "hash-success")); + + unsupported_calls = 0; + success_calls = 0; + memset(digest, 0, sizeof(digest)); + ret = hash_digest_wd_lookup(HASH_ALGO_SHA256, "test", 4, digest, 4); + ut_assertok(ret); + ut_asserteq(1, unsupported_calls); + ut_asserteq(1, success_calls); + for (int i = 0; i < sizeof(digest); i++) + ut_asserteq(0x5a, digest[i]); + + ut_assertok(hash_test_unbind_all()); + ut_assertok(hash_test_bind(DM_DRIVER_GET(hash_test_hard_error_drv), + "hash-hard-error")); + ut_assertok(hash_test_bind(DM_DRIVER_GET(hash_test_success_drv), + "hash-success")); + + hard_error_calls = 0; + success_calls = 0; + ret = hash_digest_wd_lookup(HASH_ALGO_SHA256, "test", 4, digest, 4); + ut_asserteq(-EINVAL, ret); + ut_asserteq(1, hard_error_calls); + ut_asserteq(0, success_calls); + + return 0; +} + +DM_TEST(dm_test_hash_provider_selection, UTF_SCAN_FDT);