Devicetree
 help / color / mirror / Atom feed
* [PATCH v5 0/4] clocksource/timer-econet-en751221: Support irq number per timer
@ 2026-05-16 23:39 Caleb James DeLisle
  2026-05-16 23:39 ` [PATCH v5 1/4] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Caleb James DeLisle @ 2026-05-16 23:39 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, Caleb James DeLisle

Changes from v4:
* Fixed incorrect compatible in DT schema
* Bracing in out_irq_free if statement
* I did not move cpuhp_setup_state past the point of no return
* v4: https://lore.kernel.org/linux-mips/20260516215720.4160831-1-cjd@cjdns.fr

Changes from v3:
* off-by-one in out_irq_free
* Gracefully fail if too many address resources are provided in the DT
* Allow fewer actual timers than num_possible_cpus, as long as they
never start
* v3: https://lore.kernel.org/linux-mips/20260516182648.3987792-1-cjd@cjdns.fr

v3 note:

I decided to take this patchset in a slightly different direction from
v2. There is a certain amount of initialization that once it has run,
it cannot be reverted in case of error. The original driver init was a
mixture of revertible and non-revertible code which made it too easy
for code which is designed with best practices in mind to in fact
introduce a bug. Therefore I start by making the init process more
legible but without any business logic changes, then fix a race
condition by keeping IRQ disabled until after cevt is setup, then
finally add non-percpu IRQ support.

* v2: https://lore.kernel.org/linux-mips/20260514000601.3430262-1-cjd@cjdns.fr

Changes from v1:
* Split changes over 3 refactoring patches + main patch
* Remove driver discussion from dt commit message
* v1: https://lore.kernel.org/linux-mips/20260416175101.958073-1-cjd@cjdns.fr/

Caleb James DeLisle (4):
  dt-bindings: timer: econet: Update EN751627 for multi-IRQ
  clocksource/timer-econet-en751221: Init teardown on error if possible
  clocksource/timer-econet-en751221: Disable IRQ until cevt registered
  clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ

 .../bindings/timer/econet,en751221-timer.yaml |  31 ++--
 drivers/clocksource/timer-econet-en751221.c   | 169 +++++++++++++-----
 2 files changed, 148 insertions(+), 52 deletions(-)


base-commit: ff1c0c5d07028a84837950b619d30da623f8ddb2
-- 
2.39.5


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v5 1/4] dt-bindings: timer: econet: Update EN751627 for multi-IRQ
  2026-05-16 23:39 [PATCH v5 0/4] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
@ 2026-05-16 23:39 ` Caleb James DeLisle
  2026-05-16 23:39 ` [PATCH v5 2/4] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 8+ messages in thread
From: Caleb James DeLisle @ 2026-05-16 23:39 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, Caleb James DeLisle, Conor Dooley

This hardware is found in the EN751221 SoC family as well as the
EN751627. The former uses a percpu IRQ for all timers while the
latter uses an individual IRQ number per timer.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
 .../bindings/timer/econet,en751221-timer.yaml | 31 ++++++++++++-------
 1 file changed, 20 insertions(+), 11 deletions(-)

diff --git a/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
index c1e7c2b6afde..ac25785ecfdf 100644
--- a/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
+++ b/Documentation/devicetree/bindings/timer/econet,en751221-timer.yaml
@@ -12,24 +12,23 @@ maintainers:
 description:
   The EcoNet High Precision Timer (HPT) is a timer peripheral found in various
   EcoNet SoCs, including the EN751221 and EN751627 families. It provides per-VPE
-  count/compare registers and a per-CPU control register, with a single interrupt
-  line using a percpu-devid interrupt mechanism.
+  count/compare registers and a per-CPU control register. On EN751221 it uses a
+  single interrupt line using a percpu-devid interrupt mechanism, and on
+  EN751627 it uses an interrupt per VPE.
 
 properties:
   compatible:
-    oneOf:
-      - const: econet,en751221-timer
-      - items:
-          - const: econet,en751627-timer
-          - const: econet,en751221-timer
+    enum:
+      - econet,en751221-timer
+      - econet,en751627-timer
 
   reg:
     minItems: 1
     maxItems: 2
 
   interrupts:
-    maxItems: 1
-    description: A percpu-devid timer interrupt shared across CPUs.
+    minItems: 1
+    maxItems: 4
 
   clocks:
     maxItems: 1
@@ -52,21 +51,31 @@ allOf:
           items:
             - description: VPE timers 0 and 1
             - description: VPE timers 2 and 3
+        interrupts:
+          description: An interrupt for each timer (one per VPE)
+          minItems: 4
     else:
       properties:
         reg:
           items:
             - description: VPE timers 0 and 1
+        interrupts:
+          description: A percpu-devid timer interrupt shared across timers
+          maxItems: 1
 
 additionalProperties: false
 
 examples:
   - |
+    #include <dt-bindings/interrupt-controller/mips-gic.h>
     timer@1fbf0400 {
-        compatible = "econet,en751627-timer", "econet,en751221-timer";
+        compatible = "econet,en751627-timer";
         reg = <0x1fbf0400 0x100>, <0x1fbe0000 0x100>;
         interrupt-parent = <&intc>;
-        interrupts = <30>;
+        interrupts = <GIC_SHARED 30 IRQ_TYPE_LEVEL_HIGH>,
+                     <GIC_SHARED 29 IRQ_TYPE_LEVEL_HIGH>,
+                     <GIC_SHARED 37 IRQ_TYPE_LEVEL_HIGH>,
+                     <GIC_SHARED 36 IRQ_TYPE_LEVEL_HIGH>;
         clocks = <&hpt_clock>;
     };
   - |
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v5 2/4] clocksource/timer-econet-en751221: Init teardown on error if possible
  2026-05-16 23:39 [PATCH v5 0/4] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
  2026-05-16 23:39 ` [PATCH v5 1/4] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
@ 2026-05-16 23:39 ` Caleb James DeLisle
  2026-05-17  0:20   ` sashiko-bot
  2026-05-16 23:39 ` [PATCH v5 3/4] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
  2026-05-16 23:39 ` [PATCH v5 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
  3 siblings, 1 reply; 8+ messages in thread
From: Caleb James DeLisle @ 2026-05-16 23:39 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, Caleb James DeLisle

As a clocksource, much of the initialization process is irreversible
and the impact of a failure to initialize is a failure to boot.
That said, good practice is to attempt a clean exit if probing fails,
and supporting this pattern will reduce the likelihood that future
contributions introduce a bug by trying to teardown after it is no
longer possible to do so.

Convert the init process into two clearly delineated phases, one
which is reverted in case of error, and the other which can't be.
Move all IRQ and address resource mapping before that point, and add
teardown logic in case of error before the point of no return.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/clocksource/timer-econet-en751221.c | 82 ++++++++++++---------
 1 file changed, 48 insertions(+), 34 deletions(-)

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4008076b1a21..155471f68e6f 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -24,6 +24,7 @@
 
 static struct {
 	void __iomem	*membase[ECONET_NUM_BLOCKS];
+	int		irq;
 	u32		freq_hz;
 } econet_timer __ro_after_init;
 
@@ -126,22 +127,9 @@ static void __init cevt_dev_init(uint cpu)
 	iowrite32(U32_MAX, reg_compare(cpu));
 }
 
-static int __init cevt_init(struct device_node *np)
+static void __init cevt_init(struct device_node *np)
 {
-	int i, irq, ret;
-
-	irq = irq_of_parse_and_map(np, 0);
-	if (irq <= 0) {
-		pr_err("%pOFn: irq_of_parse_and_map failed", np);
-		return -EINVAL;
-	}
-
-	ret = request_percpu_irq(irq, cevt_interrupt, np->name, &econet_timer_pcpu);
-
-	if (ret < 0) {
-		pr_err("%pOFn: IRQ %d setup failed (%d)\n", np, irq, ret);
-		goto err_unmap_irq;
-	}
+	int i;
 
 	for_each_possible_cpu(i) {
 		struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
@@ -151,21 +139,12 @@ static int __init cevt_init(struct device_node *np)
 					  CLOCK_EVT_FEAT_C3STOP |
 					  CLOCK_EVT_FEAT_PERCPU;
 		cd->set_next_event	= cevt_set_next_event;
-		cd->irq			= irq;
+		cd->irq			= econet_timer.irq;
 		cd->cpumask		= cpumask_of(i);
 		cd->name		= np->name;
 
 		cevt_dev_init(i);
 	}
-
-	cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
-			  "clockevents/econet/timer:starting",
-			  cevt_init_cpu, NULL);
-	return 0;
-
-err_unmap_irq:
-	irq_dispose_mapping(irq);
-	return ret;
 }
 
 static int __init timer_init(struct device_node *np)
@@ -186,22 +165,45 @@ static int __init timer_init(struct device_node *np)
 		econet_timer.membase[i] = of_iomap(np, i);
 		if (!econet_timer.membase[i]) {
 			pr_err("%pOFn: failed to map register [%d]\n", np, i);
-			return -ENXIO;
+			ret = -ENXIO;
+			goto out_membase;
 		}
 	}
 
+	econet_timer.irq = irq_of_parse_and_map(np, 0);
+	if (econet_timer.irq <= 0) {
+		pr_err("%pOFn: irq_of_parse_and_map failed\n", np);
+		ret = -EINVAL;
+		goto out_membase;
+	}
+
+	ret = request_percpu_irq(econet_timer.irq, cevt_interrupt, np->name,
+				 &econet_timer_pcpu);
+
+	if (ret < 0) {
+		pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
+		       econet_timer.irq, ret);
+		goto out_irq_mapping;
+	}
+
+	cevt_init(np);
+
+	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+				"clockevents/econet/timer:starting",
+				cevt_init_cpu, NULL);
+	if (ret < 0) {
+		pr_err("%pOFn: cpuhp setup failed (%d)\n", np, ret);
+		goto out_irq_free;
+	}
+
+	/* Point of no return, do not attempt to tear down after this. */
+
 	/* For clocksource purposes always read clock zero, whatever the CPU */
 	ret = clocksource_mmio_init(reg_count(0), np->name,
 				    econet_timer.freq_hz, 301, ECONET_BITS,
 				    clocksource_mmio_readl_up);
-	if (ret) {
-		pr_err("%pOFn: clocksource_mmio_init failed: %d", np, ret);
-		return ret;
-	}
-
-	ret = cevt_init(np);
-	if (ret < 0)
-		return ret;
+	if (ret)
+		pr_err("%pOFn: clocksource_mmio_init failed: %d\n", np, ret);
 
 	sched_clock_register(sched_clock_read, ECONET_BITS,
 			     econet_timer.freq_hz);
@@ -211,6 +213,18 @@ static int __init timer_init(struct device_node *np)
 		(econet_timer.freq_hz / 1000) % 1000);
 
 	return 0;
+
+out_irq_free:
+	free_percpu_irq(econet_timer.irq, &econet_timer_pcpu);
+out_irq_mapping:
+	irq_dispose_mapping(econet_timer.irq);
+out_membase:
+	for (int i = 0; i < ARRAY_SIZE(econet_timer.membase); i++) {
+		if (econet_timer.membase[i])
+			iounmap(econet_timer.membase[i]);
+	}
+
+	return ret;
 }
 
 TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v5 3/4] clocksource/timer-econet-en751221: Disable IRQ until cevt registered
  2026-05-16 23:39 [PATCH v5 0/4] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
  2026-05-16 23:39 ` [PATCH v5 1/4] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
  2026-05-16 23:39 ` [PATCH v5 2/4] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
@ 2026-05-16 23:39 ` Caleb James DeLisle
  2026-05-17  0:55   ` sashiko-bot
  2026-05-16 23:39 ` [PATCH v5 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
  3 siblings, 1 reply; 8+ messages in thread
From: Caleb James DeLisle @ 2026-05-16 23:39 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, Caleb James DeLisle

Eliminate a race condition where cevt_interrupt may trigger before
clockevents_config_and_register has been called, and dev->event_handler
is at that point NULL.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/clocksource/timer-econet-en751221.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 155471f68e6f..ed750e39cc4f 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -104,12 +104,11 @@ static int cevt_init_cpu(uint cpu)
 	reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
 	iowrite32(reg, reg_ctl(cpu));
 
-	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
-
-	/* Do this last because it synchronously configures the timer */
 	clockevents_config_and_register(cd, econet_timer.freq_hz,
 					ECONET_MIN_DELTA, ECONET_MAX_DELTA);
 
+	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+
 	return 0;
 }
 
@@ -177,6 +176,8 @@ static int __init timer_init(struct device_node *np)
 		goto out_membase;
 	}
 
+	irq_set_status_flags(econet_timer.irq, IRQ_NOAUTOEN);
+
 	ret = request_percpu_irq(econet_timer.irq, cevt_interrupt, np->name,
 				 &econet_timer_pcpu);
 
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH v5 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ
  2026-05-16 23:39 [PATCH v5 0/4] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
                   ` (2 preceding siblings ...)
  2026-05-16 23:39 ` [PATCH v5 3/4] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
@ 2026-05-16 23:39 ` Caleb James DeLisle
  2026-05-17  1:28   ` sashiko-bot
  3 siblings, 1 reply; 8+ messages in thread
From: Caleb James DeLisle @ 2026-05-16 23:39 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, Caleb James DeLisle

EN751627 is based on the 1004Kc which uses a different interrupt number
for each CPU timer. Support both this and the EN751221 which uses a
single percpu interrupt.

Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/clocksource/timer-econet-en751221.c | 120 ++++++++++++++++----
 1 file changed, 96 insertions(+), 24 deletions(-)

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index ed750e39cc4f..529f81118eef 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -21,10 +21,12 @@
 #define ECONET_MAX_DELTA		GENMASK(ECONET_BITS - 2, 0)
 /* 34Kc hardware has 1 block and 1004Kc has 2. */
 #define ECONET_NUM_BLOCKS		DIV_ROUND_UP(NR_CPUS, 2)
+#define ECONET_NUM_IRQS			NR_CPUS
 
 static struct {
 	void __iomem	*membase[ECONET_NUM_BLOCKS];
-	int		irq;
+	int		irqs[ECONET_NUM_IRQS];
+	bool		is_percpu;
 	u32		freq_hz;
 } econet_timer __ro_after_init;
 
@@ -99,6 +101,25 @@ static int cevt_init_cpu(uint cpu)
 	struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
 	u32 reg;
 
+	if (!reg_ctl(cpu)) {
+		pr_err("%s: missing address resource for CPU %d\n", cd->name,
+		       cpu);
+		return -EINVAL;
+	}
+	if (cd->irq <= 0) {
+		pr_err("%s: missing IRQ for CPU %d\n", cd->name, cpu);
+		return -EINVAL;
+	}
+	if (!econet_timer.is_percpu) {
+		int ret = irq_force_affinity(cd->irq, cpumask_of(cpu));
+
+		if (ret) {
+			pr_err("%s: failed to set IRQ affinity to CPU %d: %pe\n",
+			       cd->name, cpu, ERR_PTR(ret));
+			return ret;
+		}
+	}
+
 	pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
 
 	reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
@@ -107,7 +128,10 @@ static int cevt_init_cpu(uint cpu)
 	clockevents_config_and_register(cd, econet_timer.freq_hz,
 					ECONET_MIN_DELTA, ECONET_MAX_DELTA);
 
-	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+	if (econet_timer.is_percpu)
+		enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
+	else
+		enable_irq(cd->irq);
 
 	return 0;
 }
@@ -138,19 +162,48 @@ static void __init cevt_init(struct device_node *np)
 					  CLOCK_EVT_FEAT_C3STOP |
 					  CLOCK_EVT_FEAT_PERCPU;
 		cd->set_next_event	= cevt_set_next_event;
-		cd->irq			= econet_timer.irq;
+
+		if (econet_timer.is_percpu)
+			cd->irq = econet_timer.irqs[0];
+		else
+			cd->irq = econet_timer.irqs[i];
+
 		cd->cpumask		= cpumask_of(i);
 		cd->name		= np->name;
 
-		cevt_dev_init(i);
+		/*
+		 * Tolerate CPUs that could exist but don't.
+		 * Fail in cevt_init_cpu when they try to start.
+		 */
+		if (reg_ctl(i))
+			cevt_dev_init(i);
 	}
 }
 
 static int __init timer_init(struct device_node *np)
 {
-	int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
+	int num_blocks = of_address_count(np);
+	int num_irqs = of_irq_count(np);
 	struct clk *clk;
-	int ret;
+	int ret, i;
+
+	econet_timer.is_percpu = of_device_is_compatible(np, "econet,en751221-timer");
+
+	if (econet_timer.is_percpu && num_irqs != 1) {
+		pr_err("%pOFn: EN751221 clock must have 1 IRQ not %d\n", np,
+		       num_irqs);
+		return -EINVAL;
+	}
+	if (num_irqs > ARRAY_SIZE(econet_timer.irqs)) {
+		pr_err("%pOFn: Too many IRQs max %d got %d\n", np,
+		       ARRAY_SIZE(econet_timer.irqs), num_irqs);
+		return -EINVAL;
+	}
+	if (num_blocks > ARRAY_SIZE(econet_timer.membase)) {
+		pr_err("%pOFn: Too many regs: max %d got %d\n", np,
+		       ARRAY_SIZE(econet_timer.membase), num_blocks);
+		return -EINVAL;
+	}
 
 	clk = of_clk_get(np, 0);
 	if (IS_ERR(clk)) {
@@ -160,7 +213,7 @@ static int __init timer_init(struct device_node *np)
 
 	econet_timer.freq_hz = clk_get_rate(clk);
 
-	for (int i = 0; i < num_blocks; i++) {
+	for (i = 0; i < num_blocks; i++) {
 		econet_timer.membase[i] = of_iomap(np, i);
 		if (!econet_timer.membase[i]) {
 			pr_err("%pOFn: failed to map register [%d]\n", np, i);
@@ -169,22 +222,32 @@ static int __init timer_init(struct device_node *np)
 		}
 	}
 
-	econet_timer.irq = irq_of_parse_and_map(np, 0);
-	if (econet_timer.irq <= 0) {
-		pr_err("%pOFn: irq_of_parse_and_map failed\n", np);
-		ret = -EINVAL;
-		goto out_membase;
+	for (i = 0; i < num_irqs; i++) {
+		econet_timer.irqs[i] = irq_of_parse_and_map(np, i);
+		if (econet_timer.irqs[i] <= 0) {
+			pr_err("%pOFn: failed mapping irq %d\n", np, i);
+			ret = -EINVAL;
+			goto out_irq_mapping;
+		}
 	}
 
-	irq_set_status_flags(econet_timer.irq, IRQ_NOAUTOEN);
-
-	ret = request_percpu_irq(econet_timer.irq, cevt_interrupt, np->name,
-				 &econet_timer_pcpu);
-
-	if (ret < 0) {
-		pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
-		       econet_timer.irq, ret);
-		goto out_irq_mapping;
+	for (i = 0; i < num_irqs; i++) {
+		irq_set_status_flags(econet_timer.irqs[i], IRQ_NOAUTOEN);
+
+		if (econet_timer.is_percpu)
+			ret = request_percpu_irq(econet_timer.irqs[i],
+						 cevt_interrupt, np->name,
+						 &econet_timer_pcpu);
+		else
+			ret = request_irq(econet_timer.irqs[i], cevt_interrupt,
+					  IRQF_TIMER | IRQF_NOBALANCING,
+					  np->name, NULL);
+
+		if (ret < 0) {
+			pr_err("%pOFn: IRQ %d setup failed: %pe\n", np,
+			       i, ERR_PTR(ret));
+			goto out_irq_free;
+		}
 	}
 
 	cevt_init(np);
@@ -216,11 +279,19 @@ static int __init timer_init(struct device_node *np)
 	return 0;
 
 out_irq_free:
-	free_percpu_irq(econet_timer.irq, &econet_timer_pcpu);
+	while (--i >= 0) {
+		if (econet_timer.is_percpu)
+			free_percpu_irq(econet_timer.irqs[i], &econet_timer_pcpu);
+		else
+			free_irq(econet_timer.irqs[i], NULL);
+	}
 out_irq_mapping:
-	irq_dispose_mapping(econet_timer.irq);
+	for (i = 0; i < num_irqs; i++) {
+		if (econet_timer.irqs[i] > 0)
+			irq_dispose_mapping(econet_timer.irqs[i]);
+	}
 out_membase:
-	for (int i = 0; i < ARRAY_SIZE(econet_timer.membase); i++) {
+	for (i = 0; i < num_blocks; i++) {
 		if (econet_timer.membase[i])
 			iounmap(econet_timer.membase[i]);
 	}
@@ -229,3 +300,4 @@ static int __init timer_init(struct device_node *np)
 }
 
 TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
+TIMER_OF_DECLARE(econet_timer_en751627, "econet,en751627-timer", timer_init);
-- 
2.39.5


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH v5 2/4] clocksource/timer-econet-en751221: Init teardown on error if possible
  2026-05-16 23:39 ` [PATCH v5 2/4] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
@ 2026-05-17  0:20   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-05-17  0:20 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Failing cpuhp_setup_state causes I/O memory unmapping while clock event devices are still registered and active, leading to a fatal page fault.
- [High] Race condition during initialization leads to NULL pointer dereference.
- [High] Mapping memory blocks based on num_possible_cpus() leads to out-of-bounds accesses on systems with sparse CPU masks.
- [Medium] Error teardown path retains dangling pointers in the global econet_timer.membase array.
--

commit 120bc83e186bdd341b411bcc91406b20c22b4507
Author: Caleb James DeLisle <cjd@cjdns.fr>

clocksource/timer-econet-en751221: Init teardown on error if possible

This commit splits the initialization of the en751221 econet timer into two
phases to support a clean exit on error. It moves IRQ setup and memory
mapping before the point of no return. A teardown path is added to free
IRQs and unmap memory if initialization fails early.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index 4008076b1a210..155471f68e6f8 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c

[ ... ]

> @@ -186,22 +165,45 @@ static int __init timer_init(struct device_node *np)
>  		econet_timer.membase[i] = of_iomap(np, i);
>  		if (!econet_timer.membase[i]) {
>  			pr_err("%pOFn: failed to map register [%d]\n", np, i);
> -			return -ENXIO;
> +			ret = -ENXIO;
> +			goto out_membase;
>  		}
>  	}

Does calculating the number of memory blocks using num_possible_cpus() cause
issues on systems with sparse CPU masks?

If a system only has CPUs 0 and 4, num_possible_cpus() evaluates to 2, mapping
only one block. When hardware access paths use the absolute CPU index
(cpu >> 1), could reading econet_timer.membase[2] lead to a NULL pointer
dereference?

(Note: It is understood this issue was present before this patch).

> +	econet_timer.irq = irq_of_parse_and_map(np, 0);
> +	if (econet_timer.irq <= 0) {
> +		pr_err("%pOFn: irq_of_parse_and_map failed\n", np);
> +		ret = -EINVAL;
> +		goto out_membase;
> +	}
> +
> +	ret = request_percpu_irq(econet_timer.irq, cevt_interrupt, np->name,
> +				 &econet_timer_pcpu);
> +
> +	if (ret < 0) {
> +		pr_err("%pOFn: IRQ %d setup failed (%d)\n", np,
> +		       econet_timer.irq, ret);
> +		goto out_irq_mapping;
> +	}
> +
> +	cevt_init(np);

Is there a potential race condition here if an interrupt fires immediately
after request_percpu_irq() is called, before cevt_init() populates
cd->cpumask?

If the handler executes, could it result in a NULL pointer dereference
when reading dev->cpumask?

(Note: It is understood this might be fixed later in the patch series by
commit clocksource/timer-econet-en751221: Disable IRQ until cevt registered).

> +	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
> +				"clockevents/econet/timer:starting",
> +				cevt_init_cpu, NULL);
> +	if (ret < 0) {
> +		pr_err("%pOFn: cpuhp setup failed (%d)\n", np, ret);
> +		goto out_irq_free;
> +	}

If cpuhp_setup_state() fails on a subsequent CPU, could the already-registered
clock events on previous CPUs continue to be invoked by the timer core?

Since the teardown path unmaps the IO memory without unregistering these
clock event devices, would calling cd->set_next_event dereference unmapped
memory?

Should cpuhp_setup_state() be placed after the point of no return, or should
a teardown callback be provided to unregister the devices?

[ ... ]

> +out_irq_free:
> +	free_percpu_irq(econet_timer.irq, &econet_timer_pcpu);
> +out_irq_mapping:
> +	irq_dispose_mapping(econet_timer.irq);
> +out_membase:
> +	for (int i = 0; i < ARRAY_SIZE(econet_timer.membase); i++) {
> +		if (econet_timer.membase[i])
> +			iounmap(econet_timer.membase[i]);
> +	}
> +
> +	return ret;
>  }

When iounmap() is called in the out_membase path, should the pointers in
econet_timer.membase be set to NULL?

Since econet_timer is a global static structure, if the driver's init
function is invoked a second time (such as from a secondary matching device
tree node), could a subsequent failure cause iounmap() to be called on
already-unmapped pointers?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260516233943.49502-1-cjd@cjdns.fr?part=2

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v5 3/4] clocksource/timer-econet-en751221: Disable IRQ until cevt registered
  2026-05-16 23:39 ` [PATCH v5 3/4] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
@ 2026-05-17  0:55   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-05-17  0:55 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Uninitialized `dev->event_handler` causes a kernel panic if the clock event device is rejected by the timekeeping core.
- [High] The error path after `cpuhp_setup_state()` fails to roll back the hardware state and unregister the clockevent device, resulting in a Use-After-Free/Page fault.
- [Low] Redundant `IRQ_NOAUTOEN` flag setting on a per-CPU interrupt.
--

commit fa0659466614df142cddb9c13760b12727964dfe
Author: Caleb James DeLisle <cjd@cjdns.fr>

clocksource/timer-econet-en751221: Disable IRQ until cevt registered

This commit reorders interrupt enablement to occur after clockevent
registration to avoid a race condition with uninitialized event handlers,
and explicitly sets the no-auto-enable flag on the IRQ.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index 155471f68e6f8..ed750e39cc4fa 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c
> @@ -104,12 +104,11 @@ static int cevt_init_cpu(uint cpu)
>  	reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
>  	iowrite32(reg, reg_ctl(cpu));
>  
> -	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
> -
> -	/* Do this last because it synchronously configures the timer */
>  	clockevents_config_and_register(cd, econet_timer.freq_hz,
>  					ECONET_MIN_DELTA, ECONET_MAX_DELTA);
>  
> +	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
> +

The commit message notes this eliminates a race where cevt_interrupt triggers
before the event_handler is set. Does this reordering fully resolve the issue?

If the timekeeping core rejects the new clockevent device in
tick_check_new_device(), dev->event_handler will remain uninitialized (NULL).
Since the hardware timer is unconditionally started and the IRQ is enabled
here, could the timer eventually wrap around, assert the interrupt, and cause
a NULL pointer dereference in cevt_interrupt()?

Additionally, looking at the error path in timer_init() where cevt_init_cpu()
is registered as a hotplug callback:

timer_init() {
    ...
    ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
                            "clockevents/econet/timer:starting",
                            cevt_init_cpu, NULL);
    if (ret < 0) {
        pr_err("%pOFn: cpuhp setup failed (%d)\n", np, ret);
        goto out_irq_free;
    }
    ...
}

Because cpuhp_setup_state() is called with a NULL teardown callback, if the
overall hotplug state setup fails, there is no rollback for CPUs that already
successfully executed cevt_init_cpu().

If the out_irq_free error path then unmaps the MMIO base while the clockevent
devices remain registered and active in the core timer subsystem, will a later
access to the device (such as invoking cevt_set_next_event()) dereference the
unmapped memory and cause a fatal page fault?

>  	return 0;
>  }

[ ... ]

> @@ -177,6 +176,8 @@ static int __init timer_init(struct device_node *np)
>  		goto out_membase;
>  	}
>  
> +	irq_set_status_flags(econet_timer.irq, IRQ_NOAUTOEN);
> +

This isn't a bug, but is the manual setting of IRQ_NOAUTOEN necessary here?

Per-CPU interrupts requested via request_percpu_irq() mandate that the
interrupt descriptor has the IRQ_PER_CPU_DEVID flag set. The mechanism that
applies this flag, irq_set_percpu_devid_flags(), automatically sets the
IRQ_NOAUTOEN flag on the interrupt descriptor at the same time.

>  	ret = request_percpu_irq(econet_timer.irq, cevt_interrupt, np->name,
>  				 &econet_timer_pcpu);
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260516233943.49502-1-cjd@cjdns.fr?part=3

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v5 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ
  2026-05-16 23:39 ` [PATCH v5 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
@ 2026-05-17  1:28   ` sashiko-bot
  0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-05-17  1:28 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Missing validation for zero address blocks leads to a NULL pointer dereference on malformed device trees.
- [High] Missing CPU hotplug teardown callback causes unhandled IRQ storms and unbalanced IRQ enables.
- [High] Failure during CPU hotplug setup unmaps active timer memory, leading to a fatal page fault.
--

commit 050e8a34fec303d14b7b31dbc641fcb39833f19b
Author: Caleb James DeLisle <cjd@cjdns.fr>

clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ

This commit adds support for the EN751627 hardware. It allows the driver to
operate with a different interrupt number for each CPU timer, rather than
relying on a single percpu interrupt as used by the EN751221.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index ed750e39cc4fa..529f81118eef3 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c
> @@ -99,6 +101,25 @@ static int cevt_init_cpu(uint cpu)
>  	struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, cpu);
>  	u32 reg;
>  
> +	if (!reg_ctl(cpu)) {
> +		pr_err("%s: missing address resource for CPU %d\n", cd->name,
> +		       cpu);
> +		return -EINVAL;
> +	}
> +	if (cd->irq <= 0) {
> +		pr_err("%s: missing IRQ for CPU %d\n", cd->name, cpu);
> +		return -EINVAL;
> +	}
> +	if (!econet_timer.is_percpu) {
> +		int ret = irq_force_affinity(cd->irq, cpumask_of(cpu));
> +
> +		if (ret) {
> +			pr_err("%s: failed to set IRQ affinity to CPU %d: %pe\n",
> +			       cd->name, cpu, ERR_PTR(ret));
> +			return ret;
> +		}
> +	}
> +
>  	pr_debug("%s: Setting up clockevent for CPU %d\n", cd->name, cpu);
>  
>  	reg = ioread32(reg_ctl(cpu)) | ctl_bit_enabled(cpu);
> @@ -107,7 +128,10 @@ static int cevt_init_cpu(uint cpu)
>  	clockevents_config_and_register(cd, econet_timer.freq_hz,
>  					ECONET_MIN_DELTA, ECONET_MAX_DELTA);
>  
> -	enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
> +	if (econet_timer.is_percpu)
> +		enable_percpu_irq(cd->irq, IRQ_TYPE_NONE);
> +	else
> +		enable_irq(cd->irq);

If a CPU is taken offline, does the lack of a teardown callback for the CPU
hotplug state mean disable_irq() is never called?

If the CPU comes back online, cevt_init_cpu() will call enable_irq() again.
Since the internal depth counter of the IRQ is already 0, could this trigger
an unbalanced enable warning?

Additionally, if the CPU goes offline and the still-active standard IRQ is
migrated to another CPU, could this cause an unhandled IRQ storm? The timer
is still running and the interrupt handler might fail to clear the pending
interrupt on the new CPU.

[ ... ]
> @@ -138,19 +162,48 @@ static void __init cevt_init(struct device_node *np)
>  static int __init timer_init(struct device_node *np)
>  {
> -	int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
> +	int num_blocks = of_address_count(np);

If a malformed device tree lacks a reg property, num_blocks will be 0. The
code below checks if num_blocks is greater than ARRAY_SIZE but doesn't check
if it is 0.

Could this lead to a NULL pointer dereference later when clocksource_mmio_init()
is called?

Since the of_iomap() loop will be skipped, econet_timer.membase[0] would
remain NULL, and reg_count(0) would compute an IO address based on it.

> +	int num_irqs = of_irq_count(np);
>  	struct clk *clk;
> -	int ret;
> +	int ret, i;
[ ... ]
> @@ -216,11 +279,19 @@ static int __init timer_init(struct device_node *np)
>  	}
>  
>  	cevt_init(np);
>  
>  	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
>  				"clockevents/econet/timer:starting",
>  				cevt_init_cpu, NULL);
>  	if (ret < 0) {
>  		pr_err("%pOFn: cpuhp setup failed (%d)\n", np, ret);
>  		goto out_irq_free;
>  	}

If cevt_init_cpu() succeeds on the boot CPU but fails on a subsequent CPU
when irq_force_affinity() returns an error, cpuhp_setup_state() will return
an error and the driver will branch to out_irq_free and then out_membase.

Since the teardown callback is NULL, the clockevent device for the
successfully started CPU is never unregistered.

Could this lead to a fatal page fault when the clockevents core attempts
to access the timer registers after they have been unmapped by iounmap()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260516233943.49502-1-cjd@cjdns.fr?part=4

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-05-17  1:28 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-16 23:39 [PATCH v5 0/4] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
2026-05-16 23:39 ` [PATCH v5 1/4] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
2026-05-16 23:39 ` [PATCH v5 2/4] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
2026-05-17  0:20   ` sashiko-bot
2026-05-16 23:39 ` [PATCH v5 3/4] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
2026-05-17  0:55   ` sashiko-bot
2026-05-16 23:39 ` [PATCH v5 4/4] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
2026-05-17  1:28   ` sashiko-bot

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