diff options
-rw-r--r-- | MAINTAINERS | 5 | ||||
-rw-r--r-- | drivers/rng/Kconfig | 9 | ||||
-rw-r--r-- | drivers/rng/Makefile | 1 | ||||
-rw-r--r-- | drivers/rng/smccc_trng.c | 207 |
4 files changed, 222 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index ba9fdb5a66..5945ba1c7a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1278,6 +1278,11 @@ F: drivers/gpio/sl28cpld-gpio.c F: drivers/misc/sl28cpld.c F: drivers/watchdog/sl28cpld-wdt.c +SMCCC TRNG +M: Etienne Carriere <etienne.carriere@linaro.org> +S: Maintained +F: drivers/rng/smccc_trng.c + SPI M: Jagan Teki <jagan@amarulasolutions.com> S: Maintained diff --git a/drivers/rng/Kconfig b/drivers/rng/Kconfig index c0c49c3484..21a9ff0195 100644 --- a/drivers/rng/Kconfig +++ b/drivers/rng/Kconfig @@ -65,4 +65,13 @@ config RNG_IPROC200 depends on DM_RNG help Enable random number generator for RPI4. + +config RNG_SMCCC_TRNG + bool "Arm SMCCC TRNG interface" + depends on DM_RNG && ARM_PSCI_FW + default y if ARM_SMCCC_FEATURES + help + Enable random number generator for platforms that support Arm + SMCCC TRNG interface. + endif diff --git a/drivers/rng/Makefile b/drivers/rng/Makefile index 0ae0ed4171..2494717d7c 100644 --- a/drivers/rng/Makefile +++ b/drivers/rng/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_RNG_OPTEE) += optee_rng.o obj-$(CONFIG_RNG_STM32MP1) += stm32mp1_rng.o obj-$(CONFIG_RNG_ROCKCHIP) += rockchip_rng.o obj-$(CONFIG_RNG_IPROC200) += iproc_rng200.o +obj-$(CONFIG_RNG_SMCCC_TRNG) += smccc_trng.o diff --git a/drivers/rng/smccc_trng.c b/drivers/rng/smccc_trng.c new file mode 100644 index 0000000000..3a4bb33941 --- /dev/null +++ b/drivers/rng/smccc_trng.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022, Linaro Limited + */ + +#define LOG_CATEGORY UCLASS_RNG + +#include <common.h> +#include <dm.h> +#include <linker_lists.h> +#include <log.h> +#include <rng.h> +#include <dm/device_compat.h> +#include <linux/arm-smccc.h> +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/psci.h> + +#define DRIVER_NAME "smccc-trng" + +/** + * Arm SMCCC TRNG firmware interface specification: + * https://developer.arm.com/documentation/den0098/latest/ + */ +#define ARM_SMCCC_TRNG_VERSION 0x84000050 +#define ARM_SMCCC_TRNG_FEATURES 0x84000051 +#define ARM_SMCCC_TRNG_GET_UUID 0x84000052 +#define ARM_SMCCC_TRNG_RND_32 0x84000053 +#define ARM_SMCCC_TRNG_RND_64 0xC4000053 + +#define ARM_SMCCC_RET_TRNG_SUCCESS ((ulong)0) +#define ARM_SMCCC_RET_TRNG_NOT_SUPPORTED ((ulong)-1) +#define ARM_SMCCC_RET_TRNG_INVALID_PARAMETER ((ulong)-2) +#define ARM_SMCCC_RET_TRNG_NO_ENTROPY ((ulong)-3) + +#define TRNG_MAJOR_MASK GENMASK(30, 16) +#define TRNG_MAJOR_SHIFT 16 +#define TRNG_MINOR_MASK GENMASK(15, 0) +#define TRNG_MINOR_SHIFT 0 + +#define TRNG_MAX_RND_64 (192 / 8) +#define TRNG_MAX_RND_32 (96 / 8) + +/** + * struct smccc_trng_priv - Private data for SMCCC TRNG support + * + * @smc64 - True if TRNG_RND_64 is supported, false if TRNG_RND_32 is supported + */ +struct smccc_trng_priv { + bool smc64; +}; + +/* + * Copy random bytes from ulong SMCCC output register to target buffer + * Defines 2 function flavors for whether ARM_SMCCC_TRNG_RND_32 or + * ARM_SMCCC_TRNG_RND_64 was used to invoke the service. + */ +static size_t smc32_copy_sample(u8 **ptr, size_t size, ulong *rnd) +{ + size_t len = min(size, sizeof(u32)); + u32 sample = *rnd; + + memcpy(*ptr, &sample, len); + *ptr += len; + + return size - len; +} + +static size_t smc64_copy_sample(u8 **ptr, size_t size, ulong *rnd) +{ + size_t len = min(size, sizeof(u64)); + u64 sample = *rnd; + + memcpy(*ptr, &sample, len); + *ptr += len; + + return size - len; +} + +static int smccc_trng_read(struct udevice *dev, void *data, size_t len) +{ + struct psci_plat_data *smccc = dev_get_parent_plat(dev); + struct smccc_trng_priv *priv = dev_get_priv(dev); + struct arm_smccc_res res; + u32 func_id; + u8 *ptr = data; + size_t rem = len; + size_t max_sz; + size_t (*copy_sample)(u8 **ptr, size_t size, ulong *rnd); + + if (priv->smc64) { + copy_sample = smc64_copy_sample; + func_id = ARM_SMCCC_TRNG_RND_64; + max_sz = TRNG_MAX_RND_64; + } else { + copy_sample = smc32_copy_sample; + func_id = ARM_SMCCC_TRNG_RND_32; + max_sz = TRNG_MAX_RND_32; + } + + while (rem) { + size_t sz = min(rem, max_sz); + + smccc->invoke_fn(func_id, sz * 8, 0, 0, 0, 0, 0, 0, &res); + + switch (res.a0) { + case ARM_SMCCC_RET_TRNG_SUCCESS: + break; + case ARM_SMCCC_RET_TRNG_NO_ENTROPY: + continue; + default: + return -EIO; + } + + rem -= sz; + + sz = copy_sample(&ptr, sz, &res.a3); + if (sz) + sz = copy_sample(&ptr, sz, &res.a2); + if (sz) + sz = copy_sample(&ptr, sz, &res.a1); + } + + return 0; +} + +static const struct dm_rng_ops smccc_trng_ops = { + .read = smccc_trng_read, +}; + +static bool smccc_trng_is_supported(void (*invoke_fn)(unsigned long a0, unsigned long a1, + unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, + unsigned long a6, unsigned long a7, + struct arm_smccc_res *res)) +{ + struct arm_smccc_res res; + + (*invoke_fn)(ARM_SMCCC_ARCH_FEATURES, ARM_SMCCC_TRNG_VERSION, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 == ARM_SMCCC_RET_NOT_SUPPORTED) + return false; + + (*invoke_fn)(ARM_SMCCC_TRNG_VERSION, 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 & BIT(31)) + return false; + + /* Test 64bit interface and fallback to 32bit interface */ + invoke_fn(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND_64, + 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 == ARM_SMCCC_RET_TRNG_NOT_SUPPORTED) + invoke_fn(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND_32, + 0, 0, 0, 0, 0, 0, &res); + + return res.a0 == ARM_SMCCC_RET_TRNG_SUCCESS; +} + +ARM_SMCCC_FEATURE_DRIVER(smccc_trng) = { + .driver_name = DRIVER_NAME, + .is_supported = smccc_trng_is_supported, +}; + +static int smccc_trng_probe(struct udevice *dev) +{ + struct psci_plat_data *smccc = dev_get_parent_plat(dev); + struct smccc_trng_priv *priv = dev_get_priv(dev); + struct arm_smccc_res res; + + if (!(smccc_trng_is_supported(smccc->invoke_fn))) + return -ENODEV; + + /* At least one of 64bit and 32bit interfaces is available */ + smccc->invoke_fn(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND_64, + 0, 0, 0, 0, 0, 0, &res); + priv->smc64 = (res.a0 == ARM_SMCCC_RET_TRNG_SUCCESS); + +#ifdef DEBUG + smccc->invoke_fn(ARM_SMCCC_TRNG_GET_UUID, 0, 0, 0, 0, 0, 0, 0, &res); + if (res.a0 != ARM_SMCCC_RET_TRNG_NOT_SUPPORTED) { + unsigned long uuid_a0 = res.a0; + unsigned long uuid_a1 = res.a1; + unsigned long uuid_a2 = res.a2; + unsigned long uuid_a3 = res.a3; + unsigned long major, minor; + + smccc->invoke_fn(ARM_SMCCC_TRNG_VERSION, 0, 0, 0, 0, 0, 0, 0, &res); + major = (res.a0 & TRNG_MAJOR_MASK) >> TRNG_MAJOR_SHIFT; + minor = (res.a0 & TRNG_MINOR_MASK) >> TRNG_MINOR_SHIFT; + + dev_dbg(dev, "Version %lu.%lu, UUID %08lx-%04lx-%04lx-%04lx-%04lx%08lx\n", + major, minor, uuid_a0, uuid_a1 >> 16, uuid_a1 & GENMASK(16, 0), + uuid_a2 >> 16, uuid_a2 & GENMASK(16, 0), uuid_a3); + } else { + dev_warn(dev, "Can't get TRNG UUID\n"); + } +#endif + + return 0; +} + +U_BOOT_DRIVER(smccc_trng) = { + .name = DRIVER_NAME, + .id = UCLASS_RNG, + .ops = &smccc_trng_ops, + .probe = smccc_trng_probe, + .priv_auto = sizeof(struct smccc_trng_priv), +}; |