Thread (18 messages) 18 messages, 4 authors, 1d ago

Re: [PATCH v3 07/11] nvmem: imx-ocotp-ele: Support the ELE API

From: Frank Li <hidden>
Date: 2026-07-23 18:48:47
Also in: imx, linux-devicetree, lkml

On Thu, Jul 23, 2026 at 09:27:29AM +0200, Frieder Schrempf wrote:
quoted hunk ↗ jump to hunk
From: Frieder Schrempf <redacted>

The fuses inside the Edgelock Secure Enclave are currently not
accessed via its API but through the FSB block which provides
limited access to some fuses.

The ELE API allows us to access all fuses with read/write
permissions. Therefore use it as primary method and only fall
back to the limited FSB if the ELE API is not available.

Signed-off-by: Frieder Schrempf <redacted>
---
 drivers/nvmem/imx-ocotp-ele.c | 178 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 177 insertions(+), 1 deletion(-)
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index 47ee6bd176a3..6ac7d588c4bd 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -7,10 +7,12 @@

 #include <linux/cleanup.h>
 #include <linux/device.h>
+#include <linux/firmware/imx/se_api.h>
 #include <linux/io.h>
 #include <linux/module.h>
 #include <linux/nvmem-provider.h>
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/if_ether.h>	/* ETH_ALEN */
@@ -45,8 +47,113 @@ struct imx_ocotp_priv {
 	struct nvmem_config config;
 	struct mutex lock;
 	const struct ocotp_devtype_data *data;
+	struct se_if_priv *se_data;
 };

+/* ELE commands and message sizes used for OCOTP fuse access. */
+#define ELE_READ_FUSE_REQ		0x97
+#define ELE_READ_FUSE_REQ_MSG_SZ	0x08
+#define ELE_READ_FUSE_RSP_MSG_SZ	0x0c
+
+#define ELE_WRITE_FUSE			0xd6
+#define ELE_WRITE_FUSE_REQ_MSG_SZ	0x0c
+#define ELE_WRITE_FUSE_RSP_MSG_SZ	0x0c
+
+/*
+ * imx_ocotp_se_read_fuse() - Request the secure enclave FW to read a fuse.
+ * @priv: handle to the secure-enclave interface.
+ * @fuse_id: fuse identifier to read.
+ * @value: location to store the read fuse value.
+ *
+ * Secure enclaves like the EdgeLock Enclave manage the fuses. This requests
+ * the FW to read the fuse and returns the value reported by the FW.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+static int imx_ocotp_se_read_fuse(struct se_if_priv *priv, u16 fuse_id, u32 *value)
+{
+	struct se_api_msg *tx_msg __free(kfree) = NULL;
+	struct se_api_msg *rx_msg __free(kfree) = NULL;
+	int ret;
+
+	if (!priv)
+		return -EINVAL;
+
+	tx_msg = kzalloc(ELE_READ_FUSE_REQ_MSG_SZ, GFP_KERNEL);
the same here

struct se_api_msg *tx_msg __free(kfree) = kzalloc(..);
quoted hunk ↗ jump to hunk
+	if (!tx_msg)
+		return -ENOMEM;
+
+	rx_msg = kzalloc(ELE_READ_FUSE_RSP_MSG_SZ, GFP_KERNEL);
+	if (!rx_msg)
+		return -ENOMEM;
+
+	ret = imx_se_fill_cmd_msg_hdr(priv, &tx_msg->header, ELE_READ_FUSE_REQ,
+				      ELE_READ_FUSE_REQ_MSG_SZ, true);
+	if (ret)
+		return ret;
+
+	tx_msg->data[0] = fuse_id;
+
+	ret = imx_se_msg_send_rcv(priv, tx_msg, ELE_READ_FUSE_REQ_MSG_SZ,
+				  rx_msg, ELE_READ_FUSE_RSP_MSG_SZ);
+	if (ret < 0)
+		return ret;
+
+	ret = imx_se_val_rsp_hdr_n_status(priv, rx_msg, ELE_READ_FUSE_REQ,
+					  ELE_READ_FUSE_RSP_MSG_SZ, true);
+	if (ret)
+		return ret;
+
+	*value = rx_msg->data[1];
+
+	return 0;
+}
+
+/*
+ * imx_ocotp_se_write_fuse() - Request the secure enclave FW to write a fuse.
+ * @priv: handle to the secure-enclave interface.
+ * @fuse_id: fuse identifier to write to.
+ * @value: value to write to the fuse.
+ *
+ * Secure enclaves like the EdgeLock Enclave manage the fuses. This requests
+ * the FW to program the fuse with the given value.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+static int imx_ocotp_se_write_fuse(struct se_if_priv *priv, u16 fuse_id, u32 value)
+{
+	struct se_api_msg *tx_msg __free(kfree) = NULL;
+	struct se_api_msg *rx_msg __free(kfree) = NULL;
+	int ret;
+
+	if (!priv)
+		return -EINVAL;
+
+	tx_msg = kzalloc(ELE_WRITE_FUSE_REQ_MSG_SZ, GFP_KERNEL);
+	if (!tx_msg)
+		return -ENOMEM;
+
+	rx_msg = kzalloc(ELE_WRITE_FUSE_RSP_MSG_SZ, GFP_KERNEL);
+	if (!rx_msg)
+		return -ENOMEM;
+
+	ret = imx_se_fill_cmd_msg_hdr(priv, &tx_msg->header, ELE_WRITE_FUSE,
+				      ELE_WRITE_FUSE_REQ_MSG_SZ, true);
+	if (ret)
+		return ret;
+
+	tx_msg->data[0] = (32 << 16) | (fuse_id << 5);
+	tx_msg->data[1] = value;
+
+	ret = imx_se_msg_send_rcv(priv, tx_msg, ELE_WRITE_FUSE_REQ_MSG_SZ,
+				  rx_msg, ELE_WRITE_FUSE_RSP_MSG_SZ);
+	if (ret < 0)
+		return ret;
+
+	return imx_se_val_rsp_hdr_n_status(priv, rx_msg, ELE_WRITE_FUSE,
+					   ELE_WRITE_FUSE_RSP_MSG_SZ, true);
+}
+
 static enum fuse_type imx_ocotp_fuse_type(void *context, u32 index)
 {
 	struct imx_ocotp_priv *priv = context;
@@ -73,6 +180,7 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
 	u32 count, index, num_bytes;
 	enum fuse_type type;
 	u32 *buf;
+	int ret;
 	int i;
 	u8 skipbytes;
@@ -93,6 +201,19 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
 	buf = p;

 	for (i = index; i < (index + count); i++) {
+		/*
+		 * All fuse registers can be read via ELE. If the SE device is
+		 * available, always prefer it.
+		 */
+		if (priv->se_data) {
+			ret = imx_ocotp_se_read_fuse(priv->se_data, i, buf++);
+			if (ret) {
+				mutex_unlock(&priv->lock);
you can change to guard(mutex) firstly
quoted hunk ↗ jump to hunk
+				return ret;
+			}
+			continue;
+		}
+
 		type = imx_ocotp_fuse_type(context, i);
 		if (type == FUSE_INVALID || type == FUSE_ELE) {
 			*buf++ = 0;
@@ -112,6 +233,32 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
 	return 0;
 };

+static int imx_ocotp_reg_write(void *context, unsigned int offset, void *val, size_t bytes)
+{
+	struct imx_ocotp_priv *priv = context;
+	u32 word = offset >> 2;
+	u32 *buf = val;
+	int ret;
+
+	/* allow only writing one complete OTP word at a time */
+	if ((bytes != 4) || (offset % 4 != 0))
+		return -EINVAL;
+
+	/*
+	 * The ELE API returns an error when writing an all-zero value. As
+	 * OTP fuse bits can not be switched from 1 to 0 anyway, skip these
+	 * values.
+	 */
+	if (!*buf)
+		return 0;
+
+	mutex_lock(&priv->lock);
+	ret = imx_ocotp_se_write_fuse(priv->se_data, word, *buf);
+	mutex_unlock(&priv->lock);
guard(mutex)(&priv->lock);

return imx_ocotp_se_write_fuse();
quoted hunk ↗ jump to hunk
+
+	return ret;
+}
+
 static int imx_ocotp_cell_pp(void *context, const char *id, int index,
 			     unsigned int offset, void *data, size_t bytes)
 {
@@ -138,8 +285,10 @@ static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
 static int imx_ele_ocotp_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
+	struct platform_device *se_pdev;
 	struct imx_ocotp_priv *priv;
 	struct nvmem_device *nvmem;
+	struct device_node *np;

 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
@@ -151,16 +300,40 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
 	if (IS_ERR(priv->base))
 		return PTR_ERR(priv->base);

+	np = of_parse_phandle(pdev->dev.of_node, "secure-enclave", 0);
struct device_node *np __free(device_node) = of_parse_phandle(...);

Frank
quoted hunk ↗ jump to hunk
+	if (!np) {
+		dev_info(dev, "missing or invalid SE handle, using readonly FSB\n");
+	} else {
+		se_pdev = of_find_device_by_node(np);
+
+		of_node_put(np);
+		if (!se_pdev)
+			return dev_err_probe(dev, -ENODEV, "failed to find SE device\n");
+
+		priv->se_data = platform_get_drvdata(se_pdev);
+		if (!priv->se_data) {
+			put_device(&se_pdev->dev);
+			return dev_err_probe(dev, -EPROBE_DEFER, "SE device not ready\n");
+		}
+
+		if (!device_link_add(dev, &se_pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
+			put_device(&se_pdev->dev);
+			return dev_err_probe(dev, -EINVAL, "failed to link to SE device\n");
+		}
+
+		put_device(&se_pdev->dev);
+	}
+
 	priv->config.dev = dev;
 	priv->config.name = "ELE-OCOTP";
 	priv->config.id = NVMEM_DEVID_AUTO;
 	priv->config.owner = THIS_MODULE;
 	priv->config.size = priv->data->size;
 	priv->config.reg_read = imx_ocotp_reg_read;
+	priv->config.reg_write = imx_ocotp_reg_write;
 	priv->config.word_size = 1;
 	priv->config.stride = 1;
 	priv->config.priv = priv;
-	priv->config.read_only = true;
 	priv->config.add_legacy_fixed_of_cells = true;
 	priv->config.fixup_dt_cell_info = imx_ocotp_fixup_dt_cell_info;
@@ -169,6 +342,9 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
 		priv->config.nkeepout = priv->data->nkeepout;
 	}

+	if (!priv->se_data)
+		priv->config.read_only = true;
+
 	mutex_init(&priv->lock);

 	nvmem = devm_nvmem_register(dev, &priv->config);

--
2.55.0
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help