{"id":2220422,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2220422/?format=json","web_url":"http://patchwork.ozlabs.org/project/netfilter-devel/patch/20260407083247.630389532@kernel.org/","project":{"id":26,"url":"http://patchwork.ozlabs.org/api/1.1/projects/26/?format=json","name":"Netfilter Development","link_name":"netfilter-devel","list_id":"netfilter-devel.vger.kernel.org","list_email":"netfilter-devel@vger.kernel.org","web_url":null,"scm_url":null,"webscm_url":null},"msgid":"<20260407083247.630389532@kernel.org>","date":"2026-04-07T08:54:22","name":"[02/12] hrtimer: Provide hrtimer_start_range_ns_user()","commit_ref":null,"pull_url":null,"state":"accepted","archived":true,"hash":"e807285b44dc9cb54354ad664014103c09398431","submitter":{"id":92397,"url":"http://patchwork.ozlabs.org/api/1.1/people/92397/?format=json","name":"Thomas Gleixner","email":"tglx@kernel.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/netfilter-devel/patch/20260407083247.630389532@kernel.org/mbox/","series":[{"id":498944,"url":"http://patchwork.ozlabs.org/api/1.1/series/498944/?format=json","web_url":"http://patchwork.ozlabs.org/project/netfilter-devel/list/?series=498944","date":"2026-04-07T08:54:12","name":"hrtimers: Prevent hrtimer interrupt starvation","version":1,"mbox":"http://patchwork.ozlabs.org/series/498944/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2220422/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2220422/checks/","tags":{},"headers":{"Return-Path":"\n <netfilter-devel+bounces-11644-incoming=patchwork.ozlabs.org@vger.kernel.org>","X-Original-To":["incoming@patchwork.ozlabs.org","netfilter-devel@vger.kernel.org"],"Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256\n header.s=k20201202 header.b=YwSajaqU;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.234.253.10; helo=sea.lore.kernel.org;\n envelope-from=netfilter-devel+bounces-11644-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)","smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=\"YwSajaqU\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=10.30.226.201"],"Received":["from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fqg5p2Stdz1xtJ\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 07 Apr 2026 18:55:58 +1000 (AEST)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id CCAA93030E89\n\tfor <incoming@patchwork.ozlabs.org>; Tue,  7 Apr 2026 08:54:27 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id C4F9539B948;\n\tTue,  7 Apr 2026 08:54:26 +0000 (UTC)","from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org\n [10.30.226.201])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id 3720339A7E5;\n\tTue,  7 Apr 2026 08:54:25 +0000 (UTC)","by smtp.kernel.org (Postfix) with ESMTPSA id 44464C116C6;\n\tTue,  7 Apr 2026 08:54:24 +0000 (UTC)"],"ARC-Seal":"i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1775552066; cv=none;\n b=eag682FHmTBfJ+9fjNGj9ZJfnAL5gFa0lJ6DJuZRJw1+SoeGCObmLgrMVRRIhG7ZzivjTz2tbqC/REWlPbIJ8rGnpmtceFiCRFGUrY19V3sjgvckK6cL74H4i62jpryhqiPxsx2oJihWlilLls08VFDgmcgmb4IvMo11ErZ5l9c=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1775552066; c=relaxed/simple;\n\tbh=1moD5YRre+7PFAa0cZ6BeO4IeU3BIQxcY31TfBcyn8o=;\n\th=Date:Message-ID:From:To:Cc:Subject:References:MIME-Version:\n\t Content-Type;\n b=X9/5jmv+cUK2i6rH1WZtfQqyvTabUD9sV0vtQPO5Gy9ILzOCMYUpLLdRnIE9uIfDJ3GkUhH5OpiCHKzCA0Dk6vQGE8KzqnQIoxuuUCd0HC5dSrk/vUKFsYKzFqU8T+xlPqwhtPKsULr9b3JYh6QMiWsqWANkmuuCwrxCsGEgSuc=","ARC-Authentication-Results":"i=1; smtp.subspace.kernel.org;\n dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org\n header.b=YwSajaqU; arc=none smtp.client-ip=10.30.226.201","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org;\n\ts=k20201202; t=1775552065;\n\tbh=1moD5YRre+7PFAa0cZ6BeO4IeU3BIQxcY31TfBcyn8o=;\n\th=Date:From:To:Cc:Subject:References:From;\n\tb=YwSajaqUJG6X1oGQYuQhF19EW9GJXLFGJgVgFOwd38THJA1YukJTodCEUCrtI3ike\n\t K1tqccGIUYChfXwIiSZPLId2qP9/PvCRk/QIxGz1MZSYvnxBaAfdU/r4FHv/FMrUUh\n\t tfeAnkh1wkF6FkkTn3GMvlkqGjXkOYGt4iGBo5x2c+yWlaVaHKm7X0V9y0FJlvkV5B\n\t sSAdLo7+eeEaT789qLZCAkcYN5zdCDUqf2+kyeg6GelLjg9dW9QrymMYqT1KWJe+Cx\n\t jE8uvH30NP8oLuzPu9v9+kchbniNFQ8VA3HUy+mrq5+yRG/z+fpYolZqR1e1ZW1eNy\n\t ekyM/il3SDSCg==","Date":"Tue, 07 Apr 2026 10:54:22 +0200","Message-ID":"<20260407083247.630389532@kernel.org>","User-Agent":"quilt/0.68","From":"Thomas Gleixner <tglx@kernel.org>","To":"LKML <linux-kernel@vger.kernel.org>","Cc":"Calvin Owens <calvin@wbinvd.org>,\n Anna-Maria Behnsen <anna-maria@linutronix.de>,\n Frederic Weisbecker <frederic@kernel.org>,\n Peter Zijlstra <peterz@infradead.org>,\n Ingo Molnar <mingo@kernel.org>,\n John Stultz <jstultz@google.com>,\n Stephen Boyd <sboyd@kernel.org>,\n Alexander Viro <viro@zeniv.linux.org.uk>,\n Christian Brauner <brauner@kernel.org>,\n Jan Kara <jack@suse.cz>,\n linux-fsdevel@vger.kernel.org,\n Sebastian Reichel <sre@kernel.org>,\n linux-pm@vger.kernel.org,\n Pablo Neira Ayuso <pablo@netfilter.org>,\n Florian Westphal <fw@strlen.de>,\n Phil Sutter <phil@nwl.cc>,\n netfilter-devel@vger.kernel.org,\n coreteam@netfilter.org","Subject":"[patch 02/12] hrtimer: Provide hrtimer_start_range_ns_user()","References":"<20260407083219.478203185@kernel.org>","Precedence":"bulk","X-Mailing-List":"netfilter-devel@vger.kernel.org","List-Id":"<netfilter-devel.vger.kernel.org>","List-Subscribe":"<mailto:netfilter-devel+subscribe@vger.kernel.org>","List-Unsubscribe":"<mailto:netfilter-devel+unsubscribe@vger.kernel.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8"},"content":"Calvin reported an odd NMI watchdog lockup which claims that the CPU locked\nup in user space. He provided a reproducer, which set's up a timerfd based\ntimer and then rearms it in a loop with an absolute expiry time of 1ns.\n\nAs the expiry time is in the past, the timer ends up as the first expiring\ntimer in the per CPU hrtimer base and the clockevent device is programmed\nwith the minimum delta value. If the machine is fast enough, this ends up\nin a endless loop of programming the delta value to the minimum value\ndefined by the clock event device, before the timer interrupt can fire,\nwhich starves the interrupt and consequently triggers the lockup detector\nbecause the hrtimer callback of the lockup mechanism is never invoked.\n\nThe clockevents code already has a last resort mechanism to prevent that,\nbut it's sensible to catch such issues before trying to reprogram the clock\nevent device.\n\nProvide a variant of hrtimer_start_range_ns(), which sanity checks the\ntimer after queueing it. It does not so before because the timer might be\narmed and therefore needs to be dequeued. also we optimize for the latest\npossible point to check, so that the clock event prevention is avoided as\nmuch as possible.\n\nIf the timer is already expired _before_ the clock event is reprogrammed,\nremove the timer from the queue and signal to the caller that the operation\nfailed by returning false.\n\nThat allows the caller to take immediate action without going through the\nloops and hoops of the hrtimer interrupt.\n\nThe queueing code can't invoke the timer callback as the caller might hold\na lock which is taken in the callback.\n\nAdd a tracepoint which allows to analyze the expired at start situation.\n\nReported-by: Calvin Owens <calvin@wbinvd.org>\nSigned-off-by: Thomas Gleixner <tglx@kernel.org>\nCc: Anna-Maria Behnsen <anna-maria@linutronix.de>\nCc: Frederic Weisbecker <frederic@kernel.org>\n---\n include/linux/hrtimer.h      |   20 +++++-\n include/trace/events/timer.h |   13 ++++\n kernel/time/hrtimer.c        |  135 ++++++++++++++++++++++++++++++++++++++-----\n 3 files changed, 151 insertions(+), 17 deletions(-)","diff":"--- a/include/linux/hrtimer.h\n+++ b/include/linux/hrtimer.h\n@@ -230,6 +230,9 @@ static inline void destroy_hrtimer_on_st\n extern void hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,\n \t\t\t\t   u64 range_ns, const enum hrtimer_mode mode);\n \n+extern bool hrtimer_start_range_ns_user(struct hrtimer *timer, ktime_t tim,\n+\t\t\t\t\tu64 range_ns, const enum hrtimer_mode mode);\n+\n /**\n  * hrtimer_start - (re)start an hrtimer\n  * @timer:\tthe timer to be added\n@@ -247,17 +250,28 @@ static inline void hrtimer_start(struct\n extern int hrtimer_cancel(struct hrtimer *timer);\n extern int hrtimer_try_to_cancel(struct hrtimer *timer);\n \n-static inline void hrtimer_start_expires(struct hrtimer *timer,\n-\t\t\t\t\t enum hrtimer_mode mode)\n+static inline void hrtimer_start_expires(struct hrtimer *timer, enum hrtimer_mode mode)\n {\n-\tu64 delta;\n \tktime_t soft, hard;\n+\tu64 delta;\n+\n \tsoft = hrtimer_get_softexpires(timer);\n \thard = hrtimer_get_expires(timer);\n \tdelta = ktime_to_ns(ktime_sub(hard, soft));\n \thrtimer_start_range_ns(timer, soft, delta, mode);\n }\n \n+static inline bool hrtimer_start_expires_user(struct hrtimer *timer, enum hrtimer_mode mode)\n+{\n+\tktime_t soft, hard;\n+\tu64 delta;\n+\n+\tsoft = hrtimer_get_softexpires(timer);\n+\thard = hrtimer_get_expires(timer);\n+\tdelta = ktime_to_ns(ktime_sub(hard, soft));\n+\treturn hrtimer_start_range_ns_user(timer, soft, delta, mode);\n+}\n+\n void hrtimer_sleeper_start_expires(struct hrtimer_sleeper *sl,\n \t\t\t\t   enum hrtimer_mode mode);\n \n--- a/include/trace/events/timer.h\n+++ b/include/trace/events/timer.h\n@@ -297,6 +297,19 @@ DECLARE_EVENT_CLASS(hrtimer_class,\n );\n \n /**\n+ * hrtimer_start_expired - Invoked when a expired timer was started\n+ * @hrtimer:\tpointer to struct hrtimer\n+ *\n+ * Preceeded by a hrtimer_start tracepoint.\n+ */\n+DEFINE_EVENT(hrtimer_class, hrtimer_start_expired,\n+\n+\tTP_PROTO(struct hrtimer *hrtimer),\n+\n+\tTP_ARGS(hrtimer)\n+);\n+\n+/**\n  * hrtimer_expire_exit - called immediately after the hrtimer callback returns\n  * @hrtimer:\tpointer to struct hrtimer\n  *\n--- a/kernel/time/hrtimer.c\n+++ b/kernel/time/hrtimer.c\n@@ -1215,6 +1215,12 @@ hrtimer_update_softirq_timer(struct hrti\n \thrtimer_reprogram(cpu_base->softirq_next_timer, reprogram);\n }\n \n+enum {\n+\tHRTIMER_REPROGRAM_NONE,\n+\tHRTIMER_REPROGRAM,\n+\tHRTIMER_REPROGRAM_FORCE,\n+};\n+\n static int __hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,\n \t\t\t\t    u64 delta_ns, const enum hrtimer_mode mode,\n \t\t\t\t    struct hrtimer_clock_base *base)\n@@ -1276,7 +1282,7 @@ static int __hrtimer_start_range_ns(stru\n \t\t * expiring timer there.\n \t\t */\n \t\tif (hrtimer_base_is_online(this_cpu_base))\n-\t\t\treturn first;\n+\t\t\treturn first ? HRTIMER_REPROGRAM : HRTIMER_REPROGRAM_NONE;\n \n \t\t/*\n \t\t * Timer was enqueued remote because the current base is\n@@ -1296,8 +1302,24 @@ static int __hrtimer_start_range_ns(stru\n \t * reprogramming on removal and enqueue. Force reprogram the\n \t * hardware by evaluating the new first expiring timer.\n \t */\n-\thrtimer_force_reprogram(new_base->cpu_base, 1);\n-\treturn 0;\n+\treturn HRTIMER_REPROGRAM_FORCE;\n+}\n+\n+static int hrtimer_start_range_ns_common(struct hrtimer *timer, ktime_t tim,\n+\t\t\t\t\t u64 delta_ns, const enum hrtimer_mode mode,\n+\t\t\t\t\t struct hrtimer_clock_base *base)\n+{\n+\t/*\n+\t * Check whether the HRTIMER_MODE_SOFT bit and hrtimer.is_soft\n+\t * match on CONFIG_PREEMPT_RT = n. With PREEMPT_RT check the hard\n+\t * expiry mode because unmarked timers are moved to softirq expiry.\n+\t */\n+\tif (!IS_ENABLED(CONFIG_PREEMPT_RT))\n+\t\tWARN_ON_ONCE(!(mode & HRTIMER_MODE_SOFT) ^ !timer->is_soft);\n+\telse\n+\t\tWARN_ON_ONCE(!(mode & HRTIMER_MODE_HARD) ^ !timer->is_hard);\n+\n+\treturn __hrtimer_start_range_ns(timer, tim, delta_ns, mode, base);\n }\n \n /**\n@@ -1315,25 +1337,110 @@ void hrtimer_start_range_ns(struct hrtim\n \tstruct hrtimer_clock_base *base;\n \tunsigned long flags;\n \n-\t/*\n-\t * Check whether the HRTIMER_MODE_SOFT bit and hrtimer.is_soft\n-\t * match on CONFIG_PREEMPT_RT = n. With PREEMPT_RT check the hard\n-\t * expiry mode because unmarked timers are moved to softirq expiry.\n-\t */\n-\tif (!IS_ENABLED(CONFIG_PREEMPT_RT))\n-\t\tWARN_ON_ONCE(!(mode & HRTIMER_MODE_SOFT) ^ !timer->is_soft);\n-\telse\n-\t\tWARN_ON_ONCE(!(mode & HRTIMER_MODE_HARD) ^ !timer->is_hard);\n-\n \tbase = lock_hrtimer_base(timer, &flags);\n \n-\tif (__hrtimer_start_range_ns(timer, tim, delta_ns, mode, base))\n+\tswitch (hrtimer_start_range_ns_common(timer, tim, delta_ns, mode, base)) {\n+\tcase HRTIMER_REPROGRAM:\n \t\thrtimer_reprogram(timer, true);\n+\t\tbreak;\n+\tcase HRTIMER_REPROGRAM_FORCE:\n+\t\thrtimer_force_reprogram(timer->base->cpu_base, 1);\n+\t\tbreak;\n+\t}\n \n \tunlock_hrtimer_base(timer, &flags);\n }\n EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);\n \n+static inline bool hrtimer_check_user_timer(struct hrtimer *timer)\n+{\n+\tstruct hrtimer_cpu_base *cpu_base = timer->base->cpu_base;\n+\tktime_t expires;\n+\n+\t/*\n+\t * This uses soft expires because that's the user provided\n+\t * expiry time, while expires can be further in the past\n+\t * due to a slack value added to the user expiry time.\n+\t */\n+\texpires = hrtimer_get_softexpires(timer);\n+\n+\t/* Convert to monotonic */\n+\texpires = ktime_sub(expires, timer->base->offset);\n+\n+\t/*\n+\t * Check whether this timer will end up as the first expiring timer in\n+\t * the CPU base. If not, no further checks required as it's then\n+\t * guaranteed to expire in the future.\n+\t */\n+\tif (expires >= cpu_base->expires_next)\n+\t\treturn true;\n+\n+\t/* Validate that the expiry time is in the future. */\n+\tif (expires > ktime_get())\n+\t\treturn true;\n+\n+\tdebug_deactivate(timer);\n+\t__remove_hrtimer(timer, timer->base, HRTIMER_STATE_INACTIVE, false);\n+\ttrace_hrtimer_start_expired(timer);\n+\treturn false;\n+}\n+\n+static bool hrtimer_reprogram_user(struct hrtimer *timer)\n+{\n+\tif (!hrtimer_check_user_timer(timer))\n+\t\treturn false;\n+\thrtimer_reprogram(timer, true);\n+\treturn true;\n+}\n+\n+static bool hrtimer_force_reprogram_user(struct hrtimer *timer)\n+{\n+\tbool ret = hrtimer_check_user_timer(timer);\n+\n+\t/*\n+\t * The base must always be reevaluated, independent of the result\n+\t * above because the timer was the first pending timer.\n+\t */\n+\thrtimer_force_reprogram(timer->base->cpu_base, 1);\n+\treturn ret;\n+}\n+\n+/**\n+ * hrtimer_start_range_ns_user - (re)start an user controlled hrtimer\n+ * @timer:\tthe timer to be added\n+ * @tim:\texpiry time\n+ * @delta_ns:\t\"slack\" range for the timer\n+ * @mode:\ttimer mode: absolute (HRTIMER_MODE_ABS) or\n+ *\t\trelative (HRTIMER_MODE_REL), and pinned (HRTIMER_MODE_PINNED);\n+ *\t\tsoftirq based mode is considered for debug purpose only!\n+ *\n+ * Returns: True when the timer was queued, false if it was already expired\n+ *\n+ * This function cannot invoke the timer callback for expired timers as it might\n+ * be called under a lock which the timer callback needs to acquire. So the\n+ * caller has to handle that case.\n+ */\n+bool hrtimer_start_range_ns_user(struct hrtimer *timer, ktime_t tim,\n+\t\t\t\t u64 delta_ns, const enum hrtimer_mode mode)\n+{\n+\tstruct hrtimer_clock_base *base;\n+\tunsigned long flags;\n+\tbool ret = true;\n+\n+\tbase = lock_hrtimer_base(timer, &flags);\n+\tswitch (hrtimer_start_range_ns_common(timer, tim, delta_ns, mode, base)) {\n+\tcase HRTIMER_REPROGRAM:\n+\t\tret = hrtimer_reprogram_user(timer);\n+\t\tbreak;\n+\tcase HRTIMER_REPROGRAM_FORCE:\n+\t\tret = hrtimer_force_reprogram_user(timer);\n+\t\tbreak;\n+\t}\n+\tunlock_hrtimer_base(timer, &flags);\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(hrtimer_start_range_ns_user);\n+\n /**\n  * hrtimer_try_to_cancel - try to deactivate a timer\n  * @timer:\thrtimer to stop\n","prefixes":["02/12"]}