Merge branch 'timers/vfs' into timers/core

Pick up the VFS specific interfaces so further timekeeping changes can be
based on them.
This commit is contained in:
Thomas Gleixner 2024-10-06 21:00:01 +02:00
commit b7f6d3a09d
4 changed files with 138 additions and 0 deletions

View File

@ -45,6 +45,11 @@ extern void ktime_get_real_ts64(struct timespec64 *tv);
extern void ktime_get_coarse_ts64(struct timespec64 *ts);
extern void ktime_get_coarse_real_ts64(struct timespec64 *ts);
/* Multigrain timestamp interfaces */
extern void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts);
extern void ktime_get_real_ts64_mg(struct timespec64 *ts);
extern unsigned long timekeeping_get_mg_floor_swaps(void);
void getboottime64(struct timespec64 *ts);
/*

View File

@ -114,6 +114,23 @@ static struct tk_fast tk_fast_raw ____cacheline_aligned = {
.base[1] = FAST_TK_INIT,
};
/*
* Multigrain timestamps require tracking the latest fine-grained timestamp
* that has been issued, and never returning a coarse-grained timestamp that is
* earlier than that value.
*
* mg_floor represents the latest fine-grained time that has been handed out as
* a file timestamp on the system. This is tracked as a monotonic ktime_t, and
* converted to a realtime clock value on an as-needed basis.
*
* Maintaining mg_floor ensures the multigrain interfaces never issue a
* timestamp earlier than one that has been previously issued.
*
* The exception to this rule is when there is a backward realtime clock jump. If
* such an event occurs, a timestamp can appear to be earlier than a previous one.
*/
static __cacheline_aligned_in_smp atomic64_t mg_floor;
static inline void tk_normalize_xtime(struct timekeeper *tk)
{
while (tk->tkr_mono.xtime_nsec >= ((u64)NSEC_PER_SEC << tk->tkr_mono.shift)) {
@ -2408,6 +2425,94 @@ void ktime_get_coarse_real_ts64(struct timespec64 *ts)
}
EXPORT_SYMBOL(ktime_get_coarse_real_ts64);
/**
* ktime_get_coarse_real_ts64_mg - return latter of coarse grained time or floor
* @ts: timespec64 to be filled
*
* Fetch the global mg_floor value, convert it to realtime and compare it
* to the current coarse-grained time. Fill @ts with whichever is
* latest. Note that this is a filesystem-specific interface and should be
* avoided outside of that context.
*/
void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts)
{
struct timekeeper *tk = &tk_core.timekeeper;
u64 floor = atomic64_read(&mg_floor);
ktime_t f_real, offset, coarse;
unsigned int seq;
do {
seq = read_seqcount_begin(&tk_core.seq);
*ts = tk_xtime(tk);
offset = tk_core.timekeeper.offs_real;
} while (read_seqcount_retry(&tk_core.seq, seq));
coarse = timespec64_to_ktime(*ts);
f_real = ktime_add(floor, offset);
if (ktime_after(f_real, coarse))
*ts = ktime_to_timespec64(f_real);
}
/**
* ktime_get_real_ts64_mg - attempt to update floor value and return result
* @ts: pointer to the timespec to be set
*
* Get a monotonic fine-grained time value and attempt to swap it into
* mg_floor. If that succeeds then accept the new floor value. If it fails
* then another task raced in during the interim time and updated the
* floor. Since any update to the floor must be later than the previous
* floor, either outcome is acceptable.
*
* Typically this will be called after calling ktime_get_coarse_real_ts64_mg(),
* and determining that the resulting coarse-grained timestamp did not effect
* a change in ctime. Any more recent floor value would effect a change to
* ctime, so there is no need to retry the atomic64_try_cmpxchg() on failure.
*
* @ts will be filled with the latest floor value, regardless of the outcome of
* the cmpxchg. Note that this is a filesystem specific interface and should be
* avoided outside of that context.
*/
void ktime_get_real_ts64_mg(struct timespec64 *ts)
{
struct timekeeper *tk = &tk_core.timekeeper;
ktime_t old = atomic64_read(&mg_floor);
ktime_t offset, mono;
unsigned int seq;
u64 nsecs;
do {
seq = read_seqcount_begin(&tk_core.seq);
ts->tv_sec = tk->xtime_sec;
mono = tk->tkr_mono.base;
nsecs = timekeeping_get_ns(&tk->tkr_mono);
offset = tk_core.timekeeper.offs_real;
} while (read_seqcount_retry(&tk_core.seq, seq));
mono = ktime_add_ns(mono, nsecs);
/*
* Attempt to update the floor with the new time value. As any
* update must be later then the existing floor, and would effect
* a change to ctime from the perspective of the current task,
* accept the resulting floor value regardless of the outcome of
* the swap.
*/
if (atomic64_try_cmpxchg(&mg_floor, &old, mono)) {
ts->tv_nsec = 0;
timespec64_add_ns(ts, nsecs);
timekeeping_inc_mg_floor_swaps();
} else {
/*
* Another task changed mg_floor since "old" was fetched.
* "old" has been updated with the latest value of "mg_floor".
* That value is newer than the previous floor value, which
* is enough to effect a change to ctime. Accept it.
*/
*ts = ktime_to_timespec64(ktime_add(old, offset));
}
}
void ktime_get_coarse_ts64(struct timespec64 *ts)
{
struct timekeeper *tk = &tk_core.timekeeper;

View File

@ -17,6 +17,9 @@
#define NUM_BINS 32
/* Incremented every time mg_floor is updated */
DEFINE_PER_CPU(unsigned long, timekeeping_mg_floor_swaps);
static unsigned int sleep_time_bin[NUM_BINS] = {0};
static int tk_debug_sleep_time_show(struct seq_file *s, void *data)
@ -53,3 +56,13 @@ void tk_debug_account_sleep_time(const struct timespec64 *t)
(s64)t->tv_sec, t->tv_nsec / NSEC_PER_MSEC);
}
unsigned long timekeeping_get_mg_floor_swaps(void)
{
unsigned long sum = 0;
int cpu;
for_each_possible_cpu(cpu)
sum += data_race(per_cpu(timekeeping_mg_floor_swaps, cpu));
return sum;
}

View File

@ -10,9 +10,24 @@
* timekeeping debug functions
*/
#ifdef CONFIG_DEBUG_FS
DECLARE_PER_CPU(unsigned long, timekeeping_mg_floor_swaps);
static inline void timekeeping_inc_mg_floor_swaps(void)
{
this_cpu_inc(timekeeping_mg_floor_swaps);
}
extern void tk_debug_account_sleep_time(const struct timespec64 *t);
#else
#define tk_debug_account_sleep_time(x)
static inline void timekeeping_inc_mg_floor_swaps(void)
{
}
#endif
#ifdef CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE