@@ -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
@@ -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;
}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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 */
LDFW is a Loadable Firmware which provides additional security capabilities in EL3 monitor. For example, True Random Number Generator (TRNG) block registers can't be accessed from EL1 (where U-Boot and Linux kernel are running), but it's possible to access TRNG capabilities via corresponding SMC calls, which in turn are handled by LDFW. To do so, LDFW firmware has to be loaded first. It's stored on a raw eMMC partition, so it has to be read into NWD (Normal World) RAM buffer, and then loaded to SWD (Secure World) memory using the special SMC call to EL3 monitor program. EL3_MON will load LDFW to SWD memory, more specifically to the area starting at 0xbf700000 (with size of 7.5 MiB). That memory area is reserved in device tree, so there shouldn't be any collisions. After that LDFW becomes functional. Implement LDFW firmware loading on board init. While at it, fix the copyright date in header comments, as this board support was actually added in 2024, not in 2020: it was probably a copy-paste mistake. Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org> --- board/samsung/e850-96/Makefile | 4 +- board/samsung/e850-96/e850-96.c | 6 +- board/samsung/e850-96/fw.c | 131 ++++++++++++++++++++++++++++++++ board/samsung/e850-96/fw.h | 12 +++ 4 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 board/samsung/e850-96/fw.c create mode 100644 board/samsung/e850-96/fw.h