Merge tag 'timers-urgent-2026-09-13' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull timer fixes from Ingo Molnar:

 - Fix clockevents replacement race when a broadcast
   device is replaced which may trigger a BUG() crash
   (朱恺乾 - Zhu Kaiqian)

 - Fix potential timerqueue ordering bug when rearming
   a queued timer with nonzero slack (Andrea Parri)

* tag 'timers-urgent-2026-09-13' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  hrtimer: Use hard expiry when updating timers on the same base
  tick/broadcast: Plug clockevents replacement race
This commit is contained in:
Linus Torvalds
2026-09-13 09:10:38 -07:00
4 changed files with 56 additions and 32 deletions
+21 -14
View File
@@ -615,6 +615,24 @@ void clockevents_handle_noop(struct clock_event_device *dev)
{
}
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) {
clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED);
list_move(&old->list, &clockevents_released);
}
if (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)
@@ -626,20 +644,9 @@ void clockevents_handle_noop(struct clock_event_device *dev)
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) {
__clockevents_exchange_device(old, new);
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));
clockevents_shutdown(new);
}
}
/**
@@ -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);
}
}
+11 -4
View File
@@ -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;
+22 -14
View File
@@ -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;
+2
View File
@@ -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,