[PATCH 1/3] timekeeping: NMI safe converter from a given time to monotonic

From: kan . liang
Date: Mon Jan 23 2023 - 13:29:44 EST


From: Kan Liang <kan.liang@xxxxxxxxxxxxxxx>

It's useful to provide a NMI safe function to convert a given time to
monotonic. For example, the perf_event subsystem wants to convert a TSC
of a PEBS record to a monotonic clock in a NMI handler.

Considered the below two existing functions, but none of them fulfill
the above requirements.
- The ktime_get_mono_fast_ns() is NMI safe, but it can only return the
current clock monotonic rather than a given time's monotonic.
- The get_device_system_crosststamp() can calculate the system time from
a given device time. But it's not fast and NMI safe.

The new function is a combination of the two existing functions. Use the
tk_fast_mono timekeeper to make the new function fast and NMI safe. Use
the get_time_fn callback to retrieve the given timestamp and its
clocksource. The history is not supported, since the perf case doesn't
really need it. It can be added later once there is a use case.

Signed-off-by: Kan Liang <kan.liang@xxxxxxxxxxxxxxx>
---
include/linux/timekeeping.h | 9 +++++
kernel/time/timekeeping.c | 68 +++++++++++++++++++++++++++++++++++--
2 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h
index fe1e467ba046..234fa87a846b 100644
--- a/include/linux/timekeeping.h
+++ b/include/linux/timekeeping.h
@@ -289,6 +289,15 @@ extern int get_device_system_crosststamp(
struct system_time_snapshot *history,
struct system_device_crosststamp *xtstamp);

+/*
+ * Fast NMI safe way to convert a given timestamp to clock monotonic
+ */
+extern int get_mono_fast_from_given_time(int (*get_time_fn)
+ (struct system_counterval_t *sys_counterval,
+ void *ctx),
+ void *ctx,
+ u64 *mono_ns);
+
/*
* Simultaneously snapshot realtime and monotonic raw clocks
*/
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 5579ead449f2..5bd32b2981fd 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -431,14 +431,19 @@ static void update_fast_timekeeper(const struct tk_read_base *tkr,
memcpy(base + 1, base, sizeof(*base));
}

-static __always_inline u64 fast_tk_get_delta_ns(struct tk_read_base *tkr)
+static __always_inline u64 fast_tk_get_delta_ns_from_cycles(struct tk_read_base *tkr,
+ u64 cycles)
{
- u64 delta, cycles = tk_clock_read(tkr);
+ u64 delta = clocksource_delta(cycles, tkr->cycle_last, tkr->mask);

- delta = clocksource_delta(cycles, tkr->cycle_last, tkr->mask);
return timekeeping_delta_to_ns(tkr, delta);
}

+static __always_inline u64 fast_tk_get_delta_ns(struct tk_read_base *tkr)
+{
+ return fast_tk_get_delta_ns_from_cycles(tkr, tk_clock_read(tkr));
+}
+
static __always_inline u64 __ktime_get_fast_ns(struct tk_fast *tkf)
{
struct tk_read_base *tkr;
@@ -1303,6 +1308,63 @@ int get_device_system_crosststamp(int (*get_time_fn)
}
EXPORT_SYMBOL_GPL(get_device_system_crosststamp);

+/**
+ * get_mono_fast_from_given_time - Fast NMI safe access to convert a given
+ * timestamp to clock monotonic
+ * @get_time_fn: Callback to get the given time and its clocksource
+ * @ctx: Context passed to get_time_fn()
+ * @mono_ns: The monotonic time of the given time
+ */
+int notrace get_mono_fast_from_given_time(int (*get_time_fn)
+ (struct system_counterval_t *sys_counterval,
+ void *ctx),
+ void *ctx,
+ u64 *mono_ns)
+{
+ struct system_counterval_t system_counterval;
+ struct tk_fast *tkf = &tk_fast_mono;
+ u64 cycles, now, interval_start;
+ struct tk_read_base *tkr;
+ unsigned int seq;
+ int ret;
+
+ do {
+ seq = raw_read_seqcount_latch(&tkf->seq);
+ tkr = tkf->base + (seq & 0x01);
+
+ ret = get_time_fn(&system_counterval, ctx);
+ if (ret)
+ return ret;
+
+ /*
+ * Verify that the clocksource associated with the given
+ * timestamp is the same as the currently installed
+ * timekeeper clocksource
+ */
+ if (tkr->clock != system_counterval.cs)
+ return -EOPNOTSUPP;
+ cycles = system_counterval.cycles;
+
+ /*
+ * Check whether the given timestamp is on the current
+ * timekeeping interval.
+ */
+ now = tk_clock_read(tkr);
+ interval_start = tkr->cycle_last;
+ if (!cycle_between(interval_start, cycles, now))
+ return -EOPNOTSUPP;
+
+ now = ktime_to_ns(tkr->base);
+ now += fast_tk_get_delta_ns_from_cycles(tkr, cycles);
+
+ } while (read_seqcount_latch_retry(&tkf->seq, seq));
+
+ *mono_ns = now;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(get_mono_fast_from_given_time);
+
/**
* do_settimeofday64 - Sets the time of day.
* @ts: pointer to the timespec64 variable containing the new time
--
2.35.1