[PATCH v3 7/7] drivers: misc: Add driver to access ZynqMP efuses
lukas.funke-oss at weidmueller.com
lukas.funke-oss at weidmueller.com
Tue Jun 4 16:27:41 CEST 2024
From: Lukas Funke <lukas.funke at weidmueller.com>
Add driver to access ZynqMP efuses. This is a u-boot port of [1].
Note: Accessing eFuses requires eFuse access to be enabled in the
underlying PMU firmware.
[1] https://lore.kernel.org/all/20240224114516.86365-8-srinivas.kandagatla@linaro.org/
Signed-off-by: Lukas Funke <lukas.funke at weidmueller.com>
---
Changes in v3:
- Align ZynqMP eFuse driver with Linux kernel
- Adapt versal, versal-net and zynqmp to use common chip-id function
- Enable CMD_FUSE and ZYNQMP_EFUSE for zynqmp_virt and zynqmp_kria defconfig
- Use 'dev_err' instead 'log_msg_ret' if possible
Changes in v2:
- Drop vendor specific fuse cmd, use existing fuse cmd
- Minor code refactoring (reverse x-mas tree)
drivers/misc/Kconfig | 8 +
drivers/misc/Makefile | 1 +
drivers/misc/zynqmp_efuse.c | 360 ++++++++++++++++++++++++++++++++++++
3 files changed, 369 insertions(+)
create mode 100644 drivers/misc/zynqmp_efuse.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6009d55f400..c07f50c9a76 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -298,6 +298,14 @@ config FSL_SEC_MON
Security Monitor can be transitioned on any security failures,
like software violations or hardware security violations.
+config ZYNQMP_EFUSE
+ bool "Enable ZynqMP eFUSE Driver"
+ depends on ZYNQMP_FIRMWARE
+ help
+ Enable access to Zynq UltraScale (ZynqMP) eFUSEs thought PMU firmware
+ interface. ZnyqMP has 256 eFUSEs where some of them are security related
+ and cannot be read back (i.e. AES key).
+
choice
prompt "Security monitor interaction endianess"
depends on FSL_SEC_MON
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e53d52c47b3..68ba5648eab 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -92,3 +92,4 @@ obj-$(CONFIG_ESM_K3) += k3_esm.o
obj-$(CONFIG_ESM_PMIC) += esm_pmic.o
obj-$(CONFIG_SL28CPLD) += sl28cpld.o
obj-$(CONFIG_SPL_SOCFPGA_DT_REG) += socfpga_dtreg.o
+obj-$(CONFIG_ZYNQMP_EFUSE) += zynqmp_efuse.o
diff --git a/drivers/misc/zynqmp_efuse.c b/drivers/misc/zynqmp_efuse.c
new file mode 100644
index 00000000000..d12de7494d2
--- /dev/null
+++ b/drivers/misc/zynqmp_efuse.c
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2014 - 2015 Xilinx, Inc.
+ * Michal Simek <michal.simek at amd.com>
+ *
+ * (C) Copyright 2024 Weidmueller Interface GmbH
+ * Lukas Funke <lukas.funke at weidmueller.com>
+ */
+
+#include <compiler.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <zynqmp_firmware.h>
+#include <asm/dma-mapping.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <misc.h>
+
+#define SILICON_REVISION_MASK 0xF
+#define P_USER_0_64_UPPER_MASK GENMASK(31, 16)
+#define P_USER_127_LOWER_4_BIT_MASK GENMASK(3, 0)
+#define WORD_INBYTES 4
+#define SOC_VER_SIZE 0x4
+#define EFUSE_MEMORY_SIZE 0x177
+#define UNUSED_SPACE 0x8
+#define ZYNQMP_NVMEM_SIZE (SOC_VER_SIZE + UNUSED_SPACE + \
+ EFUSE_MEMORY_SIZE)
+#define SOC_VERSION_OFFSET 0x0
+#define EFUSE_START_OFFSET 0xC
+#define EFUSE_END_OFFSET 0xFC
+#define EFUSE_PUF_START_OFFSET 0x100
+#define EFUSE_PUF_MID_OFFSET 0x140
+#define EFUSE_PUF_END_OFFSET 0x17F
+#define EFUSE_NOT_ENABLED 29
+
+/*
+ * efuse access type
+ */
+enum efuse_access {
+ EFUSE_READ = 0,
+ EFUSE_WRITE
+};
+
+/**
+ * struct xilinx_efuse - the basic structure
+ * @src: address of the buffer to store the data to be write/read
+ * @size: read/write word count
+ * @offset: read/write offset
+ * @flag: 0 - represents efuse read and 1- represents efuse write
+ * @pufuserfuse:0 - represents non-puf efuses, offset is used for read/write
+ * 1 - represents puf user fuse row number.
+ *
+ * this structure stores all the required details to
+ * read/write efuse memory.
+ */
+struct xilinx_efuse {
+ u64 src;
+ u32 size;
+ u32 offset;
+ enum efuse_access flag;
+ u32 pufuserfuse;
+};
+
+/**
+ * struct efuse_map_entry - offset and length of zynqmp fuses
+ * @offset: offset of efuse to be read/write
+ * @length: length of efuse
+ */
+struct efuse_map_entry {
+ u32 offset;
+ u32 length;
+};
+
+struct efuse_map_entry zynqmp_efuse_table[] = {
+ {0x000, 0x04}, /* soc revision */
+ {0x00c, 0x0c}, /* SoC DNA */
+ {0x020, 0x04}, /* efuse-usr0 */
+ {0x024, 0x04}, /* efuse-usr1 */
+ {0x028, 0x04}, /* efuse-usr2 */
+ {0x02c, 0x04}, /* efuse-usr3 */
+ {0x030, 0x04}, /* efuse-usr4 */
+ {0x034, 0x04}, /* efuse-usr5 */
+ {0x038, 0x04}, /* efuse-usr6 */
+ {0x03c, 0x04}, /* efuse-usr7 */
+ {0x040, 0x04}, /* efuse-miscusr */
+ {0x050, 0x04}, /* efuse-chash */
+ {0x054, 0x04}, /* efuse-pufmisc */
+ {0x058, 0x04}, /* efuse-sec */
+ {0x05c, 0x04}, /* efuse-spkid */
+ {0x060, 0x30}, /* efuse-aeskey */
+ {0x0a0, 0x30}, /* ppk0-hash */
+ {0x0d0, 0x30}, /* ppk1-hash */
+ {0x100, 0x7f}, /* pufuser */
+};
+
+static int zynqmp_efuse_get_length(u32 offset, u32 *base_offset, u32 *len)
+{
+ struct efuse_map_entry *fuse;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(zynqmp_efuse_table); ++i) {
+ fuse = &zynqmp_efuse_table[i];
+ if (offset >= fuse->offset &&
+ offset < fuse->offset + fuse->length) {
+ *base_offset = fuse->offset;
+ *len = fuse->length;
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+static int zynqmp_efuse_access(void *context, unsigned int offset,
+ void *val, size_t bytes, enum efuse_access flag,
+ unsigned int pufflag)
+{
+ struct udevice *dev = context;
+ struct xilinx_efuse *efuse;
+ ulong dma_addr;
+ ulong dma_buf;
+ size_t words = bytes / WORD_INBYTES;
+ int ret;
+ int value;
+ char *data;
+
+ if (bytes % WORD_INBYTES != 0) {
+ dev_err(dev, "Bytes requested should be word aligned\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (pufflag == 0 && offset % WORD_INBYTES) {
+ dev_err(dev, "Offset requested should be word aligned\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (pufflag == 1 && flag == EFUSE_WRITE) {
+ memcpy(&value, val, bytes);
+ if ((offset == EFUSE_PUF_START_OFFSET ||
+ offset == EFUSE_PUF_MID_OFFSET) &&
+ value & P_USER_0_64_UPPER_MASK) {
+ dev_err(dev, "Only lower 4 bytes are allowed to be programmed in P_USER_0 & P_USER_64\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (offset == EFUSE_PUF_END_OFFSET &&
+ (value & P_USER_127_LOWER_4_BIT_MASK)) {
+ dev_err(dev, "Only MSB 28 bits are allowed to be programmed for P_USER_127\n");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ efuse = dma_alloc_coherent(sizeof(struct xilinx_efuse), &dma_addr);
+ if (!efuse)
+ return -ENOMEM;
+
+ data = dma_alloc_coherent(bytes, &dma_buf);
+ if (!data) {
+ ret = -ENOMEM;
+ goto efuse_data_fail;
+ }
+
+ if (flag == EFUSE_WRITE) {
+ memcpy(data, val, bytes);
+ efuse->flag = EFUSE_WRITE;
+ } else {
+ efuse->flag = EFUSE_READ;
+ }
+
+ efuse->src = dma_buf;
+ efuse->size = words;
+ efuse->offset = offset;
+ efuse->pufuserfuse = pufflag;
+
+ flush_dcache_range((ulong)efuse, (ulong)efuse +
+ roundup(sizeof(struct xilinx_efuse), ARCH_DMA_MINALIGN));
+ flush_dcache_range((ulong)data, (ulong)data +
+ roundup(sizeof(struct xilinx_efuse), ARCH_DMA_MINALIGN));
+
+ zynqmp_pm_efuse_access(dma_addr, (u32 *)&ret);
+ if (ret != 0) {
+ if (ret == EFUSE_NOT_ENABLED) {
+ dev_err(dev, "efuse access is not enabled\n");
+ ret = -EOPNOTSUPP;
+ } else {
+ dev_err(dev, "Error in efuse read %x\n", ret);
+ ret = -EPERM;
+ }
+ goto efuse_access_err;
+ }
+
+ if (flag == EFUSE_READ)
+ memcpy(val, data, bytes);
+efuse_access_err:
+ dma_free_coherent(data);
+efuse_data_fail:
+ dma_free_coherent(efuse);
+
+ return ret;
+}
+
+static int zynqmp_nvmem_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct udevice *dev = context;
+ int ret;
+ int pufflag = 0;
+ int idcode;
+ int version;
+
+ if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET)
+ pufflag = 1;
+
+ switch (offset) {
+ /* Soc version offset is zero */
+ case SOC_VERSION_OFFSET:
+ if (bytes != SOC_VER_SIZE)
+ return -EOPNOTSUPP;
+
+ ret = zynqmp_pm_get_chipid((u32 *)&idcode, (u32 *)&version);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "Read chipid val %x %x\n", idcode, version);
+ *(int *)val = version & SILICON_REVISION_MASK;
+ break;
+ /* Efuse offset starts from 0xc */
+ case EFUSE_START_OFFSET ... EFUSE_END_OFFSET:
+ case EFUSE_PUF_START_OFFSET ... EFUSE_PUF_END_OFFSET:
+ ret = zynqmp_efuse_access(context, offset, val,
+ bytes, EFUSE_READ, pufflag);
+ break;
+ default:
+ *(u32 *)val = 0xDEADBEEF;
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static int zynqmp_nvmem_write(void *context,
+ unsigned int offset, void *val, size_t bytes)
+{
+ int pufflag = 0;
+
+ if (offset < EFUSE_START_OFFSET || offset > EFUSE_PUF_END_OFFSET)
+ return -EOPNOTSUPP;
+
+ if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET)
+ pufflag = 1;
+
+ return zynqmp_efuse_access(context, offset,
+ val, bytes, EFUSE_WRITE, pufflag);
+}
+
+int fuse_read(u32 bank, u32 word, u32 *val)
+{
+ u32 base_offset, offset, len;
+ struct udevice *dev;
+ u8 buf[128]; /* maximal size of efuse is 0x7f (pufuse) */
+ int ret;
+
+ ret = uclass_get_device_by_driver(UCLASS_MISC,
+ DM_DRIVER_GET(zynqmp_efuse), &dev);
+ if (ret)
+ return log_msg_ret("uclass_drv", ret);
+
+ ret = zynqmp_efuse_get_length(word, &base_offset, &len);
+ if (ret) {
+ dev_err(dev, "Invalid eFuse word offset\n");
+ return ret;
+ }
+
+ if (len > sizeof(buf))
+ return -EINVAL;
+
+ memset(buf, 0, sizeof(buf));
+
+ ret = misc_read(dev, base_offset, (void *)buf, len);
+ if (ret) {
+ dev_err(dev, "Cannot read eFuse @%u: %d", word, ret);
+ return ret;
+ }
+
+ offset = word - base_offset;
+ *val = ((u32 *)buf)[offset];
+
+ return 0;
+}
+
+int fuse_prog(u32 bank, u32 word, u32 val)
+{
+ u32 base_offset, len;
+ struct udevice *dev;
+ int ret;
+
+ ret = uclass_get_device_by_driver(UCLASS_MISC,
+ DM_DRIVER_GET(zynqmp_efuse), &dev);
+ if (ret)
+ return log_msg_ret("uclass_drv", ret);
+
+ ret = zynqmp_efuse_get_length(word, &base_offset, &len);
+ if (ret) {
+ dev_err(dev, "Invalid eFuse word offset\n");
+ return ret;
+ }
+
+ if (word != base_offset || len != sizeof(val))
+ return -EINVAL;
+
+ ret = misc_write(dev, word, (void *)&val, sizeof(val));
+ if (ret) {
+ dev_err(dev, "Cannot write eFuse @%u: %d", word, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+int fuse_sense(u32 bank, u32 word, u32 *val)
+{
+ /* sense is not supported */
+ return -ENOSYS;
+}
+
+int fuse_override(u32 bank, u32 word, u32 val)
+{
+ /* overriding is not supported */
+ return -ENOSYS;
+}
+
+static int zynqmp_nvmem_read_compat(struct udevice *dev, int offset,
+ void *val, int bytes)
+{
+ return zynqmp_nvmem_read((void *)dev, offset, val, bytes);
+}
+
+static int zynqmp_nvmem_write_compat(struct udevice *dev, int offset,
+ const void *val, int bytes)
+{
+ return zynqmp_nvmem_write((void *)dev, offset, (void *)val, bytes);
+}
+
+static const struct udevice_id zynqmp_efuse_match[] = {
+ { .compatible = "xlnx,zynqmp-nvmem-fw", },
+ { /* sentinel */ },
+};
+
+static const struct misc_ops zynqmp_efuse_ops = {
+ .read = zynqmp_nvmem_read_compat,
+ .write = zynqmp_nvmem_write_compat,
+};
+
+U_BOOT_DRIVER(zynqmp_efuse) = {
+ .name = "zynqmp_efuse",
+ .id = UCLASS_MISC,
+ .of_match = zynqmp_efuse_match,
+ .ops = &zynqmp_efuse_ops,
+};
--
2.30.2
More information about the U-Boot
mailing list