preempt: Track NMI nesting to separate per-CPU counter

Move NMI nesting tracking from the preempt_count bits to a separate
per-CPU counter (nmi_nesting). This is to free up the NMI bits in the
preempt_count, allowing those bits to be repurposed for other uses.

Reduce NMI_BITS from 4 to 1, using it only to detect if we're in an NMI.
The per-CPU counter currently caps nesting at 15.

[boqun: Address Steven Rostedt's comment on the BUG_ON() condition]
[boqun: Use preempt_count_set() in __nmi_exit() to avoid underflow]

Suggested-by: Boqun Feng <boqun@kernel.org>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
Signed-off-by: Lyude Paul <lyude@redhat.com>
Signed-off-by: Boqun Feng <boqun@kernel.org>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://patch.msgid.link/20260121223933.1568682-3-lyude@redhat.com
Link: https://patch.msgid.link/20260804161447.84806-2-boqun@kernel.org
This commit is contained in:
Joel Fernandes
2026-08-04 09:14:23 -07:00
committed by Peter Zijlstra
parent fcb8ada128
commit b54aa0edf0
4 changed files with 23 additions and 7 deletions

View File

@@ -10,6 +10,8 @@
#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);
@@ -102,14 +104,17 @@ void irq_exit_rcu(void);
*/
/*
* nmi_enter() can nest up to 15 times; see NMI_BITS.
* nmi_enter() can nest - nesting is tracked in a per-CPU counter.
*/
#define __nmi_enter() \
do { \
lockdep_off(); \
arch_nmi_enter(); \
BUG_ON(in_nmi() == NMI_MASK); \
__preempt_count_add(NMI_OFFSET + HARDIRQ_OFFSET); \
/* 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); \
} while (0)
#define nmi_enter() \
@@ -124,8 +129,12 @@ void irq_exit_rcu(void);
#define __nmi_exit() \
do { \
unsigned int nesting; \
BUG_ON(!in_nmi()); \
__preempt_count_sub(NMI_OFFSET + HARDIRQ_OFFSET); \
__preempt_count_sub(HARDIRQ_OFFSET); \
nesting = __this_cpu_dec_return(nmi_nesting); \
if (!nesting) \
preempt_count_set(preempt_count() & ~NMI_MASK); \
arch_nmi_exit(); \
lockdep_on(); \
} while (0)

View File

@@ -17,6 +17,8 @@
*
* - bits 0-7 are the preemption count (max preemption depth: 256)
* - bits 8-15 are the softirq count (max # of softirqs: 256)
* - bits 16-19 are the hardirq count (max # of hardirqs: 16)
* - bit 20 is the NMI flag (no nesting count, tracked separately)
*
* The hardirq count could in theory be the same as the number of
* interrupts in the system, but we run all interrupt handlers with
@@ -24,16 +26,19 @@
* there are a few palaeontologic drivers which reenable interrupts in
* the handler, so we need more than one bit here.
*
* NMI nesting depth is tracked in a separate per-CPU variable
* (nmi_nesting) to save bits in preempt_count.
*
* PREEMPT_MASK: 0x000000ff
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_MASK: 0x000f0000
* NMI_MASK: 0x00f00000
* NMI_MASK: 0x00100000
* PREEMPT_NEED_RESCHED: 0x80000000
*/
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 4
#define NMI_BITS 1
#define PREEMPT_SHIFT 0
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)

View File

@@ -88,6 +88,8 @@ EXPORT_PER_CPU_SYMBOL_GPL(hardirqs_enabled);
EXPORT_PER_CPU_SYMBOL_GPL(hardirq_context);
#endif
DEFINE_PER_CPU(unsigned int, nmi_nesting);
/*
* SOFTIRQ_OFFSET usage:
*

View File

@@ -367,7 +367,7 @@ extern int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str,
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 4
#define NMI_BITS 1
#define PREEMPT_SHIFT 0
#define SOFTIRQ_SHIFT (PREEMPT_SHIFT + PREEMPT_BITS)