Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2c0b0b5
KRN-1117: Add stackdepot trie storage
Jul 8, 2026
94f15d7
KRN-1117: Separate stackdepot put validation checks
Jul 8, 2026
799719b
KRN-1117: Tighten stackdepot fetch_into misuse handling
Jul 9, 2026
ddc92c8
KRN-1117: Simplify stackdepot trie state management
Jul 10, 2026
6fe0cb5
KRN-1117: Make stackdepot KUnit fixtures portable
Jul 10, 2026
6fbddb5
KRN-1117: Minimize the KMSAN fetch_into conversion
Jul 10, 2026
1bba11f
KRN-1117: Reject trie handles in the stackdepot GDB helper
Jul 10, 2026
4da813b
KRN-1117: Preserve hash capacity when trie init fails
Jul 13, 2026
0fcb660
KRN-1117: Surface invalid stackdepot fetch buffers
Jul 13, 2026
32db89c
KRN-1117: Preserve configured stackdepot pool capacity
Jul 14, 2026
0ffd850
KRN-1117: Inline the stackdepot leaf limit helper
Jul 14, 2026
61af459
KRN-1117: Separate stackdepot trie handle classification
Jul 15, 2026
e2eff77
KRN-1117: Simplify stackdepot trie preallocation
Jul 16, 2026
b0b581d
KRN-1117: Simplify stackdepot side-table page handling
Jul 16, 2026
c09b693
KRN-1117: Drop unrelated stackdepot kernel-doc change
Jul 16, 2026
81ad006
KRN-1117: Simplify stackdepot trie insertion allocation
Jul 20, 2026
8abd0ab
KRN-1117: Inline the stackdepot trie append allocation
Jul 20, 2026
79059e3
KRN-1117: Remove redundant stackdepot trie checks
Jul 20, 2026
48f7d5f
KRN-1117: Expand stackdepot trie KUnit coverage
Jul 20, 2026
63fa41f
KRN-1117: Simplify singleton stackdepot trie paths
Jul 20, 2026
7b14626
KRN-1117: Correct stackdepot post-put documentation
Jul 20, 2026
628dd4c
KRN-1117: Verify stackdepot trie KUnit backend
Jul 20, 2026
fa5c309
KRN-1117: Minimize the stackdepot DRM conversion
Jul 21, 2026
57d5cb4
KRN-1117: Avoid over-specifying stackdepot compression failure
Jul 21, 2026
ee43f24
KRN-1117: Simplify stackdepot trie reuse with bitmap slots
Jul 21, 2026
58bdc01
KRN-1117: Remove remaining stackdepot helper indirection
Jul 21, 2026
fbaf117
KRN-1117: Consolidate stackdepot KUnit coverage
Jul 21, 2026
aac13cd
KRN-1117: Simplify stackdepot trie helpers
Jul 22, 2026
5ad1215
KRN-1117: Cover remaining stackdepot trie topology paths
Jul 22, 2026
6075957
KRN-1117: Simplify stackdepot trie state ownership
Jul 23, 2026
d356157
KRN-1117: Verify constrained stackdepot trie misses
Jul 23, 2026
d41c11d
KRN-1117: Simplify stackdepot trie insertion
Jul 29, 2026
65e196c
KRN-1117: Skip x86 stackdepot codec test on UML
Jul 29, 2026
b0139f5
KRN-1117: Simplify stackdepot trie insertion allocation
Jul 30, 2026
f9d58e1
KRN-1117: Strengthen stackdepot trie topology coverage
Jul 30, 2026
7e20082
KRN-1117: Simplify stackdepot trie path construction
Jul 30, 2026
6f7a250
KRN-1117: Cover maximum stackdepot trie paths
Jul 30, 2026
8b068e3
KRN-1117: Tighten stackdepot pool and trie invariants
Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions arch/arm64/include/asm/stackdepot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_STACKDEPOT_H
#define __ASM_STACKDEPOT_H

#include <linux/types.h>
#include <asm/sections.h>

/*
* Modules are allocated inside a 2 GB relocation window containing the
* kernel image. Store a signed 32-bit offset from _text so compression is
* independent of 4 GB high-bit boundaries crossed by that window.
*/
static inline unsigned long arch_stack_depot_frame_from_payload(u32 payload)
{
long offset;

offset = (s32)payload;
if (offset < 0)
return (unsigned long)_text - (unsigned long)(-offset);
return (unsigned long)_text + (unsigned long)offset;
}

static inline bool
arch_stack_depot_frame_try_compress(unsigned long frame, u32 *payload)
{
u32 candidate;

candidate = (u32)(frame - (unsigned long)_text);
if (arch_stack_depot_frame_from_payload(candidate) != frame)
return false;

*payload = candidate;
return true;
}

static inline void
arch_stack_depot_frame_decompress(u32 payload, unsigned long *frame)
{
*frame = arch_stack_depot_frame_from_payload(payload);
}

#endif /* __ASM_STACKDEPOT_H */
1 change: 1 addition & 0 deletions arch/um/include/asm/Kbuild
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ generic-y += preempt.h
generic-y += ring_buffer.h
generic-y += runtime-const.h
generic-y += softirq_stack.h
generic-y += stackdepot.h
generic-y += switch_to.h
generic-y += topology.h
generic-y += trace_clock.h
Expand Down
37 changes: 37 additions & 0 deletions arch/x86/include/asm/stackdepot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_X86_STACKDEPOT_H
#define _ASM_X86_STACKDEPOT_H

#include <linux/types.h>

#ifdef CONFIG_X86_64
/*
* Compress canonical kernel text/module addresses whose upper 32 bits are all
* ones. Other kernel virtual addresses stay raw, so decompression reconstructs
* the original frame by restoring this prefix.
*/
#define STACK_DEPOT_X86_64_FRAME_PREFIX 0xffffffff00000000UL
#define STACK_DEPOT_X86_64_FRAME_LOW_MASK 0x00000000ffffffffUL

static inline bool
arch_stack_depot_frame_try_compress(unsigned long frame, u32 *low)
{
if ((frame & ~STACK_DEPOT_X86_64_FRAME_LOW_MASK) !=
STACK_DEPOT_X86_64_FRAME_PREFIX)
return false;

*low = (u32)frame;
return true;
}

static inline void
arch_stack_depot_frame_decompress(u32 low, unsigned long *frame)
{
*frame = STACK_DEPOT_X86_64_FRAME_PREFIX | low;
}

#else
#include <asm-generic/stackdepot.h>
#endif /* CONFIG_X86_64 */

#endif /* _ASM_X86_STACKDEPOT_H */
5 changes: 1 addition & 4 deletions drivers/gpu/drm/drm_modeset_lock.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,13 @@ static noinline depot_stack_handle_t __drm_stack_depot_save(void)
static void __drm_stack_depot_print(depot_stack_handle_t stack_depot)
{
struct drm_printer p = drm_dbg_printer(NULL, DRM_UT_KMS, "drm_modeset_lock");
unsigned long *entries;
unsigned int nr_entries;
char *buf;

buf = kmalloc(PAGE_SIZE, GFP_NOWAIT | __GFP_NOWARN);
if (!buf)
return;

nr_entries = stack_depot_fetch(stack_depot, &entries);
stack_trace_snprint(buf, PAGE_SIZE, entries, nr_entries, 2);
stack_depot_snprint(stack_depot, buf, PAGE_SIZE, 2);

drm_printf(&p, "attempting to lock a contended lock without backoff:\n%s", buf);

Expand Down
1 change: 1 addition & 0 deletions include/asm-generic/Kbuild
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ mandatory-y += serial.h
mandatory-y += shmparam.h
mandatory-y += simd.h
mandatory-y += softirq_stack.h
mandatory-y += stackdepot.h
mandatory-y += switch_to.h
mandatory-y += timex.h
mandatory-y += tlbflush.h
Expand Down
19 changes: 19 additions & 0 deletions include/asm-generic/stackdepot.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ASM_GENERIC_STACKDEPOT_H
#define __ASM_GENERIC_STACKDEPOT_H

#include <linux/types.h>

static inline bool
arch_stack_depot_frame_try_compress(unsigned long frame, u32 *low)
{
return false;
}

static inline void
arch_stack_depot_frame_decompress(u32 low, unsigned long *frame)
{
/* Generic code never compresses frames, so this hook is unreachable. */
}
Comment thread
caleb-kan marked this conversation as resolved.

#endif /* __ASM_GENERIC_STACKDEPOT_H */
81 changes: 71 additions & 10 deletions include/linux/stackdepot.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ union handle_parts {
struct stack_record {
struct list_head hash_list; /* Links in the hash table */
u32 hash; /* Hash in hash table */
u32 size; /* Number of stored frames */
u16 size; /* Number of stored frames */
u16 flags;
union handle_parts handle; /* Constant after initialization */
refcount_t count;
union {
Expand Down Expand Up @@ -84,8 +85,9 @@ typedef u32 depot_flags_t;
*/
#define STACK_DEPOT_FLAG_CAN_ALLOC ((depot_flags_t)0x0001)
#define STACK_DEPOT_FLAG_GET ((depot_flags_t)0x0002)
#define STACK_DEPOT_FLAG_COUNTABLE ((depot_flags_t)0x0004)

#define STACK_DEPOT_FLAGS_NUM 2
#define STACK_DEPOT_FLAGS_NUM 3
#define STACK_DEPOT_FLAGS_MASK ((depot_flags_t)((1 << STACK_DEPOT_FLAGS_NUM) - 1))

/*
Expand Down Expand Up @@ -144,6 +146,15 @@ static inline int stack_depot_early_init(void) { return 0; }
* Users of this flag must also call stack_depot_put() when keeping the stack
* trace is no longer required to avoid overflowing the refcount.
*
* If STACK_DEPOT_FLAG_COUNTABLE is set in @depot_flags, stack depot stores the
* stack in hash-backed storage for callers that need direct stack_record count
* access. This flag does not imply %STACK_DEPOT_FLAG_CAN_ALLOC and is mutually
* exclusive with %STACK_DEPOT_FLAG_GET.
*
* When trie storage is enabled, persistent non-refcounted saves use trie
* storage. Constrained callers only look up existing stacks; they do not insert
* a missing stack. Trie failures do not fall back to hash storage.
*
* If the provided stack trace comes from the interrupt context, only the part
* up to the interrupt entry is saved.
*
Expand All @@ -152,7 +163,7 @@ static inline int stack_depot_early_init(void) { return 0; }
* this is the case for contexts where neither %GFP_ATOMIC nor
* %GFP_NOWAIT can be used (NMI, raw_spin_lock).
*
* Return: Handle of the stack struct stored in depot, 0 on failure
* Return: Handle of the stack trace stored in depot, 0 on failure
*/
depot_stack_handle_t stack_depot_save_flags(unsigned long *entries,
unsigned int nr_entries,
Expand All @@ -169,6 +180,10 @@ depot_stack_handle_t stack_depot_save_flags(unsigned long *entries,
* Does not increment the refcount on the saved stack trace; see
* stack_depot_save_flags() for more details.
*
* When trie storage is enabled, this can return trie-backed handles. Use
* stack_depot_fetch_into(), stack_depot_print(), or stack_depot_snprint() for
* backend-independent access to the stack contents.
*
* Context: Contexts where allocations via alloc_pages() are allowed;
* see stack_depot_save_flags() for more details.
*
Expand All @@ -178,11 +193,12 @@ depot_stack_handle_t stack_depot_save(unsigned long *entries,
unsigned int nr_entries, gfp_t alloc_flags);

/**
* __stack_depot_get_stack_record - Get a pointer to a stack_record struct
* __stack_depot_get_stack_record - Get a hash-backed stack record
*
* @handle: Stack depot handle
*
* This function is only for internal purposes.
* This function is only for internal purposes. @handle must have been saved
* with %STACK_DEPOT_FLAG_COUNTABLE.
*
* Return: Returns a pointer to a stack_record struct
*/
Expand All @@ -191,14 +207,55 @@ struct stack_record *__stack_depot_get_stack_record(depot_stack_handle_t handle)
/**
* stack_depot_fetch - Fetch a stack trace from stack depot
*
* @handle: Stack depot handle returned from stack_depot_save()
* @handle: Hash-backed stack depot handle
* @entries: Pointer to store the address of the stack trace
*
* This helper returns a pointer to stackdepot-owned contiguous storage for
* legacy hash-backed handles. Callers that need backend-independent access to
* stack contents should use stack_depot_fetch_into(), stack_depot_print(), or
* stack_depot_snprint(). Passing a trie-backed handle is invalid and may WARN.
*
* Return: Number of frames for the fetched stack
*/
unsigned int stack_depot_fetch(depot_stack_handle_t handle,
unsigned long **entries);

/**
* stack_depot_fetch_into - Fetch a stack trace into caller-owned storage
*
* @handle: Stack depot handle
* @entries: Caller-owned buffer to copy the stack trace into
* @max_entries: Number of frames that fit in @entries
*
* Copies the stored frames into caller-owned @entries. If fewer frames are
* stored than @max_entries, only the stored frames are written and their count
* is returned. If more frames are stored than @max_entries, the copy is skipped
* entirely and 0 is returned.
*
* Passing a NULL @entries buffer or zero @max_entries for a valid @handle is
* invalid. Callers must provide storage for @max_entries frames.
*
* Callers should size @entries to match the save-side stack depth cap (for
* example, %CONFIG_STACKDEPOT_MAX_FRAMES or the local stack_trace_save() limit)
* when losing diagnostics on an undersized buffer would be surprising.
*
* A non-zero invalid @handle, including a post-put handle, may WARN. Its return
* value and copied contents are undefined because the record may have been
* reused for another stack.
*
* Callers must ensure @handle remains valid for the duration of this call.
* Persistent handles saved without %STACK_DEPOT_FLAG_GET require no extra
* reference; handles saved with %STACK_DEPOT_FLAG_GET require a held reference.
* Callers must not call stack_depot_put() on persistent handles.
* Racing this helper with stack_depot_put() on the same handle is invalid.
*
* Return: Number of frames copied, 0 if @handle is 0, stack depot is disabled,
* or @max_entries is less than the number of stored frames.
*/
unsigned int stack_depot_fetch_into(depot_stack_handle_t handle,
unsigned long *entries,
unsigned int max_entries);

/**
* stack_depot_print - Print a stack trace from stack depot
*
Expand All @@ -224,10 +281,14 @@ int stack_depot_snprint(depot_stack_handle_t handle, char *buf, size_t size,
*
* @handle: Stack depot handle returned from stack_depot_save()
*
* The stack trace is evicted from stack depot once all references to it have
* been dropped (once the number of stack_depot_evict() calls matches the
* number of stack_depot_save_flags() calls with STACK_DEPOT_FLAG_GET set for
* this stack trace).
* Drop a reference acquired by stack_depot_save_flags() with
* %STACK_DEPOT_FLAG_GET. Calling this for a handle saved without
* %STACK_DEPOT_FLAG_GET is invalid; persistent handles, including trie-backed
* handles, are owned by stack depot for the lifetime of the system.
*
* The stack trace is evicted once the number of stack_depot_put() calls matches
* the number of successful stack_depot_save_flags() calls with
* %STACK_DEPOT_FLAG_GET for this stack trace.
*/
void stack_depot_put(depot_stack_handle_t handle);

Expand Down
17 changes: 17 additions & 0 deletions lib/Kconfig.debug
Original file line number Diff line number Diff line change
Expand Up @@ -2706,6 +2706,23 @@ config RESOURCE_KUNIT_TEST

If unsure, say N.

config STACKDEPOT_KUNIT_TEST
bool "KUnit test for stack depot" if !KUNIT_ALL_TESTS
depends on KUNIT=y && STACKDEPOT
depends on STACKDEPOT_MAX_FRAMES >= 3
default KUNIT_ALL_TESTS
help
Enable this option to test stack depot API behavior at boot.
This test is built in because it exercises internal, non-exported
stack depot helpers, so KUNIT must also be built in.

KUnit tests run during boot and output the results to the debug log
in TAP format (https://testanything.org/). Only useful for kernel
developers running the KUnit test harness, and not intended for
inclusion into a production build.

If unsure, say N.

config SYSCTL_KUNIT_TEST
tristate "KUnit test for sysctl" if !KUNIT_ALL_TESTS
depends on KUNIT
Expand Down
Loading