* Re: [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces
2026-07-15 18:09 ` [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces pankaj.gupta
@ 2026-07-15 17:25 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-15 17:25 UTC (permalink / raw)
To: pankaj.gupta; +Cc: devicetree, robh, imx, conor+dt, Frank.Li
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The patch adds a `kernel-doc` directive referencing `drivers/firmware/imx/se_ctrl.c`, but this file does not exist in the source tree. This will cause Sphinx to emit a build warning or error when building the kernel documentation (`make htmldocs`).
--
--- Patch [1]: [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces ---
commit 11c72cefa6ce5b082289d8b3c32eb18ba17eea92
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
Documentation/firmware: add imx/se to other_interfaces
This commit documents the i.MX SoC Service layer and character device drivers
for NXP hardware IPs such as the EdgeLock Enclave.
> diff --git a/Documentation/driver-api/firmware/other_interfaces.rst b/Documentation/driver-api/firmware/other_interfaces.rst
> index 06ac89adaafba..6c6fa9a0ba1df 100644
> --- a/Documentation/driver-api/firmware/other_interfaces.rst
> +++ b/Documentation/driver-api/firmware/other_interfaces.rst
[ ... ]
> @@ -49,3 +49,136 @@ of the requests on to a secure monitor (EL3).
>
> .. kernel-doc:: drivers/firmware/stratix10-svc.c
> :export:
> +
> +NXP Secure Enclave Firmware Interface
> +=====================================
[ ... ]
> +When a user sends a command to the firmware, it registers its device_ctx
> +as waiter of a response from firmware.
> +
> +Enclave's Firmware owns the storage management over a Linux filesystem.
> +For this c_dev provisions a dedicated slave device called "receiver".
> +
> +.. kernel-doc:: drivers/firmware/imx/se_ctrl.c
> + :export:
[Severity: Low]
Will this cause Sphinx build warnings?
It appears that drivers/firmware/imx/se_ctrl.c does not exist in the source
tree, which can cause make htmldocs to emit a build warning or error when it
attempts to extract the documentation.
Is this file missing from this patch series, or does the directive need to be
removed?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715-imx-se-if-v27-0-bb7c45952f06@nxp.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc
2026-07-15 18:09 ` [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc pankaj.gupta
@ 2026-07-15 17:27 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-15 17:27 UTC (permalink / raw)
To: pankaj.gupta; +Cc: Frank.Li, devicetree, robh, conor+dt, imx
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The compatible string 'fsl,imx95-se-ele-hsm' is excluded from the memory-region conditional constraints, which improperly forbids its use.
--
--- Patch [2]: [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc ---
commit a8dcc722ea68fab4850086d156b064a9fe58a7b9
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
dt-bindings: arm: fsl: add imx-se-fw binding doc
This commit adds a new device tree binding document for NXP's hardware
secure enclave IP, such as EdgeLock Enclave and V2X. It establishes the
messaging unit interface used for communication between the application core
and the enclave firmware on processors like i.MX8ULP, i.MX93, and i.MX95.
> diff --git a/Documentation/devicetree/bindings/firmware/fsl,imx-se.yaml b/Documentation/devicetree/bindings/firmware/fsl,imx-se.yaml
> new file mode 100644
> index 0000000000000..fa81adbf9b80d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/firmware/fsl,imx-se.yaml
[ ... ]
> +allOf:
> + # memory-region
> + - if:
> + properties:
> + compatible:
> + contains:
> + enum:
> + - fsl,imx8ulp-se-ele-hsm
> + - fsl,imx93-se-ele-hsm
> + then:
> + required:
> + - memory-region
> + else:
> + properties:
> + memory-region: false
[Severity: Medium]
Does this conditional block improperly forbid the memory-region property for
the fsl,imx95-se-ele-hsm compatible string? Because fsl,imx95-se-ele-hsm is
omitted from the enum in the if clause, the else clause is evaluated and
forces memory-region to be false. If an i.MX95 device tree includes the
memory-region property, it will fail schema validation.
[ ... ]
> +examples:
> + - |
> + secure-enclave {
> + compatible = "fsl,imx95-se-ele-hsm";
> + mboxes = <&ele_mu0 0 0>, <&ele_mu0 1 0>;
> + mbox-names = "tx", "rx";
> + };
> +...
[Severity: Medium]
Does this example mask the omitted memory-region conditional above? The
example relies on fsl,imx95-se-ele-hsm to bypass the memory-region
requirement, which allows a potentially incomplete hardware description to
silently pass the dt_binding_check tool.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715-imx-se-if-v27-0-bb7c45952f06@nxp.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node
2026-07-15 18:09 ` [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node pankaj.gupta
@ 2026-07-15 17:31 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-15 17:31 UTC (permalink / raw)
To: pankaj.gupta; +Cc: conor+dt, Frank.Li, imx, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing `memory-region` property causes probe failure and violates DT binding schema
--
--- Patch [6]: [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node ---
commit 1940854b723d6ccdefaa1704833b38d758fc0725
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
arm64: dts: imx8ulp: add secure enclave node
This commit adds support for the NXP secure enclave (EdgeLock Enclave
firmware) for imx8ulp-evk by introducing the hsm0 secure-enclave node
and a label for sram0.
> diff --git a/arch/arm64/boot/dts/freescale/imx8ulp.dtsi b/arch/arm64/boot/dts/freescale/imx8ulp.dtsi
> index 1de3ad60c6aa7..a5b591add9a49 100644
> --- a/arch/arm64/boot/dts/freescale/imx8ulp.dtsi
> +++ b/arch/arm64/boot/dts/freescale/imx8ulp.dtsi
[ ... ]
> @@ -185,6 +185,13 @@ scmi_sensor: protocol@15 {
> #thermal-sensor-cells = <1>;
> };
> };
> +
> + hsm0: secure-enclave {
> + compatible = "fsl,imx8ulp-se-ele-hsm";
> + mbox-names = "tx", "rx";
> + mboxes = <&s4muap 0 0>, <&s4muap 1 0>;
> + sram = <&sram0>;
> + };
> };
[Severity: High]
Does this node need a memory-region property?
Looking at se_if_probe() in drivers/firmware/imx/se_ctrl.c, the
reserved_dma_ranges flag is set to true for imx8ulp_se_ele_hsm. This
causes the driver to execute the following initialization:
drivers/firmware/imx/se_ctrl.c:se_if_probe() {
...
if (if_node->reserved_dma_ranges) {
ret = of_reserved_mem_device_init(dev);
if (ret)
return dev_err_probe(dev, ret,
"Failed to init reserved memory region.");
}
...
}
Without a memory-region property in the device tree node, will
of_reserved_mem_device_init() fail to parse a phandle, return -ENODEV,
and cause the driver probe to abort unconditionally?
It also appears that a memory-region is expected by the fsl,imx-se.yaml
DT binding schema.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715-imx-se-if-v27-0-bb7c45952f06@nxp.com?part=6
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v27 4/7] firmware: imx: device context dedicated to priv
2026-07-15 18:09 ` [PATCH v27 4/7] firmware: imx: device context dedicated to priv pankaj.gupta
@ 2026-07-15 17:34 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-15 17:34 UTC (permalink / raw)
To: pankaj.gupta; +Cc: imx, conor+dt, devicetree, Frank.Li, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] `init_misc_device_context()` unconditionally returns `-ENOMEM` on the success path, causing device probe to always fail.
- [High] Concurrent execution and lock dropping between `ele_msg_send_rcv` and `se_if_rx_callback` causes a NULL pointer dereference on `dev_ctx`.
--
--- Patch [4]: [PATCH v27 4/7] firmware: imx: device context dedicated to priv ---
commit 161a8ae1e5c289f19271beb8c2724ea2a799027b
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
firmware: imx: device context dedicated to priv
Add priv_dev_ctx to prepare enabling misc-device context based send-receive
path, to communicate with FW. No functionality change.
> diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
> index 3f5f78df49b52..955b7409ff9fc 100644
> --- a/drivers/firmware/imx/ele_common.c
> +++ b/drivers/firmware/imx/ele_common.c
[ ... ]
> @@ -167,11 +171,13 @@ int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
> if (err > 0)
> err = -ERESTARTSYS;
> priv->waiting_rsp_clbk_hdl.signal_rcvd = false;
> - dev_err(priv->dev, "Err[0x%x]:Interrupted by signal.", err);
> + dev_err(priv->dev, "%s: Err[0x%x]:Interrupted by signal.",
> + dev_ctx->devname, err);
> }
>
> clear_waiter:
> spin_lock_irqsave(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
> + priv->waiting_rsp_clbk_hdl.dev_ctx = NULL;
> priv->waiting_rsp_clbk_hdl.rx_msg = NULL;
> priv->waiting_rsp_clbk_hdl.rx_msg_sz = 0;
> spin_unlock_irqrestore(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
[ ... ]
> @@ -305,8 +311,8 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
>
> if (sz_mismatch)
> dev_err(dev,
> - "Rsp to CMD: hdr(0x%x) with different sz(%d != %d).\n",
> - *(u32 *)header,
> + "%s: Rsp to CMD: hdr(0x%x) with different sz(%d != %d).\n",
> + se_clbk_hdl->dev_ctx->devname, *(u32 *)header,
[Severity: High]
Can this result in a NULL pointer dereference if the awakened thread clears
the context?
In se_if_rx_callback(), complete() is called and the lock is dropped right
before checking sz_mismatch:
complete(&se_clbk_hdl->done);
spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
This wakes up ele_msg_send_rcv() which proceeds to the clear_waiter
cleanup path:
priv->waiting_rsp_clbk_hdl.dev_ctx = NULL;
If the awakened thread clears dev_ctx before the dev_err() is executed,
will accessing se_clbk_hdl->dev_ctx->devname here crash the kernel?
> (header->size << 2), exp_rx_msg_sz);
> } else {
> dev_err(dev, "Failed to select a device for message: %.8x\n",
[ ... ]
> diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
> index 7cb98a373334b..25f9cd5441f90 100644
> --- a/drivers/firmware/imx/se_ctrl.c
> +++ b/drivers/firmware/imx/se_ctrl.c
[ ... ]
> @@ -197,6 +197,36 @@ static int get_se_soc_info(struct se_if_priv *priv, const struct se_soc_info *se
> return 0;
> }
>
> +static int init_misc_device_context(struct se_if_priv *priv, int ch_id,
> + struct se_if_device_ctx **new_dev_ctx)
> +{
> + const char *err_str = "Failed to allocate memory";
> + struct se_if_device_ctx *dev_ctx;
> + int ret = -ENOMEM;
> +
> + dev_ctx = kzalloc_obj(*dev_ctx, GFP_KERNEL);
> +
> + if (!dev_ctx)
> + return ret;
> +
> + dev_ctx->devname = kasprintf(GFP_KERNEL, "%s0_ch%d",
> + get_se_if_name(priv->if_defs->se_if_type),
> + ch_id);
> + if (!dev_ctx->devname)
> + goto exit;
> +
> + dev_ctx->priv = priv;
> + *new_dev_ctx = dev_ctx;
> +
> + return ret;
[Severity: High]
Does this code always fail and cause the probe to abort?
The ret variable is initialized to -ENOMEM at the top of the function but is
never set to 0 on the success path. When se_if_probe() calls this function,
will it always receive an error and abort the driver initialization?
> +exit:
> + *new_dev_ctx = NULL;
> +
> + kfree(dev_ctx->devname);
> + kfree(dev_ctx);
> + return dev_err_probe(priv->dev, ret, "%s", err_str);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715-imx-se-if-v27-0-bb7c45952f06@nxp.com?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v27 5/7] firmware: drivers: imx: adds miscdev
2026-07-15 18:09 ` [PATCH v27 5/7] firmware: drivers: imx: adds miscdev pankaj.gupta
@ 2026-07-15 17:36 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-15 17:36 UTC (permalink / raw)
To: pankaj.gupta; +Cc: conor+dt, robh, devicetree, Frank.Li, imx
Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [High] The `read()` path drops `fops_lock` and `clbk_rx_lock` before accessing shared lists and `rx_msg`, leading to Use-After-Free and list corruption.
- [High] KASAN slab-out-of-bounds read in `se_val_rsp_hdr_n_status` due to insufficient response buffer size validation.
- [High] Indefinite hang during module unload or device unbind when userspace is blocked in `SE_IOCTL_CMD_SEND_RCV_RSP`.
- [High] Hung task panic triggered by signal interruption during long firmware waits in `ele_msg_rcv()`.
- [High] The `SE_IOCTL_CMD_SEND_RCV_RSP` ioctl silently swallows kernel-level validation errors, returning success to userspace.
- [High] Memory leak of dynamically allocated `se_buf_desc` structures when a device fd is closed without cleaning up pending buffers.
- [High] IOCTL error path zeroes out shared DMA memory buffers while the firmware might still be actively accessing them due to a timeout.
- [High] Fatal kernel crash during probe failure due to `dma_free_coherent` on an uninitialized pointer for the internal device context.
- [High] Unsynchronized clearing of `rx_msg_sz` in `se_if_fops_read()` causes lost commands and `-ENODEV` when interrupted by a signal.
- [High] TOCTOU race during device context creation in `open()` against unbind cleanup allows file operations on an unbound driver.
- [High] Race window during driver unbind between `cancel_work_sync()` and `se_clear_fw_busy()` allows work scheduling on freed memory.
- [Medium] Race condition exposing device node before probe initialization completes.
- [Medium] Mixing `goto`-based error handling with scope-based cleanup (`__free` and `guard`) violates kernel cleanup rules.
--
--- Patch [5]: [PATCH v27 5/7] firmware: drivers: imx: adds miscdev ---
commit 688f25f20d4d627be6c43a2c0f20ff6e2aa34b78
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
firmware: drivers: imx: adds miscdev
Adds the driver for communication interface to secure-enclave, that
enables exchanging messages with NXP secure enclave HW IP(s)
like EdgeLock Enclave, from User-Space Applications via character driver.
Link: https://lkml.kernel.org/r/20260629-imx-se-if-v26-0-146446285744@nxp.com?part=5
> diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
> index 955b7409ff9fc..7deff38c46132 100644
> --- a/drivers/firmware/imx/ele_common.c
> +++ b/drivers/firmware/imx/ele_common.c
> @@ -67,6 +122,9 @@ int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk
> * synchronized state.
> */
> if (READ_ONCE(priv->waiting_rsp_clbk_hdl.dev_ctx)) {
> + if (!is_rsp_wait_with_timeout)
> + return ret;
> +
> WRITE_ONCE(se_clbk_hdl->signal_rcvd, true);
> wait_uninterruptible = true;
> continue;
[Severity: High]
Can this sequence trigger a hung task panic on systems with the hung task
watchdog enabled?
In ele_msg_rcv(), if a signal interrupts the wait, the code sets
wait_uninterruptible = true and continues waiting. Since the timeout for
a standard command response is set to 5000 seconds, wouldn't waiting in
TASK_UNINTERRUPTIBLE for this long reliably crash the system?
> diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
> index 25f9cd5441f90..fb82df5c58d56 100644
> --- a/drivers/firmware/imx/se_ctrl.c
> +++ b/drivers/firmware/imx/se_ctrl.c
> @@ -197,11 +204,234 @@ static int get_se_soc_info(struct se_if_priv *priv, const struct se_soc_info *se
> return 0;
> }
>
> +static void cleanup_se_shared_mem(struct se_if_device_ctx *dev_ctx)
> +{
> + struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
> + struct se_if_priv *priv = dev_ctx->priv;
> +
> + /* Free non-secure shared buffer. */
> + dma_free_coherent(priv->dev, MAX_DATA_SIZE_PER_USER,
> + se_shared_mem_mgmt->non_secure_mem.ptr,
> + se_shared_mem_mgmt->non_secure_mem.dma_addr);
[Severity: High]
Could this leak memory by failing to free the se_buf_desc items in the
pending_in and pending_out lists when the device file descriptor is closed?
[Severity: High]
Can this cause a kernel crash during probe failure?
If an internal command times out during probe, the internal context
priv_dev_ctx might be marked fw_busy before shared memory is allocated
(ptr is NULL).
Calling dma_free_coherent() with a NULL pointer and a size of 65536 bytes
could trigger a bug in the DMA subsystem.
> +static void se_dev_ctx_shared_mem_cleanup(struct se_if_device_ctx *dev_ctx)
> +{
> + struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
> + struct list_head *pending_lists[] = {&se_shared_mem_mgmt->pending_in,
> + &se_shared_mem_mgmt->pending_out};
> + struct se_buf_desc *b_desc, *temp;
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(pending_lists); i++) {
> + list_for_each_entry_safe(b_desc, temp, pending_lists[i], link) {
> + if (b_desc->shared_buf_ptr)
> + memset(b_desc->shared_buf_ptr, 0, b_desc->size);
[Severity: High]
Does this zero out the shared DMA memory buffers while the firmware might
still be actively accessing them?
If ele_msg_send_rcv() times out, this cleanup is called on the error path.
Modifying active DMA buffers or reusing them for the next command could
corrupt firmware execution.
> +static void cleanup_dev_ctx(struct se_if_device_ctx *dev_ctx, bool is_fclose)
> +{
> + scoped_guard(mutex, &dev_ctx->fops_lock) {
> + if (dev_ctx->cleanup_done)
> + goto exit;
[Severity: High]
Can this cause an indefinite hang during unbind?
If userspace is blocked in ele_msg_rcv() waiting for a firmware response,
it holds fops_lock via scoped_cond_guard in se_ioctl(). This cleanup path
uninterruptibly blocks trying to acquire the same fops_lock, deadlocking
the unbind thread until the firmware replies or the timeout expires.
[Severity: Medium]
Does this violate the kernel cleanup subsystem rules?
Using a goto to jump completely out of a scoped_guard() block creates
confusing ownership semantics and can lead to bugs. The same issue appears
in ele_get_info() with __free(kfree).
> +static int se_ioctl_cmd_snd_rcv_rsp_handler(struct se_if_device_ctx *dev_ctx,
> + void __user *uarg)
> +{
[ ... ]
> + if (cmd_snd_rcv_rsp_info.rx_buf_sz < sizeof(struct se_msg_hdr) ||
> + cmd_snd_rcv_rsp_info.rx_buf_sz > MAX_ALLOWED_RX_MSG_SZ) {
> + se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
> + return -EINVAL;
> + }
[Severity: High]
Does this validation allow a user to allocate an rx_msg buffer of exactly
4 bytes?
In se_val_rsp_hdr_n_status(), msg->data[0] (bytes 4-7) is unconditionally
read. If the command bypasses size-mismatch validation (like
ELE_DEBUG_DUMP_REQ), this could cause a KASAN out-of-bounds read.
> + rsp_status_err =
> + se_val_rsp_hdr_n_status(priv, rx_msg, tx_msg->header.command,
> + cmd_snd_rcv_rsp_info.rx_buf_sz,
> + tx_msg->header.ver == priv->if_defs->base_api_ver);
> +
> + if (!rsp_status_err) {
> + err = se_dev_ctx_cpy_out_data(dev_ctx);
> + if (err < 0) {
> + se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
> + return err;
> + }
> + }
[Severity: High]
Is there a missing assignment to err here?
If se_val_rsp_hdr_n_status() fails, rsp_status_err is populated but never
assigned to err. The function falls through and returns the positive success
byte count from ele_msg_send_rcv(), silently swallowing the validation error
and misleading userspace.
> +static ssize_t se_if_fops_read(struct file *fp, char __user *buf, size_t size,
> + loff_t *ppos)
> +{
[ ... ]
> + /* We may need to copy the output data to user before
> + * delivering the completion message.
> + */
> + err = se_dev_ctx_cpy_out_data(dev_ctx);
> + if (err < 0)
> + goto exit;
[Severity: High]
Can this result in a use-after-free and list corruption?
The fops_lock is dropped before the blocking wait in ele_msg_rcv(). When
resuming, this code unconditionally accesses the pending_in and pending_out
lists in se_dev_ctx_cpy_out_data() without reacquiring fops_lock. If the
device was closed concurrently, the DMA memory and rx_msg could already be
freed.
> +exit:
> + priv->cmd_receiver_clbk_hdl.rx_msg_sz = 0;
> + se_dev_ctx_shared_mem_cleanup(dev_ctx);
> +
> + return err;
> +}
[Severity: High]
Does clearing rx_msg_sz locklessly here cause lost firmware commands?
If the wait in ele_msg_rcv() is interrupted by a signal, this jumps to exit
and clears the size. If a mailbox interrupt concurrently receives a message
and sets the size, this will overwrite it with 0. When userspace restarts
the read(), it will reject the valid message because rx_msg_sz is 0.
> +static int se_if_fops_open(struct inode *nd, struct file *fp)
> +{
[ ... ]
> + if (misc_dev_ctx->cleanup_done) {
> + err = -ENODEV;
> + goto out_unlock_misc;
> + }
> +
> + priv->dev_ctx_mono_count++;
> + err = init_device_context(priv, priv->dev_ctx_mono_count, &dev_ctx);
[Severity: High]
Is there a TOCTOU race here against unbind cleanup?
The fops_lock is dropped right after checking cleanup_done, but before
init_device_context() adds the new context to dev_ctx_list under modify_lock.
If se_if_probe_cleanup() runs in between, it could unbind the driver,
resulting in open() returning a file descriptor for a fully unbound device.
> +static void se_if_probe_cleanup(void *plat_dev)
> +{
[ ... ]
> + cancel_work_sync(&priv->fw_busy_work);
> + /*
> + * A timed-out synchronous command may have retained a dev_ctx through
> + * priv->fw_busy_dev_ctx even after the fd was closed and the context was
> + * removed from dev_ctx_list. If no late response arrived, release that
> + * retained context during driver teardown.
> + *
> + * se_clear_fw_busy() is idempotent and internally checks
> + * priv->fw_busy_dev_ctx under fw_busy_lock.
> + */
> + se_clear_fw_busy(priv);
[Severity: High]
Could this race with a late mailbox interrupt to cause a use-after-free?
cancel_work_sync() is called before se_clear_fw_busy() clears the fw_busy
state flag. If a mailbox interrupt arrives between these two calls, it will
see fw_busy is still set and call schedule_work() on the soon-to-be-freed
priv->fw_busy_work structure.
> @@ -312,15 +1449,22 @@ static int se_if_probe(struct platform_device *pdev)
> return dev_err_probe(dev, ret,
> "Failed to init reserved memory region.");
> }
>
> - ret = init_misc_device_context(priv, 0, &priv->priv_dev_ctx);
> + ret = init_misc_device_context(priv, 0, &priv->priv_dev_ctx, &se_if_fops);
> if (ret)
> return dev_err_probe(dev, ret,
> "Failed[0x%x] to create device contexts.",
[Severity: Medium]
Could this expose the device node to userspace before probe initialization
completes?
misc_register() is called inside init_misc_device_context(), but
se_if_probe() continues to initialize load_fw and SoC info after this returns.
If userspace immediately opens the device and sends a command, it could bypass
the se_load_firmware() step.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715-imx-se-if-v27-0-bb7c45952f06@nxp.com?part=5
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave
2026-07-15 18:09 ` [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave pankaj.gupta
@ 2026-07-15 17:36 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-07-15 17:36 UTC (permalink / raw)
To: pankaj.gupta; +Cc: Frank.Li, devicetree, robh, conor+dt, imx
Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [Critical] The `tx_msg` buffer can be freed while a dangling pointer remains in the mailbox core's transmit queue, leading to a Use-After-Free read.
- [High] A mathematically valid message checksum of `0` is treated as a fatal error, causing spontaneous failures in `ele_service_swap()`.
- [High] Global static variable `var_se_info.soc_rev` bypasses SoC device registration and per-device IMEM initialization on re-probes or secondary MUs.
- [High] Overriding successful firmware transaction results with `-ERESTARTSYS` causes non-idempotent commands to be executed twice.
- [High] DMA buffers are freed locally on timeout while the hardware firmware transaction is still active, causing background memory corruption.
- [High] The IMEM size returned by the firmware during export is not validated against the allocated buffer size, causing an out-of-bounds DMA read during resume.
- [High] The driver fails to set a 32-bit coherent DMA mask, despite the hardware requiring 32-bit physical addresses for key firmware commands.
- [Medium] Mixing `goto`-based error handling with scope-based `__free()` and `guard()` declarations within the same function.
- [Medium] `ele_msg_rcv()` hardcodes a check against the wrong callback handle for deferred signals, breaking abstraction.
- [Medium] Missing NULL check for dynamically allocated attributes in `get_se_soc_info()`.
- [Medium] Data race on global variable `var_se_info` during concurrent device probes.
- [Low] Residual error check for `se_fill_cmd_msg_hdr` remains in `ele_service_swap` despite the changelog explicitly claiming its removal.
--
--- Patch [3]: [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave ---
commit 625c1bf0423b041b8486ca353501df5a7d5c4833
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
firmware: imx: add driver for NXP EdgeLock Enclave
This commit adds a new driver for the NXP EdgeLock Enclave (ELE) on i.MX
SoCs. It handles messaging unit communication, firmware loading, and
provides a base API for the secure enclave.
> diff --git a/drivers/firmware/imx/ele_base_msg.c b/drivers/firmware/imx/ele_base_msg.c
> --- a/drivers/firmware/imx/ele_base_msg.c
> +++ b/drivers/firmware/imx/ele_base_msg.c
> @@ -0,0 +1,272 @@
[ ... ]
> +int ele_get_info(struct se_if_priv *priv, struct ele_dev_info *s_info)
> +{
[ ... ]
> + struct se_api_msg *tx_msg __free(kfree) =
> + kzalloc(ELE_GET_INFO_REQ_MSG_SZ, GFP_KERNEL);
[ ... ]
> + ret = ele_msg_send_rcv(priv, tx_msg, ELE_GET_INFO_REQ_MSG_SZ, rx_msg,
> + ELE_GET_INFO_RSP_MSG_SZ);
> + if (ret < 0)
> + goto exit;
[ ... ]
> +exit:
> + if (priv->mem_pool)
> + gen_pool_free(priv->mem_pool, (unsigned long)get_info_data,
> + ELE_GET_INFO_BUFF_SZ);
> + else
> + dma_free_coherent(priv->dev, ELE_GET_INFO_BUFF_SZ,
> + get_info_data, get_info_addr);
> +
> + return ret;
> +}
[Severity: Medium]
Is it safe to mix goto-based error handling with scope-based __free()
declarations in this function?
The subsystem cleanup guidelines state that functions should either use
scope-based cleanup exclusively or goto-based cleanup exclusively to avoid
ownership and double-free issues during refactoring.
[Severity: Critical]
If ele_msg_send_rcv() times out, tx_msg is automatically freed by
__free(kfree) upon returning.
However, mbox_send_message() places the tx_msg pointer into its transmit
queue. Could this leave a dangling pointer in the mailbox core that will
trigger a use-after-free when the mailbox interrupt eventually processes it?
[Severity: High]
If ele_msg_send_rcv() times out, this code frees the DMA buffer
get_info_data via dma_free_coherent() on the exit path.
Since the hardware firmware transaction might still be active in the
background, could the firmware subsequently write to this freed memory and
corrupt the kernel heap or cause IOMMU faults?
[ ... ]
> +int ele_service_swap(struct se_if_priv *priv,
> + dma_addr_t addr,
> + u32 addr_size, u16 flag)
> +{
[ ... ]
> + se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
> + ELE_SERVICE_SWAP_REQ, ELE_SERVICE_SWAP_REQ_MSG_SZ, true);
> + if (ret)
> + return ret;
[Severity: Low]
Since se_fill_cmd_msg_hdr() was modified to return void, is this error
check on ret leftover dead code from a previous refactoring?
> +
> + tx_msg->data[0] = flag;
> + tx_msg->data[1] = addr_size;
> + tx_msg->data[2] = ELE_NONE_VAL;
> + tx_msg->data[3] = lower_32_bits(addr);
> + tx_msg->data[4] = se_get_msg_chksum((u32 *)&tx_msg[0],
> + ELE_SERVICE_SWAP_REQ_MSG_SZ);
> + if (!tx_msg->data[4])
> + return -EINVAL;
[Severity: High]
Can the payload XOR checksum naturally evaluate to 0?
The se_get_msg_chksum() function computes a simple XOR across the payload
words. If the randomly assigned DMA address results in a checksum of 0, this
check would falsely treat it as an error and reject a valid command.
> diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
> --- a/drivers/firmware/imx/ele_common.c
> +++ b/drivers/firmware/imx/ele_common.c
> @@ -0,0 +1,448 @@
[ ... ]
> +int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl)
> +{
[ ... ]
> + if (ret == -ERESTARTSYS) {
[ ... ]
> + if (priv->waiting_rsp_clbk_hdl.rx_msg) {
> + WRITE_ONCE(se_clbk_hdl->signal_rcvd, true);
> + wait_uninterruptible = true;
> + continue;
> + }
> + break;
> + }
[Severity: Medium]
Does this hardcode the check against priv->waiting_rsp_clbk_hdl.rx_msg
instead of checking the passed parameter se_clbk_hdl->rx_msg?
If a different callback handle is passed, this could fail to handle the
-ERESTARTSYS signal properly since it ignores the function parameter.
[ ... ]
> +int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
> + void *rx_msg, int exp_rx_msg_sz)
> +{
> + unsigned long flags;
> + int err;
> +
> + guard(mutex)(&priv->se_if_cmd_lock);
[ ... ]
> + err = ele_msg_send(priv, tx_msg, tx_msg_sz);
> + if (err < 0)
> + goto clear_waiter;
[ ... ]
> +clear_waiter:
> + spin_lock_irqsave(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
[Severity: Medium]
Does mixing guard() scope-based resource management with the goto
clear_waiter label violate the subsystem cleanup rules?
The guidelines recommend converting all resources to scope-based cleanup or
none to avoid ownership confusion and early-exit bugs.
[ ... ]
> + if (priv->waiting_rsp_clbk_hdl.signal_rcvd) {
[ ... ]
> + if (err > 0)
> + err = -ERESTARTSYS;
> + priv->waiting_rsp_clbk_hdl.signal_rcvd = false;
> + dev_err(priv->dev, "Err[0x%x]:Interrupted by signal.", err);
> + }
[Severity: High]
If the hardware successfully processes the command and returns a positive
result, does overriding the return value to -ERESTARTSYS force userspace to
re-execute the command?
For non-idempotent hardware operations, this could cause the operation to be
executed twice.
[ ... ]
> +int se_save_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem)
> +{
[ ... ]
> + ret = ele_service_swap(priv, imem->daddr, ELE_IMEM_SIZE, ELE_IMEM_EXPORT);
> + if (ret < 0) {
> + dev_err(priv->dev, "Failed to export IMEM.");
> + imem->size = 0;
> + } else {
> + dev_dbg(priv->dev,
> + "Exported %d bytes of encrypted IMEM.",
> + ret);
> + imem->size = ret;
> + }
[Severity: High]
Does the size returned by the firmware during ELE_IMEM_EXPORT need to be
validated against ELE_IMEM_SIZE?
If the firmware returns an unexpectedly large size, it is saved directly to
imem->size. During a later resume, this unvalidated size is passed to
ELE_IMEM_IMPORT, which could cause the firmware to read past the end of the
allocated DMA buffer.
> diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
> --- a/drivers/firmware/imx/se_ctrl.c
> +++ b/drivers/firmware/imx/se_ctrl.c
> @@ -0,0 +1,410 @@
[ ... ]
> +static int get_se_soc_info(struct se_if_priv *priv, const struct se_soc_info *se_info)
> +{
[ ... ]
> + /*
> + * This function should be called once.
> + * Check if the se_soc_rev is zero to continue.
> + */
> + if (var_se_info.soc_rev)
> + return err;
> +
> + err = ele_fetch_soc_info(priv, &data);
> + if (err < 0)
> + return dev_err_probe(priv->dev, err, "Failed to fetch SoC Info.");
> + s_info = (void *)data;
> + var_se_info.soc_rev = s_info->d_info.soc_rev;
> + load_fw->imem.state = s_info->d_addn_info.imem_state;
[Severity: High]
If this driver probes multiple Messaging Unit instances, does returning early
based on the static global var_se_info.soc_rev prevent secondary instances
from initializing properly?
The early return skips setting the instance's load_fw->imem.state and
registering the soc_device attributes for the secondary device.
[Severity: Medium]
Could concurrent probes on multi-MU platforms cause a data race here?
The static global var_se_info.soc_rev is read and written without any
synchronization, which could lead to redundant queries or corrupted state.
[ ... ]
> + if (FIELD_GET(DEV_GETINFO_MIN_VER_MASK, var_se_info.soc_rev))
> + attr->revision = devm_kasprintf(priv->dev, GFP_KERNEL, "%x.%x",
> + FIELD_GET(DEV_GETINFO_MIN_VER_MASK,
> + var_se_info.soc_rev),
> + FIELD_GET(DEV_GETINFO_MAJ_VER_MASK,
> + var_se_info.soc_rev));
> + else
> + attr->revision = devm_kasprintf(priv->dev, GFP_KERNEL, "%x",
> + FIELD_GET(DEV_GETINFO_MAJ_VER_MASK,
> + var_se_info.soc_rev));
> +
> + attr->soc_id = se_info->soc_name;
> +
> + err = of_property_read_string(of_root, "model", &attr->machine);
> + if (err)
> + return -EINVAL;
> +
> + attr->family = "Freescale i.MX";
> +
> + attr->serial_number = devm_kasprintf(priv->dev,
> + GFP_KERNEL, "%016llX",
> + GET_SERIAL_NUM_FROM_UID(s_info->d_info.uid,
> + MAX_UID_SIZE >> 2));
> +
> + sdev = soc_device_register(attr);
[Severity: Medium]
Do these devm_kasprintf() calls need NULL checks?
If the system is under memory pressure, they could fail and return NULL,
which would be silently passed along to soc_device_register().
[ ... ]
> +static int se_if_probe(struct platform_device *pdev)
> +{
[ ... ]
> + if (load_fw->se_fw_img_nm->prim_fw_nm_in_rfs) {
> + /* allocate buffer where SE store encrypted IMEM */
> + load_fw->imem.buf = dmam_alloc_coherent(priv->dev, ELE_IMEM_SIZE,
> + &load_fw->imem.daddr,
> + GFP_KERNEL);
[Severity: High]
Since the hardware firmware interfaces strictly require 32-bit DMA addresses
(as verified by upper_32_bits() checks in ele_service_swap), should this
driver restrict the device's coherent DMA mask?
Calling dmam_alloc_coherent() without first setting
dma_set_coherent_mask(..., DMA_BIT_MASK(32)) could fail deterministically
on 64-bit systems mapping memory above 4GB.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715-imx-se-if-v27-0-bb7c45952f06@nxp.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave
@ 2026-07-15 18:09 pankaj.gupta
2026-07-15 18:09 ` [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces pankaj.gupta
` (6 more replies)
0 siblings, 7 replies; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel,
Frieder Schrempf, sashiko-bot
The NXP's i.MX EdgeLock Enclave, a HW IP creating an embedded secure
enclave within the SoC boundary to enable features like
- HSM
- SHE
- V2X
Communicates via message unit with linux kernel. This driver is
enables communication ensuring well defined message sequence protocol
between Application Core and enclave's firmware.
Driver configures multiple misc-device on the MU, for multiple
user-space applications can communicate on single MU.
It exists on some i.MX processors. e.g. i.MX8ULP, i.MX93 etc.
-------
Changes in v27:
Address Sashiko findings around:
Changes in v27:
Address Sashiko findings around:
- Encrypted IMEM DMA address handling, response waiter cleanup,
service-swap address validation, SoC device unregister,
devm-managed IMEM cleanup, timeout classification, signal
handling, firmware-load serialization, UAPI compatibility, and
timed-out firmware transaction cleanup.
- Use dma_addr_t for ELE firmware authentication and IMEM service-swap
buffers, store the encrypted IMEM DMA handle in the IMEM state, and pass
imem->daddr to IMEM save/restore.
- Reject ELE service-swap addresses whose upper 32 bits are set before
placing the address into the 32-bit firmware message field.
- Clear waiting_rsp_clbk_hdl state under clbk_rx_lock on all send/receive
exit paths, including ele_msg_send() failure.
- Keep mailbox TX completion handled by the mailbox controller/core with
knows_txdone set to false. The i.MX MU controller copies the message
payload into hardware registers synchronously.
- Move mutable firmware-load and IMEM state to per-device storage in
struct se_if_priv.
- Register a devres cleanup action to unregister the SoC device returned by
soc_device_register().
- Remove manual freeing of the devm-managed encrypted IMEM coherent buffer
from probe cleanup.
- Fix response-wait timeout classification by basing timeout handling on
the callback handle being waited on, not on command-receiver file context
identity.
- Use fixed-width UAPI fields and u64_to_user_ptr() to support 32-bit
userspace compatibility.
- Add compat_ioctl support for the misc-device UAPI.
- Bound userspace-provided message sizes before memdup_user().
- Track timed-out firmware transactions with the corresponding dev_ctx so
coherent DMA memory is not freed while firmware may still access it.
- Serialize firmware loading state with load_fw_lock to avoid concurrent
firmware authentication requests.
- Fix get_se_soc_id() to use the correct match-data type.
- Fix command-receiver lifecycle handling by clearing command-receiver
callback state under clbk_rx_lock and freeing rx_msg only after dropping
the lock.
- Avoid unbind/remove deadlock by not holding dev_ctx->fops_lock across the
blocking command-receiver read wait.
- Initialize dev_ctx reference counting with kref_init().
- Add ELE FW API command filtering through ele_uapi_allowed_fw_cmd().
- Move FW API command IDs and FW API-specific state transitions into
ele_fw_api.c/ele_fw_api.h.
- Allow command-receiver registration/unregistration through the supported
storage open/close FW API flow.
- Refactor shared-memory slot allocation and rollback in
se_ioctl_setup_iobuf_handler().
- Fix hsm0 device tree indentation reported by Sashiko.
- Address Lothar Waßmann's review comments:
- make se_fill_cmd_msg_hdr() return void and remove dead error checks;
- remove the trailing comma after the final empty of_device_id sentinel.
- Validate the series with checkpatch, sparse, W=1/W=2 builds, coccicheck,
dt_binding_check for fsl,imx-se.yaml, CHECK_DTBS=y for imx8ulp-evk.dtb,
and headers_install for include/uapi/linux/se_ioctl.h.
Reference:
- Link to v26: https://lore.kernel.org/r/20260629-imx-se-if-v26-0-146446285744@nxp.com
Changes in v26:
- Folded kernel test robot and Sashiko-bot fixes into the series.
- Added MAILBOX dependency for COMPILE_TEST builds.
- Hardened response waiter timeout handling and late-response processing.
- Serialized command receiver registration and callback-visible receiver state.
- Added iobuf round_up() overflow detection.
- Rolled back iobuf shared-memory reservation on setup failures.
- Bounded userspace-controlled response buffer size.
- Preserved ioctl operation errors across cleanup.
- Added explicit priv/dev_ctx lifetime handling and teardown/open serialization.
Testing:
- checkpatch.pl --strict: no warnings
- sparse: no warnings
- coccicheck on drivers/firmware/imx/: no driver-specific warnings
Reference:
- Link to v25: https://lore.kernel.org/r/20260122-imx-se-if-v25-0-5c3e3e3b69a8@nxp.com
Changes in v25:
5/7
- removes kernel bot reported warning errors.
3/7
- fix checkpatch --strict error.
1/2, 2/7, 4/7, 6/7 & 7/7
- No changes
Reference:
- Link to v24: https://lore.kernel.org/r/20260121-imx-se-if-v24-0-c5222df51cc2@nxp.com
Changes in v24:
5/7 & 3/7
- removes kernel bot reported warning errors.
1/2, 2/7, 4/7, 6/7 & 7/7
- No changes
Reference:
- Link to v23: https://lore.kernel.org/r/20251219-imx-se-if-v23-0-5c6773d00318@nxp.com
Changes in v23:
5/7
- removed un-neccessary 'kfree' from the func se_ioctl_cmd_snd_rcv_rsp_handler().
1/2, 2/7, 3/7, 4/7, 6/7 & 7/7
- No changes
Reference:
- Link to v22: https://lore.kernel.org/r/20251218-imx-se-if-v22-0-07418c872509@nxp.com
Changes in v22:
3/7 & 5/7
- reverted to previous change of using "__free(kfree)", by declare-and-initialize __free() vars next to their allocations.
1/7
- rename the se_fw.c to se_ctrl.c
2/7, 4/7, 5/7 & 7/7
- No changes.
Reference:
- Link to v21: https://lore.kernel.org/r/20251212-imx-se-if-v21-0-ee7d6052d848@nxp.com
Changes in v21:
3/7
- smatch warning fixes.
- Added "COMPILE_TEST" into "depends on IMX_MBOX && ARCH_MXC && ARM64"
- removed "__free(kfree)" & added kfree();
5/7
- removed "__free(kfree)" & added kfree();
1/2, 2/7, 4/7, & 7/7
- No changes
Reference:
- Link to v20: https://lore.kernel.org/r/20251203-imx-se-if-v20-0-a04a25c4255f@nxp.com
Changes in v20:
5/7:
- adds a func "se_chk_tx_msg_hdr", to check the validity of the in-coming message from usersapce.
1/2, 2/7, 3/7, 4/7, 6/7 & 7/7
- No changes
Reference:
- Link to v19: https://lore.kernel.org/r/20250927-imx-se-if-v19-0-d1e7e960c118@nxp.com
Changes in v19:
1/7
- Added 9 lines to the Introduction from line 73-82.
3/7
- Update the commit message for " For i.MX9x SoC(s) there is at least one
dedicated ELE MU(s) for each world - Linux(one or more) and OP-TEE OS (one or
more), that needs to be shared between them.."
Reference:
- Link to v18: https://lore.kernel.org/r/20250619-imx-se-if-v18-0-c98391ba446d@nxp.com
Changes in v18:
1/7
- Wrap both diagrams above in literal code block by using double-colon
3/7 & 5/7
- Collected Frank's R-b tag.
2/7, 4/7, 6/7 & 7/7
- No changes
Reference:
- Link to v17: https://lore.kernel.org/r/20250426-imx-se-if-v17-0-0c85155a50d1@nxp.com
Changes in v17:
- Changes to 3/7 & 5/7: to wrap code text at 80 character whereever possible.
Reference:
- Link to v16: https://lore.kernel.org/r/20250409-imx-se-if-v16-0-5394e5f3417e@nxp.com
Changes in v16:
- commit 3/7 and 4/7 are moved to end commits making them as 6/7 and 7/7 respectively.
- No change in 1/7 & 2/7.
7/7
- Collected Frank's R-b tag.
6/7
- commit message is updated to wrap at 75 characters.
5/7
- func add_b_desc_to_pending_list, removed the initialization of b_desc to
NULL.
- variable timeout in func ele_msg_rcv(), is renamed to timeout_ms.
- struct se_if_priv, member variable se_rcv_msg_timeout, is renamed to
se_rcv_msg_timeout_ms.
- in func load_firmware, move the label exit after dma_free_coherent.
4/7
- commit message is updated to wrap at 75 characters.
3/7
- ele_debug_dump, updated the assignment of keep_logging.
- ele_fw_authenticate function definition is updated to take two address
as arguments.
Reference:
- Link to v15: https://lore.kernel.org/r/20250407-imx-se-if-v15-0-e3382cecda01@nxp.com
Changes in v15:
- Patch 3/6 is split into two:
- 3/7: arm64: dts: imx8ulp-evk: add reserved memory property
- 4/7: arm64: dts: imx8ulp: add nxp secure enclave firmware
- No change in 1/7 & 2/7.
7/7
- removed the se_intance_id structure member variable.
- replace variable name from wait to timeout.
- used 'goto' to follow the common exit path calling "release_firmware(fw);" in case of error path.
- removed TBD string.
- Used ARRAY_SIZE(pending_lists).
- moved init_device_context after init_misc_device_context.
- defined err as long to avoid force convert in func
- added se_rcv_msg_timeout to priv, to control probe/suspend/resume per interface.
6/7
- removed the se_intance_id structure member variable.
- Added dev_ctx to the structure se_clbk_handle, too.
- Collected Frank's R-b tag.
5/7
- removed the se_intance_id structure member variable.
- since added se_if_probe_cleanup to devm, se_if_remove() is redundant. hence removed it.
- rename se_add_msg_chksum to se_get_msg_chksum
- added check if msg-size is 4 byte aligned.
- Fixed multiline comments.
- ele_debug_dump api is updated as part of comment disposition like single setting of flag "keep_logging" & adding if (ret).
- moved dev_err to dev_dbg, for imem save/restore functions.
- moved func get_se_if_name, from 7/7 to here.
3/7
- Updated the commit message.
- split the current patch into two:
-- 3/7 for board dts, and
-- 4/7 for chip dts
Reference:
- Link to v14: https://lore.kernel.org/r/20250327-imx-se-if-v14-0-2219448932e4@nxp.com
Changes in v14:
- Patch 5/5 is split into two:
- firmware: drivers: imx: adds miscdev
- Introduce dev-ctx dedicated to private.
-- Base patch before enabling misc-device context, to have the send-receive path, based on device context.
- No change in 1/6 & 2/6.
- Copied change logs from individual commits.
6/6
- moved definition of func se_load_firmware, from 4/6 patch to this patch.
- split init_device_context to init_misc_device_context.
- Different value of se_rcv_msg_timeout is required to be set. Receiving the response of 4K RSA operation can to take upto 3 minutes.
This long value cannot be set during Linux: boot-up and suspend-resume.
Hence, it will be set to default small-value during Linux: boot-up and suspend-resume.
- func se_dev_ctx_cpy_out_data(), in either case: do_cpy true or false, the clean-up needs to be done and it is implemented like wise.
Once do_cpy is false, no need to continue copy to user buffer. But continue to do clean-up. hence cannot return.
And every dev-ctx operation is done after taking the lock. Hence, two operations with same dev-ctx is not possible in parallel.
- func "init_device_context", for 0th misc dev_ctx, which is created at the time of probe, the device memory management is required. hence there is a difference.
- func "init_device_context", dev_er is replaced with return dev_err_probe.
- func "init_device_context", devm_add_action is replaced by devm_add_action_reset.
- removed type-cast from func se_ioctl_get_se_soc_info_handler().
- used scoped_cond_guard(mutex, _intr, return -EBUSY, &<mutex_lock>)
- combined dev_err & dev_dbg to one dev_err in se_if_fops_read().
- removed the structure member "se_shared_mem_mgmt->secure_mem".
4/6
- trimmed the ele_fetch_soc_info.
- removed the function ptr "se_info->se_fetch_soc_info" and replaced with ele_fetch_soc_info.
- moved definition of func se_load_firmware, to 6/6 patch.
- Different SoC, different ways to fetch soc_info. Generic function declaration for ele_fetch_soc_info() is needed. Hence wrapping ele_get_info() in it.
- Updated Kconfig help text for assertive tone.
- func ele_debug_dump is updated, to remove constructing the format string.
- removed the macro usage for SOC_ID_MASK.
- used low case hex number.
- Condition will never occur, where msg_len satisfy the following condition "msg_len % 4 != 0". Err msg is added if it occurs.
- Function description is added to se_add_msg_crc.
- timeout is added to function ele_msg_rcv, in 5/5 patch.
- local variable "header" is initialized with "tx_msg" and replaced "return err" with "return tx_msg_sz" in func ele_msg_send().
- replace function name from "exception_for_size" to "check_hdr_exception_for_sz"
- replaced "return ret > 0 ? 0 : -1;" with "return ret > 0 ? 0 : ret;" in func "se_save_imem_state".
- func "se_restore_imem_state", to return if the condition is false to proceed.
- removed casting by (void *).
- removed devm_kasprintf and done direct allocatiion for attr->soc_id = "i.MX8ULP" & attr->soc_id = "i.MX8ULP", & attr->family.
- Followed Reverse christmas tree order, whereever missing.
- There is no return if ele_fw_authenticate fails. Execution flow continue forward and execute the fucn dma_free_coherent().
- The loop is not for retry. The loop is needed to load secondary fw followed by loading primary fw, first. This is the case when ELE also got reset.
- dev_err_probe is corrected in func "se_if_request_channel".
3/6
-
Reference:
- Link to v13: https://lore.kernel.org/r/20250311-imx-se-if-v13-0-9cc6d8fd6d1c@nxp.com
Changes in v13:
5/5
- Updated the commit message for imperative mood.
- Remove the usage of macros- NODE_NAME, GET_ASCII_TO_U8, GET_IDX_FROM_DEV_NODE_NAME.
- Clean-up the return path by replacing "ret = -<err>; return ret;" with "return -<err>;"
- Clean-up the return path by replacing "ret = -<err>; goto exit;" with "return -<err>;"
- Removed goto statements from the entire driver, where there is no common code at function's exit.
- Fixes the check-patch erros reported with flag "--strict"
- Replaced devm_add_action, with devm_add_action_or_reset
- Removed the un-necesary and obvious code comments.
- Removed dev_probe_err at the exit of function se_if_probe().
4/5
- Clean-up the return path by replacing "ret = -<err>; return ret;" with "return -<err>;"
- Clean-up the return path by replacing "ret = -<err>; goto exit;" with "return -<err>;"
- Removed goto statements from the entire driver, where there is no common code at function's exit.
- fixes the check-patch erros reported with flag "--strict"
- removed the un-necesary and obvious code comments.
- variable received msg timeout to be different at boot-up & suspend/resume and send/recv ioctlis.
3/5
- compatible string is modified from "fsl,imx8ulp-se" to "fsl,imx8ulp-se-ele-hsm".
- updated the alias name.
2/5
- compatible string is modified from "fsl,imx8ulp-se" to "fsl,imx8ulp-se-ele-hsm".
- compatible string is modified from "fsl,imx93-se" to "fsl,imx93-se-ele-hsm".
- compatible string is modified from "fsl,imx95-se" to "fsl,imx95-se-ele-hsm".
- Mis-understood the +1 from Conor. Hence dropped the Reviewed-by tag.
- Collected Rob's R-b tag on v7 (https://lore.kernel.org/all/172589152997.4184616.5889493628960272898.robh@kernel.org/)
1/5
- No change
Reference:
- Link to v12: https://lore.kernel.org/r/20250120-imx-se-if-v12-0-c5ec9754570c@nxp.com
Changes in v12:
5/5
- increased the wait-timeout.
4/5
- rename flag "handle_susp_resm" to "imem_mgmt"
- moved the buffer allocation ot load_fw->imem.buf, to se_probe_if.
- setting imem state at initialization.
3/5
- No change
2/5
- No change
1/5
- No change
Reference:
- Link to v11: https://lore.kernel.org/r/20241220-imx-se-if-v11-0-0c7e65d7ae7b@nxp.com
Changes in v11:
5/5
- devname is constructed by concatinating get_se_if_name(se_if_id) & se_if_instance_id.
- ele_rcv_msg(), is updated to add the wait_interruptible_timeout for the non-NVM-Daemon message exchanges, such that in case of no response from FW,
Linux donot hangs.
- added a new helper function get_se_if_name(), to return the secure-enclave interface owner's name string.
- added a new helper function get_se_soc_id(), to return the secure-enclave's SoC id.
4/5
- moved the se_if_node_info member "soc_register", to the struct "se_if_node_info_list"; as soc registration done once, not per interface.
- moved the se_if_node_info member "se_fetch_soc_info", to the struct "se_if_node_info_list"; as soc info fetching is done once, not per interface.
- Added two member variable se_if_id and se_if_instance_id to struct se_if_defines.
- removed the member "se_name" from struct "se_if_node_info". Rather, it will constructed by concatinating get_se_if_name(se_if_id) & se_if_instance_id.
- moved the static global variable "se_version", to the newly created structure "struct se_var_info".
- moved the member "struct se_fw_load_info load_fw" of "se_if_node_info_list", to the newly created structure "struct se_var_info".
- Replaced RUNTIME_PM_OPS with SET_SYSTEM_SLEEP_PM_OPS, in power-managment ops.
3/5
- No change
2/5
- No change
1/5
- No change
Reference:
- Link to v10: https://lore.kernel.org/r/20241104-imx-se-if-v10-0-bf06083cc97f@nxp.com
v10: firmware: imx: driver for NXP secure-enclave
Changes in v10:
5/5
- replaced the u8, u16, u32, u64, with __u8, __u16, __u32, __u64 in
'include/uapi/linux/se_ioctl.h'.
4/5
- No change
3/5
- No change
2/5
- No change
1/5
- No change
Reference:
- Link to v9: https://lore.kernel.org/r/20241016-imx-se-if-v9-0-fd8fa0c04eab@nxp.com
Changes in v9:
4/5
- change se_if_remove function signature, required after rebase to v6.12-rc1.
- move the info->macros to a structure "struct se_if_defines if_defs".
- Removed "info" from "struct se_if_defines if_defs".
- Moved "mem_pool" from "struct se_if_defines if_defs" to "priv".
- Fetching "info" using container-of.
5/5
- Fetching "info" using container-of.
- Fixed issue reported by sparse.
Reference:
- Link to v8: https://lore.kernel.org/r/20241015-imx-se-if-v8-0-915438e267d3@nxp.com
Changes in v8:
5/5
- Remove the check for SE_IF_CTX_OPENED.
- replaced dev_ctx->priv-dev, priv->dev, whereever possible.
- func "if_misc_deregister" moved before func "init_device_context".
- func "init_device_context" before func "se_ioctl_cmd_snd_rcv_rsp_handler".
- func "se_if_fops_write" and "se_if_fops_read", are moved after func "se_ioctl_get_mu_info".
- non static functions "se_dev_ctx_cpy_out_data, se_dev_ctx_shared_mem_cleanup & init_device_context" are moved static and local scope.
- Removed back & forth between the two structs "struct se_if_device_ctx *dev_ctx" and "struct se_shared_mem_mgmt_info *se_shared_mem_mgmt"
- removed the NULL check for bdesc.
- fops_open, is corrected for acquiring the fops_lock.
- Fops_close, mutex unlock is removed. Infact check for waiting_rsp_clbk_hdl.dev_ctx, is removed.
- sema_init(&dev_ctx->fops_lock, 1);, replaced with Mutex.
- structure member se_notify, is removed.
4/5
- removed initializing err to zero in func ele_fetch_soc_info(),
- replaced 'return 0', with 'goto exit', if the condition (!priv->mem_pool) is true.
- replaced "struct *dev" with "struct se_if_priv *priv", in base_message API(s) and others.
- Created a separate structure "struct se_if_defines" to maintain interface's fixed values like cmd_tag, rsp_tag, success_tag etc.
- removed the macros "WORD_SZ", "SOC_VER_MASK", "DEFAULT_IMX_SOC_VER", "RESERVED_DMA_POOL".
- Added handling for "ctrl+c", by postponing the interrupt, till the response to the "command in flight" is received.
- Removed the mutext lock "se_if_lock".
- furnction prototype for "se_save_imem_state" and "se_restore_imem_state", is changed to pass "imem" by reference.
- Added a new structure "struct se_fw_load_info", dedicated to contain FW loading relevant info. It is a member of struct info_list.
- split "imem_mgmt_file_in_rfs" into two "prim_fw_nm_in_rfs" and "seco_fw_nm_in_rfs", to be part of "struct se_fw_load_info".
- moved the function "se_load_firmware" prior to func "if_mbox_free_channel".
- function "se_load_firmware" is updated to use "request_firmware", instead of "request_firmware_no_wait".
- function "se_load_firmware" is updated to load "primary" fw image, if the imem_state is not BAD. Then load the "secondary FW" image.
- Added a new mutex_lock in the function "se_load_firmware", for ensuring FW loading done once, when there are multiple application are in play.
- instead of "wait_queue_head_t wq", used "sruct completion".
- add devm_add_action with action as se_if_probe_cleanup.
Reference:
- Link to v7: https://lore.kernel.org/r/20240904-imx-se-if-v7-0-5afd2ab74264@nxp.com
Changes in v7:
5/5
- struct se_clbk_handle, is added with a member struct se_if_device_ctx *dev_ctx.
- func call to ele_miscdev_msg_rcv() & ele_miscdev_msg_send(), are removed.
- func se_ioctl_cmd_snd_rcv_rsp_handler(), is modified to remove the func call to ele_miscdev_msg_rcv() & ele_miscdev_msg_send()
- func se_ioctl_cmd_snd_rcv_rsp_handler is callig func ele_msg_send_rcv(), instead.
- Mutext "se_cmd_if_lock", handling is removed from this patch.
- func ele_miscdev_msg_send() is replaced with func ele_msg_send(), in fops_write.
- func ele_miscdev_msg_rcv() is replaced with func ele_msg_rcv(), in fops_read.
- fops_open is modified to create the new dev_ctx instance (using func init_device_context()), which is not registered as miscdev.
- Only one dev_ctx is registered as miscdev and its reference is stored in the struct se_if_priv, as priv_dev_ctx.
- Separate func cleanup_se_shared_mem() & func init_se_shared_mem(), for shared memory handling part of struct dev_ctx.
- Input param for func(s) ele_msg_rcv(), ele_msg_send() & ele_msg_send_rcv(), is replaced from struct se_if_priv to struct se_if_device_ctx.
4/5
- A new structure is defined name struct "se_clbk_handle", to contain members processed in mailbox call-back function.
- "struct se_if_priv" is modified to contain the two structures of "se_clbk_handle" - waiting_rsp_clbk_hdl & cmd_receiver_clbk_hdl.
- func ele_msg_rcv() is modified to take a new additional input reference param "struct se_clbk_handle *se_clbk_hdl".
- func ele_msg_send() is modified to take a new additional input tx_msg_sz.
- func ele_msg_send_rcv(), is modified to take 2 more inputs - tx_msg_sz & exp_rx_msg_sz.
- func se_val_rsp_hdr_n_status(), is modified to take input of rx_msg buffer, instead of header value, as input param.
- each caller of the func ele_msg_send_rcv(), is sending these two additional input params.
- func se_if_callback(), is modified to work on two structures of "se_clbk_handle" - waiting_rsp_clbk_hdl & cmd_receiver_clbk_hdl.
- Variable "max_dev_ctx", is removed from info & priv struture, as well its usage.
- New member variable "se_img_file_to_load", is added to structure "priv".
- Other member variables - rx_msg(ptr), rx_msg_sz, completion done & list of dev_ctxs, is removed from priv struture, along with their usage.
- func se_resume(), updated to wakeup the two "wq", part of "struct se_clbk_handle": priv->waiting_rsp_clbk_hdl & priv->cmd_receiver_clbk_hdl.
3/5
- Node name is changed from senclave-firmware@0 to "secure-enclave"
2/5
- Node name is changed to "secure-enclave".
Reference:
- Link to v6: https://lore.kernel.org/r/20240722-imx-se-if-v6-0-ee26a87b824a@nxp.com
Changes in v6:
5/5
- replaced scope_gaurd with gaurd.
4/5
- replaced scope_gaurd with gaurd.
- remove reading the regs property from dtb.
- Added NULL check for priv data fetched from device, as a sanity check, for ele_base_msg apis)
3/5
- replace firmware with senclave-firmware.
2/5
- replace firmware with senclave-firmware.
- drop description for mbox
- Replaced "items:" with maxItems:1 for "memory-region"
- Replaced "items:" with maxItems:1 for "sram"
- remove regs property.
- remove "$nodename"
Reference:
- Link to v5: https://lore.kernel.org/r/20240712-imx-se-if-v5-0-66a79903a872@nxp.com
Changes in v5:
2/5
- updated the description of mboxes
- updated the description & items for mbox-names.
- updated the description of memory-region
- move "additional properties: false" after allOf block.
- removed other example except one.
4/5
- Corrected the indentation in Kconfig.
- info members:mbox_tx_name & mbox_rx_name, are replaced with macros.
5/5
- Replaced "for secure enclaves", with "for secure enclaves"
- Replaced "user space" with "userspace".
- End the line "[include]<linux/firmware/imx/ele_mu_ioctl.h>" with a period.
Reference:
- Link to v4: https://lore.kernel.org/r/20240705-imx-se-if-v4-0-52d000e18a1d@nxp.com
Changes in v4:
1/5
a. Removed - from EdgeLock Enclave.
b. Removed , after "Each of the above feature,"
c. replace "can exists" with "can exist".
d.
-messaging units(MU) per SE. Each co-existing 'se' can have one or multiple exclusive
-MU(s), dedicated to itself. None of the MU is shared between two SEs.
+messaging units(MU) per SE. Each co-existing SE can have one or multiple exclusive
+MUs, dedicated to itself. None of the MU is shared between two SEs.
Communication of the MU is realized using the Linux mailbox driver.
e.
-All those SE interfaces 'se-if' that is/are dedicated to a particular SE, will be
-enumerated and provisioned under the very single 'SE' node.
+Although MU(s) is/are not shared between SE(s). But for SoC like i.MX95 which has
+multiple SE(s) like HSM, V2X-HSM, V2X-SHE; all the SE(s) and their interfaces 'se-if'
+that is/are dedicated to a particular SE will be enumerated and provisioned using the
+single compatible node("fsl,imx95-se").
f. Removed ",". Replaced for "Each 'se-if'," with "Each se-if'.
g. removed ","
- This layer is responsible for ensuring the communication protocol, that is defined
+ This layer is responsible for ensuring the communication protocol that is defined
h. removed "-"
- - FW can handle one command-message at a time.
+ - FW can handle one command message at a time.
i.
- Using these multiple device contexts, that are getting multiplexed over a single MU,
- user-space application(s) can call fops like write/read to send the command-message,
- and read back the command-response-message to/from Firmware.
- fops like read & write uses the above defined service layer API(s) to communicate with
+ Using these multiple device contexts that are getting multiplexed over a single MU,
+ userspace application(s) can call fops like write/read to send the command message,
+ and read back the command response message to/from Firmware.
+ fops like read & write use the above defined service layer API(s) to communicate with
Firmware.
j. Uppercase for word "Linux".
2/5
a. Rephrased the description to remove list of phandles.
b. Moved required before allOf:
+required:
+ - compatible
+ - reg
+ - mboxes
+ - mbox-names
+
+additionalProperties: false
+
allOf:
c. replaced not: required: with properties: <property-name>: false.
# memory-region
- not:
- required:
- - memory-region
+ properties:
+ memory-region: false
# sram
- else:
- not:
- required:
- - sram
d. Reduced examples. keeping example of i.MX95.
e. node-name is changed to "firmware@<hex>"
3/5
- node name changed to "firmware@<hex>".
4/5
- used sizeof(*s_info)
- return early, rather than doing goto exit, in ele_get_info().
- Use upper_32_bits() and lower_32_bits()
- use rx_msg here instead of priv->rx_msg
- Moved the status check to validate_rsp_hdr. Rename the function to "se_val_rsp_hdr_n_status"
- typecasting removed header = (struct se_msg_hdr *) msg;
- Converted the API name with prefix imx_ele_* or imx_se_*, to ele_* and se_*, respectively.
- Removed the functions definition & declaration for: free_phybuf_mem_pool() & get_phybuf_mem_pool()
- removed the mbox_free_channel() calls from clean-up.
- Flag "priv->flags" is removed.
- Converted the int se_if_probe_cleanup() to void se_if_probe_cleanup().
- Replaced NULL initialization of structure members: priv->cmd_receiver_dev & priv->waiting_rsp_dev , with comments.
- Removed the function's declaration get_phy_buf_mem_pool1
5/5
Changes to Documentation/ABI/testing/se-cdev.
a. Removed "-" from "secure-enclave" and "file-descriptor".
b. Removed "-" from "shared-library"
c. Replaced "get" with "getting".
d. Added description for the new IOCTL "send command and receive command response"
e. Replaced "wakeup_intruptible" with "wait_event_interruptible"
f. Removed ";"
g. Removd "," from "mailbox_lock,"
h. Replaced "free" with "frees"
i. In mailbox callback function, checking the buffer size before
copying.
Reference:
- Link to v3: https://lore.kernel.org/r/20240617-imx-se-if-v3-0-a7d28dea5c4a@nxp.com
Changes in v3:
5/5:
- Initialize tx_msg with NULL.
- memdup_user() returns an error pointer, not NULL. correct it by adding check for err_ptr.
- new IOCTL is added to send & recieve the message.
- replaced the while loop till list is empty, with list_for_each_entry.
- replaced __list_del_entry, with list_del.
- Removed the dev_err message from copy to user.
- Removed the casting of void *.
- corrected the typcasting in copy to user.
- removed un-necessary goto statement.
- Removed dead code for clean-up of memory.
- Removed un-mapping of secured memory
- Passing se_if_priv structure to init_device_context.
- Updated the below check to replace io.length with round_up(io.length).
if (shared_mem->size < shared_mem->pos|| io.length >= shared_mem->size - shared_mem->pos)
- Created a function to cleanup the list of shared memory buffers.
- Used list_for_each_entry_safe(). created a separate functions: se_dev_ctx_cpy_out_data() & se_dev_ctx_shared_mem_cleanup()
4/5
- Changed the compatible string to replace "-ele", to "-se".
- Declaration of imx_se_node_info, is done as const in the whole file
- Remove the unused macros from ele_base_msg.h
- Remove the function declaration get_phy_buf_mem_pool1, from the header file.
- Replace the use of dmam_alloc_coherent to dma_alloc_coherent
- Check for function pointer, before calling the fucntion pointer in imx_fetch_se_soc_info
- Removed the unused flag for SE_MU_IO_FLAGS_USE_SEC_MEM.
- Removed the unused macros WORD_SZ
- instead of struct device *dev, struct se_if_priv *priv, is used as argument to the funtions:se_save_imem_state, se_restore_imem_state, imx_fetch_se_soc_info
- Removed ret from validate_rsp_hdr.
- changed the prefix of the funtion: plat_add_msg_crc and plat_fill_cmd_msg_hdr.
- indentation correction for info structures.
- remove the check for priv not null from se_if_probe_cleanup
- Removed the casting of void *.
- se_load_firmware function is corrected for not freeing the buffer when allocation fails.
- Checking if get_imx_se_node_info() can return NULL, in se_if_probe()
- imem.size has type u32. return value from se_save_imem_state() will be assigned to imem.size in case of success only.
- removed the flag un-setting in case of failure. priv->flags &= (~RESERVED_DMA_POOL);
- removed the function call for devm_of_platform_populate(dev);
- Checking for not-NULL, before calling the funtion pointer se_fetch_soc_info.
- Removed the checking for reserved memory flag, before freeing up the reserved memory, in se_probe_if_cleanup.
3/5
- Changed the compatible string to replace "-ele", to "-se".
2/5
- to fix the warning error, replaced the "-ele" & "-v2x" in compatible string, to "-se".
- Added an example for ele@0 for compatible string "fsl,imx95-se"
Reference
- Link to v2: https://lore.kernel.org/r/20240523-imx-se-if-v2-0-5a6fd189a539@nxp.com
Changes in v2:
4/4
- Split this patch into two: 1. base driver & 2. Miscdev
- Initialize the return variable "err" as 0, before calling 'return err', in the file ele_common.c
- Fix the usage of un-iniitialized pointer variable, by initializing them with NULL, in ele_base_msg.c.
- Fix initializing the ret variable, to return the correct error code in case of issue.
- replaced dmam_alloc_coherent with dma_alloc_coherent.
- Replace the use of ELE_GET_INFO_READ_SZ, with sizeof(soc_info).
- Replaced -1 with -EPERM
- Removed the safety check on func-input param, in ele_get_info().
- fix the assigning data[1] with lower 32 address, rather than zero, for ele_fw_authenticate API.
- Correctly initializing the function's return error code, for file ele_base_msg.c.
- replaced 'return' with 'goto'.
- Use length in bytes.
- Corrected the structure se_msg_hdr.
- Moved setting of rx_msg to priv, into the function imx_ele_msg_send_rcv
- Will add lockdep_assert_held, to receive path, in v2.
- corrected the spacing at "ret = validate_rsp_hdr"
- FIELD_GET() used for RES_STATUS
- Re-write the structure soc_info, matching the information provided in response to this api.
- The "|" goes to the end of the previous line.
- Moved the locking and unlocking of the command lock to the caller of the function.
- removed the safety check for device private data.
- Structure memory reference, used to read message header.
- In the interrupt call back function, remove assigning waiting_rsp_dev to NULL, in case of response message rcv from FW.
- do while removed.
- replaced BIT(1) for RESERVED_DMA_POOL, to BIT(0)
- The backslash is removed while assigning the file name with absolute path to structure variable.fw_name_in_rfs =.
- Update the 'if' condition by removing "idx < 0".
- mbox_request_channel_byname() uses a "char" for the name not a u8. Corrected.
- devm managed resources, are not cleaned now, in function se_probe_if_cleanup
- Used dev_err_probe().
- Used %pe to print error string.
- remove "__maybe_unused" for "struct platform_device *enum_plat_dev __maybe_unused;"
- used FIELD_GET(), for RES_STATUS. Removed the use of MSG_TAG, MSG_COMMAND, MSG_SIZE, MSG_VER.
- Depricated the used of member of struct se_if_priv, bool no_dev_ctx_used;
- Moved the text explaing the synchronization logic via mutexes, from patch 1/4 to se_ctrl.h.
- removed the type casting of info_list = (struct imx_se_node_info_list *) device_get_match_data(dev->parent);
- Used static variable priv->soc_rev in the se_ctrl.c, replaced the following condition: if (info_list->soc_rev) to if (priv->soc_rev) for checking if this flow is already executed or not.
- imx_fetch_soc_info will return failure if the get_info function fails.
- Removed devm_free from imx_fetch_soc_info too.
3/3
- Made changes to move all the properties to parent node, without any child node.
2/4
- Use Hex pattern string.
- Move the properties to parent node, with no child node.
- Add i.MX95-ele to compatible nodes to fix the warning "/example-2/v2x: failed to match any schema with compatible: ['fsl,imx95-v2x']"
1/1
- Corrected the spelling from creats to creates.
- drop the braces around the plural 's' for interfaces
- written se in upper case SE.
- Replace "multiple message(s)" with messages.
- Removed too much details about locks.
Testing
- make CHECK_DTBS=y freescale/imx8ulp-evk.dtb;
- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8 dt_binding_check DT_SCHEMA_FILES=fsl,imx-se.yaml
- make C=1 CHECK=scripts/coccicheck drivers/firmware/imx/*.* W=1 > r.txt
- ./scripts/checkpatch.pl --git <>..HEAD
- Tested the Image and .dtb, on the i.MX8ULP.
Reference
- Link to v1: https://lore.kernel.org/r/20240510-imx-se-if-v1-0-27c5a674916d@nxp.com
---
Pankaj Gupta (7):
Documentation/firmware: add imx/se to other_interfaces
dt-bindings: arm: fsl: add imx-se-fw binding doc
firmware: imx: add driver for NXP EdgeLock Enclave
firmware: imx: device context dedicated to priv
firmware: drivers: imx: adds miscdev
arm64: dts: imx8ulp: add secure enclave node
arm64: dts: imx8ulp-evk: add reserved memory property
Documentation/ABI/testing/se-cdev | 44 +
.../devicetree/bindings/firmware/fsl,imx-se.yaml | 91 ++
.../driver-api/firmware/other_interfaces.rst | 133 ++
arch/arm64/boot/dts/freescale/imx8ulp-evk.dts | 12 +-
arch/arm64/boot/dts/freescale/imx8ulp.dtsi | 11 +-
drivers/firmware/imx/Kconfig | 13 +
drivers/firmware/imx/Makefile | 2 +
drivers/firmware/imx/ele_base_msg.c | 300 ++++
drivers/firmware/imx/ele_base_msg.h | 117 ++
drivers/firmware/imx/ele_common.c | 517 +++++++
drivers/firmware/imx/ele_common.h | 49 +
drivers/firmware/imx/ele_fw_api.c | 57 +
drivers/firmware/imx/ele_fw_api.h | 18 +
drivers/firmware/imx/se_ctrl.c | 1596 ++++++++++++++++++++
drivers/firmware/imx/se_ctrl.h | 185 +++
include/linux/firmware/imx/se_api.h | 14 +
include/uapi/linux/se_ioctl.h | 97 ++
17 files changed, 3253 insertions(+), 3 deletions(-)
---
base-commit: 78be1e893a8196d509f48064dd7370274f8851b8
change-id: 20240507-imx-se-if-a40055093dc6
Best regards,
--
Pankaj Gupta <pankaj.gupta@nxp.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
2026-07-15 17:25 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc pankaj.gupta
` (5 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel
From: Pankaj Gupta <pankaj.gupta@nxp.com>
Documents i.MX SoC's Service layer and C_DEV driver for selected SoC(s)
that contains the NXP hardware IP(s) for Secure Enclaves(se) like:
- NXP EdgeLock Enclave on i.MX93 & i.MX8ULP
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
.../driver-api/firmware/other_interfaces.rst | 133 +++++++++++++++++++++
1 file changed, 133 insertions(+)
diff --git a/Documentation/driver-api/firmware/other_interfaces.rst b/Documentation/driver-api/firmware/other_interfaces.rst
index 06ac89adaafb..6c6fa9a0ba1d 100644
--- a/Documentation/driver-api/firmware/other_interfaces.rst
+++ b/Documentation/driver-api/firmware/other_interfaces.rst
@@ -49,3 +49,136 @@ of the requests on to a secure monitor (EL3).
.. kernel-doc:: drivers/firmware/stratix10-svc.c
:export:
+
+NXP Secure Enclave Firmware Interface
+=====================================
+
+Introduction
+------------
+The NXP's i.MX HW IP like EdgeLock Enclave, V2X etc., creates an embedded secure
+enclave within the SoC boundary to enable features like:
+
+- Hardware Security Module (HSM)
+- Security Hardware Extension (SHE)
+- Vehicular to Anything (V2X)
+
+Each of the above features is enabled through dedicated NXP H/W IP on the SoC.
+On a single SoC, multiple hardware IP (or can say more than one secure enclave)
+can exist.
+
+NXP SoCs enabled with the such secure enclaves(SEs) IPs are:
+i.MX93, i.MX8ULP
+
+To communicate with one or more co-existing SE(s) on SoC, there is/are dedicated
+messaging units(MU) per SE. Each co-existing SE can have one or multiple exclusive
+MUs, dedicated to itself. None of the MU is shared between two SEs. Communication
+of the MU is realized using the mailbox driver. Each secure enclave can cater to
+multiple clients by virtue of these exclusive MUs. Also, they can distinguish
+transactions originating from these clients based on the MU used and core security
+state. The communication between the clients and secure enclaves is in the form of
+a command/response mechanism. Each client could expose a specific set of secure enclave
+features to the higher layers, based on the commands supported by that client. For
+example, the secure enclave could simultaneously support an OPTEE TA and Linux
+middleware as clients. Each of these clients can expose a specific set of secure
+enclave features based on the command set supported by them.
+
+NXP Secure Enclave(SE) Interface
+--------------------------------
+MU(s) is/are not shared between SE(s). But for an SoC like i.MX95 which has
+multiple SE(s) like HSM, V2X-HSM, V2X-SHE, all the SE(s) and their interfaces 'se-if'
+that is/are dedicated to a particular SE will be enumerated and provisioned using the
+single compatible node("fsl,imx95-se").
+
+Each 'se-if' comprises two layers:
+
+- (C_DEV Layer) User-Space software-access interface.
+- (Service Layer) OS-level software-access interface.
+
+::
+
+ +--------------------------------------------+
+ | Character Device(C_DEV) |
+ | |
+ | +---------+ +---------+ +---------+ |
+ | | misc #1 | | misc #2 | ... | misc #n | |
+ | | dev | | dev | | dev | |
+ | +---------+ +---------+ +---------+ |
+ | +-------------------------+ |
+ | | Misc. Dev Synchr. Logic | |
+ | +-------------------------+ |
+ | |
+ +--------------------------------------------+
+
+ +--------------------------------------------+
+ | Service Layer |
+ | |
+ | +-----------------------------+ |
+ | | Message Serialization Logic | |
+ | +-----------------------------+ |
+ | +---------------+ |
+ | | imx-mailbox | |
+ | | mailbox.c | |
+ | +---------------+ |
+ | |
+ +--------------------------------------------+
+
+- service layer:
+ This layer is responsible for ensuring the communication protocol that is defined
+ for communication with firmware.
+
+ FW Communication protocol ensures two things:
+
+ - Serializing the messages to be sent over an MU.
+ - FW can handle one command message at a time.
+
+- c_dev:
+ This layer offers character device contexts, created as '/dev/<se>_mux_chx'.
+ Using these multiple device contexts that are multiplexed over a single MU,
+ userspace application(s) can call fops like write/read to send the command message,
+ and read back the command response message to/from Firmware.
+ fops like read & write use the above defined service layer API(s) to communicate with
+ Firmware.
+
+ Misc-device(/dev/<se>_mux_chn) synchronization protocol::
+
+ Non-Secure + Secure
+ |
+ |
+ +-----------+ +-------------+ |
+ | se_ctrl.c +<---->+imx-mailbox.c| |
+ | | | mailbox.c +<-->+------+ +------+
+ +-----+-----+ +-------------+ | MU X +<-->+ ELE |
+ | +------+ +------+
+ +----------------+ |
+ | | |
+ v v |
+ logical logical |
+ receiver waiter |
+ + + |
+ | | |
+ | | |
+ | +----+------+ |
+ | | | |
+ | | | |
+ device_ctx device_ctx device_ctx |
+ |
+ User 0 User 1 User Y |
+ +------+ +------+ +------+ |
+ |misc.c| |misc.c| |misc.c| |
+ kernel space +------+ +------+ +------+ |
+ |
+ +---------------------------------------------------- |
+ | | | |
+ userspace /dev/ele_muXch0 | | |
+ /dev/ele_muXch1 | |
+ /dev/ele_muXchY |
+ |
+
+When a user sends a command to the firmware, it registers its device_ctx
+as waiter of a response from firmware.
+
+Enclave's Firmware owns the storage management over a Linux filesystem.
+For this c_dev provisions a dedicated slave device called "receiver".
+
+.. kernel-doc:: drivers/firmware/imx/se_ctrl.c
+ :export:
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
2026-07-15 18:09 ` [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
2026-07-15 17:27 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave pankaj.gupta
` (4 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel
From: Pankaj Gupta <pankaj.gupta@nxp.com>
The NXP security hardware IP(s) like: i.MX EdgeLock Enclave, V2X etc.,
creates an embedded secure enclave within the SoC boundary to enable
features like:
- HSM
- SHE
- V2X
Secure-Enclave(s) communication interface are typically via message
unit, i.e., based on mailbox linux kernel driver. This driver enables
communication ensuring well defined message sequence protocol between
Application Core and enclave's firmware.
Driver configures multiple misc-device on the MU, for multiple
user-space applications, to be able to communicate over single MU.
It exists on some i.MX processors. e.g. i.MX8ULP, i.MX93 etc.
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
.../devicetree/bindings/firmware/fsl,imx-se.yaml | 91 ++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/Documentation/devicetree/bindings/firmware/fsl,imx-se.yaml b/Documentation/devicetree/bindings/firmware/fsl,imx-se.yaml
new file mode 100644
index 000000000000..fa81adbf9b80
--- /dev/null
+++ b/Documentation/devicetree/bindings/firmware/fsl,imx-se.yaml
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/firmware/fsl,imx-se.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP i.MX HW Secure Enclave(s) EdgeLock Enclave
+
+maintainers:
+ - Pankaj Gupta <pankaj.gupta@nxp.com>
+
+description: |
+ NXP's SoC may contain one or multiple embedded secure-enclave HW
+ IP(s) like i.MX EdgeLock Enclave, V2X etc. These NXP's HW IP(s)
+ enables features like
+ - Hardware Security Module (HSM),
+ - Security Hardware Extension (SHE), and
+ - Vehicular to Anything (V2X)
+
+ Communication interface to the secure-enclaves(se) is based on the
+ messaging unit(s).
+
+properties:
+ compatible:
+ enum:
+ - fsl,imx8ulp-se-ele-hsm
+ - fsl,imx93-se-ele-hsm
+ - fsl,imx95-se-ele-hsm
+
+ mboxes:
+ items:
+ - description: mailbox phandle to send message to se firmware
+ - description: mailbox phandle to receive message from se firmware
+
+ mbox-names:
+ items:
+ - const: tx
+ - const: rx
+
+ memory-region:
+ maxItems: 1
+
+ sram:
+ maxItems: 1
+
+required:
+ - compatible
+ - mboxes
+ - mbox-names
+
+allOf:
+ # memory-region
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - fsl,imx8ulp-se-ele-hsm
+ - fsl,imx93-se-ele-hsm
+ then:
+ required:
+ - memory-region
+ else:
+ properties:
+ memory-region: false
+
+ # sram
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - fsl,imx8ulp-se-ele-hsm
+ then:
+ required:
+ - sram
+
+ else:
+ properties:
+ sram: false
+
+additionalProperties: false
+
+examples:
+ - |
+ secure-enclave {
+ compatible = "fsl,imx95-se-ele-hsm";
+ mboxes = <&ele_mu0 0 0>, <&ele_mu0 1 0>;
+ mbox-names = "tx", "rx";
+ };
+...
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
2026-07-15 18:09 ` [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces pankaj.gupta
2026-07-15 18:09 ` [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
2026-07-15 17:36 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 4/7] firmware: imx: device context dedicated to priv pankaj.gupta
` (3 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel,
Frieder Schrempf, sashiko-bot
From: Pankaj Gupta <pankaj.gupta@nxp.com>
Add MU-based communication interface for secure enclave.
NXP hardware IP(s) for secure-enclaves like Edgelock Enclave(ELE), are
embedded in the SoC to support the features like HSM, SHE & V2X, using
message based communication interface.
The secure enclave FW communicates with Linux over single or multiple
dedicated messaging unit(MU) based interface(s).
Exists on i.MX SoC(s) like i.MX8ULP, i.MX93, i.MX95 etc.
For i.MX9x SoC(s) there is at least one dedicated ELE MU(s) for each
world - Linux(one or more) and OPTEE-OS (one or more).
Other dependent kernel drivers will be:
- NVMEM: that supports non-volatile devices like EFUSES,
managed by NXP's secure-enclave.
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Tested-by: Frieder Schrempf <frieder.schrempf@kontron.de>
Reviewed-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
Changes from v26 to v27
Fix several issues reported by Sashiko in the ELE driver:
--------------------------------------------------------
1. Critical: encrypted IMEM DMA/physical address discarded / address 0
Status: Fixed.
The IMEM buffer now uses dma_addr_t daddr, and save/restore paths
pass imem->daddr to ele_service_swap(). This resolves the old issue
where imem.phyaddr could remain zero.
2. High: ele_msg_send() failure leaves waiting_rsp_clbk_hdl.rx_msg dangling
Status: Fixed.
ele_msg_send_rcv() now uses a common clear_waiter path. On ele_msg_send()
failure, the response waiter state is cleared under
waiting_rsp_clbk_hdl.clbk_rx_lock.
3. High: tx_msg may be freed while mailbox still has async pointer
Status: Addressed
The i.MX MU mailbox controller copies the message payload into
hardware registers synchronously in imx_mu_send_data(). The ELE
client does not retain the tx_msg buffer after mbox_send_message()
returns, and TX completion is handled by the mailbox controller/core
"knows_txdone" set as false.
4. High: global var_se_info shared mutable state
Status: Fixed.
Mutable firmware-load and IMEM state is now per-device in struct
se_if_priv. get_load_fw_instance() returns &priv->load_fw. The
remaining var_se_info only carries common SoC-level revision data.
5. High: ele_service_swap() silently truncates 64-bit address
Status: Fixed.
ele_service_swap() now takes dma_addr_t, and the v27 changelog
states that upper 32-bit addresses are rejected before placing
the low 32 bits into the ELE message field.
Firmware authentication and IMEM service-swap paths use DMA
addresses end-to-end.
6. High: soc_device_register() handle not unregistered
Status: Fixed.
The code now registers a devres action using devm_add_action_or_reset()
to call soc_device_unregister() for the soc_device_register() result.
7. High: manual dmam_free_coherent() can double-free devres memory
Status: Fixed.
The explicit dmam_free_coherent() for the encrypted IMEM buffer
is removed from probe cleanup. The buffer is now treated as
devres-managed and released by devres.
8. High: hardcoded MAX_SCHEDULE_TIMEOUT bypasses response timeout
Status: Fixed / design-controlled.
The response-wait path now uses callback-handle identity to decide
whether timeout applies, instead of using command-receiver file
identity. Command receiver daemon wait remains intentionally
long/indefinite by design. The miscdev patch also shows timeout
handling moved toward per-dev_ctx context.
9. Medium: ele_get_info() mixes goto cleanup with __free()
Status: Not Accepted.
This was intentionally not changed. The scoped __free(kfree) ownership
for tx_msg/rx_msg and the manual gen_pool/DMA cleanup for get_info_data
are separate ownership domains.
10. Medium: lockless clearing of rx_msg
Status: Fixed.
The response callback state is now cleared under
waiting_rsp_clbk_hdl.clbk_rx_lock in the common cleanup path.
11. Low: resume ignores se_restore_imem_state() return value
Status: Fixed with non-fatal PM policy.
The return value is now captured and logged. The driver intentionally
keeps suspend/resume non-fatal if IMEM save/restore fails.
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260629-imx-se-if-v26-0-146446285744@nxp.com?part=3
12. Address review feedback from Lothar Waßmann.
Make se_fill_cmd_msg_hdr() return void and remove dead error checks at its
call sites, since the helper only fills the message header and always
succeeded.
Remove the trailing comma after the final empty of_device_id sentinel entry
so future compatible entries cannot accidentally be added after the table
terminator.
---
drivers/firmware/imx/Kconfig | 13 ++
drivers/firmware/imx/Makefile | 2 +
drivers/firmware/imx/ele_base_msg.c | 272 ++++++++++++++++++++++
drivers/firmware/imx/ele_base_msg.h | 98 ++++++++
drivers/firmware/imx/ele_common.c | 448 ++++++++++++++++++++++++++++++++++++
drivers/firmware/imx/ele_common.h | 43 ++++
drivers/firmware/imx/se_ctrl.c | 410 +++++++++++++++++++++++++++++++++
drivers/firmware/imx/se_ctrl.h | 111 +++++++++
include/linux/firmware/imx/se_api.h | 14 ++
9 files changed, 1411 insertions(+)
diff --git a/drivers/firmware/imx/Kconfig b/drivers/firmware/imx/Kconfig
index 127ad752acf8..e3cb7f965e70 100644
--- a/drivers/firmware/imx/Kconfig
+++ b/drivers/firmware/imx/Kconfig
@@ -55,3 +55,16 @@ config IMX_SCMI_MISC_DRV
core that could provide misc functions such as board control.
This driver can also be built as a module.
+
+config IMX_SEC_ENCLAVE
+ tristate "i.MX Embedded Secure Enclave - EdgeLock Enclave Firmware driver."
+ depends on MAILBOX && ((IMX_MBOX && ARCH_MXC && ARM64) || COMPILE_TEST)
+ select FW_LOADER
+ default m if ARCH_MXC
+
+ help
+ Exposes APIs supported by the iMX Secure Enclave HW IP called:
+ - EdgeLock Enclave Firmware (for i.MX8ULP, i.MX93),
+ like base, HSM, V2X & SHE using the SAB protocol via the shared Messaging
+ Unit. This driver exposes these interfaces via a set of file descriptors
+ allowing to configure shared memory, send and receive messages.
diff --git a/drivers/firmware/imx/Makefile b/drivers/firmware/imx/Makefile
index 3bbaffa6e347..4412b15846b1 100644
--- a/drivers/firmware/imx/Makefile
+++ b/drivers/firmware/imx/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_IMX_SCU) += imx-scu.o misc.o imx-scu-irq.o rm.o imx-scu-soc.o
obj-${CONFIG_IMX_SCMI_CPU_DRV} += sm-cpu.o
obj-${CONFIG_IMX_SCMI_MISC_DRV} += sm-misc.o
obj-${CONFIG_IMX_SCMI_LMM_DRV} += sm-lmm.o
+sec_enclave-objs = se_ctrl.o ele_common.o ele_base_msg.o
+obj-${CONFIG_IMX_SEC_ENCLAVE} += sec_enclave.o
diff --git a/drivers/firmware/imx/ele_base_msg.c b/drivers/firmware/imx/ele_base_msg.c
new file mode 100644
index 000000000000..997854e10e14
--- /dev/null
+++ b/drivers/firmware/imx/ele_base_msg.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/types.h>
+
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/genalloc.h>
+
+#include "ele_base_msg.h"
+#include "ele_common.h"
+
+#define FW_DBG_DUMP_FIXED_STR "ELE"
+
+int ele_get_info(struct se_if_priv *priv, struct ele_dev_info *s_info)
+{
+ dma_addr_t get_info_addr = 0;
+ u32 *get_info_data = NULL;
+ int ret = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ memset(s_info, 0x0, sizeof(*s_info));
+
+ struct se_api_msg *tx_msg __free(kfree) =
+ kzalloc(ELE_GET_INFO_REQ_MSG_SZ, GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ struct se_api_msg *rx_msg __free(kfree) =
+ kzalloc(ELE_GET_INFO_RSP_MSG_SZ, GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ if (priv->mem_pool)
+ get_info_data = gen_pool_dma_alloc(priv->mem_pool,
+ ELE_GET_INFO_BUFF_SZ,
+ &get_info_addr);
+ else
+ get_info_data = dma_alloc_coherent(priv->dev,
+ ELE_GET_INFO_BUFF_SZ,
+ &get_info_addr,
+ GFP_KERNEL);
+ if (!get_info_data) {
+ dev_err(priv->dev,
+ "%s: Failed to allocate get_info_addr.", __func__);
+ return -ENOMEM;
+ }
+
+ se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
+ ELE_GET_INFO_REQ, ELE_GET_INFO_REQ_MSG_SZ, true);
+
+ tx_msg->data[0] = upper_32_bits(get_info_addr);
+ tx_msg->data[1] = lower_32_bits(get_info_addr);
+ tx_msg->data[2] = sizeof(*s_info);
+ ret = ele_msg_send_rcv(priv, tx_msg, ELE_GET_INFO_REQ_MSG_SZ, rx_msg,
+ ELE_GET_INFO_RSP_MSG_SZ);
+ if (ret < 0)
+ goto exit;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_GET_INFO_REQ,
+ ELE_GET_INFO_RSP_MSG_SZ, true);
+ if (ret < 0)
+ goto exit;
+
+ memcpy(s_info, get_info_data, sizeof(*s_info));
+exit:
+ if (priv->mem_pool)
+ gen_pool_free(priv->mem_pool, (unsigned long)get_info_data,
+ ELE_GET_INFO_BUFF_SZ);
+ else
+ dma_free_coherent(priv->dev, ELE_GET_INFO_BUFF_SZ,
+ get_info_data, get_info_addr);
+
+ return ret;
+}
+
+int ele_fetch_soc_info(struct se_if_priv *priv, void *data)
+{
+ return ele_get_info(priv, data);
+}
+
+int ele_ping(struct se_if_priv *priv)
+{
+ int ret = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ struct se_api_msg *tx_msg __free(kfree) = kzalloc(ELE_PING_REQ_SZ,
+ GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ struct se_api_msg *rx_msg __free(kfree) = kzalloc(ELE_PING_RSP_SZ,
+ GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
+ ELE_PING_REQ, ELE_PING_REQ_SZ, true);
+
+ ret = ele_msg_send_rcv(priv, tx_msg, ELE_PING_REQ_SZ, rx_msg,
+ ELE_PING_RSP_SZ);
+ if (ret < 0)
+ return ret;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_PING_REQ,
+ ELE_PING_RSP_SZ, true);
+
+ return ret;
+}
+
+int ele_service_swap(struct se_if_priv *priv,
+ dma_addr_t addr,
+ u32 addr_size, u16 flag)
+{
+ int ret = 0;
+
+ if (upper_32_bits(addr)) {
+ dev_err(priv->dev,
+ "ELE service-swap address exceeds 32-bit range: %pad\n",
+ &addr);
+ return -ERANGE;
+ }
+
+ if (!priv)
+ return -EINVAL;
+
+ struct se_api_msg *tx_msg __free(kfree) =
+ kzalloc(ELE_SERVICE_SWAP_REQ_MSG_SZ, GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ struct se_api_msg *rx_msg __free(kfree) =
+ kzalloc(ELE_SERVICE_SWAP_RSP_MSG_SZ, GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
+ ELE_SERVICE_SWAP_REQ, ELE_SERVICE_SWAP_REQ_MSG_SZ, true);
+ if (ret)
+ return ret;
+
+ tx_msg->data[0] = flag;
+ tx_msg->data[1] = addr_size;
+ tx_msg->data[2] = ELE_NONE_VAL;
+ tx_msg->data[3] = lower_32_bits(addr);
+ tx_msg->data[4] = se_get_msg_chksum((u32 *)&tx_msg[0],
+ ELE_SERVICE_SWAP_REQ_MSG_SZ);
+ if (!tx_msg->data[4])
+ return -EINVAL;
+
+ ret = ele_msg_send_rcv(priv, tx_msg, ELE_SERVICE_SWAP_REQ_MSG_SZ,
+ rx_msg, ELE_SERVICE_SWAP_RSP_MSG_SZ);
+ if (ret < 0)
+ return ret;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_SERVICE_SWAP_REQ,
+ ELE_SERVICE_SWAP_RSP_MSG_SZ, true);
+ if (ret)
+ return ret;
+
+ if (flag == ELE_IMEM_EXPORT)
+ ret = rx_msg->data[1];
+ else
+ ret = 0;
+
+ return ret;
+}
+
+int ele_fw_authenticate(struct se_if_priv *priv, dma_addr_t contnr_addr,
+ dma_addr_t img_addr)
+{
+ int ret = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ if (upper_32_bits(contnr_addr) || upper_32_bits(img_addr)) {
+ dev_err(priv->dev, "Wrong address: %pap %pap\n", &contnr_addr, &img_addr);
+ return -EINVAL;
+ }
+
+ struct se_api_msg *tx_msg __free(kfree) =
+ kzalloc(ELE_FW_AUTH_REQ_SZ, GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ struct se_api_msg *rx_msg __free(kfree) =
+ kzalloc(ELE_FW_AUTH_RSP_MSG_SZ, GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
+ ELE_FW_AUTH_REQ, ELE_FW_AUTH_REQ_SZ, true);
+
+ tx_msg->data[0] = lower_32_bits(contnr_addr);
+ tx_msg->data[1] = 0;
+ tx_msg->data[2] = lower_32_bits(img_addr);
+
+ ret = ele_msg_send_rcv(priv, tx_msg, ELE_FW_AUTH_REQ_SZ, rx_msg,
+ ELE_FW_AUTH_RSP_MSG_SZ);
+ if (ret < 0)
+ return ret;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_FW_AUTH_REQ,
+ ELE_FW_AUTH_RSP_MSG_SZ, true);
+
+ return ret;
+}
+
+int ele_debug_dump(struct se_if_priv *priv)
+{
+ bool keep_logging;
+ int msg_ex_cnt;
+ int ret = 0;
+ int i;
+
+ if (!priv)
+ return -EINVAL;
+
+ struct se_api_msg *tx_msg __free(kfree) = kzalloc(ELE_DEBUG_DUMP_REQ_SZ,
+ GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ struct se_api_msg *rx_msg __free(kfree) = kzalloc(ELE_DEBUG_DUMP_RSP_SZ,
+ GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ se_fill_cmd_msg_hdr(priv, &tx_msg->header, ELE_DEBUG_DUMP_REQ,
+ ELE_DEBUG_DUMP_REQ_SZ, true);
+
+ msg_ex_cnt = 0;
+ do {
+ memset(rx_msg, 0x0, ELE_DEBUG_DUMP_RSP_SZ);
+
+ ret = ele_msg_send_rcv(priv, tx_msg, ELE_DEBUG_DUMP_REQ_SZ,
+ rx_msg, ELE_DEBUG_DUMP_RSP_SZ);
+ if (ret < 0)
+ return ret;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_DEBUG_DUMP_REQ,
+ ELE_DEBUG_DUMP_RSP_SZ, true);
+ if (ret) {
+ dev_err(priv->dev, "Dump_Debug_Buffer Error: %x.", ret);
+ break;
+ }
+ keep_logging = (rx_msg->header.size >= (ELE_DEBUG_DUMP_RSP_SZ >> 2) &&
+ msg_ex_cnt < ELE_MAX_DBG_DMP_PKT);
+
+ rx_msg->header.size -= 2;
+
+ if (rx_msg->header.size > 2)
+ rx_msg->header.size--;
+
+ for (i = 0; i < rx_msg->header.size; i += 2)
+ dev_info(priv->dev, "%s%02x_%02x: 0x%08x 0x%08x",
+ FW_DBG_DUMP_FIXED_STR, msg_ex_cnt, i,
+ rx_msg->data[i + 1], rx_msg->data[i + 2]);
+
+ msg_ex_cnt++;
+ } while (keep_logging);
+
+ return ret;
+}
diff --git a/drivers/firmware/imx/ele_base_msg.h b/drivers/firmware/imx/ele_base_msg.h
new file mode 100644
index 000000000000..4c3699543e87
--- /dev/null
+++ b/drivers/firmware/imx/ele_base_msg.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2025 NXP
+ *
+ * Header file for the EdgeLock Enclave Base API(s).
+ */
+
+#ifndef ELE_BASE_MSG_H
+#define ELE_BASE_MSG_H
+
+#include <linux/device.h>
+#include <linux/types.h>
+
+#include "se_ctrl.h"
+
+#define ELE_NONE_VAL 0x0
+
+#define ELE_GET_INFO_REQ 0xda
+#define ELE_GET_INFO_REQ_MSG_SZ 0x10
+#define ELE_GET_INFO_RSP_MSG_SZ 0x08
+
+#define MAX_UID_SIZE (16)
+#define DEV_GETINFO_ROM_PATCH_SHA_SZ (32)
+#define DEV_GETINFO_FW_SHA_SZ (32)
+#define DEV_GETINFO_OEM_SRKH_SZ (64)
+#define DEV_GETINFO_MIN_VER_MASK 0xff
+#define DEV_GETINFO_MAJ_VER_MASK 0xff00
+#define ELE_DEV_INFO_EXTRA_SZ 0x60
+
+struct dev_info {
+ u8 cmd;
+ u8 ver;
+ u16 length;
+ u16 soc_id;
+ u16 soc_rev;
+ u16 lmda_val;
+ u8 ssm_state;
+ u8 dev_atts_api_ver;
+ u8 uid[MAX_UID_SIZE];
+ u8 sha_rom_patch[DEV_GETINFO_ROM_PATCH_SHA_SZ];
+ u8 sha_fw[DEV_GETINFO_FW_SHA_SZ];
+};
+
+struct dev_addn_info {
+ u8 oem_srkh[DEV_GETINFO_OEM_SRKH_SZ];
+ u8 trng_state;
+ u8 csal_state;
+ u8 imem_state;
+ u8 reserved2;
+};
+
+struct ele_dev_info {
+ struct dev_info d_info;
+ struct dev_addn_info d_addn_info;
+};
+
+#define ELE_GET_INFO_BUFF_SZ (sizeof(struct ele_dev_info) \
+ + ELE_DEV_INFO_EXTRA_SZ)
+
+#define GET_SERIAL_NUM_FROM_UID(x, uid_word_sz) ({\
+ const u32 *__x = (const u32 *)(x); \
+ size_t __sz = (uid_word_sz); \
+ ((u64)__x[__sz - 1] << 32) | __x[0]; \
+ })
+
+#define ELE_MAX_DBG_DMP_PKT 50
+#define ELE_DEBUG_DUMP_REQ 0x21
+#define ELE_DEBUG_DUMP_REQ_SZ 0x4
+#define ELE_DEBUG_DUMP_RSP_SZ 0x5c
+
+#define ELE_PING_REQ 0x01
+#define ELE_PING_REQ_SZ 0x04
+#define ELE_PING_RSP_SZ 0x08
+
+#define ELE_SERVICE_SWAP_REQ 0xdf
+#define ELE_SERVICE_SWAP_REQ_MSG_SZ 0x18
+#define ELE_SERVICE_SWAP_RSP_MSG_SZ 0x0c
+#define ELE_IMEM_SIZE 0x10000
+#define ELE_IMEM_STATE_OK 0xca
+#define ELE_IMEM_STATE_BAD 0xfe
+#define ELE_IMEM_STATE_WORD 0x27
+#define ELE_IMEM_STATE_MASK 0x00ff0000
+#define ELE_IMEM_EXPORT 0x1
+#define ELE_IMEM_IMPORT 0x2
+
+#define ELE_FW_AUTH_REQ 0x02
+#define ELE_FW_AUTH_REQ_SZ 0x10
+#define ELE_FW_AUTH_RSP_MSG_SZ 0x08
+
+int ele_get_info(struct se_if_priv *priv, struct ele_dev_info *s_info);
+int ele_fetch_soc_info(struct se_if_priv *priv, void *data);
+int ele_ping(struct se_if_priv *priv);
+int ele_service_swap(struct se_if_priv *priv, dma_addr_t addr,
+ u32 addr_size, u16 flag);
+int ele_fw_authenticate(struct se_if_priv *priv, dma_addr_t contnr_addr,
+ dma_addr_t img_addr);
+int ele_debug_dump(struct se_if_priv *priv);
+#endif
diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
new file mode 100644
index 000000000000..3f5f78df49b5
--- /dev/null
+++ b/drivers/firmware/imx/ele_common.c
@@ -0,0 +1,448 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include "ele_base_msg.h"
+#include "ele_common.h"
+
+/*
+ * se_get_msg_chksum() - to calculate checksum word by word.
+ *
+ * @msg : reference to the input msg-data.
+ * @msg_len : reference to the input msg-data length in bytes.
+ * Includes extra 4 bytes (or 1 words) chksum.
+ *
+ * This function returns the checksum calculated by ORing word by word.
+ *
+ * Return:
+ * 0: if the input length is not 4 byte aligned, or num of words < 5.
+ * chksum: calculated word by word.
+ */
+u32 se_get_msg_chksum(u32 *msg, u32 msg_len)
+{
+ u32 nb_words = msg_len / (u32)sizeof(u32);
+ u32 chksum = 0;
+ u32 i;
+
+ if (nb_words < 5)
+ return chksum;
+
+ if (msg_len % SE_MSG_WORD_SZ) {
+ pr_err("Msg-len is not 4-byte aligned.");
+ return chksum;
+ }
+
+ /* nb_words include one checksum word, so skip it. */
+ nb_words--;
+
+ for (i = 0; i < nb_words; i++)
+ chksum ^= *(msg + i);
+
+ return chksum;
+}
+
+int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl)
+{
+ bool wait_uninterruptible = false;
+ unsigned long remaining_jiffies;
+ unsigned long flags;
+ int ret;
+
+ remaining_jiffies = MAX_SCHEDULE_TIMEOUT;
+ do {
+ if (wait_uninterruptible)
+ ret = wait_for_completion_timeout(&se_clbk_hdl->done,
+ remaining_jiffies);
+ else
+ ret = wait_for_completion_interruptible_timeout(&se_clbk_hdl->done,
+ remaining_jiffies);
+ if (ret == -ERESTARTSYS) {
+ /*
+ * Record that a signal was observed, then continue waiting non-
+ * interruptibly until the response arrives or the timeout
+ * expires. The caller can surface the interruption to userspace
+ * after the protocol transaction is brought back to a
+ * synchronized state.
+ */
+ if (priv->waiting_rsp_clbk_hdl.rx_msg) {
+ WRITE_ONCE(se_clbk_hdl->signal_rcvd, true);
+ wait_uninterruptible = true;
+ continue;
+ }
+ break;
+ }
+
+ if (ret == 0) {
+ /*
+ * The response buffer belongs to the caller of ele_msg_send_rcv()
+ * and may be freed as soon as this function returns. Clear rx_msg
+ * under clbk_rx_lock so that a late se_if_rx_callback() can
+ * observe that the waiter has timed out and must not copy into
+ * the stale buffer.
+ *
+ * If the completion has not yet been signaled, mark the firmware
+ * path busy. This acts as a circuit breaker: reject new
+ * command/response transactions until the delayed response
+ * arrives and the callback closes the breaker.
+ */
+
+ spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
+ se_clbk_hdl->rx_msg = NULL;
+ if (!completion_done(&se_clbk_hdl->done))
+ atomic_set(&priv->fw_busy, 1);
+
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+ ret = -ETIMEDOUT;
+ dev_err(priv->dev,
+ "Fatal Error: SE interface: %s0, hangs indefinitely.\n",
+ get_se_if_name(priv->if_defs->se_if_type));
+ break;
+ }
+ ret = se_clbk_hdl->rx_msg_sz;
+ break;
+ } while (ret < 0);
+
+ return ret;
+}
+
+int ele_msg_send(struct se_if_priv *priv,
+ void *tx_msg,
+ int tx_msg_sz)
+{
+ struct se_msg_hdr *header = tx_msg;
+ int err;
+
+ /*
+ * Check that the size passed as argument matches the size
+ * carried in the message.
+ */
+ if (header->size << 2 != tx_msg_sz) {
+ dev_err(priv->dev,
+ "User buf hdr: 0x%x, sz mismatced with input-sz (%d != %d).",
+ *(u32 *)header, header->size << 2, tx_msg_sz);
+ return -EINVAL;
+ }
+
+ err = mbox_send_message(priv->tx_chan, tx_msg);
+ if (err < 0) {
+ dev_err(priv->dev, "Error: mbox_send_message failure.\n");
+ return err;
+ }
+
+ return tx_msg_sz;
+}
+
+/* API used for send/receive blocking call. */
+int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
+ void *rx_msg, int exp_rx_msg_sz)
+{
+ unsigned long flags;
+ int err;
+
+ guard(mutex)(&priv->se_if_cmd_lock);
+
+ if (atomic_read(&priv->fw_busy)) {
+ dev_dbg(priv->dev, "ELE became unresponsive.\n");
+ return -EBUSY;
+ }
+ reinit_completion(&priv->waiting_rsp_clbk_hdl.done);
+ priv->waiting_rsp_clbk_hdl.rx_msg_sz = exp_rx_msg_sz;
+ priv->waiting_rsp_clbk_hdl.rx_msg = rx_msg;
+
+ err = ele_msg_send(priv, tx_msg, tx_msg_sz);
+ if (err < 0)
+ goto clear_waiter;
+
+ err = ele_msg_rcv(priv, &priv->waiting_rsp_clbk_hdl);
+
+ if (priv->waiting_rsp_clbk_hdl.signal_rcvd) {
+ /*
+ * A signal was received after the command was sent. ele_msg_rcv()
+ * kept waiting until the FW/kernel protocol was synchronized again.
+ * If the transaction itself completed successfully, report the
+ * deferred signal to userspace using normal syscall-restart semantics.
+ * Do not hide real firmware/protocol errors such as -ETIMEDOUT.
+ */
+ if (err > 0)
+ err = -ERESTARTSYS;
+ priv->waiting_rsp_clbk_hdl.signal_rcvd = false;
+ dev_err(priv->dev, "Err[0x%x]:Interrupted by signal.", err);
+ }
+
+clear_waiter:
+ spin_lock_irqsave(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
+ priv->waiting_rsp_clbk_hdl.rx_msg = NULL;
+ priv->waiting_rsp_clbk_hdl.rx_msg_sz = 0;
+ spin_unlock_irqrestore(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
+
+ return err;
+}
+
+static bool check_hdr_exception_for_sz(struct se_if_priv *priv,
+ struct se_msg_hdr *header)
+{
+ /*
+ * List of API(s) header that can be accepte variable length
+ * response buffer.
+ */
+ if (header->command == ELE_DEBUG_DUMP_REQ &&
+ header->ver == priv->if_defs->base_api_ver &&
+ header->size >= 2 && header->size <= (ELE_DEBUG_DUMP_RSP_SZ / 4))
+ return true;
+
+ return false;
+}
+
+/*
+ * Callback called by mailbox FW, when data is received.
+ */
+void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
+{
+ struct se_clbk_handle *se_clbk_hdl;
+ struct device *dev = mbox_cl->dev;
+ struct se_msg_hdr *header;
+ bool sz_mismatch = false;
+ struct se_if_priv *priv;
+ unsigned long flags;
+ u32 rx_msg_sz;
+
+ priv = dev_get_drvdata(dev);
+
+ /* The function can be called with NULL msg */
+ if (!msg) {
+ dev_err(dev, "Message is invalid\n");
+ return;
+ }
+
+ header = msg;
+ rx_msg_sz = header->size << 2;
+
+ /* Incoming command: wake up the receiver if any. */
+ if (header->tag == priv->if_defs->cmd_tag) {
+ se_clbk_hdl = &priv->cmd_receiver_clbk_hdl;
+ spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
+ if (!se_clbk_hdl->rx_msg) {
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+ dev_warn(dev, "No command receiver registered for message: %.8x\n",
+ *((u32 *)header));
+ return;
+ }
+
+ /*
+ * cmd_tag messages are delivered only to the explicitly registered
+ * command receiver. Unlike the synchronous response waiter path, the
+ * command receiver uses a dedicated long-lived buffer installed by
+ * SE_IOCTL_ENABLE_CMD_RCV and is not subject to the timeout/circuit-
+ * breaker handling used for rsp_tag messages.
+ */
+ dev_dbg(dev, "Selecting cmd receiver: for mesg header:0x%x.",
+ *(u32 *)header);
+
+ /*
+ * Pre-allocated buffer of MAX_NVM_MSG_LEN
+ * as the NVM command are initiated by FW.
+ * Size is revealed as part of this call function.
+ */
+
+ if (rx_msg_sz > MAX_NVM_MSG_LEN) {
+ /* Store the response buffer maxsize in local variable.*/
+ rx_msg_sz = MAX_NVM_MSG_LEN;
+ sz_mismatch = true;
+ }
+
+ se_clbk_hdl->rx_msg_sz = rx_msg_sz;
+ memcpy(se_clbk_hdl->rx_msg, msg, se_clbk_hdl->rx_msg_sz);
+ complete(&se_clbk_hdl->done);
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+ if (sz_mismatch)
+ dev_err(dev,
+ "CMD-RCVER NVM: hdr(0x%x) with different sz(%d != %d).\n",
+ *(u32 *)header,
+ (header->size << 2), rx_msg_sz);
+ } else if (header->tag == priv->if_defs->rsp_tag) {
+ bool exception_for_sz_mismatch = check_hdr_exception_for_sz(priv, header);
+ u32 exp_rx_msg_sz = 0;
+
+ /*
+ * waiting_rsp_clbk_hdl.rx_msg is owned by the synchronous sender in
+ * ele_msg_send_rcv(). After timeout or error, that path clears rx_msg
+ * under clbk_rx_lock before returning to its caller, which may then free
+ * the buffer. Check rx_msg under the same lock here so a delayed response
+ * can be detected and dropped instead of copying into freed memory.
+ *
+ * A late response also closes the firmware-busy circuit breaker, allowing
+ * future command/response transactions to proceed again.
+ */
+ se_clbk_hdl = &priv->waiting_rsp_clbk_hdl;
+ exp_rx_msg_sz = se_clbk_hdl->rx_msg_sz;
+ spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
+ if (!se_clbk_hdl->rx_msg) {
+ /* Close circuit breaker on spinlock race */
+ atomic_set(&priv->fw_busy, 0);
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+ dev_info(dev, "ELE responded (late), recovery FW available.");
+ return;
+ }
+ dev_dbg(dev, "Selecting resp waiter: for mesg header:0x%x.",
+ *(u32 *)header);
+
+ /*
+ * For rsp_tag traffic, the sender provides the expected response
+ * buffer size. If firmware returns a different size, clamp the copy
+ * length to the caller's buffer capacity before memcpy() and report the
+ * mismatch after dropping the spinlock.
+ */
+ if (rx_msg_sz != exp_rx_msg_sz) {
+ if (!exception_for_sz_mismatch)
+ sz_mismatch = true;
+
+ se_clbk_hdl->rx_msg_sz = min(rx_msg_sz, exp_rx_msg_sz);
+ }
+ memcpy(se_clbk_hdl->rx_msg, msg, se_clbk_hdl->rx_msg_sz);
+ complete(&se_clbk_hdl->done);
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+
+ if (sz_mismatch)
+ dev_err(dev,
+ "Rsp to CMD: hdr(0x%x) with different sz(%d != %d).\n",
+ *(u32 *)header,
+ (header->size << 2), exp_rx_msg_sz);
+ } else {
+ dev_err(dev, "Failed to select a device for message: %.8x\n",
+ *((u32 *)header));
+ }
+}
+
+int se_val_rsp_hdr_n_status(struct se_if_priv *priv, struct se_api_msg *msg,
+ u8 msg_id, u8 sz, bool is_base_api)
+{
+ struct se_msg_hdr *header = &msg->header;
+ u32 status;
+
+ if (header->tag != priv->if_defs->rsp_tag) {
+ dev_err(priv->dev, "MSG[0x%x] Hdr: Resp tag mismatch. (0x%x != 0x%x)",
+ msg_id, header->tag, priv->if_defs->rsp_tag);
+ return -EINVAL;
+ }
+
+ if (header->command != msg_id) {
+ dev_err(priv->dev, "MSG Header: Cmd id mismatch. (0x%x != 0x%x)",
+ header->command, msg_id);
+ return -EINVAL;
+ }
+
+ if ((sz % 4) || (header->size != (sz >> 2) &&
+ !check_hdr_exception_for_sz(priv, header))) {
+ dev_err(priv->dev, "MSG[0x%x] Hdr: Cmd size mismatch. (0x%x != 0x%x)",
+ msg_id, header->size, (sz >> 2));
+ return -EINVAL;
+ }
+
+ if (is_base_api && header->ver != priv->if_defs->base_api_ver) {
+ dev_err(priv->dev,
+ "MSG[0x%x] Hdr: Base API Vers mismatch. (0x%x != 0x%x)",
+ msg_id, header->ver, priv->if_defs->base_api_ver);
+ return -EINVAL;
+ } else if (!is_base_api && header->ver != priv->if_defs->fw_api_ver) {
+ dev_err(priv->dev,
+ "MSG[0x%x] Hdr: FW API Vers mismatch. (0x%x != 0x%x)",
+ msg_id, header->ver, priv->if_defs->fw_api_ver);
+ return -EINVAL;
+ }
+
+ status = RES_STATUS(msg->data[0]);
+ if (status != priv->if_defs->success_tag) {
+ dev_err(priv->dev, "Command Id[%x], Response Failure = 0x%x",
+ header->command, status);
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+int se_save_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem)
+{
+ struct ele_dev_info s_info = {0};
+ int ret;
+
+ ret = ele_get_info(priv, &s_info);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get info from ELE.\n");
+ return ret;
+ }
+
+ /* Check for the imem-state before continue to save imem state. */
+ if (s_info.d_addn_info.imem_state == ELE_IMEM_STATE_BAD)
+ return 0;
+
+ /*
+ * EXPORT command will save encrypted IMEM to given address,
+ * so later in resume, IMEM can be restored from the given
+ * address.
+ *
+ * Size must be at least 64 kB.
+ */
+ ret = ele_service_swap(priv, imem->daddr, ELE_IMEM_SIZE, ELE_IMEM_EXPORT);
+ if (ret < 0) {
+ dev_err(priv->dev, "Failed to export IMEM.");
+ imem->size = 0;
+ } else {
+ dev_dbg(priv->dev,
+ "Exported %d bytes of encrypted IMEM.",
+ ret);
+ imem->size = ret;
+ }
+
+ return ret > 0 ? 0 : ret;
+}
+
+int se_restore_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem)
+{
+ struct ele_dev_info s_info;
+ int ret;
+
+ /* get info from ELE */
+ ret = ele_get_info(priv, &s_info);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get info from ELE.");
+ return ret;
+ }
+ imem->state = s_info.d_addn_info.imem_state;
+
+ /* Check for the imem-state and imem-size before continue to
+ * restore imem state.
+ */
+ if (s_info.d_addn_info.imem_state != ELE_IMEM_STATE_BAD || !imem->size)
+ return -EIO;
+
+ /*
+ * IMPORT command will restore IMEM from the given
+ * address, here size is the actual size returned by ELE
+ * during the export operation
+ */
+ ret = ele_service_swap(priv, imem->daddr, imem->size, ELE_IMEM_IMPORT);
+ if (ret) {
+ dev_err(priv->dev, "Failed to import IMEM");
+ return ret;
+ }
+
+ /*
+ * After importing IMEM, check if IMEM state is equal to 0xCA
+ * to ensure IMEM is fully loaded and
+ * ELE functionality can be used.
+ */
+ ret = ele_get_info(priv, &s_info);
+ if (ret) {
+ dev_err(priv->dev, "Failed to get info from ELE.");
+ return ret;
+ }
+ imem->state = s_info.d_addn_info.imem_state;
+
+ if (s_info.d_addn_info.imem_state == ELE_IMEM_STATE_OK)
+ dev_dbg(priv->dev, "Successfully restored IMEM.");
+ else
+ dev_err(priv->dev, "Failed to restore IMEM.");
+
+ return ret;
+}
diff --git a/drivers/firmware/imx/ele_common.h b/drivers/firmware/imx/ele_common.h
new file mode 100644
index 000000000000..0365759fdd12
--- /dev/null
+++ b/drivers/firmware/imx/ele_common.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2025 NXP
+ */
+
+#ifndef __ELE_COMMON_H__
+#define __ELE_COMMON_H__
+
+#include "se_ctrl.h"
+
+#define ELE_SUCCESS_IND 0xD6
+
+#define IMX_ELE_FW_DIR "imx/ele/"
+
+u32 se_get_msg_chksum(u32 *msg, u32 msg_len);
+
+int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl);
+
+int ele_msg_send(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz);
+
+int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
+ void *rx_msg, int exp_rx_msg_sz);
+
+void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg);
+
+int se_val_rsp_hdr_n_status(struct se_if_priv *priv, struct se_api_msg *msg,
+ u8 msg_id, u8 sz, bool is_base_api);
+
+/* Fill a command message header with a given command ID and length in bytes. */
+static inline void se_fill_cmd_msg_hdr(struct se_if_priv *priv, struct se_msg_hdr *hdr,
+ u8 cmd, u32 len, bool is_base_api)
+{
+ hdr->tag = priv->if_defs->cmd_tag;
+ hdr->ver = (is_base_api) ? priv->if_defs->base_api_ver : priv->if_defs->fw_api_ver;
+ hdr->command = cmd;
+ hdr->size = len >> 2;
+}
+
+int se_save_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem);
+
+int se_restore_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem);
+
+#endif /*__ELE_COMMON_H__ */
diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
new file mode 100644
index 000000000000..7cb98a373334
--- /dev/null
+++ b/drivers/firmware/imx/se_ctrl.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/dma-direct.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/firmware.h>
+#include <linux/firmware/imx/se_api.h>
+#include <linux/genalloc.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/miscdevice.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sys_soc.h>
+
+#include "ele_base_msg.h"
+#include "ele_common.h"
+#include "se_ctrl.h"
+
+#define MAX_SOC_INFO_DATA_SZ 256
+#define MBOX_TX_NAME "tx"
+#define MBOX_RX_NAME "rx"
+
+#define SE_TYPE_STR_DBG "dbg"
+#define SE_TYPE_STR_HSM "hsm"
+
+#define SE_TYPE_ID_DBG 0x1
+
+#define SE_TYPE_ID_HSM 0x2
+
+struct se_var_info {
+ u16 soc_rev;
+};
+
+/* contains fixed information */
+struct se_soc_info {
+ const u16 soc_id;
+ const char *soc_name;
+ const struct se_fw_img_name se_fw_img_nm;
+};
+
+struct se_if_node {
+ struct se_soc_info *se_info;
+ u8 *pool_name;
+ bool reserved_dma_ranges;
+ struct se_if_defines if_defs;
+};
+
+/* common for all the SoC. */
+static struct se_var_info var_se_info;
+
+static struct se_soc_info se_imx8ulp_info = {
+ .soc_id = SOC_ID_OF_IMX8ULP,
+ .soc_name = "i.MX8ULP",
+ .se_fw_img_nm = {
+ .prim_fw_nm_in_rfs = IMX_ELE_FW_DIR
+ "mx8ulpa2-ahab-container.img",
+ .seco_fw_nm_in_rfs = IMX_ELE_FW_DIR
+ "mx8ulpa2ext-ahab-container.img",
+ },
+};
+
+static struct se_if_node imx8ulp_se_ele_hsm = {
+ .se_info = &se_imx8ulp_info,
+ .pool_name = "sram",
+ .reserved_dma_ranges = true,
+ .if_defs = {
+ .se_if_type = SE_TYPE_ID_HSM,
+ .cmd_tag = 0x17,
+ .rsp_tag = 0xe1,
+ .success_tag = ELE_SUCCESS_IND,
+ .base_api_ver = MESSAGING_VERSION_6,
+ .fw_api_ver = MESSAGING_VERSION_7,
+ },
+};
+
+static struct se_soc_info se_imx93_info = {
+ .soc_id = SOC_ID_OF_IMX93,
+};
+
+static struct se_if_node imx93_se_ele_hsm = {
+ .se_info = &se_imx93_info,
+ .reserved_dma_ranges = true,
+ .if_defs = {
+ .se_if_type = SE_TYPE_ID_HSM,
+ .cmd_tag = 0x17,
+ .rsp_tag = 0xe1,
+ .success_tag = ELE_SUCCESS_IND,
+ .base_api_ver = MESSAGING_VERSION_6,
+ .fw_api_ver = MESSAGING_VERSION_7,
+ },
+};
+
+static const struct of_device_id se_match[] = {
+ { .compatible = "fsl,imx8ulp-se-ele-hsm", .data = &imx8ulp_se_ele_hsm },
+ { .compatible = "fsl,imx93-se-ele-hsm", .data = &imx93_se_ele_hsm },
+ {}
+};
+
+char *get_se_if_name(u8 se_if_id)
+{
+ switch (se_if_id) {
+ case SE_TYPE_ID_DBG: return SE_TYPE_STR_DBG;
+ case SE_TYPE_ID_HSM: return SE_TYPE_STR_HSM;
+ }
+
+ return NULL;
+}
+
+static struct se_fw_load_info *get_load_fw_instance(struct se_if_priv *priv)
+{
+ return &priv->load_fw;
+}
+
+static void se_soc_device_unregister(void *data)
+{
+ struct soc_device *sdev = data;
+
+ soc_device_unregister(sdev);
+}
+
+static int get_se_soc_info(struct se_if_priv *priv, const struct se_soc_info *se_info)
+{
+ struct se_fw_load_info *load_fw = get_load_fw_instance(priv);
+ struct soc_device_attribute *attr;
+ u8 data[MAX_SOC_INFO_DATA_SZ];
+ struct ele_dev_info *s_info;
+ struct soc_device *sdev;
+ int err = 0;
+
+ /*
+ * This function should be called once.
+ * Check if the se_soc_rev is zero to continue.
+ */
+ if (var_se_info.soc_rev)
+ return err;
+
+ err = ele_fetch_soc_info(priv, &data);
+ if (err < 0)
+ return dev_err_probe(priv->dev, err, "Failed to fetch SoC Info.");
+ s_info = (void *)data;
+ var_se_info.soc_rev = s_info->d_info.soc_rev;
+ load_fw->imem.state = s_info->d_addn_info.imem_state;
+
+ if (!se_info->soc_name)
+ return 0;
+
+ attr = devm_kzalloc(priv->dev, sizeof(*attr), GFP_KERNEL);
+ if (!attr)
+ return -ENOMEM;
+
+ if (FIELD_GET(DEV_GETINFO_MIN_VER_MASK, var_se_info.soc_rev))
+ attr->revision = devm_kasprintf(priv->dev, GFP_KERNEL, "%x.%x",
+ FIELD_GET(DEV_GETINFO_MIN_VER_MASK,
+ var_se_info.soc_rev),
+ FIELD_GET(DEV_GETINFO_MAJ_VER_MASK,
+ var_se_info.soc_rev));
+ else
+ attr->revision = devm_kasprintf(priv->dev, GFP_KERNEL, "%x",
+ FIELD_GET(DEV_GETINFO_MAJ_VER_MASK,
+ var_se_info.soc_rev));
+
+ attr->soc_id = se_info->soc_name;
+
+ err = of_property_read_string(of_root, "model", &attr->machine);
+ if (err)
+ return -EINVAL;
+
+ attr->family = "Freescale i.MX";
+
+ attr->serial_number = devm_kasprintf(priv->dev,
+ GFP_KERNEL, "%016llX",
+ GET_SERIAL_NUM_FROM_UID(s_info->d_info.uid,
+ MAX_UID_SIZE >> 2));
+
+ sdev = soc_device_register(attr);
+ if (IS_ERR(sdev))
+ return PTR_ERR(sdev);
+
+ err = devm_add_action_or_reset(priv->dev, se_soc_device_unregister, sdev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+/* interface for managed res to free a mailbox channel */
+static void if_mbox_free_channel(void *mbox_chan)
+{
+ mbox_free_channel(mbox_chan);
+}
+
+static int se_if_request_channel(struct device *dev, struct mbox_chan **chan,
+ struct mbox_client *cl, const char *name)
+{
+ struct mbox_chan *t_chan;
+ int ret = 0;
+
+ t_chan = mbox_request_channel_byname(cl, name);
+ if (IS_ERR(t_chan))
+ return dev_err_probe(dev, PTR_ERR(t_chan),
+ "Failed to request %s channel.", name);
+
+ ret = devm_add_action_or_reset(dev, if_mbox_free_channel, t_chan);
+ if (ret)
+ return dev_err_probe(dev, -EPERM,
+ "Failed to add-action for removal of mbox: %s.",
+ name);
+ *chan = t_chan;
+
+ return ret;
+}
+
+static void se_if_probe_cleanup(void *plat_dev)
+{
+ struct platform_device *pdev = plat_dev;
+ struct device *dev = &pdev->dev;
+ struct se_if_priv *priv;
+
+ priv = dev_get_drvdata(dev);
+ if (!priv)
+ return;
+
+ /*
+ * In se_if_request_channel(), passed the clean-up functional
+ * pointer reference as action to devm_add_action_or_reset().
+ * No need to free the mbox channels here.
+ */
+
+ /*
+ * Being device managed buffer, no need to free the buffer allocated
+ * in se probe to store encrypted IMEM.
+ */
+
+ /*
+ * No need to check, if reserved memory is allocated
+ * before calling for its release. Or clearing the
+ * un-set bit.
+ */
+ of_reserved_mem_device_release(dev);
+
+ dev_set_drvdata(dev, NULL);
+ kfree(priv);
+}
+
+static int se_if_probe(struct platform_device *pdev)
+{
+ const struct se_soc_info *se_info;
+ const struct se_if_node *if_node;
+ struct se_fw_load_info *load_fw;
+ struct device *dev = &pdev->dev;
+ struct se_if_priv *priv;
+ int ret;
+
+ if_node = device_get_match_data(dev);
+ if (!if_node)
+ return -EINVAL;
+
+ se_info = if_node->se_info;
+
+ priv = kzalloc_obj(*priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->if_defs = &if_node->if_defs;
+ dev_set_drvdata(dev, priv);
+
+ mutex_init(&priv->se_if_cmd_lock);
+ spin_lock_init(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock);
+ spin_lock_init(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock);
+ atomic_set(&priv->fw_busy, 0);
+ init_completion(&priv->waiting_rsp_clbk_hdl.done);
+ init_completion(&priv->cmd_receiver_clbk_hdl.done);
+
+ ret = devm_add_action_or_reset(dev, se_if_probe_cleanup, pdev);
+ if (ret)
+ return ret;
+
+ /* Mailbox client configuration */
+ priv->se_mb_cl.dev = dev;
+ priv->se_mb_cl.tx_block = false;
+ priv->se_mb_cl.knows_txdone = false;
+ priv->se_mb_cl.rx_callback = se_if_rx_callback;
+
+ ret = se_if_request_channel(dev, &priv->tx_chan, &priv->se_mb_cl, MBOX_TX_NAME);
+ if (ret)
+ return ret;
+
+ ret = se_if_request_channel(dev, &priv->rx_chan, &priv->se_mb_cl, MBOX_RX_NAME);
+ if (ret)
+ return ret;
+
+ if (if_node->pool_name) {
+ priv->mem_pool = of_gen_pool_get(dev->of_node, if_node->pool_name, 0);
+ if (!priv->mem_pool)
+ return dev_err_probe(dev, -ENOMEM,
+ "Unable to get sram pool = %s.",
+ if_node->pool_name);
+ }
+
+ if (if_node->reserved_dma_ranges) {
+ ret = of_reserved_mem_device_init(dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to init reserved memory region.");
+ }
+
+ if (if_node->if_defs.se_if_type == SE_TYPE_ID_HSM) {
+ ret = get_se_soc_info(priv, se_info);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to fetch SoC Info.");
+ }
+
+ /* By default, there is no pending FW to be loaded.*/
+ if (se_info->se_fw_img_nm.seco_fw_nm_in_rfs) {
+ load_fw = get_load_fw_instance(priv);
+ mutex_init(&load_fw->load_fw_lock);
+
+ load_fw->se_fw_img_nm = &se_info->se_fw_img_nm;
+ load_fw->is_fw_tobe_loaded = true;
+
+ if (load_fw->se_fw_img_nm->prim_fw_nm_in_rfs) {
+ /* allocate buffer where SE store encrypted IMEM */
+ load_fw->imem.buf = dmam_alloc_coherent(priv->dev, ELE_IMEM_SIZE,
+ &load_fw->imem.daddr,
+ GFP_KERNEL);
+ if (!load_fw->imem.buf)
+ return dev_err_probe(dev, -ENOMEM,
+ "dmam-alloc-failed: To store encr-IMEM.");
+ load_fw->imem_mgmt = true;
+ }
+ }
+ dev_info(dev, "i.MX secure-enclave: %s0 interface to firmware, configured.",
+ get_se_if_name(priv->if_defs->se_if_type));
+
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int se_suspend(struct device *dev)
+{
+ struct se_if_priv *priv = dev_get_drvdata(dev);
+ struct se_fw_load_info *load_fw;
+ int ret = 0;
+
+ load_fw = get_load_fw_instance(priv);
+
+ if (load_fw->imem_mgmt) {
+ ret = se_save_imem_state(priv, &load_fw->imem);
+ if (ret)
+ dev_warn(dev, "Failure saving IMEM state[0x%x]", ret);
+ }
+
+ return 0;
+}
+
+static int se_resume(struct device *dev)
+{
+ struct se_if_priv *priv = dev_get_drvdata(dev);
+ struct se_fw_load_info *load_fw;
+ int ret = 0;
+
+ load_fw = get_load_fw_instance(priv);
+
+ if (load_fw->imem_mgmt) {
+ ret = se_restore_imem_state(priv, &load_fw->imem);
+ if (ret)
+ dev_warn(dev, "Failure restoring IMEM state[0x%x]", ret);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops se_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(se_suspend, se_resume)
+};
+
+#define SE_PM_OPS (&se_pm)
+#else
+#define SE_PM_OPS NULL
+#endif
+
+static struct platform_driver se_driver = {
+ .driver = {
+ .name = "fsl-se",
+ .of_match_table = se_match,
+ .pm = SE_PM_OPS,
+ },
+ .probe = se_if_probe,
+};
+MODULE_DEVICE_TABLE(of, se_match);
+
+module_platform_driver(se_driver);
+MODULE_AUTHOR("Pankaj Gupta <pankaj.gupta@nxp.com>");
+MODULE_DESCRIPTION("iMX Secure Enclave Driver.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/firmware/imx/se_ctrl.h b/drivers/firmware/imx/se_ctrl.h
new file mode 100644
index 000000000000..b6833abd81d0
--- /dev/null
+++ b/drivers/firmware/imx/se_ctrl.h
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2026 NXP
+ */
+
+#ifndef SE_MU_H
+#define SE_MU_H
+
+#include <linux/bitfield.h>
+#include <linux/miscdevice.h>
+#include <linux/semaphore.h>
+#include <linux/mailbox_client.h>
+
+#define MAX_FW_LOAD_RETRIES 50
+#define SE_MSG_WORD_SZ 0x4
+
+#define RES_STATUS(x) FIELD_GET(0x000000ff, x)
+#define MAX_NVM_MSG_LEN (256)
+#define MESSAGING_VERSION_6 0x6
+#define MESSAGING_VERSION_7 0x7
+
+struct se_clbk_handle {
+ struct completion done;
+ bool signal_rcvd;
+ u32 rx_msg_sz;
+ /*
+ * Assignment of the rx_msg buffer to held till the
+ * received content as part callback function, is copied.
+ */
+ struct se_api_msg *rx_msg;
+ /*
+ * Serialise the timeout path in ele_msg_rcv() against
+ * se_if_rx_callback() so that the callback can never
+ * memcpy into a buffer that the timeout path has already
+ * freed.
+ */
+ spinlock_t clbk_rx_lock;
+};
+
+struct se_imem_buf {
+ u8 *buf;
+ dma_addr_t daddr;
+ u32 size;
+ u32 state;
+};
+
+/* Header of the messages exchange with the EdgeLock Enclave */
+struct se_msg_hdr {
+ u8 ver;
+ u8 size;
+ u8 command;
+ u8 tag;
+} __packed;
+
+#define SE_MU_HDR_SZ 4
+
+struct se_api_msg {
+ struct se_msg_hdr header;
+ u32 data[];
+};
+
+struct se_if_defines {
+ const u8 se_if_type;
+ u8 cmd_tag;
+ u8 rsp_tag;
+ u8 success_tag;
+ u8 base_api_ver;
+ u8 fw_api_ver;
+};
+
+struct se_fw_img_name {
+ const char *prim_fw_nm_in_rfs;
+ const char *seco_fw_nm_in_rfs;
+};
+
+struct se_fw_load_info {
+ const struct se_fw_img_name *se_fw_img_nm;
+ bool is_fw_tobe_loaded;
+ bool imem_mgmt;
+ struct se_imem_buf imem;
+ /* to serialize the fw load state */
+ struct mutex load_fw_lock;
+};
+
+struct se_if_priv {
+ struct device *dev;
+
+ struct se_clbk_handle cmd_receiver_clbk_hdl;
+ /*
+ * Update to the waiting_rsp_dev, to be protected
+ * under se_if_cmd_lock.
+ */
+ struct se_clbk_handle waiting_rsp_clbk_hdl;
+ /*
+ * prevent new command to be sent on the se interface while previous
+ * command is still processing. (response is awaited)
+ */
+ struct mutex se_if_cmd_lock;
+
+ struct mbox_client se_mb_cl;
+ struct mbox_chan *tx_chan, *rx_chan;
+
+ struct gen_pool *mem_pool;
+ const struct se_if_defines *if_defs;
+ struct se_fw_load_info load_fw;
+
+ atomic_t fw_busy;
+};
+
+char *get_se_if_name(u8 se_if_id);
+#endif
diff --git a/include/linux/firmware/imx/se_api.h b/include/linux/firmware/imx/se_api.h
new file mode 100644
index 000000000000..b1c4c9115d7b
--- /dev/null
+++ b/include/linux/firmware/imx/se_api.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2025 NXP
+ */
+
+#ifndef __SE_API_H__
+#define __SE_API_H__
+
+#include <linux/types.h>
+
+#define SOC_ID_OF_IMX8ULP 0x084d
+#define SOC_ID_OF_IMX93 0x9300
+
+#endif /* __SE_API_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v27 4/7] firmware: imx: device context dedicated to priv
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
` (2 preceding siblings ...)
2026-07-15 18:09 ` [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
2026-07-15 17:34 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 5/7] firmware: drivers: imx: adds miscdev pankaj.gupta
` (2 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel
From: Pankaj Gupta <pankaj.gupta@nxp.com>
Add priv_dev_ctx to prepare enabling misc-device context based send-receive
path, to communicate with FW.
No functionality change.
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
No Changes: Comments disposition as follows:
Sashiko reported 3 pre-existing issues while reviewing patch 4/7.
1. ele_msg_send() failure leaves dangling rx_msg
Status: Fixed.
fixed in patch 3/7: common clear_waiter path clears waiter state under lock.
2. ele_service_swap() truncates 64-bit address.
Status: Fixed.
fixed in patch 3/7: dma_addr_t, upper-32-bit validation, and reserved-memory
design.
3. dmam_alloc_coherent() double-free
Status: Fixed.
Fixed in patch 3/7: Manual dmam_free_coherent() for the IMEM buffer
has been removed from probe cleanup.
---
drivers/firmware/imx/ele_base_msg.c | 14 +++++-----
drivers/firmware/imx/ele_common.c | 52 +++++++++++++++++++++----------------
drivers/firmware/imx/ele_common.h | 8 +++---
drivers/firmware/imx/se_ctrl.c | 42 ++++++++++++++++++++++++++++++
drivers/firmware/imx/se_ctrl.h | 9 +++++++
5 files changed, 91 insertions(+), 34 deletions(-)
diff --git a/drivers/firmware/imx/ele_base_msg.c b/drivers/firmware/imx/ele_base_msg.c
index 997854e10e14..fcd2bcaeefc8 100644
--- a/drivers/firmware/imx/ele_base_msg.c
+++ b/drivers/firmware/imx/ele_base_msg.c
@@ -57,8 +57,8 @@ int ele_get_info(struct se_if_priv *priv, struct ele_dev_info *s_info)
tx_msg->data[0] = upper_32_bits(get_info_addr);
tx_msg->data[1] = lower_32_bits(get_info_addr);
tx_msg->data[2] = sizeof(*s_info);
- ret = ele_msg_send_rcv(priv, tx_msg, ELE_GET_INFO_REQ_MSG_SZ, rx_msg,
- ELE_GET_INFO_RSP_MSG_SZ);
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, ELE_GET_INFO_REQ_MSG_SZ,
+ rx_msg, ELE_GET_INFO_RSP_MSG_SZ);
if (ret < 0)
goto exit;
@@ -104,8 +104,8 @@ int ele_ping(struct se_if_priv *priv)
se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
ELE_PING_REQ, ELE_PING_REQ_SZ, true);
- ret = ele_msg_send_rcv(priv, tx_msg, ELE_PING_REQ_SZ, rx_msg,
- ELE_PING_RSP_SZ);
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, ELE_PING_REQ_SZ,
+ rx_msg, ELE_PING_RSP_SZ);
if (ret < 0)
return ret;
@@ -155,7 +155,7 @@ int ele_service_swap(struct se_if_priv *priv,
if (!tx_msg->data[4])
return -EINVAL;
- ret = ele_msg_send_rcv(priv, tx_msg, ELE_SERVICE_SWAP_REQ_MSG_SZ,
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, ELE_SERVICE_SWAP_REQ_MSG_SZ,
rx_msg, ELE_SERVICE_SWAP_RSP_MSG_SZ);
if (ret < 0)
return ret;
@@ -203,7 +203,7 @@ int ele_fw_authenticate(struct se_if_priv *priv, dma_addr_t contnr_addr,
tx_msg->data[1] = 0;
tx_msg->data[2] = lower_32_bits(img_addr);
- ret = ele_msg_send_rcv(priv, tx_msg, ELE_FW_AUTH_REQ_SZ, rx_msg,
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, ELE_FW_AUTH_REQ_SZ, rx_msg,
ELE_FW_AUTH_RSP_MSG_SZ);
if (ret < 0)
return ret;
@@ -241,7 +241,7 @@ int ele_debug_dump(struct se_if_priv *priv)
do {
memset(rx_msg, 0x0, ELE_DEBUG_DUMP_RSP_SZ);
- ret = ele_msg_send_rcv(priv, tx_msg, ELE_DEBUG_DUMP_REQ_SZ,
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg, ELE_DEBUG_DUMP_REQ_SZ,
rx_msg, ELE_DEBUG_DUMP_RSP_SZ);
if (ret < 0)
return ret;
diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
index 3f5f78df49b5..955b7409ff9f 100644
--- a/drivers/firmware/imx/ele_common.c
+++ b/drivers/firmware/imx/ele_common.c
@@ -42,8 +42,9 @@ u32 se_get_msg_chksum(u32 *msg, u32 msg_len)
return chksum;
}
-int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl)
+int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk_hdl)
{
+ struct se_if_priv *priv = dev_ctx->priv;
bool wait_uninterruptible = false;
unsigned long remaining_jiffies;
unsigned long flags;
@@ -65,7 +66,7 @@ int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl)
* after the protocol transaction is brought back to a
* synchronized state.
*/
- if (priv->waiting_rsp_clbk_hdl.rx_msg) {
+ if (READ_ONCE(priv->waiting_rsp_clbk_hdl.dev_ctx)) {
WRITE_ONCE(se_clbk_hdl->signal_rcvd, true);
wait_uninterruptible = true;
continue;
@@ -106,7 +107,7 @@ int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl)
return ret;
}
-int ele_msg_send(struct se_if_priv *priv,
+int ele_msg_send(struct se_if_device_ctx *dev_ctx,
void *tx_msg,
int tx_msg_sz)
{
@@ -118,15 +119,16 @@ int ele_msg_send(struct se_if_priv *priv,
* carried in the message.
*/
if (header->size << 2 != tx_msg_sz) {
- dev_err(priv->dev,
- "User buf hdr: 0x%x, sz mismatced with input-sz (%d != %d).",
- *(u32 *)header, header->size << 2, tx_msg_sz);
+ dev_err(dev_ctx->priv->dev,
+ "%s: User buf hdr: 0x%x, sz mismatched with input-sz (%d != %d).",
+ dev_ctx->devname, *(u32 *)header, header->size << 2, tx_msg_sz);
return -EINVAL;
}
- err = mbox_send_message(priv->tx_chan, tx_msg);
+ err = mbox_send_message(dev_ctx->priv->tx_chan, tx_msg);
if (err < 0) {
- dev_err(priv->dev, "Error: mbox_send_message failure.\n");
+ dev_err(dev_ctx->priv->dev,
+ "%s: Error: mbox_send_message failure.", dev_ctx->devname);
return err;
}
@@ -134,27 +136,29 @@ int ele_msg_send(struct se_if_priv *priv,
}
/* API used for send/receive blocking call. */
-int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
- void *rx_msg, int exp_rx_msg_sz)
+int ele_msg_send_rcv(struct se_if_device_ctx *dev_ctx, void *tx_msg,
+ int tx_msg_sz, void *rx_msg, int exp_rx_msg_sz)
{
+ struct se_if_priv *priv = dev_ctx->priv;
unsigned long flags;
int err;
guard(mutex)(&priv->se_if_cmd_lock);
if (atomic_read(&priv->fw_busy)) {
- dev_dbg(priv->dev, "ELE became unresponsive.\n");
+ dev_dbg(priv->dev, "%s: ELE became unresponsive.\n", dev_ctx->devname);
return -EBUSY;
}
reinit_completion(&priv->waiting_rsp_clbk_hdl.done);
+ priv->waiting_rsp_clbk_hdl.dev_ctx = dev_ctx;
priv->waiting_rsp_clbk_hdl.rx_msg_sz = exp_rx_msg_sz;
priv->waiting_rsp_clbk_hdl.rx_msg = rx_msg;
- err = ele_msg_send(priv, tx_msg, tx_msg_sz);
+ err = ele_msg_send(dev_ctx, tx_msg, tx_msg_sz);
if (err < 0)
goto clear_waiter;
- err = ele_msg_rcv(priv, &priv->waiting_rsp_clbk_hdl);
+ err = ele_msg_rcv(dev_ctx, &priv->waiting_rsp_clbk_hdl);
if (priv->waiting_rsp_clbk_hdl.signal_rcvd) {
/*
@@ -167,11 +171,13 @@ int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
if (err > 0)
err = -ERESTARTSYS;
priv->waiting_rsp_clbk_hdl.signal_rcvd = false;
- dev_err(priv->dev, "Err[0x%x]:Interrupted by signal.", err);
+ dev_err(priv->dev, "%s: Err[0x%x]:Interrupted by signal.",
+ dev_ctx->devname, err);
}
clear_waiter:
spin_lock_irqsave(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
+ priv->waiting_rsp_clbk_hdl.dev_ctx = NULL;
priv->waiting_rsp_clbk_hdl.rx_msg = NULL;
priv->waiting_rsp_clbk_hdl.rx_msg_sz = 0;
spin_unlock_irqrestore(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock, flags);
@@ -222,7 +228,7 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
if (header->tag == priv->if_defs->cmd_tag) {
se_clbk_hdl = &priv->cmd_receiver_clbk_hdl;
spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
- if (!se_clbk_hdl->rx_msg) {
+ if (!se_clbk_hdl->dev_ctx || !se_clbk_hdl->rx_msg) {
spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
dev_warn(dev, "No command receiver registered for message: %.8x\n",
*((u32 *)header));
@@ -236,8 +242,8 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
* SE_IOCTL_ENABLE_CMD_RCV and is not subject to the timeout/circuit-
* breaker handling used for rsp_tag messages.
*/
- dev_dbg(dev, "Selecting cmd receiver: for mesg header:0x%x.",
- *(u32 *)header);
+ dev_dbg(dev, "Selecting cmd receiver:%s for mesg header:0x%x.",
+ se_clbk_hdl->dev_ctx->devname, *(u32 *)header);
/*
* Pre-allocated buffer of MAX_NVM_MSG_LEN
@@ -257,8 +263,8 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
if (sz_mismatch)
dev_err(dev,
- "CMD-RCVER NVM: hdr(0x%x) with different sz(%d != %d).\n",
- *(u32 *)header,
+ "%s: CMD-RCVER NVM: hdr(0x%x) with different sz(%d != %d).\n",
+ se_clbk_hdl->dev_ctx->devname, *(u32 *)header,
(header->size << 2), rx_msg_sz);
} else if (header->tag == priv->if_defs->rsp_tag) {
bool exception_for_sz_mismatch = check_hdr_exception_for_sz(priv, header);
@@ -284,8 +290,8 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
dev_info(dev, "ELE responded (late), recovery FW available.");
return;
}
- dev_dbg(dev, "Selecting resp waiter: for mesg header:0x%x.",
- *(u32 *)header);
+ dev_dbg(dev, "Selecting resp waiter:%s for mesg header:0x%x.",
+ se_clbk_hdl->dev_ctx->devname, *(u32 *)header);
/*
* For rsp_tag traffic, the sender provides the expected response
@@ -305,8 +311,8 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
if (sz_mismatch)
dev_err(dev,
- "Rsp to CMD: hdr(0x%x) with different sz(%d != %d).\n",
- *(u32 *)header,
+ "%s: Rsp to CMD: hdr(0x%x) with different sz(%d != %d).\n",
+ se_clbk_hdl->dev_ctx->devname, *(u32 *)header,
(header->size << 2), exp_rx_msg_sz);
} else {
dev_err(dev, "Failed to select a device for message: %.8x\n",
diff --git a/drivers/firmware/imx/ele_common.h b/drivers/firmware/imx/ele_common.h
index 0365759fdd12..6c419e5c4404 100644
--- a/drivers/firmware/imx/ele_common.h
+++ b/drivers/firmware/imx/ele_common.h
@@ -14,12 +14,12 @@
u32 se_get_msg_chksum(u32 *msg, u32 msg_len);
-int ele_msg_rcv(struct se_if_priv *priv, struct se_clbk_handle *se_clbk_hdl);
+int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk_hdl);
-int ele_msg_send(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz);
+int ele_msg_send(struct se_if_device_ctx *dev_ctx, void *tx_msg, int tx_msg_sz);
-int ele_msg_send_rcv(struct se_if_priv *priv, void *tx_msg, int tx_msg_sz,
- void *rx_msg, int exp_rx_msg_sz);
+int ele_msg_send_rcv(struct se_if_device_ctx *dev_ctx, void *tx_msg,
+ int tx_msg_sz, void *rx_msg, int exp_rx_msg_sz);
void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg);
diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
index 7cb98a373334..25f9cd5441f9 100644
--- a/drivers/firmware/imx/se_ctrl.c
+++ b/drivers/firmware/imx/se_ctrl.c
@@ -197,6 +197,36 @@ static int get_se_soc_info(struct se_if_priv *priv, const struct se_soc_info *se
return 0;
}
+static int init_misc_device_context(struct se_if_priv *priv, int ch_id,
+ struct se_if_device_ctx **new_dev_ctx)
+{
+ const char *err_str = "Failed to allocate memory";
+ struct se_if_device_ctx *dev_ctx;
+ int ret = -ENOMEM;
+
+ dev_ctx = kzalloc_obj(*dev_ctx, GFP_KERNEL);
+
+ if (!dev_ctx)
+ return ret;
+
+ dev_ctx->devname = kasprintf(GFP_KERNEL, "%s0_ch%d",
+ get_se_if_name(priv->if_defs->se_if_type),
+ ch_id);
+ if (!dev_ctx->devname)
+ goto exit;
+
+ dev_ctx->priv = priv;
+ *new_dev_ctx = dev_ctx;
+
+ return ret;
+exit:
+ *new_dev_ctx = NULL;
+
+ kfree(dev_ctx->devname);
+ kfree(dev_ctx);
+ return dev_err_probe(priv->dev, ret, "%s", err_str);
+}
+
/* interface for managed res to free a mailbox channel */
static void if_mbox_free_channel(void *mbox_chan)
{
@@ -253,6 +283,12 @@ static void se_if_probe_cleanup(void *plat_dev)
of_reserved_mem_device_release(dev);
dev_set_drvdata(dev, NULL);
+
+ if (priv->priv_dev_ctx) {
+ kfree(priv->priv_dev_ctx->devname);
+ kfree(priv->priv_dev_ctx);
+ priv->priv_dev_ctx = NULL;
+ }
kfree(priv);
}
@@ -319,6 +355,12 @@ static int se_if_probe(struct platform_device *pdev)
"Failed to init reserved memory region.");
}
+ ret = init_misc_device_context(priv, 0, &priv->priv_dev_ctx);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed[0x%x] to create device contexts.",
+ ret);
+
if (if_node->if_defs.se_if_type == SE_TYPE_ID_HSM) {
ret = get_se_soc_info(priv, se_info);
if (ret)
diff --git a/drivers/firmware/imx/se_ctrl.h b/drivers/firmware/imx/se_ctrl.h
index b6833abd81d0..355d38684b1b 100644
--- a/drivers/firmware/imx/se_ctrl.h
+++ b/drivers/firmware/imx/se_ctrl.h
@@ -20,6 +20,7 @@
#define MESSAGING_VERSION_7 0x7
struct se_clbk_handle {
+ struct se_if_device_ctx *dev_ctx;
struct completion done;
bool signal_rcvd;
u32 rx_msg_sz;
@@ -44,6 +45,12 @@ struct se_imem_buf {
u32 state;
};
+/* Private struct for each char device instance. */
+struct se_if_device_ctx {
+ struct se_if_priv *priv;
+ const char *devname;
+};
+
/* Header of the messages exchange with the EdgeLock Enclave */
struct se_msg_hdr {
u8 ver;
@@ -105,6 +112,8 @@ struct se_if_priv {
struct se_fw_load_info load_fw;
atomic_t fw_busy;
+
+ struct se_if_device_ctx *priv_dev_ctx;
};
char *get_se_if_name(u8 se_if_id);
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v27 5/7] firmware: drivers: imx: adds miscdev
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
` (3 preceding siblings ...)
2026-07-15 18:09 ` [PATCH v27 4/7] firmware: imx: device context dedicated to priv pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
2026-07-15 17:36 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node pankaj.gupta
2026-07-15 18:09 ` [PATCH v27 7/7] arm64: dts: imx8ulp-evk: add reserved memory property pankaj.gupta
6 siblings, 1 reply; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel,
sashiko-bot
From: Pankaj Gupta <pankaj.gupta@nxp.com>
Adds the driver for communication interface to secure-enclave, that
enables exchanging messages with NXP secure enclave HW IP(s)
like EdgeLock Enclave, from:
- User-Space Applications via character driver.
ABI documentation for the NXP secure-enclave driver.
User-space library using this driver:
- i.MX Secure Enclave library:
-- URL: https://github.com/nxp-imx/imx-secure-enclave.git,
- i.MX Secure Middle-Ware:
-- URL: https://github.com/nxp-imx/imx-smw.git
Following checks are performed on the incoming msg-header,
to block exchanging invalid arbitrary commands:
- maximum allowed words,
- check if command-tag & response-tag are valid
- version,
- command id validation check, to allow limited base-line API(s)
and restrict following:
- exchanging power management commands.
- reset requests.
- BBSM configuration requests.
- re-initializing the FW.
- RNG init
- CAAM resource release management
- SE's internal memory management.
from user-space.
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
---
Changes from v26 to v27
Fix issues reported by Sashiko while reviewing patch 5/7:
---------------------------------------------------------
1. Critical: allocated DMA address for encrypted IMEM buffer discarded
Status: Fixed.
The IMEM buffer stores dma_addr_t daddr, and save/restore uses imem->daddr.
2. High: command receiver contexts bypass response timeout
Status: Fixed.
Timeout classification is now based on the callback handle used for the wait,
not file/device context identity.
3. High: dlink_dev_ctx() frees command receiver rx_msg without lock
Status: Fixed.
dlink_dev_ctx() now clears command-receiver callback state under
cmd_receiver_clbk_hdl.clbk_rx_lock and frees the old rx_msg after
dropping the lock.
4. High: missing kref_init() in init_device_context()
Status: Fixed.
The v27 changelog states init_device_context() now calls
kref_init(&dev_ctx->refcount)
5. High: unbind/remove hangs when userspace is blocked in read()
Status: Fixed.
The read path no longer holds dev_ctx->fops_lock across the
blocking ele_msg_rcv() wait, allowing teardown to acquire
fops_lock and clean the context.
6. High: DMA memory freed while firmware may still process timed-out transaction
Status: Fixed.
fw_busy now tracks the exact timed-out dev_ctx via priv->fw_busy_dev_ctx.
Cleanup skips freeing coherent DMA only for that context.
Late response handling uses work, and teardown calls cancel_work_sync()
plus se_clear_fw_busy().
7. High: concurrent ioctls can trigger multiple firmware authentications
Status: Fixed.
Firmware load state is serialized using load_fw->load_fw_lock.
8. High: get_se_soc_id() type confusion leaks pointer bits
Status: Fixed.
get_se_soc_id() now treats device_get_match_data() as struct se_if_node * and
reads if_node->se_info->soc_id.
9. High: early mailbox send failure leaves dangling shared response waiter
Status: Fixed.
ele_msg_send_rcv() now clears response waiter state under clbk_rx_lock
on mailbox send failure.
10. High: invalid dma_to_phys() on coherent DMA address
Status: Fixed.
Firmware authentication and IMEM service-swap paths use dma_addr_t end-to-end.
dma_to_phys() was removed from this path.
11. Medium: UAPI ioctl structs use variable-sized pointers/types
Status: Fixed.
The UAPI ioctl structures now use fixed-width __u64 for user pointers and
__u32 for sizes. Kernel code uses u64_to_user_ptr(), and
.compat_ioctl = compat_ptr_ioctl is registered.
12. Medium: unbounded user size passed to memdup_user()
Status: Fixed.
MAX_ALLOWED_TX_MSG_SZ is introduced and user-provided TX/write
sizes are validated before memdup_user().
13. Pre-existing high: misdirected signal handling in ele_msg_rcv()
Status: Fixed / design-controlled.
Deferred signal handling is tied to the response-wait callback handle
instead of blindly modifying priv->waiting_rsp_clbk_hdl from unrelated
wait paths. Command receiver daemon waits are kept separate from
synchronous response waits.
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260629-imx-se-if-v26-0-146446285744@nxp.com?part=5
---
Documentation/ABI/testing/se-cdev | 44 ++
drivers/firmware/imx/Makefile | 2 +-
drivers/firmware/imx/ele_base_msg.c | 28 +
drivers/firmware/imx/ele_base_msg.h | 19 +
drivers/firmware/imx/ele_common.c | 85 ++-
drivers/firmware/imx/ele_common.h | 6 +
drivers/firmware/imx/ele_fw_api.c | 57 ++
drivers/firmware/imx/ele_fw_api.h | 18 +
drivers/firmware/imx/se_ctrl.c | 1178 ++++++++++++++++++++++++++++++++++-
drivers/firmware/imx/se_ctrl.h | 67 +-
include/uapi/linux/se_ioctl.h | 97 +++
11 files changed, 1571 insertions(+), 30 deletions(-)
diff --git a/Documentation/ABI/testing/se-cdev b/Documentation/ABI/testing/se-cdev
new file mode 100644
index 000000000000..c6b8e16bda78
--- /dev/null
+++ b/Documentation/ABI/testing/se-cdev
@@ -0,0 +1,44 @@
+What: /dev/<se>_mu[0-9]+_ch[0-9]+
+Date: Mar 2025
+KernelVersion: 6.8
+Contact: linux-imx@nxp.com, pankaj.gupta@nxp.com
+Description:
+ NXP offers multiple hardware IP(s) for secure enclaves like EdgeLock-
+ Enclave(ELE), SECO. The character device file descriptors
+ /dev/<se>_mu*_ch* are the interface between userspace NXP's secure-
+ enclave shared library and the kernel driver.
+
+ The ioctl(2)-based ABI is defined and documented in
+ [include]<linux/firmware/imx/ele_mu_ioctl.h>.
+ ioctl(s) are used primarily for:
+
+ - shared memory management
+ - allocation of I/O buffers
+ - getting mu info
+ - setting a dev-ctx as receiver to receive all the commands from FW
+ - getting SoC info
+ - send command and receive command response
+
+ The following file operations are supported:
+
+ open(2)
+ Currently the only useful flags are O_RDWR.
+
+ read(2)
+ Every read() from the opened character device context is waiting on
+ wait_event_interruptible, that gets set by the registered mailbox callback
+ function, indicating a message received from the firmware on message-
+ unit.
+
+ write(2)
+ Every write() to the opened character device context needs to acquire
+ mailbox_lock before sending message on to the message unit.
+
+ close(2)
+ Stops and frees up the I/O contexts that were associated
+ with the file descriptor.
+
+Users: https://github.com/nxp-imx/imx-secure-enclave.git,
+ https://github.com/nxp-imx/imx-smw.git,
+ crypto/skcipher,
+ drivers/nvmem/imx-ocotp-ele.c
diff --git a/drivers/firmware/imx/Makefile b/drivers/firmware/imx/Makefile
index 4412b15846b1..3f41131a0fdc 100644
--- a/drivers/firmware/imx/Makefile
+++ b/drivers/firmware/imx/Makefile
@@ -4,5 +4,5 @@ obj-$(CONFIG_IMX_SCU) += imx-scu.o misc.o imx-scu-irq.o rm.o imx-scu-soc.o
obj-${CONFIG_IMX_SCMI_CPU_DRV} += sm-cpu.o
obj-${CONFIG_IMX_SCMI_MISC_DRV} += sm-misc.o
obj-${CONFIG_IMX_SCMI_LMM_DRV} += sm-lmm.o
-sec_enclave-objs = se_ctrl.o ele_common.o ele_base_msg.o
+sec_enclave-objs = se_ctrl.o ele_common.o ele_base_msg.o ele_fw_api.o
obj-${CONFIG_IMX_SEC_ENCLAVE} += sec_enclave.o
diff --git a/drivers/firmware/imx/ele_base_msg.c b/drivers/firmware/imx/ele_base_msg.c
index fcd2bcaeefc8..28674dc9365f 100644
--- a/drivers/firmware/imx/ele_base_msg.c
+++ b/drivers/firmware/imx/ele_base_msg.c
@@ -15,6 +15,34 @@
#define FW_DBG_DUMP_FIXED_STR "ELE"
+int ele_uapi_allowed_base_cmd(struct se_if_priv *priv,
+ struct se_msg_hdr *header)
+{
+ switch (header->command) {
+ case ELE_PING_REQ: return 0;
+ case ELE_DEBUG_DUMP_REQ: return 0;
+ case ELE_OEM_AUTH_CONTAINER_REQ: return 0;
+ case ELE_OEM_VERIFY_IMAGE_REQ: return 0;
+ case ELE_OEM_REL_CONTAINER_REQ: return 0;
+ case ELE_FW_LIFE_CYCLE_REQ: return 0;
+ case ELE_READ_FUSE_REQ: return 0;
+ case ELE_GET_FW_VERS_REQ: return 0;
+ case ELE_RETURN_LIFE_CYCLE_REQ: return 0;
+ case ELE_GET_EVENT_REQ: return 0;
+ case ELE_COMMIT_REQ: return 0;
+ case ELE_GEN_KEY_BLOB_REQ: return 0;
+ case ELE_GET_FW_STATUS_REQ: return 0;
+ case ELE_XIP_DECRYPT_REQ: return 0;
+ case ELE_WRITE_FUSE: return 0;
+ case ELE_GET_INFO_REQ: return 0;
+ case ELE_DEV_ATTEST_REQ: return 0;
+ case ELE_WRITE_SHADOW_FUSE_REQ: return 0;
+ case ELE_READ_SHADOW_FUSE_REQ: return 0;
+ default:
+ return -EACCES;
+ }
+}
+
int ele_get_info(struct se_if_priv *priv, struct ele_dev_info *s_info)
{
dma_addr_t get_info_addr = 0;
diff --git a/drivers/firmware/imx/ele_base_msg.h b/drivers/firmware/imx/ele_base_msg.h
index 4c3699543e87..238465a4eccd 100644
--- a/drivers/firmware/imx/ele_base_msg.h
+++ b/drivers/firmware/imx/ele_base_msg.h
@@ -15,6 +15,23 @@
#define ELE_NONE_VAL 0x0
+#define ELE_OEM_AUTH_CONTAINER_REQ 0x87
+#define ELE_OEM_VERIFY_IMAGE_REQ 0x88
+#define ELE_OEM_REL_CONTAINER_REQ 0x89
+#define ELE_FW_LIFE_CYCLE_REQ 0x95
+#define ELE_READ_FUSE_REQ 0x97
+#define ELE_GET_FW_VERS_REQ 0x9d
+#define ELE_RETURN_LIFE_CYCLE_REQ 0xa0
+#define ELE_GET_EVENT_REQ 0xa2
+#define ELE_COMMIT_REQ 0xa8
+#define ELE_GEN_KEY_BLOB_REQ 0xaf
+#define ELE_GET_FW_STATUS_REQ 0xc5
+#define ELE_XIP_DECRYPT_REQ 0xc6
+#define ELE_WRITE_FUSE 0xd6
+#define ELE_DEV_ATTEST_REQ 0xdb
+#define ELE_WRITE_SHADOW_FUSE_REQ 0xf2
+#define ELE_READ_SHADOW_FUSE_REQ 0xf3
+
#define ELE_GET_INFO_REQ 0xda
#define ELE_GET_INFO_REQ_MSG_SZ 0x10
#define ELE_GET_INFO_RSP_MSG_SZ 0x08
@@ -95,4 +112,6 @@ int ele_service_swap(struct se_if_priv *priv, dma_addr_t addr,
int ele_fw_authenticate(struct se_if_priv *priv, dma_addr_t contnr_addr,
dma_addr_t img_addr);
int ele_debug_dump(struct se_if_priv *priv);
+int ele_uapi_allowed_base_cmd(struct se_if_priv *priv,
+ struct se_msg_hdr *header);
#endif
diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
index 955b7409ff9f..7deff38c4613 100644
--- a/drivers/firmware/imx/ele_common.c
+++ b/drivers/firmware/imx/ele_common.c
@@ -5,6 +5,27 @@
#include "ele_base_msg.h"
#include "ele_common.h"
+#include "ele_fw_api.h"
+#include "se_ctrl.h"
+
+int se_chk_tx_msg_hdr(struct se_if_device_ctx *dev_ctx, struct se_msg_hdr *header)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+
+ if (!header->size || header->size > MAX_WORD_SIZE)
+ return -EINVAL;
+
+ if (header->tag != priv->if_defs->cmd_tag &&
+ header->tag != priv->if_defs->rsp_tag)
+ return -EINVAL;
+
+ if (header->ver == priv->if_defs->base_api_ver)
+ return ele_uapi_allowed_base_cmd(priv, header);
+ else if (header->ver == priv->if_defs->fw_api_ver)
+ return ele_uapi_allowed_fw_cmd(dev_ctx, header);
+
+ return -EINVAL;
+}
/*
* se_get_msg_chksum() - to calculate checksum word by word.
@@ -42,16 +63,50 @@ u32 se_get_msg_chksum(u32 *msg, u32 msg_len)
return chksum;
}
+static void se_mark_fw_busy(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->fw_busy_lock, flags);
+ if (!priv->fw_busy_dev_ctx) {
+ kref_get(&dev_ctx->refcount);
+ priv->fw_busy_dev_ctx = dev_ctx;
+ atomic_set(&priv->fw_busy, 1);
+ }
+ spin_unlock_irqrestore(&priv->fw_busy_lock, flags);
+}
+
+void set_se_rcv_msg_timeout(struct se_if_device_ctx *dev_ctx, u32 timeout_ms)
+{
+ dev_ctx->rcv_msg_timeout_jiffies = msecs_to_jiffies(timeout_ms);
+}
+
int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk_hdl)
{
struct se_if_priv *priv = dev_ctx->priv;
+ bool is_rsp_wait_with_timeout = false;
bool wait_uninterruptible = false;
unsigned long remaining_jiffies;
+ unsigned long deadline_jiffies;
unsigned long flags;
int ret;
- remaining_jiffies = MAX_SCHEDULE_TIMEOUT;
+ remaining_jiffies = dev_ctx->rcv_msg_timeout_jiffies;
+ if (se_clbk_hdl == &priv->waiting_rsp_clbk_hdl) {
+ is_rsp_wait_with_timeout = true;
+ deadline_jiffies = jiffies + remaining_jiffies;
+ }
+
do {
+ if (is_rsp_wait_with_timeout) {
+ if (time_after_eq(jiffies, deadline_jiffies)) {
+ ret = -ETIMEDOUT;
+ break;
+ }
+ remaining_jiffies = deadline_jiffies - jiffies;
+ }
+
if (wait_uninterruptible)
ret = wait_for_completion_timeout(&se_clbk_hdl->done,
remaining_jiffies);
@@ -67,6 +122,9 @@ int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk
* synchronized state.
*/
if (READ_ONCE(priv->waiting_rsp_clbk_hdl.dev_ctx)) {
+ if (!is_rsp_wait_with_timeout)
+ return ret;
+
WRITE_ONCE(se_clbk_hdl->signal_rcvd, true);
wait_uninterruptible = true;
continue;
@@ -91,7 +149,7 @@ int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk
spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
se_clbk_hdl->rx_msg = NULL;
if (!completion_done(&se_clbk_hdl->done))
- atomic_set(&priv->fw_busy, 1);
+ se_mark_fw_busy(dev_ctx);
spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
ret = -ETIMEDOUT;
@@ -206,6 +264,7 @@ static bool check_hdr_exception_for_sz(struct se_if_priv *priv,
void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
{
struct se_clbk_handle *se_clbk_hdl;
+ bool schedule_fw_busy_work = false;
struct device *dev = mbox_cl->dev;
struct se_msg_hdr *header;
bool sz_mismatch = false;
@@ -284,9 +343,13 @@ void se_if_rx_callback(struct mbox_client *mbox_cl, void *msg)
exp_rx_msg_sz = se_clbk_hdl->rx_msg_sz;
spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
if (!se_clbk_hdl->rx_msg) {
- /* Close circuit breaker on spinlock race */
- atomic_set(&priv->fw_busy, 0);
+ if (atomic_read(&priv->fw_busy))
+ schedule_fw_busy_work = true;
spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+
+ if (schedule_fw_busy_work)
+ schedule_work(&priv->fw_busy_work);
+
dev_info(dev, "ELE responded (late), recovery FW available.");
return;
}
@@ -327,31 +390,31 @@ int se_val_rsp_hdr_n_status(struct se_if_priv *priv, struct se_api_msg *msg,
u32 status;
if (header->tag != priv->if_defs->rsp_tag) {
- dev_err(priv->dev, "MSG[0x%x] Hdr: Resp tag mismatch. (0x%x != 0x%x)",
+ dev_dbg(priv->dev, "MSG[0x%x] Hdr: Resp tag mismatch. (0x%x != 0x%x)",
msg_id, header->tag, priv->if_defs->rsp_tag);
return -EINVAL;
}
if (header->command != msg_id) {
- dev_err(priv->dev, "MSG Header: Cmd id mismatch. (0x%x != 0x%x)",
+ dev_dbg(priv->dev, "MSG Header: Cmd id mismatch. (0x%x != 0x%x)",
header->command, msg_id);
return -EINVAL;
}
if ((sz % 4) || (header->size != (sz >> 2) &&
!check_hdr_exception_for_sz(priv, header))) {
- dev_err(priv->dev, "MSG[0x%x] Hdr: Cmd size mismatch. (0x%x != 0x%x)",
+ dev_dbg(priv->dev, "MSG[0x%x] Hdr: Cmd size mismatch. (0x%x != 0x%x)",
msg_id, header->size, (sz >> 2));
return -EINVAL;
}
if (is_base_api && header->ver != priv->if_defs->base_api_ver) {
- dev_err(priv->dev,
+ dev_dbg(priv->dev,
"MSG[0x%x] Hdr: Base API Vers mismatch. (0x%x != 0x%x)",
msg_id, header->ver, priv->if_defs->base_api_ver);
return -EINVAL;
} else if (!is_base_api && header->ver != priv->if_defs->fw_api_ver) {
- dev_err(priv->dev,
+ dev_dbg(priv->dev,
"MSG[0x%x] Hdr: FW API Vers mismatch. (0x%x != 0x%x)",
msg_id, header->ver, priv->if_defs->fw_api_ver);
return -EINVAL;
@@ -359,7 +422,7 @@ int se_val_rsp_hdr_n_status(struct se_if_priv *priv, struct se_api_msg *msg,
status = RES_STATUS(msg->data[0]);
if (status != priv->if_defs->success_tag) {
- dev_err(priv->dev, "Command Id[%x], Response Failure = 0x%x",
+ dev_dbg(priv->dev, "Command Id[%x], Response Failure = 0x%x",
header->command, status);
return -EPERM;
}
@@ -420,7 +483,7 @@ int se_restore_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem)
* restore imem state.
*/
if (s_info.d_addn_info.imem_state != ELE_IMEM_STATE_BAD || !imem->size)
- return -EIO;
+ return 0;
/*
* IMPORT command will restore IMEM from the given
diff --git a/drivers/firmware/imx/ele_common.h b/drivers/firmware/imx/ele_common.h
index 6c419e5c4404..228ca900d32c 100644
--- a/drivers/firmware/imx/ele_common.h
+++ b/drivers/firmware/imx/ele_common.h
@@ -12,6 +12,11 @@
#define IMX_ELE_FW_DIR "imx/ele/"
+#define MAX_WORD_SIZE 0x20
+#define SE_RCV_MSG_DEFAULT_TIMEOUT 5000
+#define SE_RCV_MSG_LONG_TIMEOUT 5000000
+
+void set_se_rcv_msg_timeout(struct se_if_device_ctx *dev_ctx, u32 val);
u32 se_get_msg_chksum(u32 *msg, u32 msg_len);
int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk_hdl);
@@ -40,4 +45,5 @@ int se_save_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem);
int se_restore_imem_state(struct se_if_priv *priv, struct se_imem_buf *imem);
+int se_chk_tx_msg_hdr(struct se_if_device_ctx *dev_ctx, struct se_msg_hdr *header);
#endif /*__ELE_COMMON_H__ */
diff --git a/drivers/firmware/imx/ele_fw_api.c b/drivers/firmware/imx/ele_fw_api.c
new file mode 100644
index 000000000000..4f0f2cb53857
--- /dev/null
+++ b/drivers/firmware/imx/ele_fw_api.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 NXP
+ */
+
+#include "se_ctrl.h"
+#include "ele_fw_api.h"
+
+static bool se_cmd_receiver_allowed_cmd(u8 cmd)
+{
+ switch (cmd) {
+ case ELE_SESSION_CLOSE_REQ:
+ case ELE_STORAGE_CLOSE_REQ:
+ case ELE_STORAGE_MASTER_IMPORT_REQ:
+ return true;
+ default:
+ return false;
+ }
+}
+
+int ele_uapi_allowed_fw_cmd(struct se_if_device_ctx *dev_ctx, struct se_msg_hdr *header)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+ bool is_cmd_receiver = false;
+
+ scoped_guard(mutex, &priv->modify_lock)
+ if (dev_ctx == priv->cmd_receiver_clbk_hdl.dev_ctx)
+ is_cmd_receiver = true;
+
+ if (header->tag == priv->if_defs->cmd_tag) {
+ if (is_cmd_receiver && !se_cmd_receiver_allowed_cmd(header->command))
+ return -EOPNOTSUPP;
+ }
+
+ if (header->tag == priv->if_defs->rsp_tag && !is_cmd_receiver)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+void fw_api_specific_ops(struct se_if_device_ctx *dev_ctx, struct se_msg_hdr *header)
+{
+ if (header->command == ELE_STORAGE_OPEN_REQ) {
+ int rc = 0;
+
+ rc = set_dev_ctx_as_command_receiver(dev_ctx);
+ if (rc)
+ dev_err(dev_ctx->priv->dev,
+ "Failed to register %s as CMD-Receiver: %d\n",
+ dev_ctx->devname, rc);
+ }
+ if (header->command == ELE_STORAGE_CLOSE_REQ) {
+ scoped_guard(mutex, &dev_ctx->priv->modify_lock)
+ unset_dev_ctx_as_command_receiver(dev_ctx);
+ }
+}
+
diff --git a/drivers/firmware/imx/ele_fw_api.h b/drivers/firmware/imx/ele_fw_api.h
new file mode 100644
index 000000000000..e017359b5053
--- /dev/null
+++ b/drivers/firmware/imx/ele_fw_api.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2026 NXP
+ */
+
+#ifndef ELE_FW_API_H
+#define ELE_FW_API_H
+#include "se_ctrl.h"
+
+#define ELE_SESSION_OPEN_REQ 0x10u
+#define ELE_SESSION_CLOSE_REQ 0x11u
+#define ELE_STORAGE_OPEN_REQ 0xE0u
+#define ELE_STORAGE_CLOSE_REQ 0xE1u
+#define ELE_STORAGE_MASTER_IMPORT_REQ 0xE2u
+
+int ele_uapi_allowed_fw_cmd(struct se_if_device_ctx *dev_ctx, struct se_msg_hdr *header);
+void fw_api_specific_ops(struct se_if_device_ctx *dev_ctx, struct se_msg_hdr *header);
+#endif /* ELE_FW_API_H */
diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
index 25f9cd5441f9..fb82df5c58d5 100644
--- a/drivers/firmware/imx/se_ctrl.c
+++ b/drivers/firmware/imx/se_ctrl.c
@@ -4,10 +4,10 @@
*/
#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
-#include <linux/dma-direct.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/export.h>
@@ -16,6 +16,7 @@
#include <linux/genalloc.h>
#include <linux/init.h>
#include <linux/io.h>
+#include <linux/kref.h>
#include <linux/miscdevice.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
@@ -25,22 +26,21 @@
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sys_soc.h>
+#include <uapi/linux/se_ioctl.h>
#include "ele_base_msg.h"
#include "ele_common.h"
+#include "ele_fw_api.h"
#include "se_ctrl.h"
+/* Maximum response buffer size in bytes for debug-dump replies. */
+#define MAX_ALLOWED_RX_MSG_SZ ELE_DEBUG_DUMP_RSP_SZ
+#define MAX_ALLOWED_TX_MSG_SZ SZ_4K
+
#define MAX_SOC_INFO_DATA_SZ 256
#define MBOX_TX_NAME "tx"
#define MBOX_RX_NAME "rx"
-#define SE_TYPE_STR_DBG "dbg"
-#define SE_TYPE_STR_HSM "hsm"
-
-#define SE_TYPE_ID_DBG 0x1
-
-#define SE_TYPE_ID_HSM 0x2
-
struct se_var_info {
u16 soc_rev;
};
@@ -120,6 +120,13 @@ char *get_se_if_name(u8 se_if_id)
return NULL;
}
+static u32 get_se_soc_id(struct se_if_priv *priv)
+{
+ const struct se_if_node *if_node = device_get_match_data(priv->dev);
+
+ return if_node->se_info->soc_id;
+}
+
static struct se_fw_load_info *get_load_fw_instance(struct se_if_priv *priv)
{
return &priv->load_fw;
@@ -197,11 +204,234 @@ static int get_se_soc_info(struct se_if_priv *priv, const struct se_soc_info *se
return 0;
}
+static int load_firmware(struct se_if_priv *priv, const u8 *se_img_file_to_load)
+{
+ const struct firmware *fw = NULL;
+ dma_addr_t se_fw_dma_addr;
+ u8 *se_fw_buf;
+ int ret;
+
+ if (!se_img_file_to_load) {
+ dev_err(priv->dev, "FW image is not provided.");
+ return -EINVAL;
+ }
+ ret = request_firmware(&fw, se_img_file_to_load, priv->dev);
+ if (ret)
+ return ret;
+
+ dev_info(priv->dev, "loading firmware %s.", se_img_file_to_load);
+
+ /* allocate buffer to store the SE FW */
+ se_fw_buf = dma_alloc_coherent(priv->dev, fw->size, &se_fw_dma_addr, GFP_KERNEL);
+ if (!se_fw_buf) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ memcpy(se_fw_buf, fw->data, fw->size);
+ ret = ele_fw_authenticate(priv, se_fw_dma_addr, se_fw_dma_addr);
+ if (ret < 0) {
+ dev_err(priv->dev,
+ "Error %pe: Authenticate & load SE firmware %s.",
+ ERR_PTR(ret), se_img_file_to_load);
+ ret = -EPERM;
+ }
+ dma_free_coherent(priv->dev, fw->size, se_fw_buf, se_fw_dma_addr);
+exit:
+ release_firmware(fw);
+
+ return ret;
+}
+
+static int se_load_firmware(struct se_if_priv *priv)
+{
+ struct se_fw_load_info *load_fw = get_load_fw_instance(priv);
+ int ret = 0;
+
+ guard(mutex)(&load_fw->load_fw_lock);
+ if (!load_fw->is_fw_tobe_loaded)
+ return 0;
+
+ if (load_fw->imem.state == ELE_IMEM_STATE_BAD) {
+ ret = load_firmware(priv, load_fw->se_fw_img_nm->prim_fw_nm_in_rfs);
+ if (ret) {
+ dev_err(priv->dev, "Failed to load boot firmware.");
+ return -EPERM;
+ }
+ }
+
+ ret = load_firmware(priv, load_fw->se_fw_img_nm->seco_fw_nm_in_rfs);
+ if (ret) {
+ dev_err(priv->dev, "Failed to load runtime firmware.");
+ return -EPERM;
+ }
+
+ load_fw->is_fw_tobe_loaded = false;
+
+ return ret;
+}
+
+static int init_se_shared_mem(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
+ struct se_if_priv *priv = dev_ctx->priv;
+
+ INIT_LIST_HEAD(&se_shared_mem_mgmt->pending_out);
+ INIT_LIST_HEAD(&se_shared_mem_mgmt->pending_in);
+
+ /*
+ * Allocate some memory for data exchanges with S40x.
+ * This will be used for data not requiring secure memory.
+ */
+ se_shared_mem_mgmt->non_secure_mem.ptr =
+ dma_alloc_coherent(priv->dev, MAX_DATA_SIZE_PER_USER,
+ &se_shared_mem_mgmt->non_secure_mem.dma_addr,
+ GFP_KERNEL);
+ if (!se_shared_mem_mgmt->non_secure_mem.ptr)
+ return -ENOMEM;
+
+ se_shared_mem_mgmt->non_secure_mem.size = MAX_DATA_SIZE_PER_USER;
+ se_shared_mem_mgmt->non_secure_mem.pos = 0;
+
+ return 0;
+}
+
+static void cleanup_se_shared_mem(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
+ struct se_if_priv *priv = dev_ctx->priv;
+
+ /* Free non-secure shared buffer. */
+ dma_free_coherent(priv->dev, MAX_DATA_SIZE_PER_USER,
+ se_shared_mem_mgmt->non_secure_mem.ptr,
+ se_shared_mem_mgmt->non_secure_mem.dma_addr);
+
+ se_shared_mem_mgmt->non_secure_mem.ptr = NULL;
+ se_shared_mem_mgmt->non_secure_mem.dma_addr = 0;
+ se_shared_mem_mgmt->non_secure_mem.size = 0;
+ se_shared_mem_mgmt->non_secure_mem.pos = 0;
+}
+
+/* Need to copy the output data to user-device context.
+ */
+static int se_dev_ctx_cpy_out_data(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
+ struct se_if_priv *priv = dev_ctx->priv;
+ struct se_buf_desc *b_desc, *temp;
+ bool do_cpy = true;
+
+ list_for_each_entry_safe(b_desc, temp, &se_shared_mem_mgmt->pending_out, link) {
+ if (b_desc->usr_buf_ptr && b_desc->shared_buf_ptr && do_cpy) {
+ dev_dbg(priv->dev, "Copying output data to user.");
+ if (do_cpy && copy_to_user(b_desc->usr_buf_ptr,
+ b_desc->shared_buf_ptr,
+ b_desc->size)) {
+ dev_err(priv->dev, "Failure copying output data to user.");
+ do_cpy = false;
+ }
+ }
+
+ if (b_desc->shared_buf_ptr)
+ memset(b_desc->shared_buf_ptr, 0, b_desc->size);
+
+ list_del(&b_desc->link);
+ kfree(b_desc);
+ }
+
+ return do_cpy ? 0 : -EFAULT;
+}
+
+/*
+ * Clean the used Shared Memory space,
+ * whether its Input Data copied from user buffers, or
+ * Data received from FW.
+ */
+static void se_dev_ctx_shared_mem_cleanup(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
+ struct list_head *pending_lists[] = {&se_shared_mem_mgmt->pending_in,
+ &se_shared_mem_mgmt->pending_out};
+ struct se_buf_desc *b_desc, *temp;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pending_lists); i++) {
+ list_for_each_entry_safe(b_desc, temp, pending_lists[i], link) {
+ if (b_desc->shared_buf_ptr)
+ memset(b_desc->shared_buf_ptr, 0, b_desc->size);
+
+ list_del(&b_desc->link);
+ kfree(b_desc);
+ }
+ }
+ se_shared_mem_mgmt->non_secure_mem.pos = 0;
+}
+
+static struct se_buf_desc *add_b_desc_to_pending_list(void *shared_ptr_with_pos,
+ struct se_ioctl_setup_iobuf *io,
+ struct se_if_device_ctx *dev_ctx)
+{
+ struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
+ struct se_buf_desc *b_desc = NULL;
+
+ b_desc = kzalloc_obj(*b_desc, GFP_KERNEL);
+ if (!b_desc)
+ return ERR_PTR(-ENOMEM);
+
+ b_desc->shared_buf_ptr = shared_ptr_with_pos;
+ b_desc->usr_buf_ptr = u64_to_user_ptr(io->user_buf);
+ b_desc->size = io->length;
+
+ if (io->flags & SE_IO_BUF_FLAGS_IS_INPUT) {
+ /*
+ * buffer is input:
+ * add an entry in the "pending input buffers" list so
+ * that copied data can be cleaned from shared memory
+ * later.
+ */
+ list_add_tail(&b_desc->link, &se_shared_mem_mgmt->pending_in);
+ } else {
+ /*
+ * buffer is output:
+ * add an entry in the "pending out buffers" list so data
+ * can be copied to user space when receiving Secure-Enclave
+ * response.
+ */
+ list_add_tail(&b_desc->link, &se_shared_mem_mgmt->pending_out);
+ }
+
+ return b_desc;
+}
+
+static void se_if_open_gate_release(struct kref *kref)
+{
+ struct se_if_open_gate *gate =
+ container_of(kref, struct se_if_open_gate, refcount);
+
+ kfree(gate);
+}
+
+static bool se_if_open_gate_get(struct se_if_open_gate *gate)
+{
+ if (!gate)
+ return false;
+
+ return kref_get_unless_zero(&gate->refcount);
+}
+
+static void se_if_open_gate_put(struct se_if_open_gate *gate)
+{
+ if (gate)
+ kref_put(&gate->refcount, se_if_open_gate_release);
+}
+
static int init_misc_device_context(struct se_if_priv *priv, int ch_id,
- struct se_if_device_ctx **new_dev_ctx)
+ struct se_if_device_ctx **new_dev_ctx,
+ const struct file_operations *se_if_fops)
{
const char *err_str = "Failed to allocate memory";
struct se_if_device_ctx *dev_ctx;
+ struct se_if_open_gate *gate = NULL;
int ret = -ENOMEM;
dev_ctx = kzalloc_obj(*dev_ctx, GFP_KERNEL);
@@ -215,18 +445,865 @@ static int init_misc_device_context(struct se_if_priv *priv, int ch_id,
if (!dev_ctx->devname)
goto exit;
+ mutex_init(&dev_ctx->fops_lock);
+
dev_ctx->priv = priv;
+ kref_init(&dev_ctx->refcount);
+ dev_ctx->cleanup_done = false;
*new_dev_ctx = dev_ctx;
+ set_se_rcv_msg_timeout(dev_ctx, SE_RCV_MSG_DEFAULT_TIMEOUT);
+
+ gate = kzalloc_obj(*gate, GFP_KERNEL);
+ if (!gate)
+ goto exit;
+
+ mutex_init(&gate->lock);
+ kref_init(&gate->refcount); /* device-owned reference */
+ gate->priv = priv;
+ gate->dying = false;
+ priv->open_gate = gate;
+
+ /*
+ * The miscdevice storage is now owned by the open gate object.
+ * priv->priv_dev_ctx still keeps a pointer to that miscdevice.
+ */
+ dev_ctx->miscdev = &gate->miscdev;
+
+ dev_ctx->miscdev->name = dev_ctx->devname;
+ dev_ctx->miscdev->minor = MISC_DYNAMIC_MINOR;
+ dev_ctx->miscdev->fops = se_if_fops;
+ dev_ctx->miscdev->parent = priv->dev;
+ ret = misc_register(dev_ctx->miscdev);
+ if (ret) {
+ err_str = "Failed to register misc device.";
+ goto exit;
+ }
return ret;
exit:
*new_dev_ctx = NULL;
-
+ if (gate) {
+ priv->open_gate = NULL;
+ se_if_open_gate_put(gate);
+ }
kfree(dev_ctx->devname);
kfree(dev_ctx);
return dev_err_probe(priv->dev, ret, "%s", err_str);
}
+static void se_if_priv_release(struct kref *kref)
+{
+ struct se_if_priv *priv = container_of(kref, struct se_if_priv, refcount);
+
+ /* Free priv_dev_ctx if it exists */
+ if (priv->priv_dev_ctx) {
+ /*
+ * miscdev storage belongs to open_gate, not directly to
+ * priv_dev_ctx. The gate should already have been detached
+ * from priv during teardown.
+ */
+
+ kfree(priv->priv_dev_ctx->devname);
+ kfree(priv->priv_dev_ctx);
+ priv->priv_dev_ctx = NULL;
+ }
+
+ /*
+ * Be defensive: if teardown did not already drop the device-owned
+ * gate reference for some reason, release it here.
+ */
+ if (priv->open_gate) {
+ se_if_open_gate_put(priv->open_gate);
+ priv->open_gate = NULL;
+ }
+
+ /* Free any remaining resources that weren't devm-managed */
+ kfree(priv);
+}
+
+static void se_if_dev_ctx_release(struct kref *kref)
+{
+ struct se_if_device_ctx *dev_ctx =
+ container_of(kref, struct se_if_device_ctx, refcount);
+ struct se_if_priv *priv = dev_ctx->priv;
+
+ kfree(dev_ctx);
+
+ /* drop the priv reference owned by this device context */
+ kref_put(&priv->refcount, se_if_priv_release);
+}
+
+static void se_clear_fw_busy(struct se_if_priv *priv)
+{
+ struct se_if_device_ctx *dev_ctx = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&priv->fw_busy_lock, flags);
+ dev_ctx = priv->fw_busy_dev_ctx;
+ priv->fw_busy_dev_ctx = NULL;
+ atomic_set(&priv->fw_busy, 0);
+ spin_unlock_irqrestore(&priv->fw_busy_lock, flags);
+
+ if (!dev_ctx)
+ return;
+
+ scoped_guard(mutex, &dev_ctx->fops_lock) {
+ if (dev_ctx->cleanup_done)
+ cleanup_se_shared_mem(dev_ctx);
+ }
+
+ kref_put(&dev_ctx->refcount, se_if_dev_ctx_release);
+}
+
+void unset_dev_ctx_as_command_receiver(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+ struct se_api_msg *old_rx_msg = NULL;
+ struct se_clbk_handle *se_clbk_hdl;
+ unsigned long flags;
+
+ lockdep_assert_held(&priv->modify_lock);
+ se_clbk_hdl = &priv->cmd_receiver_clbk_hdl;
+
+ if (se_clbk_hdl->dev_ctx == dev_ctx) {
+ spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
+ old_rx_msg = se_clbk_hdl->rx_msg;
+ se_clbk_hdl->dev_ctx = NULL;
+ se_clbk_hdl->rx_msg = NULL;
+ se_clbk_hdl->rx_msg_sz = 0;
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+
+ kfree(old_rx_msg);
+ complete_all(&se_clbk_hdl->done);
+ }
+}
+
+int set_dev_ctx_as_command_receiver(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+ struct se_api_msg *new_rx_msg = NULL;
+ struct se_clbk_handle *se_clbk_hdl;
+ unsigned long flags;
+
+ se_clbk_hdl = &priv->cmd_receiver_clbk_hdl;
+ guard(mutex)(&priv->modify_lock);
+ if (se_clbk_hdl->dev_ctx == dev_ctx)
+ return 0;
+
+ if (se_clbk_hdl->dev_ctx)
+ return -EBUSY;
+
+ if (!se_clbk_hdl->rx_msg) {
+ new_rx_msg = kzalloc(MAX_NVM_MSG_LEN, GFP_KERNEL);
+ if (!new_rx_msg)
+ return -ENOMEM;
+ }
+ spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
+ if (new_rx_msg)
+ se_clbk_hdl->rx_msg = new_rx_msg;
+ reinit_completion(&se_clbk_hdl->done);
+ se_clbk_hdl->rx_msg_sz = MAX_NVM_MSG_LEN;
+ se_clbk_hdl->dev_ctx = dev_ctx;
+ dev_ctx->rcv_msg_timeout_jiffies = MAX_SCHEDULE_TIMEOUT;
+ spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
+
+ return 0;
+}
+
+static void dlink_dev_ctx(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+
+ unset_dev_ctx_as_command_receiver(dev_ctx);
+
+ if (!list_empty(&dev_ctx->link)) {
+ list_del_init(&dev_ctx->link);
+ priv->active_devctx_count--;
+ }
+}
+
+static bool se_is_fw_busy_ctx(struct se_if_device_ctx *dev_ctx)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+ unsigned long flags;
+ bool match;
+
+ spin_lock_irqsave(&priv->fw_busy_lock, flags);
+ match = priv->fw_busy_dev_ctx == dev_ctx;
+ spin_unlock_irqrestore(&priv->fw_busy_lock, flags);
+
+ return match;
+}
+
+static void cleanup_dev_ctx(struct se_if_device_ctx *dev_ctx, bool is_fclose)
+{
+ scoped_guard(mutex, &dev_ctx->fops_lock) {
+ if (dev_ctx->cleanup_done)
+ goto exit;
+
+ /*
+ * fw_busy is caused by one timed-out synchronous transaction. Only that
+ * transaction's dev_ctx may still have coherent memory referenced by FW.
+ * Do not skip cleanup for unrelated contexts while fw_busy is set.
+ */
+ if (se_is_fw_busy_ctx(dev_ctx))
+ dev_warn(dev_ctx->priv->dev,
+ "%s: deferring shared memory cleanup while FW is busy\n",
+ dev_ctx->devname);
+ else
+ cleanup_se_shared_mem(dev_ctx);
+
+ kfree(dev_ctx->devname);
+ dev_ctx->devname = NULL;
+ dev_ctx->cleanup_done = true;
+ }
+exit:
+ if (is_fclose)
+ kref_put(&dev_ctx->refcount, se_if_dev_ctx_release);
+}
+
+static void dlink_n_cleanup_dev_ctx(struct se_if_device_ctx *dev_ctx, bool is_fclose)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+
+ if (is_fclose) {
+ scoped_guard(mutex, &priv->modify_lock)
+ dlink_dev_ctx(dev_ctx);
+ }
+
+ cleanup_dev_ctx(dev_ctx, is_fclose);
+}
+
+static int init_device_context(struct se_if_priv *priv, int ch_id,
+ struct se_if_device_ctx **new_dev_ctx)
+{
+ struct se_if_device_ctx *dev_ctx;
+ int ret = 0;
+
+ dev_ctx = kzalloc_obj(*dev_ctx, GFP_KERNEL);
+
+ if (!dev_ctx)
+ return -ENOMEM;
+
+ dev_ctx->devname = kasprintf(GFP_KERNEL, "%s0_ch%d",
+ get_se_if_name(priv->if_defs->se_if_type),
+ ch_id);
+ if (!dev_ctx->devname) {
+ kfree(dev_ctx);
+ return -ENOMEM;
+ }
+
+ mutex_init(&dev_ctx->fops_lock);
+ kref_init(&dev_ctx->refcount);
+ dev_ctx->priv = priv;
+ dev_ctx->cleanup_done = false;
+ INIT_LIST_HEAD(&dev_ctx->link);
+ set_se_rcv_msg_timeout(dev_ctx, SE_RCV_MSG_LONG_TIMEOUT);
+ *new_dev_ctx = dev_ctx;
+
+ ret = init_se_shared_mem(dev_ctx);
+ if (ret < 0) {
+ kfree(dev_ctx->devname);
+ kfree(dev_ctx);
+ *new_dev_ctx = NULL;
+
+ return ret;
+ }
+
+ /* Take a reference to priv for this device context */
+ kref_get(&priv->refcount);
+
+ scoped_guard(mutex, &priv->modify_lock) {
+ list_add_tail(&dev_ctx->link, &priv->dev_ctx_list);
+ priv->active_devctx_count++;
+ }
+
+ return ret;
+}
+
+static int se_ioctl_cmd_snd_rcv_cleanup(struct se_if_device_ctx *dev_ctx, void __user *uarg,
+ struct se_ioctl_cmd_snd_rcv_rsp_info *cmd_snd_rcv_rsp_info)
+{
+ /* shared memory is allocated before this IOCTL */
+ se_dev_ctx_shared_mem_cleanup(dev_ctx);
+
+ if (cmd_snd_rcv_rsp_info->rx_buf_sz &&
+ copy_to_user(uarg, cmd_snd_rcv_rsp_info, sizeof(*cmd_snd_rcv_rsp_info))) {
+ dev_err(dev_ctx->priv->dev, "%s: Failed to copy cmd_snd_rcv_rsp_info to user.",
+ dev_ctx->devname);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int se_ioctl_cmd_snd_rcv_rsp_handler(struct se_if_device_ctx *dev_ctx,
+ void __user *uarg)
+{
+ struct se_ioctl_cmd_snd_rcv_rsp_info cmd_snd_rcv_rsp_info = {0};
+ struct se_if_priv *priv = dev_ctx->priv;
+ int rsp_status_err = 0;
+ int cleanup_err = 0;
+ int err = 0;
+
+ if (copy_from_user(&cmd_snd_rcv_rsp_info, uarg,
+ sizeof(cmd_snd_rcv_rsp_info))) {
+ dev_err(priv->dev,
+ "%s: Failed to copy cmd_snd_rcv_rsp_info from user.",
+ dev_ctx->devname);
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return -EFAULT;
+ }
+
+ if (cmd_snd_rcv_rsp_info.tx_buf_sz < SE_MU_HDR_SZ ||
+ cmd_snd_rcv_rsp_info.tx_buf_sz > MAX_ALLOWED_TX_MSG_SZ) {
+ dev_err(priv->dev, "%s: User buffer too small/large(%d < %d)",
+ dev_ctx->devname, cmd_snd_rcv_rsp_info.tx_buf_sz,
+ cmd_snd_rcv_rsp_info.tx_buf_sz < SE_MU_HDR_SZ ? SE_MU_HDR_SZ :
+ MAX_ALLOWED_TX_MSG_SZ);
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return -ENOSPC;
+ }
+
+ struct se_api_msg *tx_msg __free(kfree) =
+ memdup_user(u64_to_user_ptr(cmd_snd_rcv_rsp_info.tx_buf),
+ cmd_snd_rcv_rsp_info.tx_buf_sz);
+ if (IS_ERR(tx_msg)) {
+ err = PTR_ERR(tx_msg);
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return err;
+ }
+
+ err = se_chk_tx_msg_hdr(dev_ctx, &tx_msg->header);
+ if (err) {
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return err;
+ }
+
+ if (cmd_snd_rcv_rsp_info.rx_buf_sz < sizeof(struct se_msg_hdr) ||
+ cmd_snd_rcv_rsp_info.rx_buf_sz > MAX_ALLOWED_RX_MSG_SZ) {
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return -EINVAL;
+ }
+
+ if (tx_msg->header.tag != priv->if_defs->cmd_tag) {
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return -EINVAL;
+ }
+
+ if (tx_msg->header.ver == priv->if_defs->fw_api_ver &&
+ get_load_fw_instance(priv)->is_fw_tobe_loaded) {
+ err = se_load_firmware(priv);
+ if (err) {
+ dev_err(priv->dev, "Could not send msg as FW is not loaded.");
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return -EPERM;
+ }
+ }
+
+ struct se_api_msg *rx_msg __free(kfree) =
+ kzalloc(cmd_snd_rcv_rsp_info.rx_buf_sz, GFP_KERNEL);
+ if (!rx_msg) {
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return -ENOMEM;
+ }
+
+ err = ele_msg_send_rcv(dev_ctx, tx_msg, cmd_snd_rcv_rsp_info.tx_buf_sz,
+ rx_msg, cmd_snd_rcv_rsp_info.rx_buf_sz);
+ if (err < 0) {
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+
+ return err;
+ }
+
+ dev_dbg(priv->dev, "%s: %s %s.", dev_ctx->devname, __func__,
+ "message received, start transmit to user");
+
+ rsp_status_err =
+ se_val_rsp_hdr_n_status(priv, rx_msg, tx_msg->header.command,
+ cmd_snd_rcv_rsp_info.rx_buf_sz,
+ tx_msg->header.ver == priv->if_defs->base_api_ver);
+
+ if (!rsp_status_err) {
+ err = se_dev_ctx_cpy_out_data(dev_ctx);
+ if (err < 0) {
+ se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+ return err;
+ }
+ }
+
+ /* Copy data from the buffer */
+ print_hex_dump_debug("to user ", DUMP_PREFIX_OFFSET, 4, 4, rx_msg,
+ cmd_snd_rcv_rsp_info.rx_buf_sz, false);
+
+ if (copy_to_user(u64_to_user_ptr(cmd_snd_rcv_rsp_info.rx_buf), rx_msg,
+ cmd_snd_rcv_rsp_info.rx_buf_sz)) {
+ dev_err(priv->dev, "%s: Failed to copy to user.", dev_ctx->devname);
+ err = -EFAULT;
+ }
+
+ cleanup_err = se_ioctl_cmd_snd_rcv_cleanup(dev_ctx, uarg, &cmd_snd_rcv_rsp_info);
+
+ if (cleanup_err && !err)
+ err = cleanup_err;
+
+ if (!err && !rsp_status_err)
+ fw_api_specific_ops(dev_ctx, &tx_msg->header);
+
+ return err;
+}
+
+static int se_ioctl_get_mu_info(struct se_if_device_ctx *dev_ctx,
+ void __user *uarg)
+{
+ struct se_if_priv *priv = dev_ctx->priv;
+ struct se_ioctl_get_if_info if_info;
+ struct se_if_node *if_node;
+ int err = 0;
+
+ if_node = container_of(priv->if_defs, typeof(*if_node), if_defs);
+
+ if_info.se_if_id = 0;
+ if_info.interrupt_idx = 0;
+ if_info.tz = 0;
+ if_info.did = 0;
+ if_info.cmd_tag = priv->if_defs->cmd_tag;
+ if_info.rsp_tag = priv->if_defs->rsp_tag;
+ if_info.success_tag = priv->if_defs->success_tag;
+ if_info.base_api_ver = priv->if_defs->base_api_ver;
+ if_info.fw_api_ver = priv->if_defs->fw_api_ver;
+
+ dev_dbg(priv->dev, "%s: info [se_if_id: %d, irq_idx: %d, tz: 0x%x, did: 0x%x].",
+ dev_ctx->devname, if_info.se_if_id, if_info.interrupt_idx, if_info.tz,
+ if_info.did);
+
+ if (copy_to_user(uarg, &if_info, sizeof(if_info))) {
+ dev_err(priv->dev, "%s: Failed to copy mu info to user.",
+ dev_ctx->devname);
+ err = -EFAULT;
+ }
+
+ return err;
+}
+
+static void rollback_shared_mem_pos(struct se_if_device_ctx *dev_ctx, u32 length)
+{
+ struct se_shared_mem *shared_mem = NULL;
+
+ shared_mem = &dev_ctx->se_shared_mem_mgmt.non_secure_mem;
+
+ if (WARN_ON_ONCE(length > shared_mem->pos)) {
+ shared_mem->pos = 0;
+ return;
+ }
+
+ shared_mem->pos -= length;
+}
+
+static int get_shared_mem_slot(struct se_if_device_ctx *dev_ctx,
+ u32 *length, dma_addr_t *ele_dma_addr, void **ptr)
+{
+ struct se_shared_mem *shared_mem = NULL;
+ size_t aligned_len = 0;
+ u32 pos;
+
+ aligned_len = round_up((size_t)*length, 8);
+ if (aligned_len < *length) {
+ dev_err(dev_ctx->priv->dev, "%s: Invalid buffer length.",
+ dev_ctx->devname);
+ return -EINVAL;
+ }
+
+ /* No specific requirement for this buffer. */
+ shared_mem = &dev_ctx->se_shared_mem_mgmt.non_secure_mem;
+
+ /* Check there is enough space in the shared memory. */
+ dev_dbg(dev_ctx->priv->dev, "%s: req_size = %zd, max_size= %d, curr_pos = %d",
+ dev_ctx->devname, aligned_len, shared_mem->size,
+ shared_mem->pos);
+
+ if (shared_mem->size < shared_mem->pos ||
+ aligned_len > (shared_mem->size - shared_mem->pos)) {
+ dev_err(dev_ctx->priv->dev, "%s: Not enough space in shared memory.",
+ dev_ctx->devname);
+ return -ENOMEM;
+ }
+
+ /* Allocate space in shared memory. 8 bytes aligned. */
+ pos = shared_mem->pos;
+ shared_mem->pos += aligned_len;
+ *ele_dma_addr = (u64)shared_mem->dma_addr + pos;
+ *ptr = shared_mem->ptr + pos;
+ *length = aligned_len;
+
+ memset(shared_mem->ptr + pos, 0, aligned_len);
+
+ return 0;
+}
+
+/*
+ * Copy a buffer of data to/from the user and return the address to use in
+ * messages
+ */
+static int se_ioctl_setup_iobuf_handler(struct se_if_device_ctx *dev_ctx,
+ void __user *uarg)
+{
+ struct se_ioctl_setup_iobuf io = {0};
+ struct se_buf_desc *b_desc = NULL;
+ void *dma_buf_ptr = NULL;
+ dma_addr_t ele_dma_addr;
+ u32 aligned_len = 0;
+ int err = 0;
+
+ if (copy_from_user(&io, uarg, sizeof(io))) {
+ dev_err(dev_ctx->priv->dev, "%s: Failed copy iobuf config from user.",
+ dev_ctx->devname);
+ return -EFAULT;
+ }
+
+ dev_dbg(dev_ctx->priv->dev, "%s: io [buf: %p(%d) flag: %x].", dev_ctx->devname,
+ u64_to_user_ptr(io.user_buf), io.length, io.flags);
+
+ if (io.length == 0 || !io.user_buf) {
+ /*
+ * Accept NULL pointers since some buffers are optional
+ * in FW commands. In this case we should return 0 as
+ * pointer to be embedded into the message.
+ * Skip all data copy part of code below.
+ */
+ io.ele_addr = 0;
+ goto copy;
+ }
+
+ aligned_len = io.length;
+ err = get_shared_mem_slot(dev_ctx, &aligned_len, &ele_dma_addr, &dma_buf_ptr);
+ if (err)
+ return err;
+
+ io.ele_addr = ele_dma_addr;
+ if ((io.flags & SE_IO_BUF_FLAGS_IS_INPUT) ||
+ (io.flags & SE_IO_BUF_FLAGS_IS_IN_OUT)) {
+ /*
+ * buffer is input:
+ * copy data from user space to this allocated buffer.
+ */
+ if (copy_from_user(dma_buf_ptr, u64_to_user_ptr(io.user_buf),
+ io.length)) {
+ dev_err(dev_ctx->priv->dev,
+ "%s: Failed copy data to shared memory.",
+ dev_ctx->devname);
+ err = -EFAULT;
+ goto rollback;
+ }
+ }
+
+ b_desc = add_b_desc_to_pending_list(dma_buf_ptr, &io, dev_ctx);
+ if (IS_ERR(b_desc)) {
+ err = PTR_ERR(b_desc);
+ dev_err(dev_ctx->priv->dev, "%s: Failed to allocate/link b_desc.",
+ dev_ctx->devname);
+ goto rollback;
+ }
+
+copy:
+ /* Provide the EdgeLock Enclave address to user space only if success.*/
+ if (copy_to_user(uarg, &io, sizeof(io))) {
+ dev_err(dev_ctx->priv->dev, "%s: Failed to copy iobuff setup to user.",
+ dev_ctx->devname);
+ err = -EFAULT;
+ goto rollback;
+ }
+ return err;
+
+rollback:
+ if (!IS_ERR_OR_NULL(b_desc)) {
+ list_del(&b_desc->link);
+ kfree(b_desc);
+ }
+
+ if (dma_buf_ptr && aligned_len) {
+ memset(dma_buf_ptr, 0, aligned_len);
+ rollback_shared_mem_pos(dev_ctx, aligned_len);
+ }
+
+ return err;
+}
+
+/* IOCTL to provide SoC information */
+static int se_ioctl_get_se_soc_info_handler(struct se_if_device_ctx *dev_ctx,
+ void __user *uarg)
+{
+ struct se_ioctl_get_soc_info soc_info;
+ int err = -EINVAL;
+
+ soc_info.soc_id = get_se_soc_id(dev_ctx->priv);
+ soc_info.soc_rev = var_se_info.soc_rev;
+
+ err = copy_to_user(uarg, (u8 *)(&soc_info), sizeof(soc_info));
+ if (err) {
+ dev_err(dev_ctx->priv->dev, "%s: Failed to copy soc info to user.",
+ dev_ctx->devname);
+ err = -EFAULT;
+ }
+
+ return err;
+}
+
+/*
+ * File operations for user-space
+ */
+
+/* Write a message to the MU. */
+static ssize_t se_if_fops_write(struct file *fp, const char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ struct se_if_device_ctx *dev_ctx = fp->private_data;
+ struct se_if_priv *priv;
+ int err;
+
+ scoped_cond_guard(mutex_intr, return -EBUSY, &dev_ctx->fops_lock) {
+ if (dev_ctx->cleanup_done)
+ return -ENODEV;
+
+ priv = dev_ctx->priv;
+
+ dev_dbg(priv->dev, "%s: write from buf (%p)%zu, ppos=%lld.", dev_ctx->devname,
+ buf, size, ((ppos) ? *ppos : 0));
+
+ if (dev_ctx != priv->cmd_receiver_clbk_hdl.dev_ctx)
+ return -EINVAL;
+
+ if (size < SE_MU_HDR_SZ || size > MAX_ALLOWED_TX_MSG_SZ) {
+ dev_err(priv->dev, "%s: User buffer too small/large(%zu < %d)",
+ dev_ctx->devname, size,
+ size < SE_MU_HDR_SZ ? SE_MU_HDR_SZ :
+ MAX_ALLOWED_TX_MSG_SZ);
+ return -ENOSPC;
+ }
+
+ struct se_api_msg *tx_msg __free(kfree) = memdup_user(buf, size);
+ if (IS_ERR(tx_msg))
+ return PTR_ERR(tx_msg);
+
+ err = se_chk_tx_msg_hdr(dev_ctx, &tx_msg->header);
+ if (err)
+ return err;
+
+ print_hex_dump_debug("from user ", DUMP_PREFIX_OFFSET, 4, 4,
+ tx_msg, size, false);
+
+ err = ele_msg_send(dev_ctx, tx_msg, size);
+
+ return err;
+ }
+}
+
+/*
+ * Read a message from the MU.
+ * Blocking until a message is available.
+ */
+static ssize_t se_if_fops_read(struct file *fp, char __user *buf, size_t size,
+ loff_t *ppos)
+{
+ struct se_if_device_ctx *dev_ctx = fp->private_data;
+ struct se_if_priv *priv;
+ unsigned long flags;
+ size_t copy_len;
+ int err;
+
+ scoped_cond_guard(mutex_intr, return -EBUSY, &dev_ctx->fops_lock) {
+ if (dev_ctx->cleanup_done) {
+ priv->cmd_receiver_clbk_hdl.rx_msg_sz = 0;
+ se_dev_ctx_shared_mem_cleanup(dev_ctx);
+ return -ENODEV;
+ }
+
+ priv = dev_ctx->priv;
+
+ dev_dbg(priv->dev, "%s: read to buf %p(%zu), ppos=%lld.", dev_ctx->devname,
+ buf, size, ((ppos) ? *ppos : 0));
+
+ if (dev_ctx != priv->cmd_receiver_clbk_hdl.dev_ctx) {
+ priv->cmd_receiver_clbk_hdl.rx_msg_sz = 0;
+ se_dev_ctx_shared_mem_cleanup(dev_ctx);
+ return -EINVAL;
+ }
+ }
+
+ err = ele_msg_rcv(dev_ctx, &priv->cmd_receiver_clbk_hdl);
+ if (err < 0) {
+ if (err != -ERESTARTSYS)
+ dev_err(priv->dev,
+ "%s: Er[0x%x]: Signal Interrupted. Current act-dev-ctx count: %d.",
+ dev_ctx->devname, err, dev_ctx->priv->active_devctx_count);
+ goto exit;
+ }
+
+ spin_lock_irqsave(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock, flags);
+ if (priv->cmd_receiver_clbk_hdl.dev_ctx != dev_ctx ||
+ !priv->cmd_receiver_clbk_hdl.rx_msg ||
+ !priv->cmd_receiver_clbk_hdl.rx_msg_sz) {
+ spin_unlock_irqrestore(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock, flags);
+ err = -ENODEV;
+ goto exit;
+ }
+ spin_unlock_irqrestore(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock, flags);
+
+ /* We may need to copy the output data to user before
+ * delivering the completion message.
+ */
+ err = se_dev_ctx_cpy_out_data(dev_ctx);
+ if (err < 0)
+ goto exit;
+
+ /* Copy data from the buffer */
+ print_hex_dump_debug("to user ", DUMP_PREFIX_OFFSET, 4, 4,
+ priv->cmd_receiver_clbk_hdl.rx_msg,
+ priv->cmd_receiver_clbk_hdl.rx_msg_sz,
+ false);
+
+ copy_len = min(size, priv->cmd_receiver_clbk_hdl.rx_msg_sz);
+
+ if (copy_to_user(buf, priv->cmd_receiver_clbk_hdl.rx_msg, copy_len))
+ err = -EFAULT;
+ else
+ err = copy_len;
+
+exit:
+ priv->cmd_receiver_clbk_hdl.rx_msg_sz = 0;
+ se_dev_ctx_shared_mem_cleanup(dev_ctx);
+
+ return err;
+}
+
+/* Open a character device. */
+static int se_if_fops_open(struct inode *nd, struct file *fp)
+{
+ struct miscdevice *miscdev = fp->private_data;
+ struct se_if_open_gate *gate;
+ struct se_if_device_ctx *misc_dev_ctx;
+ struct se_if_device_ctx *dev_ctx;
+ struct se_if_priv *priv;
+ int err = 0;
+
+ gate = container_of(miscdev, struct se_if_open_gate, miscdev);
+
+ if (!se_if_open_gate_get(gate))
+ return -ENODEV;
+
+ if (mutex_lock_interruptible(&gate->lock)) {
+ se_if_open_gate_put(gate);
+ return -EBUSY;
+ }
+
+ if (gate->dying || !gate->priv ||
+ !kref_get_unless_zero(&gate->priv->refcount)) {
+ err = -ENODEV;
+ goto out_unlock_gate;
+ }
+
+ priv = gate->priv;
+ mutex_unlock(&gate->lock);
+
+ misc_dev_ctx = priv->priv_dev_ctx;
+
+ if (mutex_lock_interruptible(&misc_dev_ctx->fops_lock)) {
+ err = -EBUSY;
+ goto out_put_priv;
+ }
+
+ if (misc_dev_ctx->cleanup_done) {
+ err = -ENODEV;
+ goto out_unlock_misc;
+ }
+
+ priv->dev_ctx_mono_count++;
+ err = init_device_context(priv, priv->dev_ctx_mono_count, &dev_ctx);
+ if (err) {
+ dev_err(priv->dev, "Failed[0x%x] to create dev-ctx.", err);
+ goto out_unlock_misc;
+ }
+
+ fp->private_data = dev_ctx;
+
+out_unlock_misc:
+ mutex_unlock(&misc_dev_ctx->fops_lock);
+out_put_priv:
+ kref_put(&priv->refcount, se_if_priv_release);
+ se_if_open_gate_put(gate);
+ return err;
+out_unlock_gate:
+ mutex_unlock(&gate->lock);
+ se_if_open_gate_put(gate);
+ return err;
+}
+
+/* Close a character device. */
+static int se_if_fops_close(struct inode *nd, struct file *fp)
+{
+ struct se_if_device_ctx *dev_ctx = fp->private_data;
+
+ dlink_n_cleanup_dev_ctx(dev_ctx, true);
+
+ return 0;
+}
+
+/* IOCTL entry point of a character device */
+static long se_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ struct se_if_device_ctx *dev_ctx = fp->private_data;
+ struct se_if_priv *priv;
+ void __user *uarg = (void __user *)arg;
+ long err;
+
+ /* Prevent race during change of device context */
+ scoped_cond_guard(mutex_intr, return -EBUSY, &dev_ctx->fops_lock) {
+ if (dev_ctx->cleanup_done)
+ return -ENODEV;
+
+ priv = dev_ctx->priv;
+
+ switch (cmd) {
+ case SE_IOCTL_ENABLE_CMD_RCV: {
+ err = set_dev_ctx_as_command_receiver(dev_ctx);
+ if (err)
+ dev_err(priv->dev, "Failed to register %s as CMD-Receiver: %ld\n",
+ dev_ctx->devname, err);
+ break;
+ }
+ case SE_IOCTL_GET_MU_INFO:
+ err = se_ioctl_get_mu_info(dev_ctx, uarg);
+ break;
+ case SE_IOCTL_SETUP_IOBUF:
+ err = se_ioctl_setup_iobuf_handler(dev_ctx, uarg);
+ break;
+ case SE_IOCTL_GET_SOC_INFO:
+ err = se_ioctl_get_se_soc_info_handler(dev_ctx, uarg);
+ break;
+ case SE_IOCTL_CMD_SEND_RCV_RSP:
+ err = se_ioctl_cmd_snd_rcv_rsp_handler(dev_ctx, uarg);
+ break;
+ default:
+ err = -ENOTTY;
+ dev_dbg(priv->dev, "%s: IOCTL %.8x not supported.",
+ dev_ctx->devname, cmd);
+ }
+ }
+
+ return err;
+}
+
+/* Char driver setup */
+static const struct file_operations se_if_fops = {
+ .open = se_if_fops_open,
+ .owner = THIS_MODULE,
+ .release = se_if_fops_close,
+ .unlocked_ioctl = se_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .read = se_if_fops_read,
+ .write = se_if_fops_write,
+};
+
/* interface for managed res to free a mailbox channel */
static void if_mbox_free_channel(void *mbox_chan)
{
@@ -257,6 +1334,7 @@ static int se_if_request_channel(struct device *dev, struct mbox_chan **chan,
static void se_if_probe_cleanup(void *plat_dev)
{
struct platform_device *pdev = plat_dev;
+ struct se_if_device_ctx *dev_ctx;
struct device *dev = &pdev->dev;
struct se_if_priv *priv;
@@ -264,6 +1342,61 @@ static void se_if_probe_cleanup(void *plat_dev)
if (!priv)
return;
+ /*
+ * Mark the private device context as cleanup_done first.
+ * This prevents new device contexts from being created in open().
+ */
+ if (priv->priv_dev_ctx) {
+ scoped_guard(mutex, &priv->modify_lock)
+ priv->priv_dev_ctx->cleanup_done = true;
+
+ if (priv->open_gate) {
+ scoped_guard(mutex, &priv->open_gate->lock) {
+ priv->open_gate->dying = true;
+ priv->open_gate->priv = NULL;
+ }
+ }
+
+ if (priv->priv_dev_ctx->miscdev)
+ misc_deregister(priv->priv_dev_ctx->miscdev);
+ }
+
+ while (true) {
+ dev_ctx = NULL;
+
+ scoped_guard(mutex, &priv->modify_lock) {
+ if (list_empty(&priv->dev_ctx_list))
+ goto out_done;
+
+ dev_ctx = list_first_entry(&priv->dev_ctx_list,
+ struct se_if_device_ctx, link);
+
+ /* pin this context so close() cannot free it under us */
+ kref_get(&dev_ctx->refcount);
+ dlink_dev_ctx(dev_ctx);
+ }
+
+ /*
+ * Local cleanup outside the global lock avoids ABBA deadlock
+ * with paths that already take dev_ctx->fops_lock first.
+ */
+ cleanup_dev_ctx(dev_ctx, false);
+ kref_put(&dev_ctx->refcount, se_if_dev_ctx_release);
+ }
+out_done:
+
+ cancel_work_sync(&priv->fw_busy_work);
+ /*
+ * A timed-out synchronous command may have retained a dev_ctx through
+ * priv->fw_busy_dev_ctx even after the fd was closed and the context was
+ * removed from dev_ctx_list. If no late response arrived, release that
+ * retained context during driver teardown.
+ *
+ * se_clear_fw_busy() is idempotent and internally checks
+ * priv->fw_busy_dev_ctx under fw_busy_lock.
+ */
+ se_clear_fw_busy(priv);
+
/*
* In se_if_request_channel(), passed the clean-up functional
* pointer reference as action to devm_add_action_or_reset().
@@ -284,12 +1417,16 @@ static void se_if_probe_cleanup(void *plat_dev)
dev_set_drvdata(dev, NULL);
- if (priv->priv_dev_ctx) {
- kfree(priv->priv_dev_ctx->devname);
- kfree(priv->priv_dev_ctx);
- priv->priv_dev_ctx = NULL;
- }
- kfree(priv);
+ /* Drop the initial reference - priv will be freed when last fd closes */
+ kref_put(&priv->refcount, se_if_priv_release);
+}
+
+static void se_fw_busy_work(struct work_struct *work)
+{
+ struct se_if_priv *priv =
+ container_of(work, struct se_if_priv, fw_busy_work);
+
+ se_clear_fw_busy(priv);
}
static int se_if_probe(struct platform_device *pdev)
@@ -312,15 +1449,22 @@ static int se_if_probe(struct platform_device *pdev)
return -ENOMEM;
priv->dev = dev;
+ kref_init(&priv->refcount);
priv->if_defs = &if_node->if_defs;
dev_set_drvdata(dev, priv);
mutex_init(&priv->se_if_cmd_lock);
+ mutex_init(&priv->modify_lock);
spin_lock_init(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock);
spin_lock_init(&priv->waiting_rsp_clbk_hdl.clbk_rx_lock);
atomic_set(&priv->fw_busy, 0);
+ spin_lock_init(&priv->fw_busy_lock);
+ priv->fw_busy_dev_ctx = NULL;
+ INIT_WORK(&priv->fw_busy_work, se_fw_busy_work);
+
init_completion(&priv->waiting_rsp_clbk_hdl.done);
init_completion(&priv->cmd_receiver_clbk_hdl.done);
+ INIT_LIST_HEAD(&priv->dev_ctx_list);
ret = devm_add_action_or_reset(dev, se_if_probe_cleanup, pdev);
if (ret)
@@ -355,7 +1499,7 @@ static int se_if_probe(struct platform_device *pdev)
"Failed to init reserved memory region.");
}
- ret = init_misc_device_context(priv, 0, &priv->priv_dev_ctx);
+ ret = init_misc_device_context(priv, 0, &priv->priv_dev_ctx, &se_if_fops);
if (ret)
return dev_err_probe(dev, ret,
"Failed[0x%x] to create device contexts.",
diff --git a/drivers/firmware/imx/se_ctrl.h b/drivers/firmware/imx/se_ctrl.h
index 355d38684b1b..abfd77eecb72 100644
--- a/drivers/firmware/imx/se_ctrl.h
+++ b/drivers/firmware/imx/se_ctrl.h
@@ -8,17 +8,28 @@
#include <linux/bitfield.h>
#include <linux/miscdevice.h>
-#include <linux/semaphore.h>
#include <linux/mailbox_client.h>
+#include <linux/semaphore.h>
+#include <linux/workqueue.h>
#define MAX_FW_LOAD_RETRIES 50
#define SE_MSG_WORD_SZ 0x4
#define RES_STATUS(x) FIELD_GET(0x000000ff, x)
+#define MAX_DATA_SIZE_PER_USER (65 * 1024)
#define MAX_NVM_MSG_LEN (256)
#define MESSAGING_VERSION_6 0x6
#define MESSAGING_VERSION_7 0x7
+struct se_if_open_gate {
+ struct miscdevice miscdev;
+ struct se_if_priv *priv;
+ /* to lock to update the structure */
+ struct mutex lock;
+ struct kref refcount;
+ bool dying;
+};
+
struct se_clbk_handle {
struct se_if_device_ctx *dev_ctx;
struct completion done;
@@ -45,10 +56,43 @@ struct se_imem_buf {
u32 state;
};
+struct se_buf_desc {
+ u8 *shared_buf_ptr;
+ void __user *usr_buf_ptr;
+ u32 size;
+ struct list_head link;
+};
+
+struct se_shared_mem {
+ dma_addr_t dma_addr;
+ u32 size;
+ u32 pos;
+ u8 *ptr;
+};
+
+struct se_shared_mem_mgmt_info {
+ struct list_head pending_in;
+ struct list_head pending_out;
+
+ struct se_shared_mem non_secure_mem;
+};
+
/* Private struct for each char device instance. */
struct se_if_device_ctx {
struct se_if_priv *priv;
+ struct miscdevice *miscdev;
const char *devname;
+ bool cleanup_done;
+ unsigned long rcv_msg_timeout_jiffies;
+
+ /* process one file operation at a time. */
+ struct mutex fops_lock;
+
+ struct se_shared_mem_mgmt_info se_shared_mem_mgmt;
+ struct list_head link;
+
+ /* Add reference counting */
+ struct kref refcount;
};
/* Header of the messages exchange with the EdgeLock Enclave */
@@ -112,9 +156,30 @@ struct se_if_priv {
struct se_fw_load_info load_fw;
atomic_t fw_busy;
+ /*
+ * Serialise the fw_busy_dev_ctx and fw_busy state updates between the
+ * timeout path, late-response callback/work, and teardown.
+ */
+ spinlock_t fw_busy_lock;
+ struct se_if_device_ctx *fw_busy_dev_ctx;
+ struct work_struct fw_busy_work;
struct se_if_device_ctx *priv_dev_ctx;
+ struct list_head dev_ctx_list;
+
+ /* prevent modifying priv member variable in parallel. */
+ struct mutex modify_lock;
+ u32 active_devctx_count;
+ u32 dev_ctx_mono_count;
+
+ /* Add reference counting */
+ struct kref refcount;
+
+ /* stable gate used by .open() */
+ struct se_if_open_gate *open_gate;
};
char *get_se_if_name(u8 se_if_id);
+void unset_dev_ctx_as_command_receiver(struct se_if_device_ctx *dev_ctx);
+int set_dev_ctx_as_command_receiver(struct se_if_device_ctx *dev_ctx);
#endif
diff --git a/include/uapi/linux/se_ioctl.h b/include/uapi/linux/se_ioctl.h
new file mode 100644
index 000000000000..ea14cec05020
--- /dev/null
+++ b/include/uapi/linux/se_ioctl.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause*/
+/*
+ * Copyright 2025 NXP
+ */
+
+#ifndef SE_IOCTL_H
+#define SE_IOCTL_H
+
+#include <linux/types.h>
+
+#define SE_TYPE_STR_DBG "dbg"
+#define SE_TYPE_STR_HSM "hsm"
+#define SE_TYPE_ID_UNKWN 0x0
+#define SE_TYPE_ID_DBG 0x1
+#define SE_TYPE_ID_HSM 0x2
+/* IOCTL definitions. */
+
+struct se_ioctl_setup_iobuf {
+ __u64 user_buf;
+ __u32 length;
+ __u32 flags;
+ __u64 ele_addr;
+};
+
+struct se_ioctl_shared_mem_cfg {
+ __u32 base_offset;
+ __u32 size;
+};
+
+struct se_ioctl_get_if_info {
+ __u8 se_if_id;
+ __u8 interrupt_idx;
+ __u8 tz;
+ __u8 did;
+ __u8 cmd_tag;
+ __u8 rsp_tag;
+ __u8 success_tag;
+ __u8 base_api_ver;
+ __u8 fw_api_ver;
+};
+
+struct se_ioctl_cmd_snd_rcv_rsp_info {
+ __u64 tx_buf;
+ __u32 tx_buf_sz;
+ __u64 rx_buf;
+ __u32 rx_buf_sz;
+};
+
+struct se_ioctl_get_soc_info {
+ __u16 soc_id;
+ __u16 soc_rev;
+};
+
+/* IO Buffer Flags */
+#define SE_IO_BUF_FLAGS_IS_OUTPUT (0x00u)
+#define SE_IO_BUF_FLAGS_IS_INPUT (0x01u)
+#define SE_IO_BUF_FLAGS_USE_SEC_MEM (0x02u)
+#define SE_IO_BUF_FLAGS_USE_SHORT_ADDR (0x04u)
+#define SE_IO_BUF_FLAGS_IS_IN_OUT (0x10u)
+
+/* IOCTLS */
+#define SE_IOCTL 0x0A /* like MISC_MAJOR. */
+
+/*
+ * ioctl to designated the current fd as logical-reciever.
+ * This is ioctl is send when the nvm-daemon, a slave to the
+ * firmware is started by the user.
+ */
+#define SE_IOCTL_ENABLE_CMD_RCV _IO(SE_IOCTL, 0x01)
+
+/*
+ * ioctl to get the buffer allocated from the memory, which is shared
+ * between kernel and FW.
+ * Post allocation, the kernel tagged the allocated memory with:
+ * Output
+ * Input
+ * Input-Output
+ * Short address
+ * Secure-memory
+ */
+#define SE_IOCTL_SETUP_IOBUF _IOWR(SE_IOCTL, 0x03, struct se_ioctl_setup_iobuf)
+
+/*
+ * ioctl to get the mu information, that is used to exchange message
+ * with FW, from user-spaced.
+ */
+#define SE_IOCTL_GET_MU_INFO _IOR(SE_IOCTL, 0x04, struct se_ioctl_get_if_info)
+/*
+ * ioctl to get SoC Info from user-space.
+ */
+#define SE_IOCTL_GET_SOC_INFO _IOR(SE_IOCTL, 0x06, struct se_ioctl_get_soc_info)
+
+/*
+ * ioctl to send command and receive response from user-space.
+ */
+#define SE_IOCTL_CMD_SEND_RCV_RSP _IOWR(SE_IOCTL, 0x07, struct se_ioctl_cmd_snd_rcv_rsp_info)
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
` (4 preceding siblings ...)
2026-07-15 18:09 ` [PATCH v27 5/7] firmware: drivers: imx: adds miscdev pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
2026-07-15 17:31 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 7/7] arm64: dts: imx8ulp-evk: add reserved memory property pankaj.gupta
6 siblings, 1 reply; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel
From: Pankaj Gupta <pankaj.gupta@nxp.com>
Add support for NXP secure enclave called EdgeLock Enclave firmware (se-fw)
for imx8ulp-evk.
Add label sram0 for sram@2201f000 and add secure-enclave node
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
arch/arm64/boot/dts/freescale/imx8ulp.dtsi | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/freescale/imx8ulp.dtsi b/arch/arm64/boot/dts/freescale/imx8ulp.dtsi
index c6d1bb9edf38..182f1444b643 100644
--- a/arch/arm64/boot/dts/freescale/imx8ulp.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8ulp.dtsi
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
- * Copyright 2021 NXP
+ * Copyright 2021, 2025 NXP
*/
#include <dt-bindings/clock/imx8ulp-clock.h>
@@ -153,7 +153,7 @@ sosc: clock-sosc {
#clock-cells = <0>;
};
- sram@2201f000 {
+ sram0: sram@2201f000 {
compatible = "mmio-sram";
reg = <0x0 0x2201f000 0x0 0x1000>;
@@ -185,6 +185,13 @@ scmi_sensor: protocol@15 {
#thermal-sensor-cells = <1>;
};
};
+
+ hsm0: secure-enclave {
+ compatible = "fsl,imx8ulp-se-ele-hsm";
+ mbox-names = "tx", "rx";
+ mboxes = <&s4muap 0 0>, <&s4muap 1 0>;
+ sram = <&sram0>;
+ };
};
cm33: remoteproc-cm33 {
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v27 7/7] arm64: dts: imx8ulp-evk: add reserved memory property
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
` (5 preceding siblings ...)
2026-07-15 18:09 ` [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node pankaj.gupta
@ 2026-07-15 18:09 ` pankaj.gupta
6 siblings, 0 replies; 14+ messages in thread
From: pankaj.gupta @ 2026-07-15 18:09 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Pankaj Gupta
Cc: linux-doc, linux-kernel, devicetree, imx, linux-arm-kernel
From: Pankaj Gupta <pankaj.gupta@nxp.com>
Reserve 1MB of DDR memory region due to EdgeLock Enclave's hardware
limitation restricting access to DDR addresses from 0x80000000
to 0xafffffff.
Signed-off-by: Pankaj Gupta <pankaj.gupta@nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
arch/arm64/boot/dts/freescale/imx8ulp-evk.dts | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts b/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts
index 5dea66c1e7aa..16399d921e04 100644
--- a/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx8ulp-evk.dts
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
- * Copyright 2021 NXP
+ * Copyright 2021, 2025 NXP
*/
/dts-v1/;
@@ -37,6 +37,12 @@ linux,cma {
linux,cma-default;
};
+ ele_reserved: memory@90000000 {
+ compatible = "shared-dma-pool";
+ reg = <0 0x90000000 0 0x100000>;
+ no-map;
+ };
+
m33_reserved: noncacheable-section@a8600000 {
reg = <0 0xa8600000 0 0x1000000>;
no-map;
@@ -259,6 +265,10 @@ &usdhc0 {
status = "okay";
};
+&hsm0 {
+ memory-region = <&ele_reserved>;
+};
+
&fec {
pinctrl-names = "default", "sleep";
pinctrl-0 = <&pinctrl_enet>;
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-07-15 17:36 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-15 18:09 [PATCH v27 0/7] firmware: imx: driver for NXP secure-enclave pankaj.gupta
2026-07-15 18:09 ` [PATCH v27 1/7] Documentation/firmware: add imx/se to other_interfaces pankaj.gupta
2026-07-15 17:25 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc pankaj.gupta
2026-07-15 17:27 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 3/7] firmware: imx: add driver for NXP EdgeLock Enclave pankaj.gupta
2026-07-15 17:36 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 4/7] firmware: imx: device context dedicated to priv pankaj.gupta
2026-07-15 17:34 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 5/7] firmware: drivers: imx: adds miscdev pankaj.gupta
2026-07-15 17:36 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 6/7] arm64: dts: imx8ulp: add secure enclave node pankaj.gupta
2026-07-15 17:31 ` sashiko-bot
2026-07-15 18:09 ` [PATCH v27 7/7] arm64: dts: imx8ulp-evk: add reserved memory property pankaj.gupta
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.