[PATCH 4/5] rng: Add Exynos TRNG driver

Simon Glass sjg at chromium.org
Sat Jul 13 17:13:39 CEST 2024


Hi Sam,

On Sat, 13 Jul 2024 at 00:44, Sam Protsenko <semen.protsenko at 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.protsenko at 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.protsenko at 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


More information about the U-Boot mailing list