Re: [PATCH v3 07/11] nvmem: imx-ocotp-ele: Support the ELE API
Frank Li <[email protected]>
| Newsgroups | dev.linux.lists.imx,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <amJh-rtZH--Hu38n@SMW015318> |
On Thu, Jul 23, 2026 at 09:27:29AM +0200, Frieder Schrempf wrote: > From: Frieder Schrempf <[email protected]> > > 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 <[email protected]> > --- > 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(..); > + 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 > + 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(); > + > + 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 > + 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 > >