From 034b5779b85b8ce2eacf235f41af15c21d10cf54 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Thu, 25 Jun 2026 22:09:01 +0800 Subject: 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(-) (limited to 'kernel/time') 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) { -- cgit v1.2.3 From 920f893f735e92ba3a1cd9256899a186b161928d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 3 Jul 2026 12:02:38 +0200 Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race Wongi and Jungwoo decoded and reported a non-leader exec() related race which can result in an UAF: sys_timer_delete() exec() posix_cpu_timer_del() // Observes old leader p = pid_task(pid, pid_type); de_thread() switch_leader(); release_task(old_leader) __exit_signal(old_leader) sighand = lock(old_leader, sighand); posix_cpu_timers*_exit(); sighand = lock_task_sighand(p) unhash_task(old_leader); sh = lock(p, sighand) old_leader->sighand = NULL; unlock(sighand); (p->sighand == NULL) unlock(sh) return NULL; // Returns without action if(!sighand) return 0; free_posix_timer(); This is "harmless" unless the deleted timer was armed and enqueued in p->signal because on exec() a TGID targeted timer is inherited. As sys_timer_delete() freed the underlying posix timer object run_posix_cpu_timers() or any timerqueue related add/delete operations on other timers will access the freed object's timerqueue node, which results in an UAF. There is a similar problem vs. posix_cpu_timer_set(). For regular posix timers it just transiently returns -ESRCH to user space, but for the use case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is allocated on the stack. Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops to expire. While debating solutions Frederic pointed out another problem: posix_cpu_timer_del(tmr) __exit_signal(p) posix_cpu_timers*_exit(p); unhash_task(p); p->sighand = NULL; sh = lock_task_sighand(p) sighand = p->sighand; if (!sighand) return NULL; lock(sighand); if (!sh) WARN_ON_ONCE(timer_queued(tmr)); On weakly ordered architectures it is not guaranteed that posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() when p->sighand is observed as NULL, which means the WARN() can be a false positive. Solve these issues by: 1) Changing the store in __exit_signal() to smp_store_release(). 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path of lock_task_sighand(). 3) Creating a helper function for looking up the task and locking sighand which does not return when sighand == NULL. Instead it retries the task lookup and only if that fails it gives up. 4) Using that helper in the three affected functions. #1/#2 ensures that the reader side which observes sighand == NULL also observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() and the ones in unhash_task(). #3 ensures that the above described non-leader exec() situation is handled gracefully. When the task lookup returns the old leader, but sighand == NULL then it retries. In the non-leader exec() case the subsequent task lookup will observe the new leader due to #1/#2. In normal exit() scenarios the subsequent lookup fails. When the task lookup fails, the function also checks whether the timer is still enqueued and issues a warning if that's the case. Unfortunately there is nothing which can be done about it, but as the task is already not longer visible the timer should not be accessed anymore. This check also requires memory ordering, which is not provided when the first lookup fails. To achieve that the check is preceeded by a smp_rmb() which pairs with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that the stores in posix_cpu_timers*_exit() are visible. The history of the non-leader exec() issue goes back to the early days of posix CPU timers, which stored a pointer to the group leader task in the timer. That obviously fails when a non-leader exec() switches the leader. commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems with mt exec") added a temporary workaround for that in 2010 which survived about 10 years. The fix for the workaround changed the task pointer to a pid pointer, but failed to see the subtle race described above. So the Fixes tag picks that commit, which seems to be halfways accurate. Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for review, feedback and suggestions and to Wongi and Jungwoo for the excellent bug report and analysis! Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") Reported-by: Wongi Lee Reported-by: Jungwoo Lee Signed-off-by: Thomas Gleixner Reviewed-by: Oleg Nesterov Cc: stable@vger.kernel.org --- kernel/exit.c | 7 +- kernel/signal.c | 10 ++- kernel/time/posix-cpu-timers.c | 173 +++++++++++++++++++++++++++-------------- 3 files changed, 130 insertions(+), 60 deletions(-) (limited to 'kernel/time') diff --git a/kernel/exit.c b/kernel/exit.c index 1056422bc101..2c0b1c02920f 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -212,7 +212,12 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts __unhash_process(post, tsk, group_dead); write_sequnlock(&sig->stats_lock); - tsk->sighand = NULL; + /* + * Ensure that all preceeding state is visible. Pairs with + * the smp_acquire__after_ctrl_dep() in the sighand == NULL + * path of lock_task_sighand(). + */ + smp_store_release(&tsk->sighand, NULL); spin_unlock(&sighand->siglock); __cleanup_sighand(sighand); diff --git a/kernel/signal.c b/kernel/signal.c index 9c2b32c4d755..bbc0fd4cc4d7 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -1362,8 +1362,16 @@ struct sighand_struct *lock_task_sighand(struct task_struct *tsk, rcu_read_lock(); for (;;) { sighand = rcu_dereference(tsk->sighand); - if (unlikely(sighand == NULL)) + if (unlikely(sighand == NULL)) { + /* + * Pairs with the smp_store_release() in + * __exit_signal(). It ensures that all state + * modifications to the task preceeding the store are + * visible to the callers of lock_task_sighand(). + */ + smp_acquire__after_ctrl_dep(); break; + } /* * This sighand can be already freed and even reused, but diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index 5e633d8750d1..a7d3e8229c4b 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -461,6 +461,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) trigger_base_recalc_expires(timer, p); } +/* + * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. + * + * This can race with the reaping of the task: + * + * CPU0 CPU1 + * + * // Finds task + * p = pid_task(pid, pid_type); __exit_signal(p) + * lock(p, sighand); + * posix_cpu_timers*_exit(); + * sighand = lock_task_sighand(p); unhash_task(p); + * p->sighand = NULL; + * unlock(sighand); + * + * In this case sighand is NULL, which means the task and the associated timer + * queue cannot be longer accessed safely. + * + * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is + * dead it also invokes posix_cpu_timers_group_exit(). These functions delete + * all pending timers from the related timer queues. The POSIX timers (k_itimer) + * themself are still accessible, but not longer connected to the task. + * + * exec() works slightly differently. The task which exec()'s terminates all + * other threads in the thread group and runs __exit_signal() on them. As the + * thread group is not dead they only clean up the per task timers via + * posix_cpu_timers_exit(). + * + * As the TGID on exec() stays the same per process timers stay queued, if they + * are armed. This works without a problem when exec() is done by the thread + * group leader. If a non-leader thread exec()'s this can end up in the + * following scenario: + * + * CPU0 CPU1 + * // Returns old leader + * p = pid_task(pid, pid_type); de_thread() + * switch_leader() + * release_task(old leader) + * __exit_signal() + * old_leader->sighand = NULL; + * // Returns NULL + * sighand = lock_task_sighand(p) + * + * That's problematic for several functions: + * + * - posix_cpu_timer_del(): If the timer is still enqueued on the task the + * underlying k_itimer will be freed which results in a UAF in + * run_posix_cpu_timers() or on timerqueue related add/delete operations. + * If the timer is not enqueued, the failure is harmless + * + * - posix_cpu_timer_set(): Independent of the enqueued state that results in a + * transient failure which is user space visible (-ESRCH) for regular posix + * timers. But for the use case in do_cpu_nanosleep() it's the same UAF + * problem just that the timer is allocated on the stack. + * + * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this + * silently ignores the rearm request, which is a functional problem as the + * timer wont expire anymore. + */ +static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) +{ + enum pid_type type = clock_pid_type(timer->it_clock); + struct cpu_timer *ctmr = &timer->it.cpu; + + guard(rcu)(); + + for (;;) { + struct task_struct *t = pid_task(timer->it.cpu.pid, type); + + /* Fail if the task cannot be found. */ + if (!t) + break; + + /* Try to lock the task's sighand */ + if (lock_task_sighand(t, flags)) + return t; + + /* + * The next PID lookup might either fail or return the new + * leader. This is correct for both exit() and exec(). + */ + } + + /* + * If the timer is still enqueued, warn. There is nothing safe to do + * here as there might be two timers in there which are removed in + * parallel and that will cause more damage than good. This should never + * happen! + * + * Ensure that the stores to the timer and timerqueue are visible: + * + * __exit_signal() + * posix_cpu_timers*_exit() + * write_seqlock(seqlock) + * smp_wmb(); <------- + * __unhash_process() | !pid_task() + * ----> smp_rmb(); + * WARN_ON_ONCE(...) + */ + smp_rmb(); + WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); + return NULL; +} /* * Clean up a CPU-clock timer that is about to be destroyed. @@ -470,29 +573,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) */ static int posix_cpu_timer_del(struct k_itimer *timer) { - struct cpu_timer *ctmr = &timer->it.cpu; - struct sighand_struct *sighand; struct task_struct *p; unsigned long flags; int ret = 0; - rcu_read_lock(); - p = cpu_timer_task_rcu(timer); - if (!p) - goto out; + p = timer_lock_sighand(timer, &flags); - /* - * Protect against sighand release/switch in exit/exec and process/ - * thread timer list entry concurrent read/writes. - */ - sighand = lock_task_sighand(p, &flags); - if (unlikely(sighand == NULL)) { - /* - * This raced with the reaping of the task. The exit cleanup - * should have removed this timer from the timer queue. - */ - WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); - } else { + if (likely(p)) { if (timer->it.cpu.firing) { /* * Prevent signal delivery. The timer cannot be dequeued @@ -508,11 +595,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) unlock_task_sighand(p, &flags); } -out: - rcu_read_unlock(); - if (!ret) { - put_pid(ctmr->pid); + put_pid(timer->it.cpu.pid); timer->it_status = POSIX_TIMER_DISARMED; } return ret; @@ -626,21 +710,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); struct cpu_timer *ctmr = &timer->it.cpu; u64 old_expires, new_expires, now; - struct sighand_struct *sighand; struct task_struct *p; unsigned long flags; int ret = 0; - rcu_read_lock(); - p = cpu_timer_task_rcu(timer); - if (!p) { - /* - * If p has just been reaped, we can no - * longer get any information about it at all. - */ - rcu_read_unlock(); + p = timer_lock_sighand(timer, &flags); + /* + * If p has just been reaped, we can no longer get any information about + * it at all. + */ + if (!p) return -ESRCH; - } /* * Use the to_ktime conversion because that clamps the maximum @@ -648,20 +728,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, */ new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); - /* - * Protect against sighand release/switch in exit/exec and p->cpu_timers - * and p->signal->cpu_timers read/write in arm_timer() - */ - sighand = lock_task_sighand(p, &flags); - /* - * If p has just been reaped, we can no - * longer get any information about it at all. - */ - if (unlikely(sighand == NULL)) { - rcu_read_unlock(); - return -ESRCH; - } - /* Retrieve the current expiry time before disarming the timer */ old_expires = cpu_timer_getexpires(ctmr); @@ -698,7 +764,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, /* Retry if the timer expiry is running concurrently */ if (unlikely(ret)) { unlock_task_sighand(p, &flags); - goto out; + return ret; } /* Convert relative expiry time to absolute */ @@ -733,8 +799,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, */ if (!sigev_none && new_expires && now >= new_expires) cpu_timer_fire(timer); -out: - rcu_read_unlock(); return ret; } @@ -1018,19 +1082,12 @@ static void check_process_timers(struct task_struct *tsk, static bool posix_cpu_timer_rearm(struct k_itimer *timer) { clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); - struct sighand_struct *sighand; struct task_struct *p; unsigned long flags; u64 now; - guard(rcu)(); - p = cpu_timer_task_rcu(timer); - if (!p) - return true; - - /* Protect timer list r/w in arm_timer() */ - sighand = lock_task_sighand(p, &flags); - if (unlikely(sighand == NULL)) + p = timer_lock_sighand(timer, &flags); + if (unlikely(!p)) return true; /* -- cgit v1.2.3 From a73d7f98e41a96d6e1bcb0e731ab185d9d67878e Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sat, 4 Jul 2026 13:09:36 +0200 Subject: 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(-) (limited to 'kernel/time') 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, -- cgit v1.2.3 From e2904ddb14a4198ad31eb12a072a6923f0c8ca09 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 5 Jul 2026 14:38:04 +0200 Subject: 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(-) (limited to 'kernel/time') 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, -- cgit v1.2.3 From d8966ca88566d94de9958a84f93bd08bfc431ee6 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 6 Jul 2026 16:45:56 +0800 Subject: 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(-) (limited to 'kernel/time') 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) -- cgit v1.2.3 From b3afded935a8528d3df57198aaa90651995622c5 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 6 Jul 2026 16:45:57 +0800 Subject: 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(-) (limited to 'kernel/time') 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) -- cgit v1.2.3 From 3dee6537e728bd8137fda6eaf859f26e685943f7 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 2 Jul 2026 17:57:33 -0400 Subject: 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(-) (limited to 'kernel/time') 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; } -- cgit v1.2.3 From f2eee7e31ccd4bc87d047d8670cc2ec39cf36647 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Fri, 3 Jul 2026 12:53:37 -0400 Subject: 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(-) (limited to 'kernel/time') 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); -- cgit v1.2.3 From eddfded4196542deda7cb2da3d7ebef83f7ccfa4 Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sat, 4 Jul 2026 14:25:33 +0530 Subject: 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(-) (limited to 'kernel/time') 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]; -- cgit v1.2.3 From 06aba58e58492d2b8eae059274caed29025ea96e Mon Sep 17 00:00:00 2001 From: Malaya Kumar Rout Date: Sat, 4 Jul 2026 15:04:28 +0530 Subject: 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(-) (limited to 'kernel/time') 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 -- cgit v1.2.3 From 1d28a67d496f482933b0ba5d8577835bdbe601ca Mon Sep 17 00:00:00 2001 From: Yu Peng Date: Tue, 7 Jul 2026 20:58:13 +0800 Subject: 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(-) (limited to 'kernel/time') 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"); } -- cgit v1.2.3 From c4415c993fc2c8bdf2cf0bfbcbb1ac0e0f7a9eaf Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Tue, 7 Jul 2026 08:38:00 -0700 Subject: 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(-) (limited to 'kernel/time') 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; - guard(raw_spinlock_irqsave)(&cpu_base->lock); - if (!hrtimer_hres_active(cpu_base)) - expires = __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL); + /* + * 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; - return expires; + guard(raw_spinlock_irqsave)(&cpu_base->lock); + return __hrtimer_get_next_event(cpu_base, HRTIMER_ACTIVE_ALL); } /** -- cgit v1.2.3 From a116c7582d7f7701736801a47e3432eb7cabd674 Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Thu, 2 Jul 2026 11:42:00 +0200 Subject: 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(-) (limited to 'kernel/time') 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 -- cgit v1.2.3 From 73fcec09d1629afa8819a866518cc0e8d58a7ece Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Thu, 2 Jul 2026 11:42:01 +0200 Subject: 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(+) (limited to 'kernel/time') 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" -- cgit v1.2.3 From 95cf8bbadd103944c35c55e8c4cb85fa9a4ae84b Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Thu, 2 Jul 2026 11:42:02 +0200 Subject: 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(+) (limited to 'kernel/time') 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 -- cgit v1.2.3 From 79bd39c58f2c6fdbc5fb6300d309606f3cb84ab8 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 7 Jul 2026 08:07:15 +0200 Subject: 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(-) (limited to 'kernel/time') 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 */ -- cgit v1.2.3 From 9ab500d47f5f1b8c463a4fd8f345a8756da625ab Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 30 Jun 2026 09:31:58 +0200 Subject: vdso/datastore: Explicitly prevent remote access to timens vvar page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fault handler for the timens page does not have access to the target task and therefore can not be invoked remotely. Currently the handler relies on the fact that the vvar mapping is marked as VM_IO and VM_PFNMAP for which the mm core always prevents remote access. However the VM_IO and VM_PFNMAP flags are going to be removed. Add an explicit check to prevent remote access to the mapping. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Tested-by: Nam Cao Link: https://patch.msgid.link/20260630-vdso-mlockall-v4-4-6c93708ce723@linutronix.de --- kernel/time/namespace_vdso.c | 7 ++----- lib/vdso/datastore.c | 3 +++ 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/namespace_vdso.c b/kernel/time/namespace_vdso.c index 0d74d160eec9..5ac7b6a6d3a8 100644 --- a/kernel/time/namespace_vdso.c +++ b/kernel/time/namespace_vdso.c @@ -74,11 +74,8 @@ struct page *find_timens_vvar_page(struct vm_area_struct *vma) return current->nsproxy->time_ns->vvar_page; /* - * VM_PFNMAP | VM_IO protect .fault() handler from being called - * through interfaces like /proc/$pid/mem or - * process_vm_{readv,writev}() as long as there's no .access() - * in special_mapping_vmops(). - * For more details check_vma_flags() and __access_remote_vm() + * vvar_fault() protects this from being called through remote interfaces like + * /proc/$pid/mem or process_vm_{readv,writev}(). */ WARN(1, "vvar_page accessed remotely"); diff --git a/lib/vdso/datastore.c b/lib/vdso/datastore.c index cf7aa80355cf..0ca43965a8a9 100644 --- a/lib/vdso/datastore.c +++ b/lib/vdso/datastore.c @@ -68,6 +68,9 @@ static vm_fault_t vvar_fault(const struct vm_special_mapping *sm, { struct page *page, *timens_page; + if (unlikely(vmf->flags & FAULT_FLAG_REMOTE)) + return VM_FAULT_SIGBUS; + page = vdso_data_pages + vmf->pgoff; timens_page = find_timens_vvar_page(vma); -- cgit v1.2.3 From dce21fb3d570169388e2e9d8a60e8d42c1c82a51 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Thu, 2 Jul 2026 10:21:25 +0200 Subject: time: Respect COMPAT_32BIT_TIME for old time type functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "old" time types use 32-bit seconds which are not y2038-safe. Respect COMPAT_32BIT_TIME for functions using those types. time(), stime() and gettimeofday() are disabled completely. settimeofday() is kept as it is required to do the initial timewarping after boot. However the 'tv' argument will be rejected. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Reviewed-by: Arnd Bergmann Link: https://lore.kernel.org/lkml/e9487ebe-3730-438a-9c23-e45f75986ecc@app.fastmail.com/ Link: https://patch.msgid.link/20260702-vdso-compat_32bit_time-v3-1-db9f36d8d432@linutronix.de --- kernel/sys_ni.c | 4 ++++ kernel/time/time.c | 24 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) (limited to 'kernel/time') diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c index add3032da16f..c8be0abaa407 100644 --- a/kernel/sys_ni.c +++ b/kernel/sys_ni.c @@ -351,6 +351,10 @@ COND_SYSCALL(ppoll_time32); COND_SYSCALL_COMPAT(ppoll_time32); COND_SYSCALL(utimensat_time32); COND_SYSCALL(clock_adjtime32); +COND_SYSCALL(gettimeofday); +COND_SYSCALL_COMPAT(gettimeofday); +COND_SYSCALL(time); +COND_SYSCALL(stime); /* * The syscalls below are not found in include/uapi/asm-generic/unistd.h diff --git a/kernel/time/time.c b/kernel/time/time.c index 0dd63a91e7c5..f836a31751f4 100644 --- a/kernel/time/time.c +++ b/kernel/time/time.c @@ -43,6 +43,12 @@ #include #include "timekeeping.h" +#if defined(CONFIG_64BIT) || defined(CONFIG_COMPAT_32BIT_TIME) +#define __WANT_OLD_TIME_TYPE_SYSCALL 1 +#endif + +static_assert(sizeof(__kernel_old_time_t) == 8 ? IS_ENABLED(__WANT_OLD_TIME_TYPE_SYSCALL) : true); + /* * The timezone where the local system is located. Used as a default by some * programs who obtain this value by using gettimeofday. @@ -51,7 +57,7 @@ struct timezone sys_tz; EXPORT_SYMBOL(sys_tz); -#ifdef __ARCH_WANT_SYS_TIME +#if defined(__ARCH_WANT_SYS_TIME) && defined(__WANT_OLD_TIME_TYPE_SYSCALL) /* * sys_time() can be implemented in user-level using @@ -96,7 +102,7 @@ SYSCALL_DEFINE1(stime, __kernel_old_time_t __user *, tptr) return 0; } -#endif /* __ARCH_WANT_SYS_TIME */ +#endif /* __ARCH_WANT_SYS_TIME && __WANT_OLD_TIME_TYPE_SYSCALL */ #ifdef CONFIG_COMPAT_32BIT_TIME #ifdef __ARCH_WANT_SYS_TIME32 @@ -137,6 +143,7 @@ SYSCALL_DEFINE1(stime32, old_time32_t __user *, tptr) #endif /* __ARCH_WANT_SYS_TIME32 */ #endif +#ifdef __WANT_OLD_TIME_TYPE_SYSCALL SYSCALL_DEFINE2(gettimeofday, struct __kernel_old_timeval __user *, tv, struct timezone __user *, tz) { @@ -154,6 +161,7 @@ SYSCALL_DEFINE2(gettimeofday, struct __kernel_old_timeval __user *, tv, } return 0; } +#endif /* __WANT_OLD_TIME_TYPE_SYSCALL */ /* * In case for some reason the CMOS clock has not already been running @@ -203,6 +211,9 @@ SYSCALL_DEFINE2(settimeofday, struct __kernel_old_timeval __user *, tv, struct timezone new_tz; if (tv) { + if (!IS_ENABLED(__WANT_OLD_TIME_TYPE_SYSCALL)) + return -EINVAL; + if (get_user(new_ts.tv_sec, &tv->tv_sec) || get_user(new_ts.tv_nsec, &tv->tv_usec)) return -EFAULT; @@ -220,7 +231,7 @@ SYSCALL_DEFINE2(settimeofday, struct __kernel_old_timeval __user *, tv, return do_sys_settimeofday64(tv ? &new_ts : NULL, tz ? &new_tz : NULL); } -#ifdef CONFIG_COMPAT +#ifdef CONFIG_COMPAT_32BIT_TIME COMPAT_SYSCALL_DEFINE2(gettimeofday, struct old_timeval32 __user *, tv, struct timezone __user *, tz) { @@ -239,7 +250,9 @@ COMPAT_SYSCALL_DEFINE2(gettimeofday, struct old_timeval32 __user *, tv, return 0; } +#endif /* CONFIG_COMPAT_32BIT_TIME */ +#ifdef CONFIG_COMPAT COMPAT_SYSCALL_DEFINE2(settimeofday, struct old_timeval32 __user *, tv, struct timezone __user *, tz) { @@ -247,6 +260,9 @@ COMPAT_SYSCALL_DEFINE2(settimeofday, struct old_timeval32 __user *, tv, struct timezone new_tz; if (tv) { + if (!IS_ENABLED(CONFIG_COMPAT_32BIT_TIME)) + return -EINVAL; + if (get_user(new_ts.tv_sec, &tv->tv_sec) || get_user(new_ts.tv_nsec, &tv->tv_usec)) return -EFAULT; @@ -263,7 +279,7 @@ COMPAT_SYSCALL_DEFINE2(settimeofday, struct old_timeval32 __user *, tv, return do_sys_settimeofday64(tv ? &new_ts : NULL, tz ? &new_tz : NULL); } -#endif +#endif /* CONFIG_COMPAT */ #ifdef CONFIG_64BIT SYSCALL_DEFINE1(adjtimex, struct __kernel_timex __user *, txc_p) -- cgit v1.2.3 From b7befd6d91207cf3f4cecd68fea0c212093906cf Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:55 +0100 Subject: 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(+) (limited to 'kernel/time') 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; } /* -- cgit v1.2.3 From 2bfc9e417ab815b65cbdf5497de685fcee50c501 Mon Sep 17 00:00:00 2001 From: Mete Durlu Date: Thu, 18 Jun 2026 14:00:16 +0200 Subject: tick: Remove arch_needs_cpu Remove unused arch_needs_cpu() hook. No architectures use it after s390 removed its use case. Suggested-by: Heiko Carstens Signed-off-by: Mete Durlu Reviewed-by: Heiko Carstens Reviewed-by: Thomas Gleixner Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Alexander Gordeev Signed-off-by: Vasily Gorbik --- include/linux/tick.h | 3 --- kernel/time/tick-sched.c | 7 +++---- 2 files changed, 3 insertions(+), 7 deletions(-) (limited to 'kernel/time') diff --git a/include/linux/tick.h b/include/linux/tick.h index 1cf4651f09ad..a43b7bd8ba3f 100644 --- a/include/linux/tick.h +++ b/include/linux/tick.h @@ -44,9 +44,6 @@ static inline void tick_unfreeze(void) { } #ifdef CONFIG_TICK_ONESHOT extern void tick_irq_enter(void); -# ifndef arch_needs_cpu -# define arch_needs_cpu() (0) -# endif # else static inline void tick_irq_enter(void) { } #endif diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 98a9cae915c0..6c3fea386713 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -828,8 +828,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) ts->timer_expires_base = basemono; /* - * Keep the periodic tick, when RCU, architecture or irq_work - * requests it. + * Keep the periodic tick, when RCU or irq_work requests it. * Aside of that, check whether the local timer softirq is * pending. If so, its a bad idea to call get_next_timer_interrupt(), * because there is an already expired timer, so it will request @@ -837,8 +836,8 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) * minimal delta, which brings us back to this place * immediately. Lather, rinse and repeat... */ - if (rcu_needs_cpu() || arch_needs_cpu() || - irq_work_needs_cpu() || local_timer_softirq_pending()) { + if (rcu_needs_cpu() || irq_work_needs_cpu() || + local_timer_softirq_pending()) { next_tick = basemono + TICK_NSEC; } else { /* -- cgit v1.2.3 From 869a55e662a080a5c6618b68ae320231c720eeee Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:56 +0100 Subject: 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 | 66 +++++++++++++++++++++++++++---------- 4 files changed, 78 insertions(+), 25 deletions(-) (limited to 'kernel/time') 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) tmp; + interval = (u64)NTP_INTERVAL_LENGTH << clock->shift; + interval += clock->mult / 2; + do_div(interval, clock->mult); + if (interval == 0) + interval = 1; + 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; } -- cgit v1.2.3 From d375af58990902e73dd62bd7c049e759bcff92a5 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:57 +0100 Subject: 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(-) (limited to 'kernel/time') 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; } -- cgit v1.2.3 From 289d1759494f4a97b0ca4565f3eed00964b952c4 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:58 +0100 Subject: 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 | 136 ++++++++++++++++++++++++++++++++++++---------- kernel/time/timekeeping.c | 2 +- 2 files changed, 107 insertions(+), 31 deletions(-) (limited to 'kernel/time') 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); - } else { - ntpdata->skew_delta = 0; - } + if (ntpdata->time_adjust || ntpdata->time_adjust_frac) { + s64 adj; - if (!ntpdata->time_adjust) - goto out; + 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; - if (ntpdata->time_adjust > MAX_TICKADJ) { - ntpdata->time_adjust -= MAX_TICKADJ; - ntpdata->tick_length += MAX_TICKADJ_SCALED; - goto out; - } + adj_chunk = div_s64(adj, NTP_INTERVAL_FREQ); + if (!adj_chunk) + adj_chunk = signof(ntpdata->time_adjust_frac); + } - if (ntpdata->time_adjust < -MAX_TICKADJ) { - ntpdata->time_adjust += MAX_TICKADJ; - ntpdata->tick_length -= MAX_TICKADJ_SCALED; - goto out; + /* 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; } - 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' -- cgit v1.2.3 From 34ce97c33dca9595455a63d7801e360d19fd97ed Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:53:59 +0100 Subject: 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(+) (limited to 'kernel/time') 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); -- cgit v1.2.3 From 794ddd6e15cfd133c4a5c01dac37a5da04cbe186 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sun, 21 Jun 2026 22:54:00 +0100 Subject: 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(-) (limited to 'kernel/time') 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); -- cgit v1.2.3 From ecc330e3096173f433659aa64ab3674d0d48440e Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Fri, 17 Jul 2026 03:59:47 +0000 Subject: 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(-) (limited to 'kernel/time') 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 -- cgit v1.2.3 From b96b5c6708eae2720053b50af4e9ef3d83a40a94 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 25 Jun 2026 22:38:14 +0200 Subject: sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec Make do_proc_vec static and parametrize by proc_vec_type enum which defines the type being processed and selects which converter is "live". Signed-ness and size are calculated based on proc_vec_type and table->data is now walked as raw bytes and advanced by the element size; the converter still performs the actual typed load/store. Pass converter as a union to avoid a cast from void*. The public proc_do{int,uint,ulong}vec_conv() prototypes and all converter signatures in kernel/, fs/ and the header are therefore unchanged. Remove do_proc_doulongvec_minmax. proc_doulongvec_minmax_conv uses a converter callback passed by the caller instead of conversions based on conv{mul,div}. Create uni and bi-direction converters for milliseconds to jiffies in proc_doulongvec_ms_jiffies_minmax; which is the only user of proc_doulongvec_minmax_conv. Replace do_proc_douintvec{,_w,_r} functions with a call to do_proc_vec. Disallow vectors for uint by returning -EINVAL when more than one element is detected. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 11 +- kernel/sysctl.c | 422 +++++++++++++++++++++++++------------------------ kernel/time/jiffies.c | 26 ++- 3 files changed, 249 insertions(+), 210 deletions(-) (limited to 'kernel/time') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 0d406b64968a..8f8e357b1f4d 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -117,11 +117,20 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int write, void *buffer, int proc_doulongvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv); + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table)); int proc_do_large_bitmap(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_do_static_key(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); +int proc_ulong_u2k_conv_uop(const ulong *u_ptr, ulong *k_ptr, + ulong (*u_ptr_op)(const ulong)); +int proc_ulong_k2u_conv_kop(ulong *u_ptr, const ulong *k_ptr, + ulong (*k_ptr_op)(const ulong)); +int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl, bool k_ptr_range_check, + int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr), + int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr)); /* * Register a set of sysctl names by calling register_sysctl * with an initialised array of struct ctl_table's. diff --git a/kernel/sysctl.c b/kernel/sysctl.c index ef5a9c5b1d10..97fdeeee9ef2 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -572,14 +572,80 @@ static int do_proc_int_conv_minmax(bool *negp, unsigned long *u_ptr, int *k_ptr, static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; -static int do_proc_dointvec(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr, - int dir, const struct ctl_table *table)) +/* + * Element type processed by do_proc_vec(). The tag selects the element size + * and signedness, and it selects which member of union proc_vec_conv is live. + */ +enum proc_vec_type { + PROC_VEC_INT, + PROC_VEC_UINT, + PROC_VEC_ULONG, +}; + +/* + * Converter passed to do_proc_vec(). Only the member matching the + * enum proc_vec_type tag is ever read, so every dispatch stays fully typed and + * no void * converter pointer is needed. + */ +union proc_vec_conv { + int (*int_conv)(bool *negp, ulong *u_ptr, int *k_ptr, + int dir, const struct ctl_table *table); + int (*uint_conv)(bool *negp, ulong *u_ptr, uint *k_ptr, + int dir, const struct ctl_table *table); + int (*ulong_conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table); +}; + +/* + * Dispatch to the converter member selected by @type. @k_ptr walks + * table->data as raw bytes and is cast back to the element type here. + */ +static int proc_vec_conv(enum proc_vec_type type, union proc_vec_conv conv, + bool *negp, ulong *u_ptr, char *k_ptr, int dir, + const struct ctl_table *table) { - int *i, vleft, first = 1, err = 0; - size_t left; - char *p; + switch (type) { + case PROC_VEC_INT: + return conv.int_conv(negp, u_ptr, (int *)k_ptr, dir, table); + case PROC_VEC_UINT: + return conv.uint_conv(negp, u_ptr, (uint *)k_ptr, dir, table); + case PROC_VEC_ULONG: + return conv.ulong_conv(negp, u_ptr, (ulong *)k_ptr, dir, table); + } + return -EINVAL; +} + +/* + * Read/write a vector of @type elements. The element size and signedness are + * derived from @type, so a single runtime function replaces the per-type + * variants. table->data is walked as raw bytes (@i) advanced by @size; the + * converter performs the actual typed load/store. + */ +static int do_proc_vec(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos, + enum proc_vec_type type, union proc_vec_conv conv) +{ + int vleft, first = 1, err = 0; + size_t left, size; + bool is_unsigned; + char *i, *p; + + switch (type) { + case PROC_VEC_INT: + size = sizeof(int); + is_unsigned = false; + break; + case PROC_VEC_UINT: + size = sizeof(uint); + is_unsigned = true; + break; + case PROC_VEC_ULONG: + size = sizeof(ulong); + is_unsigned = true; + break; + default: + return -EINVAL; + } if (!table->data || !table->maxlen || !*lenp || (*ppos && SYSCTL_KERN_TO_USER(dir))) { @@ -587,10 +653,14 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, return 0; } - i = (int *) table->data; - vleft = table->maxlen / sizeof(*i); + i = table->data; + vleft = table->maxlen / size; left = *lenp; + /* uint arrays are not supported, *Do not* add support for them. */ + if (type == PROC_VEC_UINT && vleft != 1) + return -EINVAL; + if (SYSCTL_USER_TO_KERN(dir)) { if (proc_first_pos_non_zero_ignore(ppos, table)) goto out; @@ -600,9 +670,9 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, p = buffer; } - for (; left && vleft--; i++, first=0) { + for (; left && vleft--; i += size, first = 0) { unsigned long lval; - bool neg; + bool neg = false; if (SYSCTL_USER_TO_KERN(dir)) { proc_skip_spaces(&p, &left); @@ -610,16 +680,18 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, if (!left) break; err = proc_get_long(&p, &left, &lval, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); + proc_wspace_sep, + sizeof(proc_wspace_sep), NULL); + if (!err && neg && is_unsigned) + err = -EINVAL; if (err) break; - if (conv(&neg, &lval, i, 1, table)) { + if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) { err = -EINVAL; break; } } else { - if (conv(&neg, &lval, i, 0, table)) { + if (proc_vec_conv(type, conv, &neg, &lval, i, dir, table)) { err = -EINVAL; break; } @@ -641,119 +713,6 @@ out: return err; } -static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *u_ptr, - unsigned int *k_ptr, int dir, - const struct ctl_table *table)) -{ - unsigned long lval; - int err = 0; - size_t left; - bool neg; - char *p = buffer; - - left = *lenp; - - if (proc_first_pos_non_zero_ignore(ppos, table)) - goto bail_early; - - if (left > PAGE_SIZE - 1) - left = PAGE_SIZE - 1; - - proc_skip_spaces(&p, &left); - if (!left) { - err = -EINVAL; - goto out_free; - } - - err = proc_get_long(&p, &left, &lval, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); - if (err || neg) { - err = -EINVAL; - goto out_free; - } - - if (conv(&neg, &lval, (unsigned int *) table->data, 1, table)) { - err = -EINVAL; - goto out_free; - } - - if (!err && left) - proc_skip_spaces(&p, &left); - -out_free: - if (err) - return -EINVAL; - - return 0; - -bail_early: - *ppos += *lenp; - return err; -} - -static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *u_ptr, - unsigned int *k_ptr, int dir, - const struct ctl_table *table)) -{ - unsigned long lval; - int err = 0; - size_t left; - bool negp; - - left = *lenp; - - if (conv(&negp, &lval, (unsigned int *) table->data, 0, table)) { - err = -EINVAL; - goto out; - } - - proc_put_long(&buffer, &left, lval, false); - if (!left) - goto out; - - proc_put_char(&buffer, &left, '\n'); - -out: - *lenp -= left; - *ppos += *lenp; - - return err; -} - -static int do_proc_douintvec(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr, - int dir, const struct ctl_table *table)) -{ - unsigned int vleft; - - if (!table->data || !table->maxlen || !*lenp || - (*ppos && SYSCTL_KERN_TO_USER(dir))) { - *lenp = 0; - return 0; - } - - vleft = table->maxlen / sizeof(unsigned int); - - /* - * Arrays are not supported, keep this simple. *Do not* add - * support for them. - */ - if (vleft != 1) { - *lenp = 0; - return -EINVAL; - } - - if (SYSCTL_USER_TO_KERN(dir)) - return do_proc_douintvec_w(table, buffer, lenp, ppos, conv); - return do_proc_douintvec_r(table, buffer, lenp, ppos, conv); -} - /** * proc_douintvec_conv - read a vector of unsigned ints with a custom converter * @@ -779,7 +738,8 @@ int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer, if (!conv) conv = do_proc_uint_conv; - return do_proc_douintvec(table, dir, buffer, lenp, ppos, conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = conv }); } /** @@ -838,7 +798,8 @@ int proc_dobool(const struct ctl_table *table, int dir, void *buffer, int proc_dointvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, dir, buffer, lenp, ppos, do_proc_int_conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT, + (union proc_vec_conv){ .int_conv = do_proc_int_conv }); } /** @@ -857,8 +818,8 @@ int proc_dointvec(const struct ctl_table *table, int dir, void *buffer, int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_douintvec(table, dir, buffer, lenp, ppos, - do_proc_uint_conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = do_proc_uint_conv }); } /** @@ -881,8 +842,8 @@ int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, int proc_dointvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_int_conv_minmax); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT, + (union proc_vec_conv){ .int_conv = do_proc_int_conv_minmax }); } /** @@ -908,8 +869,8 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir, int proc_douintvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_douintvec(table, dir, buffer, lenp, ppos, - do_proc_uint_conv_minmax); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax }); } /** @@ -952,8 +913,8 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir, tmp.extra2 = (unsigned int *) &max; val = READ_ONCE(*data); - res = do_proc_douintvec(&tmp, dir, buffer, lenp, ppos, - do_proc_uint_conv_minmax); + res = do_proc_vec(&tmp, dir, buffer, lenp, ppos, PROC_VEC_UINT, + (union proc_vec_conv){ .uint_conv = do_proc_uint_conv_minmax }); if (res) return res; if (SYSCTL_USER_TO_KERN(dir)) @@ -962,87 +923,129 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir, } EXPORT_SYMBOL_GPL(proc_dou8vec_minmax); -static int do_proc_doulongvec_minmax(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, - unsigned long convdiv) +/** + * proc_ulong_conv - Change user or kernel pointer based on direction + * + * @u_ptr: pointer to user variable + * @k_ptr: pointer to kernel variable + * @dir: %TRUE if this is a write to the sysctl file + * @tbl: the sysctl table + * @k_ptr_range_check: Check range for k_ptr when %TRUE + * @user_to_kern: Callback used to assign value from user to kernel var + * @kern_to_user: Callback used to assign value from kernel to user var + * + * When direction is kernel to user, then the u_ptr is modified. + * When direction is user to kernel, then the k_ptr is modified. + * + * Returns: 0 on success + */ +int proc_ulong_conv(ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl, bool k_ptr_range_check, + int (*user_to_kern)(const ulong *u_ptr, ulong *k_ptr), + int (*kern_to_user)(ulong *u_ptr, const ulong *k_ptr)) { - unsigned long *i, *min, *max; - int vleft, first = 1, err = 0; - size_t left; - char *p; - - if (!table->data || !table->maxlen || !*lenp || - (*ppos && SYSCTL_KERN_TO_USER(dir))) { - *lenp = 0; - return 0; - } - - i = table->data; - min = table->extra1; - max = table->extra2; - vleft = table->maxlen / sizeof(unsigned long); - left = *lenp; + if (SYSCTL_KERN_TO_USER(dir)) + return kern_to_user(u_ptr, k_ptr); - if (SYSCTL_USER_TO_KERN(dir)) { - if (proc_first_pos_non_zero_ignore(ppos, table)) - goto out; + if (k_ptr_range_check) { + ulong tmp_k; + int ret; - if (left > PAGE_SIZE - 1) - left = PAGE_SIZE - 1; - p = buffer; - } + if (!tbl) + return -EINVAL; + ret = user_to_kern(u_ptr, &tmp_k); + if (ret) + return ret; + if ((tbl->extra1 && *(ulong *)tbl->extra1 > tmp_k) || + (tbl->extra2 && *(ulong *)tbl->extra2 < tmp_k)) + return -ERANGE; + WRITE_ONCE(*k_ptr, tmp_k); + } else + return user_to_kern(u_ptr, k_ptr); + return 0; +} - for (; left && vleft--; i++, first = 0) { - unsigned long val; +/** + * proc_ulong_u2k_conv_uop - Assign user value to a kernel pointer + * + * @u_ptr: pointer to user space variable + * @k_ptr: pointer to kernel variable + * @u_ptr_op: execute this function before assigning to k_ptr + * + * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if + * not NULL. + * + * returns: 0 on success. + */ +int proc_ulong_u2k_conv_uop(const ulong *u_ptr, ulong *k_ptr, + ulong (*u_ptr_op)(const ulong)) +{ + ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr; - if (SYSCTL_USER_TO_KERN(dir)) { - bool neg; + WRITE_ONCE(*k_ptr, u); + return 0; +} - proc_skip_spaces(&p, &left); - if (!left) - break; +static int proc_ulong_u2k_conv(const ulong *u_ptr, ulong *k_ptr) +{ + return proc_ulong_u2k_conv_uop(u_ptr, k_ptr, NULL); +} - err = proc_get_long(&p, &left, &val, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); - if (err || neg) { - err = -EINVAL; - break; - } +/** + * proc_ulong_k2u_conv_kop - Assign kernel value to a user space pointer + * + * @u_ptr: pointer to user space variable + * @k_ptr: pointer to kernel variable + * @k_ptr_op: Operation applied to k_ptr before assignment + * + * Uses READ_ONCE to assign value to u_ptr. Executes k_ptr_op if + * not NULL. + * + * returns: 0 on success. + */ +int proc_ulong_k2u_conv_kop(ulong *u_ptr, const ulong *k_ptr, + ulong (*k_ptr_op)(const ulong)) +{ + ulong val = k_ptr_op ? k_ptr_op(READ_ONCE(*k_ptr)) : READ_ONCE(*k_ptr); + *u_ptr = (ulong)val; + return 0; +} - val = convmul * val / convdiv; - if ((min && val < *min) || (max && val > *max)) { - err = -EINVAL; - break; - } - WRITE_ONCE(*i, val); - } else { - val = convdiv * READ_ONCE(*i) / convmul; - if (!first) - proc_put_char(&buffer, &left, '\t'); - proc_put_long(&buffer, &left, val, false); - } - } +static int proc_ulong_k2u_conv(ulong *u_ptr, const ulong *k_ptr) +{ + return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, NULL); +} - if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err) - proc_put_char(&buffer, &left, '\n'); - if (SYSCTL_USER_TO_KERN(dir) && !err) - proc_skip_spaces(&p, &left); - if (SYSCTL_USER_TO_KERN(dir) && first) - return err ? : -EINVAL; - *lenp -= left; -out: - *ppos += *lenp; - return err; +static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, + const struct ctl_table *tbl) +{ + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true, + proc_ulong_u2k_conv, proc_ulong_k2u_conv); } +/** + * proc_doulongvec_minmax_conv - read a vector of unsigned longs with a custom converter + * + * @table: the sysctl table + * @dir: %TRUE if this is a write to the sysctl file + * @buffer: the user buffer + * @lenp: the size of the user buffer + * @ppos: file position + * @conv: Custom converter call back + * + * Reads/writes up to table->maxlen/sizeof(unsigned long) unsigned long + * values from/to the user buffer, treated as an ASCII string. Negative + * strings are not allowed. + * + * Returns: 0 on success + */ int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv) + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table)) { - return do_proc_doulongvec_minmax(table, dir, buffer, lenp, ppos, - convmul, convdiv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG, + (union proc_vec_conv){ .ulong_conv = conv }); } /** @@ -1064,7 +1067,8 @@ int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, int proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, 1l, 1l); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_ULONG, + (union proc_vec_conv){ .ulong_conv = do_proc_ulong_conv }); } /** @@ -1090,7 +1094,8 @@ int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer, { if (!conv) conv = do_proc_int_conv; - return do_proc_dointvec(table, dir, buffer, lenp, ppos, conv); + return do_proc_vec(table, dir, buffer, lenp, ppos, PROC_VEC_INT, + (union proc_vec_conv){ .int_conv = conv }); } /** @@ -1319,7 +1324,8 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir, int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv) + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *table)) { return -ENOSYS; } diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index d51428867a33..01e634fdcfd0 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -185,6 +185,24 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms); } +static int sysctl_u2k_ulong_conv_ms(const ulong *u_ptr, ulong *k_ptr) +{ + return proc_ulong_u2k_conv_uop(u_ptr, k_ptr, sysctl_msecs_to_jiffies); +} + +static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr) +{ + return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs); +} + +static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) +{ + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, false, + sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); +} + + #else // CONFIG_PROC_SYSCTL static int do_proc_int_conv_jiffies(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) @@ -211,6 +229,12 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, { return -ENOSYS; } + +static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) +{ + return -ENOSYS; +} #endif /** @@ -310,7 +334,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, - HZ, 1000l); + do_proc_ulong_conv_ms_jiffies); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); -- cgit v1.2.3 From b1ca9dae826b99d09e898c7567b2e3bb4bdc866a Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 25 Jun 2026 22:38:20 +0200 Subject: sysctl: Rename proc_doulongvec_minmax_conv to proc_doulongvec_conv Remove "_minmax" from proc_doulongvec_minmax_conv as it does not enforce min/max limits but serves as a generic converter for unsigned long vectors. Update function declaration in sysctl.h, definition in sysctl.c, and caller in jiffies.c accordingly. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 8 ++++---- kernel/sysctl.c | 6 +++--- kernel/time/jiffies.c | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'kernel/time') diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index ad268dfd9e79..39cf1bf9703f 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -121,10 +121,10 @@ int proc_douintvec_conv(const struct ctl_table *ctl, int dir, void *buf, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *ctl)); -int proc_doulongvec_minmax_conv(const struct ctl_table *ctl, int dir, void *buf, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *ctl)); +int proc_doulongvec_conv(const struct ctl_table *ctl, int dir, void *buf, + size_t *lenp, loff_t *ppos, + int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *ctl)); /* * bi-directional converter functions diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 97fdeeee9ef2..d30058d32340 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1024,7 +1024,7 @@ static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, } /** - * proc_doulongvec_minmax_conv - read a vector of unsigned longs with a custom converter + * proc_doulongvec_conv - read a vector of unsigned longs with a custom converter * * @table: the sysctl table * @dir: %TRUE if this is a write to the sysctl file @@ -1039,7 +1039,7 @@ static int do_proc_ulong_conv(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, * * Returns: 0 on success */ -int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, +int proc_doulongvec_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, const struct ctl_table *table)) @@ -1322,7 +1322,7 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir, return -ENOSYS; } -int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, +int proc_doulongvec_conv(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, ulong *u_ptr, ulong *k_ptr, int dir, const struct ctl_table *table)) diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 01e634fdcfd0..af529d558fa9 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -333,8 +333,8 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, - do_proc_ulong_conv_ms_jiffies); + return proc_doulongvec_conv(table, dir, buffer, lenp, ppos, + do_proc_ulong_conv_ms_jiffies); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); -- cgit v1.2.3 From 8d75c338f0bcecaa6c9af67f86c176b67b6acf3e Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Mon, 27 Jul 2026 16:47:22 +0200 Subject: sysctl: remove CONFIG_PROC_SYSCTL, it just mirrors CONFIG_SYSCTL CONFIG_SYSCTL used to make sense as a separate hidden bool before commit 61a47c1ad3a4 ("sysctl: Remove the sysctl system call"); it was selected by both CONFIG_SYSCTL_SYSCALL and CONFIG_PROC_SYSCTL. Today CONFIG_PROC_SYSCTL is the only selector, so the two are always equal. Kill the hidden bool, rename the PROC_SYSCTL prompt to SYSCTL, and s/CONFIG_PROC_SYSCTL/CONFIG_SYSCTL/ tree-wide. Signed-off-by: Oleg Nesterov Signed-off-by: Joel Granados --- arch/m68k/configs/amcore_defconfig | 2 +- arch/m68k/configs/stmark2_defconfig | 2 +- arch/s390/Kconfig | 4 ++-- arch/x86/kernel/cpu/bus_lock.c | 2 +- fs/proc/Kconfig | 3 +-- fs/proc/Makefile | 2 +- fs/proc/internal.h | 2 +- include/linux/utsname.h | 2 +- init/Kconfig | 3 --- kernel/delayacct.c | 2 +- kernel/sched/core.c | 8 ++++---- kernel/sched/topology.c | 4 ++-- kernel/sysctl.c | 12 ++++++------ kernel/time/jiffies.c | 5 ++--- kernel/utsname_sysctl.c | 4 ++-- lib/Kconfig.debug | 2 +- lib/test_sysctl.c | 2 +- net/mpls/Kconfig | 2 +- tools/testing/selftests/net/config | 2 +- tools/testing/selftests/net/packetdrill/config | 2 +- tools/testing/selftests/wireguard/qemu/kernel.config | 2 +- 21 files changed, 32 insertions(+), 37 deletions(-) (limited to 'kernel/time') diff --git a/arch/m68k/configs/amcore_defconfig b/arch/m68k/configs/amcore_defconfig index f310b5dacfd8..49f1de51d87e 100644 --- a/arch/m68k/configs/amcore_defconfig +++ b/arch/m68k/configs/amcore_defconfig @@ -74,7 +74,7 @@ CONFIG_RTC_DRV_DS1307=y # CONFIG_DNOTIFY is not set # CONFIG_INOTIFY_USER is not set CONFIG_FSCACHE=y -# CONFIG_PROC_SYSCTL is not set +# CONFIG_SYSCTL is not set CONFIG_JFFS2_FS=y CONFIG_ROMFS_FS=y CONFIG_ROMFS_BACKED_BY_BOTH=y diff --git a/arch/m68k/configs/stmark2_defconfig b/arch/m68k/configs/stmark2_defconfig index b3fb95f73a95..cd454369bfe8 100644 --- a/arch/m68k/configs/stmark2_defconfig +++ b/arch/m68k/configs/stmark2_defconfig @@ -87,7 +87,7 @@ CONFIG_EXT4_FS_SECURITY=y # CONFIG_DNOTIFY is not set # CONFIG_INOTIFY_USER is not set CONFIG_OVERLAY_FS=y -# CONFIG_PROC_SYSCTL is not set +# CONFIG_SYSCTL is not set CONFIG_SQUASHFS=y CONFIG_XZ_DEC=y CONFIG_PRINTK_TIME=y diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 84404e6778d5..68dc2ebd6124 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -575,7 +575,7 @@ config HIPERDISPATCH_ON def_bool y bool "Use hiperdispatch on vertical polarization by default" depends on SCHED_TOPOLOGY - depends on PROC_SYSCTL + depends on SYSCTL help Hiperdispatch aims to improve the CPU scheduler's decision making when using vertical polarization by adjusting CPU @@ -893,7 +893,7 @@ config CMM_IUCV config APPLDATA_BASE def_bool n prompt "Linux - VM Monitor Stream, base infrastructure" - depends on PROC_SYSCTL + depends on SYSCTL help This provides a kernel interface for creating and updating z/VM APPLDATA monitor records. The monitor records are updated at certain time diff --git a/arch/x86/kernel/cpu/bus_lock.c b/arch/x86/kernel/cpu/bus_lock.c index bba28607a59a..177f964d4d36 100644 --- a/arch/x86/kernel/cpu/bus_lock.c +++ b/arch/x86/kernel/cpu/bus_lock.c @@ -50,7 +50,7 @@ static struct ratelimit_state bld_ratelimit; static unsigned int sysctl_sld_mitigate = 1; static DEFINE_SEMAPHORE(buslock_sem, 1); -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static const struct ctl_table sld_sysctls[] = { { .procname = "split_lock_mitigate", diff --git a/fs/proc/Kconfig b/fs/proc/Kconfig index 6ae966c561e7..24f5a36f45df 100644 --- a/fs/proc/Kconfig +++ b/fs/proc/Kconfig @@ -80,10 +80,9 @@ config PROC_VMCORE_DEVICE_RAM Relevant architectures should select NEED_PROC_VMCORE_DEVICE_RAM. -config PROC_SYSCTL +config SYSCTL bool "Sysctl support (/proc/sys)" if EXPERT depends on PROC_FS - select SYSCTL default y help The sysctl interface provides a means of dynamically changing diff --git a/fs/proc/Makefile b/fs/proc/Makefile index 8bc615ff84e5..27e2b26b2d42 100644 --- a/fs/proc/Makefile +++ b/fs/proc/Makefile @@ -29,7 +29,7 @@ proc-y += softirqs.o proc-y += namespaces.o proc-y += self.o proc-y += thread_self.o -proc-$(CONFIG_PROC_SYSCTL) += proc_sysctl.o +proc-$(CONFIG_SYSCTL) += proc_sysctl.o proc-$(CONFIG_NET) += proc_net.o proc-$(CONFIG_PROC_KCORE) += kcore.o proc-$(CONFIG_PROC_VMCORE) += vmcore.o diff --git a/fs/proc/internal.h b/fs/proc/internal.h index b232e1098117..04bd6c9e65a7 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -351,7 +351,7 @@ extern void proc_thread_self_init(void); /* * proc_sysctl.c */ -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL extern int proc_sys_init(void); extern void proc_sys_evict_inode(struct inode *inode, struct ctl_table_header *head); diff --git a/include/linux/utsname.h b/include/linux/utsname.h index 547bd4439706..dc5594ffffcc 100644 --- a/include/linux/utsname.h +++ b/include/linux/utsname.h @@ -18,7 +18,7 @@ enum uts_proc { UTS_PROC_DOMAINNAME, }; -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL extern void uts_proc_notify(enum uts_proc proc); #else static inline void uts_proc_notify(enum uts_proc proc) diff --git a/init/Kconfig b/init/Kconfig index 5230d4879b1c..a505ab5be824 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1661,9 +1661,6 @@ config LD_ORPHAN_WARN_LEVEL default "error" if WERROR default "warn" -config SYSCTL - bool - config HAVE_UID16 bool diff --git a/kernel/delayacct.c b/kernel/delayacct.c index 2e55c493c98b..479e860aff70 100644 --- a/kernel/delayacct.c +++ b/kernel/delayacct.c @@ -54,7 +54,7 @@ void delayacct_init(void) set_delayacct(delayacct_on); } -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static int sysctl_delayacct(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 96226707c2f6..ddedc1d6847b 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -4638,7 +4638,7 @@ void set_numabalancing_state(bool enabled) __set_numabalancing_state(enabled); } -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static void reset_memory_tiering(void) { struct pglist_data *pgdat; @@ -4674,7 +4674,7 @@ static int sysctl_numa_balancing(const struct ctl_table *table, int write, } return err; } -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ #endif /* CONFIG_NUMA_BALANCING */ #ifdef CONFIG_SCHEDSTATS @@ -4718,7 +4718,7 @@ out: } __setup("schedstats=", setup_schedstats); -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static int sysctl_schedstats(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { @@ -4738,7 +4738,7 @@ static int sysctl_schedstats(const struct ctl_table *table, int write, void *buf set_schedstats(state); return err; } -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ #endif /* CONFIG_SCHEDSTATS */ #ifdef CONFIG_SYSCTL diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 622e2e01974c..5a29e506c25d 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -271,7 +271,7 @@ void rebuild_sched_domains_energy(void) mutex_unlock(&sched_energy_mutex); } -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static int sched_energy_aware_handler(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { @@ -317,7 +317,7 @@ static int __init sched_energy_aware_sysctl_init(void) } late_initcall(sched_energy_aware_sysctl_init); -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ static void free_pd(struct perf_domain *pd) { diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 0cb5d1759d42..f7b75985d542 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -35,7 +35,7 @@ EXPORT_SYMBOL_GPL(sysctl_long_vals); static const int ngroups_max = NGROUPS_MAX; static const int cap_last_cap = CAP_LAST_CAP; -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL /** * enum sysctl_writes_mode - supported sysctl write modes @@ -64,14 +64,14 @@ enum sysctl_writes_mode { }; static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT; -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ #endif /* CONFIG_SYSCTL */ /* * /proc/sys support */ -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static int _proc_do_string(char *data, int maxlen, int dir, char *buffer, size_t *lenp, loff_t *ppos) @@ -1265,7 +1265,7 @@ int proc_do_large_bitmap(const struct ctl_table *table, int dir, return err; } -#else /* CONFIG_PROC_SYSCTL */ +#else /* CONFIG_SYSCTL */ int proc_dostring(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) @@ -1364,7 +1364,7 @@ int proc_do_large_bitmap(const struct ctl_table *table, int dir, return -ENOSYS; } -#endif /* CONFIG_PROC_SYSCTL */ +#endif /* CONFIG_SYSCTL */ #if defined(CONFIG_SYSCTL) int proc_do_static_key(const struct ctl_table *table, int dir, @@ -1398,7 +1398,7 @@ int proc_do_static_key(const struct ctl_table *table, int dir, } static const struct ctl_table sysctl_subsys_table[] = { -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL { .procname = "sysctl_writes_strict", .data = &sysctl_writes_strict, diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index af529d558fa9..213ae1d6a014 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -98,7 +98,7 @@ void __init register_refined_jiffies(long cycles_per_second) __clocksource_register(&refined_jiffies); } -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static ulong mult_hz(const ulong val) { return val * HZ; @@ -202,8 +202,7 @@ static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); } - -#else // CONFIG_PROC_SYSCTL +#else // CONFIG_SYSCTL static int do_proc_int_conv_jiffies(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) { diff --git a/kernel/utsname_sysctl.c b/kernel/utsname_sysctl.c index bfbaaecb1dd4..6c1522201e39 100644 --- a/kernel/utsname_sysctl.c +++ b/kernel/utsname_sysctl.c @@ -13,7 +13,7 @@ #include #include -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL static void *get_uts(const struct ctl_table *table) { @@ -122,7 +122,7 @@ static const struct ctl_table uts_kern_table[] = { }, }; -#ifdef CONFIG_PROC_SYSCTL +#ifdef CONFIG_SYSCTL /* * Notify userspace about a change in a certain entry of uts_kern_table, * identified by the parameter proc. diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 1244dcac2294..73287ef2498c 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2710,7 +2710,7 @@ config TEST_FIRMWARE config TEST_SYSCTL tristate "sysctl test driver" - depends on PROC_SYSCTL + depends on SYSCTL help This builds the "test_sysctl" module. This driver enables to test the proc sysctl interfaces available to drivers safely without affecting diff --git a/lib/test_sysctl.c b/lib/test_sysctl.c index c02aa9c868f2..909cfcf76dbf 100644 --- a/lib/test_sysctl.c +++ b/lib/test_sysctl.c @@ -7,7 +7,7 @@ /* * This module provides an interface to the proc sysctl interfaces. This - * driver requires CONFIG_PROC_SYSCTL. It will not normally be loaded by the + * driver requires CONFIG_SYSCTL. It will not normally be loaded by the * system unless explicitly requested by name. You can also build this driver * into your kernel. */ diff --git a/net/mpls/Kconfig b/net/mpls/Kconfig index d672ab72ab12..829fb1788b1c 100644 --- a/net/mpls/Kconfig +++ b/net/mpls/Kconfig @@ -26,7 +26,7 @@ config NET_MPLS_GSO config MPLS_ROUTING tristate "MPLS: routing support" depends on NET_IP_TUNNEL || NET_IP_TUNNEL=n - depends on PROC_SYSCTL + depends on SYSCTL help Add support for forwarding of mpls packets. diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index e1ce35c2abbe..bea62d41db8d 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -118,7 +118,7 @@ CONFIG_OPENVSWITCH_GENEVE=m CONFIG_OPENVSWITCH_GRE=m CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PAGE_POOL_STATS=y -CONFIG_PROC_SYSCTL=y +CONFIG_SYSCTL=y CONFIG_PSAMPLE=m CONFIG_RPS=y CONFIG_SYN_COOKIES=y diff --git a/tools/testing/selftests/net/packetdrill/config b/tools/testing/selftests/net/packetdrill/config index c4a19a785521..83dde525c53c 100644 --- a/tools/testing/selftests/net/packetdrill/config +++ b/tools/testing/selftests/net/packetdrill/config @@ -4,7 +4,7 @@ CONFIG_IPV6=y CONFIG_NET_NS=y CONFIG_NET_SCH_FIFO=y CONFIG_NET_SCH_FQ=y -CONFIG_PROC_SYSCTL=y +CONFIG_SYSCTL=y CONFIG_SYN_COOKIES=y CONFIG_TCP_CONG_CUBIC=y CONFIG_TCP_MD5SIG=y diff --git a/tools/testing/selftests/wireguard/qemu/kernel.config b/tools/testing/selftests/wireguard/qemu/kernel.config index bb89d2dfaa2a..368fd9d600c8 100644 --- a/tools/testing/selftests/wireguard/qemu/kernel.config +++ b/tools/testing/selftests/wireguard/qemu/kernel.config @@ -63,7 +63,7 @@ CONFIG_FILE_LOCKING=y CONFIG_POSIX_TIMERS=y CONFIG_DEVTMPFS=y CONFIG_PROC_FS=y -CONFIG_PROC_SYSCTL=y +CONFIG_SYSCTL=y CONFIG_SYSFS=y CONFIG_TMPFS=y CONFIG_CONSOLE_LOGLEVEL_DEFAULT=15 -- cgit v1.2.3 From d7fc133bf91f713df72facebd41ae9dfc83e35f7 Mon Sep 17 00:00:00 2001 From: Dennis Moshegov Date: Fri, 24 Jul 2026 16:43:55 +0100 Subject: 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(-) (limited to 'kernel/time') 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, -- cgit v1.2.3 From 4b61084b11bcecce86d03804ff30f8d7b465593c Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Fri, 31 Jul 2026 16:17:43 +0200 Subject: 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(-) (limited to 'kernel/time') 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)); -- cgit v1.2.3 From 1b515771872174553fe70eadb8efecfa49035922 Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Fri, 31 Jul 2026 16:20:14 +0200 Subject: 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(-) (limited to 'kernel/time') 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 */ -- cgit v1.2.3 From b17006b45283f012a6e166517f5ca3e1af2f806a Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sat, 1 Aug 2026 17:01:40 +0100 Subject: 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(-) (limited to 'kernel/time') 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 -- cgit v1.2.3 From 18c7d85864e554adc8fad1e8d2e9d2cb6c3911c8 Mon Sep 17 00:00:00 2001 From: Jérémy Jean Date: Sun, 9 Aug 2026 19:04:16 +0000 Subject: 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(-) (limited to 'kernel/time') 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; -- cgit v1.2.3 From 32a05ba399fc52661f59f38efd2e606f45eb2c8f Mon Sep 17 00:00:00 2001 From: Liang Hao Date: Fri, 31 Jul 2026 23:04:08 +0800 Subject: 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(-) (limited to 'kernel/time') 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 @@ retry: 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); -- cgit v1.2.3 From 4f39d3c19bdc6b9cd4a8de7cbc563264cac9a348 Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Mon, 3 Aug 2026 12:04:32 +0200 Subject: 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(-) (limited to 'kernel/time') 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; -- cgit v1.2.3 From 406a037ce7a03a37bf2b03010791ea9343ecc233 Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Mon, 3 Aug 2026 12:04:33 +0200 Subject: 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(-) (limited to 'kernel/time') 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); -- cgit v1.2.3 From d2e6ee3ecb76464e7146c1b4774383fee4b48146 Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Mon, 3 Aug 2026 12:04:34 +0200 Subject: 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(+) (limited to 'kernel/time') 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; -- cgit v1.2.3 From 40e05cf391c8a815cb0a94c9d9e474ebd78807f9 Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Mon, 3 Aug 2026 12:04:35 +0200 Subject: 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(-) (limited to 'kernel/time') 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"); } -- cgit v1.2.3 From 859b069ec5a328bd0bca301f6b9d87ff1dfc4b1d Mon Sep 17 00:00:00 2001 From: "Thomas Weißschuh (Schneider Electric)" Date: Mon, 3 Aug 2026 12:04:36 +0200 Subject: 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(-) (limited to 'kernel/time') 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 @@ next_one: } 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); -- cgit v1.2.3 From c793bbfc4a0a9f5a66978fc91559e9681748dbeb Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 18 Aug 2026 00:14:57 +0200 Subject: timer: Keep debugobjects state consistent in migrate_timer_list() When timers are migrated away from an offline CPU the debugobjects state gets corrupted. The timer is accounted as inactive on deletion, but the enqueue on the alive CPU lacks the activation call. That used to work, but got broken when the trace point and the debug objects call got separated. That change missed to fixup migrate_timer_list(). Add the missing debug_timer_activate() invocation to fix it. Fixes: dc1e7dc5ac62 ("timer: Move trace point to get proper index") Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://patch.msgid.link/87bjb0l7ha.ffs@fw13 --- kernel/time/timer.c | 1 + 1 file changed, 1 insertion(+) (limited to 'kernel/time') diff --git a/kernel/time/timer.c b/kernel/time/timer.c index 655a8c6cd84d..ae9abf14688e 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -2492,6 +2492,7 @@ static void migrate_timer_list(struct timer_base *new_base, struct hlist_head *h timer = hlist_entry(head->first, struct timer_list, entry); detach_timer(timer, false); timer->flags = (timer->flags & ~TIMER_BASEMASK) | cpu; + debug_timer_activate(timer); internal_add_timer(new_base, timer); } } -- cgit v1.2.3 From 113a9796effe3376d2ec5aabcca1fef4fef4cd62 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 12 Aug 2024 16:19:48 +0200 Subject: tick/broadcast: Plug clockevents replacement race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 朱恺乾 reported and decoded the following race condition when a broadcast device is replaced: CPUA CPUB __tick_broadcast_oneshot_control() bc = tick_broadcast_device.evtdev; tick_install_broadcast_device(dev) clockevents_exchange_device(cur, dev) shutdown(cur); detach(cur); cur->handler = noop; tick_broadcast_device.evtdev = dev; tick_broadcast_set_event(bc, next_event); <- FAIL: arms a detached device. If the original broadcast device has a restricted interrupt affinity mask and the last CPU in that mask goes offline then the BUG() in tick_cleanup_dead_cpu() triggers because the clockevent device is not in detached state. The reason for this is that tick_install_broadcast_device() is not serialized vs. tick broadcast operations. The obvious cure is to serialize tick_install_broadcast_device() with tick_broadcast_lock against a concurrent tick broadcast operation. That requires to split clockevents_exchange_device() into two parts, one which does the exchange, shutdown and detach operation and the other which drops the module reference count. This is required because the module reference cannot be dropped while holding tick_broadcast_lock. Let clockevents_exchange_device() do both operations as before, but let the broadcast device code take the two step approach and do the device exchange under tick_broadcast_lock and drop the module reference count after releasing it. Fixes: f8381cba04ba ("[PATCH] tick-management: broadcast functionality") Reported-by: 朱恺乾 Signed-off-by: Thomas Gleixner Signed-off-by: Thomas Gleixner Reviewed-by: Bradley Morgan Tested-by: 刘术高 Cc: stable@vger.kernel.org Link: https://patch.msgid.link/87cymdsu0r.ffs@tglx --- kernel/time/clockevents.c | 33 ++++++++++++++++++++------------- kernel/time/tick-broadcast.c | 36 ++++++++++++++++++++++-------------- kernel/time/tick-internal.h | 2 ++ 3 files changed, 44 insertions(+), 27 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 0014d163f989..62ad7c000386 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -615,33 +615,40 @@ void clockevents_handle_noop(struct clock_event_device *dev) { } -/** - * clockevents_exchange_device - release and request clock devices - * @old: device to release (can be NULL) - * @new: device to request (can be NULL) - * - * Called from various tick functions with clockevents_lock held and - * interrupts disabled. - */ -void clockevents_exchange_device(struct clock_event_device *old, - struct clock_event_device *new) +void __clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new) { /* * Caller releases a clock event device. We queue it into the * released list and do a notify add later. */ if (old) { - module_put(old->owner); clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED); list_move(&old->list, &clockevents_released); } if (new) { - BUG_ON(!clockevent_state_detached(new)); + WARN_ON(!clockevent_state_detached(new)); clockevents_shutdown(new); } } +/** + * clockevents_exchange_device - release and request clock devices + * @old: device to release (can be NULL) + * @new: device to request (can be NULL) + * + * Called from various tick functions with clockevents_lock held and + * interrupts disabled. + */ +void clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new) +{ + __clockevents_exchange_device(old, new); + if (old) + module_put(old->owner); +} + /** * clockevents_suspend - suspend clock devices */ @@ -699,7 +706,7 @@ void tick_offline_cpu(unsigned int cpu) if (cpumask_test_cpu(cpu, dev->cpumask) && cpumask_weight(dev->cpumask) == 1 && !tick_is_broadcast_device(dev)) { - BUG_ON(!clockevent_state_detached(dev)); + WARN_ON(!clockevent_state_detached(dev)); list_del(&dev->list); } } diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c index 115e0bf01276..bda3d2391a60 100644 --- a/kernel/time/tick-broadcast.c +++ b/kernel/time/tick-broadcast.c @@ -165,23 +165,31 @@ static bool tick_set_oneshot_wakeup_device(struct clock_event_device *newdev, */ void tick_install_broadcast_device(struct clock_event_device *dev, int cpu) { - struct clock_event_device *cur = tick_broadcast_device.evtdev; + struct clock_event_device *cur; - if (tick_set_oneshot_wakeup_device(dev, cpu)) - return; + scoped_guard(raw_spinlock_irqsave, &tick_broadcast_lock) { - if (!tick_check_broadcast_device(cur, dev)) - return; + if (tick_set_oneshot_wakeup_device(dev, cpu)) + return; - if (!try_module_get(dev->owner)) - return; + cur = tick_broadcast_device.evtdev; + if (!tick_check_broadcast_device(cur, dev)) + return; - clockevents_exchange_device(cur, dev); + if (!try_module_get(dev->owner)) + return; + + __clockevents_exchange_device(cur, dev); + if (cur) + cur->event_handler = clockevents_handle_noop; + WRITE_ONCE(tick_broadcast_device.evtdev, dev); + if (!cpumask_empty(tick_broadcast_mask)) + tick_broadcast_start_periodic(dev); + } + + /* Module release must be outside of the lock */ if (cur) - cur->event_handler = clockevents_handle_noop; - tick_broadcast_device.evtdev = dev; - if (!cpumask_empty(tick_broadcast_mask)) - tick_broadcast_start_periodic(dev); + module_put(cur->owner); if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT)) return; @@ -1218,7 +1226,7 @@ int tick_broadcast_oneshot_active(void) */ bool tick_broadcast_oneshot_available(void) { - struct clock_event_device *bc = tick_broadcast_device.evtdev; + struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev); return bc ? bc->features & CLOCK_EVT_FEAT_ONESHOT : false; } @@ -1226,7 +1234,7 @@ bool tick_broadcast_oneshot_available(void) #else int __tick_broadcast_oneshot_control(enum tick_broadcast_state state) { - struct clock_event_device *bc = tick_broadcast_device.evtdev; + struct clock_event_device *bc = READ_ONCE(tick_broadcast_device.evtdev); if (!bc || (bc->features & CLOCK_EVT_FEAT_HRTIMER)) return -EBUSY; diff --git a/kernel/time/tick-internal.h b/kernel/time/tick-internal.h index 182974c4f21b..65680db95053 100644 --- a/kernel/time/tick-internal.h +++ b/kernel/time/tick-internal.h @@ -55,6 +55,8 @@ static inline void clockevent_set_state(struct clock_event_device *dev, } extern void clockevents_shutdown(struct clock_event_device *dev); +extern void __clockevents_exchange_device(struct clock_event_device *old, + struct clock_event_device *new); extern void clockevents_exchange_device(struct clock_event_device *old, struct clock_event_device *new); extern void clockevents_switch_state(struct clock_event_device *dev, -- cgit v1.2.3 From c5dcb3aadc18d7b82ba64790721b005d18193d35 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Thu, 10 Sep 2026 16:34:42 +0200 Subject: hrtimer: Use hard expiry when updating timers on the same base Rearming a queued timer with nonzero slack can leave the timerqueue out of order. remove_and_enqueue_same_base() checks the new soft expiry against its neighbours' hard expiries, then stores the new hard expiry in the node without requeueing it. For example, with A at 10 and B at 20, rearming A at 11 with slack 30 passes the neighbour check but leaves A's hard expiry of 41 before B's 20. The same function also caches the soft expiry in base->expires_next when updating or inserting the first timer, giving next-event selection an earlier deadline than the queue head's hard expiry. Set the timer expiry before handling the queue. Use its stored hard expiry for the in-place ordering check and both updates to base->expires_next. The early update is safe because remove_and_enqueue_same_base() runs with base->cpu_base->lock held. The lock keeps the queue stable while hrtimer_can_update_in_place() checks the new expiry against both neighbours. If the check fails, timerqueue_linked_del() removes the node without comparing expiry values before it is reinserted. Fixes: eddffab8282e3 ("hrtimer: Keep track of first expiring timer per clock base") Fixes: 343f2f4dc5425 ("hrtimer: Try to modify timers in place") Signed-off-by: Andrea Parri Signed-off-by: Thomas Gleixner Assisted-by: LLM Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260910143442.2018-1-parri.andrea@gmail.com --- kernel/time/hrtimer.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 530d61257b9a..cbf1693c86b3 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1263,13 +1263,23 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b { bool was_first = false; + /* + * Updating the sort key while @timer is queued can temporarily + * make the tree inconsistent. This is safe under cpu_base->lock: + * no other queue operation can observe that state. + * hrtimer_can_update_in_place() either confirms that the new expiry + * fits between the neighbours or timerqueue_linked_del() removes the + * timer without consulting the expiry. + */ + hrtimer_set_expires_range_ns(timer, expires, delta_ns); + expires = hrtimer_get_expires(timer); + /* Remove it from the timer queue if active */ if (timer->is_queued) { was_first = !timerqueue_linked_prev(&timer->node); /* Try to update in place to avoid the de/enqueue dance */ if (hrtimer_can_update_in_place(timer, base, expires)) { - hrtimer_set_expires_range_ns(timer, expires, delta_ns); trace_hrtimer_start(timer, mode, true); if (was_first) base->expires_next = expires; @@ -1280,9 +1290,6 @@ remove_and_enqueue_same_base(struct hrtimer *timer, struct hrtimer_clock_base *b timerqueue_linked_del(&base->active, &timer->node); } - /* Set the new expiry time */ - hrtimer_set_expires_range_ns(timer, expires, delta_ns); - debug_activate(timer, mode, timer->is_queued); base->cpu_base->active_bases |= 1 << base->index; -- cgit v1.2.3 From 82431877d837a6c2593efd8e66ba28b35a220ca4 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:30 +0000 Subject: sysctl: Check range in proc_dointvec_ms_jiffies_minmax Add the range check to do_proc_int_conv_ms_jiffies_minmax that commit d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") incorrectly removed. Fixes: d174174c6776 ("sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'kernel/time') diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 213ae1d6a014..70926b73905a 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -181,7 +181,7 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, int *k_ptr, int dir, const struct ctl_table *tbl) { - return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false, + return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms); } -- cgit v1.2.3 From 318012c56576e09806747f64f89f8f3cde1f999f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 5 Sep 2026 23:36:31 +0000 Subject: sysctl: Check range in do_proc_ulong_conv_ms_jiffies Add the range check back to do_proc_ulong_conv_ms_jiffies that commit b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") incorrectly removed. Append "_minmax" to the end of do_proc_ulong_conv_ms_jiffies so it is clear that there should be a range check. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Signed-off-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 70926b73905a..41f88ebac74a 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -195,10 +195,10 @@ static int sysctl_k2u_ulong_conv_ms(ulong *u_ptr, const ulong *k_ptr) return proc_ulong_k2u_conv_kop(u_ptr, k_ptr, sysctl_jiffies_to_msecs); } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { - return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, false, + return proc_ulong_conv(u_ptr, k_ptr, dir, tbl, true, sysctl_u2k_ulong_conv_ms, sysctl_k2u_ulong_conv_ms); } @@ -229,8 +229,8 @@ static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, return -ENOSYS; } -static int do_proc_ulong_conv_ms_jiffies(bool *negp, ulong *u_ptr, ulong *k_ptr, - int dir, const struct ctl_table *tbl) +static int do_proc_ulong_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr, ulong *k_ptr, + int dir, const struct ctl_table *tbl) { return -ENOSYS; } @@ -333,7 +333,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return proc_doulongvec_conv(table, dir, buffer, lenp, ppos, - do_proc_ulong_conv_ms_jiffies); + do_proc_ulong_conv_ms_jiffies_minmax); } EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); -- cgit v1.2.3 From afdf35cfae0d039a4a6c907fa5d8391f1ef0a0aa Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 10 Sep 2026 12:22:16 +0200 Subject: sysctl: Fix type truncation in sysctl_msec_to_jiffies Return MAX_JIFFY_OFFSET for all the values truncated when val (u64) is passed to msecs_to_jiffies (u32). This aligns with how very large millisecond values get translated into MAX_JIFFY_OFFSET. Fixes: b96b5c6708ea ("sysctl: Replace do_proc_do{int,ulong,uint}vec with do_proc_vec") Suggested-by: Kuniyuki Iwashima Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'kernel/time') diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 41f88ebac74a..80c354811538 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -136,6 +136,8 @@ static int sysctl_k2u_int_conv_userhz(bool *negp, ulong *u_ptr, const int *k_ptr static ulong sysctl_msecs_to_jiffies(const ulong val) { + if (val > jiffies_to_msecs(MAX_JIFFY_OFFSET)) + return MAX_JIFFY_OFFSET; return msecs_to_jiffies(val); } -- cgit v1.2.3 From c21eaa72f02fc6e85621cbe09d303d8fb8bd39cd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Wed, 16 Sep 2026 20:48:30 +0200 Subject: posix-cpu-timers: Prevent freeing a timer which is queued on the expiry list Kijo analyzed another race in the POSIX CPU timer code: Commit bf635681c906 converted cpu_timer::firing from a tristate value to a boolean. This lost the distinction between "not owned by the firing list" and "still owned, but delivery was canceled". The resulting race is: expiry handler timer_settime() timer_delete() -------------- --------------- -------------- collect timer onto private firing list firing = true observes firing = true firing = false return TIMER_RETRY wait for handler observes firing = false finish deletion unhash and free timer resume list traversal read freed elist.next -> UAF The firing bit is clearly the wrong indicator since that commit. Check whether the timer is queued on the expiry list or not instead. If it is queued clear the firing bit to prevent signal delivery as before and return TIMER_RETRY so the caller unlocks the timer which allows the expiry code to make progress and remove it from the list. Fixes: bf635681c906 ("posix-cpu-timers: Cleanup the firing logic") Reported-by: Kijo Park Debugged-by: Kijo Park Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Cc: stable@vger.kernel.org --- kernel/time/posix-cpu-timers.c | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) (limited to 'kernel/time') diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index d73d31c7994f..0bf4fcd969c8 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -408,6 +408,7 @@ static int posix_cpu_timer_create(struct k_itimer *new_timer) new_timer->kclock = &clock_posix_cpu; timerqueue_init(&new_timer->it.cpu.node); + INIT_LIST_HEAD(&new_timer->it.cpu.elist); new_timer->it.cpu.pid = get_pid(pid); rcu_read_unlock(); return 0; @@ -565,6 +566,24 @@ static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned l return NULL; } +/* + * If the timer is queued on the expiry list, then it cannot be dequeued because + * the firing list is not protected by sighand->lock. The delivery path is + * waiting for the timer lock. So go back, unlock and retry. + */ +static bool posix_cpu_timer_on_expiry_list(struct k_itimer *timer) +{ + if (list_empty(&timer->it.cpu.elist)) + return false; + + /* + * Prevent signal delivery as there is no point in delivering a signal + * which is made obsolete right away. + */ + timer->it.cpu.firing = false; + return true; +} + /* * Clean up a CPU-clock timer that is about to be destroyed. * This is called from timer deletion with the timer already locked. @@ -580,18 +599,10 @@ static int posix_cpu_timer_del(struct k_itimer *timer) p = timer_lock_sighand(timer, &flags); if (likely(p)) { - if (timer->it.cpu.firing) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) ret = TIMER_RETRY; - } else { + else disarm_timer(timer, p); - } unlock_task_sighand(p, &flags); } @@ -731,14 +742,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, /* Retrieve the current expiry time before disarming the timer */ old_expires = cpu_timer_getexpires(ctmr); - if (unlikely(timer->it.cpu.firing)) { - /* - * Prevent signal delivery. The timer cannot be dequeued - * because it is on the firing list which is not protected - * by sighand->lock. The delivery path is waiting for - * the timer lock. So go back, unlock and retry. - */ - timer->it.cpu.firing = false; + if (posix_cpu_timer_on_expiry_list(timer)) { ret = TIMER_RETRY; } else { cpu_timer_dequeue(ctmr); -- cgit v1.2.3 From fdd6553e1e53b0421d89c7469c8a36d2eadb1115 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 28 Sep 2026 10:02:05 +0800 Subject: time/jiffies: Saturate in mult_hz() instead of wrapping Return ULONG_MAX for values that don't fit an unsigned long. proc_int_u2k_conv_uop() now correctly rejects a result above INT_MAX. This is the erroneous behaviour that is being fixed. The input has to exceed ULONG_MAX / HZ for the product to wrap, so the value below is specific to CONFIG_HZ=1000: # echo 18446744073709552 > /proc/sys/net/ipv4/tcp_keepalive_time # cat /proc/sys/net/ipv4/tcp_keepalive_time 0 That value is now rejected with an error. The original bound ("*u_ptr > INT_MAX / HZ") was removed in commit 2dc164a48e6f ("sysctl: Create converter functions with two new macros"). Fixes: 2dc164a48e6f ("sysctl: Create converter functions with two new macros") Cc: stable@vger.kernel.org Signed-off-by: Zhan Xusheng Signed-off-by: Joel Granados --- kernel/time/jiffies.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'kernel/time') diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 80c354811538..9b3487d40cd6 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -101,6 +101,8 @@ void __init register_refined_jiffies(long cycles_per_second) #ifdef CONFIG_SYSCTL static ulong mult_hz(const ulong val) { + if (val >= ULONG_MAX / HZ) + return ULONG_MAX; return val * HZ; } -- cgit v1.2.3