* [PATCH v4 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic()
2026-09-29 12:14 [PATCH v4 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
@ 2026-09-29 12:14 ` Benoît Monin
2026-09-29 12:14 ` [PATCH v4 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; 8+ messages in thread
From: Benoît Monin @ 2026-09-29 12:14 UTC (permalink / raw)
To: Thomas Bogendoerfer, Thomas Gleixner, Radu Rendec,
Aleksandar Rikalo, Paul Burton, Dragan Mladjenovic, Chao-ying Fu,
Daniel Lezcano
Cc: Tawfik Bayouk, Vladimir Kondratiev, 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()")
Reviewed-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
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 [flat|nested] 8+ messages in thread* [PATCH v4 2/5] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity()
2026-09-29 12:14 [PATCH v4 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
2026-09-29 12:14 ` [PATCH v4 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() Benoît Monin
@ 2026-09-29 12:14 ` Benoît Monin
2026-09-29 12:14 ` [PATCH v4 3/5] irqchip/mips-gic: Transfer interrupt mask state across clusters Benoît Monin
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Benoît Monin @ 2026-09-29 12:14 UTC (permalink / raw)
To: Thomas Bogendoerfer, Thomas Gleixner, Radu Rendec,
Aleksandar Rikalo, Paul Burton, Dragan Mladjenovic, Chao-ying Fu,
Daniel Lezcano
Cc: Tawfik Bayouk, Vladimir Kondratiev, 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")
Reviewed-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
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 [flat|nested] 8+ messages in thread* [PATCH v4 3/5] irqchip/mips-gic: Transfer interrupt mask state across clusters
2026-09-29 12:14 [PATCH v4 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
2026-09-29 12:14 ` [PATCH v4 1/5] irqchip/mips-gic: Fix unbalanced cm_core_lock in for_each_online_cpu_gic() Benoît Monin
2026-09-29 12:14 ` [PATCH v4 2/5] irqchip/mips-gic: Fix recursive acquisition of gic_lock in gic_set_affinity() Benoît Monin
@ 2026-09-29 12:14 ` Benoît Monin
2026-09-29 17:00 ` Thomas Bogendoerfer
2026-09-29 12:14 ` [PATCH v4 4/5] clocksource: mips-gic-timer: Simplify gic_next_event() for per-cpu timer Benoît Monin
2026-09-29 12:14 ` [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Benoît Monin
4 siblings, 1 reply; 8+ messages in thread
From: Benoît Monin @ 2026-09-29 12:14 UTC (permalink / raw)
To: Thomas Bogendoerfer, Thomas Gleixner, Radu Rendec,
Aleksandar Rikalo, Paul Burton, Dragan Mladjenovic, Chao-ying Fu,
Daniel Lezcano
Cc: Tawfik Bayouk, Vladimir Kondratiev, 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 may
be left disabled in the destination cluster, so it never fires despite
being correctly routed to its new VP.
Move the mask state along with the interrupt: disable it in the old
cluster while clearing the route so it is no longer delivered, then
configure the trigger type in the new cluster and re-enable it there
if it was enabled in the old cluster.
Fixes: 322a90638768 ("irqchip/mips-gic: Multi-cluster support")
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
drivers/irqchip/irq-mips-gic.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
index f2ae60d39d66..be38989d8e73 100644
--- a/drivers/irqchip/irq-mips-gic.c
+++ b/drivers/irqchip/irq-mips-gic.c
@@ -368,6 +368,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
unsigned int cpu, cl, old_cpu, old_cl;
unsigned long flags;
+ bool enabled;
/*
* The GIC specifies that we can only route an interrupt to one VP(E),
@@ -389,15 +390,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
raw_spin_lock_irqsave(&gic_lock, flags);
/*
- * If we're moving affinity between clusters, stop routing the
- * interrupt to any VP(E) in the old cluster.
+ * If we're moving affinity between clusters, save the interrupt's
+ * mask state, stop routing it to any VP(E) in the old cluster and
+ * disable it there so it is no longer delivered.
*/
if (cl != old_cl) {
if (gic_irq_lock_cluster(d)) {
+ enabled = read_gic_redir_mask(irq);
write_gic_redir_map_vp(irq, 0);
+ write_gic_redir_rmask(irq);
mips_cm_unlock_other();
} else {
+ enabled = read_gic_mask(irq);
write_gic_map_vp(irq, 0);
+ write_gic_rmask(irq);
}
}
@@ -409,10 +415,20 @@ 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, if it was enabled in the old cluster, enable it
+ * in the new one.
*/
- if (cl != old_cl)
+ if (cl != old_cl) {
gic_set_type_locked(d, irqd_get_trigger_type(d));
+ if (enabled) {
+ 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 [flat|nested] 8+ messages in thread* Re: [PATCH v4 3/5] irqchip/mips-gic: Transfer interrupt mask state across clusters
2026-09-29 12:14 ` [PATCH v4 3/5] irqchip/mips-gic: Transfer interrupt mask state across clusters Benoît Monin
@ 2026-09-29 17:00 ` Thomas Bogendoerfer
0 siblings, 0 replies; 8+ messages in thread
From: Thomas Bogendoerfer @ 2026-09-29 17:00 UTC (permalink / raw)
To: Benoît Monin
Cc: Thomas Gleixner, Radu Rendec, Aleksandar Rikalo, Paul Burton,
Dragan Mladjenovic, Chao-ying Fu, Daniel Lezcano, Tawfik Bayouk,
Vladimir Kondratiev, Gregory CLEMENT, Théo Lebrun,
Thomas Petazzoni, linux-mips, linux-kernel
On Tue, Sep 29, 2026 at 02:14:10PM +0200, Benoît Monin wrote:
> 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 may
> be left disabled in the destination cluster, so it never fires despite
> being correctly routed to its new VP.
>
> Move the mask state along with the interrupt: disable it in the old
> cluster while clearing the route so it is no longer delivered, then
> configure the trigger type in the new cluster and re-enable it there
> if it was enabled in the old cluster.
>
> Fixes: 322a90638768 ("irqchip/mips-gic: Multi-cluster support")
> Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
> ---
> drivers/irqchip/irq-mips-gic.c | 24 ++++++++++++++++++++----
> 1 file changed, 20 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/irqchip/irq-mips-gic.c b/drivers/irqchip/irq-mips-gic.c
> index f2ae60d39d66..be38989d8e73 100644
> --- a/drivers/irqchip/irq-mips-gic.c
> +++ b/drivers/irqchip/irq-mips-gic.c
> @@ -368,6 +368,7 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
> unsigned int irq = GIC_HWIRQ_TO_SHARED(d->hwirq);
> unsigned int cpu, cl, old_cpu, old_cl;
> unsigned long flags;
> + bool enabled;
>
> /*
> * The GIC specifies that we can only route an interrupt to one VP(E),
> @@ -389,15 +390,20 @@ static int gic_set_affinity(struct irq_data *d, const struct cpumask *cpumask,
> raw_spin_lock_irqsave(&gic_lock, flags);
>
> /*
> - * If we're moving affinity between clusters, stop routing the
> - * interrupt to any VP(E) in the old cluster.
> + * If we're moving affinity between clusters, save the interrupt's
> + * mask state, stop routing it to any VP(E) in the old cluster and
> + * disable it there so it is no longer delivered.
> */
> if (cl != old_cl) {
> if (gic_irq_lock_cluster(d)) {
> + enabled = read_gic_redir_mask(irq);
> write_gic_redir_map_vp(irq, 0);
> + write_gic_redir_rmask(irq);
> mips_cm_unlock_other();
> } else {
> + enabled = read_gic_mask(irq);
> write_gic_map_vp(irq, 0);
> + write_gic_rmask(irq);
> }
> }
>
> @@ -409,10 +415,20 @@ 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, if it was enabled in the old cluster, enable it
> + * in the new one.
> */
> - if (cl != old_cl)
> + if (cl != old_cl) {
> gic_set_type_locked(d, irqd_get_trigger_type(d));
> + if (enabled) {
> + 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
Reviewed-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
--
Crap can work. Given enough thrust pigs will fly, but it's not necessarily a
good idea. [ RFC1925, 2.3 ]
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 4/5] clocksource: mips-gic-timer: Simplify gic_next_event() for per-cpu timer
2026-09-29 12:14 [PATCH v4 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
` (2 preceding siblings ...)
2026-09-29 12:14 ` [PATCH v4 3/5] irqchip/mips-gic: Transfer interrupt mask state across clusters Benoît Monin
@ 2026-09-29 12:14 ` Benoît Monin
2026-09-29 12:14 ` [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Benoît Monin
4 siblings, 0 replies; 8+ messages in thread
From: Benoît Monin @ 2026-09-29 12:14 UTC (permalink / raw)
To: Thomas Bogendoerfer, Thomas Gleixner, Radu Rendec,
Aleksandar Rikalo, Paul Burton, Dragan Mladjenovic, Chao-ying Fu,
Daniel Lezcano
Cc: Tawfik Bayouk, Vladimir Kondratiev, Gregory CLEMENT,
Théo Lebrun, Thomas Petazzoni, linux-mips, linux-kernel,
Benoît Monin
The GIC clock event device is registered per CPU and is flagged with
CLOCK_EVT_FEAT_C3STOP. This guarantees that the set_next_event() callback
is always invoked on the CPU of the event.
Drop the code path configuring the compare register of another CPU as it
is never called and does not correctly handle the case of having multiple
clusters, and document this guarantee by adding the CLOCK_EVT_FEAT_PERCPU
flag to the clock event device features.
Reviewed-by: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
Signed-off-by: Benoît Monin <benoit.monin@bootlin.com>
---
drivers/clocksource/mips-gic-timer.c | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index 1501c7db9a8e..cdaf4ba1d509 100644
--- a/drivers/clocksource/mips-gic-timer.c
+++ b/drivers/clocksource/mips-gic-timer.c
@@ -52,18 +52,13 @@ static u64 notrace gic_read_count(void)
static int gic_next_event(unsigned long delta, struct clock_event_device *evt)
{
- int cpu = cpumask_first(evt->cpumask);
u64 cnt;
int res;
cnt = gic_read_count();
cnt += (u64)delta;
- if (cpu == raw_smp_processor_id()) {
- write_gic_vl_compare(cnt);
- } else {
- write_gic_vl_other(mips_cm_vp_id(cpu));
- write_gic_vo_compare(cnt);
- }
+ write_gic_vl_compare(cnt);
+
res = ((int)(gic_read_count() - cnt) >= 0) ? -ETIME : 0;
return res;
}
@@ -82,6 +77,7 @@ static void gic_clockevent_cpu_init(unsigned int cpu,
{
cd->name = "MIPS GIC";
cd->features = CLOCK_EVT_FEAT_ONESHOT |
+ CLOCK_EVT_FEAT_PERCPU |
CLOCK_EVT_FEAT_C3STOP;
cd->rating = 350;
--
2.55.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems
2026-09-29 12:14 [PATCH v4 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for multi-cluster systems Benoît Monin
` (3 preceding siblings ...)
2026-09-29 12:14 ` [PATCH v4 4/5] clocksource: mips-gic-timer: Simplify gic_next_event() for per-cpu timer Benoît Monin
@ 2026-09-29 12:14 ` Benoît Monin
2026-09-29 16:59 ` Thomas Bogendoerfer
4 siblings, 1 reply; 8+ messages in thread
From: Benoît Monin @ 2026-09-29 12:14 UTC (permalink / raw)
To: Thomas Bogendoerfer, Thomas Gleixner, Radu Rendec,
Aleksandar Rikalo, Paul Burton, Dragan Mladjenovic, Chao-ying Fu,
Daniel Lezcano
Cc: Tawfik Bayouk, Vladimir Kondratiev, 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() sets
gic_use_local_counter so that gic_hpt_read() stops redirecting reads to
cluster 0 and uses the local counter instead. It also re-enables the GIC
VDSO clock mode and registers the GIC counter as the sched_clock. Systems
where synchronization cannot be achieved keep using the safe cross-cluster
read path.
The promotion is deferred to a workqueue because it is triggered from the
CPU hotplug STARTING callback, which runs with interrupts disabled, while
registering the sched_clock and switching the VDSO clock mode should be
done from process context.
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 and later powered on again,
gic_sync_counter_64() will be able to re-synchronize it. 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 | 128 ++++++++++++++++++++++++++++++++---
1 file changed, 117 insertions(+), 11 deletions(-)
diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c
index cdaf4ba1d509..fc97b31f0adb 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,9 @@ static int gic_timer_irq;
static unsigned int gic_frequency;
static unsigned int gic_count_width;
static bool __read_mostly gic_clock_unstable;
+static bool __read_mostly gic_use_local_counter = true;
+static unsigned long *gic_synced_cl_map;
+static struct work_struct gic_promote_work;
static void gic_clocksource_unstable(char *reason);
@@ -102,10 +106,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;
@@ -154,11 +219,6 @@ static int gic_clockevent_init(void)
return 0;
}
-static u64 gic_hpt_read(struct clocksource *cs)
-{
- return gic_read_count();
-}
-
static u64 gic_hpt_read_multicluster(struct clocksource *cs)
{
unsigned int hi, hi2, lo;
@@ -190,6 +250,14 @@ static u64 gic_hpt_read_multicluster(struct clocksource *cs)
return count;
}
+static u64 gic_hpt_read(struct clocksource *cs)
+{
+ if (unlikely(!READ_ONCE(gic_use_local_counter)))
+ return gic_hpt_read_multicluster(cs);
+
+ return gic_read_count();
+}
+
static struct clocksource gic_clocksource = {
.name = "GIC",
.read = gic_hpt_read,
@@ -209,8 +277,28 @@ 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 || READ_ONCE(gic_use_local_counter))
+ return;
+
+ WRITE_ONCE(gic_use_local_counter, true);
+
+#ifdef CONFIG_GENERIC_GETTIMEOFDAY
+ gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_GIC;
+#endif
+
+ 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. */
@@ -222,14 +310,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()) {
- gic_clocksource.read = &gic_hpt_read_multicluster;
+ 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) {
+ WRITE_ONCE(gic_use_local_counter, false);
gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE;
+ INIT_WORK(&gic_promote_work, gic_clocksource_promote);
}
ret = clocksource_register_hz(&gic_clocksource, gic_frequency);
@@ -288,7 +394,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()) {
+ READ_ONCE(gic_use_local_counter)) {
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 [flat|nested] 8+ messages in thread* Re: [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems
2026-09-29 12:14 ` [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Benoît Monin
@ 2026-09-29 16:59 ` Thomas Bogendoerfer
0 siblings, 0 replies; 8+ messages in thread
From: Thomas Bogendoerfer @ 2026-09-29 16:59 UTC (permalink / raw)
To: Benoît Monin
Cc: Thomas Gleixner, Radu Rendec, Aleksandar Rikalo, Paul Burton,
Dragan Mladjenovic, Chao-ying Fu, Daniel Lezcano, Tawfik Bayouk,
Vladimir Kondratiev, Gregory CLEMENT, Théo Lebrun,
Thomas Petazzoni, linux-mips, linux-kernel
On Tue, Sep 29, 2026 at 02:14:12PM +0200, Benoît Monin wrote:
> 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);
do we really need the workqueue ? As far as I understand
sched_clock_register() doesn't use the clocksource mutex, so we should
be able to directly call gic_promote() again.
Thomas.
--
Crap can work. Given enough thrust pigs will fly, but it's not necessarily a
good idea. [ RFC1925, 2.3 ]
^ permalink raw reply [flat|nested] 8+ messages in thread