All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer
@ 2026-07-31 12:02 Caleb James DeLisle
  2026-07-31 12:02 ` [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak Caleb James DeLisle
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Caleb James DeLisle @ 2026-07-31 12:02 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, grandmaster, Caleb James DeLisle

This timer appears on both the EcoNet EN751221 (34Kc) and on the
EN751627 / EN7528 (1004Kc) SoCs. On the 34Kc the interrupt controller
uses a percpu interrupt while on the others it uses an individual
IRQ number for each CPU timer. Add support for EN751627 and EN7528.
Add support for IRQ number for each CPU timer under the
econet,en751627-timer compatible.

Changes since v6:
* Rebase to tip/timers/core (base-commit below)

Changes since v5:
* Integrate patch fix refcount leak from:
Link: https://lore.kernel.org/linux-mips/20260526184105.18962-3-grandmaster@al2klimov.de/
* NULL econet_timer.membase after unmapping
* NULL check dev->event_handler in case clockevents_config_and_register
  failed to fully register.
* v5: https://lore.kernel.org/linux-mips/20260516233943.49502-1-cjd@cjdns.fr/

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/


Alexander A. Klimov (1):
  clocksource/timer-econet-en751221: fix refcount leak

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   | 181 ++++++++++++++----
 2 files changed, 159 insertions(+), 53 deletions(-)


base-commit: ecc330e3096173f433659aa64ab3674d0d48440e
-- 
2.39.5


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

* [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak
  2026-07-31 12:02 [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
@ 2026-07-31 12:02 ` Caleb James DeLisle
  2026-07-31 12:13   ` sashiko-bot
  2026-07-31 12:02 ` [PATCH v7 2/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Caleb James DeLisle @ 2026-07-31 12:02 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, grandmaster, Caleb James DeLisle

From: "Alexander A. Klimov" <grandmaster@al2klimov.de>

Every value returned from of_clk_get() is supposed to be cleaned up
via clk_put() once not needed anymore.

Fixes: 3b4c33ac87d0 ("clocksource/drivers: Add EcoNet Timer HPT driver")
Signed-off-by: Alexander A. Klimov <grandmaster@al2klimov.de>
Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
---
 drivers/clocksource/timer-econet-en751221.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 4008076b1a21..1859335345b5 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -181,6 +181,7 @@ static int __init timer_init(struct device_node *np)
 	}
 
 	econet_timer.freq_hz = clk_get_rate(clk);
+	clk_put(clk);
 
 	for (int i = 0; i < num_blocks; i++) {
 		econet_timer.membase[i] = of_iomap(np, i);
-- 
2.39.5


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

* [PATCH v7 2/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ
  2026-07-31 12:02 [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
  2026-07-31 12:02 ` [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak Caleb James DeLisle
@ 2026-07-31 12:02 ` Caleb James DeLisle
  2026-07-31 12:02 ` [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Caleb James DeLisle @ 2026-07-31 12:02 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, grandmaster, 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] 10+ messages in thread

* [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible
  2026-07-31 12:02 [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
  2026-07-31 12:02 ` [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak Caleb James DeLisle
  2026-07-31 12:02 ` [PATCH v7 2/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
@ 2026-07-31 12:02 ` Caleb James DeLisle
  2026-07-31 12:14   ` sashiko-bot
  2026-07-31 12:02 ` [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
  2026-07-31 12:02 ` [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
  4 siblings, 1 reply; 10+ messages in thread
From: Caleb James DeLisle @ 2026-07-31 12:02 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, grandmaster, 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 | 84 ++++++++++++---------
 1 file changed, 50 insertions(+), 34 deletions(-)

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 1859335345b5..714702b9ef12 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)
@@ -187,22 +166,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);
@@ -212,6 +214,20 @@ 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]);
+			econet_timer.membase[i] = NULL;
+		}
+	}
+
+	return ret;
 }
 
 TIMER_OF_DECLARE(econet_timer_hpt, "econet,en751221-timer", timer_init);
-- 
2.39.5


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

* [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered
  2026-07-31 12:02 [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
                   ` (2 preceding siblings ...)
  2026-07-31 12:02 ` [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
@ 2026-07-31 12:02 ` Caleb James DeLisle
  2026-07-31 12:16   ` sashiko-bot
  2026-07-31 12:02 ` [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
  4 siblings, 1 reply; 10+ messages in thread
From: Caleb James DeLisle @ 2026-07-31 12:02 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, grandmaster, 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. Additionally, NULL check dev->event_handler in
interrupt callback just in case clockevents_config_and_register failed.

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

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index 714702b9ef12..f2c4c1ee0a56 100644
--- a/drivers/clocksource/timer-econet-en751221.c
+++ b/drivers/clocksource/timer-econet-en751221.c
@@ -75,7 +75,10 @@ static irqreturn_t cevt_interrupt(int irq, void *dev_id)
 		return IRQ_NONE;
 
 	iowrite32(ioread32(reg_count(cpu)), reg_compare(cpu));
-	dev->event_handler(dev);
+
+	if (dev->event_handler)
+		dev->event_handler(dev);
+
 	return IRQ_HANDLED;
 }
 
@@ -104,12 +107,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;
 }
 
@@ -178,6 +180,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] 10+ messages in thread

* [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ
  2026-07-31 12:02 [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
                   ` (3 preceding siblings ...)
  2026-07-31 12:02 ` [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
@ 2026-07-31 12:02 ` Caleb James DeLisle
  2026-07-31 12:17   ` sashiko-bot
  4 siblings, 1 reply; 10+ messages in thread
From: Caleb James DeLisle @ 2026-07-31 12:02 UTC (permalink / raw)
  To: linux-mips
  Cc: conor+dt, daniel.lezcano, devicetree, krzk+dt, linux-kernel,
	naseefkm, robh, tglx, grandmaster, 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 | 122 ++++++++++++++++----
 1 file changed, 99 insertions(+), 23 deletions(-)

diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
index f2c4c1ee0a56..040386ded132 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;
 
@@ -102,6 +104,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);
@@ -110,7 +131,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;
 }
@@ -141,19 +165,52 @@ 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;
+	}
+	if (num_blocks == 0) {
+		pr_err("%pOFn: At least one reg block must be provided\n", np);
+		return -EINVAL;
+	}
 
 	clk = of_clk_get(np, 0);
 	if (IS_ERR(clk)) {
@@ -164,7 +221,7 @@ static int __init timer_init(struct device_node *np)
 	econet_timer.freq_hz = clk_get_rate(clk);
 	clk_put(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);
@@ -173,22 +230,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);
@@ -220,9 +287,17 @@ 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++) {
 		if (econet_timer.membase[i]) {
@@ -235,3 +310,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] 10+ messages in thread

* Re: [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak
  2026-07-31 12:02 ` [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak Caleb James DeLisle
@ 2026-07-31 12:13   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-31 12:13 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Calculating num_blocks using num_possible_cpus() may lead to a NULL pointer dereference if the cpu_possible_mask has holes.
--

--- Patch [1]: [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak ---
commit 7c522e295ecdd52460f4c1098da3b6a65821ce9c
Author: Alexander A. Klimov <grandmaster@al2klimov.de>

clocksource/timer-econet-en751221: fix refcount leak

Every value returned from of_clk_get() is supposed to be cleaned up
via clk_put() once not needed anymore.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index 4008076b1a210..1859335345b59 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c
> @@ -181,6 +181,7 @@ static int __init timer_init(struct device_node *np)
>  	}
>  
>  	econet_timer.freq_hz = clk_get_rate(clk);
> +	clk_put(clk);
>  
>  	for (int i = 0; i < num_blocks; i++) {

[Severity: High]
This is a pre-existing issue, but can this loop under-allocate memory blocks
and cause a NULL pointer dereference if the cpu_possible_mask has holes?

In timer_init(), num_blocks is calculated based on the count of possible CPUs:

    int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);

If the CPU topology is sparse (for example, CPUs 0 and 2 are possible, but
not 1), this under-allocates memory blocks relative to the maximum CPU ID.
Only one block will be mapped, leaving econet_timer.membase[1] unmapped.

Later, cevt_init() iterates over the possible CPUs:

    for_each_possible_cpu(i) {
        ...
        cevt_dev_init(i);
    }

Which unconditionally accesses the unmapped memory:

    static void __init cevt_dev_init(uint cpu)
    {
        iowrite32(0, reg_count(cpu));
        ...

The reg_count() macro dereferences econet_timer.membase[cpu >> 1]. If a block
was left unmapped due to a gap in CPU IDs, membase[1] will be NULL. This causes
iowrite32() to dereference NULL + 0x08, resulting in a crash on boot.

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

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

* Re: [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible
  2026-07-31 12:02 ` [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
@ 2026-07-31 12:14   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-31 12:14 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] On systems with sparse CPU topologies, memory blocks mapped via num_possible_cpus() are out of bounds when indexed by absolute CPU ID.
--

--- Patch [3]: [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible ---
commit aa21baac1fd919824fefec289b6741bdd5585304
Author: Caleb James DeLisle <cjd@cjdns.fr>
clocksource/timer-econet-en751221: Init teardown on error if possible

The commit converts the init process of the econet timer into two phases,
moving IRQ and address resource mapping before the point of no return. It
adds teardown logic in case of an error before this point.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index 1859335345b59..714702b9ef128 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c
[ ... ]
> -static int __init cevt_init(struct device_node *np)
> +static void __init cevt_init(struct device_node *np)
>  {
[ ... ]
>  	for_each_possible_cpu(i) {
>  		struct clock_event_device *cd = &per_cpu(econet_timer_pcpu, i);
[ ... ]
>  		cevt_dev_init(i);

[Severity: High]
This isn't a bug introduced by this patch, but could this loop cause a NULL
pointer dereference on systems with sparse CPU topologies?

If a system has sparse CPU IDs (for example, CPU 0 and CPU 4),
num_possible_cpus() evaluates to 2. When timer_init() calculates num_blocks
using the popcount of possible CPUs, it maps only the first block into membase:

timer_init()
  int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
  ...
  for (i = 0; i < num_blocks; i++)
      econet_timer.membase[i] = of_iomap(np, i);

However, the loop here iterates using for_each_possible_cpu(i). For CPU ID 4,
cevt_dev_init(4) is called. The underlying register access in reg_count()
uses the absolute CPU ID to compute the array index:

reg_count()
  return econet_timer.membase[timer_n >> 1] + (timer_n & 1) * 0x08 + 0x08;

This calculates an index of 4 >> 1 = 2, accessing membase[2]. Because
num_blocks was calculated as 1, membase[2] was never mapped and remains NULL,
which would lead to a NULL pointer dereference during hardware initialization.

>  	}

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

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

* Re: [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered
  2026-07-31 12:02 ` [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
@ 2026-07-31 12:16   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-31 12:16 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Sparse CPU IDs cause unmapped memory access due to relying on `num_possible_cpus()` instead of actual CPU IDs.
- [Medium] The driver is missing a CPU hotplug teardown callback to disable the hardware timer and mask the per-CPU interrupt when a CPU goes offline.
--

--- Patch [4]: [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered ---
commit e765af368a9556d25b3d5747a77b95b97cf2a3b9
Author: Caleb James DeLisle <cjd@cjdns.fr>

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

This commit eliminates a race condition where the interrupt may trigger
before the event handler is registered. It also adds a NULL check for the
event handler in the interrupt callback just in case configuration fails.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index 714702b9ef128..f2c4c1ee0a56b 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c
[ ... ]
> @@ -104,12 +107,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;
>  }

[Severity: Medium]
This is a pre-existing issue, but I noticed there isn't a corresponding
teardown path for CPU hotplug.

When offlining a CPU, the hardware timer is left running because
set_state_shutdown is not implemented for the clock_event_device.
Additionally, the hotplug state is registered with a NULL teardown callback
in timer_init():

timer_init() {
        ...
        ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
                                "clockevents/econet/timer:starting",
                                cevt_init_cpu, NULL);
        ...
}

This leaves the per-CPU interrupt unmasked on an offlined CPU.

Could this lead to spurious interrupts that might wedge the interrupt
controller when the CPU is brought back online?

[ ... ]
> @@ -178,6 +180,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);
>  

[Severity: High]
This isn't a bug introduced by this patch, but while looking at
timer_init(), I noticed how the memory blocks are mapped.

Does this code handle sparse CPU IDs correctly?

In timer_init(), the number of blocks is bounded by the count of possible
CPUs rather than the maximum CPU ID:

timer_init() {
        ...
        int num_blocks = DIV_ROUND_UP(num_possible_cpus(), 2);
        ...
}

If CPU IDs are sparse, for example if CPUs 0 and 4 are possible, meaning
num_possible_cpus() evaluates to 2, membase[cpu >> 1] in reg_ctl() will
access an index beyond the mapped num_blocks.

Since the unmapped membase index contains NULL, could this cause a NULL
pointer dereference in cevt_init_cpu() when it calls ioread32(reg_ctl(cpu))?

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

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

* Re: [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ
  2026-07-31 12:02 ` [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
@ 2026-07-31 12:17   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-07-31 12:17 UTC (permalink / raw)
  To: Caleb James DeLisle; +Cc: devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing CPU hotplug teardown callback and missing IRQ_PER_CPU flag causes an interrupt storm and unbalanced enable_irq warnings on CPU offline/online.
- [High] Timer driver completely fails to probe if the kernel is compiled with an `NR_CPUS` smaller than the hardware's core count.
--

--- Patch [5]: [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ ---
commit 4d23ebf2434d347d3628c6259b6442637444c87f
Author: Caleb James DeLisle <cjd@cjdns.fr>

clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ
    
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.

> diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c
> index f2c4c1ee0a56b..040386ded132a 100644
> --- a/drivers/clocksource/timer-econet-en751221.c
> +++ b/drivers/clocksource/timer-econet-en751221.c

[ ... ]

> @@ -110,7 +131,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);

[Severity: High]
Since cpuhp_setup_state() is registered in timer_init() with a NULL
teardown callback, disable_irq() is never called when a CPU goes offline.

Does this mean that onlining the CPU again will call enable_irq() on an IRQ
that was never disabled, triggering an unbalanced enable kernel warning?

[ ... ]

> @@ -141,19 +165,52 @@ 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);
> +	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;
> +	}

[Severity: High]
Does this code prevent the system from booting if the kernel is compiled
with an NR_CPUS limit smaller than the actual hardware core count?

If the Device Tree exposes more IRQs or register blocks than the
compile-time NR_CPUS bound allows, returning -EINVAL here will cause the
timer probe to fail entirely.

Would it be safer to gracefully bound the resource counts with min() and
initialize up to NR_CPUS timers instead of erroring out?

[ ... ]

> -	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);

[Severity: High]
Does this non-percpu path need to set the IRQ_PER_CPU flag on the interrupt?

When a CPU is offlined, irq_migrate_all_off_this_cpu() will forcibly migrate
this interrupt to a surviving CPU because it isn't marked as a per-CPU
interrupt.

When the hardware timer interrupts the new CPU, cevt_interrupt() uses
this_cpu_ptr() to check the new CPU's timer pending bit. That evaluates
to false, causing it to return IRQ_NONE without acknowledging the original
CPU's timer interrupt.

Could this lead to an unhandled interrupt storm that hangs the system?

> +
> +		if (ret < 0) {
> +			pr_err("%pOFn: IRQ %d setup failed: %pe\n", np,
> +			       i, ERR_PTR(ret));
> +			goto out_irq_free;
> +		}
>  	}

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

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

end of thread, other threads:[~2026-07-31 12:17 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 12:02 [PATCH v7 0/5] clocksource/timer-econet-en751221: Support irq number per timer Caleb James DeLisle
2026-07-31 12:02 ` [PATCH v7 1/5] clocksource/timer-econet-en751221: fix refcount leak Caleb James DeLisle
2026-07-31 12:13   ` sashiko-bot
2026-07-31 12:02 ` [PATCH v7 2/5] dt-bindings: timer: econet: Update EN751627 for multi-IRQ Caleb James DeLisle
2026-07-31 12:02 ` [PATCH v7 3/5] clocksource/timer-econet-en751221: Init teardown on error if possible Caleb James DeLisle
2026-07-31 12:14   ` sashiko-bot
2026-07-31 12:02 ` [PATCH v7 4/5] clocksource/timer-econet-en751221: Disable IRQ until cevt registered Caleb James DeLisle
2026-07-31 12:16   ` sashiko-bot
2026-07-31 12:02 ` [PATCH v7 5/5] clocksource/timer-econet-en751221: Support EN751627 without percpu IRQ Caleb James DeLisle
2026-07-31 12:17   ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.