This commit is contained in:
Tom Rini 2024-07-25 09:14:29 -06:00
commit 2d4925a096
14 changed files with 506 additions and 6 deletions

View file

@ -3,6 +3,17 @@
* Copyright (c) 2023 Linaro Ltd.
*/
&soc {
/* TODO: Remove this node once it appears in upstream dts */
trng: rng@12081400 {
compatible = "samsung,exynos850-trng";
reg = <0x12081400 0x100>;
clocks = <&cmu_core CLK_GOUT_SSS_ACLK>,
<&cmu_core CLK_GOUT_SSS_PCLK>;
clock-names = "secss", "pclk";
};
};
&pmu_system_controller {
bootph-all;
samsung,uart-debug-1;

View file

@ -250,6 +250,8 @@ config TARGET_E850_96
select PINCTRL
select PINCTRL_EXYNOS850
imply OF_UPSTREAM
imply DM_RNG
imply RNG_EXYNOS
endchoice
endif

View file

@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2020, Linaro Limited
# Copyright (C) 2024, Linaro Limited
# Sam Protsenko <semen.protsenko@linaro.org>
obj-y := e850-96.o
obj-y := e850-96.o fw.o

View file

@ -1,10 +1,11 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2020, Linaro Limited
* Sam Protsenko <semen.protsenko@linaro.org>
* Copyright (c) 2024, Linaro Ltd.
* Author: Sam Protsenko <semen.protsenko@linaro.org>
*/
#include <init.h>
#include "fw.h"
int dram_init(void)
{
@ -18,5 +19,6 @@ int dram_init_banksize(void)
int board_init(void)
{
load_ldfw();
return 0;
}

View file

@ -0,0 +1,26 @@
partitions=
uuid_disk=${uuid_gpt_disk};
name=efs,start=512K,size=20M,uuid=${uuid_gpt_efs};
name=env,size=16K,uuid=${uuid_gpt_env};
name=kernel,size=30M,uuid=${uuid_gpt_kernel};
name=ramdisk,size=26M,uuid=${uuid_gpt_ramdisk};
name=dtbo,size=1M,uuid=${uuid_gpt_dtbo};
name=ldfw,size=4016K,uuid=${uuid_gpt_ldfw};
name=keystorage,size=8K,uuid=${uuid_gpt_keystorage};
name=tzsw,size=1M,uuid=${uuid_gpt_tzsw};
name=harx,size=2M,uuid=${uuid_gpt_harx};
name=harx_rkp,size=2M,uuid=${uuid_gpt_harx_rkp};
name=logo,size=40M,uuid=${uuid_gpt_logo};
name=super,size=3600M,uuid=${uuid_gpt_super};
name=cache,size=300M,uuid=${uuid_gpt_cache};
name=modem,size=100M,uuid=${uuid_gpt_modem};
name=boot,size=100M,uuid=${uuid_gpt_boot};
name=persist,size=30M,uuid=${uuid_gpt_persist};
name=recovery,size=40M,uuid=${uuid_gpt_recovery};
name=misc,size=40M,uuid=${uuid_gpt_misc};
name=mnv,size=20M,uuid=${uuid_gpt_mnv};
name=frp,size=512K,uuid=${uuid_gpt_frp};
name=vbmeta,size=64K,uuid=${uuid_gpt_vbmeta};
name=metadata,size=16M,uuid=${uuid_gpt_metadata};
name=dtb,size=1M,uuid=${uuid_gpt_dtb};
name=userdata,size=-,uuid=${uuid_gpt_userdata}

131
board/samsung/e850-96/fw.c Normal file
View file

@ -0,0 +1,131 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2024 Linaro Ltd.
* Author: Sam Protsenko <semen.protsenko@linaro.org>
*
* Firmware loading code.
*/
#include <part.h>
#include <linux/arm-smccc.h>
#include "fw.h"
#define EMMC_IFACE "mmc"
#define EMMC_DEV_NUM 0
/* LDFW constants */
#define LDFW_PART_NAME "ldfw"
#define LDFW_NWD_ADDR 0x88000000
#define LDFW_MAGIC 0x10adab1e
#define SMC_CMD_LOAD_LDFW -0x500
#define SDM_HW_RESET_STATUS 0x1230
#define SDM_SW_RESET_STATUS 0x1231
#define SB_ERROR_PREFIX 0xfdaa0000
struct ldfw_header {
u32 magic;
u32 size;
u32 init_entry;
u32 entry_point;
u32 suspend_entry;
u32 resume_entry;
u32 start_smc_id;
u32 version;
u32 set_runtime_entry;
u32 reserved[3];
char fw_name[16];
};
static int read_fw(const char *part_name, void *buf)
{
struct blk_desc *blk_desc;
struct disk_partition part;
unsigned long cnt;
int part_num;
blk_desc = blk_get_dev(EMMC_IFACE, EMMC_DEV_NUM);
if (!blk_desc) {
debug("%s: Can't get eMMC device\n", __func__);
return -ENODEV;
}
part_num = part_get_info_by_name(blk_desc, part_name, &part);
if (part_num < 0) {
debug("%s: Can't get LDWF partition\n", __func__);
return -ENOENT;
}
cnt = blk_dread(blk_desc, part.start, part.size, buf);
if (cnt != part.size) {
debug("%s: Can't read LDFW partition\n", __func__);
return -EIO;
}
return 0;
}
int load_ldfw(void)
{
const phys_addr_t addr = (phys_addr_t)LDFW_NWD_ADDR;
struct ldfw_header *hdr;
struct arm_smccc_res res;
void *buf = (void *)addr;
u64 size = 0;
int err, i;
/* Load LDFW from the block device partition into RAM buffer */
err = read_fw(LDFW_PART_NAME, buf);
if (err)
return err;
/* Validate LDFW by magic number in its header */
hdr = buf;
if (hdr->magic != LDFW_MAGIC) {
debug("%s: Wrong LDFW magic; is LDFW flashed?\n", __func__);
return -EINVAL;
}
/* Calculate actual total size of all LDFW blobs */
for (i = 0; hdr->magic == LDFW_MAGIC; ++i) {
#ifdef DEBUG
char name[17] = { 0 };
strncpy(name, hdr->fw_name, 16);
debug("%s: ldfw #%d: version = 0x%x, name = %s\n", __func__, i,
hdr->version, name);
#endif
size += (u64)hdr->size;
hdr = (struct ldfw_header *)((u64)hdr + (u64)hdr->size);
}
debug("%s: The whole size of all LDFWs: 0x%llx\n", __func__, size);
/* Load LDFW firmware to SWD (Secure World) memory via EL3 monitor */
arm_smccc_smc(SMC_CMD_LOAD_LDFW, addr, size, 0, 0, 0, 0, 0, &res);
err = (int)res.a0;
if (err == -1 || err == SDM_HW_RESET_STATUS) {
debug("%s: Can't load LDFW in dump_gpr state\n", __func__);
return -EIO;
} else if (err == SDM_SW_RESET_STATUS) {
debug("%s: Can't load LDFW in kernel panic (SW RESET) state\n",
__func__);
return -EIO;
} else if (err < 0 && (err & 0xffff0000) == SB_ERROR_PREFIX) {
debug("%s: LDFW signature is corrupted! ret=0x%x\n", __func__,
(u32)err);
return -EIO;
} else if (err == 0) {
debug("%s: No LDFW is inited\n", __func__);
return -EIO;
}
#ifdef DEBUG
u32 tried = res.a0 & 0xffff;
u32 failed = (res.a0 >> 16) & 0xffff;
debug("%s: %d/%d LDFWs have been loaded successfully\n", __func__,
tried - failed, tried);
#endif
return 0;
}

View file

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (c) 2024 Linaro Ltd.
* Sam Protsenko <semen.protsenko@linaro.org>
*/
#ifndef __E850_96_FW_H
#define __E850_96_FW_H
int load_ldfw(void);
#endif /* __E850_96_FW_H */

View file

@ -3,4 +3,4 @@
# Copyright (c) 2014 Samsung Electronics Co., Ltd. All rights reserved.
# Przemyslaw Marczak <p.marczak@samsung.com>
obj-y := odroid.o
obj-$(CONFIG_TARGET_ODROID) := odroid.o

View file

@ -1,7 +1,7 @@
if TARGET_ODROID_XU3
config SYS_BOARD
default "smdk5420"
default "odroid"
config SYS_VENDOR
default "samsung"

View file

@ -11,6 +11,7 @@ CONFIG_DEFAULT_DEVICE_TREE="exynos/exynos850-e850-96"
CONFIG_SYS_LOAD_ADDR=0x80000000
# CONFIG_AUTOBOOT is not set
# CONFIG_DISPLAY_CPUINFO is not set
CONFIG_CMD_RNG=y
# CONFIG_NET is not set
CONFIG_CLK_EXYNOS850=y
# CONFIG_MMC is not set

View file

@ -323,14 +323,18 @@ U_BOOT_DRIVER(exynos850_cmu_peri) = {
/* Register Offset definitions for CMU_CORE (0x12000000) */
#define PLL_CON0_MUX_CLKCMU_CORE_BUS_USER 0x0600
#define PLL_CON0_MUX_CLKCMU_CORE_MMC_EMBD_USER 0x0620
#define PLL_CON0_MUX_CLKCMU_CORE_SSS_USER 0x0630
#define CLK_CON_DIV_DIV_CLK_CORE_BUSP 0x1800
#define CLK_CON_GAT_GOUT_CORE_MMC_EMBD_I_ACLK 0x20e8
#define CLK_CON_GAT_GOUT_CORE_MMC_EMBD_SDCLKIN 0x20ec
#define CLK_CON_GAT_GOUT_CORE_SSS_I_ACLK 0x2128
#define CLK_CON_GAT_GOUT_CORE_SSS_I_PCLK 0x212c
/* List of parent clocks for Muxes in CMU_CORE */
PNAME(mout_core_bus_user_p) = { "clock-oscclk", "dout_core_bus" };
PNAME(mout_core_mmc_embd_user_p) = { "clock-oscclk",
"dout_core_mmc_embd" };
PNAME(mout_core_sss_user_p) = { "clock-oscclk", "dout_core_sss" };
static const struct samsung_mux_clock core_mux_clks[] = {
MUX(CLK_MOUT_CORE_BUS_USER, "mout_core_bus_user", mout_core_bus_user_p,
@ -338,6 +342,8 @@ static const struct samsung_mux_clock core_mux_clks[] = {
MUX_F(CLK_MOUT_CORE_MMC_EMBD_USER, "mout_core_mmc_embd_user",
mout_core_mmc_embd_user_p, PLL_CON0_MUX_CLKCMU_CORE_MMC_EMBD_USER,
4, 1, CLK_SET_RATE_PARENT, 0),
MUX(CLK_MOUT_CORE_SSS_USER, "mout_core_sss_user", mout_core_sss_user_p,
PLL_CON0_MUX_CLKCMU_CORE_SSS_USER, 4, 1),
};
static const struct samsung_div_clock core_div_clks[] = {
@ -351,6 +357,10 @@ static const struct samsung_gate_clock core_gate_clks[] = {
GATE(CLK_GOUT_MMC_EMBD_SDCLKIN, "gout_mmc_embd_sdclkin",
"mout_core_mmc_embd_user", CLK_CON_GAT_GOUT_CORE_MMC_EMBD_SDCLKIN,
21, CLK_SET_RATE_PARENT, 0),
GATE(CLK_GOUT_SSS_ACLK, "gout_sss_aclk", "mout_core_sss_user",
CLK_CON_GAT_GOUT_CORE_SSS_I_ACLK, 21, 0, 0),
GATE(CLK_GOUT_SSS_PCLK, "gout_sss_pclk", "dout_core_busp",
CLK_CON_GAT_GOUT_CORE_SSS_I_PCLK, 21, 0, 0),
};
static const struct samsung_clk_group core_cmu_clks[] = {

View file

@ -120,4 +120,17 @@ 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 on
Exynos SoCs.
On some chips (like Exynos850) TRNG registers are protected with TZPC
(TrustZone Protection Control). For such chips the driver provides an
implementation based on SMC calls to EL3 monitor program. In that
case the LDFW (Loadable Firmware) has to be loaded first, as it
actually implements TRNG SMC calls.
endif

View file

@ -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

291
drivers/rng/exynos-trng.c Normal file
View file

@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2024 Linaro Ltd.
* Author: Sam Protsenko <semen.protsenko@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 - Chip specific data
*
* @smc: Set "true" if TRNG block has to be accessed via SMC calls
* @init: (Optional) TRNG initialization function to call on probe
* @exit: (Optional) TRNG deinitialization function to call on remove
* @read: Function to read the random data from TRNG block
*/
struct exynos_trng_variant {
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_priv - Driver's private data
*
* @base: Base address of MMIO registers of TRNG block
* @clk: Operating clock (needed for TRNG block functioning)
* @pclk: Bus clock (needed for interfacing the TRNG block registers)
* @data: Chip specific data
*/
struct exynos_trng_priv {
void __iomem *base;
struct clk *clk;
struct clk *pclk;
const struct exynos_trng_variant *data;
};
static int exynos_trng_read_reg(struct udevice *dev, void *data, size_t len)
{
struct exynos_trng_priv *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_priv *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 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_priv *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_priv *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 -EINVAL;
}
trng->clk = devm_clk_get(dev, "secss");
if (IS_ERR(trng->clk))
return PTR_ERR(trng->clk);
trng->pclk = devm_clk_get_optional(dev, "pclk");
if (IS_ERR(trng->pclk))
return PTR_ERR(trng->pclk);
return 0;
}
static int exynos_trng_probe(struct udevice *dev)
{
struct exynos_trng_priv *trng = dev_get_priv(dev);
int ret;
ret = clk_enable(trng->pclk);
if (ret)
return ret;
ret = clk_enable(trng->clk);
if (ret)
return ret;
if (trng->data->init)
ret = trng->data->init(dev);
return ret;
}
static int exynos_trng_remove(struct udevice *dev)
{
struct exynos_trng_priv *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_priv),
};