From 034b5779b85b8ce2eacf235f41af15c21d10cf54 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Thu, 25 Jun 2026 22:09:01 +0800 Subject: [PATCH 01/58] hrtimer: Remove unused clock_base_next_timer_safe() clock_base_next_timer_safe() was added by commit a64ad57e41c7 ("hrtimer: Simplify run_hrtimer_queues()") but has never had a caller; the queue iteration in __hrtimer_run_queues() uses clock_base_next_timer() instead. The two are functionally equivalent: struct hrtimer embeds the timerqueue node at offset 0, so container_of() of a NULL node yields NULL. Thus clock_base_next_timer() already returns NULL on an empty queue and the explicit NULL check in the _safe variant is redundant. Being a static __always_inline function it does not trigger -Wunused-function, so the dead code has gone unnoticed. Remove it. No functional change. Signed-off-by: Zhan Xusheng Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260625140901.929554-1-zhanxusheng@xiaomi.com --- kernel/time/hrtimer.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 313dcea127fe..2a278ae550a1 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -2060,13 +2060,6 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc base->running = NULL; } -static __always_inline struct hrtimer *clock_base_next_timer_safe(struct hrtimer_clock_base *base) -{ - struct timerqueue_linked_node *next = timerqueue_linked_first(&base->active); - - return next ? hrtimer_from_timerqueue_node(next) : NULL; -} - static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now, unsigned long flags, unsigned int active_mask) { From a73d7f98e41a96d6e1bcb0e731ab185d9d67878e Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sat, 4 Jul 2026 13:09:36 +0200 Subject: [PATCH 02/58] posix-cpu-timers: Don't abuse lock_task_sighand() in handle_posix_cpu_timers() After commit f90fff1e152d ("posix-cpu-timers: fix race between handle_posix_cpu_timers() and posix_cpu_timer_del()"), tsk->sighand is stable in handle_posix_cpu_timers(), so it can use the plain spin_lock_irqsave(&tsk->sighand->siglock). Signed-off-by: Oleg Nesterov Signed-off-by: Thomas Gleixner Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/akjp8AGpY8eJG5I1@redhat.com --- kernel/time/posix-cpu-timers.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index 5e633d8750d1..c8a9b52cc937 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -1300,8 +1300,11 @@ static void handle_posix_cpu_timers(struct task_struct *tsk) unsigned long flags, start; LIST_HEAD(firing); - if (!lock_task_sighand(tsk, &flags)) - return; + /* + * tsk is current and ->sighand is stable, see the + * tsk->exit_state check in run_posix_cpu_timers() + */ + spin_lock_irqsave(&tsk->sighand->siglock, flags); do { /* @@ -1361,7 +1364,7 @@ static void handle_posix_cpu_timers(struct task_struct *tsk) * that gets the timer lock before we do will give it up and * spin until we've taken care of that timer below. */ - unlock_task_sighand(tsk, &flags); + spin_unlock_irqrestore(&tsk->sighand->siglock, flags); /* * Now that all the timers on our list have the firing flag, From e2904ddb14a4198ad31eb12a072a6923f0c8ca09 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 5 Jul 2026 14:38:04 +0200 Subject: [PATCH 03/58] timekeeping: Document monotonic raw timestamps in snapshots correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comments related to raw monotonic timestamps for the various snapshot mechanisms in code and struct documentation are ambiguous. They reference them as CLOCK_MONOTONIC_RAW timestamps, but with the arrival of AUX clocks that's not longer correct. The raw monotonic timestamps only represent CLOCK_MONOTONIC_RAW for the system time clock IDs, i.e. REALTIME, MONOTONIC, BOOTTIME, TAI. For AUX clocks they refer to the monotonic raw clock which is related to the individual AUX clocks. These monotonic raw timestamps have the same conversion factor as CLOCK_MONOTONIC_RAW, but differ from that by an offset: MONORAW(AUX$N) = MONORAW(SYSTEM) + OFFSET(AUX$N) The offset is established when a AUX clock is enabled and stays constant for the lifetime of the AUX clock. Update the comments so they reflect reality. Reported-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/87wlv9k3wz.ffs@fw13 --- include/linux/timekeeping.h | 10 +++++++++- kernel/time/timekeeping.c | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 984a866d293b..b4191a873c39 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -276,7 +276,7 @@ static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { ret #endif /** - * struct system_time_snapshot - Simultaneous time capture of CLOCK_MONOTONIC_RAW, + * struct system_time_snapshot - Simultaneous time capture of monotonic raw time, * a selected CLOCK_* and the clocksource counter value * @cycles: Clocksource counter value to produce the system times * @hw_cycles: For derived clocksources, the hardware counter value from @@ -289,6 +289,10 @@ static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { ret * @clock_was_set_seq: The sequence number of clock-was-set events * @cs_was_changed_seq: The sequence number of clocksource change events * @valid: True if the snapshot is valid + * + * @monoraw is CLOCK_MONOTONIC_RAW for system time CLOCK ids. For CLOCK_AUX$N + * clock ids it's the monotonic raw time related to the AUX clock, which is + * CLOCK_MONOTONIC_RAW plus a AUX clock specific offset. */ struct system_time_snapshot { u64 cycles; @@ -326,6 +330,10 @@ struct system_counterval_t { * @sys_counter: Clocksource counter value simultaneous with device time * @sys_systime: System time for @clock_id * @sys_monoraw: Monotonic raw simultaneous with device time + * + * @sys_monoraw is CLOCK_MONOTONIC_RAW for system time CLOCK ids. For + * CLOCK_AUX$N clock ids it's the monotonic raw time related to the AUX clock, + * which is CLOCK_MONOTONIC_RAW plus a AUX clock specific offset. */ struct system_device_crosststamp { clockid_t clock_id; diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index b1b5ec43c0f2..5985d6652c1d 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1202,10 +1202,21 @@ static inline u64 tk_clock_read_snapshot(const struct tk_read_base *tkr, /** * ktime_get_snapshot_id - Simultaneously snapshot a given clock ID with - * CLOCK_MONOTONIC_RAW and the underlying + * the corresponding monotonic raw and the underlying * clocksource counter value. * @clock_id: The clock ID to snapshot * @systime_snapshot: Pointer to struct receiving the system time snapshot + * + * For the system time keeping clocks (REALTIME, MONOTONIC and BOOTTIME) the + * monotonic raw clock is CLOCK_MONOTONIC_RAW. For AUX clocks this is the + * monotonic raw clock related to the AUX clock. These AUX clock related + * monotonic raw clocks have a strict linear offset to the system time + * CLOCK_MONOTONIC_RAW: + * + * MONOTONIC_RAW(AUX$N) = CLOCK_MONOTONIC_RAW(system) + offset(AUX$N) + * + * The offset is established when a AUX clock is initialized, but it is + * currently not accessible. */ void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot) { @@ -1512,6 +1523,9 @@ EXPORT_SYMBOL_GPL(ktime_real_to_base_clock); * @xtstamp: Receives simultaneously captured system and device time * * Reads a timestamp from a device and correlates it to system time + * + * See documentation for ktime_get_snapshot_id() for information about the raw + * monotonic time stamp which is used here. */ int get_device_system_crosststamp(int (*get_time_fn) (ktime_t *device_time, From d8966ca88566d94de9958a84f93bd08bfc431ee6 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 6 Jul 2026 16:45:56 +0800 Subject: [PATCH 04/58] hrtimer: Remove unused next_timer argument from __hrtimer_reprogram() __hrtimer_reprogram() takes a @next_timer argument but never references it; it only stores @expires_next into cpu_base->expires_next and reprograms the clock event device. The argument has been unused since commit b14bca97c9f5 ("hrtimer: Consolidate reprogramming code"). Drop it and update the two callers. No functional change. Signed-off-by: Zhan Xusheng Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260706084557.3845091-2-zhanxusheng@xiaomi.com --- kernel/time/hrtimer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 2a278ae550a1..9c320ab7a944 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -707,7 +707,7 @@ static inline void hrtimer_rearm_event(ktime_t expires_next, bool deferred) tick_program_event(expires_next, 1); } -static void __hrtimer_reprogram(struct hrtimer_cpu_base *cpu_base, struct hrtimer *next_timer, +static void __hrtimer_reprogram(struct hrtimer_cpu_base *cpu_base, ktime_t expires_next) { cpu_base->expires_next = expires_next; @@ -743,7 +743,7 @@ static void hrtimer_force_reprogram(struct hrtimer_cpu_base *cpu_base, bool skip if (skip_equal && expires_next == cpu_base->expires_next) return; - __hrtimer_reprogram(cpu_base, cpu_base->next_timer, expires_next); + __hrtimer_reprogram(cpu_base, expires_next); } /* High resolution timer related functions */ @@ -896,7 +896,7 @@ static void hrtimer_reprogram(struct hrtimer *timer, bool reprogram) cpu_base->next_timer = timer; - __hrtimer_reprogram(cpu_base, timer, expires); + __hrtimer_reprogram(cpu_base, expires); } static bool update_needs_ipi(struct hrtimer_cpu_base *cpu_base, unsigned int active) From b3afded935a8528d3df57198aaa90651995622c5 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 6 Jul 2026 16:45:57 +0800 Subject: [PATCH 05/58] clocksource: Remove unused WATCHDOG_INTERVAL_NS macro WATCHDOG_INTERVAL_NS has been unused since it was introduced by commit 763aacf86f1b ("clocksource: Rewrite watchdog code completely"); the watchdog timer rearming uses WATCHDOG_INTERVAL (in jiffies) directly and the nanosecond variant has never had a user. Remove the dead macro. No functional change. Signed-off-by: Zhan Xusheng Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260706084557.3845091-3-zhanxusheng@xiaomi.com --- kernel/time/clocksource.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c index e48c4d379a7c..78c91b0037f7 100644 --- a/kernel/time/clocksource.c +++ b/kernel/time/clocksource.c @@ -123,7 +123,6 @@ static atomic_t watchdog_reset_pending; /* Watchdog interval: 0.5sec. */ #define WATCHDOG_INTERVAL (HZ >> 1) -#define WATCHDOG_INTERVAL_NS (WATCHDOG_INTERVAL * (NSEC_PER_SEC / HZ)) /* Maximum time between two reference watchdog readouts */ #define WATCHDOG_READOUT_MAX_NS (50U * NSEC_PER_USEC) From b4b66151a71445f3a71574136ddc82968c7b175e Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 2 Jul 2026 17:39:15 +0800 Subject: [PATCH 06/58] selftests: timers: leap-a-day: Fix -w option and update usage comment Commit 98b74e1f3104 ("kselftests: timers: leap-a-day: Change default arguments to help test runs") replaced the -s option with -w and made "wait for the leap second" the non-default behaviour, but it only updated the switch/case handling. Two things were left inconsistent: - The getopt() option string still lists 's' instead of 'w', so passing -w is rejected as an invalid option and the new behaviour cannot be selected at all. - The file header comment still documents the removed -s option and an outdated default for -i. Fix the getopt() string to accept 'w' (matching the existing case 'w': handler) and update the header comment to describe -w, -t and the current -i default. Fixes: 98b74e1f3104 ("kselftests: timers: leap-a-day: Change default arguments to help test runs") Signed-off-by: Jiangshan Yi Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260702093915.2652638-1-yijiangshan@kylinos.cn --- tools/testing/selftests/timers/leap-a-day.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/timers/leap-a-day.c b/tools/testing/selftests/timers/leap-a-day.c index 3568cfb3e815..97c8c66bea88 100644 --- a/tools/testing/selftests/timers/leap-a-day.c +++ b/tools/testing/selftests/timers/leap-a-day.c @@ -9,16 +9,19 @@ * kernel's leap-second behavior, as well as how well applications * handle the leap-second discontinuity. * - * Usage: leap-a-day [-s] [-i ] + * Usage: leap-a-day [-w] [-i ] [-t] * * Options: - * -s: Each iteration, set the date to 10 seconds before midnight GMT. - * This speeds up the number of leapsecond transitions tested, - * but because it calls settimeofday frequently, advancing the - * time by 24 hours every ~16 seconds, it may cause application - * disruption. + * -w: Only set the leap-second flag and wait for the leap second + * each iteration, instead of advancing the time. By default the + * date is set to 10 seconds before midnight GMT, which speeds up + * the number of leapsecond transitions tested, but because it + * calls settimeofday frequently, advancing the time by 24 hours + * every ~16 seconds, it may cause application disruption. * - * -i: Number of iterations to run (default: infinite) + * -i: Number of iterations to run (-1 = infinite, default: 10) + * + * -t: Print TAI time. * * Other notes: Disabling NTP prior to running this is advised, as the two * may conflict in their commands to the kernel. @@ -186,7 +189,7 @@ int main(int argc, char **argv) int opt; /* Process arguments */ - while ((opt = getopt(argc, argv, "sti:")) != -1) { + while ((opt = getopt(argc, argv, "wti:")) != -1) { switch (opt) { case 'w': printf("Only setting leap-flag, not changing time. It could take up to a day for leap to trigger.\n"); From 3dee6537e728bd8137fda6eaf859f26e685943f7 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 2 Jul 2026 17:57:33 -0400 Subject: [PATCH 07/58] clocksource: Unregister subsystem on device registration failure init_clocksource_sysfs() registers the clocksource subsystem before registering the clocksource device. If device_register() fails, the function returns the error while leaving the subsystem registered. Unregister the clocksource subsystem on that failure path so the successful subsystem registration is unwound before returning. Fixes: d369a5d8fc70 ("clocksource: convert sysdev_class to a regular subsystem") Signed-off-by: Yuho Choi Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702215733.84588-1-dbgh9129@gmail.com --- kernel/time/clocksource.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/time/clocksource.c b/kernel/time/clocksource.c index 78c91b0037f7..f1253f5795c6 100644 --- a/kernel/time/clocksource.c +++ b/kernel/time/clocksource.c @@ -1565,8 +1565,12 @@ static int __init init_clocksource_sysfs(void) { int error = subsys_system_register(&clocksource_subsys, NULL); - if (!error) - error = device_register(&device_clocksource); + if (error) + return error; + + error = device_register(&device_clocksource); + if (error) + bus_unregister(&clocksource_subsys); return error; } From f2eee7e31ccd4bc87d047d8670cc2ec39cf36647 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Fri, 3 Jul 2026 12:53:37 -0400 Subject: [PATCH 08/58] timekeeping: Unwind aux clock sysfs children on failure tk_aux_sysfs_init() creates one child kobject per auxiliary clock. If a later child or sysfs group creation fails, the current error path only puts the parent kobjects and leaves earlier children and groups behind. Store the child kobjects during init and remove the successfully created groups and kobjects on failure. Fixes: 7b5ab04f035f ("timekeeping: Fix resource leak in tk_aux_sysfs_init() error paths") Signed-off-by: Yuho Choi Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260703165337.168445-1-dbgh9129@gmail.com --- kernel/time/timekeeping.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 5985d6652c1d..2527a483d75c 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -3327,7 +3327,9 @@ static const struct attribute_group aux_clock_enable_attr_group = { static int __init tk_aux_sysfs_init(void) { struct kobject *auxo, *tko = kobject_create_and_add("time", kernel_kobj); + struct kobject *clks[MAX_AUX_CLOCKS]; int ret = -ENOMEM; + int i; if (!tko) return ret; @@ -3336,21 +3338,28 @@ static int __init tk_aux_sysfs_init(void) if (!auxo) goto err_clean; - for (int i = 0; i < MAX_AUX_CLOCKS; i++) { + for (i = 0; i < MAX_AUX_CLOCKS; i++) { char id[2] = { [0] = '0' + i, }; - struct kobject *clk = kobject_create_and_add(id, auxo); + clks[i] = kobject_create_and_add(id, auxo); - if (!clk) { + if (!clks[i]) { ret = -ENOMEM; - goto err_clean; + goto err_clks; } - ret = sysfs_create_group(clk, &aux_clock_enable_attr_group); + ret = sysfs_create_group(clks[i], &aux_clock_enable_attr_group); if (ret) - goto err_clean; + goto err_clk; } return 0; +err_clk: + kobject_put(clks[i]); +err_clks: + while (--i >= 0) { + sysfs_remove_group(clks[i], &aux_clock_enable_attr_group); + kobject_put(clks[i]); + } err_clean: kobject_put(auxo); kobject_put(tko); From eddfded4196542deda7cb2da3d7ebef83f7ccfa4 Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sat, 4 Jul 2026 14:25:33 +0530 Subject: [PATCH 09/58] timers/migration: Fix memory leak in tmigr_setup_groups() error path When the WARN_ON_ONCE(i >= tmigr_hierarchy_levels) assertion triggers, the function returns -EINVAL without freeing the 'stack' memory allocated via kzalloc_objs() at the beginning of the function. Add kfree(stack) before returning to prevent the memory leak. Fixes: 6c181b5667ee ("timers/migration: Convert "while" loops to use "for"") Signed-off-by: Malaya Kumar Rout Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260704085533.87098-1-malayarout91@gmail.com --- kernel/time/timer_migration.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index 806c23cf71fc..059d43355e65 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -1847,8 +1847,10 @@ static int tmigr_setup_groups(struct tmigr_hierarchy *hier, unsigned int cpu, } /* Assert single root without parent */ - if (WARN_ON_ONCE(i >= tmigr_hierarchy_levels)) + if (WARN_ON_ONCE(i >= tmigr_hierarchy_levels)) { + kfree(stack); return -EINVAL; + } for (; i >= start_lvl; i--) { group = stack[i]; From 06aba58e58492d2b8eae059274caed29025ea96e Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sat, 4 Jul 2026 15:04:28 +0530 Subject: [PATCH 10/58] time/namespace: Validate nanosecond field in proc_timens_set_offset() The function validates tv_sec to be within [-KTIME_SEC_MAX, KTIME_SEC_MAX] but never validates that tv_nsec is within the valid range of [0, NSEC_PER_SEC-1] before using it in timespec64_add(). timespec64_add() expects both timespec64 structures to have normalized values with tv_nsec in the range [0, 999999999]. If off->val.tv_nsec contains invalid values (negative or >= NSEC_PER_SEC), it could lead to incorrect calculations or unexpected behavior. Add validation to ensure tv_nsec is within the valid range before performing the addition. Fixes: 04a8682a71be ("fs/proc: Introduce /proc/pid/timens_offsets") Signed-off-by: Malaya Kumar Rout Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260704093429.89350-1-malayarout91@gmail.com --- kernel/time/namespace.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/time/namespace.c b/kernel/time/namespace.c index 5fa0af66cf3f..3aff27bb0a15 100644 --- a/kernel/time/namespace.c +++ b/kernel/time/namespace.c @@ -293,10 +293,12 @@ int proc_timens_set_offset(struct file *file, struct task_struct *p, return -EINVAL; } - if (off->val.tv_sec > KTIME_SEC_MAX || - off->val.tv_sec < -KTIME_SEC_MAX) + if (off->val.tv_sec > KTIME_SEC_MAX || off->val.tv_sec < -KTIME_SEC_MAX) return -ERANGE; + if (off->val.tv_nsec < 0 || off->val.tv_nsec >= NSEC_PER_SEC) + return -EINVAL; + tp = timespec64_add(tp, off->val); /* * KTIME_SEC_MAX is divided by 2 to be sure that KTIME_MAX is From 1d28a67d496f482933b0ba5d8577835bdbe601ca Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Tue, 7 Jul 2026 20:58:13 +0800 Subject: [PATCH 11/58] timer_list: Annotate print_cpu() diagnostic reads print_cpu() prints hrtimer_cpu_base and tick_sched state without synchronizing with concurrent updates. The output is diagnostic only, so use data_race(READ_ONCE()) for these scalar reads to document the intentional races and avoid KCSAN reports. Reported-by: syzbot+8f0e958900a14d08a51d@syzkaller.appspotmail.com Signed-off-by: Yu Peng Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707125813.2182532-1-pengyu@kylinos.cn Closes: https://syzkaller.appspot.com/bug?extid=8f0e958900a14d08a51d --- kernel/time/timer_list.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index 514802def1e0..8823e7dcfce6 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -118,12 +118,15 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) SEQ_printf(m, " clock %d:\n", i); print_base(m, cpu_base->clock_base + i, now); } -#define P(x) \ + +#define DIAG_READ(x) data_race(READ_ONCE(x)) + +#define P(x) \ SEQ_printf(m, " .%-15s: %Lu\n", #x, \ - (unsigned long long)(cpu_base->x)) + (unsigned long long)DIAG_READ(cpu_base->x)) #define P_ns(x) \ SEQ_printf(m, " .%-15s: %Lu nsecs\n", #x, \ - (unsigned long long)(ktime_to_ns(cpu_base->x))) + (unsigned long long)ktime_to_ns(DIAG_READ(cpu_base->x))) #ifdef CONFIG_HIGH_RES_TIMERS P_ns(expires_next); @@ -139,12 +142,12 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) #ifdef CONFIG_TICK_ONESHOT # define P(x) \ SEQ_printf(m, " .%-15s: %Lu\n", #x, \ - (unsigned long long)(ts->x)) + (unsigned long long)DIAG_READ(ts->x)) # define P_ns(x) \ SEQ_printf(m, " .%-15s: %Lu nsecs\n", #x, \ - (unsigned long long)(ktime_to_ns(ts->x))) + (unsigned long long)ktime_to_ns(DIAG_READ(ts->x))) # define P_flag(x, f) \ - SEQ_printf(m, " .%-15s: %d\n", #x, !!(ts->flags & (f))) + SEQ_printf(m, " .%-15s: %d\n", #x, !!(DIAG_READ(ts->flags) & (f))) { struct tick_sched *ts = tick_get_tick_sched(cpu); @@ -166,6 +169,8 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) #undef P #undef P_ns +#undef P_flag +#undef DIAG_READ SEQ_printf(m, "\n"); } From c4415c993fc2c8bdf2cf0bfbcbb1ac0e0f7a9eaf Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 7 Jul 2026 08:38:00 -0700 Subject: [PATCH 12/58] hrtimer: Don't take cpu_base::lock in hrtimer_get_next_event() when hres_active hrtimer_get_next_event() runs on every tick-stop decision via get_next_timer_interrupt() -> cmp_next_hrtimer_event(). When high resolution timers are active it must return KTIME_MAX -- the caller documents and depends on this. The function takes cpu_base->lock, checks hres_active, and returns KTIME_MAX. Taking the lock is not required because cpu_base->hres_active is only written by the local CPU in hrtimer_switch_to_hres() from hard interrupt context and in hrtimers_cpu_starting() during bring-up. All callers of hrtimer_get_next_event() reach it from the tick-stop / cpuidle paths with interrupts disabled on that CPU. No writer can therefore race with the read, so an unlocked hres_active check is stable and the lock can be skipped in this case. On a 176-thread AMD EPYC 9D64 running a production workload, bucketing callers of native_queued_spin_lock_slowpath(), the slowpath had 199 samples in total, of which 54 are attributed to hrtimer_get_next_event(), i.e. this accounts for ~27% of slowpath hits on this specific workload. Move the hres_active check before the lock guard region to address this. [ tglx: Massaged change log and comments ] Signed-off-by: Usama Arif Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707153800.542394-1-usama.arif@linux.dev --- kernel/time/hrtimer.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 9c320ab7a944..697816d0dc26 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1786,13 +1786,21 @@ EXPORT_SYMBOL_GPL(__hrtimer_get_remaining); ktime_t hrtimer_get_next_event(void) { struct hrtimer_cpu_base *cpu_base = this_cpu_ptr(&hrtimer_bases); - ktime_t expires = KTIME_MAX; + + /* + * When HRES is active cmp_next_hrtimer_event() expects KTIME_MAX. + * + * cpu_base->hres_active is written only by the local CPU in + * hrtimer_switch_to_hres() from hard interrupt context and in + * hrtimers_cpu_starting() during CPU bring-up, and all callers reach + * this with interrupts disabled on the same CPU, so an unlocked read is + * stable without holding the lock. + */ + if (hrtimer_hres_active(cpu_base)) + return KTIME_MAX; guard(raw_spinlock_irqsave)(&cpu_base->lock); - if (!hrtimer_hres_active(cpu_base)) - expires = __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL); - - return expires; + return __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL); } /** From 03b5d4c2798234d9ee3c4a719a2fefe785a6aec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:41:58 +0200 Subject: [PATCH 13/58] hrtimer: Rename hrtimer_defs.h to hrtimer_bases.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This header was originally added for #defines and was later extended with the hrtimer base structures. All the #defines have been removed in the meantime, so the naming is off now. Rename the header to fit its contents more. This will also make the upcoming addition of some functions nicer. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-1-c50b19bda473@linutronix.de --- include/linux/hrtimer.h | 2 +- include/linux/{hrtimer_defs.h => hrtimer_bases.h} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename include/linux/{hrtimer_defs.h => hrtimer_bases.h} (98%) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 6862dea0acc5..8aa58520a816 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -12,7 +12,7 @@ #ifndef _LINUX_HRTIMER_H #define _LINUX_HRTIMER_H -#include +#include #include #include #include diff --git a/include/linux/hrtimer_defs.h b/include/linux/hrtimer_bases.h similarity index 98% rename from include/linux/hrtimer_defs.h rename to include/linux/hrtimer_bases.h index 52ed9e46ff13..8c10f45dc469 100644 --- a/include/linux/hrtimer_defs.h +++ b/include/linux/hrtimer_bases.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _LINUX_HRTIMER_DEFS_H -#define _LINUX_HRTIMER_DEFS_H +#ifndef _LINUX_HRTIMER_BASES_H +#define _LINUX_HRTIMER_BASES_H #include #include From d3dc7fabd4c4a3baaa7e7bfa84558fedad699580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:41:59 +0200 Subject: [PATCH 14/58] hrtimer: Move hrtimer_callback_running() to hrtimer_bases.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usage of the hrtimer base introduces a dependency on the timer base structure definitions from the widely-used hrtimer.h. Move the helper to hrtimer_bases.h to trim this dependency. Also adapt the two only callers to now include hrtimer_bases.h. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-2-c50b19bda473@linutronix.de --- include/linux/hrtimer.h | 9 --------- include/linux/hrtimer_bases.h | 10 ++++++++++ kernel/sched/fair.c | 2 ++ sound/drivers/dummy.c | 1 + 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 8aa58520a816..7bf154cf3079 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -287,15 +287,6 @@ static inline bool hrtimer_is_queued(struct hrtimer *timer) return READ_ONCE(timer->is_queued); } -/* - * Helper function to check, whether the timer is running the callback - * function - */ -static inline int hrtimer_callback_running(struct hrtimer *timer) -{ - return timer->base->running == timer; -} - /** * hrtimer_update_function - Update the timer's callback function * @timer: Timer to update diff --git a/include/linux/hrtimer_bases.h b/include/linux/hrtimer_bases.h index 8c10f45dc469..70b99e651168 100644 --- a/include/linux/hrtimer_bases.h +++ b/include/linux/hrtimer_bases.h @@ -2,6 +2,7 @@ #ifndef _LINUX_HRTIMER_BASES_H #define _LINUX_HRTIMER_BASES_H +#include #include #include #include @@ -110,4 +111,13 @@ struct hrtimer_cpu_base { } ____cacheline_aligned; +/* + * Helper function to check, whether the timer is running the callback + * function + */ +static inline int hrtimer_callback_running(struct hrtimer *timer) +{ + return timer->base->running == timer; +} + #endif diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index d78467ec6ee1..09197f8e4b76 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 7283f0f18813..ce7ab986dee6 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include From a116c7582d7f7701736801a47e3432eb7cabd674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:42:00 +0200 Subject: [PATCH 15/58] hrtimer: Move hrtimer_update_function() to hrtimer.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usage of the hrtimer base forces hrtimer.h to also expose the base structure definitions. Move the function to hrtimer.c to avoid this. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-3-c50b19bda473@linutronix.de --- include/linux/hrtimer.h | 24 ++---------------------- kernel/time/hrtimer.c | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 7bf154cf3079..81ab9849e83b 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -287,28 +287,8 @@ static inline bool hrtimer_is_queued(struct hrtimer *timer) return READ_ONCE(timer->is_queued); } -/** - * hrtimer_update_function - Update the timer's callback function - * @timer: Timer to update - * @function: New callback function - * - * Only safe to call if the timer is not enqueued. Can be called in the callback function if the - * timer is not enqueued at the same time (see the comments above HRTIMER_STATE_ENQUEUED). - */ -static inline void hrtimer_update_function(struct hrtimer *timer, - enum hrtimer_restart (*function)(struct hrtimer *)) -{ -#ifdef CONFIG_PROVE_LOCKING - guard(raw_spinlock_irqsave)(&timer->base->cpu_base->lock); - - if (WARN_ON_ONCE(hrtimer_is_queued(timer))) - return; - - if (WARN_ON_ONCE(!function)) - return; -#endif - ACCESS_PRIVATE(timer, function) = function; -} +void hrtimer_update_function(struct hrtimer *timer, + enum hrtimer_restart (*function)(struct hrtimer *)); /* Forward a hrtimer so it expires after now: */ extern u64 diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 697816d0dc26..0ac8899e2617 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1039,6 +1039,30 @@ static inline void unlock_hrtimer_base(const struct hrtimer *timer, unsigned lon raw_spin_unlock_irqrestore(&timer->base->cpu_base->lock, *flags); } +/** + * hrtimer_update_function - Update the timer's callback function + * @timer: Timer to update + * @function: New callback function + * + * Only safe to call if the timer is not enqueued. Can be called in the callback function if the + * timer is not enqueued at the same time (see the comments above HRTIMER_STATE_ENQUEUED). + */ +void hrtimer_update_function(struct hrtimer *timer, + enum hrtimer_restart (*function)(struct hrtimer *)) +{ +#ifdef CONFIG_PROVE_LOCKING + guard(raw_spinlock_irqsave)(&timer->base->cpu_base->lock); + + if (WARN_ON_ONCE(hrtimer_is_queued(timer))) + return; + + if (WARN_ON_ONCE(!function)) + return; +#endif + ACCESS_PRIVATE(timer, function) = function; +} +EXPORT_SYMBOL_GPL(hrtimer_update_function); + /** * hrtimer_forward() - forward the timer expiry * @timer: hrtimer to forward From 73fcec09d1629afa8819a866518cc0e8d58a7ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:42:01 +0200 Subject: [PATCH 16/58] tick: Explicitly include linux/hrtimer_bases.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This header uses some definitions from linux/hrtimer_bases.h. Currently this header is included transitively, which will change. Include the header explicitly. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-4-c50b19bda473@linutronix.de --- kernel/time/tick-internal.h | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h index 597d816d22e8..182974c4f21b 100644 --- a/kernel/time/tick-internal.h +++ b/kernel/time/tick-internal.h @@ -3,6 +3,7 @@ * tick internal variable and functions used by low/high res code */ #include +#include #include #include "timekeeping.h" From 95cf8bbadd103944c35c55e8c4cb85fa9a4ae84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:42:02 +0200 Subject: [PATCH 17/58] hrtimer: Explicitly include linux/hrtimer_bases.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This header uses some definitions from linux/hrtimer_bases.h. Currently this header is included transitively, which will change. Include the header explicitly. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-5-c50b19bda473@linutronix.de --- kernel/time/hrtimer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 0ac8899e2617..01890b7a5f98 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include From 071993aac72ea8e6f9986bf41101974e8faa2eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:42:03 +0200 Subject: [PATCH 18/58] hrtimer: Explicitly include some necessary headers in hrtimer_rearm.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple used types and symbols are only visible through transitive dependency chains. Include the headers explicitly as those chains are going to go away. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-6-c50b19bda473@linutronix.de --- include/linux/hrtimer_rearm.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/hrtimer_rearm.h b/include/linux/hrtimer_rearm.h index a6f2e5d5e1c7..17a81826bd9a 100644 --- a/include/linux/hrtimer_rearm.h +++ b/include/linux/hrtimer_rearm.h @@ -2,7 +2,12 @@ #ifndef _LINUX_HRTIMER_REARM_H #define _LINUX_HRTIMER_REARM_H +#include + #ifdef CONFIG_HRTIMER_REARM_DEFERRED +#include +#include +#include #include void __hrtimer_rearm_deferred(void); From 0c31af3d23e62b2ac0622bc6592793e21f915657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:42:04 +0200 Subject: [PATCH 19/58] x86/speculation: Explicitly include linux/types.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The usage of 'bool' requires linux/types.h, which is currently only included through a transitive dependency chain. Include linux/types.h as that chain is going to go away. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-7-c50b19bda473@linutronix.de --- arch/x86/include/asm/nospec-branch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/include/asm/nospec-branch.h b/arch/x86/include/asm/nospec-branch.h index 4f4b5e8a1574..6734203595fe 100644 --- a/arch/x86/include/asm/nospec-branch.h +++ b/arch/x86/include/asm/nospec-branch.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include From faef65e45a2a03f1fa32bc4e55c11d79f6aaae6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Thu, 2 Jul 2026 11:42:05 +0200 Subject: [PATCH 20/58] hrtimer: Remove inclusion of hrtimer_bases.h remove from hrtimer.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hrtimer.h is used all over the kernel. Any change to hrtimer_bases.h effectively triggers a full rebuild. As all logical dependencies from hrtimer.h to hrtimer_bases.h have been removed, the inclusion is now unncessary. Remove it. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260702-hrtimer-header-dependencies-v1-8-c50b19bda473@linutronix.de --- include/linux/hrtimer.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 81ab9849e83b..29072d89e5cb 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -12,7 +12,6 @@ #ifndef _LINUX_HRTIMER_H #define _LINUX_HRTIMER_H -#include #include #include #include From 6e435911394b05c91b92d4c332c455ec569e22fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 7 Jul 2026 08:07:14 +0200 Subject: [PATCH 21/58] timekeeping: Fold vdso_time_update_aux() declarations into the generic ifdeffery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only caller of vdso_time_update_aux() is already gated behind CONFIG_POSIX_AUX. The additional check in the header files is not necessary. Remove it and then fold the declarations into the existing CONFIG_GENERIC_GETTIMEOFDAY ifdeffery. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707-timekeeping-header-cleanup-v1-1-e85ad96409a9@linutronix.de --- include/linux/timekeeper_internal.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h index 4486dfd5d0de..58c83d6e65b3 100644 --- a/include/linux/timekeeper_internal.h +++ b/include/linux/timekeeper_internal.h @@ -194,6 +194,7 @@ struct timekeeper { extern void update_vsyscall(struct timekeeper *tk); extern void update_vsyscall_tz(void); +extern void vdso_time_update_aux(struct timekeeper *tk); #else @@ -203,12 +204,9 @@ static inline void update_vsyscall(struct timekeeper *tk) static inline void update_vsyscall_tz(void) { } -#endif - -#if defined(CONFIG_GENERIC_GETTIMEOFDAY) && defined(CONFIG_POSIX_AUX_CLOCKS) -extern void vdso_time_update_aux(struct timekeeper *tk); -#else -static inline void vdso_time_update_aux(struct timekeeper *tk) { } +static inline void vdso_time_update_aux(struct timekeeper *tk) +{ +} #endif #endif /* _LINUX_TIMEKEEPER_INTERNAL_H */ From 79bd39c58f2c6fdbc5fb6300d309606f3cb84ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 7 Jul 2026 08:07:15 +0200 Subject: [PATCH 22/58] timekeeping: Move the vDSO update declarations into a private header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All architectures are now fully using the generic vDSO infrastructure. They don't need these declarations anymore to implement the functions in architecture-specific code. Move them to the private header. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260707-timekeeping-header-cleanup-v1-2-e85ad96409a9@linutronix.de --- include/linux/timekeeper_internal.h | 19 ------------------- kernel/time/time.c | 1 + kernel/time/timekeeping_internal.h | 21 +++++++++++++++++++++ 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h index 58c83d6e65b3..264db7c9c071 100644 --- a/include/linux/timekeeper_internal.h +++ b/include/linux/timekeeper_internal.h @@ -190,23 +190,4 @@ struct timekeeper { s32 tai_offset; }; -#ifdef CONFIG_GENERIC_GETTIMEOFDAY - -extern void update_vsyscall(struct timekeeper *tk); -extern void update_vsyscall_tz(void); -extern void vdso_time_update_aux(struct timekeeper *tk); - -#else - -static inline void update_vsyscall(struct timekeeper *tk) -{ -} -static inline void update_vsyscall_tz(void) -{ -} -static inline void vdso_time_update_aux(struct timekeeper *tk) -{ -} -#endif - #endif /* _LINUX_TIMEKEEPER_INTERNAL_H */ diff --git a/kernel/time/time.c b/kernel/time/time.c index 0dd63a91e7c5..d1a7efd80bf5 100644 --- a/kernel/time/time.c +++ b/kernel/time/time.c @@ -42,6 +42,7 @@ #include #include "timekeeping.h" +#include "timekeeping_internal.h" /* * The timezone where the local system is located. Used as a default by some diff --git a/kernel/time/timekeeping_internal.h b/kernel/time/timekeeping_internal.h index 973ede670a36..6d719b8e5ea2 100644 --- a/kernel/time/timekeeping_internal.h +++ b/kernel/time/timekeeping_internal.h @@ -6,6 +6,8 @@ #include #include +struct timekeeper; + /* * timekeeping debug functions */ @@ -48,4 +50,23 @@ void timekeeper_unlock_irqrestore(unsigned long flags); /* NTP specific interface to access the current seconds value */ long ktime_get_ntp_seconds(unsigned int id); +#ifdef CONFIG_GENERIC_GETTIMEOFDAY + +extern void update_vsyscall(struct timekeeper *tk); +extern void update_vsyscall_tz(void); +extern void vdso_time_update_aux(struct timekeeper *tk); + +#else + +static inline void update_vsyscall(struct timekeeper *tk) +{ +} +static inline void update_vsyscall_tz(void) +{ +} +static inline void vdso_time_update_aux(struct timekeeper *tk) +{ +} +#endif + #endif /* _TIMEKEEPING_INTERNAL_H */ From 79ced850e549e8c86b772a79ea417a1425b5c04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 4 May 2026 08:32:26 +0200 Subject: [PATCH 23/58] y2038: uapi: Use 64-bit __kernel_old_timespec::tv_nsec on x32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'struct __kernel_old_timespec' represents the 'native' time ABI of the kernel. On 32-bit systems it uses 32-bit fields and on 64-bit systems it uses 64-bit fields. However the x86 x32 ABI uses the 64-bit time ABI natively. This is correctly handled for the 'tv_sec' fields, through the typedefs of '__kernel_old_time_t' -> '__kernel_long_t' -> 'long long'. The same treatment was missed for 'tv_nsec'. In practice this might not make much of a difference as the value of 'tv_nsec' will always fit into 32 bits and the missing bits fall into the padding of the structure. When introspecting the structure however, a difference can be observed. Switch to 64-bit tv_nsec on x32. No other architectures or ABIs are affected. While this could be interpreted as violating the POSIX requirement of 'timespec::tv_nsec' being 'long': * __kernel_old_timespec is not actually the POSIX timespec type * the requirement is gone in newer versions of POSIX * this matches glibc Fixes: 94c467ddb273 ("y2038: add __kernel_old_timespec and __kernel_old_time_t") Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260504-timespec-x32-v2-1-0739c9047fc4@linutronix.de --- include/uapi/linux/time_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/time_types.h b/include/uapi/linux/time_types.h index bcc0002115d3..03a0d8aaadca 100644 --- a/include/uapi/linux/time_types.h +++ b/include/uapi/linux/time_types.h @@ -30,7 +30,7 @@ struct __kernel_old_timeval { struct __kernel_old_timespec { __kernel_old_time_t tv_sec; /* seconds */ - long tv_nsec; /* nanoseconds */ + __kernel_long_t tv_nsec; /* nanoseconds */ }; struct __kernel_old_itimerval { From 79b8bd857bd7f5a8c970fc50c611062b96fe56e5 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:54 +0100 Subject: [PATCH 24/58] MAINTAINERS: Add Miroslav as timekeeping reviewer If Thomas is going to nudge me on IRC to add Miroslav to Cc on timekeeping patches, then he might as well actually be listed in the MAINTAINERS file. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260621220051.1030462-2-dwmw2@infradead.org --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..ff01c39e7f23 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27199,6 +27199,7 @@ TIMEKEEPING, CLOCKSOURCE CORE, NTP, ALARMTIMER M: John Stultz M: Thomas Gleixner R: Stephen Boyd +R: Miroslav Lichvar L: linux-kernel@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/core From b7befd6d91207cf3f4cecd68fea0c212093906cf Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:55 +0100 Subject: [PATCH 25/58] timekeeping: Account for monotonicity adjustment in ntp_error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit timekeeping_apply_adjustment() modifies xtime_nsec to ensure monotonicity when mult changes: xtime_nsec -= offset This ensures that the time reported to userspace does not jump when the multiplier is adjusted from one tick to the next. However, the ntp_error accumulator which tracks the difference between intended and actual clock position was not being updated to reflect this additional discrepancy. An earlier attempt at this compensation existed as: ntp_error -= (interval - offset) << ntp_error_shift but was removed in commit c2cda2a5bda9 ("timekeeping/ntp: Don't align NTP frequency adjustments to ticks") because it was a major source of NTP error. That's because (interval - offset) was wrong: the subtraction of "interval" prematurely accounted for the changed xtime_interval of the next tick, which would be correctly accounted in the next accumulation anyway — a double subtraction. What is actually needed is just the "offset" part: ntp_error must be told that xtime_nsec moved by "offset" without a corresponding change in the intended position. For the normal ±1 mult dithering this is negligible (the adjustments cancel over time), but for larger mult changes — such as when an external reference clock sets a new frequency — the one-time uncompensated offset is significant. Fix by adjusting ntp_error by the correct amount: ntp_error += offset << ntp_error_shift This keeps ntp_error consistent with the actual xtime_nsec position after the adjustment, and ensures the discrepancy is correctly smoothed away over time and the clock returns to where it should have been. Fixes: c2cda2a5bda9 ("timekeeping/ntp: Don't align NTP frequency adjustments to ticks") Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.6-1m Acked-by: John Stultz Link: https://patch.msgid.link/20260621220051.1030462-3-dwmw2@infradead.org --- kernel/time/timekeeping.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 2527a483d75c..9e1cbc590988 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -2404,6 +2404,11 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, * xtime_nsec_2 = xtime_nsec_1 - offset * Which simplifies to: * xtime_nsec -= offset + * + * When subtracting offset from xtime_nsec, the same amount + * (in appropriate units) has to be added to ntp_error, in + * order to correctly track the delta between the time + * reported in xtime_nsec, and the intended time. */ if ((mult_adj > 0) && (tk->tkr_mono.mult + mult_adj < mult_adj)) { /* NTP adjustment caused clocksource mult overflow */ @@ -2414,6 +2419,7 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, tk->tkr_mono.mult += mult_adj; tk->xtime_interval += interval; tk->tkr_mono.xtime_nsec -= offset; + tk->ntp_error += offset << tk->ntp_error_shift; } /* From 869a55e662a080a5c6618b68ae320231c720eeee Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:56 +0100 Subject: [PATCH 26/58] timekeeping: Account for clocksource tick quantisation via NTP cycle_interval is an integer number of counter cycles per NTP interval, so the real time it represents differs from the nominal NTP_INTERVAL_LENGTH by up to half a counter period. For coarse clocksources this is significant: the 3.579545 MHz ACPI PM timer at HZ=1000 rounds 3579.545 cycles up to 3580, making each tick 1.000127 ms (+127 PPM). Commit a386b5af8edd ("time: Compensate for rounding on odd-frequency clocksources") introduced xtime_remainder to compensate for exactly this, citing the same 127 PPM ACPI PM example. The compensation is correct and necessary, but it was applied inside the timekeeping accumulation in timekeeping.c: subtracted in the mult computation in timekeeping_adjust() and folded into the ntp_error update in logarithmic_accumulation(). That keeps the base rate correct and leaves NTP its full symmetric +/-MAXFREQ range rather than +373/-627 PPM, but the NTP code in ntp.c never sees it: tick_length is computed without the correction, so ntp.c's notion of how long a tick is disagrees with the rate timekeeping actually produces. Make the offset an explicit part of the NTP tick_length instead. Add ntp_data::cs_tick_adj, a fixed per-second addend that ntp_update_frequency() includes alongside ntp_tick_adj and time_freq. tk_setup_internals() computes it from the difference between the real cycle_interval duration and the nominal interval, stores it in the timekeeper, and hands it to NTP through a new argument to ntp_clear() -- which already recomputes the frequency and is invoked after every clocksource (re)configuration. timekeeping_init() now uses TK_UPDATE_ALL for this; clearing NTP there is otherwise redundant since ntp_init() has just initialised it. ntp.c now computes the true tick rate, giving a single source of truth. Like ntp_tick_adj, cs_tick_adj stays internal to the kernel: userspace still sees the nominal 1.000000 ms tick via adjtimex and is unaware of the addends. timekeeping_adjust() and logarithmic_accumulation() use ntp_tick / xtime_interval directly, and xtime_remainder is removed. The base-rate arithmetic is unchanged: ntp_tick becomes xtime_interval << ntp_error_shift, so the mult division yields the same base mult and the ntp_error accumulation still nets to zero per tick. Beyond the cleanup of treating all the tick_length contributions (nominal interval, ntp_tick_adj, cs_tick_adj, time_freq) consistently as addends in one place, it also prepares for feed-forward discipline: a future timekeeping_set_reference() will set tick_length to track an absolute external reference such as a vmclock, and that path needs ntp.c to own a tick_length that already reflects the clocksource quantisation, with no hidden correction applied elsewhere. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.8 Acked-by: John Stultz Link: https://patch.msgid.link/20260621220051.1030462-4-dwmw2@infradead.org --- include/linux/timekeeper_internal.h | 8 ++-- kernel/time/ntp.c | 27 ++++++++++-- kernel/time/ntp_internal.h | 2 +- kernel/time/timekeeping.c | 64 +++++++++++++++++++++-------- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h index 264db7c9c071..9c53f44537f0 100644 --- a/include/linux/timekeeper_internal.h +++ b/include/linux/timekeeper_internal.h @@ -84,8 +84,6 @@ struct tk_read_base { * @cycle_interval: Number of clock cycles in one NTP interval * @xtime_interval: Number of clock shifted nano seconds in one NTP * interval. - * @xtime_remainder: Shifted nano seconds left over when rounding - * @cycle_interval * @raw_interval: Shifted raw nano seconds accumulated per NTP interval. * @next_leap_ktime: CLOCK_MONOTONIC time value of a pending leap-second * @ntp_tick: The ntp_tick_length() value currently being @@ -99,6 +97,10 @@ struct tk_read_base { * @ntp_error_shift: Shift conversion between clock shifted nano seconds and * ntp shifted nano seconds. * @ntp_err_mult: Multiplication factor for scaled math conversion + * @cs_tick_adj: Per-second adjustment handed to NTP via ntp_clear() + * accounting for the difference between the nominal + * NTP interval and the real time taken by the + * clocksource's integer @cycle_interval (upscaled). * @skip_second_overflow: Flag used to avoid updating NTP twice with same second * @tai_offset: The current UTC to TAI offset in seconds * @@ -178,7 +180,6 @@ struct timekeeper { u64 cycle_interval; u64 xtime_interval; - s64 xtime_remainder; u64 raw_interval; ktime_t next_leap_ktime; @@ -186,6 +187,7 @@ struct timekeeper { s64 ntp_error; u32 ntp_error_shift; u32 ntp_err_mult; + s64 cs_tick_adj; u32 skip_second_overflow; s32 tai_offset; }; diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 97fa99b96dd0..3fad82c47c4c 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -39,6 +39,10 @@ * @time_reftime: Time at last adjustment in seconds * @time_adjust: Adjustment value * @ntp_tick_adj: Constant boot-param configurable NTP tick adjustment (upscaled) + * @cs_tick_adj: Fixed per-second adjustment compensating for the difference + * between the nominal NTP interval and the real time taken + * by the clocksource's integer @cycle_interval (upscaled). + * Set by the timekeeping core via ntp_clear(). * @ntp_next_leap_sec: Second value of the next pending leapsecond, or TIME64_MAX if no leap * * @pps_valid: PPS signal watchdog counter @@ -70,6 +74,7 @@ struct ntp_data { time64_t time_reftime; long time_adjust; s64 ntp_tick_adj; + s64 cs_tick_adj; time64_t ntp_next_leap_sec; #ifdef CONFIG_NTP_PPS int pps_valid; @@ -255,6 +260,7 @@ static void ntp_update_frequency(struct ntp_data *ntpdata) second_length = (u64)(tick_usec * NSEC_PER_USEC * USER_HZ) << NTP_SCALE_SHIFT; second_length += ntpdata->ntp_tick_adj; + second_length += ntpdata->cs_tick_adj; second_length += ntpdata->time_freq; new_base = div_u64(second_length, NTP_INTERVAL_FREQ); @@ -350,11 +356,26 @@ static void __ntp_clear(struct ntp_data *ntpdata) } /** - * ntp_clear - Clears the NTP state variables - * @tkid: Timekeeper ID to be able to select proper ntp data array member + * ntp_clear - Clear NTP state and set the clocksource quantisation adjustment + * @tkid: Timekeeper ID + * @cs_tick_adj: Per-second adjustment in ns << NTP_SCALE_SHIFT + * + * The timekeeping core uses an integer number of cycles (@cycle_interval) + * per NTP interval, so the real time that interval represents differs from + * the nominal NTP_INTERVAL_LENGTH by up to half a counter period. Folding + * this fixed offset into @cs_tick_adj makes it an explicit part of the NTP + * tick_length computation in ntp.c, instead of being applied during + * timekeeping accumulation where the NTP code never saw it. Like + * @ntp_tick_adj it stays internal to the kernel; userspace still sees the + * nominal tick via adjtimex. NTP retains its full symmetric ±MAXFREQ range + * around the corrected base rate. + * + * Called whenever the clocksource is (re)configured, which is also when the + * rest of the NTP state must be cleared, so the two are done together. */ -void ntp_clear(unsigned int tkid) +void ntp_clear(unsigned int tkid, s64 cs_tick_adj) { + tk_ntp_data[tkid].cs_tick_adj = cs_tick_adj; __ntp_clear(&tk_ntp_data[tkid]); } diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h index 7084d839c207..598e5dd2fc5b 100644 --- a/kernel/time/ntp_internal.h +++ b/kernel/time/ntp_internal.h @@ -3,7 +3,7 @@ #define _LINUX_NTP_INTERNAL_H extern void ntp_init(void); -extern void ntp_clear(unsigned int tkid); +extern void ntp_clear(unsigned int tkid, s64 cs_tick_adj); /* Returns how long ticks are at present, in ns / 2^NTP_SCALE_SHIFT. */ extern u64 ntp_tick_length(unsigned int tkid); extern ktime_t ntp_get_next_leap(unsigned int tkid); diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 9e1cbc590988..e5e4cb58ca7d 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -339,7 +339,6 @@ static inline void clocksource_enable_inline_read(void) { } static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock) { u64 interval; - u64 tmp, ntpinterval; struct clocksource *old_clock; ++tk->cs_was_changed_seq; @@ -353,20 +352,16 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock) tk->tkr_raw.cycle_last = tk->tkr_mono.cycle_last; /* Do the ns -> cycle conversion first, using original mult */ - tmp = NTP_INTERVAL_LENGTH; - tmp <<= clock->shift; - ntpinterval = tmp; - tmp += clock->mult/2; - do_div(tmp, clock->mult); - if (tmp == 0) - tmp = 1; + interval = (u64)NTP_INTERVAL_LENGTH << clock->shift; + interval += clock->mult / 2; + do_div(interval, clock->mult); + if (interval == 0) + interval = 1; - interval = (u64) tmp; tk->cycle_interval = interval; /* Go back from cycles -> shifted ns */ tk->xtime_interval = interval * clock->mult; - tk->xtime_remainder = ntpinterval - tk->xtime_interval; tk->raw_interval = interval * clock->mult; /* if changing clocks, convert xtime_nsec shift units */ @@ -386,7 +381,38 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock) tk->ntp_error = 0; tk->ntp_error_shift = NTP_SCALE_SHIFT - clock->shift; - tk->ntp_tick = ntpinterval << tk->ntp_error_shift; + + /* + * ntp_tick is the tick length that NTP disciplines (its ±500 PPM + * scales only this part), in NTP-shifted ns: the real interval of + * a whole number of counter cycles. Because cycle_interval is + * rounded to an integer number of cycles, this ntp_tick differs + * from the true intended 1/HZ tick length by up to half a cycle + * period. + */ + tk->ntp_tick = (u64)tk->xtime_interval << tk->ntp_error_shift; + + /* + * cs_tick_adj is the constant difference between the disciplined + * ntp_tick above and the true 1/HZ tick, expressed per-second to + * match the ntp_update_frequency() addends and handed to NTP via + * ntp_clear() to be explicitly included in its tick_length. + * + * Worked example: HZ=1000, ACPI PM timer at 3.579545 MHz, which + * has 3579.545 cycles in 1ms, rounded to cycle_interval = 3580. + * + * So ntp_tick is actually 1.000127ms, as that is the amount of + * time that 3580 cycles will take at the nominal frequency. This + * is the part that NTP disciplines, causing each 3580 counts to + * advance the clock by up to NTP's ±500PPM of that amount. + * + * The "extra" 127ns/tick is what's stored in cs_tick_adj and + * applied as a constant correction by ntp_update_frequency() so + * that NTP *believes* it's disciplining a 1ms tick. + */ + tk->cs_tick_adj = (s64)tk->ntp_tick - + ((s64)NTP_INTERVAL_LENGTH << NTP_SCALE_SHIFT); + tk->cs_tick_adj *= NTP_INTERVAL_FREQ; /* * The timekeeper keeps its own mult values for the currently @@ -803,7 +829,7 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act if (action & TK_CLEAR_NTP) { tk->ntp_error = 0; - ntp_clear(tk->id); + ntp_clear(tk->id, tk->cs_tick_adj); } tk_update_leap_state(tk); @@ -2090,7 +2116,12 @@ void __init timekeeping_init(void) tk_set_wall_to_mono(tks, wall_to_mono); - timekeeping_update_from_shadow(&tk_core, TK_CLOCK_WAS_SET); + /* + * Use TK_UPDATE_ALL so the NTP layer picks up the clocksource's + * cs_tick_adj via ntp_clear(). Clearing NTP here is otherwise + * redundant as ntp_init() already initialised it above. + */ + timekeeping_update_from_shadow(&tk_core, TK_UPDATE_ALL); } /* time in seconds when suspend began for persistent clock */ @@ -2439,8 +2470,8 @@ static void timekeeping_adjust(struct timekeeper *tk, s64 offset) mult = tk->tkr_mono.mult - tk->ntp_err_mult; } else { tk->ntp_tick = ntp_tl; - mult = div64_u64((tk->ntp_tick >> tk->ntp_error_shift) - - tk->xtime_remainder, tk->cycle_interval); + mult = div64_u64(tk->ntp_tick >> tk->ntp_error_shift, + tk->cycle_interval); } /* @@ -2565,8 +2596,7 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset, /* Accumulate error between NTP and clock interval */ tk->ntp_error += tk->ntp_tick << shift; - tk->ntp_error -= (tk->xtime_interval + tk->xtime_remainder) << - (tk->ntp_error_shift + shift); + tk->ntp_error -= tk->xtime_interval << (tk->ntp_error_shift + shift); return offset; } From d375af58990902e73dd62bd7c049e759bcff92a5 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:57 +0100 Subject: [PATCH 27/58] timekeeping: Drive time_offset skew via per-tick ntp_error transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the phase offset of time_offset and time_adjust is delivered by adjusting tick_length in second_overflow(), and immediately draining time_offset/time_adjust by the amount that the tick_length adjustment is *estimated* to cause. This is fairly approximate, in part because it is not always correct to assume that precisely NTP_INTERVAL_FREQ ticks will occur between one call to second_overflow() and the next. It could also over and under-run in the final second of delivery. Instead of inflating tick_length, transfer the intended skew directly into ntp_error each tick to achieve the desired rate. In second_overflow(), calculate skew_delta which is the per-tick slew rate, in the same units as time_offset: (ns << NTP_SCALE_SHIFT) / HZ. In logarithmic_accumulation(), drain up to 'skew_delta' time units from time_offset into ntp_error to drive the overall effective rate. The new ntp_drain_skew() function returns the amount which is actually 'claimed' by time_offset (and in a future patch, time_adjust). Any overrun which is delivered by the changed 'mult' (as described below) but not claimed by ntp_drain_skew() will remain in ntp_error to be corrected away in subsequent ticks. Simply transferring the precise amount from time_offset to ntp_error would be sufficent to make the time *eventually* converge, however the skew delivered is limited by the choice of { mult, mult+1 } each tick and thus the convergence would be extremely slow. In theory we could inflate ntp_err_mult with the magnitude of ntp_error in the general case — but that would cause overcorrection in a tickless kernel. Instead, in timekeeping_adjust(), take skew_delta into account when calculating 'mult', such that the available {mult, mult+1} choices bracket the overall effective rate *including* the skew, to avoid the delta just building up in ntp_error. The effect is that the inflated 'mult' causes ntp_error to grow because xtime_interval is (e.g.) longer than the true tick_length. But then the same delta is removed again as it's drained from time_offset. This gives behaviour equivalent to the old tick_length += delta approach but with exact per-tick accounting of the time_offset actually imparted to the clock, and no overrun. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.8 Link: https://patch.msgid.link/20260621220051.1030462-5-dwmw2@infradead.org --- include/linux/timekeeper_internal.h | 1 + kernel/time/ntp.c | 88 +++++++++++++++++++++++++++-- kernel/time/ntp_internal.h | 2 + kernel/time/timekeeping.c | 36 ++++++++++-- 4 files changed, 118 insertions(+), 9 deletions(-) diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h index 9c53f44537f0..9c198f65621b 100644 --- a/include/linux/timekeeper_internal.h +++ b/include/linux/timekeeper_internal.h @@ -189,6 +189,7 @@ struct timekeeper { u32 ntp_err_mult; s64 cs_tick_adj; u32 skip_second_overflow; + s64 skew_delta; s32 tai_offset; }; diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 3fad82c47c4c..064e68e7a77c 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -31,6 +31,9 @@ * @time_state: State of the clock synchronization * @time_status: Clock status bits * @time_offset: Time adjustment in nanoseconds + * @skew_delta: Per-tick phase slew rate for the coming second, in + * @time_offset units (shifted-ns / HZ). Set by + * second_overflow(). * @time_constant: PLL time constant * @time_maxerror: Maximum error in microseconds holding the NTP sync distance * (NTP dispersion + delay / 2) @@ -67,6 +70,7 @@ struct ntp_data { int time_state; int time_status; s64 time_offset; + s64 skew_delta; long time_constant; long time_maxerror; long time_esterror; @@ -349,6 +353,7 @@ static void __ntp_clear(struct ntp_data *ntpdata) ntpdata->tick_length = ntpdata->tick_length_base; ntpdata->time_offset = 0; + ntpdata->skew_delta = 0; ntpdata->ntp_next_leap_sec = TIME64_MAX; /* Clear PPS state variables */ @@ -385,6 +390,55 @@ u64 ntp_tick_length(unsigned int tkid) return tk_ntp_data[tkid].tick_length; } +s64 ntp_get_skew_delta(unsigned int tkid) +{ + return tk_ntp_data[tkid].skew_delta; +} + +/* Sign of @x as +1 or -1 (zero counts as positive; callers pass nonzero). */ +static inline int signof(s64 x) +{ + return x < 0 ? -1 : 1; +} + +static s64 ntp_drain_time_offset(unsigned int tkid, s64 amount) +{ + struct ntp_data *ntpdata = &tk_ntp_data[tkid]; + + /* Only drain if amount and time_offset have the same sign */ + if (!amount || signof(amount) != signof(ntpdata->time_offset)) + return amount; + + /* Clamp: don't overshoot zero */ + if (abs(amount) > abs(ntpdata->time_offset)) { + s64 undrained = amount - ntpdata->time_offset; + + ntpdata->time_offset = 0; + return undrained; + } + + ntpdata->time_offset -= amount; + return 0; +} + +/* + * Drain one accumulation's worth of intentional skew as it is delivered. + * + * @amount is the total intentional per-tick skew for this accumulation + * (skew_delta << shift), in time_offset units (shifted_ns / HZ). Returns + * the amount actually claimed (same ÷HZ units). + */ +s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift) +{ + s64 unclaimed = ntp_drain_time_offset(tkid, amount); + + /* + * Return the amount actually drained from the intentional + * phase offset in time_offset. + */ + return amount - unclaimed; +} + /** * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t * @tkid: Timekeeper ID @@ -419,7 +473,6 @@ ktime_t ntp_get_next_leap(unsigned int tkid) int second_overflow(unsigned int tkid, time64_t secs) { struct ntp_data *ntpdata = &tk_ntp_data[tkid]; - s64 delta; int leap = 0; s32 rem; @@ -481,13 +534,38 @@ int second_overflow(unsigned int tkid, time64_t secs) /* Compute the phase adjustment for the next second */ ntpdata->tick_length = ntpdata->tick_length_base; - delta = ntp_offset_chunk(ntpdata, ntpdata->time_offset); - ntpdata->time_offset -= delta; - ntpdata->tick_length += delta; - /* Check PPS signal */ pps_dec_valid(ntpdata); + /* + * Set the per-tick skew rate for the next second. This is in + * the same units as time_offset: (ns << NTP_SCALE_SHIFT) / HZ. + * If the result is so low that the skew imparted would round + * to zero, pass the bare minimum ±1 to ensure that it *does* + * actually drain completely to zero. It won't overshoot because + * logarithmic_accumulation() only drains what it can from + * time_offset and the rest ends up in ntp_error which drives + * the selection of 'mult' immediately each tick. + */ + if (ntpdata->time_offset) { + s64 off_chunk = ntp_offset_chunk(ntpdata, ntpdata->time_offset); + + /* + * Once the exponential chunk rounds to zero, deliver the last + * remaining offset this second so it converges to zero instead + * of stalling just above it. + */ + if (!off_chunk) + off_chunk = ntpdata->time_offset; + + /* Reduce to per-tick, then floor. */ + ntpdata->skew_delta = div_s64(off_chunk, NTP_INTERVAL_FREQ); + if (!ntpdata->skew_delta) + ntpdata->skew_delta = signof(off_chunk); + } else { + ntpdata->skew_delta = 0; + } + if (!ntpdata->time_adjust) goto out; diff --git a/kernel/time/ntp_internal.h b/kernel/time/ntp_internal.h index 598e5dd2fc5b..0474a761bafc 100644 --- a/kernel/time/ntp_internal.h +++ b/kernel/time/ntp_internal.h @@ -6,6 +6,8 @@ extern void ntp_init(void); extern void ntp_clear(unsigned int tkid, s64 cs_tick_adj); /* Returns how long ticks are at present, in ns / 2^NTP_SCALE_SHIFT. */ extern u64 ntp_tick_length(unsigned int tkid); +extern s64 ntp_get_skew_delta(unsigned int tkid); +extern s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift); extern ktime_t ntp_get_next_leap(unsigned int tkid); extern int second_overflow(unsigned int tkid, time64_t secs); extern int ntp_adjtimex(unsigned int tkid, struct __kernel_timex *txc, const struct timespec64 *ts, diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index e5e4cb58ca7d..0e646e92cd34 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -423,6 +423,7 @@ static void tk_setup_internals(struct timekeeper *tk, struct clocksource *clock) tk->tkr_raw.mult = clock->mult; tk->ntp_err_mult = 0; tk->skip_second_overflow = 0; + tk->skew_delta = 0; tk->cs_id = clock->id; @@ -2460,17 +2461,26 @@ static __always_inline void timekeeping_apply_adjustment(struct timekeeper *tk, static void timekeeping_adjust(struct timekeeper *tk, s64 offset) { u64 ntp_tl = ntp_tick_length(tk->id); + s64 skew = ntp_get_skew_delta(tk->id); u32 mult; /* - * Determine the multiplier from the current NTP tick length. - * Avoid expensive division when the tick length doesn't change. + * Determine the multiplier from the current NTP tick length plus + * skew_delta. The skew biases mult so that ±1 dithering can deliver + * the time_offset slew rate. Recompute when either changes. */ - if (likely(tk->ntp_tick == ntp_tl)) { + if (likely(tk->ntp_tick == ntp_tl && tk->skew_delta == skew)) { + /* Revert to the base mult rate. */ mult = tk->tkr_mono.mult - tk->ntp_err_mult; } else { tk->ntp_tick = ntp_tl; - mult = div64_u64(tk->ntp_tick >> tk->ntp_error_shift, + tk->skew_delta = skew; + /* + * skew_delta is stored pre-divided by HZ (matching time_offset); + * scale it back up to the full per-tick rate for the mult bias. + */ + skew *= NTP_INTERVAL_FREQ; + mult = div64_u64((tk->ntp_tick + skew) >> tk->ntp_error_shift, tk->cycle_interval); } @@ -2598,6 +2608,24 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset, tk->ntp_error += tk->ntp_tick << shift; tk->ntp_error -= tk->xtime_interval << (tk->ntp_error_shift + shift); + /* + * When skewing, do so by adjusting ntp_error to impart an extra + * target delta into ntp_error per tick, limited to what can be + * drained from time_offset to avoid overshoot. + * + * The base 'mult' value was calculated with the skew taken into + * account, such that the per-tick choice of 'mult' vs. 'mult+1' + * allows for the desired effective rate and ntp_error does not + * grow unbounded. + * + * Once the full desired phase offset is delivered, any remaining + * skew imparted by the adjusted 'mult', accounted above, remains + * in ntp_error and will be compensated by the dithering over time. + */ + if (tk->skew_delta) + tk->ntp_error += ntp_drain_skew(tk->id, tk->skew_delta << shift, + shift) * NTP_INTERVAL_FREQ; + return offset; } From 289d1759494f4a97b0ca4565f3eed00964b952c4 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:58 +0100 Subject: [PATCH 28/58] timekeeping: Drive time_adjust skew via per-tick ntp_error transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy adjtime() slew (ADJ_OFFSET_SINGLESHOT) was the last user of tick_length != tick_length_base: it slewed the clock by inflating tick_length directly, which delivered the correction imprecisely (e.g. delivering only 4997.5µs when asked for a 5ms skew). Deliver it accurately through the same per-tick mechanism that is now used for time_offset, allowing it to contribute to skew_delta and thus drive the delivery through ntp_error and mult selection. To allow for accurate accounting, store the sub-microsecond part of time_adjust is separately, while keeping time_adjust in microseconds as that's the external API. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.8 Link: https://patch.msgid.link/20260621220051.1030462-6-dwmw2@infradead.org --- kernel/time/ntp.c | 138 +++++++++++++++++++++++++++++--------- kernel/time/timekeeping.c | 2 +- 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 064e68e7a77c..20d211b27908 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -41,6 +41,8 @@ * @time_freq: Frequency offset scaled nsecs/secs * @time_reftime: Time at last adjustment in seconds * @time_adjust: Adjustment value + * @time_adjust_frac: Sub-microsecond remainder of @time_adjust being + * delivered, in ns << NTP_SCALE_SHIFT (not divided by HZ). * @ntp_tick_adj: Constant boot-param configurable NTP tick adjustment (upscaled) * @cs_tick_adj: Fixed per-second adjustment compensating for the difference * between the nominal NTP interval and the real time taken @@ -77,6 +79,7 @@ struct ntp_data { s64 time_freq; time64_t time_reftime; long time_adjust; + s64 time_adjust_frac; s64 ntp_tick_adj; s64 cs_tick_adj; time64_t ntp_next_leap_sec; @@ -110,6 +113,9 @@ static struct ntp_data tk_ntp_data[TIMEKEEPERS_MAX] = { #define SECS_PER_DAY 86400 #define MAX_TICKADJ 500LL /* usecs */ +/* One microsecond of phase, in plain shifted-ns (ns << NTP_SCALE_SHIFT) */ +#define ONE_US_NS ((s64)NSEC_PER_USEC << NTP_SCALE_SHIFT) +/* Per-tick MAX_TICKADJ slew, in plain shifted-ns */ #define MAX_TICKADJ_SCALED \ (((MAX_TICKADJ * NSEC_PER_USEC) << NTP_SCALE_SHIFT) / NTP_INTERVAL_FREQ) #define MAX_TAI_OFFSET 100000 @@ -345,6 +351,7 @@ static void __ntp_clear(struct ntp_data *ntpdata) { /* Stop active adjtime() */ ntpdata->time_adjust = 0; + ntpdata->time_adjust_frac = 0; ntpdata->time_status |= STA_UNSYNC; ntpdata->time_maxerror = NTP_PHASE_LIMIT; ntpdata->time_esterror = NTP_PHASE_LIMIT; @@ -421,20 +428,88 @@ static s64 ntp_drain_time_offset(unsigned int tkid, s64 amount) return 0; } +/* + * Drain the legacy adjtime() correction (time_adjust) as it is delivered. + * + * @amount is the total intentional per-tick skew for this accumulation + * (skew_delta << shift), in time_offset units (shifted_ns / HZ); it covers + * both the exponential time_offset slew and the linear adjtime slew. This + * function claims only the adjtime share — capped at the MAX_TICKADJ rate — + * and returns the remainder for ntp_drain_time_offset(). + * + * time_adjust is in whole µs. The sub-µs remainder being delivered lives in + * time_adjust_frac (plain shifted-ns, i.e. ns << NTP_SCALE_SHIFT -- unlike + * time_offset these are NOT pre-divided by HZ); we top it up by borrowing + * whole microseconds from time_adjust as the drain consumes it. + */ +static s64 ntp_drain_time_adjust(unsigned int tkid, s64 amount, unsigned int shift) +{ + struct ntp_data *ntpdata = &tk_ntp_data[tkid]; + /* Sign reference: time_adjust if any whole us remain, else the drawer */ + s64 ref = ntpdata->time_adjust ? (s64)ntpdata->time_adjust + : ntpdata->time_adjust_frac; + s64 deliver, deficit, claimed; + + if (!amount || !ref || signof(amount) != signof(ref)) + return amount; + + /* + * Phase to deliver this accumulation, in plain shifted-ns. The drain + * @amount is in ÷HZ units, so multiply by HZ first, then clamp to the + * MAX_TICKADJ rate (MAX_TICKADJ_SCALED is the per-tick slew in + * shifted-ns). Multiply-then-clamp avoids an s64 divide for the cap. + */ + deliver = min(abs(amount) * NTP_INTERVAL_FREQ, + (s64)MAX_TICKADJ_SCALED << shift); + + /* Top up the sub-µs drawer from whole-µs time_adjust as needed */ + deficit = deliver - abs(ntpdata->time_adjust_frac); + if (deficit > 0 && ntpdata->time_adjust) { + long borrow = div64_u64(deficit + ONE_US_NS - 1, ONE_US_NS); + + if (ntpdata->time_adjust > 0) { + borrow = min(borrow, ntpdata->time_adjust); + ntpdata->time_adjust -= borrow; + ntpdata->time_adjust_frac += (s64)borrow * ONE_US_NS; + } else { + /* Clamp without negating time_adjust (UB for LONG_MIN) */ + if (ntpdata->time_adjust > -borrow) + borrow = -ntpdata->time_adjust; + ntpdata->time_adjust += borrow; + ntpdata->time_adjust_frac -= (s64)borrow * ONE_US_NS; + } + } + + /* Never deliver more than the drawer holds */ + deliver = min(deliver, abs(ntpdata->time_adjust_frac)); + if (ntpdata->time_adjust_frac > 0) + ntpdata->time_adjust_frac -= deliver; + else + ntpdata->time_adjust_frac += deliver; + + /* Return the unclaimed remainder in ÷HZ drain units for time_offset */ + claimed = div_s64(deliver, NTP_INTERVAL_FREQ); + return amount - signof(amount) * claimed; +} + /* * Drain one accumulation's worth of intentional skew as it is delivered. * * @amount is the total intentional per-tick skew for this accumulation - * (skew_delta << shift), in time_offset units (shifted_ns / HZ). Returns - * the amount actually claimed (same ÷HZ units). + * (skew_delta << shift), in time_offset units (shifted_ns / HZ). The + * adjtime() linear share is taken from time_adjust first (capped at the + * MAX_TICKADJ rate, hence @shift), then the exponential remainder from + * time_offset. Returns the amount actually claimed (same ÷HZ units). */ s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift) { - s64 unclaimed = ntp_drain_time_offset(tkid, amount); + s64 unclaimed = ntp_drain_time_adjust(tkid, amount, shift); + + unclaimed = ntp_drain_time_offset(tkid, unclaimed); /* * Return the amount actually drained from the intentional - * phase offset in time_offset. + * phase offset in time_offset and/or time_adjust. */ return amount - unclaimed; } @@ -544,11 +619,13 @@ int second_overflow(unsigned int tkid, time64_t secs) * to zero, pass the bare minimum ±1 to ensure that it *does* * actually drain completely to zero. It won't overshoot because * logarithmic_accumulation() only drains what it can from - * time_offset and the rest ends up in ntp_error which drives - * the selection of 'mult' immediately each tick. + * time_offset or time_adjust, and the rest ends up in ntp_error + * which drives the selection of 'mult' immediately each tick. */ - if (ntpdata->time_offset) { + if (ntpdata->time_offset || ntpdata->time_adjust || + ntpdata->time_adjust_frac) { s64 off_chunk = ntp_offset_chunk(ntpdata, ntpdata->time_offset); + s64 adj_chunk = 0, net; /* * Once the exponential chunk rounds to zero, deliver the last @@ -558,34 +635,31 @@ int second_overflow(unsigned int tkid, time64_t secs) if (!off_chunk) off_chunk = ntpdata->time_offset; - /* Reduce to per-tick, then floor. */ - ntpdata->skew_delta = div_s64(off_chunk, NTP_INTERVAL_FREQ); - if (!ntpdata->skew_delta) - ntpdata->skew_delta = signof(off_chunk); + if (ntpdata->time_adjust || ntpdata->time_adjust_frac) { + s64 adj; + + if (ntpdata->time_adjust >= MAX_TICKADJ) + adj = MAX_TICKADJ * ONE_US_NS; + else if (ntpdata->time_adjust <= -MAX_TICKADJ) + adj = -MAX_TICKADJ * ONE_US_NS; + else + adj = ntpdata->time_adjust * ONE_US_NS + + ntpdata->time_adjust_frac; + + adj_chunk = div_s64(adj, NTP_INTERVAL_FREQ); + if (!adj_chunk) + adj_chunk = signof(ntpdata->time_adjust_frac); + } + + /* Net is what the clock delivers; reduce to per-tick, then floor. */ + net = off_chunk + adj_chunk; + ntpdata->skew_delta = div_s64(net, NTP_INTERVAL_FREQ); + if (!ntpdata->skew_delta && net) + ntpdata->skew_delta = signof(net); } else { ntpdata->skew_delta = 0; } - if (!ntpdata->time_adjust) - goto out; - - if (ntpdata->time_adjust > MAX_TICKADJ) { - ntpdata->time_adjust -= MAX_TICKADJ; - ntpdata->tick_length += MAX_TICKADJ_SCALED; - goto out; - } - - if (ntpdata->time_adjust < -MAX_TICKADJ) { - ntpdata->time_adjust += MAX_TICKADJ; - ntpdata->tick_length -= MAX_TICKADJ_SCALED; - goto out; - } - - ntpdata->tick_length += (s64)(ntpdata->time_adjust * NSEC_PER_USEC / NTP_INTERVAL_FREQ) - << NTP_SCALE_SHIFT; - ntpdata->time_adjust = 0; - -out: return leap; } @@ -878,6 +952,7 @@ int ntp_adjtimex(unsigned int tkid, struct __kernel_timex *txc, const struct tim if (!(txc->modes & ADJ_OFFSET_READONLY)) { /* adjtime() is independent from ntp_adjtime() */ ntpdata->time_adjust = txc->offset; + ntpdata->time_adjust_frac = 0; ntp_update_frequency(ntpdata); audit_ntp_set_old(ad, AUDIT_NTP_ADJUST, save_adjust); @@ -1119,6 +1194,7 @@ static void hardpps_update_phase(struct ntp_data *ntpdata, long error) NTP_INTERVAL_FREQ); /* Cancel running adjtime() */ ntpdata->time_adjust = 0; + ntpdata->time_adjust_frac = 0; } /* Update jitter */ ntpdata->pps_jitter += (jitter - ntpdata->pps_jitter) >> PPS_INTMIN; diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 0e646e92cd34..97db2e9393f9 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -2611,7 +2611,7 @@ static u64 logarithmic_accumulation(struct timekeeper *tk, u64 offset, /* * When skewing, do so by adjusting ntp_error to impart an extra * target delta into ntp_error per tick, limited to what can be - * drained from time_offset to avoid overshoot. + * drained from time_offset / time_adjust to avoid overshoot. * * The base 'mult' value was calculated with the skew taken into * account, such that the per-tick choice of 'mult' vs. 'mult+1' From 34ce97c33dca9595455a63d7801e360d19fd97ed Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:59 +0100 Subject: [PATCH 29/58] timekeeping: Settle competing time_offset and time_adjust skew MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit time_offset (the exponential PLL phase slew) and time_adjust (the linear adjtime() slew) can be asked to move the clock in opposite directions. second_overflow() folds only their *net* into the per-tick skew_delta, so the cancelling overlap would never be drained from either tracker by the per-tick code — and if they cancel exactly, skew_delta is zero and neither converges at all. Arguably we could just let one of them entirely cancel out the other immediately, but that would be a change in userspace-visible behaviour. Instead, preserve the existing behaviour by calculating the "conflict" portion between the opposing skew each second, and transferring that amount directly from one tracker to the other. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.8 Link: https://patch.msgid.link/20260621220051.1030462-7-dwmw2@infradead.org --- kernel/time/ntp.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 20d211b27908..73a47dd775a0 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -514,6 +514,69 @@ s64 ntp_drain_skew(unsigned int tkid, s64 amount, unsigned int shift) return amount - unclaimed; } +/* + * time_offset (drained exponentially) and time_adjust (drained linearly at the + * MAX_TICKADJ rate) can be asked to slew the clock in opposite directions. + * second_overflow() only folds their *net* into skew_delta, so the cancelling + * part would never be drained from either tracker via the per-tick code -- and + * if they cancel exactly, skew_delta is zero and neither converges at all. + * + * Settle that cancelling phase directly between the two here. No clock motion + * results (the opposing slews annihilate), but both move toward zero so neither + * stalls. @amount is the phase to take off time_offset, in its (÷HZ) units and + * with its sign; the same real magnitude comes off time_adjust in the opposite + * direction. Clamped so neither tracker is driven past zero. + */ +static void ntp_transfer_offset_adjust(struct ntp_data *ntpdata, s64 amount) +{ + s64 frac_delta, carry; + + /* + * Don't drain time_offset past zero. @amount shares its sign and is + * normally bounded below it by ntp_offset_chunk(), but the ±1 skew_delta + * floor for a tiny time_offset can exceed it, so clamp. + */ + if (abs(amount) > abs(ntpdata->time_offset)) + amount = ntpdata->time_offset; + if (!amount) + return; + + /* + * Remove the matching phase from time_adjust, in plain shifted-ns. No + * clamp against time_adjust's zero is needed: @amount is bounded by the + * adjtime chunk, which second_overflow() never lets exceed time_adjust's + * own pending phase, so this cannot overshoot. + */ + frac_delta = amount * NTP_INTERVAL_FREQ; + + ntpdata->time_offset -= amount; + + /* Add the matching phase to time_adjust, carrying whole µs (O(1)). */ + ntpdata->time_adjust_frac += frac_delta; + if (ntpdata->time_adjust_frac >= ONE_US_NS || + ntpdata->time_adjust_frac <= -ONE_US_NS) { + carry = div64_s64(ntpdata->time_adjust_frac, ONE_US_NS); + ntpdata->time_adjust += carry; + ntpdata->time_adjust_frac -= carry * ONE_US_NS; + } + + /* + * Keep time_adjust and its sub-µs remainder the same sign. The + * truncating carry above can leave them opposed (e.g. +4 µs paired + * with -250 ns), and ntp_drain_time_adjust() treats abs(time_adjust_frac) + * as same-direction drawer capacity -- an opposing remainder there makes + * it over-deliver phase that was never removed from the pile. Borrow or + * repay a single whole µs to realign; the total phase is unchanged. + */ + if (ntpdata->time_adjust > 0 && ntpdata->time_adjust_frac < 0) { + ntpdata->time_adjust--; + ntpdata->time_adjust_frac += ONE_US_NS; + } else if (ntpdata->time_adjust < 0 && ntpdata->time_adjust_frac > 0) { + ntpdata->time_adjust++; + ntpdata->time_adjust_frac -= ONE_US_NS; + } +} + /** * ntp_get_next_leap - Returns the next leapsecond in CLOCK_REALTIME ktime_t * @tkid: Timekeeper ID @@ -651,6 +714,18 @@ int second_overflow(unsigned int tkid, time64_t secs) adj_chunk = signof(ntpdata->time_adjust_frac); } + /* + * If the two slews oppose, only their net would drive the + * per-tick drain, so the cancelling part would never drain from + * either tracker and an exact cancellation would stall both. + * Settle that overlap directly between them (no clock motion). + */ + if (off_chunk && adj_chunk && signof(off_chunk) != signof(adj_chunk)) { + s64 conflict = min(abs(off_chunk), abs(adj_chunk)); + + ntp_transfer_offset_adjust(ntpdata, signof(off_chunk) * conflict); + } + /* Net is what the clock delivers; reduce to per-tick, then floor. */ net = off_chunk + adj_chunk; ntpdata->skew_delta = div_s64(net, NTP_INTERVAL_FREQ); From 794ddd6e15cfd133c4a5c01dac37a5da04cbe186 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:54:00 +0100 Subject: [PATCH 30/58] ntp: Remove tick_length_base, use tick_length directly Now that nothing inflates tick_length beyond tick_length_base (the adjtime path was converted to use time_offset in the previous commit), the two fields are always equal. Remove tick_length_base and keep tick_length as the single field. Remove the per-second reset and the delta update in ntp_update_frequency() since there is no separate base to track. No functional change intended. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.6-1m Link: https://patch.msgid.link/20260621220051.1030462-8-dwmw2@infradead.org --- kernel/time/ntp.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c index 73a47dd775a0..d22b532ec536 100644 --- a/kernel/time/ntp.c +++ b/kernel/time/ntp.c @@ -26,8 +26,7 @@ /** * struct ntp_data - Structure holding all NTP related state * @tick_usec: USER_HZ period in microseconds - * @tick_length: Adjusted tick length - * @tick_length_base: Base value for @tick_length + * @tick_length: Tick length in ns << NTP_SCALE_SHIFT * @time_state: State of the clock synchronization * @time_status: Clock status bits * @time_offset: Time adjustment in nanoseconds @@ -68,7 +67,6 @@ struct ntp_data { unsigned long tick_usec; u64 tick_length; - u64 tick_length_base; int time_state; int time_status; s64 time_offset; @@ -260,8 +258,7 @@ static inline void pps_fill_timex(struct ntp_data *ntpdata, struct __kernel_time #endif /* CONFIG_NTP_PPS */ /* - * Update tick_length and tick_length_base, based on tick_usec, ntp_tick_adj and - * time_freq: + * Update tick_length based on tick_usec, ntp_tick_adj and time_freq: */ static void ntp_update_frequency(struct ntp_data *ntpdata) { @@ -279,8 +276,7 @@ static void ntp_update_frequency(struct ntp_data *ntpdata) * Don't wait for the next second_overflow, apply the change to the * tick length immediately: */ - ntpdata->tick_length += new_base - ntpdata->tick_length_base; - ntpdata->tick_length_base = new_base; + ntpdata->tick_length = new_base; } static inline s64 ntp_update_offset_fll(struct ntp_data *ntpdata, s64 offset64, long secs) @@ -358,7 +354,6 @@ static void __ntp_clear(struct ntp_data *ntpdata) ntp_update_frequency(ntpdata); - ntpdata->tick_length = ntpdata->tick_length_base; ntpdata->time_offset = 0; ntpdata->skew_delta = 0; @@ -670,7 +665,6 @@ int second_overflow(unsigned int tkid, time64_t secs) } /* Compute the phase adjustment for the next second */ - ntpdata->tick_length = ntpdata->tick_length_base; /* Check PPS signal */ pps_dec_valid(ntpdata); From f44ce7fdbdd00a596455b8689b62c12d5530d610 Mon Sep 17 00:00:00 2001 From: Wake Liu Date: Wed, 10 Jun 2026 01:47:21 +0000 Subject: [PATCH 31/58] selftests: timers: Partially revert "Remove local NSEC_PER_SEC and USEC_PER_SEC defines" This partially reverts commit 80fa614e2fbc ("selftests: timers: Remove local NSEC_PER_SEC and USEC_PER_SEC defines"). The original commit removed local definitions of NSEC_PER_SEC and USEC_PER_SEC in favor of including . However, NSEC_PER_SEC in vdso/time64.h is defined as 1000000000L, which is 32-bit on 32-bit architectures. This causes integer overflow warnings in several timer tests when doing arithmetic like NSEC_PER_SEC * 10 on 32-bit systems. To fix this, restore the local definitions of NSEC_PER_SEC and USEC_PER_SEC in the test files, but use "LL" suffix consistently (1000000000LL and 1000000LL) to ensure 64-bit arithmetic and avoid overflows. We keep the cleanup from the original commit that renamed plural definitions (NSECS_PER_SEC/USECS_PER_SEC) to singular ones in posix_timers.c, but we now define them locally there as well. This also removes the dependency of the selftests on the internal kernel header . Signed-off-by: Wake Liu Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260610014721.718362-1-wakel@google.com --- tools/testing/selftests/timers/Makefile | 2 +- tools/testing/selftests/timers/adjtick.c | 5 +++-- tools/testing/selftests/timers/alarmtimer-suspend.c | 3 ++- tools/testing/selftests/timers/inconsistency-check.c | 3 ++- tools/testing/selftests/timers/leap-a-day.c | 3 ++- tools/testing/selftests/timers/mqueue-lat.c | 3 ++- tools/testing/selftests/timers/nanosleep.c | 3 ++- tools/testing/selftests/timers/nsleep-lat.c | 3 ++- tools/testing/selftests/timers/posix_timers.c | 4 +++- tools/testing/selftests/timers/raw_skew.c | 3 ++- tools/testing/selftests/timers/set-2038.c | 3 ++- tools/testing/selftests/timers/set-timer-lat.c | 3 ++- tools/testing/selftests/timers/valid-adjtimex.c | 4 +++- 13 files changed, 28 insertions(+), 14 deletions(-) diff --git a/tools/testing/selftests/timers/Makefile b/tools/testing/selftests/timers/Makefile index 32203593c62e..0e73a16874c4 100644 --- a/tools/testing/selftests/timers/Makefile +++ b/tools/testing/selftests/timers/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 -CFLAGS += -O3 -Wl,-no-as-needed -Wall -I $(top_srcdir) +CFLAGS += -O3 -Wl,-no-as-needed -Wall LDLIBS += -lrt -lpthread -lm # these are all "safe" tests that don't modify diff --git a/tools/testing/selftests/timers/adjtick.c b/tools/testing/selftests/timers/adjtick.c index 5b3ef708d6e9..22d274d5520f 100644 --- a/tools/testing/selftests/timers/adjtick.c +++ b/tools/testing/selftests/timers/adjtick.c @@ -22,10 +22,11 @@ #include #include #include -#include - #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL +#define USEC_PER_SEC 1000000LL + #define MILLION 1000000 long systick; diff --git a/tools/testing/selftests/timers/alarmtimer-suspend.c b/tools/testing/selftests/timers/alarmtimer-suspend.c index aa66c805f6a4..d55d5b0377c6 100644 --- a/tools/testing/selftests/timers/alarmtimer-suspend.c +++ b/tools/testing/selftests/timers/alarmtimer-suspend.c @@ -28,10 +28,11 @@ #include #include #include -#include #include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + #define UNREASONABLE_LAT (NSEC_PER_SEC * 5) /* hopefully we resume in 5 secs */ #define SUSPEND_SECS 15 diff --git a/tools/testing/selftests/timers/inconsistency-check.c b/tools/testing/selftests/timers/inconsistency-check.c index e53e63e18683..9ab7066b4e32 100644 --- a/tools/testing/selftests/timers/inconsistency-check.c +++ b/tools/testing/selftests/timers/inconsistency-check.c @@ -28,9 +28,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 diff --git a/tools/testing/selftests/timers/leap-a-day.c b/tools/testing/selftests/timers/leap-a-day.c index 97c8c66bea88..71b49734e08c 100644 --- a/tools/testing/selftests/timers/leap-a-day.c +++ b/tools/testing/selftests/timers/leap-a-day.c @@ -51,9 +51,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + #define CLOCK_TAI 11 time_t next_leap; diff --git a/tools/testing/selftests/timers/mqueue-lat.c b/tools/testing/selftests/timers/mqueue-lat.c index c0d9368e4fca..ce8700c95cac 100644 --- a/tools/testing/selftests/timers/mqueue-lat.c +++ b/tools/testing/selftests/timers/mqueue-lat.c @@ -29,9 +29,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + #define TARGET_TIMEOUT 100000000 /* 100ms in nanoseconds */ #define UNRESONABLE_LATENCY 40000000 /* 40ms in nanosecs */ diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index a054680b3372..ceff18149a48 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -27,9 +27,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 diff --git a/tools/testing/selftests/timers/nsleep-lat.c b/tools/testing/selftests/timers/nsleep-lat.c index a7ba1eb1e21b..15f6493ca7b5 100644 --- a/tools/testing/selftests/timers/nsleep-lat.c +++ b/tools/testing/selftests/timers/nsleep-lat.c @@ -24,9 +24,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + #define UNRESONABLE_LATENCY 40000000 /* 40ms in nanosecs */ /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c index 2f3bac9fc6e8..fa168d802f9e 100644 --- a/tools/testing/selftests/timers/posix_timers.c +++ b/tools/testing/selftests/timers/posix_timers.c @@ -16,12 +16,14 @@ #include #include #include -#include #include #include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL +#define USEC_PER_SEC 1000000LL + #define DELAY 2 static void __fatal_error(const char *test, const char *name, const char *what) diff --git a/tools/testing/selftests/timers/raw_skew.c b/tools/testing/selftests/timers/raw_skew.c index a7bae7d80916..2dd16cb4cdd0 100644 --- a/tools/testing/selftests/timers/raw_skew.c +++ b/tools/testing/selftests/timers/raw_skew.c @@ -25,9 +25,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + #define shift_right(x, s) ({ \ __typeof__(x) __x = (x); \ __typeof__(s) __s = (s); \ diff --git a/tools/testing/selftests/timers/set-2038.c b/tools/testing/selftests/timers/set-2038.c index ecc171de4728..c1235638406d 100644 --- a/tools/testing/selftests/timers/set-2038.c +++ b/tools/testing/selftests/timers/set-2038.c @@ -27,9 +27,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + #define KTIME_MAX ((long long)~((unsigned long long)1 << 63)) #define KTIME_SEC_MAX (KTIME_MAX / NSEC_PER_SEC) diff --git a/tools/testing/selftests/timers/set-timer-lat.c b/tools/testing/selftests/timers/set-timer-lat.c index 44d2e3614fa5..e092c18befff 100644 --- a/tools/testing/selftests/timers/set-timer-lat.c +++ b/tools/testing/selftests/timers/set-timer-lat.c @@ -28,9 +28,10 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL + /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 diff --git a/tools/testing/selftests/timers/valid-adjtimex.c b/tools/testing/selftests/timers/valid-adjtimex.c index e1e56d3097d6..dc2559eb11a5 100644 --- a/tools/testing/selftests/timers/valid-adjtimex.c +++ b/tools/testing/selftests/timers/valid-adjtimex.c @@ -29,9 +29,11 @@ #include #include #include -#include #include "kselftest.h" +#define NSEC_PER_SEC 1000000000LL +#define USEC_PER_SEC 1000000LL + #define ADJ_SETOFFSET 0x0100 #include From 151ce4cf88cf2ab647d14064140f516bd78209b2 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2026 20:12:35 -0700 Subject: [PATCH 32/58] posix-timers: Clean up kernel-doc warnings Use the struct keyword when describing a struct. Add missing kernel-doc for @it_pid_type. Warning: ./include/linux/posix-timers.h:58 cannot understand function prototype: 'struct cpu_timer' Warning: ./include/linux/posix-timers.h:222 struct member 'it_pid_type' not described in 'k_itimer' Signed-off-by: Randy Dunlap Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260720031236.3070575-1-rdunlap@infradead.org --- include/linux/posix-timers.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 4d3dbcef379e..4117827efdb8 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -47,7 +47,7 @@ static inline bool clockid_aux_valid(clockid_t id) #include /** - * cpu_timer - Posix CPU timer representation for k_itimer + * struct cpu_timer - Posix CPU timer representation for k_itimer * @node: timerqueue node to queue in the task/sig * @head: timerqueue head on which this timer is queued * @pid: Pointer to target task PID @@ -174,6 +174,7 @@ static inline void posix_cputimers_init_work(void) { } * @it_sigqueue_seq: The sequence count at the point where the signal was queued * @it_sigev_notify: The notify word of sigevent struct for signal delivery * @it_interval: The interval for periodic timers + * @it_pid_type: The type of the PID * @it_signal: Pointer to the creators signal struct * @it_pid: The pid of the process/task targeted by the signal * @it_process: The task to wakeup on clock_nanosleep (CPU timers) From 98680a85df888f86e56ec12d78b2de730426e5af Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2026 20:12:36 -0700 Subject: [PATCH 33/58] posix-cpu-timers: Avoid kernel-doc warnings Use the struct keyword when describing a struct. Without that, the type defaults to function. Warning: ./include/linux/posix-timers_types.h:40 cannot understand function prototype: 'struct posix_cputimer_base' Warning: ./include/linux/posix-timers_types.h:55 cannot understand function prototype: 'struct posix_cputimers' Warning: ./include/linux/posix-timers_types.h:67 cannot understand function prototype: 'struct posix_cputimers_work' Signed-off-by: Randy Dunlap Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260720031236.3070575-2-rdunlap@infradead.org --- include/linux/posix-timers_types.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/posix-timers_types.h b/include/linux/posix-timers_types.h index a4712c1008c9..b40cf352e01d 100644 --- a/include/linux/posix-timers_types.h +++ b/include/linux/posix-timers_types.h @@ -34,7 +34,7 @@ #ifdef CONFIG_POSIX_TIMERS /** - * posix_cputimer_base - Container per posix CPU clock + * struct posix_cputimer_base - Container per posix CPU clock * @nextevt: Earliest-expiration cache * @tqhead: timerqueue head for cpu_timers */ @@ -44,7 +44,7 @@ struct posix_cputimer_base { }; /** - * posix_cputimers - Container for posix CPU timer related data + * struct posix_cputimers - Container for posix CPU timer related data * @bases: Base container for posix CPU clocks * @timers_active: Timers are queued. * @expiry_active: Timer expiry is active. Used for @@ -60,7 +60,7 @@ struct posix_cputimers { }; /** - * posix_cputimers_work - Container for task work based posix CPU timer expiry + * struct posix_cputimers_work - Container for task work based posix CPU timer expiry * @work: The task work to be scheduled * @mutex: Mutex held around expiry in context of this task work * @scheduled: @work has been scheduled already, no further processing From ecc330e3096173f433659aa64ab3674d0d48440e Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Fri, 17 Jul 2026 03:59:47 +0000 Subject: [PATCH 34/58] timers/migration: Fix bad line kernel-doc warning in struct tmigr_cpu Running kernel-doc on timer_migration.h reports: Warning: kernel/time/timer_migration.h:105 bad line: The empty line separating the @available paragraph from the @idle member documentation in the struct tmigr_cpu kernel-doc block lacks the " *" line prefix that kernel-doc requires on every line inside a block. The header is not scanned by the build-time kernel-doc checks, so the warning only shows up when kernel-doc is invoked on the file directly. Add the missing prefix. The empty line was introduced when the @available documentation was expanded by commit 45a13ba52c82 ("timers/migration: Update stale @online doc to @available"). No functional change. Signed-off-by: Babanpreet Singh Signed-off-by: Thomas Gleixner Assisted-by: Claude:claude-sonnet-5 Link: https://patch.msgid.link/20260717035947.7-1-bbnpreetsingh@gmail.com --- kernel/time/timer_migration.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/timer_migration.h b/kernel/time/timer_migration.h index 31735dd52327..c9c1c29f011d 100644 --- a/kernel/time/timer_migration.h +++ b/kernel/time/timer_migration.h @@ -103,7 +103,7 @@ struct tmigr_group { * before the timer migration hierarchy hotplug callback is * reached. During this phase, the CPU has to handle the * global timers on its own and must not act as a migrator. - + * * @idle: Indicates whether the CPU is idle in the timer migration * hierarchy * @remote: Is set when timers of the CPU are expired remotely From d7fc133bf91f713df72facebd41ae9dfc83e35f7 Mon Sep 17 00:00:00 2001 From: Dennis Moshegov Date: Fri, 24 Jul 2026 16:43:55 +0100 Subject: [PATCH 35/58] timekeeping: Use READ_ONCE/WRITE_ONCE() for xtime_sec to prevent tearing The timekeeper update path uses a bulk memcpy() to synchronize the timekeeper structure, which is not guaranteed to be atomic. This allows for torn reads in ktime_get_real_seconds() on 64-bit systems, where the sequence counter protection is bypassed for performance. To prevent reading a torn 64-bit xtime_sec value, enforce atomic-like access by using WRITE_ONCE() for the critical field before the bulk memcpy() in timekeeping_update_from_shadow(). Correspondingly, use READ_ONCE() in ktime_get_real_seconds() to ensure a fresh, consistent load from memory. [ tglx: Format changelog and add comment ] Reported-by: syzbot+72789cd1697965e714ca@syzkaller.appspotmail.com Signed-off-by: Dennis Moshegov Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260724154405.70-1-dennis@xzync.uk Closes: https://syzkaller.appspot.com/bug?extid=72789cd1697965e714ca --- kernel/time/timekeeping.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 97db2e9393f9..c4230f44e1ea 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -858,7 +858,11 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act * the downside that the reader side does not longer benefit from * the cacheline optimized data layout of the timekeeper and requires * another indirection. + * + * Write xtime_sec first so that even if the memcpy() tears the store + * data integrity is provided for ktime_get_real_seconds(). */ + WRITE_ONCE(tkd->timekeeper.xtime_sec, tk->xtime_sec); memcpy(&tkd->timekeeper, tk, sizeof(*tk)); write_seqcount_end(&tkd->seq); } @@ -1186,11 +1190,11 @@ time64_t ktime_get_real_seconds(void) unsigned int seq; if (IS_ENABLED(CONFIG_64BIT)) - return tk->xtime_sec; + return READ_ONCE(tk->xtime_sec); do { seq = read_seqcount_begin(&tk_core.seq); - seconds = tk->xtime_sec; + seconds = READ_ONCE(tk->xtime_sec); } while (read_seqcount_retry(&tk_core.seq, seq)); @@ -1212,7 +1216,7 @@ noinstr time64_t __ktime_get_real_seconds(void) { struct timekeeper *tk = &tk_core.timekeeper; - return tk->xtime_sec; + return READ_ONCE(tk->xtime_sec); } static inline u64 tk_clock_read_snapshot(const struct tk_read_base *tkr, From 4b61084b11bcecce86d03804ff30f8d7b465593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Fri, 31 Jul 2026 16:17:43 +0200 Subject: [PATCH 36/58] timekeeping: Check the return value of tk_get_aux_ts64 in __do_adjtimex() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the auxiliary clock is disabled during tk_get_aux_ts64() but is enabled before tks->clock_valid is checked, then uninitialized stackdata will be used in the calculations and indirectly leaked to userspace. The same race window also exists after this change and also for the core timekeeper. But in these cases the only effect would be incorrect adjustments and this is userspace's responsibility to avoid this. Fixes: 4eca49d0b621 ("timekeeping: Prepare do_adtimex() for auxiliary clocks") Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260731-timekeeping-aux-adjtimex-return-v1-1-b7fea4692886@linutronix.de --- kernel/time/timekeeping.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index c4230f44e1ea..c1863f2fd106 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -3025,10 +3025,12 @@ static int __do_adjtimex(struct tk_data *tkd, struct __kernel_timex *txc, return ret; add_device_randomness(txc, sizeof(*txc)); - if (!aux_clock) + if (!aux_clock) { ktime_get_real_ts64(&ts); - else - tk_get_aux_ts64(tkd->timekeeper.id, &ts); + } else { + if (!tk_get_aux_ts64(tkd->timekeeper.id, &ts)) + return -ENODEV; + } add_device_randomness(&ts, sizeof(ts)); From 1b515771872174553fe70eadb8efecfa49035922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Fri, 31 Jul 2026 16:20:14 +0200 Subject: [PATCH 37/58] timekeeping: Remove the unused ktime_get_clock_ts64() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last user was removed in commit a6d799608e6a ("ptp: Switch to ktime_get_snapshot_id() for pre/post timestamps"). Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260731-timekeeping-aux-must-check-v1-1-11ae93068497@linutronix.de --- include/linux/timekeeping.h | 1 - kernel/time/timekeeping.c | 34 ---------------------------------- 2 files changed, 35 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index b4191a873c39..ca4a881e622f 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -44,7 +44,6 @@ extern void ktime_get_ts64(struct timespec64 *ts); 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); -extern void ktime_get_clock_ts64(clockid_t id, struct timespec64 *ts); /* Multigrain timestamp interfaces */ extern void ktime_get_coarse_real_ts64_mg(struct timespec64 *ts); diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index c1863f2fd106..4e6d594ecfa9 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1941,40 +1941,6 @@ void ktime_get_raw_ts64(struct timespec64 *ts) } EXPORT_SYMBOL(ktime_get_raw_ts64); -/** - * ktime_get_clock_ts64 - Returns time of a clock in a timespec - * @id: POSIX clock ID of the clock to read - * @ts: Pointer to the timespec64 to be set - * - * The timestamp is invalidated (@ts->sec is set to -1) if the - * clock @id is not available. - */ -void ktime_get_clock_ts64(clockid_t id, struct timespec64 *ts) -{ - /* Invalidate time stamp */ - ts->tv_sec = -1; - ts->tv_nsec = 0; - - switch (id) { - case CLOCK_REALTIME: - ktime_get_real_ts64(ts); - return; - case CLOCK_MONOTONIC: - ktime_get_ts64(ts); - return; - case CLOCK_MONOTONIC_RAW: - ktime_get_raw_ts64(ts); - return; - case CLOCK_AUX ... CLOCK_AUX_LAST: - if (IS_ENABLED(CONFIG_POSIX_AUX_CLOCKS)) - ktime_get_aux_ts64(id, ts); - return; - default: - WARN_ON_ONCE(1); - } -} -EXPORT_SYMBOL_GPL(ktime_get_clock_ts64); - /** * timekeeping_valid_for_hres - Check if timekeeping is suitable for hres */ From 2838bdb772e4e94591a90437bf595dba485a3162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Fri, 31 Jul 2026 16:20:15 +0200 Subject: [PATCH 38/58] timekeeping: Annotate auxiliary clock accessors with __must_check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In contrast to the system time accessors, the ones for auxiliary clocks can fail. Make sure the callers check for this. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260731-timekeeping-aux-must-check-v1-2-11ae93068497@linutronix.de --- include/linux/timekeeping.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index ca4a881e622f..561313b5cb6d 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -267,11 +267,14 @@ extern void timekeeping_inject_sleeptime64(const struct timespec64 *delta); * Auxiliary clock interfaces */ #ifdef CONFIG_POSIX_AUX_CLOCKS -extern bool ktime_get_aux(clockid_t id, ktime_t *kt); -extern bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt); +extern bool __must_check ktime_get_aux(clockid_t id, ktime_t *kt); +extern bool __must_check ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt); #else -static inline bool ktime_get_aux(clockid_t id, ktime_t *kt) { return false; } -static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { return false; } +static inline bool __must_check ktime_get_aux(clockid_t id, ktime_t *kt) { return false; } +static inline bool __must_check ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) +{ + return false; +} #endif /** From b17006b45283f012a6e166517f5ca3e1af2f806a Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sat, 1 Aug 2026 17:01:40 +0100 Subject: [PATCH 39/58] nohz: Replace dead select with choice default 'select' does not work on config options in a 'choice', so currently the 'select VIRT_CPU_ACCOUNTING_GEN' for NO_HZ_FULL is dead, with the choice option VIRT_CPU_ACCOUNTING_GEN only being enabled when NO_HZ_FULL=y because the other choice members depend on NO_HZ_FULL=n. Remove the dead select, and encode this relationship as a default of the choice, instead. This dead select was found by kconfirm, a static analysis tool for Kconfig. Signed-off-by: Julian Braha Signed-off-by: Thomas Gleixner Reviewed-by: Bradley Morgan Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260801160140.2391000-1-julianbraha@gmail.com --- init/Kconfig | 1 + kernel/time/Kconfig | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/init/Kconfig b/init/Kconfig index 5230d4879b1c..523e41d48539 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -562,6 +562,7 @@ config VIRT_CPU_ACCOUNTING choice prompt "Cputime accounting" + default VIRT_CPU_ACCOUNTING_GEN if NO_HZ_FULL default TICK_CPU_ACCOUNTING # Kind of a stub config for the pure tick based cputime accounting diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig index d098ac39bde4..ddfb6bee0745 100644 --- a/kernel/time/Kconfig +++ b/kernel/time/Kconfig @@ -133,7 +133,6 @@ config NO_HZ_FULL depends on HAVE_VIRT_CPU_ACCOUNTING_GEN select NO_HZ_COMMON select RCU_NOCB_CPU - select VIRT_CPU_ACCOUNTING_GEN select IRQ_WORK select CPU_ISOLATION help From 18c7d85864e554adc8fad1e8d2e9d2cb6c3911c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Sun, 9 Aug 2026 19:04:16 +0000 Subject: [PATCH 40/58] timers/itimer: Zero-init old itimerval before copy to userspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On native sparc64, struct __kernel_old_timeval contains a four-byte hole after tv_usec because tv_sec is 64-bit while __kernel_suseconds_t is 32-bit. put_itimerval() fills only the named fields in a stack-allocated __kernel_old_itimerval and copies the entire object to userspace, so getitimer() can expose the two padding holes. Zero-initialize the aggregate before assigning the fields so implicit padding is deterministic before it crosses the user/kernel boundary. Signed-off-by: Jérémy Jean Signed-off-by: Thomas Gleixner Assisted-by: Codex:gpt-5 Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260809190428.1523014-1-Jeremy.Jean@oss.cyber.gouv.fr --- kernel/time/itimer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/itimer.c b/kernel/time/itimer.c index 7c6110e964e7..03a32dffc56c 100644 --- a/kernel/time/itimer.c +++ b/kernel/time/itimer.c @@ -100,7 +100,7 @@ static int do_getitimer(int which, struct itimerspec64 *value) static int put_itimerval(struct __kernel_old_itimerval __user *o, const struct itimerspec64 *i) { - struct __kernel_old_itimerval v; + struct __kernel_old_itimerval v = {}; v.it_interval.tv_sec = i->it_interval.tv_sec; v.it_interval.tv_usec = i->it_interval.tv_nsec / NSEC_PER_USEC; From 32a05ba399fc52661f59f38efd2e606f45eb2c8f Mon Sep 17 00:00:00 2001 From: Liang Hao Date: Fri, 31 Jul 2026 23:04:08 +0800 Subject: [PATCH 41/58] hrtimer: Account nr_retries on recovered interrupt retries Re-arranging hrtimer_interrupt() switched the retry path to a local counter and dropped the update of cpu_base->nr_retries, leaving the field exported via /proc/timer_list stuck at zero. Increment nr_retries only when another pass through the expiry loop is started; the third attempt that falls through to hang handling is still accounted by nr_hangs alone. Fixes: 288924384856 ("hrtimer: Re-arrange hrtimer_interrupt()") Signed-off-by: Liang Hao Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260731150408.19554-1-haohlliang@gmail.com --- kernel/time/hrtimer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 01890b7a5f98..48ee17e50c7b 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -2254,8 +2254,10 @@ void hrtimer_interrupt(struct clock_event_device *dev) expires_next = hrtimer_update_next_event(cpu_base); cpu_base->hang_detected = false; if (expires_next < now) { - if (++retries < 3) + if (++retries < 3) { + cpu_base->nr_retries++; goto retry; + } delta = ktime_sub(now, entry_time); cpu_base->max_hang_time = max_t(unsigned int, cpu_base->max_hang_time, delta); From 4f39d3c19bdc6b9cd4a8de7cbc563264cac9a348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:32 +0200 Subject: [PATCH 42/58] timekeeping: Rename clockid_aux_valid() to clockid_is_aux_clock() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current name is not clear about its behavior. Rename it. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-1-910cbd485390@linutronix.de --- include/linux/posix-timers.h | 2 +- kernel/time/timekeeping.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 4117827efdb8..9a1a0c61361c 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -37,7 +37,7 @@ static inline int clockid_to_fd(const clockid_t clk) return ~(clk >> 3); } -static inline bool clockid_aux_valid(clockid_t id) +static inline bool clockid_is_aux_clock(clockid_t id) { return IS_ENABLED(CONFIG_POSIX_AUX_CLOCKS) && id >= CLOCK_AUX && id <= CLOCK_AUX_LAST; } diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 4e6d594ecfa9..d02103b5191f 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -3101,7 +3101,7 @@ static inline unsigned int clockid_to_tkid(unsigned int id) static inline struct tk_data *aux_get_tk_data(clockid_t id) { - if (!clockid_aux_valid(id)) + if (!clockid_is_aux_clock(id)) return NULL; return &timekeeper_data[clockid_to_tkid(id)]; } @@ -3196,7 +3196,7 @@ EXPORT_SYMBOL_GPL(ktime_get_aux_ts64); static int aux_get_res(clockid_t id, struct timespec64 *tp) { - if (!clockid_aux_valid(id)) + if (!clockid_is_aux_clock(id)) return -ENODEV; tp->tv_sec = aux_clock_resolution_ns() / NSEC_PER_SEC; From 406a037ce7a03a37bf2b03010791ea9343ecc233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:33 +0200 Subject: [PATCH 43/58] timekeeping: Use u32 for clock_was_set_seq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use an explicitly sized type to make the code a bit more consistent with other fields of the datastructure and other sequence counters. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-2-910cbd485390@linutronix.de --- include/linux/hrtimer_bases.h | 2 +- include/linux/timekeeper_internal.h | 2 +- include/linux/timekeeping.h | 2 +- kernel/time/hrtimer.c | 2 +- kernel/time/timekeeping.c | 5 +++-- kernel/time/timekeeping.h | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/linux/hrtimer_bases.h b/include/linux/hrtimer_bases.h index 70b99e651168..d4c83ec5c0f8 100644 --- a/include/linux/hrtimer_bases.h +++ b/include/linux/hrtimer_bases.h @@ -84,7 +84,7 @@ struct hrtimer_cpu_base { raw_spinlock_t lock; unsigned int cpu; unsigned int active_bases; - unsigned int clock_was_set_seq; + u32 clock_was_set_seq; bool hres_active; bool deferred_rearm; bool deferred_needs_update; diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h index 9c198f65621b..fe077d97b5f8 100644 --- a/include/linux/timekeeper_internal.h +++ b/include/linux/timekeeper_internal.h @@ -169,7 +169,7 @@ struct timekeeper { u32 cs_ns_to_cyc_mult; u32 cs_ns_to_cyc_shift; u64 cs_ns_to_cyc_maxns; - unsigned int clock_was_set_seq; + u32 clock_was_set_seq; u8 cs_was_changed_seq; u8 clock_valid; diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 561313b5cb6d..efa9442aaeef 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -303,7 +303,7 @@ struct system_time_snapshot { ktime_t monoraw; enum clocksource_ids cs_id; enum clocksource_ids hw_csid; - unsigned int clock_was_set_seq; + u32 clock_was_set_seq; u8 cs_was_changed_seq; u8 valid; }; diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 48ee17e50c7b..540561d35c39 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -903,8 +903,8 @@ static void hrtimer_reprogram(struct hrtimer *timer, bool reprogram) static bool update_needs_ipi(struct hrtimer_cpu_base *cpu_base, unsigned int active) { struct hrtimer_clock_base *base; - unsigned int seq; ktime_t expires; + u32 seq; /* * Update the base offsets unconditionally so the following diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index d02103b5191f..ea2e6e55f37b 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1567,10 +1567,11 @@ int get_device_system_crosststamp(int (*get_time_fn) struct system_device_crosststamp *xtstamp) { u64 syscnt_cycles, cycles, now, interval_start; - unsigned int seq, clock_was_set_seq = 0; ktime_t base_sys, base_raw, *offs; + u32 clock_was_set_seq = 0; u64 nsec_sys, nsec_raw; u8 cs_was_changed_seq; + unsigned int seq; bool do_interp; struct timekeeper *tk; struct tk_data *tkd; @@ -2843,7 +2844,7 @@ void do_timer(unsigned long ticks) * * Called from hrtimer_interrupt() or retrigger_next_event() */ -ktime_t ktime_get_update_offsets_now(unsigned int *cwsseq, ktime_t *offs_real, +ktime_t ktime_get_update_offsets_now(u32 *cwsseq, ktime_t *offs_real, ktime_t *offs_boot, ktime_t *offs_tai) { struct timekeeper *tk = &tk_core.timekeeper; diff --git a/kernel/time/timekeeping.h b/kernel/time/timekeeping.h index 198d0608db74..4201f9e90813 100644 --- a/kernel/time/timekeeping.h +++ b/kernel/time/timekeeping.h @@ -4,7 +4,7 @@ /* * Internal interfaces for kernel/time/ */ -extern ktime_t ktime_get_update_offsets_now(unsigned int *cwsseq, +extern ktime_t ktime_get_update_offsets_now(u32 *cwsseq, ktime_t *offs_real, ktime_t *offs_boot, ktime_t *offs_tai); From d2e6ee3ecb76464e7146c1b4774383fee4b48146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:34 +0200 Subject: [PATCH 44/58] hrtimer: Add a lockdep assertion to hrtimer_update_base() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Document and verify that the hrtimer_cpu_base::lock is held at this point. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-3-910cbd485390@linutronix.de --- kernel/time/hrtimer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 540561d35c39..530d61257b9a 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -677,6 +677,8 @@ static ktime_t hrtimer_update_next_event(struct hrtimer_cpu_base *cpu_base) static inline ktime_t hrtimer_update_base(struct hrtimer_cpu_base *base) { + lockdep_assert_held(&base->lock); + ktime_t *offs_real = &base->clock_base[HRTIMER_BASE_REALTIME].offset; ktime_t *offs_boot = &base->clock_base[HRTIMER_BASE_BOOTTIME].offset; ktime_t *offs_tai = &base->clock_base[HRTIMER_BASE_TAI].offset; From 40e05cf391c8a815cb0a94c9d9e474ebd78807f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:35 +0200 Subject: [PATCH 45/58] timer_list: Use standard 'long long' format placeholders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit '%Ld' and '%Lu' are GNU extensions. While they do work for kernel code, checkpatch complains about them all the time. Replace them with the standard placeholders for 'long long' types, namely '%lld' and '%llu'. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-4-910cbd485390@linutronix.de --- kernel/time/timer_list.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index 8823e7dcfce6..ccbbb2ad1517 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -49,7 +49,7 @@ print_timer(struct seq_file *m, struct hrtimer *taddr, struct hrtimer *timer, SEQ_printf(m, " #%d: <%p>, %ps", idx, taddr, ACCESS_PRIVATE(timer, function)); SEQ_printf(m, ", S:%02x", timer->is_queued); SEQ_printf(m, "\n"); - SEQ_printf(m, " # expires at %Lu-%Lu nsecs [in %Ld to %Ld nsecs]\n", + SEQ_printf(m, " # expires at %llu-%llu nsecs [in %lld to %lld nsecs]\n", (unsigned long long)ktime_to_ns(hrtimer_get_softexpires(timer)), (unsigned long long)ktime_to_ns(hrtimer_get_expires(timer)), (long long)(ktime_to_ns(hrtimer_get_softexpires(timer)) - now), @@ -101,7 +101,7 @@ print_base(struct seq_file *m, struct hrtimer_clock_base *base, u64 now) SEQ_printf(m, " .resolution: %u nsecs\n", hrtimer_resolution); #ifdef CONFIG_HIGH_RES_TIMERS - SEQ_printf(m, " .offset: %Ld nsecs\n", + SEQ_printf(m, " .offset: %lld nsecs\n", (long long) base->offset); #endif SEQ_printf(m, "active timers:\n"); @@ -122,10 +122,10 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) #define DIAG_READ(x) data_race(READ_ONCE(x)) #define P(x) \ - SEQ_printf(m, " .%-15s: %Lu\n", #x, \ + SEQ_printf(m, " .%-15s: %llu\n", #x, \ (unsigned long long)DIAG_READ(cpu_base->x)) #define P_ns(x) \ - SEQ_printf(m, " .%-15s: %Lu nsecs\n", #x, \ + SEQ_printf(m, " .%-15s: %llu nsecs\n", #x, \ (unsigned long long)ktime_to_ns(DIAG_READ(cpu_base->x))) #ifdef CONFIG_HIGH_RES_TIMERS @@ -141,10 +141,10 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) #ifdef CONFIG_TICK_ONESHOT # define P(x) \ - SEQ_printf(m, " .%-15s: %Lu\n", #x, \ + SEQ_printf(m, " .%-15s: %llu\n", #x, \ (unsigned long long)DIAG_READ(ts->x)) # define P_ns(x) \ - SEQ_printf(m, " .%-15s: %Lu nsecs\n", #x, \ + SEQ_printf(m, " .%-15s: %llu nsecs\n", #x, \ (unsigned long long)ktime_to_ns(DIAG_READ(ts->x))) # define P_flag(x, f) \ SEQ_printf(m, " .%-15s: %d\n", #x, !!(DIAG_READ(ts->flags) & (f))) @@ -162,7 +162,7 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) P(last_jiffies); P(next_timer); P_ns(idle_expires); - SEQ_printf(m, "jiffies: %Lu\n", + SEQ_printf(m, "jiffies: %llu\n", (unsigned long long)jiffies); } #endif @@ -201,7 +201,7 @@ print_tickdevice(struct seq_file *m, struct tick_device *td, int cpu) SEQ_printf(m, " mult: %u\n", dev->mult); SEQ_printf(m, " shift: %u\n", dev->shift); SEQ_printf(m, " mode: %d\n", clockevent_get_state(dev)); - SEQ_printf(m, " next_event: %Ld nsecs\n", + SEQ_printf(m, " next_event: %lld nsecs\n", (unsigned long long) ktime_to_ns(dev->next_event)); SEQ_printf(m, " set_next_event: %ps\n", dev->set_next_event); @@ -259,7 +259,7 @@ static inline void timer_list_header(struct seq_file *m, u64 now) { SEQ_printf(m, "Timer List Version: v0.11\n"); SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES); - SEQ_printf(m, "now at %Ld nsecs\n", (unsigned long long)now); + SEQ_printf(m, "now at %lld nsecs\n", (unsigned long long)now); SEQ_printf(m, "\n"); } From 859b069ec5a328bd0bca301f6b9d87ff1dfc4b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:36 +0200 Subject: [PATCH 46/58] timer_list: Use ktime_t over nanoseconds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hrtimers use ktime_t in their implementation and API. The timer list performs a lot of unnecessary conversion to nanoseconds which make the code harder to read and are also wrong in case the values ever become negative. Remove the conversions. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-5-910cbd485390@linutronix.de --- kernel/time/timer_list.c | 57 +++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index ccbbb2ad1517..0406bf4488e9 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -20,7 +20,7 @@ struct timer_list_iter { int cpu; bool second_pass; - u64 now; + ktime_t now; }; /* @@ -44,19 +44,19 @@ static void SEQ_printf(struct seq_file *m, const char *fmt, ...) static void print_timer(struct seq_file *m, struct hrtimer *taddr, struct hrtimer *timer, - int idx, u64 now) + int idx, ktime_t now) { SEQ_printf(m, " #%d: <%p>, %ps", idx, taddr, ACCESS_PRIVATE(timer, function)); SEQ_printf(m, ", S:%02x", timer->is_queued); SEQ_printf(m, "\n"); - SEQ_printf(m, " # expires at %llu-%llu nsecs [in %lld to %lld nsecs]\n", - (unsigned long long)ktime_to_ns(hrtimer_get_softexpires(timer)), - (unsigned long long)ktime_to_ns(hrtimer_get_expires(timer)), - (long long)(ktime_to_ns(hrtimer_get_softexpires(timer)) - now), - (long long)(ktime_to_ns(hrtimer_get_expires(timer)) - now)); + SEQ_printf(m, " # expires at %lld-%lld nsecs [in %lld to %lld nsecs]\n", + (long long)hrtimer_get_softexpires(timer), + (long long)hrtimer_get_expires(timer), + (long long)ktime_sub(hrtimer_get_softexpires(timer), now), + (long long)ktime_sub(hrtimer_get_expires(timer), now)); } -static void print_active_timers(struct seq_file *m, struct hrtimer_clock_base *base, u64 now) +static void print_active_timers(struct seq_file *m, struct hrtimer_clock_base *base, ktime_t now) { struct timerqueue_linked_node *curr; struct hrtimer *timer, tmp; @@ -94,7 +94,7 @@ static void print_active_timers(struct seq_file *m, struct hrtimer_clock_base *b } static void -print_base(struct seq_file *m, struct hrtimer_clock_base *base, u64 now) +print_base(struct seq_file *m, struct hrtimer_clock_base *base, ktime_t now) { SEQ_printf(m, " .base: %p\n", base); SEQ_printf(m, " .index: %d\n", base->index); @@ -105,10 +105,10 @@ print_base(struct seq_file *m, struct hrtimer_clock_base *base, u64 now) (long long) base->offset); #endif SEQ_printf(m, "active timers:\n"); - print_active_timers(m, base, now + ktime_to_ns(base->offset)); + print_active_timers(m, base, ktime_add(now, base->offset)); } -static void print_cpu(struct seq_file *m, int cpu, u64 now) +static void print_cpu(struct seq_file *m, int cpu, ktime_t now) { struct hrtimer_cpu_base *cpu_base = &per_cpu(hrtimer_bases, cpu); int i; @@ -124,12 +124,11 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) #define P(x) \ SEQ_printf(m, " .%-15s: %llu\n", #x, \ (unsigned long long)DIAG_READ(cpu_base->x)) -#define P_ns(x) \ - SEQ_printf(m, " .%-15s: %llu nsecs\n", #x, \ - (unsigned long long)ktime_to_ns(DIAG_READ(cpu_base->x))) +#define P_ktime(x) \ + SEQ_printf(m, " .%-15s: %lld nsecs\n", #x, (long long)DIAG_READ(cpu_base->x)) #ifdef CONFIG_HIGH_RES_TIMERS - P_ns(expires_next); + P_ktime(expires_next); P(hres_active); P(nr_events); P(nr_retries); @@ -137,15 +136,14 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) P(max_hang_time); #endif #undef P -#undef P_ns +#undef P_ktime #ifdef CONFIG_TICK_ONESHOT # define P(x) \ SEQ_printf(m, " .%-15s: %llu\n", #x, \ (unsigned long long)DIAG_READ(ts->x)) -# define P_ns(x) \ - SEQ_printf(m, " .%-15s: %llu nsecs\n", #x, \ - (unsigned long long)ktime_to_ns(DIAG_READ(ts->x))) +# define P_ktime(x) \ + SEQ_printf(m, " .%-15s: %lld nsecs\n", #x, (long long)DIAG_READ(ts->x)) # define P_flag(x, f) \ SEQ_printf(m, " .%-15s: %d\n", #x, !!(DIAG_READ(ts->flags) & (f))) @@ -153,22 +151,22 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) struct tick_sched *ts = tick_get_tick_sched(cpu); P_flag(nohz, TS_FLAG_NOHZ); P_flag(highres, TS_FLAG_HIGHRES); - P_ns(last_tick); + P_ktime(last_tick); P_flag(tick_stopped, TS_FLAG_STOPPED); P(idle_calls); P(idle_sleeps); - P_ns(idle_entrytime); - P_ns(idle_waketime); + P_ktime(idle_entrytime); + P_ktime(idle_waketime); P(last_jiffies); P(next_timer); - P_ns(idle_expires); + P_ktime(idle_expires); SEQ_printf(m, "jiffies: %llu\n", (unsigned long long)jiffies); } #endif #undef P -#undef P_ns +#undef P_ktime #undef P_flag #undef DIAG_READ SEQ_printf(m, "\n"); @@ -201,8 +199,7 @@ print_tickdevice(struct seq_file *m, struct tick_device *td, int cpu) SEQ_printf(m, " mult: %u\n", dev->mult); SEQ_printf(m, " shift: %u\n", dev->shift); SEQ_printf(m, " mode: %d\n", clockevent_get_state(dev)); - SEQ_printf(m, " next_event: %lld nsecs\n", - (unsigned long long) ktime_to_ns(dev->next_event)); + SEQ_printf(m, " next_event: %lld nsecs\n", (long long)dev->next_event); SEQ_printf(m, " set_next_event: %ps\n", dev->set_next_event); @@ -255,17 +252,17 @@ static void timer_list_show_tickdevices_header(struct seq_file *m) } #endif -static inline void timer_list_header(struct seq_file *m, u64 now) +static inline void timer_list_header(struct seq_file *m, ktime_t now) { SEQ_printf(m, "Timer List Version: v0.11\n"); SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES); - SEQ_printf(m, "now at %lld nsecs\n", (unsigned long long)now); + SEQ_printf(m, "now at %lld nsecs\n", (long long)now); SEQ_printf(m, "\n"); } void sysrq_timer_list_show(void) { - u64 now = ktime_to_ns(ktime_get()); + ktime_t now = ktime_get(); int cpu; timer_list_header(NULL, now); @@ -323,7 +320,7 @@ static void *timer_list_start(struct seq_file *file, loff_t *offset) struct timer_list_iter *iter = file->private; if (!*offset) - iter->now = ktime_to_ns(ktime_get()); + iter->now = ktime_get(); iter->cpu = -1; iter->second_pass = false; return move_iter(iter, *offset); From 431012dd0a291bc135eb88d8a62d7424576800fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:37 +0200 Subject: [PATCH 47/58] selftests: Add clock-helpers.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple selftests duplicate various clock-related functionality. Add a new header to collect and deduplicate those. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-6-910cbd485390@linutronix.de --- tools/testing/selftests/clock-helpers.h | 76 +++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tools/testing/selftests/clock-helpers.h diff --git a/tools/testing/selftests/clock-helpers.h b/tools/testing/selftests/clock-helpers.h new file mode 100644 index 000000000000..01451f538e71 --- /dev/null +++ b/tools/testing/selftests/clock-helpers.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __CLOCK_HELPERS_H +#define __CLOCK_HELPERS_H + +#include +#include + +#define MSEC_PER_SEC 1000LL +#define USEC_PER_MSEC 1000LL +#define NSEC_PER_USEC 1000LL +#define NSEC_PER_MSEC 1000000LL +#define USEC_PER_SEC 1000000LL +#define NSEC_PER_SEC 1000000000LL +#define PSEC_PER_SEC 1000000000000LL +#define FSEC_PER_SEC 1000000000000000LL + +#ifndef CLOCK_AUX +#define CLOCK_AUX 16 +#endif + +#ifndef MAX_AUX_CLOCKS +#define MAX_AUX_CLOCKS 8 +#endif + +#ifndef CLOCK_AUX_LAST +#define CLOCK_AUX_LAST (CLOCK_AUX + MAX_AUX_CLOCKS - 1) +#endif + +__attribute__((unused)) +static inline const char *clock_name(clockid_t clockid) +{ + switch (clockid) { + case CLOCK_REALTIME: + return "CLOCK_REALTIME"; + case CLOCK_MONOTONIC: + return "CLOCK_MONOTONIC"; + case CLOCK_PROCESS_CPUTIME_ID: + return "CLOCK_PROCESS_CPUTIME_ID"; + case CLOCK_THREAD_CPUTIME_ID: + return "CLOCK_THREAD_CPUTIME_ID"; + case CLOCK_MONOTONIC_RAW: + return "CLOCK_MONOTONIC_RAW"; + case CLOCK_REALTIME_COARSE: + return "CLOCK_REALTIME_COARSE"; + case CLOCK_MONOTONIC_COARSE: + return "CLOCK_MONOTONIC_COARSE"; + case CLOCK_BOOTTIME: + return "CLOCK_BOOTTIME"; + case CLOCK_REALTIME_ALARM: + return "CLOCK_REALTIME_ALARM"; + case CLOCK_BOOTTIME_ALARM: + return "CLOCK_BOOTTIME_ALARM"; + case CLOCK_TAI: + return "CLOCK_TAI"; + case CLOCK_AUX + 0: + return "CLOCK_AUX0"; + case CLOCK_AUX + 1: + return "CLOCK_AUX1"; + case CLOCK_AUX + 2: + return "CLOCK_AUX2"; + case CLOCK_AUX + 3: + return "CLOCK_AUX3"; + case CLOCK_AUX + 4: + return "CLOCK_AUX4"; + case CLOCK_AUX + 5: + return "CLOCK_AUX5"; + case CLOCK_AUX + 6: + return "CLOCK_AUX6"; + case CLOCK_AUX + 7: + return "CLOCK_AUX7"; + }; + return "UNKNOWN_CLOCKID"; +} + +#endif /* __CLOCK_HELPERS_H */ From 54200b87489b39f9437a91e2156c4c2d90412704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:38 +0200 Subject: [PATCH 48/58] selftests: timers: Use clock_name() and constants from clock-helpers.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the many repetitions of custom clockid_t to string translation with the clock_name() helper from clock-helpers.h. Also drop the custom ?SEC_PER_?SEC definitions. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-7-910cbd485390@linutronix.de --- tools/testing/selftests/timers/adjtick.c | 5 +-- .../selftests/timers/alarmtimer-suspend.c | 38 ++--------------- .../selftests/timers/inconsistency-check.c | 38 ++--------------- tools/testing/selftests/timers/leap-a-day.c | 3 +- tools/testing/selftests/timers/mqueue-lat.c | 3 +- tools/testing/selftests/timers/nanosleep.c | 42 +++---------------- tools/testing/selftests/timers/nsleep-lat.c | 36 ++-------------- tools/testing/selftests/timers/posix_timers.c | 36 ++++++++-------- tools/testing/selftests/timers/raw_skew.c | 3 +- tools/testing/selftests/timers/set-2038.c | 3 +- .../testing/selftests/timers/set-timer-lat.c | 41 +++--------------- .../testing/selftests/timers/valid-adjtimex.c | 4 +- 12 files changed, 47 insertions(+), 205 deletions(-) diff --git a/tools/testing/selftests/timers/adjtick.c b/tools/testing/selftests/timers/adjtick.c index 22d274d5520f..68009a6d6de8 100644 --- a/tools/testing/selftests/timers/adjtick.c +++ b/tools/testing/selftests/timers/adjtick.c @@ -22,10 +22,9 @@ #include #include #include -#include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL -#define USEC_PER_SEC 1000000LL +#include "clock-helpers.h" +#include "kselftest.h" #define MILLION 1000000 diff --git a/tools/testing/selftests/timers/alarmtimer-suspend.c b/tools/testing/selftests/timers/alarmtimer-suspend.c index d55d5b0377c6..120b3ce8b39e 100644 --- a/tools/testing/selftests/timers/alarmtimer-suspend.c +++ b/tools/testing/selftests/timers/alarmtimer-suspend.c @@ -29,10 +29,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - #define UNREASONABLE_LAT (NSEC_PER_SEC * 5) /* hopefully we resume in 5 secs */ #define SUSPEND_SECS 15 @@ -40,37 +39,6 @@ int alarmcount; int alarm_clock_id; struct timespec start_time; - -char *clockstring(int clockid) -{ - switch (clockid) { - case CLOCK_REALTIME: - return "CLOCK_REALTIME"; - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_PROCESS_CPUTIME_ID: - return "CLOCK_PROCESS_CPUTIME_ID"; - case CLOCK_THREAD_CPUTIME_ID: - return "CLOCK_THREAD_CPUTIME_ID"; - case CLOCK_MONOTONIC_RAW: - return "CLOCK_MONOTONIC_RAW"; - case CLOCK_REALTIME_COARSE: - return "CLOCK_REALTIME_COARSE"; - case CLOCK_MONOTONIC_COARSE: - return "CLOCK_MONOTONIC_COARSE"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - case CLOCK_REALTIME_ALARM: - return "CLOCK_REALTIME_ALARM"; - case CLOCK_BOOTTIME_ALARM: - return "CLOCK_BOOTTIME_ALARM"; - case CLOCK_TAI: - return "CLOCK_TAI"; - } - return "UNKNOWN_CLOCKID"; -} - - long long timespec_sub(struct timespec a, struct timespec b) { long long ret = NSEC_PER_SEC * b.tv_sec + b.tv_nsec; @@ -130,12 +98,12 @@ int main(void) alarmcount = 0; if (timer_create(alarm_clock_id, &se, &tm1) == -1) { printf("timer_create failed, %s unsupported?: %s\n", - clockstring(alarm_clock_id), strerror(errno)); + clock_name(alarm_clock_id), strerror(errno)); break; } clock_gettime(alarm_clock_id, &start_time); - printf("Start time (%s): %ld:%ld\n", clockstring(alarm_clock_id), + printf("Start time (%s): %ld:%ld\n", clock_name(alarm_clock_id), start_time.tv_sec, start_time.tv_nsec); printf("Setting alarm for every %i seconds\n", SUSPEND_SECS); its1.it_value = start_time; diff --git a/tools/testing/selftests/timers/inconsistency-check.c b/tools/testing/selftests/timers/inconsistency-check.c index 9ab7066b4e32..d7982ac4bd18 100644 --- a/tools/testing/selftests/timers/inconsistency-check.c +++ b/tools/testing/selftests/timers/inconsistency-check.c @@ -28,44 +28,14 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 #define CALLS_PER_LOOP 64 -char *clockstring(int clockid) -{ - switch (clockid) { - case CLOCK_REALTIME: - return "CLOCK_REALTIME"; - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_PROCESS_CPUTIME_ID: - return "CLOCK_PROCESS_CPUTIME_ID"; - case CLOCK_THREAD_CPUTIME_ID: - return "CLOCK_THREAD_CPUTIME_ID"; - case CLOCK_MONOTONIC_RAW: - return "CLOCK_MONOTONIC_RAW"; - case CLOCK_REALTIME_COARSE: - return "CLOCK_REALTIME_COARSE"; - case CLOCK_MONOTONIC_COARSE: - return "CLOCK_MONOTONIC_COARSE"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - case CLOCK_REALTIME_ALARM: - return "CLOCK_REALTIME_ALARM"; - case CLOCK_BOOTTIME_ALARM: - return "CLOCK_BOOTTIME_ALARM"; - case CLOCK_TAI: - return "CLOCK_TAI"; - } - return "UNKNOWN_CLOCKID"; -} - /* returns 1 if a <= b, 0 otherwise */ static inline int in_order(struct timespec a, struct timespec b) { @@ -172,15 +142,15 @@ int main(int argc, char *argv[]) for (clockid = userclock; clockid < maxclocks; clockid++) { if (clockid == CLOCK_HWSPECIFIC || clock_gettime(clockid, &ts)) { - ksft_test_result_skip("%-31s\n", clockstring(clockid)); + ksft_test_result_skip("%-31s\n", clock_name(clockid)); continue; } if (consistency_test(clockid, runtime)) { - ksft_test_result_fail("%-31s\n", clockstring(clockid)); + ksft_test_result_fail("%-31s\n", clock_name(clockid)); ksft_exit_fail(); } else { - ksft_test_result_pass("%-31s\n", clockstring(clockid)); + ksft_test_result_pass("%-31s\n", clock_name(clockid)); } } ksft_exit_pass(); diff --git a/tools/testing/selftests/timers/leap-a-day.c b/tools/testing/selftests/timers/leap-a-day.c index 71b49734e08c..b93cb5714b37 100644 --- a/tools/testing/selftests/timers/leap-a-day.c +++ b/tools/testing/selftests/timers/leap-a-day.c @@ -51,10 +51,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - #define CLOCK_TAI 11 time_t next_leap; diff --git a/tools/testing/selftests/timers/mqueue-lat.c b/tools/testing/selftests/timers/mqueue-lat.c index ce8700c95cac..fa4c3e3f58fe 100644 --- a/tools/testing/selftests/timers/mqueue-lat.c +++ b/tools/testing/selftests/timers/mqueue-lat.c @@ -29,10 +29,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - #define TARGET_TIMEOUT 100000000 /* 100ms in nanoseconds */ #define UNRESONABLE_LATENCY 40000000 /* 40ms in nanosecs */ diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index ceff18149a48..7df86dfc897f 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -27,44 +27,14 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 #define UNSUPPORTED 0xf00f -char *clockstring(int clockid) -{ - switch (clockid) { - case CLOCK_REALTIME: - return "CLOCK_REALTIME"; - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_PROCESS_CPUTIME_ID: - return "CLOCK_PROCESS_CPUTIME_ID"; - case CLOCK_THREAD_CPUTIME_ID: - return "CLOCK_THREAD_CPUTIME_ID"; - case CLOCK_MONOTONIC_RAW: - return "CLOCK_MONOTONIC_RAW"; - case CLOCK_REALTIME_COARSE: - return "CLOCK_REALTIME_COARSE"; - case CLOCK_MONOTONIC_COARSE: - return "CLOCK_MONOTONIC_COARSE"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - case CLOCK_REALTIME_ALARM: - return "CLOCK_REALTIME_ALARM"; - case CLOCK_BOOTTIME_ALARM: - return "CLOCK_BOOTTIME_ALARM"; - case CLOCK_TAI: - return "CLOCK_TAI"; - }; - return "UNKNOWN_CLOCKID"; -} - /* returns 1 if a <= b, 0 otherwise */ static inline int in_order(struct timespec a, struct timespec b) { @@ -182,7 +152,7 @@ int main(int argc, char **argv) if (clockid == CLOCK_PROCESS_CPUTIME_ID || clockid == CLOCK_THREAD_CPUTIME_ID || clockid == CLOCK_HWSPECIFIC) { - ksft_test_result_skip("%-31s\n", clockstring(clockid)); + ksft_test_result_skip("%-31s\n", clock_name(clockid)); continue; } @@ -192,21 +162,21 @@ int main(int argc, char **argv) while (length <= (NSEC_PER_SEC * 10)) { ret = nanosleep_test(clockid, length); if (ret == UNSUPPORTED) { - ksft_test_result_skip("%-31s\n", clockstring(clockid)); + ksft_test_result_skip("%-31s\n", clock_name(clockid)); goto next; } if (ret < 0) { - ksft_test_result_fail("%-31s\n", clockstring(clockid)); + ksft_test_result_fail("%-31s\n", clock_name(clockid)); ksft_exit_fail(); } length *= 100; } ret = nanosleep_test_remaining(clockid); if (ret < 0) { - ksft_test_result_fail("%-31s\n", clockstring(clockid)); + ksft_test_result_fail("%-31s\n", clock_name(clockid)); ksft_exit_fail(); } - ksft_test_result_pass("%-31s\n", clockstring(clockid)); + ksft_test_result_pass("%-31s\n", clock_name(clockid)); next: ret = 0; } diff --git a/tools/testing/selftests/timers/nsleep-lat.c b/tools/testing/selftests/timers/nsleep-lat.c index 15f6493ca7b5..d22973c87f21 100644 --- a/tools/testing/selftests/timers/nsleep-lat.c +++ b/tools/testing/selftests/timers/nsleep-lat.c @@ -24,10 +24,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - #define UNRESONABLE_LATENCY 40000000 /* 40ms in nanosecs */ /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ @@ -35,35 +34,6 @@ #define UNSUPPORTED 0xf00f -char *clockstring(int clockid) -{ - switch (clockid) { - case CLOCK_REALTIME: - return "CLOCK_REALTIME"; - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_PROCESS_CPUTIME_ID: - return "CLOCK_PROCESS_CPUTIME_ID"; - case CLOCK_THREAD_CPUTIME_ID: - return "CLOCK_THREAD_CPUTIME_ID"; - case CLOCK_MONOTONIC_RAW: - return "CLOCK_MONOTONIC_RAW"; - case CLOCK_REALTIME_COARSE: - return "CLOCK_REALTIME_COARSE"; - case CLOCK_MONOTONIC_COARSE: - return "CLOCK_MONOTONIC_COARSE"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - case CLOCK_REALTIME_ALARM: - return "CLOCK_REALTIME_ALARM"; - case CLOCK_BOOTTIME_ALARM: - return "CLOCK_BOOTTIME_ALARM"; - case CLOCK_TAI: - return "CLOCK_TAI"; - }; - return "UNKNOWN_CLOCKID"; -} - struct timespec timespec_add(struct timespec ts, unsigned long long ns) { ts.tv_nsec += ns; @@ -156,10 +126,10 @@ int main(int argc, char **argv) } if (ret == UNSUPPORTED) { - ksft_test_result_skip("%s\n", clockstring(clockid)); + ksft_test_result_skip("%s\n", clock_name(clockid)); } else { ksft_test_result(ret >= 0, "%s\n", - clockstring(clockid)); + clock_name(clockid)); } } diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c index fa168d802f9e..a92d4b957747 100644 --- a/tools/testing/selftests/timers/posix_timers.c +++ b/tools/testing/selftests/timers/posix_timers.c @@ -19,11 +19,9 @@ #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL -#define USEC_PER_SEC 1000000LL - #define DELAY 2 static void __fatal_error(const char *test, const char *name, const char *what) @@ -143,8 +141,9 @@ static void check_itimer(int which, const char *name) ksft_test_result(check_diff(start, end) == 0, "%s\n", name); } -static void check_timer_create(int which, const char *name) +static void check_timer_create(int which) { + const char *name = clock_name(which); struct timespec start, end; struct itimerspec val = { .it_value.tv_sec = DELAY, @@ -457,8 +456,9 @@ static void check_delete(void) ksft_test_result(!tsig.signals, "check_delete\n"); } -static void check_sigev_none(int which, const char *name) +static void check_sigev_none(int which) { + const char *name = clock_name(which); struct timespec start, now; struct itimerspec its; struct sigevent sev; @@ -495,8 +495,9 @@ static void check_sigev_none(int which, const char *name) "check_sigev_none %s\n", name); } -static void check_gettime(int which, const char *name) +static void check_gettime(int which) { + const char *name = clock_name(which); struct itimerspec its, prev; struct timespec start, now; struct sigevent sev; @@ -548,8 +549,9 @@ static void check_gettime(int which, const char *name) ksft_test_result(wraps > 1, "check_gettime %s\n", name); } -static void check_overrun(int which, const char *name) +static void check_overrun(int which) { + const char *name = clock_name(which); struct timespec start, now; struct tmrsig tsig = { }; struct itimerspec its; @@ -691,7 +693,7 @@ int main(int argc, char **argv) check_itimer(ITIMER_VIRTUAL, "ITIMER_VIRTUAL"); check_itimer(ITIMER_PROF, "ITIMER_PROF"); check_itimer(ITIMER_REAL, "ITIMER_REAL"); - check_timer_create(CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID"); + check_timer_create(CLOCK_THREAD_CPUTIME_ID); /* * It's unfortunately hard to reliably test a timer expiration @@ -702,7 +704,7 @@ int main(int argc, char **argv) * to ensure true parallelism. So test only one thread until we * find a better solution. */ - check_timer_create(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); + check_timer_create(CLOCK_PROCESS_CPUTIME_ID); check_timer_distribution(); if (run_sig_ign_tests) { @@ -710,18 +712,18 @@ int main(int argc, char **argv) check_sig_ign(1); check_rearm(); check_delete(); - check_sigev_none(CLOCK_MONOTONIC, "CLOCK_MONOTONIC"); - check_sigev_none(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); - check_gettime(CLOCK_MONOTONIC, "CLOCK_MONOTONIC"); - check_gettime(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); - check_gettime(CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID"); + check_sigev_none(CLOCK_MONOTONIC); + check_sigev_none(CLOCK_PROCESS_CPUTIME_ID); + check_gettime(CLOCK_MONOTONIC); + check_gettime(CLOCK_PROCESS_CPUTIME_ID); + check_gettime(CLOCK_THREAD_CPUTIME_ID); } else { ksft_print_msg("Skipping SIG_IGN tests on kernel < 6.13\n"); } - check_overrun(CLOCK_MONOTONIC, "CLOCK_MONOTONIC"); - check_overrun(CLOCK_PROCESS_CPUTIME_ID, "CLOCK_PROCESS_CPUTIME_ID"); - check_overrun(CLOCK_THREAD_CPUTIME_ID, "CLOCK_THREAD_CPUTIME_ID"); + check_overrun(CLOCK_MONOTONIC); + check_overrun(CLOCK_PROCESS_CPUTIME_ID); + check_overrun(CLOCK_THREAD_CPUTIME_ID); ksft_finished(); } diff --git a/tools/testing/selftests/timers/raw_skew.c b/tools/testing/selftests/timers/raw_skew.c index 2dd16cb4cdd0..0c87a8fb0d7f 100644 --- a/tools/testing/selftests/timers/raw_skew.c +++ b/tools/testing/selftests/timers/raw_skew.c @@ -25,10 +25,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - #define shift_right(x, s) ({ \ __typeof__(x) __x = (x); \ __typeof__(s) __s = (s); \ diff --git a/tools/testing/selftests/timers/set-2038.c b/tools/testing/selftests/timers/set-2038.c index c1235638406d..f522a3035ec6 100644 --- a/tools/testing/selftests/timers/set-2038.c +++ b/tools/testing/selftests/timers/set-2038.c @@ -27,10 +27,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - #define KTIME_MAX ((long long)~((unsigned long long)1 << 63)) #define KTIME_SEC_MAX (KTIME_MAX / NSEC_PER_SEC) diff --git a/tools/testing/selftests/timers/set-timer-lat.c b/tools/testing/selftests/timers/set-timer-lat.c index e092c18befff..79ddba25d314 100644 --- a/tools/testing/selftests/timers/set-timer-lat.c +++ b/tools/testing/selftests/timers/set-timer-lat.c @@ -28,10 +28,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL - /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 @@ -44,36 +43,6 @@ struct timespec start_time; long long max_latency_ns; int timer_fired_early; -char *clockstring(int clockid) -{ - switch (clockid) { - case CLOCK_REALTIME: - return "CLOCK_REALTIME"; - case CLOCK_MONOTONIC: - return "CLOCK_MONOTONIC"; - case CLOCK_PROCESS_CPUTIME_ID: - return "CLOCK_PROCESS_CPUTIME_ID"; - case CLOCK_THREAD_CPUTIME_ID: - return "CLOCK_THREAD_CPUTIME_ID"; - case CLOCK_MONOTONIC_RAW: - return "CLOCK_MONOTONIC_RAW"; - case CLOCK_REALTIME_COARSE: - return "CLOCK_REALTIME_COARSE"; - case CLOCK_MONOTONIC_COARSE: - return "CLOCK_MONOTONIC_COARSE"; - case CLOCK_BOOTTIME: - return "CLOCK_BOOTTIME"; - case CLOCK_REALTIME_ALARM: - return "CLOCK_REALTIME_ALARM"; - case CLOCK_BOOTTIME_ALARM: - return "CLOCK_BOOTTIME_ALARM"; - case CLOCK_TAI: - return "CLOCK_TAI"; - } - return "UNKNOWN_CLOCKID"; -} - - long long timespec_sub(struct timespec a, struct timespec b) { long long ret = NSEC_PER_SEC * b.tv_sec + b.tv_nsec; @@ -104,7 +73,7 @@ void sigalarm(int signo) void describe_timer(int flags, int interval) { printf("%-22s %s %s ", - clockstring(clock_id), + clock_name(clock_id), flags ? "ABSTIME":"RELTIME", interval ? "PERIODIC":"ONE-SHOT"); } @@ -130,12 +99,12 @@ int setup_timer(int clock_id, int flags, int interval, timer_t *tm1) if ((clock_id == CLOCK_REALTIME_ALARM) || (clock_id == CLOCK_BOOTTIME_ALARM)) { printf("%-22s %s missing CAP_WAKE_ALARM? : [UNSUPPORTED]\n", - clockstring(clock_id), + clock_name(clock_id), flags ? "ABSTIME":"RELTIME"); /* Indicate timer isn't set, so caller doesn't wait */ return 1; } - printf("%s - timer_create() failed\n", clockstring(clock_id)); + printf("%s - timer_create() failed\n", clock_name(clock_id)); return -1; } @@ -152,7 +121,7 @@ int setup_timer(int clock_id, int flags, int interval, timer_t *tm1) err = timer_settime(*tm1, flags, &its1, &its2); if (err) { - printf("%s - timer_settime() failed\n", clockstring(clock_id)); + printf("%s - timer_settime() failed\n", clock_name(clock_id)); return -1; } diff --git a/tools/testing/selftests/timers/valid-adjtimex.c b/tools/testing/selftests/timers/valid-adjtimex.c index dc2559eb11a5..f641d5fb0902 100644 --- a/tools/testing/selftests/timers/valid-adjtimex.c +++ b/tools/testing/selftests/timers/valid-adjtimex.c @@ -29,11 +29,9 @@ #include #include #include +#include "clock-helpers.h" #include "kselftest.h" -#define NSEC_PER_SEC 1000000000LL -#define USEC_PER_SEC 1000000LL - #define ADJ_SETOFFSET 0x0100 #include From 142a2d312e7bb8e5ad524a4f2115523cf4904732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:39 +0200 Subject: [PATCH 49/58] selftests: timers: nanosleep: Drop output alignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The padding of the clock name is pointless as nothing else is printed. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-8-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nanosleep.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index 7df86dfc897f..629e800135ad 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -152,7 +152,7 @@ int main(int argc, char **argv) if (clockid == CLOCK_PROCESS_CPUTIME_ID || clockid == CLOCK_THREAD_CPUTIME_ID || clockid == CLOCK_HWSPECIFIC) { - ksft_test_result_skip("%-31s\n", clock_name(clockid)); + ksft_test_result_skip("%s\n", clock_name(clockid)); continue; } @@ -162,21 +162,21 @@ int main(int argc, char **argv) while (length <= (NSEC_PER_SEC * 10)) { ret = nanosleep_test(clockid, length); if (ret == UNSUPPORTED) { - ksft_test_result_skip("%-31s\n", clock_name(clockid)); + ksft_test_result_skip("%s\n", clock_name(clockid)); goto next; } if (ret < 0) { - ksft_test_result_fail("%-31s\n", clock_name(clockid)); + ksft_test_result_fail("%s\n", clock_name(clockid)); ksft_exit_fail(); } length *= 100; } ret = nanosleep_test_remaining(clockid); if (ret < 0) { - ksft_test_result_fail("%-31s\n", clock_name(clockid)); + ksft_test_result_fail("%s\n", clock_name(clockid)); ksft_exit_fail(); } - ksft_test_result_pass("%-31s\n", clock_name(clockid)); + ksft_test_result_pass("%s\n", clock_name(clockid)); next: ret = 0; } From 2999620932be363a97c1028f5636deefe08adb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:40 +0200 Subject: [PATCH 50/58] selftests: timers: nanosleep: Explicitly list the tested clocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test relies on the numeric values of the clockids. As not all clocks are meant to be tested, this requires skipping some clocks unconditionally, cluttering the result. Use an equivalent, explicit list instead. In addition to CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID and CLOCK_HWSPECIFIC which were unconditionally skipped before, this also unconditionally skips CLOCK_MONOTONIC_RAW, CLOCK_MONOTONIC_COARSE and CLOCK_REALTIME_COARSE. clock_nanosleep() is not supported for those. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-9-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nanosleep.c | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index 629e800135ad..9dd662226261 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -30,9 +30,6 @@ #include "clock-helpers.h" #include "kselftest.h" -/* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ -#define CLOCK_HWSPECIFIC 10 - #define UNSUPPORTED 0xf00f /* returns 1 if a <= b, 0 otherwise */ @@ -141,20 +138,21 @@ int main(int argc, char **argv) { long long length; int clockid, ret; - int max_clocks = CLOCK_TAI + 1; + + static const clockid_t tested_clocks[] = { + CLOCK_REALTIME, + CLOCK_MONOTONIC, + CLOCK_BOOTTIME, + CLOCK_BOOTTIME_ALARM, + CLOCK_REALTIME_ALARM, + CLOCK_TAI, + }; ksft_print_header(); - ksft_set_plan(max_clocks); + ksft_set_plan(ARRAY_SIZE(tested_clocks)); - for (clockid = CLOCK_REALTIME; clockid < max_clocks; clockid++) { - - /* Skip cputime clockids since nanosleep won't increment cputime */ - if (clockid == CLOCK_PROCESS_CPUTIME_ID || - clockid == CLOCK_THREAD_CPUTIME_ID || - clockid == CLOCK_HWSPECIFIC) { - ksft_test_result_skip("%s\n", clock_name(clockid)); - continue; - } + for (size_t clock_index = 0; clock_index < ARRAY_SIZE(tested_clocks); clock_index++) { + clockid = tested_clocks[clock_index]; fflush(stdout); From bfe5bf0c5fab93f90d851688fe2d65590339ecec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:41 +0200 Subject: [PATCH 51/58] selftests: timers: nanosleep: Reuse kselftest error numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test currently defines its own custom error numbers. These mirror the semantics from the standard KFST_* constants. To make the code easier to read, use the standard result constants. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-10-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nanosleep.c | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index 9dd662226261..0a2ca1791f27 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -30,8 +30,6 @@ #include "clock-helpers.h" #include "kselftest.h" -#define UNSUPPORTED 0xf00f - /* returns 1 if a <= b, 0 otherwise */ static inline int in_order(struct timespec a, struct timespec b) { @@ -60,15 +58,15 @@ int nanosleep_test(int clockid, long long ns) /* First check abs time */ if (clock_gettime(clockid, &now)) - return UNSUPPORTED; + return KSFT_SKIP; target = timespec_add(now, ns); if (clock_nanosleep(clockid, TIMER_ABSTIME, &target, NULL)) - return UNSUPPORTED; + return KSFT_SKIP; clock_gettime(clockid, &now); if (!in_order(target, now)) - return -1; + return KSFT_FAIL; /* Second check reltime */ clock_gettime(clockid, &now); @@ -80,8 +78,8 @@ int nanosleep_test(int clockid, long long ns) clock_gettime(clockid, &now); if (!in_order(target, now)) - return -1; - return 0; + return KSFT_FAIL; + return KSFT_PASS; } static void dummy_event_handler(int val) @@ -100,38 +98,38 @@ static int nanosleep_test_remaining(int clockid) sa.sa_handler = dummy_event_handler; ret = sigaction(SIGALRM, &sa, NULL); if (ret) - return -1; + return KSFT_FAIL; ret = timer_create(clockid, NULL, &timer); if (ret) - return -1; + return KSFT_FAIL; itimer.it_value.tv_nsec = NSEC_PER_SEC / 4; ret = timer_settime(timer, 0, &itimer, NULL); if (ret) - return -1; + return KSFT_FAIL; rqtp.tv_nsec = NSEC_PER_SEC / 2; ret = clock_nanosleep(clockid, 0, &rqtp, &rmtp); if (ret != EINTR) - return -1; + return KSFT_FAIL; ret = timer_delete(timer); if (ret) - return -1; + return KSFT_FAIL; sa.sa_handler = SIG_DFL; ret = sigaction(SIGALRM, &sa, NULL); if (ret) - return -1; + return KSFT_FAIL; if (!in_order((struct timespec) {}, rmtp)) - return -1; + return KSFT_FAIL; if (!in_order(rmtp, rqtp)) - return -1; + return KSFT_FAIL; - return 0; + return KSFT_PASS; } int main(int argc, char **argv) @@ -159,18 +157,18 @@ int main(int argc, char **argv) length = 10; while (length <= (NSEC_PER_SEC * 10)) { ret = nanosleep_test(clockid, length); - if (ret == UNSUPPORTED) { + if (ret == KSFT_SKIP) { ksft_test_result_skip("%s\n", clock_name(clockid)); goto next; } - if (ret < 0) { + if (ret == KSFT_FAIL) { ksft_test_result_fail("%s\n", clock_name(clockid)); ksft_exit_fail(); } length *= 100; } ret = nanosleep_test_remaining(clockid); - if (ret < 0) { + if (ret == KSFT_FAIL) { ksft_test_result_fail("%s\n", clock_name(clockid)); ksft_exit_fail(); } From 061341a568146000de8d41791bf7612b2f6813b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:42 +0200 Subject: [PATCH 52/58] selftests: timers: nanosleep: Move all single clock tests out of the loop in main() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the code easier to read by avoiding a goto. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-11-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nanosleep.c | 51 ++++++++++++---------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index 0a2ca1791f27..5037481516df 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -132,10 +132,35 @@ static int nanosleep_test_remaining(int clockid) return KSFT_PASS; } +static void nanosleep_test_clock(clockid_t clockid) +{ + long long length = 10; + int ret; + + while (length <= (NSEC_PER_SEC * 10)) { + ret = nanosleep_test(clockid, length); + if (ret == KSFT_SKIP) { + ksft_test_result_skip("%s\n", clock_name(clockid)); + return; + } + if (ret == KSFT_FAIL) { + ksft_test_result_fail("%s\n", clock_name(clockid)); + ksft_exit_fail(); + } + length *= 100; + } + + ret = nanosleep_test_remaining(clockid); + if (ret == KSFT_FAIL) { + ksft_test_result_fail("%s\n", clock_name(clockid)); + ksft_exit_fail(); + } + ksft_test_result_pass("%s\n", clock_name(clockid)); +} + int main(int argc, char **argv) { - long long length; - int clockid, ret; + int clockid; static const clockid_t tested_clocks[] = { CLOCK_REALTIME, @@ -154,27 +179,7 @@ int main(int argc, char **argv) fflush(stdout); - length = 10; - while (length <= (NSEC_PER_SEC * 10)) { - ret = nanosleep_test(clockid, length); - if (ret == KSFT_SKIP) { - ksft_test_result_skip("%s\n", clock_name(clockid)); - goto next; - } - if (ret == KSFT_FAIL) { - ksft_test_result_fail("%s\n", clock_name(clockid)); - ksft_exit_fail(); - } - length *= 100; - } - ret = nanosleep_test_remaining(clockid); - if (ret == KSFT_FAIL) { - ksft_test_result_fail("%s\n", clock_name(clockid)); - ksft_exit_fail(); - } - ksft_test_result_pass("%s\n", clock_name(clockid)); -next: - ret = 0; + nanosleep_test_clock(clockid); } ksft_exit_pass(); } From f2ff28a52255f04ab0de16a7d298e35360852890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:43 +0200 Subject: [PATCH 53/58] selftests: timers: nanosleep: Explicitly handle timer_delete() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An upcoming patch will remove the early exit on failure of a single testcase. If a test fails because a timeout timer could not be deleted, then that timeout timer might interfere with the following tettcases. Exit the whole test case with a clear error message in this case. Other failures are not problematic: * If timer_settime() fails, a harmless unarmed timer is left around. * The SIGALRM handler reconfigured for each call to nanosleep_test_remaining() anyways. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-12-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nanosleep.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index 5037481516df..dd899dcfb2cb 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -111,11 +111,14 @@ static int nanosleep_test_remaining(int clockid) rqtp.tv_nsec = NSEC_PER_SEC / 2; ret = clock_nanosleep(clockid, 0, &rqtp, &rmtp); - if (ret != EINTR) - return KSFT_FAIL; - ret = timer_delete(timer); - if (ret) + if (timer_delete(timer)) { + ksft_exit_fail_msg("Unable to delete the timeout timer for %s. " + "This might interfere with following testcases.\n", + clock_name(clockid)); + } + + if (ret != EINTR) return KSFT_FAIL; sa.sa_handler = SIG_DFL; From a1211288234058b0d13e2cc4a910b556c5308fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:44 +0200 Subject: [PATCH 54/58] selftests: timers: nanosleep: Report each test separately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the test for the sleep functionality itself and the one for the remaining time are reported together. This makes the test output a bit confusing, especially as the upcoming tests for auxiliary clocks will have different results between the two parts. Report each test part on its own. This also allows continuing testing other clocks after a single one has failed. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-13-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nanosleep.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/timers/nanosleep.c b/tools/testing/selftests/timers/nanosleep.c index dd899dcfb2cb..b45e4c855259 100644 --- a/tools/testing/selftests/timers/nanosleep.c +++ b/tools/testing/selftests/timers/nanosleep.c @@ -142,23 +142,18 @@ static void nanosleep_test_clock(clockid_t clockid) while (length <= (NSEC_PER_SEC * 10)) { ret = nanosleep_test(clockid, length); - if (ret == KSFT_SKIP) { - ksft_test_result_skip("%s\n", clock_name(clockid)); + if (ret != KSFT_PASS) { + ksft_test_result_report(ret, "%s\n", clock_name(clockid)); + ksft_test_result_skip("%s (remaining)\n", clock_name(clockid)); return; } - if (ret == KSFT_FAIL) { - ksft_test_result_fail("%s\n", clock_name(clockid)); - ksft_exit_fail(); - } + length *= 100; } + ksft_test_result_pass("%s\n", clock_name(clockid)); ret = nanosleep_test_remaining(clockid); - if (ret == KSFT_FAIL) { - ksft_test_result_fail("%s\n", clock_name(clockid)); - ksft_exit_fail(); - } - ksft_test_result_pass("%s\n", clock_name(clockid)); + ksft_test_result_report(ret, "%s (remaining)\n", clock_name(clockid)); } int main(int argc, char **argv) @@ -175,7 +170,7 @@ int main(int argc, char **argv) }; ksft_print_header(); - ksft_set_plan(ARRAY_SIZE(tested_clocks)); + ksft_set_plan(ARRAY_SIZE(tested_clocks) * 2); for (size_t clock_index = 0; clock_index < ARRAY_SIZE(tested_clocks); clock_index++) { clockid = tested_clocks[clock_index]; @@ -184,5 +179,5 @@ int main(int argc, char **argv) nanosleep_test_clock(clockid); } - ksft_exit_pass(); + ksft_finished(); } From a00f90877dcd4176da3564f5f126547aa3ddfead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:45 +0200 Subject: [PATCH 55/58] selftests: timers: nsleep-lat: Use NSEC_PER_MSEC define for unreasonable latency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the predefined symbol to make the code easier to understand. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-14-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nsleep-lat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/timers/nsleep-lat.c b/tools/testing/selftests/timers/nsleep-lat.c index d22973c87f21..d46f17404439 100644 --- a/tools/testing/selftests/timers/nsleep-lat.c +++ b/tools/testing/selftests/timers/nsleep-lat.c @@ -27,7 +27,7 @@ #include "clock-helpers.h" #include "kselftest.h" -#define UNRESONABLE_LATENCY 40000000 /* 40ms in nanosecs */ +#define UNRESONABLE_LATENCY (40 * NSEC_PER_MSEC) /* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ #define CLOCK_HWSPECIFIC 10 From 187ac443ed51e04dcf0df98d270863c3f54af8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:46 +0200 Subject: [PATCH 56/58] selftests: timers: nsleep-lat: Explicitly list the tested clocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test relies on the numeric values of the clockids. As not all clocks are meant to be tested, this requires skipping some clocks unconditionally, cluttering the result. Use an equivalent, explicit list instead. In addition to CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID and CLOCK_HWSPECIFIC which were unconditionally skipped before, this also unconditionally skips CLOCK_MONOTONIC_RAW, CLOCK_MONOTONIC_COARSE and CLOCK_REALTIME_COARSE. clock_nanosleep() is not supported for those. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-15-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nsleep-lat.c | 26 ++++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tools/testing/selftests/timers/nsleep-lat.c b/tools/testing/selftests/timers/nsleep-lat.c index d46f17404439..af47c28f086b 100644 --- a/tools/testing/selftests/timers/nsleep-lat.c +++ b/tools/testing/selftests/timers/nsleep-lat.c @@ -29,9 +29,6 @@ #define UNRESONABLE_LATENCY (40 * NSEC_PER_MSEC) -/* CLOCK_HWSPECIFIC == CLOCK_SGI_CYCLE (Deprecated) */ -#define CLOCK_HWSPECIFIC 10 - #define UNSUPPORTED 0xf00f struct timespec timespec_add(struct timespec ts, unsigned long long ns) @@ -97,24 +94,25 @@ int nanosleep_lat_test(int clockid, long long ns) return 0; } -#define SKIPPED_CLOCK_COUNT 3 - int main(int argc, char **argv) { long long length; int clockid, ret; - int max_clocks = CLOCK_TAI + 1; + + static const clockid_t tested_clocks[] = { + CLOCK_REALTIME, + CLOCK_MONOTONIC, + CLOCK_BOOTTIME, + CLOCK_BOOTTIME_ALARM, + CLOCK_REALTIME_ALARM, + CLOCK_TAI, + }; ksft_print_header(); - ksft_set_plan(max_clocks - CLOCK_REALTIME - SKIPPED_CLOCK_COUNT); + ksft_set_plan(ARRAY_SIZE(tested_clocks)); - for (clockid = CLOCK_REALTIME; clockid < max_clocks; clockid++) { - - /* Skip cputime clockids since nanosleep won't increment cputime */ - if (clockid == CLOCK_PROCESS_CPUTIME_ID || - clockid == CLOCK_THREAD_CPUTIME_ID || - clockid == CLOCK_HWSPECIFIC) - continue; + for (size_t clock_index = 0; clock_index < ARRAY_SIZE(tested_clocks); clock_index++) { + clockid = tested_clocks[clock_index]; length = 10; while (length <= (NSEC_PER_SEC * 10)) { From 0f474ee80c7ba1cab784c12a12232c53db5ad1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:47 +0200 Subject: [PATCH 57/58] selftests: timers: nsleep-lat: Reuse kselftest error numbers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test currently defines its own custom error numbers. These mirror the semantics from the standard KFST_* constants. To make the code easier to read, use the standard result constants. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-16-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nsleep-lat.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tools/testing/selftests/timers/nsleep-lat.c b/tools/testing/selftests/timers/nsleep-lat.c index af47c28f086b..266ca0cc37ec 100644 --- a/tools/testing/selftests/timers/nsleep-lat.c +++ b/tools/testing/selftests/timers/nsleep-lat.c @@ -29,8 +29,6 @@ #define UNRESONABLE_LATENCY (40 * NSEC_PER_MSEC) -#define UNSUPPORTED 0xf00f - struct timespec timespec_add(struct timespec ts, unsigned long long ns) { ts.tv_nsec += ns; @@ -60,9 +58,9 @@ int nanosleep_lat_test(int clockid, long long ns) target.tv_nsec = ns%NSEC_PER_SEC; if (clock_gettime(clockid, &start)) - return UNSUPPORTED; + return KSFT_SKIP; if (clock_nanosleep(clockid, 0, &target, NULL)) - return UNSUPPORTED; + return KSFT_SKIP; count = 10; @@ -74,7 +72,7 @@ int nanosleep_lat_test(int clockid, long long ns) if (((timespec_sub(start, end)/count)-ns) > UNRESONABLE_LATENCY) { ksft_print_msg("Large rel latency: %lld ns :", (timespec_sub(start, end)/count)-ns); - return -1; + return KSFT_FAIL; } /* Next check absolute latency */ @@ -88,10 +86,10 @@ int nanosleep_lat_test(int clockid, long long ns) if (latency/count > UNRESONABLE_LATENCY) { ksft_print_msg("Large abs latency: %lld ns :", latency/count); - return -1; + return KSFT_FAIL; } - return 0; + return KSFT_PASS; } int main(int argc, char **argv) @@ -123,12 +121,7 @@ int main(int argc, char **argv) } - if (ret == UNSUPPORTED) { - ksft_test_result_skip("%s\n", clock_name(clockid)); - } else { - ksft_test_result(ret >= 0, "%s\n", - clock_name(clockid)); - } + ksft_test_result_report(ret, "%s\n", clock_name(clockid)); } ksft_finished(); From 4fa377c19e111c539a530a8200996b911ceff9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh=20=28Schneider=20Electric=29?= Date: Mon, 3 Aug 2026 12:04:48 +0200 Subject: [PATCH 58/58] selftests: timers: nsleep-lat: Check all calls to clock_nanosleep() and clock_gettime() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both these functions can fail. The first calls to those functions are already checked and result in KSFT_SKIP. If they start failing afterwards unexpectedly, report a hard error. Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260803-auxclock-nanosleep-prep-v2-17-910cbd485390@linutronix.de --- tools/testing/selftests/timers/nsleep-lat.c | 23 ++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/timers/nsleep-lat.c b/tools/testing/selftests/timers/nsleep-lat.c index 266ca0cc37ec..5de0051ac8e3 100644 --- a/tools/testing/selftests/timers/nsleep-lat.c +++ b/tools/testing/selftests/timers/nsleep-lat.c @@ -65,10 +65,16 @@ int nanosleep_lat_test(int clockid, long long ns) count = 10; /* First check relative latency */ - clock_gettime(clockid, &start); - for (i = 0; i < count; i++) - clock_nanosleep(clockid, 0, &target, NULL); - clock_gettime(clockid, &end); + if (clock_gettime(clockid, &start)) + return KSFT_FAIL; + + for (i = 0; i < count; i++) { + if (clock_nanosleep(clockid, 0, &target, NULL)) + return KSFT_FAIL; + } + + if (clock_gettime(clockid, &end)) + return KSFT_FAIL; if (((timespec_sub(start, end)/count)-ns) > UNRESONABLE_LATENCY) { ksft_print_msg("Large rel latency: %lld ns :", (timespec_sub(start, end)/count)-ns); @@ -77,10 +83,13 @@ int nanosleep_lat_test(int clockid, long long ns) /* Next check absolute latency */ for (i = 0; i < count; i++) { - clock_gettime(clockid, &start); + if (clock_gettime(clockid, &start)) + return KSFT_FAIL; target = timespec_add(start, ns); - clock_nanosleep(clockid, TIMER_ABSTIME, &target, NULL); - clock_gettime(clockid, &end); + if (clock_nanosleep(clockid, TIMER_ABSTIME, &target, NULL)) + return KSFT_FAIL; + if (clock_gettime(clockid, &end)) + return KSFT_FAIL; latency += timespec_sub(target, end); }