* [PATCH v5 2/7] module: add kflagstab section to vmlinux and modules
From: Siddharth Nayyar @ 2026-03-26 21:25 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, maennich, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-fa0796fe88d9@google.com>
This section will contain read-only data for values of kernel symbol
flags in the form of an 8-bit bitsets for each kernel symbol. Each bit
in the bitset represents a flag value defined by ksym_flags enumeration.
The kflagstab section introduces a 1-byte overhead for each symbol
exported in the ksymtab. Given that typical kernel builds contain
roughly a few thousand exported symbols, the resulting memory increase
is negligible.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/asm-generic/vmlinux.lds.h | 7 +++++++
scripts/module.lds.S | 1 +
2 files changed, 8 insertions(+)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 1e1580febe4b..d64a475c468a 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -536,6 +536,13 @@
__stop___kcrctab_gpl = .; \
} \
\
+ /* Kernel symbol flags table */ \
+ __kflagstab : AT(ADDR(__kflagstab) - LOAD_OFFSET) { \
+ __start___kflagstab = .; \
+ KEEP(*(SORT(___kflagstab+*))) \
+ __stop___kflagstab = .; \
+ } \
+ \
/* Kernel symbol table: strings */ \
__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \
*(__ksymtab_strings) \
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index 054ef99e8288..d7a8ba278dfc 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -23,6 +23,7 @@ SECTIONS {
__ksymtab_gpl 0 : ALIGN(8) { *(SORT(___ksymtab_gpl+*)) }
__kcrctab 0 : ALIGN(4) { *(SORT(___kcrctab+*)) }
__kcrctab_gpl 0 : ALIGN(4) { *(SORT(___kcrctab_gpl+*)) }
+ __kflagstab 0 : ALIGN(1) { *(SORT(___kflagstab+*)) }
.ctors 0 : ALIGN(8) { *(SORT(.ctors.*)) *(.ctors) }
.init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) }
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 1/7] module: define ksym_flags enumeration to represent kernel symbol flags
From: Siddharth Nayyar @ 2026-03-26 21:25 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, maennich, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-fa0796fe88d9@google.com>
Symbol flags is an enumeration used to represent flags as a bitset, for
example a flag to tell if a symbol is GPL only.
The said bitset is introduced in subsequent patches and will contain
values of kernel symbol flags. These bitset will then be used to infer
flag values rather than fragmenting ksymtab for separating symbols with
different flag values, thereby eliminating the need to fragment the
ksymtab.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/linux/module_symbol.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/linux/module_symbol.h b/include/linux/module_symbol.h
index 77c9895b9ddb..574609aced99 100644
--- a/include/linux/module_symbol.h
+++ b/include/linux/module_symbol.h
@@ -2,6 +2,11 @@
#ifndef _LINUX_MODULE_SYMBOL_H
#define _LINUX_MODULE_SYMBOL_H
+/* Kernel symbol flags bitset. */
+enum ksym_flags {
+ KSYM_FLAG_GPL_ONLY = 1 << 0,
+};
+
/* This ignores the intensely annoying "mapping symbols" found in ELF files. */
static inline bool is_mapping_symbol(const char *str)
{
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 0/7] scalable symbol flags with __kflagstab
From: Siddharth Nayyar @ 2026-03-26 21:25 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, maennich, gprocida
This patch series implements a mechanism for scalable exported symbol
flags using a separate section called __kflagstab. The series introduces
__kflagstab support, removes *_gpl sections in favor of a GPL flag,
simplifies symbol resolution during module loading.
The __kflagstab contains an 8-bit bitset which can represent up to 8
boolean flags per symbol exported in the __ksymtab. The patch series
also uses this bitset to store GPL-only flag values for kernel symbols,
thereby eliminating the need for *_gpl sections for representing GPL
only symbols.
Petr Pavlu ran a small test to get a better understanding of the
different section sizes resulting from this patch series. He used
v6.17-rc6 together with the openSUSE x86_64 config [1], which is fairly
large. The resulting vmlinux.bin (no debuginfo) had an on-disk size of
58 MiB, and included 5937 + 6589 (GPL-only) exported symbols.
The following table summarizes his measurements and calculations
regarding the sizes of all sections related to exported symbols:
| HAVE_ARCH_PREL32_RELOCATIONS | !HAVE_ARCH_PREL32_RELOCATIONS
Section | Base [B] | Ext. [B] | Sep. [B] | Base [B] | Ext. [B] | Sep. [B]
----------------------------------------------------------------------------------------
__ksymtab | 71244 | 200416 | 150312 | 142488 | 400832 | 300624
__ksymtab_gpl | 79068 | NA | NA | 158136 | NA | NA
__kcrctab | 23748 | 50104 | 50104 | 23748 | 50104 | 50104
__kcrctab_gpl | 26356 | NA | NA | 26356 | NA | NA
__ksymtab_strings | 253628 | 253628 | 253628 | 253628 | 253628 | 253628
__kflagstab | NA | NA | 12526 | NA | NA | 12526
----------------------------------------------------------------------------------------
Total | 454044 | 504148 | 466570 | 604356 | 704564 | 616882
Increase to base [%] | NA | 11.0 | 2.8 | NA | 16.6 | 2.1
The column "HAVE_ARCH_PREL32_RELOCATIONS -> Base" contains themeasured
numbers. The rest of the values are calculated. The "Ext." column
represents an alternative approach of extending __ksymtab to include a
bitset of symbol flags, and the "Sep." column represents the approach of
having a separate __kflagstab. With HAVE_ARCH_PREL32_RELOCATIONS, each
kernel_symbol is 12 B in size and is extended to 16 B. With
!HAVE_ARCH_PREL32_RELOCATIONS, it is 24 B, extended to 32 B. Note that
this does not include the metadata needed to relocate __ksymtab*, which
is freed after the initial processing.
The base export data in this case totals 0.43 MiB. About 50% is used for
storing the names of exported symbols.
Adding __kflagstab as a separate section has a negligible impact, as
expected. When extending __ksymtab (kernel_symbol) instead, the worst
case with !HAVE_ARCH_PREL32_RELOCATIONS increases the export data size
by 16.6%. Note that the larger increase in size for the latter approach
is due to 4-byte alignment of kernel_symbol data structure, instead of
1-byte alignment for the flags bitset in __kflagstab in the former
approach.
Based on the above, it was concluded that introducing __kflagstab makes
senses, as the added complexity is minimal over extending kernel_symbol,
and there is overall simplification of symbol finding logic in the
module loader.
Thank you Petr Pavlu for doing a section size analysis as well as Sami
Tolvanen, Petr Pavlu and Jonathan Corbet for their valuable feedback.
---
Changes from v4:
- squashed patches #4 and #5 to fix a bisecting issue
v4:
https://lore.kernel.org/r/20260305-kflagstab-v4-0-6a76bf8b83c7@google.com
Changes from v3:
- made commit messages more descriptive
v3:
https://lore.kernel.org/20251103161954.1351784-1-sidnayyar@google.com/
Changes from v2:
- dropped symbol import protection to spin off into its own series
v2:
https://lore.kernel.org/20251013153918.2206045-1-sidnayyar@google.com/
Changes from v1:
- added a check to ensure __kflagstab is present
- added warnings for the obsolete *_gpl sections
- moved protected symbol check before ref_module() call
- moved protected symbol check failure warning to issue detection point
v1:
https://lore.kernel.org/20250829105418.3053274-1-sidnayyar@google.com/
[1] https://github.com/openSUSE/kernel-source/blob/307f149d9100a0e229eb94cbb997ae61187995c3/config/x86_64/default
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
Siddharth Nayyar (7):
module: define ksym_flags enumeration to represent kernel symbol flags
module: add kflagstab section to vmlinux and modules
module: populate kflagstab in modpost
module: use kflagstab instead of *_gpl sections
module: deprecate usage of *_gpl sections in module loader
module: remove *_gpl sections from vmlinux and modules
documentation: remove references to *_gpl sections
Documentation/kbuild/modules.rst | 11 +++--
include/asm-generic/vmlinux.lds.h | 21 +++-----
include/linux/export-internal.h | 28 +++++++----
include/linux/module.h | 4 +-
include/linux/module_symbol.h | 5 ++
kernel/module/internal.h | 4 +-
kernel/module/main.c | 101 ++++++++++++++++++--------------------
scripts/mod/modpost.c | 16 ++++--
scripts/module.lds.S | 3 +-
9 files changed, 98 insertions(+), 95 deletions(-)
---
base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
change-id: 20260305-kflagstab-51a08efed244
Best regards,
--
Siddharth Nayyar <sidnayyar@google.com>
^ permalink raw reply
* [PATCH v5 7/7] documentation: remove references to *_gpl sections
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
*_gpl sections are no longer present in the kernel binary.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
---
Documentation/kbuild/modules.rst | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/kbuild/modules.rst b/Documentation/kbuild/modules.rst
index d0703605bfa4..b3a26a36ee17 100644
--- a/Documentation/kbuild/modules.rst
+++ b/Documentation/kbuild/modules.rst
@@ -426,11 +426,12 @@ Symbols From the Kernel (vmlinux + modules)
Version Information Formats
---------------------------
- Exported symbols have information stored in __ksymtab or __ksymtab_gpl
- sections. Symbol names and namespaces are stored in __ksymtab_strings,
- using a format similar to the string table used for ELF. If
- CONFIG_MODVERSIONS is enabled, the CRCs corresponding to exported
- symbols will be added to the __kcrctab or __kcrctab_gpl.
+ Exported symbols have information stored in the __ksymtab and
+ __kflagstab sections. Symbol names and namespaces are stored in
+ __ksymtab_strings section, using a format similar to the string
+ table used for ELF. If CONFIG_MODVERSIONS is enabled, the CRCs
+ corresponding to exported symbols will be added to the
+ __kcrctab section.
If CONFIG_BASIC_MODVERSIONS is enabled (default with
CONFIG_MODVERSIONS), imported symbols will have their symbol name and
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 6/7] module: remove *_gpl sections from vmlinux and modules
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
These sections are not used anymore and can be removed from vmlinux and
modules during linking.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/asm-generic/vmlinux.lds.h | 18 ++----------------
scripts/module.lds.S | 2 --
2 files changed, 2 insertions(+), 18 deletions(-)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index d64a475c468a..6f47c4c56574 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -508,34 +508,20 @@
\
PRINTK_INDEX \
\
- /* Kernel symbol table: Normal symbols */ \
+ /* Kernel symbol table */ \
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
__start___ksymtab = .; \
KEEP(*(SORT(___ksymtab+*))) \
__stop___ksymtab = .; \
} \
\
- /* Kernel symbol table: GPL-only symbols */ \
- __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) { \
- __start___ksymtab_gpl = .; \
- KEEP(*(SORT(___ksymtab_gpl+*))) \
- __stop___ksymtab_gpl = .; \
- } \
- \
- /* Kernel symbol table: Normal symbols */ \
+ /* Kernel symbol CRC table */ \
__kcrctab : AT(ADDR(__kcrctab) - LOAD_OFFSET) { \
__start___kcrctab = .; \
KEEP(*(SORT(___kcrctab+*))) \
__stop___kcrctab = .; \
} \
\
- /* Kernel symbol table: GPL-only symbols */ \
- __kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - LOAD_OFFSET) { \
- __start___kcrctab_gpl = .; \
- KEEP(*(SORT(___kcrctab_gpl+*))) \
- __stop___kcrctab_gpl = .; \
- } \
- \
/* Kernel symbol flags table */ \
__kflagstab : AT(ADDR(__kflagstab) - LOAD_OFFSET) { \
__start___kflagstab = .; \
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index d7a8ba278dfc..23fa452eb16d 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -20,9 +20,7 @@ SECTIONS {
}
__ksymtab 0 : ALIGN(8) { *(SORT(___ksymtab+*)) }
- __ksymtab_gpl 0 : ALIGN(8) { *(SORT(___ksymtab_gpl+*)) }
__kcrctab 0 : ALIGN(4) { *(SORT(___kcrctab+*)) }
- __kcrctab_gpl 0 : ALIGN(4) { *(SORT(___kcrctab_gpl+*)) }
__kflagstab 0 : ALIGN(1) { *(SORT(___kflagstab+*)) }
.ctors 0 : ALIGN(8) { *(SORT(.ctors.*)) *(.ctors) }
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 5/7] module: deprecate usage of *_gpl sections in module loader
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
The *_gpl section are not being used populated by modpost anymore. Hence
the module loader doesn't need to find and process these sections in
modules.
This patch also simplifies symbol finding logic in module loader since
*_gpl sections don't have to be searched anymore.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/linux/module.h | 3 ---
kernel/module/internal.h | 3 ---
kernel/module/main.c | 46 ++++++++++++++++++----------------------------
3 files changed, 18 insertions(+), 34 deletions(-)
diff --git a/include/linux/module.h b/include/linux/module.h
index aee3accba73c..a0ec1a9f97b4 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -434,9 +434,6 @@ struct module {
unsigned int num_kp;
/* GPL-only exported symbols. */
- unsigned int num_gpl_syms;
- const struct kernel_symbol *gpl_syms;
- const u32 *gpl_crcs;
bool using_gplonly_symbols;
#ifdef CONFIG_MODULE_SIG
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 69b84510e097..061161cc79d9 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -53,10 +53,7 @@ extern const size_t modinfo_attrs_count;
/* Provided by the linker */
extern const struct kernel_symbol __start___ksymtab[];
extern const struct kernel_symbol __stop___ksymtab[];
-extern const struct kernel_symbol __start___ksymtab_gpl[];
-extern const struct kernel_symbol __stop___ksymtab_gpl[];
extern const u32 __start___kcrctab[];
-extern const u32 __start___kcrctab_gpl[];
extern const u8 __start___kflagstab[];
#define KMOD_PATH_LEN 256
diff --git a/kernel/module/main.c b/kernel/module/main.c
index d237fa4e0737..189e18b8103d 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1464,29 +1464,17 @@ EXPORT_SYMBOL_GPL(__symbol_get);
*/
static int verify_exported_symbols(struct module *mod)
{
- unsigned int i;
const struct kernel_symbol *s;
- struct {
- const struct kernel_symbol *sym;
- unsigned int num;
- } arr[] = {
- { mod->syms, mod->num_syms },
- { mod->gpl_syms, mod->num_gpl_syms },
- };
-
- for (i = 0; i < ARRAY_SIZE(arr); i++) {
- for (s = arr[i].sym; s < arr[i].sym + arr[i].num; s++) {
- struct find_symbol_arg fsa = {
- .name = kernel_symbol_name(s),
- .gplok = true,
- };
- if (find_symbol(&fsa)) {
- pr_err("%s: exports duplicate symbol %s"
- " (owned by %s)\n",
- mod->name, kernel_symbol_name(s),
- module_name(fsa.owner));
- return -ENOEXEC;
- }
+ for (s = mod->syms; s < mod->syms + mod->num_syms; s++) {
+ struct find_symbol_arg fsa = {
+ .name = kernel_symbol_name(s),
+ .gplok = true,
+ };
+ if (find_symbol(&fsa)) {
+ pr_err("%s: exports duplicate symbol %s (owned by %s)\n",
+ mod->name, kernel_symbol_name(s),
+ module_name(fsa.owner));
+ return -ENOEXEC;
}
}
return 0;
@@ -2608,12 +2596,15 @@ static int find_module_sections(struct module *mod, struct load_info *info)
mod->syms = section_objs(info, "__ksymtab",
sizeof(*mod->syms), &mod->num_syms);
mod->crcs = section_addr(info, "__kcrctab");
- mod->gpl_syms = section_objs(info, "__ksymtab_gpl",
- sizeof(*mod->gpl_syms),
- &mod->num_gpl_syms);
- mod->gpl_crcs = section_addr(info, "__kcrctab_gpl");
mod->flagstab = section_addr(info, "__kflagstab");
+ if (section_addr(info, "__ksymtab_gpl"))
+ pr_warn("%s: ignoring obsolete section __ksymtab_gpl\n",
+ mod->name);
+ if (section_addr(info, "__kcrctab_gpl"))
+ pr_warn("%s: ignoring obsolete section __kcrctab_gpl\n",
+ mod->name);
+
#ifdef CONFIG_CONSTRUCTORS
mod->ctors = section_objs(info, ".ctors",
sizeof(*mod->ctors), &mod->num_ctors);
@@ -2823,8 +2814,7 @@ static int check_export_symbol_sections(struct module *mod)
return -ENOEXEC;
}
#ifdef CONFIG_MODVERSIONS
- if ((mod->num_syms && !mod->crcs) ||
- (mod->num_gpl_syms && !mod->gpl_crcs)) {
+ if (mod->num_syms && !mod->crcs) {
return try_to_force_load(mod,
"no versions for exported symbols");
}
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 4/7] module: use kflagstab instead of *_gpl sections
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
Read kflagstab section for vmlinux and modules to determine whether
kernel symbols are GPL only.
This patch eliminates the need for fragmenting the ksymtab for infering
the value of GPL-only symbol flag, henceforth stop using the *_gpl
versions of the ksymtab and kcrctab in modpost.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/linux/export-internal.h | 21 ++++++++--------
include/linux/module.h | 1 +
kernel/module/internal.h | 1 +
kernel/module/main.c | 55 ++++++++++++++++++++++-------------------
scripts/mod/modpost.c | 8 +++---
5 files changed, 46 insertions(+), 40 deletions(-)
diff --git a/include/linux/export-internal.h b/include/linux/export-internal.h
index 4123c7592404..726054614752 100644
--- a/include/linux/export-internal.h
+++ b/include/linux/export-internal.h
@@ -37,14 +37,14 @@
* section flag requires it. Use '%progbits' instead of '@progbits' since the
* former apparently works on all arches according to the binutils source.
*/
-#define __KSYMTAB(name, sym, sec, ns) \
+#define __KSYMTAB(name, sym, ns) \
asm(" .section \"__ksymtab_strings\",\"aMS\",%progbits,1" "\n" \
"__kstrtab_" #name ":" "\n" \
" .asciz \"" #name "\"" "\n" \
"__kstrtabns_" #name ":" "\n" \
" .asciz \"" ns "\"" "\n" \
" .previous" "\n" \
- " .section \"___ksymtab" sec "+" #name "\", \"a\"" "\n" \
+ " .section \"___ksymtab+" #name "\", \"a\"" "\n" \
__KSYM_ALIGN "\n" \
"__ksymtab_" #name ":" "\n" \
__KSYM_REF(sym) "\n" \
@@ -59,15 +59,16 @@
#define KSYM_FUNC(name) name
#endif
-#define KSYMTAB_FUNC(name, sec, ns) __KSYMTAB(name, KSYM_FUNC(name), sec, ns)
-#define KSYMTAB_DATA(name, sec, ns) __KSYMTAB(name, name, sec, ns)
+#define KSYMTAB_FUNC(name, ns) __KSYMTAB(name, KSYM_FUNC(name), ns)
+#define KSYMTAB_DATA(name, ns) __KSYMTAB(name, name, ns)
-#define SYMBOL_CRC(sym, crc, sec) \
- asm(".section \"___kcrctab" sec "+" #sym "\",\"a\"" "\n" \
- ".balign 4" "\n" \
- "__crc_" #sym ":" "\n" \
- ".long " #crc "\n" \
- ".previous" "\n")
+#define SYMBOL_CRC(sym, crc) \
+ asm(" .section \"___kcrctab+" #sym "\",\"a\"" "\n" \
+ " .balign 4" "\n" \
+ "__crc_" #sym ":" "\n" \
+ " .long " #crc "\n" \
+ " .previous" "\n" \
+ )
#define SYMBOL_FLAGS(sym, flags) \
asm(" .section \"___kflagstab+" #sym "\",\"a\"" "\n" \
diff --git a/include/linux/module.h b/include/linux/module.h
index 14f391b186c6..aee3accba73c 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -418,6 +418,7 @@ struct module {
/* Exported symbols */
const struct kernel_symbol *syms;
const u32 *crcs;
+ const u8 *flagstab;
unsigned int num_syms;
#ifdef CONFIG_ARCH_USES_CFI_TRAPS
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 618202578b42..69b84510e097 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -57,6 +57,7 @@ extern const struct kernel_symbol __start___ksymtab_gpl[];
extern const struct kernel_symbol __stop___ksymtab_gpl[];
extern const u32 __start___kcrctab[];
extern const u32 __start___kcrctab_gpl[];
+extern const u8 __start___kflagstab[];
#define KMOD_PATH_LEN 256
extern char modprobe_path[];
diff --git a/kernel/module/main.c b/kernel/module/main.c
index c3ce106c70af..d237fa4e0737 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -11,6 +11,7 @@
#include <linux/extable.h>
#include <linux/moduleloader.h>
#include <linux/module_signature.h>
+#include <linux/module_symbol.h>
#include <linux/trace_events.h>
#include <linux/init.h>
#include <linux/kallsyms.h>
@@ -87,7 +88,7 @@ struct mod_tree_root mod_tree __cacheline_aligned = {
struct symsearch {
const struct kernel_symbol *start, *stop;
const u32 *crcs;
- enum mod_license license;
+ const u8 *flagstab;
};
/*
@@ -364,19 +365,21 @@ static bool find_exported_symbol_in_section(const struct symsearch *syms,
struct find_symbol_arg *fsa)
{
struct kernel_symbol *sym;
-
- if (!fsa->gplok && syms->license == GPL_ONLY)
- return false;
+ u8 sym_flags;
sym = bsearch(fsa->name, syms->start, syms->stop - syms->start,
sizeof(struct kernel_symbol), cmp_name);
if (!sym)
return false;
+ sym_flags = *(syms->flagstab + (sym - syms->start));
+ if (!fsa->gplok && (sym_flags & KSYM_FLAG_GPL_ONLY))
+ return false;
+
fsa->owner = owner;
fsa->crc = symversion(syms->crcs, sym - syms->start);
fsa->sym = sym;
- fsa->license = syms->license;
+ fsa->license = (sym_flags & KSYM_FLAG_GPL_ONLY) ? GPL_ONLY : NOT_GPL_ONLY;
return true;
}
@@ -387,36 +390,31 @@ static bool find_exported_symbol_in_section(const struct symsearch *syms,
*/
bool find_symbol(struct find_symbol_arg *fsa)
{
- static const struct symsearch arr[] = {
- { __start___ksymtab, __stop___ksymtab, __start___kcrctab,
- NOT_GPL_ONLY },
- { __start___ksymtab_gpl, __stop___ksymtab_gpl,
- __start___kcrctab_gpl,
- GPL_ONLY },
+ const struct symsearch syms = {
+ .start = __start___ksymtab,
+ .stop = __stop___ksymtab,
+ .crcs = __start___kcrctab,
+ .flagstab = __start___kflagstab,
};
struct module *mod;
- unsigned int i;
- for (i = 0; i < ARRAY_SIZE(arr); i++)
- if (find_exported_symbol_in_section(&arr[i], NULL, fsa))
- return true;
+ if (find_exported_symbol_in_section(&syms, NULL, fsa))
+ return true;
list_for_each_entry_rcu(mod, &modules, list,
lockdep_is_held(&module_mutex)) {
- struct symsearch arr[] = {
- { mod->syms, mod->syms + mod->num_syms, mod->crcs,
- NOT_GPL_ONLY },
- { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms,
- mod->gpl_crcs,
- GPL_ONLY },
+ const struct symsearch syms = {
+ .start = mod->syms,
+ .stop = mod->syms + mod->num_syms,
+ .crcs = mod->crcs,
+ .flagstab = mod->flagstab,
};
if (mod->state == MODULE_STATE_UNFORMED)
continue;
- for (i = 0; i < ARRAY_SIZE(arr); i++)
- if (find_exported_symbol_in_section(&arr[i], mod, fsa))
- return true;
+ if (find_exported_symbol_in_section(&syms, mod, fsa))
+ return true;
}
pr_debug("Failed to find symbol %s\n", fsa->name);
@@ -2614,6 +2612,7 @@ static int find_module_sections(struct module *mod, struct load_info *info)
sizeof(*mod->gpl_syms),
&mod->num_gpl_syms);
mod->gpl_crcs = section_addr(info, "__kcrctab_gpl");
+ mod->flagstab = section_addr(info, "__kflagstab");
#ifdef CONFIG_CONSTRUCTORS
mod->ctors = section_objs(info, ".ctors",
@@ -2817,8 +2816,12 @@ static int move_module(struct module *mod, struct load_info *info)
return ret;
}
-static int check_export_symbol_versions(struct module *mod)
+static int check_export_symbol_sections(struct module *mod)
{
+ if (mod->num_syms && !mod->flagstab) {
+ pr_err("%s: no flags for exported symbols\n", mod->name);
+ return -ENOEXEC;
+ }
#ifdef CONFIG_MODVERSIONS
if ((mod->num_syms && !mod->crcs) ||
(mod->num_gpl_syms && !mod->gpl_crcs)) {
@@ -3434,7 +3437,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
if (err)
goto free_unload;
- err = check_export_symbol_versions(mod);
+ err = check_export_symbol_sections(mod);
if (err)
goto free_unload;
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 1d721fe67caf..9d96acce60a8 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -1876,9 +1876,9 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
if (trim_unused_exports && !sym->used)
continue;
- buf_printf(buf, "KSYMTAB_%s(%s, \"%s\", \"%s\");\n",
+ buf_printf(buf, "KSYMTAB_%s(%s, \"%s\");\n",
sym->is_func ? "FUNC" : "DATA", sym->name,
- sym->is_gpl_only ? "_gpl" : "", sym->namespace);
+ sym->namespace);
buf_printf(buf, "SYMBOL_FLAGS(%s, 0x%02x);\n",
sym->name, get_symbol_flags(sym));
@@ -1899,8 +1899,8 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
sym->name, mod->name, mod->is_vmlinux ? "" : ".ko",
sym->name);
- buf_printf(buf, "SYMBOL_CRC(%s, 0x%08x, \"%s\");\n",
- sym->name, sym->crc, sym->is_gpl_only ? "_gpl" : "");
+ buf_printf(buf, "SYMBOL_CRC(%s, 0x%08x);\n",
+ sym->name, sym->crc);
}
}
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 3/7] module: populate kflagstab in modpost
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
This patch adds the ability to create entries for kernel symbol flag
bitsets in kflagstab. Modpost populates only the GPL-only flag for now.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/linux/export-internal.h | 7 +++++++
scripts/mod/modpost.c | 8 ++++++++
2 files changed, 15 insertions(+)
diff --git a/include/linux/export-internal.h b/include/linux/export-internal.h
index d445705ac13c..4123c7592404 100644
--- a/include/linux/export-internal.h
+++ b/include/linux/export-internal.h
@@ -69,4 +69,11 @@
".long " #crc "\n" \
".previous" "\n")
+#define SYMBOL_FLAGS(sym, flags) \
+ asm(" .section \"___kflagstab+" #sym "\",\"a\"" "\n" \
+ "__flags_" #sym ":" "\n" \
+ " .byte " #flags "\n" \
+ " .previous" "\n" \
+ )
+
#endif /* __LINUX_EXPORT_INTERNAL_H__ */
diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c
index 0c25b5ad497b..1d721fe67caf 100644
--- a/scripts/mod/modpost.c
+++ b/scripts/mod/modpost.c
@@ -244,6 +244,11 @@ static struct symbol *alloc_symbol(const char *name)
return s;
}
+static uint8_t get_symbol_flags(const struct symbol *sym)
+{
+ return sym->is_gpl_only ? KSYM_FLAG_GPL_ONLY : 0;
+}
+
/* For the hash of exported symbols */
static void hash_add_symbol(struct symbol *sym)
{
@@ -1874,6 +1879,9 @@ static void add_exported_symbols(struct buffer *buf, struct module *mod)
buf_printf(buf, "KSYMTAB_%s(%s, \"%s\", \"%s\");\n",
sym->is_func ? "FUNC" : "DATA", sym->name,
sym->is_gpl_only ? "_gpl" : "", sym->namespace);
+
+ buf_printf(buf, "SYMBOL_FLAGS(%s, 0x%02x);\n",
+ sym->name, get_symbol_flags(sym));
}
if (!modversions)
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 2/7] module: add kflagstab section to vmlinux and modules
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
This section will contain read-only data for values of kernel symbol
flags in the form of an 8-bit bitsets for each kernel symbol. Each bit
in the bitset represents a flag value defined by ksym_flags enumeration.
The kflagstab section introduces a 1-byte overhead for each symbol
exported in the ksymtab. Given that typical kernel builds contain
roughly a few thousand exported symbols, the resulting memory increase
is negligible.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/asm-generic/vmlinux.lds.h | 7 +++++++
scripts/module.lds.S | 1 +
2 files changed, 8 insertions(+)
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 1e1580febe4b..d64a475c468a 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -536,6 +536,13 @@
__stop___kcrctab_gpl = .; \
} \
\
+ /* Kernel symbol flags table */ \
+ __kflagstab : AT(ADDR(__kflagstab) - LOAD_OFFSET) { \
+ __start___kflagstab = .; \
+ KEEP(*(SORT(___kflagstab+*))) \
+ __stop___kflagstab = .; \
+ } \
+ \
/* Kernel symbol table: strings */ \
__ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) { \
*(__ksymtab_strings) \
diff --git a/scripts/module.lds.S b/scripts/module.lds.S
index 054ef99e8288..d7a8ba278dfc 100644
--- a/scripts/module.lds.S
+++ b/scripts/module.lds.S
@@ -23,6 +23,7 @@ SECTIONS {
__ksymtab_gpl 0 : ALIGN(8) { *(SORT(___ksymtab_gpl+*)) }
__kcrctab 0 : ALIGN(4) { *(SORT(___kcrctab+*)) }
__kcrctab_gpl 0 : ALIGN(4) { *(SORT(___kcrctab_gpl+*)) }
+ __kflagstab 0 : ALIGN(1) { *(SORT(___kflagstab+*)) }
.ctors 0 : ALIGN(8) { *(SORT(.ctors.*)) *(.ctors) }
.init_array 0 : ALIGN(8) { *(SORT(.init_array.*)) *(.init_array) }
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 1/7] module: define ksym_flags enumeration to represent kernel symbol flags
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
In-Reply-To: <20260326-kflagstab-v5-0-455cd723dddf@google.com>
Symbol flags is an enumeration used to represent flags as a bitset, for
example a flag to tell if a symbol is GPL only.
The said bitset is introduced in subsequent patches and will contain
values of kernel symbol flags. These bitset will then be used to infer
flag values rather than fragmenting ksymtab for separating symbols with
different flag values, thereby eliminating the need to fragment the
ksymtab.
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
include/linux/module_symbol.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/linux/module_symbol.h b/include/linux/module_symbol.h
index 77c9895b9ddb..574609aced99 100644
--- a/include/linux/module_symbol.h
+++ b/include/linux/module_symbol.h
@@ -2,6 +2,11 @@
#ifndef _LINUX_MODULE_SYMBOL_H
#define _LINUX_MODULE_SYMBOL_H
+/* Kernel symbol flags bitset. */
+enum ksym_flags {
+ KSYM_FLAG_GPL_ONLY = 1 << 0,
+};
+
/* This ignores the intensely annoying "mapping symbols" found in ELF files. */
static inline bool is_mapping_symbol(const char *str)
{
--
2.53.0.1018.g2bb0e51243-goog
^ permalink raw reply related
* [PATCH v5 0/7] scalable symbol flags with __kflagstab
From: Siddharth Nayyar @ 2026-03-26 21:21 UTC (permalink / raw)
To: Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, Arnd Bergmann, Nathan Chancellor, Nicolas Schier,
Jonathan Corbet, Shuah Khan
Cc: linux-modules, linux-kernel, linux-arch, linux-kbuild, linux-doc,
Siddharth Nayyar, gprocida
This patch series implements a mechanism for scalable exported symbol
flags using a separate section called __kflagstab. The series introduces
__kflagstab support, removes *_gpl sections in favor of a GPL flag,
simplifies symbol resolution during module loading.
The __kflagstab contains an 8-bit bitset which can represent up to 8
boolean flags per symbol exported in the __ksymtab. The patch series
also uses this bitset to store GPL-only flag values for kernel symbols,
thereby eliminating the need for *_gpl sections for representing GPL
only symbols.
Petr Pavlu ran a small test to get a better understanding of the
different section sizes resulting from this patch series. He used
v6.17-rc6 together with the openSUSE x86_64 config [1], which is fairly
large. The resulting vmlinux.bin (no debuginfo) had an on-disk size of
58 MiB, and included 5937 + 6589 (GPL-only) exported symbols.
The following table summarizes his measurements and calculations
regarding the sizes of all sections related to exported symbols:
| HAVE_ARCH_PREL32_RELOCATIONS | !HAVE_ARCH_PREL32_RELOCATIONS
Section | Base [B] | Ext. [B] | Sep. [B] | Base [B] | Ext. [B] | Sep. [B]
----------------------------------------------------------------------------------------
__ksymtab | 71244 | 200416 | 150312 | 142488 | 400832 | 300624
__ksymtab_gpl | 79068 | NA | NA | 158136 | NA | NA
__kcrctab | 23748 | 50104 | 50104 | 23748 | 50104 | 50104
__kcrctab_gpl | 26356 | NA | NA | 26356 | NA | NA
__ksymtab_strings | 253628 | 253628 | 253628 | 253628 | 253628 | 253628
__kflagstab | NA | NA | 12526 | NA | NA | 12526
----------------------------------------------------------------------------------------
Total | 454044 | 504148 | 466570 | 604356 | 704564 | 616882
Increase to base [%] | NA | 11.0 | 2.8 | NA | 16.6 | 2.1
The column "HAVE_ARCH_PREL32_RELOCATIONS -> Base" contains themeasured
numbers. The rest of the values are calculated. The "Ext." column
represents an alternative approach of extending __ksymtab to include a
bitset of symbol flags, and the "Sep." column represents the approach of
having a separate __kflagstab. With HAVE_ARCH_PREL32_RELOCATIONS, each
kernel_symbol is 12 B in size and is extended to 16 B. With
!HAVE_ARCH_PREL32_RELOCATIONS, it is 24 B, extended to 32 B. Note that
this does not include the metadata needed to relocate __ksymtab*, which
is freed after the initial processing.
The base export data in this case totals 0.43 MiB. About 50% is used for
storing the names of exported symbols.
Adding __kflagstab as a separate section has a negligible impact, as
expected. When extending __ksymtab (kernel_symbol) instead, the worst
case with !HAVE_ARCH_PREL32_RELOCATIONS increases the export data size
by 16.6%. Note that the larger increase in size for the latter approach
is due to 4-byte alignment of kernel_symbol data structure, instead of
1-byte alignment for the flags bitset in __kflagstab in the former
approach.
Based on the above, it was concluded that introducing __kflagstab makes
senses, as the added complexity is minimal over extending kernel_symbol,
and there is overall simplification of symbol finding logic in the
module loader.
Thank you Petr Pavlu for doing a section size analysis as well as Sami
Tolvanen, Petr Pavlu and Jonathan Corbet for their valuable feedback.
---
Changes from v4:
- squashed patches #4 and #5 to fix a bisecting issue
v4:
https://lore.kernel.org/r/20260305-kflagstab-v4-0-6a76bf8b83c7@google.com
Changes from v3:
- made commit messages more descriptive
v3:
https://lore.kernel.org/20251103161954.1351784-1-sidnayyar@google.com/
Changes from v2:
- dropped symbol import protection to spin off into its own series
v2:
https://lore.kernel.org/20251013153918.2206045-1-sidnayyar@google.com/
Changes from v1:
- added a check to ensure __kflagstab is present
- added warnings for the obsolete *_gpl sections
- moved protected symbol check before ref_module() call
- moved protected symbol check failure warning to issue detection point
v1:
https://lore.kernel.org/20250829105418.3053274-1-sidnayyar@google.com/
[1] https://github.com/openSUSE/kernel-source/blob/307f149d9100a0e229eb94cbb997ae61187995c3/config/x86_64/default
Signed-off-by: Siddharth Nayyar <sidnayyar@google.com>
Reviewed-by: Petr Pavlu <petr.pavlu@suse.com>
---
Siddharth Nayyar (7):
module: define ksym_flags enumeration to represent kernel symbol flags
module: add kflagstab section to vmlinux and modules
module: populate kflagstab in modpost
module: use kflagstab instead of *_gpl sections
module: deprecate usage of *_gpl sections in module loader
module: remove *_gpl sections from vmlinux and modules
documentation: remove references to *_gpl sections
Documentation/kbuild/modules.rst | 11 +++--
include/asm-generic/vmlinux.lds.h | 21 +++-----
include/linux/export-internal.h | 28 +++++++----
include/linux/module.h | 4 +-
include/linux/module_symbol.h | 5 ++
kernel/module/internal.h | 4 +-
kernel/module/main.c | 101 ++++++++++++++++++--------------------
scripts/mod/modpost.c | 16 ++++--
scripts/module.lds.S | 3 +-
9 files changed, 98 insertions(+), 95 deletions(-)
---
base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
change-id: 20260305-kflagstab-51a08efed244
Best regards,
--
Siddharth Nayyar <sidnayyar@google.com>
^ permalink raw reply
* Re: [PATCH v3 00/24] vfio/pci: Base Live Update support for VFIO device files
From: David Matlack @ 2026-03-26 20:43 UTC (permalink / raw)
To: Alex Williamson, Bjorn Helgaas
Cc: Adithya Jayachandran, Alexander Graf, Alex Mastro, Andrew Morton,
Ankit Agrawal, Arnd Bergmann, Askar Safin, Borislav Petkov (AMD),
Chris Li, Dapeng Mi, David Rientjes, Feng Tang, Jacob Pan,
Jason Gunthorpe, Jason Gunthorpe, Jonathan Corbet, Josh Hilke,
Kees Cook, Kevin Tian, kexec, kvm, Leon Romanovsky,
Leon Romanovsky, linux-doc, linux-kernel, linux-kselftest,
linux-mm, linux-pci, Li RongQing, Lukas Wunner, Marco Elver,
Michał Winiarski, Mike Rapoport, Parav Pandit,
Pasha Tatashin, Paul E. McKenney, Pawan Gupta,
Peter Zijlstra (Intel), Pranjal Shrivastava, Pratyush Yadav,
Raghavendra Rao Ananta, Randy Dunlap, Rodrigo Vivi,
Saeed Mahameed, Samiullah Khawaja, Shuah Khan, Vipin Sharma,
Vivek Kasireddy, William Tu, Yi Liu, Zhu Yanjun
In-Reply-To: <20260323235817.1960573-1-dmatlack@google.com>
On Mon, Mar 23, 2026 at 4:58 PM David Matlack <dmatlack@google.com> wrote:
>
> This series can be found on GitHub:
>
> https://github.com/dmatlack/linux/tree/liveupdate/vfio/cdev/v3
>
> This series adds the base support to preserve a VFIO device file across
> a Live Update. "Base support" means that this allows userspace to
> safely preserve a VFIO device file with LIVEUPDATE_SESSION_PRESERVE_FD
> and retrieve it with LIVEUPDATE_SESSION_RETRIEVE_FD, but the device
> itself is not preserved in a fully running state across Live Update.
Apologies for the large number of people who got added to the CC list
on this version of the patchset. The changes to
Documentation/admin-guide/kernel-parameters.txt in patch 4 caused
scripts/get_maintainer.pl to CC a number of additional people due to
--git-fallback. I'll fix that in the next version.
^ permalink raw reply
* Re: [PATCH v8 02/10] x86/bhi: Make clear_bhb_loop() effective on newer CPUs
From: Pawan Gupta @ 2026-03-26 20:29 UTC (permalink / raw)
To: David Laight
Cc: Borislav Petkov, x86, Jon Kohler, Nikolay Borisov, H. Peter Anvin,
Josh Poimboeuf, David Kaplan, Sean Christopherson, Dave Hansen,
Peter Zijlstra, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, KP Singh, Jiri Olsa, David S. Miller,
Andy Lutomirski, Thomas Gleixner, Ingo Molnar, David Ahern,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, Stanislav Fomichev, Hao Luo, Paolo Bonzini,
Jonathan Corbet, linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf,
netdev, linux-doc
In-Reply-To: <20260326104557.24295cbb@pumpkin>
On Thu, Mar 26, 2026 at 10:45:57AM +0000, David Laight wrote:
> On Thu, 26 Mar 2026 11:01:20 +0100
> Borislav Petkov <bp@alien8.de> wrote:
>
> > On Thu, Mar 26, 2026 at 01:39:34AM -0700, Pawan Gupta wrote:
> > > I believe the equivalent for cpu_feature_enabled() in asm is the
> > > ALTERNATIVE. Please let me know if I am missing something.
> >
> > Yes, you are.
> >
> > The point is that you don't want to stick those alternative calls inside some
> > magic bhb_loop function but hand them in from the outside, as function
> > arguments.
> >
> > Basically what I did.
> >
> > Then you were worried about this being C code and it had to be noinstr... So
> > that outer function can be rewritten in asm, I think, and still keep it well
> > separate.
> >
> > I'll try to rewrite it once I get a free minute, and see how it looks.
> >
>
> I think someone tried getting C code to write the values to global data
> and getting the asm to read them.
> That got discounted because it spilt things between two largely unrelated files.
The implementation with global variables wasn't that bad, let me revive it.
This part which ties sequence to BHI mitigation, which is not ideal,
(because VMSCAPE also uses it) it does seems a cleaner option.
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -2095,6 +2095,11 @@ static void __init bhi_select_mitigation(void)
static void __init bhi_update_mitigation(void)
{
+ if (!cpu_feature_enabled(X86_FEATURE_BHI_CTRL)) {
+ bhi_seq_outer_loop = 5;
+ bhi_seq_inner_loop = 5;
+ }
+
I believe this can be moved to somewhere common to all mitigations.
> I think the BPF code would need significant refactoring to call a C function.
Ya, true. Will use globals and keep clear_bhb_loop() in asm.
^ permalink raw reply
* [PATCH 4/4] hwmon: (witrn) Add monitoring support
From: Rong Zhang @ 2026-03-26 19:19 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, Rong Zhang
In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe>
With sensor data collected, they can be exported to userspace via hwmon.
Register hwmon driver for witrn, with appropriate channels and
attributes.
Developed and tested with WITRN K2 USB-C tester. Theoretically this
driver should work properly on other models. They can be added to the
HID match table too if someone tests the driver with them.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Documentation/hwmon/witrn.rst | 30 +++
drivers/hwmon/Kconfig | 1 +
drivers/hwmon/witrn.c | 423 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 454 insertions(+)
diff --git a/Documentation/hwmon/witrn.rst b/Documentation/hwmon/witrn.rst
index e64c527928d0..f13323fdd9d9 100644
--- a/Documentation/hwmon/witrn.rst
+++ b/Documentation/hwmon/witrn.rst
@@ -21,3 +21,33 @@ Description
This driver implements support for the WITRN USB tester family.
The device communicates with the custom protocol over USB HID.
+
+As current can flow in both directions through the tester the sign of the
+channel "curr1_input" (label "IBUS") indicates the direction.
+
+Sysfs entries
+-------------
+
+ =============== ========== ==============================================================
+ Name Label Description
+ =============== ========== ==============================================================
+ in0_input VBUS Measured VBUS voltage (mV)
+ in0_average VBUS Calculated average VBUS voltage (mV)
+ in1_input D+ Measured D+ voltage (mV)
+ in2_input D- Measured D- voltage (mV)
+ in3_input CC1 Measured CC1 voltage (mV)
+ in4_input CC2 Measured CC2 voltage (mV)
+ cur1_input IBUS Measured VBUS current (mA)
+ curr1_average IBUS Calculated average VBUS current (mA)
+ curr1_rated_min IBUS Stop accumulating (recording) below this VBUS current (mA)
+ power1_input PBUS Calculated VBUS power (uW)
+ power1_average PBUS Calculated average VBUS power (uW)
+ energy1_input EBUS Accumulated VBUS energy (uJ)
+ charge1_input CBUS Accumulated VBUS charge (mC)
+ temp1_input Thermistor Measured thermistor temperature (m°C), -EXDEV if not connected
+ record_group ID of the record group for accumulative values
+ record_time Accumulated time for recording (s), see also curr1_rated_min
+ uptime Accumulated time since the device has been powered on (s)
+ =============== ========== ==============================================================
+
+All entries are readonly.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 746184608f81..c8b5144707a1 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2632,6 +2632,7 @@ config SENSORS_W83627EHF
config SENSORS_WITRN
tristate "WITRN USB tester"
depends on USB_HID
+ select HWMON_FP
help
If you say yes here you get support for WITRN USB charging
testers.
diff --git a/drivers/hwmon/witrn.c b/drivers/hwmon/witrn.c
index e8713da6de5a..f43bdbf13435 100644
--- a/drivers/hwmon/witrn.c
+++ b/drivers/hwmon/witrn.c
@@ -11,14 +11,19 @@
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/hid.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
#include <linux/jiffies.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/spinlock.h>
+#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/units.h>
#include <linux/workqueue.h>
+#include "hwmon-fp.h"
+
#define DRIVER_NAME "witrn"
#define WITRN_EP_CMD_OUT 0x01
#define WITRN_EP_DATA_IN 0x81
@@ -74,6 +79,7 @@ struct witrn_report {
static_assert(sizeof(struct witrn_report) == WITRN_REPORT_SZ);
struct witrn_priv {
+ struct device *hwmon_dev;
struct hid_device *hdev;
struct work_struct pause_work;
@@ -178,6 +184,413 @@ static int witrn_raw_event(struct hid_device *hdev, struct hid_report *report,
return 0;
}
+/* ======== HWMON ======== */
+
+static int witrn_collect_sensor(struct witrn_priv *priv, struct witrn_sensor *sensor)
+{
+ int ret;
+
+ scoped_guard(spinlock, &priv->lock) {
+ priv->last_access = jiffies;
+
+ if (!sensor_is_outdated(priv)) {
+ memcpy(sensor, &priv->sensor, sizeof(priv->sensor));
+ return 0;
+ }
+
+ reinit_completion(&priv->completion);
+ }
+
+ ret = witrn_open_hid(priv);
+ if (ret)
+ return ret;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->completion,
+ UP_TO_DATE_TIMEOUT);
+ if (ret == 0)
+ return -ETIMEDOUT;
+ else if (ret < 0)
+ return ret;
+
+ scoped_guard(spinlock, &priv->lock)
+ memcpy(sensor, &priv->sensor, sizeof(priv->sensor));
+
+ return 0;
+}
+
+#define SECS_PER_HOUR 3600ULL
+#define WITRN_SCALE_IN_VCC (HWMON_FP_SCALE_IN / DECI) /* dV to mV */
+#define WITRN_SCALE_CHARGE (HWMON_FP_SCALE_CURR * SECS_PER_HOUR) /* Ah to mC(mAs) */
+#define WITRN_SCALE_ENERGY (HWMON_FP_SCALE_ENERGY * SECS_PER_HOUR) /* Wh to uJ(uWs) */
+
+static int witrn_read_in(const struct witrn_sensor *sensor, u32 attr, int channel, long *val)
+{
+ switch (attr) {
+ case hwmon_in_input:
+ switch (channel) {
+ case 0:
+ return hwmon_fp_float_to_long(le32_to_cpu(sensor->vbus),
+ HWMON_FP_SCALE_IN, val);
+ case 1:
+ return hwmon_fp_float_to_long(le32_to_cpu(sensor->vdp),
+ HWMON_FP_SCALE_IN, val);
+ case 2:
+ return hwmon_fp_float_to_long(le32_to_cpu(sensor->vdm),
+ HWMON_FP_SCALE_IN, val);
+ case 3:
+ *val = sensor->vcc1 * WITRN_SCALE_IN_VCC;
+ return 0;
+ case 4:
+ *val = sensor->vcc2 * WITRN_SCALE_IN_VCC;
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_in_average:
+ switch (channel) {
+ case 0:
+ return hwmon_fp_div_to_long(le32_to_cpu(sensor->record_energy),
+ le32_to_cpu(sensor->record_charge),
+ HWMON_FP_SCALE_IN, true, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int witrn_read_curr(const struct witrn_sensor *sensor, u32 attr, int channel, long *val)
+{
+ int ret;
+
+ switch (attr) {
+ case hwmon_curr_input:
+ switch (channel) {
+ case 0:
+ return hwmon_fp_float_to_long(le32_to_cpu(sensor->ibus),
+ HWMON_FP_SCALE_CURR, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_curr_average:
+ switch (channel) {
+ case 0: {
+ s64 record_time = le32_to_cpu(sensor->record_time);
+ s64 capacity; /* mC(mAs) */
+
+ if (record_time == 0) {
+ *val = 0;
+ return 0;
+ }
+
+ ret = hwmon_fp_float_to_s64(le32_to_cpu(sensor->record_charge),
+ WITRN_SCALE_CHARGE, &capacity);
+ if (ret)
+ return ret;
+
+ /* mC(mAs) / s = mA */
+ *val = hwmon_fp_s64_to_long(capacity / record_time);
+ return 0;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_curr_rated_min:
+ switch (channel) {
+ case 0:
+ *val = le16_to_cpu(sensor->record_threshold); /* already in mA */
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int witrn_read_power(const struct witrn_sensor *sensor, u32 attr, int channel, long *val)
+{
+ int ret;
+
+ switch (attr) {
+ case hwmon_power_input:
+ switch (channel) {
+ case 0:
+ /*
+ * The device provides 1e-5 precision.
+ *
+ * Though userspace programs can calculate (VBUS * IBUS)
+ * themselves, this channel is provided for convenience
+ * and accuracy.
+ *
+ * E.g., when VBUS = 5.00049V and IBUS = 0.50049A,
+ * userspace calculates 5.000V * 0.500A = 2.500000W,
+ * while this channel reports 2.502695W.
+ */
+ return hwmon_fp_mul_to_long(le32_to_cpu(sensor->vbus),
+ le32_to_cpu(sensor->ibus),
+ HWMON_FP_SCALE_POWER, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_power_average:
+ switch (channel) {
+ case 0: {
+ s64 record_time = le32_to_cpu(sensor->record_time);
+ s64 energy; /* uJ(uWs) */
+
+ if (record_time == 0) {
+ *val = 0;
+ return 0;
+ }
+
+ ret = hwmon_fp_float_to_s64(le32_to_cpu(sensor->record_energy),
+ WITRN_SCALE_ENERGY, &energy);
+ if (ret)
+ return ret;
+
+ /* uJ(uWs) / s = uW */
+ *val = hwmon_fp_s64_to_long(energy / record_time);
+ return 0;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int witrn_read_temp(const struct witrn_sensor *sensor, u32 attr, int channel, long *val)
+{
+ int ret;
+
+ switch (attr) {
+ case hwmon_temp_input:
+ switch (channel) {
+ case 0:
+ ret = hwmon_fp_float_to_long(le32_to_cpu(sensor->temp_ntc),
+ HWMON_FP_SCALE_TEMP, val);
+
+ /*
+ * The thermistor (NTC, B=3435, T0=25°C, R0=10kohm) is an optional
+ * addon. When it's missing, an extremely cold temperature
+ * (-50°C - -80°C) is reported as the device deduced a very large
+ * resistance value (~500Kohm - ~5Mohm).
+ *
+ * We choose -40°C (~250kohm) as the threshold to determine whether
+ * the thermistor is connected.
+ *
+ * The addon can be connected to the device after the device being
+ * connected to the PC, so we can't use is_visible to hide it.
+ */
+ if (!ret && *val < -40L * (long)HWMON_FP_SCALE_TEMP)
+ return -EXDEV;
+
+ return ret;
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int witrn_read_energy(const struct witrn_sensor *sensor, u32 attr, int channel, s64 *val)
+{
+ switch (attr) {
+ case hwmon_energy_input:
+ switch (channel) {
+ case 0:
+ return hwmon_fp_float_to_s64(le32_to_cpu(sensor->record_energy),
+ WITRN_SCALE_ENERGY, val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int witrn_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct witrn_priv *priv = dev_get_drvdata(dev);
+ struct witrn_sensor sensor;
+ int ret;
+
+ ret = witrn_collect_sensor(priv, &sensor);
+ if (ret)
+ return ret;
+
+ switch (type) {
+ case hwmon_in:
+ return witrn_read_in(&sensor, attr, channel, val);
+ case hwmon_curr:
+ return witrn_read_curr(&sensor, attr, channel, val);
+ case hwmon_power:
+ return witrn_read_power(&sensor, attr, channel, val);
+ case hwmon_temp:
+ return witrn_read_temp(&sensor, attr, channel, val);
+ case hwmon_energy64:
+ return witrn_read_energy(&sensor, attr, channel, (s64 *)val);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int witrn_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ static const char * const in_labels[] = {
+ "VBUS",
+ "D+",
+ "D-",
+ "CC1",
+ "CC2",
+ };
+ static const char * const curr_labels[] = {
+ "IBUS", /* VBUS current */
+ };
+ static const char * const power_labels[] = {
+ "PBUS", /* VBUS power */
+ };
+ static const char * const energy_labels[] = {
+ "EBUS", /* VBUS energy */
+ };
+ static const char * const temp_labels[] = {
+ "Thermistor",
+ };
+
+ if (type == hwmon_in && attr == hwmon_in_label &&
+ channel < ARRAY_SIZE(in_labels)) {
+ *str = in_labels[channel];
+ } else if (type == hwmon_curr && attr == hwmon_curr_label &&
+ channel < ARRAY_SIZE(curr_labels)) {
+ *str = curr_labels[channel];
+ } else if (type == hwmon_power && attr == hwmon_power_label &&
+ channel < ARRAY_SIZE(power_labels)) {
+ *str = power_labels[channel];
+ } else if (type == hwmon_energy64 && attr == hwmon_energy_label &&
+ channel < ARRAY_SIZE(energy_labels)) {
+ *str = energy_labels[channel];
+ } else if (type == hwmon_temp && attr == hwmon_temp_label &&
+ channel < ARRAY_SIZE(temp_labels)) {
+ *str = temp_labels[channel];
+ } else {
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static const struct hwmon_channel_info *const witrn_info[] = {
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_AVERAGE,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_INPUT | HWMON_C_LABEL | HWMON_C_AVERAGE | HWMON_C_RATED_MIN),
+ HWMON_CHANNEL_INFO(power,
+ HWMON_P_INPUT | HWMON_P_LABEL | HWMON_P_AVERAGE),
+ HWMON_CHANNEL_INFO(energy64,
+ HWMON_E_INPUT | HWMON_E_LABEL),
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_INPUT | HWMON_T_LABEL),
+ NULL
+};
+
+static const struct hwmon_ops witrn_hwmon_ops = {
+ .visible = 0444, /* Nothing is tunable from PC :-( */
+ .read = witrn_read,
+ .read_string = witrn_read_string,
+};
+
+static const struct hwmon_chip_info witrn_chip_info = {
+ .ops = &witrn_hwmon_ops,
+ .info = witrn_info,
+};
+
+enum witrn_attr_channel {
+ ATTR_CHARGE,
+ ATTR_RECORD_GROUP,
+ ATTR_RECORD_TIME,
+ ATTR_UPTIME,
+};
+
+static ssize_t witrn_attr_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ enum witrn_attr_channel channel = to_sensor_dev_attr(attr)->index;
+ struct witrn_priv *priv = dev_get_drvdata(dev);
+ struct witrn_sensor sensor;
+ int ret;
+ s64 val;
+
+ ret = witrn_collect_sensor(priv, &sensor);
+ if (ret)
+ return ret;
+
+ switch (channel) {
+ case ATTR_CHARGE:
+ ret = hwmon_fp_float_to_s64(le32_to_cpu(sensor.record_charge),
+ WITRN_SCALE_CHARGE, &val);
+ if (ret)
+ return ret;
+ break;
+ case ATTR_RECORD_GROUP:
+ /* +1 to match the index displayed on the meter. */
+ val = sensor.record_group + 1;
+ break;
+ case ATTR_RECORD_TIME:
+ val = le32_to_cpu(sensor.record_time);
+ break;
+ case ATTR_UPTIME:
+ val = le32_to_cpu(sensor.uptime);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return sysfs_emit(buf, "%lld\n", val);
+}
+
+static ssize_t witrn_attr_label_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ enum witrn_attr_channel channel = to_sensor_dev_attr(attr)->index;
+ const char *str;
+
+ switch (channel) {
+ case ATTR_CHARGE:
+ str = "CBUS"; /* VBUS charge */
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return sysfs_emit(buf, "%s\n", str);
+}
+
+static SENSOR_DEVICE_ATTR_RO(charge1_input, witrn_attr, ATTR_CHARGE);
+static SENSOR_DEVICE_ATTR_RO(charge1_label, witrn_attr_label, ATTR_CHARGE);
+static SENSOR_DEVICE_ATTR_RO(record_group, witrn_attr, ATTR_RECORD_GROUP);
+static SENSOR_DEVICE_ATTR_RO(record_time, witrn_attr, ATTR_RECORD_TIME);
+static SENSOR_DEVICE_ATTR_RO(uptime, witrn_attr, ATTR_UPTIME);
+
+static struct attribute *witrn_attrs[] = {
+ &sensor_dev_attr_charge1_input.dev_attr.attr,
+ &sensor_dev_attr_charge1_label.dev_attr.attr,
+ &sensor_dev_attr_record_group.dev_attr.attr,
+ &sensor_dev_attr_record_time.dev_attr.attr,
+ &sensor_dev_attr_uptime.dev_attr.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(witrn);
+
static int witrn_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct device *parent = &hdev->dev;
@@ -219,6 +632,14 @@ static int witrn_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
+ priv->hwmon_dev = hwmon_device_register_with_info(parent, DRIVER_NAME, priv,
+ &witrn_chip_info, witrn_groups);
+ if (IS_ERR(priv->hwmon_dev)) {
+ witrn_close_hid(priv);
+ hid_hw_stop(hdev);
+ return PTR_ERR(priv->hwmon_dev);
+ }
+
return 0;
}
@@ -226,6 +647,8 @@ static void witrn_remove(struct hid_device *hdev)
{
struct witrn_priv *priv = hid_get_drvdata(hdev);
+ hwmon_device_unregister(priv->hwmon_dev);
+
witrn_close_hid(priv);
/* Cancel it after closing HID so that it won't be rescheduled. */
--
2.53.0
^ permalink raw reply related
* [PATCH 3/4] hwmon: Add barebone HID driver for WITRN
From: Rong Zhang @ 2026-03-26 19:19 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, Rong Zhang
In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe>
WITRN produces a series of devices to monitor power characteristics of
USB connections and display those on a on-device display. Most of them
contain an additional port which exposes the measurements via USB HID.
Add a barebone HID driver to collect these measurements. The monitoring
support can be implemented later.
Developed and tested with WITRN K2 USB-C tester.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/witrn.rst | 23 ++++
MAINTAINERS | 7 ++
drivers/hwmon/Kconfig | 10 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/witrn.c | 268 ++++++++++++++++++++++++++++++++++++++++++
6 files changed, 310 insertions(+)
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index b2ca8513cfcd..f1f2b599c76b 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -276,6 +276,7 @@ Hardware Monitoring Kernel Drivers
w83795
w83l785ts
w83l786ng
+ witrn
wm831x
wm8350
xgene-hwmon
diff --git a/Documentation/hwmon/witrn.rst b/Documentation/hwmon/witrn.rst
new file mode 100644
index 000000000000..e64c527928d0
--- /dev/null
+++ b/Documentation/hwmon/witrn.rst
@@ -0,0 +1,23 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Kernel driver witrn
+====================
+
+Supported chips:
+
+ * WITRN K2
+
+ Prefix: 'witrn'
+
+ Addresses scanned: -
+
+Author:
+
+ - Rong Zhang <i@rong.moe>
+
+Description
+-----------
+
+This driver implements support for the WITRN USB tester family.
+
+The device communicates with the custom protocol over USB HID.
diff --git a/MAINTAINERS b/MAINTAINERS
index 0481aca2286c..18a1077d38e7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28444,6 +28444,13 @@ M: Miloslav Trmac <mitr@volny.cz>
S: Maintained
F: drivers/input/misc/wistron_btns.c
+WITRN USB TESTER HARDWARE MONITOR DRIVER
+M: Rong Zhang <i@rong.moe>
+L: linux-hwmon@vger.kernel.org
+S: Maintained
+F: Documentation/hwmon/witrn.rst
+F: drivers/hwmon/witrn.c
+
WMI BINARY MOF DRIVER
M: Armin Wolf <W_Armin@gmx.de>
R: Thomas Weißschuh <linux@weissschuh.net>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 7ad909033e79..746184608f81 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2629,6 +2629,16 @@ config SENSORS_W83627EHF
This driver can also be built as a module. If so, the module
will be called w83627ehf.
+config SENSORS_WITRN
+ tristate "WITRN USB tester"
+ depends on USB_HID
+ help
+ If you say yes here you get support for WITRN USB charging
+ testers.
+
+ This driver can also be built as a module. If so, the module
+ will be called witrn.
+
config SENSORS_WM831X
tristate "WM831x PMICs"
depends on MFD_WM831X
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 6dba69f712be..f87eb1710974 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -243,6 +243,7 @@ obj-$(CONFIG_SENSORS_VT8231) += vt8231.o
obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o
obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
+obj-$(CONFIG_SENSORS_WITRN) += witrn.o
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
obj-$(CONFIG_SENSORS_XGENE) += xgene-hwmon.o
diff --git a/drivers/hwmon/witrn.c b/drivers/hwmon/witrn.c
new file mode 100644
index 000000000000..e8713da6de5a
--- /dev/null
+++ b/drivers/hwmon/witrn.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * witrn - Driver for WITRN USB charging testers
+ *
+ * Copyright (C) 2026 Rong Zhang <i@rong.moe>
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <linux/workqueue.h>
+
+#define DRIVER_NAME "witrn"
+#define WITRN_EP_CMD_OUT 0x01
+#define WITRN_EP_DATA_IN 0x81
+
+#define WITRN_REPORT_SZ 64
+
+/* flags */
+#define WITRN_HID_OPENED 0
+
+/*
+ * The device sends reports every 10ms (100Hz!) once it's opened, which is
+ * really annoying and produces a lot of irq noise.
+ *
+ * Unfortunately, the device doesn't provide any command to start/stop reporting
+ * on demand -- it simply spams reports blindly. The only way to stop reporting
+ * is to close the HID device (i.e., to stop IN URB (re)submission).
+ *
+ * Let's close the HID device if the device has not been accessed for a while.
+ */
+#define PAUSE_TIMEOUT secs_to_jiffies(8)
+#define UP_TO_DATE_TIMEOUT msecs_to_jiffies(100)
+
+enum witrn_report_type {
+ WITRN_PD = 0xfe,
+ WITRN_SENSOR = 0xff,
+};
+
+struct witrn_sensor {
+ __le16 record_threshold; /* mA */
+ __le32 record_charge; /* Ah (float) */
+ __le32 record_energy; /* Wh (float) */
+ __le32 record_time; /* s */
+ __le32 uptime; /* s */
+ __le32 vdp; /* V (float) */
+ __le32 vdm; /* V (float) */
+ u8 __unknown[4];
+ __le32 temp_ntc; /* Celsius (float) */
+ __le32 vbus; /* V (float) */
+ __le32 ibus; /* A (float) */
+ u8 record_group; /* 0: group 1 on device, ... */
+ u8 vcc1; /* dV */
+ u8 vcc2; /* dV */
+} __packed;
+
+struct witrn_report {
+ u8 report_type;
+ u8 __unknown_0[11];
+
+ struct witrn_sensor sensor;
+
+ u8 __unknown_1[7];
+} __packed;
+static_assert(sizeof(struct witrn_report) == WITRN_REPORT_SZ);
+
+struct witrn_priv {
+ struct hid_device *hdev;
+
+ struct work_struct pause_work;
+
+ unsigned long flags;
+
+ spinlock_t lock; /* Protects members below */
+
+ struct completion completion;
+ unsigned long last_update; /* jiffies */
+ unsigned long last_access; /* jiffies */
+
+ struct witrn_sensor sensor;
+};
+
+static inline bool sensor_is_outdated(struct witrn_priv *priv)
+{
+ return time_after(jiffies, priv->last_update + UP_TO_DATE_TIMEOUT);
+}
+
+static inline bool hwmon_is_inactive(struct witrn_priv *priv)
+{
+ return time_after(jiffies, priv->last_access + PAUSE_TIMEOUT);
+}
+
+/* ======== HID ======== */
+
+static int witrn_open_hid(struct witrn_priv *priv)
+{
+ int ret;
+
+ if (test_and_set_bit(WITRN_HID_OPENED, &priv->flags))
+ return 0; /* Already opened */
+
+ hid_dbg(priv->hdev, "opening hid hw\n");
+
+ ret = hid_hw_open(priv->hdev);
+ if (ret) {
+ hid_err(priv->hdev, "hid hw open failed with %d\n", ret);
+ clear_bit(WITRN_HID_OPENED, &priv->flags);
+ }
+
+ return ret;
+}
+
+static void witrn_close_hid(struct witrn_priv *priv)
+{
+ if (!test_and_clear_bit(WITRN_HID_OPENED, &priv->flags))
+ return; /* Already closed */
+
+ hid_dbg(priv->hdev, "closing hid hw\n");
+
+ hid_hw_close(priv->hdev);
+}
+
+static void witrn_pause_hid(struct work_struct *work)
+{
+ struct witrn_priv *priv = container_of(work, struct witrn_priv, pause_work);
+
+ scoped_guard(spinlock, &priv->lock) {
+ /* Double check. Condition may change after being scheduled. */
+ if (!hwmon_is_inactive(priv))
+ return;
+ }
+
+ witrn_close_hid(priv);
+}
+
+static int witrn_raw_event(struct hid_device *hdev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct witrn_priv *priv = hid_get_drvdata(hdev);
+ const struct witrn_report *wreport;
+ bool do_pause = false;
+
+ /* HIDRAW has opened the device while we are pausing. */
+ if (!test_bit(WITRN_HID_OPENED, &priv->flags))
+ return 0;
+
+ if (size < WITRN_REPORT_SZ) {
+ hid_dbg(hdev, "report size mismatch: %d < %d\n", size, WITRN_REPORT_SZ);
+ return 0;
+ }
+
+ wreport = (const struct witrn_report *)data;
+ if (wreport->report_type != WITRN_SENSOR) {
+ hid_dbg(hdev, "report ignored with type 0x%02x", wreport->report_type);
+ return 0;
+ }
+
+ scoped_guard(spinlock, &priv->lock) {
+ priv->last_update = jiffies;
+ do_pause = hwmon_is_inactive(priv);
+
+ memcpy(&priv->sensor, &wreport->sensor, sizeof(wreport->sensor));
+ complete(&priv->completion);
+ }
+
+ if (do_pause)
+ schedule_work(&priv->pause_work);
+
+ return 0;
+}
+
+static int witrn_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ struct device *parent = &hdev->dev;
+ struct witrn_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(parent, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->hdev = hdev;
+ hid_set_drvdata(hdev, priv);
+
+ ret = hid_parse(hdev);
+ if (ret) {
+ hid_err(hdev, "hid parse failed with %d\n", ret);
+ return ret;
+ }
+
+ /* Enable HIDRAW so existing user-space tools can continue to work. */
+ ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ if (ret) {
+ hid_err(hdev, "hid hw start failed with %d\n", ret);
+ return ret;
+ }
+
+ spin_lock_init(&priv->lock);
+ init_completion(&priv->completion);
+
+ INIT_WORK(&priv->pause_work, witrn_pause_hid);
+
+ priv->last_access = jiffies;
+ priv->last_update = priv->last_access - UP_TO_DATE_TIMEOUT - 1;
+ clear_bit(WITRN_HID_OPENED, &priv->flags);
+
+ ret = witrn_open_hid(priv);
+ if (ret) {
+ hid_hw_stop(hdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void witrn_remove(struct hid_device *hdev)
+{
+ struct witrn_priv *priv = hid_get_drvdata(hdev);
+
+ witrn_close_hid(priv);
+
+ /* Cancel it after closing HID so that it won't be rescheduled. */
+ cancel_work_sync(&priv->pause_work);
+
+ hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id witrn_id_table[] = {
+ { HID_USB_DEVICE(0x0716, 0x5060) }, /* WITRN K2 USB-C tester */
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, witrn_id_table);
+
+static struct hid_driver witrn_driver = {
+ .name = DRIVER_NAME,
+ .id_table = witrn_id_table,
+ .probe = witrn_probe,
+ .remove = witrn_remove,
+ .raw_event = witrn_raw_event,
+};
+
+static int __init witrn_init(void)
+{
+ return hid_register_driver(&witrn_driver);
+}
+
+static void __exit witrn_exit(void)
+{
+ hid_unregister_driver(&witrn_driver);
+}
+
+/* When compiled into the kernel, initialize after the HID bus */
+late_initcall(witrn_init);
+module_exit(witrn_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
+MODULE_DESCRIPTION("WITRN USB tester driver");
--
2.53.0
^ permalink raw reply related
* [PATCH 2/4] hwmon: New helper module for floating-point to integer conversions
From: Rong Zhang @ 2026-03-26 19:19 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, Rong Zhang
In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe>
Some devices report sensor values in IEEE-754 float (binary32) format.
Their drivers must perform floating-point number to integer conversions
to provide hwmon channels. Meanwhile, some of these devices also report
accumulative float values, and simple division or multiplication turns
them into useful hwmon channel.
Add a new helper module called "hwmon-fp" with float-to-s64/long
conversion, multification and division methods, so that these devices
can be supported painlessly.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/hwmon/Kconfig | 3 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/hwmon-fp.c | 262 +++++++++++++++++++++++++++++++++++++++++++++++
drivers/hwmon/hwmon-fp.h | 212 ++++++++++++++++++++++++++++++++++++++
4 files changed, 478 insertions(+)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 328867242cb3..7ad909033e79 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -25,6 +25,9 @@ menuconfig HWMON
if HWMON
+config HWMON_FP
+ tristate
+
config HWMON_VID
tristate
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 5833c807c688..6dba69f712be 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -4,6 +4,7 @@
#
obj-$(CONFIG_HWMON) += hwmon.o
+obj-$(CONFIG_HWMON_FP) += hwmon-fp.o
obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
# ACPI drivers
diff --git a/drivers/hwmon/hwmon-fp.c b/drivers/hwmon/hwmon-fp.c
new file mode 100644
index 000000000000..2ad636129a0c
--- /dev/null
+++ b/drivers/hwmon/hwmon-fp.c
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Floating-point number to integer conversions
+ *
+ * Currently, only float (binary32) is supported.
+ *
+ * Copyright (c) 2026 Rong Zhang <i@rong.moe>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+
+#include "hwmon-fp.h"
+
+#define FLOAT_SIGN_MASK BIT(31)
+#define FLOAT_EXPONENT_MASK GENMASK(30, 23)
+#define FLOAT_MANTISSA_MASK GENMASK(22, 0)
+#define FLOAT_EXPONENT_OFFSET 127
+#define FLOAT_FRACTION_Q 23
+#define FLOAT_IMPLICIT_BIT BIT(23)
+
+#define HWMON_FP_SCALE_MAGNIFY_SHIFT_L 6
+
+struct float_struct {
+ u32 significand;
+ s16 shift_r; /* = Q - exponent */
+ bool sign;
+ bool inf; /* See below. */
+};
+
+/*
+ * The sign of a floating-point number carries significant information,
+ * return a saturated value for infinity so its sign is retained.
+ */
+static inline int hwmon_fp_infinity_to_s64(bool sign, s64 *val)
+{
+ *val = sign ? S64_MIN : S64_MAX;
+ return 0;
+}
+
+static int hwmon_fp_parse_float(u32 flt, struct float_struct *fs)
+{
+ u32 mantissa = FIELD_GET(FLOAT_MANTISSA_MASK, flt);
+ u8 exponent = FIELD_GET(FLOAT_EXPONENT_MASK, flt);
+ bool sign = FIELD_GET(FLOAT_SIGN_MASK, flt);
+
+ if (unlikely(exponent == FLOAT_EXPONENT_MASK >> FLOAT_FRACTION_Q)) {
+ if (mantissa != 0) /* NaN */
+ return -EINVAL;
+
+ /* Infinity */
+ fs->significand = HWMON_FP_FLOAT_SIGNIFICAND_MAX; /* Distinguish from fs(zero). */
+ fs->shift_r = 0;
+ fs->sign = sign;
+ fs->inf = true;
+
+ return 0;
+ }
+
+ fs->sign = sign;
+ fs->inf = false;
+
+ if (likely(exponent != 0)) {
+ /* Normal */
+ fs->significand = (FLOAT_IMPLICIT_BIT | mantissa);
+ fs->shift_r = FLOAT_FRACTION_Q - (exponent - FLOAT_EXPONENT_OFFSET);
+ } else if (unlikely(mantissa != 0)) { /* exponent == 0 && mantissa != 0 */
+ /* Subnormal */
+ fs->significand = mantissa;
+ fs->shift_r = FLOAT_FRACTION_Q - (1 - FLOAT_EXPONENT_OFFSET);
+ } else { /* exponent == 0 && mantissa == 0 */
+ /* Zero */
+ fs->significand = 0; /* Only fs(zero) has fs->significand == 0. */
+ fs->shift_r = 0;
+ }
+
+ return 0;
+}
+
+static int hwmon_fp_raw_to_s64(u64 significand, int shift_r, bool sign, s64 *val)
+{
+ u64 temp;
+
+ if (unlikely(shift_r >= 64) || significand == 0) {
+ *val = 0;
+ return 0;
+ }
+
+ if (shift_r < 0) {
+ /*
+ * Left shift:
+ *
+ * (significand * 2^-Q) * 2^exponent
+ * = significand * 2^(exponent - Q)
+ * = significand * 2^-shift_r
+ * = significand << -shift_r
+ */
+ shift_r = -shift_r;
+ temp = significand << shift_r;
+
+ if (unlikely(temp >> shift_r != significand))
+ return hwmon_fp_infinity_to_s64(sign, val);
+ } else if (shift_r == 0) {
+ temp = significand;
+ } else { /* shift_r > 0 */
+ /*
+ * Right shift:
+ *
+ * (significand * 2^-Q) * 2^exponent
+ * = significand / 2^(Q - exponent)
+ * = significand / 2^shift_r
+ * = significand >> shift_r
+ */
+ temp = significand >> shift_r;
+
+ /* Round to nearest. */
+ temp += !!(significand & BIT_U64(shift_r - 1));
+ }
+
+ if (unlikely((s64)temp < 0))
+ return hwmon_fp_infinity_to_s64(sign, val);
+
+ *val = (sign ? -1 : 1) * (s64)temp;
+ return 0;
+}
+
+static int __hwmon_fp_float_to_s64_unsafe(const struct float_struct *fs, u32 scale, s64 *val)
+{
+ if (unlikely(fs->inf))
+ return hwmon_fp_infinity_to_s64(fs->sign, val);
+
+ return hwmon_fp_raw_to_s64((u64)scale * (u64)fs->significand,
+ fs->shift_r, fs->sign, val);
+}
+
+int hwmon_fp_float_to_s64_unsafe(u32 flt, u32 scale, s64 *val)
+{
+ struct float_struct fs;
+ int ret;
+
+ ret = hwmon_fp_parse_float(flt, &fs);
+ if (ret)
+ return ret;
+
+ return __hwmon_fp_float_to_s64_unsafe(&fs, scale, val);
+}
+EXPORT_SYMBOL_GPL(hwmon_fp_float_to_s64_unsafe);
+
+static int __hwmon_fp_mul_to_s64_unsafe(const struct float_struct *fs1,
+ const struct float_struct *fs2,
+ u32 scale_ntz, ulong scale_ctz, s64 *val)
+{
+ bool sign = fs1->sign ^ fs2->sign;
+ u64 scaled_significand;
+ int shift_r;
+
+ if (unlikely((fs1->inf && fs2->significand == 0) || (fs1->significand == 0 && fs2->inf)))
+ return -EINVAL;
+
+ if (unlikely(fs1->inf || fs2->inf))
+ return hwmon_fp_infinity_to_s64(sign, val);
+
+ if (fs1->significand == 0 || fs2->significand == 0) {
+ *val = 0;
+ return 0;
+ }
+
+ /*
+ * scale_ntz * 2^scale_ctz * significand1 * 2^-shift_r1 * significand2 * 2^-shift_r2
+ * = scale_ntz * significand1 * significand2 * 2^-(shift_r1 + shift_r2 - scale_ctz)
+ * = (scale_ntz * significand1 * significand2) >> (shift_r1 + shift_r2 - scale_ctz)
+ */
+ scaled_significand = (u64)scale_ntz * (u64)fs1->significand * (u64)fs2->significand;
+ shift_r = fs1->shift_r + fs2->shift_r - scale_ctz;
+
+ return hwmon_fp_raw_to_s64(scaled_significand, shift_r, sign, val);
+}
+
+int hwmon_fp_mul_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale, ulong scale_ctz, s64 *val)
+{
+ struct float_struct fs1, fs2;
+ int ret;
+
+ ret = hwmon_fp_parse_float(flt1, &fs1) || hwmon_fp_parse_float(flt2, &fs2);
+ if (ret)
+ return ret;
+
+ return __hwmon_fp_mul_to_s64_unsafe(&fs1, &fs2, scale, scale_ctz, val);
+}
+EXPORT_SYMBOL_GPL(hwmon_fp_mul_to_s64_unsafe);
+
+static int __hwmon_fp_div_to_s64_unsafe(const struct float_struct *fs1,
+ const struct float_struct *fs2,
+ u32 scale, bool div0_ok, s64 *val)
+{
+ bool sign = fs1->sign ^ fs2->sign;
+ u64 scaled_significand;
+ int shift_r;
+
+ if (unlikely(fs1->inf && fs2->inf))
+ return -EINVAL;
+
+ if (fs2->significand == 0) {
+ if (div0_ok) {
+ *val = 0;
+ return 0;
+ }
+ return -EINVAL;
+ }
+
+ if (unlikely(fs1->inf))
+ return hwmon_fp_infinity_to_s64(sign, val);
+
+ if (unlikely(fs2->inf) || fs1->significand == 0) {
+ *val = 0;
+ return 0;
+ }
+
+ /*
+ * Make the dividend as large as possible to improve accuracy, otherwise
+ * the divide-and-right-shift procedure may produce an inaccurate result.
+ *
+ * scale * (significand1 * 2^-shift_r1) / (significand2 * 2^-shift_r2)
+ * = scale * 2^6 * 2^-6 * (significand1 * 2^-shift_r1) / (significand2 * 2^-shift_r2)
+ * = (((scale * 2^6) * significand1) / significand2) * 2^-(shift_r1 - shift_r2 + 6)
+ * = (((scale << 6) * significand1) / significand2) >> (shift_r1 - shift_r2 + 6)
+ *
+ * This will never overflow: (2^32 - 1) * 2^6 * (2^24 - 1) < (2^62 - 1).
+ */
+ scaled_significand = ((u64)scale << HWMON_FP_SCALE_MAGNIFY_SHIFT_L) * (u64)fs1->significand;
+ scaled_significand =
+ (scaled_significand + (u64)fs2->significand / 2) / (u64)fs2->significand;
+
+ shift_r = fs1->shift_r - fs2->shift_r + HWMON_FP_SCALE_MAGNIFY_SHIFT_L;
+
+ return hwmon_fp_raw_to_s64(scaled_significand, shift_r, sign, val);
+}
+
+int hwmon_fp_div_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale, bool div0_ok, s64 *val)
+{
+ struct float_struct fs1, fs2;
+ int ret;
+
+ ret = hwmon_fp_parse_float(flt1, &fs1) || hwmon_fp_parse_float(flt2, &fs2);
+ if (ret)
+ return ret;
+
+ return __hwmon_fp_div_to_s64_unsafe(&fs1, &fs2, scale, div0_ok, val);
+}
+EXPORT_SYMBOL_GPL(hwmon_fp_div_to_s64_unsafe);
+
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
+MODULE_DESCRIPTION("hwmon floating-point number to integer conversions");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/hwmon-fp.h b/drivers/hwmon/hwmon-fp.h
new file mode 100644
index 000000000000..55d6a5021535
--- /dev/null
+++ b/drivers/hwmon/hwmon-fp.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Floating-point number to integer conversions
+ *
+ * Copyright (c) 2026 Rong Zhang <i@rong.moe>
+ */
+
+#ifndef __HWMON_FP_H__
+#define __HWMON_FP_H__
+
+#include <asm/bitsperlong.h>
+#include <linux/bug.h>
+#include <linux/build_bug.h>
+#include <linux/compiler.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+int hwmon_fp_float_to_s64_unsafe(u32 flt, u32 scale, s64 *val);
+int hwmon_fp_mul_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale_ntz, ulong scale_ctz, s64 *val);
+int hwmon_fp_div_to_s64_unsafe(u32 flt1, u32 flt2, u32 scale, bool div0_ok, s64 *val);
+
+#define HWMON_FP_FLOAT_SIGNIFICAND_BITS 24
+#define HWMON_FP_FLOAT_SIGNIFICAND_MAX BIT(HWMON_FP_FLOAT_SIGNIFICAND_BITS)
+
+#define HWMON_FP_MUL_SCALE_MAX BIT(64 - HWMON_FP_FLOAT_SIGNIFICAND_BITS * 2)
+
+static inline int __hwmon_fp_check_scale(u32 scale)
+{
+ return WARN_ON(scale <= 0) ? -EINVAL : 0;
+}
+
+#define hwmon_fp_check_scale(scale) \
+ __builtin_choose_expr(__is_constexpr(scale), \
+ BUILD_BUG_ON_ZERO((scale) <= 0), \
+ __hwmon_fp_check_scale(scale))
+
+#define HWMON_FP_SCALE_IN MILLI /* millivolt (mV) */
+#define HWMON_FP_SCALE_TEMP MILLI /* millidegree Celsius (m°C) */
+#define HWMON_FP_SCALE_CURR MILLI /* milliampere (mA) */
+#define HWMON_FP_SCALE_POWER MICRO /* microWatt (uW) */
+#define HWMON_FP_SCALE_ENERGY MICRO /* microJoule (uJ) */
+
+/**
+ * hwmon_fp_float_to_s64() - Convert a float (binary32) number into a signed
+ * 64-bit integer.
+ * @flt: Float (binary32) number.
+ * @scale: Scale factor.
+ * @val: Pointer to store the converted value.
+ *
+ * Special case:
+ * inf -> S64_MAX or S64_MIN
+ * NaN -> -EINVAL;
+ * (overflow) -> S64_MAX or S64_MIN
+ * (underflow) -> 0
+ *
+ * Return: 0 on success, or an error code.
+ */
+#define hwmon_fp_float_to_s64(flt, scale, val) \
+ (hwmon_fp_check_scale(scale) || \
+ hwmon_fp_float_to_s64_unsafe(flt, scale, val))
+
+/*
+ * Handling multification is very tricky, as large scale factors must not lead
+ * to overflow. Fortunately, cutting off all trailing zeros and restoring them
+ * while right shifting is enough reduce the scale factor used in
+ * multiplication to a small enough value.
+ */
+static inline int __hwmon_fp_mul_to_s64(u32 flt1, u32 flt2, u32 scale, s64 *val)
+{
+ ulong scale_ctz;
+
+ if (WARN_ON(scale <= 0))
+ return -EINVAL;
+
+ scale_ctz = __ffs(scale);
+ scale >>= scale_ctz;
+
+ if (WARN_ON(scale >= HWMON_FP_MUL_SCALE_MAX))
+ return -EINVAL;
+
+ return hwmon_fp_mul_to_s64_unsafe(flt1, flt2, scale, scale_ctz, val);
+}
+
+#define __hwmon_fp_mul_to_s64_const(flt1, flt2, scale, val) \
+({ \
+ u32 _scale_ntz = (scale); \
+ ulong _scale_ctz; \
+ \
+ BUILD_BUG_ON(_scale_ntz <= 0); \
+ \
+ _scale_ctz = __builtin_ctzl(_scale_ntz); \
+ _scale_ntz >>= _scale_ctz; \
+ \
+ BUILD_BUG_ON(_scale_ntz >= HWMON_FP_MUL_SCALE_MAX); \
+ \
+ hwmon_fp_mul_to_s64_unsafe(flt1, flt2, _scale_ntz, _scale_ctz, val); \
+})
+
+/**
+ * hwmon_fp_mul_to_s64() - Multiply two float (binary32) numbers and convert the
+ * product into a signed 64-bit integer.
+ * @flt1: Multiplicand stored in float (binary32) format.
+ * @flt2: Multiplier stored in float (binary32) format.
+ * @scale: Scale factor.
+ * @val: Pointer to store the product.
+ *
+ * Calculate @scale * @flt1 * @flt2.
+ *
+ * Special case:
+ * 0 * inf -> -EINVAL
+ * x * inf -> S64_MAX or S64_MIN
+ * x * 0 -> 0
+ *
+ * Return: 0 on success, or an error code.
+ */
+#define hwmon_fp_mul_to_s64(flt1, flt2, scale, val) \
+ __builtin_choose_expr(__is_constexpr(scale), \
+ __hwmon_fp_mul_to_s64_const(flt1, flt2, scale, val), \
+ __hwmon_fp_mul_to_s64(flt1, flt2, scale, val))
+
+/**
+ * hwmon_fp_div_to_s64() - Divide two float (binary32) numbers and convert the
+ * quotient into a signed 64-bit integer.
+ * @flt1: Dividend stored in float (binary32) format.
+ * @flt2: Divisor stored in float (binary32) format.
+ * @scale: Scale factor.
+ * @div0_ok: If true, return 0 when @flt2 is 0. Otherwise, -EINVAL is returned.
+ * @val: Pointer to store the quotient.
+ *
+ * Calculate @scale * (@flt1 / @flt2).
+ *
+ * Special case:
+ * inf / inf -> -EINVAL
+ * inf / x -> S64_MAX or S64_MIN
+ * x / 0 -> See div0_ok
+ * x / inf -> 0
+ * 0 / x -> 0
+ *
+ * Return: 0 on success, or an error code.
+ */
+#define hwmon_fp_div_to_s64(flt1, flt2, scale, div0_ok, val) \
+ (hwmon_fp_check_scale(scale) || \
+ hwmon_fp_div_to_s64_unsafe(flt1, flt2, scale, div0_ok, val))
+
+#if BITS_PER_LONG == 64
+
+static_assert(sizeof(long) == sizeof(s64));
+
+static inline long hwmon_fp_s64_to_long(s64 val64)
+{
+ return val64;
+}
+
+# define hwmon_fp_float_to_long(flt, scale, val) \
+ hwmon_fp_float_to_s64(flt, scale, (s64 *)val)
+# define hwmon_fp_div_to_long(flt1, flt2, scale, div0_ok, val) \
+ hwmon_fp_div_to_s64(flt1, flt2, scale, div0_ok, (s64 *)val)
+# define hwmon_fp_mul_to_long(flt1, flt2, scale, val) \
+ hwmon_fp_mul_to_s64(flt1, flt2, scale, (s64 *)val)
+
+#else /* BITS_PER_LONG == 64 */
+
+static inline long hwmon_fp_s64_to_long(s64 val64)
+{
+ if (unlikely(val64 > LONG_MAX))
+ return LONG_MAX;
+ else if (unlikely(val64 < LONG_MIN))
+ return LONG_MIN;
+ else
+ return val64;
+}
+
+# define hwmon_fp_float_to_long(flt, scale, val) \
+({ \
+ s64 _val64; \
+ int _ret; \
+ \
+ _ret = hwmon_fp_float_to_s64(flt, scale, &_val64); \
+ if (!_ret) \
+ *(val) = hwmon_fp_s64_to_long(_val64); \
+ \
+ _ret; \
+})
+
+# define hwmon_fp_div_to_long(flt1, flt2, scale, div0_ok, val) \
+({ \
+ s64 _val64; \
+ int _ret; \
+ \
+ _ret = hwmon_fp_div_to_s64(flt1, flt2, scale, div0_ok, &_val64); \
+ if (!_ret) \
+ *(val) = hwmon_fp_s64_to_long(_val64); \
+ \
+ _ret; \
+})
+
+# define hwmon_fp_mul_to_long(flt1, flt2, scale, val) \
+({ \
+ s64 _val64; \
+ int _ret; \
+ \
+ _ret = hwmon_fp_mul_to_s64(flt1, flt2, scale, &_val64); \
+ if (!_ret) \
+ *(val) = hwmon_fp_s64_to_long(_val64); \
+ \
+ _ret; \
+})
+
+#endif /* BITS_PER_LONG == 64 */
+
+#endif /* __HWMON_FP_H__ */
--
2.53.0
^ permalink raw reply related
* [PATCH 1/4] hwmon: Add label support for 64-bit energy attributes
From: Rong Zhang @ 2026-03-26 19:19 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, Rong Zhang
In-Reply-To: <20260327-b4-hwmon-witrn-v1-0-8d2f1896c045@rong.moe>
Since commit 0bcd01f757bc ("hwmon: Introduce 64-bit energy attribute
support"), devices can report 64-bit energy values by selecting the
sensor type "energy64". However, such sensors can't report their labels
since is_string_attr() was not updated to match it.
Add label support for 64-bit energy attributes by updating
is_string_attr() to match hwmon_energy64 in addition to hwmon_energy.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/hwmon/hwmon.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c
index 9695dca62a7e..6812d1fd7c28 100644
--- a/drivers/hwmon/hwmon.c
+++ b/drivers/hwmon/hwmon.c
@@ -505,6 +505,7 @@ static bool is_string_attr(enum hwmon_sensor_types type, u32 attr)
(type == hwmon_curr && attr == hwmon_curr_label) ||
(type == hwmon_power && attr == hwmon_power_label) ||
(type == hwmon_energy && attr == hwmon_energy_label) ||
+ (type == hwmon_energy64 && attr == hwmon_energy_label) ||
(type == hwmon_humidity && attr == hwmon_humidity_label) ||
(type == hwmon_fan && attr == hwmon_fan_label);
}
--
2.53.0
^ permalink raw reply related
* [PATCH 0/4] hwmon: Add WITRN USB tester driver
From: Rong Zhang @ 2026-03-26 19:19 UTC (permalink / raw)
To: Guenter Roeck, Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, linux-kernel, linux-doc, Rong Zhang
WITRN produces a series of devices to monitor power characteristics of
USB connections and display those on a on-device display. Most of them
contain an additional port which exposes the measurements via USB HID.
These devices report sensor values in IEEE-754 float (binary32) format.
The driver must perform floating-point number to integer conversions to
provide hwmon channels. Meanwhile, they also report accumulative float
values, and simple division or multiplication turns them into useful
hwmon channels.
Patch 1 adds label support for 64-bit energy attributes, as the driver
needs it.
Patch 2 adds a helper module for floating-point to integer conversions,
so that the conversion, multification and division methods can be used
in this driver as well as other drivers (I am also working on another
USB tester driver that needs it).
Patch 3 adds a barebone HID driver for WITRN K2.
Patch 4 adds hwmon channels and attributes to the driver.
Signed-off-by: Rong Zhang <i@rong.moe>
---
Rong Zhang (4):
hwmon: Add label support for 64-bit energy attributes
hwmon: New helper module for floating-point to integer conversions
hwmon: Add barebone HID driver for WITRN
hwmon: (witrn) Add monitoring support
Documentation/hwmon/index.rst | 1 +
Documentation/hwmon/witrn.rst | 53 ++++
MAINTAINERS | 7 +
drivers/hwmon/Kconfig | 14 +
drivers/hwmon/Makefile | 2 +
drivers/hwmon/hwmon-fp.c | 262 ++++++++++++++++
drivers/hwmon/hwmon-fp.h | 212 +++++++++++++
drivers/hwmon/hwmon.c | 1 +
drivers/hwmon/witrn.c | 691 ++++++++++++++++++++++++++++++++++++++++++
9 files changed, 1243 insertions(+)
---
base-commit: 0138af2472dfdef0d56fc4697416eaa0ff2589bd
change-id: 20260327-b4-hwmon-witrn-a629b9040250
Thanks,
Rong
^ permalink raw reply
* Re: [PATCH v2] doc: Add CPU Isolation documentation
From: Waiman Long @ 2026-03-26 19:17 UTC (permalink / raw)
To: Frederic Weisbecker, LKML
Cc: Anna-Maria Behnsen, Gabriele Monaco, Ingo Molnar, Jonathan Corbet,
Marcelo Tosatti, Marco Crivellari, Michal Hocko,
Paul E . McKenney, Peter Zijlstra, Phil Auld, Steven Rostedt,
Thomas Gleixner, Valentin Schneider, Vlastimil Babka, linux-doc,
Sebastian Andrzej Siewior, Bagas Sanjaya
In-Reply-To: <20260326140055.41555-1-frederic@kernel.org>
On 3/26/26 10:00 AM, Frederic Weisbecker wrote:
> nohz_full was introduced in v3.10 in 2013, which means this
> documentation is overdue for 13 years.
>
> Fortunately Paul wrote a part of the needed documentation a while ago,
> especially concerning nohz_full in Documentation/timers/no_hz.rst and
> also about per-CPU kthreads in
> Documentation/admin-guide/kernel-per-CPU-kthreads.rst
>
> Introduce a new page that gives an overview of CPU isolation in general.
>
> Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
> ---
> v2:
> - Fix links and code blocks (Bagas and Sebastian)
> - Isolation is not only about userspace, rephrase accordingly (Valentin)
> - Paste BIOS issues suggestion from Valentin
> - Include the whole rtla suite (Valentin)
> - Rephrase a few details (Waiman)
> - Talk about RCU induced overhead rather than slower RCU (Sebastian)
>
> Documentation/admin-guide/cpu-isolation.rst | 357 ++++++++++++++++++++
> Documentation/admin-guide/index.rst | 1 +
> 2 files changed, 358 insertions(+)
> create mode 100644 Documentation/admin-guide/cpu-isolation.rst
>
> diff --git a/Documentation/admin-guide/cpu-isolation.rst b/Documentation/admin-guide/cpu-isolation.rst
> new file mode 100644
> index 000000000000..886dec79b056
> --- /dev/null
> +++ b/Documentation/admin-guide/cpu-isolation.rst
> @@ -0,0 +1,357 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +=============
> +CPU Isolation
> +=============
> +
> +Introduction
> +============
> +
> +"CPU Isolation" means leaving a CPU exclusive to a given workload
> +without any undesired code interference from the kernel.
> +
> +Those interferences, commonly pointed out as "noise", can be triggered
> +by asynchronous events (interrupts, timers, scheduler preemption by
> +workqueues and kthreads, ...) or synchronous events (syscalls and page
> +faults).
> +
> +Such noise usually goes unnoticed. After all synchronous events are a
> +component of the requested kernel service. And asynchronous events are
> +either sufficiently well distributed by the scheduler when executed
> +as tasks or reasonably fast when executed as interrupt. The timer
> +interrupt can even execute 1024 times per seconds without a significant
> +and measurable impact most of the time.
> +
> +However some rare and extreme workloads can be quite sensitive to
> +those kinds of noise. This is the case, for example, with high
> +bandwidth network processing that can't afford losing a single packet
> +or very low latency network processing. Typically those usecases
> +involve DPDK, bypassing the kernel networking stack and performing
> +direct access to the networking device from userscace.
As also pointed by by Sashiko, there is a typo "userscace" ->
"userspace". There are also typos reported in
https://sashiko.dev/#/patchset/20260326140055.41555-1-frederic%40kernel.org
> +
> +In order to run a CPU without or with limited kernel noise, the
> +related housekeeping work needs to be either shutdown, migrated or
> +offloaded.
> +
> +Housekeeping
> +============
> +
> +In the CPU isolation terminology, housekeeping is the work, often
> +asynchronous, that the kernel needs to process in order to maintain
> +all its services. It matches the noises and disturbances enumerated
> +above except when at least one CPU is isolated. Then housekeeping may
> +make use of further coping mechanisms if CPU-tied work must be
> +offloaded.
> +
> +Housekeeping CPUs are the non-isolated CPUs where the kernel noise
> +is moved away from isolated CPUs.
> +
> +The isolation can be implemented in several ways depending on the
> +nature of the noise:
> +
> +- Unbound work, where "unbound" means not tied to any CPU, can be
> + simply migrated away from isolated CPUs to housekeeping CPUs.
> + This is the case of unbound workqueues, kthreads and timers.
> +
> +- Bound work, where "bound" means tied to a specific CPU, usually
> + can't be moved away as-is by nature. Either:
> +
> + - The work must switch to a locked implementation. Eg: This is
> + the case of RCU with CONFIG_RCU_NOCB_CPU.
> +
> + - The related feature must be shutdown and considered
> + incompatible with isolated CPUs. Eg: Lockup watchdog,
> + unreliable clocksources, etc...
> +
> + - An elaborated and heavyweight coping mechanism stands as a
> + replacement. Eg: the timer tick is shutdown on nohz_full but
"shutdown" should be 2 words as "shutdown" isn't a verb. Should we add
CPU after "nohz_full" to make it more clear?
> + with the constraint of running a single task on the CPU. A
> + significant cost penalty is added on kernel entry/exit and
> + a residual 1Hz scheduler tick is offloaded to housekeeping
> + CPUs.
> +
> +In any case, housekeeping work has to be handled, which is why there
> +must be at least one housekeeping CPU in the system, preferrably more
> +if the machine runs a lot of CPUs. For example one per node on NUMA
> +systems.
> +
> +Also CPU isolation often means a tradeoff between noise-free isolated
> +CPUs and added overhead on housekeeping CPUs, sometimes even on
> +isolated CPUs entering the kernel.
> +
> +Isolation features
> +==================
> +
> +Different levels of isolation can be configured in the kernel, each of
> +which having their own drawbacks and tradeoffs.
> +
> +Scheduler domain isolation
> +--------------------------
> +
> +This feature isolates a CPU from the scheduler topology. As a result,
> +the target isn't part of the load balancing. Tasks won't migrate
> +neither from nor to it unless affined explicitly.
> +
> +As a side effect the CPU is also isolated from unbound workqueues and
> +unbound kthreads.
> +
> +Requirements
> +~~~~~~~~~~~~
> +
> +- CONFIG_CPUSETS=y for the cpusets based interface
> +
> +Tradeoffs
> +~~~~~~~~~
> +
> +By nature, the system load is overall less distributed since some CPUs
> +are extracted from the global load balancing.
> +
> +Interface
> +~~~~~~~~~
> +
> +- Documentation/admin-guide/cgroup-v2.rst cpuset isolated partitions are recommended
> + because they are tunable at runtime.
> +
> +- The 'isolcpus=' kernel boot parameter with the 'domain' flag is a
> + less flexible alternative that doesn't allow for runtime
> + reconfiguration.
> +
> +IRQs isolation
> +--------------
> +
> +Isolate the IRQs whenever possible, so that they don't fire on the
> +target CPUs.
> +
> +Interface
> +~~~~~~~~~
> +
> +- The file /proc/irq/\*/smp_affinity as explained in detail in
> + Documentation/core-api/irq/irq-affinity.rst page.
> +
> +- The "irqaffinity=" kernel boot parameter for a default setting.
> +
> +- The "managed_irq" flag in the "isolcpus=" kernel boot parameter
> + tries a best effort affinity override for managed IRQs.
> +
> +Full Dynticks (aka nohz_full)
> +-----------------------------
> +
> +Full dynticks extends the dynticks idle mode, which stop the tick when
> +the CPU is idle, to CPUs running a single task in userspace. That is,
> +the timer tick is stopped if the environment allows it.
> +
> +Global timer callbacks are also isolated from the nohz_full CPUs.
> +
> +Requirements
> +~~~~~~~~~~~~
> +
> +- CONFIG_NO_HZ_FULL=y
> +
> +Constraints
> +~~~~~~~~~~~
> +
> +- The isolated CPUs must run a single task only. Multitask requires
> + the tick to maintain preemption. This is usually fine since the
> + workload usually can't stand the latency of random context switches.
> +
> +- No call to the kernel from isolated CPUs, at the risk of triggering
> + random noise.
> +
> +- No use of posix CPU timers on isolated CPUs.
> +
> +- Architecture must have a stable and reliable clocksource (no
> + unreliable TSC that requires the watchdog).
> +
> +
> +Tradeoffs
> +~~~~~~~~~
> +
> +In terms of cost, this is the most invasive isolation feature. It is
> +assumed to be used when the workload spends most of its time in
> +userspace and doesn't rely on the kernel except for preparatory
> +work because:
> +
> +- RCU adds more overhead due to the locked, offloaded and threaded
> + callbacks processing (the same that would be obtained with "rcu_nocb"
> + boot parameter).
It should be "rcu_nocbs".
> +
> +- Kernel entry/exit through syscalls, exceptions and IRQs are more
> + costly due to fully ordered RmW operations that maintain userspace
> + as RCU extended quiescent state. Also the CPU time is accounted on
> + kernel boundaries instead of periodically from the tick.
> +
> +- Housekeeping CPUs must run a 1Hz residual remote scheduler tick
> + on behalf of the isolated CPUs.
> +
> +Checklist
> +=========
> +
> +You have set up each of the above isolation features but you still
> +observe jitters that trash your workload? Make sure to check a few
> +elements before proceeding.
> +
> +Some of these checklist items are similar to those of real time
> +workloads:
> +
> +- Use mlock() to prevent your pages from being swapped away. Page
> + faults are usually not compatible with jitter sensitive workloads.
> +
> +- Avoid SMT to prevent your hardware thread from being "preempted"
> + by another one.
> +
> +- CPU frequency changes may induce subtle sorts of jitter in a
> + workload. Cpufreq should be used and tuned with caution.
> +
> +- Deep C-states may result in latency issues upon wake-up. If this
> + happens to be a problem, C-states can be limited via kernel boot
> + parameters such as processor.max_cstate or intel_idle.max_cstate.
> + More finegrained tunings are described in
> + Documentation/admin-guide/pm/cpuidle.rst page
> +
> +- Your system may be subject to firmware-originating interrupts - x86 has
> + System Management Interrupts (SMIs) for example. Check your system BIOS
> + to disable such interference, and with some luck your vendor will have
> + a BIOS tuning guidance for low-latency operations.
> +
> +
> +Full isolation example
> +======================
> +
> +In this example, the system has 8 CPUs and the 8th is to be fully
> +isolated. Since CPUs start from 0, the 8th CPU is CPU 7.
> +
> +Kernel parameters
> +-----------------
> +
> +Set the following kernel boot parameters to disable SMT and setup tick
> +and IRQ isolation:
> +
> +- Full dynticks: nohz_full=7
> +
> +- IRQs isolation: irqaffinity=0-6
> +
> +- Managed IRQs isolation: isolcpus=managed_irq,7
> +
> +- Prevent from SMT: nosmt
> +
> +The full command line is then:
> +
> + nohz_full=7 irqaffinity=0-6 isolcpus=managed_irq,7 nosmt
> +
> +CPUSET configuration (cgroup v2)
> +--------------------------------
> +
> +Assuming cgroup v2 is mounted to /sys/fs/cgroup, the following script
> +isolates CPU 7 from scheduler domains.
> +
> +::
> +
> + cd /sys/fs/cgroup
> + # Activate the cpuset subsystem
> + echo +cpuset > cgroup.subtree_control
> + # Create partition to be isolated
> + mkdir test
> + cd test
> + echo +cpuset > cgroup.subtree_control
> + # Isolate CPU 7
> + echo 7 > cpuset.cpus
> + echo "isolated" > cpuset.cpus.partition
> +
> +The userspace workload
> +----------------------
> +
> +Fake a pure userspace workload, the below program runs a dummy
> +userspace loop on the isolated CPU 7.
> +
> +::
> +
> + #include <stdio.h>
> + #include <fcntl.h>
> + #include <unistd.h>
> + #include <errno.h>
> + int main(void)
> + {
> + // Move the current task to the isolated cpuset (bind to CPU 7)
> + int fd = open("/sys/fs/cgroup/test/cgroup.procs", O_WRONLY);
> + if (fd < 0) {
> + perror("Can't open cpuset file...\n");
> + return 0;
> + }
> +
> + write(fd, "0\n", 2);
> + close(fd);
> +
> + // Run an endless dummy loop until the launcher kills us
> + while (1)
> + ;
> +
> + return 0;
> + }
> +
> +Build it and save for later step:
> +
> +::
> +
> + # gcc user_loop.c -o user_loop
> +
> +The launcher
> +------------
> +
> +The below launcher runs the above program for 10 seconds and traces
> +the noise resulting from preempting tasks and IRQs.
> +
> +::
> +
> + TRACING=/sys/kernel/tracing/
> + # Make sure tracing is off for now
> + echo 0 > $TRACING/tracing_on
> + # Flush previous traces
> + echo > $TRACING/trace
> + # Record disturbance from other tasks
> + echo 1 > $TRACING/events/sched/sched_switch/enable
> + # Record disturbance from interrupts
> + echo 1 > $TRACING/events/irq_vectors/enable
> + # Now we can start tracing
> + echo 1 > $TRACING/tracing_on
> + # Run the dummy user_loop for 10 seconds on CPU 7
> + ./user_loop &
> + USER_LOOP_PID=$!
> + sleep 10
> + kill $USER_LOOP_PID
> + # Disable tracing and save traces from CPU 7 in a file
> + echo 0 > $TRACING/tracing_on
> + cat $TRACING/per_cpu/cpu7/trace > trace.7
> +
> +If no specific problem arose, the output of trace.7 should look like
> +the following:
> +
> +::
> +
> + <idle>-0 [007] d..2. 1980.976624: sched_switch: prev_comm=swapper/7 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=user_loop next_pid=1553 next_prio=120
> + user_loop-1553 [007] d.h.. 1990.946593: reschedule_entry: vector=253
> + user_loop-1553 [007] d.h.. 1990.946593: reschedule_exit: vector=253
> +
> +That is, no specific noise triggered between the first trace and the
> +second during 10 seconds when user_loop was running.
> +
> +Debugging
> +=========
> +
> +Of course things are never so easy, especially on this matter.
> +Chances are that actual noise will be observed in the aforementioned
> +trace.7 file.
> +
> +The best way to investigate further is to enable finer grained
> +tracepoints such as those of subsystems producing asynchronous
> +events: workqueue, timer, irq_vector, etc... It also can be
> +interesting to enable the tick_stop event to diagnose why the tick is
> +retained when that happens.
> +
> +Some tools may also be useful for higher level analysis:
> +
> +- Documentation/tools/rtla/rtla.rst provides a suite of tools to analyze
> + latency and noise in the system. For example Documentation/tools/rtla/rtla-osnoise.rst
> + runs a kernel tracer that analyzes and output a summary of the noises.
> +
> +- dynticks-testing does something similar to rtla-osnoise but in userspace. It is available
> + at git://git.kernel.org/pub/scm/linux/kernel/git/frederic/dynticks-testing.git
> diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
> index b734f8a2a2c4..cd28dfe91b06 100644
> --- a/Documentation/admin-guide/index.rst
> +++ b/Documentation/admin-guide/index.rst
> @@ -94,6 +94,7 @@ likely to be of interest on almost any system.
>
> cgroup-v2
> cgroup-v1/index
> + cpu-isolation
> cpu-load
> mm/index
> module-signing
Other than the minor nits mentioned above,
Acked-by: Waiman Long <longman@redhat.com>
^ permalink raw reply
* Re: [PATCH v3] docs: contain horizontal overflow in C API descriptions
From: Rito Rhymes @ 2026-03-26 19:10 UTC (permalink / raw)
To: Jonathan Corbet, Rito Rhymes, linux-doc; +Cc: Shuah Khan, linux-kernel, rdunlap
In-Reply-To: <87jyuzehmi.fsf@trenco.lwn.net>
> It should wrap so that the entire prototype is visible, but the
> way that happens on small screens is definitely ugly. The trick
> would be to have it wrap the way it would for an overly long line
> in the source.
I re-rolled it again to match those specs.
FWIW:
I tackled basically the same issue in Public Inbox; Eric wanted
it the way you described, but he was married to his highly efficent
custom blob rendering system where the approach caused regressions
with no reasonable workaround. And no alternative systems I tried
came close to his system's efficiency even though they fixed the
issue. At the end of the day, page-wide horizontal scroll overflow
turned out to be the best fit solution there.
I think the preserved whitespace wrapping approach should work fine
here though.
I rerolled so you can choose whichever you think is best, though
personally, I think contained horizontal scroll overflow is the
way to go since it provides a more straight-forward awareness of
the code shape without needing to be aware of wrapping distortions.
I leave it to you.
Rito
^ permalink raw reply
* Re: [PATCH v2 02/16] fs, x86/resctrl: Add architecture routines for kernel mode initialization
From: Babu Moger @ 2026-03-26 19:10 UTC (permalink / raw)
To: Reinette Chatre, corbet, tony.luck, Dave.Martin, james.morse,
tglx, mingo, bp, dave.hansen
Cc: skhan, x86, hpa, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid, kas,
rick.p.edgecombe, akpm, pmladek, rdunlap, dapeng1.mi, kees, elver,
paulmck, lirongqing, safinaskar, fvdl, seanjc, pawan.kumar.gupta,
xin, tiala, Neeraj.Upadhyay, chang.seok.bae, thomas.lendacky,
elena.reshetova, linux-doc, linux-kernel, linux-coco, kvm,
eranian, peternewman
In-Reply-To: <3ef56c9c-cfe4-4e3c-8598-f2217e538c8c@intel.com>
Hi Reinette,
On 3/24/26 17:53, Reinette Chatre wrote:
> Hi Babu,
>
> On 3/12/26 1:36 PM, Babu Moger wrote:
>> Implement the resctrl kernel mode (kmode) arch initialization.
>>
>> - Add resctrl_arch_get_kmode_cfg() to fill the default kernel mode
>> (INHERIT_CTRL_AND_MON). This can be extended later (e.g. for PLZA) to set
>> additional modes.
> I do not think this is something that the architecture should set, at least
> at this time. Every mode has different requirements and this just lets the arch set
> it without any support for what configurations it implies. For example, if
> arch sets a different default mode than INHERIT_CTRL_AND_MON then PQR_PLZA_ASSOC
> needs to be programmed as the CPUs come online and this does not seem to
> accommodate this. This implementation appears to have significant assumptions on
> what architecture will end up setting since it is only considering PLZA.
Sure. Let the arch report what is supported. Will change it to set the
default in fs code.
Users can change change modes from FS code.
>
>> - Add global resctrl_kcfg and resctrl_kmode_init() to initialize default
>> values.
>>
>> Signed-off-by: Babu Moger <babu.moger@amd.com>
>> ---
>> v2: New patch to handle PLZA interfaces with /sys/fs/resctrl/info/ directory.
>> https://lore.kernel.org/lkml/2ab556af-095b-422b-9396-f845c6fd0342@intel.com/
>> ---
>> arch/x86/kernel/cpu/resctrl/core.c | 7 +++++++
>> fs/resctrl/rdtgroup.c | 10 ++++++++++
>> 2 files changed, 17 insertions(+)
>>
>> diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
>> index 7667cf7c4e94..4c3ab2d93909 100644
>> --- a/arch/x86/kernel/cpu/resctrl/core.c
>> +++ b/arch/x86/kernel/cpu/resctrl/core.c
>> @@ -892,6 +892,13 @@ bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
>> }
>> }
>>
>> +void resctrl_arch_get_kmode_cfg(struct resctrl_kmode_cfg *kcfg)
>> +{
>> + kcfg->kmode = INHERIT_CTRL_AND_MON;
>> + kcfg->kmode_cur = INHERIT_CTRL_AND_MON;
>> + kcfg->k_rdtgrp = NULL;
>> +}
> I already commented on the arch vs filesystem settings.
>
> When using an arch helper this forces all architectures to support this helper. Is a
> helper required? Is it perhaps possible for arch to set a property instead? For example,
> how enumeration is handled?
> I think the assumption here is that INHERIT_CTRL_AND_MON is the default and expected to
> be supported by all architectures. I do not see why arch should set this as default but
> instead this should be from resctrl fs. At the same time it is expected that the
> architecture supports this mode so there needs to be a failure if an architecture does
> not support this mode?
I will change. Arch sets the supported modes. FS sets the default.
Users can change it to required mode later.
>
> I'm going to stop here. I think the comments so far may result in major changes already
> making further detailed review of patches unnecessary.
Based on my comments below you may need to re-look at the some of the
patches.
https://lore.kernel.org/lkml/47c0db32-d0e0-4c53-90bd-b74863d233dc@amd.com/
I am fine otherwise also. Let continue that discussion.
Thanks
Babu
>
> Reinette
>
^ permalink raw reply
* [PATCH v2 2/2] docs: kdoc_diff: add a helper tool to help checking kdoc regressions
From: Mauro Carvalho Chehab @ 2026-03-26 19:09 UTC (permalink / raw)
To: Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel, Jonathan Corbet, Shuah Khan
In-Reply-To: <cover.1774551940.git.mchehab+huawei@kernel.org>
Checking for regressions at kernel-doc can be hard. Add a helper
tool to make such task easier.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/docs/kdoc_diff | 508 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 508 insertions(+)
create mode 100755 tools/docs/kdoc_diff
diff --git a/tools/docs/kdoc_diff b/tools/docs/kdoc_diff
new file mode 100755
index 000000000000..1aa16bdccaa3
--- /dev/null
+++ b/tools/docs/kdoc_diff
@@ -0,0 +1,508 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright(c) 2026: Mauro Carvalho Chehab <mchehab@kernel.org>.
+#
+# pylint: disable=R0903,R0912,R0913,R0914,R0915,R0917
+
+"""
+docdiff - Check differences between kernel‑doc output between two different
+commits.
+
+Examples
+--------
+
+Compare the kernel‑doc output between the last two 5.15 releases::
+
+ $ kdoc_diff v6.18..v6.19
+
+Both outputs are cached
+
+Force a complete documentation scan and clean any previous cache from
+6.19 to the current HEAD::
+
+ $ kdoc_diff 6.19.. --full --clean
+
+Check differences only on a single driver since origin/main::
+
+ $ kdoc_diff origin/main drivers/media
+
+Generate an YAML file and use it to check for regressions::
+
+ $ kdoc_diff HEAD~ drivers/media --regression
+
+
+"""
+
+import os
+import sys
+import argparse
+import subprocess
+import shutil
+import re
+import signal
+
+from glob import iglob
+
+
+SRC_DIR = os.path.dirname(os.path.realpath(__file__))
+WORK_DIR = os.path.abspath(os.path.join(SRC_DIR, "../.."))
+
+KDOC_BINARY = os.path.join(SRC_DIR, "kernel-doc")
+KDOC_PARSER_TEST = os.path.join(WORK_DIR, "tools/unittests/test_kdoc_parser.py")
+
+CACHE_DIR = ".doc_diff_cache"
+YAML_NAME = "out.yaml"
+
+DIR_NAME = {
+ "full": os.path.join(CACHE_DIR, "full"),
+ "partial": os.path.join(CACHE_DIR, "partial"),
+ "no-cache": os.path.join(CACHE_DIR, "no_cache"),
+ "tmp": os.path.join(CACHE_DIR, "__tmp__"),
+}
+
+class GitHelper:
+ """Handles all Git operations"""
+
+ def __init__(self, work_dir=None):
+ self.work_dir = work_dir
+
+ def is_inside_repository(self):
+ """Check if we're inside a Git repository"""
+ try:
+ output = subprocess.check_output(["git", "rev-parse",
+ "--is-inside-work-tree"],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
+
+ return output.strip() == "true"
+ except subprocess.CalledProcessError:
+ return False
+
+ def is_valid_commit(self, commit_hash):
+ """
+ Validate that a ref (branch, tag, commit hash, etc.) can be
+ resolved to a commit.
+ """
+ try:
+ subprocess.check_output(["git", "rev-parse", commit_hash],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT)
+ return True
+ except subprocess.CalledProcessError:
+ return False
+
+ def get_short_hash(self, commit_hash):
+ """Get short commit hash"""
+ try:
+ return subprocess.check_output(["git", "rev-parse", "--short",
+ commit_hash],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True).strip()
+ except subprocess.CalledProcessError:
+ return ""
+
+ def has_uncommitted_changes(self):
+ """Check for uncommitted changes"""
+ try:
+ subprocess.check_output(["git", "diff-index",
+ "--quiet", "HEAD", "--"],
+ cwd=self.work_dir,
+ stderr=subprocess.STDOUT)
+ return False
+ except subprocess.CalledProcessError:
+ return True
+
+ def get_current_branch(self):
+ """Get current branch name"""
+ return subprocess.check_output(["git", "branch", "--show-current"],
+ cwd=self.work_dir,
+ universal_newlines=True).strip()
+
+ def checkout_commit(self, commit_hash, quiet=True):
+ """Checkout a commit safely"""
+ args = ["git", "checkout", "-f"]
+ if quiet:
+ args.append("-q")
+ args.append(commit_hash)
+ try:
+ subprocess.check_output(args, cwd=self.work_dir,
+ stderr=subprocess.STDOUT)
+
+ # Double-check if branch actually switched
+ branch = self.get_short_hash("HEAD")
+ if commit_hash != branch:
+ raise RuntimeError(f"Branch changed to '{branch}' instead of '{commit_hash}'")
+
+ return True
+ except subprocess.CalledProcessError as e:
+ print(f"ERROR: Failed to checkout {commit_hash}: {e}",
+ file=sys.stderr)
+ return False
+
+
+class CacheManager:
+ """Manages persistent cache directories"""
+
+ def __init__(self, work_dir):
+ self.work_dir = work_dir
+
+ def initialize(self):
+ """Create cache directories if they don't exist"""
+ for dir_path in DIR_NAME.values():
+ abs_path = os.path.join(self.work_dir, dir_path)
+ if not os.path.exists(abs_path):
+ os.makedirs(abs_path, exist_ok=True, mode=0o755)
+
+ def get_commit_cache(self, commit_hash, path):
+ """Generate cache path for a commit"""
+ hash_short = GitHelper(self.work_dir).get_short_hash(commit_hash)
+ if not hash_short:
+ hash_short = commit_hash
+
+ return os.path.join(path, hash_short)
+
+class KernelDocRunner:
+ """Runs kernel-doc documentation generator"""
+
+ def __init__(self, work_dir, kdoc_binary):
+ self.work_dir = work_dir
+ self.kdoc_binary = kdoc_binary
+ self.kdoc_files = None
+
+ def find_kdoc_references(self):
+ """Find all files marked with kernel-doc:: directives"""
+ if self.kdoc_files:
+ print("Using cached Kdoc refs")
+ return self.kdoc_files
+
+ print("Finding kernel-doc entries in Documentation...")
+
+ files = os.path.join(self.work_dir, 'Documentation/**/*.rst')
+ pattern = re.compile(r"^\.\.\s+kernel-doc::\s*(\S+)")
+ kdoc_files = set()
+
+ for file_path in iglob(files, recursive=True):
+ try:
+ with open(file_path, 'r', encoding='utf-8') as fp:
+ for line in fp:
+ match = pattern.match(line.strip())
+ if match:
+ kdoc_files.add(match.group(1))
+
+ except OSError:
+ continue
+
+ self.kdoc_files = list(kdoc_files)
+
+ return self.kdoc_files
+
+ def gen_yaml(self, yaml_file, kdoc_files):
+ """Runs kernel-doc to generate a yaml file with man and rst."""
+ cmd = [self.kdoc_binary, "--man", "--rst", "--yaml", yaml_file]
+ cmd += kdoc_files
+
+ print(f"YAML regression test file will be stored at: {yaml_file}")
+
+ try:
+ subprocess.check_call(cmd, cwd=self.work_dir,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ return False
+
+ return True
+
+ def run_unittest(self, yaml_file):
+ """Run unit tests with the generated yaml file"""
+ cmd = [KDOC_PARSER_TEST, "-q", "--yaml", yaml_file]
+ result = subprocess.run(cmd, cwd=self.work_dir)
+
+ if result.returncode:
+ print("To check for problems, try to run it again with -v\n")
+ print("Use -k <regex> to filter results\n\n\t$", end="")
+ print(" ".join(cmd) + "\n")
+
+ return True
+
+ def normal_run(self, tmp_dir, output_dir, kdoc_files):
+ """Generate man, rst and errors, storing them at tmp_dir."""
+ os.makedirs(tmp_dir, exist_ok=True)
+
+ try:
+ with open(os.path.join(tmp_dir, "man.log"), "w", encoding="utf-8") as out:
+ subprocess.check_call([self.kdoc_binary, "--man"] + kdoc_files,
+ cwd=self.work_dir,
+ stdout=out, stderr=subprocess.DEVNULL)
+
+ with open(os.path.join(tmp_dir, "rst.log"), "w", encoding="utf-8") as out:
+ with open(os.path.join(tmp_dir, "err.log"), "w", encoding="utf-8") as err:
+ subprocess.check_call([self.kdoc_binary, "--rst"] + kdoc_files,
+ cwd=self.work_dir,
+ stdout=out, stderr=err)
+ except subprocess.CalledProcessError:
+ return False
+
+ if output_dir:
+ os.replace(tmp_dir, output_dir)
+
+ return True
+
+ def run(self, commit_hash, tmp_dir, output_dir, kdoc_files, is_regression,
+ is_end):
+ """Run kernel-doc on its several ways"""
+ if not kdoc_files:
+ raise RuntimeError("No kernel-doc references found")
+
+ git_helper = GitHelper(self.work_dir)
+ if not git_helper.checkout_commit(commit_hash, quiet=True):
+ raise RuntimeError(f"ERROR: can't checkout commit {commit_hash}")
+
+ print(f"Processing {commit_hash}...")
+
+ if not is_regression:
+ return self.normal_run(tmp_dir, output_dir, kdoc_files)
+
+ yaml_file = os.path.join(tmp_dir, YAML_NAME)
+
+ if not is_end:
+ return self.gen_yaml(yaml_file, kdoc_files)
+
+ return self.run_unittest(yaml_file)
+
+class DiffManager:
+ """Compare documentation output directories with an external diff."""
+ def __init__(self, diff_tool="diff", diff_args=None):
+ self.diff_tool = diff_tool
+ # default: unified, no context, ignore whitespace changes
+ self.diff_args = diff_args or ["-u0", "-w"]
+
+ def diff_directories(self, dir1, dir2):
+ """Compare two directories using an external diff."""
+ print(f"\nDiffing {dir1} and {dir2}:")
+
+ dir1_files = set()
+ dir2_files = set()
+ has_diff = False
+
+ for root, _, files in os.walk(dir1):
+ for file in files:
+ dir1_files.add(os.path.relpath(os.path.join(root, file), dir1))
+ for root, _, files in os.walk(dir2):
+ for file in files:
+ dir2_files.add(os.path.relpath(os.path.join(root, file), dir2))
+
+ common_files = sorted(dir1_files & dir2_files)
+ for file in common_files:
+ f1 = os.path.join(dir1, file)
+ f2 = os.path.join(dir2, file)
+
+ cmd = [self.diff_tool] + self.diff_args + [f1, f2]
+ try:
+ result = subprocess.run(
+ cmd, capture_output=True, text=True, check=False
+ )
+ if result.stdout:
+ has_diff = True
+ print(f"\n{file}")
+ print(result.stdout, end="")
+ except FileNotFoundError:
+ print(f"ERROR: {self.diff_tool} not found")
+ sys.exit(1)
+
+ # Show files that exist only in one directory
+ only_in_dir1 = dir1_files - dir2_files
+ only_in_dir2 = dir2_files - dir1_files
+ if only_in_dir1 or only_in_dir2:
+ has_diff = True
+ print("\nDifferential files:")
+ for f in sorted(only_in_dir1):
+ print(f" - {f} (only in {dir1})")
+ for f in sorted(only_in_dir2):
+ print(f" + {f} (only in {dir2})")
+
+ if not has_diff:
+ print("\nNo differences between those two commits")
+
+
+class SignalHandler():
+ """Signal handler class."""
+
+ def restore(self, force_exit=False):
+ """Restore original HEAD state."""
+ if self.restored:
+ return
+
+ print(f"Restoring original branch: {self.original_head}")
+ try:
+ subprocess.check_call(
+ ["git", "checkout", "-f", self.original_head],
+ cwd=self.git_helper.work_dir,
+ stderr=subprocess.STDOUT,
+ )
+ except subprocess.CalledProcessError as e:
+ print(f"Failed to restore: {e}", file=sys.stderr)
+
+ for sig, handler in self.old_handler.items():
+ signal.signal(sig, handler)
+
+ self.restored = True
+
+ if force_exit:
+ sys.exit(1)
+
+ def signal_handler(self, sig, _):
+ """Handle interrupt signals."""
+ print(f"\nSignal {sig} received. Restoring original state...")
+
+ self.restore(force_exit=True)
+
+ def __enter__(self):
+ """Allow using it via with command."""
+ for sig in [signal.SIGINT, signal.SIGTERM]:
+ self.old_handler[sig] = signal.getsignal(sig)
+ signal.signal(sig, self.signal_handler)
+
+ return self
+
+ def __exit__(self, *args):
+ """Restore signals at the end of with block."""
+ self.restore()
+
+ def __init__(self, git_helper, original_head):
+ self.git_helper = git_helper
+ self.original_head = original_head
+ self.old_handler = {}
+ self.restored = False
+
+def parse_commit_range(value):
+ """Handle a commit range."""
+ if ".." not in value:
+ begin = value
+ end = "HEAD"
+ else:
+ begin, _, end = value.partition("..")
+ if not end:
+ end = "HEAD"
+
+ if not begin:
+ raise argparse.ArgumentTypeError("Need a commit begginning")
+
+
+ print(f"Range: {begin} to {end}")
+
+ return begin, end
+
+
+def main():
+ """Main code"""
+ parser = argparse.ArgumentParser(description="Compare kernel documentation between commits")
+ parser.add_argument("commits", type=parse_commit_range,
+ help="commit range like old..new")
+ parser.add_argument("files", nargs="*",
+ help="files to process – if supplied the --full flag is ignored")
+
+ parser.add_argument("--full", "-f", action="store_true",
+ help="Force a full scan of Documentation/*")
+
+ parser.add_argument("--regression", "-r", action="store_true",
+ help="Use YAML format to check for regressions")
+
+ parser.add_argument("--work-dir", "-w", default=WORK_DIR,
+ help="work dir (default: %(default)s)")
+
+ parser.add_argument("--clean", "-c", action="store_true",
+ help="Clean caches")
+
+ args = parser.parse_args()
+
+ if args.files and args.full:
+ raise argparse.ArgumentError(args.full,
+ "cannot combine '--full' with an explicit file list")
+
+ work_dir = os.path.abspath(args.work_dir)
+
+ # Initialize cache
+ cache = CacheManager(work_dir)
+ cache.initialize()
+
+ # Validate git repository
+ git_helper = GitHelper(work_dir)
+ if not git_helper.is_inside_repository():
+ raise RuntimeError("Must run inside Git repository")
+
+ old_commit, new_commit = args.commits
+
+ old_commit = git_helper.get_short_hash(old_commit)
+ new_commit = git_helper.get_short_hash(new_commit)
+
+ # Validate commits
+ for commit in [old_commit, new_commit]:
+ if not git_helper.is_valid_commit(commit):
+ raise RuntimeError(f"Commit '{commit}' does not exist")
+
+ # Check for uncommitted changes
+ if git_helper.has_uncommitted_changes():
+ raise RuntimeError("Uncommitted changes present. Commit or stash first.")
+
+ runner = KernelDocRunner(git_helper.work_dir, KDOC_BINARY)
+
+ # Get files to be parsed
+ cache_msg = " (results will be cached)"
+ if args.full:
+ kdoc_files = ["."]
+ diff_type = "full"
+ print(f"Parsing all files at {work_dir}")
+ if not args.files:
+ diff_type = "partial"
+ kdoc_files = runner.find_kdoc_references()
+ print(f"Parsing files with kernel-doc markups at {work_dir}/Documentation")
+ else:
+ diff_type = "no-cache"
+ cache_msg = ""
+ kdoc_files = args.files
+
+ tmp_dir = DIR_NAME["tmp"]
+ out_path = DIR_NAME[diff_type]
+
+ if not args.regression:
+ print(f"Output will be stored at: {out_path}{cache_msg}")
+
+ # Just in case - should never happen in practice
+ if not kdoc_files:
+ raise argparse.ArgumentError(args.files,
+ "No kernel-doc references found")
+
+ original_head = git_helper.get_current_branch()
+
+ old_cache = cache.get_commit_cache(old_commit, out_path)
+ new_cache = cache.get_commit_cache(new_commit, out_path)
+
+ with SignalHandler(git_helper, original_head):
+ if args.clean or diff_type == "no-cache":
+ for cache_dir in [old_cache, new_cache]:
+ if cache_dir and os.path.exists(cache_dir):
+ shutil.rmtree(cache_dir)
+
+ if args.regression or not os.path.exists(old_cache):
+ old_success = runner.run(old_commit, tmp_dir, old_cache, kdoc_files,
+ args.regression, False)
+ else:
+ old_success = True
+
+ if args.regression or not os.path.exists(new_cache):
+ new_success = runner.run(new_commit, tmp_dir, new_cache, kdoc_files,
+ args.regression, True)
+ else:
+ new_success = True
+
+ if not (old_success and new_success):
+ raise RuntimeError("Failed to generate documentation")
+
+ if not args.regression:
+ diff_manager = DiffManager()
+ diff_manager.diff_directories(old_cache, new_cache)
+
+if __name__ == "__main__":
+ main()
--
2.53.0
^ permalink raw reply related
* [PATCH v2 0/2] Add a script to check for kernel-doc regressions
From: Mauro Carvalho Chehab @ 2026-03-26 19:09 UTC (permalink / raw)
To: Jonathan Corbet, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-doc, linux-kernel, Shuah Khan
Hi Jon,
I've using this script internally to check for regressions and
changes with kernel-doc, specially those related to the new
CTokenizer code:
$ tools/docs/kdoc_diff --help
usage: kdoc_diff [-h] [--full] [--regression] [--work-dir WORK_DIR] [--clean] commits [files ...]
Compare kernel documentation between commits
positional arguments:
commits commit range like old..new
files files to process – if supplied the --full flag is ignored
options:
-h, --help show this help message and exit
--full, -f Force a full scan of Documentation/*
--regression, -r Use YAML format to check for regressions
--work-dir, -w WORK_DIR
work dir (default: /new_devel/docs)
--clean, -c Clean caches
I did today a cleanup, to be able to submit it, as I think it could
be helpful to you and others as well, as it automates the diff check
between two commits.
It has two modes of work:
1. It generates 3 files: err.log, man.log, rst.log and does
a diff between old/new commit.
On this mode, it sorts err.log and remove duplicated messages,
so it relaxes a little bit the diff comparision, if a minor
change affects its error output.
2. It uses yaml to run regressions test.
The regressions mode is nice when no regressions are expected. It
uses the tools/unittest/test_kdoc_parser, which is somewhat relaxed
with regards to trivial changes like whitespaces.
The tested files can either be:
a. Partial: only files explicitly included via kernel-doc:: markups
inside Documentation;
b. Full: includes files with broken kernel-doc markups that are all
spread inside Kernel tree;
c. A list of files or directories.
To prevent losing anything, before running, it checks if the tree
is not dirty. While running, it does git checkout -f, and, at the
end, it returns to the current branch.
There's a logic there which catches signals to avoid troubles on
errors/exit/ctrl-c. At least on my tests, it worked fine even
on python errors inside the script. Yet, in case of troubles,
one could use git reflog.
On v2, the regression tests now show only the failed tests,
and provide the command line used to run it:
$ tools/docs/kdoc_diff -r PR-more-kdoc-unit-tests drivers/media/v4l2-core/
Range: PR-more-kdoc-unit-tests to HEAD
Processing 0a4f3ef9880e...
YAML regression test file will be stored at: .doc_diff_cache/__tmp__/out.yaml
Processing 24b3116a7834...
Ran 89 tests in 0.107s
FAILED (failures=8, expected failures=4)
test_man_jpeg_stream: FAIL
test_man_v4l2_create_buffers32: FAIL
test_man_v4l2_m2m_dev: FAIL
test_man_v4l2_subdev_stream_config: FAIL
test_rst_jpeg_stream: FAIL
test_rst_v4l2_create_buffers32: FAIL
test_rst_v4l2_m2m_dev: FAIL
test_rst_v4l2_subdev_stream_config: FAIL
Ran 89 tests
FAILED (failures=8)
To check for problems, try to run it again with -v
Use -k <regex> to filter results
$/new_devel/docs/tools/unittests/test_kdoc_parser.py -q --yaml .doc_diff_cache/__tmp__/out.yaml
Restoring original branch: PR_CDataParser-v3
Switched to branch 'PR_CDataParser-v3'
---
v2:
- Added an extra patch to add quiet mode for unittest reports;
- Use quiet mode for error report with --regression;
- Fixed the error message when regression tests failed.
Mauro Carvalho Chehab (2):
tools: unittest_helper: add a quiet mode
docs: kdoc_diff: add a helper tool to help checking kdoc regressions
tools/docs/kdoc_diff | 508 ++++++++++++++++++++++++++++
tools/lib/python/unittest_helper.py | 22 +-
2 files changed, 524 insertions(+), 6 deletions(-)
create mode 100755 tools/docs/kdoc_diff
--
2.53.0
^ permalink raw reply
* [PATCH v2 1/2] tools: unittest_helper: add a quiet mode
From: Mauro Carvalho Chehab @ 2026-03-26 19:09 UTC (permalink / raw)
To: Linux Doc Mailing List, Mauro Carvalho Chehab
Cc: Mauro Carvalho Chehab, linux-kernel, Jonathan Corbet, Shuah Khan
In-Reply-To: <cover.1774551940.git.mchehab+huawei@kernel.org>
On quiet mode, only report errors.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
tools/lib/python/unittest_helper.py | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/tools/lib/python/unittest_helper.py b/tools/lib/python/unittest_helper.py
index 55d444cd73d4..f3cba5120401 100755
--- a/tools/lib/python/unittest_helper.py
+++ b/tools/lib/python/unittest_helper.py
@@ -141,7 +141,7 @@ class Summary(unittest.TestResult):
super().addSkip(test, reason)
self._record_test(test, f"SKIP ({reason})")
- def printResults(self):
+ def printResults(self, verbose):
"""
Print results using colors if tty.
"""
@@ -174,10 +174,15 @@ class Summary(unittest.TestResult):
# Print results
for module_name, classes in self.test_results.items():
- print(f"{module_name}:")
+ if verbose:
+ print(f"{module_name}:")
for class_name, tests in classes.items():
- print(f" {class_name}:")
+ if verbose:
+ print(f" {class_name}:")
for test_name, status in tests:
+ if not verbose and status in [ "OK", "EXPECTED_FAIL" ]:
+ continue
+
# Get base status without reason for SKIP
if status.startswith("SKIP"):
status_code = status.split()[0]
@@ -187,7 +192,8 @@ class Summary(unittest.TestResult):
print(
f" {test_name + ':':<{max_length}}{color}{status}{COLORS['reset']}"
)
- print()
+ if verbose:
+ print()
# Print summary
print(f"\nRan {self.testsRun} tests", end="")
@@ -230,6 +236,7 @@ class TestUnits:
"""Returns a parser for command line arguments."""
parser = argparse.ArgumentParser(description="Test runner with regex filtering")
parser.add_argument("-v", "--verbose", action="count", default=1)
+ parser.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("-f", "--failfast", action="store_true")
parser.add_argument("-k", "--keyword",
help="Regex pattern to filter test methods")
@@ -279,7 +286,10 @@ class TestUnits:
if not caller_file and not suite:
raise TypeError("Either caller_file or suite is needed at TestUnits")
- verbose = args.verbose
+ if args.quiet:
+ verbose = 0
+ else:
+ verbose = args.verbose
if not env:
env = os.environ.copy()
@@ -334,7 +344,7 @@ class TestUnits:
failfast=args.failfast)
result = runner.run(suite)
if resultclass:
- result.printResults()
+ result.printResults(verbose)
sys.exit(not result.wasSuccessful())
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v6 01/10] KVM: x86: Define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT
From: Yosry Ahmed @ 2026-03-26 19:03 UTC (permalink / raw)
To: Jim Mattson
Cc: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, kvm, linux-doc, linux-kernel, linux-kselftest
In-Reply-To: <20260326174944.3820245-2-jmattson@google.com>
On Thu, Mar 26, 2026 at 10:50 AM Jim Mattson <jmattson@google.com> wrote:
>
> Define a quirk to control whether nested SVM shares L1's PAT with L2
> (legacy behavior) or gives L2 its own independent gPAT (correct behavior
> per the APM).
>
> When the quirk is enabled (default), L2 shares L1's PAT, preserving the
> legacy KVM behavior. When userspace disables the quirk, KVM correctly
> virtualizes the PAT for nested SVM guests, giving L2 a separate gPAT as
> specified in the AMD architecture.
>
> Signed-off-by: Jim Mattson <jmattson@google.com>
> ---
> Documentation/virt/kvm/api.rst | 14 ++++++++++++++
> arch/x86/include/asm/kvm_host.h | 3 ++-
> arch/x86/include/uapi/asm/kvm.h | 1 +
> arch/x86/kvm/svm/svm.h | 7 +++++++
> 4 files changed, 24 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 032516783e96..2d56f17e3760 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -8551,6 +8551,20 @@ KVM_X86_QUIRK_VMCS12_ALLOW_FREEZE_IN_SMM By default, KVM relaxes the consisten
> bit to be cleared. Note that the vmcs02
> bit is still completely controlled by the
> host, regardless of the quirk setting.
> +
> +KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT By default, KVM for nested SVM guests
> + shares the IA32_PAT MSR between L1 and
> + L2. This is legacy behavior and does
> + not match the AMD architecture
> + specification. When this quirk is
> + disabled and nested paging (NPT) is
> + enabled for L2, KVM correctly
> + virtualizes a separate guest PAT
> + register for L2, using the g_pat
> + field in the VMCB. When NPT is
> + disabled for L2, L1 and L2 continue
> + to share the IA32_PAT MSR regardless
> + of the quirk setting.
> ======================================== ================================================
>
> 7.32 KVM_CAP_MAX_VCPU_ID
> diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
> index d3bdc9828133..0809d8f28208 100644
> --- a/arch/x86/include/asm/kvm_host.h
> +++ b/arch/x86/include/asm/kvm_host.h
> @@ -2511,7 +2511,8 @@ int memslot_rmap_alloc(struct kvm_memory_slot *slot, unsigned long npages);
> KVM_X86_QUIRK_SLOT_ZAP_ALL | \
> KVM_X86_QUIRK_STUFF_FEATURE_MSRS | \
> KVM_X86_QUIRK_IGNORE_GUEST_PAT | \
> - KVM_X86_QUIRK_VMCS12_ALLOW_FREEZE_IN_SMM)
> + KVM_X86_QUIRK_VMCS12_ALLOW_FREEZE_IN_SMM \
There is a missing "|" here, it's fixed in patch 3, but I think it
should be fixed up here (maybe when applied).
> + KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT)
>
> #define KVM_X86_CONDITIONAL_QUIRKS \
> (KVM_X86_QUIRK_CD_NW_CLEARED | \
> diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
> index 5f2b30d0405c..3ada2fa9ca86 100644
> --- a/arch/x86/include/uapi/asm/kvm.h
> +++ b/arch/x86/include/uapi/asm/kvm.h
> @@ -477,6 +477,7 @@ struct kvm_sync_regs {
> #define KVM_X86_QUIRK_STUFF_FEATURE_MSRS (1 << 8)
> #define KVM_X86_QUIRK_IGNORE_GUEST_PAT (1 << 9)
> #define KVM_X86_QUIRK_VMCS12_ALLOW_FREEZE_IN_SMM (1 << 10)
> +#define KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT (1 << 11)
>
> #define KVM_STATE_NESTED_FORMAT_VMX 0
> #define KVM_STATE_NESTED_FORMAT_SVM 1
> diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
> index ff1e4b4dc998..67aa5d34332e 100644
> --- a/arch/x86/kvm/svm/svm.h
> +++ b/arch/x86/kvm/svm/svm.h
> @@ -616,6 +616,13 @@ static inline bool nested_npt_enabled(struct vcpu_svm *svm)
> return svm->nested.ctl.misc_ctl & SVM_MISC_ENABLE_NP;
> }
>
> +static inline bool l2_has_separate_pat(struct vcpu_svm *svm)
> +{
> + return nested_npt_enabled(svm) &&
> + !kvm_check_has_quirk(svm->vcpu.kvm,
> + KVM_X86_QUIRK_NESTED_SVM_SHARED_PAT);
> +}
> +
> static inline bool nested_vnmi_enabled(struct vcpu_svm *svm)
> {
> return guest_cpu_cap_has(&svm->vcpu, X86_FEATURE_VNMI) &&
> --
> 2.53.0.1018.g2bb0e51243-goog
>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox