]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
timekeeping: Provide infrastructure for converting to/from a base clock
authorLakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>
Mon, 13 May 2024 10:38:02 +0000 (16:08 +0530)
committerThomas Gleixner <tglx@linutronix.de>
Mon, 3 Jun 2024 09:18:50 +0000 (11:18 +0200)
Hardware time stamps like provided by PTP clock implementations are based
on a clock which feeds both the PCIe device and the system clock. For
further processing the underlying hardwarre clock timestamp must be
converted to the system clock.

Right now this requires drivers to invoke an architecture specific
conversion function, e.g. to convert the ART (Always Running Timer)
timestamp to a TSC timestamp.

As the system clock is aware of the underlying base clock, this can be
moved to the core code by providing a base clock property for the system
clock which contains the conversion factors and assigning a clocksource ID
to the base clock.

Add the required data structures and the conversion infrastructure in the
core code to prepare for converting X86 and the related PTP drivers over.

[ tglx: Added a missing READ_ONCE(). Massaged change log ]

Co-developed-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Co-developed-by: Christopher S. Hall <christopher.s.hall@intel.com>
Signed-off-by: Christopher S. Hall <christopher.s.hall@intel.com>
Signed-off-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20240513103813.5666-2-lakshmi.sowjanya.d@intel.com
include/linux/clocksource.h
include/linux/timekeeping.h
kernel/time/timekeeping.c

index 0ad8b550bb4b4674fab70fc81dda94a76a7c9579..d35b677b08fe1365145160fab0f9e24e6720aff7 100644 (file)
@@ -21,6 +21,7 @@
 #include <asm/div64.h>
 #include <asm/io.h>
 
+struct clocksource_base;
 struct clocksource;
 struct module;
 
@@ -50,6 +51,7 @@ struct module;
  *                     multiplication
  * @name:              Pointer to clocksource name
  * @list:              List head for registration (internal)
+ * @freq_khz:          Clocksource frequency in khz.
  * @rating:            Rating value for selection (higher is better)
  *                     To avoid rating inflation the following
  *                     list should give you a guide as to how
@@ -70,6 +72,8 @@ struct module;
  *                     validate the clocksource from which the snapshot was
  *                     taken.
  * @flags:             Flags describing special properties
+ * @base:              Hardware abstraction for clock on which a clocksource
+ *                     is based
  * @enable:            Optional function to enable the clocksource
  * @disable:           Optional function to disable the clocksource
  * @suspend:           Optional suspend function for the clocksource
@@ -107,10 +111,12 @@ struct clocksource {
        u64                     max_cycles;
        const char              *name;
        struct list_head        list;
+       u32                     freq_khz;
        int                     rating;
        enum clocksource_ids    id;
        enum vdso_clock_mode    vdso_clock_mode;
        unsigned long           flags;
+       struct clocksource_base *base;
 
        int                     (*enable)(struct clocksource *cs);
        void                    (*disable)(struct clocksource *cs);
@@ -306,4 +312,25 @@ static inline unsigned int clocksource_get_max_watchdog_retry(void)
 
 void clocksource_verify_percpu(struct clocksource *cs);
 
+/**
+ * struct clocksource_base - hardware abstraction for clock on which a clocksource
+ *                     is based
+ * @id:                        Defaults to CSID_GENERIC. The id value is used for conversion
+ *                     functions which require that the current clocksource is based
+ *                     on a clocksource_base with a particular ID in certain snapshot
+ *                     functions to allow callers to validate the clocksource from
+ *                     which the snapshot was taken.
+ * @freq_khz:          Nominal frequency of the base clock in kHz
+ * @offset:            Offset between the base clock and the clocksource
+ * @numerator:         Numerator of the clock ratio between base clock and the clocksource
+ * @denominator:       Denominator of the clock ratio between base clock and the clocksource
+ */
+struct clocksource_base {
+       enum clocksource_ids    id;
+       u32                     freq_khz;
+       u64                     offset;
+       u32                     numerator;
+       u32                     denominator;
+};
+
 #endif /* _LINUX_CLOCKSOURCE_H */
index 0ea7823b7f31f6c3cd653ddce0204a44e1f61a8f..b2ee182d891ef53e1ea2f60f2295d86fa80a5b9e 100644 (file)
@@ -310,10 +310,12 @@ struct system_device_crosststamp {
  *             timekeeping code to verify comparability of two cycle values.
  *             The default ID, CSID_GENERIC, does not identify a specific
  *             clocksource.
+ * @use_nsecs: @cycles is in nanoseconds.
  */
 struct system_counterval_t {
        u64                     cycles;
        enum clocksource_ids    cs_id;
+       bool                    use_nsecs;
 };
 
 /*
index 4e18db1819f842a6e9d2ce4837519e688b67ca06..3096e10e0a1de0f2461cce9ef1568d9d8e036b0a 100644 (file)
@@ -1195,6 +1195,46 @@ static bool timestamp_in_interval(u64 start, u64 end, u64 ts)
        return false;
 }
 
+static bool convert_clock(u64 *val, u32 numerator, u32 denominator)
+{
+       u64 rem, res;
+
+       if (!numerator || !denominator)
+               return false;
+
+       res = div64_u64_rem(*val, denominator, &rem) * numerator;
+       *val = res + div_u64(rem * numerator, denominator);
+       return true;
+}
+
+static bool convert_base_to_cs(struct system_counterval_t *scv)
+{
+       struct clocksource *cs = tk_core.timekeeper.tkr_mono.clock;
+       struct clocksource_base *base;
+       u32 num, den;
+
+       /* The timestamp was taken from the time keeper clock source */
+       if (cs->id == scv->cs_id)
+               return true;
+
+       /*
+        * Check whether cs_id matches the base clock. Prevent the compiler from
+        * re-evaluating @base as the clocksource might change concurrently.
+        */
+       base = READ_ONCE(cs->base);
+       if (!base || base->id != scv->cs_id)
+               return false;
+
+       num = scv->use_nsecs ? cs->freq_khz : base->numerator;
+       den = scv->use_nsecs ? USEC_PER_SEC : base->denominator;
+
+       if (!convert_clock(&scv->cycles, num, den))
+               return false;
+
+       scv->cycles += base->offset;
+       return true;
+}
+
 /**
  * get_device_system_crosststamp - Synchronously capture system/device timestamp
  * @get_time_fn:       Callback to get simultaneous device time and
@@ -1241,7 +1281,7 @@ int get_device_system_crosststamp(int (*get_time_fn)
                 * installed timekeeper clocksource
                 */
                if (system_counterval.cs_id == CSID_GENERIC ||
-                   tk->tkr_mono.clock->id != system_counterval.cs_id)
+                   !convert_base_to_cs(&system_counterval))
                        return -ENODEV;
                cycles = system_counterval.cycles;