Hi Sam, On Sat, 13 Jul 2024 at 00:44, Sam Protsenko <semen.protse...@linaro.org> wrote: > > Add True Random Number Generator (TRNG) driver for Exynos chips. This > implementation is heavily based on Linux kernel's counterpart [1]. It > also follows upstream dt-bindings [2]. > > TRNG block is usually a part of SSS (Security Sub System) IP-core on > Exynos chips. Because SSS access on Exynos850 is protected by TZPC > (TrustZone Protection Control), it's not possible to read/write TRNG > registers from U-Boot, as it's running in EL1 mode. Instead, the > corresponding SMC calls should be used to make the secure software > running in EL3 mode access it for us. Those SMC calls are handled by > LDFW (Loadable Firmware), which has to be loaded first. For example, for > E850-96 board it's done in its board_init(), so by the time RNG > capabilities are needed the LDFW should be already loaded and TRNG > should be functional. > > [1] drivers/char/hw_random/exynos-trng.c > [2] dts/upstream/Bindings/rng/samsung,exynos5250-trng.yaml > > Signed-off-by: Sam Protsenko <semen.protse...@linaro.org> > --- > drivers/rng/Kconfig | 7 + > drivers/rng/Makefile | 1 + > drivers/rng/exynos-trng.c | 275 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 283 insertions(+) > create mode 100644 drivers/rng/exynos-trng.c > > diff --git a/drivers/rng/Kconfig b/drivers/rng/Kconfig > index 5758ae192a66..18cd8fe91f68 100644 > --- a/drivers/rng/Kconfig > +++ b/drivers/rng/Kconfig > @@ -120,4 +120,11 @@ config RNG_TURRIS_RWTM > on other Armada-3700 devices (like EspressoBin) if Secure > Firmware from CZ.NIC is used. > > +config RNG_EXYNOS > + bool "Samsung Exynos True Random Number Generator support" > + depends on DM_RNG > + help > + Enable support for True Random Number Generator (TRNG) > + available in Exynos SoCs.
Can you mention the needed firmware here? > + > endif > diff --git a/drivers/rng/Makefile b/drivers/rng/Makefile > index c1f1c616e009..30553c9d6e99 100644 > --- a/drivers/rng/Makefile > +++ b/drivers/rng/Makefile > @@ -18,3 +18,4 @@ obj-$(CONFIG_RNG_ARM_RNDR) += arm_rndr.o > obj-$(CONFIG_TPM_RNG) += tpm_rng.o > obj-$(CONFIG_RNG_JH7110) += jh7110_rng.o > obj-$(CONFIG_RNG_TURRIS_RWTM) += turris_rwtm_rng.o > +obj-$(CONFIG_RNG_EXYNOS) += exynos-trng.o > diff --git a/drivers/rng/exynos-trng.c b/drivers/rng/exynos-trng.c > new file mode 100644 > index 000000000000..6de27a2acd44 > --- /dev/null > +++ b/drivers/rng/exynos-trng.c > @@ -0,0 +1,275 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (c) 2024 Linaro Ltd. > + * Author: Sam Protsenko <semen.protse...@linaro.org> > + * > + * Samsung Exynos TRNG driver (True Random Number Generator). > + */ > + > +#include <clk.h> > +#include <dm.h> > +#include <rng.h> > +#include <dm/device.h> > +#include <dm/device_compat.h> > +#include <asm/io.h> > +#include <linux/arm-smccc.h> > +#include <linux/bitops.h> > +#include <linux/delay.h> > +#include <linux/iopoll.h> > +#include <linux/time.h> > + > +#define EXYNOS_TRNG_CLKDIV 0x0 > +#define EXYNOS_TRNG_CLKDIV_MASK GENMASK(15, 0) > +#define EXYNOS_TRNG_CLOCK_RATE 500000 > + > +#define EXYNOS_TRNG_CTRL 0x20 > +#define EXYNOS_TRNG_CTRL_RNGEN BIT(31) > + > +#define EXYNOS_TRNG_POST_CTRL 0x30 > +#define EXYNOS_TRNG_ONLINE_CTRL 0x40 > +#define EXYNOS_TRNG_ONLINE_STAT 0x44 > +#define EXYNOS_TRNG_ONLINE_MAXCHI2 0x48 > +#define EXYNOS_TRNG_FIFO_CTRL 0x50 > +#define EXYNOS_TRNG_FIFO_0 0x80 > +#define EXYNOS_TRNG_FIFO_1 0x84 > +#define EXYNOS_TRNG_FIFO_2 0x88 > +#define EXYNOS_TRNG_FIFO_3 0x8c > +#define EXYNOS_TRNG_FIFO_4 0x90 > +#define EXYNOS_TRNG_FIFO_5 0x94 > +#define EXYNOS_TRNG_FIFO_6 0x98 > +#define EXYNOS_TRNG_FIFO_7 0x9c > +#define EXYNOS_TRNG_FIFO_LEN 8 > +#define EXYNOS_TRNG_FIFO_TIMEOUT (1 * USEC_PER_SEC) > + > +#define EXYNOS_SMC_CALL_VAL(func_num) \ > + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ > + ARM_SMCCC_SMC_32, \ > + ARM_SMCCC_OWNER_SIP, \ > + func_num) > + > +/* SMC command for DTRNG access */ > +#define SMC_CMD_RANDOM EXYNOS_SMC_CALL_VAL(0x1012) > + > +/* SMC_CMD_RANDOM: arguments */ > +#define HWRNG_INIT 0x0 > +#define HWRNG_EXIT 0x1 > +#define HWRNG_GET_DATA 0x2 > + > +/* SMC_CMD_RANDOM: return values */ > +#define HWRNG_RET_OK 0x0 > +#define HWRNG_RET_RETRY_ERROR 0x2 > + > +#define HWRNG_MAX_TRIES 100 > + > +struct exynos_trng_variant { please add comments > + bool smc; > + int (*init)(struct udevice *dev); > + void (*exit)(struct udevice *dev); > + int (*read)(struct udevice *dev, void *data, size_t len); > +}; > + > +struct exynos_trng { The convention is to add a _priv suffix > + void __iomem *base; > + struct clk *clk; /* operating clock */ > + struct clk *pclk; /* bus clock */ > + const struct exynos_trng_variant *data; > +}; > + > +static int exynos_trng_read_reg(struct udevice *dev, void *data, size_t len) > +{ > + struct exynos_trng *trng = dev_get_priv(dev); > + int val; > + > + len = min_t(size_t, len, EXYNOS_TRNG_FIFO_LEN * 4); > + writel_relaxed(len * 8, trng->base + EXYNOS_TRNG_FIFO_CTRL); > + val = readl_poll_timeout(trng->base + EXYNOS_TRNG_FIFO_CTRL, val, > + val == 0, EXYNOS_TRNG_FIFO_TIMEOUT); > + if (val < 0) > + return val; > + > + memcpy_fromio(data, trng->base + EXYNOS_TRNG_FIFO_0, len); > + > + return 0; > +} > + > +static int exynos_trng_read_smc(struct udevice *dev, void *data, size_t len) > +{ > + struct arm_smccc_res res; > + unsigned int copied = 0; > + u32 *buf = data; > + int tries = 0; > + > + while (copied < len) { > + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_GET_DATA, 0, 0, 0, 0, 0, > 0, > + &res); > + switch (res.a0) { > + case HWRNG_RET_OK: > + *buf++ = res.a2; > + *buf++ = res.a3; > + copied += 8; > + tries = 0; > + break; > + case HWRNG_RET_RETRY_ERROR: > + if (++tries >= HWRNG_MAX_TRIES) > + return -EIO; > + udelay(10); > + break; > + default: > + return -EIO; > + } > + } > + > + return 0; > +} > + > +static int exynos_trng_init_reg(struct udevice *dev) > +{ > + const u32 max_div = EXYNOS_TRNG_CLKDIV_MASK; > + struct exynos_trng *trng = dev_get_priv(dev); > + unsigned long sss_rate; > + u32 div; > + > + sss_rate = clk_get_rate(trng->clk); > + > + /* > + * For most TRNG circuits the clock frequency of under 500 kHz is > safe. > + * The clock divider should be an even number. > + */ > + div = sss_rate / EXYNOS_TRNG_CLOCK_RATE; > + div -= div % 2; /* make sure it's even */ > + if (div > max_div) { > + dev_err(dev, "Clock divider too large: %u", div); > + return -ERANGE; > + } > + writel_relaxed(div, trng->base + EXYNOS_TRNG_CLKDIV); > + > + /* Enable the generator */ > + writel_relaxed(EXYNOS_TRNG_CTRL_RNGEN, trng->base + EXYNOS_TRNG_CTRL); > + > + /* Disable post-processing */ > + writel_relaxed(0, trng->base + EXYNOS_TRNG_POST_CTRL); > + > + return 0; > +} > + > +static int exynos_trng_init_smc(struct udevice *dev) > +{ > + struct arm_smccc_res res; > + int ret = 0; > + > + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_INIT, 0, 0, 0, 0, 0, 0, &res); > + if (res.a0 != HWRNG_RET_OK) { > + dev_err(dev, "SMC command for TRNG init failed (%d)\n", > + (int)res.a0); > + ret = -EIO; > + } > + if ((int)res.a0 == -1) > + dev_info(dev, "Make sure LDFW is loaded\n"); return error here? > + > + return ret; > +} > + > +static void exynos_trng_exit_smc(struct udevice *dev) > +{ > + struct arm_smccc_res res; > + > + arm_smccc_smc(SMC_CMD_RANDOM, HWRNG_EXIT, 0, 0, 0, 0, 0, 0, &res); > +} > + > +static int exynos_trng_read(struct udevice *dev, void *data, size_t len) > +{ > + struct exynos_trng *trng = dev_get_priv(dev); > + > + return trng->data->read(dev, data, len); > +} > + > +static int exynos_trng_of_to_plat(struct udevice *dev) > +{ > + struct exynos_trng *trng = dev_get_priv(dev); > + > + trng->data = (struct exynos_trng_variant *)dev_get_driver_data(dev); > + if (!trng->data->smc) { > + trng->base = dev_read_addr_ptr(dev); > + if (!trng->base) > + return -ENODEV; That has a special meaning in U-Boot (i.e. that there is no device). Devicetree problems should return -EINVAL > + } > + > + trng->clk = devm_clk_get(dev, "secss"); > + if (IS_ERR(trng->clk)) > + return -ENODEV; Should return the actual error > + > + trng->pclk = devm_clk_get_optional(dev, "pclk"); > + if (IS_ERR(trng->pclk)) > + return -ENODEV; same here > + > + return 0; > +} > + > +static int exynos_trng_probe(struct udevice *dev) > +{ > + struct exynos_trng *trng = dev_get_priv(dev); > + int err; s/err/ret/ > + > + err = clk_enable(trng->pclk); > + if (err) > + return err; > + > + err = clk_enable(trng->clk); > + if (err) > + return err; > + > + if (trng->data->init) > + err = trng->data->init(dev); > + > + return err; > +} > + > +static int exynos_trng_remove(struct udevice *dev) > +{ > + struct exynos_trng *trng = dev_get_priv(dev); > + > + if (trng->data->exit) > + trng->data->exit(dev); > + > + /* Keep SSS clocks enabled, they are needed for EL3_MON and kernel */ > + > + return 0; > +} > + > +static const struct dm_rng_ops exynos_trng_ops = { > + .read = exynos_trng_read, > +}; > + > +static const struct exynos_trng_variant exynos5250_trng_data = { > + .init = exynos_trng_init_reg, > + .read = exynos_trng_read_reg, > +}; > + > +static const struct exynos_trng_variant exynos850_trng_data = { > + .smc = true, > + .init = exynos_trng_init_smc, > + .exit = exynos_trng_exit_smc, > + .read = exynos_trng_read_smc, > +}; > + > +static const struct udevice_id exynos_trng_match[] = { > + { > + .compatible = "samsung,exynos5250-trng", > + .data = (ulong)&exynos5250_trng_data, > + }, { > + .compatible = "samsung,exynos850-trng", > + .data = (ulong)&exynos850_trng_data, > + }, > + { }, > +}; > + > +U_BOOT_DRIVER(exynos_trng) = { > + .name = "exynos-trng", > + .id = UCLASS_RNG, > + .of_match = exynos_trng_match, > + .of_to_plat = exynos_trng_of_to_plat, > + .probe = exynos_trng_probe, > + .remove = exynos_trng_remove, > + .ops = &exynos_trng_ops, > + .priv_auto = sizeof(struct exynos_trng), > +}; > -- > 2.39.2 > Regards, Simon