summaryrefslogtreecommitdiff
path: root/include/linux
diff options
context:
space:
mode:
authorBoqun Feng <boqun@kernel.org>2026-08-04 09:14:32 -0700
committerPeter Zijlstra <peterz@infradead.org>2026-08-10 10:50:19 +0200
commit3b0e2a22d4086ed7c1294584c4417f7d19f1ac67 (patch)
treee31c28486fac5900755c052db5ffd1fff5031906 /include/linux
parent560fcaa92ef983315023eb4ebfc1ebae1132fb2a (diff)
preempt: Introduce HAS_SEPARATE_PREEMPT_RESCHED_BITS
With the changes that enable preempt count to track IRQ disabling nesting, we don't have enough bits in 32-bit preempt count implementation, as a result we move NMI nesting bits out of the 32-bit preempt count. However on the architectures that can support 64-bit preempt count implementation, we can keep the NMI nesting bits in the 32-bit preempt count and avoid maintaining NMI nesting bits outside of the same cache line. Therefore HAS_SEPARATE_PREEMPT_RESCHED_BITS is introduced to allow architectures to select this. Note that under this Kconfig, preempt count is maintained in a 64-bit word however preempt_count() still remains as an int because all the effective bits still fit in (previously we mask out NEED_RESCHED bit in preempt_count()). This should make no functional changes for existing preempt_count() users. Enable this for x86_64 along with the introduction of the Kconfig. [boqun: Undo the __preempt_count_{add,sub}() optimization in 32-bit preempt count since it may introduce {over,under}flow] Originally-by: Peter Zijlstra <peterz@infradead.org> Signed-off-by: Boqun Feng <boqun@kernel.org> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://patch.msgid.link/20260804161447.84806-11-boqun@kernel.org
Diffstat (limited to 'include/linux')
-rw-r--r--include/linux/hardirq.h47
-rw-r--r--include/linux/preempt.h23
2 files changed, 54 insertions, 16 deletions
diff --git a/include/linux/hardirq.h b/include/linux/hardirq.h
index 8d4895531a45..73b48dd9f135 100644
--- a/include/linux/hardirq.h
+++ b/include/linux/hardirq.h
@@ -10,8 +10,6 @@
#include <linux/vtime.h>
#include <asm/hardirq.h>
-DECLARE_PER_CPU(unsigned int, nmi_nesting);
-
extern void synchronize_irq(unsigned int irq);
extern bool synchronize_hardirq(unsigned int irq);
@@ -94,6 +92,37 @@ void irq_exit_rcu(void);
#define arch_nmi_exit() do { } while (0)
#endif
+#ifdef CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS
+static __always_inline void __preempt_count_nmi_enter(void)
+{
+ __preempt_count_add(NMI_OFFSET + HARDIRQ_OFFSET);
+}
+
+static __always_inline void __preempt_count_nmi_exit(void)
+{
+ __preempt_count_sub(NMI_OFFSET + HARDIRQ_OFFSET);
+}
+#else
+DECLARE_PER_CPU(unsigned int, nmi_nesting);
+
+#define __preempt_count_nmi_enter() \
+ do { \
+ __preempt_count_add(HARDIRQ_OFFSET); \
+ /* Maximum NMI nesting is 15. */ \
+ BUG_ON(__this_cpu_read(nmi_nesting) >= 15); \
+ __this_cpu_inc(nmi_nesting); \
+ preempt_count_set(preempt_count() | NMI_MASK); \
+ } while (0)
+
+#define __preempt_count_nmi_exit() \
+ do { \
+ __preempt_count_sub(HARDIRQ_OFFSET); \
+ if (!__this_cpu_dec_return(nmi_nesting)) \
+ preempt_count_set(preempt_count() & ~NMI_MASK); \
+ } while (0)
+
+#endif
+
/*
* NMI vs Tracing
* --------------
@@ -110,18 +139,14 @@ void irq_exit_rcu(void);
do { \
lockdep_off(); \
arch_nmi_enter(); \
- /* Maximum NMI nesting is 15. */ \
- BUG_ON(__this_cpu_read(nmi_nesting) >= 15); \
- __this_cpu_inc(nmi_nesting); \
- __preempt_count_add(HARDIRQ_OFFSET); \
- preempt_count_set(preempt_count() | NMI_MASK); \
+ __preempt_count_nmi_enter(); \
} while (0)
#define nmi_enter() \
do { \
__nmi_enter(); \
lockdep_hardirq_enter(); \
- ct_nmi_enter(); \
+ ct_nmi_enter(); \
instrumentation_begin(); \
ftrace_nmi_enter(); \
instrumentation_end(); \
@@ -129,12 +154,8 @@ void irq_exit_rcu(void);
#define __nmi_exit() \
do { \
- unsigned int nesting; \
BUG_ON(!in_nmi()); \
- __preempt_count_sub(HARDIRQ_OFFSET); \
- nesting = __this_cpu_dec_return(nmi_nesting); \
- if (!nesting) \
- preempt_count_set(preempt_count() & ~NMI_MASK); \
+ __preempt_count_nmi_exit(); \
arch_nmi_exit(); \
lockdep_on(); \
} while (0)
diff --git a/include/linux/preempt.h b/include/linux/preempt.h
index 33fc4c814a9f..8299657f0f86 100644
--- a/include/linux/preempt.h
+++ b/include/linux/preempt.h
@@ -34,14 +34,31 @@
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_DISABLE_MASK: 0x00ff0000
* HARDIRQ_MASK: 0x0f000000
+ *
+ * When HAS_SEPARATE_PREEMPT_RESCHED_BITS=y, PREEMPT_NEED_RESCHED is put in a
+ * separate word and that allows 64bit load-store architectures to 'set'
+ * PREEMPT_NEED_RESCHED without messing up the otherwise symmetric
+ * modifications used on preempt_count and still load the whole thing
+ * (single-copy) atomically, without having to resort to full atomic
+ * operations.
+ *
+ * Because of the above, NMI_MASK bits are different depending on
+ * HAS_SEPARATE_PREEMPT_RESCHED_BITS:
+ *
+ * - HAS_SEPARATE_PREEMPT_RESCHED_BITS=n:
+ *
* NMI_MASK: 0x10000000
* PREEMPT_NEED_RESCHED: 0x80000000
+ *
+ * - HAS_SEPARATE_PREEMPT_RESCHED_BITS=y:
+ * NMI_MASK: 0xf0000000
+ * (PREEMPT_NEED_RESCHED is in a different word)
*/
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_DISABLE_BITS 8
#define HARDIRQ_BITS 4
-#define NMI_BITS 1
+#define NMI_BITS (1 + 3*IS_ENABLED(CONFIG_HAS_SEPARATE_PREEMPT_RESCHED_BITS))
#define PREEMPT_SHIFT 0
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)
@@ -116,8 +133,8 @@ static __always_inline unsigned char interrupt_context_level(void)
* preempt_count() is commonly implemented with READ_ONCE().
*/
-#define nmi_count() (preempt_count() & NMI_MASK)
-#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
+#define nmi_count() (preempt_count() & NMI_MASK)
+#define hardirq_count() (preempt_count() & HARDIRQ_MASK)
#ifdef CONFIG_PREEMPT_RT
# define softirq_count() (current->softirq_disable_cnt & SOFTIRQ_MASK)
# define irq_count() ((preempt_count() & (NMI_MASK | HARDIRQ_MASK)) | softirq_count())