* [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter
@ 2026-08-27 18:10 Boqun Feng
2026-08-27 19:48 ` [PATCH] preempt: Remove hardirq_disable_count() Boqun Feng
2026-08-27 20:07 ` [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter Bradley Morgan
0 siblings, 2 replies; 3+ messages in thread
From: Boqun Feng @ 2026-08-27 18:10 UTC (permalink / raw)
To: tglx
Cc: Boqun Feng, Peter Zijlstra (Intel),
Lyude Paul, Sebastian Andrzej Siewior, Joel Fernandes,
linux-kernel
Currently a softirq may be pending longer then expected if the
triggering interrupt happens in-between hardirq_disable_enter() and
_local_interrupt_disable() in local_interrupt_disable():
local_interrupt_disable():
hardirq_disable_enter();
<interrupt>
...
__irq_exit_rcu():
// false because hardirq_disable_count() is not 0
if (.. && !hardirq_disable_count() && ..) {
invoke_softirq();
}
_local_interrupt_disable();
, it'll defer the softirq to the next interrupt which can be forever.
The order between hardirq_disable_enter() and _local_interrupt_disable()
is to optimize re-disabling interrupts if they are already disabled, but
as 1) local_interrupt_disable() is not widely used yet and 2) the proper
way to achieve this optimization may need fixing up the counter at
entry/exit time [1], so reverse the order for now to avoid the softirq
pending issue.
Since we are doing this, the part of saving the current state is
separated from irq disabling, and we basically do the following in
local_interrupt_disable():
local_irq_save(flags);
if (counter++ == 0) {
this_cpu(local_interrupt_disable_state) = flags;
}
Change _local_interrupt_disable() to _local_interrupt_save_state().
Link: https://lore.kernel.org/lkml/87v78wezid.ffs@fw13/ [1]
Reported-by: Thomas Gleixner <tglx@kernel.org>
Fixes: e901c1510e24 ("irq,spin_lock: Add counted interrupt disabling/enabling")
Signed-off-by: Boqun Feng <boqun@kernel.org>
---
include/linux/interrupt_rc.h | 19 ++++++++-----------
kernel/softirq.c | 17 ++++-------------
2 files changed, 12 insertions(+), 24 deletions(-)
diff --git a/include/linux/interrupt_rc.h b/include/linux/interrupt_rc.h
index b9a7f05ecf42..e68e1bedba66 100644
--- a/include/linux/interrupt_rc.h
+++ b/include/linux/interrupt_rc.h
@@ -20,11 +20,8 @@
/* Per-CPU interrupt disabling state for local_interrupt_{disable,enable}(). */
DECLARE_PER_CPU(unsigned long, local_interrupt_disable_state);
-static __always_inline void __local_interrupt_disable(void)
+static __always_inline void __local_interrupt_save_state(unsigned long flags)
{
- unsigned long flags;
-
- local_irq_save(flags);
raw_cpu_write(local_interrupt_disable_state, flags);
}
@@ -36,9 +33,9 @@ static __always_inline void __local_interrupt_enable(void)
}
#ifndef INSTANTIATE_EXPORTED_INTERRUPT_DISABLE
-static __always_inline void _local_interrupt_disable(void)
+static __always_inline void _local_interrupt_save_state(unsigned long flags)
{
- __local_interrupt_disable();
+ __local_interrupt_save_state(flags);
}
static __always_inline void _local_interrupt_enable(void)
@@ -46,27 +43,27 @@ static __always_inline void _local_interrupt_enable(void)
__local_interrupt_enable();
}
#else
-extern void _local_interrupt_disable(void);
+extern void _local_interrupt_save_state(unsigned long flags);
extern void _local_interrupt_enable(void);
#endif
#else /* !MODULE */
-extern void _local_interrupt_disable(void);
+extern void _local_interrupt_save_state(unsigned long flags);
extern void _local_interrupt_enable(void);
#endif /* !MODULE */
static inline void local_interrupt_disable(void)
{
int new_count;
+ unsigned long flags;
WARN_ON_ONCE(in_nmi());
+ local_irq_save(flags);
new_count = hardirq_disable_enter();
- /* Interrupts can happen here, but it's OK, see __irq_exit_rcu(). */
-
if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET)
- _local_interrupt_disable();
+ _local_interrupt_save_state(flags);
}
static inline void local_interrupt_enable(void)
diff --git a/kernel/softirq.c b/kernel/softirq.c
index 7980a4a232f9..5d02c36c40e3 100644
--- a/kernel/softirq.c
+++ b/kernel/softirq.c
@@ -91,11 +91,11 @@ EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context);
DEFINE_PER_CPU(unsigned long, local_interrupt_disable_state);
-void _local_interrupt_disable(void)
+void _local_interrupt_save_state(unsigned long flags)
{
- __local_interrupt_disable();
+ __local_interrupt_save_state(flags);
}
-EXPORT_SYMBOL(_local_interrupt_disable);
+EXPORT_SYMBOL(_local_interrupt_save_state);
void _local_interrupt_enable(void)
{
@@ -749,16 +749,7 @@ static inline void __irq_exit_rcu(void)
#endif
account_hardirq_exit(current);
preempt_count_sub(HARDIRQ_OFFSET);
- /*
- * Interrupts may happen between hardirq_disable_enter() and
- * local_irq_save() in local_interrupt_disable(), if irq_exit() invokes
- * softirq here, we may have a softirq handler calling
- * local_interrupt_disable() but it won't disable the IRQ because
- * hardirq disabling count is already 1, hence we need to prevent
- * invoking softirq when a local_interrupt_disable() is ongoing.
- */
- if (!in_interrupt() && !hardirq_disable_count() &&
- local_softirq_pending()) {
+ if (!in_interrupt() && local_softirq_pending()) {
/*
* If we left hrtimers unarmed, make sure to arm them now,
* before enabling interrupts to run softirq.
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] preempt: Remove hardirq_disable_count()
2026-08-27 18:10 [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter Boqun Feng
@ 2026-08-27 19:48 ` Boqun Feng
2026-08-27 20:07 ` [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter Bradley Morgan
1 sibling, 0 replies; 3+ messages in thread
From: Boqun Feng @ 2026-08-27 19:48 UTC (permalink / raw)
To: tglx
Cc: Peter Zijlstra (Intel),
Lyude Paul, Sebastian Andrzej Siewior, Joel Fernandes,
linux-kernel, Boqun Feng
It turns out the previous usage of hardirq_disable_count() in
__irq_exit_rcu() would cause softirq pending issues. Without that usage,
hardirq_disable_count() doesn't need to exist, so remove it.
Signed-off-by: Boqun Feng <boqun@kernel.org>
---
include/linux/preempt.h | 1 -
1 file changed, 1 deletion(-)
diff --git a/include/linux/preempt.h b/include/linux/preempt.h
index 8299657f0f86..6d2fedbd6758 100644
--- a/include/linux/preempt.h
+++ b/include/linux/preempt.h
@@ -168,7 +168,6 @@ static __always_inline unsigned char interrupt_context_level(void)
#define in_softirq() (softirq_count())
#define in_interrupt() (irq_count())
-#define hardirq_disable_count() ((preempt_count() & HARDIRQ_DISABLE_MASK) >> HARDIRQ_DISABLE_SHIFT)
#define hardirq_disable_enter() __preempt_count_add_return(HARDIRQ_DISABLE_OFFSET)
#define hardirq_disable_exit() __preempt_count_sub_return(HARDIRQ_DISABLE_OFFSET)
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter
2026-08-27 18:10 [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter Boqun Feng
2026-08-27 19:48 ` [PATCH] preempt: Remove hardirq_disable_count() Boqun Feng
@ 2026-08-27 20:07 ` Bradley Morgan
1 sibling, 0 replies; 3+ messages in thread
From: Bradley Morgan @ 2026-08-27 20:07 UTC (permalink / raw)
To: boqun; +Cc: bigeasy, joelagnelf, linux-kernel, lyude, peterz, tglx
On 27 August 2026 19:10:48 BST, Boqun Feng <boqun@kernel.org> wrote:
>Currently a softirq may be pending longer then expected if the
>triggering interrupt happens in-between hardirq_disable_enter() and
>_local_interrupt_disable() in local_interrupt_disable():
>
> local_interrupt_disable():
> hardirq_disable_enter();
> <interrupt>
> ...
> __irq_exit_rcu():
> // false because hardirq_disable_count() is not 0
> if (.. && !hardirq_disable_count() && ..) {
> invoke_softirq();
> }
> _local_interrupt_disable();
>
>, it'll defer the softirq to the next interrupt which can be forever.
>
>The order between hardirq_disable_enter() and _local_interrupt_disable()
>is to optimize re-disabling interrupts if they are already disabled, but
>as 1) local_interrupt_disable() is not widely used yet and 2) the proper
>way to achieve this optimization may need fixing up the counter at
>entry/exit time [1], so reverse the order for now to avoid the softirq
>pending issue.
>
>Since we are doing this, the part of saving the current state is
>separated from irq disabling, and we basically do the following in
>local_interrupt_disable():
>
> local_irq_save(flags);
> if (counter++ == 0) {
> this_cpu(local_interrupt_disable_state) = flags;
> }
>
>Change _local_interrupt_disable() to _local_interrupt_save_state().
>
>Link: https://lore.kernel.org/lkml/87v78wezid.ffs@fw13/ [1]
>Reported-by: Thomas Gleixner <tglx@kernel.org>
>Fixes: e901c1510e24 ("irq,spin_lock: Add counted interrupt disabling/enabling")
LGTM, thanks
Reviewed-by: Bradley Morgan <brads@mainlining.org>
>Signed-off-by: Boqun Feng <boqun@kernel.org>
>---
> include/linux/interrupt_rc.h | 19 ++++++++-----------
> kernel/softirq.c | 17 ++++-------------
> 2 files changed, 12 insertions(+), 24 deletions(-)
>
>diff --git a/include/linux/interrupt_rc.h b/include/linux/interrupt_rc.h
>index b9a7f05ecf42..e68e1bedba66 100644
>--- a/include/linux/interrupt_rc.h
>+++ b/include/linux/interrupt_rc.h
>@@ -20,11 +20,8 @@
> /* Per-CPU interrupt disabling state for
> local_interrupt_{disable,enable}(). */
> DECLARE_PER_CPU(unsigned long, local_interrupt_disable_state);
>
>-static __always_inline void __local_interrupt_disable(void)
>+static __always_inline void __local_interrupt_save_state(unsigned long flags)
> {
>- unsigned long flags;
>-
>- local_irq_save(flags);
> raw_cpu_write(local_interrupt_disable_state, flags);
> }
>
>@@ -36,9 +33,9 @@ static __always_inline void __local_interrupt_enable(void)
> }
>
> #ifndef INSTANTIATE_EXPORTED_INTERRUPT_DISABLE
>-static __always_inline void _local_interrupt_disable(void)
>+static __always_inline void _local_interrupt_save_state(unsigned long flags)
> {
>- __local_interrupt_disable();
>+ __local_interrupt_save_state(flags);
> }
>
> static __always_inline void _local_interrupt_enable(void)
>@@ -46,27 +43,27 @@ static __always_inline void _local_interrupt_enable(void)
> __local_interrupt_enable();
> }
> #else
>-extern void _local_interrupt_disable(void);
>+extern void _local_interrupt_save_state(unsigned long flags);
> extern void _local_interrupt_enable(void);
> #endif
>
> #else /* !MODULE */
>-extern void _local_interrupt_disable(void);
>+extern void _local_interrupt_save_state(unsigned long flags);
> extern void _local_interrupt_enable(void);
> #endif /* !MODULE */
>
> static inline void local_interrupt_disable(void)
> {
> int new_count;
>+ unsigned long flags;
>
> WARN_ON_ONCE(in_nmi());
>
>+ local_irq_save(flags);
> new_count = hardirq_disable_enter();
>
>- /* Interrupts can happen here, but it's OK, see __irq_exit_rcu(). */
>-
> if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET)
>- _local_interrupt_disable();
>+ _local_interrupt_save_state(flags);
> }
>
> static inline void local_interrupt_enable(void)
>diff --git a/kernel/softirq.c b/kernel/softirq.c
>index 7980a4a232f9..5d02c36c40e3 100644
>--- a/kernel/softirq.c
>+++ b/kernel/softirq.c
>@@ -91,11 +91,11 @@ EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context);
>
> DEFINE_PER_CPU(unsigned long, local_interrupt_disable_state);
>
>-void _local_interrupt_disable(void)
>+void _local_interrupt_save_state(unsigned long flags)
> {
>- __local_interrupt_disable();
>+ __local_interrupt_save_state(flags);
> }
>-EXPORT_SYMBOL(_local_interrupt_disable);
>+EXPORT_SYMBOL(_local_interrupt_save_state);
>
> void _local_interrupt_enable(void)
> {
>@@ -749,16 +749,7 @@ static inline void __irq_exit_rcu(void)
> #endif
> account_hardirq_exit(current);
> preempt_count_sub(HARDIRQ_OFFSET);
>- /*
>- * Interrupts may happen between hardirq_disable_enter() and
>- * local_irq_save() in local_interrupt_disable(), if irq_exit() invokes
>- * softirq here, we may have a softirq handler calling
>- * local_interrupt_disable() but it won't disable the IRQ because
>- * hardirq disabling count is already 1, hence we need to prevent
>- * invoking softirq when a local_interrupt_disable() is ongoing.
>- */
>- if (!in_interrupt() && !hardirq_disable_count() &&
>- local_softirq_pending()) {
>+ if (!in_interrupt() && local_softirq_pending()) {
> /*
> * If we left hrtimers unarmed, make sure to arm them now,
> * before enabling interrupts to run softirq.
>
--- Thanks!
https://lore.kernel.org/all/EE579805-42F2-4C58-B752-F28779EEB717@grrlz.net/
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-27 20:07 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-27 18:10 [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter Boqun Feng
2026-08-27 19:48 ` [PATCH] preempt: Remove hardirq_disable_count() Boqun Feng
2026-08-27 20:07 ` [PATCH] interrupt: Disable interrupt before modifying hardirq_disable counter Bradley Morgan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®