From: "Benoît Monin" <benoit.monin@bootlin.com>
To: Thomas Bogendoerfer <tsbogend@alpha.franken.de>,
Thomas Gleixner <tglx@kernel.org>, Radu Rendec <radu@rendec.net>,
Aleksandar Rikalo <arikalo@gmail.com>,
Paul Burton <paulburton@kernel.org>,
Dragan Mladjenovic <dragan.mladjenovic@syrmia.com>,
Chao-ying Fu <cfu@wavecomp.com>,
Daniel Lezcano <daniel.lezcano@kernel.org>
Cc: "Tawfik Bayouk" <tawfik.bayouk@mobileye.com>,
"Vladimir Kondratiev" <vladimir.kondratiev@mobileye.com>,
"Gregory CLEMENT" <gregory.clement@bootlin.com>,
"Théo Lebrun" <theo.lebrun@bootlin.com>,
"Thomas Petazzoni" <thomas.petazzoni@bootlin.com>,
linux-mips@vger.kernel.org, linux-kernel@vger.kernel.org,
"Benoît Monin" <benoit.monin@bootlin.com>
Subject: [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems
Date: Tue, 29 Sep 2026 14:14:12 +0200 [thread overview]
Message-ID: <20260929-sync-gic-counters-v4-5-ec70c4b60434@bootlin.com> (raw)
In-Reply-To: <20260929-sync-gic-counters-v4-0-ec70c4b60434@bootlin.com>
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
next prev parent reply other threads:[~2026-09-29 12:14 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-29 12:14 [PATCH v4 0/5] MIPS: GIC clocksource/irqchip improvements and fixes for " 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 ` [PATCH v4 3/5] irqchip/mips-gic: Transfer interrupt mask state across clusters 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 ` Benoît Monin [this message]
2026-09-29 16:59 ` [PATCH v4 5/5] clocksource: mips-gic-timer: Use local counter on synced multi-cluster systems Thomas Bogendoerfer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260929-sync-gic-counters-v4-5-ec70c4b60434@bootlin.com \
--to=benoit.monin@bootlin.com \
--cc=arikalo@gmail.com \
--cc=cfu@wavecomp.com \
--cc=daniel.lezcano@kernel.org \
--cc=dragan.mladjenovic@syrmia.com \
--cc=gregory.clement@bootlin.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mips@vger.kernel.org \
--cc=paulburton@kernel.org \
--cc=radu@rendec.net \
--cc=tawfik.bayouk@mobileye.com \
--cc=tglx@kernel.org \
--cc=theo.lebrun@bootlin.com \
--cc=thomas.petazzoni@bootlin.com \
--cc=tsbogend@alpha.franken.de \
--cc=vladimir.kondratiev@mobileye.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®