From 84c304a534b844703d3437811e8f072166e3f116 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Wed, 8 Apr 2026 10:49:19 -0400 Subject: [PATCH 001/167] objtool/klp: Fix is_uncorrelated_static_local() for Clang For naming function-local static locals, GCC uses ., e.g. __already_done.15, while Clang uses . with optional ., e.g. create_worker.__already_done.111 The existing is_uncorrelated_static_local() check only matches the GCC convention where the variable name is a prefix. Handle both cases by checking for a prefix match (GCC) and by checking after the first dot separator (Clang). Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Signed-off-by: Joe Lawrence Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 0b0d1503851f..b1b068e9b4c7 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -242,16 +242,17 @@ static struct symbol *next_file_symbol(struct elf *elf, struct symbol *sym) static bool is_uncorrelated_static_local(struct symbol *sym) { static const char * const vars[] = { - "__already_done.", - "__func__.", - "__key.", - "__warned.", - "_entry.", - "_entry_ptr.", - "_rs.", - "descriptor.", - "CSWTCH.", + "__already_done", + "__func__", + "__key", + "__warned", + "_entry", + "_entry_ptr", + "_rs", + "descriptor", + "CSWTCH", }; + const char *dot; if (!is_object_sym(sym) || !is_local_sym(sym)) return false; @@ -259,8 +260,20 @@ static bool is_uncorrelated_static_local(struct symbol *sym) if (!strcmp(sym->sec->name, ".data.once")) return true; + dot = strchr(sym->name, '.'); + if (!dot) + return false; + for (int i = 0; i < ARRAY_SIZE(vars); i++) { - if (strstarts(sym->name, vars[i])) + size_t len = strlen(vars[i]); + + /* GCC: . */ + if (strstarts(sym->name, vars[i]) && (sym->name[len] == '.')) + return true; + + /* Clang: .[.] */ + if (strstarts(dot + 1, vars[i]) && + (dot[1 + len] == '.' || dot[1 + len] == '\0')) return true; } From ff529864e738f447ff4c019956319930fc274a23 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sat, 11 Apr 2026 21:11:24 -0700 Subject: [PATCH 002/167] objtool/klp: Fix .data..once static local non-correlation While there was once a section named .data.once, it has since been renamed to .data..once with commit dbefa1f31a91 ("Rename .data.once to .data..once to fix resetting WARN*_ONCE"). Fix it. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index b1b068e9b4c7..cb26c1c92a74 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -257,7 +257,8 @@ static bool is_uncorrelated_static_local(struct symbol *sym) if (!is_object_sym(sym) || !is_local_sym(sym)) return false; - if (!strcmp(sym->sec->name, ".data.once")) + /* WARN_ONCE, etc */ + if (!strcmp(sym->sec->name, ".data..once")) return true; dot = strchr(sym->name, '.'); From 8edec016255dcc74146b0486639f756f0ce75bf5 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sat, 11 Apr 2026 12:23:07 -0700 Subject: [PATCH 003/167] objtool/klp: Don't correlate __ADDRESSABLE() symbols Symbols created by __ADDRESSABLE() are only used to convince the toolchain not to optimize out the referenced symbol. Reviewed-by: Miroslav Benes Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index cb26c1c92a74..36753eeba58c 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -352,6 +352,15 @@ static bool is_special_section_aux(struct section *sec) return false; } +/* + * Symbols created by ___ADDRESSABLE() are only used to convince the toolchain + * not to optimize out the referenced symbol. + */ +static bool is_addressable_sym(struct symbol *sym) +{ + return !strcmp(sym->sec->name, ".discard.addressable"); +} + /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. @@ -365,6 +374,7 @@ static bool dont_correlate(struct symbol *sym) is_uncorrelated_static_local(sym) || is_clang_tmp_label(sym) || is_string_sec(sym->sec) || + is_addressable_sym(sym) || is_special_section(sym->sec) || is_special_section_aux(sym->sec) || strstarts(sym->name, "__initcall__"); From 710c4c254688c37478fb72a0e870afad1f54715f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 12 Apr 2026 19:25:19 -0700 Subject: [PATCH 004/167] objtool/klp: Don't correlate absolute symbols Some arch/x86/crypto/*.S files define local .set/.equ constants that get duplicated in vmlinux.o. This causes klp-diff to fail with "Multiple correlation candidates" errors since it can't uniquely match these between orig and patched builds. Skip ABS symbols in dont_correlate(). They're purely compile-time assembly constants that are never referenced by relocations, so they don't need correlation. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 36753eeba58c..27ebe1b1f463 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -361,6 +361,15 @@ static bool is_addressable_sym(struct symbol *sym) return !strcmp(sym->sec->name, ".discard.addressable"); } +/* + * ABS symbols are typically assembly .set/.equ constants which are never + * referenced by relocations. (Exclude FILE symbols which are also SHN_ABS.) + */ +static bool is_abs_sym(struct symbol *sym) +{ + return sym->sym.st_shndx == SHN_ABS && !is_file_sym(sym); +} + /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. @@ -370,6 +379,7 @@ static bool dont_correlate(struct symbol *sym) return is_file_sym(sym) || is_null_sym(sym) || is_sec_sym(sym) || + is_abs_sym(sym) || is_prefix_func(sym) || is_uncorrelated_static_local(sym) || is_clang_tmp_label(sym) || From 0a7823d1d70dd17fbf0e9771d9afd5067a69ded0 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 20 Apr 2026 19:20:42 -0700 Subject: [PATCH 005/167] objtool/klp: Don't correlate __initstub__ symbols With LTO, the initcall infrastructure generates __initstub__kmod_* wrapper functions in .init.text. These are the LTO equivalent of __initcall__kmod_* data pointers, which are already excluded from correlation. These are __init functions whose memory is freed after boot, so there's no reason to include or reference them in a livepatch module. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 27ebe1b1f463..4f668117c45e 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -370,6 +370,12 @@ static bool is_abs_sym(struct symbol *sym) return sym->sym.st_shndx == SHN_ABS && !is_file_sym(sym); } +static bool is_initcall_sym(struct symbol *sym) +{ + return strstarts(sym->name, "__initcall__") || + strstarts(sym->name, "__initstub__"); +} + /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. @@ -384,10 +390,10 @@ static bool dont_correlate(struct symbol *sym) is_uncorrelated_static_local(sym) || is_clang_tmp_label(sym) || is_string_sec(sym->sec) || + is_initcall_sym(sym) || is_addressable_sym(sym) || is_special_section(sym->sec) || - is_special_section_aux(sym->sec) || - strstarts(sym->name, "__initcall__"); + is_special_section_aux(sym->sec); } struct process_demangled_name_data { From 76eb0f8639fbc6bcc0e7c1ca649c456bfebc4d5c Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 13 Apr 2026 01:59:31 -0700 Subject: [PATCH 006/167] objtool/klp: Don't report uncorrelated functions as new Clang LTO uses __UNIQUE_ID() to generate some uniquely named wrapper functions, like initstubs. If they're uncorrelated, prevent them from being reported as new functions and included unnecessarily. Note that dont_correlate() already includes prefix functions, so prefix functions are still being ignored here. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 4f668117c45e..ccb16a45107e 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -802,7 +802,7 @@ static int mark_changed_functions(struct elfs *e) /* Find changed functions */ for_each_sym(e->orig, sym_orig) { - if (!is_func_sym(sym_orig) || is_prefix_func(sym_orig)) + if (!is_func_sym(sym_orig) || dont_correlate(sym_orig)) continue; patched_sym = sym_orig->twin; @@ -818,7 +818,7 @@ static int mark_changed_functions(struct elfs *e) /* Find added functions and print them */ for_each_sym(e->patched, patched_sym) { - if (!is_func_sym(patched_sym) || is_prefix_func(patched_sym)) + if (!is_func_sym(patched_sym) || dont_correlate(patched_sym)) continue; if (!patched_sym->twin) { From e872b3f13922ecd746f907e20261dfc11c9a9f28 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 29 Apr 2026 16:02:38 -0700 Subject: [PATCH 007/167] objtool/klp: Improve local label check Clang emits various .L-prefixed local symbols beyond .Ltmp*, such as .L__const.* for local constant data. These are assembler-local labels not present in kallsyms, so they can never be resolved at module load time. Broaden the check from .Ltmp* to all .L* symbols so they get cloned into the patch module instead. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index ccb16a45107e..c5d4c9ed8580 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -282,14 +282,14 @@ static bool is_uncorrelated_static_local(struct symbol *sym) } /* - * Clang emits several useless .Ltmp_* code labels. + * .L symbols are assembler-local labels not present in kallsyms. They must + * never become KLP relocations; instead their data is cloned into the patch + * module. This covers .Ltmp* (Clang temp labels), .L__const.* (Clang local + * constants), and any other assembler-local pattern. */ -static bool is_clang_tmp_label(struct symbol *sym) +static bool is_local_label(struct symbol *sym) { - return is_notype_sym(sym) && - is_text_sec(sym->sec) && - strstarts(sym->name, ".Ltmp") && - isdigit(sym->name[5]); + return strstarts(sym->name, ".L"); } static bool is_special_section(struct section *sec) @@ -388,7 +388,7 @@ static bool dont_correlate(struct symbol *sym) is_abs_sym(sym) || is_prefix_func(sym) || is_uncorrelated_static_local(sym) || - is_clang_tmp_label(sym) || + is_local_label(sym) || is_string_sec(sym->sec) || is_initcall_sym(sym) || is_addressable_sym(sym) || From 3de711fba73ad93b8b3fbe09cf681cefed5d573d Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Tue, 24 Mar 2026 17:34:16 -0400 Subject: [PATCH 008/167] objtool/klp: Fix create_fake_symbols() skipping entsize-based sections create_fake_symbols() has two phases: creating symbols from ANNOTATE_DATA_SPECIAL entries, and a fallback that uses sh_entsize for special sections like .static_call_sites. When .discard.annotate_data is absent, the function returns early, skipping the entsize fallback and silently allowing unsupported module-local static call keys through. Fix it by jumping to the entsize phase instead of returning early. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Assisted-by: Claude:claude-4-opus Signed-off-by: Joe Lawrence Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index c5d4c9ed8580..0653bf6a33bd 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1374,7 +1374,7 @@ static int create_fake_symbols(struct elf *elf) sec = find_section_by_name(elf, ".discard.annotate_data"); if (!sec || !sec->rsec) - return 0; + goto entsize; for_each_reloc(sec->rsec, reloc) { unsigned long offset, size; @@ -1406,7 +1406,7 @@ static int create_fake_symbols(struct elf *elf) /* * 2) Make symbols for sh_entsize, and simple arrays of pointers: */ - +entsize: for_each_sec(elf, sec) { unsigned int entry_size; unsigned long offset; From 0333b7399587ee0aaa863ed0d13a00a6c7c64068 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 12 Apr 2026 22:41:23 -0700 Subject: [PATCH 009/167] objtool: Replace iterator callback with for_each_sym_by_mangled_name() Convert the callback-based iterate_sym_by_demangled_name() with a new for_each_sym_by_demangled_name() macro. This eliminates the callback struct/function and makes the code more compact and readable. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/elf.c | 68 ++++++++--------------------- tools/objtool/include/objtool/elf.h | 32 ++++++++++++-- tools/objtool/klp-diff.c | 42 ++++++------------ 3 files changed, 60 insertions(+), 82 deletions(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index f3df2bde119f..dc39132f71c1 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -27,27 +27,16 @@ static ssize_t demangled_name_len(const char *name); -static inline u32 str_hash(const char *str) -{ - return jhash(str, strlen(str), 0); -} - -static inline u32 str_hash_demangled(const char *str) +u32 str_hash_demangled(const char *str) { return jhash(str, demangled_name_len(str), 0); } -#define __elf_table(name) (elf->name##_hash) -#define __elf_bits(name) (elf->name##_bits) - -#define __elf_table_entry(name, key) \ - __elf_table(name)[hash_min(key, __elf_bits(name))] - #define elf_hash_add(name, node, key) \ ({ \ struct elf_hash_node *__node = node; \ - __node->next = __elf_table_entry(name, key); \ - __elf_table_entry(name, key) = __node; \ + __node->next = __elf_table_entry(elf, name, key); \ + __elf_table_entry(elf, name, key) = __node; \ }) static inline void __elf_hash_del(struct elf_hash_node *node, @@ -69,30 +58,20 @@ static inline void __elf_hash_del(struct elf_hash_node *node, } #define elf_hash_del(name, node, key) \ - __elf_hash_del(node, &__elf_table_entry(name, key)) - -#define elf_list_entry(ptr, type, member) \ -({ \ - typeof(ptr) __ptr = (ptr); \ - __ptr ? container_of(__ptr, type, member) : NULL; \ -}) - -#define elf_hash_for_each_possible(name, obj, member, key) \ - for (obj = elf_list_entry(__elf_table_entry(name, key), typeof(*obj), member); \ - obj; \ - obj = elf_list_entry(obj->member.next, typeof(*(obj)), member)) + __elf_hash_del(node, &__elf_table_entry(elf, name, key)) #define elf_alloc_hash(name, size) \ ({ \ - __elf_bits(name) = max(10, ilog2(size)); \ - __elf_table(name) = mmap(NULL, sizeof(struct elf_hash_node *) << __elf_bits(name), \ + __elf_bits(elf, name) = max(10, ilog2(size)); \ + __elf_table(elf, name) = mmap(NULL, \ + sizeof(struct elf_hash_node *) << __elf_bits(elf, name), \ PROT_READ|PROT_WRITE, \ MAP_PRIVATE|MAP_ANON, -1, 0); \ - if (__elf_table(name) == (void *)-1L) { \ + if (__elf_table(elf, name) == (void *)-1L) { \ ERROR_GLIBC("mmap fail " #name); \ - __elf_table(name) = NULL; \ + __elf_table(elf, name) = NULL; \ } \ - __elf_table(name); \ + __elf_table(elf, name); \ }) static inline unsigned long __sym_start(struct symbol *s) @@ -141,7 +120,7 @@ struct section *find_section_by_name(const struct elf *elf, const char *name) { struct section *sec; - elf_hash_for_each_possible(section_name, sec, name_hash, str_hash(name)) { + elf_hash_for_each_possible(elf, section_name, sec, name_hash, str_hash(name)) { if (!strcmp(sec->name, name)) return sec; } @@ -154,7 +133,7 @@ static struct section *find_section_by_index(struct elf *elf, { struct section *sec; - elf_hash_for_each_possible(section, sec, hash, idx) { + elf_hash_for_each_possible(elf, section, sec, hash, idx) { if (sec->idx == idx) return sec; } @@ -166,7 +145,7 @@ static struct symbol *find_symbol_by_index(struct elf *elf, unsigned int idx) { struct symbol *sym; - elf_hash_for_each_possible(symbol, sym, hash, idx) { + elf_hash_for_each_possible(elf, symbol, sym, hash, idx) { if (sym->idx == idx) return sym; } @@ -285,7 +264,7 @@ struct symbol *find_symbol_by_name(const struct elf *elf, const char *name) { struct symbol *sym; - elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(name)) { + elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash(name)) { if (!strcmp(sym->name, name)) return sym; } @@ -300,7 +279,7 @@ static struct symbol *find_local_symbol_by_file_and_name(const struct elf *elf, { struct symbol *sym; - elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash_demangled(name)) { + elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash_demangled(name)) { if (sym->bind == STB_LOCAL && sym->file == file && !strcmp(sym->name, name)) { return sym; @@ -314,7 +293,7 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam { struct symbol *sym; - elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash_demangled(name)) { + elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, str_hash_demangled(name)) { if (!strcmp(sym->name, name) && !is_local_sym(sym)) return sym; } @@ -322,19 +301,6 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam return NULL; } -void iterate_global_symbol_by_demangled_name(const struct elf *elf, - const char *demangled_name, - void (*process)(struct symbol *sym, void *data), - void *data) -{ - struct symbol *sym; - - elf_hash_for_each_possible(symbol_name, sym, name_hash, str_hash(demangled_name)) { - if (!strcmp(sym->demangled_name, demangled_name) && !is_local_sym(sym)) - process(sym, data); - } -} - struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, unsigned long offset, unsigned int len) { @@ -347,7 +313,7 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se return NULL; for_offset_range(o, offset, offset + len) { - elf_hash_for_each_possible(reloc, reloc, hash, + elf_hash_for_each_possible(elf, reloc, reloc, hash, sec_offset_hash(rsec, o)) { if (reloc->sec != rsec) continue; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 25573e5af76e..b142984eb9b5 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -21,6 +21,13 @@ #define SEC_NAME_LEN 1024 #define SYM_NAME_LEN 512 +static inline u32 str_hash(const char *str) +{ + return jhash(str, strlen(str), 0); +} + +u32 str_hash_demangled(const char *str); + #define bswap_if_needed(elf, val) __bswap_if_needed(&elf->ehdr, val) #ifdef LIBELF_USE_DEPRECATED @@ -130,6 +137,23 @@ struct elf { struct symbol *symbol_data; }; +#define __elf_table(elf, name) ((elf)->name##_hash) +#define __elf_bits(elf, name) ((elf)->name##_bits) + +#define __elf_table_entry(elf, name, key) \ + __elf_table(elf, name)[hash_min(key, __elf_bits(elf, name))] + +#define elf_list_entry(ptr, type, member) \ +({ \ + typeof(ptr) __ptr = (ptr); \ + __ptr ? container_of(__ptr, type, member) : NULL; \ +}) + +#define elf_hash_for_each_possible(elf, name, obj, member, key) \ + for (obj = elf_list_entry(__elf_table_entry(elf, name, key), typeof(*obj), member); \ + obj; \ + obj = elf_list_entry(obj->member.next, typeof(*(obj)), member)) + struct elf *elf_open_read(const char *name, int flags); struct elf *elf_create_file(GElf_Ehdr *ehdr, const char *name); @@ -186,9 +210,6 @@ struct symbol *find_func_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_name(const struct elf *elf, const char *name); struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name); -void iterate_global_symbol_by_demangled_name(const struct elf *elf, const char *demangled_name, - void (*process)(struct symbol *sym, void *data), - void *data); struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset); int find_symbol_hole_containing(const struct section *sec, unsigned long offset); struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); @@ -468,6 +489,11 @@ static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next) #define for_each_sym_continue(elf, sym) \ list_for_each_entry_continue(sym, &elf->symbols, global_list) +#define for_each_sym_by_demangled_name(elf, name, sym) \ + elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \ + str_hash(name)) \ + if (strcmp(sym->demangled_name, name)) {} else + #define rsec_next_reloc(rsec, reloc) \ reloc_idx(reloc) < sec_num_entries(rsec) - 1 ? reloc + 1 : NULL diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 0653bf6a33bd..30ce234e01a1 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -46,11 +46,6 @@ static const struct option klp_diff_options[] = { static DEFINE_HASHTABLE(exports, 15); -static inline u32 str_hash(const char *str) -{ - return jhash(str, strlen(str), 0); -} - static char *escape_str(const char *orig) { size_t len = 0; @@ -396,22 +391,6 @@ static bool dont_correlate(struct symbol *sym) is_special_section_aux(sym->sec); } -struct process_demangled_name_data { - struct symbol *ret; - int count; -}; - -static void process_demangled_name(struct symbol *sym, void *d) -{ - struct process_demangled_name_data *data = d; - - if (sym->twin) - return; - - data->count++; - data->ret = sym; -} - /* * When there is no full name match, try match demangled_name. This would * match original foo.llvm.123 to patched foo.llvm.456. @@ -423,16 +402,23 @@ static void process_demangled_name(struct symbol *sym, void *d) static int find_global_symbol_by_demangled_name(struct elf *elf, struct symbol *sym, struct symbol **out_sym) { - struct process_demangled_name_data data = {}; + struct symbol *sym2, *result = NULL; + int count = 0; - iterate_global_symbol_by_demangled_name(elf, sym->demangled_name, - process_demangled_name, - &data); - if (data.count > 1) { - ERROR("Multiple (%d) correlation candidates for %s", data.count, sym->name); + for_each_sym_by_demangled_name(elf, sym->demangled_name, sym2) { + if (is_local_sym(sym2) || sym2->twin) + continue; + + count++; + result = sym2; + } + + if (count > 1) { + ERROR("Multiple (%d) correlation candidates for %s", count, sym->name); return -1; } - *out_sym = data.ret; + + *out_sym = result; return 0; } From def5b60dcd2256efab0e66f598419b17c425b8f5 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 30 Mar 2026 21:31:47 -0700 Subject: [PATCH 010/167] objtool/klp: Fix --debug-checksum for duplicate symbol names find_symbol_by_name() only returns the first match, so --debug-checksum= silently ignores any subsequent duplicately named functions after the first. Fix that, along with a new for_each_sym_by_name() helper. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 15 ++++++++++----- tools/objtool/include/objtool/elf.h | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 9b11cf3193b9..e3604b1201f9 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -3677,18 +3677,23 @@ static int checksum_debug_init(struct objtool_file *file) s = dup; while (*s) { - struct symbol *func; + bool found = false; + struct symbol *sym; char *comma; comma = strchr(s, ','); if (comma) *comma = '\0'; - func = find_symbol_by_name(file->elf, s); - if (!func || !is_func_sym(func)) + for_each_sym_by_name(file->elf, s, sym) { + if (!is_func_sym(sym)) + continue; + sym->debug_checksum = 1; + found = true; + } + + if (!found) WARN("--debug-checksum: can't find '%s'", s); - else - func->debug_checksum = 1; if (!comma) break; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index b142984eb9b5..00b04029023e 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -489,6 +489,11 @@ static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next) #define for_each_sym_continue(elf, sym) \ list_for_each_entry_continue(sym, &elf->symbols, global_list) +#define for_each_sym_by_name(elf, _name, sym) \ + elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \ + str_hash_demangled(_name)) \ + if (strcmp(sym->name, _name)) {} else + #define for_each_sym_by_demangled_name(elf, name, sym) \ elf_hash_for_each_possible(elf, symbol_name, sym, name_hash, \ str_hash(name)) \ From c4c02d4450b5b7e2fbde578252f71a5697180112 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 9 Apr 2026 18:34:50 -0700 Subject: [PATCH 011/167] objtool/klp: Fix handling of zero-length .altinstr_replacement sections When a section is empty (e.g. only zero-length alternative replacements), there are no symbols to convert a section symbol reference to. Skip the reloc instead of erroring out. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 30ce234e01a1..a226e99948b3 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1005,6 +1005,13 @@ static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc) /* No dedicated section; find the symbol manually */ sym = find_symbol_containing(sec, arch_adjusted_addend(reloc)); if (!sym) { + /* + * This is presumably an .altinstr_replacement section which is + * empty due to it only having zero-length replacement(s). + */ + if (!sec_size(sec)) + return 1; + /* * This can happen for special section references to weak code * whose symbol has been stripped by the linker. @@ -1265,6 +1272,7 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym) for_each_reloc(patched_rsec, patched_reloc) { unsigned long offset; + int ret; if (reloc_offset(patched_reloc) < start || reloc_offset(patched_reloc) >= end) @@ -1278,12 +1286,15 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym) !strcmp(patched_reloc->sym->sec->name, ".altinstr_aux")) continue; - if (convert_reloc_sym(e->patched, patched_reloc)) { + ret = convert_reloc_sym(e->patched, patched_reloc); + if (ret < 0) { ERROR_FUNC(patched_rsec->base, reloc_offset(patched_reloc), "failed to convert reloc sym '%s' to its proper format", patched_reloc->sym->name); return -1; } + if (ret > 0) + continue; offset = out_sym->offset + (reloc_offset(patched_reloc) - patched_sym->offset); From 98377f3ba7c02d6eb34e203c9f9823bc62b0d231 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 9 Apr 2026 18:35:01 -0700 Subject: [PATCH 012/167] objtool/klp: Fix cloning of zero-length section symbols Fix NULL dereference when cloning a symbol from an empty section. sec->data is only populated for sections with non-zero size. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index a226e99948b3..17a6146b9406 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -681,7 +681,7 @@ static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym size_t size; /* bss doesn't have data */ - if (patched_sym->sec->data->d_buf) + if (patched_sym->sec->data && patched_sym->sec->data->d_buf) data = patched_sym->sec->data->d_buf + patched_sym->offset; if (is_sec_sym(patched_sym)) From 479ac5260e7ea717f1ea7b1e74dfe807d3b46eda Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 2 Apr 2026 17:35:53 -0700 Subject: [PATCH 013/167] objtool/klp: Fix XXH3 state memory leak The XXH3 state allocated in checksum_init() is never freed. Free it in checksum_finish(). Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/checksum.h | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/include/objtool/checksum.h b/tools/objtool/include/objtool/checksum.h index 7fe21608722a..0bd16fe9168b 100644 --- a/tools/objtool/include/objtool/checksum.h +++ b/tools/objtool/include/objtool/checksum.h @@ -26,6 +26,7 @@ static inline void checksum_finish(struct symbol *func) { if (func && func->csum.state) { func->csum.checksum = XXH3_64bits_digest(func->csum.state); + XXH3_freeState(func->csum.state); func->csum.state = NULL; } } From 62a7a01fde87c99926cd7e9670b4226c4c79ebaf Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 14:53:32 -0700 Subject: [PATCH 014/167] objtool/klp: Fix extraction of text annotations for alternatives Objtool is failing to extract text annotations which reference .altinstr_replacement instructions: 1) Alternative replacement fake symbols are NOTYPE rather than FUNC, and they don't have sym->included set, thus they aren't recognized by should_keep_special_sym(). 2) .discard.annotate_insn gets processed before .altinstr_replacement, so the referenced (fake) symbols don't have clones yet. Fix the first issue by checking for a valid clone instead of sym->included and by accepting NOTYPE symbols when processing .discard.annotate_insn. Fix the second issue by deferring text annotation processing until after the other special sections have been cloned. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 17a6146b9406..42970b38728f 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1437,6 +1437,7 @@ entsize: /* Keep a special section entry if it references an included function */ static bool should_keep_special_sym(struct elf *elf, struct symbol *sym) { + bool annotate_insn = !strcmp(sym->sec->name, ".discard.annotate_insn"); struct reloc *reloc; if (is_sec_sym(sym) || !sym->sec->rsec) @@ -1446,7 +1447,16 @@ static bool should_keep_special_sym(struct elf *elf, struct symbol *sym) if (convert_reloc_sym(elf, reloc)) continue; - if (is_func_sym(reloc->sym) && reloc->sym->included) + if (!reloc->sym->clone || is_undef_sym(reloc->sym->clone)) + continue; + + /* + * Keep special section references to cloned functions. + * In some cases annotate_insn can also reference cloned alt + * replacement fake symbols; keep those references as well. + */ + if (is_func_sym(reloc->sym) || + (annotate_insn && is_notype_sym(reloc->sym))) return true; } @@ -1590,15 +1600,28 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec) /* Extract only the needed bits from special sections */ static int clone_special_sections(struct elfs *e) { - struct section *patched_sec; + struct section *sec, *annotate_insn = NULL; - for_each_sec(e->patched, patched_sec) { - if (is_special_section(patched_sec)) { - if (clone_special_section(e, patched_sec)) + for_each_sec(e->patched, sec) { + if (is_special_section(sec)) { + if (!strcmp(sec->name, ".discard.annotate_insn")) { + annotate_insn = sec; + continue; + } + if (clone_special_section(e, sec)) return -1; } } + /* + * Do .discard.annotate_insn last, it can reference other special + * sections (alt replacements) so they need to be cloned first. + */ + if (annotate_insn) { + if (clone_special_section(e, annotate_insn)) + return -1; + } + return 0; } From da4326573ae8d00a00a6015c8c3886812cd0adb4 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 20 Apr 2026 20:37:39 -0700 Subject: [PATCH 015/167] objtool/klp: Fix kCFI trap handling .kcfi_traps contains references to kCFI trap instruction locations. When a KCFI type check fails at an indirect call, the trap handler looks up the faulting address in this section. Add it to the special sections list so the entries get extracted for the changed functions they reference. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 42970b38728f..dd0e51dfc621 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -291,6 +291,7 @@ static bool is_special_section(struct section *sec) { static const char * const specials[] = { ".altinstructions", + ".kcfi_traps", ".smp_locks", "__bug_table", "__ex_table", From 3787e82a4e3a0a04aeb5543580ee90bed3a36e55 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 20 Apr 2026 22:59:12 -0700 Subject: [PATCH 016/167] objtool/klp: Fix relocation conversion failures for R_X86_64_NONE Objtool has some hacks which NOP out certain calls/jumps and replace their relocations with R_X86_64_NONE. The klp-diff relocation extraction code will error out when trying to copy these relocations due to their negative addend, which would only makes sense for a PC-relative branch instruction. Just ignore them. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index dd0e51dfc621..19bc811db396 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1033,6 +1033,9 @@ found_sym: */ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc) { + if (reloc_type(reloc) == R_NONE) + return 1; + if (is_reloc_allowed(reloc)) return 0; From 3e01ab44af207a2d8c197653e7278083b039a274 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 12 Apr 2026 18:35:03 -0700 Subject: [PATCH 017/167] objtool: Move mark_rodata() to elf.c Move the sec->rodata marking from check.c to elf.c so it's set during ELF reading rather than during the check pipeline. This makes the rodata flag available to all objtool users, including klp-diff which reads ELF files directly without running check(). Add an is_rodata_sec() helper to elf.h for consistency with is_text_sec() and is_string_sec(). Acked-by: Peter Zijlstra (Intel) Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 11 +++-------- tools/objtool/elf.c | 13 +++++++++++++ tools/objtool/include/objtool/elf.h | 5 +++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index e3604b1201f9..e7579c4e46dc 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2566,7 +2566,6 @@ static int classify_symbols(struct objtool_file *file) static void mark_rodata(struct objtool_file *file) { struct section *sec; - bool found = false; /* * Search for the following rodata sections, each of which can @@ -2579,15 +2578,11 @@ static void mark_rodata(struct objtool_file *file) * .rodata.str1.* sections are ignored; they don't contain jump tables. */ for_each_sec(file->elf, sec) { - if ((!strncmp(sec->name, ".rodata", 7) && - !strstr(sec->name, ".str1.")) || - !strncmp(sec->name, ".data.rel.ro", 12)) { - sec->rodata = true; - found = true; + if (is_rodata_sec(sec)) { + file->rodata = true; + return; } } - - file->rodata = found; } static void mark_holes(struct objtool_file *file) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index dc39132f71c1..87c6e00749c6 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -1138,6 +1138,17 @@ static int read_relocs(struct elf *elf) return 0; } +static void mark_rodata(struct elf *elf) +{ + struct section *sec; + + for_each_sec(elf, sec) { + if ((strstarts(sec->name, ".rodata") && !strstr(sec->name, ".str1.")) || + strstarts(sec->name, ".data.rel.ro")) + sec->rodata = true; + } +} + struct elf *elf_open_read(const char *name, int flags) { struct elf *elf; @@ -1188,6 +1199,8 @@ struct elf *elf_open_read(const char *name, int flags) if (read_sections(elf)) goto err; + mark_rodata(elf); + if (read_symbols(elf)) goto err; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 00b04029023e..ab5f7017ec34 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -317,6 +317,11 @@ static inline bool is_text_sec(struct section *sec) return sec->sh.sh_flags & SHF_EXECINSTR; } +static inline bool is_rodata_sec(struct section *sec) +{ + return sec->rodata; +} + static inline bool sec_changed(struct section *sec) { return sec->_changed; From 8fdc3585b3b09f2c55d8573edc6a1dbfeb728f03 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 29 Apr 2026 20:07:28 -0700 Subject: [PATCH 018/167] objtool/klp: Simplify reloc symbol conversion Inline section_reference_needed() and is_reloc_allowed() into convert_reloc_sym() and remove the redundant is_reloc_allowed() check in clone_reloc(). Move the is_sec_sym() checks into the convert callees so they become no-ops when the reloc is already in the right format. This allows convert_reloc_sym() to unconditionally dispatch to the right converter based on section type. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 76 +++++++++++++++------------------------- 1 file changed, 28 insertions(+), 48 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 19bc811db396..78633c9b68eb 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -838,39 +838,6 @@ static int clone_included_functions(struct elfs *e) return 0; } -/* - * Determine whether a relocation should reference the section rather than the - * underlying symbol. - */ -static bool section_reference_needed(struct section *sec) -{ - /* - * String symbols are zero-length and uncorrelated. It's easier to - * deal with them as section symbols. - */ - if (is_string_sec(sec)) - return true; - - /* - * .rodata has mostly anonymous data so there's no way to determine the - * length of a needed reference. just copy the whole section if needed. - */ - if (strstarts(sec->name, ".rodata")) - return true; - - /* UBSAN anonymous data */ - if (strstarts(sec->name, ".data..Lubsan") || /* GCC */ - strstarts(sec->name, ".data..L__unnamed_")) /* Clang */ - return true; - - return false; -} - -static bool is_reloc_allowed(struct reloc *reloc) -{ - return section_reference_needed(reloc->sym->sec) == is_sec_sym(reloc->sym); -} - static struct export *find_export(struct symbol *sym) { struct export *export; @@ -979,11 +946,15 @@ static bool klp_reloc_needed(struct reloc *patched_reloc) return true; } +/* Return -1 error, 0 success, 1 skip */ static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc) { struct symbol *sym = reloc->sym; struct section *sec = sym->sec; + if (is_sec_sym(sym)) + return 0; + if (!sec->sym && !elf_create_section_symbol(elf, sec)) return -1; @@ -993,11 +964,15 @@ static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc) return 0; } +/* Return -1 error, 0 success, 1 skip */ static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc) { struct symbol *sym = reloc->sym; struct section *sec = sym->sec; + if (!is_sec_sym(sym)) + return 0; + /* If the symbol has a dedicated section, it's easy to find */ sym = find_symbol_by_offset(sec, 0); if (sym && sym->len == sec_size(sec)) @@ -1027,22 +1002,34 @@ found_sym: return 0; } +/* + * Sections with anonymous or uncorrelated data (strings, UBSAN data) + * need section symbol references. + */ +static bool is_uncorrelated_section(struct section *sec) +{ + return is_string_sec(sec) || + strstarts(sec->name, ".rodata") || + strstarts(sec->name, ".data..Lubsan") || /* GCC */ + strstarts(sec->name, ".data..L__unnamed_"); /* Clang */ +} + /* * Convert a relocation symbol reference to the needed format: either a section - * symbol or the underlying symbol itself. + * symbol or the underlying symbol itself. Return -1 error, 0 success, 1 skip. */ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc) { + struct section *sec = reloc->sym->sec; + if (reloc_type(reloc) == R_NONE) return 1; - if (is_reloc_allowed(reloc)) - return 0; - - if (section_reference_needed(reloc->sym->sec)) + if (is_uncorrelated_section(sec)) return convert_reloc_sym_to_secsym(elf, reloc); - else - return convert_reloc_secsym_to_sym(elf, reloc); + + /* Everything else: references should use named symbols. */ + return convert_reloc_secsym_to_sym(elf, reloc); } /* @@ -1187,13 +1174,6 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc, struct symbol *out_sym; bool klp; - if (!is_reloc_allowed(patched_reloc)) { - ERROR_FUNC(patched_reloc->sec->base, reloc_offset(patched_reloc), - "missing symbol for reference to %s+%ld", - patched_sym->name, addend); - return -1; - } - klp = klp_reloc_needed(patched_reloc); dbg_clone_reloc(sec, offset, patched_sym, addend, export, klp); @@ -1223,7 +1203,7 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc, /* * For strings, all references use section symbols, thanks to - * section_reference_needed(). clone_symbol() has cloned an empty + * convert_reloc_sym(). clone_symbol() has cloned an empty * version of the string section. Now copy the string itself. */ if (is_string_sec(patched_sym->sec)) { From d5b0f025281f287759820f57b7ba1bac7827b338 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 28 Apr 2026 14:34:23 -0700 Subject: [PATCH 019/167] objtool/klp: Fix pointer comparisons for rodata objects klp-diff treats all rodata as uncorrelated, so any reference to it uses a duplicated copy rather than using a KLP reloc. For the contents of the data itself, a duplicated copy is fine. However, pointer comparisons (e.g., f->f_op == &foo_ops) are broken. Fix it by correlating non-anonymous rodata objects. Also, use a new find_symbol_containing_inclusive() helper for matching the end of a symbol so bounds calculations don't get broken, for the case where an array or other symbol's ending address is used as part of a bounds calculation. While these are really two distinct changes, they need to be done in the same patch so as to avoid introducing bisection regressions. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/elf.c | 14 ++++++++++++++ tools/objtool/include/objtool/elf.h | 1 + tools/objtool/klp-diff.c | 15 +++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 87c6e00749c6..5a20dab683dd 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -207,6 +207,20 @@ struct symbol *find_symbol_containing(const struct section *sec, unsigned long o return sym ? sym->alias : NULL; } +/* + * Also match the symbol end address which can be used for a bounds comparison. + */ +struct symbol *find_symbol_containing_inclusive(const struct section *sec, + unsigned long offset) +{ + struct symbol *sym = find_symbol_containing(sec, offset); + + if (!sym && offset) + sym = find_symbol_containing(sec, offset - 1); + + return sym; +} + /* * Returns size of hole starting at @offset. */ diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index ab5f7017ec34..8a543cea43b9 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -211,6 +211,7 @@ struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); struct symbol *find_symbol_by_name(const struct elf *elf, const char *name); struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *name); struct symbol *find_symbol_containing(const struct section *sec, unsigned long offset); +struct symbol *find_symbol_containing_inclusive(const struct section *sec, unsigned long offset); int find_symbol_hole_containing(const struct section *sec, unsigned long offset); struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset); struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 78633c9b68eb..bf37c652188b 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -386,6 +386,7 @@ static bool dont_correlate(struct symbol *sym) is_uncorrelated_static_local(sym) || is_local_label(sym) || is_string_sec(sym->sec) || + (is_rodata_sec(sym->sec) && !is_object_sym(sym)) || is_initcall_sym(sym) || is_addressable_sym(sym) || is_special_section(sym->sec) || @@ -979,7 +980,7 @@ static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc) goto found_sym; /* No dedicated section; find the symbol manually */ - sym = find_symbol_containing(sec, arch_adjusted_addend(reloc)); + sym = find_symbol_containing_inclusive(sec, arch_adjusted_addend(reloc)); if (!sym) { /* * This is presumably an .altinstr_replacement section which is @@ -988,6 +989,17 @@ static int convert_reloc_secsym_to_sym(struct elf *elf, struct reloc *reloc) if (!sec_size(sec)) return 1; + /* + * .rodata is a mixed bag of named objects and anonymous data. + * + * Convert section symbol references to named object symbols + * when possible, to preserve pointer identity for const + * structs like file_operations. Otherwise a section symbol is + * fine. + */ + if (is_rodata_sec(sec)) + return 0; + /* * This can happen for special section references to weak code * whose symbol has been stripped by the linker. @@ -1009,7 +1021,6 @@ found_sym: static bool is_uncorrelated_section(struct section *sec) { return is_string_sec(sec) || - strstarts(sec->name, ".rodata") || strstarts(sec->name, ".data..Lubsan") || /* GCC */ strstarts(sec->name, ".data..L__unnamed_"); /* Clang */ } From 51e1dfce24c8f4b1cdfa614a43ee195088901eca Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 29 Apr 2026 17:35:48 -0700 Subject: [PATCH 020/167] objtool/klp: Don't correlate .rodata.cst* constant pool objects Clang aggregates UBSAN type descriptors into shared anonymous .data..L__unnamed_* sections. This data is used by UBSAN trap handlers. When a changed function has an UBSAN bounds check, klp-diff clones the entire UBSAN data section associated with the TU. Relocations within the cloned section that reference named rodata objects in .rodata.cst* (like 'exponent', 'pirq_ali_set.irqmap') become KLP relocations because those objects now get correlated. That results in a .klp.rela.vmlinux..data section which can easily have thousands of KLP relocs, most of which are completely superfluous, used by functions which aren't cloned to the patch module. The .rodata.cst* sections are SHF_MERGE constant pool sections containing small fixed-size data (lookup tables, bitmasks) that is only read by value. Pointer identity is never relevant for these objects, so correlating them is unnecessary. Exclude .rodata.cst* objects from correlation so they get cloned as local data instead of generating KLP relocations. It might be possible to someday treat UBSAN data sections as special sections, and only extract the few needed entries. But this works for now. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index bf37c652188b..ca87bcb9afa3 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -372,6 +372,21 @@ static bool is_initcall_sym(struct symbol *sym) strstarts(sym->name, "__initstub__"); } +/* + * Some .rodata is anonymous and can't be correlated due to there being no + * symbol names. + * + * The .rodata.cst* sections aren't technically anonymous, they're SHF_MERGE + * constant pool sections containing small fixed-size data (lookup tables, + * bitmasks) which are only read by value, so pointer equivalence isn't needed. + * They are typically referenced by UBSAN data sections. + */ +static bool is_anonymous_rodata(struct symbol *sym) +{ + return is_rodata_sec(sym->sec) && + (!is_object_sym(sym) || strstarts(sym->sec->name, ".rodata.cst")); +} + /* * These symbols should never be correlated, so their local patched versions * are used instead of linking to the originals. @@ -386,7 +401,7 @@ static bool dont_correlate(struct symbol *sym) is_uncorrelated_static_local(sym) || is_local_label(sym) || is_string_sec(sym->sec) || - (is_rodata_sec(sym->sec) && !is_object_sym(sym)) || + is_anonymous_rodata(sym) || is_initcall_sym(sym) || is_addressable_sym(sym) || is_special_section(sym->sec) || From 5f49ec82b9f65507c262a6e3f1b53663f79eedce Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 29 Apr 2026 21:54:41 -0700 Subject: [PATCH 021/167] objtool/klp: Fix reloc corruption in convert_reloc_sym_to_secsym() Use the section symbol's index instead of the old symbol's index when updating the ELF relocation entry in convert_reloc_sym_to_secsym(). Found by Sashiko review. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index ca87bcb9afa3..463b6daa5234 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -975,7 +975,7 @@ static int convert_reloc_sym_to_secsym(struct elf *elf, struct reloc *reloc) return -1; reloc->sym = sec->sym; - set_reloc_sym(elf, reloc, sym->idx); + set_reloc_sym(elf, reloc, sec->sym->idx); set_reloc_addend(elf, reloc, sym->offset + reloc_addend(reloc)); return 0; } From a375e327b63e0da29b82a92b569bfdf4628fa38a Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 21 Apr 2026 07:21:19 -0700 Subject: [PATCH 022/167] objtool: Fix reloc hash collision in find_reloc_by_dest_range() In find_reloc_by_dest_range(), hash collisions can cause a high-offset relocation to appear when probing a low-offset hash bucket. Only return early when the best match found so far genuinely belongs to the current bucket (its offset is within the bucket's stride range). Otherwise, continue scanning later buckets which may contain lower-offset matches. This ensures the first reloc in the range gets returned. Fixes: 74b873e49d92 ("objtool: Optimize find_rela_by_dest_range()") Acked-by: Peter Zijlstra (Intel) Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- tools/objtool/elf.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 5a20dab683dd..f41280e454ca 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -315,8 +315,9 @@ struct symbol *find_global_symbol_by_name(const struct elf *elf, const char *nam return NULL; } +/* If there are multiple matches, return the first one in the range */ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *sec, - unsigned long offset, unsigned int len) + unsigned long offset, unsigned int len) { struct reloc *reloc, *r = NULL; struct section *rsec; @@ -338,11 +339,11 @@ struct reloc *find_reloc_by_dest_range(const struct elf *elf, struct section *se r = reloc; } } - if (r) + if (r && (reloc_offset(r) & OFFSET_STRIDE_MASK) == o) return r; } - return NULL; + return r; } struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsigned long offset) From cc39ccce7d5bc623100f07dcda070cef1bf690f6 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 2 Apr 2026 19:47:10 -0700 Subject: [PATCH 023/167] klp-build: Fix hang on out-of-date .config If .config is out of date with the kernel source, 'make syncconfig' hangs while waiting for user input on new config options. Detect the mismatch and return an error. Fixes: 6f93f7b06810 ("livepatch/klp-build: Fix inconsistent kernel version") Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 0ad7e6631314..e19d93b78fcb 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -306,7 +306,12 @@ set_kernelversion() { stash_file "$file" - kernelrelease="$(cd "$SRC" && make syncconfig &>/dev/null && make -s kernelrelease)" + if [[ -n "$(make -s listnewconfig 2>/dev/null)" ]]; then + die ".config mismatch, check your .config or run 'make olddefconfig'" + fi + make syncconfig &>/dev/null || die "make syncconfig failed" + + kernelrelease="$(make -s kernelrelease)" [[ -z "$kernelrelease" ]] && die "failed to get kernel version" sed -i "2i echo $kernelrelease; exit 0" scripts/setlocalversion From ba77fe55781a2464f68b6c13b4b31d05abd2abcf Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 10 Apr 2026 21:49:56 -0700 Subject: [PATCH 024/167] klp-build: Fix checksum comparison for changed offsets The klp-build -f/--show-first-changed feature uses diff to compare checksum log lines between original and patched objects. However, diff compares entire lines, including the offset field. When a function is at a different section offset, the offset field differs even though the instruction checksum is identical, causing the wrong instruction to be printed. Only compare the checksum field when looking for the first changed instruction. Also print both the original and patched offsets when they differ. Fixes: 78be9facfb5e ("livepatch/klp-build: Add --show-first-changed option to show function divergence") Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index e19d93b78fcb..8f0ea56f2640 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -727,13 +727,29 @@ diff_checksums() { ) for func in ${funcs[$file]}; do - diff <( grep0 -E "^DEBUG: .*checksum: $func " "$orig_log" | sed "s|$ORIG_DIR/||") \ - <( grep0 -E "^DEBUG: .*checksum: $func " "$patched_log" | sed "s|$PATCHED_DIR/||") \ - | gawk '/^< DEBUG: / { - gsub(/:/, "") - printf "%s: %s: %s\n", $3, $5, $6 - exit - }' || true + local -a orig patched + paste <(grep0 -E "^DEBUG: .*checksum: $func " "$orig_log") \ + <(grep0 -E "^DEBUG: .*checksum: $func " "$patched_log") | + while IFS= read -r line; do + read -ra orig <<< "${line%%$'\t'*}" + read -ra patched <<< "${line#*$'\t'}" + + if [[ ${#patched[@]} -eq 0 ]]; then + printf "%s: %s: %s (removed)\n" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}" + break + elif [[ ${#orig[@]} -eq 0 ]]; then + printf "%s: %s: %s (added)\n" "${patched[1]%:}" "${patched[3]}" "${patched[-2]}" + break + fi + + [[ "${orig[-1]}" == "${patched[-1]}" ]] && continue + + printf "%s: %s: %s" "${orig[1]%:}" "${orig[3]}" "${orig[-2]}" + [[ "${orig[-2]}" != "${patched[-2]}" ]] && \ + printf " (patched: %s)" "${patched[-2]}" + printf "\n" + break + done || true done done } From 946d3510fe19dca5cf8c1ea5d5eedff47a72ddc3 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 5 Apr 2026 16:17:08 -0700 Subject: [PATCH 025/167] klp-build: Don't use errexit The errtrace option (combined with the ERR trap) already serves the same function (and more) as errexit, so errexit is redundant. And it has more pitfalls. Remove it. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 8f0ea56f2640..68d61b72f39a 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -3,7 +3,7 @@ # # Build a livepatch module -# shellcheck disable=SC1090,SC2155 +# shellcheck disable=SC1090,SC2155,SC2164 if (( BASH_VERSINFO[0] < 4 || \ (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 4) )); then @@ -11,13 +11,12 @@ if (( BASH_VERSINFO[0] < 4 || \ exit 1 fi -set -o errexit set -o errtrace set -o pipefail set -o nounset # Allow doing 'cmd | mapfile -t array' instead of 'mapfile -t array < <(cmd)'. -# This helps keep execution in pipes so pipefail+errexit can catch errors. +# This helps keep execution in pipes so pipefail+ERR trap can catch errors. shopt -s lastpipe unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE From b3ece3019e8ebcda5e8451580e34bfbc97ef33e3 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 2 Apr 2026 19:48:45 -0700 Subject: [PATCH 026/167] klp-build: Validate patch file existence Make sure all patch files actually exist. Otherwise there can be confusing errors later. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 68d61b72f39a..13709d20e295 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -157,6 +157,7 @@ process_args() { local short local long local args + local patch short="hfj:o:vdS:T" long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp" @@ -235,6 +236,10 @@ process_args() { KEEP_TMP="$keep_tmp" PATCHES=("$@") + + for patch in "${PATCHES[@]}"; do + [[ -f "$patch" ]] || die "$patch doesn't exist" + done } # temporarily disable xtrace for especially verbose code From 96524543740ead39a79cf36bb4361f93cde1629c Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 16:17:31 -0700 Subject: [PATCH 027/167] klp-build: Suppress excessive fuzz output by default When a patch applies with fuzz, the detailed output from the patch tool can be very noisy, especially for big patches. Suppress the fuzz details by default, while keeping the "applied with fuzz" warning. The noise can be restored with '--verbose'. Acked-by: Song Liu Reviewed-by: Miroslav Benes Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 13709d20e295..dc2c5c33d1db 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -19,12 +19,11 @@ set -o nounset # This helps keep execution in pipes so pipefail+ERR trap can catch errors. shopt -s lastpipe -unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE +unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE REPLACE=1 SHORT_CIRCUIT=0 JOBS="$(getconf _NPROCESSORS_ONLN)" -VERBOSE="-s" shopt -o xtrace | grep -q 'on' && XTRACE=1 # Avoid removing the previous $TMP_DIR until args have been fully processed. @@ -194,7 +193,7 @@ process_args() { shift ;; -v | --verbose) - VERBOSE="V=1" + VERBOSE=1 shift ;; -d | --debug) @@ -381,7 +380,7 @@ apply_patch() { echo "$output" >&2 die "$patch did not apply" elif [[ "$output" =~ $drift_regex ]]; then - echo "$output" >&2 + [[ -v VERBOSE ]] && echo "$output" >&2 warn "${patch} applied with fuzz" fi @@ -544,7 +543,11 @@ build_kernel() { # cmd+=("KBUILD_MODPOST_WARN=1") - cmd+=("$VERBOSE") + if [[ -v VERBOSE ]]; then + cmd+=("V=1") + else + cmd+=("-s") + fi cmd+=("-j$JOBS") cmd+=("KCFLAGS=-ffunction-sections -fdata-sections") cmd+=("OBJTOOL_ARGS=${objtool_args[*]}") @@ -805,7 +808,11 @@ build_patch_module() { [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE") cmd=("make") - cmd+=("$VERBOSE") + if [[ -v VERBOSE ]]; then + cmd+=("V=1") + else + cmd+=("-s") + fi cmd+=("-j$JOBS") cmd+=("--directory=.") cmd+=("M=$KMOD_DIR") From f3048888ea62ac1c573db91e74e0dcabe058e89f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 2 Apr 2026 19:08:39 -0700 Subject: [PATCH 028/167] klp-build: Fix patch cleanup on interrupt If a build error occurs and the user hits Ctrl-C while a large patch is being reverted during cleanup, the cleanup EXIT trap gets re-triggered and tries to re-revert the already partially-reverted patch. That causes 'patch -R' to repeatedly prompt "Unreversed patch detected! Ignore -R? [n]" for each already-reverted hunk, with no way to break out. Fix it by adding '--force' to the patch revert command in revert_patch(), which causes it to silently ignore already-reverted hunks. And ignore errors, as the cleanup is always best-effort. For similar reasons, add to APPLIED_PATCHES before (rather than after) applying the patch in apply_patch() so an interrupted apply will also get cleaned up. Fixes: d36a7343f4ba ("livepatch/klp-build: switch to GNU patch and recountdiff") Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index dc2c5c33d1db..9970e1f274ef 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -384,15 +384,15 @@ apply_patch() { warn "${patch} applied with fuzz" fi - patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" APPLIED_PATCHES+=("$patch") + patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" } revert_patch() { local patch="$1" local tmp=() - patch -d "$SRC" -p1 -R --silent --no-backup-if-mismatch -r /dev/null < "$patch" + patch -d "$SRC" -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true for p in "${APPLIED_PATCHES[@]}"; do [[ "$p" == "$patch" ]] && continue From d8c3e262361b04984f0322ce5b88ea52dab37318 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 12 Apr 2026 12:09:39 -0700 Subject: [PATCH 029/167] klp-build: Reject patches to vDSO vDSO code runs in userspace and can't be livepatched. Such patches also cause spurious "new function" errors due to generated files like vdso*-image.c having unstable line numbers across builds. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 9970e1f274ef..a70d48d98453 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -357,7 +357,7 @@ check_unsupported_patches() { for file in "${files[@]}"; do case "$file" in - lib/*|*.S) + lib/*|*/vdso/*|*.S) die "${patch}: unsupported patch to $file" ;; esac From df0d7bb04a27e48a0fb5fd32223f5ab248876cab Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 19 Apr 2026 19:57:48 -0700 Subject: [PATCH 030/167] klp-build: Reject patches to realmode Realmode code is compiled as a separate 16-bit binary and embedded into the kernel image via rmpiggy.S. It can't be livepatched. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index a70d48d98453..2bb35de5db75 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -357,7 +357,7 @@ check_unsupported_patches() { for file in "${files[@]}"; do case "$file" in - lib/*|*/vdso/*|*.S) + lib/*|*/vdso/*|*/realmode/rm/*|*.S) die "${patch}: unsupported patch to $file" ;; esac From e950d2a10a30aa5f3ebb92d4bf807d1d5dd96de1 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 16:17:46 -0700 Subject: [PATCH 031/167] klp-build: Print "objtool klp diff" command in verbose mode Print the full objtool command line when '--verbose' is given to help with debugging. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 2bb35de5db75..327aef4d9cf8 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -681,6 +681,7 @@ diff_objects() { ( cd "$ORIG_DIR" + [[ -v VERBOSE ]] && echo "cd $ORIG_DIR && ${cmd[*]}" "${cmd[@]}" \ 1> >(tee -a "$log") \ 2> >(tee -a "$log" | "${filter[@]}" >&2) || \ From b6480aaedf3cbdd0a8e4b75c77434423eb52e3b8 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 30 Apr 2026 08:54:57 -0700 Subject: [PATCH 032/167] klp-build: Remove redundant SRC and OBJ variables SRC and OBJ are both set to $(pwd) and are always identical. The script already enforces that klp-build runs from the kernel root directory, and builds are done in-place, making these variables unnecessary. Suggested-by: Song Liu Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 67 ++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 327aef4d9cf8..215d2301510b 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -33,11 +33,9 @@ SCRIPT="$(basename "$0")" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines" -SRC="$(pwd)" -OBJ="$(pwd)" - -CONFIG="$OBJ/.config" -TMP_DIR="$OBJ/klp-tmp" +OBJTOOL="$PWD/tools/objtool/objtool" +CONFIG="$PWD/.config" +TMP_DIR="$PWD/klp-tmp" ORIG_DIR="$TMP_DIR/orig" PATCHED_DIR="$TMP_DIR/patched" @@ -88,7 +86,7 @@ declare -a STASHED_FILES stash_file() { local file="$1" - local rel_file="${file#"$SRC"/}" + local rel_file="${file#"$PWD"/}" [[ ! -e "$file" ]] && die "no file to stash: $file" @@ -102,7 +100,7 @@ restore_files() { local file for file in "${STASHED_FILES[@]}"; do - mv -f "$STASH_DIR/$file" "$SRC/$file" || warn "can't restore file: $file" + mv -f "$STASH_DIR/$file" "$PWD/$file" || warn "can't restore file: $file" done STASHED_FILES=() @@ -304,7 +302,7 @@ set_module_name() { # Hardcode the value printed by the localversion script to prevent patch # application from appending it with '+' due to a dirty working tree. set_kernelversion() { - local file="$SRC/scripts/setlocalversion" + local file="$PWD/scripts/setlocalversion" local kernelrelease stash_file "$file" @@ -375,7 +373,7 @@ apply_patch() { [[ ! -f "$patch" ]] && die "$patch doesn't exist" status=0 - output=$(patch -d "$SRC" -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? + output=$(patch -p1 --dry-run --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" < "$patch" 2>&1) || status=$? if [[ "$status" -ne 0 ]]; then echo "$output" >&2 die "$patch did not apply" @@ -385,14 +383,14 @@ apply_patch() { fi APPLIED_PATCHES+=("$patch") - patch -d "$SRC" -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" + patch -p1 --no-backup-if-mismatch -r /dev/null "${extra_args[@]}" --silent < "$patch" } revert_patch() { local patch="$1" local tmp=() - patch -d "$SRC" -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true + patch -p1 -R --force --no-backup-if-mismatch -r /dev/null &> /dev/null < "$patch" || true for p in "${APPLIED_PATCHES[@]}"; do [[ "$p" == "$patch" ]] && continue @@ -430,8 +428,7 @@ validate_patches() { do_init() { # We're not yet smart enough to handle anything other than in-tree # builds in pwd. - [[ ! "$SRC" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" - [[ ! "$OBJ" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" + [[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" (( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR" mkdir -p "$TMP_DIR" @@ -462,11 +459,11 @@ refresh_patch() { get_patch_output_files "$patch" | mapfile -t output_files # Copy orig source files to 'a' - ( cd "$SRC" && echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" ) + echo "${input_files[@]}" | xargs cp --parents --target-directory="$tmpdir/a" # Copy patched source files to 'b' apply_patch "$patch" "--silent" - ( cd "$SRC" && echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" ) + echo "${output_files[@]}" | xargs cp --parents --target-directory="$tmpdir/b" revert_patch "$patch" # Diff 'a' and 'b' to make a clean patch @@ -510,10 +507,7 @@ clean_kernel() { cmd+=("-j$JOBS") cmd+=("clean") - ( - cd "$SRC" - "${cmd[@]}" - ) + "${cmd[@]}" } build_kernel() { @@ -554,12 +548,10 @@ build_kernel() { cmd+=("vmlinux") cmd+=("modules") - ( - cd "$SRC" - "${cmd[@]}" \ - 1> >(tee -a "$log") \ - 2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) - ) || die "$build kernel build failed" + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" | grep0 -v "modpost.*undefined!" >&2) \ + || die "$build kernel build failed" } find_objects() { @@ -567,9 +559,9 @@ find_objects() { # Find root-level vmlinux.o and non-root-level .ko files, # excluding klp-tmp/ and .git/ - find "$OBJ" \( -path "$TMP_DIR" -o -path "$OBJ/.git" -o -regex "$OBJ/[^/][^/]*\.ko" \) -prune -o \ + find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ -type f "${opts[@]}" \ - \( -name "*.ko" -o -path "$OBJ/vmlinux.o" \) \ + \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ -printf '%P\n' } @@ -585,7 +577,7 @@ copy_orig_objects() { xtrace_save "copying orig objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" - local file="$OBJ/$rel_file" + local file="$PWD/$rel_file" local orig_file="$ORIG_DIR/$rel_file" local orig_dir="$(dirname "$orig_file")" @@ -618,7 +610,7 @@ copy_patched_objects() { xtrace_save "copying changed objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" - local file="$OBJ/$rel_file" + local file="$PWD/$rel_file" local orig_file="$ORIG_DIR/$rel_file" local patched_file="$PATCHED_DIR/$rel_file" local patched_dir="$(dirname "$patched_file")" @@ -663,7 +655,7 @@ diff_objects() { mkdir -p "$(dirname "$out_file")" - cmd=("$SRC/tools/objtool/objtool") + cmd=("$OBJTOOL") cmd+=("klp") cmd+=("diff") (( ${#opts[@]} > 0 )) && cmd+=("${opts[@]}") @@ -716,7 +708,7 @@ diff_checksums() { fi done - cmd=("$SRC/tools/objtool/objtool") + cmd=("$OBJTOOL") cmd+=("--checksum") cmd+=("--link") cmd+=("--dry-run") @@ -774,7 +766,7 @@ build_patch_module() { rm -rf "$KMOD_DIR" mkdir -p "$KMOD_DIR" - cp -f "$SRC/scripts/livepatch/init.c" "$KMOD_DIR" + cp -f "$SCRIPT_DIR/init.c" "$KMOD_DIR" echo "obj-m := $NAME.o" > "$makefile" echo -n "$NAME-y := init.o" >> "$makefile" @@ -820,12 +812,9 @@ build_patch_module() { cmd+=("KCFLAGS=${cflags[*]}") # Build a "normal" kernel module with init.c and the diffed objects - ( - cd "$SRC" - "${cmd[@]}" \ - 1> >(tee -a "$log") \ - 2> >(tee -a "$log" >&2) - ) + "${cmd[@]}" \ + 1> >(tee -a "$log") \ + 2> >(tee -a "$log" >&2) kmod_file="$KMOD_DIR/$NAME.ko" @@ -836,7 +825,7 @@ build_patch_module() { objcopy --remove-section=.BTF "$kmod_file" # Fix (and work around) linker wreckage for klp syms / relocs - "$SRC/tools/objtool/objtool" klp post-link "$kmod_file" || die "objtool klp post-link failed" + "$OBJTOOL" klp post-link "$kmod_file" || die "objtool klp post-link failed" cp -f "$kmod_file" "$OUTFILE" } From 8d4cbb6d0caf03b0a22be4d6d2198a9edac46517 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 12 Apr 2026 22:40:02 -0700 Subject: [PATCH 033/167] objtool/klp: Don't set sym->file for section symbols Section symbols aren't grouped after their corresponding FILE symbols. Their sym->file should really be NULL rather than whatever random FILE happened to be last. Acked-by: Peter Zijlstra (Intel) Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/elf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index f41280e454ca..d9cee8d5d9e8 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -649,7 +649,7 @@ static int read_symbols(struct elf *elf) if (is_file_sym(sym)) file = sym; - else if (sym->bind == STB_LOCAL) + else if (sym->bind == STB_LOCAL && !is_sec_sym(sym)) sym->file = file; } From ac999926774a71e30c2e2e2eb48d5419dc167781 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 12 Apr 2026 10:59:42 -0700 Subject: [PATCH 034/167] objtool: Include libsubcmd headers directly from source tree Instead of installing libsubcmd headers to a build output directory and including from there, include directly from tools/lib/ where they already exist. This fixes clangd indexing which otherwise can't find libsubcmd headers. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index b71d1886022e..a4484fd22a96 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -58,7 +58,7 @@ INCLUDES := -I$(srctree)/tools/include \ -I$(srctree)/tools/arch/$(SRCARCH)/include \ -I$(srctree)/tools/objtool/include \ -I$(srctree)/tools/objtool/arch/$(SRCARCH)/include \ - -I$(LIBSUBCMD_OUTPUT)/include + -I$(srctree)/tools/lib OBJTOOL_CFLAGS := -std=gnu11 -fomit-frame-pointer -O2 -g $(WARNINGS) \ $(INCLUDES) $(LIBELF_FLAGS) $(LIBXXHASH_CFLAGS) $(HOSTCFLAGS) @@ -135,7 +135,7 @@ $(LIBSUBCMD): fixdep $(LIBSUBCMD_OUTPUT) FORCE $(Q)$(MAKE) -C $(LIBSUBCMD_DIR) O=$(LIBSUBCMD_OUTPUT) \ DESTDIR=$(LIBSUBCMD_OUTPUT) prefix= subdir= \ $(HOST_OVERRIDES) EXTRA_CFLAGS="$(OBJTOOL_CFLAGS)" \ - $@ install_headers + $@ $(LIBSUBCMD)-clean: $(call QUIET_CLEAN, libsubcmd) From 9e4512d7de5ab27c1b0c3891f7a0e9202a5a2eef Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 30 Mar 2026 21:50:46 -0700 Subject: [PATCH 035/167] objtool/klp: Create empty checksum sections for function-less object files If an object file has no functions, objtool has nothing to checksum, so it doesn't create the .discard.sym_checksum symbol. Then when 'objtool klp diff' reads symbol checksums, it errors out due to the missing .discard.sym_checksum section. Instead, just create an empty checksum section to signal to read_sym_checksums() that the file has been processed. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index e7579c4e46dc..f020f21f94a7 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1044,9 +1044,6 @@ static int create_sym_checksum_section(struct objtool_file *file) if (sym->csum.checksum) idx++; - if (!idx) - return 0; - sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, idx, idx); if (!sec) From ff0cf5efef40e3b60e7bb27a6072f9a0c6e4c5ce Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 9 Apr 2026 20:50:35 -0700 Subject: [PATCH 036/167] objtool/klp: Handle Clang .data..Lanon anonymous data sections Clang generates anonymous data sections named .data..Lanon.. These need section-symbol references in the same way as .data..Lubsan (GCC) and .data..L__unnamed_ (Clang UBSAN) sections. Without this, convert_reloc_sym() fails when processing relocations that reference these sections. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 463b6daa5234..7e58ef36f805 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1030,14 +1030,15 @@ found_sym: } /* - * Sections with anonymous or uncorrelated data (strings, UBSAN data) - * need section symbol references. + * Sections with anonymous or uncorrelated data (strings, UBSAN data, Clang + * anonymous constants) need section symbol references. */ static bool is_uncorrelated_section(struct section *sec) { return is_string_sec(sec) || strstarts(sec->name, ".data..Lubsan") || /* GCC */ - strstarts(sec->name, ".data..L__unnamed_"); /* Clang */ + strstarts(sec->name, ".data..L__unnamed_") || /* Clang */ + strstarts(sec->name, ".data..Lanon."); /* Clang */ } /* From 8eebd573113362ef3a3c75f166a83ee060dae11b Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 18 Mar 2026 15:42:30 -0700 Subject: [PATCH 037/167] objtool: Add is_alias_sym() helper Improve readability with a new is_alias_sym() helper. No functional changes intended. Acked-by: Peter Zijlstra (Intel) Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 6 +++--- tools/objtool/include/objtool/elf.h | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index f020f21f94a7..6c94eb32c090 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -491,7 +491,7 @@ static int decode_instructions(struct objtool_file *file) return -1; } - if (func->embedded_insn || func->alias != func) + if (func->embedded_insn || is_alias_sym(func)) continue; if (!find_insn(file, sec, func->offset)) { @@ -2229,7 +2229,7 @@ static int add_jump_table_alts(struct objtool_file *file) return 0; for_each_sym(file->elf, func) { - if (!is_func_sym(func) || func->alias != func) + if (!is_func_sym(func) || is_alias_sym(func)) continue; mark_func_jump_tables(file, func); @@ -4523,7 +4523,7 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, return 1; } - if (sym->pfunc != sym || sym->alias != sym) + if (sym->pfunc != sym || is_alias_sym(sym)) return 0; insn = find_insn(file, sec, sym->offset); diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 8a543cea43b9..ccc72a692d9a 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -298,6 +298,11 @@ static inline bool is_local_sym(struct symbol *sym) return sym->bind == STB_LOCAL; } +static inline bool is_alias_sym(struct symbol *sym) +{ + return sym->alias != sym; +} + static inline bool is_prefix_func(struct symbol *sym) { return sym->prefix; From 6282e9f46b4fa3ac554852bb1aef17843fc47ee2 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 18 Mar 2026 15:33:58 -0700 Subject: [PATCH 038/167] objtool: Add is_cold_func() helper Add an is_cold_func() helper. No functional changes intended. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 6 +++--- tools/objtool/include/objtool/elf.h | 5 +++++ tools/objtool/klp-diff.c | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 6c94eb32c090..93a054adf209 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2614,7 +2614,7 @@ static void mark_holes(struct objtool_file *file) if (insn->jump_dest) { struct symbol *dest_func = insn_func(insn->jump_dest); - if (dest_func && dest_func->cold) + if (dest_func && is_cold_func(dest_func)) dest_func->ignore = true; } } @@ -4422,8 +4422,8 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func) char name[SYM_NAME_LEN]; struct cfi_state *cfi; - if (!is_func_sym(func) || is_prefix_func(func) || - func->cold || func->static_call_tramp) + if (!is_func_sym(func) || is_prefix_func(func) || is_cold_func(func) || + func->static_call_tramp) return 0; if ((strlen(func->name) + sizeof("__pfx_") > SYM_NAME_LEN)) { diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index ccc72a692d9a..e452784df702 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -308,6 +308,11 @@ static inline bool is_prefix_func(struct symbol *sym) return sym->prefix; } +static inline bool is_cold_func(struct symbol *sym) +{ + return sym->cold; +} + static inline bool is_reloc_sec(struct section *sec) { return sec->sh.sh_type == SHT_RELA || sec->sh.sh_type == SHT_REL; diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 7e58ef36f805..8728dda1e08c 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1709,7 +1709,8 @@ static int create_klp_sections(struct elfs *e) unsigned long sympos; void *func_data; - if (!is_func_sym(sym) || sym->cold || !sym->clone || !sym->clone->changed) + if (!is_func_sym(sym) || is_cold_func(sym) || + !sym->clone || !sym->clone->changed) continue; /* allocate klp_func_ext */ From 30cae58cdc13b91035faae3dcc8a96fe8a2daae3 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 12:11:17 -0700 Subject: [PATCH 039/167] objtool/klp: Extricate checksum calculation from validate_branch() In preparation for porting the checksum code to other arches, make its functionality independent from the CFG reverse engineering code. Move it into a standalone calculate_checksums() function which iterates all functions and instructions directly, rather than being called inline from do_validate_branch(). Since checksum_update_insn() is no longer called during CFG traversal, it needs to manually iterate the alternatives. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 106 +++++++++++++++++------ tools/objtool/include/objtool/checksum.h | 6 +- 2 files changed, 80 insertions(+), 32 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 93a054adf209..f019e1f06780 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1350,10 +1350,7 @@ static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *i { struct reloc *reloc; - if (insn->no_reloc) - return NULL; - - if (!file) + if (!file || insn->no_reloc || insn->fake) return NULL; reloc = find_reloc_by_dest_range(file->elf, insn->sec, @@ -2622,9 +2619,17 @@ static void mark_holes(struct objtool_file *file) static bool validate_branch_enabled(void) { - return opts.stackval || - opts.orc || - opts.uaccess || + return opts.stackval || + opts.orc || + opts.uaccess; +} + +static bool alts_needed(void) +{ + return validate_branch_enabled() || + opts.noinstr || + opts.hack_jump_label || + opts.disas || opts.checksum; } @@ -2658,7 +2663,7 @@ static int decode_sections(struct objtool_file *file) * Must be before add_jump_destinations(), which depends on 'func' * being set for alternatives, to enable proper sibling call detection. */ - if (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label || opts.disas) { + if (alts_needed()) { if (add_special_section_alts(file)) return -1; } @@ -3654,6 +3659,7 @@ static bool skip_alt_group(struct instruction *insn) return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC; } +#ifdef BUILD_KLP static int checksum_debug_init(struct objtool_file *file) { char *dup, *s; @@ -3701,8 +3707,10 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, struct instruction *insn) { struct reloc *reloc = insn_reloc(file, insn); + struct alternative *alt; unsigned long offset; struct symbol *sym; + static bool in_alt; if (insn->fake) return; @@ -3715,7 +3723,7 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, if (call_dest) checksum_update(func, insn, call_dest->demangled_name, strlen(call_dest->demangled_name)); - return; + goto alts; } sym = reloc->sym; @@ -3726,21 +3734,78 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, str = sym->sec->data->d_buf + sym->offset + offset; checksum_update(func, insn, str, strlen(str)); - return; + goto alts; } if (is_sec_sym(sym)) { sym = find_symbol_containing(reloc->sym->sec, offset); if (!sym) - return; + goto alts; offset -= sym->offset; } checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name)); checksum_update(func, insn, &offset, sizeof(offset)); + +alts: + for (alt = insn->alts; alt; alt = alt->next) { + struct alt_group *alt_group = alt->insn->alt_group; + + /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ + if (in_alt) + break; + in_alt = true; + + checksum_update(func, insn, &alt->type, sizeof(alt->type)); + + if (alt_group && alt_group->orig_group) { + struct instruction *alt_insn; + + checksum_update(func, insn, &alt_group->feature, sizeof(alt_group->feature)); + + for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { + checksum_update_insn(file, func, alt_insn); + if (!alt_group->last_insn || alt_insn == alt_group->last_insn) + break; + } + } else { + checksum_update_insn(file, func, alt->insn); + } + + in_alt = false; + } } +static int calculate_checksums(struct objtool_file *file) +{ + struct instruction *insn; + struct symbol *func; + + if (checksum_debug_init(file)) + return -1; + + for_each_sym(file->elf, func) { + /* + * Skip cold subfunctions and aliases: they share the + * parent's checksum via func_for_each_insn() which + * follows func->cfunc into the cold subfunction. + */ + if (!is_func_sym(func) || is_cold_func(func) || + is_alias_sym(func) || !func->len) + continue; + + checksum_init(func); + + func_for_each_insn(file, func, insn) + checksum_update_insn(file, func, insn); + + checksum_finish(func); + } + return 0; +} +#endif /* BUILD_KLP */ + static int validate_branch(struct objtool_file *file, struct symbol *func, struct instruction *insn, struct insn_state state); static int do_validate_branch(struct objtool_file *file, struct symbol *func, @@ -4022,9 +4087,6 @@ static int do_validate_branch(struct objtool_file *file, struct symbol *func, insn->trace = 0; next_insn = next_insn_to_validate(file, insn); - if (opts.checksum && func && insn->sec) - checksum_update_insn(file, func, insn); - if (func && insn_func(insn) && func != insn_func(insn)->pfunc) { /* Ignore KCFI type preambles, which always fall through */ if (is_prefix_func(func)) @@ -4090,9 +4152,6 @@ static int validate_unwind_hint(struct objtool_file *file, struct symbol *func = insn_func(insn); int ret; - if (opts.checksum) - checksum_init(func); - ret = validate_branch(file, func, insn, *state); if (ret) BT_INSN(insn, "<=== (hint)"); @@ -4535,9 +4594,6 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, func = insn_func(insn); - if (opts.checksum) - checksum_init(func); - if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) { trace_enable(); TRACE("%s: validation begin\n", sym->name); @@ -4550,9 +4606,6 @@ static int validate_symbol(struct objtool_file *file, struct section *sec, TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end"); trace_disable(); - if (opts.checksum) - checksum_finish(func); - return ret; } @@ -5007,10 +5060,6 @@ int check(struct objtool_file *file) cfi_hash_add(&init_cfi); cfi_hash_add(&func_cfi); - ret = checksum_debug_init(file); - if (ret) - goto out; - ret = decode_sections(file); if (ret) goto out; @@ -5101,6 +5150,9 @@ int check(struct objtool_file *file) warnings += check_abs_references(file); if (opts.checksum) { + ret = calculate_checksums(file); + if (ret) + goto out; ret = create_sym_checksum_section(file); if (ret) goto out; diff --git a/tools/objtool/include/objtool/checksum.h b/tools/objtool/include/objtool/checksum.h index 0bd16fe9168b..3f25df90305d 100644 --- a/tools/objtool/include/objtool/checksum.h +++ b/tools/objtool/include/objtool/checksum.h @@ -33,11 +33,7 @@ static inline void checksum_finish(struct symbol *func) #else /* !BUILD_KLP */ -static inline void checksum_init(struct symbol *func) {} -static inline void checksum_update(struct symbol *func, - struct instruction *insn, - const void *data, size_t size) {} -static inline void checksum_finish(struct symbol *func) {} +static inline int calculate_checksums(struct objtool_file *file) { return -ENOSYS; } #endif /* !BUILD_KLP */ From a5b6612332622d809e5c0a4f5637eef92dc06c06 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 13:04:28 -0700 Subject: [PATCH 040/167] objtool: Consolidate file decoding into decode_file() decode_sections() relies on CFI and cfi_hash initialization done separately in check(), making it unusable outside of check(). Consolidate the initialization into decode_sections() and rename it to decode_file(), and make it global along with free_insns() and insn_reloc() for use by other objtool components -- namely, the checksum code which will be moving to another file. Acked-by: Peter Zijlstra (Intel) Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 36 +++++++++++++-------------- tools/objtool/include/objtool/check.h | 5 ++++ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index f019e1f06780..49171ddc6f54 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -1346,7 +1346,7 @@ __weak bool arch_is_embedded_insn(struct symbol *sym) return false; } -static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn) +struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn) { struct reloc *reloc; @@ -2633,8 +2633,21 @@ static bool alts_needed(void) opts.checksum; } -static int decode_sections(struct objtool_file *file) +int decode_file(struct objtool_file *file) { + arch_initial_func_cfi_state(&initial_func_cfi); + init_cfi_state(&init_cfi); + init_cfi_state(&func_cfi); + set_func_state(&func_cfi); + init_cfi_state(&force_undefined_cfi); + force_undefined_cfi.force_undefined = true; + + if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) + return -1; + + cfi_hash_add(&init_cfi); + cfi_hash_add(&func_cfi); + file->klp = is_livepatch_module(file); mark_rodata(file); @@ -4998,7 +5011,7 @@ struct insn_chunk { * which can trigger more allocations for .debug_* sections whose data hasn't * been read yet. */ -static void free_insns(struct objtool_file *file) +void free_insns(struct objtool_file *file) { struct instruction *insn; struct insn_chunk *chunks = NULL, *chunk; @@ -5045,22 +5058,7 @@ int check(struct objtool_file *file) objtool_disas_ctx = disas_ctx; } - arch_initial_func_cfi_state(&initial_func_cfi); - init_cfi_state(&init_cfi); - init_cfi_state(&func_cfi); - set_func_state(&func_cfi); - init_cfi_state(&force_undefined_cfi); - force_undefined_cfi.force_undefined = true; - - if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) { - ret = -1; - goto out; - } - - cfi_hash_add(&init_cfi); - cfi_hash_add(&func_cfi); - - ret = decode_sections(file); + ret = decode_file(file); if (ret) goto out; diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index 5f2f77bd9b41..6489e52ea2f2 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -155,6 +155,11 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc insn && insn->offset < sym->offset + sym->len; \ insn = next_insn_same_sec(file, insn)) +struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn); + +int decode_file(struct objtool_file *file); +void free_insns(struct objtool_file *file); + const char *objtool_disas_insn(struct instruction *insn); extern size_t sym_name_max_len; From e10764614ad634071d3bc8cfbf8bce43285d458d Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 13:08:32 -0700 Subject: [PATCH 041/167] objtool/klp: Add "objtool klp checksum" subcommand Move the checksum functionality out of the main objtool command into a new "objtool klp checksum" subcommand. This has the benefit of making the code (and the patch generation process itself) more modular. For bisectability, both "objtool --checksum" and "objtool klp checksum" work for now. The former will be removed after klp-build has been converted to use the new subcommand. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/Build | 2 +- tools/objtool/builtin-klp.c | 1 + tools/objtool/check.c | 205 +----------------- tools/objtool/include/objtool/check.h | 6 + tools/objtool/include/objtool/checksum.h | 4 + tools/objtool/include/objtool/klp.h | 1 + tools/objtool/klp-checksum.c | 253 +++++++++++++++++++++++ tools/objtool/klp-diff.c | 2 +- 8 files changed, 269 insertions(+), 205 deletions(-) create mode 100644 tools/objtool/klp-checksum.c diff --git a/tools/objtool/Build b/tools/objtool/Build index 600da051af12..93a37b0dfd31 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -12,7 +12,7 @@ objtool-$(BUILD_DISAS) += disas.o objtool-$(BUILD_DISAS) += trace.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o -objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o +objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o klp-post-link.o objtool-y += libstring.o objtool-y += libctype.o diff --git a/tools/objtool/builtin-klp.c b/tools/objtool/builtin-klp.c index 56d5a5b92f72..58c3b9bda3eb 100644 --- a/tools/objtool/builtin-klp.c +++ b/tools/objtool/builtin-klp.c @@ -13,6 +13,7 @@ struct subcmd { }; static struct subcmd subcmds[] = { + { "checksum", "Generate per-function checksums", cmd_klp_checksum, }, { "diff", "Generate binary diff of two object files", cmd_klp_diff, }, { "post-link", "Finalize klp symbols/relocs after module linking", cmd_klp_post_link, }, }; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 49171ddc6f54..3e5d335d0e29 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -64,8 +64,8 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, return insn; } -static struct instruction *next_insn_same_func(struct objtool_file *file, - struct instruction *insn) +struct instruction *next_insn_same_func(struct objtool_file *file, + struct instruction *insn) { struct instruction *next = next_insn_same_sec(file, insn); struct symbol *func = insn_func(insn); @@ -113,10 +113,6 @@ static struct instruction *prev_insn_same_sym(struct objtool_file *file, for_each_sec(file->elf, __sec) \ sec_for_each_insn(file, __sec, insn) -#define func_for_each_insn(file, func, insn) \ - for (insn = find_insn(file, func->sec, func->offset); \ - insn; \ - insn = next_insn_same_func(file, insn)) #define sym_for_each_insn(file, sym, insn) \ for (insn = find_insn(file, sym->sec, sym->offset); \ @@ -1023,56 +1019,6 @@ static int create_direct_call_sections(struct objtool_file *file) return 0; } -#ifdef BUILD_KLP -static int create_sym_checksum_section(struct objtool_file *file) -{ - struct section *sec; - struct symbol *sym; - unsigned int idx = 0; - struct sym_checksum *checksum; - size_t entsize = sizeof(struct sym_checksum); - - sec = find_section_by_name(file->elf, ".discard.sym_checksum"); - if (sec) { - if (!opts.dryrun) - WARN("file already has .discard.sym_checksum section, skipping"); - - return 0; - } - - for_each_sym(file->elf, sym) - if (sym->csum.checksum) - idx++; - - sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, - idx, idx); - if (!sec) - return -1; - - idx = 0; - for_each_sym(file->elf, sym) { - if (!sym->csum.checksum) - continue; - - if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize, - sym, 0, R_TEXT64)) - return -1; - - checksum = (struct sym_checksum *)sec->data->d_buf + idx; - checksum->addr = 0; /* reloc */ - checksum->checksum = sym->csum.checksum; - - mark_sec_changed(file->elf, sec, true); - - idx++; - } - - return 0; -} -#else -static int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; } -#endif - /* * Warnings shouldn't be reported for ignored functions. */ @@ -3672,153 +3618,6 @@ static bool skip_alt_group(struct instruction *insn) return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC; } -#ifdef BUILD_KLP -static int checksum_debug_init(struct objtool_file *file) -{ - char *dup, *s; - - if (!opts.debug_checksum) - return 0; - - dup = strdup(opts.debug_checksum); - if (!dup) { - ERROR_GLIBC("strdup"); - return -1; - } - - s = dup; - while (*s) { - bool found = false; - struct symbol *sym; - char *comma; - - comma = strchr(s, ','); - if (comma) - *comma = '\0'; - - for_each_sym_by_name(file->elf, s, sym) { - if (!is_func_sym(sym)) - continue; - sym->debug_checksum = 1; - found = true; - } - - if (!found) - WARN("--debug-checksum: can't find '%s'", s); - - if (!comma) - break; - - s = comma + 1; - } - - free(dup); - return 0; -} - -static void checksum_update_insn(struct objtool_file *file, struct symbol *func, - struct instruction *insn) -{ - struct reloc *reloc = insn_reloc(file, insn); - struct alternative *alt; - unsigned long offset; - struct symbol *sym; - static bool in_alt; - - if (insn->fake) - return; - - checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); - - if (!reloc) { - struct symbol *call_dest = insn_call_dest(insn); - - if (call_dest) - checksum_update(func, insn, call_dest->demangled_name, - strlen(call_dest->demangled_name)); - goto alts; - } - - sym = reloc->sym; - offset = arch_insn_adjusted_addend(insn, reloc); - - if (is_string_sec(sym->sec)) { - char *str; - - str = sym->sec->data->d_buf + sym->offset + offset; - checksum_update(func, insn, str, strlen(str)); - goto alts; - } - - if (is_sec_sym(sym)) { - sym = find_symbol_containing(reloc->sym->sec, offset); - if (!sym) - goto alts; - - offset -= sym->offset; - } - - checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name)); - checksum_update(func, insn, &offset, sizeof(offset)); - -alts: - for (alt = insn->alts; alt; alt = alt->next) { - struct alt_group *alt_group = alt->insn->alt_group; - - /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ - if (in_alt) - break; - in_alt = true; - - checksum_update(func, insn, &alt->type, sizeof(alt->type)); - - if (alt_group && alt_group->orig_group) { - struct instruction *alt_insn; - - checksum_update(func, insn, &alt_group->feature, sizeof(alt_group->feature)); - - for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { - checksum_update_insn(file, func, alt_insn); - if (!alt_group->last_insn || alt_insn == alt_group->last_insn) - break; - } - } else { - checksum_update_insn(file, func, alt->insn); - } - - in_alt = false; - } -} - -static int calculate_checksums(struct objtool_file *file) -{ - struct instruction *insn; - struct symbol *func; - - if (checksum_debug_init(file)) - return -1; - - for_each_sym(file->elf, func) { - /* - * Skip cold subfunctions and aliases: they share the - * parent's checksum via func_for_each_insn() which - * follows func->cfunc into the cold subfunction. - */ - if (!is_func_sym(func) || is_cold_func(func) || - is_alias_sym(func) || !func->len) - continue; - - checksum_init(func); - - func_for_each_insn(file, func, insn) - checksum_update_insn(file, func, insn); - - checksum_finish(func); - } - return 0; -} -#endif /* BUILD_KLP */ - static int validate_branch(struct objtool_file *file, struct symbol *func, struct instruction *insn, struct insn_state state); static int do_validate_branch(struct objtool_file *file, struct symbol *func, diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index 6489e52ea2f2..eea64728d39b 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -144,6 +144,12 @@ struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset); struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruction *insn); +struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn); + +#define func_for_each_insn(file, func, insn) \ + for (insn = find_insn(file, func->sec, func->offset); \ + insn; \ + insn = next_insn_same_func(file, insn)) #define sec_for_each_insn(file, _sec, insn) \ for (insn = find_insn(file, _sec, 0); \ diff --git a/tools/objtool/include/objtool/checksum.h b/tools/objtool/include/objtool/checksum.h index 3f25df90305d..be4eb7dfe6f2 100644 --- a/tools/objtool/include/objtool/checksum.h +++ b/tools/objtool/include/objtool/checksum.h @@ -31,9 +31,13 @@ static inline void checksum_finish(struct symbol *func) } } +int calculate_checksums(struct objtool_file *file); +int create_sym_checksum_section(struct objtool_file *file); + #else /* !BUILD_KLP */ static inline int calculate_checksums(struct objtool_file *file) { return -ENOSYS; } +static inline int create_sym_checksum_section(struct objtool_file *file) { return -EINVAL; } #endif /* !BUILD_KLP */ diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index e32e5e8bc631..6f60cf05db86 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -29,6 +29,7 @@ struct klp_reloc { u32 type; }; +int cmd_klp_checksum(int argc, const char **argv); int cmd_klp_diff(int argc, const char **argv); int cmd_klp_post_link(int argc, const char **argv); diff --git a/tools/objtool/klp-checksum.c b/tools/objtool/klp-checksum.c new file mode 100644 index 000000000000..e4a910f3211c --- /dev/null +++ b/tools/objtool/klp-checksum.c @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int checksum_debug_init(struct objtool_file *file) +{ + char *dup, *s; + + if (!opts.debug_checksum) + return 0; + + dup = strdup(opts.debug_checksum); + if (!dup) { + ERROR_GLIBC("strdup"); + return -1; + } + + s = dup; + while (*s) { + bool found = false; + struct symbol *sym; + char *comma; + + comma = strchr(s, ','); + if (comma) + *comma = '\0'; + + for_each_sym_by_name(file->elf, s, sym) { + if (!is_func_sym(sym)) + continue; + sym->debug_checksum = 1; + found = true; + } + + if (!found) + WARN("--debug-checksum: can't find '%s'", s); + + if (!comma) + break; + + s = comma + 1; + } + + free(dup); + return 0; +} + +static void checksum_update_insn(struct objtool_file *file, struct symbol *func, + struct instruction *insn) +{ + struct reloc *reloc = insn_reloc(file, insn); + struct alternative *alt; + unsigned long offset; + struct symbol *sym; + static bool in_alt; + + if (insn->fake) + return; + + checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); + + if (!reloc) { + struct symbol *call_dest = insn_call_dest(insn); + + if (call_dest) + checksum_update(func, insn, call_dest->demangled_name, + strlen(call_dest->demangled_name)); + goto alts; + } + + sym = reloc->sym; + offset = arch_insn_adjusted_addend(insn, reloc); + + if (is_string_sec(sym->sec)) { + char *str; + + str = sym->sec->data->d_buf + sym->offset + offset; + checksum_update(func, insn, str, strlen(str)); + goto alts; + } + + if (is_sec_sym(sym)) { + sym = find_symbol_containing(reloc->sym->sec, offset); + if (!sym) + goto alts; + + offset -= sym->offset; + } + + checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name)); + checksum_update(func, insn, &offset, sizeof(offset)); + +alts: + for (alt = insn->alts; alt; alt = alt->next) { + struct alt_group *alt_group = alt->insn->alt_group; + + /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ + if (in_alt) + break; + in_alt = true; + + checksum_update(func, insn, &alt->type, sizeof(alt->type)); + + if (alt_group && alt_group->orig_group) { + struct instruction *alt_insn; + + checksum_update(func, insn, &alt_group->feature, sizeof(alt_group->feature)); + + for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { + checksum_update_insn(file, func, alt_insn); + if (!alt_group->last_insn || alt_insn == alt_group->last_insn) + break; + } + } else { + checksum_update_insn(file, func, alt->insn); + } + + in_alt = false; + } +} + +int calculate_checksums(struct objtool_file *file) +{ + struct instruction *insn; + struct symbol *func; + + if (checksum_debug_init(file)) + return -1; + + for_each_sym(file->elf, func) { + /* + * Skip cold subfunctions and aliases: they share the + * parent's checksum via func_for_each_insn() which + * follows func->cfunc into the cold subfunction. + */ + if (!is_func_sym(func) || is_cold_func(func) || + is_alias_sym(func) || !func->len) + continue; + + checksum_init(func); + + func_for_each_insn(file, func, insn) + checksum_update_insn(file, func, insn); + + checksum_finish(func); + } + return 0; +} + +int create_sym_checksum_section(struct objtool_file *file) +{ + struct section *sec; + struct symbol *sym; + unsigned int idx = 0; + struct sym_checksum *checksum; + size_t entsize = sizeof(struct sym_checksum); + + sec = find_section_by_name(file->elf, ".discard.sym_checksum"); + if (sec) { + if (!opts.dryrun) + WARN("file already has .discard.sym_checksum section, skipping"); + + return 0; + } + + for_each_sym(file->elf, sym) + if (sym->csum.checksum) + idx++; + + sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, + idx, idx); + if (!sec) + return -1; + + idx = 0; + for_each_sym(file->elf, sym) { + if (!sym->csum.checksum) + continue; + + if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize, + sym, 0, R_TEXT64)) + return -1; + + checksum = (struct sym_checksum *)sec->data->d_buf + idx; + checksum->addr = 0; /* reloc */ + checksum->checksum = sym->csum.checksum; + + mark_sec_changed(file->elf, sec, true); + + idx++; + } + + return 0; +} + +static const char * const klp_checksum_usage[] = { + "objtool klp checksum [] file.o", + NULL, +}; + +int cmd_klp_checksum(int argc, const char **argv) +{ + struct objtool_file *file; + int ret; + + const struct option options[] = { + OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "funcs", "enable checksum debug output"), + OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), + OPT_END(), + }; + + argc = parse_options(argc, argv, options, klp_checksum_usage, 0); + if (argc != 1) + usage_with_options(klp_checksum_usage, options); + + opts.checksum = true; + + objname = argv[0]; + + file = objtool_open_read(objname); + if (!file) + return 1; + + ret = decode_file(file); + if (ret) + goto out; + + ret = calculate_checksums(file); + if (ret) + goto out; + + ret = create_sym_checksum_section(file); + +out: + free_insns(file); + + if (ret) + return ret; + + if (!opts.dryrun && file->elf->changed && elf_write(file->elf)) + return 1; + + return elf_close(file->elf); +} diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 8728dda1e08c..95359ad336bb 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -166,7 +166,7 @@ static int read_sym_checksums(struct elf *elf) sec = find_section_by_name(elf, ".discard.sym_checksum"); if (!sec) { - ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool --checksum'?", + ERROR("'%s' missing .discard.sym_checksum section, file not processed by 'objtool klp checksum'?", elf->name); return -1; } From d4888d58041d1a61d66f2c81cb398f9685bc7576 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 13:09:34 -0700 Subject: [PATCH 042/167] klp-build: Use "objtool klp checksum" subcommand Use the new "objtool klp checksum" subcommand instead of injecting --checksum into every objtool invocation via OBJTOOL_ARGS during the kernel build. This decouples checksum generation from the build, running it in separate post-build passes, making the code (and the patch generation pipeline itself) more modular. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 95 +++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 30 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 215d2301510b..6103345cd2a9 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -37,10 +37,12 @@ OBJTOOL="$PWD/tools/objtool/objtool" CONFIG="$PWD/.config" TMP_DIR="$PWD/klp-tmp" -ORIG_DIR="$TMP_DIR/orig" -PATCHED_DIR="$TMP_DIR/patched" -DIFF_DIR="$TMP_DIR/diff" -KMOD_DIR="$TMP_DIR/kmod" +ORIG_DIR="$TMP_DIR/1-orig" +PATCHED_DIR="$TMP_DIR/2-patched" +ORIG_CSUM_DIR="$TMP_DIR/3-checksum-orig" +PATCHED_CSUM_DIR="$TMP_DIR/3-checksum-patched" +DIFF_DIR="$TMP_DIR/4-diff" +KMOD_DIR="$TMP_DIR/5-kmod" STASH_DIR="$TMP_DIR/stash" TIMESTAMP="$TMP_DIR/timestamp" @@ -136,10 +138,11 @@ Options: Advanced Options: -d, --debug Show symbol/reloc cloning decisions -S, --short-circuit=STEP Start at build step (requires prior --keep-tmp) - 1|orig Build original kernel (default) - 2|patched Build patched kernel - 3|diff Diff objects - 4|kmod Build patch module + 1|orig Build original kernel (default) + 2|patched Build patched kernel + 3|checksum Generate checksums + 4|diff Diff objects + 5|kmod Build patch module -T, --keep-tmp Preserve tmp dir on exit EOF @@ -203,10 +206,11 @@ process_args() { [[ ! -d "$TMP_DIR" ]] && die "--short-circuit requires preserved klp-tmp dir" keep_tmp=1 case "$2" in - 1 | orig) SHORT_CIRCUIT=1; ;; - 2 | patched) SHORT_CIRCUIT=2; ;; - 3 | diff) SHORT_CIRCUIT=3; ;; - 4 | mod) SHORT_CIRCUIT=4; ;; + 1 | orig) SHORT_CIRCUIT=1; ;; + 2 | patched) SHORT_CIRCUIT=2; ;; + 3 | checksum) SHORT_CIRCUIT=3; ;; + 4 | diff) SHORT_CIRCUIT=4; ;; + 5 | kmod) SHORT_CIRCUIT=5; ;; *) die "invalid short-circuit step '$2'" ;; esac shift 2 @@ -513,11 +517,8 @@ clean_kernel() { build_kernel() { local build="$1" local log="$TMP_DIR/build.log" - local objtool_args=() local cmd=() - objtool_args=("--checksum") - cmd=("make") # When a patch to a kernel module references a newly created unexported @@ -544,7 +545,6 @@ build_kernel() { fi cmd+=("-j$JOBS") cmd+=("KCFLAGS=-ffunction-sections -fdata-sections") - cmd+=("OBJTOOL_ARGS=${objtool_args[*]}") cmd+=("vmlinux") cmd+=("modules") @@ -574,7 +574,7 @@ copy_orig_objects() { find_objects | mapfile -t files - xtrace_save "copying orig objects" + xtrace_save "copying original objects" for _file in "${files[@]}"; do local rel_file="${_file/.ko/.o}" local file="$PWD/$rel_file" @@ -630,6 +630,35 @@ copy_patched_objects() { mv -f "$TMP_DIR/build.log" "$PATCHED_DIR" } +# Copy .o files to a separate directory and run "objtool klp checksum" on each +# copy. The checksums are written to a .discard.sym_checksum section. +# +# If match_dir is given, only process files which also exist there. +generate_checksums() { + local src_dir="$1" + local dest_dir="$2" + local match_dir="${3:-}" + local files=() + local file + + rm -rf "$dest_dir" + mkdir -p "$dest_dir" + + find "$src_dir" -type f -name "*.o" | mapfile -t files + for file in "${files[@]}"; do + local rel="${file#"$src_dir"/}" + local dest="$dest_dir/$rel" + + [[ -n "$match_dir" && ! -f "$match_dir/$rel" ]] && continue + + mkdir -p "$(dirname "$dest")" + cp -f "$file" "$dest" + "$OBJTOOL" klp checksum "$dest" + done + + touch "$dest_dir/.complete" +} + # Diff changed objects, writing output object to $DIFF_DIR diff_objects() { local log="$KLP_DIFF_LOG" @@ -639,16 +668,16 @@ diff_objects() { rm -rf "$DIFF_DIR" mkdir -p "$DIFF_DIR" - find "$PATCHED_DIR" -type f -name "*.o" | mapfile -t files + find "$PATCHED_CSUM_DIR" -type f -name "*.o" | mapfile -t files [[ ${#files[@]} -eq 0 ]] && die "no changes detected" [[ -v DEBUG_CLONE ]] && opts=("--debug") # Diff all changed objects for file in "${files[@]}"; do - local rel_file="${file#"$PATCHED_DIR"/}" + local rel_file="${file#"$PATCHED_CSUM_DIR"/}" local orig_file="$rel_file" - local patched_file="$PATCHED_DIR/$rel_file" + local patched_file="$PATCHED_CSUM_DIR/$rel_file" local out_file="$DIFF_DIR/$rel_file" local filter=() local cmd=() @@ -672,8 +701,8 @@ diff_objects() { fi ( - cd "$ORIG_DIR" - [[ -v VERBOSE ]] && echo "cd $ORIG_DIR && ${cmd[*]}" + cd "$ORIG_CSUM_DIR" + [[ -v VERBOSE ]] && echo "cd $ORIG_CSUM_DIR && ${cmd[*]}" "${cmd[@]}" \ 1> >(tee -a "$log") \ 2> >(tee -a "$log" | "${filter[@]}" >&2) || \ @@ -682,9 +711,9 @@ diff_objects() { done } -# For each changed object, run objtool with --debug-checksum to get the -# per-instruction checksums, and then diff those to find the first changed -# instruction for each function. +# For each changed object, run "objtool klp checksum" with --debug-checksum to +# get the per-instruction checksums, and then diff those to find the first +# changed instruction for each function. diff_checksums() { local orig_log="$ORIG_DIR/checksum.log" local patched_log="$PATCHED_DIR/checksum.log" @@ -709,8 +738,7 @@ diff_checksums() { done cmd=("$OBJTOOL") - cmd+=("--checksum") - cmd+=("--link") + cmd+=("klp" "checksum") cmd+=("--dry-run") for file in "${!funcs[@]}"; do @@ -719,11 +747,11 @@ diff_checksums() { ( cd "$ORIG_DIR" "${cmd[@]}" "$opt" "$file" &> "$orig_log" || \ - ( cat "$orig_log" >&2; die "objtool --debug-checksum failed" ) + ( cat "$orig_log" >&2; die "objtool klp checksum failed" ) cd "$PATCHED_DIR" "${cmd[@]}" "$opt" "$file" &> "$patched_log" || \ - ( cat "$patched_log" >&2; die "objtool --debug-checksum failed" ) + ( cat "$patched_log" >&2; die "objtool klp checksum failed" ) ) for func in ${funcs[$file]}; do @@ -861,6 +889,13 @@ if (( SHORT_CIRCUIT <= 2 )); then fi if (( SHORT_CIRCUIT <= 3 )); then + status "Generating original checksums" + generate_checksums "$ORIG_DIR" "$ORIG_CSUM_DIR" "$PATCHED_DIR" + status "Generating patched checksums" + generate_checksums "$PATCHED_DIR" "$PATCHED_CSUM_DIR" +fi + +if (( SHORT_CIRCUIT <= 4 )); then status "Diffing objects" diff_objects if [[ -v DIFF_CHECKSUM ]]; then @@ -869,7 +904,7 @@ if (( SHORT_CIRCUIT <= 3 )); then fi fi -if (( SHORT_CIRCUIT <= 4 )); then +if (( SHORT_CIRCUIT <= 5 )); then status "Building patch module: $OUTFILE" build_patch_module fi From 3b8e56b86faa3e44745aa764c2e1fd9dbbd16104 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 13:10:29 -0700 Subject: [PATCH 043/167] objtool/klp: Remove "objtool --checksum" The checksum functionality has been moved to "objtool klp checksum" which is now used by klp-build. Remove the now-dead --checksum and --debug-checksum options from the default objtool command. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 3 +++ tools/objtool/builtin-check.c | 17 +---------------- tools/objtool/check.c | 10 ---------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index 6103345cd2a9..c1475c003298 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -275,6 +275,9 @@ validate_config() { [[ "$CONFIG_AS_VERSION" -lt 200000 ]] && \ die "Clang assembler version < 20 not supported" + [[ -x "$OBJTOOL" ]] && "$OBJTOOL" klp 2>&1 | command grep -q "not implemented" && \ + die "objtool not built with KLP support; install xxhash-devel/libxxhash-dev (version >= 0.8) and recompile" + return 0 } diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index b780df513715..ec7f10a5ef19 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -73,7 +73,6 @@ static int parse_hacks(const struct option *opt, const char *str, int unset) static const struct option check_options[] = { OPT_GROUP("Actions:"), - OPT_BOOLEAN(0, "checksum", &opts.checksum, "generate per-function checksums"), OPT_BOOLEAN(0, "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"), OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"), OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks), @@ -95,7 +94,6 @@ static const struct option check_options[] = { OPT_GROUP("Options:"), OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"), OPT_BOOLEAN(0, "backup", &opts.backup, "create backup (.orig) file on warning/error"), - OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "funcs", "enable checksum debug output"), OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), OPT_BOOLEAN(0, "link", &opts.link, "object is a linked object"), OPT_BOOLEAN(0, "module", &opts.module, "object is part of a kernel module"), @@ -165,20 +163,7 @@ static bool opts_valid(void) return false; } -#ifndef BUILD_KLP - if (opts.checksum) { - ERROR("--checksum not supported; install xxhash-devel/libxxhash-dev (version >= 0.8) and recompile"); - return false; - } -#endif - - if (opts.debug_checksum && !opts.checksum) { - ERROR("--debug-checksum requires --checksum"); - return false; - } - - if (opts.checksum || - opts.disas || + if (opts.disas || opts.hack_jump_label || opts.hack_noinstr || opts.ibt || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 3e5d335d0e29..ae047be919c5 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include @@ -4946,15 +4945,6 @@ int check(struct objtool_file *file) if (opts.noabs) warnings += check_abs_references(file); - if (opts.checksum) { - ret = calculate_checksums(file); - if (ret) - goto out; - ret = create_sym_checksum_section(file); - if (ret) - goto out; - } - if (opts.orc && nr_insns) { ret = orc_create(file); if (ret) From 225d16dd510d92c8eaba8e6496cfaa7881a24827 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 17 Apr 2026 13:33:55 -0700 Subject: [PATCH 044/167] klp-build: Validate short-circuit prerequisites The --short-circuit option implicitly requires that certain directories are already in klp-tmp. Enforce that to prevent confusing errors. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- scripts/livepatch/klp-build | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index c1475c003298..c4a7acf8edc3 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -437,6 +437,20 @@ do_init() { # builds in pwd. [[ ! "$PWD" -ef "$SCRIPT_DIR/../.." ]] && die "please run from the kernel root directory" + if (( SHORT_CIRCUIT >= 2 )); then + [[ -f "$ORIG_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_DIR" + fi + if (( SHORT_CIRCUIT >= 3 )); then + [[ -f "$PATCHED_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_DIR" + fi + if (( SHORT_CIRCUIT >= 4 )); then + [[ -f "$ORIG_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $ORIG_CSUM_DIR" + [[ -f "$PATCHED_CSUM_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $PATCHED_CSUM_DIR" + fi + if (( SHORT_CIRCUIT >= 5 )); then + [[ -f "$DIFF_DIR/.complete" ]] || die "-S $SHORT_CIRCUIT requires completed $DIFF_DIR" + fi + (( SHORT_CIRCUIT <= 1 )) && rm -rf "$TMP_DIR" mkdir -p "$TMP_DIR" @@ -593,6 +607,7 @@ copy_orig_objects() { mv -f "$TMP_DIR/build.log" "$ORIG_DIR" touch "$TIMESTAMP" + touch "$ORIG_DIR/.complete" } # Copy all changed objects to $PATCHED_DIR @@ -631,6 +646,7 @@ copy_patched_objects() { (( found == 0 )) && die "no changes detected" mv -f "$TMP_DIR/build.log" "$PATCHED_DIR" + touch "$PATCHED_DIR/.complete" } # Copy .o files to a separate directory and run "objtool klp checksum" on each @@ -712,6 +728,8 @@ diff_objects() { die "objtool klp diff failed" ) done + + touch "$DIFF_DIR/.complete" } # For each changed object, run "objtool klp checksum" with --debug-checksum to From 873a2208ea31e822e0a5eea86b8a3fd07208db45 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 15 Apr 2026 22:49:53 -0700 Subject: [PATCH 045/167] objtool/klp: Calculate object checksums Start checksumming data objects in preparation for revamping the correlation algorithm. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/checksum.h | 44 ++++++++---- tools/objtool/include/objtool/warn.h | 29 ++++---- tools/objtool/klp-checksum.c | 89 +++++++++++++++++++----- tools/objtool/klp-diff.c | 2 +- 4 files changed, 117 insertions(+), 47 deletions(-) diff --git a/tools/objtool/include/objtool/checksum.h b/tools/objtool/include/objtool/checksum.h index be4eb7dfe6f2..d46293f54716 100644 --- a/tools/objtool/include/objtool/checksum.h +++ b/tools/objtool/include/objtool/checksum.h @@ -6,28 +6,44 @@ #ifdef BUILD_KLP -static inline void checksum_init(struct symbol *func) +static inline void checksum_init(struct symbol *sym) { - if (func && !func->csum.state) { - func->csum.state = XXH3_createState(); - XXH3_64bits_reset(func->csum.state); + if (sym && !sym->csum.state) { + sym->csum.state = XXH3_createState(); + XXH3_64bits_reset(sym->csum.state); } } -static inline void checksum_update(struct symbol *func, - struct instruction *insn, - const void *data, size_t size) +static inline void __checksum_update(struct symbol *sym, const void *data, + size_t size) { - XXH3_64bits_update(func->csum.state, data, size); - dbg_checksum(func, insn, XXH3_64bits_digest(func->csum.state)); + XXH3_64bits_update(sym->csum.state, data, size); } -static inline void checksum_finish(struct symbol *func) +static inline void __checksum_update_insn(struct symbol *sym, + struct instruction *insn, + const void *data, size_t size) { - if (func && func->csum.state) { - func->csum.checksum = XXH3_64bits_digest(func->csum.state); - XXH3_freeState(func->csum.state); - func->csum.state = NULL; + __checksum_update(sym, data, size); + dbg_checksum_insn(sym, insn, XXH3_64bits_digest(sym->csum.state)); +} + +static inline void __checksum_update_object(struct symbol *sym, + unsigned long offset, + const char *what, const void *data, + size_t size) +{ + __checksum_update(sym, &offset, sizeof(offset)); + __checksum_update(sym, data, size); + dbg_checksum_object(sym, offset, what, XXH3_64bits_digest(sym->csum.state)); +} + +static inline void checksum_finish(struct symbol *sym) +{ + if (sym && sym->csum.state) { + sym->csum.checksum = XXH3_64bits_digest(sym->csum.state); + XXH3_freeState(sym->csum.state); + sym->csum.state = NULL; } } diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index fa8b7d292e83..595ee8009667 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -130,10 +130,22 @@ static inline void unindent(int *unused) { indent--; } objname ? ": " : "", \ ##__VA_ARGS__) -#define dbg(args...) \ +#define dbg_checksum_insn(func, insn, checksum) \ ({ \ - if (unlikely(debug)) \ - __dbg(args); \ + if (unlikely(func->debug_checksum)) { \ + char *insn_off = offstr(insn->sec, insn->offset); \ + __dbg("checksum: %s(): %s %016llx", \ + func->name, insn_off, (unsigned long long)checksum);\ + free(insn_off); \ + } \ +}) + +#define dbg_checksum_object(sym, offset, what, checksum) \ +({ \ + if (unlikely(sym->debug_checksum)) \ + __dbg("checksum: %s+0x%lx: %s %016llx", \ + sym->name, offset, what, \ + (unsigned long long)checksum); \ }) #define __dbg_indent(format, ...) \ @@ -147,15 +159,4 @@ static inline void unindent(int *unused) { indent--; } __dbg_indent(args); \ indent++ -#define dbg_checksum(func, insn, checksum) \ -({ \ - if (unlikely(insn->sym && insn->sym->pfunc && \ - insn->sym->pfunc->debug_checksum)) { \ - char *insn_off = offstr(insn->sec, insn->offset); \ - __dbg("checksum: %s %s %016llx", \ - func->name, insn_off, (unsigned long long)checksum);\ - free(insn_off); \ - } \ -}) - #endif /* _WARN_H */ diff --git a/tools/objtool/klp-checksum.c b/tools/objtool/klp-checksum.c index e4a910f3211c..19653dbe109d 100644 --- a/tools/objtool/klp-checksum.c +++ b/tools/objtool/klp-checksum.c @@ -35,7 +35,7 @@ static int checksum_debug_init(struct objtool_file *file) *comma = '\0'; for_each_sym_by_name(file->elf, s, sym) { - if (!is_func_sym(sym)) + if (!is_func_sym(sym) && !is_object_sym(sym)) continue; sym->debug_checksum = 1; found = true; @@ -66,14 +66,14 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, if (insn->fake) return; - checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); + __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); if (!reloc) { struct symbol *call_dest = insn_call_dest(insn); if (call_dest) - checksum_update(func, insn, call_dest->demangled_name, - strlen(call_dest->demangled_name)); + __checksum_update_insn(func, insn, call_dest->demangled_name, + strlen(call_dest->demangled_name)); goto alts; } @@ -84,7 +84,7 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, char *str; str = sym->sec->data->d_buf + sym->offset + offset; - checksum_update(func, insn, str, strlen(str)); + __checksum_update_insn(func, insn, str, strlen(str)); goto alts; } @@ -96,8 +96,9 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, offset -= sym->offset; } - checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name)); - checksum_update(func, insn, &offset, sizeof(offset)); + __checksum_update_insn(func, insn, sym->demangled_name, + strlen(sym->demangled_name)); + __checksum_update_insn(func, insn, &offset, sizeof(offset)); alts: for (alt = insn->alts; alt; alt = alt->next) { @@ -108,12 +109,13 @@ alts: break; in_alt = true; - checksum_update(func, insn, &alt->type, sizeof(alt->type)); + __checksum_update_insn(func, insn, &alt->type, + sizeof(alt->type)); if (alt_group && alt_group->orig_group) { struct instruction *alt_insn; - checksum_update(func, insn, &alt_group->feature, sizeof(alt_group->feature)); + __checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature)); for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { checksum_update_insn(file, func, alt_insn); @@ -128,31 +130,82 @@ alts: } } +static void checksum_update_object(struct objtool_file *file, struct symbol *sym) +{ + struct reloc *reloc; + + __checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len)); + + if (sym->sec->data->d_buf) + __checksum_update_object(sym, 0, "data", + sym->sec->data->d_buf + sym->offset, + sym->len); + + sym_for_each_reloc(file->elf, sym, reloc) { + unsigned long sym_offset = reloc_offset(reloc) - sym->offset; + struct symbol *target = reloc->sym; + s64 offset; + + offset = reloc_addend(reloc); + + if (is_string_sec(target->sec)) { + char *str; + + str = target->sec->data->d_buf + target->offset + offset; + __checksum_update_object(sym, sym_offset, + "reloc string", str, strlen(str)); + continue; + } + + if (is_sec_sym(target)) { + target = find_symbol_containing(reloc->sym->sec, offset); + if (!target) + continue; + + offset -= target->offset; + } + + __checksum_update_object(sym, sym_offset, "reloc name", + target->demangled_name, + strlen(target->demangled_name)); + __checksum_update_object(sym, sym_offset, "reloc addend", + &offset, sizeof(offset)); + } +} + int calculate_checksums(struct objtool_file *file) { struct instruction *insn; - struct symbol *func; + struct symbol *sym; if (checksum_debug_init(file)) return -1; - for_each_sym(file->elf, func) { + for_each_sym(file->elf, sym) { + /* * Skip cold subfunctions and aliases: they share the * parent's checksum via func_for_each_insn() which * follows func->cfunc into the cold subfunction. */ - if (!is_func_sym(func) || is_cold_func(func) || - is_alias_sym(func) || !func->len) + if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len || + !sym->sec || !sym->sec->data) continue; - checksum_init(func); + if (is_func_sym(sym)) { + checksum_init(sym); + func_for_each_insn(file, sym, insn) + checksum_update_insn(file, sym, insn); + checksum_finish(sym); - func_for_each_insn(file, func, insn) - checksum_update_insn(file, func, insn); + } else if (is_object_sym(sym)) { + checksum_init(sym); + checksum_update_object(file, sym); + checksum_finish(sym); + } - checksum_finish(func); } + return 0; } @@ -213,7 +266,7 @@ int cmd_klp_checksum(int argc, const char **argv) int ret; const struct option options[] = { - OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "funcs", "enable checksum debug output"), + OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"), OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), OPT_END(), }; diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 95359ad336bb..5f13d759e02f 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -201,7 +201,7 @@ static int read_sym_checksums(struct elf *elf) return -1; } - if (is_func_sym(sym)) + if (is_func_sym(sym) || is_object_sym(sym)) sym->csum.checksum = sym_checksum->checksum; } From 6016dd33a10a89d7db9509124d678d38916ab337 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 9 Apr 2026 20:51:27 -0700 Subject: [PATCH 046/167] objtool/klp: Rewrite symbol correlation algorithm Rewrite the symbol correlation code, using a tiered list of deterministic strategies in a loop. For duplicately named symbols, each tier applies a filter with the goal of finding a 1:1 deterministic correlation between the original and patched version of the symbol. The three matching strategies are: find_twin(): A funnel of progressively tighter filters. Candidates with the same demangled name are counted at four levels: name, scope (local-vs-global), file (strict file association), and checksum (unchanged functions). The widest level that yields a 1:1 match wins, narrower levels are only tried when the wider level is ambiguous. find_twin_suffixed(): Uses already-correlated LLVM symbol pairs to map .llvm. suffixes from orig to patched. Because all promoted symbols from the same TU share the same hash, one correlated pair seeds the mapping for the entire TU. find_twin_positional(): Last resort, matches symbols by position among same-named candidates, similar to livepatch sympos. Used for data objects like __quirk variables where no deterministic filter can distinguish the candidates. Overall this works much better than the existing algorithm, particularly with LTO kernels. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 521 +++++++++++++++++++++++++++++---------- 1 file changed, 385 insertions(+), 136 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 5f13d759e02f..a9c993298b82 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -408,78 +408,358 @@ static bool dont_correlate(struct symbol *sym) is_special_section_aux(sym->sec); } -/* - * When there is no full name match, try match demangled_name. This would - * match original foo.llvm.123 to patched foo.llvm.456. - * - * Note that, in very rare cases, it is possible to have multiple - * foo.llvm. in the same kernel. When this happens, report error and - * fail the diff. - */ -static int find_global_symbol_by_demangled_name(struct elf *elf, struct symbol *sym, - struct symbol **out_sym) +static const char *llvm_suffix(const char *name) { - struct symbol *sym2, *result = NULL; - int count = 0; + return strstr(name, ".llvm."); +} - for_each_sym_by_demangled_name(elf, sym->demangled_name, sym2) { - if (is_local_sym(sym2) || sym2->twin) +static bool is_llvm_sym(struct symbol *sym) +{ + return llvm_suffix(sym->name); +} + +/* + * Determine if two symbols have compatible source file origins: + * + * - If both symbols are local, only return true if they belong to the same + * ELF file symbol. + * + * - If both symbols are global, always return true, as globals don't have + * file associations. + * + * - If they have different scopes, also return true, as the patch might have + * changed the symbol's scope. + * + * Works for both same-ELF (direct pointer compare) and cross-ELF + * (compare via file->twin) cases. + */ +static bool maybe_same_file(struct symbol *sym1, struct symbol *sym2) +{ + if (!sym1->file || !sym2->file) + return true; + if (sym1->file == sym2->file) + return true; + return sym1->file->twin == sym2->file; +} + +/* + * Similar to maybe_same_file(), but strict: no scope changes allowed. + * + * Works for both same-ELF (direct pointer compare) and cross-ELF + * (compare via file->twin) cases. + */ +static bool same_file(struct symbol *sym1, struct symbol *sym2) +{ + if (llvm_suffix(sym1->name) && llvm_suffix(sym2->name)) + return true; + if (!sym1->file && !sym2->file) + return true; + if (!sym1->file || !sym2->file) + return false; + if (sym1->file == sym2->file) + return true; + return sym1->file->twin == sym2->file; +} + +/* + * Is it a local symbol, or at least was it local in the translation unit + * before LLVM promoted it? + */ +static bool is_tu_local_sym(struct symbol *sym) +{ + return is_local_sym(sym) || is_llvm_sym(sym); +} + +/* + * Try to find sym1's twin in patched using deterministic matching. + * + * Multiple symbols can share a demangled name (e.g., static functions in + * different TUs). This function counts same-named candidates through a + * funnel of progressively tighter filters. Each level is a strict subset + * of the previous one. + * + * The widest level that yields a 1:1 match wins. Narrower levels are only + * needed when the wider level is ambiguous (count > 1). + * + * Candidates are pre-filtered by maybe_same_file(), which narrows most + * local symbols to their own TU. For example, 19 different static + * type_show() functions across vmlinux.o each see only one candidate after + * pre-filtering, so they match immediately at Level 1. + * + * Level 1 (name): Works when the demangled name is unique after + * pre-filtering. Handles most symbols: unique globals like copy_signal(), + * or per-TU locals like pcspkr_probe(). + * + * Level 2 (scope): Filters by local-vs-global (TU-local-vs-not). Example: + * parse_header() exists as both a static and a global function. Level 1 + * sees both (same demangled name), but Level 2 separates them by scope. + * + * Level 3 (file): Strict file matching via same_file(), which rejects scope + * changes. Example: LLVM-promoted foo.llvm.12345 (global, no FILE symbol) + * vs genuine local foo (has FILE symbol). Both are TU-local so Level 2 + * can't distinguish them, but same_file() rejects the pair because one has + * a file association and the other doesn't. + * + * Level 4 (checksum): Distinguishes by function checksum. Example: + * usb_devnode.llvm.AAA and usb_devnode.llvm.BBB are two LLVM-promoted + * functions from different TUs with the same demangled name. After a TU + * change, the .llvm. hashes change but the functions themselves may be + * unchanged. Level 4 matches each to the patched candidate with the + * same checksum. + */ +static struct symbol *find_twin(struct elfs *e, struct symbol *sym1) +{ + struct symbol *name_last = NULL, *scope_last = NULL, + *file_last = NULL, *csum_last = NULL; + unsigned int name_orig = 0, name_patched = 0; + unsigned int scope_orig = 0, scope_patched = 0; + unsigned int file_orig = 0, file_patched = 0; + unsigned int csum_orig = 0, csum_patched = 0; + struct symbol *sym2, *match = NULL; + + /* Count orig candidates */ + for_each_sym_by_demangled_name(e->orig, sym1->demangled_name, sym2) { + if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + (!maybe_same_file(sym1, sym2))) continue; - count++; - result = sym2; + /* Level 1: name match (widest filter) */ + name_orig++; + + /* Level 2: scope (scope changes allowed) */ + if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2)) + continue; + scope_orig++; + + /* Level 3: file (scope changes disallowed) */ + if (!same_file(sym1, sym2)) + continue; + file_orig++; + + /* Level 4: checksum (unchanged symbols) */ + if (sym1->len != sym2->len || !sym1->csum.checksum || + sym1->csum.checksum != sym2->csum.checksum) + continue; + csum_orig++; } - if (count > 1) { - ERROR("Multiple (%d) correlation candidates for %s", count, sym->name); - return -1; + /* Count patched candidates */ + for_each_sym_by_demangled_name(e->patched, sym1->demangled_name, sym2) { + if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + !maybe_same_file(sym1, sym2)) + continue; + + /* Level 1 */ + name_patched++; + name_last = sym2; + + /* Level 2 */ + if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2)) + continue; + scope_patched++; + scope_last = sym2; + + /* Level 3 */ + if (!same_file(sym1, sym2)) + continue; + file_patched++; + file_last = sym2; + + /* Level 4 */ + if (sym1->len != sym2->len || !sym1->csum.checksum || + sym1->csum.checksum != sym2->csum.checksum) + continue; + csum_patched++; + csum_last = sym2; + } + + /* Return the widest level that yields a unique (1:1) match */ + if (name_orig == 1 && name_patched == 1) + match = name_last; + else if (scope_orig == 1 && scope_patched == 1) + match = scope_last; + else if (file_orig == 1 && file_patched == 1) + match = file_last; + else if (csum_orig == 1 && csum_patched == 1) + match = csum_last; + + return match; +} + +struct llvm_suffix_pair { + struct hlist_node hash; + const char *orig; + const char *patched; +}; + +static DECLARE_HASHTABLE(suffix_map, 7); + +/* + * Build a mapping of known orig-to-patched LLVM suffixes based on + * already-correlated symbol pairs. All promoted symbols from the same TU + * share the same .llvm. suffix, so one correlated pair seeds the map + * for the entire TU. + */ +static int update_suffix_map(struct elf *elf) +{ + struct llvm_suffix_pair *entry; + struct symbol *sym; + + for_each_sym(elf, sym) { + const char *s1, *s2; + bool found; + + if (!sym->twin) + continue; + + s1 = llvm_suffix(sym->name); + s2 = llvm_suffix(sym->twin->name); + + if (!s1 || !s2) + continue; + + found = false; + hash_for_each_possible(suffix_map, entry, hash, str_hash(s1)) { + if (!strcmp(entry->orig, s1)) { + found = true; + break; + } + } + if (found) + continue; + + entry = calloc(1, sizeof(*entry)); + if (!entry) { + ERROR_GLIBC("calloc"); + return -1; + } + + entry->orig = s1; + entry->patched = s2; + hash_add(suffix_map, &entry->hash, str_hash(s1)); } - *out_sym = result; return 0; } /* - * For each symbol in the original kernel, find its corresponding "twin" in the - * patched kernel. + * Match by translating the symbol's .llvm. suffix through the suffix + * map to find the corresponding hash suffix for the patched object. + * + * Example: In the original kernel, TU drivers/base/core.c contains + * foo.llvm.12345 and bar.llvm.12345 (same TU, same hash). After patching, + * they become foo.llvm.67890 and bar.llvm.67890. If foo was already + * correlated by find_twin() (e.g., unique by name), the suffix map records + * .llvm.12345 -> .llvm.67890. When processing bar.llvm.12345, this + * function looks up .llvm.12345, gets .llvm.67890, constructs the name + * bar.llvm.67890, and finds the match. + */ +static struct symbol *find_twin_suffixed(struct elf *elf, struct symbol *sym1) +{ + const char *suffix, *patched_suffix = NULL; + struct symbol *sym2, *match = NULL; + char name[SYM_NAME_LEN]; + struct llvm_suffix_pair *entry; + int count = 0; + + suffix = llvm_suffix(sym1->name); + if (!suffix) + return NULL; + + hash_for_each_possible(suffix_map, entry, hash, str_hash(suffix)) { + if (!strcmp(entry->orig, suffix)) { + patched_suffix = entry->patched; + break; + } + } + if (!patched_suffix) + return NULL; + + if (snprintf_check(name, SYM_NAME_LEN, "%s%s", + sym1->demangled_name, patched_suffix)) + return NULL; + + for_each_sym_by_name(elf, name, sym2) { + if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2)) + continue; + count++; + match = sym2; + } + + if (count == 1) + return match; + + return NULL; +} + +/* + * Last-resort positional matching. + * + * Finds a symbol with the same position in the symbol table among + * same-demangled-name candidates, similar to livepatch sympos. Note that + * LLVM-promoted symbols are globals, which come after locals in the symbol + * table, so we have to be careful not to compare different scopes. + * + * Example: arch/x86/events/intel/core.c defines many __quirk variables via + * X86_MATCH_*() macros. In the symbol table they appear as __quirk.90, + * __quirk.97, __quirk.101, etc., all with demangled name __quirk, same + * scope, and same FILE symbol. No deterministic filter can distinguish + * them, so they're matched by position: the 1st __quirk in orig matches the + * 1st in patched, the 2nd matches the 2nd, etc. + * + * This is less deterministic than the other strategies, so it's done last. + */ +static struct symbol *find_twin_positional(struct elfs *e, struct symbol *sym1) +{ + unsigned int idx_orig = 0, idx_patched = 0; + unsigned int sym1_pos = 0; + struct symbol *sym2, *match = NULL; + + for_each_sym_by_demangled_name(e->orig, sym1->demangled_name, sym2) { + if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + !maybe_same_file(sym1, sym2)) + continue; + if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2) || + is_llvm_sym(sym1) != is_llvm_sym(sym2)) + continue; + if (sym1 == sym2) + sym1_pos = idx_orig; + idx_orig++; + } + + for_each_sym_by_demangled_name(e->patched, sym1->demangled_name, sym2) { + if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + !maybe_same_file(sym1, sym2)) + continue; + if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2) || + is_llvm_sym(sym1) != is_llvm_sym(sym2)) + continue; + if (idx_patched == sym1_pos) + match = sym2; + idx_patched++; + } + + if (idx_orig != idx_patched) + return NULL; + + return match; +} + +/* + * Correlate symbols between the orig and patched objects. This is a + * prerequisite for detecting changed functions, as well as for properly + * translating relocations so they point to the correct symbol. */ static int correlate_symbols(struct elfs *e) { struct symbol *file1_sym, *file2_sym; struct symbol *sym1, *sym2; + bool progress; + /* Correlate FILE symbols */ file1_sym = first_file_symbol(e->orig); file2_sym = first_file_symbol(e->patched); - /* - * Correlate any locals before the first FILE symbol. This has been - * seen when LTO inexplicably strips the initramfs_data.o FILE symbol - * due to the file only containing data and no code. - */ - for_each_sym(e->orig, sym1) { - if (sym1 == file1_sym || !is_local_sym(sym1)) - break; - - if (dont_correlate(sym1)) - continue; - - for_each_sym(e->patched, sym2) { - if (sym2 == file2_sym || !is_local_sym(sym2)) - break; - - if (sym2->twin || dont_correlate(sym2)) - continue; - - if (strcmp(sym1->demangled_name, sym2->demangled_name)) - continue; - - sym1->twin = sym2; - sym2->twin = sym1; - break; - } - } - - /* Correlate locals after the first FILE symbol */ for (; ; file1_sym = next_file_symbol(e->orig, file1_sym), file2_sym = next_file_symbol(e->patched, file2_sym)) { @@ -503,92 +783,52 @@ static int correlate_symbols(struct elfs *e) file1_sym->twin = file2_sym; file2_sym->twin = file1_sym; - - sym1 = file1_sym; - - for_each_sym_continue(e->orig, sym1) { - if (is_file_sym(sym1) || !is_local_sym(sym1)) - break; - - if (dont_correlate(sym1)) - continue; - - sym2 = file2_sym; - for_each_sym_continue(e->patched, sym2) { - if (is_file_sym(sym2) || !is_local_sym(sym2)) - break; - - if (sym2->twin || dont_correlate(sym2)) - continue; - - if (strcmp(sym1->demangled_name, sym2->demangled_name)) - continue; - - sym1->twin = sym2; - sym2->twin = sym1; - break; - } - } } - /* Correlate globals */ - for_each_sym(e->orig, sym1) { - if (sym1->bind == STB_LOCAL) - continue; - - sym2 = find_global_symbol_by_name(e->patched, sym1->name); - if (sym2 && !sym2->twin) { - sym1->twin = sym2; - sym2->twin = sym1; - } - } /* - * Correlate globals with demangled_name. - * A separate loop is needed because we want to finish all the - * full name correlations first. + * Correlate in two phases: loop deterministic levels until no more + * progress, then use positional fallback for the rest. This prevents + * the nondeterministic positional matching from stealing symbols that + * have deterministic matches. */ + hash_init(suffix_map); + do { + progress = false; + for_each_sym(e->orig, sym1) { + if (sym1->twin || dont_correlate(sym1)) + continue; + sym2 = find_twin(e, sym1); + if (!sym2) + continue; + sym1->twin = sym2; + sym2->twin = sym1; + progress = true; + } + + if (update_suffix_map(e->orig)) + return -1; + + for_each_sym(e->orig, sym1) { + if (sym1->twin || dont_correlate(sym1)) + continue; + sym2 = find_twin_suffixed(e->patched, sym1); + if (!sym2) + continue; + sym1->twin = sym2; + sym2->twin = sym1; + progress = true; + } + } while (progress); + for_each_sym(e->orig, sym1) { - if (sym1->bind == STB_LOCAL || sym1->twin) + if (sym1->twin || dont_correlate(sym1)) continue; - - if (find_global_symbol_by_demangled_name(e->patched, sym1, &sym2)) - return -1; - - if (sym2 && !sym2->twin) { - sym1->twin = sym2; - sym2->twin = sym1; - } - } - - /* Correlate original locals with patched globals */ - for_each_sym(e->orig, sym1) { - if (sym1->twin || dont_correlate(sym1) || !is_local_sym(sym1)) + sym2 = find_twin_positional(e, sym1); + if (!sym2) continue; - - sym2 = find_global_symbol_by_name(e->patched, sym1->name); - if (!sym2 && find_global_symbol_by_demangled_name(e->patched, sym1, &sym2)) - return -1; - - if (sym2 && !sym2->twin) { - sym1->twin = sym2; - sym2->twin = sym1; - } - } - - /* Correlate original globals with patched locals */ - for_each_sym(e->patched, sym2) { - if (sym2->twin || dont_correlate(sym2) || !is_local_sym(sym2)) - continue; - - sym1 = find_global_symbol_by_name(e->orig, sym2->name); - if (!sym1 && find_global_symbol_by_demangled_name(e->orig, sym2, &sym1)) - return -1; - - if (sym1 && !sym1->twin) { - sym2->twin = sym1; - sym1->twin = sym2; - } + sym1->twin = sym2; + sym2->twin = sym1; } for_each_sym(e->orig, sym1) { @@ -800,19 +1040,24 @@ static void mark_included_function(struct symbol *func) */ static int mark_changed_functions(struct elfs *e) { - struct symbol *sym_orig, *patched_sym; + struct symbol *orig_sym, *patched_sym; bool changed = false; /* Find changed functions */ - for_each_sym(e->orig, sym_orig) { - if (!is_func_sym(sym_orig) || dont_correlate(sym_orig)) + for_each_sym(e->orig, orig_sym) { + if (dont_correlate(orig_sym)) continue; - patched_sym = sym_orig->twin; + patched_sym = orig_sym->twin; if (!patched_sym) continue; - if (sym_orig->csum.checksum != patched_sym->csum.checksum) { + if (orig_sym->csum.checksum != patched_sym->csum.checksum) { + if (!is_func_sym(orig_sym)) { + ERROR("changed data: %s", orig_sym->name); + return -1; + } + patched_sym->changed = 1; mark_included_function(patched_sym); changed = true; @@ -837,7 +1082,7 @@ static int mark_changed_functions(struct elfs *e) printf("%s: changed function: %s\n", objname, patched_sym->name); } - return !changed ? -1 : 0; + return !changed ? 1 : 0; } static int clone_included_functions(struct elfs *e) @@ -1870,6 +2115,7 @@ static int copy_import_ns(struct elfs *e) int cmd_klp_diff(int argc, const char **argv) { struct elfs e = {0}; + int ret; argc = parse_options(argc, argv, klp_diff_options, klp_diff_usage, 0); if (argc != 3) @@ -1896,7 +2142,10 @@ int cmd_klp_diff(int argc, const char **argv) if (correlate_symbols(&e)) return -1; - if (mark_changed_functions(&e)) + ret = mark_changed_functions(&e); + if (ret < 0) + return -1; + if (ret > 0) return 0; e.out = elf_create_file(&e.orig->ehdr, argv[2]); From 5d6a03eeb7173179ef3c64e31806d21966a99875 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 20 Apr 2026 22:58:37 -0700 Subject: [PATCH 047/167] objtool/klp: Add correlation debugging output Add debugging messages to show how duplicate symbols get correlated, and split the --debug feature into --debug-correlate and --debug-clone. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/warn.h | 16 +++++++---- tools/objtool/klp-diff.c | 42 ++++++++++++++++++++++------ tools/objtool/objtool.c | 3 -- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index 595ee8009667..a9936d60980c 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -109,7 +109,7 @@ static inline char *offstr(struct section *sec, unsigned long offset) #define ERROR_FUNC(sec, offset, format, ...) __WARN_FUNC(ERROR_STR, sec, offset, format, ##__VA_ARGS__) #define ERROR_INSN(insn, format, ...) ERROR_FUNC(insn->sec, insn->offset, format, ##__VA_ARGS__) -extern bool debug; +extern bool debug, debug_correlate, debug_clone; extern int indent; static inline void unindent(int *unused) { indent--; } @@ -148,15 +148,21 @@ static inline void unindent(int *unused) { indent--; } (unsigned long long)checksum); \ }) -#define __dbg_indent(format, ...) \ +#define dbg_correlate(args...) \ ({ \ - if (unlikely(debug)) \ + if (unlikely(debug_correlate)) \ + __dbg(args); \ +}) + +#define __dbg_clone(format, ...) \ +({ \ + if (unlikely(debug_clone)) \ __dbg("%*s" format, indent * 8, "", ##__VA_ARGS__); \ }) -#define dbg_indent(args...) \ +#define dbg_clone(args...) \ int __cleanup(unindent) __dummy_##__COUNTER__; \ - __dbg_indent(args); \ + __dbg_clone(args); \ indent++ #endif /* _WARN_H */ diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index a9c993298b82..ed3bf1c55001 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -33,6 +33,9 @@ struct export { char *mod, *sym; }; +bool debug, debug_correlate, debug_clone; +int indent; + static const char * const klp_diff_usage[] = { "objtool klp diff [] ", NULL, @@ -40,7 +43,9 @@ static const char * const klp_diff_usage[] = { static const struct option klp_diff_options[] = { OPT_GROUP("Options:"), - OPT_BOOLEAN('d', "debug", &debug, "enable debug output"), + OPT_BOOLEAN('d', "debug", &debug, "enable all debug output"), + OPT_BOOLEAN(0, "debug-correlate", &debug_correlate, "enable correlation debug output"), + OPT_BOOLEAN(0, "debug-clone", &debug_clone, "enable cloning debug output"), OPT_END(), }; @@ -583,6 +588,14 @@ static struct symbol *find_twin(struct elfs *e, struct symbol *sym1) else if (csum_orig == 1 && csum_patched == 1) match = csum_last; + if (!match) + return NULL; + + if (name_orig != 1 || name_patched != 1) + dbg_correlate("find_twin(): %s%s -> %s%s", + sym1->name, is_func_sym(sym1) ? "()" : "", + match->name, is_func_sym(match) ? "()" : ""); + return match; } @@ -686,10 +699,14 @@ static struct symbol *find_twin_suffixed(struct elf *elf, struct symbol *sym1) match = sym2; } - if (count == 1) - return match; + if (count != 1) + return NULL; - return NULL; + dbg_correlate("find_suffixed_twin(): %s%s -> %s%s", + sym1->name, is_func_sym(sym1) ? "()" : "", + match->name, is_func_sym(match) ? "()" : ""); + + return match; } /* @@ -742,6 +759,10 @@ static struct symbol *find_twin_positional(struct elfs *e, struct symbol *sym1) if (idx_orig != idx_patched) return NULL; + dbg_correlate("find_twin_positional(): %s%s -> %s%s", + sym1->name, is_func_sym(sym1) ? "()" : "", + match->name, is_func_sym(match) ? "()" : ""); + return match; } @@ -998,7 +1019,7 @@ static struct symbol *clone_symbol(struct elfs *e, struct symbol *patched_sym, if (patched_sym->clone) return patched_sym->clone; - dbg_indent("%s%s", patched_sym->name, data_too ? " [+DATA]" : ""); + dbg_clone("%s%s", patched_sym->name, data_too ? " [+DATA]" : ""); /* Make sure the prefix gets cloned first */ if (is_func_sym(patched_sym) && data_too) { @@ -1375,7 +1396,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, klp_sym = find_symbol_by_name(e->out, sym_name); if (!klp_sym) { - __dbg_indent("%s", sym_name); + __dbg_clone("%s", sym_name); /* STB_WEAK: avoid modpost undefined symbol warnings */ klp_sym = elf_create_symbol(e->out, sym_name, NULL, @@ -1426,7 +1447,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, } #define dbg_clone_reloc(sec, offset, patched_sym, addend, export, klp) \ - dbg_indent("%s+0x%lx: %s%s0x%lx [%s%s%s%s%s%s]", \ + dbg_clone("%s+0x%lx: %s%s0x%lx [%s%s%s%s%s%s]", \ sec->name, offset, patched_sym->name, \ addend >= 0 ? "+" : "-", labs(addend), \ sym_type(patched_sym), \ @@ -1481,7 +1502,7 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc, if (is_string_sec(patched_sym->sec)) { const char *str = patched_sym->sec->data->d_buf + addend; - __dbg_indent("\"%s\"", escape_str(str)); + __dbg_clone("\"%s\"", escape_str(str)); addend = elf_add_string(e->out, out_sym->sec, str); if (addend == -1) @@ -2121,6 +2142,11 @@ int cmd_klp_diff(int argc, const char **argv) if (argc != 3) usage_with_options(klp_diff_usage, klp_diff_options); + if (debug) { + debug_correlate = true; + debug_clone = true; + } + objname = argv[0]; e.orig = elf_open_read(argv[0], O_RDONLY); diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c index 1c3622117c33..a4e139dee7e9 100644 --- a/tools/objtool/objtool.c +++ b/tools/objtool/objtool.c @@ -16,9 +16,6 @@ #include #include -bool debug; -int indent; - static struct objtool_file file; struct objtool_file *objtool_open_read(const char *filename) From 3ee67629b2b7fbe270f6c21d9a95219bbd214630 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sat, 4 Apr 2026 11:30:37 -0700 Subject: [PATCH 048/167] objtool: Add insn_sym() helper Alternative replacement instructions awkwardly have insn->sym set to the function they get patched to rather than the symbol (or rather lack thereof) they belong to in the file. This makes it difficult to know where a given instruction actually lives. Add a new insn_sym() helper which preserves the existing semantic of insn->sym. Rename insn->sym to insn->_sym, which contains the actual ELF binary symbol (or NULL, for alternative replacements) an instruction lives in. The private insn->_sym value will be needed for a subsequent patch. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 31 ++++++++++++--------------- tools/objtool/disas.c | 22 +++++++++---------- tools/objtool/include/objtool/check.h | 20 +++++++++++++++-- tools/objtool/include/objtool/warn.h | 6 +++--- tools/objtool/trace.c | 8 +++---- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index ae047be919c5..4242582e941a 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -495,7 +495,7 @@ static int decode_instructions(struct objtool_file *file) } sym_for_each_insn(file, func, insn) { - insn->sym = func; + insn->_sym = func; if (is_func_sym(func) && insn->type == INSN_ENDBR && list_empty(&insn->call_node)) { @@ -859,15 +859,14 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file) list_for_each_entry(insn, &file->endbr_list, call_node) { int *site = (int *)sec->data->d_buf + idx; - struct symbol *sym = insn->sym; + struct symbol *func = insn_func(insn); *site = 0; - if (opts.module && sym && is_func_sym(sym) && - insn->offset == sym->offset && - (!strcmp(sym->name, "init_module") || - !strcmp(sym->name, "cleanup_module"))) { + if (opts.module && func && insn->offset == func->offset && + (!strcmp(func->name, "init_module") || + !strcmp(func->name, "cleanup_module"))) { ERROR("%s(): Magic init_module() function name is deprecated, use module_init(fn) instead", - sym->name); + func->name); return -1; } @@ -1581,7 +1580,7 @@ static int add_jump_destinations(struct objtool_file *file) } if (!dest_sym || is_sec_sym(dest_sym)) { - dest_sym = dest_insn->sym; + dest_sym = insn_sym(dest_insn); if (!dest_sym) goto set_jump_dest; } @@ -1597,7 +1596,7 @@ static int add_jump_destinations(struct objtool_file *file) continue; } - if (!insn->sym || insn->sym->pfunc == dest_sym->pfunc) + if (!insn_sym(insn) || insn_sym(insn)->pfunc == dest_sym->pfunc) goto set_jump_dest; /* @@ -1770,7 +1769,6 @@ static int handle_group_alt(struct objtool_file *file, nop->offset = special_alt->new_off + special_alt->new_len; nop->len = special_alt->orig_len - special_alt->new_len; nop->type = INSN_NOP; - nop->sym = orig_insn->sym; nop->alt_group = new_alt_group; nop->fake = 1; } @@ -1789,7 +1787,6 @@ static int handle_group_alt(struct objtool_file *file, last_new_insn = insn; - insn->sym = orig_insn->sym; insn->alt_group = new_alt_group; /* @@ -2432,12 +2429,12 @@ static int __annotate_late(struct objtool_file *file, int type, struct instructi break; case ANNOTYPE_NOCFI: - sym = insn->sym; + sym = insn_sym(insn); if (!sym) { ERROR_INSN(insn, "dodgy NOCFI annotation"); return -1; } - insn->sym->nocfi = 1; + sym->nocfi = 1; break; default: @@ -2538,7 +2535,7 @@ static void mark_holes(struct objtool_file *file) * favour of a regular symbol, but leaves the code in place. */ for_each_insn(file, insn) { - if (insn->sym || !find_symbol_hole_containing(insn->sec, insn->offset)) { + if (insn_sym(insn) || !find_symbol_hole_containing(insn->sec, insn->offset)) { in_hole = false; continue; } @@ -2982,7 +2979,7 @@ static int update_cfi_state(struct instruction *insn, } if (op->dest.reg == CFI_BP && op->src.reg == CFI_SP && - insn->sym->frame_pointer) { + insn_sym(insn)->frame_pointer) { /* addi.d fp,sp,imm on LoongArch */ if (cfa->base == CFI_SP && cfa->offset == op->src.offset) { cfa->base = CFI_BP; @@ -2994,7 +2991,7 @@ static int update_cfi_state(struct instruction *insn, if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) { /* addi.d sp,fp,imm on LoongArch */ if (cfa->base == CFI_BP && cfa->offset == 0) { - if (insn->sym->frame_pointer) { + if (insn_sym(insn)->frame_pointer) { cfa->base = CFI_SP; cfa->offset = -op->src.offset; } @@ -4171,7 +4168,7 @@ static int validate_retpoline(struct objtool_file *file) * broken. */ list_for_each_entry(insn, &file->retpoline_call_list, call_node) { - struct symbol *sym = insn->sym; + struct symbol *sym = insn_sym(insn); if (sym && (is_notype_sym(sym) || is_func_sym(sym)) && !sym->nocfi) { diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c index 59090234af19..e6a54a83605c 100644 --- a/tools/objtool/disas.c +++ b/tools/objtool/disas.c @@ -210,7 +210,7 @@ static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo) offset = addr - alt_group->first_insn->offset; addr = orig_first_insn->offset + offset; - sym = orig_first_insn->sym; + sym = insn_sym(orig_first_insn); disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo); @@ -222,15 +222,13 @@ static void disas_print_addr_noreloc(bfd_vma addr, { struct disas_context *dctx = dinfo->application_data; struct instruction *insn = dctx->insn; - struct symbol *sym = NULL; + struct symbol *sym = insn_sym(insn); if (disas_print_addr_alt(addr, dinfo)) return; - if (insn->sym && addr >= insn->sym->offset && - addr < insn->sym->offset + insn->sym->len) { - sym = insn->sym; - } + if (sym && (addr < sym->offset || addr >= sym->offset + sym->len)) + sym = NULL; disas_print_addr_sym(insn->sec, sym, addr, dinfo); } @@ -291,9 +289,9 @@ static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo) * up. So check it first. */ jump_dest = insn->jump_dest; - if (jump_dest && jump_dest->sym && jump_dest->offset == addr) { + if (jump_dest && insn_sym(jump_dest) && jump_dest->offset == addr) { if (!disas_print_addr_alt(addr, dinfo)) - disas_print_addr_sym(jump_dest->sec, jump_dest->sym, + disas_print_addr_sym(jump_dest->sec, insn_sym(jump_dest), addr, dinfo); return; } @@ -768,8 +766,8 @@ static int disas_alt_jump(struct disas_alt *dalt) if (orig_insn->len == 5) suffix[0] = 'q'; str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix, - dest_insn->offset, dest_insn->sym->name, - dest_insn->offset - dest_insn->sym->offset); + dest_insn->offset, insn_sym(dest_insn)->name, + dest_insn->offset - insn_sym(dest_insn)->offset); nops = 0; } else { str = strfmt("nop%d", orig_insn->len); @@ -794,8 +792,8 @@ static int disas_alt_extable(struct disas_alt *dalt) alt_insn = dalt->alt->insn; str = strfmt("resume at 0x%lx <%s+0x%lx>", - alt_insn->offset, alt_insn->sym->name, - alt_insn->offset - alt_insn->sym->offset); + alt_insn->offset, insn_sym(alt_insn)->name, + alt_insn->offset - insn_sym(alt_insn)->offset); if (!str) return -1; diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index eea64728d39b..fe08205d8eb1 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -94,14 +94,30 @@ struct instruction { }; }; struct alternative *alts; - struct symbol *sym; + struct symbol *_sym; struct stack_op *stack_ops; struct cfi_state *cfi; }; +/* + * Return the symbol associated with an instruction. For alternative + * replacements, return the symbol of the original code being replaced rather + * than NULL. insn->_sym reflects the actual location in the ELF file. + */ +static inline struct symbol *insn_sym(struct instruction *insn) +{ + struct symbol *sym = insn->_sym; + + if ((!sym || !is_func_sym(sym)) && + insn->alt_group && insn->alt_group->orig_group) + sym = insn->alt_group->orig_group->first_insn->_sym; + + return sym; +} + static inline struct symbol *insn_func(struct instruction *insn) { - struct symbol *sym = insn->sym; + struct symbol *sym = insn_sym(insn); if (sym && sym->type != STT_FUNC) sym = NULL; diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h index a9936d60980c..870e147f3a56 100644 --- a/tools/objtool/include/objtool/warn.h +++ b/tools/objtool/include/objtool/warn.h @@ -77,13 +77,13 @@ static inline char *offstr(struct section *sec, unsigned long offset) #define WARN_INSN(insn, format, ...) \ ({ \ struct instruction *_insn = (insn); \ - if (!_insn->sym || !_insn->sym->warned) { \ + if (!insn_sym(_insn) || !insn_sym(_insn)->warned) { \ WARN_FUNC(_insn->sec, _insn->offset, format, \ ##__VA_ARGS__); \ BT_INSN(_insn, ""); \ } \ - if (_insn->sym) \ - _insn->sym->warned = 1; \ + if (insn_sym(_insn)) \ + insn_sym(_insn)->warned = 1; \ }) #define BT_INSN(insn, format, ...) \ diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c index 5dec44dab781..61c6aa302bc3 100644 --- a/tools/objtool/trace.c +++ b/tools/objtool/trace.c @@ -169,8 +169,8 @@ void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt, */ TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s for instruction at 0x%lx <%s+0x%lx>", alt_name, - orig_insn->offset, orig_insn->sym->name, - orig_insn->offset - orig_insn->sym->offset); + orig_insn->offset, insn_sym(orig_insn)->name, + orig_insn->offset - insn_sym(orig_insn)->offset); } else { TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s", alt_name); } @@ -185,8 +185,8 @@ void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt, if (orig_insn->type == INSN_NOP) { suffix[0] = (orig_insn->len == 5) ? 'q' : '\0'; TRACE_ADDR(orig_insn, "jmp%-3s %lx <%s+0x%lx>", suffix, - alt_insn->offset, alt_insn->sym->name, - alt_insn->offset - alt_insn->sym->offset); + alt_insn->offset, insn_sym(alt_insn)->name, + alt_insn->offset - insn_sym(alt_insn)->offset); } else { TRACE_ADDR(orig_insn, "nop%d", orig_insn->len); trace_depth--; From cca84cb12908f1cfcecaef80a7692017e2d6a945 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 3 Apr 2026 11:57:02 -0700 Subject: [PATCH 049/167] objtool/klp: Fix position-dependent checksums for non-relocated jumps/calls When computing klp checksums, instructions with non-relocated jump/call destination offsets are problematic because the offset values can change when surrounding code has moved, causing the function to be incorrectly marked as changed. Specifically, that includes jumps from alternatives to the end of the alternative, which from objtool's perspective are jumps to the end of the alternative instruction block in the original function. Note that 'jump_dest' jumps don't include sibling calls (those use call_dest), nor do they include jumps to/from .cold sub functions (those are cross-section and need a reloc). Fix it by hashing the opcode bytes (excluding the immediate operand) along with a position-independent representation of the destination. For calls, use the function name, and for jumps, use the destination's offset within its function. [Note the "9 bit hole" comment was wrong: it has been 8 bits since commit 70589843b36f ("objtool: Add option to trace function validation") added the 'trace' field. Adding the 4-bit 'immediate_len' field now leaves a 4-bit hole.] Fixes: 0d83da43b1e1 ("objtool/klp: Add --checksum option to generate per-function checksums") Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/arch/x86/decode.c | 17 ++++++++- tools/objtool/include/objtool/arch.h | 3 ++ tools/objtool/include/objtool/check.h | 3 +- tools/objtool/klp-checksum.c | 53 ++++++++++++++++++++++++--- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 350b8ee6e776..1b387d5a195b 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -805,14 +805,27 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec break; } - if (ins.immediate.nbytes) + if (ins.immediate.nbytes) { insn->immediate = ins.immediate.value; - else if (ins.displacement.nbytes) + insn->immediate_len = ins.immediate.nbytes; + } else if (ins.displacement.nbytes) { insn->immediate = ins.displacement.value; + insn->immediate_len = ins.displacement.nbytes; + } return 0; } +size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn, + unsigned char *buf) +{ + size_t len; + + len = insn->len - insn->immediate_len; + memcpy(buf, insn->sec->data->d_buf + insn->offset, len); + return len; +} + void arch_initial_func_cfi_state(struct cfi_init_state *state) { int i; diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h index 8866158975fc..96d828a8401f 100644 --- a/tools/objtool/include/objtool/arch.h +++ b/tools/objtool/include/objtool/arch.h @@ -79,6 +79,9 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec unsigned long offset, unsigned int maxlen, struct instruction *insn); +size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn, + unsigned char *buf); + bool arch_callee_saved_reg(unsigned char reg); unsigned long arch_jump_destination(struct instruction *insn); diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h index fe08205d8eb1..063f5985fecd 100644 --- a/tools/objtool/include/objtool/check.h +++ b/tools/objtool/include/objtool/check.h @@ -68,6 +68,7 @@ struct instruction { s8 instr; u32 idx : INSN_CHUNK_BITS, + immediate_len : 4, dead_end : 1, ignore_alts : 1, hint : 1, @@ -81,7 +82,7 @@ struct instruction { hole : 1, fake : 1, trace : 1; - /* 9 bit hole */ + /* 4 bit hole */ struct alt_group *alt_group; struct instruction *jump_dest; diff --git a/tools/objtool/klp-checksum.c b/tools/objtool/klp-checksum.c index 19653dbe109d..b8e47f28997e 100644 --- a/tools/objtool/klp-checksum.c +++ b/tools/objtool/klp-checksum.c @@ -66,17 +66,58 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func, if (insn->fake) return; - __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); - if (!reloc) { struct symbol *call_dest = insn_call_dest(insn); + struct instruction *jump_dest = insn->jump_dest; - if (call_dest) - __checksum_update_insn(func, insn, call_dest->demangled_name, - strlen(call_dest->demangled_name)); - goto alts; + /* + * For a jump/call non-relocated dest offset embedded in the + * instruction, the offset may vary due to changes in + * surrounding code. Just hash the opcode and a + * position-independent representation of the destination. + */ + + if (call_dest || jump_dest) { + unsigned char buf[16]; + size_t len; + + len = arch_jump_opcode_bytes(file, insn, buf); + __checksum_update_insn(func, insn, buf, len); + + if (call_dest) { + __checksum_update_insn(func, insn, call_dest->demangled_name, + strlen(call_dest->demangled_name)); + + } else if (jump_dest) { + struct symbol *dest_sym; + unsigned long offset; + + /* + * use insn->_sym instead of insn_sym() here. + * For alternative replacements, the latter + * would give the function of the code being + * replaced. + */ + dest_sym = jump_dest->_sym; + if (!dest_sym) + goto alts; + + __checksum_update_insn(func, insn, dest_sym->demangled_name, + strlen(dest_sym->demangled_name)); + + offset = jump_dest->offset - dest_sym->offset; + __checksum_update_insn(func, insn, &offset, sizeof(offset)); + } + + goto alts; + } } + __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); + + if (!reloc) + goto alts; + sym = reloc->sym; offset = arch_insn_adjusted_addend(insn, reloc); From fc0bb9915bce0c333f918ca76958d804ccd79f89 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 23 Apr 2026 15:53:17 -0700 Subject: [PATCH 050/167] objtool: Grow __cfi_* prefix symbols for all CFI+CALL_PADDING For all CONFIG_CFI+CONFIG_CALL_PADDING configs, for C functions, the __cfi_ symbols only cover the 5-byte kCFI type hash. After that there also N bytes of NOP padding between the hash and the function entry which aren't associated with any symbol. The NOPs can be replaced with actual code at runtime. Without a symbol, unwinders and tooling have no way of knowing where those bytes belong. Grow the existing __cfi_* symbols to fill that gap. Note that assembly functions with SYM_TYPED_FUNC_START() aren't affected by this issue, their __cfi_ symbols also cover the padding. Also, CONFIG_PREFIX_SYMBOLS has no reason to exist: CONFIG_CALL_PADDING is what causes the compiler to emit NOP padding before function entry (via -fpatchable-function-entry), so it's the right condition for creating prefix symbols. Remove CONFIG_PREFIX_SYMBOLS, as it's no longer needed. Simplify the LONGEST_SYM_KUNIT_TEST dependency accordingly. Rework objtool's arguments a bit to handle the variety of prefix/cfi-related cases. Suggested-by: Peter Zijlstra Signed-off-by: Josh Poimboeuf --- arch/x86/Kconfig | 4 -- lib/Kconfig.debug | 2 +- scripts/Makefile.lib | 7 +++- tools/objtool/builtin-check.c | 15 +++++++- tools/objtool/check.c | 49 ++++++++++++++++++++----- tools/objtool/elf.c | 20 ++++++++++ tools/objtool/include/objtool/builtin.h | 7 ++-- tools/objtool/include/objtool/elf.h | 1 + 8 files changed, 84 insertions(+), 21 deletions(-) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index f3f7cb01d69d..3eb3c48d764a 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2437,10 +2437,6 @@ config CALL_THUNKS def_bool n select CALL_PADDING -config PREFIX_SYMBOLS - def_bool y - depends on CALL_PADDING && !CFI - menuconfig CPU_MITIGATIONS bool "Mitigations for CPU vulnerabilities" default y diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 8ff5adcfe1e0..4f7496b3268d 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3070,7 +3070,7 @@ config FORTIFY_KUNIT_TEST config LONGEST_SYM_KUNIT_TEST tristate "Test the longest symbol possible" if !KUNIT_ALL_TESTS depends on KUNIT && KPROBES - depends on !PREFIX_SYMBOLS && !CFI && !GCOV_KERNEL + depends on !CALL_PADDING && !CFI && !GCOV_KERNEL default KUNIT_ALL_TESTS help Tests the longest symbol possible diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 0718e39cedda..7e216d82e988 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -187,7 +187,11 @@ objtool-args-$(CONFIG_HAVE_JUMP_LABEL_HACK) += --hacks=jump_label objtool-args-$(CONFIG_HAVE_NOINSTR_HACK) += --hacks=noinstr objtool-args-$(CONFIG_MITIGATION_CALL_DEPTH_TRACKING) += --hacks=skylake objtool-args-$(CONFIG_X86_KERNEL_IBT) += --ibt -objtool-args-$(CONFIG_FINEIBT) += --cfi +objtool-args-$(CONFIG_CALL_PADDING) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES) +ifdef CONFIG_CALL_PADDING +objtool-args-$(CONFIG_CFI) += --cfi +objtool-args-$(CONFIG_FINEIBT) += --fineibt +endif objtool-args-$(CONFIG_FTRACE_MCOUNT_USE_OBJTOOL) += --mcount ifdef CONFIG_FTRACE_MCOUNT_USE_OBJTOOL objtool-args-$(CONFIG_HAVE_OBJTOOL_NOP_MCOUNT) += --mnop @@ -200,7 +204,6 @@ objtool-args-$(CONFIG_STACK_VALIDATION) += --stackval objtool-args-$(CONFIG_HAVE_STATIC_CALL_INLINE) += --static-call objtool-args-$(CONFIG_HAVE_UACCESS_VALIDATION) += --uaccess objtool-args-$(or $(CONFIG_GCOV_KERNEL),$(CONFIG_KCOV)) += --no-unreachable -objtool-args-$(CONFIG_PREFIX_SYMBOLS) += --prefix=$(CONFIG_FUNCTION_PADDING_BYTES) objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror objtool-args = $(objtool-args-y) \ diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index ec7f10a5ef19..118c3de2f293 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -73,7 +73,6 @@ static int parse_hacks(const struct option *opt, const char *str, int unset) static const struct option check_options[] = { OPT_GROUP("Actions:"), - OPT_BOOLEAN(0, "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"), OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"), OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), @@ -84,7 +83,7 @@ static const struct option check_options[] = { OPT_BOOLEAN('r', "retpoline", &opts.retpoline, "validate and annotate retpoline usage"), OPT_BOOLEAN(0, "rethunk", &opts.rethunk, "validate and annotate rethunk usage"), OPT_BOOLEAN(0, "unret", &opts.unret, "validate entry unret placement"), - OPT_INTEGER(0, "prefix", &opts.prefix, "generate prefix symbols"), + OPT_INTEGER(0, "prefix", &opts.prefix, "generate or grow prefix symbols for N-byte function padding"), OPT_BOOLEAN('l', "sls", &opts.sls, "validate straight-line-speculation mitigations"), OPT_BOOLEAN('s', "stackval", &opts.stackval, "validate frame pointer rules"), OPT_BOOLEAN('t', "static-call", &opts.static_call, "annotate static calls"), @@ -92,6 +91,8 @@ static const struct option check_options[] = { OPT_CALLBACK_OPTARG(0, "dump", NULL, NULL, "orc", "dump metadata", parse_dump), OPT_GROUP("Options:"), + OPT_BOOLEAN(0, "cfi", &opts.cfi, "grow kCFI preamble symbols (use with --prefix)"), + OPT_BOOLEAN(0, "fineibt", &opts.fineibt, "create .cfi_sites section for FineIBT"), OPT_BOOLEAN(0, "backtrace", &opts.backtrace, "unwind on error"), OPT_BOOLEAN(0, "backup", &opts.backup, "create backup (.orig) file on warning/error"), OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), @@ -163,6 +164,16 @@ static bool opts_valid(void) return false; } + if (opts.cfi && !opts.prefix) { + ERROR("--cfi requires --prefix"); + return false; + } + + if (opts.fineibt && !opts.cfi) { + ERROR("--fineibt requires --cfi"); + return false; + } + if (opts.disas || opts.hack_jump_label || opts.hack_noinstr || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 4242582e941a..73e99bdfaaf6 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -881,6 +881,31 @@ static int create_ibt_endbr_seal_sections(struct objtool_file *file) return 0; } +/* +* Grow __cfi_ symbols to fill the NOP gap between the 'mov , %rax' and +* the start of the function. +*/ +static int grow_cfi_symbols(struct objtool_file *file) +{ + struct symbol *sym; + + for_each_sym(file->elf, sym) { + if (!is_func_sym(sym) || !strstarts(sym->name, "__cfi_") || + sym->len != 5) + continue; + + if (!find_func_by_offset(sym->sec, sym->offset + sym->len + opts.prefix)) + continue; + + sym->len += opts.prefix; + sym->sym.st_size = sym->len; + if (elf_write_symbol(file->elf, sym)) + return -1; + } + + return 0; +} + static int create_cfi_sections(struct objtool_file *file) { struct section *sec; @@ -4903,12 +4928,6 @@ int check(struct objtool_file *file) goto out; } - if (opts.cfi) { - ret = create_cfi_sections(file); - if (ret) - goto out; - } - if (opts.rethunk) { ret = create_return_sites_sections(file); if (ret) @@ -4928,9 +4947,21 @@ int check(struct objtool_file *file) } if (opts.prefix) { - ret = create_prefix_symbols(file); - if (ret) - goto out; + if (!opts.cfi) { + ret = create_prefix_symbols(file); + if (ret) + goto out; + } else { + ret = grow_cfi_symbols(file); + if (ret) + goto out; + + if (opts.fineibt) { + ret = create_cfi_sections(file); + if (ret) + goto out; + } + } } if (opts.ibt) { diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index d9cee8d5d9e8..33c95a74a51b 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -997,6 +997,26 @@ non_local: return sym; } +int elf_write_symbol(struct elf *elf, struct symbol *sym) +{ + struct section *symtab, *symtab_shndx; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("no .symtab"); + return -1; + } + + symtab_shndx = find_section_by_name(elf, ".symtab_shndx"); + + if (elf_update_symbol(elf, symtab, symtab_shndx, sym)) + return -1; + + mark_sec_changed(elf, symtab, true); + + return 0; +} + struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec) { struct symbol *sym = calloc(1, sizeof(*sym)); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index b9e229ed4dc0..e844e9c82b7b 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -9,8 +9,8 @@ struct opts { /* actions: */ - bool cfi; bool checksum; + const char *disas; bool dump_orc; bool hack_jump_label; bool hack_noinstr; @@ -20,6 +20,7 @@ struct opts { bool noabs; bool noinstr; bool orc; + int prefix; bool retpoline; bool rethunk; bool unret; @@ -27,14 +28,14 @@ struct opts { bool stackval; bool static_call; bool uaccess; - int prefix; - const char *disas; /* options: */ bool backtrace; bool backup; + bool cfi; const char *debug_checksum; bool dryrun; + bool fineibt; bool link; bool mnop; bool module; diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index e452784df702..305183f30a33 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -199,6 +199,7 @@ struct reloc *elf_init_reloc_data_sym(struct elf *elf, struct section *sec, struct symbol *sym, s64 addend); +int elf_write_symbol(struct elf *elf, struct symbol *sym); int elf_write_insn(struct elf *elf, struct section *sec, unsigned long offset, unsigned int len, const char *insn); From f7ceffd21a8a59ba1e92078750eef86e628dea80 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 27 Apr 2026 21:15:52 -0700 Subject: [PATCH 051/167] objtool/klp: Fix kCFI prefix finding/cloning With CFI+CALL_PADDING, Clang places .Ltmp labels at the start of the NOP padding (offset 5) between the __cfi_ prefix and the function entry point. get_func_prefix() only checks the immediately previous symbol, so the intervening .Ltmp label causes it to miss the __cfi_ prefix symbol. This results in klp-diff not cloning the kCFI type hash into the livepatch module, causing a CFI failure at module load when calling callback functions through indirect calls: CFI failure at __klp_enable_patch+0xab/0x140 (target: pre_patch_callback+0x0/0x80 [livepatch_combined]; expected type: 0xde073954) Instead of walking backward through the section's symbol list, just use find_func_containing() for the byte before the function. This works now that __cfi_ symbols are being grown by objtool to fill the padding. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/elf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index 305183f30a33..fccf72cbd343 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -539,10 +539,10 @@ static inline struct symbol *get_func_prefix(struct symbol *func) { struct symbol *prev; - if (!is_func_sym(func)) + if (!is_func_sym(func) || !func->offset) return NULL; - prev = sec_prev_sym(func); + prev = find_func_containing(func->sec, func->offset - 1); if (prev && is_prefix_func(prev)) return prev; From fe6a87e0abac45b20664377545760901d4537ea8 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 7 Apr 2026 20:56:48 -0700 Subject: [PATCH 052/167] objtool: Improve and simplify prefix symbol detection Only create prefix symbols for functions that have __patchable_function_entries entries, since those are the only C functions where prefix NOPs are intentional. This both simplifies the detection and makes it more accurate. Note that assembly functions using SYM_TYPED_FUNC_START() can also have prefixed NOPs, but that macro already creates their __cfi_ symbols. Signed-off-by: Josh Poimboeuf --- tools/objtool/check.c | 90 ++++++++++--------------------------------- 1 file changed, 21 insertions(+), 69 deletions(-) diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 73e99bdfaaf6..10b18cf9c360 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -4296,17 +4296,6 @@ static bool ignore_unreachable_insn(struct objtool_file *file, struct instructio * For FineIBT or kCFI, a certain number of bytes preceding the function may be * NOPs. Those NOPs may be rewritten at runtime and executed, so give them a * proper function name: __pfx_. - * - * The NOPs may not exist for the following cases: - * - * - compiler cloned functions (*.cold, *.part0, etc) - * - asm functions created with inline asm or without SYM_FUNC_START() - * - * Also, the function may already have a prefix from a previous objtool run - * (livepatch extracted functions, or manually running objtool multiple times). - * - * So return 0 if the NOPs are missing or the function already has a prefix - * symbol. */ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func) { @@ -4314,10 +4303,6 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func) char name[SYM_NAME_LEN]; struct cfi_state *cfi; - if (!is_func_sym(func) || is_prefix_func(func) || is_cold_func(func) || - func->static_call_tramp) - return 0; - if ((strlen(func->name) + sizeof("__pfx_") > SYM_NAME_LEN)) { WARN("%s: symbol name too long, can't create __pfx_ symbol", func->name); @@ -4327,59 +4312,21 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func) if (snprintf_check(name, SYM_NAME_LEN, "__pfx_%s", func->name)) return -1; - if (file->klp) { - struct symbol *pfx; - - pfx = find_symbol_by_offset(func->sec, func->offset - opts.prefix); - if (pfx && is_prefix_func(pfx) && !strcmp(pfx->name, name)) - return 0; - } - - insn = find_insn(file, func->sec, func->offset); - if (!insn) { - WARN("%s: can't find starting instruction", func->name); + if (!elf_create_symbol(file->elf, name, func->sec, + GELF_ST_BIND(func->sym.st_info), + GELF_ST_TYPE(func->sym.st_info), + func->offset - opts.prefix, opts.prefix)) return -1; - } - - for (prev = prev_insn_same_sec(file, insn); - prev; - prev = prev_insn_same_sec(file, prev)) { - u64 offset; - - if (prev->type != INSN_NOP) - return 0; - - offset = func->offset - prev->offset; - - if (offset > opts.prefix) - return 0; - - if (offset < opts.prefix) - continue; - - if (!elf_create_symbol(file->elf, name, func->sec, - GELF_ST_BIND(func->sym.st_info), - GELF_ST_TYPE(func->sym.st_info), - prev->offset, opts.prefix)) - return -1; - - break; - } - - if (!prev) - return 0; - - if (!insn->cfi) { - /* - * This can happen if stack validation isn't enabled or the - * function is annotated with STACK_FRAME_NON_STANDARD. - */ - return 0; - } /* Propagate insn->cfi to the prefix code */ + insn = find_insn(file, func->sec, func->offset); + if (!insn || !insn->cfi) + return 0; + cfi = cfi_hash_find_or_add(insn->cfi); - for (; prev != insn; prev = next_insn_same_sec(file, prev)) + for (prev = find_insn(file, func->sec, func->offset - opts.prefix); + prev && prev != insn; + prev = next_insn_same_sec(file, prev)) prev->cfi = cfi; return 0; @@ -4387,15 +4334,20 @@ static int create_prefix_symbol(struct objtool_file *file, struct symbol *func) static int create_prefix_symbols(struct objtool_file *file) { - struct section *sec; + struct section *pfe_sec; struct symbol *func; + struct reloc *reloc; - for_each_sec(file->elf, sec) { - if (!is_text_sec(sec)) + for_each_sec(file->elf, pfe_sec) { + if (strcmp(pfe_sec->name, "__patchable_function_entries")) + continue; + if (!pfe_sec->rsec) continue; - sec_for_each_sym(sec, func) { - if (create_prefix_symbol(file, func)) + for_each_reloc(pfe_sec->rsec, reloc) { + func = find_func_by_offset(reloc->sym->sec, + reloc->sym->offset + reloc_addend(reloc) + opts.prefix); + if (func && create_prefix_symbol(file, func)) return -1; } } From 2d3bb398861ad3ddbf87959cc2e34a7001f0b3ad Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 19 Apr 2026 21:06:28 -0700 Subject: [PATCH 053/167] objtool/klp: Cache dont_correlate() result Cache the dont_correlate() result once per symbol at the start of correlate_symbols(). This reduces klp diff time on an arm64 LTO vmlinux.o from 2m51s to 35s. Acked-by: Song Liu Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/elf.h | 1 + tools/objtool/klp-diff.c | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index fccf72cbd343..d9c44df9cc76 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -96,6 +96,7 @@ struct symbol { u8 changed : 1; u8 included : 1; u8 klp : 1; + u8 dont_correlate : 1; struct list_head pv_target; struct reloc *relocs; struct section *group_sec; diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index ed3bf1c55001..f8787d7d1454 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -524,7 +524,7 @@ static struct symbol *find_twin(struct elfs *e, struct symbol *sym1) /* Count orig candidates */ for_each_sym_by_demangled_name(e->orig, sym1->demangled_name, sym2) { - if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate || (!maybe_same_file(sym1, sym2))) continue; @@ -550,7 +550,7 @@ static struct symbol *find_twin(struct elfs *e, struct symbol *sym1) /* Count patched candidates */ for_each_sym_by_demangled_name(e->patched, sym1->demangled_name, sym2) { - if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate || !maybe_same_file(sym1, sym2)) continue; @@ -693,7 +693,7 @@ static struct symbol *find_twin_suffixed(struct elf *elf, struct symbol *sym1) return NULL; for_each_sym_by_name(elf, name, sym2) { - if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2)) + if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate) continue; count++; match = sym2; @@ -733,7 +733,7 @@ static struct symbol *find_twin_positional(struct elfs *e, struct symbol *sym1) struct symbol *sym2, *match = NULL; for_each_sym_by_demangled_name(e->orig, sym1->demangled_name, sym2) { - if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate || !maybe_same_file(sym1, sym2)) continue; if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2) || @@ -745,7 +745,7 @@ static struct symbol *find_twin_positional(struct elfs *e, struct symbol *sym1) } for_each_sym_by_demangled_name(e->patched, sym1->demangled_name, sym2) { - if (sym2->twin || sym1->type != sym2->type || dont_correlate(sym2) || + if (sym2->twin || sym1->type != sym2->type || sym2->dont_correlate || !maybe_same_file(sym1, sym2)) continue; if (is_tu_local_sym(sym1) != is_tu_local_sym(sym2) || @@ -777,6 +777,11 @@ static int correlate_symbols(struct elfs *e) struct symbol *sym1, *sym2; bool progress; + for_each_sym(e->orig, sym1) + sym1->dont_correlate = dont_correlate(sym1); + for_each_sym(e->patched, sym2) + sym2->dont_correlate = dont_correlate(sym2); + /* Correlate FILE symbols */ file1_sym = first_file_symbol(e->orig); file2_sym = first_file_symbol(e->patched); @@ -817,7 +822,7 @@ static int correlate_symbols(struct elfs *e) do { progress = false; for_each_sym(e->orig, sym1) { - if (sym1->twin || dont_correlate(sym1)) + if (sym1->twin || sym1->dont_correlate) continue; sym2 = find_twin(e, sym1); if (!sym2) @@ -831,7 +836,7 @@ static int correlate_symbols(struct elfs *e) return -1; for_each_sym(e->orig, sym1) { - if (sym1->twin || dont_correlate(sym1)) + if (sym1->twin || sym1->dont_correlate) continue; sym2 = find_twin_suffixed(e->patched, sym1); if (!sym2) @@ -843,7 +848,7 @@ static int correlate_symbols(struct elfs *e) } while (progress); for_each_sym(e->orig, sym1) { - if (sym1->twin || dont_correlate(sym1)) + if (sym1->twin || sym1->dont_correlate) continue; sym2 = find_twin_positional(e, sym1); if (!sym2) @@ -853,7 +858,7 @@ static int correlate_symbols(struct elfs *e) } for_each_sym(e->orig, sym1) { - if (sym1->twin || dont_correlate(sym1)) + if (sym1->twin || sym1->dont_correlate) continue; WARN("no correlation: %s", sym1->name); } @@ -1066,7 +1071,7 @@ static int mark_changed_functions(struct elfs *e) /* Find changed functions */ for_each_sym(e->orig, orig_sym) { - if (dont_correlate(orig_sym)) + if (orig_sym->dont_correlate) continue; patched_sym = orig_sym->twin; @@ -1087,7 +1092,7 @@ static int mark_changed_functions(struct elfs *e) /* Find added functions and print them */ for_each_sym(e->patched, patched_sym) { - if (!is_func_sym(patched_sym) || dont_correlate(patched_sym)) + if (!is_func_sym(patched_sym) || patched_sym->dont_correlate) continue; if (!patched_sym->twin) { @@ -1193,7 +1198,7 @@ static bool klp_reloc_needed(struct reloc *patched_reloc) struct export *export; /* no external symbol to reference */ - if (dont_correlate(patched_sym)) + if (patched_sym->dont_correlate) return false; /* For included functions, a regular reloc will do. */ From 5c3cdc74af25fc7f64a3ed260b4f6eb8313a3b75 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Thu, 30 Apr 2026 08:25:58 +0800 Subject: [PATCH 054/167] perf/x86/intel: Consolidate MSR_IA32_PERF_CFG_C tracking Both Auto Counter Reload (ACR) and Architectural PEBS use the PERF_CFG_C MSRs to configure event behavior. Currently, the driver maintains two independent variables acr_cfg_c and cfg_c_val to cache the values intended for these MSRs. Using separate variables to track a single hardware register state is error-prone and can lead to configuration conflicts. Consolidate the tracking into a single cfg_c_val variable to ensure a unified and consistent view of the PERF_CFG_C MSR state. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260430002558.712334-6-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 13 +++++++------ arch/x86/events/perf_event.h | 4 +--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index dd1e3aa75ee9..2b05587c84f7 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3169,10 +3169,10 @@ static void intel_pmu_config_acr(int idx, u64 mask, u32 reload) wrmsrl(msr_b + msr_offset, mask); cpuc->acr_cfg_b[idx] = mask; } - /* Only need to update the reload value when there is a valid config value. */ - if (mask && cpuc->acr_cfg_c[idx] != reload) { + /* Only update CFG_C reload when ACR is actively enabled (mask != 0) */ + if (mask && ((cpuc->cfg_c_val[idx] & ARCH_PEBS_RELOAD) != reload)) { wrmsrl(msr_c + msr_offset, reload); - cpuc->acr_cfg_c[idx] = reload; + cpuc->cfg_c_val[idx] = reload; } } @@ -3198,14 +3198,15 @@ static void intel_pmu_enable_event_ext(struct perf_event *event) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct hw_perf_event *hwc = &event->hw; - union arch_pebs_index old, new; - struct arch_pebs_cap cap; u64 ext = 0; - cap = hybrid(cpuc->pmu, arch_pebs_cap); + if (is_acr_event_group(event)) + ext |= (-hwc->sample_period) & ARCH_PEBS_RELOAD; if (event->attr.precise_ip) { u64 pebs_data_cfg = intel_get_arch_pebs_data_config(event); + struct arch_pebs_cap cap = hybrid(cpuc->pmu, arch_pebs_cap); + union arch_pebs_index old, new; ext |= ARCH_PEBS_EN; if (hwc->flags & PERF_X86_EVENT_AUTO_RELOAD) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 524668dcf4cc..40d6fe0afc4a 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -322,10 +322,8 @@ struct cpu_hw_events { u64 fixed_ctrl_val; u64 active_fixed_ctrl_val; - /* Intel ACR configuration */ + /* Intel ACR/arch-PEBS configuration */ u64 acr_cfg_b[X86_PMC_IDX_MAX]; - u64 acr_cfg_c[X86_PMC_IDX_MAX]; - /* Cached CFG_C values */ u64 cfg_c_val[X86_PMC_IDX_MAX]; /* From b00192d78bb4f8b85d3545c7f86722610f088554 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 30 Apr 2026 16:39:27 -0700 Subject: [PATCH 055/167] locking/barrier: Use correct parameter names Use the correct parameter names in kernel-doc comments to avoid warnings: Warning: include/asm-generic/barrier.h:245 function parameter 'cond_expr' not described in 'smp_cond_load_relaxed' Warning: include/asm-generic/barrier.h:267 function parameter 'cond_expr' not described in 'smp_cond_load_acquire' Signed-off-by: Randy Dunlap Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260430233927.2620050-1-rdunlap@infradead.org --- include/asm-generic/barrier.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/asm-generic/barrier.h b/include/asm-generic/barrier.h index d4f581c1e21d..b99cb57dfccc 100644 --- a/include/asm-generic/barrier.h +++ b/include/asm-generic/barrier.h @@ -235,7 +235,7 @@ do { \ /** * smp_cond_load_relaxed() - (Spin) wait for cond with no ordering guarantees * @ptr: pointer to the variable to wait on - * @cond: boolean expression to wait for + * @cond_expr: boolean expression to wait for * * Equivalent to using READ_ONCE() on the condition variable. * @@ -259,7 +259,7 @@ do { \ /** * smp_cond_load_acquire() - (Spin) wait for cond with ACQUIRE ordering * @ptr: pointer to the variable to wait on - * @cond: boolean expression to wait for + * @cond_expr: boolean expression to wait for * * Equivalent to using smp_load_acquire() on the condition variable but employs * the control dependency of the wait to reduce the barrier on many platforms. From 89976cd73739dcb73745705a63ccc67a8be26cdf Mon Sep 17 00:00:00 2001 From: Aniket Gattani Date: Sun, 3 May 2026 21:22:03 +0000 Subject: [PATCH 056/167] sched/membarrier: Use per-CPU mutexes for targeted commands Currently, the membarrier system call uses a single global mutex (`membarrier_ipi_mutex`) to serialize expedited commands. This causes significant contention on large systems when multiple threads invoke membarrier concurrently, even if they target different CPUs. This contention becomes critical when combined with CFS bandwidth throttling/unthrottling, during which interrupts can be disabled for relatively long periods on target CPUs. If membarrier is waiting for a response from such a CPU, it holds the global mutex, blocking all other membarrier calls on the system. This cascade effect can lead to hard lockups when thousands of threads stall waiting for the mutex. Optimize `MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ` when a specific CPU is targeted by introducing per-CPU mutexes. Broadcast commands and commands without a specific CPU target continue to use the global mutex. This prevents the cascade lockup scenario. As measured by the stress test introduced in the subsequent patch, on an AMD Turin machine with 384 CPUs (2 NUMA nodes with SMT=2), this optimization yields 200x more throughput. Signed-off-by: Aniket Gattani Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260503212205.3714217-2-aniketgattani@google.com --- kernel/sched/membarrier.c | 77 ++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/kernel/sched/membarrier.c b/kernel/sched/membarrier.c index 623445603725..3d88e900a17f 100644 --- a/kernel/sched/membarrier.c +++ b/kernel/sched/membarrier.c @@ -164,8 +164,26 @@ | MEMBARRIER_PRIVATE_EXPEDITED_RSEQ_BITMASK \ | MEMBARRIER_CMD_GET_REGISTRATIONS) +/* + * Scoped guard for memory barriers on entry and exit. + * Matches memory barriers before & after rq->curr modification in scheduler. + */ +DEFINE_LOCK_GUARD_0(mb, smp_mb(), smp_mb()) static DEFINE_MUTEX(membarrier_ipi_mutex); +static DEFINE_PER_CPU(struct mutex, membarrier_cpu_mutexes); + #define SERIALIZE_IPI() guard(mutex)(&membarrier_ipi_mutex) +#define SERIALIZE_IPI_CPU(cpu_id) guard(mutex)(&per_cpu(membarrier_cpu_mutexes, cpu_id)) + +static int __init membarrier_init(void) +{ + int i; + + for_each_possible_cpu(i) + mutex_init(&per_cpu(membarrier_cpu_mutexes, i)); + return 0; +} +core_initcall(membarrier_init); static void ipi_mb(void *info) { @@ -315,7 +333,6 @@ static int membarrier_global_expedited(void) static int membarrier_private_expedited(int flags, int cpu_id) { - cpumask_var_t tmpmask; struct mm_struct *mm = current->mm; smp_call_func_t ipi_func = ipi_mb; @@ -352,30 +369,45 @@ static int membarrier_private_expedited(int flags, int cpu_id) * On RISC-V, this barrier pairing is also needed for the * SYNC_CORE command when switching between processes, cf. * the inline comments in membarrier_arch_switch_mm(). + * + * Memory barrier on the caller thread _after_ we finished + * waiting for the last IPI. Matches memory barriers before + * rq->curr modification in scheduler. */ - smp_mb(); /* system call entry is not a mb. */ - - if (cpu_id < 0 && !zalloc_cpumask_var(&tmpmask, GFP_KERNEL)) - return -ENOMEM; - - SERIALIZE_IPI(); - cpus_read_lock(); - + guard(mb)(); if (cpu_id >= 0) { + if (cpu_id >= nr_cpu_ids || !cpu_possible(cpu_id)) + return 0; + + SERIALIZE_IPI_CPU(cpu_id); + guard(cpus_read_lock)(); struct task_struct *p; - if (cpu_id >= nr_cpu_ids || !cpu_online(cpu_id)) - goto out; + if (!cpu_online(cpu_id)) + return 0; + rcu_read_lock(); p = rcu_dereference(cpu_rq(cpu_id)->curr); if (!p || p->mm != mm) { rcu_read_unlock(); - goto out; + return 0; } rcu_read_unlock(); + /* + * smp_call_function_single() will call ipi_func() if cpu_id + * is the calling CPU. + */ + smp_call_function_single(cpu_id, ipi_func, NULL, 1); } else { + cpumask_var_t __free(free_cpumask_var) tmpmask = CPUMASK_VAR_NULL; int cpu; + if (!zalloc_cpumask_var(&tmpmask, GFP_KERNEL)) + return -ENOMEM; + + SERIALIZE_IPI(); + guard(cpus_read_lock)(); + rcu_read_lock(); for_each_online_cpu(cpu) { struct task_struct *p; @@ -385,15 +417,6 @@ static int membarrier_private_expedited(int flags, int cpu_id) __cpumask_set_cpu(cpu, tmpmask); } rcu_read_unlock(); - } - - if (cpu_id >= 0) { - /* - * smp_call_function_single() will call ipi_func() if cpu_id - * is the calling CPU. - */ - smp_call_function_single(cpu_id, ipi_func, NULL, 1); - } else { /* * For regular membarrier, we can save a few cycles by * skipping the current cpu -- we're about to do smp_mb() @@ -420,18 +443,6 @@ static int membarrier_private_expedited(int flags, int cpu_id) } } -out: - if (cpu_id < 0) - free_cpumask_var(tmpmask); - cpus_read_unlock(); - - /* - * Memory barrier on the caller thread _after_ we finished - * waiting for the last IPI. Matches memory barriers before - * rq->curr modification in scheduler. - */ - smp_mb(); /* exit from system call is not a mb */ - return 0; } From a5959728548caf0aa17879342ee6571a29d54574 Mon Sep 17 00:00:00 2001 From: Aniket Gattani Date: Sun, 3 May 2026 21:22:04 +0000 Subject: [PATCH 057/167] sched/membarrier: Modernize membarrier_global_expedited with cleanup guards Replace explicit lock/unlock and free calls with scoped guards and automatic cleanup constructs. Signed-off-by: Aniket Gattani Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260503212205.3714217-3-aniketgattani@google.com --- kernel/sched/membarrier.c | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/kernel/sched/membarrier.c b/kernel/sched/membarrier.c index 3d88e900a17f..12b68a77630a 100644 --- a/kernel/sched/membarrier.c +++ b/kernel/sched/membarrier.c @@ -267,23 +267,19 @@ void membarrier_update_current_mm(struct mm_struct *next_mm) static int membarrier_global_expedited(void) { + cpumask_var_t __free(free_cpumask_var) tmpmask = CPUMASK_VAR_NULL; int cpu; - cpumask_var_t tmpmask; if (num_online_cpus() == 1) return 0; - /* - * Matches memory barriers after rq->curr modification in - * scheduler. - */ - smp_mb(); /* system call entry is not a mb. */ - if (!zalloc_cpumask_var(&tmpmask, GFP_KERNEL)) return -ENOMEM; + guard(mb)(); SERIALIZE_IPI(); - cpus_read_lock(); + guard(cpus_read_lock)(); + rcu_read_lock(); for_each_online_cpu(cpu) { struct task_struct *p; @@ -319,15 +315,6 @@ static int membarrier_global_expedited(void) smp_call_function_many(tmpmask, ipi_mb, NULL, 1); preempt_enable(); - free_cpumask_var(tmpmask); - cpus_read_unlock(); - - /* - * Memory barrier on the caller thread _after_ we finished - * waiting for the last IPI. Matches memory barriers before - * rq->curr modification in scheduler. - */ - smp_mb(); /* exit from system call is not a mb */ return 0; } From 03240f5de2dd312f388f3e493f194d54d43e2924 Mon Sep 17 00:00:00 2001 From: Aniket Gattani Date: Sun, 3 May 2026 21:22:05 +0000 Subject: [PATCH 058/167] selftests/membarrier: Add rseq stress test for CFS throttle interactions Add a new stress test to exercise the interaction between targeted expedited membarrier commands and CFS bandwidth throttling. The test creates a deep cgroup hierarchy and aggressively hammers the membarrier syscall to expose lock contention and latency issues. This serves as a reliable reproducer for the `membarrier_ipi_mutex` cascade lockup, ensuring future changes to membarrier locking do not regress targeted command latency. Closes: https://lore.kernel.org/r/202604151516.Vc7Ro4LP-lkp@intel.com/ Reported-by: kernel test robot Signed-off-by: Aniket Gattani Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260503212205.3714217-4-aniketgattani@google.com --- tools/testing/selftests/membarrier/Makefile | 5 +- .../membarrier/membarrier_rseq_stress.c | 951 ++++++++++++++++++ 2 files changed, 954 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/membarrier/membarrier_rseq_stress.c diff --git a/tools/testing/selftests/membarrier/Makefile b/tools/testing/selftests/membarrier/Makefile index fc840e06ff56..829f95c83515 100644 --- a/tools/testing/selftests/membarrier/Makefile +++ b/tools/testing/selftests/membarrier/Makefile @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only -CFLAGS += -g $(KHDR_INCLUDES) +CFLAGS += -g $(KHDR_INCLUDES) -pthread -I../../../../tools/include LDLIBS += -lpthread TEST_GEN_PROGS := membarrier_test_single_thread \ - membarrier_test_multi_thread + membarrier_test_multi_thread \ + membarrier_rseq_stress include ../lib.mk diff --git a/tools/testing/selftests/membarrier/membarrier_rseq_stress.c b/tools/testing/selftests/membarrier/membarrier_rseq_stress.c new file mode 100644 index 000000000000..c188d7498610 --- /dev/null +++ b/tools/testing/selftests/membarrier/membarrier_rseq_stress.c @@ -0,0 +1,951 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Membarrier stress test for CFS throttle interactions. + * + * Reproducer for the interaction between CFS throttle and expedited membarrier. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest.h" + +/* -- Architecture-specific rseq signature -- */ +#if defined(__x86_64__) || defined(__i386__) +# define RSEQ_SIG 0x53053053U +#elif defined(__aarch64__) +# define RSEQ_SIG 0xd428bc00U +#elif defined(__powerpc__) || defined(__powerpc64__) +# define RSEQ_SIG 0x0f000000U +#elif defined(__s390__) || defined(__s390x__) +# define RSEQ_SIG 0x0c000000U +#else +# define RSEQ_SIG 0 +# define UNSUPPORTED_ARCH 1 +#endif + +/* -- rseq ABI (kernel uapi; define locally for portability) -- */ +#define RSEQ_CPU_ID_UNINITIALIZED ((__u32)-1) + +#include + +struct rseq_abi { + __u32 cpu_id_start; + __u32 cpu_id; + __u64 rseq_cs; + __u32 flags; + __u32 node_id; + __u32 mm_cid; + char end[0]; +} __aligned(32); + +/* -- membarrier constants (not in all distro headers) -- */ +#ifndef MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ +# define MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ (1 << 7) +#endif +#ifndef MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ +# define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ (1 << 8) +#endif +#ifndef MEMBARRIER_CMD_FLAG_CPU +# define MEMBARRIER_CMD_FLAG_CPU (1 << 0) +#endif + +/* -- Test parameters -- */ +#define N_SIBLINGS 2000 +#define NEST_DEPTH 5 +static char g_cgroup_path[4096]; +static int use_cgroup_v2; + +#define CFS_QUOTA_US 1000 +#define CFS_PERIOD_US 5000 +#define N_HAMMER_PER_CPU 25 +#define N_BURNER_PER_CPU 50 +#define MAX_STRESS_CPUS 1024 +#define TEST_DURATION_SEC 20 + +/* Latency thresholds for the sentinel */ +#define LATENCY_WARN_MS 50 +#define LATENCY_CRITICAL_MS 200 + +/* Sentinel sampling interval */ +#define SENTINEL_INTERVAL_US 500 + +/* -- Shared globals -- */ +static atomic_int g_stop; +static atomic_int g_stop_sentinel; +static atomic_long g_max_latency_us; +static atomic_long g_interval_max_latency_us; +static atomic_long g_mb_ok; +static atomic_long g_mb_err; +static int g_ncpus_stress; +static int *g_stress_cpus; + +static atomic_int g_test_ready; + +/* Per-thread rseq ABI block registered with the kernel */ +static __thread struct rseq_abi tls_rseq + __attribute__((tls_model("initial-exec"))) __aligned(32) = { + .cpu_id = RSEQ_CPU_ID_UNINITIALIZED, +}; + +/* -- Utility -- */ +static int write_file(const char *path, const char *val) +{ + int fd = open(path, O_WRONLY | O_CLOEXEC); + + if (fd < 0) + return -errno; + + size_t len = strlen(val); + ssize_t r = write(fd, val, len); + + close(fd); + if (r < 0) + return -errno; + if ((size_t)r != len) + return -EIO; + return 0; +} + +static uint64_t monotonic_us(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000ULL + ts.tv_nsec / 1000ULL; +} + +static void update_max_latency(long lat) +{ + long old = atomic_load_explicit(&g_max_latency_us, memory_order_relaxed); + + while (lat > old) { + if (atomic_compare_exchange_weak_explicit(&g_max_latency_us, &old, lat, + memory_order_relaxed, memory_order_relaxed)) + break; + } + + old = atomic_load_explicit(&g_interval_max_latency_us, memory_order_relaxed); + while (lat > old) { + if (atomic_compare_exchange_weak_explicit(&g_interval_max_latency_us, &old, lat, + memory_order_relaxed, memory_order_relaxed)) + break; + } +} + +static void init_stress_cpus(void) +{ + cpu_set_t set; + int capacity = MAX_STRESS_CPUS; + + g_stress_cpus = malloc(capacity * sizeof(int)); + if (!g_stress_cpus) + ksft_exit_fail_msg("malloc failed for g_stress_cpus\n"); + + if (sched_getaffinity(0, sizeof(set), &set) < 0) + ksft_exit_fail_msg("sched_getaffinity failed\n"); + + for (int i = 0; i < CPU_SETSIZE && g_ncpus_stress < capacity; i++) { + if (CPU_ISSET(i, &set)) + g_stress_cpus[g_ncpus_stress++] = i; + } + + if (g_ncpus_stress == 0) + ksft_exit_skip("No CPUs available for stress test\n"); + + ksft_print_msg("Stressing %d CPUs discovered via affinity\n", g_ncpus_stress); +} + +/* -- rseq / membarrier helpers -- */ +static int rseq_register_thread(void) +{ + int r = syscall(SYS_rseq, &tls_rseq, sizeof(tls_rseq), 0, RSEQ_SIG); + + return (r == 0 || errno == EBUSY || errno == EINVAL) ? 0 : -1; +} + +static int rseq_register_thread_at(struct rseq_abi *rseq) +{ + int r = syscall(SYS_rseq, rseq, sizeof(*rseq), 0, RSEQ_SIG); + + return (r == 0 || errno == EBUSY || errno == EINVAL) ? 0 : -1; +} + +static int membarrier_register_rseq_mm(void) +{ + return syscall(SYS_membarrier, + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ, 0, 0); +} + +/* -- cgroup helpers -- */ +static void rm_cgroup_recursive(const char *path) +{ + DIR *dir = opendir(path); + + if (!dir) + return; + struct dirent *entry; + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + if (entry->d_type == DT_DIR) { + char sub_path[4096]; + + snprintf(sub_path, sizeof(sub_path), "%s/%s", path, entry->d_name); + rm_cgroup_recursive(sub_path); + } + } + closedir(dir); + rmdir(path); +} + +static void cgroup_teardown(void); + +static int cgroup_setup(void) +{ + struct stat st; + + if (stat("/sys/fs/cgroup/cpu", &st) == 0) { + use_cgroup_v2 = 0; + snprintf(g_cgroup_path, sizeof(g_cgroup_path), + "/sys/fs/cgroup/cpu/membarrier_stress_test"); + } else if (stat("/dev/cgroup/cpu", &st) == 0) { + use_cgroup_v2 = 0; + snprintf(g_cgroup_path, sizeof(g_cgroup_path), + "/dev/cgroup/cpu/membarrier_stress_test"); + } else if (stat("/cgroup/cpu", &st) == 0) { + use_cgroup_v2 = 0; + snprintf(g_cgroup_path, sizeof(g_cgroup_path), + "/cgroup/cpu/membarrier_stress_test"); + } else if (stat("/sys/fs/cgroup/cgroup.controllers", &st) == 0) { + use_cgroup_v2 = 1; + snprintf(g_cgroup_path, sizeof(g_cgroup_path), + "/sys/fs/cgroup/membarrier_stress_test"); + } else { + ksft_print_msg("WARN: cgroup mount not found. Using v2 at /sys/fs/cgroup\n"); + use_cgroup_v2 = 1; + snprintf(g_cgroup_path, sizeof(g_cgroup_path), + "/sys/fs/cgroup/membarrier_stress_test"); + } + + /* Robust cleanup before setup */ + cgroup_teardown(); + + if (use_cgroup_v2) { + /* Enable cpu controller in root cgroup */ + if (write_file("/sys/fs/cgroup/cgroup.subtree_control", "+cpu") < 0) + ksft_print_msg("WARN: failed to enable cpu controller in /sys/fs/cgroup\n"); + } + + if (mkdir(g_cgroup_path, 0755) < 0 && errno != EEXIST) { + ksft_print_msg("mkdir base %s failed: %s\n", g_cgroup_path, strerror(errno)); + return -1; + } + + if (use_cgroup_v2) { + char ctrl_path[4096]; + + snprintf(ctrl_path, sizeof(ctrl_path), "%s/cgroup.subtree_control", g_cgroup_path); + if (write_file(ctrl_path, "+cpu") < 0) + ksft_print_msg("WARN: failed to enable cpu controller in %s\n", + g_cgroup_path); + } + + for (int i = 0; i < N_SIBLINGS; i++) { + char sibling_path[4096]; + + snprintf(sibling_path, sizeof(sibling_path), "%s/n%d", g_cgroup_path, i); + if (mkdir(sibling_path, 0755) < 0 && errno != EEXIST) { + ksft_print_msg("mkdir wide %s failed: %s\n", sibling_path, strerror(errno)); + return -1; + } + + if (use_cgroup_v2) { + char ctrl_path[4096]; + + snprintf(ctrl_path, sizeof(ctrl_path), + "%s/cgroup.subtree_control", sibling_path); + if (write_file(ctrl_path, "+cpu") < 0) + ksft_print_msg("WARN: failed to enable cpu controller in %s\n", + sibling_path); + } + + char current_path[4096]; + + snprintf(current_path, sizeof(current_path), "%s", sibling_path); + for (int j = 0; j < NEST_DEPTH; j++) { + snprintf(current_path + strlen(current_path), + sizeof(current_path) - strlen(current_path), "/d%d", j); + if (mkdir(current_path, 0755) < 0 && errno != EEXIST) { + ksft_print_msg("mkdir deep %s failed: %s\n", + current_path, strerror(errno)); + return -1; + } + + /* Enable for all but the leaf */ + if (use_cgroup_v2 && j < NEST_DEPTH - 1) { + char ctrl_path[4096]; + + snprintf(ctrl_path, sizeof(ctrl_path), "%s/cgroup.subtree_control", + current_path); + if (write_file(ctrl_path, "+cpu") < 0) + ksft_print_msg("WARN: cannot enable cpu controller in %s\n", + current_path); + } + } + } + + char quota[64], period[64], max_str[128]; + + snprintf(quota, sizeof(quota), "%d", CFS_QUOTA_US); + snprintf(period, sizeof(period), "%d", CFS_PERIOD_US); + snprintf(max_str, sizeof(max_str), "%d %d", CFS_QUOTA_US, CFS_PERIOD_US); + + if (use_cgroup_v2) { + char max_path[4096]; + + snprintf(max_path, sizeof(max_path), "%s/cpu.max", g_cgroup_path); + if (write_file(max_path, max_str) < 0) { + ksft_print_msg("ERROR: cannot write cpu.max at %s\n", max_path); + return -1; + } + ksft_print_msg("cgroup (v2) %s: cpu.max=%s\n", g_cgroup_path, max_str); + } else { + char quota_path[4096], period_path[4096]; + + snprintf(quota_path, sizeof(quota_path), "%s/cpu.cfs_quota_us", g_cgroup_path); + snprintf(period_path, sizeof(period_path), "%s/cpu.cfs_period_us", g_cgroup_path); + + if (write_file(period_path, period) < 0) { + ksft_print_msg("ERROR: cannot write cpu.cfs_period_us at %s\n", + period_path); + return -1; + } + if (write_file(quota_path, quota) < 0) { + ksft_print_msg("ERROR: cannot write cpu.cfs_quota_us at %s\n", quota_path); + return -1; + } + ksft_print_msg("cgroup (v1) %s: cpu.cfs_quota_us=%d cpu.cfs_period_us=%d\n", + g_cgroup_path, CFS_QUOTA_US, CFS_PERIOD_US); + } + + return 0; +} + +static int cgroup_add_pid_to_path(pid_t pid, const char *path) +{ + char buf[32], file_path[4096]; + + snprintf(buf, sizeof(buf), "%d", (int)pid); + if (use_cgroup_v2) { + snprintf(file_path, sizeof(file_path), "%s/cgroup.procs", path); + return write_file(file_path, buf); + } + /* In v1, try tasks first, fallback to cgroup.procs */ + snprintf(file_path, sizeof(file_path), "%s/tasks", path); + int r = write_file(file_path, buf); + + if (r < 0) { + snprintf(file_path, sizeof(file_path), "%s/cgroup.procs", path); + r = write_file(file_path, buf); + } + return r; +} + +static void cgroup_teardown(void) +{ + rm_cgroup_recursive(g_cgroup_path); +} + +static void cgroup_unthrottle(void) +{ + if (use_cgroup_v2) { + char max_path[4096]; + + snprintf(max_path, sizeof(max_path), "%s/cpu.max", g_cgroup_path); + write_file(max_path, "max"); + } else { + char quota_path[4096]; + + snprintf(quota_path, sizeof(quota_path), "%s/cpu.cfs_quota_us", g_cgroup_path); + write_file(quota_path, "-1"); + } +} + +/* -- CPU burner (inside throttled child process) -- */ +static void *burner_thread_fn(void *arg) +{ + struct rseq_abi my_rseq; + int cpu = (int)(uintptr_t)arg; + + memset(&my_rseq, 0, sizeof(my_rseq)); + my_rseq.cpu_id = RSEQ_CPU_ID_UNINITIALIZED; + + if (rseq_register_thread_at(&my_rseq) < 0) { + perror("rseq_register (burner)"); + return NULL; + } + + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + if (sched_setaffinity(0, sizeof(set), &set) < 0) + perror("sched_setaffinity (burner)"); + + unsigned long sink = 0; + + while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) { + sink++; + /* Prevent compiler from optimizing the loop away */ + asm volatile("" : "+g"(sink)); + } + + return NULL; +} + +static int burner_thread_fn_wrapper(void *arg) +{ + burner_thread_fn(arg); + return 0; +} + +static int leaf_child_fn(void *arg) +{ + int i = (int)(uintptr_t)arg; + int total_burners = g_ncpus_stress * N_BURNER_PER_CPU; + int n_threads_per_leaf = total_burners / N_SIBLINGS; + + if (i < (total_burners % N_SIBLINGS)) + n_threads_per_leaf++; + + prctl(PR_SET_PDEATHSIG, SIGTERM); + if (getppid() == 1) + _exit(1); + + char leaf_path[4096]; + + snprintf(leaf_path, sizeof(leaf_path), "%s/n%d", g_cgroup_path, i); + for (int j = 0; j < NEST_DEPTH; j++) + snprintf(leaf_path + strlen(leaf_path), + sizeof(leaf_path) - strlen(leaf_path), "/d%d", j); + + int r = cgroup_add_pid_to_path(getpid(), leaf_path); + + if (r < 0) { + char buf[512]; + int len = snprintf(buf, sizeof(buf), + "[leaf child %d] failed to join cgroup %s: err %d\n", + i, leaf_path, -r); + (void)!write(2, buf, len); + _exit(1); + } + + for (int j = 0; j < n_threads_per_leaf; j++) { + int cpu = g_stress_cpus[(i * n_threads_per_leaf + j) % g_ncpus_stress]; + + /* Allocate stack via mmap (bypasses heap) */ + size_t stack_size = 64 * 1024; + void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (stack == MAP_FAILED) { + const char *msg = "mmap stack failed\n"; + (void)!write(2, msg, strlen(msg)); + _exit(1); + } + + /* Use raw clone to create a thread sharing the VM and thread group */ + pid_t pid = clone(burner_thread_fn_wrapper, stack + stack_size, + CLONE_VM | CLONE_THREAD | CLONE_SIGHAND, + (void *)(uintptr_t)cpu); + if (pid < 0) { + const char *msg = "clone burner failed\n"; + (void)!write(2, msg, strlen(msg)); + _exit(1); + } + } + + // Wait for SIGTERM + sigset_t mask; + + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + int sig; + + sigwait(&mask, &sig); + + _exit(0); +} + +struct leaf_info { + pid_t pid; + void *stack; +}; + +static int run_throttle_child(void *arg) +{ + (void)arg; + prctl(PR_SET_PDEATHSIG, SIGTERM); + if (getppid() == 1) + _exit(1); + + int n_leafs = N_SIBLINGS; + + /* Block signals before spawning to avoid missing early failures */ + sigset_t mask; + + sigemptyset(&mask); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &mask, NULL); + + /* Use mmap for tracking structures to avoid glibc heap usage */ + struct leaf_info *leaves = mmap(NULL, n_leafs * sizeof(struct leaf_info), + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (leaves == MAP_FAILED) { + const char *msg = "mmap leaves array failed\n"; + (void)!write(2, msg, strlen(msg)); + _exit(1); + } + + for (int i = 0; i < n_leafs; i++) { + size_t stack_size = 64 * 1024; + void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (stack == MAP_FAILED) { + const char *msg = "mmap leaf stack failed\n"; + (void)!write(2, msg, strlen(msg)); + _exit(1); + } + + leaves[i].stack = stack; + + pid_t pid = clone(leaf_child_fn, stack + stack_size, + CLONE_VM | SIGCHLD, (void *)(uintptr_t)i); + + if (pid < 0) { + const char *msg = "clone (leaf child) failed\n"; + (void)!write(2, msg, strlen(msg)); + + /* Clean up successfully spawned children */ + for (int j = 0; j < i; j++) { + kill(leaves[j].pid, SIGTERM); + waitpid(leaves[j].pid, NULL, 0); + munmap(leaves[j].stack, stack_size); + } + munmap(leaves, n_leafs * sizeof(struct leaf_info)); + + if (errno == EAGAIN) + _exit(4); + else + _exit(1); + } + leaves[i].pid = pid; + } + + int failed = 0; + + while (1) { + int sig; + + sigwait(&mask, &sig); + + if (sig == SIGTERM) { + break; + } else if (sig == SIGCHLD) { + int status; + pid_t pid; + + // Reap all dead children + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + for (int i = 0; i < n_leafs; i++) { + if (leaves[i].pid == pid) { + leaves[i].pid = 0; + break; + } + } + if ((WIFEXITED(status) && WEXITSTATUS(status) != 0) || + WIFSIGNALED(status)) { + char buf[128]; + int len = snprintf(buf, sizeof(buf), + "[manager] child %d died unexpectedly (status %d)\n", + pid, WEXITSTATUS(status)); + (void)!write(2, buf, len); + failed = 1; + } + } + if (failed) + break; + } + } + + // Terminate all leaf kids + for (int i = 0; i < n_leafs; i++) { + if (leaves[i].pid > 0) + kill(leaves[i].pid, SIGTERM); + } + + for (int i = 0; i < n_leafs; i++) { + if (leaves[i].pid > 0) + waitpid(leaves[i].pid, NULL, 0); + munmap(leaves[i].stack, 64 * 1024); + } + + munmap(leaves, n_leafs * sizeof(struct leaf_info)); + + _exit(failed ? 1 : 0); +} + +/* -- Membarrier hammer thread -- */ +static void *hammer_thread_fn(void *arg) +{ + int target_cpu = *(int *)arg; + long local_ok = 0; + long local_err = 0; + int count = 0; + const int batch_size = 1024; + + if (rseq_register_thread() < 0) { + ksft_print_msg("[hammer] rseq_register failed: %s\n", strerror(errno)); + return NULL; + } + + membarrier_register_rseq_mm(); + + while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) { + int r = syscall(SYS_membarrier, + MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ, + MEMBARRIER_CMD_FLAG_CPU, + target_cpu); + if (__builtin_expect(r == 0, 1)) + local_ok++; + else + local_err++; + + count++; + if (__builtin_expect(count >= batch_size, 0)) { + atomic_fetch_add_explicit(&g_mb_ok, local_ok, memory_order_relaxed); + atomic_fetch_add_explicit(&g_mb_err, local_err, memory_order_relaxed); + local_ok = 0; + local_err = 0; + count = 0; + } + } + + /* Flush any remaining counts on exit */ + if (local_ok > 0) + atomic_fetch_add_explicit(&g_mb_ok, local_ok, memory_order_relaxed); + if (local_err > 0) + atomic_fetch_add_explicit(&g_mb_err, local_err, memory_order_relaxed); + + return NULL; +} + +/* -- Latency sentinel -- */ +static void *sentinel_thread_fn(void *arg) +{ + (void)arg; + struct sched_param sp = { .sched_priority = 20 }; + + if (sched_setscheduler(0, SCHED_FIFO, &sp) < 0) + ksft_print_msg("WARN: no SCHED_FIFO for sentinel (less precise)\n"); + + while (!atomic_load_explicit(&g_test_ready, memory_order_relaxed) && + !atomic_load_explicit(&g_stop_sentinel, memory_order_relaxed)) { + struct timespec ts = {0, 1000 * 1000}; /* 1ms */ + + clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); + } + + uint64_t prev = monotonic_us(); + + while (!atomic_load_explicit(&g_stop_sentinel, memory_order_relaxed)) { + struct timespec ts = { + .tv_sec = 0, + .tv_nsec = SENTINEL_INTERVAL_US * 1000L, + }; + clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); + + uint64_t now = monotonic_us(); + long latency_us = (long)(now - prev) - SENTINEL_INTERVAL_US; + + prev = now; + + if (latency_us <= 0) + continue; + + update_max_latency(latency_us); + + if (latency_us > LATENCY_CRITICAL_MS * 1000L) { + ksft_print_msg("\n[SENTINEL] CRITICAL: %ld ms delay (lockup precursor!)\n", + latency_us / 1000); + } else if (latency_us > LATENCY_WARN_MS * 1000L) { + ksft_print_msg("\n[SENTINEL] WARN: %ld ms latency spike\n", + latency_us / 1000); + } + } + return NULL; +} + +/* -- Progress reporter -- */ +static void *reporter_thread_fn(void *arg) +{ + (void)arg; + int elapsed = 0; + + while (!atomic_load_explicit(&g_stop_sentinel, memory_order_relaxed)) { + for (int i = 0; i < 5; i++) { + sleep(1); + if (atomic_load_explicit(&g_stop_sentinel, memory_order_relaxed)) + break; + } + if (atomic_load_explicit(&g_stop_sentinel, memory_order_relaxed)) + break; + elapsed += 5; + long interval_max = atomic_exchange_explicit(&g_interval_max_latency_us, + 0, memory_order_relaxed); + + ksft_print_msg("[%3ds] mb: ok=%-10ld err=%-8ld | max_lat=%ld us\n", + elapsed, + atomic_load(&g_mb_ok), + atomic_load(&g_mb_err), + interval_max); + } + return NULL; +} + +/* -- Main -- */ +int main(void) +{ + ksft_print_header(); +#ifdef UNSUPPORTED_ARCH + ksft_exit_skip("Unsupported architecture\n"); +#endif + ksft_set_plan(1); + + if (geteuid() != 0) + ksft_exit_skip("Must run as root (cgroup + SCHED_FIFO)\n"); + + init_stress_cpus(); + + ksft_print_msg("=== membarrier rseq + CFS unthrottle stress ===\n"); + ksft_print_msg("Stressing CPUs: %d\n", g_ncpus_stress); + ksft_print_msg("Quota: %d/%d us (~%d unthrottles/sec/CPU)\n", + CFS_QUOTA_US, CFS_PERIOD_US, + 1000000 / CFS_PERIOD_US); + ksft_print_msg("Hammer threads: %d per CPU (%d total)\n", + N_HAMMER_PER_CPU, g_ncpus_stress * N_HAMMER_PER_CPU); + ksft_print_msg("Duration: %d seconds\n\n", TEST_DURATION_SEC); + + if (cgroup_setup() < 0) { + cgroup_teardown(); + ksft_exit_skip("cgroup_setup failed (missing permissions or v2 ctrls?)\n"); + } + + if (rseq_register_thread() < 0) { + ksft_print_msg("rseq_register (%s) failed: %s\n", __func__, strerror(errno)); + cgroup_teardown(); + ksft_exit_skip("rseq syscall failed or not available\n"); + } + if (membarrier_register_rseq_mm() < 0) { + ksft_print_msg("MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ: %s\n" + "Kernel >= 5.10 with CONFIG_RSEQ required.\n", + strerror(errno)); + cgroup_teardown(); + ksft_exit_skip("membarrier register failed\n"); + } + ksft_print_msg("rseq membarrier registered OK\n"); + + sigset_t sigmask; + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + sigprocmask(SIG_BLOCK, &sigmask, NULL); + + void *stack = malloc(1024 * 1024); + + if (!stack) { + perror("malloc stack"); + cgroup_teardown(); + ksft_exit_fail_msg("Malloc stack failed\n"); + } + pid_t child = clone(run_throttle_child, stack + 1024 * 1024, CLONE_VM | SIGCHLD, NULL); + + if (child < 0) { + perror("clone"); + cgroup_teardown(); + ksft_exit_fail_msg("Clone failed\n"); + } + + sigprocmask(SIG_UNBLOCK, &sigmask, NULL); + ksft_print_msg("Throttle child PID %d started\n", child); + + int n_threads = g_ncpus_stress * N_HAMMER_PER_CPU + 2; + pthread_t *threads = (pthread_t *)calloc(n_threads, sizeof(pthread_t)); + int *cpuargs = (int *)calloc(g_ncpus_stress * N_HAMMER_PER_CPU, sizeof(int)); + + if (!threads || !cpuargs) { + perror("calloc"); + kill(child, SIGTERM); + waitpid(child, NULL, 0); + cgroup_teardown(); + ksft_exit_fail_msg("Thread allocation failed\n"); + } + + int ti = 0, ai = 0; + int r; + + ksft_print_msg("Creating sentinel thread...\n"); + r = pthread_create(&threads[ti], NULL, sentinel_thread_fn, NULL); + if (r != 0) { + kill(child, SIGTERM); + waitpid(child, NULL, 0); + cgroup_teardown(); + free(threads); + free(cpuargs); + free(g_stress_cpus); + ksft_exit_fail_msg("pthread_create (sentinel) failed: %s\n", strerror(r)); + } + ti++; + + ksft_print_msg("Creating reporter thread...\n"); + r = pthread_create(&threads[ti], NULL, reporter_thread_fn, NULL); + if (r != 0) { + atomic_store(&g_stop_sentinel, 1); + pthread_join(threads[0], NULL); + kill(child, SIGTERM); + waitpid(child, NULL, 0); + cgroup_teardown(); + free(threads); + free(cpuargs); + free(g_stress_cpus); + ksft_exit_fail_msg("pthread_create (reporter) failed: %s\n", strerror(r)); + } + ti++; + + ksft_print_msg("Creating %d hammer threads...\n", g_ncpus_stress * N_HAMMER_PER_CPU); + for (int i = 0; i < g_ncpus_stress; i++) { + int cpu = g_stress_cpus[i]; + + for (int j = 0; j < N_HAMMER_PER_CPU; j++) { + cpuargs[ai] = cpu; + r = pthread_create(&threads[ti], NULL, hammer_thread_fn, &cpuargs[ai]); + if (r != 0) { + ksft_print_msg("pthread_create failed at thread %d: %s\n", + ti, strerror(r)); + + atomic_store(&g_stop_sentinel, 1); + pthread_join(threads[0], NULL); + pthread_join(threads[1], NULL); + + atomic_store(&g_stop, 1); + for (int k = 2; k < ti; k++) + pthread_join(threads[k], NULL); + + kill(child, SIGTERM); + waitpid(child, NULL, 0); + cgroup_teardown(); + + free(threads); + free(cpuargs); + free(g_stress_cpus); + + if (r == EAGAIN) + ksft_exit_skip("Resource limits prevent threads\n"); + else + ksft_exit_fail_msg("Failed to create hammer thread\n"); + } + ti++; + ai++; + } + } + + ksft_print_msg("All threads running. Tip: monitor dmesg for lockups\n\n"); + + atomic_store_explicit(&g_test_ready, 1, memory_order_relaxed); + int child_failed = 0; + int child_status = 0; + + for (int i = 0; i < TEST_DURATION_SEC; i++) { + sleep(1); + int r = waitpid(child, &child_status, WNOHANG); + + if (r == child) { + child_failed = 1; + break; + } + } + + atomic_store(&g_stop_sentinel, 1); + pthread_join(threads[0], NULL); + pthread_join(threads[1], NULL); + + atomic_store(&g_stop, 1); + + /* Unthrottle to allow children to exit quickly */ + cgroup_unthrottle(); + + if (!child_failed) { + kill(child, SIGTERM); + waitpid(child, NULL, 0); + } + for (int i = 2; i < ti; i++) + pthread_join(threads[i], NULL); + + long max_lat = atomic_load(&g_max_latency_us); + long total_ok = atomic_load(&g_mb_ok); + long total_err = atomic_load(&g_mb_err); + + ksft_print_msg("\n=== RESULTS ===\n"); + ksft_print_msg("membarrier syscalls : %ld ok %ld errors\n", total_ok, total_err); + ksft_print_msg("Max scheduler latency: %ld us (%ld ms)\n", max_lat, max_lat / 1000); + cgroup_teardown(); + free(threads); + free(cpuargs); + free(g_stress_cpus); + + if (child_failed) { + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 4) + ksft_exit_skip("Manager child skipped (resource limits?)\n"); + ksft_test_result_fail("membarrier_rseq_stress: Manager child died early\n"); + ksft_exit_fail(); + } else if (total_ok == 0) { + ksft_test_result_fail("membarrier_rseq_stress: No successful membarrier calls\n"); + ksft_exit_fail(); + } else if (total_err > 0) { + ksft_test_result_fail("membarrier_rseq_stress: syscall errors\n"); + ksft_exit_fail(); + } else if (max_lat > LATENCY_CRITICAL_MS * 1000L) { + ksft_test_result_fail("membarrier_rseq_stress: LOCKUP PRECURSOR\n"); + ksft_exit_fail(); + } else if (max_lat > LATENCY_WARN_MS * 1000L) { + ksft_test_result_fail("membarrier_rseq_stress: significant latency spike\n"); + ksft_exit_fail(); + } else { + ksft_test_result_pass("membarrier_rseq_stress\n"); + ksft_exit_pass(); + } + + return 0; +} From a9e4e50519e99e5be2d69fa4eebec6a4848ae201 Mon Sep 17 00:00:00 2001 From: Bart Van Assche Date: Fri, 8 May 2026 10:45:16 -0700 Subject: [PATCH 059/167] locking/rtmutex: Annotate API and implementation Enable context analysis for struct rt_mutex and annotate all functions that accept a struct rt_mutex pointer. In the __rt_mutex_lock_common() callers, instead of adding the __no_context_analysis annotation, emit a runtime warning if the __rt_mutex_lock_common() return value is not zero and add an __acquire() statement. Signed-off-by: Bart Van Assche Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260508174520.1416285-1-bvanassche@acm.org --- include/linux/rtmutex.h | 22 +++++++++++++++------- kernel/locking/rtmutex.c | 4 ++++ kernel/locking/rtmutex_api.c | 31 ++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/include/linux/rtmutex.h b/include/linux/rtmutex.h index 78e7e588817c..9e1f012f89db 100644 --- a/include/linux/rtmutex.h +++ b/include/linux/rtmutex.h @@ -56,6 +56,8 @@ static inline struct task_struct *rt_mutex_owner(struct rt_mutex_base *lock) #endif extern void rt_mutex_base_init(struct rt_mutex_base *rtb); +context_lock_struct(rt_mutex); + /** * The rt_mutex structure * @@ -108,8 +110,10 @@ do { \ extern void __rt_mutex_init(struct rt_mutex *lock, const char *name, struct lock_class_key *key); #ifdef CONFIG_DEBUG_LOCK_ALLOC -extern void rt_mutex_lock_nested(struct rt_mutex *lock, unsigned int subclass); -extern void _rt_mutex_lock_nest_lock(struct rt_mutex *lock, struct lockdep_map *nest_lock); +extern void rt_mutex_lock_nested(struct rt_mutex *lock, unsigned int subclass) + __acquires(lock); +extern void _rt_mutex_lock_nest_lock(struct rt_mutex *lock, struct lockdep_map *nest_lock) + __acquires(lock); #define rt_mutex_lock(lock) rt_mutex_lock_nested(lock, 0) #define rt_mutex_lock_nest_lock(lock, nest_lock) \ do { \ @@ -118,15 +122,19 @@ extern void _rt_mutex_lock_nest_lock(struct rt_mutex *lock, struct lockdep_map * } while (0) #else -extern void rt_mutex_lock(struct rt_mutex *lock); +extern void rt_mutex_lock(struct rt_mutex *lock) __acquires(lock); #define rt_mutex_lock_nested(lock, subclass) rt_mutex_lock(lock) #define rt_mutex_lock_nest_lock(lock, nest_lock) rt_mutex_lock(lock) #endif -extern int rt_mutex_lock_interruptible(struct rt_mutex *lock); -extern int rt_mutex_lock_killable(struct rt_mutex *lock); -extern int rt_mutex_trylock(struct rt_mutex *lock); +extern int rt_mutex_lock_interruptible(struct rt_mutex *lock) + __cond_acquires(0, lock); +extern int rt_mutex_lock_killable(struct rt_mutex *lock) + __cond_acquires(0, lock); +extern int rt_mutex_trylock(struct rt_mutex *lock) + __cond_acquires(true, lock); -extern void rt_mutex_unlock(struct rt_mutex *lock); +extern void rt_mutex_unlock(struct rt_mutex *lock) + __releases(lock); #endif diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c index 4f386ea6c792..9147d6a31b78 100644 --- a/kernel/locking/rtmutex.c +++ b/kernel/locking/rtmutex.c @@ -484,6 +484,7 @@ static __always_inline bool __waiter_less(struct rb_node *a, const struct rb_nod static __always_inline void rt_mutex_enqueue(struct rt_mutex_base *lock, struct rt_mutex_waiter *waiter) + __must_hold(&lock->wait_lock) { lockdep_assert_held(&lock->wait_lock); @@ -492,6 +493,7 @@ rt_mutex_enqueue(struct rt_mutex_base *lock, struct rt_mutex_waiter *waiter) static __always_inline void rt_mutex_dequeue(struct rt_mutex_base *lock, struct rt_mutex_waiter *waiter) + __must_hold(&lock->wait_lock) { lockdep_assert_held(&lock->wait_lock); @@ -1092,6 +1094,7 @@ static int __sched rt_mutex_adjust_prio_chain(struct task_struct *task, static int __sched try_to_take_rt_mutex(struct rt_mutex_base *lock, struct task_struct *task, struct rt_mutex_waiter *waiter) + __must_hold(&lock->wait_lock) { lockdep_assert_held(&lock->wait_lock); @@ -1319,6 +1322,7 @@ static int __sched task_blocks_on_rt_mutex(struct rt_mutex_base *lock, */ static void __sched mark_wakeup_next_waiter(struct rt_wake_q_head *wqh, struct rt_mutex_base *lock) + __must_hold(&lock->wait_lock) { struct rt_mutex_waiter *waiter; diff --git a/kernel/locking/rtmutex_api.c b/kernel/locking/rtmutex_api.c index 124219aea46e..7c40b91422a0 100644 --- a/kernel/locking/rtmutex_api.c +++ b/kernel/locking/rtmutex_api.c @@ -41,6 +41,7 @@ static __always_inline int __rt_mutex_lock_common(struct rt_mutex *lock, unsigned int state, struct lockdep_map *nest_lock, unsigned int subclass) + __cond_acquires(0, lock) { int ret; @@ -67,13 +68,27 @@ EXPORT_SYMBOL(rt_mutex_base_init); */ void __sched rt_mutex_lock_nested(struct rt_mutex *lock, unsigned int subclass) { - __rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, NULL, subclass); + if (__rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, NULL, subclass) == 0) + return; + /* + * The code below is never reached because __rt_mutex_lock_common() only + * returns an error code if interrupted by a signal or upon a timeout. + */ + WARN_ON_ONCE(true); + __acquire(lock); } EXPORT_SYMBOL_GPL(rt_mutex_lock_nested); void __sched _rt_mutex_lock_nest_lock(struct rt_mutex *lock, struct lockdep_map *nest_lock) { - __rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, nest_lock, 0); + if (__rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, nest_lock, 0) == 0) + return; + /* + * The code below is never reached because __rt_mutex_lock_common() only + * returns an error code if interrupted by a signal or upon a timeout. + */ + WARN_ON_ONCE(true); + __acquire(lock); } EXPORT_SYMBOL_GPL(_rt_mutex_lock_nest_lock); @@ -86,7 +101,14 @@ EXPORT_SYMBOL_GPL(_rt_mutex_lock_nest_lock); */ void __sched rt_mutex_lock(struct rt_mutex *lock) { - __rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, NULL, 0); + if (__rt_mutex_lock_common(lock, TASK_UNINTERRUPTIBLE, NULL, 0) == 0) + return; + /* + * The code below is never reached because __rt_mutex_lock_common() only + * returns an error code if interrupted by a signal or upon a timeout. + */ + WARN_ON_ONCE(true); + __acquire(lock); } EXPORT_SYMBOL_GPL(rt_mutex_lock); #endif @@ -157,6 +179,7 @@ void __sched rt_mutex_unlock(struct rt_mutex *lock) { mutex_release(&lock->dep_map, _RET_IP_); __rt_mutex_unlock(&lock->rtmutex); + __release(lock); } EXPORT_SYMBOL_GPL(rt_mutex_unlock); @@ -182,6 +205,7 @@ int __sched __rt_mutex_futex_trylock(struct rt_mutex_base *lock) */ bool __sched __rt_mutex_futex_unlock(struct rt_mutex_base *lock, struct rt_wake_q_head *wqh) + __must_hold(&lock->wait_lock) { lockdep_assert_held(&lock->wait_lock); @@ -312,6 +336,7 @@ int __sched __rt_mutex_start_proxy_lock(struct rt_mutex_base *lock, struct rt_mutex_waiter *waiter, struct task_struct *task, struct wake_q_head *wake_q) + __must_hold(&lock->wait_lock) { int ret; From acc41cdcb091453f48381d1c0d2e76b63c9d985f Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:33 +0800 Subject: [PATCH 060/167] perf/x86/intel: Update event constraints and cache_extra_regsfor ICX Update perf hard-coded event constraints and cache_extra_regs[] for Icelake server according to the latest ICX perfmon events (v1.30). Since the value of cache extra registers differs with previous generations, introduce new snc_hw_cache_extra_regs[] to represent the value of extra registers on ICX. ICX perfmon events: https://github.com/intel/perfmon/blob/main/ICX/events/icelakex_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-2-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 48 ++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 2b05587c84f7..db409caef83d 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -310,10 +310,11 @@ static struct extra_reg intel_skl_extra_regs[] __read_mostly = { static struct event_constraint intel_icl_event_constraints[] = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x01c0, 0), /* old INST_RETIRED.PREC_DIST */ - FIXED_EVENT_CONSTRAINT(0x0100, 0), /* INST_RETIRED.PREC_DIST */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* CPU_CLK_UNHALTED.REF */ - FIXED_EVENT_CONSTRAINT(0x0400, 3), /* SLOTS */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ + FIXED_EVENT_CONSTRAINT(0x0400, 3), /* pseudo TOPDOWN.SLOTS */ METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_RETIRING, 0), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_BAD_SPEC, 1), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_FE_BOUND, 2), @@ -1019,6 +1020,41 @@ static __initconst const u64 skl_hw_cache_extra_regs }, }; +static __initconst const u64 snc_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = +{ + [ C(LL ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x3FBFC00001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x3F3FFC0002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x3F3FC00002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + [ C(OP_PREFETCH) ] = { + [ C(RESULT_ACCESS) ] = 0x0, + [ C(RESULT_MISS) ] = 0x0, + }, + }, + [ C(NODE) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x104000001, /* OCR.DEMAND_DATA_RD.LOCAL_DRAM */ + [ C(RESULT_MISS) ] = 0x730000001, /* OCR.DEMAND_DATA_RD.REMOTE_DRAM */ + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x104000002, /* OCR.DEMAND_RFO.LOCAL_DRAM */ + [ C(RESULT_MISS) ] = 0x730000002, /* OCR.DEMAND_RFO.REMOTE_DRAM */ + }, + [ C(OP_PREFETCH) ] = { + [ C(RESULT_ACCESS) ] = 0x0, + [ C(RESULT_MISS) ] = 0x0, + }, + }, +}; + #define SNB_DMND_DATA_RD (1ULL << 0) #define SNB_DMND_RFO (1ULL << 1) #define SNB_DMND_IFETCH (1ULL << 2) @@ -8154,17 +8190,19 @@ __init int intel_pmu_init(void) case INTEL_ICELAKE_X: case INTEL_ICELAKE_D: + memcpy(hw_cache_extra_regs, snc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); x86_pmu.pebs_ept = 1; pmem = true; - fallthrough; + goto snc_common; case INTEL_ICELAKE_L: case INTEL_ICELAKE: case INTEL_TIGERLAKE_L: case INTEL_TIGERLAKE: case INTEL_ROCKETLAKE: + memcpy(hw_cache_extra_regs, skl_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); + snc_common: x86_pmu.late_ack = true; memcpy(hw_cache_event_ids, skl_hw_cache_event_ids, sizeof(hw_cache_event_ids)); - memcpy(hw_cache_extra_regs, skl_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); hw_cache_event_ids[C(ITLB)][C(OP_READ)][C(RESULT_ACCESS)] = -1; intel_pmu_lbr_init_skl(); From 30d82ddee085b4b0f9a1b5bded53acaca68d9a52 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:34 +0800 Subject: [PATCH 061/167] perf/x86/intel: Update event constraints and cache_extra_regsfor SPR Update perf hard-coded event constraints and cache_extra_regs[] for Sapphire rapids according to the latest SPR perfmon events (v1.39). Emerald Rapids (EMR) and Granite Rapids (GNR) share exactly same event constraints and extra MSR values with SPR. No extra changes are needed for EMR and GNR. Please note the change could temporarily impact other platforms which share the hard coded data structures, but it would be fixed in subsequent patches soon. SPR perfmon events: https://github.com/intel/perfmon/blob/main/SPR/events/sapphirerapids_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-3-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index db409caef83d..932f612a99f3 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -356,11 +356,12 @@ static struct extra_reg intel_glc_extra_regs[] __read_mostly = { static struct event_constraint intel_glc_event_constraints[] = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ - FIXED_EVENT_CONSTRAINT(0x0100, 0), /* INST_RETIRED.PREC_DIST */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* CPU_CLK_UNHALTED.REF */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ FIXED_EVENT_CONSTRAINT(0x013c, 2), /* CPU_CLK_UNHALTED.REF_TSC_P */ - FIXED_EVENT_CONSTRAINT(0x0400, 3), /* SLOTS */ + FIXED_EVENT_CONSTRAINT(0x0400, 3), /* pseudo TOPDOWN.SLOTS */ METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_RETIRING, 0), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_BAD_SPEC, 1), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_FE_BOUND, 2), @@ -380,9 +381,13 @@ static struct event_constraint intel_glc_event_constraints[] = { INTEL_UEVENT_CONSTRAINT(0x01a3, 0xf), INTEL_UEVENT_CONSTRAINT(0x02a3, 0xf), + INTEL_UEVENT_CONSTRAINT(0x05a3, 0xf), + INTEL_UEVENT_CONSTRAINT(0x06a3, 0xf), INTEL_UEVENT_CONSTRAINT(0x08a3, 0xf), + INTEL_UEVENT_CONSTRAINT(0x0ca3, 0xf), INTEL_UEVENT_CONSTRAINT(0x04a4, 0x1), INTEL_UEVENT_CONSTRAINT(0x08a4, 0x1), + INTEL_UEVENT_CONSTRAINT(0x01cd, 0xfe), INTEL_UEVENT_CONSTRAINT(0x02cd, 0x1), INTEL_EVENT_CONSTRAINT(0xce, 0x1), INTEL_EVENT_CONSTRAINT_RANGE(0xd0, 0xdf, 0xf), @@ -714,18 +719,18 @@ static __initconst const u64 glc_hw_cache_extra_regs { [ C(LL ) ] = { [ C(OP_READ) ] = { - [ C(RESULT_ACCESS) ] = 0x10001, - [ C(RESULT_MISS) ] = 0x3fbfc00001, + [ C(RESULT_ACCESS) ] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x3fbfc00001, /* OCR.DEMAND_DATA_RD.L3_MISS */ }, [ C(OP_WRITE) ] = { - [ C(RESULT_ACCESS) ] = 0x3f3ffc0002, - [ C(RESULT_MISS) ] = 0x3f3fc00002, + [ C(RESULT_ACCESS) ] = 0x3f3ffc0002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x3f3fc00002, /* OCR.DEMAND_RFO.L3_MISS */ }, }, [ C(NODE) ] = { [ C(OP_READ) ] = { - [ C(RESULT_ACCESS) ] = 0x10c000001, - [ C(RESULT_MISS) ] = 0x3fb3000001, + [ C(RESULT_ACCESS) ] = 0x104000001, /* OCR.DEMAND_DATA_RD.LOCAL_DRAM */ + [ C(RESULT_MISS) ] = 0x730000001, /* OCR.DEMAND_DATA_RD.REMOTE_DRAM */ }, }, }; From 070bd45e1dba684bfa4e7fdaa8ef8efa99b4572e Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:35 +0800 Subject: [PATCH 062/167] perf/x86/intel: Update event constraints for DMR Add missed event constraint for 0x0200 event and add comments to show the event names in pnc_hw_cache_extra_regs[]. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-4-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 932f612a99f3..da2ade0bb6c4 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -466,11 +466,12 @@ static struct extra_reg intel_lnc_extra_regs[] __read_mostly = { static struct event_constraint intel_pnc_event_constraints[] = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ - FIXED_EVENT_CONSTRAINT(0x0100, 0), /* INST_RETIRED.PREC_DIST */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* CPU_CLK_UNHALTED.REF */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ FIXED_EVENT_CONSTRAINT(0x013c, 2), /* CPU_CLK_UNHALTED.REF_TSC_P */ - FIXED_EVENT_CONSTRAINT(0x0400, 3), /* SLOTS */ + FIXED_EVENT_CONSTRAINT(0x0400, 3), /* pseudo TOPDOWN.SLOTS */ METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_RETIRING, 0), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_BAD_SPEC, 1), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_FE_BOUND, 2), @@ -821,12 +822,12 @@ static __initconst const u64 pnc_hw_cache_extra_regs { [ C(LL ) ] = { [ C(OP_READ) ] = { - [ C(RESULT_ACCESS) ] = 0x4000000000000001, - [ C(RESULT_MISS) ] = 0xFFFFF000000001, + [ C(RESULT_ACCESS) ] = 0x4000000000000001, /* OMR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0xFFFFF000000001, /* OMR.DEMAND_DATA_RD.L3_MISS */ }, [ C(OP_WRITE) ] = { - [ C(RESULT_ACCESS) ] = 0x4000000000000002, - [ C(RESULT_MISS) ] = 0xFFFFF000000002, + [ C(RESULT_ACCESS) ] = 0x4000000000000002, /* OMR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0xFFFFF000000002, /* OMR.DEMAND_RFO.L3_MISS */ }, }, }; From 4ef863352bcde482d65722ed721c3fd3967a67d6 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:36 +0800 Subject: [PATCH 063/167] perf/x86/intel: Update event constraints and cache_extra_regsfor ADL Update perf hard-coded event constraints and cache_extra_regs[] for Alderlake according to the latest ADL perfmon events (V1.39). One important note is that ADL has differences on the L3/node related OCR events although it shares same uarch with SPR server, e.g., ADL has different extra MSR values and no node events. So some variants of structures and functions are introduced to reflect these differences, like adl_glc_hw_cache_event_ids[], adl_glc_hw_cache_extra_regs[] and intel_pmu_init_glc_hybrid(), etc. Please note these changes would temporarily impact other platforms like MTL/ARL-U which shares hard-coded event structures, but it would be fixed soon in subsequent patches. ADL perfmon events: https://github.com/intel/perfmon/blob/main/ADL/events/alderlake_goldencove_core.json https://github.com/intel/perfmon/blob/main/ADL/events/alderlake_gracemont_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-5-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 137 +++++++++++++++++++++++++++++++++-- arch/x86/events/intel/ds.c | 2 +- 2 files changed, 131 insertions(+), 8 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index da2ade0bb6c4..be860513eb04 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -215,8 +215,10 @@ static struct event_constraint intel_slm_event_constraints[] __read_mostly = static struct event_constraint intel_grt_event_constraints[] __read_mostly = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ FIXED_EVENT_CONSTRAINT(0x013c, 2), /* CPU_CLK_UNHALTED.REF_TSC_P */ EVENT_CONSTRAINT_END }; @@ -713,6 +715,80 @@ static __initconst const u64 glc_hw_cache_event_ids }, }; +/* ADL P-core (Golden cove) specific event code. */ +static __initconst const u64 adl_glc_hw_cache_event_ids + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = +{ + [ C(L1D ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x81d0, + [ C(RESULT_MISS) ] = 0xe124, + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x82d0, + }, + }, + [ C(L1I ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_MISS) ] = 0xe424, + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = -1, + [ C(RESULT_MISS) ] = -1, + }, + }, + [ C(LL ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x12a, + [ C(RESULT_MISS) ] = 0x12a, + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x12a, + [ C(RESULT_MISS) ] = 0x12a, + }, + }, + [ C(DTLB) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x81d0, + [ C(RESULT_MISS) ] = 0xe12, + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x82d0, + [ C(RESULT_MISS) ] = 0xe13, + }, + }, + [ C(ITLB) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = -1, + [ C(RESULT_MISS) ] = 0xe11, + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = -1, + [ C(RESULT_MISS) ] = -1, + }, + [ C(OP_PREFETCH) ] = { + [ C(RESULT_ACCESS) ] = -1, + [ C(RESULT_MISS) ] = -1, + }, + }, + [ C(BPU ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x4c4, + [ C(RESULT_MISS) ] = 0x4c5, + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = -1, + [ C(RESULT_MISS) ] = -1, + }, + [ C(OP_PREFETCH) ] = { + [ C(RESULT_ACCESS) ] = -1, + [ C(RESULT_MISS) ] = -1, + }, + }, +}; + static __initconst const u64 glc_hw_cache_extra_regs [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -736,6 +812,24 @@ static __initconst const u64 glc_hw_cache_extra_regs }, }; +/* ADL P-core (Golden cove) specific extra regs value. */ +static __initconst const u64 adl_glc_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = +{ + [ C(LL ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x3fbfc00001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x3fbfc00002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + static __initconst const u64 pnc_hw_cache_event_ids [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -2384,6 +2478,23 @@ static __initconst const u64 tnt_hw_cache_extra_regs }, }; +static __initconst const u64 grt_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + [C(LL)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x3F84400001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x3F84400002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + + static __initconst const u64 arw_hw_cache_extra_regs [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -2434,9 +2545,12 @@ static struct attribute *grt_mem_attrs[] = { }; static struct extra_reg intel_grt_extra_regs[] __read_mostly = { - /* must define OFFCORE_RSP_X first, see intel_fixup_er() */ - INTEL_UEVENT_EXTRA_REG(0x01b7, MSR_OFFCORE_RSP_0, 0x3fffffffffull, RSP_0), - INTEL_UEVENT_EXTRA_REG(0x02b7, MSR_OFFCORE_RSP_1, 0x3fffffffffull, RSP_1), + /* + * Must define OFFCORE_RSP_X first, see intel_fixup_er(). + * Bit 63 only valid on OFFCORE_RSP_0 MSR. + */ + INTEL_UEVENT_EXTRA_REG(0x01b7, MSR_OFFCORE_RSP_0, 0x8003f03fffffffffull, RSP_0), + INTEL_UEVENT_EXTRA_REG(0x02b7, MSR_OFFCORE_RSP_1, 0x3f03fffffffffull, RSP_1), INTEL_UEVENT_PEBS_LDLAT_EXTRA_REG(0x5d0), EVENT_EXTRA_END }; @@ -7533,6 +7647,15 @@ static __always_inline void intel_pmu_init_glc(struct pmu *pmu) intel_pmu_ref_cycles_ext(); } +static __always_inline void intel_pmu_init_glc_hybrid(struct pmu *pmu) +{ + intel_pmu_init_glc(pmu); + + /* ADL has different extra MSR values from Server for the L3 or node OCR/OMR events. */ + memcpy(hybrid_var(pmu, hw_cache_event_ids), adl_glc_hw_cache_event_ids, sizeof(hw_cache_event_ids)); + memcpy(hybrid_var(pmu, hw_cache_extra_regs), adl_glc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); +} + static __always_inline void intel_pmu_init_grt(struct pmu *pmu) { x86_pmu.mid_ack = true; @@ -7545,7 +7668,7 @@ static __always_inline void intel_pmu_init_grt(struct pmu *pmu) x86_pmu.flags |= PMU_FL_INSTR_LATENCY; memcpy(hybrid_var(pmu, hw_cache_event_ids), glp_hw_cache_event_ids, sizeof(hw_cache_event_ids)); - memcpy(hybrid_var(pmu, hw_cache_extra_regs), tnt_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); + memcpy(hybrid_var(pmu, hw_cache_extra_regs), grt_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); hybrid_var(pmu, hw_cache_event_ids)[C(ITLB)][C(OP_READ)][C(RESULT_ACCESS)] = -1; hybrid(pmu, event_constraints) = intel_grt_event_constraints; hybrid(pmu, pebs_constraints) = intel_grt_pebs_event_constraints; @@ -8304,7 +8427,7 @@ __init int intel_pmu_init(void) /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; - intel_pmu_init_glc(&pmu->pmu); + intel_pmu_init_glc_hybrid(&pmu->pmu); if (cpu_feature_enabled(X86_FEATURE_HYBRID_CPU)) { pmu->cntr_mask64 <<= 2; pmu->cntr_mask64 |= 0x3; @@ -8361,7 +8484,7 @@ __init int intel_pmu_init(void) /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; - intel_pmu_init_glc(&pmu->pmu); + intel_pmu_init_glc_hybrid(&pmu->pmu); pmu->extra_regs = intel_rwc_extra_regs; /* Initialize Atom core specific PerfMon capabilities.*/ diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 7f0d515c07c5..efab3cb47885 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1292,7 +1292,7 @@ struct event_constraint intel_glm_pebs_event_constraints[] = { struct event_constraint intel_grt_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xf), + INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0x3f), EVENT_CONSTRAINT_END }; From e99fb45436eaa4ac1b72a8e4af56381f59759b0c Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:37 +0800 Subject: [PATCH 064/167] perf/x86/intel: Update event constraints and cache_extra_regsfor MTL Update perf hard-coded event constraints and cache_extra_regs[] for Meteor Lake according to the latest MTL perfmon events (V1.21). MTL P-core (redwoodcove) inherits same perf events list from previous generation (Goldencove), but the E-core (Crestmont) brings some difference on the perf event list comparing with Gracemont. So apply the changes for Crestmont core. MTL perfmon events: https://github.com/intel/perfmon/blob/main/MTL/events/meteorlake_redwoodcove_core.json https://github.com/intel/perfmon/blob/main/MTL/events/meteorlake_crestmont_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-6-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 27 +++++++++++++++++++++++++-- arch/x86/events/intel/ds.c | 7 +++++++ arch/x86/events/perf_event.h | 2 ++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index be860513eb04..81619127ef04 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -2494,6 +2494,21 @@ static __initconst const u64 grt_hw_cache_extra_regs }, }; +static __initconst const u64 cmt_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + [C(LL)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x3fbfc00001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x3fbfc00002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; static __initconst const u64 arw_hw_cache_extra_regs [PERF_COUNT_HW_CACHE_MAX] @@ -7677,6 +7692,15 @@ static __always_inline void intel_pmu_init_grt(struct pmu *pmu) intel_pmu_ref_cycles_ext(); } +static __always_inline void intel_pmu_init_cmt(struct pmu *pmu) +{ + intel_pmu_init_grt(pmu); + memcpy(hybrid_var(pmu, hw_cache_extra_regs), + cmt_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); + hybrid(pmu, pebs_constraints) = intel_cmt_pebs_event_constraints; + hybrid(pmu, extra_regs) = intel_cmt_extra_regs; +} + static __always_inline void intel_pmu_init_lnc(struct pmu *pmu) { intel_pmu_init_glc(pmu); @@ -8489,8 +8513,7 @@ __init int intel_pmu_init(void) /* Initialize Atom core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; - intel_pmu_init_grt(&pmu->pmu); - pmu->extra_regs = intel_cmt_extra_regs; + intel_pmu_init_cmt(&pmu->pmu); intel_pmu_pebs_data_source_mtl(); pr_cont("Meteorlake Hybrid events, "); diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index efab3cb47885..75b7f6f6d8bc 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1296,6 +1296,13 @@ struct event_constraint intel_grt_pebs_event_constraints[] = { EVENT_CONSTRAINT_END }; +struct event_constraint intel_cmt_pebs_event_constraints[] = { + /* Allow all events as PEBS with no flags */ + INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0x3), + INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + EVENT_CONSTRAINT_END +}; + struct event_constraint intel_arw_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 40d6fe0afc4a..a9acfbe3c435 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1710,6 +1710,8 @@ extern struct event_constraint intel_glp_pebs_event_constraints[]; extern struct event_constraint intel_grt_pebs_event_constraints[]; +extern struct event_constraint intel_cmt_pebs_event_constraints[]; + extern struct event_constraint intel_arw_pebs_event_constraints[]; extern struct event_constraint intel_nehalem_pebs_event_constraints[]; From 331c3e4fa39a87560c09bdd878652090ae040b69 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:38 +0800 Subject: [PATCH 065/167] perf/x86/intel: Update event constraints and cache_extra_regsfor LNL Update perf hard-coded event constraints and cache_extra_regs[] for Lunarlake according to the latest LNL perfmon events (V1.22). LNL introduces new extra register values for the OCR L3 cache events, so introduce lnc_hw_cache_extra_regs[] and skt_hw_cache_extra_regs[] to reflect the changes. LNL perfmon events: https://github.com/intel/perfmon/blob/main/LNL/events/lunarlake_lioncove_core.json https://github.com/intel/perfmon/blob/main/LNL/events/lunarlake_skymont_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-7-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 62 +++++++++++++++++++++++++++++------- arch/x86/events/intel/ds.c | 8 +++++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 81619127ef04..dc0991cee8c6 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -225,12 +225,17 @@ static struct event_constraint intel_grt_event_constraints[] __read_mostly = { static struct event_constraint intel_skt_event_constraints[] __read_mostly = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ FIXED_EVENT_CONSTRAINT(0x013c, 2), /* CPU_CLK_UNHALTED.REF_TSC_P */ FIXED_EVENT_CONSTRAINT(0x0073, 4), /* TOPDOWN_BAD_SPECULATION.ALL */ + FIXED_EVENT_CONSTRAINT(0x0500, 4), /* pseudo TOPDOWN_BAD_SPECULATION.ALL */ FIXED_EVENT_CONSTRAINT(0x019c, 5), /* TOPDOWN_FE_BOUND.ALL */ + FIXED_EVENT_CONSTRAINT(0x0600, 5), /* pseudo TOPDOWN_FE_BOUND.ALL */ FIXED_EVENT_CONSTRAINT(0x02c2, 6), /* TOPDOWN_RETIRING.ALL */ + FIXED_EVENT_CONSTRAINT(0x0700, 6), /* pseudo TOPDOWN_RETIRING.ALL */ EVENT_CONSTRAINT_END }; @@ -415,11 +420,12 @@ static struct extra_reg intel_rwc_extra_regs[] __read_mostly = { static struct event_constraint intel_lnc_event_constraints[] = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ - FIXED_EVENT_CONSTRAINT(0x0100, 0), /* INST_RETIRED.PREC_DIST */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* CPU_CLK_UNHALTED.REF */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ FIXED_EVENT_CONSTRAINT(0x013c, 2), /* CPU_CLK_UNHALTED.REF_TSC_P */ - FIXED_EVENT_CONSTRAINT(0x0400, 3), /* SLOTS */ + FIXED_EVENT_CONSTRAINT(0x0400, 3), /* pseudo TOPDOWN.SLOTS */ METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_RETIRING, 0), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_BAD_SPEC, 1), METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_FE_BOUND, 2), @@ -431,8 +437,6 @@ static struct event_constraint intel_lnc_event_constraints[] = { INTEL_EVENT_CONSTRAINT(0x20, 0xf), - INTEL_UEVENT_CONSTRAINT(0x012a, 0xf), - INTEL_UEVENT_CONSTRAINT(0x012b, 0xf), INTEL_UEVENT_CONSTRAINT(0x0148, 0x4), INTEL_UEVENT_CONSTRAINT(0x0175, 0x4), @@ -443,15 +447,14 @@ static struct event_constraint intel_lnc_event_constraints[] = { INTEL_UEVENT_CONSTRAINT(0x0ca3, 0x4), INTEL_UEVENT_CONSTRAINT(0x04a4, 0x1), INTEL_UEVENT_CONSTRAINT(0x08a4, 0x1), - INTEL_UEVENT_CONSTRAINT(0x10a4, 0x1), + INTEL_UEVENT_CONSTRAINT(0x10a4, 0x8), INTEL_UEVENT_CONSTRAINT(0x01b1, 0x8), INTEL_UEVENT_CONSTRAINT(0x01cd, 0x3fc), INTEL_UEVENT_CONSTRAINT(0x02cd, 0x3), + INTEL_UEVENT_CONSTRAINT(0x87d0, 0x3ff), INTEL_EVENT_CONSTRAINT_RANGE(0xd0, 0xdf, 0xf), - INTEL_UEVENT_CONSTRAINT(0x00e0, 0xf), - EVENT_CONSTRAINT_END }; @@ -830,6 +833,23 @@ static __initconst const u64 adl_glc_hw_cache_extra_regs }, }; +static __initconst const u64 lnc_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = +{ + [ C(LL ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x9E7FA000001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0x9E7FA000002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + static __initconst const u64 pnc_hw_cache_event_ids [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -2510,6 +2530,22 @@ static __initconst const u64 cmt_hw_cache_extra_regs }, }; +static __initconst const u64 skt_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + [C(LL)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x13FBFC00001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x13FBFC00002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + static __initconst const u64 arw_hw_cache_extra_regs [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -7707,6 +7743,9 @@ static __always_inline void intel_pmu_init_lnc(struct pmu *pmu) hybrid(pmu, event_constraints) = intel_lnc_event_constraints; hybrid(pmu, pebs_constraints) = intel_lnc_pebs_event_constraints; hybrid(pmu, extra_regs) = intel_lnc_extra_regs; + + memcpy(hybrid_var(pmu, hw_cache_event_ids), adl_glc_hw_cache_event_ids, sizeof(hw_cache_event_ids)); + memcpy(hybrid_var(pmu, hw_cache_extra_regs), lnc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); } static __always_inline void intel_pmu_init_pnc(struct pmu *pmu) @@ -7726,9 +7765,10 @@ static __always_inline void intel_pmu_init_pnc(struct pmu *pmu) static __always_inline void intel_pmu_init_skt(struct pmu *pmu) { - intel_pmu_init_grt(pmu); + intel_pmu_init_cmt(pmu); hybrid(pmu, event_constraints) = intel_skt_event_constraints; - hybrid(pmu, extra_regs) = intel_cmt_extra_regs; + memcpy(hybrid_var(pmu, hw_cache_extra_regs), + skt_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); static_call_update(intel_pmu_enable_acr_event, intel_pmu_enable_acr); } diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 75b7f6f6d8bc..ce23b50f449a 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1507,6 +1507,13 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { INTEL_FLAGS_UEVENT_CONSTRAINT(0x100, 0x100000000ULL), /* INST_RETIRED.PREC_DIST */ INTEL_FLAGS_UEVENT_CONSTRAINT(0x0400, 0x800000000ULL), + INTEL_FLAGS_UEVENT_CONSTRAINT(0x012a, 0x1), /* OCR.* events */ + INTEL_FLAGS_UEVENT_CONSTRAINT(0x012b, 0x1), /* OCR.* events */ + + INTEL_FLAGS_UEVENT_CONSTRAINT(0x04a4, 0x1), /* TOPDOWN.BAD_SPEC_SLOTS */ + INTEL_FLAGS_UEVENT_CONSTRAINT(0x08a4, 0x1), /* TOPDOWN.BR_MISPREDICT_SLOTS */ + INTEL_FLAGS_UEVENT_CONSTRAINT(0x10a4, 0x8), /* TOPDOWN.MEMORY_BOUND_SLOTS */ + INTEL_HYBRID_LDLAT_CONSTRAINT(0x1cd, 0x3fc), INTEL_HYBRID_STLAT_CONSTRAINT(0x2cd, 0x3), INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x11d0, 0xf), /* MEM_INST_RETIRED.STLB_MISS_LOADS */ @@ -1516,6 +1523,7 @@ struct event_constraint intel_lnc_pebs_event_constraints[] = { INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x42d0, 0xf), /* MEM_INST_RETIRED.SPLIT_STORES */ INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_LD(0x81d0, 0xf), /* MEM_INST_RETIRED.ALL_LOADS */ INTEL_FLAGS_UEVENT_CONSTRAINT_DATALA_ST(0x82d0, 0xf), /* MEM_INST_RETIRED.ALL_STORES */ + INTEL_FLAGS_UEVENT_CONSTRAINT(0x87d0, 0x3ff), /* MEM_INST_RETIRED.ANY */ INTEL_FLAGS_EVENT_CONSTRAINT_DATALA_LD_RANGE(0xd1, 0xd4, 0xf), From 0073ed169226f4ed65e339f770118f54ce4e1370 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:39 +0800 Subject: [PATCH 066/167] perf/x86/intel: Update event constraints and cache_extra_regsfor ARL Update perf hard-coded event constraints and cache_extra_regs[] for Arrowlake according to the latest ARL perfmon events (V1.17). ARL shares almost same event constraints and extra MSR configuration with LNL except 2 differences. - ARL P-core has different extra MSR value for OCR.DEMAND_DATA_RD.L3_MISS and OCR.DEMAND_RFO.L3_MISS. So introduce arl_lnc_hw_cache_extra_regs[] to reflect the difference. - ARL-H has extra LPE cores which use crestmont architectures. Add crestmont specific event constraints and hw_cache_extra_regs[] for LPE cores. ARL perfmon events: https://github.com/intel/perfmon/blob/main/ARL/events/arrowlake_lioncove_core.json https://github.com/intel/perfmon/blob/main/ARL/events/arrowlake_skymont_core.json https://github.com/intel/perfmon/blob/main/ARL/events/arrowlake_crestmont_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-8-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 56 ++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index dc0991cee8c6..86ed34d2451c 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -850,6 +850,24 @@ static __initconst const u64 lnc_hw_cache_extra_regs }, }; +/* ARL specific lioncove hw_cache_extra_regs[] variant. */ +static __initconst const u64 arl_lnc_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = +{ + [ C(LL ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0xFE7F8000001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0xFE7F8000002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + static __initconst const u64 pnc_hw_cache_event_ids [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -8564,16 +8582,41 @@ __init int intel_pmu_init(void) case INTEL_WILDCATLAKE_L: pr_cont("Pantherlake Hybrid events, "); name = "pantherlake_hybrid"; + + intel_pmu_init_hybrid(hybrid_big_small); + + /* Initialize big core specific PerfMon capabilities.*/ + pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; + intel_pmu_init_lnc(&pmu->pmu); + + goto lnl_common; + + case INTEL_ARROWLAKE: + pr_cont("Arrowlake Hybrid events, "); + name = "arrowlake_hybrid"; + + intel_pmu_init_hybrid(hybrid_big_small); + + /* Initialize big core specific PerfMon capabilities.*/ + pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; + intel_pmu_init_lnc(&pmu->pmu); + memcpy(hybrid_var(&pmu->pmu, hw_cache_extra_regs), + arl_lnc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); + goto lnl_common; case INTEL_LUNARLAKE_M: - case INTEL_ARROWLAKE: pr_cont("Lunarlake Hybrid events, "); name = "lunarlake_hybrid"; - lnl_common: intel_pmu_init_hybrid(hybrid_big_small); + /* Initialize big core specific PerfMon capabilities.*/ + pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; + intel_pmu_init_lnc(&pmu->pmu); + + lnl_common: + x86_pmu.pebs_latency_data = lnl_latency_data; x86_pmu.get_event_constraints = mtl_get_event_constraints; x86_pmu.hw_config = adl_hw_config; @@ -8584,10 +8627,6 @@ __init int intel_pmu_init(void) extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? mtl_hybrid_extra_attr_rtm : mtl_hybrid_extra_attr; - /* Initialize big core specific PerfMon capabilities.*/ - pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; - intel_pmu_init_lnc(&pmu->pmu); - /* Initialize Atom core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; intel_pmu_init_skt(&pmu->pmu); @@ -8611,6 +8650,8 @@ __init int intel_pmu_init(void) /* Initialize big core specific PerfMon capabilities. */ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; intel_pmu_init_lnc(&pmu->pmu); + memcpy(hybrid_var(&pmu->pmu, hw_cache_extra_regs), + arl_lnc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); /* Initialize Atom core specific PerfMon capabilities. */ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; @@ -8618,8 +8659,7 @@ __init int intel_pmu_init(void) /* Initialize Lower Power Atom specific PerfMon capabilities. */ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_TINY_IDX]; - intel_pmu_init_grt(&pmu->pmu); - pmu->extra_regs = intel_cmt_extra_regs; + intel_pmu_init_cmt(&pmu->pmu); intel_pmu_pebs_data_source_arl_h(); pr_cont("ArrowLake-H Hybrid events, "); From 65fd435095bb299b9c50d3285d4e6569b79b86e2 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:40 +0800 Subject: [PATCH 067/167] perf/x86/intel: Update event constraints for PTL Update perf hard-coded event constraints for Pantherlake according to the latest PTL perfmon events (V1.05). PTL has almost same perf event list as LNL except some PEBS event constraints of E-core (exactly same on P-core). Define intel_dkt_pebs_event_constraints[] to reflect the PTL E-core specific PEBS event constraints. PTL perfmon events: https://github.com/intel/perfmon/blob/main/PTL/events/pantherlake_cougarcove_core.json https://github.com/intel/perfmon/blob/main/PTL/events/pantherlake_darkmont_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-9-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 20 ++++++++++++++++---- arch/x86/events/intel/ds.c | 7 +++++++ arch/x86/events/perf_event.h | 2 ++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 86ed34d2451c..60a107c5b4d0 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -7790,6 +7790,13 @@ static __always_inline void intel_pmu_init_skt(struct pmu *pmu) static_call_update(intel_pmu_enable_acr_event, intel_pmu_enable_acr); } +/* Hybrid client variant. */ +static __always_inline void intel_pmu_init_dkt_hybrid(struct pmu *pmu) +{ + intel_pmu_init_skt(pmu); + hybrid(pmu, pebs_constraints) = intel_dkt_pebs_event_constraints; +} + static __always_inline void intel_pmu_init_arw(struct pmu *pmu) { intel_pmu_init_grt(pmu); @@ -8588,6 +8595,9 @@ __init int intel_pmu_init(void) /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; intel_pmu_init_lnc(&pmu->pmu); + /* Initialize Atom core specific PerfMon capabilities.*/ + pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; + intel_pmu_init_dkt_hybrid(&pmu->pmu); goto lnl_common; @@ -8602,6 +8612,9 @@ __init int intel_pmu_init(void) intel_pmu_init_lnc(&pmu->pmu); memcpy(hybrid_var(&pmu->pmu, hw_cache_extra_regs), arl_lnc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); + /* Initialize Atom core specific PerfMon capabilities.*/ + pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; + intel_pmu_init_skt(&pmu->pmu); goto lnl_common; @@ -8614,6 +8627,9 @@ __init int intel_pmu_init(void) /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; intel_pmu_init_lnc(&pmu->pmu); + /* Initialize Atom core specific PerfMon capabilities.*/ + pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; + intel_pmu_init_skt(&pmu->pmu); lnl_common: @@ -8627,10 +8643,6 @@ __init int intel_pmu_init(void) extra_attr = boot_cpu_has(X86_FEATURE_RTM) ? mtl_hybrid_extra_attr_rtm : mtl_hybrid_extra_attr; - /* Initialize Atom core specific PerfMon capabilities.*/ - pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; - intel_pmu_init_skt(&pmu->pmu); - intel_pmu_pebs_data_source_lnl(); break; diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index ce23b50f449a..5159adabb9a2 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1303,6 +1303,13 @@ struct event_constraint intel_cmt_pebs_event_constraints[] = { EVENT_CONSTRAINT_END }; +struct event_constraint intel_dkt_pebs_event_constraints[] = { + /* Allow all events as PEBS with no flags */ + INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), + INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), + EVENT_CONSTRAINT_END +}; + struct event_constraint intel_arw_pebs_event_constraints[] = { /* Allow all events as PEBS with no flags */ INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index a9acfbe3c435..982864c7a064 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1712,6 +1712,8 @@ extern struct event_constraint intel_grt_pebs_event_constraints[]; extern struct event_constraint intel_cmt_pebs_event_constraints[]; +extern struct event_constraint intel_dkt_pebs_event_constraints[]; + extern struct event_constraint intel_arw_pebs_event_constraints[]; extern struct event_constraint intel_nehalem_pebs_event_constraints[]; From 42f47511d9795dd4ddeca7145e171f75a1a7b8eb Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:41 +0800 Subject: [PATCH 068/167] perf/x86/intel: Update event constraints and cache_extra_regsfor NVL Update perf hard-coded event constraints and cache_extra_regs[] for Novalake according to the latest NVL perfmon events. The 4 PRECISE_OMR events (0xd4) are broken on Arcticwolf and would be removed from upcoming released event list, so delete them from event constraints and extra_regs array accordingly. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-10-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 55 +++++++++++++++++++++++------------- arch/x86/events/intel/ds.c | 11 -------- arch/x86/events/perf_event.h | 2 -- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 60a107c5b4d0..332761d25373 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -241,20 +241,21 @@ static struct event_constraint intel_skt_event_constraints[] __read_mostly = { static struct event_constraint intel_arw_event_constraints[] __read_mostly = { FIXED_EVENT_CONSTRAINT(0x00c0, 0), /* INST_RETIRED.ANY */ + FIXED_EVENT_CONSTRAINT(0x0100, 0), /* pseudo INST_RETIRED.ANY */ FIXED_EVENT_CONSTRAINT(0x003c, 1), /* CPU_CLK_UNHALTED.CORE */ - FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF */ + FIXED_EVENT_CONSTRAINT(0x0200, 1), /* pseudo CPU_CLK_UNHALTED.THREAD */ + FIXED_EVENT_CONSTRAINT(0x0300, 2), /* pseudo CPU_CLK_UNHALTED.REF_TSC */ FIXED_EVENT_CONSTRAINT(0x013c, 2), /* CPU_CLK_UNHALTED.REF_TSC_P */ FIXED_EVENT_CONSTRAINT(0x0073, 4), /* TOPDOWN_BAD_SPECULATION.ALL */ + FIXED_EVENT_CONSTRAINT(0x0500, 4), /* pseudo TOPDOWN_BAD_SPECULATION.ALL */ FIXED_EVENT_CONSTRAINT(0x019c, 5), /* TOPDOWN_FE_BOUND.ALL */ + FIXED_EVENT_CONSTRAINT(0x0600, 5), /* pseudo TOPDOWN_FE_BOUND.ALL */ FIXED_EVENT_CONSTRAINT(0x02c2, 6), /* TOPDOWN_RETIRING.ALL */ + FIXED_EVENT_CONSTRAINT(0x0700, 6), /* pseudo TOPDOWN_RETIRING.ALL */ INTEL_UEVENT_CONSTRAINT(0x01b7, 0x1), INTEL_UEVENT_CONSTRAINT(0x02b7, 0x2), INTEL_UEVENT_CONSTRAINT(0x04b7, 0x4), INTEL_UEVENT_CONSTRAINT(0x08b7, 0x8), - INTEL_UEVENT_CONSTRAINT(0x01d4, 0x1), - INTEL_UEVENT_CONSTRAINT(0x02d4, 0x2), - INTEL_UEVENT_CONSTRAINT(0x04d4, 0x4), - INTEL_UEVENT_CONSTRAINT(0x08d4, 0x8), INTEL_UEVENT_CONSTRAINT(0x0175, 0x1), INTEL_UEVENT_CONSTRAINT(0x0275, 0x2), INTEL_UEVENT_CONSTRAINT(0x21d3, 0x1), @@ -964,6 +965,23 @@ static __initconst const u64 pnc_hw_cache_extra_regs }, }; +static __initconst const u64 cyc_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = +{ + [ C(LL ) ] = { + [ C(OP_READ) ] = { + [ C(RESULT_ACCESS) ] = 0x4000000000000001, /* OMR.DEMAND_DATA_RD.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0xFF03F000000001, /* OMR.DEMAND_DATA_RD.L3_MISS */ + }, + [ C(OP_WRITE) ] = { + [ C(RESULT_ACCESS) ] = 0x4000000000000002, /* OMR.DEMAND_RFO.ANY_RESPONSE */ + [ C(RESULT_MISS) ] = 0xFF03F000000002, /* OMR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + /* * Notes on the events: * - data reads do not include code reads (comparable to earlier tables) @@ -2570,16 +2588,12 @@ static __initconst const u64 arw_hw_cache_extra_regs [PERF_COUNT_HW_CACHE_RESULT_MAX] = { [C(LL)] = { [C(OP_READ)] = { - [C(RESULT_ACCESS)] = 0x4000000000000001, - [C(RESULT_MISS)] = 0xFFFFF000000001, + [C(RESULT_ACCESS)] = 0x4000000000000009, /* OMR.DEMAND_DATA_RD.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0xFF03F000000009, /* OMR.DEMAND_DATA_RD.L3_MISS */ }, [C(OP_WRITE)] = { - [C(RESULT_ACCESS)] = 0x4000000000000002, - [C(RESULT_MISS)] = 0xFFFFF000000002, - }, - [C(OP_PREFETCH)] = { - [C(RESULT_ACCESS)] = 0x0, - [C(RESULT_MISS)] = 0x0, + [C(RESULT_ACCESS)] = 0x400000000000000A, /* OMR.DEMAND_RFO.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0xFF03F00000000A, /* OMR.DEMAND_RFO.L3_MISS */ }, }, }; @@ -2651,10 +2665,6 @@ static struct extra_reg intel_arw_extra_regs[] __read_mostly = { INTEL_UEVENT_EXTRA_REG(0x02b7, MSR_OMR_1, 0xc0ffffffffffffffull, OMR_1), INTEL_UEVENT_EXTRA_REG(0x04b7, MSR_OMR_2, 0xc0ffffffffffffffull, OMR_2), INTEL_UEVENT_EXTRA_REG(0x08b7, MSR_OMR_3, 0xc0ffffffffffffffull, OMR_3), - INTEL_UEVENT_EXTRA_REG(0x01d4, MSR_OMR_0, 0xc0ffffffffffffffull, OMR_0), - INTEL_UEVENT_EXTRA_REG(0x02d4, MSR_OMR_1, 0xc0ffffffffffffffull, OMR_1), - INTEL_UEVENT_EXTRA_REG(0x04d4, MSR_OMR_2, 0xc0ffffffffffffffull, OMR_2), - INTEL_UEVENT_EXTRA_REG(0x08d4, MSR_OMR_3, 0xc0ffffffffffffffull, OMR_3), INTEL_UEVENT_PEBS_LDLAT_EXTRA_REG(0x5d0), INTEL_UEVENT_EXTRA_REG(0x0127, MSR_SNOOP_RSP_0, 0xffffffffffffffffull, SNOOP_0), INTEL_UEVENT_EXTRA_REG(0x0227, MSR_SNOOP_RSP_1, 0xffffffffffffffffull, SNOOP_1), @@ -7781,6 +7791,13 @@ static __always_inline void intel_pmu_init_pnc(struct pmu *pmu) static_call_update(intel_pmu_enable_acr_event, intel_pmu_enable_acr); } +static __always_inline void intel_pmu_init_cyc(struct pmu *pmu) +{ + intel_pmu_init_pnc(pmu); + memcpy(hybrid_var(pmu, hw_cache_extra_regs), + cyc_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); +} + static __always_inline void intel_pmu_init_skt(struct pmu *pmu) { intel_pmu_init_cmt(pmu); @@ -7805,7 +7822,7 @@ static __always_inline void intel_pmu_init_arw(struct pmu *pmu) memcpy(hybrid_var(pmu, hw_cache_extra_regs), arw_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); hybrid(pmu, event_constraints) = intel_arw_event_constraints; - hybrid(pmu, pebs_constraints) = intel_arw_pebs_event_constraints; + hybrid(pmu, pebs_constraints) = intel_dkt_pebs_event_constraints; hybrid(pmu, extra_regs) = intel_arw_extra_regs; static_call_update(intel_pmu_enable_acr_event, intel_pmu_enable_acr); } @@ -8696,7 +8713,7 @@ __init int intel_pmu_init(void) /* Initialize big core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_CORE_IDX]; - intel_pmu_init_pnc(&pmu->pmu); + intel_pmu_init_cyc(&pmu->pmu); /* Initialize Atom core specific PerfMon capabilities.*/ pmu = &x86_pmu.hybrid_pmu[X86_HYBRID_PMU_ATOM_IDX]; diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 5159adabb9a2..cb72af9b61ce 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1310,17 +1310,6 @@ struct event_constraint intel_dkt_pebs_event_constraints[] = { EVENT_CONSTRAINT_END }; -struct event_constraint intel_arw_pebs_event_constraints[] = { - /* Allow all events as PEBS with no flags */ - INTEL_HYBRID_LAT_CONSTRAINT(0x5d0, 0xff), - INTEL_HYBRID_LAT_CONSTRAINT(0x6d0, 0xff), - INTEL_FLAGS_UEVENT_CONSTRAINT(0x01d4, 0x1), - INTEL_FLAGS_UEVENT_CONSTRAINT(0x02d4, 0x2), - INTEL_FLAGS_UEVENT_CONSTRAINT(0x04d4, 0x4), - INTEL_FLAGS_UEVENT_CONSTRAINT(0x08d4, 0x8), - EVENT_CONSTRAINT_END -}; - struct event_constraint intel_nehalem_pebs_event_constraints[] = { INTEL_PLD_CONSTRAINT(0x100b, 0xf), /* MEM_INST_RETIRED.* */ INTEL_FLAGS_EVENT_CONSTRAINT(0x0f, 0xf), /* MEM_UNCORE_RETIRED.* */ diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 982864c7a064..eae24bb35dc1 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1714,8 +1714,6 @@ extern struct event_constraint intel_cmt_pebs_event_constraints[]; extern struct event_constraint intel_dkt_pebs_event_constraints[]; -extern struct event_constraint intel_arw_pebs_event_constraints[]; - extern struct event_constraint intel_nehalem_pebs_event_constraints[]; extern struct event_constraint intel_westmere_pebs_event_constraints[]; From 7ae5f58517a6604ea86ae2b34cc7252d13d37180 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:42 +0800 Subject: [PATCH 069/167] perf/x86/intel: Update event constraints and cache_extra_regsfor SRF Update perf hard-coded event constraints and cache_extra_regs[] for Sierra Forest according to the latest SRF perfmon events (V1.17). SRF has same uarch (crestmont) as MTL E-core and shares same perf events, so directly apply the crestmont perf events. SRF perfmon events: https://github.com/intel/perfmon/blob/main/SRF/events/sierraforest_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-11-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 332761d25373..c4efb87eea9a 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -8136,8 +8136,7 @@ __init int intel_pmu_init(void) case INTEL_ATOM_CRESTMONT: case INTEL_ATOM_CRESTMONT_X: - intel_pmu_init_grt(NULL); - x86_pmu.extra_regs = intel_cmt_extra_regs; + intel_pmu_init_cmt(NULL); intel_pmu_pebs_data_source_cmt(); x86_pmu.pebs_latency_data = cmt_latency_data; x86_pmu.get_event_constraints = cmt_get_event_constraints; From 66cc29745f2f5815482587bb9fbc1e8a3e6fcf00 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Fri, 15 May 2026 14:11:43 +0800 Subject: [PATCH 070/167] perf/x86/intel: Update event constraints and cache_extra_regsfor CWF Update perf hard-coded event constraints and cache_extra_regs[] for Clearwater Forest according to the latest CWF perfmon events (V1.02). An important difference is that CWF introduce new extra register values for the L3 cache OCR events, so define darkmont specific dkt_hw_cache_extra_regs[] array. CWF perfmon events: https://github.com/intel/perfmon/blob/main/CWF/events/clearwaterforest_core.json Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260515061143.338553-12-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index c4efb87eea9a..0217e701aeeb 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -2582,6 +2582,22 @@ static __initconst const u64 skt_hw_cache_extra_regs }, }; +static __initconst const u64 dkt_hw_cache_extra_regs + [PERF_COUNT_HW_CACHE_MAX] + [PERF_COUNT_HW_CACHE_OP_MAX] + [PERF_COUNT_HW_CACHE_RESULT_MAX] = { + [C(LL)] = { + [C(OP_READ)] = { + [C(RESULT_ACCESS)] = 0x10001, /* OCR.DEMAND_DATA_RD.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x33FBFC00001, /* OCR.DEMAND_DATA_RD.L3_MISS */ + }, + [C(OP_WRITE)] = { + [C(RESULT_ACCESS)] = 0x10002, /* OCR.DEMAND_RFO.ANY_RESPONSE */ + [C(RESULT_MISS)] = 0x33FBFC00002, /* OCR.DEMAND_RFO.L3_MISS */ + }, + }, +}; + static __initconst const u64 arw_hw_cache_extra_regs [PERF_COUNT_HW_CACHE_MAX] [PERF_COUNT_HW_CACHE_OP_MAX] @@ -7814,6 +7830,18 @@ static __always_inline void intel_pmu_init_dkt_hybrid(struct pmu *pmu) hybrid(pmu, pebs_constraints) = intel_dkt_pebs_event_constraints; } +/* + * Darkmont is used by the CWF and PTL E-cores, but their L3 OCR + * events require different extra MSR values. Keep a separate init + * function for the non-hybrid server variant. + */ +static __always_inline void intel_pmu_init_dkt(struct pmu *pmu) +{ + intel_pmu_init_dkt_hybrid(pmu); + memcpy(hybrid_var(pmu, hw_cache_extra_regs), + dkt_hw_cache_extra_regs, sizeof(hw_cache_extra_regs)); +} + static __always_inline void intel_pmu_init_arw(struct pmu *pmu) { intel_pmu_init_grt(pmu); @@ -8148,7 +8176,7 @@ __init int intel_pmu_init(void) break; case INTEL_ATOM_DARKMONT_X: - intel_pmu_init_skt(NULL); + intel_pmu_init_dkt(NULL); intel_pmu_pebs_data_source_cmt(); x86_pmu.pebs_latency_data = cmt_latency_data; x86_pmu.get_event_constraints = cmt_get_event_constraints; From f45c5c4adb27540d43302155e2fbaeca6c3c6d03 Mon Sep 17 00:00:00 2001 From: Marco Elver Date: Fri, 15 May 2026 14:43:31 +0200 Subject: [PATCH 071/167] compiler-context-analysis: Bump required Clang version to 23 Clang 23 introduces several major improvements: 1. Support for multiple arguments in the `guarded_by` and `pt_guarded_by` attributes [1]. This allows defining variables protected by multiple context locks, where read access requires holding at least one lock (shared or exclusive), and write access requires holding all of them exclusively. 2. Function pointer support [2]. We can now add attributes to function pointers just like we do on normal functions. 3. A fix to use arrays of locks [3]. Each index is now correctly treated as a separate lock instance. 4. A fix for implicit member access in attributes [4]. This allows to use __guarded_by(&foo->lock) correctly. Overall that makes it worthwhile bumping the compiler version instead of trying to make both Clang 22 and later work while supporting these new features. Signed-off-by: Marco Elver Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Nathan Chancellor Reviewed-by: Bart Van Assche Link: https://github.com/llvm/llvm-project/pull/186838 [1] Link: https://github.com/llvm/llvm-project/pull/191187 [2] Link: https://github.com/llvm/llvm-project/pull/148551 [3] Link: https://github.com/llvm/llvm-project/pull/194457 [4] Link: https://patch.msgid.link/20260515124426.2227783-1-elver@google.com --- Documentation/dev-tools/context-analysis.rst | 2 +- include/linux/compiler-context-analysis.h | 30 ++++++++++++++------ lib/Kconfig.debug | 4 +-- lib/test_context-analysis.c | 24 ++++++++++++++++ 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/Documentation/dev-tools/context-analysis.rst b/Documentation/dev-tools/context-analysis.rst index 54d9ee28de98..8e71e1e75b5b 100644 --- a/Documentation/dev-tools/context-analysis.rst +++ b/Documentation/dev-tools/context-analysis.rst @@ -17,7 +17,7 @@ features. To enable for Clang, configure the kernel with:: CONFIG_WARN_CONTEXT_ANALYSIS=y -The feature requires Clang 22 or later. +The feature requires Clang 23 or later. The analysis is *opt-in by default*, and requires declaring which modules and subsystems should be analyzed in the respective `Makefile`:: diff --git a/include/linux/compiler-context-analysis.h b/include/linux/compiler-context-analysis.h index a9317571e6af..8302ebc2ea8c 100644 --- a/include/linux/compiler-context-analysis.h +++ b/include/linux/compiler-context-analysis.h @@ -39,12 +39,14 @@ # define __assumes_shared_ctx_lock(...) __attribute__((assert_shared_capability(__VA_ARGS__))) /** - * __guarded_by - struct member and globals attribute, declares variable - * only accessible within active context + * __guarded_by() - struct member and globals attribute, declares variable + * only accessible within active context + * @...: context lock instance pointer(s) * * Declares that the struct member or global variable is only accessible within - * the context entered by the given context lock. Read operations on the data - * require shared access, while write operations require exclusive access. + * the context entered by the given context lock(s). Read operations on the data + * require shared access to at least one of the context locks, while write + * operations require exclusive access to all listed context locks. * * .. code-block:: c * @@ -52,17 +54,24 @@ * spinlock_t lock; * long counter __guarded_by(&lock); * }; + * + * struct some_state { + * spinlock_t lock1, lock2; + * long counter __guarded_by(&lock1, &lock2); + * }; */ # define __guarded_by(...) __attribute__((guarded_by(__VA_ARGS__))) /** - * __pt_guarded_by - struct member and globals attribute, declares pointed-to - * data only accessible within active context + * __pt_guarded_by() - struct member and globals attribute, declares pointed-to + * data only accessible within active context + * @...: context lock instance pointer(s) * * Declares that the data pointed to by the struct member pointer or global * pointer is only accessible within the context entered by the given context - * lock. Read operations on the data require shared access, while write - * operations require exclusive access. + * lock(s). Read operations on the data require shared access to at least one + * of the context locks, while write operations require exclusive access to all + * listed context locks. * * .. code-block:: c * @@ -70,6 +79,11 @@ * spinlock_t lock; * long *counter __pt_guarded_by(&lock); * }; + * + * struct some_state { + * spinlock_t lock1, lock2; + * long *counter __pt_guarded_by(&lock1, &lock2); + * }; */ # define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__))) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 8ff5adcfe1e0..b0f3028a213d 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -630,7 +630,7 @@ config DEBUG_FORCE_WEAK_PER_CPU config WARN_CONTEXT_ANALYSIS bool "Compiler context-analysis warnings" - depends on CC_IS_CLANG && CLANG_VERSION >= 220100 + depends on CC_IS_CLANG && CLANG_VERSION >= 230000 # Branch profiling re-defines "if", which messes with the compiler's # ability to analyze __cond_acquires(..), resulting in false positives. depends on !TRACE_BRANCH_PROFILING @@ -641,7 +641,7 @@ config WARN_CONTEXT_ANALYSIS and releasing user-definable "context locks". Clang's name of the feature is "Thread Safety Analysis". Requires - Clang 22.1.0 or later. + Clang 23 or later. Produces warnings by default. Select CONFIG_WERROR if you wish to turn these warnings into errors. diff --git a/lib/test_context-analysis.c b/lib/test_context-analysis.c index 06b4a6a028e0..316f4dfcda65 100644 --- a/lib/test_context-analysis.c +++ b/lib/test_context-analysis.c @@ -159,6 +159,10 @@ TEST_SPINLOCK_COMMON(read_lock, struct test_mutex_data { struct mutex mtx; int counter __guarded_by(&mtx); + + struct mutex mtx2; + int anyread __guarded_by(&mtx, &mtx2); + int *anyptr __pt_guarded_by(&mtx, &mtx2); }; static void __used test_mutex_init(struct test_mutex_data *d) @@ -219,6 +223,26 @@ static void __used test_mutex_cond_guard(struct test_mutex_data *d) } } +static void __used test_mutex_multiguard(struct test_mutex_data *d) +{ + mutex_lock(&d->mtx); + (void)d->anyread; + (void)*d->anyptr; + mutex_unlock(&d->mtx); + + mutex_lock(&d->mtx2); + (void)d->anyread; + (void)*d->anyptr; + mutex_unlock(&d->mtx2); + + mutex_lock(&d->mtx); + mutex_lock(&d->mtx2); + d->anyread++; + (*d->anyptr)++; + mutex_unlock(&d->mtx2); + mutex_unlock(&d->mtx); +} + struct test_seqlock_data { seqlock_t sl; int counter __guarded_by(&sl); From 88331c4ec23a28c1006ec532fa64763d4c695e90 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 19 May 2026 13:03:15 +0200 Subject: [PATCH 072/167] seqlock: Allow UBSAN_ALIGNMENT to fail optimizing With gcc-15 and gcc-16 with UBSAN_ALIGNMENT enabled the compiler fails to inline and optimize __scoped_seqlock_bug() away on s390: s390x-16.1.0-ld: kernel/sched/build_policy.o: in function `__scoped_seqlock_next': /.../seqlock.h:1286:(.text+0x22030): undefined reference to `__scoped_seqlock_bug' Fix this by adding UBSAN_ALIGNMENT to the list of config options where a not inlined empty __scoped_seqlock_bug() is allowed. Closes: https://lore.kernel.org/r/20260515092057.810542-1-arnd@kernel.org/ Reported-by: Arnd Bergmann Signed-off-by: Heiko Carstens Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260519110315.1385307-1-hca@linux.ibm.com --- include/linux/seqlock.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/linux/seqlock.h b/include/linux/seqlock.h index 5a40252b8334..f865491c4f2c 100644 --- a/include/linux/seqlock.h +++ b/include/linux/seqlock.h @@ -1259,14 +1259,15 @@ static __always_inline void __scoped_seqlock_cleanup(struct ss_tmp *sst) extern void __scoped_seqlock_invalid_target(void); -#if (defined(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 90000) || defined(CONFIG_KASAN) +#if (defined(CONFIG_CC_IS_GCC) && CONFIG_GCC_VERSION < 90000) || \ + defined(CONFIG_KASAN) || defined(CONFIG_UBSAN_ALIGNMENT) /* * For some reason some GCC-8 architectures (nios2, alpha) have trouble * determining that the ss_done state is impossible in __scoped_seqlock_next() * below. * - * Similarly KASAN is known to confuse compilers enough to break this. But we - * don't care about code quality for KASAN builds anyway. + * Similarly KASAN and UBSAN_ALIGNMENT are known to confuse compilers enough + * to break this. But we don't care about code quality for such builds anyway. */ static inline void __scoped_seqlock_bug(void) { } #else From eba302268a019275fd6ff452d4ff0b94fef11c76 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 21:59:51 +0200 Subject: [PATCH 073/167] timekeeping: Provide ktime_get_snapshot_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ktime_get_snapshot() provides a snapshot of the underlying clocksource counter value and the corresponding CLOCK_MONOTONIC_RAW, CLOCK_REALTIME and CLOCK_BOOTTIME timestamps. There is no usage of CLOCK_REALTIME and CLOCK_BOOTTIME at the same time and CLOCK_BOOTTIME support was just added for the ARM64 KVM tracing mechanism, which needs CLOCK_BOOTTIME and the underlying clocksource counter value. ktime_get_snapshot() is also not suitable for usage with CLOCK_AUX, but that's a prerequisite to support PTP hardware timestamping for CLOCK_AUX steering. As a first step, rename ktime_get_snapshot() to ktime_get_snapshot_id(), which now takes a clockid argument to select the clock which needs to be captured. The result is stored in system_time_snapshot::systime, which will replace the system_time_snapshot::real/boot members once all usage sites have been converted. ktime_get_snapshot() is a simple wrapper which hands in CLOCK_REALTIME as clockid argument for the conversion period. That means CLOCK_REALTIME is now captured twice, but that redunancy is only temporary. As all usage sites of struct system_time_snapshot has to be updated anyway, rename the 'raw' member to 'monoraw' for clarity. No functional change vs. current users of ktime_get_snapshot() Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195556.971591633@kernel.org --- include/linux/timekeeping.h | 34 ++++++++++----- kernel/time/timekeeping.c | 85 ++++++++++++++++++++++++++++--------- 2 files changed, 88 insertions(+), 31 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index aee2c1a46e47..161157c8d27e 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -276,24 +276,32 @@ static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { ret #endif /** - * struct system_time_snapshot - simultaneous raw/real time capture with - * counter value - * @cycles: Clocksource counter value to produce the system times - * @real: Realtime system time - * @boot: Boot time - * @raw: Monotonic raw system time - * @cs_id: Clocksource ID + * struct system_time_snapshot - Simultaneous time capture of CLOCK_MONOTONIC_RAW, + * a selected CLOCK_* and the clocksource counter value + * @cycles: Clocksource counter value to produce the system times + * @systime: The system time of the selected CLOCK ID + * @real: Realtime system time + * @boot: Boot time + * @raw: Monotonic raw system time + * @monoraw: Monotonic raw system time + * @cs_id: Clocksource ID * @clock_was_set_seq: The sequence number of clock-was-set events * @cs_was_changed_seq: The sequence number of clocksource change events + * @valid: True if the snapshot is valid */ struct system_time_snapshot { u64 cycles; + ktime_t systime; ktime_t real; ktime_t boot; - ktime_t raw; + union { + ktime_t raw; + ktime_t monoraw; + }; enum clocksource_ids cs_id; unsigned int clock_was_set_seq; u8 cs_was_changed_seq; + u8 valid; }; /** @@ -341,9 +349,15 @@ extern int get_device_system_crosststamp( struct system_device_crosststamp *xtstamp); /* - * Simultaneously snapshot realtime and monotonic raw clocks + * Simultaneously snapshot a given clock with MONOTONIC_RAW and the underlying + * clocksource counter value. */ -extern void ktime_get_snapshot(struct system_time_snapshot *systime_snapshot); +extern void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot); + +static inline void ktime_get_snapshot(struct system_time_snapshot *systime_snapshot) +{ + ktime_get_snapshot_id(CLOCK_REALTIME, systime_snapshot); +} /* * Persistent clock related interfaces diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index c493a4010305..0053dc0010f4 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1183,43 +1183,86 @@ noinstr time64_t __ktime_get_real_seconds(void) } /** - * ktime_get_snapshot - snapshots the realtime/monotonic raw clocks with counter - * @systime_snapshot: pointer to struct receiving the system time snapshot + * ktime_get_snapshot_id - Simultaneously snapshot a given clock ID with + * CLOCK_MONOTONIC_RAW and the underlying + * clocksource counter value. + * @clock_id: The clock ID to snapshot + * @systime_snapshot: Pointer to struct receiving the system time snapshot */ -void ktime_get_snapshot(struct system_time_snapshot *systime_snapshot) +void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot) { - struct timekeeper *tk = &tk_core.timekeeper; + ktime_t base_raw, base_sys, offs_sys, *offs, offs_zero = 0; + u64 nsec_raw, nsec_sys, now; + struct timekeeper *tk; + struct tk_data *tkd; unsigned int seq; - ktime_t base_raw; ktime_t base_real; ktime_t base_boot; - u64 nsec_raw; - u64 nsec_real; - u64 now; - WARN_ON_ONCE(timekeeping_suspended); + /* Invalidate the snapshot for all failure cases */ + systime_snapshot->valid = false; + + if (WARN_ON_ONCE(timekeeping_suspended)) + return; + + switch (clock_id) { + case CLOCK_REALTIME: + tkd = &tk_core; + offs = &tk_core.timekeeper.offs_real; + break; + /* Map RAW to MONOTONIC so the loop below is trivial */ + case CLOCK_MONOTONIC_RAW: + case CLOCK_MONOTONIC: + tkd = &tk_core; + offs = &offs_zero; + break; + case CLOCK_BOOTTIME: + tkd = &tk_core; + offs = &tk_core.timekeeper.offs_boot; + break; + default: + WARN_ON_ONCE(1); + return; + } + + tk = &tkd->timekeeper; do { - seq = read_seqcount_begin(&tk_core.seq); + seq = read_seqcount_begin(&tkd->seq); + now = tk_clock_read(&tk->tkr_mono); systime_snapshot->cs_id = tk->tkr_mono.clock->id; systime_snapshot->cs_was_changed_seq = tk->cs_was_changed_seq; systime_snapshot->clock_was_set_seq = tk->clock_was_set_seq; - base_real = ktime_add(tk->tkr_mono.base, - tk_core.timekeeper.offs_real); - base_boot = ktime_add(tk->tkr_mono.base, - tk_core.timekeeper.offs_boot); + + base_sys = tk->tkr_mono.base; + offs_sys = *offs; base_raw = tk->tkr_raw.base; - nsec_real = timekeeping_cycles_to_ns(&tk->tkr_mono, now); - nsec_raw = timekeeping_cycles_to_ns(&tk->tkr_raw, now); - } while (read_seqcount_retry(&tk_core.seq, seq)); + + /* Kept around until the callers are fixed up */ + base_real = ktime_add(base_sys, tk_core.timekeeper.offs_real); + base_boot = ktime_add(base_sys, tk_core.timekeeper.offs_boot); + + nsec_sys = timekeeping_cycles_to_ns(&tk->tkr_mono, now); + nsec_raw = timekeeping_cycles_to_ns(&tk->tkr_raw, now); + } while (read_seqcount_retry(&tkd->seq, seq)); systime_snapshot->cycles = now; - systime_snapshot->real = ktime_add_ns(base_real, nsec_real); - systime_snapshot->boot = ktime_add_ns(base_boot, nsec_real); - systime_snapshot->raw = ktime_add_ns(base_raw, nsec_raw); + systime_snapshot->systime = ktime_add_ns(base_sys, offs_sys + nsec_sys); + systime_snapshot->real = ktime_add_ns(base_real, nsec_sys); + systime_snapshot->boot = ktime_add_ns(base_boot, nsec_sys); + systime_snapshot->monoraw = ktime_add_ns(base_raw, nsec_raw); + + /* + * Special case for PTP. Just transfer the raw time into sys, + * so the call sites can consistently use snap::systime. + */ + if (clock_id == CLOCK_MONOTONIC_RAW) + systime_snapshot->systime = systime_snapshot->monoraw; + /* Tell the consumer that this snapshot is valid */ + systime_snapshot->valid = true; } -EXPORT_SYMBOL_GPL(ktime_get_snapshot); +EXPORT_SYMBOL_GPL(ktime_get_snapshot_id); /* Scale base by mult/div checking for overflow */ static int scale64_check_overflow(u64 mult, u64 div, u64 *base) From ef22786707e3967b539c3b1e6b5c7ea8b408430f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 21:59:55 +0200 Subject: [PATCH 074/167] timekeeping: Use system_time_snapshot::systime/monoraw instead of ::real/raw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit system_time_snapshot::systime provides the same information as system_time_snapshot::real when the snapshot was taken with ktime_get_snapshot_id(CLOCK_REALTIME). Convert the history interpolation over to use 'systime' and 'monoraw' as 'real/raw' are going away once all users are converted. As a side effect this is the first step to support CLOCK_AUX with get_device_crosstime_stamp() and the history interpolation. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.024415766@kernel.org --- kernel/time/timekeeping.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 0053dc0010f4..ccd04addb021 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1323,7 +1323,7 @@ static int adjust_historical_crosststamp(struct system_time_snapshot *history, * partial_history_cycles / total_history_cycles */ corr_raw = (u64)ktime_to_ns( - ktime_sub(ts->sys_monoraw, history->raw)); + ktime_sub(ts->sys_monoraw, history->monoraw)); ret = scale64_check_overflow(partial_history_cycles, total_history_cycles, &corr_raw); if (ret) @@ -1341,7 +1341,7 @@ static int adjust_historical_crosststamp(struct system_time_snapshot *history, (corr_raw, tk->tkr_mono.mult, tk->tkr_raw.mult); } else { corr_real = (u64)ktime_to_ns( - ktime_sub(ts->sys_realtime, history->real)); + ktime_sub(ts->sys_realtime, history->systime)); ret = scale64_check_overflow(partial_history_cycles, total_history_cycles, &corr_real); if (ret) @@ -1350,8 +1350,8 @@ static int adjust_historical_crosststamp(struct system_time_snapshot *history, /* Fixup monotonic raw and real time time values */ if (interp_forward) { - ts->sys_monoraw = ktime_add_ns(history->raw, corr_raw); - ts->sys_realtime = ktime_add_ns(history->real, corr_real); + ts->sys_monoraw = ktime_add_ns(history->monoraw, corr_raw); + ts->sys_realtime = ktime_add_ns(history->systime, corr_real); } else { ts->sys_monoraw = ktime_sub_ns(ts->sys_monoraw, corr_raw); ts->sys_realtime = ktime_sub_ns(ts->sys_realtime, corr_real); From fe1159fe49b40f73de88d2c29200c813952e061f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 21:59:59 +0200 Subject: [PATCH 075/167] pps: generators: Use ktime_get_real_ts64() instead of ktime_get_snapshot() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no reason to use the more complex ktime_get_snapshot() for retrieving CLOCK_REALTIME. Just use ktime_get_real_ts64(), which avoids the extra timespec64 conversion as a bonus. No functional change intended. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Acked-by: Rodolfo Giometti Link: https://patch.msgid.link/20260529195557.074439049@kernel.org --- drivers/pps/generators/pps_gen-dummy.c | 6 +----- drivers/pps/generators/pps_gen_tio.c | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/pps/generators/pps_gen-dummy.c b/drivers/pps/generators/pps_gen-dummy.c index 547fa7fe29f4..a4395543c4bb 100644 --- a/drivers/pps/generators/pps_gen-dummy.c +++ b/drivers/pps/generators/pps_gen-dummy.c @@ -39,11 +39,7 @@ static void pps_gen_ktimer_event(struct timer_list *unused) static int pps_gen_dummy_get_time(struct pps_gen_device *pps_gen, struct timespec64 *time) { - struct system_time_snapshot snap; - - ktime_get_snapshot(&snap); - *time = ktime_to_timespec64(snap.real); - + ktime_get_real_ts64(time); return 0; } diff --git a/drivers/pps/generators/pps_gen_tio.c b/drivers/pps/generators/pps_gen_tio.c index de00a85bfafa..9483d126ada0 100644 --- a/drivers/pps/generators/pps_gen_tio.c +++ b/drivers/pps/generators/pps_gen_tio.c @@ -189,11 +189,7 @@ static int pps_tio_gen_enable(struct pps_gen_device *pps_gen, bool enable) static int pps_tio_get_time(struct pps_gen_device *pps_gen, struct timespec64 *time) { - struct system_time_snapshot snap; - - ktime_get_snapshot(&snap); - *time = ktime_to_timespec64(snap.real); - + ktime_get_real_ts64(time); return 0; } From d5becddf8a0f18bcc18129efa086530b5263c6b4 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:04 +0200 Subject: [PATCH 076/167] pps: Convert to ktime_get_snapshot_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ktime_get_snapshot() resolves to ktime_get_snapshot_id(CLOCK_REALTIME). Make it obvious in the code and convert the readout to use the snapshot::systime and monoraw fields instead of snapshot::real and raw, which aregoing away. Similar to the PPS generators, avoid the more expensive snapshot when CONFIG_NTP_PPS is disabled. No functional change intended. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.123410250@kernel.org --- include/linux/pps_kernel.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/linux/pps_kernel.h b/include/linux/pps_kernel.h index aab0aebb529e..9f088c9023b1 100644 --- a/include/linux/pps_kernel.h +++ b/include/linux/pps_kernel.h @@ -99,12 +99,14 @@ static inline void timespec_to_pps_ktime(struct pps_ktime *kt, static inline void pps_get_ts(struct pps_event_time *ts) { +#ifdef CONFIG_NTP_PPS struct system_time_snapshot snap; - ktime_get_snapshot(&snap); - ts->ts_real = ktime_to_timespec64(snap.real); -#ifdef CONFIG_NTP_PPS - ts->ts_raw = ktime_to_timespec64(snap.raw); + ktime_get_snapshot_id(CLOCK_REALTIME, &snap); + ts->ts_real = ktime_to_timespec64(snap.systime); + ts->ts_raw = ktime_to_timespec64(snap.monoraw); +#else + ktime_get_real_ts64(&ts->ts_real); #endif } From 86db4084b4b5d1a074bcc66c108a4c9d266812d4 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:33 +0200 Subject: [PATCH 077/167] tick/sched: Fix TOCTOU in nohz idle time fetch When the nohz idle time is fetched, the current clock timestamp is taken outside the seqcount, which can result in a race as reported by Sashiko: get_cpu_sleep_time_us() tick_nohz_start_idle() ----------------------- --------------------- now = ktime_get() write_seqcount_begin(idle_sleeptime_seq); idle_entrytime = ktime_get() tick_sched_flag_set(ts, TS_FLAG_IDLE_ACTIVE); write_seqcount_end(&ts->idle_sleeptime_seq); read_seqcount_begin(idle_sleeptime_seq) delta = now - idle_entrytime); //!! But now < idle_entrytime idle = *sleeptime + delta; read_seqcount_retry(&ts->idle_sleeptime_seq, seq) Here the read side fetches the timestamp before the write side and its update. As a result the time delta computed on the read side is negative (ktime_t is signed) and breaks the cputime monotonicity guarantee. This could possibly be fixed with reading the current clock timestamp inside the seqcount but the reader overhead might then increase. Also simply checking that the current timestamp is above the idle entry time is enough to prevent any issue of the like. Fixes: 620a30fa0bd1 ("timers/nohz: Protect idle/iowait sleep time under seqcount") Reported-by: Sashiko Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260508131647.43868-2-frederic@kernel.org --- kernel/time/tick-sched.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index cbbb87a0c6e7..171393367b5c 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -797,15 +797,16 @@ static u64 get_cpu_sleep_time_us(struct tick_sched *ts, ktime_t *sleeptime, *last_update_time = ktime_to_us(now); do { + ktime_t delta = 0; + seq = read_seqcount_begin(&ts->idle_sleeptime_seq); if (tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE) && compute_delta) { - ktime_t delta = ktime_sub(now, ts->idle_entrytime); - - idle = ktime_add(*sleeptime, delta); - } else { - idle = *sleeptime; + if (now > ts->idle_entrytime) + delta = ktime_sub(now, ts->idle_entrytime); } + + idle = ktime_add(*sleeptime, delta); } while (read_seqcount_retry(&ts->idle_sleeptime_seq, seq)); return ktime_to_us(idle); From 0236aaf07b406100c8c3a6b78dba211f32449f49 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:34 +0200 Subject: [PATCH 078/167] sched/idle: Handle offlining first in idle loop Offline handling happens from within the inner idle loop, after the beginning of dyntick cputime accounting, nohz idle load balancing and TIF_NEED_RESCHED polling. This is not necessary and even buggy because: * There is no dyntick handling to do. And calling tick_nohz_idle_enter() messes up with the struct tick_sched reset that was performed on tick_sched_timer_dying(). * There is no nohz idle balancing to do. * Polling on TIF_RESCHED is irrelevant at this stage, there are no more tasks allowed to run. * No need to check if need_resched() before offline handling since stop_machine is done and all per-cpu kthread should be done with their job. Therefore move the offline handling at the beginning of the idle loop. This will also ease the idle cputime unification later by not elapsing idle time while offline through the call to: tick_nohz_idle_enter() -> tick_nohz_start_idle() Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Reviewed-by: Rafael J. Wysocki (Intel) Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-3-frederic@kernel.org --- kernel/sched/idle.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c index a83be0c834dd..aa7e3dc59856 100644 --- a/kernel/sched/idle.c +++ b/kernel/sched/idle.c @@ -280,6 +280,14 @@ static void do_idle(void) int cpu = smp_processor_id(); bool got_tick = false; + if (cpu_is_offline(cpu)) { + local_irq_disable(); + /* All per-CPU kernel threads should be done by now. */ + WARN_ON_ONCE(need_resched()); + cpuhp_report_idle_dead(); + arch_cpu_idle_dead(); + } + /* * Check if we need to update blocked load */ @@ -331,11 +339,6 @@ static void do_idle(void) */ local_irq_disable(); - if (cpu_is_offline(cpu)) { - cpuhp_report_idle_dead(); - arch_cpu_idle_dead(); - } - arch_cpu_idle_enter(); rcu_nocb_flush_deferred_wakeup(); From 080b5c6d95034e46f5ed1abe98c06218a1386aef Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:35 +0200 Subject: [PATCH 079/167] sched/cputime: Remove superfluous and error prone kcpustat_field() parameter The first parameter to kcpustat_field() is a pointer to the cpu kcpustat to be fetched from. This parameter is error prone because a copy to a kcpustat could be passed by accident instead of the original one. Also the kcpustat structure can already be retrieved with the help of the mandatory CPU argument. Remove the needless parameter. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-4-frederic@kernel.org --- drivers/cpufreq/cpufreq_governor.c | 6 +++--- drivers/macintosh/rack-meter.c | 2 +- include/linux/kernel_stat.h | 8 +++----- kernel/rcu/tree.c | 9 +++------ kernel/rcu/tree_stall.h | 7 +++---- kernel/sched/cputime.c | 5 ++--- 6 files changed, 15 insertions(+), 22 deletions(-) diff --git a/drivers/cpufreq/cpufreq_governor.c b/drivers/cpufreq/cpufreq_governor.c index 86f35e451914..3c4a1f9af3ae 100644 --- a/drivers/cpufreq/cpufreq_governor.c +++ b/drivers/cpufreq/cpufreq_governor.c @@ -105,7 +105,7 @@ void gov_update_cpu_data(struct dbs_data *dbs_data) j_cdbs->prev_cpu_idle = get_cpu_idle_time(j, &j_cdbs->prev_update_time, dbs_data->io_is_busy); if (dbs_data->ignore_nice_load) - j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j); + j_cdbs->prev_cpu_nice = kcpustat_field(CPUTIME_NICE, j); } } } @@ -165,7 +165,7 @@ unsigned int dbs_update(struct cpufreq_policy *policy) j_cdbs->prev_cpu_idle = cur_idle_time; if (ignore_nice) { - u64 cur_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j); + u64 cur_nice = kcpustat_field(CPUTIME_NICE, j); idle_time += div_u64(cur_nice - j_cdbs->prev_cpu_nice, NSEC_PER_USEC); j_cdbs->prev_cpu_nice = cur_nice; @@ -539,7 +539,7 @@ int cpufreq_dbs_governor_start(struct cpufreq_policy *policy) j_cdbs->prev_load = 0; if (ignore_nice) - j_cdbs->prev_cpu_nice = kcpustat_field(&kcpustat_cpu(j), CPUTIME_NICE, j); + j_cdbs->prev_cpu_nice = kcpustat_field(CPUTIME_NICE, j); } gov->start(policy); diff --git a/drivers/macintosh/rack-meter.c b/drivers/macintosh/rack-meter.c index 8a1e2c08b096..26cb93191ede 100644 --- a/drivers/macintosh/rack-meter.c +++ b/drivers/macintosh/rack-meter.c @@ -87,7 +87,7 @@ static inline u64 get_cpu_idle_time(unsigned int cpu) kcpustat->cpustat[CPUTIME_IOWAIT]; if (rackmeter_ignore_nice) - retval += kcpustat_field(kcpustat, CPUTIME_NICE, cpu); + retval += kcpustat_field(CPUTIME_NICE, cpu); return retval; } diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index b97ce2df376f..dd020ecaf67b 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -100,14 +100,12 @@ static inline unsigned long kstat_cpu_irqs_sum(unsigned int cpu) } #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN -extern u64 kcpustat_field(struct kernel_cpustat *kcpustat, - enum cpu_usage_stat usage, int cpu); +extern u64 kcpustat_field(enum cpu_usage_stat usage, int cpu); extern void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu); #else -static inline u64 kcpustat_field(struct kernel_cpustat *kcpustat, - enum cpu_usage_stat usage, int cpu) +static inline u64 kcpustat_field(enum cpu_usage_stat usage, int cpu) { - return kcpustat->cpustat[usage]; + return kcpustat_cpu(cpu).cpustat[usage]; } static inline void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu) diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 55df6d37145e..3cbf79bee976 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -969,14 +969,11 @@ static int rcu_watching_snap_recheck(struct rcu_data *rdp) if (rcu_cpu_stall_cputime && rdp->snap_record.gp_seq != rdp->gp_seq) { int cpu = rdp->cpu; struct rcu_snap_record *rsrp; - struct kernel_cpustat *kcsp; - - kcsp = &kcpustat_cpu(cpu); rsrp = &rdp->snap_record; - rsrp->cputime_irq = kcpustat_field(kcsp, CPUTIME_IRQ, cpu); - rsrp->cputime_softirq = kcpustat_field(kcsp, CPUTIME_SOFTIRQ, cpu); - rsrp->cputime_system = kcpustat_field(kcsp, CPUTIME_SYSTEM, cpu); + rsrp->cputime_irq = kcpustat_field(CPUTIME_IRQ, cpu); + rsrp->cputime_softirq = kcpustat_field(CPUTIME_SOFTIRQ, cpu); + rsrp->cputime_system = kcpustat_field(CPUTIME_SYSTEM, cpu); rsrp->nr_hardirqs = kstat_cpu_irqs_sum(cpu) + arch_irq_stat_cpu(cpu); rsrp->nr_softirqs = kstat_cpu_softirqs_sum(cpu); rsrp->nr_csw = nr_context_switches_cpu(cpu); diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h index b67532cb8770..cf7ae51cba40 100644 --- a/kernel/rcu/tree_stall.h +++ b/kernel/rcu/tree_stall.h @@ -479,7 +479,6 @@ static void print_cpu_stat_info(int cpu) { struct rcu_snap_record rsr, *rsrp; struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu); - struct kernel_cpustat *kcsp = &kcpustat_cpu(cpu); if (!rcu_cpu_stall_cputime) return; @@ -488,9 +487,9 @@ static void print_cpu_stat_info(int cpu) if (rsrp->gp_seq != rdp->gp_seq) return; - rsr.cputime_irq = kcpustat_field(kcsp, CPUTIME_IRQ, cpu); - rsr.cputime_softirq = kcpustat_field(kcsp, CPUTIME_SOFTIRQ, cpu); - rsr.cputime_system = kcpustat_field(kcsp, CPUTIME_SYSTEM, cpu); + rsr.cputime_irq = kcpustat_field(CPUTIME_IRQ, cpu); + rsr.cputime_softirq = kcpustat_field(CPUTIME_SOFTIRQ, cpu); + rsr.cputime_system = kcpustat_field(CPUTIME_SYSTEM, cpu); pr_err("\t hardirqs softirqs csw/system\n"); pr_err("\t number: %8lld %10d %12lld\n", diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index fbf31db0d2f3..caaaf0a04ced 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -975,10 +975,9 @@ static int kcpustat_field_vtime(u64 *cpustat, return 0; } -u64 kcpustat_field(struct kernel_cpustat *kcpustat, - enum cpu_usage_stat usage, int cpu) +u64 kcpustat_field(enum cpu_usage_stat usage, int cpu) { - u64 *cpustat = kcpustat->cpustat; + u64 *cpustat = kcpustat_cpu(cpu).cpustat; u64 val = cpustat[usage]; struct rq *rq; int err; From 650a59805a9baeff76379ea9309df1395eb15a46 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:36 +0200 Subject: [PATCH 080/167] sched/cputime: Correctly support generic vtime idle time Currently whether generic vtime is running or not, the idle cputime is fetched from the nohz accounting. However generic vtime already does its own idle cputime accounting. Only the kernel stat accessors are not plugged to support it. Read the idle generic vtime cputime when it's running, this will allow to later more clearly split nohz and vtime cputime accounting. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-5-frederic@kernel.org --- include/linux/vtime.h | 9 +++++++-- kernel/sched/cputime.c | 38 +++++++++++++++++++++++++++++--------- kernel/time/tick-sched.c | 12 +++++++++--- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/include/linux/vtime.h b/include/linux/vtime.h index 29dd5b91dd7d..336875bea767 100644 --- a/include/linux/vtime.h +++ b/include/linux/vtime.h @@ -10,7 +10,6 @@ */ #ifdef CONFIG_VIRT_CPU_ACCOUNTING extern void vtime_account_kernel(struct task_struct *tsk); -extern void vtime_account_idle(struct task_struct *tsk); #endif /* !CONFIG_VIRT_CPU_ACCOUNTING */ #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN @@ -27,7 +26,13 @@ static inline void vtime_guest_exit(struct task_struct *tsk) { } static inline void vtime_init_idle(struct task_struct *tsk, int cpu) { } #endif +static inline bool vtime_generic_enabled_cpu(int cpu) +{ + return context_tracking_enabled_cpu(cpu); +} + #ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE +extern void vtime_account_idle(struct task_struct *tsk); extern void vtime_account_irq(struct task_struct *tsk, unsigned int offset); extern void vtime_account_softirq(struct task_struct *tsk); extern void vtime_account_hardirq(struct task_struct *tsk); @@ -74,7 +79,7 @@ static inline bool vtime_accounting_enabled(void) static inline bool vtime_accounting_enabled_cpu(int cpu) { - return context_tracking_enabled_cpu(cpu); + return vtime_generic_enabled_cpu(cpu); } static inline bool vtime_accounting_enabled_this_cpu(void) diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index caaaf0a04ced..815d0f772cae 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -773,9 +773,9 @@ void vtime_guest_exit(struct task_struct *tsk) } EXPORT_SYMBOL_GPL(vtime_guest_exit); -void vtime_account_idle(struct task_struct *tsk) +static void __vtime_account_idle(struct vtime *vtime) { - account_idle_time(get_vtime_delta(&tsk->vtime)); + account_idle_time(get_vtime_delta(vtime)); } void vtime_task_switch_generic(struct task_struct *prev) @@ -784,7 +784,7 @@ void vtime_task_switch_generic(struct task_struct *prev) write_seqcount_begin(&vtime->seqcount); if (vtime->state == VTIME_IDLE) - vtime_account_idle(prev); + __vtime_account_idle(vtime); else __vtime_account_kernel(prev, vtime); vtime->state = VTIME_INACTIVE; @@ -926,6 +926,7 @@ static int kcpustat_field_vtime(u64 *cpustat, int cpu, u64 *val) { struct vtime *vtime = &tsk->vtime; + struct rq *rq = cpu_rq(cpu); unsigned int seq; do { @@ -967,6 +968,14 @@ static int kcpustat_field_vtime(u64 *cpustat, if (state == VTIME_GUEST && task_nice(tsk) > 0) *val += vtime->gtime + vtime_delta(vtime); break; + case CPUTIME_IDLE: + if (state == VTIME_IDLE && !atomic_read(&rq->nr_iowait)) + *val += vtime_delta(vtime); + break; + case CPUTIME_IOWAIT: + if (state == VTIME_IDLE && atomic_read(&rq->nr_iowait) > 0) + *val += vtime_delta(vtime); + break; default: break; } @@ -1029,8 +1038,8 @@ static int kcpustat_cpu_fetch_vtime(struct kernel_cpustat *dst, *dst = *src; cpustat = dst->cpustat; - /* Task is sleeping, dead or idle, nothing to add */ - if (state < VTIME_SYS) + /* Task is sleeping or dead, nothing to add */ + if (state < VTIME_IDLE) continue; delta = vtime_delta(vtime); @@ -1039,15 +1048,17 @@ static int kcpustat_cpu_fetch_vtime(struct kernel_cpustat *dst, * Task runs either in user (including guest) or kernel space, * add pending nohz time to the right place. */ - if (state == VTIME_SYS) { + switch (state) { + case VTIME_SYS: cpustat[CPUTIME_SYSTEM] += vtime->stime + delta; - } else if (state == VTIME_USER) { + break; + case VTIME_USER: if (task_nice(tsk) > 0) cpustat[CPUTIME_NICE] += vtime->utime + delta; else cpustat[CPUTIME_USER] += vtime->utime + delta; - } else { - WARN_ON_ONCE(state != VTIME_GUEST); + break; + case VTIME_GUEST: if (task_nice(tsk) > 0) { cpustat[CPUTIME_GUEST_NICE] += vtime->gtime + delta; cpustat[CPUTIME_NICE] += vtime->gtime + delta; @@ -1055,6 +1066,15 @@ static int kcpustat_cpu_fetch_vtime(struct kernel_cpustat *dst, cpustat[CPUTIME_GUEST] += vtime->gtime + delta; cpustat[CPUTIME_USER] += vtime->gtime + delta; } + break; + case VTIME_IDLE: + if (atomic_read(&cpu_rq(cpu)->nr_iowait) > 0) + cpustat[CPUTIME_IOWAIT] += delta; + else + cpustat[CPUTIME_IDLE] += delta; + break; + default: + WARN_ON_ONCE(1); } } while (read_seqcount_retry(&vtime->seqcount, seq)); diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 171393367b5c..597c3a0682e7 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -783,9 +783,10 @@ static void tick_nohz_start_idle(struct tick_sched *ts) sched_clock_idle_sleep_event(); } -static u64 get_cpu_sleep_time_us(struct tick_sched *ts, ktime_t *sleeptime, +static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *sleeptime, bool compute_delta, u64 *last_update_time) { + struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); ktime_t now, idle; unsigned int seq; @@ -796,6 +797,11 @@ static u64 get_cpu_sleep_time_us(struct tick_sched *ts, ktime_t *sleeptime, if (last_update_time) *last_update_time = ktime_to_us(now); + if (vtime_generic_enabled_cpu(cpu)) { + idle = kcpustat_field(idx, cpu); + return ktime_to_us(idle); + } + do { ktime_t delta = 0; @@ -834,7 +840,7 @@ u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time) { struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); - return get_cpu_sleep_time_us(ts, &ts->idle_sleeptime, + return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE, &ts->idle_sleeptime, !nr_iowait_cpu(cpu), last_update_time); } EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); @@ -860,7 +866,7 @@ u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) { struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); - return get_cpu_sleep_time_us(ts, &ts->iowait_sleeptime, + return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT, &ts->iowait_sleeptime, nr_iowait_cpu(cpu), last_update_time); } EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us); From c8ba971cf8567d49eb5f43ee90c4e50424331c18 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:37 +0200 Subject: [PATCH 081/167] powerpc/time: Prepare to stop elapsing in dynticks-idle Currently the tick subsystem stores the idle cputime accounting in private fields, allowing cohabitation with architecture idle vtime accounting. The former is fetched on online CPUs, the latter on offline CPUs. For consolidation purpose, architecture vtime accounting will continue to account the cputime but will make a break when the idle tick is stopped. The dyntick cputime accounting will then be relayed by the tick subsystem so that the idle cputime is still seen advancing coherently even when the tick isn't there to flush the idle vtime. Prepare for that and introduce three new APIs which will be used in subsequent patches: - vtime_dynticks_start() is deemed to be called when idle enters in dyntick mode. The idle cputime that elapsed so far is accumulated. - vtime_dynticks_stop() is deemed to be called when idle exits from dyntick mode. The vtime entry clocks are fast-forward to current time so that idle accounting restarts elapsing from now. - vtime_reset() is deemed to be called from dynticks idle IRQ entry to fast-forward the clock to current time so that the IRQ time is still accounted by vtime while nohz cputime is paused. Also accumulated vtime won't be flushed from dyntick-idle ticks to avoid accounting twice the idle cputime, along with nohz accounting. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-6-frederic@kernel.org --- arch/powerpc/kernel/time.c | 41 ++++++++++++++++++++++++++++++++++++++ include/linux/vtime.h | 6 ++++++ 2 files changed, 47 insertions(+) diff --git a/arch/powerpc/kernel/time.c b/arch/powerpc/kernel/time.c index b4472288e0d4..3460d1a5a97c 100644 --- a/arch/powerpc/kernel/time.c +++ b/arch/powerpc/kernel/time.c @@ -376,6 +376,47 @@ void vtime_task_switch(struct task_struct *prev) acct->starttime = acct0->starttime; } } + +#ifdef CONFIG_NO_HZ_COMMON +/** + * vtime_reset - Fast forward vtime entry clocks + * + * Called from dynticks idle IRQ entry to fast-forward the clocks to current time + * so that the IRQ time is still accounted by vtime while nohz cputime is paused. + */ +void vtime_reset(void) +{ + struct cpu_accounting_data *acct = get_accounting(current); + + acct->starttime = mftb(); +#ifdef CONFIG_ARCH_HAS_SCALED_CPUTIME + acct->startspurr = read_spurr(acct->starttime); +#endif +} + +/** + * vtime_dyntick_start - Inform vtime about entry to idle-dynticks + * + * Called when idle enters in dyntick mode. The idle cputime that elapsed so far + * is accumulated and the tick subsystem takes over the idle cputime accounting. + */ +void vtime_dyntick_start(void) +{ + vtime_account_idle(current); +} + +/** + * vtime_dyntick_stop - Inform vtime about exit from idle-dynticks + * + * Called when idle exits from dyntick mode. The vtime entry clocks are + * fast-forward to current time so that idle accounting restarts elapsing from + * now. + */ +void vtime_dyntick_stop(void) +{ + vtime_reset(); +} +#endif /* CONFIG_NO_HZ_COMMON */ #endif /* CONFIG_VIRT_CPU_ACCOUNTING_NATIVE */ void __no_kcsan __delay(unsigned long loops) diff --git a/include/linux/vtime.h b/include/linux/vtime.h index 336875bea767..5516ad549e60 100644 --- a/include/linux/vtime.h +++ b/include/linux/vtime.h @@ -37,11 +37,17 @@ extern void vtime_account_irq(struct task_struct *tsk, unsigned int offset); extern void vtime_account_softirq(struct task_struct *tsk); extern void vtime_account_hardirq(struct task_struct *tsk); extern void vtime_flush(struct task_struct *tsk); +extern void vtime_reset(void); +extern void vtime_dyntick_start(void); +extern void vtime_dyntick_stop(void); #else /* !CONFIG_VIRT_CPU_ACCOUNTING_NATIVE */ static inline void vtime_account_irq(struct task_struct *tsk, unsigned int offset) { } static inline void vtime_account_softirq(struct task_struct *tsk) { } static inline void vtime_account_hardirq(struct task_struct *tsk) { } static inline void vtime_flush(struct task_struct *tsk) { } +static inline void vtime_reset(void) { } +static inline void vtime_dyntick_start(void) { } +static inline void vtime_dyntick_stop(void) { } #endif /* From ad5a9e14ec8b4a868fea13a9dfa1fb38b2c35354 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:38 +0200 Subject: [PATCH 082/167] s390/time: Prepare to stop elapsing in dynticks-idle Currently the tick subsystem stores the idle cputime accounting in private fields, allowing cohabitation with architecture idle vtime accounting. The former is fetched on online CPUs, the latter on offline CPUs. For consolidation purposes, architecture vtime accounting will continue to account the cputime but will make a break when the idle tick is stopped. The dyntick cputime accounting will then be relayed by the tick subsystem so that the idle cputime is still seen advancing coherently even when the tick isn't there to flush the idle vtime. Prepare for that and introduce three new APIs which will be used in subsequent patches: - vtime_dynticks_start() is deemed to be called when idle enters in dyntick mode. The idle cputime that elapsed so far is accumulated and accounted. Also idle time accounting is ignored. - vtime_dynticks_stop() is deemed to be called when idle exits from dyntick mode. The vtime entry clocks are fast-forward to current time so that idle accounting restarts elapsing from now. Also idle time accounting is resumed. - vtime_reset() is deemed to be called from dynticks idle IRQ entry to fast-forward the clock to current time so that the IRQ time is still accounted by vtime while nohz cputime is paused. Also accumulated vtime won't be flushed from dyntick-idle ticks to avoid accounting twice the idle cputime, along with nohz accounting. Co-developed-by: Heiko Carstens Signed-off-by: Heiko Carstens Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-7-frederic@kernel.org --- arch/s390/include/asm/idle.h | 2 + arch/s390/kernel/idle.c | 5 ++- arch/s390/kernel/vtime.c | 75 ++++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/arch/s390/include/asm/idle.h b/arch/s390/include/asm/idle.h index 32536ee34aa0..e4ad09a22400 100644 --- a/arch/s390/include/asm/idle.h +++ b/arch/s390/include/asm/idle.h @@ -8,10 +8,12 @@ #ifndef _S390_IDLE_H #define _S390_IDLE_H +#include #include #include struct s390_idle_data { + bool idle_dyntick; unsigned long idle_count; unsigned long idle_time; unsigned long clock_idle_enter; diff --git a/arch/s390/kernel/idle.c b/arch/s390/kernel/idle.c index 1f1b06b6b4ef..4685d7c5bc51 100644 --- a/arch/s390/kernel/idle.c +++ b/arch/s390/kernel/idle.c @@ -31,7 +31,10 @@ void account_idle_time_irq(void) /* Account time spent with enabled wait psw loaded as idle time. */ __atomic64_add(idle_time, &idle->idle_time); __atomic64_add_const(1, &idle->idle_count); - account_idle_time(cputime_to_nsecs(idle_time)); + + /* Dyntick idle time accounted by nohz/scheduler */ + if (!idle->idle_dyntick) + account_idle_time(cputime_to_nsecs(idle_time)); } void noinstr arch_cpu_idle(void) diff --git a/arch/s390/kernel/vtime.c b/arch/s390/kernel/vtime.c index bf48744d0912..d1102a6f80bd 100644 --- a/arch/s390/kernel/vtime.c +++ b/arch/s390/kernel/vtime.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "entry.h" @@ -110,6 +111,16 @@ static void account_system_index_scaled(struct task_struct *p, u64 cputime, account_system_index_time(p, cputime_to_nsecs(cputime), index); } +static inline void vtime_reset_last_update(struct lowcore *lc) +{ + asm volatile( + " stpt %0\n" /* Store current cpu timer value */ + " stckf %1" /* Store current tod clock value */ + : "=Q" (lc->last_update_timer), + "=Q" (lc->last_update_clock) + : : "cc"); +} + /* * Update process times based on virtual cpu times stored by entry.S * to the lowcore fields user_timer, system_timer & steal_clock. @@ -121,17 +132,16 @@ static int do_account_vtime(struct task_struct *tsk) timer = lc->last_update_timer; clock = lc->last_update_clock; - asm volatile( - " stpt %0\n" /* Store current cpu timer value */ - " stckf %1" /* Store current tod clock value */ - : "=Q" (lc->last_update_timer), - "=Q" (lc->last_update_clock) - : : "cc"); + + vtime_reset_last_update(lc); + clock = lc->last_update_clock - clock; timer -= lc->last_update_timer; if (hardirq_count()) lc->hardirq_timer += timer; + else if (in_serving_softirq()) + lc->softirq_timer += timer; else lc->system_timer += timer; @@ -231,14 +241,63 @@ EXPORT_SYMBOL_GPL(vtime_account_kernel); void vtime_account_softirq(struct task_struct *tsk) { - get_lowcore()->softirq_timer += vtime_delta(); + if (!__this_cpu_read(s390_idle.idle_dyntick)) + get_lowcore()->softirq_timer += vtime_delta(); + else + vtime_flush(tsk); } void vtime_account_hardirq(struct task_struct *tsk) { - get_lowcore()->hardirq_timer += vtime_delta(); + if (!__this_cpu_read(s390_idle.idle_dyntick)) { + get_lowcore()->hardirq_timer += vtime_delta(); + } else { + /* + * In dynticks mode, the idle cputime is accounted by the nohz + * subsystem. Therefore the s390 timer/clocks are reset on IRQ + * entry and steal time must be accounted now. + */ + vtime_flush(tsk); + } } +#ifdef CONFIG_NO_HZ_COMMON +/** + * vtime_reset - Fast forward vtime entry clocks + * + * Called from dynticks idle IRQ entry to fast-forward the clocks to current time + * so that the IRQ time is still accounted by vtime while nohz cputime is paused. + */ +void vtime_reset(void) +{ + vtime_reset_last_update(get_lowcore()); +} + +/** + * vtime_dyntick_start - Inform vtime about entry to idle-dynticks + * + * Called when idle enters in dyntick mode. The idle cputime that elapsed so far + * is flushed and the tick subsystem takes over the idle cputime accounting. + */ +void vtime_dyntick_start(void) +{ + __this_cpu_write(s390_idle.idle_dyntick, true); + vtime_flush(current); +} + +/** + * vtime_dyntick_stop - Inform vtime about exit from idle-dynticks + * + * Called when idle exits from dyntick mode. The vtime entry clocks are + * fast-forward to current time and idle accounting resumes. + */ +void vtime_dyntick_stop(void) +{ + vtime_reset_last_update(get_lowcore()); + __this_cpu_write(s390_idle.idle_dyntick, false); +} +#endif /* CONFIG_NO_HZ_COMMON */ + /* * Sorted add to a list. List is linear searched until first bigger * element is found. From cf6444c3e1bb7dd5974441bbd74840e9821d36f9 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:39 +0200 Subject: [PATCH 083/167] tick/sched: Unify idle cputime accounting The non-vtime dynticks-idle cputime accounting is a big mess that accumulates within two concurrent statistics, each having their own shortcomings: * The accounting for online CPUs which is based on the delta between tick_nohz_start_idle() and tick_nohz_stop_idle(). Pros: - Works when the tick is off - Has nsecs granularity Cons: - Account idle steal time but doesn't substract it from idle cputime. - Assumes CONFIG_IRQ_TIME_ACCOUNTING by not accounting IRQs but the IRQ time is simply ignored when CONFIG_IRQ_TIME_ACCOUNTING=n - The windows between 1) idle task scheduling and the first call to tick_nohz_start_idle() and 2) idle task between the last tick_nohz_stop_idle() and the rest of the idle time are blindspots wrt. cputime accounting (though mostly insignificant amount) - Relies on private fields outside of kernel stats, with specific accessors. * The accounting for offline CPUs which is based on ticks and the jiffies delta during which the tick was stopped. Pros: - Handles steal time correctly - Handle CONFIG_IRQ_TIME_ACCOUNTING=y and CONFIG_IRQ_TIME_ACCOUNTING=n correctly. - Handles the whole idle task - Accounts directly to kernel stats, without midlayer accumulator. Cons: - Doesn't elapse when the tick is off, which doesn't make it suitable for online CPUs. - Has TICK_NSEC granularity (jiffies) - Needs to track the dyntick-idle ticks that were accounted and substract them from the total jiffies time spent while the tick was stopped. This is an ugly workaround. Having two different accounting for a single context is not the only problem: since those accountings are of different natures, it is possible to observe the global idle time going backward after a CPU goes offline. Clean up the situation with introducing a hybrid approach that stays coherent and works for both online and offline CPUs: * Tick based or native vtime accounting operate before the idle loop is entered and resume once the idle loop prepares to exit. * When the idle loop starts, switch to dynticks-idle accounting as is done currently, except that the statistics accumulate directly to the relevant kernel stat fields. * Private dyntick cputime accounting fields are removed. * Works on both online and offline case. Further improvement will include: * Only switch to dynticks-idle cputime accounting when the tick actually goes in dynticks mode. * Handle CONFIG_IRQ_TIME_ACCOUNTING=n correctly such that the dynticks-idle accounting still elapses while on IRQs. * Correctly substract idle steal cputime from idle time Reported-by: Xin Zhao Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-8-frederic@kernel.org --- include/linux/kernel_stat.h | 24 ++++++++++--- include/linux/vtime.h | 7 +++- kernel/sched/cputime.c | 62 ++++++++++++++++----------------- kernel/time/tick-sched.c | 69 ++++++++++--------------------------- 4 files changed, 75 insertions(+), 87 deletions(-) diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index dd020ecaf67b..ba65aad308a1 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -34,6 +34,9 @@ enum cpu_usage_stat { }; struct kernel_cpustat { +#ifdef CONFIG_NO_HZ_COMMON + int idle_dyntick; +#endif u64 cpustat[NR_STATS]; }; @@ -99,6 +102,20 @@ static inline unsigned long kstat_cpu_irqs_sum(unsigned int cpu) return kstat_cpu(cpu).irqs_sum; } +#ifdef CONFIG_NO_HZ_COMMON +extern void kcpustat_dyntick_start(void); +extern void kcpustat_dyntick_stop(void); +static inline bool kcpustat_idle_dyntick(void) +{ + return __this_cpu_read(kernel_cpustat.idle_dyntick); +} +#else +static inline bool kcpustat_idle_dyntick(void) +{ + return false; +} +#endif /* CONFIG_NO_HZ_COMMON */ + #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN extern u64 kcpustat_field(enum cpu_usage_stat usage, int cpu); extern void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu); @@ -113,7 +130,7 @@ static inline void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu) *dst = kcpustat_cpu(cpu); } -#endif +#endif /* !CONFIG_VIRT_CPU_ACCOUNTING_GEN */ extern void account_user_time(struct task_struct *, u64); extern void account_guest_time(struct task_struct *, u64); @@ -127,14 +144,13 @@ extern u64 get_idle_time(struct kernel_cpustat *kcs, int cpu); #ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE static inline void account_process_tick(struct task_struct *tsk, int user) { - vtime_flush(tsk); + if (!kcpustat_idle_dyntick()) + vtime_flush(tsk); } #else extern void account_process_tick(struct task_struct *, int user); #endif -extern void account_idle_ticks(unsigned long ticks); - #ifdef CONFIG_SCHED_CORE extern void __account_forceidle_time(struct task_struct *tsk, u64 delta); #endif diff --git a/include/linux/vtime.h b/include/linux/vtime.h index 5516ad549e60..9dc25b04a119 100644 --- a/include/linux/vtime.h +++ b/include/linux/vtime.h @@ -31,6 +31,11 @@ static inline bool vtime_generic_enabled_cpu(int cpu) return context_tracking_enabled_cpu(cpu); } +static inline bool vtime_generic_enabled_this_cpu(void) +{ + return context_tracking_enabled_this_cpu(); +} + #ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE extern void vtime_account_idle(struct task_struct *tsk); extern void vtime_account_irq(struct task_struct *tsk, unsigned int offset); @@ -90,7 +95,7 @@ static inline bool vtime_accounting_enabled_cpu(int cpu) static inline bool vtime_accounting_enabled_this_cpu(void) { - return context_tracking_enabled_this_cpu(); + return vtime_generic_enabled_this_cpu(); } extern void vtime_task_switch_generic(struct task_struct *prev); diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index 815d0f772cae..a5733789e0bd 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -414,16 +414,30 @@ static void irqtime_account_process_tick(struct task_struct *p, int user_tick, } } -static void irqtime_account_idle_ticks(int ticks) -{ - irqtime_account_process_tick(current, 0, ticks); -} #else /* !CONFIG_IRQ_TIME_ACCOUNTING: */ -static inline void irqtime_account_idle_ticks(int ticks) { } static inline void irqtime_account_process_tick(struct task_struct *p, int user_tick, int nr_ticks) { } #endif /* !CONFIG_IRQ_TIME_ACCOUNTING */ +#ifdef CONFIG_NO_HZ_COMMON +void kcpustat_dyntick_start(void) +{ + if (!vtime_generic_enabled_this_cpu()) { + vtime_dyntick_start(); + __this_cpu_write(kernel_cpustat.idle_dyntick, 1); + } +} + +void kcpustat_dyntick_stop(void) +{ + if (!vtime_generic_enabled_this_cpu()) { + __this_cpu_write(kernel_cpustat.idle_dyntick, 0); + vtime_dyntick_stop(); + steal_account_process_time(ULONG_MAX); + } +} +#endif /* CONFIG_NO_HZ_COMMON */ + /* * Use precise platform statistics if available: */ @@ -437,11 +451,15 @@ void vtime_account_irq(struct task_struct *tsk, unsigned int offset) vtime_account_hardirq(tsk); } else if (pc & SOFTIRQ_OFFSET) { vtime_account_softirq(tsk); - } else if (!IS_ENABLED(CONFIG_HAVE_VIRT_CPU_ACCOUNTING_IDLE) && - is_idle_task(tsk)) { - vtime_account_idle(tsk); + } else if (!kcpustat_idle_dyntick()) { + if (!IS_ENABLED(CONFIG_HAVE_VIRT_CPU_ACCOUNTING_IDLE) && + is_idle_task(tsk)) { + vtime_account_idle(tsk); + } else { + vtime_account_kernel(tsk); + } } else { - vtime_account_kernel(tsk); + vtime_reset(); } } @@ -483,6 +501,9 @@ void account_process_tick(struct task_struct *p, int user_tick) if (vtime_accounting_enabled_this_cpu()) return; + if (kcpustat_idle_dyntick()) + return; + if (irqtime_enabled()) { irqtime_account_process_tick(p, user_tick, 1); return; @@ -504,29 +525,6 @@ void account_process_tick(struct task_struct *p, int user_tick) account_idle_time(cputime); } -/* - * Account multiple ticks of idle time. - * @ticks: number of stolen ticks - */ -void account_idle_ticks(unsigned long ticks) -{ - u64 cputime, steal; - - if (irqtime_enabled()) { - irqtime_account_idle_ticks(ticks); - return; - } - - cputime = ticks * TICK_NSEC; - steal = steal_account_process_time(ULONG_MAX); - - if (steal >= cputime) - return; - - cputime -= steal; - account_idle_time(cputime); -} - /* * Adjust tick based cputime random precision against scheduler runtime * accounting. diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 597c3a0682e7..c3efd3583cf9 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -285,8 +285,6 @@ static void tick_sched_handle(struct tick_sched *ts, struct pt_regs *regs) if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) { touch_softlockup_watchdog_sched(); - if (is_idle_task(current)) - ts->idle_jiffies++; /* * In case the current tick fired too early past its expected * expiration, make sure we don't bypass the next clock reprogramming @@ -753,8 +751,12 @@ static void tick_nohz_update_jiffies(ktime_t now) static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now) { + u64 *cpustat = kcpustat_this_cpu->cpustat; ktime_t delta; + if (vtime_generic_enabled_this_cpu()) + return; + if (WARN_ON_ONCE(!tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE))) return; @@ -762,9 +764,9 @@ static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now) write_seqcount_begin(&ts->idle_sleeptime_seq); if (nr_iowait_cpu(smp_processor_id()) > 0) - ts->iowait_sleeptime = ktime_add(ts->iowait_sleeptime, delta); + cpustat[CPUTIME_IOWAIT] = ktime_add(cpustat[CPUTIME_IOWAIT], delta); else - ts->idle_sleeptime = ktime_add(ts->idle_sleeptime, delta); + cpustat[CPUTIME_IDLE] = ktime_add(cpustat[CPUTIME_IDLE], delta); ts->idle_entrytime = now; tick_sched_flag_clear(ts, TS_FLAG_IDLE_ACTIVE); @@ -775,18 +777,21 @@ static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now) static void tick_nohz_start_idle(struct tick_sched *ts) { + if (vtime_generic_enabled_this_cpu()) + return; + write_seqcount_begin(&ts->idle_sleeptime_seq); ts->idle_entrytime = ktime_get(); tick_sched_flag_set(ts, TS_FLAG_IDLE_ACTIVE); write_seqcount_end(&ts->idle_sleeptime_seq); - sched_clock_idle_sleep_event(); } -static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *sleeptime, +static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, bool compute_delta, u64 *last_update_time) { struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); + u64 *cpustat = kcpustat_cpu(cpu).cpustat; ktime_t now, idle; unsigned int seq; @@ -812,7 +817,7 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *slee delta = ktime_sub(now, ts->idle_entrytime); } - idle = ktime_add(*sleeptime, delta); + idle = ktime_add(cpustat[idx], delta); } while (read_seqcount_retry(&ts->idle_sleeptime_seq, seq)); return ktime_to_us(idle); @@ -838,9 +843,7 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t *slee */ u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time) { - struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); - - return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE, &ts->idle_sleeptime, + return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE, !nr_iowait_cpu(cpu), last_update_time); } EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); @@ -864,9 +867,7 @@ EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); */ u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) { - struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); - - return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT, &ts->iowait_sleeptime, + return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT, nr_iowait_cpu(cpu), last_update_time); } EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us); @@ -1279,10 +1280,8 @@ void tick_nohz_idle_stop_tick(void) ts->idle_sleeps++; ts->idle_expires = expires; - if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) { - ts->idle_jiffies = ts->last_jiffies; + if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) nohz_balance_enter_idle(cpu); - } } else { tick_nohz_retain_tick(ts); } @@ -1311,6 +1310,7 @@ void tick_nohz_idle_enter(void) WARN_ON_ONCE(ts->timer_expires_base); tick_sched_flag_set(ts, TS_FLAG_INIDLE); + kcpustat_dyntick_start(); tick_nohz_start_idle(ts); local_irq_enable(); @@ -1436,37 +1436,12 @@ unsigned long tick_nohz_get_idle_calls_cpu(int cpu) return ts->idle_calls; } -static void tick_nohz_account_idle_time(struct tick_sched *ts, - ktime_t now) -{ - unsigned long ticks; - - ts->idle_exittime = now; - - if (vtime_accounting_enabled_this_cpu()) - return; - /* - * We stopped the tick in idle. update_process_times() would miss the - * time we slept, as it does only a 1 tick accounting. - * Enforce that this is accounted to idle ! - */ - ticks = jiffies - ts->idle_jiffies; - /* - * We might be one off. Do not randomly account a huge number of ticks! - */ - if (ticks && ticks < LONG_MAX) - account_idle_ticks(ticks); -} - void tick_nohz_idle_restart_tick(void) { struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); - if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) { - ktime_t now = ktime_get(); - tick_nohz_restart_sched_tick(ts, now); - tick_nohz_account_idle_time(ts, now); - } + if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) + tick_nohz_restart_sched_tick(ts, ktime_get()); } static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now) @@ -1475,8 +1450,6 @@ static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now) __tick_nohz_full_update_tick(ts, now); else tick_nohz_restart_sched_tick(ts, now); - - tick_nohz_account_idle_time(ts, now); } /** @@ -1518,6 +1491,7 @@ void tick_nohz_idle_exit(void) if (tick_stopped) tick_nohz_idle_update_tick(ts, now); + kcpustat_dyntick_stop(); local_irq_enable(); } @@ -1655,20 +1629,15 @@ void tick_setup_sched_timer(bool hrtimer) void tick_sched_timer_dying(int cpu) { struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); - ktime_t idle_sleeptime, iowait_sleeptime; unsigned long idle_calls, idle_sleeps; /* This must happen before hrtimers are migrated! */ if (tick_sched_flag_test(ts, TS_FLAG_HIGHRES)) hrtimer_cancel(&ts->sched_timer); - idle_sleeptime = ts->idle_sleeptime; - iowait_sleeptime = ts->iowait_sleeptime; idle_calls = ts->idle_calls; idle_sleeps = ts->idle_sleeps; memset(ts, 0, sizeof(*ts)); - ts->idle_sleeptime = idle_sleeptime; - ts->iowait_sleeptime = iowait_sleeptime; ts->idle_calls = idle_calls; ts->idle_sleeps = idle_sleeps; } From bd0c77cd46c63d02bc33dacdba56133ec1fe44a0 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:40 +0200 Subject: [PATCH 084/167] tick/sched: Remove nohz disabled special case in cputime fetch Even when nohz is not runtime enabled, the dynticks idle cputime accounting can run and the common idle cputime accessors are still relevant. Remove the nohz disabled special case accordingly. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-9-frederic@kernel.org --- kernel/time/tick-sched.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index c3efd3583cf9..cb235ec7d2d6 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -795,9 +795,6 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, ktime_t now, idle; unsigned int seq; - if (!tick_nohz_active) - return -1; - now = ktime_get(); if (last_update_time) *last_update_time = ktime_to_us(now); @@ -839,7 +836,7 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, * This time is measured via accounting rather than sampling, * and is as accurate as ktime_get() is. * - * Return: -1 if NOHZ is not enabled, else total idle time of the @cpu + * Return: -1 if generic vtime is enabled, else total idle time of the @cpu */ u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time) { @@ -863,7 +860,7 @@ EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); * This time is measured via accounting rather than sampling, * and is as accurate as ktime_get() is. * - * Return: -1 if NOHZ is not enabled, else total iowait time of @cpu + * Return: -1 if generic vtime is enabled, else total iowait time of @cpu */ u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) { From a5fe724e206ec7ff3ceb15b285d94316c7fe6c41 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:41 +0200 Subject: [PATCH 085/167] tick/sched: Move dyntick-idle cputime accounting to cputime code Although the dynticks-idle cputime accounting is necessarily tied to the tick subsystem, the actual related accounting code has no business residing there and should be part of the scheduler cputime code. Move away the relevant pieces and state machine to where they belong. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-10-frederic@kernel.org --- include/linux/kernel_stat.h | 14 +++- kernel/sched/core.c | 6 +- kernel/sched/cputime.c | 148 ++++++++++++++++++++++++++++++-- kernel/time/tick-sched.c | 163 +++++++----------------------------- 4 files changed, 188 insertions(+), 143 deletions(-) diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index ba65aad308a1..9343353ac7a3 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -35,9 +35,12 @@ enum cpu_usage_stat { struct kernel_cpustat { #ifdef CONFIG_NO_HZ_COMMON - int idle_dyntick; + bool idle_dyntick; + bool idle_elapse; + seqcount_t idle_sleeptime_seq; + u64 idle_entrytime; #endif - u64 cpustat[NR_STATS]; + u64 cpustat[NR_STATS]; }; struct kernel_stat { @@ -103,8 +106,11 @@ static inline unsigned long kstat_cpu_irqs_sum(unsigned int cpu) } #ifdef CONFIG_NO_HZ_COMMON -extern void kcpustat_dyntick_start(void); -extern void kcpustat_dyntick_stop(void); +extern void kcpustat_dyntick_start(u64 now); +extern void kcpustat_dyntick_stop(u64 now); +extern void kcpustat_irq_enter(u64 now); +extern void kcpustat_irq_exit(u64 now); + static inline bool kcpustat_idle_dyntick(void) { return __this_cpu_read(kernel_cpustat.idle_dyntick); diff --git a/kernel/sched/core.c b/kernel/sched/core.c index b8871449d3c6..d797d6696c58 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5518,7 +5518,11 @@ void sched_exec(void) } DEFINE_PER_CPU(struct kernel_stat, kstat); -DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat); +DEFINE_PER_CPU(struct kernel_cpustat, kernel_cpustat) = { +#ifdef CONFIG_NO_HZ_COMMON + .idle_sleeptime_seq = SEQCNT_ZERO(kernel_cpustat.idle_sleeptime_seq) +#endif +}; EXPORT_PER_CPU_SYMBOL(kstat); EXPORT_PER_CPU_SYMBOL(kernel_cpustat); diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index a5733789e0bd..4c00163b74b9 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -2,6 +2,7 @@ /* * Simple CPU accounting cgroup controller */ +#include #include #include #include "sched.h" @@ -420,22 +421,155 @@ static inline void irqtime_account_process_tick(struct task_struct *p, int user_ #endif /* !CONFIG_IRQ_TIME_ACCOUNTING */ #ifdef CONFIG_NO_HZ_COMMON -void kcpustat_dyntick_start(void) +static void kcpustat_idle_stop(struct kernel_cpustat *kc, u64 now) { - if (!vtime_generic_enabled_this_cpu()) { - vtime_dyntick_start(); - __this_cpu_write(kernel_cpustat.idle_dyntick, 1); - } + u64 *cpustat = kc->cpustat; + u64 delta; + + if (!kc->idle_elapse) + return; + + delta = now - kc->idle_entrytime; + + write_seqcount_begin(&kc->idle_sleeptime_seq); + if (nr_iowait_cpu(smp_processor_id()) > 0) + cpustat[CPUTIME_IOWAIT] += delta; + else + cpustat[CPUTIME_IDLE] += delta; + + kc->idle_entrytime = now; + kc->idle_elapse = false; + write_seqcount_end(&kc->idle_sleeptime_seq); } -void kcpustat_dyntick_stop(void) +static void kcpustat_idle_start(struct kernel_cpustat *kc, u64 now) { + write_seqcount_begin(&kc->idle_sleeptime_seq); + kc->idle_entrytime = now; + kc->idle_elapse = true; + write_seqcount_end(&kc->idle_sleeptime_seq); +} + +void kcpustat_dyntick_stop(u64 now) +{ + struct kernel_cpustat *kc = kcpustat_this_cpu; + if (!vtime_generic_enabled_this_cpu()) { - __this_cpu_write(kernel_cpustat.idle_dyntick, 0); + WARN_ON_ONCE(!kc->idle_dyntick); + kcpustat_idle_stop(kc, now); + kc->idle_dyntick = false; vtime_dyntick_stop(); steal_account_process_time(ULONG_MAX); } } + +void kcpustat_dyntick_start(u64 now) +{ + struct kernel_cpustat *kc = kcpustat_this_cpu; + + if (!vtime_generic_enabled_this_cpu()) { + vtime_dyntick_start(); + kc->idle_dyntick = true; + kcpustat_idle_start(kc, now); + } +} + +void kcpustat_irq_enter(u64 now) +{ + struct kernel_cpustat *kc = kcpustat_this_cpu; + + if (!vtime_generic_enabled_this_cpu()) + kcpustat_idle_stop(kc, now); +} + +void kcpustat_irq_exit(u64 now) +{ + struct kernel_cpustat *kc = kcpustat_this_cpu; + + if (!vtime_generic_enabled_this_cpu()) + kcpustat_idle_start(kc, now); +} + +static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, + bool compute_delta, u64 *last_update_time) +{ + struct kernel_cpustat *kc = &kcpustat_cpu(cpu); + u64 *cpustat = kc->cpustat; + unsigned int seq; + ktime_t now; + u64 idle; + + now = ktime_get(); + if (last_update_time) + *last_update_time = ktime_to_us(now); + + if (vtime_generic_enabled_cpu(cpu)) { + idle = kcpustat_field(idx, cpu); + goto to_us; + } + + do { + seq = read_seqcount_begin(&kc->idle_sleeptime_seq); + + idle = cpustat[idx]; + if (kc->idle_elapse && compute_delta && now > kc->idle_entrytime) + idle += (now - kc->idle_entrytime); + } while (read_seqcount_retry(&kc->idle_sleeptime_seq, seq)); + +to_us: + do_div(idle, NSEC_PER_USEC); + + return idle; +} + +/** + * get_cpu_idle_time_us - get the total idle time of a CPU + * @cpu: CPU number to query + * @last_update_time: variable to store update time in. Do not update + * counters if NULL. + * + * Return the cumulative idle time (since boot) for a given + * CPU, in microseconds. Note that this is partially broken due to + * the counter of iowait tasks that can be remotely updated without + * any synchronization. Therefore it is possible to observe backward + * values within two consecutive reads. + * + * This time is measured via accounting rather than sampling, + * and is as accurate as ktime_get() is. + * + * Return: -1 if generic vtime is enabled, else total idle time of the @cpu + */ +u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time) +{ + return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE, + !nr_iowait_cpu(cpu), last_update_time); +} +EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); + +/** + * get_cpu_iowait_time_us - get the total iowait time of a CPU + * @cpu: CPU number to query + * @last_update_time: variable to store update time in. Do not update + * counters if NULL. + * + * Return the cumulative iowait time (since boot) for a given + * CPU, in microseconds. Note this is partially broken due to + * the counter of iowait tasks that can be remotely updated without + * any synchronization. Therefore it is possible to observe backward + * values within two consecutive reads. + * + * This time is measured via accounting rather than sampling, + * and is as accurate as ktime_get() is. + * + * Return: -1 if generic vtime is enabled, else total iowait time of @cpu + */ +u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) +{ + return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT, + nr_iowait_cpu(cpu), last_update_time); +} +EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us); + #endif /* CONFIG_NO_HZ_COMMON */ /* diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index cb235ec7d2d6..fa03cf7b3cec 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -749,126 +749,6 @@ static void tick_nohz_update_jiffies(ktime_t now) touch_softlockup_watchdog_sched(); } -static void tick_nohz_stop_idle(struct tick_sched *ts, ktime_t now) -{ - u64 *cpustat = kcpustat_this_cpu->cpustat; - ktime_t delta; - - if (vtime_generic_enabled_this_cpu()) - return; - - if (WARN_ON_ONCE(!tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE))) - return; - - delta = ktime_sub(now, ts->idle_entrytime); - - write_seqcount_begin(&ts->idle_sleeptime_seq); - if (nr_iowait_cpu(smp_processor_id()) > 0) - cpustat[CPUTIME_IOWAIT] = ktime_add(cpustat[CPUTIME_IOWAIT], delta); - else - cpustat[CPUTIME_IDLE] = ktime_add(cpustat[CPUTIME_IDLE], delta); - - ts->idle_entrytime = now; - tick_sched_flag_clear(ts, TS_FLAG_IDLE_ACTIVE); - write_seqcount_end(&ts->idle_sleeptime_seq); - - sched_clock_idle_wakeup_event(); -} - -static void tick_nohz_start_idle(struct tick_sched *ts) -{ - if (vtime_generic_enabled_this_cpu()) - return; - - write_seqcount_begin(&ts->idle_sleeptime_seq); - ts->idle_entrytime = ktime_get(); - tick_sched_flag_set(ts, TS_FLAG_IDLE_ACTIVE); - write_seqcount_end(&ts->idle_sleeptime_seq); - sched_clock_idle_sleep_event(); -} - -static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, - bool compute_delta, u64 *last_update_time) -{ - struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); - u64 *cpustat = kcpustat_cpu(cpu).cpustat; - ktime_t now, idle; - unsigned int seq; - - now = ktime_get(); - if (last_update_time) - *last_update_time = ktime_to_us(now); - - if (vtime_generic_enabled_cpu(cpu)) { - idle = kcpustat_field(idx, cpu); - return ktime_to_us(idle); - } - - do { - ktime_t delta = 0; - - seq = read_seqcount_begin(&ts->idle_sleeptime_seq); - - if (tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE) && compute_delta) { - if (now > ts->idle_entrytime) - delta = ktime_sub(now, ts->idle_entrytime); - } - - idle = ktime_add(cpustat[idx], delta); - } while (read_seqcount_retry(&ts->idle_sleeptime_seq, seq)); - - return ktime_to_us(idle); - -} - -/** - * get_cpu_idle_time_us - get the total idle time of a CPU - * @cpu: CPU number to query - * @last_update_time: variable to store update time in. Do not update - * counters if NULL. - * - * Return the cumulative idle time (since boot) for a given - * CPU, in microseconds. Note that this is partially broken due to - * the counter of iowait tasks that can be remotely updated without - * any synchronization. Therefore it is possible to observe backward - * values within two consecutive reads. - * - * This time is measured via accounting rather than sampling, - * and is as accurate as ktime_get() is. - * - * Return: -1 if generic vtime is enabled, else total idle time of the @cpu - */ -u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time) -{ - return get_cpu_sleep_time_us(cpu, CPUTIME_IDLE, - !nr_iowait_cpu(cpu), last_update_time); -} -EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); - -/** - * get_cpu_iowait_time_us - get the total iowait time of a CPU - * @cpu: CPU number to query - * @last_update_time: variable to store update time in. Do not update - * counters if NULL. - * - * Return the cumulative iowait time (since boot) for a given - * CPU, in microseconds. Note this is partially broken due to - * the counter of iowait tasks that can be remotely updated without - * any synchronization. Therefore it is possible to observe backward - * values within two consecutive reads. - * - * This time is measured via accounting rather than sampling, - * and is as accurate as ktime_get() is. - * - * Return: -1 if generic vtime is enabled, else total iowait time of @cpu - */ -u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) -{ - return get_cpu_sleep_time_us(cpu, CPUTIME_IOWAIT, - nr_iowait_cpu(cpu), last_update_time); -} -EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us); - /* Simplified variant of hrtimer_forward_now() */ static ktime_t tick_forward_now(ktime_t expires, ktime_t now) { @@ -1289,6 +1169,20 @@ void tick_nohz_idle_retain_tick(void) tick_nohz_retain_tick(this_cpu_ptr(&tick_cpu_sched)); } +static void tick_nohz_clock_sleep(struct tick_sched *ts) +{ + tick_sched_flag_set(ts, TS_FLAG_IDLE_ACTIVE); + sched_clock_idle_sleep_event(); +} + +static void tick_nohz_clock_wakeup(struct tick_sched *ts) +{ + if (tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE)) { + tick_sched_flag_clear(ts, TS_FLAG_IDLE_ACTIVE); + sched_clock_idle_wakeup_event(); + } +} + /** * tick_nohz_idle_enter - prepare for entering idle on the current CPU * @@ -1303,12 +1197,11 @@ void tick_nohz_idle_enter(void) local_irq_disable(); ts = this_cpu_ptr(&tick_cpu_sched); - WARN_ON_ONCE(ts->timer_expires_base); - tick_sched_flag_set(ts, TS_FLAG_INIDLE); - kcpustat_dyntick_start(); - tick_nohz_start_idle(ts); + ts->idle_entrytime = ktime_get(); + kcpustat_dyntick_start(ts->idle_entrytime); + tick_nohz_clock_sleep(ts); local_irq_enable(); } @@ -1336,10 +1229,13 @@ void tick_nohz_irq_exit(void) { struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); - if (tick_sched_flag_test(ts, TS_FLAG_INIDLE)) - tick_nohz_start_idle(ts); - else + if (tick_sched_flag_test(ts, TS_FLAG_INIDLE)) { + ts->idle_entrytime = ktime_get(); + kcpustat_irq_exit(ts->idle_entrytime); + tick_nohz_clock_sleep(ts); + } else { tick_nohz_full_update_tick(ts); + } } /** @@ -1484,11 +1380,11 @@ void tick_nohz_idle_exit(void) now = ktime_get(); if (idle_active) - tick_nohz_stop_idle(ts, now); + tick_nohz_clock_wakeup(ts); if (tick_stopped) tick_nohz_idle_update_tick(ts, now); - kcpustat_dyntick_stop(); + kcpustat_dyntick_stop(now); local_irq_enable(); } @@ -1545,9 +1441,14 @@ static inline void tick_nohz_irq_enter(void) if (!tick_sched_flag_test(ts, TS_FLAG_STOPPED | TS_FLAG_IDLE_ACTIVE)) return; + now = ktime_get(); - if (tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE)) - tick_nohz_stop_idle(ts, now); + + if (tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE)) { + tick_nohz_clock_wakeup(ts); + kcpustat_irq_enter(now); + } + /* * If all CPUs are idle we may need to update a stale jiffies value. * Note nohz_full is a special case: a timekeeper is guaranteed to stay From 29807c524d66e27762cdc8992c2cac89b4c3fda9 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:42 +0200 Subject: [PATCH 086/167] tick/sched: Remove unused fields Remove fields after the dyntick-idle cputime migration to scheduler code. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-11-frederic@kernel.org --- kernel/time/tick-sched.h | 12 ------------ kernel/time/timer_list.c | 6 +----- scripts/gdb/linux/timerlist.py | 4 ---- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/kernel/time/tick-sched.h b/kernel/time/tick-sched.h index b4a7822f495d..79b9252047b1 100644 --- a/kernel/time/tick-sched.h +++ b/kernel/time/tick-sched.h @@ -44,9 +44,7 @@ struct tick_device { * to resume the tick timer operation in the timeline * when the CPU returns from nohz sleep. * @next_tick: Next tick to be fired when in dynticks mode. - * @idle_jiffies: jiffies at the entry to idle for idle time accounting * @idle_waketime: Time when the idle was interrupted - * @idle_sleeptime_seq: sequence counter for data consistency * @idle_entrytime: Time when the idle call was entered * @last_jiffies: Base jiffies snapshot when next event was last computed * @timer_expires_base: Base time clock monotonic for @timer_expires @@ -55,9 +53,6 @@ struct tick_device { * @idle_expires: Next tick in idle, for debugging purpose only * @idle_calls: Total number of idle calls * @idle_sleeps: Number of idle calls, where the sched tick was stopped - * @idle_exittime: Time when the idle state was left - * @idle_sleeptime: Sum of the time slept in idle with sched tick stopped - * @iowait_sleeptime: Sum of the time slept in idle with sched tick stopped, with IO outstanding * @tick_dep_mask: Tick dependency mask - is set, if someone needs the tick * @check_clocks: Notification mechanism about clocksource changes */ @@ -73,12 +68,10 @@ struct tick_sched { struct hrtimer sched_timer; ktime_t last_tick; ktime_t next_tick; - unsigned long idle_jiffies; ktime_t idle_waketime; unsigned int got_idle_tick; /* Idle entry */ - seqcount_t idle_sleeptime_seq; ktime_t idle_entrytime; /* Tick stop */ @@ -90,11 +83,6 @@ struct tick_sched { unsigned long idle_calls; unsigned long idle_sleeps; - /* Idle exit */ - ktime_t idle_exittime; - ktime_t idle_sleeptime; - ktime_t iowait_sleeptime; - /* Full dynticks handling */ atomic_t tick_dep_mask; diff --git a/kernel/time/timer_list.c b/kernel/time/timer_list.c index 427d7ddea3af..514802def1e0 100644 --- a/kernel/time/timer_list.c +++ b/kernel/time/timer_list.c @@ -152,14 +152,10 @@ static void print_cpu(struct seq_file *m, int cpu, u64 now) P_flag(highres, TS_FLAG_HIGHRES); P_ns(last_tick); P_flag(tick_stopped, TS_FLAG_STOPPED); - P(idle_jiffies); P(idle_calls); P(idle_sleeps); P_ns(idle_entrytime); P_ns(idle_waketime); - P_ns(idle_exittime); - P_ns(idle_sleeptime); - P_ns(iowait_sleeptime); P(last_jiffies); P(next_timer); P_ns(idle_expires); @@ -256,7 +252,7 @@ static void timer_list_show_tickdevices_header(struct seq_file *m) static inline void timer_list_header(struct seq_file *m, u64 now) { - SEQ_printf(m, "Timer List Version: v0.10\n"); + SEQ_printf(m, "Timer List Version: v0.11\n"); SEQ_printf(m, "HRTIMER_MAX_CLOCK_BASES: %d\n", HRTIMER_MAX_CLOCK_BASES); SEQ_printf(m, "now at %Ld nsecs\n", (unsigned long long)now); SEQ_printf(m, "\n"); diff --git a/scripts/gdb/linux/timerlist.py b/scripts/gdb/linux/timerlist.py index 9fb3436a217c..744b032e4d38 100644 --- a/scripts/gdb/linux/timerlist.py +++ b/scripts/gdb/linux/timerlist.py @@ -90,14 +90,10 @@ def print_cpu(hrtimer_bases, cpu, max_clock_bases): text += f" .{'nohz':15s}: {int(bool(ts['flags'] & TS_FLAG_NOHZ))}\n" text += f" .{'last_tick':15s}: {ts['last_tick']}\n" text += f" .{'tick_stopped':15s}: {int(bool(ts['flags'] & TS_FLAG_STOPPED))}\n" - text += f" .{'idle_jiffies':15s}: {ts['idle_jiffies']}\n" text += f" .{'idle_calls':15s}: {ts['idle_calls']}\n" text += f" .{'idle_sleeps':15s}: {ts['idle_sleeps']}\n" text += f" .{'idle_entrytime':15s}: {ts['idle_entrytime']} nsecs\n" text += f" .{'idle_waketime':15s}: {ts['idle_waketime']} nsecs\n" - text += f" .{'idle_exittime':15s}: {ts['idle_exittime']} nsecs\n" - text += f" .{'idle_sleeptime':15s}: {ts['idle_sleeptime']} nsecs\n" - text += f" .{'iowait_sleeptime':15s}: {ts['iowait_sleeptime']} nsecs\n" text += f" .{'last_jiffies':15s}: {ts['last_jiffies']}\n" text += f" .{'next_timer':15s}: {ts['next_timer']}\n" text += f" .{'idle_expires':15s}: {ts['idle_expires']} nsecs\n" From 6a1f6a9dd0736257f5e5af32dd955d186cdc075d Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:43 +0200 Subject: [PATCH 087/167] tick/sched: Account tickless idle cputime only when tick is stopped There is no real point in switching to dyntick-idle cputime accounting mode if the tick is not actually stopped. This just adds overhead, notably fetching the GTOD, on each idle exit and each idle IRQ entry for no reason during short idle trips. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-12-frederic@kernel.org --- kernel/time/tick-sched.c | 50 +++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index fa03cf7b3cec..c1ee0b256445 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -1157,8 +1157,10 @@ void tick_nohz_idle_stop_tick(void) ts->idle_sleeps++; ts->idle_expires = expires; - if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) + if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED)) { + kcpustat_dyntick_start(ts->idle_entrytime); nohz_balance_enter_idle(cpu); + } } else { tick_nohz_retain_tick(ts); } @@ -1200,7 +1202,6 @@ void tick_nohz_idle_enter(void) WARN_ON_ONCE(ts->timer_expires_base); tick_sched_flag_set(ts, TS_FLAG_INIDLE); ts->idle_entrytime = ktime_get(); - kcpustat_dyntick_start(ts->idle_entrytime); tick_nohz_clock_sleep(ts); local_irq_enable(); @@ -1230,9 +1231,10 @@ void tick_nohz_irq_exit(void) struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); if (tick_sched_flag_test(ts, TS_FLAG_INIDLE)) { - ts->idle_entrytime = ktime_get(); - kcpustat_irq_exit(ts->idle_entrytime); tick_nohz_clock_sleep(ts); + ts->idle_entrytime = ktime_get(); + if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) + kcpustat_irq_exit(ts->idle_entrytime); } else { tick_nohz_full_update_tick(ts); } @@ -1333,8 +1335,17 @@ void tick_nohz_idle_restart_tick(void) { struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); - if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) - tick_nohz_restart_sched_tick(ts, ktime_get()); + if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) { + /* + * Update entrytime here in case the tick restart is due to temporary + * polling on forced broadcast. The tick may be stopped again later within + * the same idle trip. The idle_entrytime was updated recently but make sure + * no tiny amount of idle time is accounted twice. + */ + ts->idle_entrytime = ktime_get(); + kcpustat_dyntick_stop(ts->idle_entrytime); + tick_nohz_restart_sched_tick(ts, ts->idle_entrytime); + } } static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now) @@ -1364,7 +1375,6 @@ static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now) void tick_nohz_idle_exit(void) { struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); - bool idle_active, tick_stopped; ktime_t now; local_irq_disable(); @@ -1373,18 +1383,13 @@ void tick_nohz_idle_exit(void) WARN_ON_ONCE(ts->timer_expires_base); tick_sched_flag_clear(ts, TS_FLAG_INIDLE); - idle_active = tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE); - tick_stopped = tick_sched_flag_test(ts, TS_FLAG_STOPPED); + tick_nohz_clock_wakeup(ts); - if (idle_active || tick_stopped) + if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) { now = ktime_get(); - - if (idle_active) - tick_nohz_clock_wakeup(ts); - - if (tick_stopped) + kcpustat_dyntick_stop(now); tick_nohz_idle_update_tick(ts, now); - kcpustat_dyntick_stop(now); + } local_irq_enable(); } @@ -1439,15 +1444,13 @@ static inline void tick_nohz_irq_enter(void) struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched); ktime_t now; - if (!tick_sched_flag_test(ts, TS_FLAG_STOPPED | TS_FLAG_IDLE_ACTIVE)) + tick_nohz_clock_wakeup(ts); + + if (!tick_sched_flag_test(ts, TS_FLAG_STOPPED)) return; now = ktime_get(); - - if (tick_sched_flag_test(ts, TS_FLAG_IDLE_ACTIVE)) { - tick_nohz_clock_wakeup(ts); - kcpustat_irq_enter(now); - } + kcpustat_irq_enter(now); /* * If all CPUs are idle we may need to update a stale jiffies value. @@ -1456,8 +1459,7 @@ static inline void tick_nohz_irq_enter(void) * rare case (typically stop machine). So we must make sure we have a * last resort. */ - if (tick_sched_flag_test(ts, TS_FLAG_STOPPED)) - tick_nohz_update_jiffies(now); + tick_nohz_update_jiffies(now); } #else From 127b2eb44f36d5d7059f1af425b5800cb27440f9 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:44 +0200 Subject: [PATCH 088/167] tick/sched: Consolidate idle time fetching APIs Fetching the idle cputime is available through a variety of accessors all over the place depending on the different accounting flavours and needs: - idle vtime generic accounting can be accessed by kcpustat_field(), kcpustat_cpu_fetch(), get_idle/iowait_time() and get_cpu_idle/iowait_time_us() - dynticks-idle accounting can only be accessed by get_idle/iowait_time() or get_cpu_idle/iowait_time_us() - CONFIG_NO_HZ_COMMON=n idle accounting can be accessed by kcpustat_field() kcpustat_cpu_fetch(), or get_idle/iowait_time() but not by get_cpu_idle/iowait_time_us() Moreover get_idle/iowait_time() relies on get_cpu_idle/iowait_time_us() with a non-sensical conversion to microseconds and back to nanoseconds on the way. Start consolidating the APIs with removing get_idle/iowait_time() and make kcpustat_field() and kcpustat_cpu_fetch() work for all cases. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-13-frederic@kernel.org --- fs/proc/stat.c | 40 +++--------------------- fs/proc/uptime.c | 8 ++--- include/linux/kernel_stat.h | 34 ++++++++++++++++++--- kernel/sched/cputime.c | 61 ++++++++++++++++++++++++------------- 4 files changed, 76 insertions(+), 67 deletions(-) diff --git a/fs/proc/stat.c b/fs/proc/stat.c index 8b444e862319..c00468a83f64 100644 --- a/fs/proc/stat.c +++ b/fs/proc/stat.c @@ -22,38 +22,6 @@ #define arch_irq_stat() 0 #endif -u64 get_idle_time(struct kernel_cpustat *kcs, int cpu) -{ - u64 idle, idle_usecs = -1ULL; - - if (cpu_online(cpu)) - idle_usecs = get_cpu_idle_time_us(cpu, NULL); - - if (idle_usecs == -1ULL) - /* !NO_HZ or cpu offline so we can rely on cpustat.idle */ - idle = kcs->cpustat[CPUTIME_IDLE]; - else - idle = idle_usecs * NSEC_PER_USEC; - - return idle; -} - -static u64 get_iowait_time(struct kernel_cpustat *kcs, int cpu) -{ - u64 iowait, iowait_usecs = -1ULL; - - if (cpu_online(cpu)) - iowait_usecs = get_cpu_iowait_time_us(cpu, NULL); - - if (iowait_usecs == -1ULL) - /* !NO_HZ or cpu offline so we can rely on cpustat.iowait */ - iowait = kcs->cpustat[CPUTIME_IOWAIT]; - else - iowait = iowait_usecs * NSEC_PER_USEC; - - return iowait; -} - static void show_irq_gap(struct seq_file *p, unsigned int gap) { static const char zeros[] = " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; @@ -105,8 +73,8 @@ static int show_stat(struct seq_file *p, void *v) user += cpustat[CPUTIME_USER]; nice += cpustat[CPUTIME_NICE]; system += cpustat[CPUTIME_SYSTEM]; - idle += get_idle_time(&kcpustat, i); - iowait += get_iowait_time(&kcpustat, i); + idle += cpustat[CPUTIME_IDLE]; + iowait += cpustat[CPUTIME_IOWAIT]; irq += cpustat[CPUTIME_IRQ]; softirq += cpustat[CPUTIME_SOFTIRQ]; steal += cpustat[CPUTIME_STEAL]; @@ -146,8 +114,8 @@ static int show_stat(struct seq_file *p, void *v) user = cpustat[CPUTIME_USER]; nice = cpustat[CPUTIME_NICE]; system = cpustat[CPUTIME_SYSTEM]; - idle = get_idle_time(&kcpustat, i); - iowait = get_iowait_time(&kcpustat, i); + idle = cpustat[CPUTIME_IDLE]; + iowait = cpustat[CPUTIME_IOWAIT]; irq = cpustat[CPUTIME_IRQ]; softirq = cpustat[CPUTIME_SOFTIRQ]; steal = cpustat[CPUTIME_STEAL]; diff --git a/fs/proc/uptime.c b/fs/proc/uptime.c index b5343d209381..433aa947cd57 100644 --- a/fs/proc/uptime.c +++ b/fs/proc/uptime.c @@ -18,12 +18,8 @@ static int uptime_proc_show(struct seq_file *m, void *v) int i; idle_nsec = 0; - for_each_possible_cpu(i) { - struct kernel_cpustat kcs; - - kcpustat_cpu_fetch(&kcs, i); - idle_nsec += get_idle_time(&kcs, i); - } + for_each_possible_cpu(i) + idle_nsec += kcpustat_field(CPUTIME_IDLE, i); ktime_get_boottime_ts64(&uptime); timens_add_boottime(&uptime); diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index 9343353ac7a3..3680519d7b2c 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -110,32 +110,59 @@ extern void kcpustat_dyntick_start(u64 now); extern void kcpustat_dyntick_stop(u64 now); extern void kcpustat_irq_enter(u64 now); extern void kcpustat_irq_exit(u64 now); +extern u64 kcpustat_field_idle(int cpu); +extern u64 kcpustat_field_iowait(int cpu); static inline bool kcpustat_idle_dyntick(void) { return __this_cpu_read(kernel_cpustat.idle_dyntick); } #else +static inline u64 kcpustat_field_idle(int cpu) +{ + return kcpustat_cpu(cpu).cpustat[CPUTIME_IDLE]; +} +static inline u64 kcpustat_field_iowait(int cpu) +{ + return kcpustat_cpu(cpu).cpustat[CPUTIME_IOWAIT]; +} + static inline bool kcpustat_idle_dyntick(void) { return false; } #endif /* CONFIG_NO_HZ_COMMON */ +/* Fetch cputime values when vtime is disabled on a CPU */ +static inline u64 kcpustat_field_default(enum cpu_usage_stat usage, int cpu) +{ + if (usage == CPUTIME_IDLE) + return kcpustat_field_idle(cpu); + if (usage == CPUTIME_IOWAIT) + return kcpustat_field_iowait(cpu); + return kcpustat_cpu(cpu).cpustat[usage]; +} + +static inline void kcpustat_cpu_fetch_default(struct kernel_cpustat *dst, int cpu) +{ + *dst = kcpustat_cpu(cpu); + dst->cpustat[CPUTIME_IDLE] = kcpustat_field_idle(cpu); + dst->cpustat[CPUTIME_IOWAIT] = kcpustat_field_iowait(cpu); +} + #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN extern u64 kcpustat_field(enum cpu_usage_stat usage, int cpu); extern void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu); #else static inline u64 kcpustat_field(enum cpu_usage_stat usage, int cpu) { - return kcpustat_cpu(cpu).cpustat[usage]; + return kcpustat_field_default(usage, cpu); } static inline void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu) { - *dst = kcpustat_cpu(cpu); + kcpustat_cpu_fetch_default(dst, cpu); } - #endif /* !CONFIG_VIRT_CPU_ACCOUNTING_GEN */ extern void account_user_time(struct task_struct *, u64); @@ -145,7 +172,6 @@ extern void account_system_index_time(struct task_struct *, u64, enum cpu_usage_stat); extern void account_steal_time(u64); extern void account_idle_time(u64); -extern u64 get_idle_time(struct kernel_cpustat *kcs, int cpu); #ifdef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE static inline void account_process_tick(struct task_struct *tsk, int user) diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index 4c00163b74b9..c91fd67f93ea 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -490,24 +490,14 @@ void kcpustat_irq_exit(u64 now) kcpustat_idle_start(kc, now); } -static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, - bool compute_delta, u64 *last_update_time) +static u64 kcpustat_field_dyntick(int cpu, enum cpu_usage_stat idx, + bool compute_delta, u64 now) { struct kernel_cpustat *kc = &kcpustat_cpu(cpu); u64 *cpustat = kc->cpustat; unsigned int seq; - ktime_t now; u64 idle; - now = ktime_get(); - if (last_update_time) - *last_update_time = ktime_to_us(now); - - if (vtime_generic_enabled_cpu(cpu)) { - idle = kcpustat_field(idx, cpu); - goto to_us; - } - do { seq = read_seqcount_begin(&kc->idle_sleeptime_seq); @@ -516,12 +506,42 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, idle += (now - kc->idle_entrytime); } while (read_seqcount_retry(&kc->idle_sleeptime_seq, seq)); -to_us: - do_div(idle, NSEC_PER_USEC); - return idle; } +u64 kcpustat_field_idle(int cpu) +{ + return kcpustat_field_dyntick(cpu, CPUTIME_IDLE, + !nr_iowait_cpu(cpu), ktime_get()); +} +EXPORT_SYMBOL_GPL(kcpustat_field_idle); + +u64 kcpustat_field_iowait(int cpu) +{ + return kcpustat_field_dyntick(cpu, CPUTIME_IOWAIT, + nr_iowait_cpu(cpu), ktime_get()); +} +EXPORT_SYMBOL_GPL(kcpustat_field_iowait); + +static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, + bool compute_delta, u64 *last_update_time) +{ + ktime_t now = ktime_get(); + u64 res; + + if (vtime_generic_enabled_cpu(cpu)) + res = kcpustat_field(idx, cpu); + else + res = kcpustat_field_dyntick(cpu, idx, compute_delta, now); + + do_div(res, NSEC_PER_USEC); + + if (last_update_time) + *last_update_time = ktime_to_us(now); + + return res; +} + /** * get_cpu_idle_time_us - get the total idle time of a CPU * @cpu: CPU number to query @@ -569,7 +589,6 @@ u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) nr_iowait_cpu(cpu), last_update_time); } EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us); - #endif /* CONFIG_NO_HZ_COMMON */ /* @@ -1123,8 +1142,8 @@ u64 kcpustat_field(enum cpu_usage_stat usage, int cpu) struct rq *rq; int err; - if (!vtime_accounting_enabled_cpu(cpu)) - return val; + if (!vtime_generic_enabled_cpu(cpu)) + return kcpustat_field_default(usage, cpu); rq = cpu_rq(cpu); @@ -1219,8 +1238,8 @@ void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu) struct rq *rq; int err; - if (!vtime_accounting_enabled_cpu(cpu)) { - *dst = *src; + if (!vtime_generic_enabled_cpu(cpu)) { + kcpustat_cpu_fetch_default(dst, cpu); return; } @@ -1233,7 +1252,7 @@ void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu) curr = rcu_dereference(rq->curr); if (WARN_ON_ONCE(!curr)) { rcu_read_unlock(); - *dst = *src; + kcpustat_cpu_fetch_default(dst, cpu); return; } From 3b45b4f188f3a0ebd16ab71efd2ffcc7a16ad861 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:45 +0200 Subject: [PATCH 089/167] sched/cputime: Provide get_cpu_[idle|iowait]_time_us() off-case The last reason why get_cpu_idle/iowait_time_us() may return -1 now is if the config doesn't support nohz. The ad-hoc replacement solution by cpufreq is to compute jiffies minus the whole busy cputime. Although the intention should provide a coherent low resolution estimation of the idle and iowait time, the implementation is buggy because jiffies don't start at 0. Just provide instead a real get_cpu_[idle|iowait]_time_us() offcase. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-14-frederic@kernel.org --- drivers/cpufreq/cpufreq.c | 29 +---------------------------- include/linux/kernel_stat.h | 3 +++ include/linux/tick.h | 4 ---- kernel/sched/cputime.c | 12 +++++++++--- 4 files changed, 13 insertions(+), 35 deletions(-) diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 44eb1b7e7fc1..dda0d34d3c02 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -130,38 +130,11 @@ struct kobject *get_governor_parent_kobj(struct cpufreq_policy *policy) } EXPORT_SYMBOL_GPL(get_governor_parent_kobj); -static inline u64 get_cpu_idle_time_jiffy(unsigned int cpu, u64 *wall) -{ - struct kernel_cpustat kcpustat; - u64 cur_wall_time; - u64 idle_time; - u64 busy_time; - - cur_wall_time = jiffies64_to_nsecs(get_jiffies_64()); - - kcpustat_cpu_fetch(&kcpustat, cpu); - - busy_time = kcpustat.cpustat[CPUTIME_USER]; - busy_time += kcpustat.cpustat[CPUTIME_SYSTEM]; - busy_time += kcpustat.cpustat[CPUTIME_IRQ]; - busy_time += kcpustat.cpustat[CPUTIME_SOFTIRQ]; - busy_time += kcpustat.cpustat[CPUTIME_STEAL]; - busy_time += kcpustat.cpustat[CPUTIME_NICE]; - - idle_time = cur_wall_time - busy_time; - if (wall) - *wall = div_u64(cur_wall_time, NSEC_PER_USEC); - - return div_u64(idle_time, NSEC_PER_USEC); -} - u64 get_cpu_idle_time(unsigned int cpu, u64 *wall, int io_busy) { u64 idle_time = get_cpu_idle_time_us(cpu, io_busy ? wall : NULL); - if (idle_time == -1ULL) - return get_cpu_idle_time_jiffy(cpu, wall); - else if (!io_busy) + if (!io_busy) idle_time += get_cpu_iowait_time_us(cpu, wall); return idle_time; diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index 3680519d7b2c..512104b0ff49 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -133,6 +133,9 @@ static inline bool kcpustat_idle_dyntick(void) } #endif /* CONFIG_NO_HZ_COMMON */ +extern u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time); +extern u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time); + /* Fetch cputime values when vtime is disabled on a CPU */ static inline u64 kcpustat_field_default(enum cpu_usage_stat usage, int cpu) { diff --git a/include/linux/tick.h b/include/linux/tick.h index 738007d6f577..1cf4651f09ad 100644 --- a/include/linux/tick.h +++ b/include/linux/tick.h @@ -139,8 +139,6 @@ extern bool tick_nohz_idle_got_tick(void); extern ktime_t tick_nohz_get_next_hrtimer(void); extern ktime_t tick_nohz_get_sleep_length(ktime_t *delta_next); extern unsigned long tick_nohz_get_idle_calls_cpu(int cpu); -extern u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time); -extern u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time); #else /* !CONFIG_NO_HZ_COMMON */ #define tick_nohz_enabled (0) static inline bool tick_nohz_is_active(void) { return false; } @@ -162,8 +160,6 @@ static inline ktime_t tick_nohz_get_sleep_length(ktime_t *delta_next) *delta_next = TICK_NSEC; return *delta_next; } -static inline u64 get_cpu_idle_time_us(int cpu, u64 *unused) { return -1; } -static inline u64 get_cpu_iowait_time_us(int cpu, u64 *unused) { return -1; } #endif /* !CONFIG_NO_HZ_COMMON */ /* diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index c91fd67f93ea..335d2c127763 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -522,6 +522,13 @@ u64 kcpustat_field_iowait(int cpu) nr_iowait_cpu(cpu), ktime_get()); } EXPORT_SYMBOL_GPL(kcpustat_field_iowait); +#else +static u64 kcpustat_field_dyntick(int cpu, enum cpu_usage_stat idx, + bool compute_delta, ktime_t now) +{ + return kcpustat_cpu(cpu).cpustat[idx]; +} +#endif /* CONFIG_NO_HZ_COMMON */ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, bool compute_delta, u64 *last_update_time) @@ -557,7 +564,7 @@ static u64 get_cpu_sleep_time_us(int cpu, enum cpu_usage_stat idx, * This time is measured via accounting rather than sampling, * and is as accurate as ktime_get() is. * - * Return: -1 if generic vtime is enabled, else total idle time of the @cpu + * Return: total idle time of the @cpu */ u64 get_cpu_idle_time_us(int cpu, u64 *last_update_time) { @@ -581,7 +588,7 @@ EXPORT_SYMBOL_GPL(get_cpu_idle_time_us); * This time is measured via accounting rather than sampling, * and is as accurate as ktime_get() is. * - * Return: -1 if generic vtime is enabled, else total iowait time of @cpu + * Return: total iowait time of @cpu */ u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) { @@ -589,7 +596,6 @@ u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) nr_iowait_cpu(cpu), last_update_time); } EXPORT_SYMBOL_GPL(get_cpu_iowait_time_us); -#endif /* CONFIG_NO_HZ_COMMON */ /* * Use precise platform statistics if available: From 7198e3927a14535475a24cce559f41f97e6c0b66 Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:46 +0200 Subject: [PATCH 090/167] sched/cputime: Handle idle irqtime gracefully The dyntick-idle cputime accounting always assumes that interrupt time accounting is enabled and consequently stops elapsing the idle time during dyntick-idle interrupts. This doesn't mix up well with disabled interrupt time accounting because then idle interrupts become a cputime blind-spot. Also this feature is disabled on most configurations and the overhead of pausing dyntick-idle accounting while in idle interrupts could then be avoided. Fix the situation with conditionally pausing dyntick-idle accounting during idle interrupts only iff either native vtime (which does interrupt time accounting) or generic interrupt time accounting are enabled. Also make sure that the accumulated interrupt time is not accidentally substracted from later accounting. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-15-frederic@kernel.org --- kernel/sched/cputime.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index 335d2c127763..94be22aa5cb6 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -47,7 +47,8 @@ static void irqtime_account_delta(struct irqtime *irqtime, u64 delta, u64_stats_update_begin(&irqtime->sync); cpustat[idx] += delta; irqtime->total += delta; - irqtime->tick_delta += delta; + if (!kcpustat_idle_dyntick()) + irqtime->tick_delta += delta; u64_stats_update_end(&irqtime->sync); } @@ -444,6 +445,10 @@ static void kcpustat_idle_stop(struct kernel_cpustat *kc, u64 now) static void kcpustat_idle_start(struct kernel_cpustat *kc, u64 now) { + /* Irqtime accounting might have been enabled in the middle of the IRQ */ + if (kc->idle_elapse) + return; + write_seqcount_begin(&kc->idle_sleeptime_seq); kc->idle_entrytime = now; kc->idle_elapse = true; @@ -478,7 +483,8 @@ void kcpustat_irq_enter(u64 now) { struct kernel_cpustat *kc = kcpustat_this_cpu; - if (!vtime_generic_enabled_this_cpu()) + if (!vtime_generic_enabled_this_cpu() && + (irqtime_enabled() || vtime_accounting_enabled_this_cpu())) kcpustat_idle_stop(kc, now); } @@ -486,7 +492,15 @@ void kcpustat_irq_exit(u64 now) { struct kernel_cpustat *kc = kcpustat_this_cpu; - if (!vtime_generic_enabled_this_cpu()) + /* + * Generic vtime already does its own idle accounting. + * But irqtime accounting or arch vtime which also accounts IRQs + * need to pause nohz accounting. Resume nohz accounting as long + * as the irqtime config is enabled to handle case where irqtime + * accounting got runtime disabled in the middle of an IRQ. + */ + if (!vtime_generic_enabled_this_cpu() && + (IS_ENABLED(CONFIG_IRQ_TIME_ACCOUNTING) || vtime_accounting_enabled_this_cpu())) kcpustat_idle_start(kc, now); } From 6199f9999a9b62b2b84a1bf5b52a9fd0bb8de5af Mon Sep 17 00:00:00 2001 From: Frederic Weisbecker Date: Fri, 8 May 2026 15:16:47 +0200 Subject: [PATCH 091/167] sched/cputime: Handle dyntick-idle steal time correctly The dyntick-idle steal time is currently accounted when the tick restarts but the stolen idle time is not subtracted from the idle time that was already accounted. This is to avoid observing the idle time going backward as the dyntick-idle cputime accessors can't reliably know in advance the stolen idle time. In order to maintain a forward progressing idle cputime while subtracting idle steal time from it, keep track of the previously accounted idle stolen time and substract it from _later_ idle cputime accounting. Signed-off-by: Frederic Weisbecker Signed-off-by: Thomas Gleixner Tested-by: Shrikanth Hegde Link: https://patch.msgid.link/20260508131647.43868-16-frederic@kernel.org --- include/linux/kernel_stat.h | 1 + kernel/sched/cputime.c | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index 512104b0ff49..fce1392e2140 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -39,6 +39,7 @@ struct kernel_cpustat { bool idle_elapse; seqcount_t idle_sleeptime_seq; u64 idle_entrytime; + u64 idle_stealtime[2]; #endif u64 cpustat[NR_STATS]; }; diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index 94be22aa5cb6..244b57417240 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -425,19 +425,32 @@ static inline void irqtime_account_process_tick(struct task_struct *p, int user_ static void kcpustat_idle_stop(struct kernel_cpustat *kc, u64 now) { u64 *cpustat = kc->cpustat; - u64 delta; + u64 delta, steal, steal_delta; + int iowait; if (!kc->idle_elapse) return; + iowait = nr_iowait_cpu(smp_processor_id()) > 0; delta = now - kc->idle_entrytime; + steal = steal_account_process_time(delta); + /* + * Record the idle time after substracting the steal time from + * previous update sequence. Don't substract the steal time from + * the current update sequence to avoid readers moving backward. + */ write_seqcount_begin(&kc->idle_sleeptime_seq); - if (nr_iowait_cpu(smp_processor_id()) > 0) + steal_delta = min_t(u64, kc->idle_stealtime[iowait], delta); + delta -= steal_delta; + kc->idle_stealtime[iowait] -= steal_delta; + + if (iowait) cpustat[CPUTIME_IOWAIT] += delta; else cpustat[CPUTIME_IDLE] += delta; + kc->idle_stealtime[iowait] += steal; kc->idle_entrytime = now; kc->idle_elapse = false; write_seqcount_end(&kc->idle_sleeptime_seq); @@ -464,7 +477,6 @@ void kcpustat_dyntick_stop(u64 now) kcpustat_idle_stop(kc, now); kc->idle_dyntick = false; vtime_dyntick_stop(); - steal_account_process_time(ULONG_MAX); } } @@ -508,6 +520,7 @@ static u64 kcpustat_field_dyntick(int cpu, enum cpu_usage_stat idx, bool compute_delta, u64 now) { struct kernel_cpustat *kc = &kcpustat_cpu(cpu); + int iowait = idx == CPUTIME_IOWAIT; u64 *cpustat = kc->cpustat; unsigned int seq; u64 idle; @@ -516,8 +529,13 @@ static u64 kcpustat_field_dyntick(int cpu, enum cpu_usage_stat idx, seq = read_seqcount_begin(&kc->idle_sleeptime_seq); idle = cpustat[idx]; - if (kc->idle_elapse && compute_delta && now > kc->idle_entrytime) - idle += (now - kc->idle_entrytime); + + if (kc->idle_elapse && compute_delta && now > kc->idle_entrytime) { + u64 delta = now - kc->idle_entrytime; + + delta -= min_t(u64, kc->idle_stealtime[iowait], delta); + idle += delta; + } } while (read_seqcount_retry(&kc->idle_sleeptime_seq, seq)); return idle; From 91d2a1a916a96bc0ed542704c4e20a84ff2cf554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 19 May 2026 08:26:15 +0200 Subject: [PATCH 092/167] riscv: vdso: Drop CONFIG_GENERIC_TIME_VSYSCALL guard around syscall fallbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The syscall definitions can be built just fine for 32-bit systems. Also the guard does not cover __arch_get_hw_counter() which is always used together with those system call fallbacks. Also this header is unused when no vDSO is built anyways. Drop the ifdeffery. The logic will be simpler to understand. Furthermore this prepares the complete removal of CONFIG_GENERIC_TIME_VSYSCALL. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260519-vdso-generic_time_vsyscal-v1-1-5c2a5905d5f5@linutronix.de --- arch/riscv/include/asm/vdso/gettimeofday.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/arch/riscv/include/asm/vdso/gettimeofday.h b/arch/riscv/include/asm/vdso/gettimeofday.h index 9ec08fa04d35..61cb3cbab143 100644 --- a/arch/riscv/include/asm/vdso/gettimeofday.h +++ b/arch/riscv/include/asm/vdso/gettimeofday.h @@ -9,12 +9,6 @@ #include #include -/* - * 32-bit land is lacking generic time vsyscalls as well as the legacy 32-bit - * time syscalls like gettimeofday. Skip these definitions since on 32-bit. - */ -#ifdef CONFIG_GENERIC_TIME_VSYSCALL - #define VDSO_HAS_CLOCK_GETRES 1 static __always_inline @@ -66,8 +60,6 @@ int clock_getres_fallback(clockid_t _clkid, struct __kernel_timespec *_ts) return ret; } -#endif /* CONFIG_GENERIC_TIME_VSYSCALL */ - static __always_inline u64 __arch_get_hw_counter(s32 clock_mode, const struct vdso_time_data *vd) { From bf8f968a63159661acc20254ccdf0dc3c9f1bac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 19 May 2026 08:26:16 +0200 Subject: [PATCH 093/167] vdso/vsyscall: Gate update_vsyscall() behind CONFIG_GENERIC_GETTIMEOFDAY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the compilation of kernel/time/vsyscall.c, which contains the real definition of update_vsyscall() and the other vDSO definitions in timekeeper_internal.h use CONFIG_GENERIC_GETTIMEOFDAY and not CONFIG_GENERIC_TIME_VSYSCALL. Align the code to use a single Kconfig symbol. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260519-vdso-generic_time_vsyscal-v1-2-5c2a5905d5f5@linutronix.de --- include/linux/timekeeper_internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/timekeeper_internal.h b/include/linux/timekeeper_internal.h index e36d11e33e0c..4486dfd5d0de 100644 --- a/include/linux/timekeeper_internal.h +++ b/include/linux/timekeeper_internal.h @@ -190,7 +190,7 @@ struct timekeeper { s32 tai_offset; }; -#ifdef CONFIG_GENERIC_TIME_VSYSCALL +#ifdef CONFIG_GENERIC_GETTIMEOFDAY extern void update_vsyscall(struct timekeeper *tk); extern void update_vsyscall_tz(void); From 96942092d5e67c71af246fa3bc1422cdf80a5dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 19 May 2026 08:26:17 +0200 Subject: [PATCH 094/167] vdso/treewide: Drop GENERIC_TIME_VSYSCALL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This Kconfig symbol is not used anymore, remove it. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260519-vdso-generic_time_vsyscal-v1-3-5c2a5905d5f5@linutronix.de --- arch/arm/mm/Kconfig | 1 - arch/arm64/Kconfig | 1 - arch/loongarch/Kconfig | 1 - arch/mips/Kconfig | 1 - arch/powerpc/Kconfig | 1 - arch/riscv/Kconfig | 1 - arch/s390/Kconfig | 1 - arch/sparc/Kconfig | 1 - arch/x86/Kconfig | 1 - kernel/time/Kconfig | 4 ---- 10 files changed, 13 deletions(-) diff --git a/arch/arm/mm/Kconfig b/arch/arm/mm/Kconfig index 7b27ee9482b3..871bd58d2ccc 100644 --- a/arch/arm/mm/Kconfig +++ b/arch/arm/mm/Kconfig @@ -925,7 +925,6 @@ config VDSO depends on AEABI && MMU && CPU_V7 default y if ARM_ARCH_TIMER select HAVE_GENERIC_VDSO - select GENERIC_TIME_VSYSCALL select GENERIC_GETTIMEOFDAY help Place in the process address space an ELF shared object diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index fe60738e5943..7e331b4f480a 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -140,7 +140,6 @@ config ARM64 select GENERIC_PCI_IOMAP select GENERIC_SCHED_CLOCK select GENERIC_SMP_IDLE_THREAD - select GENERIC_TIME_VSYSCALL select GENERIC_GETTIMEOFDAY select HARDIRQS_SW_RESEND select HAS_IOPORT diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 606597da46b8..3f69c5d7e48e 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -110,7 +110,6 @@ config LOONGARCH select GENERIC_PCI_IOMAP select GENERIC_SCHED_CLOCK select GENERIC_SMP_IDLE_THREAD - select GENERIC_TIME_VSYSCALL if GENERIC_GETTIMEOFDAY select GPIOLIB select HAS_IOPORT select HAVE_ALIGNED_STRUCT_PAGE if 64BIT diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 4364f3dba688..3249880650da 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -51,7 +51,6 @@ config MIPS select GENERIC_SCHED_CLOCK if !CAVIUM_OCTEON_SOC select GENERIC_SMP_IDLE_THREAD select GENERIC_IDLE_POLL_SETUP - select GENERIC_TIME_VSYSCALL select GUP_GET_PXX_LOW_HIGH if CPU_MIPS32 && PHYS_ADDR_T_64BIT select HAS_IOPORT if !NO_IOPORT_MAP || ISA select HAVE_ARCH_COMPILER_H diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index e93df95b79e7..c99fd8335ddc 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -213,7 +213,6 @@ config PPC select GENERIC_IRQ_SHOW_LEVEL select GENERIC_PCI_IOMAP if PCI select GENERIC_SMP_IDLE_THREAD - select GENERIC_TIME_VSYSCALL select HAS_IOPORT if PCI select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_HUGE_VMALLOC if HAVE_ARCH_HUGE_VMAP diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index c5754942cf85..195ebc21049a 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -123,7 +123,6 @@ config RISCV select GENERIC_PCI_IOMAP select GENERIC_SCHED_CLOCK select GENERIC_SMP_IDLE_THREAD - select GENERIC_TIME_VSYSCALL if GENERIC_GETTIMEOFDAY select HARDIRQS_SW_RESEND select HAS_IOPORT if MMU select HAVE_ALIGNED_STRUCT_PAGE diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index ecbcbb781e40..2a5e78465fb8 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -177,7 +177,6 @@ config S390 select GENERIC_ENTRY select GENERIC_GETTIMEOFDAY select GENERIC_SMP_IDLE_THREAD - select GENERIC_TIME_VSYSCALL select GENERIC_IOREMAP if PCI select HAVE_ALIGNED_STRUCT_PAGE select HAVE_ARCH_AUDITSYSCALL diff --git a/arch/sparc/Kconfig b/arch/sparc/Kconfig index a6b787efc2c4..f83d5065c3cf 100644 --- a/arch/sparc/Kconfig +++ b/arch/sparc/Kconfig @@ -103,7 +103,6 @@ config SPARC64 select HAVE_REGS_AND_STACK_ACCESS_API select ARCH_USE_QUEUED_RWLOCKS select ARCH_USE_QUEUED_SPINLOCKS - select GENERIC_TIME_VSYSCALL select ARCH_HAS_PTE_SPECIAL select PCI_DOMAINS if PCI select ARCH_HAS_GIGANTIC_PAGE diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index f3f7cb01d69d..43d8105068f4 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -180,7 +180,6 @@ config X86 select GENERIC_IRQ_SHOW select GENERIC_PENDING_IRQ if SMP select GENERIC_SMP_IDLE_THREAD - select GENERIC_TIME_VSYSCALL select GENERIC_GETTIMEOFDAY select GENERIC_VDSO_OVERFLOW_PROTECT select GUP_GET_PXX_LOW_HIGH if X86_PAE diff --git a/kernel/time/Kconfig b/kernel/time/Kconfig index 02aac7c5aa76..d098ac39bde4 100644 --- a/kernel/time/Kconfig +++ b/kernel/time/Kconfig @@ -16,10 +16,6 @@ config ARCH_CLOCKSOURCE_INIT config ARCH_WANTS_CLOCKSOURCE_READ_INLINE bool -# Timekeeping vsyscall support -config GENERIC_TIME_VSYSCALL - bool - # The generic clock events infrastructure config GENERIC_CLOCKEVENTS def_bool !LEGACY_TIMER_TICK From cb19b8a9add2ed3774c1276e0a3906e52e8cf39a Mon Sep 17 00:00:00 2001 From: "WEI-HONG, YE" <1234567weewee457@gmail.com> Date: Mon, 25 May 2026 13:04:50 +0000 Subject: [PATCH 095/167] locking/qspinlock: Clarify pending field layout For CONFIG_NR_CPUS < 16K, _Q_PENDING_BITS is 8 and the pending field occupies bits 8-15 of the lock word. The current comment documents bit 8 as pending and bits 9-15 as unused, which describes the pending flag value rather than the field layout. Describe bits 8-15 as the pending byte so the layout description is consistent with the lock byte. Signed-off-by: WEI-HONG, YE <1234567weewee457@gmail.com> Signed-off-by: Peter Zijlstra (Intel) Acked-by: Waiman Long Link: https://patch.msgid.link/20260525130450.723937-1-1234567weewee457@gmail.com --- include/asm-generic/qspinlock_types.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/asm-generic/qspinlock_types.h b/include/asm-generic/qspinlock_types.h index 2fd1fb89ec36..7d17b9bb069c 100644 --- a/include/asm-generic/qspinlock_types.h +++ b/include/asm-generic/qspinlock_types.h @@ -53,8 +53,7 @@ typedef struct qspinlock { * * When NR_CPUS < 16K * 0- 7: locked byte - * 8: pending - * 9-15: not used + * 8-15: pending byte * 16-17: tail index * 18-31: tail cpu (+1) * From d8c897b20bf4d4cbb1e935a8ceb666bcc0f82580 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 23 May 2026 20:51:22 +0200 Subject: [PATCH 096/167] lockdep/selftests: Restore migrate_disable() state on PREEMPT_RT The lockdep selftests deliberately run unbalanced locking patterns. dotest() restores the task state they leave behind before running the next testcase. On PREEMPT_RT, spin_lock() uses migrate_disable() instead of disabling preemption. dotest() cleans up the resulting migration-disabled state, but that cleanup is still guarded by CONFIG_SMP. That used to match the scheduler data model, where migration_disabled was also CONFIG_SMP-only. The commit referenced below made SMP scheduler state unconditional, so CONFIG_SMP=n PREEMPT_RT kernels with CONFIG_DEBUG_LOCKING_API_SELFTESTS=y report success from the selftests and then trip over stale current->migration_disabled state: releasing a pinned lock bad: scheduling from the idle thread! Kernel panic - not syncing: Fatal exception Save and restore current->migration_disabled for every PREEMPT_RT build. Fixes: cac5cefbade9 ("sched/smp: Make SMP unconditional") Assisted-by: Codex:gpt-5 Signed-off-by: Karl Mehltretter Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260523185123.17482-2-kmehltretter@gmail.com --- lib/locking-selftest.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/locking-selftest.c b/lib/locking-selftest.c index d939403331b5..cb806b628352 100644 --- a/lib/locking-selftest.c +++ b/lib/locking-selftest.c @@ -1431,9 +1431,7 @@ static void dotest(void (*testcase_fn)(void), int expected, int lockclass_mask) { int saved_preempt_count = preempt_count(); #ifdef CONFIG_PREEMPT_RT -#ifdef CONFIG_SMP int saved_mgd_count = current->migration_disabled; -#endif int saved_rcu_count = current->rcu_read_lock_nesting; #endif @@ -1471,10 +1469,8 @@ static void dotest(void (*testcase_fn)(void), int expected, int lockclass_mask) preempt_count_set(saved_preempt_count); #ifdef CONFIG_PREEMPT_RT -#ifdef CONFIG_SMP while (current->migration_disabled > saved_mgd_count) migrate_enable(); -#endif while (current->rcu_read_lock_nesting > saved_rcu_count) rcu_read_unlock(); From 06961d60a0e410bf8df69ccff7eb1bd824912b8f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 23 May 2026 20:51:23 +0200 Subject: [PATCH 097/167] lockdep/selftests: Restore sched_rt_mutex state on PREEMPT_RT The WW-mutex selftests deliberately exercise failing lock paths. On PREEMPT_RT, some of those paths enter the RT-mutex scheduler helpers. The change referenced by the Fixes tag made those helpers track RT-mutex scheduling state in current->sched_rt_mutex. The bit is normally cleared by the matching post-schedule helper, but some WW-mutex selftests disable the runtime debug_locks flag before that happens. With debug_locks cleared, lockdep_assert() does not evaluate the expression that clears the bit, leaving stale state for the next testcase. With CONFIG_PREEMPT_RT=y and CONFIG_DEBUG_LOCKING_API_SELFTESTS=y, that stale state produces warnings such as: WARNING: kernel/sched/core.c:7557 at rt_mutex_pre_schedule+0x26/0x2d RIP: 0010:rt_mutex_pre_schedule+0x26/0x2d Save and restore current->sched_rt_mutex around each testcase, matching the existing PREEMPT_RT cleanup for task-local migration and RCU state. Fixes: d14f9e930b90 ("locking/rtmutex: Use rt_mutex specific scheduler helpers") Assisted-by: Codex:gpt-5 Signed-off-by: Karl Mehltretter Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Sebastian Andrzej Siewior Link: https://patch.msgid.link/20260523185123.17482-3-kmehltretter@gmail.com --- lib/locking-selftest.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/locking-selftest.c b/lib/locking-selftest.c index cb806b628352..bfafe1204c7b 100644 --- a/lib/locking-selftest.c +++ b/lib/locking-selftest.c @@ -1433,6 +1433,7 @@ static void dotest(void (*testcase_fn)(void), int expected, int lockclass_mask) #ifdef CONFIG_PREEMPT_RT int saved_mgd_count = current->migration_disabled; int saved_rcu_count = current->rcu_read_lock_nesting; + int saved_sched_rt_mutex = current->sched_rt_mutex; #endif WARN_ON(irqs_disabled()); @@ -1469,6 +1470,8 @@ static void dotest(void (*testcase_fn)(void), int expected, int lockclass_mask) preempt_count_set(saved_preempt_count); #ifdef CONFIG_PREEMPT_RT + current->sched_rt_mutex = saved_sched_rt_mutex; + while (current->migration_disabled > saved_mgd_count) migrate_enable(); From 813e5598e5b551a1fb82b516428ce2f135921122 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Tue, 2 Jun 2026 07:12:50 +0000 Subject: [PATCH 098/167] nvdimm: Convert nvdimm_bus guard to class The nvdimm_bus guard accepts NULL and skips locking when NULL is passed. Convert from DEFINE_GUARD() to DEFINE_CLASS() + DEFINE_CLASS_IS_GUARD(). This is a preparatory change for making DEFINE_GUARD() constructors __nonnull_args(). nvdimm_bus legitimately passes NULL, so it must be adjusted to avoid a compile error. No functional change. Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dave Jiang Link: https://patch.msgid.link/8c0417904d280896ecf2e9923ffa9f20076f59b8.1780064327.git.d@ilvokhin.com --- drivers/nvdimm/nd.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/nvdimm/nd.h b/drivers/nvdimm/nd.h index b199eea3260e..18b64559664b 100644 --- a/drivers/nvdimm/nd.h +++ b/drivers/nvdimm/nd.h @@ -632,8 +632,11 @@ u64 nd_region_interleave_set_cookie(struct nd_region *nd_region, u64 nd_region_interleave_set_altcookie(struct nd_region *nd_region); void nvdimm_bus_lock(struct device *dev); void nvdimm_bus_unlock(struct device *dev); -DEFINE_GUARD(nvdimm_bus, struct device *, - if (_T) nvdimm_bus_lock(_T), if (_T) nvdimm_bus_unlock(_T)); +DEFINE_CLASS(nvdimm_bus, struct device *, + if (_T) nvdimm_bus_unlock(_T), + ({ if (_T) nvdimm_bus_lock(_T); _T; }), + struct device *_T); +DEFINE_CLASS_IS_GUARD(nvdimm_bus); bool is_nvdimm_bus_locked(struct device *dev); void nvdimm_check_and_set_ro(struct gendisk *disk); From 08d4a7837f008ea6031c1292aa839ad881d7b3f1 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Tue, 2 Jun 2026 07:12:51 +0000 Subject: [PATCH 099/167] genirq: Move NULL check into irqdesc_lock guard unlock expression irqdesc_lock uses __DEFINE_UNLOCK_GUARD() directly with a custom constructor that can set .lock to NULL. In preparation for removing the NULL check from __DEFINE_UNLOCK_GUARD(), move the NULL check into the irqdesc_lock unlock expression, making the NULL handling explicit at the call site. No functional change. Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Acked-by: Thomas Gleixner Link: https://patch.msgid.link/ab457810653e4356e29b2d74ba616478bd9328ad.1780064327.git.d@ilvokhin.com --- kernel/irq/internals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/irq/internals.h b/kernel/irq/internals.h index 9412e57056f5..347cb333b9fe 100644 --- a/kernel/irq/internals.h +++ b/kernel/irq/internals.h @@ -171,7 +171,7 @@ void __irq_put_desc_unlock(struct irq_desc *desc, unsigned long flags, bool bus) __DEFINE_CLASS_IS_CONDITIONAL(irqdesc_lock, true); __DEFINE_UNLOCK_GUARD(irqdesc_lock, struct irq_desc, - __irq_put_desc_unlock(_T->lock, _T->flags, _T->bus), + if (_T->lock) __irq_put_desc_unlock(_T->lock, _T->flags, _T->bus), unsigned long flags; bool bus); static inline class_irqdesc_lock_t class_irqdesc_lock_constructor(unsigned int irq, bool bus, From 22302af28d3f7c3ca38536978c80db51d2a9e283 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Tue, 2 Jun 2026 07:12:52 +0000 Subject: [PATCH 100/167] cleanup: Annotate guard constructors with nonnull Add __nonnull_args() to unconditional guard constructors so the compiler warns when NULL is statically known to be passed: - DEFINE_GUARD(): re-declare the constructor with __nonnull_args(). - __DEFINE_LOCK_GUARD_1(): annotate the constructor directly. DEFINE_LOCK_GUARD_0() needs no annotation: its constructor takes no pointer arguments (.lock is hardcoded to (void *)1). Define the __nonnull_args() macro in compiler_attributes.h, following the existing convention for attribute wrappers. Deliberately not named '__nonnull', to avoid clashing with glibc's __nonnull() when kernel and userspace headers are combined (User Mode Linux for example). Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/85fee12eec20abfcf711443518e8f0caec982a86.1780064327.git.d@ilvokhin.com --- include/linux/cleanup.h | 4 +++- include/linux/compiler_attributes.h | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h index ea95ca4bc11c..4e60d519713c 100644 --- a/include/linux/cleanup.h +++ b/include/linux/cleanup.h @@ -397,6 +397,7 @@ static __maybe_unused const bool class_##_name##_is_conditional = _is_cond __DEFINE_GUARD_LOCK_PTR(_name, _T) #define DEFINE_GUARD(_name, _type, _lock, _unlock) \ + static __always_inline __nonnull_args() _type class_##_name##_constructor(_type _T); \ DEFINE_CLASS(_name, _type, if (_T) { _unlock; }, ({ _lock; _T; }), _type _T); \ DEFINE_CLASS_IS_GUARD(_name) @@ -497,7 +498,8 @@ static __always_inline void class_##_name##_destructor(class_##_name##_t *_T) \ __DEFINE_GUARD_LOCK_PTR(_name, &_T->lock) #define __DEFINE_LOCK_GUARD_1(_name, _type, ...) \ -static __always_inline class_##_name##_t class_##_name##_constructor(_type *l) \ +static __always_inline __nonnull_args() \ +class_##_name##_t class_##_name##_constructor(_type *l) \ __no_context_analysis \ { \ class_##_name##_t _t = { .lock = l }, *_T = &_t; \ diff --git a/include/linux/compiler_attributes.h b/include/linux/compiler_attributes.h index c16d4199bf92..cffe09387ea6 100644 --- a/include/linux/compiler_attributes.h +++ b/include/linux/compiler_attributes.h @@ -230,6 +230,15 @@ */ #define noinline __attribute__((__noinline__)) +/* + * Note: deliberately not named '__nonnull', to avoid clashing with glibc's + * __nonnull() when kernel and userspace headers are combined. + * + * gcc: https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-nonnull + * clang: https://clang.llvm.org/docs/AttributeReference.html#nonnull + */ +#define __nonnull_args(x...) __attribute__((__nonnull__(x))) + /* * Optional: only supported since gcc >= 8 * Optional: not supported by clang From a13ab9dd6eb2a89f14b466c3884730ea7969253f Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Tue, 2 Jun 2026 07:12:53 +0000 Subject: [PATCH 101/167] cleanup: Remove NULL check from unconditional guards The unconditional guard destructors check whether the lock pointer is NULL before unlocking. This check is dead code because unconditional guards guarantee a non-NULL lock pointer at destructor time. DEFINE_GUARD() runs the lock operation unconditionally in the constructor. If the pointer were NULL, the lock operation (e.g. mutex_lock(NULL)) would crash before the constructor returns. The destructor never runs with a NULL pointer. All DEFINE_GUARD() users dereference the pointer in their lock. Verified by auditing every instance found by: git grep -n -A 1 'DEFINE_GUARD('. The only exception is xe_pm_runtime_release_only, whose constructor is a noop, but it has no callers. __DEFINE_UNLOCK_GUARD() has only a few usages outside of include/linux/cleanup.h: tty_port_tty (NULL-checks in its tty_kref_put() call), irqdesc_lock (fixed earlier) and two guards in kernel/sched/sched.h (dereference the pointer unconditionally in their lock constructors). DEFINE_LOCK_GUARD_1() sets .lock from its argument and runs the lock operation in the constructor. Same reasoning applies. All DEFINE_LOCK_GUARD_1() users dereference the pointer in their lock. Also, verified by auditing every match of: git grep -n 'DEFINE_LOCK_GUARD_1('. DEFINE_LOCK_GUARD_0() hardcodes .lock = (void *)1 in the constructor, so it is never NULL by construction. Conditional (_try) variants: DEFINE_GUARD_COND() and DEFINE_LOCK_GUARD_1_COND() use EXTEND_CLASS_COND(), whose wrapper destructor returns early when the lock was not acquired, before reaching the base destructor since commit 2deccd5c862a ("cleanup: Optimize guards"): if (_cond) return; class_##_name##_destructor(_T); As compiled by GCC-11 with defconfig on top of the locking/core: Total: Before=23889980, After=23834334, chg -0.23% Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/0503a089389b2270c478a873e095cf0a4ff26d24.1780064327.git.d@ilvokhin.com --- include/linux/cleanup.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h index 4e60d519713c..65416938e318 100644 --- a/include/linux/cleanup.h +++ b/include/linux/cleanup.h @@ -398,7 +398,7 @@ static __maybe_unused const bool class_##_name##_is_conditional = _is_cond #define DEFINE_GUARD(_name, _type, _lock, _unlock) \ static __always_inline __nonnull_args() _type class_##_name##_constructor(_type _T); \ - DEFINE_CLASS(_name, _type, if (_T) { _unlock; }, ({ _lock; _T; }), _type _T); \ + DEFINE_CLASS(_name, _type, _unlock, ({ _lock; _T; }), _type _T); \ DEFINE_CLASS_IS_GUARD(_name) #define DEFINE_GUARD_COND_4(_name, _ext, _lock, _cond) \ @@ -492,7 +492,7 @@ typedef struct { \ static __always_inline void class_##_name##_destructor(class_##_name##_t *_T) \ __no_context_analysis \ { \ - if (_T->lock) { _unlock; } \ + _unlock; \ } \ \ __DEFINE_GUARD_LOCK_PTR(_name, &_T->lock) From c06cd66387da92e6cdac44e16c7b5ef9219c53ac Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:21 +0200 Subject: [PATCH 102/167] percpu: Sanitize __percpu_qual include hell Slapping __percpu_qual into the next available header is sloppy at best. It's required by __percpu which is defined in compiler_types.h and that is meant to be included without requiring a boatload of other headers so that a struct or function declaration can contain a __percpu qualifier w/o further prerequisites. This implicit dependency on linux/percpu.h makes that impossible and causes a major problem when trying to separate headers. Create asm/percpu_types.h and move it there. Include that from compiler_types.h and the whole recursion problem goes away. Fix up UM so it uses the generic header and includes it in the UM_HOST build, which pulls in compiler_types.h. The USER_CFLAGS fix was suggested by Richard. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260602090535.254874125@kernel.org --- arch/um/Makefile | 3 ++- arch/um/include/asm/Kbuild | 1 + arch/x86/include/asm/percpu.h | 5 ----- arch/x86/include/asm/percpu_types.h | 17 +++++++++++++++++ include/asm-generic/Kbuild | 1 + include/asm-generic/percpu_types.h | 19 +++++++++++++++++++ include/linux/compiler_types.h | 3 +++ include/linux/percpu.h | 11 ++++++----- 8 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 arch/x86/include/asm/percpu_types.h create mode 100644 include/asm-generic/percpu_types.h diff --git a/arch/um/Makefile b/arch/um/Makefile index 721b652ffb65..937639edc295 100644 --- a/arch/um/Makefile +++ b/arch/um/Makefile @@ -71,7 +71,8 @@ USER_CFLAGS = $(patsubst $(KERNEL_DEFINES),,$(patsubst -I%,,$(KBUILD_CFLAGS))) \ -D_FILE_OFFSET_BITS=64 -idirafter $(srctree)/include \ -idirafter $(objtree)/include -D__KERNEL__ -D__UM_HOST__ \ -include $(srctree)/include/linux/compiler-version.h \ - -include $(srctree)/include/linux/kconfig.h + -include $(srctree)/include/linux/kconfig.h \ + -idirafter $(ARCH_DIR)/include/generated #This will adjust *FLAGS accordingly to the platform. include $(srctree)/$(ARCH_DIR)/Makefile-os-Linux diff --git a/arch/um/include/asm/Kbuild b/arch/um/include/asm/Kbuild index 1b9b82bbe322..e91ba12b7ffc 100644 --- a/arch/um/include/asm/Kbuild +++ b/arch/um/include/asm/Kbuild @@ -16,6 +16,7 @@ generic-y += module.h generic-y += module.lds.h generic-y += parport.h generic-y += percpu.h +generic-y += percpu_types.h generic-y += preempt.h generic-y += runtime-const.h generic-y += softirq_stack.h diff --git a/arch/x86/include/asm/percpu.h b/arch/x86/include/asm/percpu.h index 409981468cba..cef9a4ca9841 100644 --- a/arch/x86/include/asm/percpu.h +++ b/arch/x86/include/asm/percpu.h @@ -40,12 +40,10 @@ #endif #define __percpu_prefix -#define __percpu_seg_override CONCATENATE(__seg_, __percpu_seg) #else /* !CONFIG_CC_HAS_NAMED_AS: */ #define __percpu_prefix __force_percpu_prefix -#define __percpu_seg_override #endif /* CONFIG_CC_HAS_NAMED_AS */ @@ -82,7 +80,6 @@ #define __force_percpu_prefix #define __percpu_prefix -#define __percpu_seg_override #define PER_CPU_VAR(var) (var)__percpu_rel @@ -92,8 +89,6 @@ # define __my_cpu_type(var) typeof(var) # define __my_cpu_ptr(ptr) (ptr) # define __my_cpu_var(var) (var) - -# define __percpu_qual __percpu_seg_override #else # define __my_cpu_type(var) typeof(var) __percpu_seg_override # define __my_cpu_ptr(ptr) (__my_cpu_type(*(ptr))*)(__force uintptr_t)(ptr) diff --git a/arch/x86/include/asm/percpu_types.h b/arch/x86/include/asm/percpu_types.h new file mode 100644 index 000000000000..0aa3e47a3643 --- /dev/null +++ b/arch/x86/include/asm/percpu_types.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_PERCPU_TYPES_H +#define _ASM_X86_PERCPU_TYPES_H + +#if defined(CONFIG_SMP) && defined(CONFIG_CC_HAS_NAMED_AS) +#define __percpu_seg_override CONCATENATE(__seg_, __percpu_seg) +#else /* !CONFIG_CC_HAS_NAMED_AS: */ +#define __percpu_seg_override +#endif + +#if defined(CONFIG_USE_X86_SEG_SUPPORT) && defined(USE_TYPEOF_UNQUAL) +#define __percpu_qual __percpu_seg_override +#endif + +#include + +#endif diff --git a/include/asm-generic/Kbuild b/include/asm-generic/Kbuild index 2c53a1e0b760..15df9dcb42a5 100644 --- a/include/asm-generic/Kbuild +++ b/include/asm-generic/Kbuild @@ -44,6 +44,7 @@ mandatory-y += module.lds.h mandatory-y += msi.h mandatory-y += pci.h mandatory-y += percpu.h +mandatory-y += percpu_types.h mandatory-y += pgalloc.h mandatory-y += preempt.h mandatory-y += rqspinlock.h diff --git a/include/asm-generic/percpu_types.h b/include/asm-generic/percpu_types.h new file mode 100644 index 000000000000..a095cea7fa20 --- /dev/null +++ b/include/asm-generic/percpu_types.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_GENERIC_PERCPU_TYPES_H_ +#define _ASM_GENERIC_PERCPU_TYPES_H_ + +#ifndef __ASSEMBLER__ +/* + * __percpu_qual is the qualifier for the percpu named address space. + * + * Most architectures use generic named address space for percpu variables but + * some architectures define percpu variables in different named address space. + * E.g. on x86, percpu variable may be declared as being relative to the %fs or + * %gs segments using __seg_fs or __seg_gs named address space qualifier. + */ +#ifndef __percpu_qual +# define __percpu_qual +#endif + +#endif /* __ASSEMBLER__ */ +#endif /* _ASM_GENERIC_PERCPU_TYPES_H_ */ diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h index e8fd77593b68..7ad37adda1dd 100644 --- a/include/linux/compiler_types.h +++ b/include/linux/compiler_types.h @@ -634,6 +634,9 @@ struct ftrace_likely_data { #else #define __unqual_scalar_typeof(x) __typeof_unqual__(x) #endif + +#include + #endif /* !__ASSEMBLY__ */ /* diff --git a/include/linux/percpu.h b/include/linux/percpu.h index 85bf8dd9f087..2f5a889aa50d 100644 --- a/include/linux/percpu.h +++ b/include/linux/percpu.h @@ -3,13 +3,14 @@ #define __LINUX_PERCPU_H #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include +#include #include From c1ffc9c6e4f8a13dd68e97920c9a24d095c6e41a Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:25 +0200 Subject: [PATCH 103/167] futex: Move futex task related data into a struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Having all these members in task_struct along with the required #ifdeffery is annoying, does not allow efficient initializing of the data with memset() and makes extending it tedious. Move it into a data structure and fix up all usage sites. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Mathieu Desnoyers Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.308220888@kernel.org --- Documentation/locking/robust-futexes.rst | 8 ++-- include/linux/futex.h | 12 ++--- include/linux/futex_types.h | 36 ++++++++++++++ include/linux/sched.h | 16 ++----- kernel/exit.c | 4 +- kernel/futex/core.c | 61 ++++++++++++------------ kernel/futex/pi.c | 26 +++++----- kernel/futex/syscalls.c | 23 ++++----- 8 files changed, 102 insertions(+), 84 deletions(-) create mode 100644 include/linux/futex_types.h diff --git a/Documentation/locking/robust-futexes.rst b/Documentation/locking/robust-futexes.rst index 6361fb01c9c1..1423f53ea2f4 100644 --- a/Documentation/locking/robust-futexes.rst +++ b/Documentation/locking/robust-futexes.rst @@ -94,7 +94,7 @@ time, the kernel checks this user-space list: are there any robust futex locks to be cleaned up? In the common case, at do_exit() time, there is no list registered, so -the cost of robust futexes is just a simple current->robust_list != NULL +the cost of robust futexes is just a current->futex.robust_list != NULL comparison. If the thread has registered a list, then normally the list is empty. If the thread/process crashed or terminated in some incorrect way then the list might be non-empty: in this case the kernel carefully @@ -178,9 +178,9 @@ one to query the registered list pointer:: size_t __user *len_ptr); List registration is very fast: the pointer is simply stored in -current->robust_list. [Note that in the future, if robust futexes become -widespread, we could extend sys_clone() to register a robust-list head -for new threads, without the need of another syscall.] +current->futex.robust_list. [Note that in the future, if robust futexes +become widespread, we could extend sys_clone() to register a robust-list +head for new threads, without the need of another syscall.] So there is virtually zero overhead for tasks not using robust futexes, and even for robust futex users, there is only one extra syscall per diff --git a/include/linux/futex.h b/include/linux/futex.h index 9e9750f04980..563e8dd67179 100644 --- a/include/linux/futex.h +++ b/include/linux/futex.h @@ -64,14 +64,10 @@ enum { static inline void futex_init_task(struct task_struct *tsk) { - tsk->robust_list = NULL; -#ifdef CONFIG_COMPAT - tsk->compat_robust_list = NULL; -#endif - INIT_LIST_HEAD(&tsk->pi_state_list); - tsk->pi_state_cache = NULL; - tsk->futex_state = FUTEX_STATE_OK; - mutex_init(&tsk->futex_exit_mutex); + memset(&tsk->futex, 0, sizeof(tsk->futex)); + INIT_LIST_HEAD(&tsk->futex.pi_state_list); + tsk->futex.state = FUTEX_STATE_OK; + mutex_init(&tsk->futex.exit_mutex); } void futex_exit_recursive(struct task_struct *tsk); diff --git a/include/linux/futex_types.h b/include/linux/futex_types.h new file mode 100644 index 000000000000..9c6c0dc4148d --- /dev/null +++ b/include/linux/futex_types.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_FUTEX_TYPES_H +#define _LINUX_FUTEX_TYPES_H + +#ifdef CONFIG_FUTEX +#include +#include + +struct compat_robust_list_head; +struct futex_pi_state; +struct robust_list_head; + +/** + * struct futex_sched_data - Futex related per task data + * @robust_list: User space registered robust list pointer + * @compat_robust_list: User space registered robust list pointer for compat tasks + * @pi_state_list: List head for Priority Inheritance (PI) state management + * @pi_state_cache: Pointer to cache one PI state object per task + * @exit_mutex: Mutex for serializing exit + * @state: Futex handling state to handle exit races correctly + */ +struct futex_sched_data { + struct robust_list_head __user *robust_list; +#ifdef CONFIG_COMPAT + struct compat_robust_list_head __user *compat_robust_list; +#endif + struct list_head pi_state_list; + struct futex_pi_state *pi_state_cache; + struct mutex exit_mutex; + unsigned int state; +}; +#else +struct futex_sched_data { }; +#endif /* !CONFIG_FUTEX */ + +#endif /* _LINUX_FUTEX_TYPES_H */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 368c7b4d7cb5..c88fc10e9c38 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -64,7 +65,6 @@ struct bpf_net_context; struct capture_control; struct cfs_rq; struct fs_struct; -struct futex_pi_state; struct io_context; struct io_uring_task; struct mempolicy; @@ -76,7 +76,6 @@ struct pid_namespace; struct pipe_inode_info; struct rcu_node; struct reclaim_state; -struct robust_list_head; struct root_domain; struct rq; struct sched_attr; @@ -1331,16 +1330,9 @@ struct task_struct { u32 closid; u32 rmid; #endif -#ifdef CONFIG_FUTEX - struct robust_list_head __user *robust_list; -#ifdef CONFIG_COMPAT - struct compat_robust_list_head __user *compat_robust_list; -#endif - struct list_head pi_state_list; - struct futex_pi_state *pi_state_cache; - struct mutex futex_exit_mutex; - unsigned int futex_state; -#endif + + struct futex_sched_data futex; + #ifdef CONFIG_PERF_EVENTS u8 perf_recursion[PERF_NR_CONTEXTS]; struct perf_event_context *perf_event_ctxp; diff --git a/kernel/exit.c b/kernel/exit.c index 25e9cb6de7e7..1b4e55b60bc2 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -988,8 +988,8 @@ void __noreturn do_exit(long code) proc_exit_connector(tsk); mpol_put_task_policy(tsk); #ifdef CONFIG_FUTEX - if (unlikely(current->pi_state_cache)) - kfree(current->pi_state_cache); + if (unlikely(current->futex.pi_state_cache)) + kfree(current->futex.pi_state_cache); #endif /* * Make sure we are holding no locks: diff --git a/kernel/futex/core.c b/kernel/futex/core.c index ff2a4fb2993f..e7d33d2771ec 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -32,18 +32,19 @@ * "But they come in a choice of three flavours!" */ #include -#include -#include #include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include "futex.h" #include "../locking/rtmutex_common.h" @@ -829,7 +830,7 @@ void wait_for_owner_exiting(int ret, struct task_struct *exiting) if (WARN_ON_ONCE(ret == -EBUSY && !exiting)) return; - mutex_lock(&exiting->futex_exit_mutex); + mutex_lock(&exiting->futex.exit_mutex); /* * No point in doing state checking here. If the waiter got here * while the task was in exec()->exec_futex_release() then it can @@ -838,7 +839,7 @@ void wait_for_owner_exiting(int ret, struct task_struct *exiting) * already. Highly unlikely and not a problem. Just one more round * through the futex maze. */ - mutex_unlock(&exiting->futex_exit_mutex); + mutex_unlock(&exiting->futex.exit_mutex); put_task_struct(exiting); } @@ -1047,7 +1048,7 @@ retry: * * In both cases the following conditions are met: * - * 1) task->robust_list->list_op_pending != NULL + * 1) task->futex.robust_list->list_op_pending != NULL * @pending_op == true * 2) The owner part of user space futex value == 0 * 3) Regular futex: @pi == false @@ -1152,7 +1153,7 @@ static inline int fetch_robust_entry(struct robust_list __user **entry, */ static void exit_robust_list(struct task_struct *curr) { - struct robust_list_head __user *head = curr->robust_list; + struct robust_list_head __user *head = curr->futex.robust_list; struct robust_list __user *entry, *next_entry, *pending; unsigned int limit = ROBUST_LIST_LIMIT, pi, pip; unsigned int next_pi; @@ -1246,7 +1247,7 @@ compat_fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **ent */ static void compat_exit_robust_list(struct task_struct *curr) { - struct compat_robust_list_head __user *head = curr->compat_robust_list; + struct compat_robust_list_head __user *head = curr->futex.compat_robust_list; struct robust_list __user *entry, *next_entry, *pending; unsigned int limit = ROBUST_LIST_LIMIT, pi, pip; unsigned int next_pi; @@ -1322,7 +1323,7 @@ static void compat_exit_robust_list(struct task_struct *curr) */ static void exit_pi_state_list(struct task_struct *curr) { - struct list_head *next, *head = &curr->pi_state_list; + struct list_head *next, *head = &curr->futex.pi_state_list; struct futex_pi_state *pi_state; union futex_key key = FUTEX_KEY_INIT; @@ -1406,19 +1407,19 @@ static inline void exit_pi_state_list(struct task_struct *curr) { } static void futex_cleanup(struct task_struct *tsk) { - if (unlikely(tsk->robust_list)) { + if (unlikely(tsk->futex.robust_list)) { exit_robust_list(tsk); - tsk->robust_list = NULL; + tsk->futex.robust_list = NULL; } #ifdef CONFIG_COMPAT - if (unlikely(tsk->compat_robust_list)) { + if (unlikely(tsk->futex.compat_robust_list)) { compat_exit_robust_list(tsk); - tsk->compat_robust_list = NULL; + tsk->futex.compat_robust_list = NULL; } #endif - if (unlikely(!list_empty(&tsk->pi_state_list))) + if (unlikely(!list_empty(&tsk->futex.pi_state_list))) exit_pi_state_list(tsk); } @@ -1442,23 +1443,23 @@ static void futex_cleanup(struct task_struct *tsk) void futex_exit_recursive(struct task_struct *tsk) { /* If the state is FUTEX_STATE_EXITING then futex_exit_mutex is held */ - if (tsk->futex_state == FUTEX_STATE_EXITING) { - __assume_ctx_lock(&tsk->futex_exit_mutex); - mutex_unlock(&tsk->futex_exit_mutex); + if (tsk->futex.state == FUTEX_STATE_EXITING) { + __assume_ctx_lock(&tsk->futex.exit_mutex); + mutex_unlock(&tsk->futex.exit_mutex); } - tsk->futex_state = FUTEX_STATE_DEAD; + tsk->futex.state = FUTEX_STATE_DEAD; } static void futex_cleanup_begin(struct task_struct *tsk) - __acquires(&tsk->futex_exit_mutex) + __acquires(&tsk->futex.exit_mutex) { /* * Prevent various race issues against a concurrent incoming waiter * including live locks by forcing the waiter to block on - * tsk->futex_exit_mutex when it observes FUTEX_STATE_EXITING in + * tsk->futex.exit_mutex when it observes FUTEX_STATE_EXITING in * attach_to_pi_owner(). */ - mutex_lock(&tsk->futex_exit_mutex); + mutex_lock(&tsk->futex.exit_mutex); /* * Switch the state to FUTEX_STATE_EXITING under tsk->pi_lock. @@ -1472,23 +1473,23 @@ static void futex_cleanup_begin(struct task_struct *tsk) * be observed in exit_pi_state_list(). */ raw_spin_lock_irq(&tsk->pi_lock); - tsk->futex_state = FUTEX_STATE_EXITING; + tsk->futex.state = FUTEX_STATE_EXITING; raw_spin_unlock_irq(&tsk->pi_lock); } static void futex_cleanup_end(struct task_struct *tsk, int state) - __releases(&tsk->futex_exit_mutex) + __releases(&tsk->futex.exit_mutex) { /* * Lockless store. The only side effect is that an observer might * take another loop until it becomes visible. */ - tsk->futex_state = state; + tsk->futex.state = state; /* * Drop the exit protection. This unblocks waiters which observed * FUTEX_STATE_EXITING to reevaluate the state. */ - mutex_unlock(&tsk->futex_exit_mutex); + mutex_unlock(&tsk->futex.exit_mutex); } void futex_exec_release(struct task_struct *tsk) diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c index 643199fdbe62..e037a97fe277 100644 --- a/kernel/futex/pi.c +++ b/kernel/futex/pi.c @@ -14,7 +14,7 @@ int refill_pi_state_cache(void) { struct futex_pi_state *pi_state; - if (likely(current->pi_state_cache)) + if (likely(current->futex.pi_state_cache)) return 0; pi_state = kzalloc_obj(*pi_state); @@ -28,17 +28,17 @@ int refill_pi_state_cache(void) refcount_set(&pi_state->refcount, 1); pi_state->key = FUTEX_KEY_INIT; - current->pi_state_cache = pi_state; + current->futex.pi_state_cache = pi_state; return 0; } static struct futex_pi_state *alloc_pi_state(void) { - struct futex_pi_state *pi_state = current->pi_state_cache; + struct futex_pi_state *pi_state = current->futex.pi_state_cache; WARN_ON(!pi_state); - current->pi_state_cache = NULL; + current->futex.pi_state_cache = NULL; return pi_state; } @@ -60,7 +60,7 @@ static void pi_state_update_owner(struct futex_pi_state *pi_state, if (new_owner) { raw_spin_lock(&new_owner->pi_lock); WARN_ON(!list_empty(&pi_state->list)); - list_add(&pi_state->list, &new_owner->pi_state_list); + list_add(&pi_state->list, &new_owner->futex.pi_state_list); pi_state->owner = new_owner; raw_spin_unlock(&new_owner->pi_lock); } @@ -96,7 +96,7 @@ void put_pi_state(struct futex_pi_state *pi_state) raw_spin_unlock_irqrestore(&pi_state->pi_mutex.wait_lock, flags); } - if (current->pi_state_cache) { + if (current->futex.pi_state_cache) { kfree(pi_state); } else { /* @@ -106,7 +106,7 @@ void put_pi_state(struct futex_pi_state *pi_state) */ pi_state->owner = NULL; refcount_set(&pi_state->refcount, 1); - current->pi_state_cache = pi_state; + current->futex.pi_state_cache = pi_state; } } @@ -179,7 +179,7 @@ void put_pi_state(struct futex_pi_state *pi_state) * * p->pi_lock: * - * p->pi_state_list -> pi_state->list, relation + * p->futex.pi_state_list -> pi_state->list, relation * pi_mutex->owner -> pi_state->owner, relation * * pi_state->refcount: @@ -327,7 +327,7 @@ static int handle_exit_race(u32 __user *uaddr, u32 uval, * If the futex exit state is not yet FUTEX_STATE_DEAD, tell the * caller that the alleged owner is busy. */ - if (tsk && tsk->futex_state != FUTEX_STATE_DEAD) + if (tsk && tsk->futex.state != FUTEX_STATE_DEAD) return -EBUSY; /* @@ -346,8 +346,8 @@ static int handle_exit_race(u32 __user *uaddr, u32 uval, * *uaddr = 0xC0000000; tsk = get_task(PID); * } if (!tsk->flags & PF_EXITING) { * ... attach(); - * tsk->futex_state = } else { - * FUTEX_STATE_DEAD; if (tsk->futex_state != + * tsk->futex.state = } else { + * FUTEX_STATE_DEAD; if (tsk->futex.state != * FUTEX_STATE_DEAD) * return -EAGAIN; * return -ESRCH; <--- FAIL @@ -396,7 +396,7 @@ static void __attach_to_pi_owner(struct task_struct *p, union futex_key *key, pi_state->key = *key; WARN_ON(!list_empty(&pi_state->list)); - list_add(&pi_state->list, &p->pi_state_list); + list_add(&pi_state->list, &p->futex.pi_state_list); /* * Assignment without holding pi_state->pi_mutex.wait_lock is safe * because there is no concurrency as the object is not published yet. @@ -440,7 +440,7 @@ static int attach_to_pi_owner(u32 __user *uaddr, u32 uval, union futex_key *key, * in futex_exit_release(), we do this protected by p->pi_lock: */ raw_spin_lock_irq(&p->pi_lock); - if (unlikely(p->futex_state != FUTEX_STATE_OK)) { + if (unlikely(p->futex.state != FUTEX_STATE_OK)) { /* * The task is on the way out. When the futex state is * FUTEX_STATE_DEAD, we know that the task has finished diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c index 77ad9691f6a6..8944ff4930d7 100644 --- a/kernel/futex/syscalls.c +++ b/kernel/futex/syscalls.c @@ -25,17 +25,13 @@ * @head: pointer to the list-head * @len: length of the list-head, as userspace expects */ -SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head, - size_t, len) +SYSCALL_DEFINE2(set_robust_list, struct robust_list_head __user *, head, size_t, len) { - /* - * The kernel knows only one size for now: - */ + /* The kernel knows only one size for now. */ if (unlikely(len != sizeof(*head))) return -EINVAL; - current->robust_list = head; - + current->futex.robust_list = head; return 0; } @@ -43,9 +39,9 @@ static inline void __user *futex_task_robust_list(struct task_struct *p, bool co { #ifdef CONFIG_COMPAT if (compat) - return p->compat_robust_list; + return p->futex.compat_robust_list; #endif - return p->robust_list; + return p->futex.robust_list; } static void __user *futex_get_robust_list_common(int pid, bool compat) @@ -475,15 +471,13 @@ SYSCALL_DEFINE4(futex_requeue, } #ifdef CONFIG_COMPAT -COMPAT_SYSCALL_DEFINE2(set_robust_list, - struct compat_robust_list_head __user *, head, - compat_size_t, len) +COMPAT_SYSCALL_DEFINE2(set_robust_list, struct compat_robust_list_head __user *, head, + compat_size_t, len) { if (unlikely(len != sizeof(*head))) return -EINVAL; - current->compat_robust_list = head; - + current->futex.compat_robust_list = head; return 0; } @@ -523,4 +517,3 @@ SYSCALL_DEFINE6(futex_time32, u32 __user *, uaddr, int, op, u32, val, return do_futex(uaddr, op, val, tp, uaddr2, (unsigned long)utime, val3); } #endif /* CONFIG_COMPAT_32BIT_TIME */ - From d7b3f52c861f54ba2fff15696d3798277fb4c19f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:29 +0200 Subject: [PATCH 104/167] futex: Make futex_mm_init() void Nothing fails there. Mop up the leftovers of the early version of this, which did an allocation. While at it clean up the stubs and the #ifdef comments to make the header file readable. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260602090535.356789395@kernel.org --- include/linux/futex.h | 28 +++++++++++----------------- kernel/fork.c | 8 ++------ kernel/futex/core.c | 3 +-- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/include/linux/futex.h b/include/linux/futex.h index 563e8dd67179..9e6218c2be66 100644 --- a/include/linux/futex.h +++ b/include/linux/futex.h @@ -81,22 +81,20 @@ int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4) #ifdef CONFIG_FUTEX_PRIVATE_HASH int futex_hash_allocate_default(void); void futex_hash_free(struct mm_struct *mm); -int futex_mm_init(struct mm_struct *mm); - -#else /* !CONFIG_FUTEX_PRIVATE_HASH */ +void futex_mm_init(struct mm_struct *mm); +#else /* CONFIG_FUTEX_PRIVATE_HASH */ static inline int futex_hash_allocate_default(void) { return 0; } static inline int futex_hash_free(struct mm_struct *mm) { return 0; } -static inline int futex_mm_init(struct mm_struct *mm) { return 0; } -#endif /* CONFIG_FUTEX_PRIVATE_HASH */ +static inline void futex_mm_init(struct mm_struct *mm) { } +#endif /* !CONFIG_FUTEX_PRIVATE_HASH */ -#else /* !CONFIG_FUTEX */ +#else /* CONFIG_FUTEX */ static inline void futex_init_task(struct task_struct *tsk) { } static inline void futex_exit_recursive(struct task_struct *tsk) { } static inline void futex_exit_release(struct task_struct *tsk) { } static inline void futex_exec_release(struct task_struct *tsk) { } -static inline long do_futex(u32 __user *uaddr, int op, u32 val, - ktime_t *timeout, u32 __user *uaddr2, - u32 val2, u32 val3) +static inline long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout, + u32 __user *uaddr2, u32 val2, u32 val3) { return -EINVAL; } @@ -104,13 +102,9 @@ static inline int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsig { return -EINVAL; } -static inline int futex_hash_allocate_default(void) -{ - return 0; -} +static inline int futex_hash_allocate_default(void) { return 0; } static inline int futex_hash_free(struct mm_struct *mm) { return 0; } -static inline int futex_mm_init(struct mm_struct *mm) { return 0; } +static inline void futex_mm_init(struct mm_struct *mm) { } +#endif /* !CONFIG_FUTEX */ -#endif - -#endif +#endif /* _LINUX_FUTEX_H */ diff --git a/kernel/fork.c b/kernel/fork.c index 5f3fdfdb14c7..bb490d97c222 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1101,6 +1101,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p, #endif mm_init_uprobes_state(mm); hugetlb_count_init(mm); + futex_mm_init(mm); mm_flags_clear_all(mm); if (current->mm) { @@ -1113,11 +1114,8 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p, mm->def_flags = 0; } - if (futex_mm_init(mm)) - goto fail_mm_init; - if (mm_alloc_pgd(mm)) - goto fail_nopgd; + goto fail_mm_init; if (mm_alloc_id(mm)) goto fail_noid; @@ -1144,8 +1142,6 @@ fail_nocontext: mm_free_id(mm); fail_noid: mm_free_pgd(mm); -fail_nopgd: - futex_hash_free(mm); fail_mm_init: free_mm(mm); return NULL; diff --git a/kernel/futex/core.c b/kernel/futex/core.c index e7d33d2771ec..ec23de4912b3 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1720,7 +1720,7 @@ static bool futex_ref_is_dead(struct futex_private_hash *fph) return atomic_long_read(&mm->futex_atomic) == 0; } -int futex_mm_init(struct mm_struct *mm) +void futex_mm_init(struct mm_struct *mm) { mutex_init(&mm->futex_hash_lock); RCU_INIT_POINTER(mm->futex_phash, NULL); @@ -1729,7 +1729,6 @@ int futex_mm_init(struct mm_struct *mm) mm->futex_ref = NULL; atomic_long_set(&mm->futex_atomic, 0); mm->futex_batches = get_state_synchronize_rcu(); - return 0; } void futex_hash_free(struct mm_struct *mm) From 1f7f4816b9b05e5110bc1c8a05c3c478e2dae11b Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:34 +0200 Subject: [PATCH 105/167] futex: Move futex related mm_struct data into a struct Having all these members in mm_struct along with the required #ifdeffery is annoying, does not allow efficient initializing of the data with memset() and makes extending it tedious. Move it into a data structure and fix up all usage sites. The extra struct for the private hash is intentional to make integration of other conditional mechanisms easier in terms of initialization and separation. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260602090535.407756793@kernel.org --- include/linux/futex_types.h | 36 +++++++++- include/linux/mm_types.h | 12 +--- kernel/futex/core.c | 133 +++++++++++++++++------------------- 3 files changed, 98 insertions(+), 83 deletions(-) diff --git a/include/linux/futex_types.h b/include/linux/futex_types.h index 9c6c0dc4148d..d41557d2790d 100644 --- a/include/linux/futex_types.h +++ b/include/linux/futex_types.h @@ -3,6 +3,7 @@ #define _LINUX_FUTEX_TYPES_H #ifdef CONFIG_FUTEX +#include #include #include @@ -29,8 +30,41 @@ struct futex_sched_data { struct mutex exit_mutex; unsigned int state; }; -#else + +#ifdef CONFIG_FUTEX_PRIVATE_HASH +/** + * struct futex_mm_phash - Futex private hash related per MM data + * @lock: Mutex to protect the private hash operations + * @hash: RCU managed pointer to the private hash + * @hash_new: Pointer to a newly allocated private hash + * @batches: Batch state for RCU synchronization + * @rcu: RCU head for call_rcu() + * @atomic: Aggregate value for @hash_ref + * @ref: Per CPU reference counter for a private hash + */ +struct futex_mm_phash { + struct mutex lock; + struct futex_private_hash __rcu *hash; + struct futex_private_hash *hash_new; + unsigned long batches; + struct rcu_head rcu; + atomic_long_t atomic; + unsigned int __percpu *ref; +}; +#else /* CONFIG_FUTEX_ROBUST_UNLOCK */ +struct futex_mm_phash { }; +#endif /* !CONFIG_FUTEX_ROBUST_UNLOCK */ + +/** + * struct futex_mm_data - Futex related per MM data + * @phash: Futex private hash related data + */ +struct futex_mm_data { + struct futex_mm_phash phash; +}; +#else /* CONFIG_FUTEX */ struct futex_sched_data { }; +struct futex_mm_data { }; #endif /* !CONFIG_FUTEX */ #endif /* _LINUX_FUTEX_TYPES_H */ diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index a308e2c23b82..1d0c8d8875aa 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -1270,16 +1271,7 @@ struct mm_struct { */ seqcount_t mm_lock_seq; #endif -#ifdef CONFIG_FUTEX_PRIVATE_HASH - struct mutex futex_hash_lock; - struct futex_private_hash __rcu *futex_phash; - struct futex_private_hash *futex_phash_new; - /* futex-ref */ - unsigned long futex_batches; - struct rcu_head futex_rcu; - atomic_long_t futex_atomic; - unsigned int __percpu *futex_ref; -#endif + struct futex_mm_data futex; unsigned long hiwater_rss; /* High-watermark of RSS usage */ unsigned long hiwater_vm; /* High-water virtual memory usage */ diff --git a/kernel/futex/core.c b/kernel/futex/core.c index ec23de4912b3..79456b0ac1c6 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -188,13 +188,13 @@ __futex_hash_private(union futex_key *key, struct futex_private_hash *fph) return NULL; if (!fph) - fph = rcu_dereference(key->private.mm->futex_phash); + fph = rcu_dereference(key->private.mm->futex.phash.hash); if (!fph || !fph->hash_mask) return NULL; - hash = jhash2((void *)&key->private.address, - sizeof(key->private.address) / 4, + hash = jhash2((void *)&key->private.address, sizeof(key->private.address) / 4, key->both.offset); + return &fph->queues[hash & fph->hash_mask]; } @@ -233,18 +233,17 @@ static void futex_rehash_private(struct futex_private_hash *old, } } -static bool __futex_pivot_hash(struct mm_struct *mm, - struct futex_private_hash *new) +static bool __futex_pivot_hash(struct mm_struct *mm, struct futex_private_hash *new) { + struct futex_mm_phash *mmph = &mm->futex.phash; struct futex_private_hash *fph; - WARN_ON_ONCE(mm->futex_phash_new); + WARN_ON_ONCE(mmph->hash_new); - fph = rcu_dereference_protected(mm->futex_phash, - lockdep_is_held(&mm->futex_hash_lock)); + fph = rcu_dereference_protected(mmph->hash, lockdep_is_held(&mmph->lock)); if (fph) { if (!futex_ref_is_dead(fph)) { - mm->futex_phash_new = new; + mmph->hash_new = new; return false; } @@ -252,8 +251,8 @@ static bool __futex_pivot_hash(struct mm_struct *mm, } new->state = FR_PERCPU; scoped_guard(rcu) { - mm->futex_batches = get_state_synchronize_rcu(); - rcu_assign_pointer(mm->futex_phash, new); + mmph->batches = get_state_synchronize_rcu(); + rcu_assign_pointer(mmph->hash, new); } kvfree_rcu(fph, rcu); return true; @@ -261,12 +260,12 @@ static bool __futex_pivot_hash(struct mm_struct *mm, static void futex_pivot_hash(struct mm_struct *mm) { - scoped_guard(mutex, &mm->futex_hash_lock) { + scoped_guard(mutex, &mm->futex.phash.lock) { struct futex_private_hash *fph; - fph = mm->futex_phash_new; + fph = mm->futex.phash.hash_new; if (fph) { - mm->futex_phash_new = NULL; + mm->futex.phash.hash_new = NULL; __futex_pivot_hash(mm, fph); } } @@ -289,7 +288,7 @@ again: scoped_guard(rcu) { struct futex_private_hash *fph; - fph = rcu_dereference(mm->futex_phash); + fph = rcu_dereference(mm->futex.phash.hash); if (!fph) return NULL; @@ -412,8 +411,7 @@ static int futex_mpol(struct mm_struct *mm, unsigned long addr) * private hash) is returned if existing. Otherwise a hash bucket from the * global hash is returned. */ -static struct futex_hash_bucket * -__futex_hash(union futex_key *key, struct futex_private_hash *fph) +static struct futex_hash_bucket *__futex_hash(union futex_key *key, struct futex_private_hash *fph) { int node = key->both.node; u32 hash; @@ -426,8 +424,7 @@ __futex_hash(union futex_key *key, struct futex_private_hash *fph) return hb; } - hash = jhash2((u32 *)key, - offsetof(typeof(*key), both.offset) / sizeof(u32), + hash = jhash2((u32 *)key, offsetof(typeof(*key), both.offset) / sizeof(u32), key->both.offset); if (node == FUTEX_NO_NODE) { @@ -442,8 +439,7 @@ __futex_hash(union futex_key *key, struct futex_private_hash *fph) */ node = (hash >> futex_hashshift) % nr_node_ids; if (!node_possible(node)) { - node = find_next_bit_wrap(node_possible_map.bits, - nr_node_ids, node); + node = find_next_bit_wrap(node_possible_map.bits, nr_node_ids, node); } } @@ -460,9 +456,8 @@ __futex_hash(union futex_key *key, struct futex_private_hash *fph) * Return: Initialized hrtimer_sleeper structure or NULL if no timeout * value given */ -struct hrtimer_sleeper * -futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout, - int flags, u64 range_ns) +struct hrtimer_sleeper *futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout, + int flags, u64 range_ns) { if (!time) return NULL; @@ -1554,17 +1549,17 @@ static void __futex_ref_atomic_begin(struct futex_private_hash *fph) * otherwise it would be impossible for it to have reported success * from futex_ref_is_dead(). */ - WARN_ON_ONCE(atomic_long_read(&mm->futex_atomic) != 0); + WARN_ON_ONCE(atomic_long_read(&mm->futex.phash.atomic) != 0); /* * Set the atomic to the bias value such that futex_ref_{get,put}() * will never observe 0. Will be fixed up in __futex_ref_atomic_end() * when folding in the percpu count. */ - atomic_long_set(&mm->futex_atomic, LONG_MAX); + atomic_long_set(&mm->futex.phash.atomic, LONG_MAX); smp_store_release(&fph->state, FR_ATOMIC); - call_rcu_hurry(&mm->futex_rcu, futex_ref_rcu); + call_rcu_hurry(&mm->futex.phash.rcu, futex_ref_rcu); } static void __futex_ref_atomic_end(struct futex_private_hash *fph) @@ -1585,7 +1580,7 @@ static void __futex_ref_atomic_end(struct futex_private_hash *fph) * Therefore the per-cpu counter is now stable, sum and reset. */ for_each_possible_cpu(cpu) { - unsigned int *ptr = per_cpu_ptr(mm->futex_ref, cpu); + unsigned int *ptr = per_cpu_ptr(mm->futex.phash.ref, cpu); count += *ptr; *ptr = 0; } @@ -1593,7 +1588,7 @@ static void __futex_ref_atomic_end(struct futex_private_hash *fph) /* * Re-init for the next cycle. */ - this_cpu_inc(*mm->futex_ref); /* 0 -> 1 */ + this_cpu_inc(*mm->futex.phash.ref); /* 0 -> 1 */ /* * Add actual count, subtract bias and initial refcount. @@ -1601,7 +1596,7 @@ static void __futex_ref_atomic_end(struct futex_private_hash *fph) * The moment this atomic operation happens, futex_ref_is_dead() can * become true. */ - ret = atomic_long_add_return(count - LONG_MAX - 1, &mm->futex_atomic); + ret = atomic_long_add_return(count - LONG_MAX - 1, &mm->futex.phash.atomic); if (!ret) wake_up_var(mm); @@ -1611,8 +1606,8 @@ static void __futex_ref_atomic_end(struct futex_private_hash *fph) static void futex_ref_rcu(struct rcu_head *head) { - struct mm_struct *mm = container_of(head, struct mm_struct, futex_rcu); - struct futex_private_hash *fph = rcu_dereference_raw(mm->futex_phash); + struct mm_struct *mm = container_of(head, struct mm_struct, futex.phash.rcu); + struct futex_private_hash *fph = rcu_dereference_raw(mm->futex.phash.hash); if (fph->state == FR_PERCPU) { /* @@ -1641,7 +1636,7 @@ static void futex_ref_drop(struct futex_private_hash *fph) /* * Can only transition the current fph; */ - WARN_ON_ONCE(rcu_dereference_raw(mm->futex_phash) != fph); + WARN_ON_ONCE(rcu_dereference_raw(mm->futex.phash.hash) != fph); /* * We enqueue at least one RCU callback. Ensure mm stays if the task * exits before the transition is completed. @@ -1652,9 +1647,9 @@ static void futex_ref_drop(struct futex_private_hash *fph) * In order to avoid the following scenario: * * futex_hash() __futex_pivot_hash() - * guard(rcu); guard(mm->futex_hash_lock); - * fph = mm->futex_phash; - * rcu_assign_pointer(&mm->futex_phash, new); + * guard(rcu); guard(mm->futex.phash.lock); + * fph = mm->futex.phash.hash; + * rcu_assign_pointer(&mm->futex.phash.hash, new); * futex_hash_allocate() * futex_ref_drop() * fph->state = FR_ATOMIC; @@ -1669,7 +1664,7 @@ static void futex_ref_drop(struct futex_private_hash *fph) * There must be at least one full grace-period between publishing a * new fph and trying to replace it. */ - if (poll_state_synchronize_rcu(mm->futex_batches)) { + if (poll_state_synchronize_rcu(mm->futex.phash.batches)) { /* * There was a grace-period, we can begin now. */ @@ -1677,7 +1672,7 @@ static void futex_ref_drop(struct futex_private_hash *fph) return; } - call_rcu_hurry(&mm->futex_rcu, futex_ref_rcu); + call_rcu_hurry(&mm->futex.phash.rcu, futex_ref_rcu); } static bool futex_ref_get(struct futex_private_hash *fph) @@ -1687,11 +1682,11 @@ static bool futex_ref_get(struct futex_private_hash *fph) guard(preempt)(); if (READ_ONCE(fph->state) == FR_PERCPU) { - __this_cpu_inc(*mm->futex_ref); + __this_cpu_inc(*mm->futex.phash.ref); return true; } - return atomic_long_inc_not_zero(&mm->futex_atomic); + return atomic_long_inc_not_zero(&mm->futex.phash.atomic); } static bool futex_ref_put(struct futex_private_hash *fph) @@ -1701,11 +1696,11 @@ static bool futex_ref_put(struct futex_private_hash *fph) guard(preempt)(); if (READ_ONCE(fph->state) == FR_PERCPU) { - __this_cpu_dec(*mm->futex_ref); + __this_cpu_dec(*mm->futex.phash.ref); return false; } - return atomic_long_dec_and_test(&mm->futex_atomic); + return atomic_long_dec_and_test(&mm->futex.phash.atomic); } static bool futex_ref_is_dead(struct futex_private_hash *fph) @@ -1717,27 +1712,23 @@ static bool futex_ref_is_dead(struct futex_private_hash *fph) if (smp_load_acquire(&fph->state) == FR_PERCPU) return false; - return atomic_long_read(&mm->futex_atomic) == 0; + return atomic_long_read(&mm->futex.phash.atomic) == 0; } void futex_mm_init(struct mm_struct *mm) { - mutex_init(&mm->futex_hash_lock); - RCU_INIT_POINTER(mm->futex_phash, NULL); - mm->futex_phash_new = NULL; - /* futex-ref */ - mm->futex_ref = NULL; - atomic_long_set(&mm->futex_atomic, 0); - mm->futex_batches = get_state_synchronize_rcu(); + memset(&mm->futex, 0, sizeof(mm->futex)); + mutex_init(&mm->futex.phash.lock); + mm->futex.phash.batches = get_state_synchronize_rcu(); } void futex_hash_free(struct mm_struct *mm) { struct futex_private_hash *fph; - free_percpu(mm->futex_ref); - kvfree(mm->futex_phash_new); - fph = rcu_dereference_raw(mm->futex_phash); + free_percpu(mm->futex.phash.ref); + kvfree(mm->futex.phash.hash_new); + fph = rcu_dereference_raw(mm->futex.phash.hash); if (fph) kvfree(fph); } @@ -1748,10 +1739,10 @@ static bool futex_pivot_pending(struct mm_struct *mm) guard(rcu)(); - if (!mm->futex_phash_new) + if (!mm->futex.phash.hash_new) return true; - fph = rcu_dereference(mm->futex_phash); + fph = rcu_dereference(mm->futex.phash.hash); return futex_ref_is_dead(fph); } @@ -1793,7 +1784,7 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) * Once we've disabled the global hash there is no way back. */ scoped_guard(rcu) { - fph = rcu_dereference(mm->futex_phash); + fph = rcu_dereference(mm->futex.phash.hash); if (fph && !fph->hash_mask) { if (custom) return -EBUSY; @@ -1801,15 +1792,15 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) } } - if (!mm->futex_ref) { + if (!mm->futex.phash.ref) { /* * This will always be allocated by the first thread and * therefore requires no locking. */ - mm->futex_ref = alloc_percpu(unsigned int); - if (!mm->futex_ref) + mm->futex.phash.ref = alloc_percpu(unsigned int); + if (!mm->futex.phash.ref) return -ENOMEM; - this_cpu_inc(*mm->futex_ref); /* 0 -> 1 */ + this_cpu_inc(*mm->futex.phash.ref); /* 0 -> 1 */ } fph = kvzalloc(struct_size(fph, queues, hash_slots), @@ -1832,14 +1823,14 @@ again: wait_var_event(mm, futex_pivot_pending(mm)); } - scoped_guard(mutex, &mm->futex_hash_lock) { + scoped_guard(mutex, &mm->futex.phash.lock) { struct futex_private_hash *free __free(kvfree) = NULL; struct futex_private_hash *cur, *new; - cur = rcu_dereference_protected(mm->futex_phash, - lockdep_is_held(&mm->futex_hash_lock)); - new = mm->futex_phash_new; - mm->futex_phash_new = NULL; + cur = rcu_dereference_protected(mm->futex.phash.hash, + lockdep_is_held(&mm->futex.phash.lock)); + new = mm->futex.phash.hash_new; + mm->futex.phash.hash_new = NULL; if (fph) { if (cur && !cur->hash_mask) { @@ -1849,7 +1840,7 @@ again: * the second one returns here. */ free = fph; - mm->futex_phash_new = new; + mm->futex.phash.hash_new = new; return -EBUSY; } if (cur && !new) { @@ -1879,7 +1870,7 @@ again: if (new) { /* - * Will set mm->futex_phash_new on failure; + * Will set mm->futex.phash.new_hash on failure; * futex_private_hash_get() will try again. */ if (!__futex_pivot_hash(mm, new) && custom) @@ -1898,11 +1889,9 @@ int futex_hash_allocate_default(void) return 0; scoped_guard(rcu) { - threads = min_t(unsigned int, - get_nr_threads(current), - num_online_cpus()); + threads = min_t(unsigned int, get_nr_threads(current), num_online_cpus()); - fph = rcu_dereference(current->mm->futex_phash); + fph = rcu_dereference(current->mm->futex.phash.hash); if (fph) { if (fph->custom) return 0; @@ -1929,7 +1918,7 @@ static int futex_hash_get_slots(void) struct futex_private_hash *fph; guard(rcu)(); - fph = rcu_dereference(current->mm->futex_phash); + fph = rcu_dereference(current->mm->futex.phash.hash); if (fph && fph->hash_mask) return fph->hash_mask + 1; return 0; From 2cb5251d3d64d57c172185b9b608f704b3015f26 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:38 +0200 Subject: [PATCH 106/167] futex: Provide UABI defines for robust list entry modifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The marker for PI futexes in the robust list is a hardcoded 0x1 which lacks any sensible form of documentation. Provide proper defines for the bit and the mask and fix up the usage sites. Thereby convert the boolean pi argument into a modifier argument, which allows new modifier bits to be trivially added and conveyed. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Mathieu Desnoyers Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.458758556@kernel.org --- include/uapi/linux/futex.h | 4 +++ kernel/futex/core.c | 53 ++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h index 7e2744ec8933..29bf2f65406a 100644 --- a/include/uapi/linux/futex.h +++ b/include/uapi/linux/futex.h @@ -177,6 +177,10 @@ struct robust_list_head { */ #define ROBUST_LIST_LIMIT 2048 +/* Modifiers for robust_list_head::list_op_pending */ +#define FUTEX_ROBUST_MOD_PI (0x1UL) +#define FUTEX_ROBUST_MOD_MASK (FUTEX_ROBUST_MOD_PI) + /* * bitset with all bits set for the FUTEX_xxx_BITSET OPs to request a * match of any bit. diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 79456b0ac1c6..61f4f559afa1 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1008,8 +1008,9 @@ void futex_unqueue_pi(struct futex_q *q) * dying task, and do notification if so: */ static int handle_futex_death(u32 __user *uaddr, struct task_struct *curr, - bool pi, bool pending_op) + unsigned int mod, bool pending_op) { + bool pi = !!(mod & FUTEX_ROBUST_MOD_PI); u32 uval, nval, mval; pid_t owner; int err; @@ -1127,21 +1128,21 @@ retry: */ static inline int fetch_robust_entry(struct robust_list __user **entry, struct robust_list __user * __user *head, - unsigned int *pi) + unsigned int *mod) { unsigned long uentry; if (get_user(uentry, (unsigned long __user *)head)) return -EFAULT; - *entry = (void __user *)(uentry & ~1UL); - *pi = uentry & 1; + *entry = (void __user *)(uentry & ~FUTEX_ROBUST_MOD_MASK); + *mod = uentry & FUTEX_ROBUST_MOD_MASK; return 0; } /* - * Walk curr->robust_list (very carefully, it's a userspace list!) + * Walk curr->futex.robust_list (very carefully, it's a userspace list!) * and mark any locks found there dead, and notify any waiters. * * We silently return on any sign of list-walking problem. @@ -1149,9 +1150,8 @@ static inline int fetch_robust_entry(struct robust_list __user **entry, static void exit_robust_list(struct task_struct *curr) { struct robust_list_head __user *head = curr->futex.robust_list; + unsigned int limit = ROBUST_LIST_LIMIT, cur_mod, next_mod, pend_mod; struct robust_list __user *entry, *next_entry, *pending; - unsigned int limit = ROBUST_LIST_LIMIT, pi, pip; - unsigned int next_pi; unsigned long futex_offset; int rc; @@ -1159,7 +1159,7 @@ static void exit_robust_list(struct task_struct *curr) * Fetch the list head (which was registered earlier, via * sys_set_robust_list()): */ - if (fetch_robust_entry(&entry, &head->list.next, &pi)) + if (fetch_robust_entry(&entry, &head->list.next, &cur_mod)) return; /* * Fetch the relative futex offset: @@ -1170,7 +1170,7 @@ static void exit_robust_list(struct task_struct *curr) * Fetch any possibly pending lock-add first, and handle it * if it exists: */ - if (fetch_robust_entry(&pending, &head->list_op_pending, &pip)) + if (fetch_robust_entry(&pending, &head->list_op_pending, &pend_mod)) return; next_entry = NULL; /* avoid warning with gcc */ @@ -1179,20 +1179,20 @@ static void exit_robust_list(struct task_struct *curr) * Fetch the next entry in the list before calling * handle_futex_death: */ - rc = fetch_robust_entry(&next_entry, &entry->next, &next_pi); + rc = fetch_robust_entry(&next_entry, &entry->next, &next_mod); /* * A pending lock might already be on the list, so * don't process it twice: */ if (entry != pending) { if (handle_futex_death((void __user *)entry + futex_offset, - curr, pi, HANDLE_DEATH_LIST)) + curr, cur_mod, HANDLE_DEATH_LIST)) return; } if (rc) return; entry = next_entry; - pi = next_pi; + cur_mod = next_mod; /* * Avoid excessively long or circular lists: */ @@ -1204,7 +1204,7 @@ static void exit_robust_list(struct task_struct *curr) if (pending) { handle_futex_death((void __user *)pending + futex_offset, - curr, pip, HANDLE_DEATH_PENDING); + curr, pend_mod, HANDLE_DEATH_PENDING); } } @@ -1223,29 +1223,28 @@ static void __user *futex_uaddr(struct robust_list __user *entry, */ static inline int compat_fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **entry, - compat_uptr_t __user *head, unsigned int *pi) + compat_uptr_t __user *head, unsigned int *pflags) { if (get_user(*uentry, head)) return -EFAULT; - *entry = compat_ptr((*uentry) & ~1); - *pi = (unsigned int)(*uentry) & 1; + *entry = compat_ptr((*uentry) & ~FUTEX_ROBUST_MOD_MASK); + *pflags = (unsigned int)(*uentry) & FUTEX_ROBUST_MOD_MASK; return 0; } /* - * Walk curr->robust_list (very carefully, it's a userspace list!) + * Walk curr->futex.robust_list (very carefully, it's a userspace list!) * and mark any locks found there dead, and notify any waiters. * * We silently return on any sign of list-walking problem. */ static void compat_exit_robust_list(struct task_struct *curr) { - struct compat_robust_list_head __user *head = curr->futex.compat_robust_list; + struct compat_robust_list_head __user *head = current->futex.compat_robust_list; + unsigned int limit = ROBUST_LIST_LIMIT, cur_mod, next_mod, pend_mod; struct robust_list __user *entry, *next_entry, *pending; - unsigned int limit = ROBUST_LIST_LIMIT, pi, pip; - unsigned int next_pi; compat_uptr_t uentry, next_uentry, upending; compat_long_t futex_offset; int rc; @@ -1254,7 +1253,7 @@ static void compat_exit_robust_list(struct task_struct *curr) * Fetch the list head (which was registered earlier, via * sys_set_robust_list()): */ - if (compat_fetch_robust_entry(&uentry, &entry, &head->list.next, &pi)) + if (compat_fetch_robust_entry(&uentry, &entry, &head->list.next, &cur_mod)) return; /* * Fetch the relative futex offset: @@ -1265,8 +1264,7 @@ static void compat_exit_robust_list(struct task_struct *curr) * Fetch any possibly pending lock-add first, and handle it * if it exists: */ - if (compat_fetch_robust_entry(&upending, &pending, - &head->list_op_pending, &pip)) + if (compat_fetch_robust_entry(&upending, &pending, &head->list_op_pending, &pend_mod)) return; next_entry = NULL; /* avoid warning with gcc */ @@ -1276,7 +1274,7 @@ static void compat_exit_robust_list(struct task_struct *curr) * handle_futex_death: */ rc = compat_fetch_robust_entry(&next_uentry, &next_entry, - (compat_uptr_t __user *)&entry->next, &next_pi); + (compat_uptr_t __user *)&entry->next, &next_mod); /* * A pending lock might already be on the list, so * dont process it twice: @@ -1284,15 +1282,14 @@ static void compat_exit_robust_list(struct task_struct *curr) if (entry != pending) { void __user *uaddr = futex_uaddr(entry, futex_offset); - if (handle_futex_death(uaddr, curr, pi, - HANDLE_DEATH_LIST)) + if (handle_futex_death(uaddr, curr, cur_mod, HANDLE_DEATH_LIST)) return; } if (rc) return; uentry = next_uentry; entry = next_entry; - pi = next_pi; + cur_mod = next_mod; /* * Avoid excessively long or circular lists: */ @@ -1304,7 +1301,7 @@ static void compat_exit_robust_list(struct task_struct *curr) if (pending) { void __user *uaddr = futex_uaddr(pending, futex_offset); - handle_futex_death(uaddr, curr, pip, HANDLE_DEATH_PENDING); + handle_futex_death(uaddr, curr, pend_mod, HANDLE_DEATH_PENDING); } } #endif From 6149fc36c09b91050b62e8e68a91027df8df7345 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:42 +0200 Subject: [PATCH 107/167] uaccess: Provide unsafe_atomic_store_release_user() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upcoming support for unlocking robust futexes in the kernel requires store release semantics. Syscalls do not imply memory ordering on all architectures so the unlock operation requires a barrier. This barrier can be avoided when stores imply release like on x86. Provide a generic version with a smp_mb() before the unsafe_put_user(), which can be overridden by architectures. Provide also a ARCH_MEMORY_ORDER_TSO Kconfig option, which can be selected by architectures with Total Store Order (TSO), where store implies release, so that the smp_mb() in the generic implementation can be avoided. If that is set a barrier() is used instead of smp_mb(), which is not required for the use case at hand, but makes it future proof for other usage to prevent the compiler from reordering. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.513181528@kernel.org --- arch/Kconfig | 4 ++++ include/linux/uaccess.h | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/arch/Kconfig b/arch/Kconfig index e86880045158..83d362fca38c 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -403,6 +403,10 @@ config ARCH_32BIT_OFF_T config ARCH_32BIT_USTAT_F_TINODE bool +# Selected by architectures with Total Store Order (TSO) +config ARCH_MEMORY_ORDER_TSO + bool + config HAVE_ASM_MODVERSIONS bool help diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h index 56328601218c..c6bd20065a5d 100644 --- a/include/linux/uaccess.h +++ b/include/linux/uaccess.h @@ -649,6 +649,17 @@ static inline void user_access_restore(unsigned long flags) { } #define user_read_access_end user_access_end #endif +#ifndef unsafe_atomic_store_release_user +# define unsafe_atomic_store_release_user(val, uptr, elbl) \ + do { \ + if (!IS_ENABLED(CONFIG_ARCH_MEMORY_ORDER_TSO)) \ + smp_mb(); \ + else \ + barrier(); \ + unsafe_put_user(val, uptr, elbl); \ + } while (0) +#endif + /* Define RW variant so the below _mode macro expansion works */ #define masked_user_rw_access_begin(u) masked_user_access_begin(u) #define user_rw_access_begin(u, s) user_access_begin(u, s) From 7b125c44d0b7f617ee81dffd14ce116149d03cb6 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:47 +0200 Subject: [PATCH 108/167] x86: Select ARCH_MEMORY_ORDER_TSO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generic unsafe_atomic_store_release_user() implementation does: if (!IS_ENABLED(CONFIG_ARCH_MEMORY_ORDER_TSO)) smp_mb(); unsafe_put_user(); As x86 implements Total Store Order (TSO) which means stores imply release, select ARCH_MEMORY_ORDER_TSO to avoid the unnecessary smp_mb(). Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.564499644@kernel.org --- arch/x86/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index f3f7cb01d69d..1ce62a996192 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -113,6 +113,7 @@ config X86 select ARCH_HAS_ZONE_DMA_SET if EXPERT select ARCH_HAVE_NMI_SAFE_CMPXCHG select ARCH_HAVE_EXTRA_ELF_NOTES + select ARCH_MEMORY_ORDER_TSO select ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE select ARCH_MIGHT_HAVE_ACPI_PDC if ACPI select ARCH_MIGHT_HAVE_PC_PARPORT From 1fd053d26f0333485cdbaa9d6e7b8cb53f54de95 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:51 +0200 Subject: [PATCH 109/167] futex: Cleanup UAPI defines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the operand defines tabular for readability sake. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.615600933@kernel.org --- include/uapi/linux/futex.h | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h index 29bf2f65406a..75df1eaff75d 100644 --- a/include/uapi/linux/futex.h +++ b/include/uapi/linux/futex.h @@ -25,23 +25,22 @@ #define FUTEX_PRIVATE_FLAG 128 #define FUTEX_CLOCK_REALTIME 256 -#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) -#define FUTEX_WAIT_PRIVATE (FUTEX_WAIT | FUTEX_PRIVATE_FLAG) -#define FUTEX_WAKE_PRIVATE (FUTEX_WAKE | FUTEX_PRIVATE_FLAG) -#define FUTEX_REQUEUE_PRIVATE (FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG) -#define FUTEX_CMP_REQUEUE_PRIVATE (FUTEX_CMP_REQUEUE | FUTEX_PRIVATE_FLAG) -#define FUTEX_WAKE_OP_PRIVATE (FUTEX_WAKE_OP | FUTEX_PRIVATE_FLAG) -#define FUTEX_LOCK_PI_PRIVATE (FUTEX_LOCK_PI | FUTEX_PRIVATE_FLAG) -#define FUTEX_LOCK_PI2_PRIVATE (FUTEX_LOCK_PI2 | FUTEX_PRIVATE_FLAG) -#define FUTEX_UNLOCK_PI_PRIVATE (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG) -#define FUTEX_TRYLOCK_PI_PRIVATE (FUTEX_TRYLOCK_PI | FUTEX_PRIVATE_FLAG) +#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) + +#define FUTEX_WAIT_PRIVATE (FUTEX_WAIT | FUTEX_PRIVATE_FLAG) +#define FUTEX_WAKE_PRIVATE (FUTEX_WAKE | FUTEX_PRIVATE_FLAG) +#define FUTEX_REQUEUE_PRIVATE (FUTEX_REQUEUE | FUTEX_PRIVATE_FLAG) +#define FUTEX_CMP_REQUEUE_PRIVATE (FUTEX_CMP_REQUEUE | FUTEX_PRIVATE_FLAG) +#define FUTEX_WAKE_OP_PRIVATE (FUTEX_WAKE_OP | FUTEX_PRIVATE_FLAG) +#define FUTEX_LOCK_PI_PRIVATE (FUTEX_LOCK_PI | FUTEX_PRIVATE_FLAG) +#define FUTEX_LOCK_PI2_PRIVATE (FUTEX_LOCK_PI2 | FUTEX_PRIVATE_FLAG) +#define FUTEX_UNLOCK_PI_PRIVATE (FUTEX_UNLOCK_PI | FUTEX_PRIVATE_FLAG) +#define FUTEX_TRYLOCK_PI_PRIVATE (FUTEX_TRYLOCK_PI | FUTEX_PRIVATE_FLAG) #define FUTEX_WAIT_BITSET_PRIVATE (FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG) #define FUTEX_WAKE_BITSET_PRIVATE (FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG) -#define FUTEX_WAIT_REQUEUE_PI_PRIVATE (FUTEX_WAIT_REQUEUE_PI | \ - FUTEX_PRIVATE_FLAG) -#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | \ - FUTEX_PRIVATE_FLAG) +#define FUTEX_WAIT_REQUEUE_PI_PRIVATE (FUTEX_WAIT_REQUEUE_PI | FUTEX_PRIVATE_FLAG) +#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | FUTEX_PRIVATE_FLAG) /* * Flags for futex2 syscalls. From 3ca9595d9fb6cce6633a5b03d98c2aecb5499838 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:55 +0200 Subject: [PATCH 110/167] futex: Add support for unlocking robust futexes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unlocking robust non-PI futexes happens in user space with the following sequence: 1) robust_list_set_op_pending(mutex); 2) robust_list_remove(mutex); lval = 0; 3) lval = atomic_xchg(lock, lval); 4) if (lval & WAITERS) 5) sys_futex(WAKE,....); 6) robust_list_clear_op_pending(); That opens a window between #3 and #6 where the mutex could be acquired by some other task which observes that it is the last user and: A) unmaps the mutex memory B) maps a different file, which ends up covering the same address When the original task exits before reaching #6 then the kernel robust list handling observes the pending op entry and tries to fix up user space. In case that the newly mapped data contains the TID of the exiting thread at the address of the mutex/futex the kernel will set the owner died bit in that memory and therefore corrupting unrelated data. PI futexes have a similar problem both for the non-contented user space unlock and the in kernel unlock: 1) robust_list_set_op_pending(mutex); 2) robust_list_remove(mutex); lval = gettid(); 3) if (!atomic_try_cmpxchg(lock, lval, 0)) 4) sys_futex(UNLOCK_PI,....); 5) robust_list_clear_op_pending(); Address the first part of the problem where the futexes have waiters and need to enter the kernel anyway. Add a new FUTEX_ROBUST_UNLOCK flag, which is valid for the sys_futex() FUTEX_UNLOCK_PI, FUTEX_WAKE, FUTEX_WAKE_BITSET operations. This deliberately omits FUTEX_WAKE_OP from this treatment as it's unclear whether this is needed and there is no usage of it in glibc either to investigate. For the futex2 syscall family this needs to be implemented with a new syscall. The sys_futex() case [ab]uses the @uaddr2 argument to hand the pointer to robust_list_head::list_pending_op into the kernel. This argument is only evaluated when the FUTEX_ROBUST_UNLOCK bit is set and is therefore backward compatible. This is an explicit argument to avoid the lookup of the robust list pointer and retrieving the pending op pointer from there. User space has the pointer already available so it can just put it into the @uaddr2 argument. Aside of that this allows the usage of multiple robust lists in the future without any changes to the internal functions as they just operate on the provided pointer. This requires a second flag FUTEX_ROBUST_LIST32 which indicates that the robust list pointer points to an u32 and not to an u64. This is required for two reasons: 1) sys_futex() has no compat variant 2) The gaming emulators use both both 64-bit and compat 32-bit robust lists in the same 64-bit application As a consequence 32-bit applications have to set this flag unconditionally so they can run on a 64-bit kernel in compat mode unmodified. 32-bit kernels return an error code when the flag is not set. 64-bit kernels will happily clear the full 64 bits if user space fails to set it. In case of FUTEX_UNLOCK_PI this clears the robust list pending op when the unlock succeeded. In case of errors, the user space value is still locked by the caller and therefore the above cannot happen. In case of FUTEX_WAKE* this does the unlock of the futex in the kernel and clears the robust list pending op when the unlock was successful. If not, the user space value is still locked and user space has to deal with the returned error. That means that the unlocking of non-PI robust futexes has to use the same try_cmpxchg() unlock scheme as PI futexes. If the clearing of the pending list op fails (fault) then the kernel clears the registered robust list pointer if it matches to prevent that exit() will try to handle invalid data. That's a valid paranoid decision because the robust list head sits usually in the TLS and if the TLS is not longer accessible then the chance for fixing up the resulting mess is very close to zero. The problem of non-contended unlocks still exists and will be addressed separately. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.670514505@kernel.org --- include/uapi/linux/futex.h | 29 ++++++++++++++++++++- io_uring/futex.c | 2 +- kernel/futex/core.c | 53 ++++++++++++++++++++++++++++++++++++-- kernel/futex/futex.h | 15 +++++++++-- kernel/futex/pi.c | 15 +++++++++-- kernel/futex/syscalls.c | 13 +++++++--- kernel/futex/waitwake.c | 30 +++++++++++++++++++-- 7 files changed, 144 insertions(+), 13 deletions(-) diff --git a/include/uapi/linux/futex.h b/include/uapi/linux/futex.h index 75df1eaff75d..10a36c551675 100644 --- a/include/uapi/linux/futex.h +++ b/include/uapi/linux/futex.h @@ -25,8 +25,11 @@ #define FUTEX_PRIVATE_FLAG 128 #define FUTEX_CLOCK_REALTIME 256 +#define FUTEX_ROBUST_UNLOCK 512 +#define FUTEX_ROBUST_LIST32 1024 -#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) +#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME | \ + FUTEX_ROBUST_UNLOCK | FUTEX_ROBUST_LIST32) #define FUTEX_WAIT_PRIVATE (FUTEX_WAIT | FUTEX_PRIVATE_FLAG) #define FUTEX_WAKE_PRIVATE (FUTEX_WAKE | FUTEX_PRIVATE_FLAG) @@ -42,6 +45,30 @@ #define FUTEX_WAIT_REQUEUE_PI_PRIVATE (FUTEX_WAIT_REQUEUE_PI | FUTEX_PRIVATE_FLAG) #define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | FUTEX_PRIVATE_FLAG) +/* + * Operations to unlock a futex, clear the robust list pending op pointer and + * wake waiters. + */ +#define FUTEX_UNLOCK_PI_LIST64 (FUTEX_UNLOCK_PI | FUTEX_ROBUST_UNLOCK) +#define FUTEX_UNLOCK_PI_LIST64_PRIVATE (FUTEX_UNLOCK_PI_LIST64 | FUTEX_PRIVATE_FLAG) +#define FUTEX_UNLOCK_PI_LIST32 (FUTEX_UNLOCK_PI | FUTEX_ROBUST_UNLOCK | \ + FUTEX_ROBUST_LIST32) +#define FUTEX_UNLOCK_PI_LIST32_PRIVATE (FUTEX_UNLOCK_PI_LIST32 | FUTEX_PRIVATE_FLAG) + +#define FUTEX_UNLOCK_WAKE_LIST64 (FUTEX_WAKE | FUTEX_ROBUST_UNLOCK) +#define FUTEX_UNLOCK_WAKE_LIST64_PRIVATE (FUTEX_UNLOCK_WAKE_LIST64 | FUTEX_PRIVATE_FLAG) + +#define FUTEX_UNLOCK_WAKE_LIST32 (FUTEX_WAKE | FUTEX_ROBUST_UNLOCK | \ + FUTEX_ROBUST_LIST32) +#define FUTEX_UNLOCK_WAKE_LIST32_PRIVATE (FUTEX_UNLOCK_WAKE_LIST32 | FUTEX_PRIVATE_FLAG) + +#define FUTEX_UNLOCK_BITSET_LIST64 (FUTEX_WAKE_BITSET | FUTEX_ROBUST_UNLOCK) +#define FUTEX_UNLOCK_BITSET_LIST64_PRIVATE (FUTEX_UNLOCK_BITSET_LIST64 | FUTEX_PRIVATE_FLAG) + +#define FUTEX_UNLOCK_BITSET_LIST32 (FUTEX_WAKE_BITSET | FUTEX_ROBUST_UNLOCK | \ + FUTEX_ROBUST_LIST32) +#define FUTEX_UNLOCK_BITSET_LIST32_PRIVATE (FUTEX_UNLOCK_BITSET_LIST32 | FUTEX_PRIVATE_FLAG) + /* * Flags for futex2 syscalls. * diff --git a/io_uring/futex.c b/io_uring/futex.c index 9cc1788ef4c6..906701b3c5c6 100644 --- a/io_uring/futex.c +++ b/io_uring/futex.c @@ -327,7 +327,7 @@ int io_futex_wake(struct io_kiocb *req, unsigned int issue_flags) * Strict flags - ensure that waking 0 futexes yields a 0 result. * See commit 43adf8449510 ("futex: FLAGS_STRICT") for details. */ - ret = futex_wake(iof->uaddr, FLAGS_STRICT | iof->futex_flags, + ret = futex_wake(iof->uaddr, FLAGS_STRICT | iof->futex_flags, NULL, iof->futex_val, iof->futex_mask); if (ret < 0) req_set_fail(req); diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 61f4f559afa1..77ccb7787c34 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1062,7 +1062,7 @@ retry: owner = uval & FUTEX_TID_MASK; if (pending_op && !pi && !owner) { - futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, 1, + futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, NULL, 1, FUTEX_BITSET_MATCH_ANY); return 0; } @@ -1116,7 +1116,7 @@ retry: * PI futexes happens in exit_pi_state(): */ if (!pi && (uval & FUTEX_WAITERS)) { - futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, 1, + futex_wake(uaddr, FLAGS_SIZE_32 | FLAGS_SHARED, NULL, 1, FUTEX_BITSET_MATCH_ANY); } @@ -1208,6 +1208,27 @@ static void exit_robust_list(struct task_struct *curr) } } +static bool robust_list_clear_pending(unsigned long __user *pop) +{ + struct robust_list_head __user *head = current->futex.robust_list; + + if (!put_user(0UL, pop)) + return true; + + /* + * Just give up. The robust list head is usually part of TLS, so the + * chance that this gets resolved is close to zero. + * + * If @pop_addr is the robust_list_head::list_op_pending pointer then + * clear the robust list head pointer to prevent further damage when the + * task exits. Better a few stale futexes than corrupted memory. But + * that's mostly an academic exercise. + */ + if (pop == (unsigned long __user *)&head->list_op_pending) + current->futex.robust_list = NULL; + return false; +} + #ifdef CONFIG_COMPAT static void __user *futex_uaddr(struct robust_list __user *entry, compat_long_t futex_offset) @@ -1304,6 +1325,21 @@ static void compat_exit_robust_list(struct task_struct *curr) handle_futex_death(uaddr, curr, pend_mod, HANDLE_DEATH_PENDING); } } + +static bool compat_robust_list_clear_pending(u32 __user *pop) +{ + struct compat_robust_list_head __user *head = current->futex.compat_robust_list; + + if (!put_user(0U, pop)) + return true; + + /* See comment in robust_list_clear_pending(). */ + if (pop == &head->list_op_pending) + current->futex.compat_robust_list = NULL; + return false; +} +#else +static bool compat_robust_list_clear_pending(u32 __user *pop_addr) { return false; } #endif #ifdef CONFIG_FUTEX_PI @@ -1397,6 +1433,19 @@ static void exit_pi_state_list(struct task_struct *curr) static inline void exit_pi_state_list(struct task_struct *curr) { } #endif +bool futex_robust_list_clear_pending(void __user *pop, unsigned int flags) +{ + bool size32bit = !!(flags & FLAGS_ROBUST_LIST32); + + if (!IS_ENABLED(CONFIG_64BIT) && !size32bit) + return false; + + if (IS_ENABLED(CONFIG_64BIT) && size32bit) + return compat_robust_list_clear_pending(pop); + + return robust_list_clear_pending(pop); +} + static void futex_cleanup(struct task_struct *tsk) { if (unlikely(tsk->futex.robust_list)) { diff --git a/kernel/futex/futex.h b/kernel/futex/futex.h index 9f6bf6f585fc..79ef2c709c81 100644 --- a/kernel/futex/futex.h +++ b/kernel/futex/futex.h @@ -40,6 +40,8 @@ #define FLAGS_NUMA 0x0080 #define FLAGS_STRICT 0x0100 #define FLAGS_MPOL 0x0200 +#define FLAGS_ROBUST_UNLOCK 0x0400 +#define FLAGS_ROBUST_LIST32 0x0800 /* FUTEX_ to FLAGS_ */ static inline unsigned int futex_to_flags(unsigned int op) @@ -52,6 +54,12 @@ static inline unsigned int futex_to_flags(unsigned int op) if (op & FUTEX_CLOCK_REALTIME) flags |= FLAGS_CLOCKRT; + if (op & FUTEX_ROBUST_UNLOCK) + flags |= FLAGS_ROBUST_UNLOCK; + + if (op & FUTEX_ROBUST_LIST32) + flags |= FLAGS_ROBUST_LIST32; + return flags; } @@ -449,13 +457,16 @@ extern int futex_unqueue_multiple(struct futex_vector *v, int count); extern int futex_wait_multiple(struct futex_vector *vs, unsigned int count, struct hrtimer_sleeper *to); -extern int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset); +extern int futex_wake(u32 __user *uaddr, unsigned int flags, void __user *pop, + int nr_wake, u32 bitset); extern int futex_wake_op(u32 __user *uaddr1, unsigned int flags, u32 __user *uaddr2, int nr_wake, int nr_wake2, int op); -extern int futex_unlock_pi(u32 __user *uaddr, unsigned int flags); +extern int futex_unlock_pi(u32 __user *uaddr, unsigned int flags, void __user *pop); extern int futex_lock_pi(u32 __user *uaddr, unsigned int flags, ktime_t *time, int trylock); +bool futex_robust_list_clear_pending(void __user *pop, unsigned int flags); + #endif /* _FUTEX_H */ diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c index e037a97fe277..9dd5c0b1ac6a 100644 --- a/kernel/futex/pi.c +++ b/kernel/futex/pi.c @@ -1139,7 +1139,7 @@ out: * This is the in-kernel slowpath: we look up the PI state (if any), * and do the rt-mutex unlock. */ -int futex_unlock_pi(u32 __user *uaddr, unsigned int flags) +static int __futex_unlock_pi(u32 __user *uaddr, unsigned int flags) { u32 curval, uval, vpid = task_pid_vnr(current); union futex_key key = FUTEX_KEY_INIT; @@ -1148,7 +1148,6 @@ int futex_unlock_pi(u32 __user *uaddr, unsigned int flags) if (!IS_ENABLED(CONFIG_FUTEX_PI)) return -ENOSYS; - retry: if (get_user(uval, uaddr)) return -EFAULT; @@ -1302,3 +1301,15 @@ pi_faulted: return ret; } +int futex_unlock_pi(u32 __user *uaddr, unsigned int flags, void __user *pop) +{ + int ret = __futex_unlock_pi(uaddr, flags); + + if (ret || !(flags & FLAGS_ROBUST_UNLOCK)) + return ret; + + if (!futex_robust_list_clear_pending(pop, flags)) + return -EFAULT; + + return 0; +} diff --git a/kernel/futex/syscalls.c b/kernel/futex/syscalls.c index 8944ff4930d7..2fa19d9d008d 100644 --- a/kernel/futex/syscalls.c +++ b/kernel/futex/syscalls.c @@ -118,6 +118,13 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout, return -ENOSYS; } + if (flags & FLAGS_ROBUST_UNLOCK) { + if (cmd != FUTEX_WAKE && + cmd != FUTEX_WAKE_BITSET && + cmd != FUTEX_UNLOCK_PI) + return -ENOSYS; + } + switch (cmd) { case FUTEX_WAIT: val3 = FUTEX_BITSET_MATCH_ANY; @@ -128,7 +135,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout, val3 = FUTEX_BITSET_MATCH_ANY; fallthrough; case FUTEX_WAKE_BITSET: - return futex_wake(uaddr, flags, val, val3); + return futex_wake(uaddr, flags, uaddr2, val, val3); case FUTEX_REQUEUE: return futex_requeue(uaddr, flags, uaddr2, flags, val, val2, NULL, 0); case FUTEX_CMP_REQUEUE: @@ -141,7 +148,7 @@ long do_futex(u32 __user *uaddr, int op, u32 val, ktime_t *timeout, case FUTEX_LOCK_PI2: return futex_lock_pi(uaddr, flags, timeout, 0); case FUTEX_UNLOCK_PI: - return futex_unlock_pi(uaddr, flags); + return futex_unlock_pi(uaddr, flags, uaddr2); case FUTEX_TRYLOCK_PI: return futex_lock_pi(uaddr, flags, NULL, 1); case FUTEX_WAIT_REQUEUE_PI: @@ -375,7 +382,7 @@ SYSCALL_DEFINE4(futex_wake, if (!futex_validate_input(flags, mask)) return -EINVAL; - return futex_wake(uaddr, FLAGS_STRICT | flags, nr, mask); + return futex_wake(uaddr, FLAGS_STRICT | flags, NULL, nr, mask); } /* diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c index ceed9d879059..8f5e5d302356 100644 --- a/kernel/futex/waitwake.c +++ b/kernel/futex/waitwake.c @@ -149,13 +149,36 @@ void futex_wake_mark(struct wake_q_head *wake_q, struct futex_q *q) wake_q_add_safe(wake_q, p); } +/* + * If requested, clear the robust list pending op and unlock the futex + */ +static bool futex_robust_unlock(u32 __user *uaddr, unsigned int flags, void __user *pop) +{ + if (!(flags & FLAGS_ROBUST_UNLOCK)) + return true; + + /* First unlock the futex, which requires release semantics. */ + scoped_user_write_access(uaddr, efault) + unsafe_atomic_store_release_user(0, uaddr, efault); + + /* + * Clear the pending list op now. If that fails, then the task is in + * deeper trouble as the robust list head is usually part of the TLS. + * The chance of survival is close to zero. + */ + return futex_robust_list_clear_pending(pop, flags); + +efault: + return false; +} + /* * Wake up waiters matching bitset queued on this futex (uaddr). */ -int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset) +int futex_wake(u32 __user *uaddr, unsigned int flags, void __user *pop, int nr_wake, u32 bitset) { - struct futex_q *this, *next; union futex_key key = FUTEX_KEY_INIT; + struct futex_q *this, *next; DEFINE_WAKE_Q(wake_q); int ret; @@ -166,6 +189,9 @@ int futex_wake(u32 __user *uaddr, unsigned int flags, int nr_wake, u32 bitset) if (unlikely(ret != 0)) return ret; + if (!futex_robust_unlock(uaddr, flags, pop)) + return -EFAULT; + if ((flags & FLAGS_STRICT) && !nr_wake) return 0; From 042df0c1d48609a85580dcbaff498c95ced20a5f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:09:59 +0200 Subject: [PATCH 111/167] futex: Add robust futex unlock IP range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There will be a VDSO function to unlock robust futexes in user space. The unlock sequence is racy vs. clearing the list_pending_op pointer in the tasks robust list head. To plug this race the kernel needs to know the instruction window. As the VDSO is per MM the addresses are stored in mm_struct::futex. Architectures which implement support for this have to update these addresses when the VDSO is (re)mapped and indicate the pending op pointer size which is matching the IP. Arguably this could be resolved by chasing mm->context->vdso->image, but that's architecture specific and requires to touch quite some cache lines. Having it in mm::futex reduces the cache line impact and avoids having yet another set of architecture specific functionality. To support multi size robust list applications (gaming) this provides two ranges when COMPAT is enabled. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.718926819@kernel.org --- include/linux/futex.h | 21 +++++++++++++--- include/linux/futex_types.h | 28 ++++++++++++++++++++++ init/Kconfig | 6 +++++ kernel/futex/core.c | 48 +++++++++++++++++++++++++++++-------- 4 files changed, 90 insertions(+), 13 deletions(-) diff --git a/include/linux/futex.h b/include/linux/futex.h index 9e6218c2be66..cb2a182547dd 100644 --- a/include/linux/futex.h +++ b/include/linux/futex.h @@ -81,11 +81,9 @@ int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4) #ifdef CONFIG_FUTEX_PRIVATE_HASH int futex_hash_allocate_default(void); void futex_hash_free(struct mm_struct *mm); -void futex_mm_init(struct mm_struct *mm); #else /* CONFIG_FUTEX_PRIVATE_HASH */ static inline int futex_hash_allocate_default(void) { return 0; } static inline int futex_hash_free(struct mm_struct *mm) { return 0; } -static inline void futex_mm_init(struct mm_struct *mm) { } #endif /* !CONFIG_FUTEX_PRIVATE_HASH */ #else /* CONFIG_FUTEX */ @@ -104,7 +102,24 @@ static inline int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsig } static inline int futex_hash_allocate_default(void) { return 0; } static inline int futex_hash_free(struct mm_struct *mm) { return 0; } -static inline void futex_mm_init(struct mm_struct *mm) { } #endif /* !CONFIG_FUTEX */ +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK +void futex_reset_cs_ranges(struct futex_mm_data *fd); + +static inline void futex_set_vdso_cs_range(struct futex_mm_data *fd, unsigned int idx, + unsigned long start, unsigned long end, bool sz32) +{ + fd->unlock.cs_ranges[idx].start_ip = start; + fd->unlock.cs_ranges[idx].len = end - start; + fd->unlock.cs_ranges[idx].pop_size32 = sz32; +} +#endif /* CONFIG_FUTEX_ROBUST_UNLOCK */ + +#if defined(CONFIG_FUTEX_PRIVATE_HASH) || defined(CONFIG_FUTEX_ROBUST_UNLOCK) +void futex_mm_init(struct mm_struct *mm); +#else +static inline void futex_mm_init(struct mm_struct *mm) { } +#endif + #endif /* _LINUX_FUTEX_H */ diff --git a/include/linux/futex_types.h b/include/linux/futex_types.h index d41557d2790d..d320c0571f0c 100644 --- a/include/linux/futex_types.h +++ b/include/linux/futex_types.h @@ -55,12 +55,40 @@ struct futex_mm_phash { struct futex_mm_phash { }; #endif /* !CONFIG_FUTEX_ROBUST_UNLOCK */ +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK +/** + * struct futex_unlock_cs_range - Range for the VDSO unlock critical section + * @start_ip: The start IP of the robust futex unlock critical section (inclusive) + * @len: The length of the robust futex unlock critical section + * @pop_size32: Pending OP pointer size indicator. 0 == 64-bit, 1 == 32-bit + */ +struct futex_unlock_cs_range { + unsigned long start_ip; + unsigned int len; + unsigned int pop_size32; +}; + +#define FUTEX_ROBUST_MAX_CS_RANGES (1 + IS_ENABLED(CONFIG_COMPAT)) + +/** + * struct futex_unlock_cs_ranges - Futex unlock VSDO critical sections + * @cs_ranges: Array of critical section ranges + */ +struct futex_unlock_cs_ranges { + struct futex_unlock_cs_range cs_ranges[FUTEX_ROBUST_MAX_CS_RANGES]; +}; +#else /* CONFIG_FUTEX_ROBUST_UNLOCK */ +struct futex_unlock_cs_ranges { }; +#endif /* !CONFIG_FUTEX_ROBUST_UNLOCK */ + /** * struct futex_mm_data - Futex related per MM data * @phash: Futex private hash related data + * @unlock: Futex unlock VDSO critical sections */ struct futex_mm_data { struct futex_mm_phash phash; + struct futex_unlock_cs_ranges unlock; }; #else /* CONFIG_FUTEX */ struct futex_sched_data { }; diff --git a/init/Kconfig b/init/Kconfig index 2937c4d308ae..165b08e336a8 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1842,6 +1842,12 @@ config FUTEX_MPOL depends on FUTEX && NUMA default y +config HAVE_FUTEX_ROBUST_UNLOCK + bool + +config FUTEX_ROBUST_UNLOCK + def_bool FUTEX && HAVE_GENERIC_VDSO && GENERIC_IRQ_ENTRY && RSEQ && HAVE_FUTEX_ROBUST_UNLOCK + config EPOLL bool "Enable eventpoll support" if EXPERT default y diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 77ccb7787c34..aad6e501fabd 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1761,11 +1761,11 @@ static bool futex_ref_is_dead(struct futex_private_hash *fph) return atomic_long_read(&mm->futex.phash.atomic) == 0; } -void futex_mm_init(struct mm_struct *mm) +static void futex_hash_init_mm(struct futex_mm_data *fd) { - memset(&mm->futex, 0, sizeof(mm->futex)); - mutex_init(&mm->futex.phash.lock); - mm->futex.phash.batches = get_state_synchronize_rcu(); + memset(&fd->phash, 0, sizeof(fd->phash)); + mutex_init(&fd->phash.lock); + fd->phash.batches = get_state_synchronize_rcu(); } void futex_hash_free(struct mm_struct *mm) @@ -1969,19 +1969,47 @@ static int futex_hash_get_slots(void) return fph->hash_mask + 1; return 0; } +#else /* CONFIG_FUTEX_PRIVATE_HASH */ +static inline int futex_hash_allocate(unsigned int hslots, unsigned int flags) { return -EINVAL; } +static inline int futex_hash_get_slots(void) { return 0; } +static inline void futex_hash_init_mm(struct futex_mm_data *fd) { } +#endif /* !CONFIG_FUTEX_PRIVATE_HASH */ -#else - -static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK +static void futex_invalidate_cs_ranges(struct futex_mm_data *fd) { - return -EINVAL; + /* + * Invalidate start_ip so that the quick check fails for ip >= start_ip + * if VDSO is not mapped or the second slot is not available for compat + * tasks as they use VDSO32 which does not provide the 64-bit pointer + * variant. + */ + for (int i = 0; i < FUTEX_ROBUST_MAX_CS_RANGES; i++) + fd->unlock.cs_ranges[i].start_ip = ~0UL; } -static int futex_hash_get_slots(void) +void futex_reset_cs_ranges(struct futex_mm_data *fd) { - return 0; + memset(fd->unlock.cs_ranges, 0, sizeof(fd->unlock.cs_ranges)); + futex_invalidate_cs_ranges(fd); } +static void futex_robust_unlock_init_mm(struct futex_mm_data *fd) +{ + /* mm_dup() preserves the range, mm_alloc() clears it */ + if (!fd->unlock.cs_ranges[0].start_ip) + futex_invalidate_cs_ranges(fd); +} +#else /* CONFIG_FUTEX_ROBUST_UNLOCK */ +static inline void futex_robust_unlock_init_mm(struct futex_mm_data *fd) { } +#endif /* !CONFIG_FUTEX_ROBUST_UNLOCK */ + +#if defined(CONFIG_FUTEX_PRIVATE_HASH) || defined(CONFIG_FUTEX_ROBUST_UNLOCK) +void futex_mm_init(struct mm_struct *mm) +{ + futex_hash_init_mm(&mm->futex); + futex_robust_unlock_init_mm(&mm->futex); +} #endif int futex_hash_prctl(unsigned long arg2, unsigned long arg3, unsigned long arg4) From 7010c39d8fc5063af69ee63f905e592e046f8e5d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:10:04 +0200 Subject: [PATCH 112/167] futex: Provide infrastructure to plug the non contended robust futex unlock race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the FUTEX_ROBUST_UNLOCK mechanism is used for unlocking (PI-)futexes, then the unlock sequence in user space looks like this: 1) robust_list_set_op_pending(mutex); 2) robust_list_remove(mutex); lval = gettid(); 3) if (atomic_try_cmpxchg(&mutex->lock, lval, 0)) 4) robust_list_clear_op_pending(); else 5) sys_futex(OP | FUTEX_ROBUST_UNLOCK, ....); That still leaves a minimal race window between #3 and #4 where the mutex could be acquired by some other task, which observes that it is the last user and: 1) unmaps the mutex memory 2) maps a different file, which ends up covering the same address When then the original task exits before reaching #5 then the kernel robust list handling observes the pending op entry and tries to fix up user space. In case that the newly mapped data contains the TID of the exiting thread at the address of the mutex/futex the kernel will set the owner died bit in that memory and therefore corrupt unrelated data. On X86 this boils down to this simplified assembly sequence: mov %esi,%eax // Load TID into EAX xor %ecx,%ecx // Set ECX to 0 #3 lock cmpxchg %ecx,(%rdi) // Try the TID -> 0 transition .Lstart: jnz .Lend #4 movq %rcx,(%rdx) // Clear list_op_pending .Lend: If the cmpxchg() succeeds and the task is interrupted before it can clear list_op_pending in the robust list head (#4) and the task crashes in a signal handler or gets killed then it ends up in do_exit() and subsequently in the robust list handling, which then might run into the unmap/map issue described above. This is only relevant when user space was interrupted and a signal is pending. The fix-up has to be done before signal delivery is attempted because: 1) The signal might be fatal so get_signal() ends up in do_exit() 2) The signal handler might crash or the task is killed before returning from the handler. At that point the instruction pointer in pt_regs is not longer the instruction pointer of the initially interrupted unlock sequence. The right place to handle this is in __exit_to_user_mode_loop() before invoking arch_do_signal_or_restart() as this covers obviously both scenarios. As this is only relevant when the task was interrupted in user space, this is tied to RSEQ and the generic entry code as RSEQ keeps track of user space interrupts unconditionally even if the task does not have a RSEQ region installed. That makes the decision very lightweight: if (current->rseq.user_irq && within(regs, csr->unlock_ip_range)) futex_fixup_robust_unlock(regs, csr); futex_fixup_robust_unlock() then invokes a architecture specific function to return the pending op pointer or NULL. The function evaluates the register content to decide whether the pending ops pointer in the robust list head needs to be cleared. Assuming the above unlock sequence, then on x86 this decision is the trivial evaluation of the zero flag: return regs->eflags & X86_EFLAGS_ZF ? regs->dx : NULL; Other architectures might need to do more complex evaluations due to LLSC, but the approach is valid in general. The size of the pointer is determined from the matching range struct, which covers both 32-bit and 64-bit builds including COMPAT. The unlock sequence is going to be placed in the VDSO so that the kernel can keep everything synchronized, especially the register usage. The resulting code sequence for user space is: if (__vdso_futex_robust_list$SZ_try_unlock(lock, tid, &pending_op) != tid) err = sys_futex($OP | FUTEX_ROBUST_UNLOCK,....); Both the VDSO unlock and the kernel side unlock ensure that the pending_op pointer is always cleared when the lock becomes unlocked. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.773669210@kernel.org --- include/linux/futex.h | 39 +++++++++++++++++++++++++++++++- include/vdso/futex.h | 52 +++++++++++++++++++++++++++++++++++++++++++ kernel/entry/common.c | 9 +++++--- kernel/futex/core.c | 18 +++++++++++++++ 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 include/vdso/futex.h diff --git a/include/linux/futex.h b/include/linux/futex.h index cb2a182547dd..51f4ccdc9092 100644 --- a/include/linux/futex.h +++ b/include/linux/futex.h @@ -105,7 +105,41 @@ static inline int futex_hash_free(struct mm_struct *mm) { return 0; } #endif /* !CONFIG_FUTEX */ #ifdef CONFIG_FUTEX_ROBUST_UNLOCK +#include + void futex_reset_cs_ranges(struct futex_mm_data *fd); +void __futex_fixup_robust_unlock(struct pt_regs *regs, struct futex_unlock_cs_range *csr); + +static inline bool futex_within_robust_unlock(struct pt_regs *regs, + struct futex_unlock_cs_range *csr) +{ + unsigned long ip = instruction_pointer(regs); + + return ip >= csr->start_ip && ip < csr->start_ip + csr->len; +} + +static inline void futex_fixup_robust_unlock(struct pt_regs *regs) +{ + struct futex_unlock_cs_range *csr; + + /* + * Avoid dereferencing current->mm if not returning from interrupt. + * current->rseq.event is going to be used subsequently, so bringing the + * cache line in is not a big deal. + */ + if (!current->rseq.event.user_irq) + return; + + csr = current->mm->futex.unlock.cs_ranges; + + /* The loop is optimized out for !COMPAT */ + for (int r = 0; r < FUTEX_ROBUST_MAX_CS_RANGES; r++, csr++) { + if (unlikely(futex_within_robust_unlock(regs, csr))) { + __futex_fixup_robust_unlock(regs, csr); + return; + } + } +} static inline void futex_set_vdso_cs_range(struct futex_mm_data *fd, unsigned int idx, unsigned long start, unsigned long end, bool sz32) @@ -114,7 +148,10 @@ static inline void futex_set_vdso_cs_range(struct futex_mm_data *fd, unsigned in fd->unlock.cs_ranges[idx].len = end - start; fd->unlock.cs_ranges[idx].pop_size32 = sz32; } -#endif /* CONFIG_FUTEX_ROBUST_UNLOCK */ +#else /* CONFIG_FUTEX_ROBUST_UNLOCK */ +static inline void futex_fixup_robust_unlock(struct pt_regs *regs) { } +#endif /* !CONFIG_FUTEX_ROBUST_UNLOCK */ + #if defined(CONFIG_FUTEX_PRIVATE_HASH) || defined(CONFIG_FUTEX_ROBUST_UNLOCK) void futex_mm_init(struct mm_struct *mm); diff --git a/include/vdso/futex.h b/include/vdso/futex.h new file mode 100644 index 000000000000..3cd175eefe64 --- /dev/null +++ b/include/vdso/futex.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _VDSO_FUTEX_H +#define _VDSO_FUTEX_H + +#include + +/** + * __vdso_futex_robust_list64_try_unlock - Try to unlock an uncontended robust futex + * with a 64-bit pending op pointer + * @lock: Pointer to the futex lock object + * @tid: The TID of the calling task + * @pop: Pointer to the task's robust_list_head::list_pending_op + * + * Return: The content of *@lock. On success this is the same as @tid. + * + * The function implements: + * if (atomic_try_cmpxchg(lock, &tid, 0)) + * *op = NULL; + * return tid; + * + * There is a race between a successful unlock and clearing the pending op + * pointer in the robust list head. If the calling task is interrupted in the + * race window and has to handle a (fatal) signal on return to user space then + * the kernel handles the clearing of @pending_op before attempting to deliver + * the signal. That ensures that a task cannot exit with a potentially invalid + * pending op pointer. + * + * User space uses it in the following way: + * + * if (__vdso_futex_robust_list64_try_unlock(lock, tid, &pending_op) != tid) + * err = sys_futex($OP | FUTEX_ROBUST_UNLOCK,....); + * + * If the unlock attempt fails due to the FUTEX_WAITERS bit set in the lock, + * then the syscall does the unlock, clears the pending op pointer and wakes the + * requested number of waiters. + */ +__u32 __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop); + +/** + * __vdso_futex_robust_list32_try_unlock - Try to unlock an uncontended robust futex + * with a 32-bit pending op pointer + * @lock: Pointer to the futex lock object + * @tid: The TID of the calling task + * @pop: Pointer to the task's robust_list_head::list_pending_op + * + * Return: The content of *@lock. On success this is the same as @tid. + * + * Same as __vdso_futex_robust_list64_try_unlock() just with a 32-bit @pop pointer. + */ +__u32 __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop); + +#endif diff --git a/kernel/entry/common.c b/kernel/entry/common.c index 19d2244a9fef..e3d381fd3d25 100644 --- a/kernel/entry/common.c +++ b/kernel/entry/common.c @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 -#include -#include +#include #include +#include #include #include #include +#include #include /* Workaround to allow gradual conversion of architecture code */ @@ -60,8 +61,10 @@ static __always_inline unsigned long __exit_to_user_mode_loop(struct pt_regs *re if (ti_work & _TIF_PATCH_PENDING) klp_update_patch_state(current); - if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) + if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) { + futex_fixup_robust_unlock(regs); arch_do_signal_or_restart(regs); + } if (ti_work & _TIF_NOTIFY_RESUME) resume_user_mode_work(regs); diff --git a/kernel/futex/core.c b/kernel/futex/core.c index aad6e501fabd..6ea4a97796a1 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -46,6 +46,8 @@ #include #include +#include + #include "futex.h" #include "../locking/rtmutex_common.h" @@ -1446,6 +1448,22 @@ bool futex_robust_list_clear_pending(void __user *pop, unsigned int flags) return robust_list_clear_pending(pop); } +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK +void __futex_fixup_robust_unlock(struct pt_regs *regs, struct futex_unlock_cs_range *csr) +{ + /* + * arch_futex_robust_unlock_get_pop() returns the list pending op pointer from + * @regs if the try_cmpxchg() succeeded. + */ + void __user *pop = arch_futex_robust_unlock_get_pop(regs); + + if (!pop) + return; + + futex_robust_list_clear_pending(pop, csr->pop_size32 ? FLAGS_ROBUST_LIST32 : 0); +} +#endif /* CONFIG_FUTEX_ROBUST_UNLOCK */ + static void futex_cleanup(struct task_struct *tsk) { if (unlikely(tsk->futex.robust_list)) { From 61cfc8e372d1971e0a96d3f1f8b5ee29916b3385 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:10:08 +0200 Subject: [PATCH 113/167] x86/vdso: Prepare for robust futex unlock support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There will be a VDSO function to unlock non-contended robust futexes in user space. The unlock sequence is racy vs. clearing the list_pending_op pointer in the task's robust list head. To plug this race the kernel needs to know the critical section window so it can clear the pointer when the task is interrupted within that race window. The window is determined by labels in the inline assembly. Add these symbols to the vdso2c generator and use them in the VDSO VMA code to update the critical section addresses in mm_struct::futex on (re)map(). The symbols are not exported to user space, but available in the debug version of the vDSO. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Link: https://patch.msgid.link/20260602090535.828312645@kernel.org --- arch/x86/entry/vdso/vma.c | 29 +++++++++++++++++++++++++++++ arch/x86/include/asm/vdso.h | 4 ++++ arch/x86/tools/vdso2c.c | 16 ++++++++++------ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c index a6bfcc8243cd..9a953e7c76db 100644 --- a/arch/x86/entry/vdso/vma.c +++ b/arch/x86/entry/vdso/vma.c @@ -6,6 +6,7 @@ */ #include #include +#include #include #include #include @@ -73,6 +74,31 @@ static void vdso_fix_landing(const struct vdso_image *image, regs->ip = new_vma->vm_start + ipoffset; } +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK +static void vdso_futex_robust_unlock_update_ips(void) +{ + const struct vdso_image *image = current->mm->context.vdso_image; + unsigned long vdso = (unsigned long) current->mm->context.vdso; + struct futex_mm_data *fd = ¤t->mm->futex; + unsigned int idx = 0; + + futex_reset_cs_ranges(fd); + +#ifdef CONFIG_X86_64 + futex_set_vdso_cs_range(fd, idx, vdso + image->sym___futex_list64_try_unlock_cs_start, + vdso + image->sym___futex_list64_try_unlock_cs_end, false); + idx++; +#endif /* CONFIG_X86_64 */ + +#if defined(CONFIG_X86_32) || defined(CONFIG_COMPAT) + futex_set_vdso_cs_range(fd, idx, vdso + image->sym___futex_list32_try_unlock_cs_start, + vdso + image->sym___futex_list32_try_unlock_cs_end, true); +#endif /* CONFIG_X86_32 || CONFIG_COMPAT */ +} +#else +static inline void vdso_futex_robust_unlock_update_ips(void) { } +#endif + static int vdso_mremap(const struct vm_special_mapping *sm, struct vm_area_struct *new_vma) { @@ -80,6 +106,7 @@ static int vdso_mremap(const struct vm_special_mapping *sm, vdso_fix_landing(image, new_vma); current->mm->context.vdso = (void __user *)new_vma->vm_start; + vdso_futex_robust_unlock_update_ips(); return 0; } @@ -185,6 +212,8 @@ static int map_vdso(const struct vdso_image *image, unsigned long addr) current->mm->context.vdso = (void __user *)text_start; current->mm->context.vdso_image = image; + vdso_futex_robust_unlock_update_ips(); + up_fail: mmap_write_unlock(mm); return ret; diff --git a/arch/x86/include/asm/vdso.h b/arch/x86/include/asm/vdso.h index f2d49212ae90..4e735158c1b2 100644 --- a/arch/x86/include/asm/vdso.h +++ b/arch/x86/include/asm/vdso.h @@ -24,6 +24,10 @@ struct vdso_image { long sym_int80_landing_pad; long sym_vdso32_sigreturn_landing_pad; long sym_vdso32_rt_sigreturn_landing_pad; + long sym___futex_list64_try_unlock_cs_start; + long sym___futex_list64_try_unlock_cs_end; + long sym___futex_list32_try_unlock_cs_start; + long sym___futex_list32_try_unlock_cs_end; }; extern const struct vdso_image vdso64_image; diff --git a/arch/x86/tools/vdso2c.c b/arch/x86/tools/vdso2c.c index b8a555763f43..64a636b9fc37 100644 --- a/arch/x86/tools/vdso2c.c +++ b/arch/x86/tools/vdso2c.c @@ -75,12 +75,16 @@ struct vdso_sym { }; struct vdso_sym required_syms[] = { - {"__kernel_vsyscall", true}, - {"__kernel_sigreturn", true}, - {"__kernel_rt_sigreturn", true}, - {"int80_landing_pad", true}, - {"vdso32_rt_sigreturn_landing_pad", true}, - {"vdso32_sigreturn_landing_pad", true}, + {"__kernel_vsyscall", true}, + {"__kernel_sigreturn", true}, + {"__kernel_rt_sigreturn", true}, + {"int80_landing_pad", true}, + {"vdso32_rt_sigreturn_landing_pad", true}, + {"vdso32_sigreturn_landing_pad", true}, + {"__futex_list64_try_unlock_cs_start", true}, + {"__futex_list64_try_unlock_cs_end", true}, + {"__futex_list32_try_unlock_cs_start", true}, + {"__futex_list32_try_unlock_cs_end", true}, }; __attribute__((format(printf, 1, 2))) __attribute__((noreturn)) From a2274cc0091ed4fdce10fad68d08c529b8d3e7dd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 2 Jun 2026 11:10:12 +0200 Subject: [PATCH 114/167] x86/vdso: Implement __vdso_futex_robust_try_unlock() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the FUTEX_ROBUST_UNLOCK mechanism is used for unlocking (PI-)futexes, then the unlock sequence in userspace looks like this: 1) robust_list_set_op_pending(mutex); 2) robust_list_remove(mutex); lval = gettid(); 3) if (atomic_try_cmpxchg(&mutex->lock, lval, 0)) 4) robust_list_clear_op_pending(); else 5) sys_futex(OP,...FUTEX_ROBUST_UNLOCK); That still leaves a minimal race window between #3 and #4 where the mutex could be acquired by some other task which observes that it is the last user and: 1) unmaps the mutex memory 2) maps a different file, which ends up covering the same address When then the original task exits before reaching #5 then the kernel robust list handling observes the pending op entry and tries to fix up user space. In case that the newly mapped data contains the TID of the exiting thread at the address of the mutex/futex the kernel will set the owner died bit in that memory and therefore corrupt unrelated data. Provide a VDSO function which exposes the critical section window in the VDSO symbol table. The resulting addresses are updated in the task's mm when the VDSO is (re)map()'ed. The core code detects when a task was interrupted within the critical section and is about to deliver a signal. It then invokes an architecture specific function which determines whether the pending op pointer has to be cleared or not. The unlock assembly sequence on 64-bit is: mov %esi,%eax // Load TID into EAX xor %ecx,%ecx // Set ECX to 0 lock cmpxchg %ecx,(%rdi) // Try the TID -> 0 transition .Lstart: jnz .Lend movq %rcx,(%rdx) // Clear list_op_pending .Lend: ret So the decision can be simply based on the ZF state in regs->flags. The pending op pointer is always in DX independent of the build mode (32/64-bit) to make the pending op pointer retrieval uniform. The size of the pointer is stored in the matching criticial section range struct and the core code retrieves it from there. So the pointer retrieval function does not have to care. It is bit-size independent: return regs->flags & X86_EFLAGS_ZF ? regs->dx : NULL; There are two entry points to handle the different robust list pending op pointer size: __vdso_futex_robust_list64_try_unlock() __vdso_futex_robust_list32_try_unlock() The 32-bit VDSO provides only __vdso_futex_robust_list32_try_unlock(). The 64-bit VDSO provides always __vdso_futex_robust_list64_try_unlock() and when COMPAT is enabled also the list32 variant, which is required to support multi-size robust list pointers used by gaming emulators. The unlock function is inspired by an idea from Mathieu Desnoyers. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: André Almeida Acked-by: Uros Bizjak Link: https://lore.kernel.org/20260311185409.1988269-1-mathieu.desnoyers@efficios.com Link: https://patch.msgid.link/20260602090535.883796247@kernel.org --- arch/x86/Kconfig | 1 + arch/x86/entry/vdso/common/vfutex.c | 71 ++++++++++++++++++++++++ arch/x86/entry/vdso/vdso32/Makefile | 5 +- arch/x86/entry/vdso/vdso32/vdso32.lds.S | 3 + arch/x86/entry/vdso/vdso32/vfutex.c | 1 + arch/x86/entry/vdso/vdso64/Makefile | 7 ++- arch/x86/entry/vdso/vdso64/vdso64.lds.S | 7 +++ arch/x86/entry/vdso/vdso64/vdsox32.lds.S | 7 +++ arch/x86/entry/vdso/vdso64/vfutex.c | 1 + arch/x86/include/asm/futex_robust.h | 19 +++++++ 10 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 arch/x86/entry/vdso/common/vfutex.c create mode 100644 arch/x86/entry/vdso/vdso32/vfutex.c create mode 100644 arch/x86/entry/vdso/vdso64/vfutex.c create mode 100644 arch/x86/include/asm/futex_robust.h diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 1ce62a996192..fdaef60b46d6 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -239,6 +239,7 @@ config X86 select HAVE_EFFICIENT_UNALIGNED_ACCESS select HAVE_EISA if X86_32 select HAVE_EXIT_THREAD + select HAVE_FUTEX_ROBUST_UNLOCK select HAVE_GENERIC_TIF_BITS select HAVE_GUP_FAST select HAVE_FENTRY if X86_64 || DYNAMIC_FTRACE diff --git a/arch/x86/entry/vdso/common/vfutex.c b/arch/x86/entry/vdso/common/vfutex.c new file mode 100644 index 000000000000..454f059278e4 --- /dev/null +++ b/arch/x86/entry/vdso/common/vfutex.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include + +/* + * Assembly template for the try unlock functions. The basic functionality is: + * + * mov esi, %eax Move the TID into EAX + * xor %ecx, %ecx Clear ECX + * lock_cmpxchgl %ecx, (%rdi) Attempt the TID -> 0 transition + * .Lcs_start: Start of the critical section + * jnz .Lcs_end If cmpxchl failed jump to the end + * .Lcs_success: Start of the success section + * movq %rcx, (%rdx) Set the pending op pointer to 0 + * .Lcs_end: End of the critical section + * + * .Lcs_start and .Lcs_end establish the critical section range. .Lcs_success is + * technically not required, but there for illustration, debugging and testing. + * + * When CONFIG_COMPAT is enabled then the 64-bit VDSO provides two functions. + * One for the regular 64-bit sized pending operation pointer and one for a + * 32-bit sized pointer to support gaming emulators. + * + * The 32-bit VDSO provides only the one for 32-bit sized pointers. + */ +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +#define LABEL(prefix, which) __stringify(prefix##_try_unlock_cs_##which:) + +#define JNZ_END(prefix) "jnz " __stringify(prefix) "_try_unlock_cs_end\n" + +#define CLEAR_POPQ "movq %[zero], %a[pop]\n" +#define CLEAR_POPL "movl %k[zero], %a[pop]\n" + +#define futex_robust_try_unlock(prefix, clear_pop, __lock, __tid, __pop)\ +({ \ + asm volatile ( \ + " \n" \ + " lock cmpxchgl %k[zero], %a[lock] \n" \ + " \n" \ + LABEL(prefix, start) \ + " \n" \ + JNZ_END(prefix) \ + " \n" \ + LABEL(prefix, success) \ + " \n" \ + clear_pop \ + " \n" \ + LABEL(prefix, end) \ + : [tid] "+&a" (__tid) \ + : [lock] "D" (__lock), \ + [pop] "d" (__pop), \ + [zero] "r" (0UL) \ + : "memory" \ + ); \ + __tid; \ +}) + +#ifdef __x86_64__ +__u32 __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop) +{ + return futex_robust_try_unlock(__futex_list64, CLEAR_POPQ, lock, tid, pop); +} +#endif /* __x86_64__ */ + +#if defined(CONFIG_X86_32) || defined(CONFIG_COMPAT) +__u32 __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop) +{ + return futex_robust_try_unlock(__futex_list32, CLEAR_POPL, lock, tid, pop); +} +#endif /* CONFIG_X86_32 || CONFIG_COMPAT */ diff --git a/arch/x86/entry/vdso/vdso32/Makefile b/arch/x86/entry/vdso/vdso32/Makefile index ded4fc6a48cd..ab4b1f635f66 100644 --- a/arch/x86/entry/vdso/vdso32/Makefile +++ b/arch/x86/entry/vdso/vdso32/Makefile @@ -7,8 +7,9 @@ vdsos-y := 32 # Files to link into the vDSO: -vobjs-y := note.o vclock_gettime.o vgetcpu.o -vobjs-y += system_call.o sigreturn.o +vobjs-y := note.o vclock_gettime.o vgetcpu.o +vobjs-y += system_call.o sigreturn.o +vobjs-$(CONFIG_FUTEX_ROBUST_UNLOCK) += vfutex.o # Compilation flags flags-y := -DBUILD_VDSO32 -m32 -mregparm=0 diff --git a/arch/x86/entry/vdso/vdso32/vdso32.lds.S b/arch/x86/entry/vdso/vdso32/vdso32.lds.S index 55554f80d930..cee8f7f9fe80 100644 --- a/arch/x86/entry/vdso/vdso32/vdso32.lds.S +++ b/arch/x86/entry/vdso/vdso32/vdso32.lds.S @@ -30,6 +30,9 @@ VERSION __vdso_clock_gettime64; __vdso_clock_getres_time64; __vdso_getcpu; +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK + __vdso_futex_robust_list32_try_unlock; +#endif }; LINUX_2.5 { diff --git a/arch/x86/entry/vdso/vdso32/vfutex.c b/arch/x86/entry/vdso/vdso32/vfutex.c new file mode 100644 index 000000000000..940a6ee30026 --- /dev/null +++ b/arch/x86/entry/vdso/vdso32/vfutex.c @@ -0,0 +1 @@ +#include "common/vfutex.c" diff --git a/arch/x86/entry/vdso/vdso64/Makefile b/arch/x86/entry/vdso/vdso64/Makefile index bfffaf1aeecc..7c0790065b5e 100644 --- a/arch/x86/entry/vdso/vdso64/Makefile +++ b/arch/x86/entry/vdso/vdso64/Makefile @@ -8,9 +8,10 @@ vdsos-y := 64 vdsos-$(CONFIG_X86_X32_ABI) += x32 # Files to link into the vDSO: -vobjs-y := note.o vclock_gettime.o vgetcpu.o -vobjs-y += vgetrandom.o vgetrandom-chacha.o -vobjs-$(CONFIG_X86_SGX) += vsgx.o +vobjs-y := note.o vclock_gettime.o vgetcpu.o +vobjs-y += vgetrandom.o vgetrandom-chacha.o +vobjs-$(CONFIG_X86_SGX) += vsgx.o +vobjs-$(CONFIG_FUTEX_ROBUST_UNLOCK) += vfutex.o # Compilation flags flags-y := -DBUILD_VDSO64 -m64 -mcmodel=small diff --git a/arch/x86/entry/vdso/vdso64/vdso64.lds.S b/arch/x86/entry/vdso/vdso64/vdso64.lds.S index 5ce3f2b6373a..4a72122da81b 100644 --- a/arch/x86/entry/vdso/vdso64/vdso64.lds.S +++ b/arch/x86/entry/vdso/vdso64/vdso64.lds.S @@ -32,6 +32,13 @@ VERSION { #endif getrandom; __vdso_getrandom; + +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK + __vdso_futex_robust_list64_try_unlock; +#ifdef CONFIG_COMPAT + __vdso_futex_robust_list32_try_unlock; +#endif +#endif local: *; }; } diff --git a/arch/x86/entry/vdso/vdso64/vdsox32.lds.S b/arch/x86/entry/vdso/vdso64/vdsox32.lds.S index 3dbd20c8dacc..b917dc69f62f 100644 --- a/arch/x86/entry/vdso/vdso64/vdsox32.lds.S +++ b/arch/x86/entry/vdso/vdso64/vdsox32.lds.S @@ -22,6 +22,13 @@ VERSION { __vdso_getcpu; __vdso_time; __vdso_clock_getres; + +#ifdef CONFIG_FUTEX_ROBUST_UNLOCK + __vdso_futex_robust_list64_try_unlock; +#ifdef CONFIG_COMPAT + __vdso_futex_robust_list32_try_unlock; +#endif +#endif local: *; }; } diff --git a/arch/x86/entry/vdso/vdso64/vfutex.c b/arch/x86/entry/vdso/vdso64/vfutex.c new file mode 100644 index 000000000000..940a6ee30026 --- /dev/null +++ b/arch/x86/entry/vdso/vdso64/vfutex.c @@ -0,0 +1 @@ +#include "common/vfutex.c" diff --git a/arch/x86/include/asm/futex_robust.h b/arch/x86/include/asm/futex_robust.h new file mode 100644 index 000000000000..e87954703ae2 --- /dev/null +++ b/arch/x86/include/asm/futex_robust.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_FUTEX_ROBUST_H +#define _ASM_X86_FUTEX_ROBUST_H + +#include + +static __always_inline void __user *x86_futex_robust_unlock_get_pop(struct pt_regs *regs) +{ + /* + * If ZF is set then the cmpxchg succeeded and the pending op pointer + * needs to be cleared. + */ + return regs->flags & X86_EFLAGS_ZF ? (void __user *)regs->dx : NULL; +} + +#define arch_futex_robust_unlock_get_pop(regs) \ + x86_futex_robust_unlock_get_pop(regs) + +#endif /* _ASM_X86_FUTEX_ROBUST_H */ From 3f63e2545978abda58f2cf7ff0d7a2942965e8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Almeida?= Date: Tue, 2 Jun 2026 11:10:16 +0200 Subject: [PATCH 115/167] Documentation: futex: Add a note about robust list race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a note to the documentation giving a brief explanation why doing a robust futex release in userspace is racy, what should be done to avoid it and provide links to read more. [ tglx: Fixed a few typos ] Signed-off-by: André Almeida Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260329-tonyk-vdso_test-v2-1-b7db810e44a1@igalia.com Link: https://patch.msgid.link/20260602090535.936286833@kernel.org --- Documentation/locking/robust-futex-ABI.rst | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Documentation/locking/robust-futex-ABI.rst b/Documentation/locking/robust-futex-ABI.rst index f24904f1c16f..5e6a0665b8ba 100644 --- a/Documentation/locking/robust-futex-ABI.rst +++ b/Documentation/locking/robust-futex-ABI.rst @@ -153,6 +153,9 @@ On removal: 3) release the futex lock, and 4) clear the 'lock_op_pending' word. +Please note that the removal of a robust futex purely in userspace is +racy. Refer to the next chapter to learn more and how to avoid this. + On exit, the kernel will consider the address stored in 'list_op_pending' and the address of each 'lock word' found by walking the list starting at 'head'. For each such address, if the bottom 30 @@ -182,3 +185,44 @@ any point: When the kernel sees a list entry whose 'lock word' doesn't have the current threads TID in the lower 30 bits, it does nothing with that entry, and goes on to the next entry. + +Robust release is racy +---------------------- + +The removal of a robust futex from the list is racy when doing it solely in +userspace. Quoting Thomas Gleixner for the explanation: + + The robust futex unlock mechanism is racy in respect to the clearing of the + robust_list_head::list_op_pending pointer because unlock and clearing the + pointer are not atomic. The race window is between the unlock and clearing + the pending op pointer. If the task is forced to exit in this window, exit + will access a potentially invalid pending op pointer when cleaning up the + robust list. That happens if another task manages to unmap the object + containing the lock before the cleanup, which results in an UAF. In the + worst case this UAF can lead to memory corruption when unrelated content + has been mapped to the same address by the time the access happens. + +A full in-depth analysis can be read at +https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/ + +To overcome that, the kernel needs to participate in the lock release operation. +This ensures that the release happens "atomically" with regard to releasing +the lock and removing the address from ``list_op_pending``. If the release is +interrupted by a signal, the kernel will also verify if it interrupted the +release operation. + +For the contended unlock case, where other threads are waiting for the lock +release, there's the ``FUTEX_ROBUST_UNLOCK`` operation feature flag for the +``futex()`` system call, which must be used with one of the following +operations: ``FUTEX_WAKE``, ``FUTEX_WAKE_BITSET`` or ``FUTEX_UNLOCK_PI``. +The kernel will release the lock (set the futex word to zero), clean the +``list_op_pending`` field. Then, it will proceed with the normal wake path. + +For the non-contended path, there's still a race between checking the futex word +and clearing the ``list_op_pending`` field. To solve this without the need of a +complete system call, userspace should call the virtual syscall +``__vdso_futex_robust_listXX_try_unlock()`` (where XX is either 32 or 64, +depending on the size of the pointer). If the vDSO call succeeds, it means that +it released the lock and cleared ``list_op_pending``. If it fails, that means +that there are waiters for this lock and a call to ``futex()`` syscall with +``FUTEX_ROBUST_UNLOCK`` is needed. From 608323bf7bb85bbb647eca4373acef247f105e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Almeida?= Date: Tue, 2 Jun 2026 11:10:21 +0200 Subject: [PATCH 116/167] selftests: futex: Add tests for robust release operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests for __vdso_futex_robust_listXX_try_unlock() and for the futex() op FUTEX_ROBUST_UNLOCK. Test the contended and uncontended cases for the vDSO functions and all ops combinations for FUTEX_ROBUST_UNLOCK. [ tglx: Replace the VDSO function lookup ] Signed-off-by: André Almeida Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260329-tonyk-vdso_test-v2-2-b7db810e44a1@igalia.com Link: https://patch.msgid.link/20260602090535.988101541@kernel.org --- .../selftests/futex/functional/robust_list.c | 239 ++++++++++++++++++ .../selftests/futex/include/futextest.h | 6 + 2 files changed, 245 insertions(+) diff --git a/tools/testing/selftests/futex/functional/robust_list.c b/tools/testing/selftests/futex/functional/robust_list.c index e7d1254e18ca..b3fab60181d5 100644 --- a/tools/testing/selftests/futex/functional/robust_list.c +++ b/tools/testing/selftests/futex/functional/robust_list.c @@ -27,12 +27,15 @@ #include "futextest.h" #include "../../kselftest_harness.h" +#include #include #include #include +#include #include #include #include +#include #include #include @@ -42,6 +45,10 @@ #define SLEEP_US 100 +#if __SIZEOF_LONG__ == 8 +# define BUILD_64 +#endif + static pthread_barrier_t barrier, barrier2; static int set_robust_list(struct robust_list_head *head, size_t len) @@ -54,6 +61,12 @@ static int get_robust_list(int pid, struct robust_list_head **head, size_t *len_ return syscall(SYS_get_robust_list, pid, head, len_ptr); } +static int sys_futex_robust_unlock(_Atomic(uint32_t) *uaddr, unsigned int op, int val, + void *list_op_pending, unsigned int val3) +{ + return syscall(SYS_futex, uaddr, op, val, NULL, list_op_pending, val3, 0); +} + /* * Basic lock struct, contains just the futex word and the robust list element * Real implementations have also a *prev to easily walk in the list @@ -549,4 +562,230 @@ TEST(test_circular_list) ksft_test_result_pass("%s\n", __func__); } +/* + * Below are tests for the fix of robust release race condition. Please read the following + * thread to learn more about the issue in the first place and why the following functions fix it: + * https://lore.kernel.org/lkml/20260316162316.356674433@kernel.org/ + */ + +/* + * Auxiliary code for binding the vDSO functions + */ +static void *get_vdso_func_addr(const char *function) +{ + const char *vdso_names[] = { + "linux-vdso.so.1", "linux-gate.so.1", "linux-vdso32.so.1", "linux-vdso64.so.1", + }; + + for (int i = 0; i < ARRAY_SIZE(vdso_names); i++) { + void *vdso = dlopen(vdso_names[i], RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + + if (vdso) + return dlsym(vdso, function); + } + return NULL; +} + +/* + * These are the real vDSO function signatures: + * + * __vdso_futex_robust_list64_try_unlock(__u32 *lock, __u32 tid, __u64 *pop) + * __vdso_futex_robust_list32_try_unlock(__u32 *lock, __u32 tid, __u32 *pop) + * + * So for the generic entry point we need to use a void pointer as the last argument + */ +FIXTURE(vdso_unlock) +{ + uint32_t (*vdso)(_Atomic(uint32_t) *lock, uint32_t tid, void *pop); +}; + +FIXTURE_VARIANT(vdso_unlock) +{ + bool is_32; + char func_name[]; +}; + +FIXTURE_SETUP(vdso_unlock) +{ + self->vdso = get_vdso_func_addr(variant->func_name); +} + +FIXTURE_TEARDOWN(vdso_unlock) {} + +FIXTURE_VARIANT_ADD(vdso_unlock, 32) +{ + .func_name = "__vdso_futex_robust_list32_try_unlock", + .is_32 = true, +}; + +FIXTURE_VARIANT_ADD(vdso_unlock, 64) +{ + .func_name = "__vdso_futex_robust_list64_try_unlock", + .is_32 = false, +}; + +/* + * Test the vDSO robust_listXX_try_unlock() for the uncontended case. The virtual syscall should + * return the thread ID of the lock owner, the lock word must be 0 and the list_op_pending should + * be NULL. + */ +TEST_F(vdso_unlock, test_robust_try_unlock_uncontended) +{ + struct lock_struct lock = { .futex = 0 }; + _Atomic(unsigned int) *futex = &lock.futex; + struct robust_list_head head; + uintptr_t exp = (uintptr_t) NULL; + pid_t tid = gettid(); + int ret; + + if (!self->vdso) { + ksft_test_result_skip("%s not found\n", variant->func_name); + return; + } + + *futex = tid; + + ret = set_list(&head); + if (ret) + ksft_test_result_fail("set_robust_list error\n"); + + head.list_op_pending = &lock.list; + + ret = self->vdso(futex, tid, &head.list_op_pending); + + ASSERT_EQ(ret, tid); + ASSERT_EQ(*futex, 0); + + /* Check only the lower 32 bits for the 32-bit entry point */ + if (variant->is_32) { + exp = (uintptr_t)(unsigned long)&lock.list; + exp &= ~0xFFFFFFFFULL; + } + + ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp); +} + +/* + * If the lock is contended, the operation fails. The return value is the value found at the + * futex word (tid | FUTEX_WAITERS), the futex word is not modified and the list_op_pending is_32 + * not cleared. + */ +TEST_F(vdso_unlock, test_robust_try_unlock_contended) +{ + struct lock_struct lock = { .futex = 0 }; + _Atomic(unsigned int) *futex = &lock.futex; + struct robust_list_head head; + pid_t tid = gettid(); + int ret; + + if (!self->vdso) { + ksft_test_result_skip("%s not found\n", variant->func_name); + return; + } + + *futex = tid | FUTEX_WAITERS; + + ret = set_list(&head); + if (ret) + ksft_test_result_fail("set_robust_list error\n"); + + head.list_op_pending = &lock.list; + + ret = self->vdso(futex, tid, &head.list_op_pending); + + ASSERT_EQ(ret, tid | FUTEX_WAITERS); + ASSERT_EQ(*futex, tid | FUTEX_WAITERS); + ASSERT_EQ(head.list_op_pending, &lock.list); +} + +FIXTURE(futex_op) {}; + +FIXTURE_VARIANT(futex_op) +{ + unsigned int op; + unsigned int val3; +}; + +FIXTURE_SETUP(futex_op) {} + +FIXTURE_TEARDOWN(futex_op) {} + +FIXTURE_VARIANT_ADD(futex_op, wake) +{ + .op = FUTEX_WAKE, + .val3 = 0, +}; + +FIXTURE_VARIANT_ADD(futex_op, wake_bitset) +{ + .op = FUTEX_WAKE_BITSET, + .val3 = FUTEX_BITSET_MATCH_ANY, +}; + +FIXTURE_VARIANT_ADD(futex_op, unlock_pi) +{ + .op = FUTEX_UNLOCK_PI, + .val3 = 0, +}; + +FIXTURE_VARIANT_ADD(futex_op, wake32) +{ + .op = FUTEX_WAKE | FUTEX_ROBUST_LIST32, + .val3 = 0, +}; + +FIXTURE_VARIANT_ADD(futex_op, wake_bitset32) +{ + .op = FUTEX_WAKE_BITSET | FUTEX_ROBUST_LIST32, + .val3 = FUTEX_BITSET_MATCH_ANY, +}; + +FIXTURE_VARIANT_ADD(futex_op, unlock_pi32) +{ + .op = FUTEX_UNLOCK_PI | FUTEX_ROBUST_LIST32, + .val3 = 0, +}; + +/* + * The syscall should return the number of tasks waken (for this test, 0), clear the futex word and + * clear list_op_pending + */ +TEST_F(futex_op, test_futex_robust_unlock) +{ + struct lock_struct lock = { .futex = 0 }; + _Atomic(unsigned int) *futex = &lock.futex; + uintptr_t exp = (uintptr_t) NULL; + struct robust_list_head head; + pid_t tid = gettid(); + int ret; + +#ifndef BUILD_64 + if (!(variant->op & FUTEX_ROBUST_LIST32)) { + ksft_test_result_skip("Not supported for 32 bit build\n"); + return; + } +#endif + + *futex = tid | FUTEX_WAITERS; + + ret = set_list(&head); + if (ret) + ksft_test_result_fail("set_robust_list error\n"); + + head.list_op_pending = &lock.list; + + ret = sys_futex_robust_unlock(futex, FUTEX_ROBUST_UNLOCK | variant->op, tid, + &head.list_op_pending, variant->val3); + + ASSERT_EQ(ret, 0); + ASSERT_EQ(*futex, 0); + + if (variant->op & FUTEX_ROBUST_LIST32) { + exp = (uint64_t)(unsigned long)&lock.list; + exp &= ~0xFFFFFFFFULL; + } + + ASSERT_EQ((uintptr_t)(unsigned long)head.list_op_pending, exp); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/futex/include/futextest.h b/tools/testing/selftests/futex/include/futextest.h index 3d48e9789d9f..df33f31d6994 100644 --- a/tools/testing/selftests/futex/include/futextest.h +++ b/tools/testing/selftests/futex/include/futextest.h @@ -38,6 +38,12 @@ typedef volatile u_int32_t futex_t; #ifndef FUTEX_CMP_REQUEUE_PI #define FUTEX_CMP_REQUEUE_PI 12 #endif +#ifndef FUTEX_ROBUST_UNLOCK +#define FUTEX_ROBUST_UNLOCK 512 +#endif +#ifndef FUTEX_ROBUST_LIST32 +#define FUTEX_ROBUST_LIST32 1024 +#endif #ifndef FUTEX_WAIT_REQUEUE_PI_PRIVATE #define FUTEX_WAIT_REQUEUE_PI_PRIVATE (FUTEX_WAIT_REQUEUE_PI | \ FUTEX_PRIVATE_FLAG) From ed645be2ab1bcbc0e27242c7316fc15475962333 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:08 +0200 Subject: [PATCH 117/167] KVM: arm64: Use ktime_get_snapshot_id() to retrieve CLOCK_BOOTTIME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ktime_get_snapshot() is replaced by ktime_get_snapshot_id() which allows to request a particular CLOCK ID to be captured along with the clocksource counter. Convert the tracing mechanism over and use the new system_time_snapshot::systime field, which holds the system timestamp selected by the CLOCK ID argument. No functional change intended. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Acked-by: Marc Zyngier Acked-by: Vincent Donnefort Link: https://patch.msgid.link/20260529195557.174373054@kernel.org --- arch/arm64/kvm/hyp_trace.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kvm/hyp_trace.c b/arch/arm64/kvm/hyp_trace.c index 8b7f2bf2fba8..822ce32d39ae 100644 --- a/arch/arm64/kvm/hyp_trace.c +++ b/arch/arm64/kvm/hyp_trace.c @@ -51,8 +51,8 @@ static void __hyp_clock_work(struct work_struct *work) hyp_clock = container_of(dwork, struct hyp_trace_clock, work); - ktime_get_snapshot(&snap); - boot = ktime_to_ns(snap.boot); + ktime_get_snapshot_id(CLOCK_BOOTTIME, &snap); + boot = ktime_to_ns(snap.systime); delta_boot = boot - hyp_clock->boot; delta_cycles = snap.cycles - hyp_clock->cycles; @@ -118,9 +118,9 @@ static void hyp_trace_clock_enable(struct hyp_trace_clock *hyp_clock, bool enabl hyp_clock->running = false; } - ktime_get_snapshot(&snap); + ktime_get_snapshot_id(CLOCK_BOOTTIME, &snap); - hyp_clock->boot = ktime_to_ns(snap.boot); + hyp_clock->boot = ktime_to_ns(snap.systime); hyp_clock->cycles = snap.cycles; hyp_clock->mult = 0; From 744f8406a3d197b8e466d1ab09c4c3b8de8d65ea Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:12 +0200 Subject: [PATCH 118/167] KVM: arm64: Use ktime_get_snapshot_id() to snapshot CLOCK_REALTIME MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ktime_get_snapshot() is replaced by ktime_get_snapshot_id() which allows to request a particular CLOCK ID to be captured along with the clocksource counter. Convert the usage in kvm_get_ptp_time() over and use the new system_time_snapshot::systime field, which holds the system timestamp selected by the CLOCK ID argument. No functional change intended. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Acked-by: Marc Zyngier Link: https://patch.msgid.link/20260529195557.225399927@kernel.org --- arch/arm64/kvm/hypercalls.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/kvm/hypercalls.c b/arch/arm64/kvm/hypercalls.c index 58c5fe7d7572..b11b8821c9fb 100644 --- a/arch/arm64/kvm/hypercalls.c +++ b/arch/arm64/kvm/hypercalls.c @@ -28,7 +28,7 @@ static void kvm_ptp_get_time(struct kvm_vcpu *vcpu, u64 *val) * system time and counter value must captured at the same * time to keep consistency and precision. */ - ktime_get_snapshot(&systime_snapshot); + ktime_get_snapshot_id(CLOCK_REALTIME, &systime_snapshot); /* * This is only valid if the current clocksource is the @@ -61,8 +61,8 @@ static void kvm_ptp_get_time(struct kvm_vcpu *vcpu, u64 *val) * in the future (about 292 years from 1970, and at that stage * nobody will give a damn about it). */ - val[0] = upper_32_bits(systime_snapshot.real); - val[1] = lower_32_bits(systime_snapshot.real); + val[0] = upper_32_bits(systime_snapshot.systime); + val[1] = lower_32_bits(systime_snapshot.systime); val[2] = upper_32_bits(cycles); val[3] = lower_32_bits(cycles); } From 50de63d924deb0357d510818c7443b3446a837be Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:16 +0200 Subject: [PATCH 119/167] ptp: ptp_vmclock: Convert to ktime_get_snapshot_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ktime_get_snapshot() is replaced by ktime_get_snapshot_id() which allows to request a particular CLOCK ID to be captured along with the clocksource counter. Convert vmclock over and use the new system_time_snapshot::systime field, which holds the system timestamp selected by the CLOCK ID argument. No functional change intended. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.281425262@kernel.org --- drivers/ptp/ptp_vmclock.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index 8b630eb916b5..0f12b54be740 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -132,7 +132,7 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, * will be derived from the *same* counter value. * * If the system isn't using the same counter, then the value - * from ktime_get_snapshot() will still be used as pre_ts, and + * from ktime_get_snapshot_id() will still be used as pre_ts, and * ptp_read_system_postts() is called to populate postts after * calling get_cycles(). * @@ -140,7 +140,7 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, * the seq_count loop. */ if (sts) { - ktime_get_snapshot(&systime_snapshot); + ktime_get_snapshot_id(CLOCK_REALTIME, &systime_snapshot); if (systime_snapshot.cs_id == st->cs_id) { cycle = systime_snapshot.cycles; } else { @@ -181,7 +181,7 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, } if (sts) { - sts->pre_ts = ktime_to_timespec64(systime_snapshot.real); + sts->pre_ts = ktime_to_timespec64(systime_snapshot.systime); if (systime_snapshot.cs_id == st->cs_id) sts->post_ts = sts->pre_ts; } @@ -272,7 +272,7 @@ static int ptp_vmclock_getcrosststamp(struct ptp_clock_info *ptp, if (ret == -ENODEV) { struct system_time_snapshot systime_snapshot; - ktime_get_snapshot(&systime_snapshot); + ktime_get_snapshot_id(CLOCK_REALTIME, &systime_snapshot); if (systime_snapshot.cs_id == CSID_X86_TSC || systime_snapshot.cs_id == CSID_X86_KVM_CLK) { From 705e1068071f82b6c66b9e28124fbb7123b04c1d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:20 +0200 Subject: [PATCH 120/167] timekeeping: Remove system_time_snapshot::real/boot/raw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All users are converted over to ktime_get_snapshot_id() and system_time_snapshot::systime and ::monoraw. Remove the leftovers. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.330029635@kernel.org --- include/linux/timekeeping.h | 10 +--------- kernel/time/timekeeping.c | 8 -------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 161157c8d27e..eb64c90c4127 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -280,9 +280,6 @@ static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { ret * a selected CLOCK_* and the clocksource counter value * @cycles: Clocksource counter value to produce the system times * @systime: The system time of the selected CLOCK ID - * @real: Realtime system time - * @boot: Boot time - * @raw: Monotonic raw system time * @monoraw: Monotonic raw system time * @cs_id: Clocksource ID * @clock_was_set_seq: The sequence number of clock-was-set events @@ -292,12 +289,7 @@ static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { ret struct system_time_snapshot { u64 cycles; ktime_t systime; - ktime_t real; - ktime_t boot; - union { - ktime_t raw; - ktime_t monoraw; - }; + ktime_t monoraw; enum clocksource_ids cs_id; unsigned int clock_was_set_seq; u8 cs_was_changed_seq; diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index ccd04addb021..a134b1bad923 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1196,8 +1196,6 @@ void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *syst struct timekeeper *tk; struct tk_data *tkd; unsigned int seq; - ktime_t base_real; - ktime_t base_boot; /* Invalidate the snapshot for all failure cases */ systime_snapshot->valid = false; @@ -1239,18 +1237,12 @@ void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *syst offs_sys = *offs; base_raw = tk->tkr_raw.base; - /* Kept around until the callers are fixed up */ - base_real = ktime_add(base_sys, tk_core.timekeeper.offs_real); - base_boot = ktime_add(base_sys, tk_core.timekeeper.offs_boot); - nsec_sys = timekeeping_cycles_to_ns(&tk->tkr_mono, now); nsec_raw = timekeeping_cycles_to_ns(&tk->tkr_raw, now); } while (read_seqcount_retry(&tkd->seq, seq)); systime_snapshot->cycles = now; systime_snapshot->systime = ktime_add_ns(base_sys, offs_sys + nsec_sys); - systime_snapshot->real = ktime_add_ns(base_real, nsec_sys); - systime_snapshot->boot = ktime_add_ns(base_boot, nsec_sys); systime_snapshot->monoraw = ktime_add_ns(base_raw, nsec_raw); /* From 23c1bfa9f8dbefe0be1fa32aaf235c08cb24bd78 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:24 +0200 Subject: [PATCH 121/167] timekeeping: Add CLOCK_AUX support for ktime_get_snapshot_id() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that all users are converted it's possible to enable snapshotting of CLOCK_AUX time. The underlying clocksource is the same as for all other CLOCK variants. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.380601005@kernel.org --- kernel/time/timekeeping.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index a134b1bad923..e43aa11dbfd6 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -67,6 +67,7 @@ static inline bool tk_is_aux(const struct timekeeper *tk) { return tk->id >= TIMEKEEPER_AUX_FIRST && tk->id <= TIMEKEEPER_AUX_LAST; } +static inline struct tk_data *aux_get_tk_data(clockid_t id); #else static inline bool tk_get_aux_ts64(unsigned int tkid, struct timespec64 *ts) { @@ -77,6 +78,10 @@ static inline bool tk_is_aux(const struct timekeeper *tk) { return false; } +static inline struct tk_data *aux_get_tk_data(clockid_t id) +{ + return NULL; +} #endif static inline void tk_update_aux_offs(struct timekeeper *tk, ktime_t offs) @@ -1218,6 +1223,12 @@ void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *syst tkd = &tk_core; offs = &tk_core.timekeeper.offs_boot; break; + case CLOCK_AUX ... CLOCK_AUX_LAST: + tkd = aux_get_tk_data(clock_id); + if (!tkd) + return; + offs = &tkd->timekeeper.offs_aux; + break; default: WARN_ON_ONCE(1); return; @@ -1228,6 +1239,10 @@ void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *syst do { seq = read_seqcount_begin(&tkd->seq); + /* Aux clocks can be invalid */ + if (!tk->clock_valid) + return; + now = tk_clock_read(&tk->tkr_mono); systime_snapshot->cs_id = tk->tkr_mono.clock->id; systime_snapshot->cs_was_changed_seq = tk->cs_was_changed_seq; From 6c14771816435a7ecf52d981a341e2de21463268 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:28 +0200 Subject: [PATCH 122/167] timekeeping: Add system_counterval_t to struct system_device_crosststamp An upcoming extension to the PTP IOCTL requires to return the system counter value and the clocksource ID to user space. get_device_system_crosststamp() has this information already. Extend struct system_device_crosststamp with a system_counterval_t member and fill in the data. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.429406675@kernel.org --- include/linux/timekeeping.h | 28 ++++++++++++---------- kernel/time/timekeeping.c | 48 +++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index eb64c90c4127..3867db65f391 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -296,19 +296,6 @@ struct system_time_snapshot { u8 valid; }; -/** - * struct system_device_crosststamp - system/device cross-timestamp - * (synchronized capture) - * @device: Device time - * @sys_realtime: Realtime simultaneous with device time - * @sys_monoraw: Monotonic raw simultaneous with device time - */ -struct system_device_crosststamp { - ktime_t device; - ktime_t sys_realtime; - ktime_t sys_monoraw; -}; - /** * struct system_counterval_t - system counter value with the ID of the * corresponding clocksource @@ -325,6 +312,21 @@ struct system_counterval_t { bool use_nsecs; }; +/** + * struct system_device_crosststamp - system/device cross-timestamp + * (synchronized capture) + * @device: Device time + * @sys_counter: Clocksource counter value simultaneous with device time + * @sys_realtime: Realtime simultaneous with device time + * @sys_monoraw: Monotonic raw simultaneous with device time + */ +struct system_device_crosststamp { + ktime_t device; + struct system_counterval_t sys_counter; + ktime_t sys_realtime; + ktime_t sys_monoraw; +}; + extern bool ktime_real_to_base_clock(ktime_t treal, enum clocksource_ids base_id, u64 *cycles); extern bool timekeeping_clocksource_has_base(enum clocksource_ids id); diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index e43aa11dbfd6..372179ff27a3 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1418,6 +1418,8 @@ static bool convert_base_to_cs(struct system_counterval_t *scv) return false; scv->cycles += base->offset; + /* Set the clocksource ID as scv::cycles is now clocksource based */ + scv->cs_id = cs->id; return true; } @@ -1485,11 +1487,11 @@ EXPORT_SYMBOL_GPL(ktime_real_to_base_clock); /** * get_device_system_crosststamp - Synchronously capture system/device timestamp - * @get_time_fn: Callback to get simultaneous device time and - * system counter from the device driver + * @get_time_fn: Callback to get simultaneous device time and system counter + * from the device driver * @ctx: Context passed to get_time_fn() - * @history_begin: Historical reference point used to interpolate system - * time when counter provided by the driver is before the current interval + * @history_begin: Historical reference point used to interpolate system time when + * the counter value provided by the driver is before the current interval * @xtstamp: Receives simultaneously captured system and device time * * Reads a timestamp from a device and correlates it to system time @@ -1502,14 +1504,12 @@ int get_device_system_crosststamp(int (*get_time_fn) struct system_time_snapshot *history_begin, struct system_device_crosststamp *xtstamp) { - struct system_counterval_t system_counterval = {}; + u64 syscnt_cycles, cycles, now, interval_start; struct timekeeper *tk = &tk_core.timekeeper; - u64 cycles, now, interval_start; - unsigned int clock_was_set_seq = 0; + unsigned int seq, clock_was_set_seq = 0; ktime_t base_real, base_raw; u64 nsec_real, nsec_raw; u8 cs_was_changed_seq; - unsigned int seq; bool do_interp; int ret; @@ -1519,19 +1519,20 @@ int get_device_system_crosststamp(int (*get_time_fn) * Try to synchronously capture device time and a system * counter value calling back into the device driver */ - ret = get_time_fn(&xtstamp->device, &system_counterval, ctx); + ret = get_time_fn(&xtstamp->device, &xtstamp->sys_counter, ctx); if (ret) return ret; /* * Verify that the clocksource ID associated with the captured * system counter value is the same as for the currently - * installed timekeeper clocksource + * installed timekeeper clocksource and convert to it. */ - if (system_counterval.cs_id == CSID_GENERIC || - !convert_base_to_cs(&system_counterval)) + if (xtstamp->sys_counter.cs_id == CSID_GENERIC || + !convert_base_to_cs(&xtstamp->sys_counter)) return -ENODEV; - cycles = system_counterval.cycles; + + cycles = syscnt_cycles = xtstamp->sys_counter.cycles; /* * Check whether the system counter value provided by the @@ -1573,24 +1574,19 @@ int get_device_system_crosststamp(int (*get_time_fn) * clocksource change */ if (!history_begin || - !timestamp_in_interval(history_begin->cycles, - cycles, system_counterval.cycles) || + !timestamp_in_interval(history_begin->cycles, cycles, syscnt_cycles) || history_begin->cs_was_changed_seq != cs_was_changed_seq) return -EINVAL; - partial_history_cycles = cycles - system_counterval.cycles; - total_history_cycles = cycles - history_begin->cycles; - discontinuity = - history_begin->clock_was_set_seq != clock_was_set_seq; - ret = adjust_historical_crosststamp(history_begin, - partial_history_cycles, - total_history_cycles, - discontinuity, xtstamp); - if (ret) - return ret; + partial_history_cycles = cycles - syscnt_cycles; + total_history_cycles = cycles - history_begin->cycles; + discontinuity = history_begin->clock_was_set_seq != clock_was_set_seq; + + ret = adjust_historical_crosststamp(history_begin, partial_history_cycles, + total_history_cycles, discontinuity, xtstamp); } - return 0; + return ret; } EXPORT_SYMBOL_GPL(get_device_system_crosststamp); From e3b8d03faa4d266d19ff7a7534e7bf810ff681cf Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:32 +0200 Subject: [PATCH 123/167] timekeeping: Add CLOCK ID to system_device_crosststamp The normal capture for system/device cross timestamps is CLOCK_REALTIME, but that's meaningless for AUX clocks. Add a clock_id field to struct system_device_crosststamp and initialize it with CLOCK_REALTIME at the two places which prepare for cross timestamps. After the related code has been cleaned up, the core code will honor the clock_id field when calculating the system time from the system counter snapshot. No functional change. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.482153523@kernel.org --- drivers/ptp/ptp_chardev.c | 2 +- include/linux/timekeeping.h | 2 ++ sound/hda/common/controller.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c index c61cf9edac48..da964c3778fb 100644 --- a/drivers/ptp/ptp_chardev.c +++ b/drivers/ptp/ptp_chardev.c @@ -317,8 +317,8 @@ typedef int (*ptp_crosststamp_fn)(struct ptp_clock_info *, static long ptp_sys_offset_precise(struct ptp_clock *ptp, void __user *arg, ptp_crosststamp_fn crosststamp_fn) { + struct system_device_crosststamp xtstamp = { .clock_id = CLOCK_REALTIME }; struct ptp_sys_offset_precise precise_offset; - struct system_device_crosststamp xtstamp; struct timespec64 ts; int err; diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 3867db65f391..fff6d9e319c4 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -315,12 +315,14 @@ struct system_counterval_t { /** * struct system_device_crosststamp - system/device cross-timestamp * (synchronized capture) + * @clock_id: System time Clock ID to capture * @device: Device time * @sys_counter: Clocksource counter value simultaneous with device time * @sys_realtime: Realtime simultaneous with device time * @sys_monoraw: Monotonic raw simultaneous with device time */ struct system_device_crosststamp { + clockid_t clock_id; ktime_t device; struct system_counterval_t sys_counter; ktime_t sys_realtime; diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index 5934e5cdfdfd..a880dcd8f2e8 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -489,9 +489,9 @@ static int azx_get_time_info(struct snd_pcm_substream *substream, struct snd_pcm_audio_tstamp_config *audio_tstamp_config, struct snd_pcm_audio_tstamp_report *audio_tstamp_report) { + struct system_device_crosststamp xtstamp = { .clock_id = CLOCK_REALTIME }; struct azx_dev *azx_dev = get_azx_dev(substream); struct snd_pcm_runtime *runtime = substream->runtime; - struct system_device_crosststamp xtstamp; int ret; u64 nsec; From 570cf418bfc9d7d0ab662794dd2e2f4eeb79dfe5 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:36 +0200 Subject: [PATCH 124/167] wifi: iwlwifi: Adopt PTP cross timestamps to core changes iwlwifi only supports CLOCK_REALTIME timestamps and provides an incomplete result without system counter values etc. It also zeros struct system_device_crosststamp, which is already zeroed in the core and initialized with the clock ID. Remove the zeroing and reject any request for a clock ID other than REALTIME. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.535447186@kernel.org --- drivers/net/wireless/intel/iwlwifi/mld/ptp.c | 3 ++- drivers/net/wireless/intel/iwlwifi/mvm/ptp.c | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c index c65f4b56a327..921b93c9e836 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c @@ -250,7 +250,8 @@ iwl_mld_phc_get_crosstimestamp(struct ptp_clock_info *ptp, /* System (wall) time */ ktime_t sys_time; - memset(xtstamp, 0, sizeof(struct system_device_crosststamp)); + if (xtstamp->clock_id != CLOCK_REALTIME) + return -ENOTSUPP; ret = iwl_mld_get_crosstimestamp_fw(mld, &gp2, &sys_time); if (ret) { diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c index f7b620136c85..4c40a5218436 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c @@ -160,13 +160,14 @@ iwl_mvm_phc_get_crosstimestamp(struct ptp_clock_info *ptp, /* System (wall) time */ ktime_t sys_time; - memset(xtstamp, 0, sizeof(struct system_device_crosststamp)); - if (!mvm->ptp_data.ptp_clock) { IWL_ERR(mvm, "No PHC clock registered\n"); return -ENODEV; } + if (xtstamp->clock_id != CLOCK_REALTIME) + return -ENOTSUPP; + mutex_lock(&mvm->mutex); if (fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_SYNCED_TIME)) { ret = iwl_mvm_get_crosstimestamp_fw(mvm, &gp2, &sys_time); From 23bc637750e4142ef9e86a06872fdf32e74a9050 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:40 +0200 Subject: [PATCH 125/167] ice/ptp: Use provided clock ID for history snapshot The PTP core indicates in system_device_crosststamp::clock_id the clock ID for which then system time stamp should be taken. That allows to utilize hardware timestamps with e.g. AUX clocks. Save the provided clock ID and use it in ice_capture_crosststamp() for taking the history snapshot. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.587226681@kernel.org --- drivers/net/ethernet/intel/ice/ice_ptp.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c index 36df742c326c..f9e4ec6f7ebb 100644 --- a/drivers/net/ethernet/intel/ice/ice_ptp.c +++ b/drivers/net/ethernet/intel/ice/ice_ptp.c @@ -2065,11 +2065,13 @@ static const struct ice_crosststamp_cfg ice_crosststamp_cfg_e830 = { /** * struct ice_crosststamp_ctx - Device cross timestamp context * @snapshot: snapshot of system clocks for historic interpolation + * @snapshot_clock_id: System clock ID for @snapshot * @pf: pointer to the PF private structure * @cfg: pointer to hardware configuration for cross timestamp */ struct ice_crosststamp_ctx { struct system_time_snapshot snapshot; + clockid_t snapshot_clock_id; struct ice_pf *pf; const struct ice_crosststamp_cfg *cfg; }; @@ -2115,7 +2117,7 @@ static int ice_capture_crosststamp(ktime_t *device, } /* Snapshot system time for historic interpolation */ - ktime_get_snapshot(&ctx->snapshot); + ktime_get_snapshot_id(ctx->snapshot_clock_id, &ctx->snapshot); /* Program cmd to master timer */ ice_ptp_src_cmd(hw, ICE_PTP_READ_TIME); @@ -2176,6 +2178,7 @@ static int ice_ptp_getcrosststamp(struct ptp_clock_info *info, { struct ice_pf *pf = ptp_info_to_pf(info); struct ice_crosststamp_ctx ctx = { + .snapshot_clock_id = cts->clock_id, .pf = pf, }; From af1816babcd9eff5c40eaf0de149b469debdfdb4 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:44 +0200 Subject: [PATCH 126/167] igc: Use provided clock ID for history snapshot The PTP core indicates in system_device_crosststamp::clock_id the clock ID for which the system time stamp should be taken. That allows to utilize hardware timestamps with e.g. AUX clocks. Save the provided clock ID and use it in igc_phc_get_syncdevicetime() for taking the history snapshot. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.637381831@kernel.org --- drivers/net/ethernet/intel/igc/igc.h | 1 + drivers/net/ethernet/intel/igc/igc_ptp.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/intel/igc/igc.h b/drivers/net/ethernet/intel/igc/igc.h index 17236813965d..46d625b15f44 100644 --- a/drivers/net/ethernet/intel/igc/igc.h +++ b/drivers/net/ethernet/intel/igc/igc.h @@ -326,6 +326,7 @@ struct igc_adapter { struct timespec64 prev_ptp_time; /* Pre-reset PTP clock */ ktime_t ptp_reset_start; /* Reset time in clock mono */ struct system_time_snapshot snapshot; + clockid_t snapshot_clock_id; struct mutex ptm_lock; /* Only allow one PTM transaction at a time */ char fw_version[32]; diff --git a/drivers/net/ethernet/intel/igc/igc_ptp.c b/drivers/net/ethernet/intel/igc/igc_ptp.c index 3d6b2264164a..b40aba9ab685 100644 --- a/drivers/net/ethernet/intel/igc/igc_ptp.c +++ b/drivers/net/ethernet/intel/igc/igc_ptp.c @@ -1049,7 +1049,7 @@ static int igc_phc_get_syncdevicetime(ktime_t *device, */ do { /* Get a snapshot of system clocks to use as historic value. */ - ktime_get_snapshot(&adapter->snapshot); + ktime_get_snapshot_id(adapter->snapshot_clock_id, &adapter->snapshot); igc_ptm_trigger(hw); @@ -1103,6 +1103,8 @@ static int igc_ptp_getcrosststamp(struct ptp_clock_info *ptp, /* This blocks until any in progress PTM transactions complete */ mutex_lock(&adapter->ptm_lock); + adapter->snapshot_clock_id = cts->clock_id; + ret = get_device_system_crosststamp(igc_phc_get_syncdevicetime, adapter, &adapter->snapshot, cts); mutex_unlock(&adapter->ptm_lock); From 8216433ebe7e1b904c9e0f3176d7f99cbdb78bcd Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:48 +0200 Subject: [PATCH 127/167] net/mlx5: Use provided clock ID for history snapshot The PTP core indicates in system_device_crosststamp::clock_id the clock ID for which the system time stamp should be taken. That allows to utilize hardware timestamps with e.g. AUX clocks. Use ktime_get_snapshot_id() and hand the provided clock ID in. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.689836531@kernel.org --- drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c index d785f1b4f2e1..5df786133e4b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/clock.c @@ -340,7 +340,7 @@ static int mlx5_ptp_getcrosststamp(struct ptp_clock_info *ptp, goto unlock; } - ktime_get_snapshot(&history_begin); + ktime_get_snapshot_id(cts->clock_id, &history_begin); err = get_device_system_crosststamp(mlx5_mtctr_syncdevicetime, mdev, &history_begin, cts); @@ -366,7 +366,7 @@ static int mlx5_ptp_getcrosscycles(struct ptp_clock_info *ptp, goto unlock; } - ktime_get_snapshot(&history_begin); + ktime_get_snapshot_id(cts->clock_id, &history_begin); err = get_device_system_crosststamp(mlx5_mtctr_syncdevicecyclestime, mdev, &history_begin, cts); From 786493096848006020d7b8e57a53742a64fe5d4d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:52 +0200 Subject: [PATCH 128/167] virtio_rtc: Use provided clock ID for history snapshot The PTP core indicates in system_device_crosststamp::clock_id the clock ID for which the system time stamp should be taken. That allows to utilize hardware timestamps with e.g. AUX clocks. Use ktime_get_snapshot_id() and hand the provided clock ID in. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260529195557.744271454@kernel.org --- drivers/virtio/virtio_rtc_ptp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virtio/virtio_rtc_ptp.c b/drivers/virtio/virtio_rtc_ptp.c index f84599950cd4..ff8d834493dc 100644 --- a/drivers/virtio/virtio_rtc_ptp.c +++ b/drivers/virtio/virtio_rtc_ptp.c @@ -139,7 +139,7 @@ static int viortc_ptp_getcrosststamp(struct ptp_clock_info *ptp, if (ret) return ret; - ktime_get_snapshot(&history_begin); + ktime_get_snapshot_id(xtstamp->clock_id, &history_begin); if (history_begin.cs_id != cs_id) return -EOPNOTSUPP; From b21d442c9aebe5ed95303883d3365e430e134cf1 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:00:56 +0200 Subject: [PATCH 129/167] timekeeping: Remove ktime_get_snapshot() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All users have been converted to ktime_get_snapshot_id(). Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.795510496@kernel.org --- include/linux/timekeeping.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index fff6d9e319c4..2d9860ecb285 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -350,11 +350,6 @@ extern int get_device_system_crosststamp( */ extern void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *systime_snapshot); -static inline void ktime_get_snapshot(struct system_time_snapshot *systime_snapshot) -{ - ktime_get_snapshot_id(CLOCK_REALTIME, systime_snapshot); -} - /* * Persistent clock related interfaces */ From 09fb74f77e02489d1d8c555d44bab99d4905127c Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:00 +0200 Subject: [PATCH 130/167] timekeeping: Prepare for cross timestamps on arbitrary clock IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PTP device system crosstime stamps support only CLOCK_REALTIME, which is meaningless for AUX clocks. The PTP core hands in the clock ID already, so prepare the core code to honor it. - Add a new sys_systime field to struct system_device_crosststamp which aliases the sys_realtime field. Once all users are converted sys_realtime can be removed. - Prepare get_device_system_crosststamp() and the related code for it by switching to sys_systime and providing the initial changes to utilize different time keepers. No functional change intended. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.846634842@kernel.org --- include/linux/timekeeping.h | 7 ++++- kernel/time/timekeeping.c | 60 +++++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 2d9860ecb285..017599d04eb4 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -319,13 +319,18 @@ struct system_counterval_t { * @device: Device time * @sys_counter: Clocksource counter value simultaneous with device time * @sys_realtime: Realtime simultaneous with device time + * @sys_systime: System time for @clock_id * @sys_monoraw: Monotonic raw simultaneous with device time */ struct system_device_crosststamp { clockid_t clock_id; ktime_t device; struct system_counterval_t sys_counter; - ktime_t sys_realtime; + union { + /* realtime goes away once all users are converted */ + ktime_t sys_realtime; + ktime_t sys_systime; + }; ktime_t sys_monoraw; }; diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 372179ff27a3..dae61c9e31e6 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1312,7 +1312,7 @@ static int adjust_historical_crosststamp(struct system_time_snapshot *history, struct system_device_crosststamp *ts) { struct timekeeper *tk = &tk_core.timekeeper; - u64 corr_raw, corr_real; + u64 corr_raw, corr_sys; bool interp_forward; int ret; @@ -1329,8 +1329,7 @@ static int adjust_historical_crosststamp(struct system_time_snapshot *history, * Scale the monotonic raw time delta by: * partial_history_cycles / total_history_cycles */ - corr_raw = (u64)ktime_to_ns( - ktime_sub(ts->sys_monoraw, history->monoraw)); + corr_raw = (u64)ktime_to_ns(ktime_sub(ts->sys_monoraw, history->monoraw)); ret = scale64_check_overflow(partial_history_cycles, total_history_cycles, &corr_raw); if (ret) @@ -1338,30 +1337,29 @@ static int adjust_historical_crosststamp(struct system_time_snapshot *history, /* * If there is a discontinuity in the history, scale monotonic raw - * correction by: - * mult(real)/mult(raw) yielding the realtime correction - * Otherwise, calculate the realtime correction similar to monotonic - * raw calculation + * correction by: + * mult(sys)/mult(raw) yielding the system time correction + * + * Otherwise, calculate the system time correction similar to monotonic + * raw calculation */ if (discontinuity) { - corr_real = mul_u64_u32_div - (corr_raw, tk->tkr_mono.mult, tk->tkr_raw.mult); + corr_sys = mul_u64_u32_div(corr_raw, tk->tkr_mono.mult, tk->tkr_raw.mult); } else { - corr_real = (u64)ktime_to_ns( - ktime_sub(ts->sys_realtime, history->systime)); - ret = scale64_check_overflow(partial_history_cycles, - total_history_cycles, &corr_real); + corr_sys = (u64)ktime_to_ns(ktime_sub(ts->sys_systime, history->systime)); + ret = scale64_check_overflow(partial_history_cycles, total_history_cycles, + &corr_sys); if (ret) return ret; } - /* Fixup monotonic raw and real time time values */ + /* Fixup monotonic raw and system time time values */ if (interp_forward) { ts->sys_monoraw = ktime_add_ns(history->monoraw, corr_raw); - ts->sys_realtime = ktime_add_ns(history->systime, corr_real); + ts->sys_systime = ktime_add_ns(history->systime, corr_sys); } else { ts->sys_monoraw = ktime_sub_ns(ts->sys_monoraw, corr_raw); - ts->sys_realtime = ktime_sub_ns(ts->sys_realtime, corr_real); + ts->sys_systime = ktime_sub_ns(ts->sys_systime, corr_sys); } return 0; @@ -1505,16 +1503,29 @@ int get_device_system_crosststamp(int (*get_time_fn) struct system_device_crosststamp *xtstamp) { u64 syscnt_cycles, cycles, now, interval_start; - struct timekeeper *tk = &tk_core.timekeeper; unsigned int seq, clock_was_set_seq = 0; - ktime_t base_real, base_raw; - u64 nsec_real, nsec_raw; + ktime_t base_sys, base_raw, *offs; + u64 nsec_sys, nsec_raw; u8 cs_was_changed_seq; bool do_interp; + struct timekeeper *tk; + struct tk_data *tkd; int ret; + switch (xtstamp->clock_id) { + case CLOCK_REALTIME: + tkd = &tk_core; + offs = &tk_core.timekeeper.offs_real; + break; + default: + WARN_ON_ONCE(1); + return -ENODEV; + } + + tk = &tkd->timekeeper; + do { - seq = read_seqcount_begin(&tk_core.seq); + seq = read_seqcount_begin(&tkd->seq); /* * Try to synchronously capture device time and a system * counter value calling back into the device driver @@ -1549,15 +1560,14 @@ int get_device_system_crosststamp(int (*get_time_fn) do_interp = false; } - base_real = ktime_add(tk->tkr_mono.base, - tk_core.timekeeper.offs_real); + base_sys = ktime_add(tk->tkr_mono.base, *offs); base_raw = tk->tkr_raw.base; - nsec_real = timekeeping_cycles_to_ns(&tk->tkr_mono, cycles); + nsec_sys = timekeeping_cycles_to_ns(&tk->tkr_mono, cycles); nsec_raw = timekeeping_cycles_to_ns(&tk->tkr_raw, cycles); - } while (read_seqcount_retry(&tk_core.seq, seq)); + } while (read_seqcount_retry(&tkd->seq, seq)); - xtstamp->sys_realtime = ktime_add_ns(base_real, nsec_real); + xtstamp->sys_systime = ktime_add_ns(base_sys, nsec_sys); xtstamp->sys_monoraw = ktime_add_ns(base_raw, nsec_raw); /* From 12b497db7cbd2f07b7f182452bce6bb0e48e71dc Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:05 +0200 Subject: [PATCH 131/167] ptp: Use system_device_crosststamp::sys_systime .. to prepare for cross timestamps with variable clock IDs. No functional change. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.897808371@kernel.org --- drivers/ptp/ptp_chardev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c index da964c3778fb..f52bbb39cd8b 100644 --- a/drivers/ptp/ptp_chardev.c +++ b/drivers/ptp/ptp_chardev.c @@ -333,7 +333,7 @@ static long ptp_sys_offset_precise(struct ptp_clock *ptp, void __user *arg, ts = ktime_to_timespec64(xtstamp.device); precise_offset.device.sec = ts.tv_sec; precise_offset.device.nsec = ts.tv_nsec; - ts = ktime_to_timespec64(xtstamp.sys_realtime); + ts = ktime_to_timespec64(xtstamp.sys_systime); precise_offset.sys_realtime.sec = ts.tv_sec; precise_offset.sys_realtime.nsec = ts.tv_nsec; ts = ktime_to_timespec64(xtstamp.sys_monoraw); From 25af33c5c532b4429ffa803320aa626af7888f6c Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:09 +0200 Subject: [PATCH 132/167] wifi: iwlwifi: Use system_device_crosststamp::sys_systime sys_systime is an alias for sys_realtime. The latter will be removed so switch the code over to the new naming scheme. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.946612509@kernel.org --- drivers/net/wireless/intel/iwlwifi/mld/ptp.c | 2 +- drivers/net/wireless/intel/iwlwifi/mvm/ptp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c index 921b93c9e836..f829156d42b3 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/ptp.c @@ -271,7 +271,7 @@ iwl_mld_phc_get_crosstimestamp(struct ptp_clock_info *ptp, /* System monotonic raw time is not used */ xtstamp->device = ns_to_ktime(gp2_ns); - xtstamp->sys_realtime = sys_time; + xtstamp->sys_systime = sys_time; return ret; } diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c index 4c40a5218436..bcd6f7cead2a 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/ptp.c @@ -185,7 +185,7 @@ iwl_mvm_phc_get_crosstimestamp(struct ptp_clock_info *ptp, /* System monotonic raw time is not used */ xtstamp->device = (ktime_t)gp2_ns; - xtstamp->sys_realtime = sys_time; + xtstamp->sys_systime = sys_time; out: mutex_unlock(&mvm->mutex); From 13029a25eaa92457d8363a94c48592991770cc67 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:13 +0200 Subject: [PATCH 133/167] ALSA: hda/common: Use system_device_crosststamp::sys_systime sys_systime is an alias for sys_realtime. The latter will be removed so switch the code over to the new naming scheme. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195557.995298795@kernel.org --- sound/hda/common/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/common/controller.c b/sound/hda/common/controller.c index a880dcd8f2e8..77a67fb9eaf9 100644 --- a/sound/hda/common/controller.c +++ b/sound/hda/common/controller.c @@ -525,7 +525,7 @@ static int azx_get_time_info(struct snd_pcm_substream *substream, break; default: - *system_ts = ktime_to_timespec64(xtstamp.sys_realtime); + *system_ts = ktime_to_timespec64(xtstamp.sys_systime); break; } From 5ccaf1e569f04d8fd5f0167be728fdb48f448f81 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:17 +0200 Subject: [PATCH 134/167] timekeeping: Remove system_device_crosststamp::sys_realtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All users are converted to sys_systime. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195558.046694580@kernel.org --- include/linux/timekeeping.h | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 017599d04eb4..7f03e1cefcf6 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -318,7 +318,6 @@ struct system_counterval_t { * @clock_id: System time Clock ID to capture * @device: Device time * @sys_counter: Clocksource counter value simultaneous with device time - * @sys_realtime: Realtime simultaneous with device time * @sys_systime: System time for @clock_id * @sys_monoraw: Monotonic raw simultaneous with device time */ @@ -326,11 +325,7 @@ struct system_device_crosststamp { clockid_t clock_id; ktime_t device; struct system_counterval_t sys_counter; - union { - /* realtime goes away once all users are converted */ - ktime_t sys_realtime; - ktime_t sys_systime; - }; + ktime_t sys_systime; ktime_t sys_monoraw; }; From 9aebde8af6fe247d92816c197badf7e236c8f037 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:21 +0200 Subject: [PATCH 135/167] timekeeping: Add support for AUX clock cross timestamping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that all prerequisites are in place add the final support for AUX clocks in get_device_system_crosststamp(), which enables the PTP layer to support hardware cross timestamps with a new IOTCL. Signed-off-by: Thomas Gleixner Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Thomas Weißschuh Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260529195558.097464513@kernel.org --- kernel/time/timekeeping.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index dae61c9e31e6..65d870c2d40d 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -1517,6 +1517,12 @@ int get_device_system_crosststamp(int (*get_time_fn) tkd = &tk_core; offs = &tk_core.timekeeper.offs_real; break; + case CLOCK_AUX ... CLOCK_AUX_LAST: + tkd = aux_get_tk_data(xtstamp->clock_id); + if (!tkd) + return -ENODEV; + offs = &tkd->timekeeper.offs_aux; + break; default: WARN_ON_ONCE(1); return -ENODEV; From a6d799608e6a61b48908bff955200d03c34c90ca Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 29 May 2026 22:01:25 +0200 Subject: [PATCH 136/167] ptp: Switch to ktime_get_snapshot_id() for pre/post timestamps To prepare for a new PTP IOCTL, which exposes the raw counter value along with the requested system time snapshot, switch the pre/post time stamp sampling over to use ktime_get_snapshot_id() and fix up all usage sites. No functional change intended. The ptp_vmclock conversion was simplified by David Woodhouse. Signed-off-by: Thomas Gleixner Tested-by: David Woodhouse Tested-by: Arthur Kiyanovski Reviewed-by: David Woodhouse Reviewed-by: Jacob Keller Acked-by: Vadim Fedorenko Link: https://patch.msgid.link/20260529195558.149589566@kernel.org --- drivers/net/dsa/sja1105/sja1105_main.c | 8 ++++---- drivers/ptp/ptp_chardev.c | 14 +++++++++----- drivers/ptp/ptp_ocp.c | 11 ++++------- drivers/ptp/ptp_vmclock.c | 23 +++++++---------------- include/linux/ptp_clock_kernel.h | 15 ++++++++------- 5 files changed, 32 insertions(+), 39 deletions(-) diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index c72c2bfdcffb..2697073dbf90 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -2310,10 +2310,10 @@ int sja1105_static_config_reload(struct sja1105_private *priv, goto out; } - t1 = timespec64_to_ns(&ptp_sts_before.pre_ts); - t2 = timespec64_to_ns(&ptp_sts_before.post_ts); - t3 = timespec64_to_ns(&ptp_sts_after.pre_ts); - t4 = timespec64_to_ns(&ptp_sts_after.post_ts); + t1 = ktime_to_ns(ptp_sts_before.pre_sts.systime); + t2 = ktime_to_ns(ptp_sts_before.post_sts.systime); + t3 = ktime_to_ns(ptp_sts_after.pre_sts.systime); + t4 = ktime_to_ns(ptp_sts_after.post_sts.systime); /* Mid point, corresponds to pre-reset PTPCLKVAL */ t12 = t1 + (t2 - t1) / 2; /* Mid point, corresponds to post-reset PTPCLKVAL, aka 0 */ diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c index f52bbb39cd8b..dc23cd708cfe 100644 --- a/drivers/ptp/ptp_chardev.c +++ b/drivers/ptp/ptp_chardev.c @@ -386,15 +386,19 @@ static long ptp_sys_offset_extended(struct ptp_clock *ptp, void __user *arg, return err; /* Filter out disabled or unavailable clocks */ - if (sts.pre_ts.tv_sec < 0 || sts.post_ts.tv_sec < 0) + if (!sts.pre_sts.valid || !sts.post_sts.valid) return -EINVAL; - extoff->ts[i][0].sec = sts.pre_ts.tv_sec; - extoff->ts[i][0].nsec = sts.pre_ts.tv_nsec; extoff->ts[i][1].sec = ts.tv_sec; extoff->ts[i][1].nsec = ts.tv_nsec; - extoff->ts[i][2].sec = sts.post_ts.tv_sec; - extoff->ts[i][2].nsec = sts.post_ts.tv_nsec; + + ts = ktime_to_timespec64(sts.pre_sts.systime); + extoff->ts[i][0].sec = ts.tv_sec; + extoff->ts[i][0].nsec = ts.tv_nsec; + + ts = ktime_to_timespec64(sts.post_sts.systime); + extoff->ts[i][2].sec = ts.tv_sec; + extoff->ts[i][2].nsec = ts.tv_nsec; } return copy_to_user(arg, extoff, sizeof(*extoff)) ? -EFAULT : 0; diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index beacc2ffb166..28b0302c6250 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -1491,11 +1491,8 @@ __ptp_ocp_gettime_locked(struct ptp_ocp *bp, struct timespec64 *ts, } ptp_read_system_postts(sts); - if (sts && bp->ts_window_adjust) { - s64 ns = timespec64_to_ns(&sts->post_ts); - - sts->post_ts = ns_to_timespec64(ns - bp->ts_window_adjust); - } + if (sts && bp->ts_window_adjust) + sts->post_sts.systime -= bp->ts_window_adjust; time_ns = ioread32(&bp->reg->time_ns); time_sec = ioread32(&bp->reg->time_sec); @@ -4595,8 +4592,8 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) struct timespec64 sys_ts; s64 pre_ns, post_ns, ns; - pre_ns = timespec64_to_ns(&sts.pre_ts); - post_ns = timespec64_to_ns(&sts.post_ts); + pre_ns = ktime_to_ns(sts.pre_sts.systime); + post_ns = ktime_to_ns(sts.post_sts.systime); ns = (pre_ns + post_ns) / 2; ns += (s64)bp->utc_tai_offset * NSEC_PER_SEC; sys_ts = ns_to_timespec64(ns); diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index 0f12b54be740..d6a5a533164a 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -101,7 +101,6 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, struct timespec64 *tspec) { ktime_t deadline = ktime_add(ktime_get(), VMCLOCK_MAX_WAIT); - struct system_time_snapshot systime_snapshot; uint64_t cycle, delta, seq, frac_sec; #ifdef CONFIG_X86 @@ -132,17 +131,15 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, * will be derived from the *same* counter value. * * If the system isn't using the same counter, then the value - * from ktime_get_snapshot_id() will still be used as pre_ts, and - * ptp_read_system_postts() is called to populate postts after - * calling get_cycles(). - * - * The conversion to timespec64 happens further down, outside - * the seq_count loop. + * from ptp_read_system_prets() will still be used as pre_ts, + * and ptp_read_system_postts() is called to populate postts + * after calling get_cycles(). */ if (sts) { - ktime_get_snapshot_id(CLOCK_REALTIME, &systime_snapshot); - if (systime_snapshot.cs_id == st->cs_id) { - cycle = systime_snapshot.cycles; + ptp_read_system_prets(sts); + if (sts->pre_sts.cs_id == st->cs_id) { + cycle = sts->pre_sts.cycles; + sts->post_sts = sts->pre_sts; } else { cycle = get_cycles(); ptp_read_system_postts(sts); @@ -180,12 +177,6 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, system_counter->cs_id = st->cs_id; } - if (sts) { - sts->pre_ts = ktime_to_timespec64(systime_snapshot.systime); - if (systime_snapshot.cs_id == st->cs_id) - sts->post_ts = sts->pre_ts; - } - return 0; } diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h index 884364596dd3..36a27a910595 100644 --- a/include/linux/ptp_clock_kernel.h +++ b/include/linux/ptp_clock_kernel.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #define PTP_CLOCK_NAME_LEN 32 @@ -45,13 +46,13 @@ struct system_device_crosststamp; /** * struct ptp_system_timestamp - system time corresponding to a PHC timestamp - * @pre_ts: system timestamp before capturing PHC - * @post_ts: system timestamp after capturing PHC - * @clockid: clock-base used for capturing the system timestamps + * @pre_sts: system time snapshot before capturing PHC + * @post_sts: system time snapshot after capturing PHC + * @clockid: clock-base used for capturing the system timestamps */ struct ptp_system_timestamp { - struct timespec64 pre_ts; - struct timespec64 post_ts; + struct system_time_snapshot pre_sts; + struct system_time_snapshot post_sts; clockid_t clockid; }; @@ -510,13 +511,13 @@ static inline ktime_t ptp_convert_timestamp(const ktime_t *hwtstamp, static inline void ptp_read_system_prets(struct ptp_system_timestamp *sts) { if (sts) - ktime_get_clock_ts64(sts->clockid, &sts->pre_ts); + ktime_get_snapshot_id(sts->clockid, &sts->pre_sts); } static inline void ptp_read_system_postts(struct ptp_system_timestamp *sts) { if (sts) - ktime_get_clock_ts64(sts->clockid, &sts->post_ts); + ktime_get_snapshot_id(sts->clockid, &sts->post_sts); } #endif From ca1ec8bfac8c95d0fed9e3611ea21400d1f37262 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Fri, 29 May 2026 22:01:29 +0200 Subject: [PATCH 137/167] timekeeping: Add clocksource read_snapshot() method and hw_cycles to snapshot Add a read_snapshot() callback to struct clocksource which returns the derived clocksource value while also providing the underlying hardware counter reading and the related clocksource ID. This allows ktime_get_snapshot_id() to populate new hw_cycles and hw_csid fields in struct system_time_snapshot. For clocksources that are derived from an underlying counter (e.g., Hyper-V TSC page scales TSC to 10MHz, kvmclock scales TSC to 1GHz), this provides atomic access to both the derived value needed for timekeeping calculations, and the raw hardware counter needed by consumers like KVM's master clock and the vmclock PTP driver. [ tglx: Reworked it slightly ] Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Reviewed-by: Jacob Keller Assisted-by: Kiro:claude-opus-4.6-1m Link: https://patch.msgid.link/20260526230635.136914-1-dwmw2@infradead.org Link: https://patch.msgid.link/20260529195558.202568489@kernel.org --- include/linux/clocksource.h | 24 ++++++++++++++++++++++++ include/linux/timekeeping.h | 6 ++++++ kernel/time/timekeeping.c | 21 ++++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/linux/clocksource.h b/include/linux/clocksource.h index 7c38190b10bf..6d9ddf1587a2 100644 --- a/include/linux/clocksource.h +++ b/include/linux/clocksource.h @@ -31,6 +31,21 @@ struct module; #include +/** + * struct clocksource_hw_snapshot - Snapshot for the underlying hardware counter of derived + * clocksources like kvmclock or Hyper-V scaled TSC + * @hw_cycles: The hardware counter value + * @hw_csid: Clocksource ID of the hardware counter + * + * Such clocksources must implement the read_snapshot() callback and fill in the + * hardware counter value, the clocksource ID of the hardware counter and derive + * the actual clocksource cycles from @hw_cycles to provide an atomic snapshot + */ +struct clocksource_hw_snapshot { + u64 hw_cycles; + enum clocksource_ids hw_csid; +}; + /** * struct clocksource - hardware abstraction for a free running counter * Provides mostly state-free accessors to the underlying hardware. @@ -72,6 +87,14 @@ struct module; * @flags: Flags describing special properties * @base: Hardware abstraction for clock on which a clocksource * is based + * @read_snapshot: Extended @read() function for clocksources such as + * kvmclock or the Hyper-V scaled TSC where the actual + * clocksource value for timekeeping is calculated from an + * underlying hardware counter. Returns the timekeeping + * relevant cycle value and stores the raw value of the + * underlying counter from which it was calculated + * including the clocksource ID of that counter in the + * clocksource hardware snapshot. * @enable: Optional function to enable the clocksource * @disable: Optional function to disable the clocksource * @suspend: Optional suspend function for the clocksource @@ -113,6 +136,7 @@ struct clocksource { unsigned long flags; struct clocksource_base *base; + u64 (*read_snapshot)(struct clocksource *cs, struct clocksource_hw_snapshot *chs); int (*enable)(struct clocksource *cs); void (*disable)(struct clocksource *cs); void (*suspend)(struct clocksource *cs); diff --git a/include/linux/timekeeping.h b/include/linux/timekeeping.h index 7f03e1cefcf6..984a866d293b 100644 --- a/include/linux/timekeeping.h +++ b/include/linux/timekeeping.h @@ -279,18 +279,24 @@ static inline bool ktime_get_aux_ts64(clockid_t id, struct timespec64 *kt) { ret * struct system_time_snapshot - Simultaneous time capture of CLOCK_MONOTONIC_RAW, * a selected CLOCK_* and the clocksource counter value * @cycles: Clocksource counter value to produce the system times + * @hw_cycles: For derived clocksources, the hardware counter value from + * which @cycles was derived * @systime: The system time of the selected CLOCK ID * @monoraw: Monotonic raw system time * @cs_id: Clocksource ID + * @hw_csid: Clocksource ID of the underlying hardware counter for derived + * clocksources which implement the read_snapshot() callback. * @clock_was_set_seq: The sequence number of clock-was-set events * @cs_was_changed_seq: The sequence number of clocksource change events * @valid: True if the snapshot is valid */ struct system_time_snapshot { u64 cycles; + u64 hw_cycles; ktime_t systime; ktime_t monoraw; enum clocksource_ids cs_id; + enum clocksource_ids hw_csid; unsigned int clock_was_set_seq; u8 cs_was_changed_seq; u8 valid; diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index 65d870c2d40d..0d5b67f609bb 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -320,6 +320,7 @@ static __always_inline u64 tk_clock_read(const struct tk_read_base *tkr) return clock->read(clock); } + static inline void clocksource_disable_inline_read(void) { } static inline void clocksource_enable_inline_read(void) { } #endif @@ -1187,6 +1188,18 @@ noinstr time64_t __ktime_get_real_seconds(void) return tk->xtime_sec; } +static inline u64 tk_clock_read_snapshot(const struct tk_read_base *tkr, + struct clocksource_hw_snapshot *chs) +{ + struct clocksource *clock = READ_ONCE(tkr->clock); + + if (unlikely(clock->read_snapshot)) + return clock->read_snapshot(clock, chs); + + return clock->read(clock); +} + + /** * ktime_get_snapshot_id - Simultaneously snapshot a given clock ID with * CLOCK_MONOTONIC_RAW and the underlying @@ -1237,14 +1250,20 @@ void ktime_get_snapshot_id(clockid_t clock_id, struct system_time_snapshot *syst tk = &tkd->timekeeper; do { + struct clocksource_hw_snapshot chs = { }; + seq = read_seqcount_begin(&tkd->seq); /* Aux clocks can be invalid */ if (!tk->clock_valid) return; - now = tk_clock_read(&tk->tkr_mono); + now = tk_clock_read_snapshot(&tk->tkr_mono, &chs); systime_snapshot->cs_id = tk->tkr_mono.clock->id; + + systime_snapshot->hw_cycles = chs.hw_cycles; + systime_snapshot->hw_csid = chs.hw_csid; + systime_snapshot->cs_was_changed_seq = tk->cs_was_changed_seq; systime_snapshot->clock_was_set_seq = tk->clock_was_set_seq; From 73a4c02f94a98d94480c3e5c81450215a4da05ba Mon Sep 17 00:00:00 2001 From: Sandipan Das Date: Mon, 1 Jun 2026 20:28:46 +0530 Subject: [PATCH 138/167] perf/x86/amd/core: Always use the NMI latency mitigation Commit df4d29732fda ("perf/x86/amd: Change/fix NMI latency mitigation to use a timestamp") fixed handling of late-arriving NMIs but limited the mitigation to processors having X86_FEATURE_PERFCTR_CORE. However, it is unclear if processors without this feature are also affected. When Mediated vPMU is enabled on affected hardware, it is also possible to bypass the fix inside KVM guests if X86_FEATURE_PERFCTR_CORE is removed from the guest CPUID (e.g. using "-cpu host,-perfctr-core" with QEMU). Hence, use the mitigation at all times. Fixes: df4d29732fda ("perf/x86/amd: Change/fix NMI latency mitigation to use a timestamp") Signed-off-by: Sandipan Das Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/29a3c970da289ab8f24282933bdb36545c0403e8.1780325517.git.sandipan.das@amd.com --- arch/x86/events/amd/core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/amd/core.c b/arch/x86/events/amd/core.c index 0c92ed5f464b..abc84a92cd59 100644 --- a/arch/x86/events/amd/core.c +++ b/arch/x86/events/amd/core.c @@ -1412,12 +1412,12 @@ static int __init amd_core_pmu_init(void) u64 even_ctr_mask = 0ULL; int i; - if (!boot_cpu_has(X86_FEATURE_PERFCTR_CORE)) - return 0; - /* Avoid calculating the value each time in the NMI handler */ perf_nmi_window = msecs_to_jiffies(100); + if (!boot_cpu_has(X86_FEATURE_PERFCTR_CORE)) + return 0; + /* * If core performance counter extensions exists, we must use * MSR_F15H_PERF_CTL/MSR_F15H_PERF_CTR msrs. See also From 63f48abd55d0417996bca86022925c853a2b436b Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:02 -0700 Subject: [PATCH 139/167] perf/x86/intel/uncore: Fix discovery unit lookup for multi-die systems In uncore_find_add_unit(), PMON units with the same unit ID may be added to the uncore discovery RB-tree for different dies. These units are distinguished by node->die. However, intel_generic_uncore_box_ctl() uses a fixed die ID of -1 when looking up the discovery unit, which may retrieve the wrong node on multi-die systems. Use box->dieid instead so the correct discovery unit is selected. No functional issue has been observed so far because currently supported platforms happen to use the same unit control register for such units. Remove WARN_ON_ONCE() because with the above change a NULL unit can be expected, e.g. when a CPU die is offline during uncore enumeration and the unit is not added to the RB-tree. In this case, intel_uncore_find_discovery_unit() returns NULL once the die becomes online, and it is expected that the PMU box is not functional for that die. Fixes: b1d9ea2e1ca4 ("perf/x86/uncore: Apply the unit control RB tree to MSR uncore units") Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260602144908.263680-2-zide.chen@intel.com --- arch/x86/events/intel/uncore_discovery.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/uncore_discovery.c b/arch/x86/events/intel/uncore_discovery.c index 583cbd06b9b8..60e1200c4691 100644 --- a/arch/x86/events/intel/uncore_discovery.c +++ b/arch/x86/events/intel/uncore_discovery.c @@ -481,8 +481,8 @@ static u64 intel_generic_uncore_box_ctl(struct intel_uncore_box *box) struct intel_uncore_discovery_unit *unit; unit = intel_uncore_find_discovery_unit(box->pmu->type->boxes, - -1, box->pmu->pmu_idx); - if (WARN_ON_ONCE(!unit)) + box->dieid, box->pmu->pmu_idx); + if (!unit) return 0; return unit->addr; From 3c19ea24f02658c4b8ad364458fae4d77fdb3fae Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:03 -0700 Subject: [PATCH 140/167] perf/x86/intel/uncore: Guard against invalid box control address Theoretically, intel_uncore_find_discovery_unit() could return NULL, e.g., when a CPU die is offline during uncore enumeration and its PMU units are not added to the discovery RB-tree. Guard against a NULL return value and the resulting invalid box control address (0) before accessing hardware. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260602144908.263680-3-zide.chen@intel.com --- arch/x86/events/intel/uncore_discovery.c | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/arch/x86/events/intel/uncore_discovery.c b/arch/x86/events/intel/uncore_discovery.c index 60e1200c4691..af7e80fee81f 100644 --- a/arch/x86/events/intel/uncore_discovery.c +++ b/arch/x86/events/intel/uncore_discovery.c @@ -490,17 +490,28 @@ static u64 intel_generic_uncore_box_ctl(struct intel_uncore_box *box) void intel_generic_uncore_msr_init_box(struct intel_uncore_box *box) { - wrmsrq(intel_generic_uncore_box_ctl(box), GENERIC_PMON_BOX_CTL_INT); + u64 box_ctl = intel_generic_uncore_box_ctl(box); + + if (!box_ctl) + return; + + wrmsrq(box_ctl, GENERIC_PMON_BOX_CTL_INT); } void intel_generic_uncore_msr_disable_box(struct intel_uncore_box *box) { - wrmsrq(intel_generic_uncore_box_ctl(box), GENERIC_PMON_BOX_CTL_FRZ); + u64 box_ctl = intel_generic_uncore_box_ctl(box); + + if (box_ctl) + wrmsrq(box_ctl, GENERIC_PMON_BOX_CTL_FRZ); } void intel_generic_uncore_msr_enable_box(struct intel_uncore_box *box) { - wrmsrq(intel_generic_uncore_box_ctl(box), 0); + u64 box_ctl = intel_generic_uncore_box_ctl(box); + + if (box_ctl) + wrmsrq(box_ctl, 0); } static void intel_generic_uncore_msr_enable_event(struct intel_uncore_box *box, @@ -549,6 +560,7 @@ bool intel_generic_uncore_assign_hw_event(struct perf_event *event, if (box->pci_dev) { box_ctl = UNCORE_DISCOVERY_PCI_BOX_CTRL(box_ctl); + hwc->config_base = box_ctl + uncore_pci_event_ctl(box, hwc->idx); hwc->event_base = box_ctl + uncore_pci_perf_ctr(box, hwc->idx); return true; @@ -567,27 +579,30 @@ static inline int intel_pci_uncore_box_ctl(struct intel_uncore_box *box) void intel_generic_uncore_pci_init_box(struct intel_uncore_box *box) { - struct pci_dev *pdev = box->pci_dev; int box_ctl = intel_pci_uncore_box_ctl(box); + if (!box_ctl) + return; + __set_bit(UNCORE_BOX_FLAG_CTL_OFFS8, &box->flags); - pci_write_config_dword(pdev, box_ctl, GENERIC_PMON_BOX_CTL_INT); + pci_write_config_dword(box->pci_dev, box_ctl, GENERIC_PMON_BOX_CTL_INT); } void intel_generic_uncore_pci_disable_box(struct intel_uncore_box *box) { - struct pci_dev *pdev = box->pci_dev; int box_ctl = intel_pci_uncore_box_ctl(box); - pci_write_config_dword(pdev, box_ctl, GENERIC_PMON_BOX_CTL_FRZ); + if (box_ctl) + pci_write_config_dword(box->pci_dev, box_ctl, + GENERIC_PMON_BOX_CTL_FRZ); } void intel_generic_uncore_pci_enable_box(struct intel_uncore_box *box) { - struct pci_dev *pdev = box->pci_dev; int box_ctl = intel_pci_uncore_box_ctl(box); - pci_write_config_dword(pdev, box_ctl, 0); + if (box_ctl) + pci_write_config_dword(box->pci_dev, box_ctl, 0); } static void intel_generic_uncore_pci_enable_event(struct intel_uncore_box *box, From 81ec618f59415bdcf3e8989e2691c1f240be27fb Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:04 -0700 Subject: [PATCH 141/167] perf/x86/intel/uncore: Fix PCI device refcount leak in UPI discovery pci_get_domain_bus_and_slot() increments the reference count of the returned PCI device and therefore requires a matching pci_dev_put(). In skx_upi_topology_cb() and discover_upi_topology(), the lookup is performed inside a loop, but pci_dev_put() is only called once after the loop. As a result, references from all previous iterations are leaked. Move pci_dev_put(dev) into the if (dev) block immediately after upi_fill_topology() returns. Opportunistically, fix uninitialized variable in skx_upi_topology_cb(). Fixes: 4cfce57fa42d ("perf/x86/intel/uncore: Enable UPI topology discovery for Skylake Server") Fixes: f680b6e6062e ("perf/x86/intel/uncore: Enable UPI topology discovery for Icelake Server") Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260602144908.263680-4-zide.chen@intel.com --- arch/x86/events/intel/uncore_snbep.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/uncore_snbep.c b/arch/x86/events/intel/uncore_snbep.c index 215d33e260ed..c9ce206fcbb6 100644 --- a/arch/x86/events/intel/uncore_snbep.c +++ b/arch/x86/events/intel/uncore_snbep.c @@ -4261,7 +4261,7 @@ err: static int skx_upi_topology_cb(struct intel_uncore_type *type, int segment, int die, u64 cpu_bus_msr) { - int idx, ret; + int idx, ret = 0; struct intel_uncore_topology *upi; unsigned int devfn; struct pci_dev *dev = NULL; @@ -4274,12 +4274,12 @@ static int skx_upi_topology_cb(struct intel_uncore_type *type, int segment, dev = pci_get_domain_bus_and_slot(segment, bus, devfn); if (dev) { ret = upi_fill_topology(dev, upi, idx); + pci_dev_put(dev); if (ret) break; } } - pci_dev_put(dev); return ret; } @@ -5499,6 +5499,7 @@ static int discover_upi_topology(struct intel_uncore_type *type, int ubox_did, i devfn); if (dev) { ret = upi_fill_topology(dev, upi, idx); + pci_dev_put(dev); if (ret) goto err; } @@ -5506,7 +5507,6 @@ static int discover_upi_topology(struct intel_uncore_type *type, int ubox_did, i } err: pci_dev_put(ubox); - pci_dev_put(dev); return ret; } From 9a0bb848a37150aeccc10088e141339917d995dc Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:05 -0700 Subject: [PATCH 142/167] perf/x86/intel/uncore: Defer ADL global PMON enable to enable_box() On some Raptor Cove CPUs, enabling uncore PMON globally at driver init may increase power consumption even when no perf events are in use. Drop adl_uncore_msr_init_box() and defer programming the global control register to enable_box(), so it is only set when a box is actually used. IMC and IMC freerunning counters use a separate control path and are unaffected. Fixes: 772ed05f3c5c ("perf/x86/intel/uncore: Add Alder Lake support") Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260602144908.263680-5-zide.chen@intel.com --- arch/x86/events/intel/uncore_snb.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c index 3dbc6bacbd9d..edddd4f9ab5f 100644 --- a/arch/x86/events/intel/uncore_snb.c +++ b/arch/x86/events/intel/uncore_snb.c @@ -563,12 +563,6 @@ void tgl_uncore_cpu_init(void) skl_uncore_msr_ops.init_box = rkl_uncore_msr_init_box; } -static void adl_uncore_msr_init_box(struct intel_uncore_box *box) -{ - if (box->pmu->pmu_idx == 0) - wrmsrq(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); -} - static void adl_uncore_msr_enable_box(struct intel_uncore_box *box) { wrmsrq(ADL_UNC_PERF_GLOBAL_CTL, SNB_UNC_GLOBAL_CTL_EN); @@ -587,7 +581,6 @@ static void adl_uncore_msr_exit_box(struct intel_uncore_box *box) } static struct intel_uncore_ops adl_uncore_msr_ops = { - .init_box = adl_uncore_msr_init_box, .enable_box = adl_uncore_msr_enable_box, .disable_box = adl_uncore_msr_disable_box, .exit_box = adl_uncore_msr_exit_box, From ce044cfb7a365742b2e9229a4b70cf2a663d7270 Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:06 -0700 Subject: [PATCH 143/167] perf/x86/intel/uncore: Move die_to_cpu() to uncore.c Move die_to_cpu() into uncore.c so it can be reused by the MSR initialization path, preparing for the introduction of an MSR global initialization callback. Move the cpus_read_{lock,unlock}() out of the API, in order to make it possible to be called when the lock is being held. Add the uncore_ prefix for consistency with other uncore APIs. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260602144908.263680-6-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 19 +++++++++++++++++++ arch/x86/events/intel/uncore.h | 1 + arch/x86/events/intel/uncore_snbep.c | 23 +++-------------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index e9cc1ba921c5..22256ded2d67 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -83,6 +83,25 @@ int uncore_device_to_die(struct pci_dev *dev) return -1; } +/* + * Using cpus_read_lock() to ensure cpu is not going down between + * looking at cpu_online_mask. + * + * The lock must be held by the caller. + */ +int uncore_die_to_cpu(int die) +{ + int res = 0, cpu; + + for_each_online_cpu(cpu) { + if (topology_logical_die_id(cpu) == die) { + res = cpu; + break; + } + } + return res; +} + static void uncore_free_pcibus_map(void) { struct pci2phy_map *map, *tmp; diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index c35918c01afa..94c68e3417b6 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -235,6 +235,7 @@ struct pci2phy_map *__find_pci2phy_map(int segment); int uncore_pcibus_to_dieid(struct pci_bus *bus); int uncore_die_to_segment(int die); int uncore_device_to_die(struct pci_dev *dev); +int uncore_die_to_cpu(int die); ssize_t uncore_event_show(struct device *dev, struct device_attribute *attr, char *buf); diff --git a/arch/x86/events/intel/uncore_snbep.c b/arch/x86/events/intel/uncore_snbep.c index c9ce206fcbb6..772b78237424 100644 --- a/arch/x86/events/intel/uncore_snbep.c +++ b/arch/x86/events/intel/uncore_snbep.c @@ -3704,25 +3704,6 @@ static int skx_msr_cpu_bus_read(int cpu, u64 *topology) return 0; } -static int die_to_cpu(int die) -{ - int res = 0, cpu, current_die; - /* - * Using cpus_read_lock() to ensure cpu is not going down between - * looking at cpu_online_mask. - */ - cpus_read_lock(); - for_each_online_cpu(cpu) { - current_die = topology_logical_die_id(cpu); - if (current_die == die) { - res = cpu; - break; - } - } - cpus_read_unlock(); - return res; -} - enum { IIO_TOPOLOGY_TYPE, UPI_TOPOLOGY_TYPE, @@ -3794,8 +3775,9 @@ static int skx_pmu_get_topology(struct intel_uncore_type *type, int die, ret = -EPERM; u64 cpu_bus_msr; + cpus_read_lock(); for (die = 0; die < uncore_max_dies(); die++) { - ret = skx_msr_cpu_bus_read(die_to_cpu(die), &cpu_bus_msr); + ret = skx_msr_cpu_bus_read(uncore_die_to_cpu(die), &cpu_bus_msr); if (ret) break; @@ -3807,6 +3789,7 @@ static int skx_pmu_get_topology(struct intel_uncore_type *type, if (ret) break; } + cpus_read_unlock(); return ret; } From 2aa7dacf8a3d928303df9c770c1bd7f50d1f8f2a Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:07 -0700 Subject: [PATCH 144/167] perf/x86/intel/uncore: Fix uncore_die_to_cpu() for offline dies If the die is offline when uncore_die_to_cpu() is called, it silently returns 0, which is misleading. Return -1 in this case to indicate that all CPUs on the die are offline and the caller can take care of it accordingly. Opportunistically, replace -EPERM with -ENODEV, as -ENODEV is the appropriate error when no CPUs are online across all dies. Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260602144908.263680-7-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 2 +- arch/x86/events/intel/uncore_snbep.c | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 22256ded2d67..4b3a1fa5b41b 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -91,7 +91,7 @@ int uncore_device_to_die(struct pci_dev *dev) */ int uncore_die_to_cpu(int die) { - int res = 0, cpu; + int res = -1, cpu; for_each_online_cpu(cpu) { if (topology_logical_die_id(cpu) == die) { diff --git a/arch/x86/events/intel/uncore_snbep.c b/arch/x86/events/intel/uncore_snbep.c index 772b78237424..334dc384b5b9 100644 --- a/arch/x86/events/intel/uncore_snbep.c +++ b/arch/x86/events/intel/uncore_snbep.c @@ -3772,12 +3772,17 @@ static void pmu_free_topology(struct intel_uncore_type *type) static int skx_pmu_get_topology(struct intel_uncore_type *type, int (*topology_cb)(struct intel_uncore_type*, int, int, u64)) { - int die, ret = -EPERM; + int die, ret = -ENODEV; u64 cpu_bus_msr; + int cpu; cpus_read_lock(); for (die = 0; die < uncore_max_dies(); die++) { - ret = skx_msr_cpu_bus_read(uncore_die_to_cpu(die), &cpu_bus_msr); + cpu = uncore_die_to_cpu(die); + if (cpu == -1) + continue; + + ret = skx_msr_cpu_bus_read(cpu, &cpu_bus_msr); if (ret) break; From 1a308a168c9286a335a05926204ef3ebb68fba1c Mon Sep 17 00:00:00 2001 From: Zide Chen Date: Tue, 2 Jun 2026 07:49:08 -0700 Subject: [PATCH 145/167] perf/x86/intel/uncore: Implement global init callback for GNR uncore On Sierra Forest and Clearwater Forest, the FRZ_ALL bit in the global control register defaults to 0 at boot, but UBOX PMON units do not work until the global control register is explicitly written with 0 to trigger hardware initialization properly. Implement the generic uncore_msr_global_init() callback and add it to gnr_uncore_init[], which is shared by GNR, GRR, SRF, and CWF. Fixes: 632c4bf6d007 ("perf/x86/intel/uncore: Support Granite Rapids") Signed-off-by: Zide Chen Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260602144908.263680-8-zide.chen@intel.com --- arch/x86/events/intel/uncore.c | 13 ++++++++++++- arch/x86/events/intel/uncore.h | 2 +- arch/x86/events/intel/uncore_discovery.c | 9 +++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c index 4b3a1fa5b41b..7857959c6e82 100644 --- a/arch/x86/events/intel/uncore.c +++ b/arch/x86/events/intel/uncore.c @@ -1716,7 +1716,7 @@ err: return ret; } -static int uncore_mmio_global_init(u64 ctl) +static int uncore_mmio_global_init(int die, u64 ctl) { void __iomem *io_addr; @@ -1731,6 +1731,16 @@ static int uncore_mmio_global_init(u64 ctl) return 0; } +static int uncore_msr_global_init(int die, u64 msr) +{ + int cpu = uncore_die_to_cpu(die); + + if (cpu == -1) + return -ENODEV; + + return wrmsrq_on_cpu(cpu, msr, 0); +} + static const struct uncore_plat_init nhm_uncore_init __initconst = { .cpu_init = nhm_uncore_cpu_init, }; @@ -1871,6 +1881,7 @@ static const struct uncore_plat_init gnr_uncore_init __initconst = { .domain[0].base_is_pci = true, .domain[0].discovery_base = UNCORE_DISCOVERY_TABLE_DEVICE, .domain[0].units_ignore = gnr_uncore_units_ignore, + .domain[0].global_init = uncore_msr_global_init, }; static const struct uncore_plat_init dmr_uncore_init __initconst = { diff --git a/arch/x86/events/intel/uncore.h b/arch/x86/events/intel/uncore.h index 94c68e3417b6..c2e5ccb1d72c 100644 --- a/arch/x86/events/intel/uncore.h +++ b/arch/x86/events/intel/uncore.h @@ -53,7 +53,7 @@ struct uncore_discovery_domain { /* MSR address or PCI device used as the discovery base */ u32 discovery_base; bool base_is_pci; - int (*global_init)(u64 ctl); + int (*global_init)(int die, u64 ctl); /* The units in the discovery table should be ignored. */ int *units_ignore; diff --git a/arch/x86/events/intel/uncore_discovery.c b/arch/x86/events/intel/uncore_discovery.c index af7e80fee81f..e50776222256 100644 --- a/arch/x86/events/intel/uncore_discovery.c +++ b/arch/x86/events/intel/uncore_discovery.c @@ -287,7 +287,7 @@ static int __parse_discovery_table(struct uncore_discovery_domain *domain, if (!io_addr) return -ENOMEM; - if (domain->global_init && domain->global_init(global.ctl)) { + if (domain->global_init && domain->global_init(die, global.ctl)) { ret = -ENODEV; goto out; } @@ -399,7 +399,6 @@ static bool uncore_discovery_msr(struct uncore_discovery_domain *domain) if (!die_mask) return false; - cpus_read_lock(); for_each_online_cpu(cpu) { die = topology_logical_die_id(cpu); if (__test_and_set_bit(die, die_mask)) @@ -414,8 +413,6 @@ static bool uncore_discovery_msr(struct uncore_discovery_domain *domain) __parse_discovery_table(domain, base, die, &parsed); } - cpus_read_unlock(); - kfree(die_mask); return parsed; } @@ -429,10 +426,14 @@ bool uncore_discovery(struct uncore_plat_init *init) for (i = 0; i < UNCORE_DISCOVERY_DOMAINS; i++) { domain = &init->domain[i]; if (domain->discovery_base) { + cpus_read_lock(); + if (!domain->base_is_pci) ret |= uncore_discovery_msr(domain); else ret |= uncore_discovery_pci(domain); + + cpus_read_unlock(); } } From 2369ce0f16b89e3d85c9683c06eb104545999378 Mon Sep 17 00:00:00 2001 From: Chun-Tse Shao Date: Tue, 2 Jun 2026 11:13:48 -0700 Subject: [PATCH 146/167] perf: Reveal PMU type in fdinfo It gives useful info on knowing which PMUs are reserved by this process. Also add config which would be useful. Testing cycles: $ ./perf stat -e cycles & $ cat /proc/`pidof perf`/fdinfo/3 pos: 0 flags: 02000002 mnt_id: 16 ino: 3081 perf_event_attr.type: 0 perf_event_attr.config: 0x0 perf_event_attr.config1: 0x0 perf_event_attr.config2: 0x0 perf_event_attr.config3: 0x0 perf_event_attr.config4: 0x0 Testing L1-dcache-load-misses: $ ./perf stat -e L1-dcache-load-misses & $ cat /proc/`pidof perf`/fdinfo/3 pos: 0 flags: 02000002 mnt_id: 16 ino: 1072 perf_event_attr.type: 3 perf_event_attr.config: 0x10000 perf_event_attr.config1: 0x0 perf_event_attr.config2: 0x0 perf_event_attr.config3: 0x0 perf_event_attr.config4: 0x0 Signed-off-by: Chun-Tse Shao Assisted-by: Gemini:gemini-3.1-pro-preview Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Ian Rogers Link: https://patch.msgid.link/20260602181349.3969429-1-ctshao@google.com --- kernel/events/core.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index 7935d5663944..95d806bba654 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -58,6 +58,7 @@ #include #include #include +#include #include "internal.h" @@ -7546,6 +7547,33 @@ static int perf_fasync(int fd, struct file *filp, int on) return 0; } +static void perf_show_fdinfo(struct seq_file *m, struct file *f) +{ + struct perf_event *event = f->private_data; + struct perf_event_context *ctx; + struct mutex *child_mutex; + + ctx = perf_event_ctx_lock(event); + child_mutex = event->parent ? &event->parent->child_mutex : &event->child_mutex; + mutex_lock(child_mutex); + + seq_printf(m, "perf_event_attr.type:\t%u\n", event->orig_type); + if (event->pmu) + seq_printf(m, "pmu_type:\t%u\n", event->pmu->type); + seq_printf(m, "perf_event_attr.config:\t0x%llx\n", (unsigned long long)event->attr.config); + seq_printf(m, "perf_event_attr.config1:\t0x%llx\n", + (unsigned long long)event->attr.config1); + seq_printf(m, "perf_event_attr.config2:\t0x%llx\n", + (unsigned long long)event->attr.config2); + seq_printf(m, "perf_event_attr.config3:\t0x%llx\n", + (unsigned long long)event->attr.config3); + seq_printf(m, "perf_event_attr.config4:\t0x%llx\n", + (unsigned long long)event->attr.config4); + + mutex_unlock(child_mutex); + perf_event_ctx_unlock(event, ctx); +} + static const struct file_operations perf_fops = { .release = perf_release, .read = perf_read, @@ -7554,6 +7582,7 @@ static const struct file_operations perf_fops = { .compat_ioctl = perf_compat_ioctl, .mmap = perf_mmap, .fasync = perf_fasync, + .show_fdinfo = perf_show_fdinfo, }; /* From 67d27727854def4a7e2b386429941f5c4741ccc4 Mon Sep 17 00:00:00 2001 From: Sandipan Das Date: Mon, 1 Jun 2026 17:43:05 +0530 Subject: [PATCH 147/167] perf/x86/amd/uncore: Use Node ID to identify DF and UMC domains For DF and UMC PMUs, a single context is shared across all CPUs that are connected to the same Data Fabric (DF) instance. Currently, the Package ID, which also happens to be the Socket ID, is used to identify DF instances. This approach works for configurations having a single IO Die (IOD) but fails in the following cases. * Older Zen 1 processors, where each chiplet has its own DF instance. * Any configurations with multiple DF instances or multiple IODs in the same package. The correct way to identify DF instances is through the Node ID (not to be confused with NUMA Node ID). This is available in ECX[7:0] of CPUID leaf 0x8000001e and returned via topology_amd_node_id(). Hence, replace usage of topology_logical_package_id() with topology_amd_node_id(). Fixes: 07888daa056e ("perf/x86/amd/uncore: Move discovery and registration") Signed-off-by: Sandipan Das Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/e7a71a727c6a7b118c23d3e469929c538c4665aa.1780315832.git.sandipan.das@amd.com --- arch/x86/events/amd/uncore.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index dd956cfcadef..a0364ca2f917 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -700,7 +700,7 @@ void amd_uncore_df_ctx_scan(struct amd_uncore *uncore, unsigned int cpu) info.split.aux_data = 0; info.split.num_pmcs = NUM_COUNTERS_NB; info.split.gid = 0; - info.split.cid = topology_logical_package_id(cpu); + info.split.cid = topology_amd_node_id(cpu); if (pmu_version >= 2) { ebx.full = cpuid_ebx(EXT_PERFMON_DEBUG_FEATURES); @@ -999,8 +999,8 @@ void amd_uncore_umc_ctx_scan(struct amd_uncore *uncore, unsigned int cpu) cpuid(EXT_PERFMON_DEBUG_FEATURES, &eax, &ebx.full, &ecx, &edx); info.split.aux_data = ecx; /* stash active mask */ info.split.num_pmcs = ebx.split.num_umc_pmc; - info.split.gid = topology_logical_package_id(cpu); - info.split.cid = topology_logical_package_id(cpu); + info.split.gid = topology_amd_node_id(cpu); + info.split.cid = topology_amd_node_id(cpu); *per_cpu_ptr(uncore->info, cpu) = info; } From 1d453fff35806b1108ae7709de0521bd42770b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 19 May 2026 08:14:20 +0200 Subject: [PATCH 148/167] vdso/gettimeofday: Rename __arch_get_vdso_u_timens_data() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Originally this function was supposed to work the same way as __arch_get_vdso_u_time_data() and be overridden on some architectures. However the actually used implementation, which just adds PAGE_SIZE, does not need this override mechanism. Adjust the name to reflect the true nature of the function. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260519-vdso-arch_get_vdso_u_timens_data-v1-1-43f0d62716e8@linutronix.de --- lib/vdso/gettimeofday.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/vdso/gettimeofday.c b/lib/vdso/gettimeofday.c index da224011fafd..e0f289d3d110 100644 --- a/lib/vdso/gettimeofday.c +++ b/lib/vdso/gettimeofday.c @@ -126,7 +126,7 @@ bool vdso_get_timestamp(const struct vdso_time_data *vd, const struct vdso_clock } static __always_inline -const struct vdso_time_data *__arch_get_vdso_u_timens_data(const struct vdso_time_data *vd) +const struct vdso_time_data *vdso_timens_data(const struct vdso_time_data *vd) { return (void *)vd + PAGE_SIZE; } @@ -135,7 +135,7 @@ static __always_inline bool do_hres_timens(const struct vdso_time_data *vdns, const struct vdso_clock *vcns, clockid_t clk, struct __kernel_timespec *ts) { - const struct vdso_time_data *vd = __arch_get_vdso_u_timens_data(vdns); + const struct vdso_time_data *vd = vdso_timens_data(vdns); const struct timens_offset *offs = &vcns->offset[clk]; const struct vdso_clock *vc = vd->clock_data; u32 seq; @@ -191,7 +191,7 @@ static __always_inline bool do_coarse_timens(const struct vdso_time_data *vdns, const struct vdso_clock *vcns, clockid_t clk, struct __kernel_timespec *ts) { - const struct vdso_time_data *vd = __arch_get_vdso_u_timens_data(vdns); + const struct vdso_time_data *vd = vdso_timens_data(vdns); const struct timens_offset *offs = &vcns->offset[clk]; const struct vdso_clock *vc = vd->clock_data; const struct vdso_timestamp *vdso_ts; @@ -250,7 +250,7 @@ bool do_aux(const struct vdso_time_data *vd, clockid_t clock, struct __kernel_ti do { while (vdso_read_begin_timens(vc, &seq)) { /* Re-read from the real time data page, reload seq by looping */ - vd = __arch_get_vdso_u_timens_data(vd); + vd = vdso_timens_data(vd); vc = &vd->aux_clock_data[idx]; } @@ -360,7 +360,7 @@ __cvdso_gettimeofday_data(const struct vdso_time_data *vd, if (unlikely(tz != NULL)) { if (vdso_is_timens_clock(vc)) - vd = __arch_get_vdso_u_timens_data(vd); + vd = vdso_timens_data(vd); tz->tz_minuteswest = vd[CS_HRES_COARSE].tz_minuteswest; tz->tz_dsttime = vd[CS_HRES_COARSE].tz_dsttime; @@ -383,7 +383,7 @@ __cvdso_time_data(const struct vdso_time_data *vd, __kernel_old_time_t *time) __kernel_old_time_t t; if (vdso_is_timens_clock(vc)) { - vd = __arch_get_vdso_u_timens_data(vd); + vd = vdso_timens_data(vd); vc = vd->clock_data; } @@ -414,7 +414,7 @@ bool __cvdso_clock_getres_common(const struct vdso_time_data *vd, clockid_t cloc return false; if (vdso_is_timens_clock(vc)) - vd = __arch_get_vdso_u_timens_data(vd); + vd = vdso_timens_data(vd); /* * Convert the clockid to a bitmask and use it to check which From facdd1b82a41092f7f7c69f2881b946d74ede997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:15 +0200 Subject: [PATCH 149/167] MAINTAINERS: Add include/linux/vdso_datastore.h to vDSO block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This file is part of the generic vDSO subsystem. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-1-2f79dcd6c78f@linutronix.de --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9ec290e38b44..65b2336f4fae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10870,6 +10870,7 @@ L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/vdso F: include/asm-generic/vdso/vsyscall.h +F: include/linux/vdso_datastore.h F: include/vdso/ F: kernel/time/namespace_vdso.c F: kernel/time/vsyscall.c From 551f209164c4d2cb8d5542351f145e8257c44702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:16 +0200 Subject: [PATCH 150/167] vdso/datastore: Always provide symbol declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow callers to easily reference these symbols in code that is built even when the generic datastore is disabled. As there are no good default no-op variants of these symbols, do not provide stubs but require users to have their own fallback handling using IS_ENABLED(CONFIG_HAVE_GENERIC_VDSO). Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-2-2f79dcd6c78f@linutronix.de --- include/linux/vdso_datastore.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/vdso_datastore.h b/include/linux/vdso_datastore.h index 0b530428db71..3dfba9502d78 100644 --- a/include/linux/vdso_datastore.h +++ b/include/linux/vdso_datastore.h @@ -2,12 +2,12 @@ #ifndef _LINUX_VDSO_DATASTORE_H #define _LINUX_VDSO_DATASTORE_H -#ifdef CONFIG_HAVE_GENERIC_VDSO #include extern const struct vm_special_mapping vdso_vvar_mapping; struct vm_area_struct *vdso_install_vvar_mapping(struct mm_struct *mm, unsigned long addr); +#ifdef CONFIG_HAVE_GENERIC_VDSO void __init vdso_setup_data_pages(void); #else /* !CONFIG_HAVE_GENERIC_VDSO */ static inline void vdso_setup_data_pages(void) { } From 60ad2f1158577b4923987f0a410456cfccd0fd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:17 +0200 Subject: [PATCH 151/167] MIPS: Introduce Kconfig MIPS_GENERIC_GETTIMEOFDAY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The logic to enable the generic vDSO Kconfig symbols is about to become more complex. Introduce a new helper symbol to keep the configuration readable. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-3-2f79dcd6c78f@linutronix.de --- arch/mips/Kconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 3249880650da..b041d3daf7c7 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -38,7 +38,6 @@ config MIPS select GENERIC_BUILTIN_DTB if BUILTIN_DTB select GENERIC_CMOS_UPDATE select GENERIC_CPU_AUTOPROBE - select GENERIC_GETTIMEOFDAY select GENERIC_IRQ_PROBE select GENERIC_IRQ_SHOW select GENERIC_ISA_DMA if EISA @@ -3169,6 +3168,10 @@ endmenu config MIPS_EXTERNAL_TIMER bool +config MIPS_GENERIC_GETTIMEOFDAY + def_bool y + select GENERIC_GETTIMEOFDAY + menu "CPU Power Management" if CPU_SUPPORTS_CPUFREQ && MIPS_EXTERNAL_TIMER From 8ea920c48d82a0ef031bedfb649d4d8c77ef3d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:18 +0200 Subject: [PATCH 152/167] MIPS: VDSO: Only map the data pages when the vDSO is used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A future change will make it possible to disable the time-related vDSO. In that case there is no point in calling into the datastore. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-4-2f79dcd6c78f@linutronix.de --- arch/mips/Kconfig | 2 +- arch/mips/kernel/vdso.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index b041d3daf7c7..f56e1a5f5cd9 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -74,7 +74,6 @@ config MIPS select HAVE_FUNCTION_GRAPH_TRACER select HAVE_FUNCTION_TRACER select HAVE_GCC_PLUGINS - select HAVE_GENERIC_VDSO select HAVE_IOREMAP_PROT select HAVE_IRQ_EXIT_ON_IRQ_STACK select HAVE_IRQ_TIME_ACCOUNTING @@ -3171,6 +3170,7 @@ config MIPS_EXTERNAL_TIMER config MIPS_GENERIC_GETTIMEOFDAY def_bool y select GENERIC_GETTIMEOFDAY + select HAVE_GENERIC_VDSO menu "CPU Power Management" diff --git a/arch/mips/kernel/vdso.c b/arch/mips/kernel/vdso.c index 2fa4df3e46e4..bd1fc17d3975 100644 --- a/arch/mips/kernel/vdso.c +++ b/arch/mips/kernel/vdso.c @@ -129,7 +129,7 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) * This ensures that when the kernel updates the VDSO data userland * will observe it without requiring cache invalidations. */ - if (cpu_has_dc_aliases) { + if (cpu_has_dc_aliases && IS_ENABLED(CONFIG_HAVE_GENERIC_VDSO)) { base = __ALIGN_MASK(base, shm_align_mask); base += ((unsigned long)vdso_k_time_data - gic_size) & shm_align_mask; } @@ -137,10 +137,12 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp) data_addr = base + gic_size; vdso_addr = data_addr + VDSO_NR_PAGES * PAGE_SIZE; - vma = vdso_install_vvar_mapping(mm, data_addr); - if (IS_ERR(vma)) { - ret = PTR_ERR(vma); - goto out; + if (IS_ENABLED(CONFIG_HAVE_GENERIC_VDSO)) { + vma = vdso_install_vvar_mapping(mm, data_addr); + if (IS_ERR(vma)) { + ret = PTR_ERR(vma); + goto out; + } } /* Map GIC user page. */ From c51100f9e26857f2b2376d5cd657a15f52b9e05c Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 4 Jun 2026 10:35:16 +0100 Subject: [PATCH 153/167] clocksource/hyperv: Implement read_snapshot() for TSC page clocksource Implement the read_snapshot() callback for the Hyper-V TSC page clock- source. This returns the derived 10MHz reference time (for timekeeping) while also providing the raw TSC value that was used to compute it. When the TSC page is valid, hv_read_tsc_page_tsc() atomically captures both values from a single RDTSC inside the sequence-counter protected read. When the TSC page is invalid (sequence == 0), the hw_csid and hw_cycles are set to zero indicating no value is available. This enables ktime_get_snapshot_id() to provide the raw TSC to consumers like KVM's master clock when running nested guests under Hyper-V. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.6-1m Reviewed-by: Michael Kelley Link: https://patch.msgid.link/20260604095755.64849-2-dwmw2@infradead.org --- drivers/clocksource/hyperv_timer.c | 37 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/drivers/clocksource/hyperv_timer.c b/drivers/clocksource/hyperv_timer.c index e9f5034a1bc8..df567795d175 100644 --- a/drivers/clocksource/hyperv_timer.c +++ b/drivers/clocksource/hyperv_timer.c @@ -444,6 +444,22 @@ static u64 notrace read_hv_clock_tsc_cs(struct clocksource *arg) return read_hv_clock_tsc(); } +static u64 notrace read_hv_clock_tsc_cs_snapshot(struct clocksource *arg, + struct clocksource_hw_snapshot *chs) +{ + u64 time; + + if (hv_read_tsc_page_tsc(tsc_page, &chs->hw_cycles, &time)) { + chs->hw_csid = CSID_X86_TSC; + } else { + chs->hw_cycles = 0; + chs->hw_csid = CSID_GENERIC; + time = read_hv_clock_msr(); + } + + return time; +} + static u64 noinstr read_hv_sched_clock_tsc(void) { return (read_hv_clock_tsc() - hv_sched_clock_offset) * @@ -492,18 +508,19 @@ static int hv_cs_enable(struct clocksource *cs) #endif static struct clocksource hyperv_cs_tsc = { - .name = "hyperv_clocksource_tsc_page", - .rating = 500, - .read = read_hv_clock_tsc_cs, - .mask = CLOCKSOURCE_MASK(64), - .flags = CLOCK_SOURCE_IS_CONTINUOUS, - .suspend= suspend_hv_clock_tsc, - .resume = resume_hv_clock_tsc, + .name = "hyperv_clocksource_tsc_page", + .rating = 500, + .read = read_hv_clock_tsc_cs, + .read_snapshot = read_hv_clock_tsc_cs_snapshot, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .suspend = suspend_hv_clock_tsc, + .resume = resume_hv_clock_tsc, #ifdef HAVE_VDSO_CLOCKMODE_HVCLOCK - .enable = hv_cs_enable, - .vdso_clock_mode = VDSO_CLOCKMODE_HVCLOCK, + .enable = hv_cs_enable, + .vdso_clock_mode = VDSO_CLOCKMODE_HVCLOCK, #else - .vdso_clock_mode = VDSO_CLOCKMODE_NONE, + .vdso_clock_mode = VDSO_CLOCKMODE_NONE, #endif }; From 19fa3e50644d2d47fca713b0df640f67dcd26634 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 4 Jun 2026 10:35:17 +0100 Subject: [PATCH 154/167] x86/kvmclock: Implement read_snapshot() for kvmclock clocksource Implement the read_snapshot() callback for the kvmclock clocksource. This returns the kvmclock nanosecond value (for timekeeping) while also providing the raw TSC value that was used to compute it. The TSC is read inside the pvclock seqlock-protected region, ensuring the raw TSC and derived kvmclock value are atomically paired. This enables ktime_get_snapshot_id() to provide the raw TSC to consumers like the vmclock PTP driver, which currently has to do a separate call to get_cycles() to obtain a value at *approximately* the same time, to feed through the vmclock calculation. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.6-1m Link: https://patch.msgid.link/20260604095755.64849-3-dwmw2@infradead.org --- arch/x86/kernel/kvmclock.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/arch/x86/kernel/kvmclock.c b/arch/x86/kernel/kvmclock.c index b5991d53fc0e..cb3d0ca1fa22 100644 --- a/arch/x86/kernel/kvmclock.c +++ b/arch/x86/kernel/kvmclock.c @@ -87,6 +87,27 @@ static u64 kvm_clock_get_cycles(struct clocksource *cs) return kvm_clock_read(); } +static u64 kvm_clock_get_cycles_snapshot(struct clocksource *cs, + struct clocksource_hw_snapshot *chs) +{ + struct pvclock_vcpu_time_info *src; + unsigned version; + u64 ret, tsc; + + preempt_disable_notrace(); + src = this_cpu_pvti(); + do { + version = pvclock_read_begin(src); + tsc = rdtsc_ordered(); + ret = __pvclock_read_cycles(src, tsc); + } while (pvclock_read_retry(src, version)); + preempt_enable_notrace(); + + chs->hw_cycles = tsc; + chs->hw_csid = CSID_X86_TSC; + return ret; +} + static noinstr u64 kvm_sched_clock_read(void) { return pvclock_clocksource_read_nowd(this_cpu_pvti()) - kvm_sched_clock_offset; @@ -156,13 +177,14 @@ static int kvm_cs_enable(struct clocksource *cs) } static struct clocksource kvm_clock = { - .name = "kvm-clock", - .read = kvm_clock_get_cycles, - .rating = 400, - .mask = CLOCKSOURCE_MASK(64), - .flags = CLOCK_SOURCE_IS_CONTINUOUS, - .id = CSID_X86_KVM_CLK, - .enable = kvm_cs_enable, + .name = "kvm-clock", + .read = kvm_clock_get_cycles, + .read_snapshot = kvm_clock_get_cycles_snapshot, + .rating = 400, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .id = CSID_X86_KVM_CLK, + .enable = kvm_cs_enable, }; static void kvm_register_clock(char *txt) From bc484a5096732cd858771cccd3164ec985bdc03d Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Thu, 4 Jun 2026 10:35:18 +0100 Subject: [PATCH 155/167] ptp: vmclock: Use hw_cycles from snapshot for precise TSC pairing When the system clocksource is kvmclock or Hyper-V (not the TSC directly), vmclock_get_crosststamp() falls through to a separate get_cycles() call, losing the atomic pairing between the system time snapshot and the TSC reading. Now that ktime_get_snapshot_id() populates hw_cycles with the underlying TSC value for derived clocksources, use it when available. This gives a perfect (system_time, tsc) pairing for the device time calculation. The SUPPORT_KVMCLOCK wrapper is still needed to convert the TSC into kvmclock nanoseconds for system_counter->cycles, because otherwise get_device_system_crosststamp() can't interpret the result against the system clock. Signed-off-by: David Woodhouse Signed-off-by: Thomas Gleixner Assisted-by: Kiro:claude-opus-4.6-1m Link: https://patch.msgid.link/20260604095755.64849-4-dwmw2@infradead.org --- drivers/ptp/ptp_vmclock.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index d6a5a533164a..eebdcd5ebc08 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -140,6 +140,10 @@ static int vmclock_get_crosststamp(struct vmclock_state *st, if (sts->pre_sts.cs_id == st->cs_id) { cycle = sts->pre_sts.cycles; sts->post_sts = sts->pre_sts; + } else if (sts->pre_sts.hw_csid == st->cs_id && + sts->pre_sts.hw_cycles) { + cycle = sts->pre_sts.hw_cycles; + sts->post_sts = sts->pre_sts; } else { cycle = get_cycles(); ptp_read_system_postts(sts); From a40e0f8eadd44d7b0f856b54c876aea1b93415f4 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Fri, 5 Jun 2026 03:06:22 -0700 Subject: [PATCH 156/167] cleanup: Specify nonnull argument index The guard constructors were annotated with an empty __nonnull_args(), relying on __nonnull__() marking every pointer parameter as non-NULL. Sparse cannot parse the empty argument list. Both constructors take the lock pointer as their first parameter, so specify the index explicitly: __nonnull_args(1). Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aiJi0WcYE8FZt-jO@stanley.mountain/ Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/aiKpH3cLBEj3TF2Q@shell.ilvokhin.com --- include/linux/cleanup.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h index 65416938e318..b1b5698cbf1b 100644 --- a/include/linux/cleanup.h +++ b/include/linux/cleanup.h @@ -397,7 +397,7 @@ static __maybe_unused const bool class_##_name##_is_conditional = _is_cond __DEFINE_GUARD_LOCK_PTR(_name, _T) #define DEFINE_GUARD(_name, _type, _lock, _unlock) \ - static __always_inline __nonnull_args() _type class_##_name##_constructor(_type _T); \ + static __always_inline __nonnull_args(1) _type class_##_name##_constructor(_type _T); \ DEFINE_CLASS(_name, _type, _unlock, ({ _lock; _T; }), _type _T); \ DEFINE_CLASS_IS_GUARD(_name) @@ -498,7 +498,7 @@ static __always_inline void class_##_name##_destructor(class_##_name##_t *_T) \ __DEFINE_GUARD_LOCK_PTR(_name, &_T->lock) #define __DEFINE_LOCK_GUARD_1(_name, _type, ...) \ -static __always_inline __nonnull_args() \ +static __always_inline __nonnull_args(1) \ class_##_name##_t class_##_name##_constructor(_type *l) \ __no_context_analysis \ { \ From 4770880855359b345314ca1d0b28f9be8886eba8 Mon Sep 17 00:00:00 2001 From: Boqun Feng Date: Thu, 4 Jun 2026 22:23:29 -0700 Subject: [PATCH 157/167] MAINTAINERS: Add RUST [SYNC] entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have two pull requests on Rust synchronization primitives with 10+ patches in a row for recent cycles, so it makes sense to start the effort of handling this area as a group. Luckily for me, Gary Guo and Alice Ryhl agreed to help as co-maintainers, and we also have a talented group of reviewers: Lyude Paul started the SpinLockIrq work [1] and did an amazing job at improving the design and implementation. Daniel Almeida resolved the Lock issue [2] and he did a fair amount of reviews in areas related to synchronization primitives already. Onur Özkan started the ww_mutex work [3] and did an amazing job at consolidating various design requirements and decisions. Of course, this only reflects my own knowledge, and I believe they did way more outside what I'm aware of ;-) Note that having this MAINTAINERS entry is meant to bring more people to help on the synchronization primitives in Rust, which means for patch submissions and design discussion, please still involve the corresponding maintainers (e.g. LOCKING and ATOMIC), scripts/get_maintainers.pl should have this covered. Signed-off-by: Boqun Feng Signed-off-by: Peter Zijlstra (Intel) Acked-by: Daniel Almeida Acked-by: Danilo Krummrich Acked-by: Onur Özkan Acked-by: Gary Guo Acked-by: Alice Ryhl Link: https://lore.kernel.org/rust-for-linux/20260302232154.861916-1-lyude@redhat.com/ [1] Link: https://lore.kernel.org/all/20250828-lock-t-when-t-is-pinned-v2-0-b067c4b93fd6@collabora.com/ [2] Link: https://lore.kernel.org/rust-for-linux/20260103073554.34855-1-work@onurozkan.dev/ [3] Link: https://patch.msgid.link/20260415232830.8128-1-boqun@kernel.org Link: https://patch.msgid.link/20260605052331.1628-2-boqun@kernel.org --- MAINTAINERS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 882214b0e7db..7c0f853b881b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23450,6 +23450,20 @@ S: Maintained T: git https://github.com/Rust-for-Linux/linux.git rust-analyzer-next F: scripts/generate_rust_analyzer.py +RUST [SYNC] +M: Boqun Feng +M: Gary Guo +M: Alice Ryhl +R: Lyude Paul +R: Daniel Almeida +R: "Onur Özkan" +L: linux-kernel@vger.kernel.org +L: rust-for-linux@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/boqun/linux.git rust-sync +F: rust/kernel/sync.rs +F: rust/kernel/sync/ + RXRPC SOCKETS (AF_RXRPC) M: David Howells M: Marc Dionne From a837dd95e841586c3a6bbe41c41843b392a1b725 Mon Sep 17 00:00:00 2001 From: Fabricio Parra Date: Thu, 4 Jun 2026 22:23:31 -0700 Subject: [PATCH 158/167] rust: sync: completion: Mark inline complete_all and wait_for_completion When building the kernel using the llvm-22.1.0-rust-1.93.1-x86_64 toolchain provided by kernel.org with ARCH=x86_64, the following symbols are generated: $ nm vmlinux | grep ' _R'.*Completion | rustfilt ffffffff81827930 T ::complete_all ffffffff81827950 T ::wait_for_completion These Rust methods are thin wrappers around the C completion helpers `complete_all` and `wait_for_completion`. Mark them `#[inline]` to keep the wrapper pattern consistent with other small Rust helper methods. After applying this patch, the above command will produce no output. Suggested-by: Alice Ryhl Signed-off-by: Fabricio Parra Signed-off-by: Boqun Feng Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Gary Guo Link: https://github.com/Rust-for-Linux/linux/issues/1145 Link: https://patch.msgid.link/20260316151056.287-1-a@alice0.com Link: https://patch.msgid.link/20260605052331.1628-4-boqun@kernel.org --- rust/kernel/sync/completion.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/kernel/sync/completion.rs b/rust/kernel/sync/completion.rs index c50012a940a3..35ff049ff078 100644 --- a/rust/kernel/sync/completion.rs +++ b/rust/kernel/sync/completion.rs @@ -94,6 +94,7 @@ impl Completion { /// /// This method wakes up all tasks waiting on this completion; after this operation the /// completion is permanently done, i.e. signals all current and future waiters. + #[inline] pub fn complete_all(&self) { // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`. unsafe { bindings::complete_all(self.as_raw()) }; @@ -105,6 +106,7 @@ impl Completion { /// timeout. /// /// See also [`Completion::complete_all`]. + #[inline] pub fn wait_for_completion(&self) { // SAFETY: `self.as_raw()` is a pointer to a valid `struct completion`. unsafe { bindings::wait_for_completion(self.as_raw()) }; From f3dc8fb24f90fa2901a87fb5273edabcc4e8f2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:19 +0200 Subject: [PATCH 159/167] MIPS: csrc-r4k: Only use VDSO_CLOCKMODE_R4K when it is a available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VDSO_CLOCKMODE_R4K is only defined if CONFIG_GENERIC_GETTIMEOFDAY is enabled. Right now this is always the case, but it will change soon. Prepare for the potential unavailability of VDSO_CLOCKMODE_R4K. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-5-2f79dcd6c78f@linutronix.de --- arch/mips/kernel/csrc-r4k.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/mips/kernel/csrc-r4k.c b/arch/mips/kernel/csrc-r4k.c index 59eca397f297..241a934543a8 100644 --- a/arch/mips/kernel/csrc-r4k.c +++ b/arch/mips/kernel/csrc-r4k.c @@ -126,12 +126,14 @@ int __init init_r4k_clocksource(void) clocksource_mips.rating = 200; clocksource_mips.rating += clamp(mips_hpt_frequency / 10000000, 0, 99); +#ifdef CONFIG_GENERIC_GETTIMEOFDAY /* * R2 onwards makes the count accessible to user mode so it can be used * by the VDSO (HWREna is configured by configure_hwrena()). */ if (cpu_has_mips_r2_r6 && rdhwr_count_usable()) clocksource_mips.vdso_clock_mode = VDSO_CLOCKMODE_R4K; +#endif clocksource_register_hz(&clocksource_mips, mips_hpt_frequency); From 66e0b2e3e3817efe0e0f283c04a9dd773ec4ac5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:20 +0200 Subject: [PATCH 160/167] clocksource/drivers/mips-gic-timer: Only use VDSO_CLOCKMODE_GIC when it is a available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VDSO_CLOCKMODE_GIC is only defined if CONFIG_GENERIC_GETTIMEOFDAY is enabled. Right now this is always the case, but it will change soon. Prepare for the potential unavailability of VDSO_CLOCKMODE_GIC. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-6-2f79dcd6c78f@linutronix.de --- drivers/clocksource/mips-gic-timer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c index 1501c7db9a8e..a1669266c94d 100644 --- a/drivers/clocksource/mips-gic-timer.c +++ b/drivers/clocksource/mips-gic-timer.c @@ -198,7 +198,9 @@ static struct clocksource gic_clocksource = { .name = "GIC", .read = gic_hpt_read, .flags = CLOCK_SOURCE_IS_CONTINUOUS, +#ifdef CONFIG_GENERIC_GETTIMEOFDAY .vdso_clock_mode = VDSO_CLOCKMODE_GIC, +#endif }; static void gic_clocksource_unstable(char *reason) From 51512c216e1d2ab4822282c9ecdd40f43fa1484c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:21 +0200 Subject: [PATCH 161/167] MIPS: VDSO: Fold MIPS_DISABLE_VDSO into MIPS_GENERIC_GETTIMEOFDAY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The currently used MIPS_DISABLE_VDSO will disable only the userspace bits of the time-related vDSO. The kernel part is still pointlessly built and running. Remove MIPS_DISABLE_VDSO and fold its usecase into MIPS_GENERIC_GETTIMEOFDAY, which works correctly. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Cc: Daniel Lezcano Cc: Thomas Gleixner Cc: Andy Lutomirski Cc: Vincenzo Frascino Cc: Arnd Bergmann Cc: linux-mips@vger.kernel.org Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-7-2f79dcd6c78f@linutronix.de --- arch/mips/Kconfig | 6 ++++-- arch/mips/vdso/Kconfig | 6 ------ arch/mips/vdso/Makefile | 7 ++----- arch/mips/vdso/vdso.lds.S | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 arch/mips/vdso/Kconfig diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index f56e1a5f5cd9..6463b0b1e165 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -3171,6 +3171,10 @@ config MIPS_GENERIC_GETTIMEOFDAY def_bool y select GENERIC_GETTIMEOFDAY select HAVE_GENERIC_VDSO + # GCC (at least up to version 9.2) appears to emit function calls that make use + # of the GOT when targeting microMIPS, which we can't use in the VDSO due to + # the lack of relocations. As such, we disable the VDSO for microMIPS builds. + depends on !CPU_MICROMIPS menu "CPU Power Management" @@ -3183,5 +3187,3 @@ source "drivers/cpuidle/Kconfig" endmenu source "arch/mips/kvm/Kconfig" - -source "arch/mips/vdso/Kconfig" diff --git a/arch/mips/vdso/Kconfig b/arch/mips/vdso/Kconfig deleted file mode 100644 index 70140248da72..000000000000 --- a/arch/mips/vdso/Kconfig +++ /dev/null @@ -1,6 +0,0 @@ -# GCC (at least up to version 9.2) appears to emit function calls that make use -# of the GOT when targeting microMIPS, which we can't use in the VDSO due to -# the lack of relocations. As such, we disable the VDSO for microMIPS builds. - -config MIPS_DISABLE_VDSO - def_bool CPU_MICROMIPS diff --git a/arch/mips/vdso/Makefile b/arch/mips/vdso/Makefile index 69d4593f64fe..00d3ba2c482a 100644 --- a/arch/mips/vdso/Makefile +++ b/arch/mips/vdso/Makefile @@ -4,7 +4,7 @@ # Include the generic Makefile to check the built vdso. include $(srctree)/lib/vdso/Makefile.include -obj-vdso-y := elf.o vgettimeofday.o sigreturn.o +obj-vdso-y := elf.o sigreturn.o # Common compiler flags between ABIs. ccflags-vdso := \ @@ -36,6 +36,7 @@ aflags-vdso := $(ccflags-vdso) \ -D__ASSEMBLY__ -Wa,-gdwarf-2 ifneq ($(c-gettimeofday-y),) +obj-vdso-y += vgettimeofday.o CFLAGS_vgettimeofday.o = -include $(c-gettimeofday-y) # config-n32-o32-env.c prepares the environment to build a 32bit vDSO @@ -47,10 +48,6 @@ endif CFLAGS_REMOVE_vgettimeofday.o = $(CC_FLAGS_FTRACE) -ifdef CONFIG_MIPS_DISABLE_VDSO - obj-vdso-y := $(filter-out vgettimeofday.o, $(obj-vdso-y)) -endif - # VDSO linker flags. ldflags-y := -Bsymbolic --no-undefined -soname=linux-vdso.so.1 \ $(filter -E%,$(KBUILD_CFLAGS)) -shared \ diff --git a/arch/mips/vdso/vdso.lds.S b/arch/mips/vdso/vdso.lds.S index 5d08be3a6b85..fd263b05d3e7 100644 --- a/arch/mips/vdso/vdso.lds.S +++ b/arch/mips/vdso/vdso.lds.S @@ -94,7 +94,7 @@ PHDRS VERSION { LINUX_2.6 { -#ifndef CONFIG_MIPS_DISABLE_VDSO +#ifdef CONFIG_GENERIC_GETTIMEOFDAY global: __vdso_clock_gettime; #ifdef CONFIG_MIPS_CLOCK_VSYSCALL From d051ede1adeeb8ccc9314bac00f33e013deedbf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:22 +0200 Subject: [PATCH 162/167] MIPS: VDSO: Gate microMIPS restriction on GCC version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As explained in the comment, the problematic GOT references are only emitted by old versions of GCC. Limit the restriction to those compilers. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-8-2f79dcd6c78f@linutronix.de --- arch/mips/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 6463b0b1e165..ccc66e3df1f9 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -3174,7 +3174,7 @@ config MIPS_GENERIC_GETTIMEOFDAY # GCC (at least up to version 9.2) appears to emit function calls that make use # of the GOT when targeting microMIPS, which we can't use in the VDSO due to # the lack of relocations. As such, we disable the VDSO for microMIPS builds. - depends on !CPU_MICROMIPS + depends on !(CPU_MICROMIPS && CC_IS_GCC && GCC_VERSION < 90300) menu "CPU Power Management" From 8d563bd7904734c05a4f2abf4ecca0e4fe764b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 21 May 2026 08:53:23 +0200 Subject: [PATCH 163/167] MIPS: VDSO: Fold MIPS_CLOCK_VSYSCALL into MIPS_GENERIC_GETTIMEOFDAY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This configuration option exists so "that we don't provide the symbol when there's no possibility of there being a usable clocksource". However it only covers __vdso_gettimeofday() and none of the other vDSO functions which should be affected by the same circumstances.slightly slightly. Remove MIPS_CLOCK_VSYSCALL and fold its usecase into MIPS_GENERIC_GETTIMEOFDAY, which works correctly. Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Cc: Thomas Bogendoerfer Cc: Daniel Lezcano Cc: Thomas Gleixner Cc: Andy Lutomirski Cc: Vincenzo Frascino Cc: Arnd Bergmann Cc: linux-mips@vger.kernel.org Link: https://patch.msgid.link/20260521-vdso-mips-kconfig-v1-9-2f79dcd6c78f@linutronix.de --- arch/mips/Kconfig | 4 +--- arch/mips/vdso/vdso.lds.S | 2 -- arch/mips/vdso/vgettimeofday.c | 20 -------------------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index ccc66e3df1f9..323ca084e79a 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -1133,9 +1133,6 @@ config CSRC_R4K config CSRC_SB1250 bool -config MIPS_CLOCK_VSYSCALL - def_bool CSRC_R4K || CLKSRC_MIPS_GIC - config GPIO_TXX9 select GPIOLIB bool @@ -3171,6 +3168,7 @@ config MIPS_GENERIC_GETTIMEOFDAY def_bool y select GENERIC_GETTIMEOFDAY select HAVE_GENERIC_VDSO + depends on CSRC_R4K || CLKSRC_MIPS_GIC # GCC (at least up to version 9.2) appears to emit function calls that make use # of the GOT when targeting microMIPS, which we can't use in the VDSO due to # the lack of relocations. As such, we disable the VDSO for microMIPS builds. diff --git a/arch/mips/vdso/vdso.lds.S b/arch/mips/vdso/vdso.lds.S index fd263b05d3e7..05badf3ae0ff 100644 --- a/arch/mips/vdso/vdso.lds.S +++ b/arch/mips/vdso/vdso.lds.S @@ -97,9 +97,7 @@ VERSION #ifdef CONFIG_GENERIC_GETTIMEOFDAY global: __vdso_clock_gettime; -#ifdef CONFIG_MIPS_CLOCK_VSYSCALL __vdso_gettimeofday; -#endif __vdso_clock_getres; #if _MIPS_SIM != _MIPS_SIM_ABI64 __vdso_clock_gettime64; diff --git a/arch/mips/vdso/vgettimeofday.c b/arch/mips/vdso/vgettimeofday.c index 1d236215e8f6..00f9fcfc327e 100644 --- a/arch/mips/vdso/vgettimeofday.c +++ b/arch/mips/vdso/vgettimeofday.c @@ -18,22 +18,12 @@ int __vdso_clock_gettime(clockid_t clock, return __cvdso_clock_gettime32(clock, ts); } -#ifdef CONFIG_MIPS_CLOCK_VSYSCALL - -/* - * This is behind the ifdef so that we don't provide the symbol when there's no - * possibility of there being a usable clocksource, because there's nothing we - * can do without it. When libc fails the symbol lookup it should fall back on - * the standard syscall path. - */ int __vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz) { return __cvdso_gettimeofday(tv, tz); } -#endif /* CONFIG_MIPS_CLOCK_VSYSCALL */ - int __vdso_clock_getres(clockid_t clock_id, struct old_timespec32 *res) { @@ -59,22 +49,12 @@ int __vdso_clock_gettime(clockid_t clock, return __cvdso_clock_gettime(clock, ts); } -#ifdef CONFIG_MIPS_CLOCK_VSYSCALL - -/* - * This is behind the ifdef so that we don't provide the symbol when there's no - * possibility of there being a usable clocksource, because there's nothing we - * can do without it. When libc fails the symbol lookup it should fall back on - * the standard syscall path. - */ int __vdso_gettimeofday(struct __kernel_old_timeval *tv, struct timezone *tz) { return __cvdso_gettimeofday(tv, tz); } -#endif /* CONFIG_MIPS_CLOCK_VSYSCALL */ - int __vdso_clock_getres(clockid_t clock_id, struct __kernel_timespec *res) { From a734d9fca84e1d4fa0cb442ef5f84c88f8212d32 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Wed, 10 Jun 2026 23:20:09 +0200 Subject: [PATCH 164/167] futex: Optimize futex hash bucket access patterns Breno reported significant c2c HITM in a futex hash heavy workload. It turns out that the hash bucket to private hash table reverse pointer (futex_hash_bucket::priv) was to blame. Notably when the hash buckets are heavily contended, the: 'fph = bh->priv;' load in futex_hash() will typically miss and consequently become quite expensive. Since this load in particular is quite superfluous, removing it is fairly straight forward. However, removing it does not in fact achieve anything much. The pain moves to the next user, notably: futex_hash_put(). Therefore rework the whole private hash refcounting to avoid needing this back pointer (and removing it). Instead of passing around 'struct futex_hash_bucket *hb', pass around a new structure that contains it and the related 'struct futex_private_hash *fph' pointer in tandem. Funnily this turns out to remove more code than it adds and significantly improves futex hash performance (as measured by 'perf bench futex hash'): SKL dual socket 112 threads: Baseline Patched shared (16k) 1571857 1641435 + 4.4% autosize (512) 646390 903371 +39.7% -b 256 464395 587014 +26.4% -b 512 715687 995943 +39.2% -b 1024 995085 1396328 +40.3% -b 2048 1293114 1668395 +29.0% -b 4096 2124438 2240228 + 5.5% Zen3 dual socket 256 threads: Baseline Patched shared (16k) 1275840 1381279 + 8.2% autosize (512) 1252745 1482179 +18.3% -b 256 856274 955455 +11.5% -b 512 1267490 1544010 +21.8% -b 1024 1424013 1625424 +14.1% -b 2048 1505181 1669342 +10.9% -b 4096 1465993 1688932 +15.2% AMD EPYC 9D64 (Zen4, single socket) 176 threads: Baseline Patched Delta shared (16k) 1,230,599 1,368,655 +11.2% autosize (1024) 1,285,440 1,556,946 +21.1% -b 256 1,341,471 1,520,303 +13.3% -b 512 1,438,330 1,599,319 +11.2% -b 1024 1,443,772 1,622,493 +12.4% -b 2048 1,472,108 1,643,975 +11.7% -b 4096 1,333,098 1,570,897 +17.8% Reported-by: Breno Leitao Signed-off-by: Peter Zijlstra (Intel) Tested-by: Breno Leitao Tested-by: Thomas Gleixner Link: https://patch.msgid.link/20260610135510.GB1430057@noisy.programming.kicks-ass.net --- kernel/futex/core.c | 111 ++++++++++++---------------------------- kernel/futex/futex.h | 37 +++++++++----- kernel/futex/pi.c | 21 ++++---- kernel/futex/requeue.c | 20 ++++---- kernel/futex/waitwake.c | 17 +++--- 5 files changed, 87 insertions(+), 119 deletions(-) diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 6ea4a97796a1..179b26e9c934 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -127,7 +127,7 @@ late_initcall(fail_futex_debugfs); #endif /* CONFIG_FAIL_FUTEX */ static struct futex_hash_bucket * -__futex_hash(union futex_key *key, struct futex_private_hash *fph); +__futex_hash(union futex_key *key, struct futex_private_hash *fph, struct futex_private_hash **fph_p); #ifdef CONFIG_FUTEX_PRIVATE_HASH static bool futex_ref_get(struct futex_private_hash *fph); @@ -136,15 +136,6 @@ static bool futex_ref_is_dead(struct futex_private_hash *fph); enum { FR_PERCPU = 0, FR_ATOMIC }; -static inline bool futex_key_is_private(union futex_key *key) -{ - /* - * Relies on get_futex_key() to set either bit for shared - * futexes -- see comment with union futex_key. - */ - return !(key->both.offset & (FUT_OFF_INODE | FUT_OFF_MMSHARED)); -} - static bool futex_private_hash_get(struct futex_private_hash *fph) { return futex_ref_get(fph); @@ -152,48 +143,15 @@ static bool futex_private_hash_get(struct futex_private_hash *fph) void futex_private_hash_put(struct futex_private_hash *fph) { - if (futex_ref_put(fph)) + if (fph && futex_ref_put(fph)) wake_up_var(fph->mm); } -/** - * futex_hash_get - Get an additional reference for the local hash. - * @hb: ptr to the private local hash. - * - * Obtain an additional reference for the already obtained hash bucket. The - * caller must already own an reference. - */ -void futex_hash_get(struct futex_hash_bucket *hb) -{ - struct futex_private_hash *fph = hb->priv; - - if (!fph) - return; - WARN_ON_ONCE(!futex_private_hash_get(fph)); -} - -void futex_hash_put(struct futex_hash_bucket *hb) -{ - struct futex_private_hash *fph = hb->priv; - - if (!fph) - return; - futex_private_hash_put(fph); -} - static struct futex_hash_bucket * __futex_hash_private(union futex_key *key, struct futex_private_hash *fph) { u32 hash; - if (!futex_key_is_private(key)) - return NULL; - - if (!fph) - fph = rcu_dereference(key->private.mm->futex.phash.hash); - if (!fph || !fph->hash_mask) - return NULL; - hash = jhash2((void *)&key->private.address, sizeof(key->private.address) / 4, key->both.offset); @@ -214,13 +172,12 @@ static void futex_rehash_private(struct futex_private_hash *old, spin_lock(&hb_old->lock); plist_for_each_entry_safe(this, tmp, &hb_old->chain, list) { - plist_del(&this->list, &hb_old->chain); futex_hb_waiters_dec(hb_old); WARN_ON_ONCE(this->lock_ptr != &hb_old->lock); - hb_new = __futex_hash(&this->key, new); + hb_new = __futex_hash(&this->key, new, NULL); futex_hb_waiters_inc(hb_new); /* * The new pointer isn't published yet but an already @@ -273,9 +230,8 @@ static void futex_pivot_hash(struct mm_struct *mm) } } -struct futex_private_hash *futex_private_hash(void) +struct futex_private_hash *futex_private_hash(struct mm_struct *mm) { - struct mm_struct *mm = current->mm; /* * Ideally we don't loop. If there is a replacement in progress * then a new private hash is already prepared and a reference can't be @@ -301,18 +257,17 @@ again: goto again; } -struct futex_hash_bucket *futex_hash(union futex_key *key) +struct futex_bucket_ref futex_hash(union futex_key *key) { - struct futex_private_hash *fph; - struct futex_hash_bucket *hb; - again: scoped_guard(rcu) { - hb = __futex_hash(key, NULL); - fph = hb->priv; + struct futex_private_hash *fph = NULL; + struct futex_hash_bucket *hb; + + hb = __futex_hash(key, NULL, &fph); if (!fph || futex_private_hash_get(fph)) - return hb; + return (struct futex_bucket_ref){ .hb = hb, .fph = fph }; } futex_pivot_hash(key->private.mm); goto again; @@ -320,15 +275,9 @@ again: #else /* !CONFIG_FUTEX_PRIVATE_HASH */ -static struct futex_hash_bucket * -__futex_hash_private(union futex_key *key, struct futex_private_hash *fph) +struct futex_bucket_ref futex_hash(union futex_key *key) { - return NULL; -} - -struct futex_hash_bucket *futex_hash(union futex_key *key) -{ - return __futex_hash(key, NULL); + return (struct futex_bucket_ref){ .hb = __futex_hash(key, NULL, NULL), .fph = NULL }; } #endif /* CONFIG_FUTEX_PRIVATE_HASH */ @@ -406,6 +355,8 @@ static int futex_mpol(struct mm_struct *mm, unsigned long addr) * __futex_hash - Return the hash bucket * @key: Pointer to the futex key for which the hash is calculated * @fph: Pointer to private hash if known + * @fph_p: Pointer to a private hash pointer; output for the private hash + * used when set. * * We hash on the keys returned from get_futex_key (see below) and return the * corresponding hash bucket. @@ -413,18 +364,23 @@ static int futex_mpol(struct mm_struct *mm, unsigned long addr) * private hash) is returned if existing. Otherwise a hash bucket from the * global hash is returned. */ -static struct futex_hash_bucket *__futex_hash(union futex_key *key, struct futex_private_hash *fph) +static struct futex_hash_bucket * +__futex_hash(union futex_key *key, struct futex_private_hash *fph, struct futex_private_hash **fph_p) { int node = key->both.node; u32 hash; - if (node == FUTEX_NO_NODE) { - struct futex_hash_bucket *hb; - - hb = __futex_hash_private(key, fph); - if (hb) - return hb; +#ifdef CONFIG_FUTEX_PRIVATE_HASH + if (node == FUTEX_NO_NODE && futex_key_is_private(key)) { + if (!fph) + fph = rcu_dereference(key->private.mm->futex.phash.hash); + if (fph && fph->hash_mask) { + if (fph_p) + *fph_p = fph; + return __futex_hash_private(key, fph); + } } +#endif hash = jhash2((u32 *)key, offsetof(typeof(*key), both.offset) / sizeof(u32), key->both.offset); @@ -1367,7 +1323,7 @@ static void exit_pi_state_list(struct task_struct *curr) * on the mutex. */ WARN_ON(curr != current); - guard(private_hash)(); + guard(private_hash)(current->mm); /* * We are a ZOMBIE and nobody can enqueue itself on * pi_state_list anymore, but we have to be careful @@ -1379,7 +1335,8 @@ static void exit_pi_state_list(struct task_struct *curr) pi_state = list_entry(next, struct futex_pi_state, list); key = pi_state->key; if (1) { - CLASS(hb, hb)(&key); + CLASS(hbr, hbr)(&key); + auto hb = hbr.hb; /* * We can race against put_pi_state() removing itself from the @@ -1576,12 +1533,8 @@ void futex_exit_release(struct task_struct *tsk) futex_cleanup_end(tsk, FUTEX_STATE_DEAD); } -static void futex_hash_bucket_init(struct futex_hash_bucket *fhb, - struct futex_private_hash *fph) +static void futex_hash_bucket_init(struct futex_hash_bucket *fhb) { -#ifdef CONFIG_FUTEX_PRIVATE_HASH - fhb->priv = fph; -#endif atomic_set(&fhb->waiters, 0); plist_head_init(&fhb->chain); spin_lock_init(&fhb->lock); @@ -1877,7 +1830,7 @@ static int futex_hash_allocate(unsigned int hash_slots, unsigned int flags) fph->mm = mm; for (i = 0; i < hash_slots; i++) - futex_hash_bucket_init(&fph->queues[i], fph); + futex_hash_bucket_init(&fph->queues[i]); if (custom) { /* @@ -2082,7 +2035,7 @@ static int __init futex_init(void) BUG_ON(!table); for (i = 0; i < hashsize; i++) - futex_hash_bucket_init(&table[i], NULL); + futex_hash_bucket_init(&table[i]); futex_queues[n] = table; } diff --git a/kernel/futex/futex.h b/kernel/futex/futex.h index 79ef2c709c81..f00f0863ed44 100644 --- a/kernel/futex/futex.h +++ b/kernel/futex/futex.h @@ -134,6 +134,15 @@ static inline bool should_fail_futex(bool fshared) } #endif +static inline bool futex_key_is_private(union futex_key *key) +{ + /* + * Relies on get_futex_key() to set either bit for shared + * futexes -- see comment with union futex_key. + */ + return !(key->both.offset & (FUT_OFF_INODE | FUT_OFF_MMSHARED)); +} + /* * Hash buckets are shared by all the futex_keys that hash to the same * location. Each key may have multiple futex_q structures, one for each task @@ -143,7 +152,6 @@ struct futex_hash_bucket { atomic_t waiters; spinlock_t lock; struct plist_head chain; - struct futex_private_hash *priv; } ____cacheline_aligned_in_smp; /* @@ -183,7 +191,7 @@ typedef void (futex_wake_fn)(struct wake_q_head *wake_q, struct futex_q *q); * @requeue_pi_key: the requeue_pi target futex key * @bitset: bitset for the optional bitmasked wakeup * @requeue_state: State field for futex_requeue_pi() - * @drop_hb_ref: Waiter should drop the extra hash bucket reference if true + * @drop_fph: Waiter should drop the extra private hash reference when set * @requeue_wait: RCU wait for futex_requeue_pi() (RT only) * * We use this hashed waitqueue, instead of a normal wait_queue_entry_t, so @@ -210,7 +218,7 @@ struct futex_q { union futex_key *requeue_pi_key; u32 bitset; atomic_t requeue_state; - bool drop_hb_ref; + struct futex_private_hash *drop_fph; #ifdef CONFIG_PREEMPT_RT struct rcuwait requeue_wait; #endif @@ -230,28 +238,29 @@ extern struct hrtimer_sleeper * futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout, int flags, u64 range_ns); -extern struct futex_hash_bucket *futex_hash(union futex_key *key); -#ifdef CONFIG_FUTEX_PRIVATE_HASH -extern void futex_hash_get(struct futex_hash_bucket *hb); -extern void futex_hash_put(struct futex_hash_bucket *hb); +struct futex_bucket_ref { + struct futex_hash_bucket *hb; + struct futex_private_hash *fph; +}; -extern struct futex_private_hash *futex_private_hash(void); +#ifdef CONFIG_FUTEX_PRIVATE_HASH +extern struct futex_private_hash *futex_private_hash(struct mm_struct *mm); extern void futex_private_hash_put(struct futex_private_hash *fph); #else /* !CONFIG_FUTEX_PRIVATE_HASH */ -static inline void futex_hash_get(struct futex_hash_bucket *hb) { } -static inline void futex_hash_put(struct futex_hash_bucket *hb) { } -static inline struct futex_private_hash *futex_private_hash(void) { return NULL; } +static inline struct futex_private_hash *futex_private_hash(struct mm_struct *mm) { return NULL; } static inline void futex_private_hash_put(struct futex_private_hash *fph) { } #endif -DEFINE_CLASS(hb, struct futex_hash_bucket *, - if (_T) futex_hash_put(_T), +extern struct futex_bucket_ref futex_hash(union futex_key *key); + +DEFINE_CLASS(hbr, struct futex_bucket_ref, + if (_T.fph) futex_private_hash_put(_T.fph), futex_hash(key), union futex_key *key); DEFINE_CLASS(private_hash, struct futex_private_hash *, if (_T) futex_private_hash_put(_T), - futex_private_hash(), void); + futex_private_hash(mm), struct mm_struct *mm); /** * futex_match - Check whether two futex keys are equal diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c index 9dd5c0b1ac6a..795011ea1202 100644 --- a/kernel/futex/pi.c +++ b/kernel/futex/pi.c @@ -945,7 +945,8 @@ retry: retry_private: if (1) { - CLASS(hb, hb)(&q.key); + CLASS(hbr, hbr)(&q.key); + auto hb = hbr.hb; futex_q_lock(&q, hb); @@ -1009,7 +1010,7 @@ retry_private: * the thread, performing resize, will block on hb->lock during * the requeue. */ - futex_hash_put(no_free_ptr(hb)); + futex_private_hash_put(no_free_ptr(hbr.fph)); /* * Must be done before we enqueue the waiter, here is unfortunately * under the hb lock, but that *should* work because it does nothing. @@ -1100,11 +1101,9 @@ no_block: __release(&hb->lock); futex_unqueue_pi(&q); spin_unlock(q.lock_ptr); - if (q.drop_hb_ref) { - CLASS(hb, hb)(&q.key); - /* Additional reference from futex_unlock_pi() */ - futex_hash_put(hb); - } + + /* Additional reference from futex_unlock_pi() */ + futex_private_hash_put(q.drop_fph); goto out; out_unlock_put_key: @@ -1161,7 +1160,8 @@ retry: if (ret) return ret; - CLASS(hb, hb)(&key); + CLASS(hbr, hbr)(&key); + auto hb = hbr.hb; spin_lock(&hb->lock); retry_hb: @@ -1218,8 +1218,9 @@ retry_hb: * Acquire a reference for the leaving waiter to ensure * valid futex_q::lock_ptr. */ - futex_hash_get(hb); - top_waiter->drop_hb_ref = true; + if (futex_key_is_private(&key)) + top_waiter->drop_fph = futex_private_hash(key.private.mm); + __futex_unqueue(top_waiter); raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock); goto retry_hb; diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index b597cb3d17fc..79823ad13683 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -241,8 +241,8 @@ void requeue_pi_wake_futex(struct futex_q *q, union futex_key *key, * Acquire a reference for the waiter to ensure valid * futex_q::lock_ptr. */ - futex_hash_get(hb); - q->drop_hb_ref = true; + if (futex_key_is_private(key)) + q->drop_fph = futex_private_hash(key->private.mm); q->lock_ptr = &hb->lock; task = READ_ONCE(q->task); @@ -459,8 +459,10 @@ retry: retry_private: if (1) { - CLASS(hb, hb1)(&key1); - CLASS(hb, hb2)(&key2); + CLASS(hbr, hbr1)(&key1); + CLASS(hbr, hbr2)(&key2); + auto hb1 = hbr1.hb; + auto hb2 = hbr2.hb; futex_hb_waiters_inc(hb2); double_lock_hb(hb1, hb2); @@ -832,7 +834,8 @@ int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, switch (futex_requeue_pi_wakeup_sync(&q)) { case Q_REQUEUE_PI_IGNORE: { - CLASS(hb, hb)(&q.key); + CLASS(hbr, hbr)(&q.key); + auto hb = hbr.hb; /* The waiter is still on uaddr1 */ spin_lock(&hb->lock); ret = handle_early_requeue_pi_wakeup(hb, &q, to); @@ -902,11 +905,8 @@ int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, default: BUG(); } - if (q.drop_hb_ref) { - CLASS(hb, hb)(&q.key); - /* Additional reference from requeue_pi_wake_futex() */ - futex_hash_put(hb); - } + /* Additional reference from requeue_pi_wake_futex() */ + futex_private_hash_put(q.drop_fph); out: if (to) { diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c index 8f5e5d302356..d4483d15d30a 100644 --- a/kernel/futex/waitwake.c +++ b/kernel/futex/waitwake.c @@ -195,7 +195,8 @@ int futex_wake(u32 __user *uaddr, unsigned int flags, void __user *pop, int nr_w if ((flags & FLAGS_STRICT) && !nr_wake) return 0; - CLASS(hb, hb)(&key); + CLASS(hbr, hbr)(&key); + auto hb = hbr.hb; /* Make sure we really have tasks to wakeup */ if (!futex_hb_waiters_pending(hb)) @@ -292,8 +293,10 @@ retry: retry_private: if (1) { - CLASS(hb, hb1)(&key1); - CLASS(hb, hb2)(&key2); + CLASS(hbr, hbr1)(&key1); + CLASS(hbr, hbr2)(&key2); + auto hb1 = hbr1.hb; + auto hb2 = hbr2.hb; double_lock_hb(hb1, hb2); op_ret = futex_atomic_op_inuser(op, uaddr2); @@ -435,7 +438,7 @@ int futex_wait_multiple_setup(struct futex_vector *vs, int count, int *woken) * Make sure to have a reference on the private_hash such that we * don't block on rehash after changing the task state below. */ - guard(private_hash)(); + guard(private_hash)(current->mm); /* * Enqueuing multiple futexes is tricky, because we need to enqueue @@ -472,7 +475,8 @@ retry: u32 val = vs[i].w.val; if (1) { - CLASS(hb, hb)(&q->key); + CLASS(hbr, hbr)(&q->key); + auto hb = hbr.hb; futex_q_lock(q, hb); ret = futex_get_value_locked(&uval, uaddr); @@ -647,7 +651,8 @@ retry: retry_private: if (1) { - CLASS(hb, hb)(&q->key); + CLASS(hbr, hbr)(&q->key); + auto hb = hbr.hb; futex_q_lock(q, hb); From 76080d81e511f66eeb094b204e1e217bff674922 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Thu, 4 Jun 2026 07:15:05 +0000 Subject: [PATCH 165/167] tracing/lock: Remove unnecessary linux/sched.h include None of the trace events in lock.h reference anything from linux/sched.h. Remove the unnecessary include. Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Acked-by: Usama Arif Link: https://patch.msgid.link/85e14a0685c1d46c3e80de2dc01358a48169b263.1780506267.git.d@ilvokhin.com --- include/trace/events/lock.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/trace/events/lock.h b/include/trace/events/lock.h index 8e89baa3775f..da978f2afb45 100644 --- a/include/trace/events/lock.h +++ b/include/trace/events/lock.h @@ -5,7 +5,6 @@ #if !defined(_TRACE_LOCK_H) || defined(TRACE_HEADER_MULTI_READ) #define _TRACE_LOCK_H -#include #include /* flags for lock:contention_begin */ From 7cfa62cf9432df67b1c95a59984a03a3bc023f98 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Thu, 4 Jun 2026 07:15:06 +0000 Subject: [PATCH 166/167] locking/percpu-rwsem: Extract __percpu_up_read() Move the percpu_up_read() slowpath out of the inline function into a new __percpu_up_read() to avoid binary size increase from adding a tracepoint to an inlined function. Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Acked-by: Usama Arif Link: https://patch.msgid.link/3dd2a1b9ab4f469e1892766cb63f41d6b0f53d29.1780506267.git.d@ilvokhin.com --- include/linux/percpu-rwsem.h | 15 +++------------ kernel/locking/percpu-rwsem.c | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/include/linux/percpu-rwsem.h b/include/linux/percpu-rwsem.h index c8cb010d655e..39d5bf8e6562 100644 --- a/include/linux/percpu-rwsem.h +++ b/include/linux/percpu-rwsem.h @@ -107,6 +107,8 @@ static inline bool percpu_down_read_trylock(struct percpu_rw_semaphore *sem) return ret; } +extern void __percpu_up_read(struct percpu_rw_semaphore *sem); + static inline void percpu_up_read(struct percpu_rw_semaphore *sem) { rwsem_release(&sem->dep_map, _RET_IP_); @@ -118,18 +120,7 @@ static inline void percpu_up_read(struct percpu_rw_semaphore *sem) if (likely(rcu_sync_is_idle(&sem->rss))) { this_cpu_dec(*sem->read_count); } else { - /* - * slowpath; reader will only ever wake a single blocked - * writer. - */ - smp_mb(); /* B matches C */ - /* - * In other words, if they see our decrement (presumably to - * aggregate zero, as that is the only time it matters) they - * will also see our critical section. - */ - this_cpu_dec(*sem->read_count); - rcuwait_wake_up(&sem->writer); + __percpu_up_read(sem); } preempt_enable(); } diff --git a/kernel/locking/percpu-rwsem.c b/kernel/locking/percpu-rwsem.c index ef234469baac..f3ee7a0d6047 100644 --- a/kernel/locking/percpu-rwsem.c +++ b/kernel/locking/percpu-rwsem.c @@ -288,3 +288,21 @@ void percpu_up_write(struct percpu_rw_semaphore *sem) rcu_sync_exit(&sem->rss); } EXPORT_SYMBOL_GPL(percpu_up_write); + +void __percpu_up_read(struct percpu_rw_semaphore *sem) +{ + lockdep_assert_preemption_disabled(); + /* + * slowpath; reader will only ever wake a single blocked + * writer. + */ + smp_mb(); /* B matches C */ + /* + * In other words, if they see our decrement (presumably to + * aggregate zero, as that is the only time it matters) they + * will also see our critical section. + */ + this_cpu_dec(*sem->read_count); + rcuwait_wake_up(&sem->writer); +} +EXPORT_SYMBOL_GPL(__percpu_up_read); From 4f070ccb4dc4692e3b6757819fb80655f58b4f12 Mon Sep 17 00:00:00 2001 From: Dmitry Ilvokhin Date: Thu, 4 Jun 2026 07:15:07 +0000 Subject: [PATCH 167/167] locking: Add contended_release tracepoint to sleepable locks Add the contended_release trace event. This tracepoint fires on the holder side when a contended lock is released, complementing the existing contention_begin/contention_end tracepoints which fire on the waiter side. This enables correlating lock hold time under contention with waiter events by lock address. Add trace_contended_release()/trace_call__contended_release() calls to the slowpath unlock paths of sleepable locks: mutex, rtmutex, semaphore, rwsem, percpu-rwsem, and RT-specific rwbase locks. Where possible, trace_contended_release() fires before the lock is released and before the waiter is woken. For some lock types, the tracepoint fires after the release but before the wake. Making the placement consistent across all lock types is not worth the added complexity. For reader/writer locks, the tracepoint fires for every reader releasing while a writer is waiting, not only for the last reader. Signed-off-by: Dmitry Ilvokhin Signed-off-by: Peter Zijlstra (Intel) Acked-by: Paul E. McKenney Acked-by: Usama Arif Link: https://patch.msgid.link/02f4f6c5ce6761e7f6587cf0ff2289d962ecddd4.1780506267.git.d@ilvokhin.com --- include/trace/events/lock.h | 17 +++++++++++++++++ kernel/locking/mutex.c | 4 ++++ kernel/locking/percpu-rwsem.c | 11 +++++++++++ kernel/locking/rtmutex.c | 1 + kernel/locking/rwbase_rt.c | 6 ++++++ kernel/locking/rwsem.c | 10 ++++++++-- kernel/locking/semaphore.c | 4 ++++ 7 files changed, 51 insertions(+), 2 deletions(-) diff --git a/include/trace/events/lock.h b/include/trace/events/lock.h index da978f2afb45..1ded869cd619 100644 --- a/include/trace/events/lock.h +++ b/include/trace/events/lock.h @@ -137,6 +137,23 @@ TRACE_EVENT(contention_end, TP_printk("%p (ret=%d)", __entry->lock_addr, __entry->ret) ); +TRACE_EVENT(contended_release, + + TP_PROTO(void *lock), + + TP_ARGS(lock), + + TP_STRUCT__entry( + __field(void *, lock_addr) + ), + + TP_fast_assign( + __entry->lock_addr = lock; + ), + + TP_printk("%p", __entry->lock_addr) +); + #endif /* _TRACE_LOCK_H */ /* This part must be outside protection */ diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c index 09534628dc01..43b7f7e281a0 100644 --- a/kernel/locking/mutex.c +++ b/kernel/locking/mutex.c @@ -1023,6 +1023,9 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne wake_q_add(&wake_q, next); } + if (trace_contended_release_enabled() && waiter) + trace_call__contended_release(lock); + if (owner & MUTEX_FLAG_HANDOFF) __mutex_handoff(lock, next); @@ -1220,6 +1223,7 @@ EXPORT_SYMBOL(ww_mutex_lock_interruptible); EXPORT_TRACEPOINT_SYMBOL_GPL(contention_begin); EXPORT_TRACEPOINT_SYMBOL_GPL(contention_end); +EXPORT_TRACEPOINT_SYMBOL_GPL(contended_release); /** * atomic_dec_and_mutex_lock - return holding mutex if we dec to 0 diff --git a/kernel/locking/percpu-rwsem.c b/kernel/locking/percpu-rwsem.c index f3ee7a0d6047..f7e152c40d6d 100644 --- a/kernel/locking/percpu-rwsem.c +++ b/kernel/locking/percpu-rwsem.c @@ -263,6 +263,9 @@ void percpu_up_write(struct percpu_rw_semaphore *sem) { rwsem_release(&sem->dep_map, _RET_IP_); + if (trace_contended_release_enabled() && wq_has_sleeper(&sem->waiters)) + trace_call__contended_release(sem); + /* * Signal the writer is done, no fast path yet. * @@ -292,6 +295,14 @@ EXPORT_SYMBOL_GPL(percpu_up_write); void __percpu_up_read(struct percpu_rw_semaphore *sem) { lockdep_assert_preemption_disabled(); + /* + * After percpu_up_write() completes, rcu_sync_is_idle() can still + * return false during the grace period, forcing readers into this + * slowpath. Only trace when a writer is actually waiting for + * readers to drain. + */ + if (trace_contended_release_enabled() && rcuwait_active(&sem->writer)) + trace_call__contended_release(sem); /* * slowpath; reader will only ever wake a single blocked * writer. diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c index 9147d6a31b78..28beae7d21fe 100644 --- a/kernel/locking/rtmutex.c +++ b/kernel/locking/rtmutex.c @@ -1470,6 +1470,7 @@ static void __sched rt_mutex_slowunlock(struct rt_mutex_base *lock) raw_spin_lock_irqsave(&lock->wait_lock, flags); } + trace_contended_release(lock); /* * The wakeup next waiter path does not suffer from the above * race. See the comments there. diff --git a/kernel/locking/rwbase_rt.c b/kernel/locking/rwbase_rt.c index 82e078c0665a..2835c9ef9b3f 100644 --- a/kernel/locking/rwbase_rt.c +++ b/kernel/locking/rwbase_rt.c @@ -174,6 +174,8 @@ static void __sched __rwbase_read_unlock(struct rwbase_rt *rwb, static __always_inline void rwbase_read_unlock(struct rwbase_rt *rwb, unsigned int state) { + if (trace_contended_release_enabled() && rt_mutex_owner(&rwb->rtmutex)) + trace_call__contended_release(rwb); /* * rwb->readers can only hit 0 when a writer is waiting for the * active readers to leave the critical section. @@ -205,6 +207,8 @@ static inline void rwbase_write_unlock(struct rwbase_rt *rwb) unsigned long flags; raw_spin_lock_irqsave(&rtm->wait_lock, flags); + if (trace_contended_release_enabled() && rt_mutex_has_waiters(rtm)) + trace_call__contended_release(rwb); __rwbase_write_unlock(rwb, WRITER_BIAS, flags); } @@ -214,6 +218,8 @@ static inline void rwbase_write_downgrade(struct rwbase_rt *rwb) unsigned long flags; raw_spin_lock_irqsave(&rtm->wait_lock, flags); + if (trace_contended_release_enabled() && rt_mutex_has_waiters(rtm)) + trace_call__contended_release(rwb); /* Release it and account current as reader */ __rwbase_write_unlock(rwb, WRITER_BIAS - 1, flags); } diff --git a/kernel/locking/rwsem.c b/kernel/locking/rwsem.c index bf647097369c..b9c180ac1eee 100644 --- a/kernel/locking/rwsem.c +++ b/kernel/locking/rwsem.c @@ -1387,6 +1387,8 @@ static inline void __up_read(struct rw_semaphore *sem) rwsem_clear_reader_owned(sem); tmp = atomic_long_add_return_release(-RWSEM_READER_BIAS, &sem->count); DEBUG_RWSEMS_WARN_ON(tmp < 0, sem); + if (trace_contended_release_enabled() && (tmp & RWSEM_FLAG_WAITERS)) + trace_call__contended_release(sem); if (unlikely((tmp & (RWSEM_LOCK_MASK|RWSEM_FLAG_WAITERS)) == RWSEM_FLAG_WAITERS)) { clear_nonspinnable(sem); @@ -1413,8 +1415,10 @@ static inline void __up_write(struct rw_semaphore *sem) preempt_disable(); rwsem_clear_owner(sem); tmp = atomic_long_fetch_add_release(-RWSEM_WRITER_LOCKED, &sem->count); - if (unlikely(tmp & RWSEM_FLAG_WAITERS)) + if (unlikely(tmp & RWSEM_FLAG_WAITERS)) { + trace_contended_release(sem); rwsem_wake(sem); + } preempt_enable(); } @@ -1437,8 +1441,10 @@ static inline void __downgrade_write(struct rw_semaphore *sem) tmp = atomic_long_fetch_add_release( -RWSEM_WRITER_LOCKED+RWSEM_READER_BIAS, &sem->count); rwsem_set_reader_owned(sem); - if (tmp & RWSEM_FLAG_WAITERS) + if (tmp & RWSEM_FLAG_WAITERS) { + trace_contended_release(sem); rwsem_downgrade_wake(sem); + } preempt_enable(); } diff --git a/kernel/locking/semaphore.c b/kernel/locking/semaphore.c index 74d41433ba13..233730c25933 100644 --- a/kernel/locking/semaphore.c +++ b/kernel/locking/semaphore.c @@ -230,6 +230,10 @@ void __sched up(struct semaphore *sem) sem->count++; else __up(sem, &wake_q); + + if (trace_contended_release_enabled() && !wake_q_empty(&wake_q)) + trace_call__contended_release(sem); + raw_spin_unlock_irqrestore(&sem->lock, flags); if (!wake_q_empty(&wake_q)) wake_up_q(&wake_q);