The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems
@ 2026-08-10 14:25 Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() Benoît Monin
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Benoît Monin @ 2026-08-10 14:25 UTC (permalink / raw)
  To: Thomas Bogendoerfer, Daniel Lezcano, Thomas Gleixner,
	Dragan Mladjenovic, Chao-ying Fu, Aleksandar Rikalo, Paul Burton
  Cc: Vladimir Kondratiev, Tawfik Bayouk, Gregory CLEMENT,
	Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
	Benoît Monin

This series addresses multi-cluster MIPS GIC handling: it fixes a few
cross-cluster and lock bugs, and allows the GIC timer to be used as a
fast local clocksource on multi-cluster systems by synchronizing the
per-cluster counters.

If you are wondering how a single patch in version one turned into a
five-patch series in version two, the answser is PROVE_LOCKING which
found some issues that this series addresses.

The first three patches are for the irq-mips-gic irqchip driver.

The first patch fixes the for_each_online_cpu_gic() macro which
was releasing an unheld per-CPU cm_core_lock on every invocation
on multi-cluster hardware. Move both the acquire and release into
__gic_with_next_online_cpu() so they stay balanced and the for_each
macro accesses all VP on a multi-cluster system.

The second patch fixes a recursive acquisition of gic_lock in
gic_set_affinity() when moving interrupt SMP affinity across clusters,
caused by a call to gic_set_type(). Split out a gic_set_type_locked()
helper to resolve the issue.

The third patch enables the interrupt when moving affinity across
clusters. The interrupt mask is per-cluster, so after a cross-cluster
affinity change the interrupt was left disabled in the destination
cluster. Handle the mask explicitly on both the old and the new side.

The last two patches are for the mips-gic-timer clocksource driver.

The fourth patch sets next GIC event on the correct VP. On multi-cluster
systems the VO window redirected by write_gic_vl_other() belongs to the
local GIC, so write_gic_vo_compare() hit the wrong GIC. Redirect the
core-other region to the target CPU with mips_cm_lock_other_cpu() instead.

The last patch is the main functional change, carried over from v1. It
synchronizes each secondary cluster's GIC counter to cluster 0's counter
as its CPUs come online, and once every cluster is in sync, promote the
clocksource back from the slow cross-cluster gic_hpt_read_multicluster()
path to the fast local gic_hpt_read(), also re-enabling the GIC
VDSO clock mode. On the dual-cluster Mobileye EyeQ6H SoC this makes
clock_gettime(CLOCK_MONOTONIC) about four times faster on the secondary
cluster and replaces the jiffies-based sched_clock with the high-precision
GIC counter.

This series was tested on Mobileye SoCs with different cluster
configurations:
* The EyeQ5 with two clusters but only one is equipped with VP
  (VP topology {2,2,2,2},{} total 8).
* The EyeQ6Lplus which is strictly single cluster
  (VP topology {4,4} total 8).
* The EyeQ6H with two identical clusters
  (VP topology {4,4,4,4},{4,4,4,4} total 32).

Although I am posting this as a single series since the patches
are all related to the GIC, the changes to the irqchip driver and
to the clocksource driver are independent from each other and can be
merged in different trees. I can split the series in two if it helps
reviewing/merging.

Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
Changes in v2:
- Four new patches described above fixing multi-cluster
  handling in the GIC irqchip and clocksource drivers.
- In the last patch, gic_clocksource_promote() is now deferred via a
  workqueue instead of a CPUHP ONLINE callback, to avoid a lockdep
  issue on clocksource_mutex.
- Link to v1: https://patch.msgid.link/20260724-sync-gic-counters-v1-1-aa24fd0f30bf@bootlin.com

To: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
To: Thomas Gleixner <tglx@kernel.org>
To: Dragan Mladjenovic <dragan.mladjenovic@syrmia.com>
To: Chao-ying Fu <cfu@wavecomp.com>
To: Aleksandar Rikalo <arikalo@gmail.com>
To: Paul Burton <paulburton@kernel.org>
To: Daniel Lezcano <daniel.lezcano@kernel.org>
Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Cc: linux-mips@vger.kernel.org
Cc: linux-kernel@vger.kernel.org

---
Benoît Monin (5):
      irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic()
      irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity()
      irqchip/mips-gic: Enable interrupt when moving affinity across clusters
      clocksource: mips-gic-timer: Set next GIC event on the correct VP
      clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems

 drivers/clocksource/mips-gic-timer.c | 120 +++++++++++++++++++++++++++++++++--
 drivers/irqchip/irq-mips-gic.c       |  51 ++++++++-------
 2 files changed, 143 insertions(+), 28 deletions(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260717-sync-gic-counters-1cd6b40b968e

Best regards,
--  
Benoît Monin, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com


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

* [PATCH v2 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic()
  2026-08-10 14:25 [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
@ 2026-08-10 14:25 ` Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 2/5] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity() Benoît Monin
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Benoît Monin @ 2026-08-10 14:25 UTC (permalink / raw)
  To: Thomas Bogendoerfer, Daniel Lezcano, Thomas Gleixner,
	Dragan Mladjenovic, Chao-ying Fu, Aleksandar Rikalo, Paul Burton
  Cc: Vladimir Kondratiev, Tawfik Bayouk, Gregory CLEMENT,
	Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
	Benoît Monin

Commit d9e2ed610a60 ("irqchip/mips-gic: Support multi-cluster in
for_each_online_cpu_gic()") added a gic_unlock_cluster() call to the
macro's loop increment, which unconditionally invokes
mips_cm_unlock_other() on multi-cluster systems. However nothing in the
loop ever acquires the corresponding mips_cm_lock_other(), so on
multi-cluster hardware every invocation of for_each_online_cpu_gic()
releases an unheld per-CPU cm_core_lock.

With CONFIG_PROVE_LOCKING this triggers a "bad unlock balance detected"
warning at boot, e.g. from gic_irq_domain_map() while mapping local
interrupts. Only the first occurrence is reported, since the first
warning permanently disables lockdep (debug_locks = 0); the unbalanced
release itself silently persists.

Fix this by moving both the acquire and release into
__gic_with_next_online_cpu() so they stay balanced. When advancing to a
CPU in a remote cluster, lock the CM redirect block for that cluster via
mips_cm_lock_other(); when leaving a remote cluster (or finishing the
iteration) release it with mips_cm_unlock_other(). Local-cluster CPUs
require no locking, so single-cluster systems are unaffected. This also
makes the redirect region behave correctly when accessing local register
blocks of CPUs in other clusters.

Drop the now-unused gic_unlock_cluster() helper and its call from the
for_each_online_cpu_gic() increment.

Fixes: d9e2ed610a60 ("irqchip/mips-gic: Support multi-cluster in for_each_online_cpu_gic()")
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/irqchip/irq-mips-gic.c | 20 ++++++--------------
 1 file changed, 6 insertions(+), 14 deletions(-)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 19a57c5e2b2e..3b31cbcbed6f 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -70,6 +70,10 @@ static int __gic_with_next_online_cpu(int prev)
 {
 	unsigned int cpu;
 
+	/* Release the redirect/other region lock to the previous CPU, if any. */
+	if (prev >= 0)
+		mips_cm_unlock_other();
+
 	/* Discover the next online CPU */
 	cpu = cpumask_next(prev, cpu_online_mask);
 
@@ -77,23 +81,12 @@ static int __gic_with_next_online_cpu(int prev)
 	if (cpu >= nr_cpu_ids)
 		return cpu;
 
-	/*
-	 * Move the access lock to the next CPU's GIC local register block.
-	 *
-	 * Set GIC_VL_OTHER. Since the caller holds gic_lock nothing can
-	 * clobber the written value.
-	 */
-	write_gic_vl_other(mips_cm_vp_id(cpu));
+	/* Lock access to redirect/other region to the next CPU */
+	mips_cm_lock_other_cpu(cpu, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
 
 	return cpu;
 }
 
-static inline void gic_unlock_cluster(void)
-{
-	if (mips_cps_multicluster_cpus())
-		mips_cm_unlock_other();
-}
-
 /**
  * for_each_online_cpu_gic() - Iterate over online CPUs, access local registers
  * @cpu: An integer variable to hold the current CPU number
@@ -108,7 +101,6 @@ static inline void gic_unlock_cluster(void)
 	guard(raw_spinlock_irqsave)(gic_lock);		\
 	for ((cpu) = __gic_with_next_online_cpu(-1);	\
 	     (cpu) < nr_cpu_ids;			\
-	     gic_unlock_cluster(),			\
 	     (cpu) = __gic_with_next_online_cpu(cpu))
 
 /**

-- 
2.55.0


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

* [PATCH v2 2/5] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity()
  2026-08-10 14:25 [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() Benoît Monin
@ 2026-08-10 14:25 ` Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 3/5] irqchip/mips-gic: Enable interrupt when moving affinity across clusters Benoît Monin
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Benoît Monin @ 2026-08-10 14:25 UTC (permalink / raw)
  To: Thomas Bogendoerfer, Daniel Lezcano, Thomas Gleixner,
	Dragan Mladjenovic, Chao-ying Fu, Aleksandar Rikalo, Paul Burton
  Cc: Vladimir Kondratiev, Tawfik Bayouk, Gregory CLEMENT,
	Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
	Benoît Monin

Commit 322a90638768 ("irqchip/mips-gic: Multi-cluster support")
made gic_set_affinity() call gic_set_type() when an interrupt's
affinity is moved between clusters. gic_set_type() takes gic_lock with
raw_spin_lock_irqsave(), but gic_set_affinity() holds gic_lock at that
point, so the same raw spinlock is acquired twice, triggering a deadlock.

Split gic_set_type() into a gic_set_type_locked() helper that assumes
gic_lock is already held. gic_set_type() takes gic_lock and calls the
helper, and gic_set_affinity() calls gic_set_type_locked() directly,
since it already holds gic_lock.

Fixes: 322a90638768 ("irqchip/mips-gic: Multi-cluster support")
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/irqchip/irq-mips-gic.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index 3b31cbcbed6f..f2ae60d39d66 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -297,14 +297,14 @@ static void gic_ack_irq(struct irq_data *d)
 	}
 }
 
-static int gic_set_type(struct irq_data *d, unsigned int type)
+static int gic_set_type_locked(struct irq_data *d, unsigned int type)
 {
 	unsigned int irq, pol, trig, dual;
-	unsigned long flags;
+
+	lockdep_assert_held(&gic_lock);
 
 	irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
 
-	raw_spin_lock_irqsave(&gic_lock, flags);
 	switch (type & IRQ_TYPE_SENSE_MASK) {
 	case IRQ_TYPE_EDGE_FALLING:
 		pol = GIC_POL_FALLING_EDGE;
@@ -351,11 +351,16 @@ static int gic_set_type(struct irq_data *d, unsigned int type)
 	else
 		irq_set_chip_handler_name_locked(d, &gic_level_irq_controller,
 						 handle_level_irq, NULL);
-	raw_spin_unlock_irqrestore(&gic_lock, flags);
 
 	return 0;
 }
 
+static int gic_set_type(struct irq_data *d, unsigned int type)
+{
+	guard(raw_spinlock_irqsave)(&gic_lock);
+	return gic_set_type_locked(d, type);
+}
+
 #ifdef CONFIG_SMP
 static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
 			    bool force)
@@ -407,7 +412,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
 	 * trigger type in the new cluster.
 	 */
 	if (cl != old_cl)
-		gic_set_type(d, irqd_get_trigger_type(d));
+		gic_set_type_locked(d, irqd_get_trigger_type(d));
 
 	/* Route the interrupt to its new VP(E) */
 	if (gic_irq_lock_cluster(d)) {

-- 
2.55.0


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

* [PATCH v2 3/5] irqchip/mips-gic: Enable interrupt when moving affinity across clusters
  2026-08-10 14:25 [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 2/5] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity() Benoît Monin
@ 2026-08-10 14:25 ` Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 4/5] clocksource: mips-gic-timer: Set next GIC event on the correct VP Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Benoît Monin
  4 siblings, 0 replies; 6+ messages in thread
From: Benoît Monin @ 2026-08-10 14:25 UTC (permalink / raw)
  To: Thomas Bogendoerfer, Daniel Lezcano, Thomas Gleixner,
	Dragan Mladjenovic, Chao-ying Fu, Aleksandar Rikalo, Paul Burton
  Cc: Vladimir Kondratiev, Tawfik Bayouk, Gregory CLEMENT,
	Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
	Benoît Monin

When an interrupt's affinity is moved to a CPU in another cluster,
gic_set_affinity() updates the routing (GIC_SH_MAP_VP) and trigger type
in the destination cluster, but never touched the interrupt's mask state.

The interrupt mask is per-cluster. After such a move the interrupt was
left disabled in the destination cluster, so it never fires despite
being correctly routed to its new VP.

Handle the mask explicitly on a cross-cluster affinity change: in the
old cluster, write GIC_SH_RMASK to disable the interrupt while clearing
the route so it is no longer delivered. And in the new cluster, set the
mask to enable the interrupt along with reconfiguring the trigger type.

Fixes: 322a90638768 ("irqchip/mips-gic: Multi-cluster support")
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/irqchip/irq-mips-gic.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index f2ae60d39d66..4b76a65f12c9 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -390,14 +390,17 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
 
 	/*
 	 * If we're moving affinity between clusters, stop routing the
-	 * interrupt to any VP(E) in the old cluster.
+	 * interrupt to any VP(E) in the old cluster and disable
+	 * the interrupt in that cluster.
 	 */
 	if (cl != old_cl) {
 		if (gic_irq_lock_cluster(d)) {
 			write_gic_redir_map_vp(irq, 0);
+			write_gic_redir_rmask(irq);
 			mips_cm_unlock_other();
 		} else {
 			write_gic_map_vp(irq, 0);
+			write_gic_rmask(irq);
 		}
 	}
 
@@ -409,10 +412,17 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
 
 	/*
 	 * If we're moving affinity between clusters, configure the interrupt
-	 * trigger type in the new cluster.
+	 * trigger type and enable the interrupt in the new cluster.
 	 */
-	if (cl != old_cl)
+	if (cl != old_cl) {
 		gic_set_type_locked(d, irqd_get_trigger_type(d));
+		if (gic_irq_lock_cluster(d)) {
+			write_gic_redir_smask(irq);
+			mips_cm_unlock_other();
+		} else {
+			write_gic_smask(irq);
+		}
+	}
 
 	/* Route the interrupt to its new VP(E) */
 	if (gic_irq_lock_cluster(d)) {

-- 
2.55.0


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

* [PATCH v2 4/5] clocksource: mips-gic-timer: Set next GIC event on the correct VP
  2026-08-10 14:25 [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
                   ` (2 preceding siblings ...)
  2026-08-10 14:25 ` [PATCH v2 3/5] irqchip/mips-gic: Enable interrupt when moving affinity across clusters Benoît Monin
@ 2026-08-10 14:25 ` Benoît Monin
  2026-08-10 14:25 ` [PATCH v2 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Benoît Monin
  4 siblings, 0 replies; 6+ messages in thread
From: Benoît Monin @ 2026-08-10 14:25 UTC (permalink / raw)
  To: Thomas Bogendoerfer, Daniel Lezcano, Thomas Gleixner,
	Dragan Mladjenovic, Chao-ying Fu, Aleksandar Rikalo, Paul Burton
  Cc: Vladimir Kondratiev, Tawfik Bayouk, Gregory CLEMENT,
	Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
	Benoît Monin

gic_next_event() programs the next event on the clock_event_device's target
CPU. When that CPU is not the current one, the compare register is reached
through the GIC VO (other/redirect) window, which is first pointed at the
target VP with write_gic_vl_other().

On a multi-cluster system each cluster has its own GIC. The VO window of
the local GIC is what write_gic_vl_other() redirects, so when the target
CPU belongs to another cluster, write_gic_vo_compare() lands in the
wrong GIC, overwriting the COMPARE register of a VP in the local cluster.

Redirect the core-other region to the target CPU's GIC local block
with mips_cm_lock_other_cpu() using CM_GCR_Cx_OTHER_BLOCK_LOCAL before
writing GIC_VO_COMPARE, and release it with mips_cm_unlock_other()
afterwards. This ensures that the correct VP is accessed in all cases.

Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/clocksource/mips-gic-timer.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index a1669266c94d..ddbb2f827bf0 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -61,8 +61,9 @@ static int gic_next_event(unsigned long delta, struct clock_event_device *evt)
 	if (cpu == raw_smp_processor_id()) {
 		write_gic_vl_compare(cnt);
 	} else {
-		write_gic_vl_other(mips_cm_vp_id(cpu));
+		mips_cm_lock_other_cpu(cpu, CM_GCR_Cx_OTHER_BLOCK_LOCAL);
 		write_gic_vo_compare(cnt);
+		mips_cm_unlock_other();
 	}
 	res = ((int)(gic_read_count() - cnt) >= 0) ? -ETIME : 0;
 	return res;

-- 
2.55.0


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

* [PATCH v2 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems
  2026-08-10 14:25 [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
                   ` (3 preceding siblings ...)
  2026-08-10 14:25 ` [PATCH v2 4/5] clocksource: mips-gic-timer: Set next GIC event on the correct VP Benoît Monin
@ 2026-08-10 14:25 ` Benoît Monin
  4 siblings, 0 replies; 6+ messages in thread
From: Benoît Monin @ 2026-08-10 14:25 UTC (permalink / raw)
  To: Thomas Bogendoerfer, Daniel Lezcano, Thomas Gleixner,
	Dragan Mladjenovic, Chao-ying Fu, Aleksandar Rikalo, Paul Burton
  Cc: Vladimir Kondratiev, Tawfik Bayouk, Gregory CLEMENT,
	Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
	Benoît Monin

In a multi-cluster MIPS system there is one GIC per cluster, each with
its own independent counter. These counters are not synchronized in
hardware and can drift relative to one another, which is why multi-
cluster systems currently fall back to gic_hpt_read_multicluster():
every clocksource read is redirected to cluster 0's counter via a
cross-cluster register access.

Instead, actively synchronize the counter of each secondary cluster
to cluster 0's counter as its CPUs come online. When the first CPU of
a cluster starts and the GIC counter is stopped, gic_sync_counter_64()
is used to align the local counter with cluster 0 on systems using 64-bit
CM accesses:

  - the local counter is stopped, loaded with cluster 0's counter value
    plus an accumulated offset, and restarted;
  - the alignment is checked by reading the local counter, cluster 0's
    counter and the local counter again (t0, t1, t2). If t1 lies between
    t0 and t2 the two counters are considered in sync;
  - otherwise the offset is refined by half of the measured error to
    compensate for the cross-cluster access latency, and the process is
    retried.

After a short delay the alignment is re-checked to confirm the counters
have not drifted apart, and only then is the cluster recorded as
synchronized in gic_synced_cl_map. Clusters with no cores and cluster
0 itself are marked synchronized up-front.

Once every cluster is synchronized, gic_clocksource_promote() switches
the clocksource back from gic_hpt_read_multicluster() to the fast
local gic_hpt_read(), re-registers it, and re-enables the GIC VDSO clock
mode. It also registers the GIC counter as the sched_clock. Systems where
synchronization cannot be achieved keep using the safe cross-cluster
read path.

Since gic_clocksource_promote() calls clocksource_unregister() and
clocksource_register_hz() which internally use a mutex, it cannot be called
directly from the CPU hotplug STARTING callback because interrupts are
disabled. Instead the call is deferred to a workqueue.

gic_clocksource_promote() also registers the local GIC counter as the
sched clock via sched_clock_register(). This is valid from a non-__init
context because sched_clock_register() lost its __init marker in
commit 84b1a903aed8 ("time/sched_clock: Export symbol for sched_clock
register function").

Note that the clocksource is only promoted once when all clusters are
first online and all GIC counters are in sync. It is assumed that even
if a cluster is fully powered-off then on later, gic_sync_counter_64()
will be able to synchronize it once again. Said differently: there is
no support to "demote" the clocksource.

On the dual-cluster Mobileye EyeQ6H SoC, this allows four times faster
clock_gettime(CLOCK_MONOTONIC) and a much higher precision sched_clock
instead of jiffies.

Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
 drivers/clocksource/mips-gic-timer.c | 117 +++++++++++++++++++++++++++++++++--
 1 file changed, 112 insertions(+), 5 deletions(-)

diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index ddbb2f827bf0..ee9dd1b84767 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -6,6 +6,7 @@
 #include <linux/clk.h>
 #include <linux/clockchips.h>
 #include <linux/cpu.h>
+#include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/notifier.h>
@@ -21,6 +22,8 @@ static int gic_timer_irq;
 static unsigned int gic_frequency;
 static unsigned int gic_count_width;
 static bool __read_mostly gic_clock_unstable;
+static unsigned long *gic_synced_cl_map;
+static struct work_struct gic_promote_work;
 
 static void gic_clocksource_unstable(char *reason);
 
@@ -107,10 +110,71 @@ static void gic_update_frequency(void *data)
 	clockevents_update_freq(this_cpu_ptr(&gic_clockevent_device), rate);
 }
 
+/* Number of iterations to synchronize the local GIC counter */
+#define GIC_SYNC_ITERATIONS 4
+
+/* Delay in us to check if the local GIC counter is still in sync with cluster 0 */
+#define GIC_SYNC_CHECK_DELAY 100
+
+static void gic_sync_counter_64(unsigned int cluster)
+{
+	unsigned int config = read_gic_config();
+	u64 t0, t1, t2;
+	s64 offset = 0;
+
+	mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+
+	for (int i = 0; i < GIC_SYNC_ITERATIONS; i++) {
+		write_gic_config(config | GIC_CONFIG_COUNTSTOP);
+		write_gic_counter(read_gic_redir_counter() + offset);
+		write_gic_config(config & ~GIC_CONFIG_COUNTSTOP);
+
+		t0 = read_gic_counter();
+		t1 = read_gic_redir_counter();
+		t2 = read_gic_counter();
+
+		if (time_in_range64(t1, t0, t2))
+			break;
+
+		/*
+		 * Compute the offset to apply to the local counter
+		 * so that (t1 - t0) equals (t2 - t1).
+		 */
+		offset += (s64)(2 * t1 - t0 - t2) / 2;
+	}
+
+	mips_cm_unlock_other();
+
+	if (!time_in_range64(t1, t0, t2))
+		return;
+
+	udelay(GIC_SYNC_CHECK_DELAY);
+
+	mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL);
+	t0 = read_gic_counter();
+	t1 = read_gic_redir_counter();
+	t2 = read_gic_counter();
+	mips_cm_unlock_other();
+
+	/* If so, mark the cluster as synchronized */
+	if (time_in_range64(t1, t0, t2) && gic_synced_cl_map)
+		bitmap_set(gic_synced_cl_map, cluster, 1);
+}
+
 static int gic_starting_cpu(unsigned int cpu)
 {
-	/* Ensure the GIC counter is running */
-	clear_gic_config(GIC_CONFIG_COUNTSTOP);
+	unsigned int cluster = cpu_cluster(&cpu_data[cpu]);
+
+	if (read_gic_config() & GIC_CONFIG_COUNTSTOP) {
+		clear_gic_config(GIC_CONFIG_COUNTSTOP);
+
+		if (cluster && mips_cm_is64 && !gic_clock_unstable)
+			gic_sync_counter_64(cluster);
+
+		if (gic_synced_cl_map &&
+		    bitmap_full(gic_synced_cl_map, mips_cps_numclusters()))
+			schedule_work(&gic_promote_work);
+	}
 
 	gic_clockevent_cpu_init(cpu, this_cpu_ptr(&gic_clockevent_device));
 	return 0;
@@ -216,8 +280,33 @@ static void gic_clocksource_unstable(char *reason)
 	clocksource_mark_unstable(&gic_clocksource);
 }
 
+static void gic_clocksource_promote(struct work_struct *work)
+{
+	if (gic_clock_unstable || gic_clocksource.read == &gic_hpt_read)
+		return;
+
+	if (clocksource_unregister(&gic_clocksource) < 0)
+		return;
+
+	gic_clocksource.read = &gic_hpt_read;
+#ifdef CONFIG_GENERIC_GETTIMEOFDAY
+	gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_GIC;
+#endif
+
+	if (clocksource_register_hz(&gic_clocksource, gic_frequency) < 0)
+		return;
+
+	if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) {
+		sched_clock_register(mips_cm_is64 ?
+				     gic_read_count_64 : gic_read_count_2x32,
+				     gic_count_width, gic_frequency);
+	}
+}
+
 static int __init __gic_clocksource_init(void)
 {
+	unsigned int numclusters;
+	bool synced = false;
 	int ret;
 
 	/* Set clocksource mask. */
@@ -229,14 +318,32 @@ static int __init __gic_clocksource_init(void)
 
 	/* Calculate a somewhat reasonable rating value. */
 	if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ))
-		gic_clocksource.rating = 300; /* Good when frequecy is stable */
+		gic_clocksource.rating = 300; /* Good when frequency is stable */
 	else
 		gic_clocksource.rating = 200;
 	gic_clocksource.rating += clamp(gic_frequency / 10000000, 0, 99);
 
-	if (mips_cps_multicluster_cpus()) {
+	numclusters = mips_cps_numclusters();
+	if (numclusters > 1)
+		gic_synced_cl_map = bitmap_zalloc(numclusters, GFP_KERNEL);
+
+	/*
+	 * Mark cluster 0 as synchronized (with itself), and all clusters
+	 * without cores since there is no local GIC counter access on those.
+	 */
+	if (gic_synced_cl_map) {
+		bitmap_set(gic_synced_cl_map, 0, 1);
+		for (unsigned int cl = 0; cl < numclusters; cl++) {
+			if (!mips_cps_numcores(cl))
+				bitmap_set(gic_synced_cl_map, cl, 1);
+		}
+		synced = bitmap_full(gic_synced_cl_map, numclusters);
+	}
+
+	if (numclusters > 1 && !synced) {
 		gic_clocksource.read = &gic_hpt_read_multicluster;
 		gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
+		INIT_WORK(&gic_promote_work, gic_clocksource_promote);
 	}
 
 	ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
@@ -295,7 +402,7 @@ static int __init gic_clocksource_of_init(struct device_node *node)
 	 * change performed by the CPC core clocks divider.
 	 */
 	if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) &&
-	     !mips_cps_multicluster_cpus()) {
+	     gic_clocksource.read == &gic_hpt_read) {
 		sched_clock_register(mips_cm_is64 ?
 				     gic_read_count_64 : gic_read_count_2x32,
 				     gic_count_width, gic_frequency);

-- 
2.55.0


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

end of thread, other threads:[~2026-08-10 14:26 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 14:25 [PATCH v2 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
2026-08-10 14:25 ` [PATCH v2 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() Benoît Monin
2026-08-10 14:25 ` [PATCH v2 2/5] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity() Benoît Monin
2026-08-10 14:25 ` [PATCH v2 3/5] irqchip/mips-gic: Enable interrupt when moving affinity across clusters Benoît Monin
2026-08-10 14:25 ` [PATCH v2 4/5] clocksource: mips-gic-timer: Set next GIC event on the correct VP Benoît Monin
2026-08-10 14:25 ` [PATCH v2 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Benoît Monin

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